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/cockroachdbconnects through@db-x/postgres-library, so a destructive apply reached forpg_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 probesselect version()on apply and recordsserverKind. On CockroachDB it publishessnapshotUnsupportedinstead ofsnapshotDriver: 'pg-dump', anddb-x applyrefuses the destructive change with a message naming the engine and pointing atBACKUP+--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 ano-opnever re-applies, so<Postgres>plans one update to fill it in.psqlagainst CockroachDB is unaffected; it is the dump path specifically that has no support.
Added
-
SQLite drift detection now looks past names.
<Table>'srefresh()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_listgives the name, and onlyPRAGMA index_infosays what it covers.Index shape drift lands in
outputs.indexes, where the existing diff turns it into aDROP INDEX+CREATE INDEX; verified end to end againstexamples/sqlite. Column attribute drift is reported in a newoutputs.columnDriftand deliberately not folded back intooutputs.columns: SQLite has noALTER COLUMN(#56), so a rewritten spec would make the diff throw and takepreviewdown for the whole deployment over a change no JSX edit can fix.db-x refreshnames each drifted attribute and points at #56;applyclears the key by not emitting it.The comparison is normalised against what
columnSqlactually writes, which is what keeps an in-sync database quiet —serialresolves toINTEGER, a primary key suppressesNOT NULL(SQLite reportsnotnull=0forINTEGER PRIMARY KEYregardless), and SQLite strips one layer of parens off a default on the way back out, so the authored(datetime('now'))and the reporteddatetime('now')agree.uniqueis still not compared: it lives in ansqlite_autoindex_*entry rather than intable_info. -
Drift detection for Postgres and MongoDB.
<Table>and<Collection>grew therefresh()hook SQLite already had, sodb-x refreshno longer answers "no refresh() hook" for every resource on those engines and drift stops being invisible until an apply blows up. Postgres readsinformation_schema.columnsandpg_indexes(excluding the indexes that back a PRIMARY KEY / UNIQUE constraint — those belong to the constraint, not to us); Mongo readsgetCollectionNames()+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
missingflag rather than an empty index list, because declaring no indexes is legal and onlycreateCollectionrestores the validator.
Fixed
-
A recreated table no longer comes back empty.
<SeedData>records only{ name, ranAt }, so a table thatrefreshfound missing was rebuilt while the seed that fills it plannedno-op— its own props and state hadn't moved, and the rows never returned. Components can now setreapplyOnDependencyRecreateon their spec, and the diff engine upgrades theirno-opto an update when anything theydependsOnis being created or replaced. All three<SeedData>components opt in. Onlyno-opis upgraded and only acreate/replacedependency 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 theirrefresh()hooks, added below. -
State no longer records changes that never ran. When a diff produced no SQL but the props had moved,
applystill persisted the desired columns intooutputs— so state described a database that didn't exist, and because the next diff readsoutputs, the repair it planned could never run (a re-added column failed withduplicate column name). All three libraries now return the last-applied shape when nothing executed.indexesstill 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.
diffTablecomputed renames, additions, alterations and dropped indexes, but never dropped columns — removing a<Column>from the JSX produced no SQL, nodestructiveentry and no warning, and the column survived in the database. Both libraries now emitALTER TABLE ... DROP COLUMN, classified destructive, so the--allow-destructivegate and the pre-flight snapshot cover it. SQLite refuses a drop of a primary-key,UNIQUEor 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/snapshotsgrew without bound —applycaptured a pre-flight snapshot before every destructive change and nothing ever removed one, so a whole-database copy accumulated per destructive apply.applynow 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, sorestorekeeps its default target.
Added
- Standalone repo. Extracted DB-X from the
infra-xmonorepo 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,defineComponentcontract, reconciler, diff engine, and state I/O (state lives in.dbx/). Forked from@infra-x/runtime.@db-x/cli(BSL-1.1) — thedb-xbinary (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/postgresdemo, runnable viadb-x preview ./dbx.tsx.- Tooling scaffolded with
@rtorcato/js-tooling(Biome, Vitest, TypeScript, Husky) over a pnpm workspace.