@prisma/orm-postgres 8.0.0-rc.17-dev.36 → 8.0.0-rc.17-dev.38

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/orm-postgres",
3
- "version": "8.0.0-rc.17-dev.36",
3
+ "version": "8.0.0-rc.17-dev.38",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -10,10 +10,10 @@
10
10
  "skills"
11
11
  ],
12
12
  "dependencies": {
13
- "@prisma/orm-family-sql": "8.0.0-rc.17-dev.36",
14
- "@prisma/orm-framework": "8.0.0-rc.17-dev.36",
15
- "@prisma/orm-target-postgres": "8.0.0-rc.17-dev.36",
16
- "@prisma/orm-toolchain": "8.0.0-rc.17-dev.36",
13
+ "@prisma/orm-family-sql": "8.0.0-rc.17-dev.38",
14
+ "@prisma/orm-framework": "8.0.0-rc.17-dev.38",
15
+ "@prisma/orm-target-postgres": "8.0.0-rc.17-dev.38",
16
+ "@prisma/orm-toolchain": "8.0.0-rc.17-dev.38",
17
17
  "@types/node": "26.1.2",
18
18
  "@types/pg": "8.20.4",
19
19
  "arktype": "^2.2.2",
@@ -22,9 +22,9 @@
22
22
  "tinyglobby": "^0.2.17"
23
23
  },
24
24
  "devDependencies": {
25
- "@internal/postgres": "8.0.0-rc.17-dev.36",
26
- "@repo/tsconfig": "8.0.0-rc.17-dev.36",
27
- "@repo/tsdown": "8.0.0-rc.17-dev.36",
25
+ "@internal/postgres": "8.0.0-rc.17-dev.38",
26
+ "@repo/tsconfig": "8.0.0-rc.17-dev.38",
27
+ "@repo/tsdown": "8.0.0-rc.17-dev.38",
28
28
  "tsdown": "0.22.14",
29
29
  "typescript": "5.9.3"
30
30
  },
@@ -15,7 +15,7 @@ description: >-
15
15
  ORM 7 or earlier (schema.prisma + @prisma/client).
16
16
  metadata:
17
17
  library: '@prisma/orm-postgres'
18
- library_version: '8.0.0-rc.17-dev.36'
18
+ library_version: '8.0.0-rc.17-dev.38'
19
19
  version: '2026-09-12'
20
20
  ---
21
21
 
@@ -30,8 +30,8 @@ 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. Before an apply it asks what each operation that would lose data means, answered by `--delete <subject>` or `--rename <subject>:<new name>`, and whether each operation that would widen who can read or write a model's rows may run, answered by `--allow <Model>` (see *Answer the data-loss questions* below). **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
- - **`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>/`. A plan that would lose data is written only once `--delete` or `--rename` says what each such operation means. If any operation needs a data transform, the package's `migration.ts` contains `placeholder(...)` calls you fill in.
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. Before an apply it asks what each operation that would lose data means, answered by `--delete <subject>` or `--rename <subject>:<new name>`, and whether each operation that would widen who can read or write a model's rows may run, answered by `--allow <Model>` (see *Answer the data-loss questions* below). **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. It refuses `--convert` and `--backfill` (`MIGRATION.STATEMENT_NEEDS_MIGRATION_FILE`) and prints the `migration plan` command to run instead. Use only against a database that has no shared history with anyone else (your local dev DB).
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>/`. A plan that would lose data is written only once `--delete`, `--rename` or, for a type change, `--convert` says what each such operation means. The package's `migration.ts` contains `placeholder(...)` calls only where `--convert` or `--backfill` asked for a slot (see *Type changes and new required fields*).
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>/`):
37
37
  - `migration.json` — manifest (metadata + `migrationHash`).
@@ -87,7 +87,8 @@ Treat the rendered import lines as framework-managed on both targets:
87
87
  | Local dev, schema in flux | `db update` | Fast, interactive, no migration files. |
88
88
  | Shared branch with other developers | `migration plan` + `db migrate` | Replayable, reviewable, content-hashed. |
89
89
  | Anything reaching production | `migration plan` + `db migrate` | Production must run a reviewed, hashed migration. |
90
- | Adding a column that needs a backfill | `migration plan` (writes `placeholder`), edit `migration.ts`, self-emit, then `db migrate` | `db update` does not author data transforms; the formal path does. |
90
+ | Adding a column that needs a backfill | `migration plan --backfill Model.field` (writes `placeholder`), edit `migration.ts`, self-emit, then `db migrate` | `db update` does not author data transforms; the formal path does. Without `--backfill`, existing rows get a temporary value. |
91
+ | Changing a field's type so values may not survive | `migration plan --convert Model.field`, fill `using`, self-emit, `db migrate` | Or `--delete Model.field` to let the database cast; the cast may cut or round values (Postgres cuts a longer text to a shorter `VarChar`), and a value it cannot cast fails the apply. |
91
92
  | Recovering from drift (DB diverged from contract) | `db sign` after manual fix, *or* `migration plan` if PN can plan the fix | Depends on which side is right. See *Recover from drift* below. |
92
93
 
93
94
  ## Dev → ship transition (the `db` ref pattern)
@@ -222,7 +223,7 @@ The concept: the planner can detect *that* a data transform is needed but not *w
222
223
 
223
224
  ### Postgres
224
225
 
225
- The planner can detect *that* a data transform is needed (e.g. backfilling a new `NOT NULL` column with no default) but not *what* it should do. You fill `check` and `run` closures with real query plans built against `endContract`.
226
+ `--backfill` asks the planner for a data transform it cannot write itself, for example to fill a new required column `name` on a table with rows: `pnpm prisma migration plan --name add_user_name --backfill User.name`. Without the flag the rows get a temporary value instead. Each slot is named by the field and the part to fill, `User.name:check` and `User.name:run`. You fill `check` and `run` closures with real query plans built against `endContract`.
226
227
 
227
228
  The scaffold the planner emits looks like:
228
229
 
@@ -246,8 +247,9 @@ export default class M extends Migration<Start, End> {
246
247
  column: col('name', 'text', { codecRef: { codecId: 'pg/text@1' } }),
247
248
  }),
248
249
  this.dataTransform(endContract, 'backfill-user-name', {
249
- check: () => placeholder('backfill-user-name:check'),
250
- run: () => placeholder('backfill-user-name:run'),
250
+ backfillsColumn: { schema: 'public', table: 'user', column: 'name' },
251
+ check: () => placeholder('User.name:check'),
252
+ run: () => placeholder('User.name:run'),
251
253
  }),
252
254
  this.setNotNull({ schema: 'public', table: 'user', column: 'name' }),
253
255
  ];
@@ -285,6 +287,7 @@ export default class M extends Migration<Start, End> {
285
287
  column: col('name', 'text', { codecRef: { codecId: 'pg/text@1' } }),
286
288
  }),
287
289
  this.dataTransform(contract, 'backfill-user-name', {
290
+ backfillsColumn: { schema: 'public', table: 'user', column: 'name' },
288
291
  check: () => db.public.user.select('id').where((f, fns) => fns.eq(f.name, null)).limit(1),
289
292
  run: () => db.public.user.update({ name: '' }).where((f, fns) => fns.eq(f.name, null)),
290
293
  }),
@@ -296,12 +299,32 @@ export default class M extends Migration<Start, End> {
296
299
  MigrationCLI.run(import.meta.url, M);
297
300
  ```
298
301
 
302
+ `backfillsColumn` names the column the transform fills. Keep it: when `check` still finds rows after `run`, the apply fails, changes nothing, and counts the rows that still hold NULL, as on SQLite: `1 row of "user" still holds NULL in "name" after its backfill step, so it cannot become required.` The advice then says to make `run` fill every row in that `migration.ts` and re-run `db migrate`. Without `backfillsColumn` the same failure reports only that the postcheck failed.
303
+
304
+ `run` takes one closure, or an array of closures, each returning one query: `run: [() => db.public.user.update(…), () => db.public.post.update(…)]`. It does not take one closure that returns an array.
305
+
299
306
  Self-emit:
300
307
 
301
308
  ```bash
302
309
  node migrations/app/20260515T1200_add_user_name/migration.ts
303
310
  ```
304
311
 
312
+ ### SQLite
313
+
314
+ On SQLite, `--backfill User.name` writes a `dataTransform` with one slot, `User.name:run`, and no `check`; the table rebuild after it makes the column required, and fails, changing nothing, if a row still has no value. `run` is a closure that returns the SQL text of one statement:
315
+
316
+ ```typescript
317
+ dataTransform({
318
+ id: 'data_migration.backfill-User-name',
319
+ label: 'Backfill NULLs in "User"."name" before NOT NULL tightening',
320
+ table: 'User',
321
+ description: 'Backfill NULL name values in User',
322
+ run: () => `UPDATE "User" SET name = 'user ' || id WHERE name IS NULL`,
323
+ }),
324
+ ```
325
+
326
+ Write the statement so it fills every row the field has no value in, then self-emit with `node <package dir>/migration.ts` as on Postgres.
327
+
305
328
  Self-emit regenerates `ops.json` and recomputes `migrationHash` in `migration.json`. The next `db migrate` will see a consistent package.
306
329
 
307
330
  ### Mongo
@@ -351,6 +374,15 @@ MigrationCLI.run(import.meta.url, M);
351
374
 
352
375
  Self-emit the same way: `node <package dir>/migration.ts`.
353
376
 
377
+ ## Type changes and new required fields
378
+
379
+ Postgres and SQLite. `migration plan` cannot see rows, so these rules are about what you state, not what the table holds.
380
+
381
+ - **A type change that can lose values is a question.** Answer it with `--convert User.age` (the plan has a slot for the conversion: `using: placeholder('User.age:convert')` in `this.alterColumnType(...)` on Postgres, the column's copy expression in `this.recreateTable(...)` on SQLite; write SQL over the column, such as `'"age"::integer'`, self-emit, `db migrate`) or `--delete User.age` (the database's own cast runs; it is listed as `accept lossy type change of field "User.age"`). The cast may cut or round values: on Postgres a longer text is cut to a shorter `VarChar` and a number rounded to fewer digits. A value the cast cannot convert fails the apply, changes nothing, and the error names `--convert`. On SQLite the rebuild checks after its copy that every value took the new type: each column listed in `retypedColumns` is checked, as the conversion when it has a copy expression in `copyExpressions` and as SQLite's own cast when it has none, and a column with a copy expression that is not listed is checked as a conversion too. A hand-written rebuild that converts needs only its copy expression. A widening such as `int4` to `int8` asks nothing.
382
+ - **A new required field** gets a temporary value in the existing rows (`''`, `0`, `false`, …) with no question and no placeholder; the output names the field and the value. On SQLite the rebuild writes it in `temporaryValues: { name: sql`''` }`, apart from the conversions in `copyExpressions`. `--backfill User.name` writes a data transform to fill instead: add nullable, fill (`check` and `run` slots on Postgres, `run` on SQLite), then make it required. A new unique, primary-key or foreign-key field, or one whose values come from a value set, gets no temporary value: on a table with rows, use `--backfill`.
383
+ - **An optional field made required** is tightened directly. If rows hold NULL, the apply fails and counts them (`3 rows of "Item" hold NULL in "note", so it cannot become required.`); set the values and re-run, or plan with `--backfill`.
384
+ - **Following failure advice under `db migrate`:** the migration is already written, so the error says to copy any code you wrote in its `migration.ts`, delete its directory, and run the printed `migration plan --from <hash> --name <name> …` command, which keeps the migration's name, together with the other statements you planned it with. Paths in the advice are relative to the directory the command ran from, and the commands it prints repeat the `--config` it was given, so each runs as printed from there.
385
+
354
386
  ## Workflow — Author a migration by hand
355
387
 
356
388
  The concept: the same `Migration` class shape lets you author operations directly when the planner has nothing to plan (a custom data fix, an extension install, a baseline). Even here you don't write the file from scratch — `migration new` renders an empty package for you, and you edit the `operations` getter inside it, then self-emit.
@@ -364,7 +396,7 @@ On Postgres the operations are **methods on the `Migration` base class**, each t
364
396
  **Postgres** operations (representative set, all `this.<name>({...})`):
365
397
 
366
398
  - Tables: `createTable`, `renameTable`, `dropTable`. `renameTable` reads the migration's start and end contracts and returns the table rename plus a rename of every constraint and index named after the old table, so spread it: `...this.renameTable({ table: 'userProfile', to: 'UserProfile' })`.
367
- - Columns: `addColumn` (`column: col(name, nativeType, { codecRef })`), `renameColumn` (also renames the constraints and indexes named after the column, so spread it: `...this.renameColumn({ table: 'User', column: 'email', to: 'emailAddress' })`), `dropColumn`, `alterColumnType` (pass `operationClass: 'widening'` only when every value of the old type converts to the new type unchanged, such as `int4` to `int8`; the default is `'destructive'`), `setNotNull`, `dropNotNull`, `setDefault` (`column: col(name, nativeType, { default: lit(value) or fn(…), codecRef })`, where `fn` takes a SQL expression; `fn`, `checkExpression` and the methods that take a SQL fragment accept a ``sql`...` `` value, which generated files write, or a string; a `sql` value's text is canonicalized like a contract's, a string is kept exactly as written, so do not wrap a committed string in `sql`; `fn('autoincrement()')` on a smallint, integer or bigint column creates or reuses the `{table}_{column}_seq` sequence, attaches it, and starts it past the column's largest value), `dropDefault`.
399
+ - Columns: `addColumn` (`column: col(name, nativeType, { codecRef })`), `renameColumn` (also renames the constraints and indexes named after the column, so spread it: `...this.renameColumn({ table: 'User', column: 'email', to: 'emailAddress' })`), `dropColumn`, `alterColumnType({ schema, table, column, type, using? })` (`type` is the new type as a migration writes it, such as `int8` or `varchar(20)`; `using` is a SQL expression over the column that converts each value, and without it the database casts the column; the class is `'data'` with `using` and `'destructive'` without it; pass `operationClass: 'widening'` only when every value of the old type converts to the new type unchanged, such as `int4` to `int8`), `setNotNull`, `dropNotNull`, `setDefault` (`column: col(name, nativeType, { default: lit(value) or fn(…), codecRef })`, where `fn` takes a SQL expression; `fn`, `checkExpression` and the methods that take a SQL fragment accept a ``sql`...` `` value, which generated files write, or a string; a `sql` value's text is canonicalized like a contract's, a string is kept exactly as written, so do not wrap a committed string in `sql`; `fn('autoincrement()')` on a smallint, integer or bigint column creates or reuses the `{table}_{column}_seq` sequence, attaches it, and starts it past the column's largest value), `dropDefault`.
368
400
  - Constraints: `addPrimaryKey`, `addForeignKey`, `addUnique`, `addCheckConstraint`, `renameConstraint` (`kind`: `primaryKey`, `unique`, `foreignKey` or `checkConstraint`), `renameCheckConstraint`, `dropCheckConstraint`, `dropConstraint`.
369
401
  - Indexes: `createIndex`, `renameIndex`, `dropIndex`.
370
402
  - Enums: `createNativeEnumType`, `addNativeEnumValue`, `dropNativeEnumType`.