Changelog

Unreleased

in progress

Added

  • core debuggable errors — SCHEMIC_DEBUG=1 (or --stack) prints the full stack + the .cause chain on any CLI failure (default output unchanged, now with a hint line), and a crashing schema module always reports the FAILING FILE path (original error as cause).
  • core typed cross-connection resolution — (A) a resolver's ctx.connections.<name> handle is now THENABLE to that sibling's FULL ORM client (const main = await ctx.connections.main; main.select(...)) while keeping direct .query; (B) the CHAINED config builder — defineConfig().connection(name, driverFactory, staticConfig | (ctx, args) => config | config[]) — where the driver FACTORY itself is the marker and each resolver's ctx.connections is contextually typed with the ACCUMULATED prior connections (order = visibility = structural cycle prevention). The literal defineConfig({ connections }) form is unchanged.
  • table COMPOSITION + derived schemas (cross-driver convention, from real usage): defineTable(name, s.object()), a public native s.object().fields map (inverse of .shape), TableDef.extend(shape | s.object()) typed cast-free column mixins, and derived Standard-Schema input schemas — TableDef.create (defaults/id optional, internal dropped) and TableDef.update (partial, id/readonly excluded) — composable via .partial/.extend/.refine/.or.
  • core ORM client P1 foundation — OrmClientBase (disposable bound-client contract: close + [Symbol.asyncDispose], so await using db = await connect() auto-closes), the asyncDisposable mixin, and resolveConnection(name?) (managed path over the project config).
  • the bound ORM client (P1 reads) at @schemic/<driver>/clientconnect(name?) MANAGED from the config / connect(client) BYO (close = no-op), db.select(table) pre-bound + awaitable (thenable builder; standalone .run(db) still works), AsyncDisposable; surreal adds a disposable forkSession().
  • core config-as-factory with PARAMETERIZED connections — defineConfig is generic and returns the config with a typed connect(name, args?): connection names autocomplete (a typo is a compile error), args is the resolver's own declared 2nd param ((ctx, args) => config | config[], typed per connection; absent for static connections), and each entry's own client type is inferred (heterogeneous-driver projects type per-connection). An ARRAY resolution is bulk-only (migrations enumerate it) — connect throws a teaching error; pass args selecting one. key is a display label (not an address); entries may add a dialect label hook for bulk reporting. Resolvers can query sibling connections via ctx.connections at runtime (lazy open through the entries' embedded client openers, cycle-detected, auto-closed). CLI: --args <json> + --arg k=v sugar feed resolver args.
  • core the config loader accepts a NAMED schemic export as well as a default — scaffolded form becomes export const schemic = defineConfig(...) in schemic.config.ts (deterministic import { schemic } -> schemic.connect() auto-import, no file rename); a bare schemic.ts is also discovered (shape-guarded).
  • ORM P2 WRITES on the bound client — split builders (db.create(T).content(data), db.update(T, id).merge(...) / .content(...) / .set(...), db.delete(T, id) — pg exports remove), decoding through the codec channel fail-fast and returning typed rows that CARRY their id.
  • query Phase 1 READS — richer WHERE operators under the shared cross-driver op contract, pagination, and one() / get() / count() terminals on the select builder.
  • table PRESETS — defineTable.preset(...) reusable table fragments applied via the chained single-arg TableDef.use(a).use(b) (ratified cross-driver form; columns + indexes).

Fixed

  • core the DEFAULT migrations dir now follows the documented contract — RELATIVE TO THE SCHEMA (its sibling migrations dir) instead of a root-fixed ./database/migrations. A nested schema (schema: "./src/database/schema") previously split state: init scaffolded the snapshot schema-relative while gen wrote migrations + a second snapshot at the root default. Standard scaffold layouts are unchanged; an explicit migrations override still resolves from the root.
  • returned rows carry the implicit id (select + write RETURNING).
  • core multi-line DDL renders with PER-LINE diff indicators — now that drivers pretty-print display statements, every line of a statement gets its +/- in sc diff (a bare continuation line read as context), unified-patch hunk counts count LINES not statements, the rollback block dims/indents per line, and the inline word-diff view collapses whitespace onto one line.

0.1.0-alpha.24

July 1, 2026

Added

  • core KindEngine.excludeFromMigrations — a kind can opt OUT of the migration pipeline entirely. Objects of an excluded kind are skipped by snapshot, diff, gen, and the introspect-compare, so they never enter a migration file nor phantom-diff; the kind is managed out-of-band by the driver's own sc <kind> … commands. For secret-bearing kinds whose lifecycle doesn't fit committed migrations (SurrealDB DEFINE ACCESS: the DB redacts keys on introspection, and keys rotate independently).
  • core DriverCommand contract — drivers can contribute dialect-specific CLI commands invoked as sc <kind> <verb> [args] (e.g. surreal access rotate <name>, postgres matview refresh <name>). Core owns only the general mechanism: it discovers driver.commands, parses argv (variadic positionals + value/boolean flags), resolves the connection, and dispatches to run with a CommandContext ({conn, config, io with prompt(), secrets}); the driver owns each kind/verb's meaning.
  • cli the dispatch for DriverCommands — the schemic/sc bin discovers the active driver's commands (from the project config) and registers each as sc <kind> <verb> [args], grouped by kind, with --help. It parses the invocation (variadic positionals + value/boolean flags), opens the connection, and runs the command with its CommandContext. No project / no driver commands -> no-op (built-in commands unaffected).
  • core secret-bearing DDL foundations (Phase-2a of the DEFINE ACCESS secret contract) — SecretRef + env()/secret() author-time helpers + a pluggable SecretProvider (default reads process.env), and a write-only bindings carrier ($param -> SecretRef) on Statement + Diff. The secret value never lives in the schema, snapshot, or migration: it is resolved at apply through the provider and passed as a bound parameter. Drivers re-export env/secret; apply-time resolution + migration persistence land next.
  • core s.* fields now expose the Standard Schema ~standard interface (forwarded from the wrapped Zod schema on SFieldBase), so a Schemic field drops straight into any Standard Schema consumer (tRPC, TanStack Form/Router, …) without unwrapping to .schema. validate runs the decode direction (wire -> app). Postgres inherits it via core's SFieldBase; surrealdb mirrors it on its own base (it does not yet share core's SFieldBase).
  • core SFieldBase (the s.* base) gains the remaining Zod 4 shared-base methods for closer drop-in parity — nonoptional, exactOptional, isOptional, isNullable, toJSONSchema, a description getter, register, and spa. @schemic/postgres inherits them immediately (it composes core's SFieldBase); @schemic/surrealdb mirrors them on its own base.

Changed (BREAKING — alpha) Breaking

  • connect() now fails loud on a postgres:// (any non-file: URL scheme) connection url — it throws instead of silently spinning up an in-memory throwaway (a silent data-loss footgun where a user pointing at a real server "succeeded" against a disposable DB). connect is now async. file:<dir> (persistent) and ""/omitted (in-memory) are unchanged; hosted postgres:// is reserved for a future node-postgres client.

Fixed

  • cli bare schemic / sc (no args) now lists the active driver's contributed commands (sc <kind> <verb>) in its help, like sc --help already did. The no-arg help printed before driver commands had registered; registration now runs first.

0.1.0-alpha.22

June 26, 2026

Changed

  • cli the driver loader now requires the @schemic/<driver>/driver entry (dropped the index fallback) — completes the M0.3 package split. Drivers must be >= 0.1.0-alpha.21.

Fixed

  • cli schemic gen now shows the rendered migration before the title prompt (you review the actual DDL while naming it), instead of after writing.

0.1.0-alpha.21

June 23, 2026

Changed (BREAKING — alpha) Breaking

  • the authoring index (@schemic/<driver>) is now side-effect-frees.*/define*/surql only. Moved out: - the connection factory + connection types → @schemic/<driver>/connection (surrealConnection, postgresConnection/PgConn/pgSql). Update schemic.config.ts imports. - the Driver impl + emit*/lower/introspect + the registerDriver side-effect → @schemic/<driver>/driver (engine/CLI-only). So importing s.* no longer drags the diff/emit engine or registers the driver. The query builder stays at @schemic/<driver>/query. (surrealdb also made its field registries globalThis singletons so the index and /driver module instances share state.)

Changed

  • cli the driver loader resolves a driver via its @schemic/<driver>/driver subpath first (falling back to the package index for not-yet-split drivers).

0.1.0-alpha.20

June 23, 2026

Added

  • core callFunction in @schemic/core/query — invoke a defined DB function via the callable capability and decode the result through .returns(R) (the neutral half of the query layer's (B) .call()). CallableFunctions.invoke now returns the raw function result for R to decode (no driver implemented callable yet, so no break).
  • cli schemic pull --watch — poll the live DB (--interval, default 2s) and re-pull as it changes (preview, or apply with --write); a DB-poll loop, not fsWatch (which would self-trigger on pull's own file writes).

0.1.0-alpha.18

June 23, 2026

Added

  • core @schemic/core/query — the neutral query toolkit driver builders compose: FieldRefBase (+ brandRef), Project<P> projection inference, projectionSchema/decodeProjection. Plus the callable capability on the Driver contract.
  • typed single-table select() query builder at @schemic/<driver>/query (where/orderBy/limit/.return projection; decode-by-default; .raw() opts out) — the driver-owned builder composing the core toolkit.
  • standalone DDL objects — defineSequence / defineDomain / defineExtension / defineMaterializedView.
  • functions, triggers, and RLS policies — defineFunction / defineTrigger / definePolicy (auto-enables RLS).
  • composite + non-id foreign keys (defineTable().foreignKey({ columns, refTable, refColumns })); richer indexes — access methods (gin/gist/brin/hash) + partial (where).

Older versions (pre-alpha.18) → repo CHANGELOG