Skip to main content

Changelog

All notable changes to DB-X are documented here. Versions are per-repo; each package tracks its own version in its package.json.

[Unreleased]

Fixed

  • CockroachDB no longer claims a snapshot driver it doesn't have. examples/cockroachdb connects through @db-x/postgres-library, so a destructive apply reached for pg_dump — which is not supported against CockroachDB. Tested against CRDB v26.2.4 with pg_dump 17.10, it fails outright (schema with OID 105 does not exist) on both the demo schema and a two-column throwaway table, so the previous behaviour was an opaque tool error at the moment of the migration.

    <Postgres> now probes select version() on apply and records serverKind. On CockroachDB it publishes snapshotUnsupported instead of snapshotDriver: 'pg-dump', and db-x apply refuses the destructive change with a message naming the engine and pointing at BACKUP + --no-snapshot. The CLI's legacy shape match now yields to that declaration — otherwise the <DatabaseTarget> record upstream, which carries the same connection fields and cannot know which engine answers them, resurrected pg-dump anyway.

    State written before the probe carries no serverKind, and a no-op never re-applies, so <Postgres> plans one update to fill it in. psql against CockroachDB is unaffected; it is the dump path specifically that has no support.

Added

  • SQLite drift detection now looks past names. <Table>'s refresh() compared the set of column and index names, so a column rebuilt with a different type, default, nullability or primary key read as in sync, and so did an index rebuilt over different columns — PRAGMA index_list gives the name, and only PRAGMA index_info says what it covers.

    Index shape drift lands in outputs.indexes, where the existing diff turns it into a DROP INDEX + CREATE INDEX; verified end to end against examples/sqlite. Column attribute drift is reported in a new outputs.columnDrift and deliberately not folded back into outputs.columns: SQLite has no ALTER COLUMN (#56), so a rewritten spec would make the diff throw and take preview down for the whole deployment over a change no JSX edit can fix. db-x refresh names each drifted attribute and points at #56; apply clears the key by not emitting it.

    The comparison is normalised against what columnSql actually writes, which is what keeps an in-sync database quiet — serial resolves to INTEGER, a primary key suppresses NOT NULL (SQLite reports notnull=0 for INTEGER PRIMARY KEY regardless), and SQLite strips one layer of parens off a default on the way back out, so the authored (datetime('now')) and the reported datetime('now') agree. unique is still not compared: it lives in an sqlite_autoindex_* entry rather than in table_info.

  • Drift detection for Postgres and MongoDB. <Table> and <Collection> grew the refresh() hook SQLite already had, so db-x refresh no longer answers "no refresh() hook" for every resource on those engines and drift stops being invisible until an apply blows up. Postgres reads information_schema.columns and pg_indexes (excluding the indexes that back a PRIMARY KEY / UNIQUE constraint — those belong to the constraint, not to us); Mongo reads getCollectionNames() + getIndexes(), ignoring the built-in _id_.

    Both follow the rules SQLite learned the hard way: an in-sync database returns its stored outputs untouched, so nothing churns on engine-normalised type spellings, and a surviving column keeps its authored spec rather than being overwritten with the engine's view of it. A table or collection that has gone missing entirely is now routed back through create — for Mongo that means an explicit missing flag rather than an empty index list, because declaring no indexes is legal and only createCollection restores the validator.

Fixed

  • A recreated table no longer comes back empty. <SeedData> records only { name, ranAt }, so a table that refresh found missing was rebuilt while the seed that fills it planned no-op — its own props and state hadn't moved, and the rows never returned. Components can now set reapplyOnDependencyRecreate on their spec, and the diff engine upgrades their no-op to an update when anything they dependsOn is being created or replaced. All three <SeedData> components opt in. Only no-op is upgraded and only a create/replace dependency triggers it, so an unchanged — or merely altered — dependency still re-runs nothing.

    A seed's SQL is opaque to the runtime, so it can only know what a seed is downstream of if you say: <SeedData dependsOn={['table:todos']} …>. The examples now declare it. Detecting the out-of-band drop that triggers this on Postgres and MongoDB needed their refresh() hooks, added below.

  • State no longer records changes that never ran. When a diff produced no SQL but the props had moved, apply still persisted the desired columns into outputs — so state described a database that didn't exist, and because the next diff reads outputs, the repair it planned could never run (a re-added column failed with duplicate column name). All three libraries now return the last-applied shape when nothing executed. indexes still track the props: every declared index is re-created on each apply, so they are applied either way.

  • Dropped columns are no longer silently ignored. diffTable computed renames, additions, alterations and dropped indexes, but never dropped columns — removing a <Column> from the JSX produced no SQL, no destructive entry and no warning, and the column survived in the database. Both libraries now emit ALTER TABLE ... DROP COLUMN, classified destructive, so the --allow-destructive gate and the pre-flight snapshot cover it. SQLite refuses a drop of a primary-key, UNIQUE or still-indexed column, so those fail the plan with an explicit message instead of dying mid-apply; an index dropped in the same edit is dropped first, which SQLite does allow.

  • Snapshot retention. .dbx/snapshots grew without bound — apply captured a pre-flight snapshot before every destructive change and nothing ever removed one, so a whole-database copy accumulated per destructive apply. apply now prunes to the 5 most recent after a successful apply that captured one. Never prunes on failure, and the snapshot pinned to the current state revision is always among those kept, so restore keeps its default target.

Added

  • Standalone repo. Extracted DB-X from the infra-x monorepo into its own project. The runtime engine and CLI are vendored so DB-X stands alone:
    • @db-x/runtime (BSL-1.1) — the JSX runtime, defineComponent contract, reconciler, diff engine, and state I/O (state lives in .dbx/). Forked from @infra-x/runtime.
    • @db-x/cli (BSL-1.1) — the db-x binary (preview / apply / destroy / refresh / state / describe). Forked from @infra-x/cli.
    • @db-x/postgres-library (MIT) — Postgres schema components moved as-is.
  • Docusaurus docs site under apps/docs (db-x.dev).
  • examples/dbx/postgres demo, runnable via db-x preview ./dbx.tsx.
  • Tooling scaffolded with @rtorcato/js-tooling (Biome, Vitest, TypeScript, Husky) over a pnpm workspace.