Squashing Migrations
Over time the migrations/ folder grows — mostly from per-migration snapshot JSON under meta/. Drizzle Kit has no first-class squash command, but you can reset to a single baseline migration without wiping existing databases.
DbManager runs reconcileSquashedMigrations before migrate(). That helper detects when the journal was reset while a DB still has old __drizzle_migrations rows, and rewrites history to match the new journal so the baseline is not re-applied on top of an existing schema.
When to squash
- Prod (and any shared environments) are caught up on the current head migration.
- You're fine with stale local DBs being wiped and recreated if someone is behind.
- You accept that one-off data backfills in old SQL (e.g.
UPDATE … SET …) will not be replayed on new databases — only schema from the current Drizzle definitions is regenerated. Bake important defaults into the schema if new installs need them.
Steps
Work from the db package (e.g. service/db).
Backup any on-disk DB you care about keeping:
bashcp data/db-<deployment>.sqlite data/db-<deployment>.pre-squash-backup.sqliteDelete the migrations folder entirely (not just the SQL files — an empty
meta/without_journal.jsonconfusesdrizzle-kit generate):bashrm -rf migrationsRegenerate a single baseline from the current schema:
bashnpm run generateYou should get one
migrations/0000_….sqlplusmeta/_journal.jsonandmeta/0000_snapshot.json.Verify an existing DB — connect the same way the app does (or restart
npm run dev). Expect a log about squashed history being rewritten, then a clean migrate. App tables and row counts should be unchanged;__drizzle_migrationsshould contain one row matching the new journalwhen.Verify a fresh DB — delete (or point at a new) sqlite file, connect again, and confirm the baseline creates the full schema.
Ship the deleted old migrations, the new baseline, and the
@saflib/drizzlereconcile change together. Existing environments reconcile on first boot; brand-new environments just run the baseline.
How reconcile decides
It rewrites __drizzle_migrations only when all of these are true:
- The migrations table already has rows.
- Some applied
created_atvalues are not in the current journal (whens) — orphan history from before the squash. - Some journal entries are missing from applied history.
- The DB already has application tables (not only
__drizzle_migrations).
Otherwise it no-ops: fresh installs and normal incremental migrations behave as before.
Drizzle itself decides what to run by comparing each journal entry's when to the latest __drizzle_migrations.created_at. After a squash without reconcile, a new baseline's when is newer than old history, so Drizzle would try to execute the full CREATE TABLE SQL and fail. Reconcile prevents that.
Local recovery
If something goes wrong and you kept a backup:
cp data/db-<deployment>.pre-squash-backup.sqlite data/db-<deployment>.sqliteOr delete the sqlite file and let ALLOW_DB_CREATION=true recreate from the baseline.