prisma-next-migrations
此技能用于 Prisma Next 数据迁移编写,能根据数据契约变更自动规划迁移计划,生成含占位符的 migration.ts 并支持数据转换填充,还能处理哈希不匹配等故障,让开发者安全高效地将数据库变更落地。适用于 db update、migration plan、migrate 等场景,显著降低迁移编写与故障恢复成本。
npx skills add https://github.com/prisma/prisma-next --skill prisma-next-migrationsBefore / After 效果对比
1 组在没有这个技能之前,开发者面对数据库结构变更和回填需求时,需要手动编写迁移脚本并反复调试,遇到哈希不匹配或未填占位符等错误时,往往要耗费大量时间排查,容易出错且导致上线延期。
此技能能自动规划迁移计划并生成带占位符的迁移文件,支持数据转换填充和自我发布,遇到常见迁移故障也能快速定位恢复,使开发者可以专注于数据逻辑本身,大幅缩短迁移开发与排障时间。
Prisma Next — Migration Authoring
Edit your data contract. Prisma Next plans the migration. You fill in any data transforms.
The three-step user model:
- You edit your data contract. (
prisma-next-contract) - Prisma Next plans the migration for you. ← this skill
- If a data transform is needed, you edit
migration.tsand self-emit. ← this skill
Once the contract changes, you choose how the change reaches the database. This skill covers the two paths (db update and migration plan + migrate), the migration-package contract, the migration.ts authoring API, and the failure modes you recover from without leaving the loop.
Targets. Migration authoring is first-class for Postgres and Mongo. The CLI reads the target from prisma-next.config.ts (set during prisma-next init --target …). Migration commands do not accept a --target flag — use a config scoped to the target you need. Examples below call out target-specific imports, markers, factories, and transaction behavior where they diverge.
When to Use
- User edited the contract and wants to apply the change to the DB.
- User wants to author a migration with a data transform.
- User wants to run pending migrations against a local DB.
- User hit
MIGRATION.HASH_MISMATCH,PN-MIG-2001(unfilled placeholder), or a partially-applied migration. - User mentions: migrate, migration, db push, db update,
prisma migrate dev,prisma migrate deploy, drift, hash mismatch, data backfill.
When Not to Use
- User wants to know what migrations will run on deploy / on merge, or to manage refs and invariants →
prisma-next-migration-review. - User wants to edit the contract →
prisma-next-contract. - User wants a deeper read of a single structured error envelope →
prisma-next-debug.
Key Concepts
db update(quick path). Reads the emitted contract, diffs against the live DB, applies the change. Optional--dry-runprints the plan without executing. Interactive destructive-op confirmation (or-yto auto-accept). Writes no migration directory. Operations needing data transforms are not handled by this path —db updateexcludes thedataoperation class entirely and short-circuits where a data transform would be required. Use only against a database that has no shared history with anyone else (your local dev DB).migration plan(formal path). Reads the emitted contract, diffs against the head of the on-disk migration graph, writes a new migration package undermigrations/app/<YYYYMMDDTHHMM>_<snake_slug>/. If any operation needs a data transform, the package'smigration.tscontainsplaceholder(...)calls you fill in.- The
app/segment in migration paths is the consuming application's contract-space id. Every migration you author lives undermigrations/app/. Extensions your contract depends on get their own sibling directories (migrations/<extension-space-id>/) — those are managed by the extension package and you don't write into them. Theapp/segment lands automatically the first time you runmigration plan/db initagainst an app-level config. - Migration package files (inside each
migrations/app/<dir>/):migration.json— manifest (metadata +migrationHash).ops.json— canonical operation list. Content-addressed;migrationHashis computed over this.migration.ts— TypeScript authoring source, framework-rendered bymigration plan(ormigration new). You edit specific holes in it (see Fill a placeholder below) and re-emitops.json/migration.jsonby running it.
- Contract snapshots.
migration.tsimports its bookend contracts from the shared, content-addressed store atmigrations/snapshots/<hex>/contract.json+contract.d.ts(<hex>is the contract's 64-hex storage hash) — not from files inside the migration package. - Self-emit. Running
node migrations/app/<dir>/migration.tsregeneratesops.jsonandmigration.jsonfrom the (possibly edited) TS source. This is the only supported way to update an existing migration package after edits. migration.tsshape. Framework-rendered. A class extendingMigration(from@prisma-next/family-mongo/migrationon Mongo, or re-exported via@prisma-next/postgres/migrationon Postgres — see the framing block below), with anoperationsgetter that returns an array of factory-call values. The file ends withMigrationCLI.run(import.meta.url, M)so executing it self-emits.placeholder(slot). A sentinel the planner emits into the renderedmigration.ts(from@prisma-next/errors/migrationon Mongo, or the@prisma-next/postgres/migrationimport on Postgres) wherever a data transform is needed. Callingplaceholder(...)at emit time throwsPN-MIG-2001Unfilled migration placeholder. The user replaces the() => placeholder(...)arrow with a real query-plan closure (Postgres) or fillsdataTransform({ check, run })sources (Mongo — see Fill a placeholder), then self-emits.this.dataTransform(endContract, name, { check, run }). The data-transform factory.checkis a rowset query whose presence-of-any-row signals "work remains";runis one or more mutation queries that perform the backfill. Both are lazy closures returning query-plans built againstendContract. The runner wrapscheckasEXISTS(...)for precheck andNOT EXISTS(...)for postcheck, so the same closure asserts both "there is work" and "the work is done".pendingPlaceholders. A boolean field on the JSON result ofmigration plan.truemeans the package was written but contains unfilled placeholders —migratewill throwPN-MIG-2001until you editmigration.tsand self-emit.migrationHash. Content-addressed identity of a migration package.MIGRATION.HASH_MISMATCHfires when the stored hash inmigration.jsondisagrees with the hash recomputed from the on-disk files (almost always: someone editedmigration.tswithout self-emitting).- Marker. Records "this database is at contract hash X for space Y". Postgres: a row in
prisma_contract.marker. Mongo: a document in the_prisma_migrationscollection (keyed by space). Each successful migration advances the marker once schema verification passes for that space.db signwrites the marker from the current contract hash, but only after a schema-verification pass succeeds (it will not sign a database whose live schema disagrees with the contract). - Apply atomicity. Postgres: each migration runs inside
BEGIN ... COMMIT; on failure, Postgres rolls back and the marker stays at the previous migration'stohash. Mongo: DDL ops (createCollection,createIndex,collMod,setValidation, …) are not wrapped in a multi-document transaction; the runner applies ops, verifies the live schema against the destination contract, and advances the marker only on verify-pass (resumable across spaces — see the MongoDB family doc). Ordinary DDL +dataTransformflows stay consistent; partial state from failed mid-migration runs is diagnosed withdb verify/db schema, not assumed away. - Operation classes. Every operation declares an
operationClass:additive,widening,data, ordestructive. The CLI surfaces these in the plan preview and in JSON output. There is nolong-runningclass and the framework does not emitCREATE INDEX CONCURRENTLY— operations stay transactional.
migration.ts is framework-rendered, not hand-authored
Files under migrations/<space-id>/<timestamp>/migration.ts (for your own app, <space-id> is always app/) are rendered for you by the framework — prisma-next migration plan writes a populated package whenever the contract changes, and prisma-next migration new writes an empty scaffold when you want to author operations directly. You do not write these files from scratch. You edit specific holes the framework leaves behind — chiefly replacing placeholder("<slot>") sentinels (Postgres) or filling dataTransform({ check, run }) pipeline slots (Mongo) — then self-emit.
Postgres rendered imports point at @prisma-next/postgres/migration (or @prisma-next/sqlite/migration for SQLite projects).
Mongo rendered imports use @prisma-next/family-mongo/migration for the Migration base class and @prisma-next/target-mongo/migration for operation factories (createIndex, dataTransform, …). MigrationCLI comes from @prisma-next/cli/migration-cli.
Treat the rendered import lines as framework-managed on both targets:
- Leave them where they are. Don't rewrite them to a different
@prisma-next/<…>path; the framework's renderer is the authoritative shape and any change you make by hand will be reverted (and may tripMIGRATION.HASH_MISMATCH) the next time the package is re-rendered or self-emitted. - If you need an additional factory symbol, add it to the existing rendered import line (Postgres:
@prisma-next/postgres/migration; Mongo:@prisma-next/target-mongo/migration) rather than introducing a second import from a different@prisma-next/...subpath. - The "user code imports only from
@prisma-next/<target>" convention applies to your own modules (queries, runtime setup, contract authoring). The framework-renderedmigration.tsscaffold is the framework's surface, not yours; the rule is suspended for that one file.
Diagnostic codes you route on
| Code | Source | Move |
|---|---|---|
PN-MIG-2001 Unfilled migration placeholder | Throwing placeholder(...) at emit time | Open migration.ts, replace the named placeholder("<slot>") call with the real query closure, self-emit. |
PN-MIG-2002 migration.ts not found | Reading a migration package | The package is malformed. Recover from version control, or run prisma-next migration new for a fresh one. |
PN-MIG-2003 invalid default export | Loading migration.ts | The file's default export is not a Migration subclass or factory function. Restore the planner-emitted scaffold from version control or re-run migration plan for a clean package. |
PN-MIG-2005 dataTransform contract mismatch | Building a data-transform query plan | The query builder was instantiated with a contract reference different from the endContract passed to this.dataTransform(...). Use the endContract imported at module scope for both. |
MIGRATION.HASH_MISMATCH Migration package is corrupt | migrate (or any read of the package) | ops.json / migration.json were edited without self-emitting. Run node migrations/app/<dir>/migration.ts to re-emit, then re-run migrate. |
PN-RUN-3002 Hash mismatch | db verify | The marker disagrees with the contract hash (Postgres: prisma_contract.marker; Mongo: _prisma_migrations). The DB is at a different contract version than the code thinks. Either run a migration forward, or — if the DB is correct and the marker is stale after a manual fix-up — run db sign. |
PN-RUN-3001 Database not signed | Any command needing a marker | The DB has no marker yet. Run prisma-next db init --db <url> to baseline an empty database, or db update --db <url> to apply the current contract directly. |
Decision — which path do you take?
| Situation | Path | Why |
|---|---|---|
| Local dev, schema in flux | db update | Fast, interactive, no migration files. |
| Shared branch with other developers | migration plan + migrate | Replayable, reviewable, content-hashed. |
| Anything reaching production | migration plan + migrate | Production must run a reviewed, hashed migration. |
| Adding a column that needs a backfill | migration plan (writes placeholder), edit migration.ts, self-emit, then migrate | db update does not author data transforms; the formal path does. |
| Recovering from drift (DB diverged from contract) | db sign after manual fix, or migration plan if PN can plan the fix | Depends on which side is right. See Recover from drift below. |
Dev → ship transition (the db ref pattern)
Example — iterate locally with db update, then publish the first real migration:
pnpm prisma-next db init --db $DATABASE_URL
pnpm prisma-next contract emit && pnpm prisma-next db update --db $DATABASE_URL
pnpm prisma-next contract emit && pnpm prisma-next migration plan --name add_feature
pnpm prisma-next migrate --db $DATABASE_URL
pnpm prisma-next db verify --db $DATABASE_URL
The db ref is a named pointer at migrations/app/refs/db.json — just { hash, invariants }. It records which contract hash the project's dev database has been brought up to — the offline planner's stand-in for "where is my local DB?" without opening a connection at plan time. The contract it names resolves through the shared content-addressed store at migrations/snapshots/<hex>/contract.json by that hash, the same store every migration graph node resolves through.
What db init / db update write. When run against the project's default --db URL (no explicit --db flag), both commands implicitly advance the db ref: they write-if-absent the post-command contract IR into the snapshot store, then write the ref's pointer. Override the ref name with --advance-ref <name>. When you pass --db <non-default-url>, ref advancement is suppressed unless --advance-ref is explicit — reconciling a different database is not the same as checkpointing this project's dev state.
The on-disk layout is just the pointer:
migrations/app/refs/
└── db.json # { "hash": "<hex>", "invariants": [] }
First migration plan after dev iteration. migration plan defaults --from to the db ref. When the on-disk migration graph is still empty and the db ref points at a non-null hash with a store entry (typical after one or more db update cycles), the planner emits two bundles instead of one:
- Baseline:
null → from-hash(introducesfrom-hashas a graph node) - Delta:
from-hash → current_contract
Both land on disk in one invocation — expect two new directories in git status. migrate then finds a path through the baseline and applies the delta. This closes the dev → ship trap where a single-bundle plan referenced a hash that was not yet a graph node and produced an unapplyable migration (MIGRATION.PATH_UNREACHABLE at apply time).
The forgot-the-flag pitfall. After the graph is non-empty, the default db ref may point past the graph tip (the ref advanced on every db update while you iterated, but you never committed migrations). The next implicit-default migration plan refuses with MIGRATION.HASH_NOT_IN_GRAPH and names reachable refs that point at graph nodes.
Recovery when you see MIGRATION.HASH_NOT_IN_GRAPH on plan:
# Option A — plan from a graph node explicitly
pnpm prisma-next migration plan --from production --name my_change
# Option B — realign the db ref to a graph-node hash, then
...
用户评价 (0)
发表评价
暂无评价
统计数据
用户评分
为此 Skill 评分