@prisma/orm-mongo 8.0.0-rc.9-dev.7 → 8.0.0-rc.9-dev.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,18 +54,15 @@ The graph is a static, committed artifact. Several branch tips may coexist, roll
54
54
 
55
55
  ### Diagnostic codes
56
56
 
57
- `migration status` emits structured diagnostics on the result envelope (`diagnostics[].code`) so the agent can branch on the code rather than parsing the prose summary. Each diagnostic also carries `severity` (`warn` or `info`), a human `message`, and `hints` — the same hints the CLI prints under the summary line.
57
+ `migration status` answers with a document (`--json`): `spaces[]`, each with `currentContract` (the marker hash, or `null` when the database has no marker for that space), `targetContract`, and `migrations[]` carrying the `applied` / `pending` / `unreachable` status above; a `summary` line; and `diagnostics[]`. The ordinary states — up to date, N pending, no marker yet, marker on another branch, no path to the target are read from `spaces[]` and the summary, not from diagnostic codes. The three diagnostics it can attach are all `warn`, and each carries a `message` and `hints` — the same hints the CLI prints under the summary line:
58
58
 
59
- | Code | Severity | Meaning in the navigation model | Next move |
60
- |---|---|---|---|
61
- | `MIGRATION.UP_TO_DATE` | info | Marker = destination; no edges to walk. | Nothing to do. |
62
- | `MIGRATION.DATABASE_BEHIND` | info | Marker is an ancestor of the destination; N pending edges in between. | `db migrate --to <name> --db $URL`. |
63
- | `MIGRATION.MISSING_INVARIANTS` | info | Marker reached destination structurally but missing required invariants the ref declares. | `db migrate --to <name> --db $URL` to take a path that covers them. |
64
- | `MIGRATION.NO_MARKER` | warn | Online, but the database has no marker row — never initialised. | `db migrate --db $URL` (first apply writes the marker). |
65
- | `MIGRATION.MARKER_NOT_IN_HISTORY` | warn | Online; marker hash is not a node in the graph. The database was changed outside the migration system. | Decide which side is truth: `db sign` (accept DB as truth), `db update` (push contract to DB), `contract infer` (re-derive contract from DB), or `db verify` (inspect first). **Not** the same as `MIGRATION.MARKER_MISMATCH`: `MARKER_NOT_IN_HISTORY` is emitted during the runner's graph walk when the live marker is off the path being traversed; `MARKER_MISMATCH` fires earlier, at the CLI pre-DDL gate, when the marker hash is not a graph node at all. |
66
- | `MIGRATION.DIVERGED` | warn | Multiple valid leaves; the destination is ambiguous. | Pass `--to <name>`, or `migration ref set <name> <hash>` to create one. |
67
- | `CONTRACT.AHEAD` | warn | Contract head is not in the graph — the contract was edited without re-planning. | `migration plan` to extend the graph. |
68
- | `CONTRACT.UNREADABLE` | warn | `contract.json` couldn't be read. | `contract emit` to regenerate it. |
59
+ | Code | Meaning in the navigation model | Next move |
60
+ |---|---|---|
61
+ | `MIGRATION.MARKER_NOT_IN_HISTORY` | Online; marker hash is not a node in the graph. The database was changed outside the migration system. | Decide which side is truth: `db sign` (accept DB as truth), `db update` (push contract to DB), `contract infer` (re-derive contract from DB), or `db verify` (inspect first). **Not** the same as `MIGRATION.MARKER_MISMATCH`, which `db migrate` raises as an error before any DDL when the marker hash is not a graph node. |
62
+ | `MIGRATION.MISSING_INVARIANTS` | Marker reached the destination structurally but lacks invariants the target ref declares. | `db migrate --to <name> --db $URL` to take a path that covers them. |
63
+ | `CONTRACT.UNREADABLE` | `contract.json` couldn't be read. | `contract emit` to regenerate it. |
64
+
65
+ Conditions that make the run *refuse* instead (exit `2`) arrive as ordinary errors: `MIGRATION.NO_INVARIANT_PATH` (no path covers the missing invariants), `MIGRATION.UNKNOWN_INVARIANT` (the ref names an invariant no edge provides), and the ref-resolution errors for a bad `--to` / `--from`.
69
66
 
70
67
  ### Graph-tree output
71
68
 
@@ -182,9 +179,25 @@ For a human-readable ordered preview of the migration path before applying, use
182
179
  --to staging --db "$STAGING_DATABASE_URL" --json > status.json
183
180
  node -e '
184
181
  const s = JSON.parse(require("fs").readFileSync("status.json", "utf8"));
185
- const warns = (s.diagnostics ?? []).filter(d => d.severity === "warn");
186
- if (warns.length) {
187
- console.error("Blocking diagnostics:", warns);
182
+ const problems = [];
183
+ for (const d of s.diagnostics ?? []) {
184
+ if (d.severity === "warn") problems.push(`${d.code}: ${d.message}`);
185
+ }
186
+ for (const space of s.spaces ?? []) {
187
+ if (space.currentContract === null) problems.push(`${space.space}: database has no marker`);
188
+ const unreachable = space.migrations.filter(m => m.status === "unreachable");
189
+ if (unreachable.length) problems.push(`${space.space}: ${unreachable.length} unreachable migration(s)`);
190
+ }
191
+ // Pending migrations are the normal case before Apply; block on them
192
+ // only if this job is a verify-only gate (set EXPECT_UP_TO_DATE=1).
193
+ if (process.env.EXPECT_UP_TO_DATE === "1") {
194
+ for (const space of s.spaces ?? []) {
195
+ const pending = space.migrations.filter(m => m.status === "pending");
196
+ if (pending.length) problems.push(`${space.space}: ${pending.length} pending migration(s)`);
197
+ }
198
+ }
199
+ if (problems.length) {
200
+ console.error("Blocking:\n" + problems.join("\n"));
188
201
  process.exit(1);
189
202
  }
190
203
  '
@@ -192,7 +205,7 @@ For a human-readable ordered preview of the migration path before applying, use
192
205
  run: pnpm prisma db migrate --to staging --db "$STAGING_DATABASE_URL"
193
206
  ```
194
207
 
195
- `migration status` exits non-zero only on hard errors (unreadable migrations directory, unsatisfiable invariants, unreconstructable history). Diagnostics like `MIGRATION.MARKER_NOT_IN_HISTORY`, `MIGRATION.DIVERGED`, `CONTRACT.AHEAD`, and `MIGRATION.NO_MARKER` are reported on the result envelope with `severity: 'warn'` but the process exits `0` — the agent (or a CI gate) must inspect `diagnostics[]` and fail the build itself. Use `--json` so the gate parses a structured shape rather than the human summary.
208
+ `migration status` exits non-zero only on hard errors (unreadable migrations directory, unsatisfiable invariants, unreconstructable history). Pending migrations, a missing marker (`currentContract: null`), and the `warn` diagnostics (`MIGRATION.MARKER_NOT_IN_HISTORY`, `MIGRATION.MISSING_INVARIANTS`, `CONTRACT.UNREADABLE`) all leave the exit code at `0` — the agent (or a CI gate) must inspect `spaces[]` and `diagnostics[]` and fail the build itself. Use `--json` so the gate parses a structured shape rather than the human summary.
196
209
 
197
210
  `db migrate` is interactive-free and has no destructive-op confirmation prompt — the safety rails that prompt for destructive changes live on `db update` (see the `references/migrations.md` skill). Whatever the planner put in the migration graph is what `db migrate` runs; review happens at `migration plan` and at `migration status` time, before the apply step.
198
211
 
@@ -18,7 +18,7 @@ Once the contract changes, you choose how the change reaches the database. This
18
18
  - User edited the contract and wants to apply the change to the DB.
19
19
  - User wants to author a migration with a data transform.
20
20
  - User wants to run pending migrations against a local DB.
21
- - User hit `MIGRATION.HASH_MISMATCH`, `PN-MIG-2001` (unfilled placeholder), or a partially-applied migration.
21
+ - User hit `MIGRATION.HASH_MISMATCH`, `MIGRATION.UNFILLED_PLACEHOLDER`, or a partially-applied migration.
22
22
  - User mentions: *migrate, migration, db push, db update, `prisma migrate dev`, `prisma migrate deploy`, drift, hash mismatch, data backfill*.
23
23
 
24
24
  ## When Not to Use
@@ -30,7 +30,7 @@ Once the contract changes, you choose how the change reaches the database. This
30
30
 
31
31
  ## Key Concepts
32
32
 
33
- - **`db update` (quick path).** Reads the emitted contract, diffs against the live DB, applies the change. Optional `--dry-run` prints the plan without executing. Interactive destructive-op confirmation (or `-y` to auto-accept). **Writes no migration directory.** Operations needing data transforms are not handled by this path — `db update` excludes the `data` operation class entirely and short-circuits where a data transform would be required. Use only against a database that has no shared history with anyone else (your local dev DB).
33
+ - **`db update` (quick path).** Reads the emitted contract, diffs against the live DB, applies the change. Optional `--dry-run` prints the plan without executing. A destructive operation is applied only with consent: interactively you type the database name; non-interactively pass `--confirm <database>` (`--yes` does not grant it). **Writes no migration directory.** Operations needing data transforms are not handled by this path — `db update` excludes the `data` operation class entirely and short-circuits where a data transform would be required. Use only against a database that has no shared history with anyone else (your local dev DB).
34
34
  - **`migration plan` (formal path).** Reads the emitted contract, diffs it against a resolved origin — explicit `--from`, else the `db` ref, else the empty database; there is no "head of the graph" to chain from (see `references/migration-model.md`) — and writes a new migration package under `migrations/app/<YYYYMMDDTHHMM>_<snake_slug>/`. If any operation needs a data transform, the package's `migration.ts` contains `placeholder(...)` calls you fill in.
35
35
  - **The `app/` segment in migration paths is the consuming application's contract-space id.** Every migration *you* author lives under `migrations/app/`. Extensions your contract depends on get their own sibling directories (`migrations/<extension-space-id>/`) — those are managed by the extension package and you don't write into them. The `app/` segment lands automatically the first time you run `migration plan` / `db init` against an app-level config.
36
36
  - **Migration package files** (inside each `migrations/app/<dir>/`):
@@ -39,40 +39,41 @@ Once the contract changes, you choose how the change reaches the database. This
39
39
  - `migration.ts` — TypeScript authoring source, **framework-rendered** by `migration plan` (or `migration new`). You edit specific holes in it (see *Fill a placeholder* below) and re-emit `ops.json` / `migration.json` by running it.
40
40
  - **Contract snapshots.** `migration.ts` imports its bookend contracts from the shared, content-addressed store at `migrations/snapshots/<hex>/contract.json` + `contract.d.ts` (`<hex>` is the contract's 64-hex storage hash) — not from files inside the migration package.
41
41
  - **Self-emit.** Running `node migrations/app/<dir>/migration.ts` regenerates `ops.json` and `migration.json` from the (possibly edited) TS source. This is the only supported way to update an existing migration package after edits.
42
- - **`migration.ts` shape.** Framework-rendered. A class extending `Migration` (from `@internal/family-mongo/migration` on Mongo, or re-exported via `@internal/postgres/migration` on Postgres — see the framing block below), with an `operations` getter that returns an array of factory-call values. The file ends with `MigrationCLI.run(import.meta.url, M)` so executing it self-emits.
43
- - **`placeholder(slot)`.** A sentinel the planner emits into the rendered `migration.ts` (from `@internal/errors/migration` on Mongo, or the `@internal/postgres/migration` import on Postgres) wherever a data transform is needed. Calling `placeholder(...)` at emit time throws `PN-MIG-2001` *Unfilled migration placeholder*. The user replaces the `() => placeholder(...)` arrow with a real query-plan closure (Postgres) or fills `dataTransform({ check, run })` sources (Mongo — see *Fill a placeholder*), then self-emits.
42
+ - **`migration.ts` shape.** Framework-rendered. A class `M extends Migration<Start, End>` (from `@internal/postgres/migration` on Postgres, `@prisma/orm-mongo/target/migration` on Mongo — see the framing block below) that assigns the two snapshot imports to `startContractJson` / `endContractJson` (`Start` is `never` and there is no `startContractJson` on a baseline) and has an `operations` getter returning an array of operation values. **On Postgres the operation factories are methods on the base class** (`this.addColumn({...})`, `this.setNotNull({...})`, `this.dataTransform(...)`) taking one options object; free helpers like `col(...)` build the column descriptors they take. **On Mongo they are free factories** (`createIndex(...)`, `dataTransform(...)`) imported beside `Migration`. The file ends with `MigrationCLI.run(import.meta.url, M)` so executing it self-emits.
43
+ - **`placeholder(slot)`.** A sentinel the planner emits into the rendered `migration.ts` (from the same `.../migration` import as `Migration`) wherever a data transform is needed. Calling `placeholder(...)` at emit time throws `MIGRATION.UNFILLED_PLACEHOLDER` with `meta.slot` naming the hole. The user replaces the `() => placeholder(...)` arrow with a real query-plan closure (Postgres) or fills `dataTransform({ check, run })` sources (Mongo — see *Fill a placeholder*), then self-emits.
44
44
  - **`this.dataTransform(endContract, name, { check, run })`.** The data-transform factory. `check` is a rowset query whose presence-of-any-row signals "work remains"; `run` is one or more mutation queries that perform the backfill. Both are lazy closures returning query-plans built against `endContract`. The runner wraps `check` as `EXISTS(...)` for precheck and `NOT EXISTS(...)` for postcheck, so the same closure asserts both "there is work" and "the work is done".
45
- - **`pendingPlaceholders`.** A boolean field on the JSON result of `migration plan`. `true` means the package was written but contains unfilled placeholders — `db migrate` will throw `PN-MIG-2001` until you edit `migration.ts` and self-emit.
45
+ - **`pendingPlaceholders`.** A boolean field on the JSON result of `migration plan`. `true` means the package was written but contains unfilled placeholders — `db migrate` will throw `MIGRATION.UNFILLED_PLACEHOLDER` until you edit `migration.ts` and self-emit.
46
46
  - **`migrationHash`.** Content-addressed identity of a migration package. `MIGRATION.HASH_MISMATCH` fires when the stored hash in `migration.json` disagrees with the hash recomputed from the on-disk files (almost always: someone edited `migration.ts` without self-emitting).
47
47
  - **Marker.** Records "this database is at contract hash X for space Y". **Postgres:** a row in `prisma_contract.marker`. **Mongo:** a document in the `_prisma_migrations` collection (keyed by space). Each successful migration advances the marker once schema verification passes for that space. `db sign` writes the marker from the current contract hash, but only after a schema-verification pass succeeds (it will not sign a database whose live schema disagrees with the contract).
48
- - **Apply atomicity.** **Postgres:** each migration runs inside `BEGIN ... COMMIT`; on failure, Postgres rolls back and the marker stays at the previous migration's `to` hash. **Mongo:** DDL ops (`createCollection`, `createIndex`, `collMod`, `setValidation`, …) are not wrapped in a multi-document transaction; the runner applies ops, verifies the live schema against the destination contract, and advances the marker only on verify-pass (resumable across spaces — see the MongoDB family doc). Ordinary DDL + `dataTransform` flows stay consistent; partial state from failed mid-migration runs is diagnosed with `db verify` / `db schema`, not assumed away.
48
+ - **Apply atomicity.** **Postgres:** one `db migrate` run is one transaction — the runner issues a single `BEGIN`, applies every pending migration for every contract space, then one `COMMIT`; any failure issues `ROLLBACK` for the whole run, so the marker stays where it was before the command. **Mongo:** DDL ops (`createCollection`, `createIndex`, `collMod`, `setValidation`, …) are not wrapped in a multi-document transaction; the runner applies ops, verifies the live schema against the destination contract, and advances the marker only on verify-pass (resumable across spaces — see the MongoDB family doc). Ordinary DDL + `dataTransform` flows stay consistent; partial state from failed mid-migration runs is diagnosed with `db verify` / `db schema`, not assumed away.
49
49
  - **Operation classes.** Every operation declares an `operationClass`: `additive`, `widening`, `data`, or `destructive`. The CLI surfaces these in the plan preview and in JSON output. There is no `long-running` class and the framework does not emit `CREATE INDEX CONCURRENTLY` — operations stay transactional.
50
50
 
51
51
  ## `migration.ts` is framework-rendered, not hand-authored
52
52
 
53
53
  Files under `migrations/<space-id>/<timestamp>/migration.ts` (for your own app, `<space-id>` is always `app/`) are **rendered for you** by the framework — `prisma migration plan` writes a populated package whenever the contract changes, and `prisma migration new` writes an empty scaffold when you want to author operations directly. You do not write these files from scratch. You edit specific holes the framework leaves behind — chiefly replacing `placeholder("<slot>")` sentinels (Postgres) or filling `dataTransform({ check, run })` pipeline slots (Mongo) — then self-emit.
54
54
 
55
- **Postgres** rendered imports point at `@internal/postgres/migration` (or `@internal/sqlite/migration` for SQLite projects).
55
+ **Postgres** rendered imports point at `@internal/postgres/migration` (or `@internal/sqlite/migration` for SQLite projects): one line carrying `Migration`, `MigrationCLI`, `col`, `placeholder`, `rawSql`, and any other free helper the operations need.
56
56
 
57
- **Mongo** rendered imports use `@internal/family-mongo/migration` for the `Migration` base class and `@internal/target-mongo/migration` for operation factories (`createIndex`, `dataTransform`, …). `MigrationCLI` comes from `@internal/cli/migration-cli`.
57
+ **Mongo** rendered imports point at one module too, `@prisma/orm-mongo/target/migration`, which carries `Migration`, `MigrationCLI`, `placeholder`, and the operation factories (`createIndex`, `dataTransform`, …). Raw command classes for data transforms come from `@prisma/orm-mongo/query-ast/execution`.
58
58
 
59
59
  Treat the rendered import lines as framework-managed on both targets:
60
60
 
61
- - Leave them where they are. Don't rewrite them to a different `@internal/<…>` path; the framework's renderer is the authoritative shape and any change you make by hand will be reverted (and may trip `MIGRATION.HASH_MISMATCH`) the next time the package is re-rendered or self-emitted.
62
- - If you need an additional factory symbol, **add it to the existing rendered import line** (Postgres: `@internal/postgres/migration`; Mongo: `@internal/target-mongo/migration`) rather than introducing a second import from a different `@internal/...` subpath.
61
+ - Leave them where they are. Don't rewrite them to a different path; the framework's renderer is the authoritative shape and any change you make by hand will be reverted (and may trip `MIGRATION.HASH_MISMATCH`) the next time the package is re-rendered or self-emitted.
62
+ - If you need an additional helper symbol, **add it to the existing rendered import line** rather than introducing a second import from a different subpath.
63
63
  - The "user code imports only from `@internal/<target>`" convention applies to *your* own modules (queries, runtime setup, contract authoring). The framework-rendered `migration.ts` scaffold is the framework's surface, not yours; the rule is suspended for that one file.
64
64
 
65
65
  ## Diagnostic codes you route on
66
66
 
67
67
  | Code | Source | Move |
68
68
  |---|---|---|
69
- | `PN-MIG-2001` *Unfilled migration placeholder* | Throwing `placeholder(...)` at emit time | Open `migration.ts`, replace the named `placeholder("<slot>")` call with the real query closure, self-emit. |
70
- | `PN-MIG-2002` *migration.ts not found* | Reading a migration package | The package is malformed. Recover from version control, or run `prisma migration new` for a fresh one. |
71
- | `PN-MIG-2003` *invalid default export* | Loading `migration.ts` | The file's default export is not a `Migration` subclass or factory function. Restore the planner-emitted scaffold from version control or re-run `migration plan` for a clean package. |
72
- | `PN-MIG-2005` *dataTransform contract mismatch* | Building a data-transform query plan | The query builder was instantiated with a contract reference different from the `endContract` passed to `this.dataTransform(...)`. Use the `endContract` imported at module scope for both. |
69
+ | `MIGRATION.UNFILLED_PLACEHOLDER` | Throwing `placeholder(...)` at emit time | Open `migration.ts`, replace the `placeholder("<slot>")` call named by `meta.slot` with the real query closure, self-emit. |
70
+ | `MIGRATION.FILE_MISSING` | Reading a migration package | `migration.ts`, `migration.json`, or `ops.json` is absent. Recover from version control, re-emit via `migration.ts`, or run `prisma migration new` for a fresh one. |
71
+ | `MIGRATION.INVALID_DEFAULT_EXPORT` | Loading `migration.ts` | The file's default export is not a `Migration` subclass or factory function. Restore the planner-emitted scaffold from version control or re-run `migration plan` for a clean package. |
72
+ | `MIGRATION.DATA_TRANSFORM_CONTRACT_MISMATCH` | Building a data-transform query plan | The query builder was instantiated with a contract reference different from the `endContract` passed to `this.dataTransform(...)`. Use the `endContract` imported at module scope for both. |
73
73
  | `MIGRATION.HASH_MISMATCH` *Migration package is corrupt* | `db migrate` (or any read of the package) | `ops.json` / `migration.json` were edited without self-emitting. Run `node migrations/app/<dir>/migration.ts` to re-emit, then re-run `db migrate`. |
74
- | `PN-RUN-3002` *Hash mismatch* | `db verify` | The marker disagrees with the contract hash (**Postgres:** `prisma_contract.marker`; **Mongo:** `_prisma_migrations`). The DB is at a different contract version than the code thinks. Either run a migration forward, or — if the DB is correct and the marker is stale after a manual fix-up — run `db sign`. |
75
- | `PN-RUN-3001` *Database not signed* | Any command needing a marker | The DB has no marker yet. Run `prisma db init --db <url>` to baseline an empty database, or `db update --db <url>` to apply the current contract directly. |
74
+ | `MIGRATION.DESTRUCTIVE_CHANGES` | `db update` run non-interactively without consent | Re-run with `--confirm <database>` (the database name from the connection), or `--dry-run` to preview. |
75
+ | `CONTRACT.MARKER_MISMATCH` | `db verify` (finding, exit 4) | The marker disagrees with the contract hash (**Postgres:** `prisma_contract.marker`; **Mongo:** `_prisma_migrations`). The DB is at a different contract version than the code thinks. Either run a migration forward, or if the DB is correct and the marker is stale after a manual fix-up — run `db sign`. |
76
+ | `CONTRACT.MARKER_MISSING` | `db verify` (finding, exit 4), runtime startup (warning) | The DB has no marker yet. Run `prisma db init --db <url>` to baseline an empty database, or `db update --db <url>` to apply the current contract directly. |
76
77
 
77
78
  ## Decision — which path do you take?
78
79
 
@@ -143,7 +144,7 @@ Canonical detail: [Migration System § Contract resolution through the snapshot
143
144
 
144
145
  ## Workflow — `db update` (quick path)
145
146
 
146
- The concept: `db update` resolves the destination (`emitted contract`) against the live DB and applies the difference. Preview with `--dry-run`. Destructive ops prompt interactively unless you pass `-y` or `--no-interactive`. The path excludes operations of the `data` class entirely — if the diff requires a data transform, `db update` fails with a planning error and you switch to `migration plan` to author the transform.
147
+ The concept: `db update` resolves the destination (`emitted contract`) against the live DB and applies the difference. Preview with `--dry-run`. Destructive ops need consent: interactively the command asks you to type the database name; with `--no-interactive` (CI) it reads `--confirm <database>` instead, and refuses with `MIGRATION.DESTRUCTIVE_CHANGES` if neither is given. `--yes` accepts prompt defaults and never grants this consent. The path excludes operations of the `data` class entirely — if the diff requires a data transform, `db update` fails with a planning error and you switch to `migration plan` to author the transform.
147
148
 
148
149
  Run after a contract edit:
149
150
 
@@ -167,7 +168,7 @@ The JSON contains `plan.operations[]` with each `operationClass`, plus (in apply
167
168
 
168
169
  ## Workflow — `migration plan` + `db migrate` (formal path)
169
170
 
170
- The concept: `migration plan` writes a new migration package on disk. If the planner needed any data transforms, the package is *pending* — `migration.ts` holds `placeholder(...)` calls until you fill them in. `db migrate` runs every pending package in graph order, transactionally.
171
+ The concept: `migration plan` writes a new migration package on disk. If the planner needed any data transforms, the package is *pending* — `migration.ts` holds `placeholder(...)` calls until you fill them in. `db migrate` runs every pending package in graph order — on Postgres inside one transaction for the whole run; on Mongo op by op with verify-gated marker advancement (see *Apply atomicity* above).
171
172
 
172
173
  Plan a change:
173
174
 
@@ -183,11 +184,11 @@ Read the result. The JSON shape exposes the queryable signals:
183
184
  - `operations[].operationClass` — for spotting `destructive` and `data` ops.
184
185
  - `preview.statements` — family-agnostic textual preview.
185
186
 
186
- Inspect the package:
187
+ Inspect the package (the `<target>` positional is required — a directory name, hash or hash prefix, ref, or path):
187
188
 
188
189
  ```bash
189
- pnpm prisma migration show
190
190
  pnpm prisma migration show <dirName-or-migrationHash-prefix>
191
+ pnpm prisma migration show migrations/app/20260515T1200_add_user_email
191
192
  ```
192
193
 
193
194
  `migration show` displays a single migration package. To see the ordered list of migrations that would run — across all contract spaces — use `db migrate --show`:
@@ -222,22 +223,28 @@ The scaffold the planner emits looks like:
222
223
 
223
224
  ```typescript
224
225
  // migrations/app/20260515T1200_add_user_name/migration.ts
226
+ import { col, Migration, MigrationCLI, placeholder } from '@internal/postgres/migration';
227
+ import type { Contract as End } from '../../snapshots/93f07d1b…c9e1e5a2/contract';
225
228
  import endContract from '../../snapshots/93f07d1b…c9e1e5a2/contract.json' with { type: 'json' };
226
- import { Migration, MigrationCLI, addColumn, placeholder } from '@internal/postgres/migration';
229
+ import type { Contract as Start } from '../../snapshots/f62a4154…d07dddc/contract';
230
+ import startContract from '../../snapshots/f62a4154…d07dddc/contract.json' with { type: 'json' };
231
+
232
+ export default class M extends Migration<Start, End> {
233
+ override readonly startContractJson = startContract;
234
+ override readonly endContractJson = endContract;
227
235
 
228
- export default class M extends Migration {
229
236
  override get operations() {
230
237
  return [
231
- addColumn('public', 'user', {
232
- name: 'name',
233
- typeSql: 'text',
234
- defaultSql: '',
235
- nullable: true,
238
+ this.addColumn({
239
+ schema: 'public',
240
+ table: 'user',
241
+ column: col('name', 'text', { codecRef: { codecId: 'pg/text@1' } }),
236
242
  }),
237
- this.dataTransform(endContract, 'backfill user.name', {
238
- check: () => placeholder('backfill user.name:check'),
239
- run: () => placeholder('backfill user.name:run'),
243
+ this.dataTransform(endContract, 'backfill-user-name', {
244
+ check: () => placeholder('backfill-user-name:check'),
245
+ run: () => placeholder('backfill-user-name:run'),
240
246
  }),
247
+ this.setNotNull({ schema: 'public', table: 'user', column: 'name' }),
241
248
  ];
242
249
  }
243
250
  }
@@ -245,29 +252,38 @@ export default class M extends Migration {
245
252
  MigrationCLI.run(import.meta.url, M);
246
253
  ```
247
254
 
255
+ (`examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.ts` is a committed rendered package to compare against.)
256
+
248
257
  Replace both `placeholder(...)` calls with query-plan closures built from `endContract`. The `check` closure must return a **rowset query whose presence of any row signals "work remains"** — conventionally `<table>.select('id').where(<violation predicate>).limit(1)`. Scalar/aggregate shapes (`count(*)`, `bool_and(...)`) silently break the contract: the runner wraps `check` twice (`EXISTS(...)` for precheck, `NOT EXISTS(...)` for postcheck), and a query that always returns one row makes `EXISTS` always true and `NOT EXISTS` always false.
249
258
 
250
- Build the query builder against `endContract` so the storage hashes line up — using a different contract reference raises `PN-MIG-2005`. The filled-in shape (the rendered scaffold above with `placeholder(...)` calls replaced; if you need an extra factory like `setNotNull`, add it to the *existing* `@internal/postgres/migration` import line rather than authoring a second import). See `references/queries.md` for the surrounding `db` setup:
259
+ Build the query builder against `endContract` so the storage hashes line up — using a different contract reference raises `MIGRATION.DATA_TRANSFORM_CONTRACT_MISMATCH`. The cheapest way to get a typed SQL builder over the end contract is the façade itself: `postgres<End>({ contractJson: endContract })` connects lazily, so constructing it inside `migration.ts` opens no connection; its `sql` is the builder and its `contract` is the validated contract to hand to `this.dataTransform`. The filled-in shape is the rendered scaffold above with only the two `placeholder(...)` arrows replaced (the operation list, including the `this.setNotNull({...})` the planner rendered after the transform, stays as rendered):
251
260
 
252
261
  ```typescript
262
+ import { col, Migration, MigrationCLI } from '@internal/postgres/migration';
263
+ import postgres from '@internal/postgres/runtime';
264
+ import type { Contract as End } from '../../snapshots/93f07d1b…c9e1e5a2/contract';
253
265
  import endContract from '../../snapshots/93f07d1b…c9e1e5a2/contract.json' with { type: 'json' };
254
- import { Migration, MigrationCLI, addColumn, setNotNull } from '@internal/postgres/migration';
255
- import { db } from './db'; // sql({ context: createExecutionContext({ contract: endContract, ... }) })
266
+ import type { Contract as Start } from '../../snapshots/f62a4154…d07dddc/contract';
267
+ import startContract from '../../snapshots/f62a4154…d07dddc/contract.json' with { type: 'json' };
268
+
269
+ const { sql: db, contract } = postgres<End>({ contractJson: endContract });
270
+
271
+ export default class M extends Migration<Start, End> {
272
+ override readonly startContractJson = startContract;
273
+ override readonly endContractJson = endContract;
256
274
 
257
- export default class M extends Migration {
258
275
  override get operations() {
259
276
  return [
260
- addColumn('public', 'user', {
261
- name: 'name',
262
- typeSql: 'text',
263
- defaultSql: '',
264
- nullable: true,
277
+ this.addColumn({
278
+ schema: 'public',
279
+ table: 'user',
280
+ column: col('name', 'text', { codecRef: { codecId: 'pg/text@1' } }),
265
281
  }),
266
- this.dataTransform(endContract, 'backfill user.name', {
267
- check: () => db.users.select('id').where((f, fns) => fns.eq(f.name, null)).limit(1),
268
- run: () => db.users.update({ name: '' }).where((f, fns) => fns.eq(f.name, null)),
282
+ this.dataTransform(contract, 'backfill-user-name', {
283
+ check: () => db.public.user.select('id').where((f, fns) => fns.eq(f.name, null)).limit(1),
284
+ run: () => db.public.user.update({ name: '' }).where((f, fns) => fns.eq(f.name, null)),
269
285
  }),
270
- setNotNull('public', 'user', 'name'),
286
+ this.setNotNull({ schema: 'public', table: 'user', column: 'name' }),
271
287
  ];
272
288
  }
273
289
  }
@@ -285,17 +301,15 @@ Self-emit regenerates `ops.json` and recomputes `migrationHash` in `migration.js
285
301
 
286
302
  ### Mongo
287
303
 
288
- Mongo `dataTransform` operations take `{ check, run }` objects whose `source` / `run` return Mongo query-plan shapes (often `RawAggregateCommand` / `RawUpdateManyCommand` from `@internal/mongo-query-ast/execution`). The planner may leave `placeholder(...)` inside those sources until you fill them. Every rendered `migration.ts` includes `describe()` bookends (`from` / `to` contract hashes) the Postgres examples above omit them for brevity. Import factories from `@internal/target-mongo/migration`:
304
+ Mongo `dataTransform` operations are free factories taking `{ check, run }` objects whose `source` / `run` return Mongo query-plan shapes (often `RawAggregateCommand` / `RawUpdateManyCommand` from `@prisma/orm-mongo/query-ast/execution`). The planner may leave `placeholder(...)` inside those sources until you fill them. A rendered package binds its bookends through `startContractJson` / `endContractJson` exactly as on Postgres; a hand-authored `migration new` package may instead override `describe()` with the `from` / `to` hashes from its `migration.json`, as below. Everything comes from one import:
289
305
 
290
306
  ```typescript
291
- import { MigrationCLI } from '@internal/cli/migration-cli';
292
- import { Migration } from '@internal/family-mongo/migration';
293
- import { createIndex, dataTransform } from '@internal/target-mongo/migration';
294
- import { RawAggregateCommand, RawUpdateManyCommand } from '@internal/mongo-query-ast/execution';
307
+ import { createIndex, dataTransform, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration';
308
+ import { RawAggregateCommand, RawUpdateManyCommand } from '@prisma/orm-mongo/query-ast/execution';
295
309
 
296
310
  class M extends Migration {
297
311
  override describe() {
298
- return { from: '<hex>', to: '<hex>', labels: ['normalize-names'] };
312
+ return { from: '<hex>', to: '<hex>' };
299
313
  }
300
314
 
301
315
  override get operations() {
@@ -340,20 +354,21 @@ The concept: the same `Migration` class shape lets you author operations directl
340
354
  pnpm prisma migration new --name <snake_slug>
341
355
  ```
342
356
 
343
- Add factory names to the framework-rendered import line for your target (Postgres: `@internal/postgres/migration`; Mongo: `@internal/target-mongo/migration`). Browse with `--help` and the import list the renderer emitted.
357
+ On Postgres the operations are **methods on the `Migration` base class**, each taking one options object (`this.addColumn({ schema, table, column })`); only helpers such as `col(...)` and `rawSql(...)` are imported, on the rendered `@internal/postgres/migration` line. On Mongo the operations are **free factories** imported from `@prisma/orm-mongo/target/migration`. The authoritative list for either target is the base class / module's declaration file in your `node_modules`.
344
358
 
345
- **Postgres** factories (representative set):
359
+ **Postgres** operations (representative set, all `this.<name>({...})`):
346
360
 
347
361
  - Tables: `createTable`, `dropTable`.
348
- - Columns: `addColumn`, `dropColumn`, `alterColumnType`, `setNotNull`, `dropNotNull`, `setDefault`, `dropDefault`.
349
- - Constraints: `addPrimaryKey`, `addForeignKey`, `addUnique`, `dropConstraint`.
350
- - Indexes: `createIndex`, `dropIndex`.
351
- - Enums: `createEnumType`, `addEnumValues`, `renameType`, `dropEnumType`.
352
- - Dependencies: `createSchema`, `createExtension`, `installExtension`.
353
- - Raw escape hatch: `rawSql({ id, label, operationClass, target, precheck, execute, postcheck, ... })`.
354
- - Data transforms: `this.dataTransform(endContract, name, { check, run })` (instance method, not a free factory).
362
+ - Columns: `addColumn` (`column: col(name, nativeType, { codecRef })`), `dropColumn`, `alterColumnType`, `setNotNull`, `dropNotNull`, `setDefault`, `dropDefault`.
363
+ - Constraints: `addPrimaryKey`, `addForeignKey`, `addUnique`, `addCheckConstraint`, `renameCheckConstraint`, `dropCheckConstraint`, `dropConstraint`.
364
+ - Indexes: `createIndex`, `renameIndex`, `dropIndex`.
365
+ - Enums: `createNativeEnumType`, `addNativeEnumValue`, `dropNativeEnumType`.
366
+ - Row-level security: `enableRowLevelSecurity`, `disableRowLevelSecurity`, `createRlsPolicy`, `renameRlsPolicy`, `dropRlsPolicy`.
367
+ - Dependencies: `createSchema`, `installExtension`.
368
+ - Free helpers on the import line: `col`, `primaryKey`, `unique`, `foreignKey`, `checkExpression`, `lit`, `fn` (column and constraint descriptors), `createExtension`, and the raw escape hatch `rawSql({ id, label, operationClass, target, precheck, execute, postcheck, ... })`.
369
+ - Data transforms: `this.dataTransform(endContract, name, { check, run })`.
355
370
 
356
- **Mongo** factories (from `@internal/target-mongo/migration`):
371
+ **Mongo** factories (from `@prisma/orm-mongo/target/migration`):
357
372
 
358
373
  - Collections: `createCollection`, `dropCollection`, `validatedCollection`, `setValidation`.
359
374
  - Indexes: `createIndex`, `dropIndex`.
@@ -380,7 +395,7 @@ The concept: `db verify` is a **standalone diagnostic** — not a routine step a
380
395
  - Following manual SQL or ad-hoc edits outside Prisma 8.
381
396
  - When restoring a database from backup.
382
397
  - If a `db migrate` fails or partially applies (especially on Mongo, where DDL is resumable rather than transaction-wrapped).
383
- - When `PN-RUN-3002` / `PN-RUN-3001` surfaces at runtime or from another command.
398
+ - When `CONTRACT.MARKER_MISMATCH` / `CONTRACT.MARKER_MISSING` surfaces at runtime or from another command.
384
399
 
385
400
  Modes:
386
401
 
@@ -393,7 +408,7 @@ Modes:
393
408
  pnpm prisma db verify --db $DATABASE_URL
394
409
  ```
395
410
 
396
- On mismatch, the error envelope names the failure mode (`PN-RUN-3002` hash mismatch, `PN-RUN-3001` marker missing, target mismatch, schema issues with structured paths).
411
+ `db verify` exits `0` when everything matches, `4` when it ran and found something, and `2` only when it could not run. Findings ride the completed envelope as `error` diagnostics: `CONTRACT.MARKER_MISMATCH`, `CONTRACT.MARKER_MISSING`, `CONTRACT.TARGET_MISMATCH`, `CONTRACT.SCHEMA_VERIFICATION_FAILED` (with `meta.issues` naming the drifted paths).
397
412
 
398
413
  ## Workflow — Re-sign the marker
399
414
 
@@ -421,9 +436,9 @@ Use `db verify` to confirm which side is wrong, then re-run it after either bran
421
436
 
422
437
  ## Workflow — Recover from a partially-applied migration
423
438
 
424
- The concept: on **Postgres**, each migration applies inside a transaction — a mid-migration failure rolls back and the marker stays at the previous migration's `to` hash. On **Mongo**, DDL is resumable with verify-gated marker advancement; diagnose with `db verify` / `db schema`, fix the failed package's `migration.ts`, self-emit, and re-run `db migrate`.
439
+ The concept: on **Postgres**, the whole `db migrate` run is one transaction — a failure anywhere rolls back every migration the run had applied, and the marker stays where it was before the command. On **Mongo**, DDL is resumable with verify-gated marker advancement; diagnose with `db verify` / `db schema`, fix the failed package's `migration.ts`, self-emit, and re-run `db migrate`.
425
440
 
426
- Failures that *can* leak partial state include: Postgres `rawSql(...)` steps outside the transaction wrapper, Mongo DDL that partially applied before verify failed, or external side-effects (calls out to other systems from a `run` closure).
441
+ Failures that *can* leak partial state: Mongo DDL that partially applied before verify failed, and external side-effects (calls out to other systems from a `run` closure). On Postgres nothing runs outside the transaction — `rawSql(...)` steps are ordinary steps inside it and roll back with the rest.
427
442
 
428
443
  Diagnose:
429
444
 
@@ -467,20 +482,21 @@ Routing:
467
482
  - Re-shape the migration via `migration plan` and hand-edit `migration.ts` to preserve the data (e.g. copy-to-new-column, then drop), or
468
483
  - Skip the destructive operation by reverting the contract change.
469
484
 
470
- In non-interactive contexts (CI, `--no-interactive`, `--json`), the destructive-op response is returned as a structured error — `meta.destructiveOperations[]` lists what would have been dropped. Re-run with `-y` to auto-accept, or address each operation individually.
485
+ Interactively, consent is typing the database name back (the prompt names it). In non-interactive contexts (CI, `--no-interactive`), the destructive-op response is returned as `MIGRATION.DESTRUCTIVE_CHANGES` — `meta.destructiveOperations[]` lists what would have been dropped. Re-run with `--confirm <database>` to grant consent (`--yes` does not), or address each operation individually.
471
486
 
472
487
  ## Common Pitfalls
473
488
 
474
489
  1. **Using `db update` against shared or production databases.** Never. The change leaves no migration history. Use `migration plan` + `db migrate`.
475
- 2. **Skipping a data transform.** Leaving `placeholder(...)` in `migration.ts` makes the next `db migrate` throw `PN-MIG-2001`. Fill every placeholder slot and self-emit.
490
+ 2. **Skipping a data transform.** Leaving `placeholder(...)` in `migration.ts` makes the next `db migrate` throw `MIGRATION.UNFILLED_PLACEHOLDER`. Fill every placeholder slot and self-emit.
476
491
  3. **Editing `ops.json` directly.** It's the canonical artifact, not the authoring source. Edit `migration.ts`, then self-emit.
477
492
  4. **Forgetting to self-emit after editing `migration.ts`.** The next `db migrate` either uses the stale `ops.json` (if you only added comments) or fails with `MIGRATION.HASH_MISMATCH` (if you changed operations). Always self-emit.
478
493
  5. **Routine `db verify` after a successful `db update` or `db migrate`.** Redundant on the happy path — reserve `db verify` for drift diagnosis (manual edits, restore, failed `db migrate`).
479
494
  6. **Aggregate `check` closure in Postgres `this.dataTransform`.** Returning `count(*)` or `bool_and(...)` breaks the precheck/postcheck contract — both sides resolve to constants. Use a rowset shape: `select('id').where(<violation>).limit(1)`.
480
- 7. **Two contract references in one migration.** Building a query plan against a different contract than the one passed to `this.dataTransform(endContract, ...)` raises `PN-MIG-2005`. Always import `endContract` once at module scope and use the same reference.
495
+ 7. **Two contract references in one migration.** Building a query plan against a different contract than the one passed to `this.dataTransform(endContract, ...)` raises `MIGRATION.DATA_TRANSFORM_CONTRACT_MISMATCH`. Always import `endContract` once at module scope and use the same reference.
496
+ 11. **Calling Postgres operations as free functions.** `addColumn('public', 'user', {...})` does not exist as an import; the operations are `this.addColumn({ schema, table, column })` and friends on the `Migration` base class, with `col(...)` building the column. Only `col`, `rawSql`, `placeholder`, `Migration`, and `MigrationCLI` are imported.
481
497
  8. **Renaming and expecting the planner to detect it (Postgres).** Prisma 8 has no in-contract rename hint today; the planner emits a destructive drop+add. Hand-edit `migration.ts` to rewrite the destructive op as a `rawSql({ ... })` that issues `ALTER TABLE ... RENAME COLUMN ...` (or use the two-migration keep / backfill / drop pattern), then self-emit. See `references/contract.md` § *Edit a field — rename*.
482
498
  9. **Planning with no `db` ref and no `--from` in a project that already has migrations.** The origin falls through to the empty database, which would make the plan a full-create migration; `migration plan` refuses with `MIGRATION.PLAN_ORIGIN_UNKNOWN` rather than writing it. Pick the exit that matches your intent — the error lists them, and `references/migration-model.md` § *The trap* explains which to choose.
483
- 10. **Hand-authoring `migration.ts` from a blank file, or rewriting the rendered import line.** Migration files are framework-rendered — let `prisma migration plan` (or `migration new`) render the package, then edit only the holes the framework leaves for you. On Postgres leave the rendered `@internal/postgres/migration` (or `@internal/sqlite/migration`) import path alone; on Mongo use `@internal/family-mongo/migration` + `@internal/target-mongo/migration` as rendered. Add symbols to the existing factory import line rather than introducing new import paths.
499
+ 10. **Hand-authoring `migration.ts` from a blank file, or rewriting the rendered import line.** Migration files are framework-rendered — let `prisma migration plan` (or `migration new`) render the package, then edit only the holes the framework leaves for you. On Postgres leave the rendered `@internal/postgres/migration` (or `@internal/sqlite/migration`) import path alone; on Mongo leave `@prisma/orm-mongo/target/migration` as rendered. Add symbols to the existing import line rather than introducing new import paths.
484
500
 
485
501
  ## What Prisma 8 doesn't do yet
486
502
 
@@ -511,7 +527,7 @@ The CLI collects anonymous usage data by default. To opt out, set `PRISMA_NEXT_D
511
527
  - [ ] Contract emitted (`contract.json` + `contract.d.ts` current).
512
528
  - [ ] Chose the right path: `db update` (local dev) vs `migration plan` + `db migrate` (anything shared).
513
529
  - [ ] For `migration plan`: confirmed the output's `from:` line names the intended origin — not `(baseline)` over an existing graph (`references/migration-model.md`).
514
- - [ ] For `migration plan`: ran `migration show` to review before `db migrate`.
530
+ - [ ] For `migration plan`: ran `migration show <dir>` to review before `db migrate`.
515
531
  - [ ] Filled every `placeholder(...)` in `migration.ts` (if any), built against `endContract`.
516
532
  - [ ] `check` closures are rowset queries, not scalar aggregates.
517
533
  - [ ] Self-emitted (`node migrations/app/<dir>/migration.ts`) after editing the TS.
@@ -519,4 +535,4 @@ The CLI collects anonymous usage data by default. To opt out, set `PRISMA_NEXT_D
519
535
  - [ ] Used `db verify` only when diagnosing drift — not as a routine post-apply step.
520
536
  - [ ] Did NOT use `db update` against a shared or production database.
521
537
  - [ ] Did NOT edit `ops.json` directly.
522
- - [ ] Did NOT skip a destructive-op prompt without inspecting `meta.destructiveOperations[]`.
538
+ - [ ] Did NOT skip a destructive-op prompt without inspecting `meta.destructiveOperations[]`; granted consent with the database name (or `--confirm <database>`), not `--yes`.
@@ -9,7 +9,7 @@ Shared concepts (result consumption, script teardown, cross-target pitfalls, cap
9
9
  **Mongo** (`mongo<Contract>(...)` from `@internal/mongo/runtime`):
10
10
 
11
11
  - **`db.orm.<root>`** — ORM, lowercased plural contract root (`db.orm.users`, `db.orm.posts`). Same fluent chaining; `.where({ field: value })` object equality is the idiomatic filter form.
12
- - **`db.query`** — typed aggregation-pipeline builder. Start with `db.query.from('<root>')`, chain `.match(...)` / `.project(...)` / `.group(...)` / `.lookup(...)`, terminal with `.build()`. Execute via `(await db.runtime()).execute(plan)`.
12
+ - **`db.query`** — typed aggregation-pipeline builder. Start with `db.query.from('<root>')`, chain `.match(...)` / `.project(...)` / `.group(...)` / `.lookup(...)`, terminal with `.build()`. Run via `(await db.runtime()).query(plan)` for anything that returns documents; `execute(plan)` is only for a write you want an affected count from, and it throws `RUNTIME.MONGO_STATISTICS_UNSUPPORTED` on a find or aggregate.
13
13
 
14
14
  Reach for the ORM first; drop to `db.query` when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app.
15
15
 
@@ -49,7 +49,7 @@ const recent = await db.orm.posts
49
49
 
50
50
  **`.where(...)`** accepts a plain object whose keys are model field names and values are compared with equality (codec-aware — `ObjectId` fields accept string ids from the contract). Chain multiple `.where({ ... })` calls to AND-compose filters.
51
51
 
52
- For operators the object form doesn't cover (`.in([...])`, range comparisons, nested logic), pass a `MongoFilterExpr` — today that means importing filter helpers from `@internal/mongo-query-ast/execution` (a façade-completeness gap; see *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md)). Prefer the object form whenever equality suffices.
52
+ For operators the object form doesn't cover (`.in([...])`, range comparisons, nested logic), pass a `MongoFilterExpr` — today that means importing filter helpers from `@prisma/orm-mongo/query-ast/execution` (a façade-completeness gap; see *What Prisma 8 doesn't do yet* in [`queries.md`](./queries.md)). Prefer the object form whenever equality suffices.
53
53
 
54
54
  **Polymorphic roots.** When the contract declares variants on a model, narrow before querying:
55
55
 
@@ -123,7 +123,7 @@ await db.orm.users.where({ email: 'alice@example.com' }).upsert({
123
123
  The Mongo ORM does not expose `.aggregate(...)` / `.groupBy(...)`. Express aggregations through **`db.query`** — the pipeline builder — with `.group(...)` and accumulator helpers:
124
124
 
125
125
  ```typescript
126
- import { acc } from '@internal/mongo-query-builder';
126
+ import { acc } from '@prisma/orm-mongo/query-builder';
127
127
 
128
128
  const runtime = await db.runtime();
129
129
  const plan = db.query
@@ -137,18 +137,18 @@ const plan = db.query
137
137
  .sort({ postCount: -1 })
138
138
  .build();
139
139
 
140
- const byKind = await runtime.execute(plan);
140
+ const byKind = await runtime.query(plan);
141
141
  ```
142
142
 
143
- Import `acc` and expression helpers (`fn`) from `@internal/mongo-query-builder` when building computed pipeline stages.
143
+ Import `acc` and expression helpers (`fn`) from `@prisma/orm-mongo/query-builder` when building computed pipeline stages (as `examples/mongo-demo/src/server.ts` does).
144
144
 
145
145
  ## Workflow — Query builder (`db.query`)
146
146
 
147
- The concept: `db.query.from('<root>')` starts a typed aggregation-pipeline chain. Terminal methods produce a `MongoQueryPlan`; execute it through the runtime:
147
+ The concept: `db.query.from('<root>')` starts a typed aggregation-pipeline chain. Terminal methods produce a `MongoQueryPlan`; run it through the runtime with `query(plan)` (an `AsyncIterableResult` of documents — `await` it for an array):
148
148
 
149
149
  ```typescript
150
150
  // src/queries/analytics.ts
151
- import { acc, fn } from '@internal/mongo-query-builder';
151
+ import { acc, fn } from '@prisma/orm-mongo/query-builder';
152
152
  import { db } from '../prisma/db';
153
153
 
154
154
  const runtime = await db.runtime();
@@ -161,7 +161,7 @@ const plan = db.query
161
161
  .limit(10)
162
162
  .project('title', 'authorId', 'createdAt')
163
163
  .build();
164
- const recent = await runtime.execute(plan);
164
+ const recent = await runtime.query(plan);
165
165
 
166
166
  // Cross-collection join ($lookup).
167
167
  const withAuthor = db.query
@@ -175,26 +175,26 @@ const withAuthor = db.query
175
175
  .as('author'),
176
176
  )
177
177
  .build();
178
- const rows = await runtime.execute(withAuthor);
178
+ const rows = await runtime.query(withAuthor);
179
179
  ```
180
180
 
181
181
  **Filters — `.match(...)`.** Callback form: `.match((f) => f.status.eq('active'))`. Filters AND-compose across chained `.match(...)` calls. Field accessors support property access (`f.email`), callable dot paths (`f('address.city').eq('NYC')`), and `f.rawPath('path')` for migration/backfill paths outside the current contract.
182
182
 
183
- **Write terminals on the builder.** After `.from('users')` or `.from('users').match(...)`, use insert/update/delete terminals:
183
+ **Write terminals on the builder.** After `.from('users')` or `.from('users').match(...)`, use insert/update/delete terminals. Write plans run through `query(plan)` too (one result row carrying the driver's response — the inserted ids, or the document `findOneAndUpdate` returns); reach for `execute(plan)` only on an `update*` / `delete*` plan when all you want is the affected count (any other command kind throws `RUNTIME.MONGO_STATISTICS_UNSUPPORTED`):
184
184
 
185
185
  ```typescript
186
- await runtime.execute(
186
+ const inserted = await runtime.query(
187
187
  db.query.from('users').insertOne({ name: 'Alice', email: 'a@e.com', bio: null }),
188
188
  );
189
189
 
190
- await runtime.execute(
190
+ const { affectedRows } = await runtime.execute(
191
191
  db.query
192
192
  .from('users')
193
193
  .match((f) => f.name.eq('Alice'))
194
194
  .updateMany((f) => [f.bio.set('filled')]),
195
195
  );
196
196
 
197
- await runtime.execute(
197
+ const [updated] = await runtime.query(
198
198
  db.query
199
199
  .from('users')
200
200
  .match((f) => f.email.eq('a@e.com'))
@@ -204,7 +204,7 @@ await runtime.execute(
204
204
 
205
205
  Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.pull`, …). Pipeline-style updates use `f.stage.set(...)` inside an aggregation chain, then `.updateMany()` with no callback.
206
206
 
207
- **Plans vs ORM.** The ORM's `.create` / `.update` / `.all` issue queries directly. Don't pass ORM collections to `runtime.execute` — that entry point is for `db.query` plans (and migration/runtime internals).
207
+ **Plans vs ORM.** The ORM's `.create` / `.update` / `.all` issue queries directly. Don't pass ORM collections to `runtime.query` / `runtime.execute` — those entry points are for `db.query` plans (and migration/runtime internals).
208
208
 
209
209
  ## Common Pitfalls (Mongo)
210
210
 
@@ -215,7 +215,7 @@ Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.p
215
215
  5. **Expecting Postgres-style lambda `.where((u) => u.email.eq(...))` on ORM.** Prefer object equality `.where({ email: '...' })`; richer operators need `MongoFilterExpr` helpers (façade gap today).
216
216
  6. **Expecting `db.transaction(...)`.** The Mongo façade does not expose it today. Multi-document atomicity requires MongoDB transactions on a replica set via the driver — not yet wrapped in the Prisma 8 façade. Route to *What Prisma 8 doesn't do yet* / `references/feedback.md` if the user needs this.
217
217
  7. **Trying to use `db.sql`.** There is no `db.sql` on Mongo.
218
- 8. **Trying to `db.execute(plan)` directly.** Execute query-builder plans via `(await db.runtime()).execute(plan)`.
218
+ 8. **Trying to `db.execute(plan)` directly, or reading documents with `execute`.** Run query-builder plans via `(await db.runtime()).query(plan)`. `execute(plan)` resolves statistics only and throws `RUNTIME.MONGO_STATISTICS_UNSUPPORTED` for a find or aggregate.
219
219
  9. **Expecting ORM `.aggregate(...)` / `.groupBy(...)`.** Use `db.query.from(...).group(...).build()` instead.
220
220
 
221
221
  ## Reference Files
@@ -230,7 +230,7 @@ Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.p
230
230
  - [ ] Used lowercased plural ORM roots (`db.orm.users`, not `db.orm.User`).
231
231
  - [ ] Chose the right lane (ORM by default; `db.query` for shapes the ORM doesn't express).
232
232
  - [ ] Used `.where({ ... }).first()` for single-row reads — not `.all()`.
233
- - [ ] Executed query-builder plans via `(await db.runtime()).execute(plan)`.
233
+ - [ ] Ran query-builder plans via `(await db.runtime()).query(plan)`; used `execute(plan)` only for an affected count on a write.
234
234
  - [ ] For aggregations, used `db.query.from(...).group(...)` rather than a non-existent ORM `.aggregate(...)`.
235
235
  - [ ] Did NOT confabulate `db.transaction`, `db.sql`, or ORM `.aggregate(...)` — routed to *What Prisma 8 doesn't do yet* / `references/feedback.md` instead.
236
236
  - [ ] Did NOT use the lower-level builder for something the ORM cleanly expresses.