From d2ca62d7e7540fa6c8d1588044b7d25ec7a132dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 17:35:35 +0700 Subject: [PATCH 01/15] docs: add Prisma Next Migrations section Six pages under /docs/orm/next/migrations covering the full migration workflow: how migrations work, the migration graph, generating, editing, applying, and rollbacks/recovery. All commands, file layouts, and CLI outputs verified against prisma-next origin/main by running the flows end-to-end (plan, placeholder backfill, self-emit, apply, status, log, rollback, failure/atomicity behavior). Adds three ConceptAnimation presets (migration-loop, migration-graph, migration-rollback), temporary redirects from the Prisma 7 migrate pages that have clean equivalents, and cspell entries for migration terms. Co-Authored-By: Claude Fable 5 --- .claude/launch.json | 11 + apps/docs/content/docs/orm/next/meta.json | 5 +- .../next/migrations/applying-a-migration.mdx | 167 ++++++++++++++ .../next/migrations/editing-a-migration.mdx | 207 +++++++++++++++++ .../migrations/generating-a-migration.mdx | 212 ++++++++++++++++++ .../next/migrations/how-migrations-work.mdx | 148 ++++++++++++ .../docs/orm/next/migrations/meta.json | 11 + .../migrations/rollbacks-and-recovery.mdx | 117 ++++++++++ .../next/migrations/the-migration-graph.mdx | 119 ++++++++++ apps/docs/cspell.json | 7 + apps/docs/next.config.mjs | 28 +++ .../concept-animation/flow-presets.ts | 6 +- .../components/concept-animation/index.tsx | 10 +- .../components/concept-animation/presets.ts | 149 ++++++++++++ 14 files changed, 1186 insertions(+), 11 deletions(-) create mode 100644 .claude/launch.json create mode 100644 apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx create mode 100644 apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx create mode 100644 apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx create mode 100644 apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx create mode 100644 apps/docs/content/docs/orm/next/migrations/meta.json create mode 100644 apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx create mode 100644 apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000000..7f171c4ae5 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "docs", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--filter", "docs", "exec", "next", "dev", "--port", "3105"], + "port": 3105 + } + ] +} diff --git a/apps/docs/content/docs/orm/next/meta.json b/apps/docs/content/docs/orm/next/meta.json index ae5f3be609..b348843f34 100644 --- a/apps/docs/content/docs/orm/next/meta.json +++ b/apps/docs/content/docs/orm/next/meta.json @@ -2,8 +2,5 @@ "title": "Next", "defaultOpen": true, "root": true, - "pages": [ - "---Introduction---", - "index" - ] + "pages": ["---Introduction---", "index", "---Migrations---", "...migrations"] } diff --git a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx new file mode 100644 index 0000000000..805cba2d42 --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx @@ -0,0 +1,167 @@ +--- +title: Applying a migration +description: prisma-next migrate walks the graph from where your database is to where you want it — with a preview, a checkpoint at every step, and safe retries. +url: /orm/next/migrations/applying-a-migration +metaTitle: Applying a migration in Prisma Next +metaDescription: How prisma-next migrate applies migrations — targeting refs and hashes, previewing with --show, checking status, and running safely in development and production. +badge: early-access +--- + +Applying is the one step that touches a database, and it has one command: + +```bash +npx prisma-next migrate +``` + +Note the verb: it's `migrate`, not `migration apply`. The `migration ...` subcommands manage files on disk; `migrate` moves a database. It reads where the database currently is (its **marker**), finds a path through the [migration graph](/orm/next/migrations/the-migration-graph) to the target, and applies each migration along the path — running every operation's precheck, execute, and postcheck as it goes. + +The connection comes from `db.connection` in `prisma-next.config.ts`, or `--db` to override: + +```bash +npx prisma-next migrate --db $DATABASE_URL +``` + +A successful run reports every operation it performed and the marker it left behind: + +```text +✔ Applied 1 migration(s) (3 operation(s)) across 1 contract space(s) + +App space + ├─ Add column "displayName" to "user" + ├─ Data transform: backfill-user-displayName + └─ Set NOT NULL on "user"."displayName" (destructive) + marker: sha256:e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63 + +Next: prisma-next migration status +``` + +## Check before, preview, then apply + +The habit worth building — especially against shared databases — is a three-step rhythm: + +```bash +# 1. Where is the database, what's pending? +npx prisma-next migration status --db $DATABASE_URL + +# 2. What exactly would run? +npx prisma-next migrate --show --db $DATABASE_URL + +# 3. Run it. +npx prisma-next migrate --db $DATABASE_URL +``` + +`migration status` draws the path between the database's marker and the target, flagging each migration as applied or pending: + +```text +* 925198f @contract +|^ 20260707T1006_add_user_phone 705b1a6 -> 925198f 1 ops > pending +* 705b1a6 @db (db) +|^ 20260707T1005_init - -> 705b1a6 2 ops + applied +* - + +1 pending — run `prisma-next migrate --to 925198f3cc27` +``` + +Read the markers on the right: `@db` is where the database is, `@contract` is where your emitted contract is, and `(db)` is the [ref](/orm/next/migrations/the-migration-graph#refs-naming-places-in-the-graph) of that name pointing at the same node. + +`migrate --show` is the read-only dry run: it prints the migrations that would execute and stops. Nothing touches the database. + +After applying, `migration log` shows the database's own record of what ran — an append-only **ledger** the runner writes alongside the marker: + +```text + Applied at Migration Change Ops +---------------------- ------------------------------- -------------------- ------ + 2026-07-07 10:05:32Z 20260707T1005_init - -> 705b1a6 2 ops + 2026-07-07 10:09:55Z 20260707T1006_add_user_phone 705b1a6 -> 925198f 1 ops +``` + +## Choosing a target + +With no `--to`, `migrate` advances toward your emitted contract. To aim somewhere specific, `--to` accepts the same reference grammar as everywhere else — a ref name, a contract hash or prefix, a migration directory name, or `^` for the state before a migration: + +```bash +npx prisma-next migrate --to prod --db $DATABASE_URL # a ref +npx prisma-next migrate --to sha256:e6b5c28 --db $DATABASE_URL # a hash prefix +npx prisma-next migrate --to 20260707T1005_init --db $DATABASE_URL +``` + +If the graph has branched and more than one tip is reachable, `migrate` refuses to guess and asks for an explicit `--to`. That's the graph protecting you: two feature branches may both be valid futures, and picking one is a human decision. + +`--advance-ref` moves a named ref to the post-apply state in the same step. Advancing one called `db` is what keeps [`migration plan`](/orm/next/migrations/generating-a-migration#skipping---from-the-db-ref) incremental: + +```bash +npx prisma-next migrate --advance-ref db +``` + +## When something goes wrong + +The runner stops at the first failing operation and tells you which one, why, and what to do: + +```text +✖ Operation pgvector.install-vector-extension failed during execution: create extension "vector" (PN-RUN-3000) + Why: extension "vector" is not available + Fix: Fix the issue and re-run `prisma-next migrate --to ` — previously applied migrations are preserved. +``` + +Three properties make failure boring instead of terrifying: + +- **On PostgreSQL, a failed run leaves nothing behind.** The entire `migrate` run executes inside one transaction, so when an operation fails, everything from that run rolls back and the database is exactly where it was before you started. Migrations applied in *earlier* runs are untouched — that's what "previously applied migrations are preserved" means. +- **The error is specific.** It names the operation, the phase (precheck, execute, or postcheck), and the check that failed — enough to fix the cause without spelunking. +- **Re-running is safe.** Operations are idempotent: before running one, the runner evaluates its postcheck and skips it if the database already satisfies it. A change that snuck in out-of-band doesn't break the run — it just becomes a skip. On MongoDB, where cross-collection transactions don't exist, this same mechanism is what makes a partially-applied run converge on retry. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery) for the full failure playbook. + +Before running any DDL, `migrate` also verifies the database's marker is a state the graph knows. A database that was changed outside of migrations fails fast with a marker mismatch instead of getting SQL applied on top of unknown drift. + +## Development vs. production + +The commands are the same everywhere; what changes is where the files come from and who runs them. + +**In development**, you're the one planning and editing, and you apply immediately: + +```bash +npx prisma-next migration plan --name my_change && npx prisma-next migrate --advance-ref db +``` + +**In CI and production**, migrations arrive via your repo, already planned, reviewed, and merged. The deploy step is: + +```bash +npx prisma-next migration check # files intact, graph well-formed (offline) +npx prisma-next migrate --show --db $DATABASE_URL # log what's about to run +npx prisma-next migrate --db $DATABASE_URL +``` + +A production nicety that falls out of the design: the runner executes only `ops.json` — plain data. Your `migration.ts` files, and any TypeScript they import, are never executed with production credentials. + +Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transaction guarded by an advisory lock, so two `migrate` runs serialize instead of interleaving. On MongoDB — where cross-collection DDL transactions don't exist — each migration advances the marker with compare-and-swap and the runner verifies the resulting schema before committing the marker, so a re-run converges rather than double-applying. + +## Extension spaces + +If your project uses database extensions (say pgvector), you'll see more than one *contract space* in the output: extensions ship their own migrations (for example `CREATE EXTENSION vector`), tracked in `migrations//` next to your app's. One `migrate` run walks them all — extensions first, then your app — and reports each space separately: + +```text +✔ Applied 2 migration(s) (20 operation(s)) across 2 contract space(s) + +Extension space: pgvector + └─ Enable extension "vector" + +App space + ├─ Create table "user" + └─ ... +``` + +:::note[What's early] + +Apply, targeting, preview, refs, the ledger, and multi-space runs all work today. Not built yet: a shadow-database rehearsal (`migrate` runs against the real target; use `--show` and a staging database), and an apply-time check that `ops.json` still matches `migration.ts` (today that's `migration check`'s job — run it in CI). + +::: + +## Prompt your coding agent + +- "Check migration status against staging and apply whatever is pending." +- "Preview what `migrate --to prod` would run and summarize the destructive operations." +- "Apply the pending migrations and advance the `db` ref." + +## See also + +- [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery): when you need to go back instead of forward +- [The migration graph](/orm/next/migrations/the-migration-graph): markers, refs, and pathfinding +- [Generating a migration](/orm/next/migrations/generating-a-migration): producing what `migrate` runs diff --git a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx new file mode 100644 index 0000000000..e4ed3d551e --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx @@ -0,0 +1,207 @@ +--- +title: Editing a migration +description: A migration is TypeScript you own. Fill in backfills, reorder steps, or drop to raw SQL — then recompile it with one command. +url: /orm/next/migrations/editing-a-migration +metaTitle: Editing a migration in Prisma Next +metaDescription: How to edit a Prisma Next migration.ts — fill placeholder data transforms, write typed backfills, use rawSql, and recompile ops.json with node migration.ts. +badge: early-access +--- + +The planner writes a good first draft, but plenty of real migrations need a human (or agent) decision: what to put in existing rows when a column becomes required, which order two steps must run in, one statement the planner has no factory for. In Prisma Next you make those changes by editing `migration.ts` — ordinary TypeScript with autocomplete and type checking — and recompiling it. You never hand-edit SQL files, and you never hand-edit `ops.json`. + +The rule that makes this safe: + +> **You edit `migration.ts`. Running it regenerates `ops.json`. The runner only ever executes `ops.json`.** + +After any edit, recompile from inside your project: + +```bash +node migrations/app/20260707T1008_add_display_name/migration.ts +``` + +```text +Wrote ops.json + migration.json to migrations/app/20260707T1008_add_display_name +``` + +The recompile also re-attests the package: `migration.json` gets a fresh `migrationHash` covering the compiled output. If someone edits `ops.json` directly — or edits `migration.ts` and forgets to recompile — `migration check` fails in CI with a hash mismatch. Commit `migration.ts` and `ops.json` together, like a lockfile and its manifest. + +## Worked example: making a column required + +This is the classic case. `User` gets a required `displayName`, but the table already has rows, and those rows have no `displayName`. Plan it: + +```bash +npx prisma-next migration plan --name add_display_name +``` + +```text +⚠ Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit + +Open migration.ts and replace each `placeholder(...)` call with your actual query. +``` + +The planner knew the shape of the fix — add the column nullable, backfill, then tighten — and scaffolded exactly that, leaving the backfill query to you: + +```ts title="migration.ts (as planned)" +override get operations() { + return [ + this.addColumn({ + schema: 'public', + table: 'user', + column: col('displayName', 'text', { codecRef: { codecId: 'pg/text@1' } }), + }), + this.dataTransform(endContract, 'backfill-user-displayName', { + check: () => placeholder('backfill-user-displayName:check'), + run: () => placeholder('backfill-user-displayName:run'), + }), + this.setNotNull({ schema: 'public', table: 'user', column: 'displayName' }), + ]; +} +``` + +A `dataTransform` takes two closures. `check` asks "are there rows that still need this?" — it must be a row-returning query where *any row* means work remains (the conventional shape is `select('id').where().limit(1)`). `run` performs the change. Fill them with the typed SQL query builder, wired to the contract snapshot sitting next to the migration: + +```ts title="migration.ts (filled in)" +import type { Contract as End } from './end-contract'; +import endContractJson from './end-contract.json' with { type: 'json' }; +import type { Contract as Start } from './start-contract'; +import startContract from './start-contract.json' with { type: 'json' }; +import { Migration, MigrationCLI, col } from '@prisma-next/postgres/migration'; +import postgresAdapter from '@prisma-next/adapter-postgres/runtime'; +import { sql } from '@prisma-next/sql-builder/runtime'; +import { createExecutionContext, createSqlExecutionStack } from '@prisma-next/sql-runtime'; +import postgresTarget, { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime'; + +const endContract = new PostgresContractSerializer().deserializeContract(endContractJson); + +const db = sql({ + context: createExecutionContext({ + contract: endContract, + stack: createSqlExecutionStack({ target: postgresTarget, adapter: postgresAdapter }), + }), +}); + +export default class M extends Migration { + override readonly endContractJson = endContract; + override readonly startContractJson = startContract; + + override get operations() { + return [ + this.addColumn({ + schema: 'public', + table: 'user', + column: col('displayName', 'text', { codecRef: { codecId: 'pg/text@1' } }), + }), + this.dataTransform(endContract, 'backfill-user-displayName', { + check: () => + db.public.user + .select('id') + .where((f, fns) => fns.eq(f.displayName, null)) + .limit(1), + run: () => + db.public.user + .update({ displayName: 'Anonymous' }) + .where((f, fns) => fns.eq(f.displayName, null)), + }), + this.setNotNull({ schema: 'public', table: 'user', column: 'displayName' }), + ]; + } +} + +MigrationCLI.run(import.meta.url, M); +``` + +Two details worth pausing on: + +- **The types come from the migration's own contract snapshot** (`./end-contract`), not your live app contract. You're type-checking against the schema *as it will exist when this step runs* — which is why the builder happily references `displayName` even though the column doesn't exist yet, and why a migration written months ago keeps compiling after your contract moves on. +- **The query is real application-grade TypeScript.** You can import shared constants, and typos in column names fail the type check instead of failing in production. + +Recompile with `node migration.ts` and inspect what the backfill became: + +```json title="ops.json (the compiled dataTransform)" +{ + "id": "data_migration.backfill-user-displayName", + "label": "Data transform: backfill-user-displayName", + "operationClass": "data", + "precheck": [ + { + "description": "Check backfill-user-displayName has work to do", + "sql": "SELECT EXISTS (SELECT \"id\" AS \"id\" FROM \"public\".\"user\" WHERE \"displayName\" IS NULL LIMIT 1) AS ok", + "params": [] + } + ], + "execute": [ + { + "description": "Run backfill-user-displayName", + "sql": "UPDATE \"public\".\"user\" SET \"displayName\" = $1 WHERE \"displayName\" IS NULL", + "params": ["Anonymous"] + } + ], + "postcheck": [ + { + "description": "Verify backfill-user-displayName resolved all violations", + "sql": "SELECT NOT EXISTS (SELECT \"id\" AS \"id\" FROM \"public\".\"user\" WHERE \"displayName\" IS NULL LIMIT 1) AS ok", + "params": [] + } + ] +} +``` + +Your `check` closure became both the precheck (`EXISTS` — is there work?) and the postcheck (`NOT EXISTS` — is it done?). Your `run` became a parameterized `UPDATE` — note `"Anonymous"` travels in `params`, through the driver's parameter binder, never spliced into SQL text. The reviewer reading your PR sees intent in `migration.ts` and the exact statements in `ops.json`, side by side. + +## Escape hatch: raw SQL + +For anything the operation factories don't cover — enabling an extension, `CREATE INDEX CONCURRENTLY`, a vendor-specific statement — use `rawSql`, and keep the same three-phase safety if you can: + +```ts +rawSql({ + id: 'extension.pgcrypto', + label: 'Enable extension "pgcrypto"', + operationClass: 'additive', + target: { id: 'postgres' }, + precheck: [ + { description: 'not yet enabled', sql: "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto')" }, + ], + execute: [{ description: 'enable it', sql: 'CREATE EXTENSION IF NOT EXISTS pgcrypto' }], + postcheck: [ + { description: 'now enabled', sql: "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto')" }, + ], +}), +``` + +The prechecks and postchecks are optional — but they're what makes a failed run resumable and a mistake diagnosable, so skipping them trades away most of what this system gives you. If you write the same `rawSql` twice, lift it into a function; the built-in factories are plain functions doing exactly this. + +## Starting from a blank migration + +Sometimes there's no contract change at all — you want a data-only migration, or you'd rather write the whole thing by hand. `migration new` scaffolds an empty, attested package: + +```bash +npx prisma-next migration new --name backfill_scores +``` + +Write your operations in the generated `migration.ts`, then compile it the same way: `node migration.ts`. + +## Editing checklist + +1. Edit `migration.ts` — never `ops.json`. +2. Recompile: `node /migration.ts`. +3. Review the diff of `ops.json` — that's what will run. +4. Verify: `npx prisma-next migration check`. +5. Commit `migration.ts`, `ops.json`, and `migration.json` together. + +:::note[What's early] + +The typed-builder wiring at the top of the filled example (deserializing the contract, building the `db` handle) is more ceremony than we want; expect it to shrink. On MongoDB, hand-authored data transforms currently use raw Mongo command shapes rather than the full typed builder. `rawSql` and the scaffolded placeholder flow shown here work today, end to end. + +::: + +## Prompt your coding agent + +- "Fill in the placeholder in the latest migration: backfill `displayName` with the user's email prefix." +- "Add a data transform to this migration that normalizes existing `phone` values before the unique constraint." +- "Recompile the migration I just edited and show me the ops.json diff." + +## See also + +- [Generating a migration](/orm/next/migrations/generating-a-migration): where the scaffold comes from +- [Applying a migration](/orm/next/migrations/applying-a-migration): run the edited migration +- [Data Migrations in Prisma Next](https://www.prisma.io/blog/data-migrations-in-prisma-next): the design story for `dataTransform` diff --git a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx new file mode 100644 index 0000000000..894f0cb51e --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx @@ -0,0 +1,212 @@ +--- +title: Generating a migration +description: Turn a contract change into a reviewable migration package with prisma-next migration plan. +url: /orm/next/migrations/generating-a-migration +metaTitle: Generating a migration in Prisma Next +metaDescription: A step-by-step tutorial for prisma-next migration plan — from contract change to a planned, reviewable migration package, entirely offline. +badge: early-access +--- + +This tutorial takes a contract change from your editor to a planned migration on disk. Planning is **fully offline**: `migration plan` reads your emitted contract and your existing migrations — it never connects to a database, so you can plan on a plane, in CI, or in a sandbox with no credentials. + +We'll start from a minimal project. If you don't have one, `npx create-prisma@next` scaffolds it. + +## Your first migration + +Say your contract has a single model: + +```prisma title="contract.prisma" +model User { + id Int @id + email String + name String? + + @@map("user") +} +``` + +First, emit the contract — this compiles the schema into the `contract.json` artifact that every other command reads: + +```bash +npx prisma-next contract emit +``` + +Now plan. `--name` sets the human-readable part of the directory name; without it the directory is just called `migration`: + +```bash +npx prisma-next migration plan --name init +``` + +```text +✔ Planned 2 operation(s) + +│ +├─ Create schema "public" +└─ Create table "user" + +from: null +to: sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5 +App space → migrations/app/20260707T1005_init + +Next: review migrations/app/20260707T1005_init if needed, then run prisma-next migrate. + +DDL preview + +CREATE SCHEMA IF NOT EXISTS "public"; +CREATE TABLE "public"."user" ( + "email" text NOT NULL, + "id" int4 NOT NULL, + "name" text, + PRIMARY KEY ("id") +); +``` + +Three things to notice: + +- **The DDL preview is right there.** You see the exact SQL before anything exists but files. +- **`from: null`** means this migration starts from an empty database — it's the root of your [migration graph](/orm/next/migrations/the-migration-graph). +- **`to:` is your contract's hash.** The migration promises to deliver a database matching exactly the contract you just emitted. + +The planned package contains the TypeScript source, the compiled operations, and the contract snapshots: + +```text +migrations/app/20260707T1005_init/ +├── migration.ts +├── ops.json +├── migration.json +└── end-contract.json (+ end-contract.d.ts) +``` + +Open `migration.ts` — it reads like a description of the change, because it is one: + +```ts title="migrations/app/20260707T1005_init/migration.ts" +import type { Contract as End } from './end-contract'; +import endContract from './end-contract.json' with { type: 'json' }; +import { Migration, MigrationCLI, col, primaryKey } from '@prisma-next/postgres/migration'; + +export default class M extends Migration { + override readonly endContractJson = endContract; + + override get operations() { + return [ + this.createSchema({ schema: 'public' }), + this.createTable({ + schema: 'public', + table: 'user', + columns: [ + col('email', 'text', { notNull: true, codecRef: { codecId: 'pg/text@1' } }), + col('id', 'int4', { notNull: true, codecRef: { codecId: 'pg/int4@1' } }), + col('name', 'text', { codecRef: { codecId: 'pg/text@1' } }), + ], + constraints: [primaryKey(['id'])], + }), + ]; + } +} + +MigrationCLI.run(import.meta.url, M); +``` + +For a simple change you don't need to touch this file. When you do — to add a data backfill, reorder operations, or drop in raw SQL — that's [Editing a migration](/orm/next/migrations/editing-a-migration). + +## The second migration: planning a delta + +Apply the first migration (covered properly in [Applying a migration](/orm/next/migrations/applying-a-migration)), then make another change — add a `phone` field: + +```prisma +model User { + id Int @id + email String + name String? + phone String? + + @@map("user") +} +``` + +Emit and plan again. This time, tell the planner where to start from, so it plans the *delta* rather than the whole schema: + +```bash +npx prisma-next contract emit +npx prisma-next migration plan --name add_user_phone --from 20260707T1005_init +``` + +```text +✔ Planned 1 operation(s) + +│ +└─ Add column "phone" to "user" + +from: sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5 +to: sha256:925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72 +App space → migrations/app/20260707T1006_add_user_phone + +DDL preview + +ALTER TABLE "public"."user" ADD COLUMN "phone" text; +``` + +`--from` accepts a migration directory name (as here), a contract hash or unambiguous prefix, a ref name, or `^` meaning "the state *before* that migration". + +### Skipping `--from`: the `db` ref + +Passing `--from` every time gets old. The planner's default is smarter: if a ref named **`db`** exists, planning starts from whatever it points at. Keep it advanced as part of applying: + +```bash +npx prisma-next migrate --advance-ref db +npx prisma-next migration plan --name next_change # starts from the db ref automatically +``` + +:::warning[Without `--from` or a `db` ref, plans start from empty] + +If there's no `db` ref and you omit `--from`, `migration plan` plans from an **empty database** — a full `CREATE`-everything migration, not a delta. If you expected a one-column change and got a wall of `CREATE TABLE`, this is why. Pass `--from` or set up the `db` ref. + +::: + +## When the planner needs your input + +Some changes can't be planned from the schema diff alone. Add a **required** field to a table that already has rows, and the planner can write the `ADD COLUMN` and the `SET NOT NULL` — but only you know what the existing rows should contain. Rather than guessing, it scaffolds the decision as a **placeholder**: + +```text +⚠ Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit + +Open migration.ts and replace each `placeholder(...)` call with your actual query. +Then run: node migrations/app/20260707T1008_add_display_name/migration.ts +``` + +The generated migration sandwiches a `dataTransform` between the schema steps, with `placeholder(...)` where your backfill query goes. Filling it in is the subject of [Editing a migration](/orm/next/migrations/editing-a-migration). + +## Reviewing what you planned + +Three offline commands close the loop before anything runs: + +```bash +# One migration in detail: operations, metadata, DDL preview +npx prisma-next migration show 20260707T1006_add_user_phone + +# The whole graph, with your new edge in place +npx prisma-next migration graph + +# Integrity check: hashes match, files complete, graph well-formed — exits non-zero on failure +npx prisma-next migration check +``` + +`migration check` is designed for CI: exit code `0` means clean, `2` means it couldn't resolve what you asked for, `4` means an integrity failure — for example, someone hand-edited `ops.json` without re-running `migration.ts`. + +:::note[What's early] + +Planning covers tables, columns, indexes, constraints, and the backfill scaffold shown above. Rename inference is not built yet: renaming a field plans as **drop column + add column** — flagged destructive, with a data-loss warning — so for a true rename, edit the migration and replace the pair with a `rawSql` `ALTER TABLE ... RENAME COLUMN`. There is also no interactive mode; the planner writes its best answer and leaves refinement to you in `migration.ts`. + +::: + +## Prompt your coding agent + +- "Add a required `displayName` field to User, emit the contract, and plan the migration." +- "Plan a migration named `add-orders-table` and show me its DDL preview before I commit it." +- "Run `migration check` and explain any integrity failures." + +## See also + +- [Editing a migration](/orm/next/migrations/editing-a-migration): fill placeholders, add data steps, write raw SQL +- [Applying a migration](/orm/next/migrations/applying-a-migration): run what you planned +- [TypeScript Migrations in Prisma Next](https://www.prisma.io/blog/typescript-migrations-in-prisma-next): the design story behind `migration.ts` diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx new file mode 100644 index 0000000000..30b4755759 --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -0,0 +1,148 @@ +--- +title: How migrations work +description: Change your contract, plan a migration, review it, apply it. Every step is checked before and after it runs. +url: /orm/next/migrations/how-migrations-work +metaTitle: How migrations work in Prisma Next +metaDescription: What a Prisma Next migration is, the plan-review-apply loop, the files inside a migration package, and why every operation is safe to retry. +badge: early-access +--- + +A migration is how Prisma Next changes your database when your contract changes. You add a field to a model, and something has to run `ALTER TABLE` against every database that serves your app: your laptop, staging, production. Migrations are that something, recorded as files in your repo so the change is reviewable, repeatable, and versioned with your code. + +The workflow is a loop you will run many times a day in development: + + + +1. **Change your contract**: edit your `.prisma` file (or TypeScript contract) and run `prisma-next contract emit`. +2. **Plan a migration**: `prisma-next migration plan` compares the new contract against your migration history and writes a migration package to `migrations/`. +3. **Review it**: read the generated TypeScript and the DDL preview. Edit the migration if the change needs a data step, then re-run the file to recompile it. +4. **Apply it**: `prisma-next migrate` runs the pending migrations against your database. + +```bash +npx prisma-next contract emit +npx prisma-next migration plan --name add_user_phone +npx prisma-next migrate +``` + +Each page in this section walks through one part of the loop in detail. This page covers the model: what a migration actually is, and why it looks the way it does. + +## What a migration is + +A migration is a directory under `migrations/app/`, named with a timestamp and a slug: + +```text +migrations/ +└── app/ + └── 20260707T1005_add_user_phone/ + ├── migration.ts # the migration, in TypeScript — this is what you edit + ├── ops.json # the compiled operations — this is what runs + ├── migration.json # metadata: from/to contract hashes + ├── start-contract.json # snapshot of the contract before this migration + └── end-contract.json # snapshot of the contract after this migration +``` + +Two files do the real work: + +- **`migration.ts`** is the authoring surface. It lists the migration's operations as plain function calls: `this.addColumn(...)`, `this.createTable(...)`, `this.dataTransform(...)`. It is ordinary TypeScript, type-checked against the contract snapshots sitting next to it, so your editor autocompletes column names and catches typos before anything touches a database. See [Editing a migration](/orm/next/migrations/editing-a-migration). +- **`ops.json`** is what the runner executes. When you run `migration.ts` (the planner does this for you; after an edit you re-run it with `node migration.ts`), it compiles the operations to JSON. Production never executes your TypeScript — the runner only reads `ops.json`, so no application code runs with production credentials. + +Both files are committed side by side, like `package.json` and `package-lock.json`: one you edit, one records exactly what will happen. + +`migration.json` records where the migration fits in history. Every contract compiles to a deterministic JSON artifact, and hashing it gives a short identifier for that exact database shape — like a git commit hash for your schema. A migration records the hash it starts **from** and the hash it moves the database **to**: + +```json title="migrations/app/20260707T1005_add_user_phone/migration.json" +{ + "from": "sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5", + "to": "sha256:925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72", + "providedInvariants": [], + "createdAt": "2026-07-07T10:05:55.937Z", + "migrationHash": "sha256:4b57fa2141c8ad94476d5de66c451468fd6c864210481ad00ac89b259491fbcf" +} +``` + +These `from`/`to` hashes are what link migrations into a [graph](/orm/next/migrations/the-migration-graph) instead of a numbered list — that's the next page. + +## Every operation checks itself + +Inside `ops.json`, each operation has three parts: a **precheck** that confirms the database is in the expected state, the **execute** statements that make the change, and a **postcheck** that confirms the change worked. + +```json title="One operation from ops.json" +{ + "id": "column.public.user.phone", + "label": "Add column \"phone\" to \"user\"", + "operationClass": "additive", + "precheck": [ + { + "description": "ensure column \"phone\" is missing", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"information_schema\".\"columns\" WHERE (\"table_schema\" = $1 AND \"table_name\" = $2 AND \"column_name\" = $3)) AS \"result\"", + "params": ["public", "user", "phone"] + } + ], + "execute": [ + { + "description": "add column \"phone\"", + "sql": "ALTER TABLE \"public\".\"user\" ADD COLUMN \"phone\" text" + } + ], + "postcheck": [ + { + "description": "verify column \"phone\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"information_schema\".\"columns\" WHERE (\"table_schema\" = $1 AND \"table_name\" = $2 AND \"column_name\" = $3)) AS \"result\"", + "params": ["public", "user", "phone"] + } + ] +} +``` + +This structure is what makes Prisma Next migrations safe in the situations where classic SQL migrations hurt: + +- **A migration fails.** On PostgreSQL the whole run is one transaction, so a failure rolls everything back — the database returns to exactly where it was, and you fix the cause and re-run. And because the runner skips any operation whose postcheck already holds, re-running never double-applies work that's already true in the database. No commenting out statements, no hand-editing the database. +- **The database isn't in the state you assumed.** The precheck catches it before the change runs, and the error names the exact operation and the exact check that failed — not a generic SQL error halfway through. +- **Someone (or some agent) wrote the migration for you.** The intent (`migration.ts`), the exact SQL (`ops.json`), and the verification for every step are all in the diff, so review is straightforward. + +## The commands + +Everything lives under the `prisma-next` CLI. Planning and inspection are offline — they read files, not your database: + +| Command | What it does | +| ------------------------- | ----------------------------------------------------------------------- | +| `migration plan` | [Generate a migration](/orm/next/migrations/generating-a-migration) from your contract changes | +| `migration new` | Scaffold an empty migration for [manual authoring](/orm/next/migrations/editing-a-migration#starting-from-a-blank-migration) | +| `migration show ` | Print one migration's operations, DDL preview, and metadata | +| `migration list` | List every on-disk migration | +| `migration graph` | Draw the [migration graph](/orm/next/migrations/the-migration-graph) | +| `migration check` | Verify migration files and graph integrity (useful in CI) | + +Two commands talk to a database: + +| Command | What it does | +| ------------------ | ---------------------------------------------------------------------------- | +| `migrate` | [Apply pending migrations](/orm/next/migrations/applying-a-migration) | +| `migration status` | Show where the database is in the graph and which migrations are pending | +| `migration log` | Show the history of applied migrations from the database's ledger | + +Note that applying is `prisma-next migrate`, not `migration apply` — `migration ...` commands manage the on-disk packages, `migrate` moves a database. + +## The same model for SQL and MongoDB + +Everything on this page applies to both database families. On PostgreSQL, operations compile to SQL DDL and the applied state is tracked in a marker table. On MongoDB, operations create collections, indexes, and JSON Schema validators, and state is tracked in a `_prisma_migrations` collection. The commands, the file layout, the graph, and the precheck/execute/postcheck structure are identical. + +:::note[Migrations are early] + +Prisma Next is in Early Access and migrations are one of its newest parts. The core loop on this page — plan, edit, apply, roll back — works today and is exercised in the Prisma Next test suite. Some things classic migration tools grew over years are not built yet: there are no squash/baseline commands, and no shadow-database dry run. We call out what's missing on each page rather than papering over it. + +::: + +## Prompt your coding agent + +Projects scaffolded with `create-prisma` install Prisma Next skills for your coding agent. Prompts that map to this page: + +- "Add a `phone` field to the User model and plan a migration for it." +- "Show me the pending migrations and what SQL they will run." +- "Explain what the ops.json in the latest migration does." + +## See also + +- [The migration graph](/orm/next/migrations/the-migration-graph): why migrations form a graph and what that buys you +- [Generating a migration](/orm/next/migrations/generating-a-migration): the `migration plan` walkthrough +- [Rethinking Database Migrations](https://www.prisma.io/blog/rethinking-database-migrations): the blog post on why this design exists diff --git a/apps/docs/content/docs/orm/next/migrations/meta.json b/apps/docs/content/docs/orm/next/migrations/meta.json new file mode 100644 index 0000000000..bafa457ac4 --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/meta.json @@ -0,0 +1,11 @@ +{ + "title": "Migrations", + "pages": [ + "how-migrations-work", + "the-migration-graph", + "generating-a-migration", + "editing-a-migration", + "applying-a-migration", + "rollbacks-and-recovery" + ] +} diff --git a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx new file mode 100644 index 0000000000..6ee1ee0f30 --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx @@ -0,0 +1,117 @@ +--- +title: Rollbacks and recovery +description: Rolling back is planning one more migration to a state you've already been in. Recovery is re-running — safely. +url: /orm/next/migrations/rollbacks-and-recovery +metaTitle: Rollbacks and recovery in Prisma Next +metaDescription: How to roll back a Prisma Next migration with a reverse plan, and how precheck/postcheck verification makes failed migrations safe to diagnose and retry. +badge: early-access +--- + +Two different situations get called "rollback", and Prisma Next treats them differently: + +- **The migration applied, but the change was wrong.** You need to move the database *back* to an earlier state. That's a **rollback** — and in Prisma Next it's just one more migration. +- **The migration failed partway.** You need to get *unstuck*. That's **recovery** — and usually means fixing the cause and re-running, because re-running is safe. + +## Rollback: a migration like any other + +There is no `migrate down` command, and no separate "down migration" files. In the [graph model](/orm/next/migrations/the-migration-graph), the state you want to return to is a node you've already visited — so rolling back means planning a new edge that points at it. If you know git, this is `git revert`, not `git reset`: history only ever grows, and the ledger keeps a full record of the round trip. + + + +Suppose `20260707T1008_add_display_name` shipped and needs to come back out. Plan the reverse edge — `^` means "the state just before that migration": + +```bash +npx prisma-next migration plan \ + --from 20260707T1008_add_display_name \ + --to 20260707T1008_add_display_name^ \ + --name rollback_display_name +``` + +```text +✔ Planned 1 operation(s) + +│ +└─ Drop column "displayName" from "user" (destructive) + +⚠ This migration contains destructive operations that may cause data loss. + +from: sha256:e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63 +to: sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5 + +DDL preview + +ALTER TABLE "public"."user" DROP COLUMN "displayName"; +``` + +The planner diffs the two contract states and writes the operations that undo the change — flagged **destructive**, because they are. This is a real migration package: review it, edit it (for example, to archive the column's data into another table before the `DROP`), commit it. Then apply it like any other migration: + +```bash +npx prisma-next migrate --to 20260707T1008_add_display_name^ +``` + +Afterwards the graph shows the round trip — a forward edge up, a rollback edge back down: + +```text +* e6b5c28 @contract +|^ 20260707T1008_add_display_name 705b1a6 -> e6b5c28 3 ops +|v 20260707T1010_rollback_display_name e6b5c28 -> 705b1a6 1 ops +* 705b1a6 +|^ 20260707T1005_init - -> 705b1a6 2 ops +* - +``` + +The database's marker is back at `705b1a6`, and the ledger records both the apply and the rollback. Nothing was rewritten or deleted. + +Two things to be clear-eyed about: + +- **A rollback does not resurrect data.** Dropping the column discards whatever the forward migration and the app wrote into it. If that data matters, edit the rollback migration to save it somewhere first — that's exactly why the rollback is an editable migration and not an automatic mechanism. +- **You don't have to retrace every step.** An edge can jump from the current state directly to any earlier node, skipping intermediate states — one planned migration, one apply, even if you're rolling back three changes. + +### One planning caveat after a rollback + +A rollback edge creates a cycle in the graph (`A → B → A`), and with a cycle the planner can no longer infer "the latest state" on its own. The next time you run `migration plan`, pass `--from` explicitly (a migration directory name or hash); the error you'd otherwise get, `MIGRATION.NO_TARGET`, says exactly this. + +## Recovery: when a migration fails partway + +A failed `migrate` run stops at the failing operation and reports it precisely: + +```text +✖ Operation alterNullability.setNotNull.user.nickname failed during precheck: + ensure no NULL values in "nickname" (PN-RUN-3000) + Why: Migration runner failed + Fix: Fix the issue and re-run `prisma-next migrate --to ` — previously applied migrations are preserved. +``` + +The playbook: + +1. **Read which check failed.** The error names the operation and the specific precheck or postcheck, in plain language — here, rows with `NULL` still exist, so tightening the constraint would fail. +2. **Nothing to clean up.** On PostgreSQL the whole run was one transaction, so the failure rolled it back completely: the database is exactly where it was before the run, and migrations applied in earlier runs are untouched. There is no "half-applied migration" to untangle by hand. +3. **Fix the cause, then re-run `prisma-next migrate`.** Sometimes the cause is the environment (extension missing, permissions). Sometimes it's the migration itself — in this example you'd [edit the migration](/orm/next/migrations/editing-a-migration) to add a backfill before the `setNotNull`, recompile with `node migration.ts`, and apply again. Re-running is always safe: the runner skips any operation whose postcheck already holds, so retries converge instead of double-applying. + +The precheck is also what protects you against the works-in-dev-breaks-in-prod trap. Your dev database had no `NULL`s, production does; the precheck halts production *before* the destructive `ALTER` touches anything, with an error pointing at the exact rows-with-NULLs condition — not a generic constraint violation mid-statement. + +### Drift: when the database isn't where migrations left it + +If someone changed the database outside of migrations — a hand-run `ALTER`, a restore from an old backup — the marker or the live schema won't match any state the graph knows, and `migrate` refuses to run rather than pile changes onto drift. Your options, in order of preference: + +- **`prisma-next db verify`** tells you exactly how the live schema differs from your contract. +- **In development**, `prisma-next db update` reconciles the database directly to your contract without walking the graph — quick, but off the record, so treat it as a dev-only reset. +- **For a database with no history at all** (a fresh environment, or adopting Prisma Next on an existing schema), `prisma-next db init` bootstraps it to the current contract and signs the marker. + +:::note[What's early] + +Reverse planning (`--to ^`), destructive-operation warnings, resumable re-runs, and the ledger all work today, as shown above. Honest gaps: there's no rehearsal mode that executes a migration against a shadow copy first, recovery from drift beyond the `db verify`/`db update`/`db init` trio is manual, and the planning caveat after cycles (explicit `--from`) is a real papercut we expect to smooth out. For deep or unusual situations, `#prisma-next` on [Discord](https://pris.ly/discord) is the fastest route. + +::: + +## Prompt your coding agent + +- "Plan a rollback for the last migration and show me its destructive operations before I decide." +- "This migrate run failed — read the error, fix the migration, and re-run it." +- "Check whether staging has drifted from the contract and explain the differences." + +## See also + +- [Applying a migration](/orm/next/migrations/applying-a-migration): the failure model in the apply flow +- [Editing a migration](/orm/next/migrations/editing-a-migration): fixing a migration that failed for a data reason +- [The migration graph](/orm/next/migrations/the-migration-graph): why backwards is just another edge diff --git a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx new file mode 100644 index 0000000000..b60f59761c --- /dev/null +++ b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx @@ -0,0 +1,119 @@ +--- +title: The migration graph +description: Migrations form a graph of contract states, not a numbered list. That's what makes history, branching, and parallel work safe. +url: /orm/next/migrations/the-migration-graph +metaTitle: The migration graph in Prisma Next +metaDescription: How Prisma Next models migrations as a graph — contract states as nodes, migrations as edges — and how that enables branching, merging, rollback, and parallel work. +badge: early-access +--- + +Classic migration tools keep migrations in a straight line: a folder of files executed in timestamp order, exactly once. That model breaks in the situations modern teams hit constantly — two branches each add a migration, timestamps interleave, CI fails, and someone spends an afternoon renaming files. + +Prisma Next drops the line. Migrations form a **graph**, and the graph is the one idea that everything else in this section builds on. + +## Nodes are contract states, edges are migrations + +Every time you emit your contract, the result is a deterministic JSON artifact. Hashing it produces an identifier for that exact schema shape — `sha256:705b1a6...` — the way a git commit hash identifies an exact state of your files. + +Those hashes are the **nodes** of the graph. A migration is an **edge**: it records the contract hash it starts `from` and the hash it moves the database `to`. Nothing about a migration's position in history comes from its file name — the timestamps in directory names are for humans, and the linkage lives entirely in the `from`/`to` hashes inside each `migration.json`. + + + +A database always sits at exactly one node: its **marker**, a record Prisma Next keeps in the database itself, holds the hash of the contract it currently matches. Applying a migration means walking one edge and moving the marker. When you run `prisma-next migrate`, the runner finds a path through the graph from the marker to your target and applies each edge along the way. + +You can see the graph of any project without touching a database: + +```bash +npx prisma-next migration graph +``` + +```text +* f9a41d7 (prod) +|-\ +|^| 20260303T1000_merge_alice 93be6c2 -> f9a41d7 1 ops +| |^ 20260303T1100_merge_bob 7e3fa7f -> f9a41d7 1 ops +* | 93be6c2 +|^| 20260302T1000_alice_add_phone 789dd79 -> 93be6c2 1 ops +| * 7e3fa7f +| |^ 20260302T1100_bob_add_avatar 789dd79 -> 7e3fa7f 1 ops +|-/ +* 789dd79 +|^ 20260301T1000_init - -> 789dd79 1 ops +* - + +1 space(s), 5 contract(s), 5 migration(s) +``` + +This project's history is a diamond. Read it bottom-up: from an empty database (`-`), `init` establishes state `789dd79`. Alice and Bob branch from it in parallel — she adds a phone column, he adds an avatar. Then each branch gets a merge migration into the combined state `f9a41d7`, which the `prod` ref points at. + +## What the graph gives you + +**Parallel work without ordering conflicts.** Alice and Bob each planned a migration from `789dd79` on their own branches. In a timestamp-ordered system, whoever merges second gets a broken history. Here, both edges are simply in the graph. A database that followed Alice's branch sits at `93be6c2` and reaches the merged state through `merge_alice`; a database that followed Bob's sits at `7e3fa7f` and takes `merge_bob`. Every environment finds its own path. This matters double when the "two developers" are two AI agents planning migrations concurrently — neither has to know about the other. + +**History you can trust.** Because each edge declares its `from` state, a migration can never silently run against a database in the wrong shape. If the marker doesn't match, the run stops before any SQL executes, with an error naming the mismatch. + +**Rollback as a first-class move.** An edge can point "backwards" — from a later contract state to an earlier one. Rolling back isn't a special mode; it's planning one more migration whose destination is a state you've already been in. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery). + +**More than one shape of history.** The graph accommodates whatever your team's workflow produces: long linear spines, wide fan-outs where many branches leave one node, diamonds that converge again, and even a fast-forward edge that jumps several states in one hop. + +## Refs: naming places in the graph + +Raw hashes are awkward to type, so the graph supports **refs** — named pointers to a node, stored as small files in `migrations/app/refs/`: + +```bash +npx prisma-next ref set prod sha256:f9a41d7... +npx prisma-next ref list +npx prisma-next migrate --to prod +``` + +Refs are committed to your repo, so "where production should be" is a reviewable fact, not tribal knowledge. A ref named `db` has one extra job: `migration plan` uses it as the default starting point when you don't pass `--from`. Keep it advanced with `prisma-next migrate --advance-ref db` and planning stays incremental automatically. + +Alongside refs, two reserved tokens work anywhere a command accepts a contract reference: `@contract` (the contract you most recently emitted) and `@db` (whatever state the connected database's marker holds). + +## Inspecting the graph + +Four read-only commands, each answering a different question: + +| Command | Question it answers | Needs a database? | +| ------------------ | ------------------------------------------------ | ----------------- | +| `migration graph` | What does the whole topology look like? | No | +| `migration list` | Which migration packages exist on disk? | No | +| `migration status` | Where is my database, and what's pending? | Yes | +| `migration log` | What has actually been applied, and when? | Yes | + +`migration graph` also takes `--json` for machine-readable output and `--dot` to render with Graphviz. `migration log` reads a **ledger** the runner appends to on every apply — so even a rollback shows up as history, never as deleted history. + +### Try it on real fixtures + +The Prisma Next repo ships a set of example graphs — a diamond, a wide fan-out, converging branches, rollback chains — as ready-to-render fixtures: + +```bash +git clone https://github.com/prisma/prisma-next +cd prisma-next && pnpm install && pnpm -w build && pnpm install +cd examples/prisma-next-demo +npx prisma-next migration graph --config fixtures/showcase/prisma-next.config.ts --legend +``` + +Swap `showcase` for `diamond`, `wide-fan`, `converging-branches`, `multi-branch`, `long-spine`, or `skip-rollback` to explore each shape. + +## SQL and MongoDB share the graph + +The graph model is family-neutral. On PostgreSQL, the marker lives in a `prisma_contract.marker` table and applies are wrapped in a transaction guarded by an advisory lock. On MongoDB, the marker and ledger live in a `_prisma_migrations` collection updated with compare-and-swap. Same nodes, same edges, same commands. + +:::note[What's early] + +The graph, pathfinding, refs, and the marker/ledger model all work today. What doesn't exist yet: commands to squash a chain of migrations into one, baseline an existing history, or split a large migration — the graph was designed for them, but the commands haven't shipped. If two branch tips are both reachable, `migrate` asks you to pick a target with `--to` rather than guessing. + +::: + +## Prompt your coding agent + +- "Draw the migration graph for this project and explain the branches." +- "Which contract state is the `prod` ref pointing at, and is the database there yet?" +- "Two feature branches both added migrations — check whether they conflict." + +## See also + +- [How migrations work](/orm/next/migrations/how-migrations-work): the plan–review–apply loop +- [Applying a migration](/orm/next/migrations/applying-a-migration): how the runner walks the graph +- [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery): backwards edges in practice diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json index af3453afcd..cd52ef9cd0 100644 --- a/apps/docs/cspell.json +++ b/apps/docs/cspell.json @@ -9,6 +9,13 @@ "Aiven", "amcheck", "amet", + "Graphviz", + "papercut", + "pathfinding", + "postcheck", + "postchecks", + "precheck", + "prechecks", "lanczos", "lanczos3", "EXIF", diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index dfc0f742f5..0a7ae46abd 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -297,6 +297,34 @@ const config = { destination: "/next/add-to-existing-project/:path*", permanent: false, }, + // Prisma 7 migrate docs → Prisma Next migrations (temporary; the + // underlying model changed, so only pages with a clean equivalent + // redirect — see apps/docs/content/docs/orm/next/migrations/). + { + source: "/orm/prisma-migrate", + destination: "/orm/next/migrations/how-migrations-work", + permanent: false, + }, + { + source: "/orm/prisma-migrate/getting-started", + destination: "/orm/next/migrations/how-migrations-work", + permanent: false, + }, + { + source: "/orm/prisma-migrate/understanding-prisma-migrate/mental-model", + destination: "/orm/next/migrations/the-migration-graph", + permanent: false, + }, + { + source: "/orm/prisma-migrate/workflows/development-and-production", + destination: "/orm/next/migrations/applying-a-migration", + permanent: false, + }, + { + source: "/orm/prisma-migrate/workflows/customizing-migrations", + destination: "/orm/next/migrations/editing-a-migration", + permanent: false, + }, ]; }, async rewrites() { diff --git a/apps/docs/src/components/concept-animation/flow-presets.ts b/apps/docs/src/components/concept-animation/flow-presets.ts index 75aa65b67c..302f20ee23 100644 --- a/apps/docs/src/components/concept-animation/flow-presets.ts +++ b/apps/docs/src/components/concept-animation/flow-presets.ts @@ -1,5 +1,3 @@ -import type { ConceptName } from "./presets"; - /** * A flow scene is a fixed box-and-arrow diagram drawn in a viewBox. Every node * and edge is laid out once; each step only chooses which of them are visible @@ -528,4 +526,6 @@ export const FLOW_SCENES = { "compute-model": computeModel, "env-layers": envLayers, "github-connection": githubConnection, -} satisfies Partial>; +} satisfies Record; + +export type FlowName = keyof typeof FLOW_SCENES; diff --git a/apps/docs/src/components/concept-animation/index.tsx b/apps/docs/src/components/concept-animation/index.tsx index f0d3d2c03d..b939984187 100644 --- a/apps/docs/src/components/concept-animation/index.tsx +++ b/apps/docs/src/components/concept-animation/index.tsx @@ -1,4 +1,4 @@ -import { FLOW_SCENES } from "./flow-presets"; +import { FLOW_SCENES, type FlowName } from "./flow-presets"; import { FlowPlayer } from "./flow"; import { ConceptPlayer } from "./player"; import { CONCEPT_PRESETS, type ConceptName, parseStepTokens } from "./presets"; @@ -12,11 +12,13 @@ import { CONCEPT_PRESETS, type ConceptName, parseStepTokens } from "./presets"; * other name falls back to the Code Hike token animation in presets.ts. Either * way the surrounding layout never shifts as you step through. */ -export function ConceptAnimation({ name }: { name: ConceptName }) { - const scene = FLOW_SCENES[name]; +export function ConceptAnimation({ name }: { name: ConceptName | FlowName }) { + const scene = (FLOW_SCENES as Partial>)[name]; if (scene) return ; - const preset = CONCEPT_PRESETS[name]; + const preset = ( + CONCEPT_PRESETS as Partial> + )[name]; if (!preset) throw new Error(`Unknown concept animation: ${String(name)}`); const steps = preset.steps.map((step) => ({ ...parseStepTokens(step.code), diff --git a/apps/docs/src/components/concept-animation/presets.ts b/apps/docs/src/components/concept-animation/presets.ts index 2588ab620c..accd1c5100 100644 --- a/apps/docs/src/components/concept-animation/presets.ts +++ b/apps/docs/src/components/concept-animation/presets.ts @@ -153,6 +153,155 @@ export const CONCEPT_PRESETS = { }, ], }, + "migration-loop": { + label: "The migration loop: change, plan, review, apply", + steps: [ + { + title: "1. Change the contract", + code: + "model User {\n" + + " id Int @id\n" + + " email String\n" + + " [[phone String? ← new field]]\n" + + "}\n" + + "\n" + + "$ [[npx prisma-next contract emit]]", + caption: + "Everything starts in your contract. Add the field, then emit: the schema compiles to contract.json, the artifact every migration command reads.", + }, + { + title: "2. Plan a migration", + code: + "$ [[npx prisma-next migration plan --name add_phone]]\n" + + " │\n" + + " ▼\n" + + "migrations/app/20260707T1005_add_phone/\n" + + "├─ [[migration.ts]] ← TypeScript, yours to edit\n" + + "├─ [[ops.json]] ← compiled SQL steps, what runs\n" + + "└─ migration.json ← from/to contract hashes", + caption: + "The planner diffs the new contract against your migration history and writes a migration package — no database connection needed.", + }, + { + title: "3. Review (and edit)", + code: + "migration.ts: this.addColumn({ table: 'user', ... })\n" + + 'ops.json: ALTER TABLE "user" ADD COLUMN "phone" text\n' + + "\n" + + "# edited? recompile:\n" + + "$ [[node migrations/app/20260707T1005_add_phone/migration.ts]]", + caption: + "Intent and exact SQL sit side by side in the diff. Need a data backfill or a custom step? Edit migration.ts and re-run it to regenerate ops.json.", + }, + { + title: "4. Apply", + code: + "$ [[npx prisma-next migrate]]\n" + + " │\n" + + " ▼\n" + + "✔ Applied 1 migration(s)\n" + + '└─ Add column "phone" to "user"\n' + + " precheck ✓ → execute ✓ → postcheck ✓", + caption: + "migrate walks the pending migrations. Every operation verifies the database before it runs and confirms the result after — so a failed run is safe to retry.", + }, + ], + }, + "migration-graph": { + label: "How migrations form a graph", + steps: [ + { + title: "1. A chain of states", + code: + "* [[9f49f8f]] ← contract hash = a database state\n" + + "|^ add_posts\n" + + "* 705b1a6\n" + + "|^ init\n" + + "* - ← empty database", + caption: + "Every emitted contract hashes to an identifier, like a git commit. Each migration is an edge that moves a database from one state (its `from`) to another (its `to`).", + }, + { + title: "2. Two people branch", + code: + "[[* 93be6c2 * 7e3fa7f]]\n" + + "[[|^ alice_phone |^ bob_avatar]]\n" + + " \\ /\n" + + " * 9f49f8f ← both branched from here\n" + + " |^ add_posts\n" + + " * 705b1a6", + caption: + "Alice and Bob each plan a migration from the same state, on separate git branches. No timestamps to fight over — both edges are simply valid futures of 9f49f8f.", + }, + { + title: "3. The branches merge", + code: + " * [[f9a41d7 (prod)]]\n" + + " / \\\n" + + "[[|^ merge_alice |^ merge_bob]]\n" + + " * 93be6c2 * 7e3fa7f\n" + + " |^ alice_phone |^ bob_avatar\n" + + " \\ /\n" + + " * 9f49f8f", + caption: + "After the git merge, each branch gets a small merge migration into the combined state. A database that followed Alice takes merge_alice; one that followed Bob takes merge_bob. Every environment finds its own path.", + }, + { + title: "4. Databases walk the graph", + code: + "* f9a41d7 [[(prod) @db ← production is here]]\n" + + "|^ merge_alice\n" + + "* 93be6c2 [[← staging is here]]\n" + + "|^ alice_phone\n" + + "* 9f49f8f\n" + + "\n" + + "$ [[npx prisma-next migrate --to prod]]", + caption: + "Each database carries a marker naming its current node. Applying means finding the path from the marker to the target and walking it, edge by edge. Refs like `prod` give important nodes a name.", + }, + ], + }, + "migration-rollback": { + label: "Rollback: a new edge to a state you've been in", + steps: [ + { + title: "1. A change ships", + code: + "* [[e6b5c28 @db ← database is here]]\n" + + "|^ add_display_name\n" + + "* 705b1a6\n" + + "|^ init\n" + + "* -", + caption: + "add_display_name applied cleanly and the marker moved to e6b5c28. Then the team decides the change was wrong.", + }, + { + title: "2. Plan the reverse edge", + code: + "$ [[npx prisma-next migration plan \\]]\n" + + " [[--to add_display_name^ --name rollback]]\n" + + " │\n" + + " ▼\n" + + '└─ Drop column "displayName" [[(destructive)]]\n' + + "⚠ may cause data loss — review before applying", + caption: + '`add_display_name^` means "the state before that migration". The planner writes a real migration that undoes the change — and warns you it\'s destructive. Review it, edit it, commit it.', + }, + { + title: "3. Apply it — history grows", + code: + "$ [[npx prisma-next migrate --to add_display_name^]]\n" + + "\n" + + "* e6b5c28\n" + + "|^ add_display_name\n" + + "[[|v rollback e6b5c28 -> 705b1a6]]\n" + + "* [[705b1a6 @db ← database is back here]]\n" + + "|^ init", + caption: + "The rollback is applied like any other migration: the marker moves back, the ledger records the round trip, and nothing is rewritten. Like git revert, not git reset.", + }, + ], + }, } satisfies Record; export type ConceptName = keyof typeof CONCEPT_PRESETS; From 41e2dd0c47a45090c618f20589e90ba466ed8a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 17:43:41 +0700 Subject: [PATCH 02/15] docs: first review pass on migrations pages Fresh-eyes review as two personas (newcomer, evaluating expert); applied the top findings: define contract/emit/marker/attestation on first use, explain operation classes, de-jargon codecRef, standardize on "migration directory", fix a timestamp inconsistency, and stabilize the db-ref heading anchor. Co-Authored-By: Claude Fable 5 --- .../next/migrations/applying-a-migration.mdx | 2 +- .../next/migrations/editing-a-migration.mdx | 4 ++-- .../migrations/generating-a-migration.mdx | 10 +++++---- .../next/migrations/how-migrations-work.mdx | 22 ++++++++++--------- .../migrations/rollbacks-and-recovery.mdx | 2 +- .../next/migrations/the-migration-graph.mdx | 2 +- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx index 805cba2d42..dfe302009b 100644 --- a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx @@ -87,7 +87,7 @@ npx prisma-next migrate --to 20260707T1005_init --db $DATABASE_URL If the graph has branched and more than one tip is reachable, `migrate` refuses to guess and asks for an explicit `--to`. That's the graph protecting you: two feature branches may both be valid futures, and picking one is a human decision. -`--advance-ref` moves a named ref to the post-apply state in the same step. Advancing one called `db` is what keeps [`migration plan`](/orm/next/migrations/generating-a-migration#skipping---from-the-db-ref) incremental: +`--advance-ref` moves a named ref to the post-apply state in the same step. Advancing one called `db` is what keeps [`migration plan`](/orm/next/migrations/generating-a-migration#the-db-ref-skipping---from) incremental: ```bash npx prisma-next migrate --advance-ref db diff --git a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx index e4ed3d551e..de324c0609 100644 --- a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx @@ -23,7 +23,7 @@ node migrations/app/20260707T1008_add_display_name/migration.ts Wrote ops.json + migration.json to migrations/app/20260707T1008_add_display_name ``` -The recompile also re-attests the package: `migration.json` gets a fresh `migrationHash` covering the compiled output. If someone edits `ops.json` directly — or edits `migration.ts` and forgets to recompile — `migration check` fails in CI with a hash mismatch. Commit `migration.ts` and `ops.json` together, like a lockfile and its manifest. +The recompile also **re-attests** the migration: `migration.json` gets a fresh `migrationHash` fingerprinting the compiled output, so any later hand-edit of `ops.json` is detectable. If someone edits `ops.json` directly — or edits `migration.ts` and forgets to recompile — `migration check` fails in CI with a hash mismatch. Commit `migration.ts` and `ops.json` together, like a lockfile and its manifest. ## Worked example: making a column required @@ -172,7 +172,7 @@ The prechecks and postchecks are optional — but they're what makes a failed ru ## Starting from a blank migration -Sometimes there's no contract change at all — you want a data-only migration, or you'd rather write the whole thing by hand. `migration new` scaffolds an empty, attested package: +Sometimes there's no contract change at all — you want a data-only migration, or you'd rather write the whole thing by hand. `migration new` scaffolds an empty migration directory (already attested, like everything the CLI writes): ```bash npx prisma-next migration new --name backfill_scores diff --git a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx index 894f0cb51e..5f06affe46 100644 --- a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx @@ -1,9 +1,9 @@ --- title: Generating a migration -description: Turn a contract change into a reviewable migration package with prisma-next migration plan. +description: Turn a contract change into a reviewable migration with prisma-next migration plan. url: /orm/next/migrations/generating-a-migration metaTitle: Generating a migration in Prisma Next -metaDescription: A step-by-step tutorial for prisma-next migration plan — from contract change to a planned, reviewable migration package, entirely offline. +metaDescription: A step-by-step tutorial for prisma-next migration plan — from contract change to a planned, reviewable migration, entirely offline. badge: early-access --- @@ -67,7 +67,7 @@ Three things to notice: - **`from: null`** means this migration starts from an empty database — it's the root of your [migration graph](/orm/next/migrations/the-migration-graph). - **`to:` is your contract's hash.** The migration promises to deliver a database matching exactly the contract you just emitted. -The planned package contains the TypeScript source, the compiled operations, and the contract snapshots: +The planned migration directory contains the TypeScript source, the compiled operations, and the contract snapshots: ```text migrations/app/20260707T1005_init/ @@ -107,6 +107,8 @@ export default class M extends Migration { MigrationCLI.run(import.meta.url, M); ``` +Two notes on reading it: the `col(...)` calls mirror your contract fields, and you never write `codecRef` by hand — the planner fills it in (it pins how values convert between TypeScript and Postgres), so treat it as noise when reviewing. + For a simple change you don't need to touch this file. When you do — to add a data backfill, reorder operations, or drop in raw SQL — that's [Editing a migration](/orm/next/migrations/editing-a-migration). ## The second migration: planning a delta @@ -148,7 +150,7 @@ ALTER TABLE "public"."user" ADD COLUMN "phone" text; `--from` accepts a migration directory name (as here), a contract hash or unambiguous prefix, a ref name, or `^` meaning "the state *before* that migration". -### Skipping `--from`: the `db` ref +### The db ref: skipping --from Passing `--from` every time gets old. The planner's default is smarter: if a ref named **`db`** exists, planning starts from whatever it points at. Keep it advanced as part of applying: diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index 30b4755759..0bd3e6e934 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -3,18 +3,18 @@ title: How migrations work description: Change your contract, plan a migration, review it, apply it. Every step is checked before and after it runs. url: /orm/next/migrations/how-migrations-work metaTitle: How migrations work in Prisma Next -metaDescription: What a Prisma Next migration is, the plan-review-apply loop, the files inside a migration package, and why every operation is safe to retry. +metaDescription: What a Prisma Next migration is, the plan-review-apply loop, the files inside a migration directory, and why every operation is safe to retry. badge: early-access --- -A migration is how Prisma Next changes your database when your contract changes. You add a field to a model, and something has to run `ALTER TABLE` against every database that serves your app: your laptop, staging, production. Migrations are that something, recorded as files in your repo so the change is reviewable, repeatable, and versioned with your code. +A migration is how Prisma Next changes your database when your contract changes. Your **contract** is your schema — the `.prisma` file (or TypeScript definition) that declares your models, plus the `contract.json` artifact it compiles to. You add a field to a model, and something has to run `ALTER TABLE` against every database that serves your app: your laptop, staging, production. Migrations are that something, recorded as files in your repo so the change is reviewable, repeatable, and versioned with your code. The workflow is a loop you will run many times a day in development: -1. **Change your contract**: edit your `.prisma` file (or TypeScript contract) and run `prisma-next contract emit`. -2. **Plan a migration**: `prisma-next migration plan` compares the new contract against your migration history and writes a migration package to `migrations/`. +1. **Change your contract**: edit your `.prisma` file, then run `prisma-next contract emit` — this compiles the schema into the `contract.json` that every other command reads. +2. **Plan a migration**: `prisma-next migration plan` compares the new contract against your migration history and writes a migration directory under `migrations/`. 3. **Review it**: read the generated TypeScript and the DDL preview. Edit the migration if the change needs a data step, then re-run the file to recompile it. 4. **Apply it**: `prisma-next migrate` runs the pending migrations against your database. @@ -33,7 +33,7 @@ A migration is a directory under `migrations/app/`, named with a timestamp and a ```text migrations/ └── app/ - └── 20260707T1005_add_user_phone/ + └── 20260707T1006_add_user_phone/ ├── migration.ts # the migration, in TypeScript — this is what you edit ├── ops.json # the compiled operations — this is what runs ├── migration.json # metadata: from/to contract hashes @@ -50,21 +50,23 @@ Both files are committed side by side, like `package.json` and `package-lock.jso `migration.json` records where the migration fits in history. Every contract compiles to a deterministic JSON artifact, and hashing it gives a short identifier for that exact database shape — like a git commit hash for your schema. A migration records the hash it starts **from** and the hash it moves the database **to**: -```json title="migrations/app/20260707T1005_add_user_phone/migration.json" +```json title="migrations/app/20260707T1006_add_user_phone/migration.json" { "from": "sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5", "to": "sha256:925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72", "providedInvariants": [], - "createdAt": "2026-07-07T10:05:55.937Z", + "createdAt": "2026-07-07T10:06:55.937Z", "migrationHash": "sha256:4b57fa2141c8ad94476d5de66c451468fd6c864210481ad00ac89b259491fbcf" } ``` +(`migrationHash` fingerprints the compiled migration itself so tampering is detectable; `providedInvariants` is used by extension migrations and stays empty for typical app migrations.) + These `from`/`to` hashes are what link migrations into a [graph](/orm/next/migrations/the-migration-graph) instead of a numbered list — that's the next page. ## Every operation checks itself -Inside `ops.json`, each operation has three parts: a **precheck** that confirms the database is in the expected state, the **execute** statements that make the change, and a **postcheck** that confirms the change worked. +Inside `ops.json`, each operation has three parts: a **precheck** that confirms the database is in the expected state, the **execute** statements that make the change, and a **postcheck** that confirms the change worked. Each operation also carries a class — **additive** (safe, like adding a column), **destructive** (can lose data, like dropping one), or **data** (changes rows, not structure) — which is what drives the `(destructive)` flags and data-loss warnings you'll see in CLI output. ```json title="One operation from ops.json" { @@ -121,11 +123,11 @@ Two commands talk to a database: | `migration status` | Show where the database is in the graph and which migrations are pending | | `migration log` | Show the history of applied migrations from the database's ledger | -Note that applying is `prisma-next migrate`, not `migration apply` — `migration ...` commands manage the on-disk packages, `migrate` moves a database. +Note that applying is `prisma-next migrate`, not `migration apply` — `migration ...` commands manage the on-disk migration directories, `migrate` moves a database. ## The same model for SQL and MongoDB -Everything on this page applies to both database families. On PostgreSQL, operations compile to SQL DDL and the applied state is tracked in a marker table. On MongoDB, operations create collections, indexes, and JSON Schema validators, and state is tracked in a `_prisma_migrations` collection. The commands, the file layout, the graph, and the precheck/execute/postcheck structure are identical. +Everything on this page applies to both database families. On PostgreSQL, operations compile to SQL DDL and the applied state is tracked in a **marker** — a record Prisma Next keeps in the database itself, naming the contract state the database currently matches. On MongoDB, operations create collections, indexes, and JSON Schema validators, and the marker lives in a `_prisma_migrations` collection. The commands, the file layout, the graph, and the precheck/execute/postcheck structure are identical. :::note[Migrations are early] diff --git a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx index 6ee1ee0f30..966c174b3b 100644 --- a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx +++ b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx @@ -43,7 +43,7 @@ DDL preview ALTER TABLE "public"."user" DROP COLUMN "displayName"; ``` -The planner diffs the two contract states and writes the operations that undo the change — flagged **destructive**, because they are. This is a real migration package: review it, edit it (for example, to archive the column's data into another table before the `DROP`), commit it. Then apply it like any other migration: +The planner diffs the two contract states and writes the operations that undo the change — flagged **destructive**, because they are. This is a real migration: review it, edit it (for example, to archive the column's data into another table before the `DROP`), commit it. Then apply it like any other migration: ```bash npx prisma-next migrate --to 20260707T1008_add_display_name^ diff --git a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx index b60f59761c..2f944248c9 100644 --- a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx +++ b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx @@ -77,7 +77,7 @@ Four read-only commands, each answering a different question: | Command | Question it answers | Needs a database? | | ------------------ | ------------------------------------------------ | ----------------- | | `migration graph` | What does the whole topology look like? | No | -| `migration list` | Which migration packages exist on disk? | No | +| `migration list` | Which migration directories exist on disk? | No | | `migration status` | Where is my database, and what's pending? | Yes | | `migration log` | What has actually been applied, and when? | Yes | From e1f14f3fd6c19c8223644dc7f452b9dffebdd322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 18:04:24 +0700 Subject: [PATCH 03/15] docs: verification pass and full review of migrations pages Verified every runnable example against prisma-next origin/main (remaining fixtures, ref list, migrate --show, hash-prefix targeting, db verify, migration new, the rawSql sample compiled in a real scaffold). Two fixes from verification: softened the db verify claim to match its actual output, and added the real migrate --show path preview. Full review per house docs style: removed prose em dashes and filler, standardized terminology, added a MongoDB data-transform section with a link to the working retail-store example, moved the wiring-ceremony note next to the code it excuses, consolidated CI guidance (commit both files, check exit codes), and linked the db-ref explanation to one canonical home. Co-Authored-By: Claude Fable 5 --- .../next/migrations/applying-a-migration.mdx | 41 +++++++++----- .../next/migrations/editing-a-migration.mdx | 55 ++++++++++++++----- .../migrations/generating-a-migration.mdx | 28 +++++----- .../next/migrations/how-migrations-work.mdx | 29 +++++----- .../migrations/rollbacks-and-recovery.mdx | 32 +++++------ .../next/migrations/the-migration-graph.mdx | 24 ++++---- .../components/concept-animation/presets.ts | 10 ++-- 7 files changed, 129 insertions(+), 90 deletions(-) diff --git a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx index dfe302009b..f88d6dc500 100644 --- a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx @@ -1,9 +1,9 @@ --- title: Applying a migration -description: prisma-next migrate walks the graph from where your database is to where you want it — with a preview, a checkpoint at every step, and safe retries. +description: prisma-next migrate walks the graph from where your database is to where you want it, with a preview, a checkpoint at every step, and safe retries. url: /orm/next/migrations/applying-a-migration metaTitle: Applying a migration in Prisma Next -metaDescription: How prisma-next migrate applies migrations — targeting refs and hashes, previewing with --show, checking status, and running safely in development and production. +metaDescription: How prisma-next migrate applies migrations, from targeting refs and hashes and previewing with --show to checking status and running safely in development and production. badge: early-access --- @@ -13,7 +13,7 @@ Applying is the one step that touches a database, and it has one command: npx prisma-next migrate ``` -Note the verb: it's `migrate`, not `migration apply`. The `migration ...` subcommands manage files on disk; `migrate` moves a database. It reads where the database currently is (its **marker**), finds a path through the [migration graph](/orm/next/migrations/the-migration-graph) to the target, and applies each migration along the path — running every operation's precheck, execute, and postcheck as it goes. +Note the verb: it's `migrate`, not `migration apply`. The `migration ...` subcommands manage files on disk; `migrate` moves a database. It reads where the database currently is (its **marker**), finds a path through the [migration graph](/orm/next/migrations/the-migration-graph) to the target, and applies each migration along the path, running every operation's precheck, execute, and postcheck as it goes. The connection comes from `db.connection` in `prisma-next.config.ts`, or `--db` to override: @@ -37,7 +37,7 @@ Next: prisma-next migration status ## Check before, preview, then apply -The habit worth building — especially against shared databases — is a three-step rhythm: +The habit worth building, especially against shared databases, is a three-step rhythm: ```bash # 1. Where is the database, what's pending? @@ -64,9 +64,20 @@ npx prisma-next migrate --db $DATABASE_URL Read the markers on the right: `@db` is where the database is, `@contract` is where your emitted contract is, and `(db)` is the [ref](/orm/next/migrations/the-migration-graph#refs-naming-places-in-the-graph) of that name pointing at the same node. -`migrate --show` is the read-only dry run: it prints the migrations that would execute and stops. Nothing touches the database. +`migrate --show` is the read-only dry run: it draws the path from the database's position to the target and stops. Nothing touches the database: -After applying, `migration log` shows the database's own record of what ran — an append-only **ledger** the runner writes alongside the marker: +```text +│↑ 20260707T1006_add_user_phone 705b1a6 → 925198f ↑ will run +○ 705b1a6 +│↑ 20260707T1005_init ∅ → 705b1a6 ↑ will run +○ ∅ @db + +The following 2 migrations will run: + 20260707T1005_init ∅ → 705b1a6 + 20260707T1006_add_user_phone 705b1a6 → 925198f +``` + +After applying, `migration log` shows the database's own record of what ran: an append-only **ledger** the runner writes alongside the marker: ```text Applied at Migration Change Ops @@ -77,7 +88,7 @@ After applying, `migration log` shows the database's own record of what ran — ## Choosing a target -With no `--to`, `migrate` advances toward your emitted contract. To aim somewhere specific, `--to` accepts the same reference grammar as everywhere else — a ref name, a contract hash or prefix, a migration directory name, or `^` for the state before a migration: +With no `--to`, `migrate` advances toward your emitted contract. To aim somewhere specific, `--to` accepts the same reference grammar as everywhere else: a ref name, a contract hash or prefix, a migration directory name, or `^` for the state before a migration: ```bash npx prisma-next migrate --to prod --db $DATABASE_URL # a ref @@ -105,9 +116,9 @@ The runner stops at the first failing operation and tells you which one, why, an Three properties make failure boring instead of terrifying: -- **On PostgreSQL, a failed run leaves nothing behind.** The entire `migrate` run executes inside one transaction, so when an operation fails, everything from that run rolls back and the database is exactly where it was before you started. Migrations applied in *earlier* runs are untouched — that's what "previously applied migrations are preserved" means. -- **The error is specific.** It names the operation, the phase (precheck, execute, or postcheck), and the check that failed — enough to fix the cause without spelunking. -- **Re-running is safe.** Operations are idempotent: before running one, the runner evaluates its postcheck and skips it if the database already satisfies it. A change that snuck in out-of-band doesn't break the run — it just becomes a skip. On MongoDB, where cross-collection transactions don't exist, this same mechanism is what makes a partially-applied run converge on retry. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery) for the full failure playbook. +- **On PostgreSQL, a failed run leaves nothing behind.** The entire `migrate` run executes inside one transaction, so when an operation fails, everything from that run rolls back and the database is exactly where it was before you started. Migrations applied in *earlier* runs are untouched; that's what "previously applied migrations are preserved" means. +- **The error is specific.** It names the operation, the phase (precheck, execute, or postcheck), and the check that failed, which is enough to fix the cause without spelunking. +- **Re-running is safe.** Operations are idempotent: before running one, the runner evaluates its postcheck and skips it if the database already satisfies it. A change that snuck in out-of-band doesn't break the run; it becomes a skip. On MongoDB, where cross-collection transactions don't exist, this same mechanism is what makes a partially-applied run converge on retry. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery) for the full failure playbook. Before running any DDL, `migrate` also verifies the database's marker is a state the graph knows. A database that was changed outside of migrations fails fast with a marker mismatch instead of getting SQL applied on top of unknown drift. @@ -121,7 +132,7 @@ The commands are the same everywhere; what changes is where the files come from npx prisma-next migration plan --name my_change && npx prisma-next migrate --advance-ref db ``` -**In CI and production**, migrations arrive via your repo, already planned, reviewed, and merged. The deploy step is: +**In CI and production**, migrations arrive via your repo, already planned, reviewed, and merged. That's why the [editing rule](/orm/next/migrations/editing-a-migration) matters: commit `migration.ts` and `ops.json` together, and let `migration check` (exit codes `0`/`2`/`4`, see [Generating a migration](/orm/next/migrations/generating-a-migration#reviewing-what-you-planned)) catch a stale recompile before deploy. The deploy step is: ```bash npx prisma-next migration check # files intact, graph well-formed (offline) @@ -129,13 +140,13 @@ npx prisma-next migrate --show --db $DATABASE_URL # log what's about to run npx prisma-next migrate --db $DATABASE_URL ``` -A production nicety that falls out of the design: the runner executes only `ops.json` — plain data. Your `migration.ts` files, and any TypeScript they import, are never executed with production credentials. +A production nicety that falls out of the design: the runner executes only `ops.json`, plain data. Your `migration.ts` files, and any TypeScript they import, are never executed with production credentials. -Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transaction guarded by an advisory lock, so two `migrate` runs serialize instead of interleaving. On MongoDB — where cross-collection DDL transactions don't exist — each migration advances the marker with compare-and-swap and the runner verifies the resulting schema before committing the marker, so a re-run converges rather than double-applying. +Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transaction guarded by an advisory lock, so two `migrate` runs serialize instead of interleaving. On MongoDB, where cross-collection DDL transactions don't exist, each migration advances the marker with compare-and-swap and the runner verifies the resulting schema before committing the marker, so a re-run converges rather than double-applying. ## Extension spaces -If your project uses database extensions (say pgvector), you'll see more than one *contract space* in the output: extensions ship their own migrations (for example `CREATE EXTENSION vector`), tracked in `migrations//` next to your app's. One `migrate` run walks them all — extensions first, then your app — and reports each space separately: +If your project uses database extensions (say pgvector), you'll see more than one *contract space* in the output: extensions ship their own migrations (for example `CREATE EXTENSION vector`), tracked in `migrations//` next to your app's. One `migrate` run walks them all (extensions first, then your app) and reports each space separately: ```text ✔ Applied 2 migration(s) (20 operation(s)) across 2 contract space(s) @@ -150,7 +161,7 @@ App space :::note[What's early] -Apply, targeting, preview, refs, the ledger, and multi-space runs all work today. Not built yet: a shadow-database rehearsal (`migrate` runs against the real target; use `--show` and a staging database), and an apply-time check that `ops.json` still matches `migration.ts` (today that's `migration check`'s job — run it in CI). +Apply, targeting, preview, refs, the ledger, and multi-space runs all work today. Not built yet: a shadow-database rehearsal (`migrate` runs against the real target; use `--show` and a staging database), and an apply-time check that `ops.json` still matches `migration.ts` (today that's `migration check`'s job; run it in CI). ::: diff --git a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx index de324c0609..f106a1a390 100644 --- a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx @@ -1,13 +1,13 @@ --- title: Editing a migration -description: A migration is TypeScript you own. Fill in backfills, reorder steps, or drop to raw SQL — then recompile it with one command. +description: A migration is TypeScript you own. Fill in backfills, reorder steps, or drop to raw SQL, then recompile it with one command. url: /orm/next/migrations/editing-a-migration metaTitle: Editing a migration in Prisma Next -metaDescription: How to edit a Prisma Next migration.ts — fill placeholder data transforms, write typed backfills, use rawSql, and recompile ops.json with node migration.ts. +metaDescription: How to edit a Prisma Next migration.ts by filling placeholder data transforms, writing typed backfills, using rawSql, and recompiling ops.json with node migration.ts. badge: early-access --- -The planner writes a good first draft, but plenty of real migrations need a human (or agent) decision: what to put in existing rows when a column becomes required, which order two steps must run in, one statement the planner has no factory for. In Prisma Next you make those changes by editing `migration.ts` — ordinary TypeScript with autocomplete and type checking — and recompiling it. You never hand-edit SQL files, and you never hand-edit `ops.json`. +The planner writes a good first draft, but plenty of real migrations need a human (or agent) decision: what to put in existing rows when a column becomes required, which order two steps must run in, one statement the planner has no factory for. In Prisma Next you make those changes by editing `migration.ts`, ordinary TypeScript with autocomplete and type checking, and recompiling it. You never hand-edit SQL files, and you never hand-edit `ops.json`. The rule that makes this safe: @@ -23,7 +23,7 @@ node migrations/app/20260707T1008_add_display_name/migration.ts Wrote ops.json + migration.json to migrations/app/20260707T1008_add_display_name ``` -The recompile also **re-attests** the migration: `migration.json` gets a fresh `migrationHash` fingerprinting the compiled output, so any later hand-edit of `ops.json` is detectable. If someone edits `ops.json` directly — or edits `migration.ts` and forgets to recompile — `migration check` fails in CI with a hash mismatch. Commit `migration.ts` and `ops.json` together, like a lockfile and its manifest. +The recompile also **re-attests** the migration: `migration.json` gets a fresh `migrationHash` fingerprinting the compiled output, so any later hand-edit of `ops.json` is detectable. If someone edits `ops.json` directly, or edits `migration.ts` and forgets to recompile, `migration check` fails in CI with a hash mismatch. Commit `migration.ts` and `ops.json` together, like a lockfile and its manifest. ## Worked example: making a column required @@ -39,7 +39,7 @@ npx prisma-next migration plan --name add_display_name Open migration.ts and replace each `placeholder(...)` call with your actual query. ``` -The planner knew the shape of the fix — add the column nullable, backfill, then tighten — and scaffolded exactly that, leaving the backfill query to you: +The planner knew the shape of the fix (add the column nullable, backfill, then tighten) and scaffolded exactly that, leaving the backfill query to you: ```ts title="migration.ts (as planned)" override get operations() { @@ -58,7 +58,7 @@ override get operations() { } ``` -A `dataTransform` takes two closures. `check` asks "are there rows that still need this?" — it must be a row-returning query where *any row* means work remains (the conventional shape is `select('id').where().limit(1)`). `run` performs the change. Fill them with the typed SQL query builder, wired to the contract snapshot sitting next to the migration: +A `dataTransform` takes two closures. `check` asks "are there rows that still need this?" It must be a row-returning query where *any row* means work remains; the conventional shape is `select('id').where().limit(1)`. `run` performs the change. Fill them with the typed SQL query builder, wired to the contract snapshot sitting next to the migration: ```ts title="migration.ts (filled in)" import type { Contract as End } from './end-contract'; @@ -112,9 +112,11 @@ MigrationCLI.run(import.meta.url, M); Two details worth pausing on: -- **The types come from the migration's own contract snapshot** (`./end-contract`), not your live app contract. You're type-checking against the schema *as it will exist when this step runs* — which is why the builder happily references `displayName` even though the column doesn't exist yet, and why a migration written months ago keeps compiling after your contract moves on. +- **The types come from the migration's own contract snapshot** (`./end-contract`), not your live app contract. You're type-checking against the schema *as it will exist when this step runs*. That's why the builder happily references `displayName` even though the column doesn't exist yet, and why a migration written months ago keeps compiling after your contract moves on. - **The query is real application-grade TypeScript.** You can import shared constants, and typos in column names fail the type check instead of failing in production. +The wiring block at the top (deserializing the contract, building the `db` handle) is the current Early Access shape; expect it to shrink to a one-liner. It's the price of a query builder that's typed against this migration's snapshot instead of your live app client. + Recompile with `node migration.ts` and inspect what the backfill became: ```json title="ops.json (the compiled dataTransform)" @@ -146,11 +148,11 @@ Recompile with `node migration.ts` and inspect what the backfill became: } ``` -Your `check` closure became both the precheck (`EXISTS` — is there work?) and the postcheck (`NOT EXISTS` — is it done?). Your `run` became a parameterized `UPDATE` — note `"Anonymous"` travels in `params`, through the driver's parameter binder, never spliced into SQL text. The reviewer reading your PR sees intent in `migration.ts` and the exact statements in `ops.json`, side by side. +Your `check` closure became both the precheck (`EXISTS`: is there work?) and the postcheck (`NOT EXISTS`: is it done?). Your `run` became a parameterized `UPDATE`; note that `"Anonymous"` travels in `params`, through the driver's parameter binder, never spliced into SQL text. The reviewer reading your PR sees intent in `migration.ts` and the exact statements in `ops.json`, side by side. ## Escape hatch: raw SQL -For anything the operation factories don't cover — enabling an extension, `CREATE INDEX CONCURRENTLY`, a vendor-specific statement — use `rawSql`, and keep the same three-phase safety if you can: +For anything the operation factories don't cover (enabling an extension, `CREATE INDEX CONCURRENTLY`, a vendor-specific statement), use `rawSql`, and keep the same three-phase safety if you can: ```ts rawSql({ @@ -168,11 +170,36 @@ rawSql({ }), ``` -The prechecks and postchecks are optional — but they're what makes a failed run resumable and a mistake diagnosable, so skipping them trades away most of what this system gives you. If you write the same `rawSql` twice, lift it into a function; the built-in factories are plain functions doing exactly this. +The prechecks and postchecks are optional, but they're what makes a failed run resumable and a mistake diagnosable, so skipping them trades away most of what this system gives you. If you write the same `rawSql` twice, lift it into a function; the built-in factories are plain functions doing exactly this. + +## The same pattern on MongoDB + +Data transforms work on MongoDB with the same `check`/`run` shape and the same compilation to precheck/execute/postcheck. Hand-authored Mongo transforms currently build their queries from raw Mongo command shapes rather than the full typed builder: + +```ts title="migration.ts (MongoDB, excerpt)" +import { dataTransform, setValidation } from '@prisma-next/target-mongo/migration'; + +override get operations() { + const storageHash = this.endContract.storage.storageHash; + const productsValidator = this.endContract.collection.products.validator; + return [ + setValidation('products', productsValidator.jsonSchema, { + validationLevel: productsValidator.validationLevel, + validationAction: productsValidator.validationAction, + }), + dataTransform('backfill-product-status', { + check: { source: () => existingProductsWithoutStatus(storageHash) }, + run: () => backfillRun(storageHash), + }), + ]; +} +``` + +The full working migration, including the two query-plan helpers, is in the Prisma Next repo's [retail-store example](https://github.com/prisma/prisma-next/blob/main/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts). ## Starting from a blank migration -Sometimes there's no contract change at all — you want a data-only migration, or you'd rather write the whole thing by hand. `migration new` scaffolds an empty migration directory (already attested, like everything the CLI writes): +Sometimes there's no contract change at all: you want a data-only migration, or you'd rather write the whole thing by hand. `migration new` scaffolds an empty migration directory (already attested, like everything the CLI writes): ```bash npx prisma-next migration new --name backfill_scores @@ -182,15 +209,15 @@ Write your operations in the generated `migration.ts`, then compile it the same ## Editing checklist -1. Edit `migration.ts` — never `ops.json`. +1. Edit `migration.ts`, never `ops.json`. 2. Recompile: `node /migration.ts`. -3. Review the diff of `ops.json` — that's what will run. +3. Review the diff of `ops.json`; that's what will run. 4. Verify: `npx prisma-next migration check`. 5. Commit `migration.ts`, `ops.json`, and `migration.json` together. :::note[What's early] -The typed-builder wiring at the top of the filled example (deserializing the contract, building the `db` handle) is more ceremony than we want; expect it to shrink. On MongoDB, hand-authored data transforms currently use raw Mongo command shapes rather than the full typed builder. `rawSql` and the scaffolded placeholder flow shown here work today, end to end. +The typed-builder wiring and the raw Mongo command shapes above are the current Early Access surface and will get slimmer. Everything shown on this page (the scaffolded placeholder flow, the typed backfill, `rawSql`, the recompile loop) works today, end to end. ::: diff --git a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx index 5f06affe46..888493433f 100644 --- a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx @@ -3,11 +3,11 @@ title: Generating a migration description: Turn a contract change into a reviewable migration with prisma-next migration plan. url: /orm/next/migrations/generating-a-migration metaTitle: Generating a migration in Prisma Next -metaDescription: A step-by-step tutorial for prisma-next migration plan — from contract change to a planned, reviewable migration, entirely offline. +metaDescription: A step-by-step tutorial for prisma-next migration plan, from contract change to a planned, reviewable migration, entirely offline. badge: early-access --- -This tutorial takes a contract change from your editor to a planned migration on disk. Planning is **fully offline**: `migration plan` reads your emitted contract and your existing migrations — it never connects to a database, so you can plan on a plane, in CI, or in a sandbox with no credentials. +This tutorial takes a contract change from your editor to a planned migration on disk. Planning is **fully offline**: `migration plan` reads your emitted contract and your existing migrations; it never connects to a database, so you can plan on a plane, in CI, or in a sandbox with no credentials. We'll start from a minimal project. If you don't have one, `npx create-prisma@next` scaffolds it. @@ -25,13 +25,13 @@ model User { } ``` -First, emit the contract — this compiles the schema into the `contract.json` artifact that every other command reads: +First, emit the contract. This compiles the schema into the `contract.json` artifact that every other command reads: ```bash npx prisma-next contract emit ``` -Now plan. `--name` sets the human-readable part of the directory name; without it the directory is just called `migration`: +Now plan. `--name` sets the human-readable part of the directory name; without it the directory is called `migration`: ```bash npx prisma-next migration plan --name init @@ -64,7 +64,7 @@ CREATE TABLE "public"."user" ( Three things to notice: - **The DDL preview is right there.** You see the exact SQL before anything exists but files. -- **`from: null`** means this migration starts from an empty database — it's the root of your [migration graph](/orm/next/migrations/the-migration-graph). +- **`from: null`** means this migration starts from an empty database; it's the root of your [migration graph](/orm/next/migrations/the-migration-graph). - **`to:` is your contract's hash.** The migration promises to deliver a database matching exactly the contract you just emitted. The planned migration directory contains the TypeScript source, the compiled operations, and the contract snapshots: @@ -77,7 +77,7 @@ migrations/app/20260707T1005_init/ └── end-contract.json (+ end-contract.d.ts) ``` -Open `migration.ts` — it reads like a description of the change, because it is one: +Open `migration.ts`. It reads like a description of the change, because it is one: ```ts title="migrations/app/20260707T1005_init/migration.ts" import type { Contract as End } from './end-contract'; @@ -107,13 +107,13 @@ export default class M extends Migration { MigrationCLI.run(import.meta.url, M); ``` -Two notes on reading it: the `col(...)` calls mirror your contract fields, and you never write `codecRef` by hand — the planner fills it in (it pins how values convert between TypeScript and Postgres), so treat it as noise when reviewing. +Two notes on reading it: the `col(...)` calls mirror your contract fields, and you never write `codecRef` by hand. The planner fills it in to pin how values convert between TypeScript and Postgres; treat it as noise when reviewing. -For a simple change you don't need to touch this file. When you do — to add a data backfill, reorder operations, or drop in raw SQL — that's [Editing a migration](/orm/next/migrations/editing-a-migration). +For a simple change you don't need to touch this file. When you do (to add a data backfill, reorder operations, or drop in raw SQL), that's [Editing a migration](/orm/next/migrations/editing-a-migration). ## The second migration: planning a delta -Apply the first migration (covered properly in [Applying a migration](/orm/next/migrations/applying-a-migration)), then make another change — add a `phone` field: +Apply the first migration (covered properly in [Applying a migration](/orm/next/migrations/applying-a-migration)), then make another change: add a `phone` field: ```prisma model User { @@ -152,7 +152,7 @@ ALTER TABLE "public"."user" ADD COLUMN "phone" text; ### The db ref: skipping --from -Passing `--from` every time gets old. The planner's default is smarter: if a ref named **`db`** exists, planning starts from whatever it points at. Keep it advanced as part of applying: +Passing `--from` every time gets old. The planner's default is smarter: if a [ref](/orm/next/migrations/the-migration-graph#refs-naming-places-in-the-graph) named **`db`** exists, planning starts from whatever it points at. Keep it advanced as part of applying: ```bash npx prisma-next migrate --advance-ref db @@ -161,13 +161,13 @@ npx prisma-next migration plan --name next_change # starts from the db ref aut :::warning[Without `--from` or a `db` ref, plans start from empty] -If there's no `db` ref and you omit `--from`, `migration plan` plans from an **empty database** — a full `CREATE`-everything migration, not a delta. If you expected a one-column change and got a wall of `CREATE TABLE`, this is why. Pass `--from` or set up the `db` ref. +If there's no `db` ref and you omit `--from`, `migration plan` plans from an **empty database**: a full `CREATE`-everything migration, not a delta. If you expected a one-column change and got a wall of `CREATE TABLE`, this is why. Pass `--from` or set up the `db` ref. ::: ## When the planner needs your input -Some changes can't be planned from the schema diff alone. Add a **required** field to a table that already has rows, and the planner can write the `ADD COLUMN` and the `SET NOT NULL` — but only you know what the existing rows should contain. Rather than guessing, it scaffolds the decision as a **placeholder**: +Some changes can't be planned from the schema diff alone. Add a **required** field to a table that already has rows, and the planner can write the `ADD COLUMN` and the `SET NOT NULL`, but only you know what the existing rows should contain. Rather than guessing, it scaffolds the decision as a **placeholder**: ```text ⚠ Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit @@ -193,11 +193,11 @@ npx prisma-next migration graph npx prisma-next migration check ``` -`migration check` is designed for CI: exit code `0` means clean, `2` means it couldn't resolve what you asked for, `4` means an integrity failure — for example, someone hand-edited `ops.json` without re-running `migration.ts`. +`migration check` is designed for CI: exit code `0` means clean, `2` means it couldn't resolve what you asked for, `4` means an integrity failure, for example someone hand-edited `ops.json` without re-running `migration.ts`. :::note[What's early] -Planning covers tables, columns, indexes, constraints, and the backfill scaffold shown above. Rename inference is not built yet: renaming a field plans as **drop column + add column** — flagged destructive, with a data-loss warning — so for a true rename, edit the migration and replace the pair with a `rawSql` `ALTER TABLE ... RENAME COLUMN`. There is also no interactive mode; the planner writes its best answer and leaves refinement to you in `migration.ts`. +Planning covers tables, columns, indexes, constraints, and the backfill scaffold shown above. Rename inference is not built yet: renaming a field plans as **drop column + add column**, flagged destructive with a data-loss warning. For a true rename, edit the migration and replace the pair with a `rawSql` `ALTER TABLE ... RENAME COLUMN`. There is also no interactive mode; the planner writes its best answer and leaves refinement to you in `migration.ts`. ::: diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index 0bd3e6e934..a114c66d71 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -7,13 +7,13 @@ metaDescription: What a Prisma Next migration is, the plan-review-apply loop, th badge: early-access --- -A migration is how Prisma Next changes your database when your contract changes. Your **contract** is your schema — the `.prisma` file (or TypeScript definition) that declares your models, plus the `contract.json` artifact it compiles to. You add a field to a model, and something has to run `ALTER TABLE` against every database that serves your app: your laptop, staging, production. Migrations are that something, recorded as files in your repo so the change is reviewable, repeatable, and versioned with your code. +A migration is how Prisma Next changes your database when your contract changes. Your **contract** is your schema: the `.prisma` file (or TypeScript definition) that declares your models, plus the `contract.json` artifact it compiles to. You add a field to a model, and something has to run `ALTER TABLE` against every database that serves your app: your laptop, staging, production. Migrations are that something, recorded as files in your repo so the change is reviewable, repeatable, and versioned with your code. The workflow is a loop you will run many times a day in development: -1. **Change your contract**: edit your `.prisma` file, then run `prisma-next contract emit` — this compiles the schema into the `contract.json` that every other command reads. +1. **Change your contract**: edit your `.prisma` file, then run `prisma-next contract emit`, which compiles the schema into the `contract.json` that every other command reads. 2. **Plan a migration**: `prisma-next migration plan` compares the new contract against your migration history and writes a migration directory under `migrations/`. 3. **Review it**: read the generated TypeScript and the DDL preview. Edit the migration if the change needs a data step, then re-run the file to recompile it. 4. **Apply it**: `prisma-next migrate` runs the pending migrations against your database. @@ -24,7 +24,7 @@ npx prisma-next migration plan --name add_user_phone npx prisma-next migrate ``` -Each page in this section walks through one part of the loop in detail. This page covers the model: what a migration actually is, and why it looks the way it does. +Each page in this section walks through one part of the loop in detail. This page covers the model: what a migration is, and why it looks the way it does. ## What a migration is @@ -44,11 +44,11 @@ migrations/ Two files do the real work: - **`migration.ts`** is the authoring surface. It lists the migration's operations as plain function calls: `this.addColumn(...)`, `this.createTable(...)`, `this.dataTransform(...)`. It is ordinary TypeScript, type-checked against the contract snapshots sitting next to it, so your editor autocompletes column names and catches typos before anything touches a database. See [Editing a migration](/orm/next/migrations/editing-a-migration). -- **`ops.json`** is what the runner executes. When you run `migration.ts` (the planner does this for you; after an edit you re-run it with `node migration.ts`), it compiles the operations to JSON. Production never executes your TypeScript — the runner only reads `ops.json`, so no application code runs with production credentials. +- **`ops.json`** is what the runner executes. When you run `migration.ts` (the planner does this for you; after an edit you re-run it with `node migration.ts`), it compiles the operations to JSON. Production never executes your TypeScript: the runner only reads `ops.json`, so no application code runs with production credentials. Both files are committed side by side, like `package.json` and `package-lock.json`: one you edit, one records exactly what will happen. -`migration.json` records where the migration fits in history. Every contract compiles to a deterministic JSON artifact, and hashing it gives a short identifier for that exact database shape — like a git commit hash for your schema. A migration records the hash it starts **from** and the hash it moves the database **to**: +`migration.json` records where the migration fits in history. Every contract compiles to a deterministic JSON artifact, and hashing it gives a short identifier for that exact database shape, like a git commit hash for your schema. A migration records the hash it starts **from** and the hash it moves the database **to**: ```json title="migrations/app/20260707T1006_add_user_phone/migration.json" { @@ -62,11 +62,11 @@ Both files are committed side by side, like `package.json` and `package-lock.jso (`migrationHash` fingerprints the compiled migration itself so tampering is detectable; `providedInvariants` is used by extension migrations and stays empty for typical app migrations.) -These `from`/`to` hashes are what link migrations into a [graph](/orm/next/migrations/the-migration-graph) instead of a numbered list — that's the next page. +These `from`/`to` hashes are what link migrations into a [graph](/orm/next/migrations/the-migration-graph) instead of a numbered list. That graph is the next page. ## Every operation checks itself -Inside `ops.json`, each operation has three parts: a **precheck** that confirms the database is in the expected state, the **execute** statements that make the change, and a **postcheck** that confirms the change worked. Each operation also carries a class — **additive** (safe, like adding a column), **destructive** (can lose data, like dropping one), or **data** (changes rows, not structure) — which is what drives the `(destructive)` flags and data-loss warnings you'll see in CLI output. +Inside `ops.json`, each operation has three parts: a **precheck** that confirms the database is in the expected state, the **execute** statements that make the change, and a **postcheck** that confirms the change worked. Each operation also carries a class: **additive** (safe, like adding a column), **destructive** (can lose data, like dropping one), or **data** (changes rows, not structure). The class is what drives the `(destructive)` flags and data-loss warnings you'll see in CLI output. ```json title="One operation from ops.json" { @@ -98,13 +98,13 @@ Inside `ops.json`, each operation has three parts: a **precheck** that confirms This structure is what makes Prisma Next migrations safe in the situations where classic SQL migrations hurt: -- **A migration fails.** On PostgreSQL the whole run is one transaction, so a failure rolls everything back — the database returns to exactly where it was, and you fix the cause and re-run. And because the runner skips any operation whose postcheck already holds, re-running never double-applies work that's already true in the database. No commenting out statements, no hand-editing the database. -- **The database isn't in the state you assumed.** The precheck catches it before the change runs, and the error names the exact operation and the exact check that failed — not a generic SQL error halfway through. +- **A migration fails.** On PostgreSQL the whole run is one transaction, so a failure rolls everything back: the database returns to exactly where it was while you fix the cause and re-run. And because the runner skips any operation whose postcheck already holds, re-running never double-applies work that's already true in the database. No commenting out statements, no hand-editing the database. +- **The database isn't in the state you assumed.** The precheck catches it before the change runs, and the error names the exact operation and the exact check that failed, not a generic SQL error halfway through. - **Someone (or some agent) wrote the migration for you.** The intent (`migration.ts`), the exact SQL (`ops.json`), and the verification for every step are all in the diff, so review is straightforward. ## The commands -Everything lives under the `prisma-next` CLI. Planning and inspection are offline — they read files, not your database: +Everything lives under the `prisma-next` CLI. Planning and inspection are offline; they read files, not your database: | Command | What it does | | ------------------------- | ----------------------------------------------------------------------- | @@ -123,15 +123,15 @@ Two commands talk to a database: | `migration status` | Show where the database is in the graph and which migrations are pending | | `migration log` | Show the history of applied migrations from the database's ledger | -Note that applying is `prisma-next migrate`, not `migration apply` — `migration ...` commands manage the on-disk migration directories, `migrate` moves a database. +Note that applying is `prisma-next migrate`, not `migration apply`: `migration ...` commands manage the on-disk migration directories, while `migrate` moves a database. ## The same model for SQL and MongoDB -Everything on this page applies to both database families. On PostgreSQL, operations compile to SQL DDL and the applied state is tracked in a **marker** — a record Prisma Next keeps in the database itself, naming the contract state the database currently matches. On MongoDB, operations create collections, indexes, and JSON Schema validators, and the marker lives in a `_prisma_migrations` collection. The commands, the file layout, the graph, and the precheck/execute/postcheck structure are identical. +Everything on this page applies to both database families. On PostgreSQL, operations compile to SQL DDL and the applied state is tracked in a **marker**, a record Prisma Next keeps in the database itself naming the contract state the database currently matches. On MongoDB, operations create collections, indexes, and JSON Schema validators, and the marker lives in a `_prisma_migrations` collection. The commands, the file layout, the graph, and the precheck/execute/postcheck structure are identical. :::note[Migrations are early] -Prisma Next is in Early Access and migrations are one of its newest parts. The core loop on this page — plan, edit, apply, roll back — works today and is exercised in the Prisma Next test suite. Some things classic migration tools grew over years are not built yet: there are no squash/baseline commands, and no shadow-database dry run. We call out what's missing on each page rather than papering over it. +Prisma Next is in Early Access and migrations are one of its newest parts. The core loop on this page (plan, edit, apply, roll back) works today and is exercised in the Prisma Next test suite. Some things classic migration tools grew over years are not built yet: there are no squash/baseline commands, and no shadow-database dry run. We call out what's missing on each page rather than papering over it. ::: @@ -146,5 +146,6 @@ Projects scaffolded with `create-prisma` install Prisma Next skills for your cod ## See also - [The migration graph](/orm/next/migrations/the-migration-graph): why migrations form a graph and what that buys you -- [Generating a migration](/orm/next/migrations/generating-a-migration): the `migration plan` walkthrough +- [Generating a migration](/orm/next/migrations/generating-a-migration) and [Applying a migration](/orm/next/migrations/applying-a-migration): the hands-on loop +- [Editing a migration](/orm/next/migrations/editing-a-migration): backfills, raw SQL, and the recompile step - [Rethinking Database Migrations](https://www.prisma.io/blog/rethinking-database-migrations): the blog post on why this design exists diff --git a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx index 966c174b3b..bf98dd58cf 100644 --- a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx +++ b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx @@ -1,6 +1,6 @@ --- title: Rollbacks and recovery -description: Rolling back is planning one more migration to a state you've already been in. Recovery is re-running — safely. +description: Rolling back is planning one more migration to a state you've already been in. Recovery is fixing the cause and re-running, safely. url: /orm/next/migrations/rollbacks-and-recovery metaTitle: Rollbacks and recovery in Prisma Next metaDescription: How to roll back a Prisma Next migration with a reverse plan, and how precheck/postcheck verification makes failed migrations safe to diagnose and retry. @@ -9,16 +9,16 @@ badge: early-access Two different situations get called "rollback", and Prisma Next treats them differently: -- **The migration applied, but the change was wrong.** You need to move the database *back* to an earlier state. That's a **rollback** — and in Prisma Next it's just one more migration. -- **The migration failed partway.** You need to get *unstuck*. That's **recovery** — and usually means fixing the cause and re-running, because re-running is safe. +- **The migration applied, but the change was wrong.** You need to move the database *back* to an earlier state. That's a **rollback**, and in Prisma Next it's one more migration. +- **The migration failed partway.** You need to get *unstuck*. That's **recovery**, and it usually means fixing the cause and re-running, because re-running is safe. ## Rollback: a migration like any other -There is no `migrate down` command, and no separate "down migration" files. In the [graph model](/orm/next/migrations/the-migration-graph), the state you want to return to is a node you've already visited — so rolling back means planning a new edge that points at it. If you know git, this is `git revert`, not `git reset`: history only ever grows, and the ledger keeps a full record of the round trip. +There is no `migrate down` command, and no separate "down migration" files. In the [graph model](/orm/next/migrations/the-migration-graph), the state you want to return to is a node you've already visited, so rolling back means planning a new edge that points at it. If you know git, this is `git revert`, not `git reset`: history only ever grows, and the ledger keeps a full record of the round trip. -Suppose `20260707T1008_add_display_name` shipped and needs to come back out. Plan the reverse edge — `^` means "the state just before that migration": +Suppose `20260707T1008_add_display_name` shipped and needs to come back out. Plan the reverse edge; `^` means "the state before that migration": ```bash npx prisma-next migration plan \ @@ -43,13 +43,13 @@ DDL preview ALTER TABLE "public"."user" DROP COLUMN "displayName"; ``` -The planner diffs the two contract states and writes the operations that undo the change — flagged **destructive**, because they are. This is a real migration: review it, edit it (for example, to archive the column's data into another table before the `DROP`), commit it. Then apply it like any other migration: +The planner diffs the two contract states and writes the operations that undo the change, flagged **destructive** because they are. This is a real migration: review it, edit it (for example, to archive the column's data into another table before the `DROP`), commit it. Then apply it like any other migration: ```bash npx prisma-next migrate --to 20260707T1008_add_display_name^ ``` -Afterwards the graph shows the round trip — a forward edge up, a rollback edge back down: +Afterwards the graph shows the round trip: a forward edge up, a rollback edge back down: ```text * e6b5c28 @contract @@ -64,8 +64,8 @@ The database's marker is back at `705b1a6`, and the ledger records both the appl Two things to be clear-eyed about: -- **A rollback does not resurrect data.** Dropping the column discards whatever the forward migration and the app wrote into it. If that data matters, edit the rollback migration to save it somewhere first — that's exactly why the rollback is an editable migration and not an automatic mechanism. -- **You don't have to retrace every step.** An edge can jump from the current state directly to any earlier node, skipping intermediate states — one planned migration, one apply, even if you're rolling back three changes. +- **A rollback does not resurrect data.** Dropping the column discards whatever the forward migration and the app wrote into it. If that data matters, edit the rollback migration to save it somewhere first; that's exactly why the rollback is an editable migration and not an automatic mechanism. +- **You don't have to retrace every step.** An edge can jump from the current state directly to any earlier node, skipping intermediate states: one planned migration, one apply, even if you're rolling back three changes. ### One planning caveat after a rollback @@ -84,18 +84,18 @@ A failed `migrate` run stops at the failing operation and reports it precisely: The playbook: -1. **Read which check failed.** The error names the operation and the specific precheck or postcheck, in plain language — here, rows with `NULL` still exist, so tightening the constraint would fail. +1. **Read which check failed.** The error names the operation and the specific precheck or postcheck, in plain language. Here, rows with `NULL` still exist, so tightening the constraint would fail. 2. **Nothing to clean up.** On PostgreSQL the whole run was one transaction, so the failure rolled it back completely: the database is exactly where it was before the run, and migrations applied in earlier runs are untouched. There is no "half-applied migration" to untangle by hand. -3. **Fix the cause, then re-run `prisma-next migrate`.** Sometimes the cause is the environment (extension missing, permissions). Sometimes it's the migration itself — in this example you'd [edit the migration](/orm/next/migrations/editing-a-migration) to add a backfill before the `setNotNull`, recompile with `node migration.ts`, and apply again. Re-running is always safe: the runner skips any operation whose postcheck already holds, so retries converge instead of double-applying. +3. **Fix the cause, then re-run `prisma-next migrate`.** Sometimes the cause is the environment (extension missing, permissions). Sometimes it's the migration itself; here you'd [edit the migration](/orm/next/migrations/editing-a-migration) to add a backfill before the `setNotNull`, recompile with `node migration.ts`, and apply again. Re-running is safe, for the reasons covered in [the failure model](/orm/next/migrations/applying-a-migration#when-something-goes-wrong). -The precheck is also what protects you against the works-in-dev-breaks-in-prod trap. Your dev database had no `NULL`s, production does; the precheck halts production *before* the destructive `ALTER` touches anything, with an error pointing at the exact rows-with-NULLs condition — not a generic constraint violation mid-statement. +The precheck is also what protects you against the works-in-dev-breaks-in-prod trap. Your dev database had no `NULL`s, production does; the precheck halts production *before* the destructive `ALTER` touches anything, with an error pointing at the exact rows-with-NULLs condition, not a generic constraint violation mid-statement. ### Drift: when the database isn't where migrations left it -If someone changed the database outside of migrations — a hand-run `ALTER`, a restore from an old backup — the marker or the live schema won't match any state the graph knows, and `migrate` refuses to run rather than pile changes onto drift. Your options, in order of preference: +If someone changed the database outside of migrations (a hand-run `ALTER`, a restore from an old backup), the marker or the live schema won't match any state the graph knows, and `migrate` refuses to run rather than pile changes onto drift. Your options, in order of preference: -- **`prisma-next db verify`** tells you exactly how the live schema differs from your contract. -- **In development**, `prisma-next db update` reconciles the database directly to your contract without walking the graph — quick, but off the record, so treat it as a dev-only reset. +- **`prisma-next db verify`** checks whether the database still matches your contract, and fails with a precise error when it doesn't. +- **In development**, `prisma-next db update` reconciles the database directly to your contract without walking the graph: quick, but off the record, so treat it as a dev-only reset. - **For a database with no history at all** (a fresh environment, or adopting Prisma Next on an existing schema), `prisma-next db init` bootstraps it to the current contract and signs the marker. :::note[What's early] @@ -107,7 +107,7 @@ Reverse planning (`--to ^`), destructive-operation warnings, resumable re-r ## Prompt your coding agent - "Plan a rollback for the last migration and show me its destructive operations before I decide." -- "This migrate run failed — read the error, fix the migration, and re-run it." +- "This migrate run failed. Read the error, fix the migration, and re-run it." - "Check whether staging has drifted from the contract and explain the differences." ## See also diff --git a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx index 2f944248c9..0d8e57c8d7 100644 --- a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx +++ b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx @@ -3,19 +3,19 @@ title: The migration graph description: Migrations form a graph of contract states, not a numbered list. That's what makes history, branching, and parallel work safe. url: /orm/next/migrations/the-migration-graph metaTitle: The migration graph in Prisma Next -metaDescription: How Prisma Next models migrations as a graph — contract states as nodes, migrations as edges — and how that enables branching, merging, rollback, and parallel work. +metaDescription: How Prisma Next models migrations as a graph, with contract states as nodes and migrations as edges, and how that enables branching, merging, rollback, and parallel work. badge: early-access --- -Classic migration tools keep migrations in a straight line: a folder of files executed in timestamp order, exactly once. That model breaks in the situations modern teams hit constantly — two branches each add a migration, timestamps interleave, CI fails, and someone spends an afternoon renaming files. +Classic migration tools keep migrations in a straight line: a folder of files executed in timestamp order, exactly once. That model breaks in the situations modern teams hit constantly: two branches each add a migration, timestamps interleave, CI fails, and someone spends an afternoon renaming files. Prisma Next drops the line. Migrations form a **graph**, and the graph is the one idea that everything else in this section builds on. ## Nodes are contract states, edges are migrations -Every time you emit your contract, the result is a deterministic JSON artifact. Hashing it produces an identifier for that exact schema shape — `sha256:705b1a6...` — the way a git commit hash identifies an exact state of your files. +Every time you emit your contract, the result is a deterministic JSON artifact. Hashing it produces an identifier for that exact schema shape, `sha256:705b1a6...`, the way a git commit hash identifies an exact state of your files. -Those hashes are the **nodes** of the graph. A migration is an **edge**: it records the contract hash it starts `from` and the hash it moves the database `to`. Nothing about a migration's position in history comes from its file name — the timestamps in directory names are for humans, and the linkage lives entirely in the `from`/`to` hashes inside each `migration.json`. +Those hashes are the **nodes** of the graph. A migration is an **edge**: it records the contract hash it starts `from` and the hash it moves the database `to`. Nothing about a migration's position in history comes from its file name: the timestamps in directory names are for humans, and the linkage lives entirely in the `from`/`to` hashes inside each `migration.json`. @@ -44,21 +44,21 @@ npx prisma-next migration graph 1 space(s), 5 contract(s), 5 migration(s) ``` -This project's history is a diamond. Read it bottom-up: from an empty database (`-`), `init` establishes state `789dd79`. Alice and Bob branch from it in parallel — she adds a phone column, he adds an avatar. Then each branch gets a merge migration into the combined state `f9a41d7`, which the `prod` ref points at. +This project's history is a diamond. Read it bottom-up: from an empty database (`-`), `init` establishes state `789dd79`. Alice and Bob branch from it in parallel: she adds a phone column, he adds an avatar. Then each branch gets a merge migration into the combined state `f9a41d7`, which the `prod` ref points at. ## What the graph gives you -**Parallel work without ordering conflicts.** Alice and Bob each planned a migration from `789dd79` on their own branches. In a timestamp-ordered system, whoever merges second gets a broken history. Here, both edges are simply in the graph. A database that followed Alice's branch sits at `93be6c2` and reaches the merged state through `merge_alice`; a database that followed Bob's sits at `7e3fa7f` and takes `merge_bob`. Every environment finds its own path. This matters double when the "two developers" are two AI agents planning migrations concurrently — neither has to know about the other. +**Parallel work without ordering conflicts.** Alice and Bob each planned a migration from `789dd79` on their own branches. In a timestamp-ordered system, whoever merges second gets a broken history. Here, both edges are in the graph. A database that followed Alice's branch sits at `93be6c2` and reaches the merged state through `merge_alice`; a database that followed Bob's sits at `7e3fa7f` and takes `merge_bob`. Every environment finds its own path. This matters double when the "two developers" are two AI agents planning migrations concurrently; neither has to know about the other. **History you can trust.** Because each edge declares its `from` state, a migration can never silently run against a database in the wrong shape. If the marker doesn't match, the run stops before any SQL executes, with an error naming the mismatch. -**Rollback as a first-class move.** An edge can point "backwards" — from a later contract state to an earlier one. Rolling back isn't a special mode; it's planning one more migration whose destination is a state you've already been in. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery). +**Rollback as a first-class move.** An edge can point "backwards", from a later contract state to an earlier one. Rolling back isn't a special mode; it's planning one more migration whose destination is a state you've already been in. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery). **More than one shape of history.** The graph accommodates whatever your team's workflow produces: long linear spines, wide fan-outs where many branches leave one node, diamonds that converge again, and even a fast-forward edge that jumps several states in one hop. ## Refs: naming places in the graph -Raw hashes are awkward to type, so the graph supports **refs** — named pointers to a node, stored as small files in `migrations/app/refs/`: +Raw hashes are awkward to type, so the graph supports **refs**: named pointers to a node, stored as small files in `migrations/app/refs/`: ```bash npx prisma-next ref set prod sha256:f9a41d7... @@ -81,11 +81,11 @@ Four read-only commands, each answering a different question: | `migration status` | Where is my database, and what's pending? | Yes | | `migration log` | What has actually been applied, and when? | Yes | -`migration graph` also takes `--json` for machine-readable output and `--dot` to render with Graphviz. `migration log` reads a **ledger** the runner appends to on every apply — so even a rollback shows up as history, never as deleted history. +`migration graph` also takes `--json` for machine-readable output and `--dot` to render with Graphviz. `migration log` reads a **ledger** the runner appends to on every apply, so even a rollback shows up as history, never as deleted history. ### Try it on real fixtures -The Prisma Next repo ships a set of example graphs — a diamond, a wide fan-out, converging branches, rollback chains — as ready-to-render fixtures: +The Prisma Next repo ships a set of example graphs (a diamond, a wide fan-out, converging branches, rollback chains) as ready-to-render fixtures: ```bash git clone https://github.com/prisma/prisma-next @@ -102,7 +102,7 @@ The graph model is family-neutral. On PostgreSQL, the marker lives in a `prisma_ :::note[What's early] -The graph, pathfinding, refs, and the marker/ledger model all work today. What doesn't exist yet: commands to squash a chain of migrations into one, baseline an existing history, or split a large migration — the graph was designed for them, but the commands haven't shipped. If two branch tips are both reachable, `migrate` asks you to pick a target with `--to` rather than guessing. +The graph, pathfinding, refs, and the marker/ledger model all work today. What doesn't exist yet: commands to squash a chain of migrations into one, baseline an existing history, or split a large migration; the graph was designed for them, but the commands haven't shipped. If two branch tips are both reachable, `migrate` asks you to pick a target with `--to` rather than guessing. ::: @@ -110,7 +110,7 @@ The graph, pathfinding, refs, and the marker/ledger model all work today. What d - "Draw the migration graph for this project and explain the branches." - "Which contract state is the `prod` ref pointing at, and is the database there yet?" -- "Two feature branches both added migrations — check whether they conflict." +- "Two feature branches both added migrations. Check whether they conflict." ## See also diff --git a/apps/docs/src/components/concept-animation/presets.ts b/apps/docs/src/components/concept-animation/presets.ts index accd1c5100..28eabcbd29 100644 --- a/apps/docs/src/components/concept-animation/presets.ts +++ b/apps/docs/src/components/concept-animation/presets.ts @@ -180,7 +180,7 @@ export const CONCEPT_PRESETS = { "├─ [[ops.json]] ← compiled SQL steps, what runs\n" + "└─ migration.json ← from/to contract hashes", caption: - "The planner diffs the new contract against your migration history and writes a migration package — no database connection needed.", + "The planner diffs the new contract against your migration history and writes a migration directory. No database connection needed.", }, { title: "3. Review (and edit)", @@ -203,7 +203,7 @@ export const CONCEPT_PRESETS = { '└─ Add column "phone" to "user"\n' + " precheck ✓ → execute ✓ → postcheck ✓", caption: - "migrate walks the pending migrations. Every operation verifies the database before it runs and confirms the result after — so a failed run is safe to retry.", + "migrate walks the pending migrations. Every operation verifies the database before it runs and confirms the result after, so a failed run is safe to retry.", }, ], }, @@ -231,7 +231,7 @@ export const CONCEPT_PRESETS = { " |^ add_posts\n" + " * 705b1a6", caption: - "Alice and Bob each plan a migration from the same state, on separate git branches. No timestamps to fight over — both edges are simply valid futures of 9f49f8f.", + "Alice and Bob each plan a migration from the same state, on separate git branches. No timestamps to fight over: both edges are valid futures of 9f49f8f.", }, { title: "3. The branches merge", @@ -285,10 +285,10 @@ export const CONCEPT_PRESETS = { '└─ Drop column "displayName" [[(destructive)]]\n' + "⚠ may cause data loss — review before applying", caption: - '`add_display_name^` means "the state before that migration". The planner writes a real migration that undoes the change — and warns you it\'s destructive. Review it, edit it, commit it.', + '`add_display_name^` means "the state before that migration". The planner writes a real migration that undoes the change, and warns you it\'s destructive. Review it, edit it, commit it.', }, { - title: "3. Apply it — history grows", + title: "3. Apply it: history grows", code: "$ [[npx prisma-next migrate --to add_display_name^]]\n" + "\n" + From d9ed6b283985ec5c985b8a9abb4058aa80a7eedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 18:26:21 +0700 Subject: [PATCH 04/15] docs: address review; fix preset merge seam CodeRabbit: bind endContractJson to the raw end-contract.json import in the filled editing example (verified the variant self-emits with identical hashes); restore the extensions cross-link now that #8008 is merged; re-add the closing braces the merge common-suffix folding dropped between the migration and middleware presets. Co-Authored-By: Claude Fable 5 --- .../content/docs/orm/next/migrations/applying-a-migration.mdx | 2 +- .../content/docs/orm/next/migrations/editing-a-migration.mdx | 2 +- apps/docs/src/components/concept-animation/presets.ts | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx index f88d6dc500..1aa6b1c482 100644 --- a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx @@ -146,7 +146,7 @@ Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transac ## Extension spaces -If your project uses database extensions (say pgvector), you'll see more than one *contract space* in the output: extensions ship their own migrations (for example `CREATE EXTENSION vector`), tracked in `migrations//` next to your app's. One `migrate` run walks them all (extensions first, then your app) and reports each space separately: +If your project uses [database extensions](/orm/next/extensions/using-extensions) (say pgvector), you'll see more than one *contract space* in the output: extensions ship their own migrations (for example `CREATE EXTENSION vector`), tracked in `migrations//` next to your app's. One `migrate` run walks them all (extensions first, then your app) and reports each space separately: ```text ✔ Applied 2 migration(s) (20 operation(s)) across 2 contract space(s) diff --git a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx index f106a1a390..24b4990473 100644 --- a/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx @@ -81,7 +81,7 @@ const db = sql({ }); export default class M extends Migration { - override readonly endContractJson = endContract; + override readonly endContractJson = endContractJson; override readonly startContractJson = startContract; override get operations() { diff --git a/apps/docs/src/components/concept-animation/presets.ts b/apps/docs/src/components/concept-animation/presets.ts index f646c41fb8..68e0c9be71 100644 --- a/apps/docs/src/components/concept-animation/presets.ts +++ b/apps/docs/src/components/concept-animation/presets.ts @@ -299,6 +299,9 @@ export const CONCEPT_PRESETS = { "|^ init", caption: "The rollback is applied like any other migration: the marker moves back, the ledger records the round trip, and nothing is rewritten. Like git revert, not git reset.", + }, + ], + }, "middleware-pipeline": { label: "How a query moves through the middleware chain", steps: [ From 04964e8566a99db3f9359cbc306023601b06300e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 19:06:24 +0700 Subject: [PATCH 05/15] docs: five-minute path, forward-reference pass, backlog polish The overview page now runs the whole loop inline: three commands with the verified plan and apply output and an explicit success signal, so a newcomer is productive before leaving page one. Swept all six pages for forward references: terms like ledger, marker, and contract space are now glossed or linked at first use instead of assuming later sections. Also links the graph page from the Prisma Next index bullet, links the contract to Data Modeling, and straightens the branch/merge lanes in the migration-graph animation. Co-Authored-By: Claude Fable 5 --- apps/docs/content/docs/orm/next/index.mdx | 2 +- .../next/migrations/applying-a-migration.mdx | 2 ++ .../migrations/generating-a-migration.mdx | 3 ++- .../next/migrations/how-migrations-work.mdx | 25 +++++++++++++++++-- .../migrations/rollbacks-and-recovery.mdx | 4 +-- .../next/migrations/the-migration-graph.mdx | 2 +- .../components/concept-animation/presets.ts | 22 ++++++++-------- 7 files changed, 42 insertions(+), 18 deletions(-) diff --git a/apps/docs/content/docs/orm/next/index.mdx b/apps/docs/content/docs/orm/next/index.mdx index 2dbafc33b2..01357c39a1 100644 --- a/apps/docs/content/docs/orm/next/index.mdx +++ b/apps/docs/content/docs/orm/next/index.mdx @@ -25,7 +25,7 @@ Prisma's current architecture tightly couples three layers: the schema language, - **Better queries**: a cleaner API with simpler nested queries, custom collection methods on your models, and streaming results, plus a low-level, type-safe SQL query builder for the cases that need raw control. - **Extensible by design**: a minimal core exposed through a public SPI. Everything around it, including Postgres support itself, is an extension, so you can add databases, query builders, data types, and middleware. -- **Rethought migrations**: graph-based migrations that resolve branch conflicts automatically and make partial failures safe to retry, with both schema and data migrations written in TypeScript and validated against your contract. +- **Rethought migrations**: [graph-based migrations](/orm/next/migrations/the-migration-graph) that resolve branch conflicts automatically and make partial failures safe to retry, with both schema and data migrations written in TypeScript and validated against your contract. - **AI-agent friendly**: your schema compiles to a machine-readable contract, every query produces a structured, inspectable plan, and middleware adds compile-time guardrails. The installers also register agent skills so your editor's AI assistant can drive Prisma Next changes safely. ## How it works diff --git a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx index 1aa6b1c482..9f80eeb6c5 100644 --- a/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx @@ -35,6 +35,8 @@ App space Next: prisma-next migration status ``` +`App space` is your application's migration lane, one of the run's *contract spaces*; projects that use database extensions gain additional spaces, covered in [Extension spaces](#extension-spaces) below. + ## Check before, preview, then apply The habit worth building, especially against shared databases, is a three-step rhythm: diff --git a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx index 888493433f..ea827cc659 100644 --- a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx @@ -13,7 +13,7 @@ We'll start from a minimal project. If you don't have one, `npx create-prisma@ne ## Your first migration -Say your contract has a single model: +Say your [contract](/orm/next/data-modeling) has a single model: ```prisma title="contract.prisma" model User { @@ -66,6 +66,7 @@ Three things to notice: - **The DDL preview is right there.** You see the exact SQL before anything exists but files. - **`from: null`** means this migration starts from an empty database; it's the root of your [migration graph](/orm/next/migrations/the-migration-graph). - **`to:` is your contract's hash.** The migration promises to deliver a database matching exactly the contract you just emitted. +- **`App space`** is your application's migration lane. Database extensions bring their own lanes; see [extension spaces](/orm/next/migrations/applying-a-migration#extension-spaces). The planned migration directory contains the TypeScript source, the compiled operations, and the contract snapshots: diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index a114c66d71..60d5feca62 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -18,13 +18,34 @@ The workflow is a loop you will run many times a day in development: 3. **Review it**: read the generated TypeScript and the DDL preview. Edit the migration if the change needs a data step, then re-run the file to recompile it. 4. **Apply it**: `prisma-next migrate` runs the pending migrations against your database. +In a project with a database connection configured (the [quickstart](/next/quickstart/postgresql) gives you one), the whole loop is three commands. Add an optional `phone String?` field to a model in your `.prisma` file, then: + ```bash npx prisma-next contract emit npx prisma-next migration plan --name add_user_phone npx prisma-next migrate ``` -Each page in this section walks through one part of the loop in detail. This page covers the model: what a migration is, and why it looks the way it does. +`migration plan` prints the SQL it wrote before anything runs: + +```text +✔ Planned 1 operation(s) + +│ +└─ Add column "phone" to "user" + +DDL preview + +ALTER TABLE "public"."user" ADD COLUMN "phone" text; +``` + +and `migrate` confirms what it applied: + +```text +✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s) +``` + +(The contract-space count only matters once you add database extensions; ignore it for now.) If you see that `✔ Applied` line, you've already run the entire workflow; the rest of this section is detail. This page covers the model: what a migration is, and why it looks the way it does. ## What a migration is @@ -121,7 +142,7 @@ Two commands talk to a database: | ------------------ | ---------------------------------------------------------------------------- | | `migrate` | [Apply pending migrations](/orm/next/migrations/applying-a-migration) | | `migration status` | Show where the database is in the graph and which migrations are pending | -| `migration log` | Show the history of applied migrations from the database's ledger | +| `migration log` | Show the history of migrations the database has actually applied | Note that applying is `prisma-next migrate`, not `migration apply`: `migration ...` commands manage the on-disk migration directories, while `migrate` moves a database. diff --git a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx index bf98dd58cf..8e7f81f865 100644 --- a/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx +++ b/apps/docs/content/docs/orm/next/migrations/rollbacks-and-recovery.mdx @@ -14,7 +14,7 @@ Two different situations get called "rollback", and Prisma Next treats them diff ## Rollback: a migration like any other -There is no `migrate down` command, and no separate "down migration" files. In the [graph model](/orm/next/migrations/the-migration-graph), the state you want to return to is a node you've already visited, so rolling back means planning a new edge that points at it. If you know git, this is `git revert`, not `git reset`: history only ever grows, and the ledger keeps a full record of the round trip. +There is no `migrate down` command, and no separate "down migration" files. In the [graph model](/orm/next/migrations/the-migration-graph), the state you want to return to is a node you've already visited, so rolling back means planning a new edge that points at it. If you know git, this is `git revert`, not `git reset`: history only ever grows, and the ledger (the applied-history record every database keeps) retains the full round trip. @@ -60,7 +60,7 @@ Afterwards the graph shows the round trip: a forward edge up, a rollback edge ba * - ``` -The database's marker is back at `705b1a6`, and the ledger records both the apply and the rollback. Nothing was rewritten or deleted. +The database's marker (its record of which graph node it currently matches) is back at `705b1a6`, and the ledger records both the apply and the rollback. Nothing was rewritten or deleted. Two things to be clear-eyed about: diff --git a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx index 0d8e57c8d7..aeec138aea 100644 --- a/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx +++ b/apps/docs/content/docs/orm/next/migrations/the-migration-graph.mdx @@ -44,7 +44,7 @@ npx prisma-next migration graph 1 space(s), 5 contract(s), 5 migration(s) ``` -This project's history is a diamond. Read it bottom-up: from an empty database (`-`), `init` establishes state `789dd79`. Alice and Bob branch from it in parallel: she adds a phone column, he adds an avatar. Then each branch gets a merge migration into the combined state `f9a41d7`, which the `prod` ref points at. +This project's history is a diamond. Read it bottom-up: from an empty database (`-`), `init` establishes state `789dd79`. Alice and Bob branch from it in parallel: she adds a phone column, he adds an avatar. Then each branch gets a merge migration into the combined state `f9a41d7`, which the `prod` ref points at. (The summary line counts contract *spaces*: independent migration lanes, one for your app plus one per [database extension](/orm/next/migrations/applying-a-migration#extension-spaces).) ## What the graph gives you diff --git a/apps/docs/src/components/concept-animation/presets.ts b/apps/docs/src/components/concept-animation/presets.ts index 68e0c9be71..c1122d6dca 100644 --- a/apps/docs/src/components/concept-animation/presets.ts +++ b/apps/docs/src/components/concept-animation/presets.ts @@ -224,10 +224,10 @@ export const CONCEPT_PRESETS = { { title: "2. Two people branch", code: - "[[* 93be6c2 * 7e3fa7f]]\n" + - "[[|^ alice_phone |^ bob_avatar]]\n" + - " \\ /\n" + - " * 9f49f8f ← both branched from here\n" + + "[[* 93be6c2]] [[* 7e3fa7f]]\n" + + "[[|^ alice_phone]] [[|^ bob_avatar]]\n" + + " \\ /\n" + + " * 9f49f8f ← both branched from here\n" + " |^ add_posts\n" + " * 705b1a6", caption: @@ -236,13 +236,13 @@ export const CONCEPT_PRESETS = { { title: "3. The branches merge", code: - " * [[f9a41d7 (prod)]]\n" + - " / \\\n" + - "[[|^ merge_alice |^ merge_bob]]\n" + - " * 93be6c2 * 7e3fa7f\n" + - " |^ alice_phone |^ bob_avatar\n" + - " \\ /\n" + - " * 9f49f8f", + " * [[f9a41d7 (prod)]]\n" + + " / \\\n" + + "[[|^ merge_alice]] [[|^ merge_bob]]\n" + + "* 93be6c2 * 7e3fa7f\n" + + "|^ alice_phone |^ bob_avatar\n" + + " \\ /\n" + + " * 9f49f8f", caption: "After the git merge, each branch gets a small merge migration into the combined state. A database that followed Alice takes merge_alice; one that followed Bob takes merge_bob. Every environment finds its own path.", }, From 5b54aac4cbe159e1117b2339b25d209af898acd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 19:55:34 +0700 Subject: [PATCH 06/15] docs: fix two list-count mismatches from later additions Co-Authored-By: Claude Fable 5 --- .../content/docs/orm/next/migrations/generating-a-migration.mdx | 2 +- .../content/docs/orm/next/migrations/how-migrations-work.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx index ea827cc659..489389935c 100644 --- a/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/next/migrations/generating-a-migration.mdx @@ -61,7 +61,7 @@ CREATE TABLE "public"."user" ( ); ``` -Three things to notice: +Four things to notice: - **The DDL preview is right there.** You see the exact SQL before anything exists but files. - **`from: null`** means this migration starts from an empty database; it's the root of your [migration graph](/orm/next/migrations/the-migration-graph). diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index 60d5feca62..cc0eb656d4 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -136,7 +136,7 @@ Everything lives under the `prisma-next` CLI. Planning and inspection are offlin | `migration graph` | Draw the [migration graph](/orm/next/migrations/the-migration-graph) | | `migration check` | Verify migration files and graph integrity (useful in CI) | -Two commands talk to a database: +Three commands talk to a database: | Command | What it does | | ------------------ | ---------------------------------------------------------------------------- | From 8f1fc8f5f0e36139634daa6ab81bc124fd53f8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 20:02:02 +0700 Subject: [PATCH 07/15] docs: fix animation color bleed and narrow step-1 emphasis User-reported: the migration-loop diagram intermittently painted "User {" in the accent color. Two causes: WAAPI fill-both animations persist on token spans React reuses across steps, so a wrap from the last step back to the first could keep painting the previous color onto a new token (fixed by cancelling subtree animations before each transition), and the step-1 emphasis spanned the whole contract line, reading as broken syntax highlighting (narrowed to the annotation arrow). Verified mid-transition and settled colors in the browser. Co-Authored-By: Claude Fable 5 --- .../concept-animation/flow-presets.ts | 51 ++++++++++++++++--- .../components/concept-animation/index.tsx | 4 +- .../components/concept-animation/presets.ts | 2 +- .../components/concept-animation/shell.tsx | 4 +- .../concept-animation/smooth-pre.tsx | 6 +++ 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/apps/docs/src/components/concept-animation/flow-presets.ts b/apps/docs/src/components/concept-animation/flow-presets.ts index 7e24d4d54b..7e476bd906 100644 --- a/apps/docs/src/components/concept-animation/flow-presets.ts +++ b/apps/docs/src/components/concept-animation/flow-presets.ts @@ -1,4 +1,3 @@ - /** * A flow scene is a fixed box-and-arrow diagram drawn in a viewBox. Every node * and edge is laid out once; each step only chooses which of them are visible @@ -621,11 +620,47 @@ const middlewareLifecycle: FlowScene = { width: 700, height: 150, nodes: [ - { id: "bc", label: "beforeCompile", sub: "rewrite", variant: "source", x: 16, y: 40, w: 128, h: 64 }, - { id: "be", label: "beforeExecute", sub: "guard", variant: "scope", x: 156, y: 40, w: 128, h: 64 }, - { id: "ic", label: "intercept", sub: "answer early", variant: "production", x: 296, y: 40, w: 122, h: 64 }, + { + id: "bc", + label: "beforeCompile", + sub: "rewrite", + variant: "source", + x: 16, + y: 40, + w: 128, + h: 64, + }, + { + id: "be", + label: "beforeExecute", + sub: "guard", + variant: "scope", + x: 156, + y: 40, + w: 128, + h: 64, + }, + { + id: "ic", + label: "intercept", + sub: "answer early", + variant: "production", + x: 296, + y: 40, + w: 122, + h: 64, + }, { id: "or", label: "onRow", sub: "each row", variant: "vars", x: 470, y: 40, w: 100, h: 64 }, - { id: "ae", label: "afterExecute", sub: "observe", variant: "branch", x: 582, y: 40, w: 112, h: 64 }, + { + id: "ae", + label: "afterExecute", + sub: "observe", + variant: "branch", + x: 582, + y: 40, + w: 112, + h: 64, + }, ], edges: [ { id: "e1", from: "bc", fromSide: "r", to: "be", toSide: "l" }, @@ -823,7 +858,7 @@ const relationOneToOne: FlowScene = { { title: "3. Query from the profile", caption: - "The model that holds the foreign key declares the relation, so you query from that side: Profile.include(\"user\") follows userId and attaches the matching user to the result.", + 'The model that holds the foreign key declares the relation, so you query from that side: Profile.include("user") follows userId and attaches the matching user to the result.', nodes: ["user", "profile"], edges: ["fk"], emphasize: ["user"], @@ -908,7 +943,7 @@ const relationOneToMany: FlowScene = { { title: "3. Query either direction", caption: - "User.include(\"posts\") gathers every post with a matching authorId into an array on the user. Post.include(\"author\") follows the key the other way and attaches one user to each post.", + 'User.include("posts") gathers every post with a matching authorId into an array on the user. Post.include("author") follows the key the other way and attaches one user to each post.', nodes: ["user", "p1", "p2", "p3"], edges: ["e1", "e2", "e3"], emphasize: ["user"], @@ -1021,7 +1056,7 @@ const relationManyToMany: FlowScene = { { title: "3. Traverse in two hops", caption: - "Queries follow the same two hops: Post.include(\"tags\") fetches the link records, and nesting include(\"tag\") inside it attaches each tag. One query, both hops.", + 'Queries follow the same two hops: Post.include("tags") fetches the link records, and nesting include("tag") inside it attaches each tag. One query, both hops.', nodes: ["post1", "post2", "pt1", "pt2", "pt3", "tag1", "tag2"], edges: ["a1", "b1", "a2", "b2", "a3", "b3"], emphasize: ["post1", "tag1", "tag2"], diff --git a/apps/docs/src/components/concept-animation/index.tsx b/apps/docs/src/components/concept-animation/index.tsx index 71802383d8..b939984187 100644 --- a/apps/docs/src/components/concept-animation/index.tsx +++ b/apps/docs/src/components/concept-animation/index.tsx @@ -16,7 +16,9 @@ export function ConceptAnimation({ name }: { name: ConceptName | FlowName }) { const scene = (FLOW_SCENES as Partial>)[name]; if (scene) return ; - const preset = (CONCEPT_PRESETS as Partial>)[name]; + const preset = ( + CONCEPT_PRESETS as Partial> + )[name]; if (!preset) throw new Error(`Unknown concept animation: ${String(name)}`); const steps = preset.steps.map((step) => ({ ...parseStepTokens(step.code), diff --git a/apps/docs/src/components/concept-animation/presets.ts b/apps/docs/src/components/concept-animation/presets.ts index c1122d6dca..1ab3893656 100644 --- a/apps/docs/src/components/concept-animation/presets.ts +++ b/apps/docs/src/components/concept-animation/presets.ts @@ -162,7 +162,7 @@ export const CONCEPT_PRESETS = { "model User {\n" + " id Int @id\n" + " email String\n" + - " [[phone String? ← new field]]\n" + + " phone String? [[← new field]]\n" + "}\n" + "\n" + "$ [[npx prisma-next contract emit]]", diff --git a/apps/docs/src/components/concept-animation/shell.tsx b/apps/docs/src/components/concept-animation/shell.tsx index 4b5df417e3..d48535b072 100644 --- a/apps/docs/src/components/concept-animation/shell.tsx +++ b/apps/docs/src/components/concept-animation/shell.tsx @@ -110,7 +110,9 @@ export function PlayerShell({ aria-label={label} className="not-prose my-4 overflow-hidden rounded-square border border-stroke-neutral bg-fd-card" > - + {/* Autoplay progress bar: refills every step, so it is obvious the diagram is advancing on its own and that more steps are coming. */} diff --git a/apps/docs/src/components/concept-animation/smooth-pre.tsx b/apps/docs/src/components/concept-animation/smooth-pre.tsx index 6dc8962dbf..cc5cf0d816 100644 --- a/apps/docs/src/components/concept-animation/smooth-pre.tsx +++ b/apps/docs/src/components/concept-animation/smooth-pre.tsx @@ -40,6 +40,12 @@ export class SmoothPre extends React.Component { if (!this.ref.current || !snapshot) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const transitions = calculateTransitions(this.ref.current, snapshot); + // Cancel leftover fill-both animations before starting new ones. React + // reuses token spans across steps, and a stale animation otherwise keeps + // painting the previous step's color/position onto the reused element. + for (const animation of this.ref.current.getAnimations({ subtree: true })) { + animation.cancel(); + } for (const { element, keyframes, options } of transitions) { const { translateX, translateY, ...rest } = keyframes as Record< string, From 9f0c93ccdfc1006836abd51ee8c9332f1730dfab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 20:06:20 +0700 Subject: [PATCH 08/15] docs: make migration-loop step 3 self-explanatory User-reported: "# edited? recompile:" was too terse to parse. Step 3 now reads as review-the-pair (migration.ts = intent, ops.json = exact SQL) with an explicit conditional: only if you edit migration.ts, run it once to regenerate ops.json so the two stay in sync. Caption rewritten to match. Co-Authored-By: Claude Fable 5 --- .../src/components/concept-animation/presets.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/docs/src/components/concept-animation/presets.ts b/apps/docs/src/components/concept-animation/presets.ts index 1ab3893656..b015ec9da0 100644 --- a/apps/docs/src/components/concept-animation/presets.ts +++ b/apps/docs/src/components/concept-animation/presets.ts @@ -183,15 +183,17 @@ export const CONCEPT_PRESETS = { "The planner diffs the new contract against your migration history and writes a migration directory. No database connection needed.", }, { - title: "3. Review (and edit)", + title: "3. Review (edit if needed)", code: - "migration.ts: this.addColumn({ table: 'user', ... })\n" + - 'ops.json: ALTER TABLE "user" ADD COLUMN "phone" text\n' + + "review the pair:\n" + + " migration.ts → this.addColumn({ table: 'user', ... })\n" + + ' ops.json → ALTER TABLE "user" ADD COLUMN "phone" text\n' + "\n" + - "# edited? recompile:\n" + - "$ [[node migrations/app/20260707T1005_add_phone/migration.ts]]", + "only if you [[edit migration.ts]], run it once to\n" + + "[[regenerate ops.json]] so the two stay in sync:\n" + + "$ node migrations/app/20260707T1005_add_phone/migration.ts", caption: - "Intent and exact SQL sit side by side in the diff. Need a data backfill or a custom step? Edit migration.ts and re-run it to regenerate ops.json.", + "migration.ts states the intent; ops.json is the exact SQL that will run. For most changes you review both and move on. If you do edit migration.ts (say, to add a data backfill), running the file rewrites ops.json to match.", }, { title: "4. Apply", From 20b35b7eee3ec1629b28e728a86d40607423f0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 20:10:52 +0700 Subject: [PATCH 09/15] docs: clearer wording for the three-command walkthrough plan prepares SQL but does not run it; migrate applies and confirms; the closing note reads as one plain paragraph. Co-Authored-By: Claude Fable 5 --- .../docs/orm/next/migrations/how-migrations-work.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index cc0eb656d4..23f7117e61 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -26,7 +26,7 @@ npx prisma-next migration plan --name add_user_phone npx prisma-next migrate ``` -`migration plan` prints the SQL it wrote before anything runs: +`migration plan` prepares the SQL for the migration, but doesn't run it: ```text ✔ Planned 1 operation(s) @@ -39,13 +39,13 @@ DDL preview ALTER TABLE "public"."user" ADD COLUMN "phone" text; ``` -and `migrate` confirms what it applied: +and `migrate` applies the migration to your database and confirms what it applied: ```text ✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s) ``` -(The contract-space count only matters once you add database extensions; ignore it for now.) If you see that `✔ Applied` line, you've already run the entire workflow; the rest of this section is detail. This page covers the model: what a migration is, and why it looks the way it does. +The contract-space count only matters once you add database extensions; ignore it for now. If you see the `✔ Applied` line, you've already run the entire workflow. The rest of this page covers additional detail: what a migration is, and why it looks the way it does. ## What a migration is From 0831b029fe21f962b3b3d1735b9db2993f27b9e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 20:16:05 +0700 Subject: [PATCH 10/15] docs: explain why the graph matters, and that it is ignorable Co-Authored-By: Claude Fable 5 --- .../content/docs/orm/next/migrations/how-migrations-work.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index 23f7117e61..489e1957e6 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -83,7 +83,7 @@ Both files are committed side by side, like `package.json` and `package-lock.jso (`migrationHash` fingerprints the compiled migration itself so tampering is detectable; `providedInvariants` is used by extension migrations and stays empty for typical app migrations.) -These `from`/`to` hashes are what link migrations into a [graph](/orm/next/migrations/the-migration-graph) instead of a numbered list. That graph is the next page. +These `from`/`to` hashes are what link migrations into a [graph](/orm/next/migrations/the-migration-graph) instead of a numbered list. Thinking about your migrations as a graph instead of a linear set of actions enables Prisma Next migrations to help you in complex scenarios that other migration systems can't. If you want a simple migration system, you can ignore this. ## Every operation checks itself From 9e0e4d34d9f0b25c985dd935324ae4a2e4c4b987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Tue, 7 Jul 2026 20:39:16 +0700 Subject: [PATCH 11/15] docs: add 30s migration-loop video to How migrations work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Composed and rendered with HeyGen HyperFrames (HTML/CSS/GSAP, local render): idea → contract change → plan → review/apply → the new column in Prisma Studio → autocomplete against the new schema. CLI output in the video matches the verified outputs used across the section. Served from public/ (2.6 MB, same-origin per CSP media-src) and embedded at the bottom of "The commands". Co-Authored-By: Claude Fable 5 --- .../next/migrations/how-migrations-work.mdx | 12 ++++++++++++ .../orm/next/migrations/migration-loop.mp4 | Bin 0 -> 2703251 bytes 2 files changed, 12 insertions(+) create mode 100644 apps/docs/public/img/orm/next/migrations/migration-loop.mp4 diff --git a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx index 489e1957e6..240edaa230 100644 --- a/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/next/migrations/how-migrations-work.mdx @@ -146,6 +146,18 @@ Three commands talk to a database: Note that applying is `prisma-next migrate`, not `migration apply`: `migration ...` commands manage the on-disk migration directories, while `migrate` moves a database. +Here is the whole loop in motion, from idea to typed code against the new schema: + +