@prisma/orm-mongo 8.0.0-rc.4-dev.17 → 8.0.0-rc.5-dev.1

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.
Files changed (64) hide show
  1. package/package.json +11 -10
  2. package/skills/prisma-8/SKILL.md +84 -0
  3. package/skills/prisma-8/references/build.md +142 -0
  4. package/skills/prisma-8/references/contract.md +417 -0
  5. package/skills/prisma-8/references/debug.md +141 -0
  6. package/skills/prisma-8/references/feedback.md +251 -0
  7. package/skills/prisma-8/references/migration-review.md +224 -0
  8. package/skills/prisma-8/references/migrations.md +519 -0
  9. package/skills/prisma-8/references/queries-mongo.md +236 -0
  10. package/skills/prisma-8/references/queries-postgres.md +415 -0
  11. package/skills/prisma-8/references/queries.md +168 -0
  12. package/skills/prisma-8/references/quickstart.md +326 -0
  13. package/skills/prisma-8/references/runtime.md +344 -0
  14. package/skills/prisma-8/references/supabase.md +244 -0
  15. package/skills/prisma-8/references/upgrade-app.md +101 -0
  16. package/skills/prisma-8/references/upgrade-extension.md +105 -0
  17. package/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md +56 -0
  18. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +381 -0
  19. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +202 -0
  20. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts +201 -0
  21. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts +198 -0
  22. package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
  23. package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +339 -0
  24. package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts +229 -0
  25. package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +543 -0
  26. package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
  27. package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
  28. package/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +359 -0
  29. package/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md +173 -0
  30. package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +805 -0
  31. package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
  32. package/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md +72 -0
  33. package/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md +8 -0
  34. package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md +36 -0
  35. package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
  36. package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md +86 -0
  37. package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
  38. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +588 -0
  39. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
  40. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +158 -0
  41. package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +42 -0
  42. package/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md +276 -0
  43. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +738 -0
  44. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts +97 -0
  45. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts +223 -0
  46. package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
  47. package/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +266 -0
  48. package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +522 -0
  49. package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
  50. package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
  51. package/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +803 -0
  52. package/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md +219 -0
  53. package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +731 -0
  54. package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
  55. package/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +194 -0
  56. package/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md +8 -0
  57. package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md +57 -0
  58. package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
  59. package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md +150 -0
  60. package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
  61. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +746 -0
  62. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
  63. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +137 -0
  64. package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +129 -0
@@ -0,0 +1,805 @@
1
+ ---
2
+ from: "0.16"
3
+ to: "0.17"
4
+ changes:
5
+ - id: one-prisma-package-per-application
6
+ summary: |
7
+ Do this first — nothing else in this upgrade can run until the project installs. From
8
+ 0.17 the `@prisma-next/*` scope is retired: nothing publishes under it again, so a
9
+ manifest that still names it fails at install. The public surface is the `@prisma`
10
+ scope, and an application depends on exactly ONE database facade —
11
+ `@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo` — plus any
12
+ extension packs it uses, which remain direct dependencies. Everything else that used
13
+ to be a separate dependency (framework, family, target, adapter, driver, CLI) arrives
14
+ transitively as the facade's exact-pinned dependencies. In `package.json`: delete
15
+ EVERY `@prisma-next/*` entry across dependencies/devDependencies/peerDependencies,
16
+ add the one facade for your database, and keep your extension packs — renamed
17
+ (`@prisma-next/extension-<x>` → `@prisma/orm-extension-<x>`; the middleware cache is
18
+ `@prisma/orm-extension-middleware-cache`). Drop the `prisma-next` devDependency if you
19
+ have one — the facade provides the `prisma-next` bin; the standalone `prisma-next`
20
+ package remains only as the bootstrap path for projects with no Prisma dependencies
21
+ yet. Reinstall, then regenerate your contract artefacts (`prisma-next contract emit`):
22
+ generated files now import facade entrypoints (e.g. `@prisma/orm-postgres/components`)
23
+ instead of old-scope package names. The `contractHash` is unchanged by regeneration —
24
+ `prisma-next db verify` passes with no database work. Finally rewrite hand-written
25
+ imports: same-package entrypoints keep their subpath under the facade
26
+ (`@prisma-next/postgres/config` → `@prisma/orm-postgres/config`, likewise `/runtime`,
27
+ `/target`, `/family`, `/migration`, `/control`, …); programmatic tooling imports
28
+ (`@prisma-next/cli/*`, `@prisma-next/config-loader`, `@prisma-next/migration-tools/*`,
29
+ `@prisma-next/emitter`) move to the matching `@prisma/orm-toolchain/*` subpath
30
+ (`@prisma/orm-toolchain/cli/config-types`, `/cli/control-api`, `/config-loader`,
31
+ `/migration-tools/<subpath>`, `/emitter`). The rule for every rewrite: import only
32
+ from packages your manifest names directly — the facade, your extension packs, and
33
+ (for tooling authors) `@prisma/orm-toolchain`.
34
+ detection:
35
+ glob: "**/*.{json,ts,tsx,mts,cts,js,mjs,cjs}"
36
+ contains:
37
+ - '@prisma-next/'
38
+ anyMatch: true
39
+ - id: strip-sha256-hash-prefixes
40
+ summary: |
41
+ Content hashes are bare lowercase hex from 0.17 — the `sha256:` prefix is gone from every
42
+ surface (emitted `contract.json` / `contract.d.ts`, migration manifests, refs, CLI output,
43
+ and the database marker/ledger), and loaders reject the legacy prefixed form. Contract hash
44
+ VALUES are unchanged (only the prefix drops; `pnpm emit` regenerates live artefacts), but
45
+ `migrationHash` VALUES change because the hashed manifest bytes embed the now-bare
46
+ `from`/`to` strings. Run the colocated codemod over your checked-in `migrations/` trees
47
+ FIRST, before the snapshot-layout migrator in the entries below — the 0.17 layout migrator
48
+ accepts only bare-hex trees. The codemod handles both layouts: it strips the prefix from
49
+ every hash literal (manifests, `ops.json`, pre-store sibling contract snapshots, store
50
+ entries under `migrations/snapshots/`, `.d.ts` branded literals), maps the empty-tree
51
+ sentinel `sha256:empty` to `empty`, recomputes each `migrationHash`, and repoints
52
+ `refs/*.json`. Store directory names are the hash's hex, which does not change. Databases
53
+ whose marker/ledger still hold prefixed values report a hash mismatch on verify — there is
54
+ no compatibility shim; re-sign against the regenerated contract (`prisma-next db sign`).
55
+ detection:
56
+ glob: "**/*.{json,ts,tsx}"
57
+ contains:
58
+ - 'sha256:'
59
+ anyMatch: true
60
+ script: ./strip-sha256-hash-prefixes.ts
61
+ - id: migration-contract-snapshots-moved-to-content-addressed-store
62
+ summary: |
63
+ Committed migration contract snapshots move from per-package sibling files
64
+ (`start-contract.json` / `start-contract.d.ts` / `end-contract.json` /
65
+ `end-contract.d.ts`) and per-space head copies
66
+ (`migrations/<space-id>/contract.json` / `contract.d.ts`) into a single
67
+ content-addressed store per migrations root, at
68
+ `migrations/snapshots/<hex>/contract.json` + `contract.d.ts`, where `<hex>`
69
+ is the contract's 64-hex storage hash (bare hex after the
70
+ `strip-sha256-hash-prefixes` entry above, which must run first). Every
71
+ distinct contract is stored once, however many migrations reference it.
72
+ Every emitted `migration.ts` now imports its bookend contracts from the
73
+ store (`../../snapshots/<hex>/contract.json`, `../../snapshots/<hex>/contract.d.ts`)
74
+ instead of from sibling files in its own directory.
75
+ This is a clean break: there is no fallback reader for the old sibling-file
76
+ layout, so a committed migrations tree that has not been converted fails to
77
+ load once you upgrade — `migration plan` / `migration new` / `migrate` /
78
+ `migration check` all read contract snapshots through the store only, and
79
+ a missing store entry fails with `MIGRATION.CONTRACT_SNAPSHOT_MISSING`
80
+ naming the expected hash and path. `migration.json` / `ops.json` /
81
+ `migrationHash` are unaffected — the contract snapshot was never part of
82
+ migration identity, so converting the layout changes no migration's hash.
83
+ To convert an existing project, run the migrator once per migrations root
84
+ from a checkout of the `prisma/prisma` repository at (or above) the
85
+ version you're upgrading to: `node scripts/migrate-migrations-layout.mjs
86
+ [migrationsRoot...]` (with no arguments it auto-discovers every migrations
87
+ root under the current directory). Per migration package, it reads
88
+ `migration.json`, write-if-absents the destination contract (and the
89
+ source contract, when present) into the store under the matching hash,
90
+ rewrites the committed `migration.ts` import specifiers, and deletes the
91
+ four sibling files. Per contract space, it store-writes any remaining
92
+ per-space `contract.json` / `contract.d.ts` keyed by that space's
93
+ `refs/head.json` hash, then deletes it. It asserts every contract's inner
94
+ `storage.storageHash` against the hash it's filed under before writing
95
+ anything (mismatch aborts the whole run, nothing is deleted), and
96
+ re-verifies every `migrationHash` is unchanged after conversion. Run it,
97
+ review the diff, then `pnpm typecheck` (or your project's equivalent) to
98
+ confirm every rewritten `migration.ts` import resolves.
99
+ detection:
100
+ glob: "**/migration.ts"
101
+ contains:
102
+ - "./start-contract.json"
103
+ - "./end-contract.json"
104
+ - "./start-contract'"
105
+ - "./end-contract'"
106
+ anyMatch: true
107
+ - id: ref-paired-snapshots-moved-to-content-addressed-store
108
+ summary: |
109
+ Ref-paired contract snapshot files (`refs/<name>.contract.json` /
110
+ `refs/<name>.contract.d.ts`, written by `ref set` and `--advance-ref`) are
111
+ no longer written or read. A ref is now only its pointer file,
112
+ `refs/<name>.json` (`{ hash, invariants }`); the contract it names
113
+ resolves through the same content-addressed store as every migration
114
+ graph node, `migrations/snapshots/<hex>/contract.json` + `contract.d.ts`,
115
+ by that hash. This is a clean break: a pointer whose store entry is
116
+ missing now fails with `MIGRATION.CONTRACT_SNAPSHOT_MISSING` naming the
117
+ expected hash and path, rather than silently falling back to the
118
+ migration graph. The same one-shot migrator that folds per-package and
119
+ per-space sibling snapshots (see the entry above) also folds any
120
+ existing `refs/<name>.contract.json` / `refs/<name>.contract.d.ts`
121
+ pairs: it write-if-absents the pair into the store under the sibling
122
+ pointer's `hash`, then deletes the pair — the pointer file itself is
123
+ read but never written, so it stays byte-identical. A `.contract.json`
124
+ with no sibling pointer, or whose inner `storage.storageHash` disagrees
125
+ with the pointer's `hash`, aborts the whole run before anything is
126
+ written or deleted. Run `node scripts/migrate-migrations-layout.mjs
127
+ [migrationsRoot...]` (same invocation as above; one run folds both
128
+ migration-package and ref-paired snapshots), review the diff, then
129
+ re-run `prisma-next ref list` to confirm your refs are unaffected.
130
+ detection:
131
+ glob: "**/refs/*.contract.json"
132
+ anyMatch: true
133
+ - id: extension-packs-config-key-renamed-to-extensions
134
+ summary: |
135
+ The `extensionPacks` key is renamed to `extensions` everywhere: the
136
+ low-level `defineConfig` in `prisma.config.ts`, the TS builder's
137
+ `defineContract` (record form), runtime/control client options, and the
138
+ top-level key of the emitted `contract.json` / `contract.d.ts`. The old
139
+ config key now fails loudly with "Config.extensionPacks is no longer
140
+ supported; rename it to Config.extensions" — it is never silently
141
+ ignored. Rename the key in `prisma.config.ts` (and `contract.ts` /
142
+ `db.ts` if they pass `extensionPacks` to client factories). The target
143
+ façades' `defineConfig` already used `extensions`; only projects on the
144
+ low-level config change. Because the key sits in the canonicalized bytes
145
+ of every contract hash, all three hashes (`storageHash`,
146
+ `executionHash`, `profileHash`) change for every contract: re-emit with
147
+ `prisma-next contract emit`, then re-anchor migrations — regenerate
148
+ `migrations/snapshots/<hex>/` store entries and refs for the new hashes
149
+ (a schema-unchanged project needs a hash-advance migration or a
150
+ re-baseline; the database schema itself does not change).
151
+ detection:
152
+ glob: "**/{prisma.config.ts,contract.ts,db.ts}"
153
+ contains:
154
+ - "extensionPacks"
155
+ - id: contract-source-format-key-renamed
156
+ summary: |
157
+ The contract source provider field `sourceFormat` is renamed to `format`
158
+ (`contract.source.format` in the low-level config; provider objects from
159
+ `prismaContract()` / `typescriptContract()` emit the new field
160
+ automatically once upgraded). Rename any literal `sourceFormat:` in
161
+ hand-written provider objects or config assertions.
162
+ detection:
163
+ glob: "**/prisma.config.ts"
164
+ contains:
165
+ - "sourceFormat"
166
+ - id: sugar-output-path-key-renamed-to-output
167
+ summary: |
168
+ The target façades' `defineConfig` option `outputPath` is renamed to
169
+ `output`. Semantics are unchanged (a directory; `contract.json` is
170
+ written inside it). Rename the key in `prisma.config.ts`.
171
+ detection:
172
+ glob: "**/prisma.config.ts"
173
+ contains:
174
+ - "outputPath"
175
+ - id: orm-count-only-mutation-terminals-renamed
176
+ summary: Replace `createCount(...)`, `updateCount(...)`, and `deleteCount()` with `createAndCount(...)`, `updateAndCount(...)`, and `deleteAndCount()` in ORM call sites; arguments, guards, behavior, and `Promise<number>` results are unchanged, and no compatibility aliases remain.
177
+ detection:
178
+ glob: "**/*.{ts,tsx,mts,cts}"
179
+ contains:
180
+ - ".createCount("
181
+ - ".updateCount("
182
+ - ".deleteCount("
183
+ anyMatch: true
184
+ - id: psl-format-error-class-removed
185
+ summary: |
186
+ The `PslFormatError` class is deleted from `@internal/psl-parser`. `format()`
187
+ on source with parse errors now throws a structured envelope with code
188
+ `PSL.PARSE_FAILED`; the diagnostics previously on `error.diagnostics` are at
189
+ `error.meta.diagnostics`. Replace `error instanceof PslFormatError` with
190
+ `isStructuredError(error) && error.code === 'PSL.PARSE_FAILED'`
191
+ (`isStructuredError` from `@internal/utils/structured-error`). The message
192
+ text is unchanged.
193
+ detection:
194
+ glob: "**/*.{ts,mts,cts}"
195
+ contains:
196
+ - "PslFormatError"
197
+ - id: scalar-type-descriptors-channel-removed
198
+ summary: |
199
+ The scalar-type descriptor channel is retired in favour of the unified authoring type
200
+ namespace. Projects with custom control-stack setups that import
201
+ `createPostgresScalarTypeDescriptors` / `createSqliteScalarTypeDescriptors`, or that read
202
+ `scalarTypeDescriptors` from a control stack or contract-source context, must migrate:
203
+ those exports are deleted, and scalar types are now zero-arg type-constructor
204
+ contributions in the component's `authoring.type` namespace — e.g.
205
+ `String: { kind: 'typeConstructor', output: { codecId: 'pg/text@1', nativeType: 'text' } }`.
206
+ Read the scalar type names via `stack.scalarTypes`, or the full name ->
207
+ `{ codecId, nativeType }` map via `collectScalarTypeConstructors(stack.authoringContributions.type)`
208
+ from `@internal/framework-components/authoring`. Standard target setups
209
+ (`@internal/postgres`, `@internal/sqlite`) supply the contributions themselves.
210
+ detection:
211
+ glob: "**/*.{ts,mts,cts}"
212
+ contains:
213
+ - "createPostgresScalarTypeDescriptors"
214
+ - "createSqliteScalarTypeDescriptors"
215
+ - "scalarTypeDescriptors"
216
+ anyMatch: true
217
+ - id: postgres-native-types-move-to-type-position
218
+ summary: |
219
+ PostgreSQL native storage types are authored directly in PSL type position, and the legacy `@db.*` attribute channel is removed. Rewrite `BaseType @db.Type` as `Type` and `BaseType @db.Type(args)` as `Type(args)` in both `types {}` aliases and model fields, then re-run `prisma-next contract emit`. Any remaining `@db.X(args)` fails with `@db.X(args) is no longer supported; use X(args) in type position`, preserving the constructor name and arguments in the suggested replacement. The supported translations are `@db.Char` → `Char`, `@db.VarChar` → `VarChar`, `@db.Numeric` → `Numeric`, `@db.Uuid` → `Uuid`, `@db.Inet` → `Inet`, `@db.SmallInt` → `SmallInt`, `@db.Real` → `Real`, `@db.Timestamp` → `Timestamp`, `@db.Timestamptz` → `Timestamptz`, `@db.Date` → `Date`, `@db.Time` → `Time`, and `@db.Timetz` → `Timetz`; preserve constructor arguments. Rewrite the old native-json spelling `Json @db.Json` as bare `Json`. This source migration preserves native types and supplied type parameters. It also preserves codec ids except for `@db.Date` → `Date`, which rebinds `pg/timestamptz@1` to `pg/date@1`, changes the contract storage hash, and requires re-emission plus re-signing; see the `postgres-date-rebound-to-pg-date` entry below. Separately, apply the `postgres-json-rebound-to-native-json` entry below to old bare `Json` fields that meant jsonb storage.
220
+ detection:
221
+ glob: "**/*.prisma"
222
+ contains:
223
+ - "@db."
224
+ anyMatch: true
225
+ - id: postgres-json-rebound-to-native-json
226
+ summary: |
227
+ On the postgres target the PSL `Json` scalar re-binds from `pg/jsonb@1` / `jsonb` to
228
+ `pg/json@1` / `json`; a new bare `Jsonb` scalar carries `pg/jsonb@1` / `jsonb`. Postgres
229
+ schemas that use `Json` and mean jsonb storage (which every pre-0.16 `Json` field did)
230
+ must switch those fields — and `types {}` aliases — to `Jsonb`, then re-run
231
+ `prisma-next contract emit`; with `Jsonb` the emitted `contract.json` is byte-identical
232
+ to the pre-0.16 output. A field left as `Json` now emits a native `json` column and a
233
+ new storage hash, which against an existing jsonb database is a schema change. The
234
+ removed `@db.Json` spelling must be rewritten from `Json @db.Json` to bare `Json`; any remaining use fails with migration guidance to use `Json` in type position. SQLite and Mongo `Json` bindings are untouched. The TS builder surface (`field.json()`, `jsonbColumn`) is unchanged and stays jsonb.
235
+ detection:
236
+ glob: "**/*.prisma"
237
+ contains:
238
+ - "Json"
239
+ anyMatch: true
240
+ - id: default-generators-no-longer-set-storage
241
+ summary: |
242
+ `@default(<generator>)` no longer influences a column's storage — the type position is
243
+ the only storage decider. Pre-0.16, a generator default on a bare `String` field re-picked
244
+ the column's storage to a sized char: `String @default(uuid())` / `@default(uuid(7))`
245
+ emitted `sql/char@1` / `character(36)`, `@default(cuid(2))` `character(24)`,
246
+ `@default(nanoid())` `character(21)` (or `character(<size>)` for `nanoid(<size>)`), and
247
+ `@default(ulid())` `character(26)`. From 0.16 such fields emit the target's `String`
248
+ storage (postgres: `pg/text@1` / `text`) with the same execution-time generator, so a
249
+ re-emit produces a new storage hash — against an existing database created with the char
250
+ storage this is a schema change. To keep the prior storage byte-identical, name it in the
251
+ type position: `Char(36) @default(uuid())`, `Char(24) @default(cuid(2))`,
252
+ `Char(21) @default(nanoid())` (or `Char(<size>)` for a sized nanoid), `Char(26)
253
+ @default(ulid())` — or adopt native `Uuid` for `uuid()` if a `uuid`-typed column is
254
+ preferred (that is a schema change too). Then re-run `prisma-next contract emit` and, if
255
+ you accepted a storage change, plan/apply the matching migration. Generator applicability
256
+ validation is unchanged (`uuid()` on `Int` still fails with
257
+ `PSL_INVALID_DEFAULT_APPLICABILITY`), and the TS builder presets
258
+ (`field.id.uuidv4String()`, `field.generated(uuidv4())`, …) are untouched — they bundle
259
+ their `char(N)` storage explicitly.
260
+ detection:
261
+ glob: "**/*.prisma"
262
+ contains:
263
+ - "@default(uuid("
264
+ - "@default(cuid("
265
+ - "@default(nanoid("
266
+ - "@default(ulid("
267
+ anyMatch: true
268
+ - id: postgres-date-rebound-to-pg-date
269
+ summary: |
270
+ On the postgres target, the bare `Date` type constructor re-binds from `pg/timestamptz@1` to the dedicated `pg/date@1` codec. Rewrite the removed `DateTime @db.Date` spelling as `Date`; leaving it unchanged now fails with migration guidance to use `Date` in type position. The stored native type is unchanged (`date`), so no schema migration is needed, but a re-emit changes the column's codec ref and therefore the contract's storage hash: run `prisma-next contract emit`, then re-sign any signed database against the regenerated contract (`prisma-next db sign`) — verify reports a hash mismatch until you do.
271
+ Contracts emitted before the upgrade keep working (`pg/timestamptz@1` still exists).
272
+ Runtime behavior changes on re-emit: date columns decode as a `Date` at UTC midnight
273
+ (previously the driver's local-midnight `Date` passed through, so the instant depended
274
+ on the process timezone — code reading local getters near midnight in negative-UTC-offset
275
+ zones saw the neighboring day), encode formats `YYYY-MM-DD` from UTC getters, and
276
+ relation `.include()` over a date column now decodes instead of failing with
277
+ `RUNTIME.DECODE_FAILED`. Update tests or application code that pinned the old
278
+ local-midnight instants to expect `new Date(Date.UTC(y, m, d))`.
279
+ detection:
280
+ glob: "**/*.prisma"
281
+ regex:
282
+ - '@db\.Date'
283
+ - '\sDate(\s|\?|\[|$)'
284
+ anyMatch: true
285
+ - id: sql-escape-error-class-removed
286
+ summary: |
287
+ The `SqlEscapeError` class is deleted from `@internal/target-postgres` and
288
+ `@internal/target-sqlite` (including its re-export from the postgres/sqlite
289
+ adapter `control` entrypoints). Identifier/literal escaping failures now throw a
290
+ structured envelope with code `CONTRACT.IDENTIFIER_INVALID`. Replace
291
+ `error instanceof SqlEscapeError` with
292
+ `isStructuredError(error) && error.code === 'CONTRACT.IDENTIFIER_INVALID'`
293
+ (`isStructuredError` from `@internal/utils/structured-error`). Message text is
294
+ unchanged.
295
+ detection:
296
+ glob: "**/*.{ts,mts,cts}"
297
+ contains:
298
+ - "SqlEscapeError"
299
+ - id: supabase-error-classes-removed
300
+ summary: |
301
+ The `SupabaseConfigError` and `InvalidJwtError` classes are deleted from
302
+ `@internal/extension-supabase/runtime`. The same failures now throw
303
+ structured envelopes with codes `SUPABASE.CONFIG_INVALID` and
304
+ `SUPABASE.JWT_INVALID`. Replace `error instanceof SupabaseConfigError` with
305
+ `isStructuredError(error) && error.code === 'SUPABASE.CONFIG_INVALID'` and
306
+ `error instanceof InvalidJwtError` with
307
+ `isStructuredError(error) && error.code === 'SUPABASE.JWT_INVALID'`
308
+ (`isStructuredError` from `@internal/utils/structured-error`). Message
309
+ text is unchanged.
310
+ detection:
311
+ glob: "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"
312
+ contains:
313
+ - "SupabaseConfigError"
314
+ - "InvalidJwtError"
315
+ anyMatch: true
316
+
317
+ - id: indexes-are-name-identified
318
+ summary: |
319
+ Secondary indexes are name-identified from 0.17. Every index entry in the emitted
320
+ `contract.json` / `contract.d.ts` now carries `name` (the full physical name) and
321
+ `unique`, plus `prefix` when the name is toolchain-owned; `columns` became optional
322
+ (an index carries either `columns` or an opaque `expression` — never both). Contracts
323
+ emitted by 0.16 fail validation on load (the error message contains "indexes[0].name
324
+ must be a string (was missing)"), and storage hashes move for every contract that
325
+ declares indexes — re-emit with `prisma-next contract emit`. Physical names change for wire-named indexes: an unnamed
326
+ PSL `@@index([a, b])` / TS `constraints.index([a, b])` and every FK-backing index now
327
+ CREATE as `<default-prefix>_<8hex>` content-hash wire names (e.g.
328
+ `user_email_idx_46df9cad`), and a TS `constraints.index([...], { name: "x" })` name is
329
+ now a wire prefix — the physical name becomes `x_<8hex>`. PSL
330
+ `@@index([...], map: "x")` is now an exact physical name whose identity is verified
331
+ against the live catalog. Existing databases converge without rebuilds: after
332
+ re-emitting, the first plan that allows the `widening` class (`db update`, or
333
+ `migration plan` + `migrate`) is `ALTER INDEX … RENAME TO` ops only — renames happen
334
+ only when a widening plan runs FIRST. Under an additive-only policy the rename pairing
335
+ does not run: the new wire-named index is created beside the old one, and once both
336
+ exist a later plan can no longer pair them — the old index is removed only by a
337
+ destructive-allowed plan dropping it. Update any code or tests that hard-code the old
338
+ physical index names.
339
+ detection:
340
+ glob: "**/*.{prisma,ts,json}"
341
+ contains:
342
+ - "@@index"
343
+ - "constraints.index"
344
+ - '"indexes":'
345
+ anyMatch: true
346
+ - id: rls-policy-migration-literal-carries-the-naming-union
347
+ summary: |
348
+ A generated migration's `this.createRlsPolicy({ policy: … })` literal spells the policy's
349
+ name differently from 0.17. The flat `name` / `prefix` pair is replaced by a single
350
+ `naming` field carrying one of two shapes: `{ kind: "exact", name: "<physical name>" }`
351
+ for a policy whose name the author owns, or
352
+ `{ kind: "wire", prefix: "<prefix>", hash: "<8hex>" }` for a toolchain-named one
353
+ (0.16's `name: "<prefix>_<8hex>"` plus `prefix: "<prefix>"`). Every other key is
354
+ unchanged. A migration file emitted by 0.16 that calls `createRlsPolicy` therefore stops
355
+ compiling — TypeScript reports `Property 'naming' is missing`. Regenerate the affected
356
+ migrations with `prisma-next migration plan`, or edit the literal by hand: the shape is
357
+ mechanical, and the migration's identity (`migrationHash`, the SQL it executes) does not
358
+ depend on the literal's spelling.
359
+ detection:
360
+ glob: "**/migrations/**/migration.ts"
361
+ contains:
362
+ - "createRlsPolicy"
363
+ - id: framework-error-classes-removed
364
+ summary: |
365
+ Three exported framework error classes are deleted: `ConfigFileNotFoundError`
366
+ (from `@internal/config-loader`), `ConfigValidationError` (from
367
+ `@internal/config/config-validation`), and `DomainNamespaceResolutionError`
368
+ (from `@internal/contract/types`). The same failures now throw structured
369
+ envelopes with codes `CONFIG.FILE_NOT_FOUND`, `CONFIG.VALIDATION_FAILED`, and
370
+ `CONTRACT.NAMESPACE_INVALID` respectively. Replace each
371
+ `error instanceof <Class>` with
372
+ `isStructuredError(error) && error.code === '<CODE>'`
373
+ (`isStructuredError` from `@internal/utils/structured-error`). Message
374
+ text is unchanged.
375
+ detection:
376
+ glob: "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"
377
+ contains:
378
+ - "ConfigFileNotFoundError"
379
+ - "ConfigValidationError"
380
+ - "DomainNamespaceResolutionError"
381
+ anyMatch: true
382
+ - id: pg-int8-application-values-are-bigint
383
+ summary: |
384
+ `pg/int8@1` carries `bigint` application values where it carried `number`. A JS `number`
385
+ cannot represent the whole signed 64-bit range, so any value past 2^53 was already being
386
+ silently rounded. Every read of an `int8` column now yields a `bigint`, and every value
387
+ compared against one must be a `bigint` literal. `count()` is the widest instance: it
388
+ resolves to `pg/int8@1`, so a counted column's row type is `bigint` and a `having`
389
+ comparison reads `fns.gt(fns.count(), 5n)`. Update row-type annotations, comparison
390
+ literals, and any arithmetic that mixes a counted value with a `number` — TypeScript will
391
+ not implicitly convert between the two, so `pnpm typecheck` locates every site.
392
+ A contract's `int8` literal defaults are also emitted as decimal strings rather than JSON
393
+ numbers; re-emit to pick that up.
394
+ detection:
395
+ glob: "**/*.{ts,tsx,mts,cts}"
396
+ contains:
397
+ - "fns.count("
398
+ - "pg/int8@1"
399
+ - "int8Column"
400
+ anyMatch: true
401
+ - id: pg-interval-values-are-structured-durations
402
+ summary: |
403
+ Reading a `pg/interval@1` column returns `{ months, days, micros }` — the three fields
404
+ PostgreSQL stores — where it returned a `JSON.stringify` of the driver's component object
405
+ such as `{"days":1}`. `months` and `days` are `number`; `micros` is `bigint`, because
406
+ PostgreSQL stores it as a 64-bit integer. Writing takes the same object. Replace any
407
+ parsing of the old string with field access, and replace interval literals with the object
408
+ (`{ months: 0, days: 1, micros: 0n }` for one day). The three fields stay independent
409
+ because a month has no fixed length: one month and thirty days are different values and
410
+ neither converts to the other. Serialized form is unchanged in kind but not in spelling —
411
+ a contract holds the ISO-8601 duration string (`P1M`, `P1Y2M3DT4H5M6S`, `PT0S`), so
412
+ re-emit; `micros` past microsecond resolution rounds as PostgreSQL rounds.
413
+ detection:
414
+ glob: "**/*.{ts,tsx,mts,cts}"
415
+ contains:
416
+ - "pg/interval@1"
417
+ - "intervalColumn"
418
+ anyMatch: true
419
+ - id: codec-json-forms-are-canonical
420
+ summary: |
421
+ Several codecs' JSON representation changed so a value survives the round trip through a
422
+ contract. `pg/numeric@1` and `sqlite/bigint@1` are decimal text where they were JSON
423
+ numbers — `9007199254740993` reached JSON as `…992` before, and arbitrary-precision
424
+ decimals lost their tail. `pg/bytea@1` is base64 where it was PostgreSQL's `\x`-prefixed
425
+ hex. `sqlite/blob@1` is uppercase hexadecimal where it was base64. `sqlite/bigint@1`
426
+ additionally accepts values it previously refused outright: half of SQLite's INTEGER range
427
+ had no JSON representation at all.
428
+ This now reaches **reads**, not only contract literals. A query that returns JSON — an
429
+ `.include()`'s nested rows, an aggregated child row set — projects each column through its
430
+ codec, so these codecs' values arrive in the forms above where they previously arrived in
431
+ whatever the database's own JSON conversion produced. Nine codecs project non-identically:
432
+ `pg/numeric@1`, `pg/int8@1`, `pg/bytea@1`, `pg/interval@1`, `pg/timestamptz@1`,
433
+ `pg/vector@1`, `sqlite/bigint@1`, `sqlite/blob@1` and `sqlite/json@1`. If you read such a
434
+ column out of an include and parse or compare its raw JSON yourself — rather than letting
435
+ the ORM decode it — update that code to the new form.
436
+ Run `prisma-next contract emit` to regenerate
437
+ `contract.json` / `contract.d.ts`; any literal default on one of these codecs changes
438
+ spelling, and with it the `storageHash`. Code that reads such a default out of a contract,
439
+ or that hand-writes one, must use the new form.
440
+ detection:
441
+ glob: "**/*.{ts,tsx,mts,cts,json,d.ts}"
442
+ contains:
443
+ - "pg/numeric@1"
444
+ - "pg/bytea@1"
445
+ - "pg/int8@1"
446
+ - "pg/interval@1"
447
+ - "pg/timestamptz@1"
448
+ - "pg/vector@1"
449
+ - "sqlite/bigint@1"
450
+ - "sqlite/blob@1"
451
+ - "sqlite/json@1"
452
+ anyMatch: true
453
+ - id: float-json-requires-extra-float-digits-at-least-one
454
+ summary: |
455
+ The canonical JSON of `pg/float4@1`, `pg/float8@1`, `pg/float@1` and `pg/vector@1` holds
456
+ only where the PostgreSQL session's `extra_float_digits` is 1 or above. That is the default
457
+ from PostgreSQL 12 onward, so most deployments already satisfy it — but a connection that
458
+ sets the GUC to 0 or below reverts to a fixed digit count and truncates: `1/3` reads back
459
+ as `0.333333333333333` rather than `0.3333333333333333`, and the value no longer
460
+ round-trips. Check any connection string, pool `options`, server config or proxy that sets
461
+ `extra_float_digits` and remove settings of 0 or below.
462
+ detection:
463
+ glob: "**/*.{ts,tsx,mts,cts,js,mjs,cjs,json,toml,yaml,yml,env}"
464
+ contains:
465
+ - "extra_float_digits"
466
+ anyMatch: true
467
+ - id: sqlite-real-rejects-non-finite-values
468
+ summary: |
469
+ `sqlite/real@1` rejects infinities and `NaN` on both the encode and decode sides. JSON has
470
+ no spelling for either, and SQLite renders an infinity as `9.0e+999`, which reads back as
471
+ `Infinity` rather than failing — so a non-finite value used to pass through and corrupt the
472
+ value silently. Guard any computation that can produce a non-finite float before writing it
473
+ to a `REAL` column, or store it in a column whose codec admits it.
474
+ detection:
475
+ glob: "**/*.{ts,tsx,mts,cts}"
476
+ contains:
477
+ - "sqlite/real@1"
478
+ - "realColumn"
479
+ anyMatch: true
480
+ - id: pg-timestamptz-json-is-utc-iso
481
+ summary: |
482
+ `pg/timestamptz@1`'s canonical JSON is a UTC ISO-8601 timestamp with an explicit `+00:00`
483
+ offset, constructed by the projection rather than inherited from the session. The form
484
+ previously followed the connection's `DateStyle` and `TimeZone`, so the same stored instant
485
+ read back differently on two connections, and under a non-ISO `DateStyle` could fail to
486
+ parse at all. Nothing to change if you decode through the ORM. If you read a timestamptz out
487
+ of database-produced JSON yourself it is now always `YYYY-MM-DDTHH:MM:SS.mmm+00:00`: drop any
488
+ session-dependent parsing, and drop any `SET DateStyle` / `SET TimeZone` you added to
489
+ stabilise it.
490
+ detection:
491
+ glob: "**/*.{ts,tsx,mts,cts,sql}"
492
+ contains:
493
+ - "pg/timestamptz@1"
494
+ - "timestamptzColumn"
495
+ - "DateStyle"
496
+ anyMatch: true
497
+ - id: sqlite-json-documents-survive-nesting
498
+ summary: |
499
+ A `sqlite/json@1` column read through a nested `.include()` arrives as a parsed document
500
+ where it previously arrived as a string containing JSON. SQLite carries "this text is JSON"
501
+ as a subtype on the value, and that subtype does not survive a derived table — which every
502
+ include's child row set passes through — so a document came back double-encoded. The
503
+ projection retags it at the boundary that consumes it. A `sqlite/text@1` column whose
504
+ characters happen to look like JSON is unaffected and still arrives as a string: the retag
505
+ follows the column's codec, not its content. Remove any `JSON.parse` you added to compensate
506
+ for the double encoding.
507
+ detection:
508
+ glob: "**/*.{ts,tsx,mts,cts}"
509
+ contains:
510
+ - "sqlite/json@1"
511
+ - "jsonColumn"
512
+ anyMatch: true
513
+ - id: sqlite-blob-null-is-distinct-from-empty
514
+ summary: |
515
+ A `NULL` `sqlite/blob@1` column read through database-produced JSON is `null`, where it
516
+ previously became an empty `Uint8Array`. SQLite's `hex(NULL)` is the empty string, which is
517
+ also the hex of a zero-length blob, so absence and emptiness were the same value and nothing
518
+ raised. If your code distinguishes "no blob" from "empty blob" — and especially if it worked
519
+ around the old behaviour by treating a zero-length blob as absent — that check now needs to
520
+ test for `null`.
521
+ detection:
522
+ glob: "**/*.{ts,tsx,mts,cts}"
523
+ contains:
524
+ - "sqlite/blob@1"
525
+ - "blobColumn"
526
+ anyMatch: true
527
+ - id: sql-float-rejects-non-finite-values
528
+ summary: |
529
+ `sql/float@1` rejects infinities and `NaN` in both JSON directions, matching
530
+ `sqlite/real@1`. Its `decodeJson` previously performed no check at all, and a database can
531
+ hold a non-finite float and spells it as a *string* in JSON — PostgreSQL emits `"NaN"` — so
532
+ the codec handed back a string typed as `number`, silently. Guard any computation that can
533
+ produce a non-finite float before writing it to a `sql/float@1` column, or use
534
+ `pg/numeric@1`, whose application value is text and which admits all three.
535
+ detection:
536
+ glob: "**/*.{ts,tsx,mts,cts}"
537
+ contains:
538
+ - "sql/float@1"
539
+ - "sqlFloatColumn"
540
+ anyMatch: true
541
+ - id: explicit-codec-refs-need-readable-type-params
542
+ summary: |
543
+ A codec ref supplied explicitly — `sql.value(v, { codec: { codecId: 'pg/enum@1' } })` and
544
+ the other surfaces that take a bare `codecId` — must carry `typeParams` the codec's schema
545
+ accepts when that codec is parameterized. For `pg/enum@1` that means
546
+ `typeParams: { typeName: '<enum type>' }`. Such a ref never passes contract validation, so
547
+ the omission used to surface as a static `text` native type — correct only because
548
+ PostgreSQL implicitly casts text to an enum, and wrong for any parameterized codec whose
549
+ type is not text-compatible. It now fails at lowering instead. The failure currently
550
+ surfaces as a params-validation error rather than a message naming the surface that produced
551
+ it; that diagnostic is tracked as
552
+ [TML-3114](https://linear.app/prisma-company/issue/TML-3114). Add the `typeParams` your
553
+ column declares, or drop the explicit codec and let the column's own codec resolve.
554
+ detection:
555
+ glob: "**/*.{ts,tsx,mts,cts}"
556
+ contains:
557
+ - "codec: { codecId"
558
+ - "pg/enum@1"
559
+ anyMatch: true
560
+ - id: sql-timestamp-json-is-utc-not-local
561
+ summary: |
562
+ **`sql/timestamp@1` now reads a zone-less timestamp as UTC where it read it in the running
563
+ process's local zone.** This is an interpretation change, not a formatting one, and it is the
564
+ dangerous half: `new Date('2026-01-02T03:04:05')` resolves in the local zone, so the same
565
+ stored value used to decode to a different instant on a machine in `Europe/Berlin` than on one
566
+ in `UTC` — shifted by the offset, silently. It now resolves as UTC on every machine.
567
+ If you compensated for the old shift anywhere downstream — adding the offset back, forcing
568
+ `TZ=UTC` on the process, normalising after decode — **remove that compensation**, or it now
569
+ double-corrects and the instant is wrong by twice the offset. Nothing raises: the value is
570
+ plausible, just wrong. If you ran with `TZ=UTC` there was no shift to compensate for and
571
+ nothing to change.
572
+ The JSON form changes with it: `encodeJson` emits `2026-01-02T03:04:05.678` where it emitted
573
+ `2026-01-02T03:04:05.678Z`. A `timestamp` carries no zone, so the trailing `Z` claimed one it
574
+ did not have; `decodeJson` now rejects an offset-bearing string outright rather than
575
+ reinterpreting it, since this codec cannot reproduce an offset it was handed. Update any
576
+ hand-written JSON, fixture or comparison that spells the old form.
577
+ `pg/timestamp@1` is unaffected — it already read as UTC and already emitted the zone-less
578
+ form.
579
+ detection:
580
+ glob: "**/*.{ts,tsx,mts,cts,json}"
581
+ contains:
582
+ - "sql/timestamp@1"
583
+ - "sqlTimestampColumn"
584
+ anyMatch: true
585
+ ---
586
+
587
+ # 0.16 → 0.17 — User upgrade instructions
588
+
589
+ ## `strip-sha256-hash-prefixes`
590
+
591
+ Starting at the 0.17 release, every content hash Prisma Next mints or accepts is bare lowercase hex — the `sha256:` prefix is removed across the board: emitted `contract.json` / `contract.d.ts` (including the `StorageHashBase<'…'>` / `ProfileHashBase<'…'>` branded type literals), migration manifests, refs, CLI output, and the marker/ledger bookkeeping tables in your database. The prefix carried no information (the algorithm never varied per hash), and the hash **value** — not an in-band tag — signals a format change. Loaders and validators now reject the legacy prefixed form outright.
592
+
593
+ Two distinct effects on your checked-in artefacts:
594
+
595
+ - **Contract hashes keep their value.** `storageHash` / `profileHash` are computed over contract content, which never embedded its own hash — only the textual prefix drops.
596
+ - **Migration hash values change.** `migrationHash` is computed over the manifest bytes, which embed the `from` / `to` contract-hash strings; with those now bare, every recomputed `migrationHash` differs from the stored one.
597
+
598
+ ### Migrate checked-in `migrations/` trees — before the layout migrator
599
+
600
+ Run the colocated codemod from your project root, **before** `scripts/migrate-migrations-layout.mjs` (the snapshot-layout entries below) — the 0.17 layout migrator accepts only bare-hex trees:
601
+
602
+ ```bash
603
+ pnpm exec tsx ./strip-sha256-hash-prefixes.ts
604
+ ```
605
+
606
+ For every on-disk migration package (a `migration.json` with a sibling `ops.json`) it strips the prefix from the manifest's `from` / `to`, from hash literals inside `ops.json`, in pre-store sibling contract snapshots (`*-contract.json`, `*.d.ts`, `migration.ts`), and in content-addressed store entries (`migrations/snapshots/<hex>/contract.json` + `contract.d.ts` — the directory name is the hash's hex and does not change), recomputes `migrationHash` over the bare-hex content, and rewrites `refs/*.json` — repointing refs that held old migration hashes at the recomputed ones, and mapping the empty-tree sentinel `sha256:empty` to `empty`. The edit is format-preserving (only hash literals and the recomputed hash value change) and idempotent: re-running over an already-bare tree makes no further changes.
607
+
608
+ Use `--check` for a dry run that lists files still needing the fix and exits non-zero if any remain:
609
+
610
+ ```bash
611
+ pnpm exec tsx ./strip-sha256-hash-prefixes.ts --check
612
+ ```
613
+
614
+ ### Re-emit live contract artefacts
615
+
616
+ Regenerate your emitted artefacts so `contract.json` / `contract.d.ts` pick up the bare-hex form:
617
+
618
+ ```bash
619
+ pnpm emit
620
+ # (runs `prisma-next contract emit` under the hood)
621
+ ```
622
+
623
+ The regenerated files differ only in hash representation — the hash values themselves are unchanged.
624
+
625
+ ### Update hash literals your own code carries
626
+
627
+ If your application or tests hard-code hash strings (asserting a `migrationHash`, comparing a `storageHash`, matching CLI output), drop the `sha256:` prefix — and for migration hashes, read the new value from the regenerated manifest, since the value itself changed.
628
+
629
+ ### Database marker/ledger
630
+
631
+ There is no compatibility shim: a database whose marker/ledger rows still hold prefixed values reports a hash mismatch on `prisma-next db verify`. Re-sign the database against your regenerated contract:
632
+
633
+ ```bash
634
+ prisma-next db sign
635
+ ```
636
+
637
+ ### Validation
638
+
639
+ After the codemod and re-emit, run `pnpm typecheck && pnpm test` (or your application's equivalent), and exercise any command that loads your migrations (deploy or migration-status step) — the loader recomputes and verifies each manifest's `migrationHash` on read, so a stale or still-prefixed manifest fails immediately. `git grep -n "sha256:"` over your project should return no hits in committed artefacts.
640
+
641
+ Also in this release, the ORM client's internal `throw new Error(...)` sites
642
+ were converted to a structured-error scheme (`ORM.*` codes via `structuredError`,
643
+ or `InternalError` for invariants). These are internal throw sites: the errors
644
+ are still `Error` instances with unchanged message text, so application code
645
+ that catches them by message or by `instanceof Error` is unaffected. No action
646
+ required beyond the migration contract-snapshot layout change above.
647
+
648
+ ## `indexes-are-name-identified`
649
+
650
+ Secondary indexes are **name-identified**: the contract stores every index's full physical name, and schema verification and migration planning pair indexes by that name instead of by column tuple.
651
+
652
+ ### What changed in the emitted contract
653
+
654
+ Each entry in a table's `indexes` array in `contract.json` / `contract.d.ts` now always carries:
655
+
656
+ - `name` — the full physical name of the index in the database.
657
+ - `unique` — always present (`false` for everything authored today).
658
+ - `prefix` — present when the name is toolchain-owned: the physical name is then `<prefix>_<8hex>`, where the suffix is a content hash of the index definition.
659
+ - `columns` — now optional; an index carries either `columns` or an opaque `expression` string, never both.
660
+
661
+ Newly available in 0.17 (additive — no migration needed): `contract infer` captures the full index matrix (expression, partial `where:`, unique non-constraint, `type:`/`options:`) and the RLS surface (`@@rls`, every policy as a `policy_<operation>` block with `@@map` and verbatim reprinted bodies, `permissive = false` for RESTRICTIVE rows), so `infer → emit → db verify` is zero-issue on databases carrying those objects. Re-running `contract infer` therefore rewrites `contract.prisma` with more entries than 0.16 emitted; an index whose live name is wire-shaped (`<prefix>_<8hex>`, created by this toolchain) and whose hash recomputes now re-infers as wire-named `name:` instead of exact `map:` — both spellings verify clean, the wire-named one keeps renames first-class. `permissive` is an authorable policy-block property (default `true`; wire names for `permissive = true` policies are byte-unchanged). Contracts may also now carry two content-identical exact-named (`map:`) indexes under different names — legal twins a signed database can have. RLS policy blocks (`policy_select` etc.) accept `@@map("physical name")` to adopt an existing live policy under its exact name — no wire-name hash, drift detection byte-compares the body against Postgres's reprint (hand-authoring the text warns with `PN_EXACT_NAME_BODY_COMPARISON`), and replacing `@@map` with the plain head later converges via a single `ALTER POLICY … RENAME`. Also newly available: both authoring surfaces accept the full index parameter matrix. PSL `@@index` and TS `constraints.index` take `expression:` (instead of a fields list; requires `name:` or `map:`), `where:` (partial-index predicate), `unique:`, `type:`/`options:` (target-registered access method), and `name:` xor `map:`. Combining `map:` with a SQL body emits the `PN_EXACT_NAME_BODY_COMPARISON` warning at build time — drift detection byte-compares hand-authored text against Postgres's reprint, so prefer `name:` unless the text was captured by `contract infer`. SQLite contracts reject `expression:`/`where:` with `CONTRACT.ARGUMENT_INVALID` (the target does not support them).
662
+
663
+ A contract emitted by 0.16 fails validation when a 0.17 toolchain loads it — a `Contract structural validation failed: storage.namespaces.<ns> …` error whose message contains `indexes[0].name must be a string (was missing)` and `indexes[0].unique must be boolean (was missing)` — and the storage hash moves for every contract that declares indexes. Re-emit:
664
+
665
+ ```bash
666
+ prisma-next contract emit
667
+ ```
668
+
669
+ ### What changed about physical index names
670
+
671
+ | Authoring input | 0.16 physical name | 0.17 physical name |
672
+ | --- | --- | --- |
673
+ | PSL `@@index([a, b])` / TS `constraints.index([cols.a, cols.b])` (unnamed) | `<table>_<a>_<b>_idx` | `<table>_<a>_<b>_idx_<8hex>` (wire-named) |
674
+ | FK-backing index (derived from a relation) | `<table>_<col>_idx` | `<table>_<col>_idx_<8hex>` (wire-named) |
675
+ | TS `constraints.index([...], { name: "x" })` | `x` | `x_<8hex>` — the name is now a wire *prefix* |
676
+ | PSL `@@index([...], map: "x")` | `x` | `x` — an exact physical name, now verified against the live catalog |
677
+
678
+ The `<8hex>` suffix is a content hash over the index definition (element list, predicate, uniqueness, access method, options), so an unchanged definition always produces the same name.
679
+
680
+ ### Converging an existing database
681
+
682
+ No index is rebuilt. After re-emitting the contract, the first plan that allows the `widening` operation class converges the live names with `ALTER INDEX … RENAME TO` ops only:
683
+
684
+ - `prisma-next db update` (its default policy includes widening), or
685
+ - `prisma-next migration plan --name converge-index-names` followed by `prisma-next migrate`.
686
+
687
+ Inspect the plan before applying — for a schema whose only drift is the index naming, it contains nothing but renames.
688
+
689
+ Under an **additive-only** policy (e.g. `db init`'s class set) the rename pairing is skipped: the plan creates the new wire-named index beside the old one. Once both indexes exist, a later widening plan has nothing left to pair — the new name is already present, and the rename op's own precheck requires its target name to be absent — so after the additive create the old index is removed **only** by a destructive-allowed plan dropping it. A rename happens only when a widening-allowed plan is the *first* convergence, before any create. This degradation is deliberate — an additive-only run never emits an op class it is not allowed to execute; if you want renames instead of create-then-drop, run the widening plan first.
690
+
691
+ ### Hard-coded names
692
+
693
+ If application code, tests, or operational scripts hard-code physical index names (e.g. `user_email_idx`), read the new names from the regenerated `contract.json` — wire names now carry the hash suffix. PSL schemas that must keep a byte-exact legacy name can pin it with `@@index([...], map: "<exact name>")`.
694
+
695
+ ## `rls-policy-migration-literal-carries-the-naming-union`
696
+
697
+ Generated migrations that create an RLS policy carry the policy as a literal. Where 0.16 spelled its name as two flat fields, 0.17 spells it as one `naming` field with two shapes:
698
+
699
+ ```ts
700
+ // 0.16
701
+ this.createRlsPolicy({ schema: "public", table: "post", policy: {
702
+ name: "post_owner_a1b2c3d4",
703
+ prefix: "post_owner",
704
+ // …
705
+ } })
706
+
707
+ // 0.17
708
+ this.createRlsPolicy({ schema: "public", table: "post", policy: {
709
+ naming: { kind: "wire", prefix: "post_owner", hash: "a1b2c3d4" },
710
+ // …
711
+ } })
712
+ ```
713
+
714
+ A policy whose name the author owns (adopted through `@@map`) carries `naming: { kind: "exact", name: "Tenant members can read" }` instead. Every other key of the literal is unchanged.
715
+
716
+ The two fields could disagree — a `prefix` that is not what `name` ends with was representable and had to be checked at runtime — while the union cannot be written wrong. A 0.16 migration file that calls `createRlsPolicy` stops compiling against 0.17 with `Property 'naming' is missing`. Either regenerate the migration (`prisma-next migration plan`) or rewrite the two fields as the one union field by hand; the migration's identity and the SQL it runs do not depend on the literal's spelling, so a hand edit needs no re-hashing.
717
+
718
+ ## Incidental dependency and lint-config bumps
719
+
720
+ Routine dev-dependency bumps and biome `$schema` version alignment in `examples/` (dependabot `dev-deps` group, PR #1058) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
721
+
722
+ ## Incidental dependency bumps in examples
723
+
724
+ Routine runtime dependency bumps in `examples/` (dependabot `runtime-deps` group, PR #1065) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
725
+
726
+ ## Incidental runtime dependency bumps in examples (August 2026)
727
+
728
+ Routine runtime dependency bumps in `examples/` (dependabot `runtime-deps` group, PR #29872) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
729
+
730
+ ## Incidental dev-dependency bumps in examples (August 2026)
731
+
732
+ Routine dev-dependency bumps in `examples/` (dependabot `dev-deps` group, PR #29877) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
733
+
734
+ ## Incidental example dependency bumps (react-router 8)
735
+
736
+ The `react-router-demo` example moves its `react-router`, `@react-router/dev`, `@react-router/node`, and `@react-router/serve` dependencies from 7.x to 8.x. This is an example-local framework upgrade and requires no Prisma Next-specific upgrade action; the Prisma Next surfaces the example uses are unchanged.
737
+
738
+ ## `pg-int8-application-values-are-bigint`
739
+
740
+ An `int8` is a signed 64-bit integer; a JS `number` holds integers exactly only to 2^53. The codec previously handed you a `number`, so anything larger was already wrong by the time your code saw it. It now hands you a `bigint`.
741
+
742
+ TypeScript does not implicitly convert between `number` and `bigint`, so `pnpm typecheck` finds every affected site. Three shapes recur:
743
+
744
+ - **Row-type annotations.** A counted column is `bigint`: `SqlQueryPlan<{ name: string; postCount: bigint }>`.
745
+ - **Comparison literals.** `fns.gt(fns.count(), 5)` becomes `fns.gt(fns.count(), 5n)`.
746
+ - **Values read from a driver.** A raw `pg` query returns an `int8` as a decimal *string*; convert with `BigInt(row.id)` rather than annotating it `number`.
747
+
748
+ Arithmetic mixing the two throws at runtime rather than coercing, so a site that typechecks after a cast is worth reading again.
749
+
750
+ ## `pg-interval-values-are-structured-durations`
751
+
752
+ An interval is not a duration. PostgreSQL stores three independent fields — months, days and microseconds — because a month has no fixed length, so `{ months: 1 }` and `{ days: 30 }` are different intervals and neither can be converted into the other. The application value is now those three fields, so reading an interval hands you numbers to compute with rather than a string to parse.
753
+
754
+ ```ts
755
+ // before
756
+ const gap: string = row.gap; // "{\"days\":1}"
757
+
758
+ // after
759
+ const gap = row.gap; // { months: 0, days: 1, micros: 0n }
760
+ const totalDays = gap.days + gap.months * 30; // your calendar rule, not ours
761
+ ```
762
+
763
+ The representation is separate from the value, as it is for `pg/bytea@1` (a `Uint8Array` carried as base64) and `pg/int8@1` (a `bigint` carried as decimal text). A contract holds the ISO-8601 duration string, so re-emit to pick up the spelling — `P1M`, `P1Y2M3DT4H5M6S`, `PT0S` for zero, each component carrying its own sign.
764
+
765
+ Two details worth knowing:
766
+
767
+ - **The ISO rendering normalises where the value does not.** Thirteen months render as `P1Y1M` and read back as `{ months: 13 }`. The value keeps what you gave it.
768
+ - **Fractional seconds round.** PostgreSQL rounds past microsecond resolution rather than truncating — `1.1234567` seconds is `1.123457` — and both paths into the value now agree with it.
769
+
770
+ ## `codec-json-forms-are-canonical`
771
+
772
+ The rule these follow is that a value written through a codec and read back must be the same value. Where a codec's JSON form could not carry its own range, the form changed rather than the range being quietly clipped.
773
+
774
+ Re-emit first (`prisma-next contract emit`), then reconcile any code that reads or writes one of these forms directly. Literal defaults are where this most often surfaces: an `int8` default of `0` is now `"0"` in `contract.json`, and the `storageHash` moves with it.
775
+
776
+ The second place it surfaces is reads. A query that returns JSON projects each column through its codec, so a column whose codec is one of the nine listed above arrives in that codec's canonical form rather than in whatever the database's own JSON conversion produced. Decoding through the ORM needs no change — the codec's `decodeJson` is the other half of the same pair, and the two moved together. What needs checking is code that bypasses the ORM's decoding: a raw query that reads an aggregated JSON column and parses it itself, a comparison against a hand-written JSON string, a snapshot of database-produced JSON.
777
+
778
+ Where a form is a strict improvement in range, nothing downstream breaks by widening. Where a form changes spelling — `bytea` from `\x`-hex to base64, `sqlite/blob@1` from base64 to uppercase hex — a hand-written comparison is the thing that breaks, and it breaks loudly rather than silently.
779
+
780
+ ## `sql-timestamp-json-is-utc-not-local`
781
+
782
+ The formatting change is easy to see and easy to fix. The interpretation change is neither, so take it first.
783
+
784
+ A `timestamp` column carries no time zone. Its JSON form is therefore a zone-less string, and something has to decide which instant that string denotes. `sql/timestamp@1` used to hand the string to `new Date(...)`, which resolves a zone-less form **in the zone the process happens to be running in**. The same stored value decoded to a different instant depending on where the code ran, and it decoded silently — a `Date` is a `Date`, whichever instant it holds.
785
+
786
+ It now resolves as UTC, unconditionally, and `encodeJson` writes UTC. The pair round-trips on any machine.
787
+
788
+ The migration hazard is compensation you may already have in place:
789
+
790
+ - If you added an offset back after decoding, **remove it.** It now double-corrects, and the result is wrong by twice your offset.
791
+ - If you set `TZ=UTC` on the process specifically to stabilise these values, you can drop that — though leaving it costs nothing, since UTC was already the case it produced.
792
+ - If you normalised timestamps after reading them, check whether the normalisation is still doing anything.
793
+
794
+ None of these fail loudly. A doubled offset produces a timestamp that parses, compares and serialises perfectly well and denotes the wrong moment, which is why this entry leads with the interpretation rather than the dropped `Z`.
795
+
796
+ Two smaller consequences follow:
797
+
798
+ - `encodeJson` emits `2026-01-02T03:04:05.678` rather than `2026-01-02T03:04:05.678Z`. Update fixtures, snapshots and hand-written comparisons.
799
+ - `decodeJson` rejects an offset-bearing string instead of reinterpreting it. The codec cannot reproduce an offset, so accepting one would decode a value it could never encode back.
800
+
801
+ `pg/timestamp@1` needs no attention: it already read as UTC and already emitted the zone-less form, and this change brings the generic codec into line with it.
802
+
803
+ ## Incidental release version bump to 0.17.0
804
+
805
+ The `chore(release): bump to 0.17.0` commit rewrites every workspace manifest's `version` field and `workspace:` pins, which touches `examples/` manifests. No user action beyond the entries above.