Skip to content

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).

  1. Backup any on-disk DB you care about keeping:

    bash
    cp data/db-<deployment>.sqlite data/db-<deployment>.pre-squash-backup.sqlite
  2. Delete the migrations folder entirely (not just the SQL files — an empty meta/ without _journal.json confuses drizzle-kit generate):

    bash
    rm -rf migrations
  3. Regenerate a single baseline from the current schema:

    bash
    npm run generate

    You should get one migrations/0000_….sql plus meta/_journal.json and meta/0000_snapshot.json.

  4. 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_migrations should contain one row matching the new journal when.

  5. Verify a fresh DB — delete (or point at a new) sqlite file, connect again, and confirm the baseline creates the full schema.

  6. Ship the deleted old migrations, the new baseline, and the @saflib/drizzle reconcile 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_at values 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:

bash
cp data/db-<deployment>.pre-squash-backup.sqlite data/db-<deployment>.sqlite

Or delete the sqlite file and let ALLOW_DB_CREATION=true recreate from the baseline.