Migrations
MasterRecord migrations compare your entity definitions against a saved snapshot, generate the SQL to reconcile them, and apply it in order. You get version-controlled schema changes with up/down support across SQLite, MySQL, and PostgreSQL.
enable-migrations AppContext looks for *AppContext.js. Keep your context in app/models/AppContext.js.The workflow#
$ # 1. one-time: create the snapshot$ masterrecord enable-migrations AppContext$$ # 2. generate a migration from current models$ masterrecord add-migration InitialCreate AppContext$$ # 3. apply pending migrations$ masterrecord update-database AppContext$$ # roll back the most recent migration$ masterrecord update-database-down AppContext
Inside a Master app, use the friendlier wrapper — it targets AppContext, sets NODE_ENV (--env), and runs enable-migrations for you the first time, so master db new just works on a fresh app (as dotnet ef migrations add does):
$ master db new InitialCreate$ master db migrate$ master db status$ master db rollback
What a migration looks like#
Generated migrations are plain ESM with up() and down() methods built on the schema API. Review and edit them before applying:
import masterrecord from 'masterrecord';
class InitialCreate extends masterrecord.schema {
constructor(context) { super(context); }
async up(table) {
await this.init(table);
await this.createTable(table.User);
await this.createTable(table.Post);
}
async down(table) {
await this.init(table);
await this.dropTable(table.Post);
await this.dropTable(table.User);
}
}
export default InitialCreate;Evolving the schema#
Change a model — add a field, an index, or a relationship — then generate another migration. MasterRecord diffs against the snapshot and emits just the delta (add column, create index, etc.).
$ master db new AddViewsToPost$ master db migrate
Migration tracking#
Applied migrations are recorded in a _masterrecord_migrations table (EF’s __EFMigrationsHistory), so update-databaseruns only what’s pending and is safe to re-run (idempotent). It applies every pending migration in timestamp order — not just the latest file — and each migration commits atomically with its tracking row on Postgres and SQLite (one transaction per migration, as EF Core does), so a failure never leaves a half-applied schema. MySQL DDL implicitly commits, so a migration is not atomic there (EF documents the same). The snapshot records the latestMigration id, so two branches that each add a migration conflict on merge instead of silently diverging.
What the snapshot is#
<ctx>_contextSnapShot.json is the model as of the newest migration file— not the state of any database. It is written when a migration is authored (add-migration, enable-migrations, remove-migration) and never when one is applied, exactly as dotnet ef migrations add regenerates ModelSnapshot while dotnet ef database update leaves it alone. That is what makes each migration a delta between authored states, and it is why a brand-new database can replay them all in order. Commit it with your migrations.
CREATE TABLE is invisible to the snapshot, so later migrations build on schema nothing creates — they pass where you authored them and fail on a fresh database. Author a catch-up migration and masterrecord baseline <ctx> --all the databases that already have it. See Troubleshooting.Review, status, remove#
$ # applied / pending / missing files (EF: migrations list)$ masterrecord migrations-status AppContext$ # print the SQL update-database WOULD run, without applying (EF: migrations script)$ masterrecord script AppContext -o review.sql$ # delete the latest migration file; --force reverts it first if applied (EF: migrations remove)$ masterrecord remove-migration AppContext --force$ # override the env-file connection for one run (EF: --connection)$ masterrecord update-database AppContext --connection '{"type":"sqlite","connection":"./tmp/"}'
Migrating every context at once#
update-database-all applies pending migrations for every context in the project in one command. It has the same guarantees as update-database per context (runs all pending, records each in _masterrecord_migrations), isolates each context’s database connection, prints a per-context summary of what was applied, and exits non-zero if any context fails — so a deploy or CI step can detect a partial failure instead of it passing silently.
this._execute(...)) inside the migration’s up() and test the run against a copy of your dev database first, since a backfill is not automatically idempotent the way the generated DDL is.CLI command reference#
master db | masterrecord | Does | EF Core |
|---|---|---|---|
enable | enable-migrations | Create the initial snapshot. | — |
new <Name> | add-migration <Name> | Diff models → migration file. | migrations add |
migrate | update-database | Apply all pending migrations (one context). | database update |
| — | update-database-all | Apply pending migrations for every context. | --context "*" |
rollback | update-database-down | Revert the most recently applied migration. | database update <prev> |
remove [--force] | remove-migration | Delete the latest migration file (revert first with --force). | migrations remove |
status | migrations-status | Applied vs pending. | migrations list |
script [-o file] | script | SQL for pending migrations, nothing applied. | migrations script |
list | get-migrations | List migration files. | — |
ensure | ensure-database | Create the database if missing (MySQL / Postgres). | — |
Every command, alias and option — including --connection, update-database-target and the *-all variants — is on the CLI reference.