@prisma/orm-postgres 8.0.0-rc.12-dev.22 → 8.0.0-rc.13-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/orm-postgres",
3
- "version": "8.0.0-rc.12-dev.22",
3
+ "version": "8.0.0-rc.13-dev.1",
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.12-dev.22",
14
- "@prisma/orm-framework": "8.0.0-rc.12-dev.22",
15
- "@prisma/orm-target-postgres": "8.0.0-rc.12-dev.22",
16
- "@prisma/orm-toolchain": "8.0.0-rc.12-dev.22",
13
+ "@prisma/orm-family-sql": "8.0.0-rc.13-dev.1",
14
+ "@prisma/orm-framework": "8.0.0-rc.13-dev.1",
15
+ "@prisma/orm-target-postgres": "8.0.0-rc.13-dev.1",
16
+ "@prisma/orm-toolchain": "8.0.0-rc.13-dev.1",
17
17
  "@types/node": "26.1.2",
18
18
  "@types/pg": "8.20.4",
19
19
  "pathe": "^2.0.3",
@@ -21,9 +21,9 @@
21
21
  "tinyglobby": "^0.2.17"
22
22
  },
23
23
  "devDependencies": {
24
- "@internal/postgres": "8.0.0-rc.12-dev.22",
25
- "@repo/tsconfig": "8.0.0-rc.12-dev.22",
26
- "@repo/tsdown": "8.0.0-rc.12-dev.22",
24
+ "@internal/postgres": "8.0.0-rc.13-dev.1",
25
+ "@repo/tsconfig": "8.0.0-rc.13-dev.1",
26
+ "@repo/tsdown": "8.0.0-rc.13-dev.1",
27
27
  "tsdown": "0.22.14",
28
28
  "typescript": "5.9.3"
29
29
  },
@@ -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.12-dev.22'
18
+ library_version: '8.0.0-rc.13-dev.1'
19
19
  version: '2026-09-12'
20
20
  ---
21
21
 
@@ -0,0 +1,349 @@
1
+ ---
2
+ from: "8.0.0-rc.12"
3
+ to: "8.0.0-rc.13"
4
+ changes:
5
+ - id: execution-ref-entry-field
6
+ summary: |
7
+ Each entry in `contract.execution.mutations.defaults` now names its target as
8
+ `ref: { namespace, entry, field }` instead of `ref: { namespace, table, column }`. The values
9
+ are the same table and column names. Re-emit the contract; code that reads `.ref.table` or
10
+ `.ref.column` reads `.ref.entry` and `.ref.field`.
11
+ detection:
12
+ glob: "**/*.{json,ts,mts,cts}"
13
+ matches:
14
+ - '"ref"\s*:\s*\{(?![^{}]*"kind")[^{}]*"(?:table|column)"\s*:'
15
+ - '\.ref\??\.(?:table|column)\b'
16
+ - '\bref\s*:\s*\{(?![^{}]*\bkind\b)[^{}]*\b(?:table|column)\s*:[^{}]*\b(?:table|column)\s*:'
17
+ - id: ts-defaults-encoded-by-codec
18
+ summary: |
19
+ `defineContract` from the Postgres and SQLite packages now encodes every literal `.default(value)` through the column's codec. The literal is the codec's input type. TypeScript checks it for fields built inside the `defineContract` factory, and the build fails with `CONTRACT.DEFAULT_INVALID` for a value the codec refuses. Pass a value of the codec's input type, or choose the field preset whose codec takes the value you have.
20
+ detection:
21
+ glob: "**/*.{ts,mts,cts}"
22
+ matches:
23
+ - '\.default\(\s*(?!now\(\)|autoincrement\(\)|sql`)'
24
+ - id: contract-format-formats-prisma7-schema
25
+ summary: prisma contract format now formats a Prisma 7 schema configured through prisma7Schema().
26
+ - id: config-contract-source-requires-format
27
+ summary: A contract source object written in prisma.config.ts must declare format 'psl' or 'typescript'.
28
+ - id: policy-expression-json-escapes
29
+ summary: A policy expression in a PSL contract decodes every JSON escape, so \t, \b, \f, \/ and \uXXXX no longer read as written.
30
+ - id: cursor-rejects-expression-orders
31
+ summary: "cursor() now throws ORM.ARGUMENT_INVALID when an active orderBy item is not a plain column (extension-operation orders such as vector distance were previously dropped from the keyset silently)"
32
+ detection:
33
+ glob: "**/*.{ts,mts,cts}"
34
+ matches:
35
+ - '\.cursor\('
36
+ - id: mongo-codec-subpaths-move-to-target
37
+ summary: |
38
+ The Mongo codec subpaths moved from the adapter to the target:
39
+ `adapter/codec-types`, `adapter/codecs`, `adapter/codec-ids` and `adapter/data-types` under
40
+ `@prisma/orm-mongo` and `@prisma/orm-target-mongo` are now `target/...`. Emitted
41
+ `contract.d.ts` files, including migration snapshots, import `adapter/codec-types` and no
42
+ longer compile until rewritten or re-emitted.
43
+ detection:
44
+ glob: "**/*.{ts,mts,cts,md}"
45
+ matches:
46
+ - '@prisma/orm-(?:target-)?mongo/adapter/(?:codec-types|codecs|codec-ids|data-types)(?![\w-])'
47
+ - id: create-mongo-runner-deps-removed
48
+ summary: |
49
+ `createMongoRunnerDeps(...)` is removed from `@prisma/orm-mongo/adapter/control`. Build the
50
+ runner dependencies with `new MongoControlAdapterImpl().createRunnerDependencies(controlDriver)`.
51
+ detection:
52
+ glob: "**/*.{ts,mts,cts}"
53
+ matches:
54
+ - '\bcreateMongoRunnerDeps\b'
55
+ - id: mongo-runner-dependency-types-move-to-family
56
+ summary: |
57
+ `MongoRunnerDependencies` and `MarkerOperations` are no longer exported from
58
+ `adapter/control` or `target/control`; import them from `@prisma/orm-mongo/family/control-adapter`.
59
+ detection:
60
+ glob: "**/*.{ts,mts,cts}"
61
+ matches:
62
+ - '\b(?:MongoRunnerDependencies|MarkerOperations)\b[^;]*?from\s*[''"]@prisma/orm-(?:target-)?mongo/(?:adapter|target)/control[''"]'
63
+ - id: mongo-create-runner-needs-adapter-on-stack
64
+ summary: |
65
+ `mongoTargetDescriptor.migrations.createRunner(family)` now reaches the database through the
66
+ control adapter on the family's control stack. A family instance created from an empty
67
+ stack (`createMongoFamilyInstance({} as ...)`), or from a `createControlStack(...)` with no
68
+ `adapter`, fails with "Mongo family requires an adapter descriptor in ControlStack" when the
69
+ runner executes.
70
+ detection:
71
+ glob: "**/*.{ts,mts,cts}"
72
+ matches:
73
+ - 'createMongoFamilyInstance\(\s*\{\s*\}'
74
+ - 'createControlStack\(\s*\{(?:(?!adapter)[^}])*mongoTargetDescriptor(?:(?!adapter)[^}])*\}\s*\)'
75
+ - id: mongo-psl-scalar-names
76
+ summary: |
77
+ Four Mongo PSL scalar names are deprecated in favour of the name of the BSON type they store:
78
+ `Int` → `Int32`, `Float` → `Double`, `Boolean` → `Bool`, `DateTime` → `Date`. The old names
79
+ are still accepted, with a `PSL_DEPRECATED_SCALAR_NAME` warning, and will be removed in a later
80
+ release; rename them now. Codec ids, `contract.json` and every hash are unchanged.
81
+ detection:
82
+ glob: "**/*.prisma"
83
+ matches:
84
+ - '(?:^|\n)[ \t]*[A-Za-z_][A-Za-z0-9_]*[ \t]+(?:Int|Float|Boolean|DateTime)(?:\[\])?\??(?![ \t]*\{)(?=\s|$)'
85
+ - id: mongo-json-field-semantics
86
+ summary: |
87
+ A Mongo `Json` field now admits only JSON values. Its collection validator lists the
88
+ JSON-representable BSON types, which changes the validator and `storageHash` of every Mongo
89
+ contract with a `Json` field, and a document whose `Json` field holds a `Date`, `ObjectId`,
90
+ `Decimal128`, `Binary`, or another non-JSON BSON value at any depth fails to decode.
91
+ - id: mongo-variant-field-codecs
92
+ summary: |
93
+ Through `.variant(...)`, a field declared only on the variant model is now written and read
94
+ through its codec, as base-model fields always were. An `ObjectId` field there is stored as
95
+ an `ObjectId` and read back as a hex string; an `Int64`, `Decimal128` or `Binary` field reads
96
+ back as its application type; `Json` and `Bson` fields refuse values outside their type.
97
+ - id: contract-artifacts-restamp
98
+ summary: |
99
+ The emitted `contract.json` / `contract.d.ts` embed the toolchain version, which moves
100
+ to 8.0.0-rc.13. Run `contract emit` once after upgrading so the emitted artifacts match
101
+ the installed toolchain.
102
+ detection:
103
+ glob: "**/contract.json"
104
+ contains:
105
+ - '"version": "8.0.0-rc.12"'
106
+ ---
107
+
108
+ # 8.0.0-rc.12 → 8.0.0-rc.13 — User upgrade instructions
109
+
110
+ ## `execution-ref-entry-field`
111
+
112
+ A contract with generated defaults (`temporal.createdAt()`, `temporal.updatedAt()`, `@default(uuid())` and the like) carries them under `execution.mutations.defaults`. Each entry's `ref` changes keys; the values do not:
113
+
114
+ ```json
115
+ // before
116
+ { "ref": { "namespace": "public", "table": "user", "column": "updated_at" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } }
117
+
118
+ // after
119
+ { "ref": { "entry": "user", "field": "updated_at", "namespace": "public" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } }
120
+ ```
121
+
122
+ 1. Run `prisma contract emit` so `contract.json` and `contract.d.ts` use the new keys. The runtime rejects a contract whose refs still say `table` and `column` with `Contract structural validation failed: execution.mutations.defaults[0].ref.entry must be a string`.
123
+ 2. Contract snapshots under `migrations/snapshots/<hash>/` that carry an `execution` section have the old keys too. In each `execution.mutations.defaults[].ref`, in both `contract.json` and `contract.d.ts`, rename `table` to `entry` and `column` to `field`, and write the keys in the order `entry`, `field`, `namespace`, which is the order `prisma contract emit` writes. Leave the snapshot's existing `executionHash` as it is. It no longer matches the renamed content, but the snapshot loader re-hashes only the storage section, so nothing checks it. `storageHash` and `profileHash` do not move, so snapshot directory names stay the same.
124
+ 3. Code that reads the section directly changes `.ref.table` to `.ref.entry` and `.ref.column` to `.ref.field`.
125
+
126
+ `executionHash` changes for every contract with generated defaults, because the canonical JSON changes. Nothing compares it against the database, so no migration or re-sign is needed.
127
+
128
+ ## `ts-defaults-encoded-by-codec`
129
+
130
+ A TypeScript contract used to store the value passed to `.default(value)` as it stood. The column's codec now encodes it, so the value must be the codec's input type. For a field built inside the `defineContract` factory, `.default()` is typed with that input type, so a wrong value is a type error in `contract.ts`. PSL contracts, `.default(now())`, `.default(autoincrement())` and `` .default(sql`...`) `` are not affected.
131
+
132
+ What now fails, on Postgres:
133
+
134
+ ```typescript
135
+ // before: emitted, although the codec of field.dateTime() holds a Temporal.Instant
136
+ createdAt: field.dateTime().default('2024-01-01T00:00:00Z'),
137
+ ```
138
+
139
+ ```text
140
+ CONTRACT.DEFAULT_INVALID: Field "Event.createdAt" has a default that its codec refuses: Codec 'pg/timestamptz-temporal@1' encodes a Temporal.Instant, but received a string.
141
+ ```
142
+
143
+ 1. Search your contract files for `.default(` with a literal argument.
144
+ 2. Type-check the contract file, then run `prisma contract emit`. TypeScript reports a default of the wrong type; the emit reports each default the codec refuses, with its model and field.
145
+ 3. For each one, either pass the codec's type or change the preset:
146
+
147
+ | You have | Write |
148
+ | --- | --- |
149
+ | an ISO 8601 string | `field.temporal.timestamptzString().default('2024-01-01T00:00:00Z')` |
150
+ | a JavaScript `Date` | `field.temporal.timestamptzJsDate().default(new Date('2024-01-01T00:00:00Z'))` |
151
+ | a `Temporal.Instant` | `field.dateTime().default(Temporal.Instant.from('2024-01-01T00:00:00Z'))` |
152
+
153
+ Changing the preset changes the column's codec, and so the type your queries read and write for that field. On Postgres, `field.bigint()` takes a `bigint` and `field.bytes()` takes a `Uint8Array`. On SQLite, `field.temporal.datetime()` takes a `Date`, `field.column(bigintColumn)` takes a `bigint` and `field.column(blobColumn)` takes a `Uint8Array`.
154
+
155
+ On Node 24 there is no global `Temporal`. A contract file that creates a `Temporal` value must load an implementation itself, for example with `import 'temporal-polyfill/full/global'` as its first import. The type check on a `Temporal` field needs the `Temporal` type declarations in the project, for example from `temporal-polyfill/global`; without them the parameter is unchecked. The error shown above is the one you get with an implementation loaded; without one, the codec reports that the runtime has no global `Temporal` implementation.
156
+
157
+ `.default(null)` used to store `null`. It is now a type error on a field whose codec input type does not include `null`, which is every built-in codec except the JSON codecs: `field.json().optional().default(null)` still compiles and stores `null`. When the contract is built, the codec receives the `null`: a codec that checks its input, such as `pg/int8@1`, refuses it with `CONTRACT.DEFAULT_INVALID`, and a codec that passes any value through, such as `pg/text@1`, still stores `null`. A column without a default already defaults to `NULL` in the database, so remove the call.
158
+
159
+ A JavaScript `number` on a `bigint` field (codec `pg/int8@1`), such as `field.bigint().default(1)` inside the `defineContract` factory, is now a type error. Write a `bigint` literal: `field.bigint().default(1n)`. The default is stored as `"1"`, as the table below shows.
160
+
161
+ A default that gets past the type check, for example from an untyped caller, is stored in a different form than before:
162
+
163
+ | Default | Stored before | Stored now |
164
+ | --- | --- | --- |
165
+ | `field.bigint().default(1)` (also SQLite `bigintColumn`) | `1` | `"1"` |
166
+ | `field.bytes().default('x')` | `"x"` | `"eA=="` (base64) |
167
+ | SQLite `blobColumn` with `.default('x')` | `"x"` | `"78"` (hex) |
168
+
169
+ A contract with such a default emits a different `contract.json` and a different storage hash. Re-emit the contract and review the diff of `contract.json`.
170
+
171
+ A literal default on a column whose codec no pack in the contract declares now fails with `CONTRACT.DEFAULT_INVALID`, because nothing can check it. List the pack that owns the codec in the `extensions` of `defineContract`.
172
+
173
+ ## `contract-format-formats-prisma7-schema`
174
+
175
+ A project whose `contract` is `prisma7Schema('./prisma/schema.prisma')` used to be skipped by `prisma contract format`. The Prisma 7 source is now a PSL source, so the command formats that file with the Prisma 8 formatter when it parses, and refuses with `PSL.PARSE_FAILED`, without writing, when it does not (for example, a schema with a model whose closing brace is missing). A schema with a `view` block parses, so the command formats it.
176
+
177
+ If the Prisma 7 schema must keep Prisma 7's own formatting, do not run `prisma contract format` on it; format it with Prisma 7's `prisma format` instead.
178
+
179
+ ## `config-contract-source-requires-format`
180
+
181
+ A config that builds `contract.source` itself, as an object with a `load` function, must now give it a `format`: `'psl'` when its inputs are PSL text, and `'typescript'` when it builds the contract in TypeScript, for example `source: { format: 'typescript', load: async () => ok(contract) }`. Without it, or with any other value, every command that reads the config fails with `CONFIG.VALIDATION_FAILED` on the field `contract.source.format`. Sources made by `defineConfig`, `prisma7Schema()`, `prismaContract()` and the TypeScript contract helpers already declare one.
182
+
183
+ ## `policy-expression-json-escapes`
184
+
185
+ A `using` or `withCheck` expression in a `policy_*` block of a PSL contract is a JSON string, the form `prisma contract print` and `prisma contract infer` write. The reader used to decode only `\n`, `\r`, `\"` and `\\`, and kept every other backslash sequence as written. It now also decodes `\t`, `\b`, `\f`, `\/` and `\uXXXX`, so `"a\tb"` reads as `a`, a tab and `b`. A backslash sequence that is not a JSON escape, such as `\d`, is still kept as written.
186
+
187
+ If a PSL contract writes one of those five sequences in a policy expression and means the backslash and the letter, write the backslash twice (`\\t`), then run `prisma contract emit`. Otherwise the emitted policy, and the contract's storage hash, change.
188
+
189
+ ## `cursor-rejects-expression-orders`
190
+
191
+ `db.orm.<ns>.<Model>....cursor(...)` builds its keyset from plain model columns. It throws `ORM.ARGUMENT_INVALID`, naming the 1-based `orderBy` position, when an active order is any of these:
192
+
193
+ - an extension-operation result, such as `(p) => p.embedding.cosineDistance(v).asc()` or `(m) => m.text.fullTextRank(q).desc()`;
194
+ - a relation field, such as `(p) => p.author.name.asc()`;
195
+ - a relation count, such as `(u) => u.posts.count().desc()`;
196
+ - an order with null placement, such as `(p) => p.title.asc({ nulls: 'last' })`.
197
+
198
+ The check runs when `cursor()` is called and again when the query is planned, so an order added after `cursor()` is refused too. Before this change an extension-operation order was left out of the keyset without an error, which returned wrong pages.
199
+
200
+ For each `.cursor(` call on a `db.orm` chain that also calls `.orderBy(`, look at every `orderBy` lambda in the chain. If any of them is one of the orders above, do one of these:
201
+
202
+ - Paginate with `.limit(n).offset(n)` and remove `.cursor(...)`:
203
+
204
+ ```ts
205
+ const page = await db.orm.public.Post
206
+ .orderBy((p) => p.embedding.cosineDistance(v).asc())
207
+ .limit(20)
208
+ .offset(pageIndex * 20)
209
+ .all();
210
+ ```
211
+
212
+ - Keep the cursor and order by plain columns only, for example `(p) => p.createdAt.desc()` and `(p) => p.id.desc()`.
213
+
214
+ `distinctOn()` throws the same error when one of the first N orders, where N is the number of `distinctOn` columns, is not a plain column: Postgres needs those leading orders to match the `DISTINCT ON` columns, so such a query already failed in the database; it now fails earlier, with `ORM.ARGUMENT_INVALID`. Put the `distinctOn` columns first; a relation order, a count or an operation result may follow them. For example, `.orderBy([(post) => post.title.asc(), (post) => post.author.name.asc()]).distinctOn('title')` is accepted.
215
+
216
+ ## `mongo-codec-subpaths-move-to-target`
217
+
218
+ The Mongo target package owns the codecs now. Rewrite each specifier, in every file that names it (source, emitted `contract.d.ts`, the `contract.d.ts` in each `migrations/snapshots/<hash>/` directory, and docs):
219
+
220
+ | Before | After |
221
+ | --- | --- |
222
+ | `@prisma/orm-mongo/adapter/codec-types` | `@prisma/orm-mongo/target/codec-types` |
223
+ | `@prisma/orm-mongo/adapter/codecs` | `@prisma/orm-mongo/target/codecs` |
224
+ | `@prisma/orm-mongo/adapter/codec-ids` | `@prisma/orm-mongo/target/codec-ids` |
225
+ | `@prisma/orm-mongo/adapter/data-types` | `@prisma/orm-mongo/target/data-types` |
226
+ | `@prisma/orm-target-mongo/adapter/<same four>` | `@prisma/orm-target-mongo/target/<same four>` |
227
+
228
+ The exported names are unchanged. For the application's own `contract.d.ts`, running `prisma contract emit` produces the same result as the rewrite. Snapshot `contract.d.ts` files under `migrations/snapshots/` are not re-emitted, so rewrite them. The contract JSON and every hash stay the same.
229
+
230
+ ## `create-mongo-runner-deps-removed`
231
+
232
+ ```ts
233
+ // before
234
+ import { createMongoRunnerDeps, extractDb } from '@prisma/orm-mongo/adapter/control';
235
+ import { MongoDriverImpl } from '@prisma/orm-mongo/driver';
236
+ const runner = new MongoMigrationRunner(
237
+ createMongoRunnerDeps(controlDriver, MongoDriverImpl.fromDb(extractDb(controlDriver)), family),
238
+ );
239
+
240
+ // after
241
+ import { MongoControlAdapterImpl } from '@prisma/orm-mongo/adapter/control';
242
+ const runner = new MongoMigrationRunner(
243
+ new MongoControlAdapterImpl().createRunnerDependencies(controlDriver),
244
+ );
245
+ ```
246
+
247
+ Drop imports that are now unused (`extractDb`, `MongoDriverImpl`, `createMongoFamilyInstance`) and any family instance built only to pass as the third argument.
248
+
249
+ ## `mongo-runner-dependency-types-move-to-family`
250
+
251
+ Change the import of `MongoRunnerDependencies` or `MarkerOperations` to `@prisma/orm-mongo/family/control-adapter`. The shapes are unchanged.
252
+
253
+ ## `mongo-create-runner-needs-adapter-on-stack`
254
+
255
+ Build the family instance from a control stack that includes the Mongo adapter:
256
+
257
+ ```ts
258
+ import mongoAdapter from '@prisma/orm-mongo/adapter/control';
259
+ import { createMongoFamilyInstance, mongoFamilyDescriptor } from '@prisma/orm-mongo/family/control';
260
+ import { createControlStack } from '@prisma/orm-mongo/components/control';
261
+ import { mongoTargetDescriptor } from '@prisma/orm-mongo/target/control';
262
+
263
+ const family = createMongoFamilyInstance(
264
+ createControlStack({ family: mongoFamilyDescriptor, target: mongoTargetDescriptor, adapter: mongoAdapter }),
265
+ );
266
+ ```
267
+
268
+ Code that goes through the CLI or `defineConfig` already has the adapter on the stack and needs no change.
269
+
270
+ ## `mongo-psl-scalar-names`
271
+
272
+ `Int`, `Float`, `Boolean` and `DateTime` are deprecated in Mongo schemas. They are still accepted, and they produce the same contract as the new names, but `prisma contract emit` and the language server report a `PSL_DEPRECATED_SCALAR_NAME` warning for each use, and a later release removes them. Rename them now.
273
+
274
+ Apply this only in a schema whose `prisma.config.ts` uses `@prisma/orm-mongo`. The detection pattern also matches Postgres and SQLite schemas, whose scalar names do not change in this release.
275
+
276
+ In each field whose type is one of the deprecated names, replace the type name, keeping any `[]` and `?`:
277
+
278
+ | Deprecated | Use | Stored as |
279
+ | --- | --- | --- |
280
+ | `Int` | `Int32` | BSON int |
281
+ | `Float` | `Double` | BSON double |
282
+ | `Boolean` | `Bool` | BSON bool |
283
+ | `DateTime` | `Date` | BSON date |
284
+
285
+ ```prisma
286
+ // before
287
+ model Post {
288
+ id ObjectId @id @map("_id")
289
+ views Int
290
+ rating Float?
291
+ published Boolean
292
+ createdAt DateTime
293
+ tags Int[]
294
+ }
295
+
296
+ // after
297
+ model Post {
298
+ id ObjectId @id @map("_id")
299
+ views Int32
300
+ rating Double?
301
+ published Bool
302
+ createdAt Date
303
+ tags Int32[]
304
+ }
305
+ ```
306
+
307
+ This includes the `contract.prisma` copies under `migrations/app/<migration>/`. Then run `prisma contract emit`: `contract.json` and `contract.d.ts` come out the same as before, so no migration or `db sign` is needed. Until the rename, each use reports `warning <file>:<line>:<column> PSL_DEPRECATED_SCALAR_NAME Scalar type "Int" is deprecated and will be removed; use "Int32" (stored as BSON int).`
308
+
309
+ Docs: list only `Int32`, `Double`, `Bool`, `Date`; the old names must not appear in the scalar tables.
310
+
311
+ ## `mongo-json-field-semantics`
312
+
313
+ This change has no detection pattern: which `Json` fields hold non-JSON values depends on the data, which a search of the source cannot see.
314
+
315
+ A Mongo `Json` field (`field.json()` in TypeScript) means a JSON value, no more. Its validator admits BSON `object`, `array`, `string`, `double`, `int`, `long`, `bool` and `null`. Reading a document fails with `RUNTIME.DECODE_FAILED` when the field holds a `Date`, `ObjectId`, `Decimal128`, `Binary`, regular expression, timestamp, or a 64-bit integer outside the safe-integer range, at any depth; the message names the path inside the field. Writing such a value fails with `RUNTIME.ENCODE_FAILED`.
316
+
317
+ To find the affected fields, query each collection with a `Json` field for documents whose field holds a non-JSON value at its top level or as a direct element of an array: a BSON type outside the JSON types, a `long` outside the safe-integer range, or a `NaN` or infinite `double`:
318
+
319
+ ```js
320
+ db.<collection>.find({
321
+ $or: [
322
+ { <field>: { $exists: true, $not: { $type: ['object', 'array', 'string', 'double', 'int', 'long', 'bool', 'null'] } } },
323
+ { <field>: { $elemMatch: { $not: { $type: ['object', 'array', 'string', 'double', 'int', 'long', 'bool', 'null'] } } } },
324
+ { <field>: { $type: 'long', $gt: 9007199254740991 } },
325
+ { <field>: { $type: 'long', $lt: -9007199254740991 } },
326
+ { <field>: { $in: [NaN, Infinity, -Infinity] } },
327
+ ],
328
+ })
329
+ ```
330
+
331
+ The first clause checks the field's own value, and the second checks each direct element when the value is an array. The last three clauses match at both levels. A non-JSON value nested deeper, inside an object or an array of objects, is not visible to this query; reading every document through the ORM finds it, because the read fails with the path of the first such value.
332
+
333
+ 1. For each Mongo `Json` field whose documents hold such values, change its type to `Bson` in PSL (`field.bson()` in TypeScript), which admits any BSON value. A project whose contract source is a Prisma 6 schema (`prisma6Schema`) cannot declare `Bson`: keep its `Json` fields JSON-only, or move the contract source to a Prisma 8 schema and change the type there.
334
+ 2. Run `prisma contract emit`. Every Mongo contract written in Prisma 8 PSL with a `Json` field changes: its validator lists the JSON types and its `storageHash` moves, whether or not step 1 changed anything. A contract built with the TypeScript builder or read from a Prisma 6 schema has no validator, so it does not change; the codec's checks on read and write still apply to it.
335
+ 3. If the contract changed in step 2, run `prisma db update` so each collection's validator matches the contract. It reports the validator change as destructive and asks for confirmation: type the database name, or pass `--confirm <database>` when nobody is there to ask. A project that deploys with migrations runs `prisma migration plan` instead and applies the new migration the way it applies any other.
336
+
337
+ ## `mongo-variant-field-codecs`
338
+
339
+ This change has no detection pattern: it applies to every model with `@@base` whose own fields are written or read through `.variant(...)`, and the effect depends on the field types.
340
+
341
+ Before, a field declared only on a variant model (`model Photo { ownerId ObjectId @@base(Asset, "photo") }`) was passed to the driver as the application wrote it and returned as the driver read it. An `ObjectId` field written as a hex string was refused by the collection validator when the contract was written in Prisma 8 PSL; a contract built with the TypeScript builder or read from a Prisma 6 schema has no validator, so there the hex string was stored as a string. A where filter on such a field passed its value unencoded, so a hex string matched nothing where an `ObjectId` was stored; it is now encoded and matches. A stored `ObjectId`, `Long`, `Decimal128` or `Binary` came back as the driver's class instead of the application type.
342
+
343
+ 1. Remove any workaround that converted such values by hand, for example passing `new ObjectId(hex)` for a variant `ObjectId` field or calling `.toHexString()` on what a read returned; pass and expect the application types listed in the scalar types reference instead.
344
+ 2. Documents written before this change by a TypeScript-builder or Prisma 6 contract may hold a variant `ObjectId` field as a string; a filter with a hex string now looks for an `ObjectId` and does not match them. Convert the ones that are 24-character hex strings with `db.<collection>.updateMany({ <field>: { $type: 'string', $regex: /^[0-9a-fA-F]{24}$/ } }, [{ $set: { <field>: { $toObjectId: '$<field>' } } }])`. The regex matters: without it, one string that is not an `ObjectId` in hex makes `$toObjectId` fail the whole command. Other strings, `ObjectId` values and documents without the field are left as they are.
345
+ 3. A read through the base collection (without `.variant(...)`) still returns variant-only fields as the driver read them.
346
+
347
+ ## `contract-artifacts-restamp`
348
+
349
+ For every `contract.json` matched by `detection`, run the project's emit command (`prisma contract emit`, or the project's `contract:emit` script) once after upgrading. This entry accounts for the embedded `version` moving to `8.0.0-rc.13`; any other difference in the emitted files comes from an earlier entry in this guide.
@@ -0,0 +1,473 @@
1
+ ---
2
+ from: "8.0.0-rc.12"
3
+ to: "8.0.0-rc.13"
4
+ changes:
5
+ - id: execution-ref-entry-field
6
+ summary: |
7
+ Each entry in `contract.execution.mutations.defaults` now names its target as
8
+ `ref: { namespace, entry, field }` instead of `ref: { namespace, table, column }`. The values
9
+ are the same table and column names. Re-emit the contract; code and types that read `.ref.table` or
10
+ `.ref.column` read `.ref.entry` and `.ref.field`.
11
+ detection:
12
+ glob: "**/*.{json,ts,mts,cts}"
13
+ matches:
14
+ - '"ref"\s*:\s*\{(?![^{}]*"kind")[^{}]*"(?:table|column)"\s*:'
15
+ - '\.ref\??\.(?:table|column)\b'
16
+ - '\bref\s*:\s*\{(?![^{}]*\bkind\b)[^{}]*\b(?:table|column)\s*:[^{}]*\b(?:table|column)\s*:'
17
+ - id: mutation-default-generator-types-move-to-framework
18
+ summary: |
19
+ `GeneratorStability` and `RuntimeMutationDefaultGenerator` are no longer exported by the SQL
20
+ runtime (`@prisma/orm-family-sql/runtime`, `@prisma/orm-postgres/family-runtime`,
21
+ `@prisma/orm-sqlite/family-runtime`). Import them from `@prisma/orm-framework/components/runtime`.
22
+ detection:
23
+ glob: "**/*.{ts,mts,cts}"
24
+ matches:
25
+ - '\bimport\s+(?:type\s+)?\{[^}]*\b(?:GeneratorStability|RuntimeMutationDefaultGenerator)\b[^}]*\}\s*from\s*[''"](?:@internal/sql-runtime|@prisma/orm-family-sql/runtime|@prisma/orm-(?:postgres|sqlite)/family-runtime)[''"]'
26
+ - id: mutation-defaults-options-entry-field
27
+ summary: |
28
+ `applyMutationDefaults` options name the storage entry as `entry` instead of `table`, and each
29
+ applied default names its field as `field` instead of `column`. `MutationDefaultsOptions`,
30
+ `AppliedMutationDefault` and `MutationDefaultsOp` now come from
31
+ `@prisma/orm-framework/components/runtime`, not from the SQL `relational-core/query-lane-context` subpath.
32
+ detection:
33
+ glob: "**/*.{ts,mts,cts}"
34
+ matches:
35
+ - '\bapplyMutationDefaults\s*\(\s*\{[^{}]*\btable\s*:'
36
+ - '\bimport\s+(?:type\s+)?\{[^}]*\b(?:AppliedMutationDefault|MutationDefaultsOptions|MutationDefaultsOp)\b[^}]*\}\s*from\s*[''"](?:@internal/sql-relational-core/query-lane-context|@prisma/orm-(?:family-sql|postgres|sqlite)/relational-core/query-lane-context)[''"]'
37
+ - id: temporal-presets-move-to-framework
38
+ summary: |
39
+ `TIMESTAMP_NOW_GENERATOR_ID`, `temporalAuthoringPresets` and `temporalCodecPreset` moved from
40
+ the SQL family's `family/control` subpath to the framework's `components/authoring`, and
41
+ `timestampNowControlDescriptor` moved to `components/control`. `temporalCodecPresetWithPrecision`
42
+ and `temporalStringAuthoringPresets` stay in `family/control`.
43
+ detection:
44
+ glob: "**/*.{ts,mts,cts}"
45
+ matches:
46
+ - '\b(?:TIMESTAMP_NOW_GENERATOR_ID|temporalAuthoringPresets|temporalCodecPreset|timestampNowControlDescriptor)\b[^;]*?from\s*[''"](?:@internal/family-sql/control|@prisma/orm-(?:family-sql|postgres|sqlite)/family/control)[''"]'
47
+ - id: temporal-preset-builders-take-storage-template
48
+ summary: |
49
+ `temporalAuthoringPresets`, `temporalStringAuthoringPresets` and `temporalCodecPreset` take the
50
+ codec's storage template as one type parameter (`<Storage, GeneratorId>`, or `<Storage>` for
51
+ `temporalCodecPreset`) instead of `<CodecId, NativeType, GeneratorId>`. Calls that let
52
+ TypeScript infer the type arguments are unchanged.
53
+ detection:
54
+ glob: "**/*.{ts,mts,cts}"
55
+ matches:
56
+ - '\b(?:temporalAuthoringPresets|temporalStringAuthoringPresets|temporalCodecPreset)\s*<'
57
+ - id: ts-defaults-encoded-by-codec
58
+ summary: |
59
+ `defineContract` from the Postgres and SQLite packages now encodes every literal `.default(value)` through the column's codec. The literal is the codec's input type. TypeScript checks it for fields built inside the `defineContract` factory, and the build fails with `CONTRACT.DEFAULT_INVALID` for a value the codec refuses. Pass a value of the codec's input type, or choose the field preset whose codec takes the value you have.
60
+ detection:
61
+ glob: "**/*.{ts,mts,cts}"
62
+ matches:
63
+ - '\.default\(\s*(?!now\(\)|autoincrement\(\)|sql`)'
64
+ - id: contract-source-format-is-psl-or-typescript
65
+ summary: Every contract source declares format 'psl' or 'typescript'; format is required, and OpaqueContractSourceProvider is removed.
66
+ - id: prisma7-schema-source-declares-psl
67
+ summary: The prisma7Schema() contract source declares format 'psl' instead of 'prisma7'.
68
+ - id: print-psl-description-option
69
+ summary: printPsl() from @internal/psl-printer opens every file with only the // use prisma-8 marker unless the caller passes description.
70
+ - id: family-sql-psl-build-export
71
+ summary: mapDefault, its option types, the PslTypeMap types and toEnumMemberName moved from @internal/family-sql/psl-infer to @internal/family-sql/psl-build.
72
+ - id: cursor-rejects-expression-orders
73
+ summary: "cursor() now throws ORM.ARGUMENT_INVALID when an active orderBy item is not a plain column (extension-operation orders such as vector distance were previously dropped from the keyset silently)"
74
+ detection:
75
+ glob: "**/*.{ts,mts,cts}"
76
+ matches:
77
+ - '\.cursor\('
78
+ - id: order-by-item-nulls
79
+ summary: "OrderByItem from @internal/sql-relational-core/ast carries a nulls placement: its constructor takes a required third argument, withExpr rebuilds an item around a new expression, and every renderer must emit nulls wherever it emits dir"
80
+ detection:
81
+ glob: "**/*.{ts,mts,cts}"
82
+ matches:
83
+ - 'new OrderByItem\('
84
+ - '\.dir\.toUpperCase\('
85
+ - id: include-expr-join-column-lists
86
+ summary: |
87
+ `IncludeExpr.localColumn` and `IncludeExpr.targetColumn` are now `localColumns` and
88
+ `targetColumns`: ordered string arrays paired by index, with one entry per column of the
89
+ relation's key.
90
+ detection:
91
+ glob: "**/*.{ts,mts,cts}"
92
+ matches:
93
+ - '\.(?:localColumn|targetColumn)\b'
94
+ - id: mongo-codec-subpaths-move-to-target
95
+ summary: |
96
+ The Mongo codecs moved from `@internal/adapter-mongo` to `@internal/target-mongo`: the
97
+ `codec-types`, `codecs`, `codec-ids` and `data-types` subpaths of the adapter are gone and
98
+ live under the target. The same move applies to the published `adapter/*` subpaths of
99
+ `@prisma/orm-mongo` and `@prisma/orm-target-mongo`, which are now `target/*`.
100
+ detection:
101
+ glob: "**/*.{ts,mts,cts,md}"
102
+ matches:
103
+ - '@internal/adapter-mongo/(?:codec-types|codecs|codec-ids|data-types)(?![\w-])'
104
+ - '@prisma/orm-(?:target-)?mongo/adapter/(?:codec-types|codecs|codec-ids|data-types)(?![\w-])'
105
+ - id: create-mongo-runner-deps-removed
106
+ summary: |
107
+ `createMongoRunnerDeps(...)` is removed from `@internal/adapter-mongo/control`. Build the
108
+ runner dependencies with `new MongoControlAdapterImpl().createRunnerDependencies(controlDriver)`.
109
+ detection:
110
+ glob: "**/*.{ts,mts,cts}"
111
+ matches:
112
+ - '\bcreateMongoRunnerDeps\b'
113
+ - id: mongo-runner-dependency-types-move-to-family
114
+ summary: |
115
+ `MongoRunnerDependencies` and `MarkerOperations` are exported from
116
+ `@internal/family-mongo/control-adapter`, no longer from `@internal/adapter-mongo/control` or
117
+ `@internal/target-mongo/control`.
118
+ detection:
119
+ glob: "**/*.{ts,mts,cts}"
120
+ matches:
121
+ - '\b(?:MongoRunnerDependencies|MarkerOperations)\b[^;]*?from\s*[''"]@internal/(?:adapter|target)-mongo/control[''"]'
122
+ - '\b(?:MongoRunnerDependencies|MarkerOperations)\b[^;]*?from\s*[''"]@prisma/orm-(?:target-)?mongo/(?:adapter|target)/control[''"]'
123
+ - id: mongo-control-adapter-creates-runner-dependencies
124
+ summary: |
125
+ The `MongoControlAdapter` SPI gains `createRunnerDependencies(driver)`, and
126
+ `MongoControlFamilyInstance` gains `createRunnerDependencies({ driver })`. A custom
127
+ implementation of either must add the method.
128
+ detection:
129
+ glob: "**/*.{ts,mts,cts}"
130
+ matches:
131
+ - '\bimplements\s+(?:[\w.]+\s*,\s*)*MongoControlAdapter\b'
132
+ - ':\s*MongoControlFamilyInstance\s*=\s*\{'
133
+ - id: mongo-create-runner-needs-adapter-on-stack
134
+ summary: |
135
+ `mongoTargetDescriptor.migrations.createRunner(family)` reaches the database through the
136
+ control adapter on the family's control stack. A family instance created from an empty stack,
137
+ or from a `createControlStack(...)` with no `adapter`, fails with "Mongo family requires an
138
+ adapter descriptor in ControlStack" when the runner executes.
139
+ detection:
140
+ glob: "**/*.{ts,mts,cts}"
141
+ matches:
142
+ - 'createMongoFamilyInstance\(\s*\{\s*\}'
143
+ - 'createControlStack\(\s*\{(?:(?!adapter)[^}])*mongoTargetDescriptor(?:(?!adapter)[^}])*\}\s*\)'
144
+ - id: mongo-execution-context-applies-mutation-defaults
145
+ summary: |
146
+ `MongoExecutionContext` gains a required `applyMutationDefaults(options)` method, which fills
147
+ the contract's execution defaults on ORM writes. Contexts built with
148
+ `createMongoExecutionContext` have it; an object literal typed as a `MongoExecutionContext`
149
+ (usually a test double) must add it.
150
+ detection:
151
+ glob: "**/*.{ts,mts,cts}"
152
+ matches:
153
+ - '(?:\)|\b\w+)\s*:\s*MongoExecutionContext\b(?:<[^>]*>)?\s*(?:=\s*\{|\{)'
154
+ - '\bsatisfies\s+MongoExecutionContext\b'
155
+ - id: mongo-field-builder-execution-defaults-parameter
156
+ summary: |
157
+ The Mongo `FieldBuilder` type gains a fifth type parameter, the field's execution defaults,
158
+ defaulting to `undefined`, so a bare `FieldBuilder` constraint rejects preset fields such as
159
+ `field.temporal.createdAt()`; widen it to
160
+ `FieldBuilder<ContractFieldType, boolean, boolean, EnumTypeHandle | undefined, ExecutionMutationDefaultPhases | undefined>`.
161
+ - id: mongo-bson-codec-added
162
+ summary: |
163
+ The Mongo target gains the codec `mongo/bson@1` for any BSON value, typed `BsonInputValue`
164
+ on write and `BsonValue` on read in `CodecTypes`; `mongo/json@1` now admits only JSON values.
165
+ Collection validators now read a codec's whole `targetTypes` list, not only its first entry,
166
+ and an enum's codec must declare exactly one.
167
+ detection:
168
+ glob: "**/*.{ts,mts,cts}"
169
+ matches:
170
+ - 'targetTypes\s*:\s*\[\s*[^\]\s,][^\],]*,\s*[^\]\s]'
171
+ ---
172
+
173
+ # 8.0.0-rc.12 → 8.0.0-rc.13 — Extension author upgrade instructions
174
+
175
+ ## `execution-ref-entry-field`
176
+
177
+ A contract with generated defaults (`temporal.createdAt()`, `temporal.updatedAt()`, `@default(uuid())` and the like) carries them under `execution.mutations.defaults`. Each entry's `ref` changes keys; the values do not:
178
+
179
+ ```json
180
+ // before
181
+ { "ref": { "namespace": "public", "table": "user", "column": "updated_at" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } }
182
+
183
+ // after
184
+ { "ref": { "entry": "user", "field": "updated_at", "namespace": "public" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } }
185
+ ```
186
+
187
+ 1. Run `prisma contract emit` so `contract.json` and `contract.d.ts` use the new keys. The runtime rejects a contract whose refs still say `table` and `column` with `Contract structural validation failed: execution.mutations.defaults[0].ref.entry must be a string`.
188
+ 2. Contract snapshots under `migrations/snapshots/<hash>/` that carry an `execution` section have the old keys too. In each `execution.mutations.defaults[].ref`, in both `contract.json` and `contract.d.ts`, rename `table` to `entry` and `column` to `field`, and write the keys in the order `entry`, `field`, `namespace`, which is the order `prisma contract emit` writes. Leave the snapshot's existing `executionHash` as it is. It no longer matches the renamed content, but the snapshot loader re-hashes only the storage section, so nothing checks it. `storageHash` and `profileHash` do not move, so snapshot directory names stay the same.
189
+ 3. Code that reads the section directly changes `.ref.table` to `.ref.entry` and `.ref.column` to `.ref.field`.
190
+
191
+ `executionHash` changes for every contract with generated defaults, because the canonical JSON changes. Nothing compares it against the database, so no migration or re-sign is needed.
192
+
193
+ ### For extension authors
194
+
195
+ - The framework type `ExecutionMutationDefault['ref']` from `@internal/contract/types` is `{ namespace: string; entry: string; field: string }`. A type that matches refs by shape (for example a create-input type that checks whether a column has a generated default) matches `entry` and `field`.
196
+ - A pack that ships a contract with generated defaults re-emits it with `prisma contract emit`, and updates its pinned contract-space snapshots as in step 2.
197
+ - The options passed to `applyMutationDefaults` change separately: see `mutation-defaults-options-entry-field` below for the rename of `table` to `entry`.
198
+
199
+ ## `mutation-default-generator-types-move-to-framework`
200
+
201
+ The mutation-default generator runtime now lives in the framework and serves every family. A pack that contributes generators (`mutationDefaultGenerators: () => [...]` on a runtime target, adapter, or extension descriptor) types them with the framework type:
202
+
203
+ ```ts
204
+ // before
205
+ import type { RuntimeMutationDefaultGenerator } from '@prisma/orm-family-sql/runtime';
206
+
207
+ // after
208
+ import type { RuntimeMutationDefaultGenerator } from '@prisma/orm-framework/components/runtime';
209
+ ```
210
+
211
+ Do the same for `GeneratorStability`. The shape is unchanged: `{ id, generate(params?), stability: 'field' | 'row' | 'query' }`.
212
+
213
+ ## `mutation-defaults-options-entry-field`
214
+
215
+ Code that calls `applyMutationDefaults` on an execution context, or stubs it in tests, renames two keys:
216
+
217
+ ```ts
218
+ // before
219
+ const applied = context.applyMutationDefaults({ op: 'create', table: tableName, namespace, values });
220
+ for (const def of applied) row[def.column] = def.value;
221
+
222
+ // after
223
+ const applied = context.applyMutationDefaults({ op: 'create', entry: tableName, namespace, values });
224
+ for (const def of applied) row[def.field] = def.value;
225
+ ```
226
+
227
+ 1. In every `applyMutationDefaults({ ... })` call, rename the `table` key to `entry`. The value is the same table name.
228
+ 2. Where the result is read, rename `.column` to `.field`. A stub that returns applied defaults returns `{ field, value }`.
229
+ 3. Import `MutationDefaultsOptions`, `AppliedMutationDefault` and `MutationDefaultsOp` from `@prisma/orm-framework/components/runtime`; the SQL `relational-core/query-lane-context` subpath no longer exports them.
230
+
231
+ A key present in `values` counts as explicit whatever its value, `undefined` included, and gets no default. That rule is unchanged; drop `undefined` values before the call if they should be defaulted.
232
+
233
+ ## `temporal-presets-move-to-framework`
234
+
235
+ The target-neutral temporal preset builders now live in the framework, so a non-SQL target can build `temporal.*` presets too. Change the import source; the functions behave the same.
236
+
237
+ ```ts
238
+ // before
239
+ import {
240
+ TIMESTAMP_NOW_GENERATOR_ID,
241
+ temporalAuthoringPresets,
242
+ temporalCodecPreset,
243
+ timestampNowControlDescriptor,
244
+ } from '@prisma/orm-family-sql/family/control';
245
+
246
+ // after
247
+ import {
248
+ TIMESTAMP_NOW_GENERATOR_ID,
249
+ temporalAuthoringPresets,
250
+ temporalCodecPreset,
251
+ } from '@prisma/orm-framework/components/authoring';
252
+ import { timestampNowControlDescriptor } from '@prisma/orm-framework/components/control';
253
+ ```
254
+
255
+ The same applies to the `family/control` subpaths of `@prisma/orm-postgres` and `@prisma/orm-sqlite` (move to their `components/authoring` and `components/control` subpaths), and to `@internal/family-sql/control` (move to `@internal/framework-components/authoring` and `@internal/framework-components/control`). Keep importing `temporalCodecPresetWithPrecision` and `temporalStringAuthoringPresets` from `family/control`.
256
+
257
+ ## `temporal-preset-builders-take-storage-template`
258
+
259
+ The builders now carry the codec's whole storage template through, so their type parameters changed. Drop explicit type arguments and let TypeScript infer them:
260
+
261
+ ```ts
262
+ // before
263
+ temporalAuthoringPresets<'pg/timestamptz@1', 'timestamptz'>({ codecId: 'pg/timestamptz@1', nativeType: 'timestamptz' });
264
+
265
+ // after
266
+ temporalAuthoringPresets({ codecId: 'pg/timestamptz@1', nativeType: 'timestamptz' });
267
+ ```
268
+
269
+ If you need to name them, the first type parameter is the storage template object type (`{ readonly codecId: ...; readonly nativeType: ... }`).
270
+
271
+ ## `ts-defaults-encoded-by-codec`
272
+
273
+ A TypeScript contract used to store the value passed to `.default(value)` as it stood. The column's codec now encodes it, so the value must be the codec's input type. For a field built inside the `defineContract` factory, `.default()` is typed with that input type, so a wrong value is a type error in `contract.ts`. PSL contracts, `.default(now())`, `.default(autoincrement())` and `` .default(sql`...`) `` are not affected.
274
+
275
+ What now fails, on Postgres:
276
+
277
+ ```typescript
278
+ // before: emitted, although the codec of field.dateTime() holds a Temporal.Instant
279
+ createdAt: field.dateTime().default('2024-01-01T00:00:00Z'),
280
+ ```
281
+
282
+ ```text
283
+ CONTRACT.DEFAULT_INVALID: Field "Event.createdAt" has a default that its codec refuses: Codec 'pg/timestamptz-temporal@1' encodes a Temporal.Instant, but received a string.
284
+ ```
285
+
286
+ 1. Search your contract files for `.default(` with a literal argument.
287
+ 2. Type-check the contract file, then run `prisma contract emit`. TypeScript reports a default of the wrong type; the emit reports each default the codec refuses, with its model and field.
288
+ 3. For each one, either pass the codec's type or change the preset:
289
+
290
+ | You have | Write |
291
+ | --- | --- |
292
+ | an ISO 8601 string | `field.temporal.timestamptzString().default('2024-01-01T00:00:00Z')` |
293
+ | a JavaScript `Date` | `field.temporal.timestamptzJsDate().default(new Date('2024-01-01T00:00:00Z'))` |
294
+ | a `Temporal.Instant` | `field.dateTime().default(Temporal.Instant.from('2024-01-01T00:00:00Z'))` |
295
+
296
+ Changing the preset changes the column's codec, and so the type your queries read and write for that field. On Postgres, `field.bigint()` takes a `bigint` and `field.bytes()` takes a `Uint8Array`. On SQLite, `field.temporal.datetime()` takes a `Date`, `field.column(bigintColumn)` takes a `bigint` and `field.column(blobColumn)` takes a `Uint8Array`.
297
+
298
+ On Node 24 there is no global `Temporal`. A contract file that creates a `Temporal` value must load an implementation itself, for example with `import 'temporal-polyfill/full/global'` as its first import. The type check on a `Temporal` field needs the `Temporal` type declarations in the project, for example from `temporal-polyfill/global`; without them the parameter is unchecked. The error shown above is the one you get with an implementation loaded; without one, the codec reports that the runtime has no global `Temporal` implementation.
299
+
300
+ `.default(null)` used to store `null`. It is now a type error on a field whose codec input type does not include `null`, which is every built-in codec except the JSON codecs: `field.json().optional().default(null)` still compiles and stores `null`. When the contract is built, the codec receives the `null`: a codec that checks its input, such as `pg/int8@1`, refuses it with `CONTRACT.DEFAULT_INVALID`, and a codec that passes any value through, such as `pg/text@1`, still stores `null`. A column without a default already defaults to `NULL` in the database, so remove the call.
301
+
302
+ A JavaScript `number` on a `bigint` field (codec `pg/int8@1`), such as `field.bigint().default(1)` inside the `defineContract` factory, is now a type error. Write a `bigint` literal: `field.bigint().default(1n)`. The default is stored as `"1"`, as the table below shows.
303
+
304
+ A default that gets past the type check, for example from an untyped caller, is stored in a different form than before:
305
+
306
+ | Default | Stored before | Stored now |
307
+ | --- | --- | --- |
308
+ | `field.bigint().default(1)` (also SQLite `bigintColumn`) | `1` | `"1"` |
309
+ | `field.bytes().default('x')` | `"x"` | `"eA=="` (base64) |
310
+ | SQLite `blobColumn` with `.default('x')` | `"x"` | `"78"` (hex) |
311
+
312
+ A contract with such a default emits a different `contract.json` and a different storage hash. Re-emit the contract and review the diff of `contract.json`.
313
+
314
+ A literal default on a column whose codec no pack in the contract declares now fails with `CONTRACT.DEFAULT_INVALID`, because nothing can check it. List the pack that owns the codec in the `extensions` of `defineContract`.
315
+
316
+ ### For extension authors
317
+
318
+ - A pack passed in `extensions` contributes its codecs to this lookup through `types.codecTypes.codecDescriptors`. A default on a column of your codec is now passed to your codec's `encodeJson`, built with the column's `typeParams`. Make `encodeJson` throw for a value it cannot encode; the build reports your message.
319
+ - A codec descriptor that the target's codec registry refuses (for Postgres, one that does not extend `PostgresCodecDescriptor` and is not wrapped with `postgresCodec()`), or that reuses a built-in codec id, now fails `defineContract` as it already failed when the control stack was assembled.
320
+ - A caller who passes `codecLookup` to `defineContract` keeps that lookup; the facade does not add to it.
321
+ - A column helper that is a function, such as `varcharColumn(n)`, `timeTemporalColumn()` or pgvector's `vector(n)`, now declares its literal codec id in its return type. One variable can no longer be reassigned between the results of different helpers; annotate such a variable as `ColumnTypeDescriptor`.
322
+
323
+ ## `contract-source-format-is-psl-or-typescript`
324
+
325
+ `ContractSourceProvider` from `@internal/config/config-types` is now the union of `PslContractSourceProvider` (`format: 'psl'`) and `TypeScriptContractSourceProvider` (`format: 'typescript'`). `format` is required on both. `OpaqueContractSourceProvider` and every other `format` value are gone, and config validation reports a source with no `format`, or any other value, as an issue on `contract.source.format`.
326
+
327
+ Give every contract source an extension defines a `format`: `'psl'` when its inputs are PSL text, and `'typescript'` when it builds the contract in TypeScript. Replace imports of `OpaqueContractSourceProvider` with `ContractSourceProvider`.
328
+
329
+ ## `prisma7-schema-source-declares-psl`
330
+
331
+ The source returned by `prisma7Schema()` now declares `format: 'psl'`, because a Prisma 7 schema is PSL text. It still does not implement `interpret`. Code that compared `source.format` with `'prisma7'` must stop doing so; nothing in the framework tells a Prisma 7 schema from a Prisma 8 one by its format.
332
+
333
+ ## `print-psl-description-option`
334
+
335
+ `printPsl()` from `@internal/psl-printer` used to open every file with the `// use prisma-8` marker and a line saying the contract was inferred from the live database. It now writes only the marker, and a second comment line only when the caller passes `description`. Code that prints an inferred contract and wants the old second line passes it:
336
+
337
+ ```ts
338
+ printPsl(ast, {
339
+ pslBlockDescriptors,
340
+ description:
341
+ 'Contract inferred from the live database schema. Edit as needed, then run `prisma contract emit`.',
342
+ });
343
+ ```
344
+
345
+ ## `family-sql-psl-build-export`
346
+
347
+ `contract print` uses some of what `@internal/family-sql/psl-infer` exported, so those exports moved to the new subpath `@internal/family-sql/psl-build`: `mapDefault`, `DefaultMappingOptions`, `DefaultMappingResult`, `PslTypeMap`, `PslTypeReference`, `PslTypeResolution` and `toEnumMemberName`. Import them from `@internal/family-sql/psl-build`. Everything else stays in `@internal/family-sql/psl-infer`.
348
+
349
+ ## `cursor-rejects-expression-orders`
350
+
351
+ A SQL ORM `cursor()` builds its keyset from plain model columns. It throws `ORM.ARGUMENT_INVALID`, naming the 1-based `orderBy` position, when an active order is any of these:
352
+
353
+ - an extension-operation result, such as `(p) => p.embedding.cosineDistance(v).asc()` or `(m) => m.text.fullTextRank(q).desc()`;
354
+ - a relation field, such as `(p) => p.author.name.asc()`;
355
+ - a relation count, such as `(u) => u.posts.count().desc()`;
356
+ - an order with null placement, such as `(p) => p.title.asc({ nulls: 'last' })`.
357
+
358
+ The check runs when `cursor()` is called and again when the query is planned, so an order added after `cursor()` is refused too. Before this change an extension-operation order was left out of the keyset without an error, which returned wrong pages.
359
+
360
+ For each `.cursor(` call in a chain that also calls `.orderBy(`, look at every `orderBy` lambda in the chain. If any of them is one of the orders above, do one of these:
361
+
362
+ - Paginate with `.limit(n).offset(n)` and remove `.cursor(...)`.
363
+ - Keep the cursor and order by plain columns only, for example `(p) => p.createdAt.desc()` and `(p) => p.id.desc()`.
364
+
365
+ `distinctOn()` throws the same error when one of the first N orders, where N is the number of `distinctOn` columns, is not a plain column: Postgres needs those leading orders to match the `DISTINCT ON` columns, so such a query already failed in the database; it now fails earlier, with `ORM.ARGUMENT_INVALID`. Put the `distinctOn` columns first; a relation order, a count or an operation result may follow them. For example, `.orderBy([(post) => post.title.asc(), (post) => post.author.name.asc()]).distinctOn('title')` is accepted.
366
+
367
+ ## `order-by-item-nulls`
368
+
369
+ `OrderByItem` has a `nulls` field of type `'first' | 'last' | undefined`, and `undefined` means the database default. The constructor refuses any other value, and any direction other than `'asc'` / `'desc'`, with `RUNTIME.AST_INVALID`.
370
+
371
+ Constructing an item:
372
+
373
+ - `new OrderByItem(expr, dir)` no longer compiles. Pass the placement as the third argument, `new OrderByItem(expr, dir, undefined)`, or use `OrderByItem.asc(expr, { nulls })` / `OrderByItem.desc(expr, { nulls })`.
374
+ - To reorder by a different expression while keeping an existing item's direction and placement, write `item.withExpr(expr)` instead of `new OrderByItem(expr, item.dir)`. The hand-written form drops `nulls`.
375
+
376
+ Rendering an item: an adapter or renderer that writes `ORDER BY` itself must write the placement after the direction, `NULLS FIRST` for `'first'` and `NULLS LAST` for `'last'`, in every position it renders an `OrderByItem` (query, window and aggregate `ORDER BY`). A renderer that ignores `nulls` compiles and returns rows in the wrong order. Map `dir` and `nulls` through a fixed table rather than interpolating the string. A dialect without `NULLS FIRST` / `NULLS LAST` must emulate the placement, for example with a leading `expr IS NULL` key, and must not drop it.
377
+
378
+ ## `include-expr-join-column-lists`
379
+
380
+ An ORM include now joins the related rows on every column of the relation's key, not only the first. So `IncludeExpr` (exported from `@prisma/orm-family-sql/orm-client`, and the element type of `Collection.state.includes`) carries the key as two lists instead of two strings:
381
+
382
+ - `localColumn: string` is now `localColumns: readonly string[]`, the key columns in the parent table.
383
+ - `targetColumn: string` is now `targetColumns: readonly string[]`, the matching columns in the related table.
384
+
385
+ Both lists have the same length, and `localColumns[i]` joins to `targetColumns[i]`. A single-column relation has one entry in each list.
386
+
387
+ Update code that reads these fields to use the whole lists and pair them by index. Code that builds an `IncludeExpr` by hand passes arrays, for example `{ localColumns: ['id'], targetColumns: ['user_id'] }` instead of `{ localColumn: 'id', targetColumn: 'user_id' }`. Do not keep only the first entry of each list: for a composite key that matches every related row that shares the first key column, which is the bug this change fixes.
388
+
389
+ ## `mongo-codec-subpaths-move-to-target`
390
+
391
+ Rewrite each specifier. The exported names are unchanged.
392
+
393
+ | Before | After |
394
+ | --- | --- |
395
+ | `@internal/adapter-mongo/codec-types` | `@internal/target-mongo/codec-types` |
396
+ | `@internal/adapter-mongo/codecs` | `@internal/target-mongo/codecs` |
397
+ | `@internal/adapter-mongo/codec-ids` | `@internal/target-mongo/codec-ids` |
398
+ | `@internal/adapter-mongo/data-types` | `@internal/target-mongo/data-types` |
399
+ | `@prisma/orm-mongo/adapter/<same four>` | `@prisma/orm-mongo/target/<same four>` |
400
+ | `@prisma/orm-target-mongo/adapter/<same four>` | `@prisma/orm-target-mongo/target/<same four>` |
401
+
402
+ Then re-sort the import block with the package's formatter (the new specifier sorts after `@internal/mongo-*`). A package that imports from `@internal/target-mongo` for the first time needs it in `dependencies`. A pack that emits a Mongo `contract.d.ts` re-emits it with `prisma contract emit`, or applies the same rewrite to the committed file; the contract JSON and hashes do not change.
403
+
404
+ `@internal/target-mongo/codecs` also exports `mongoStandardCodecs` and `buildStandardCodecRegistry`, which the adapter used internally before.
405
+
406
+ ## `create-mongo-runner-deps-removed`
407
+
408
+ ```ts
409
+ // before
410
+ import { createMongoRunnerDeps, extractDb } from '@internal/adapter-mongo/control';
411
+ import { MongoDriverImpl } from '@internal/driver-mongo';
412
+ const deps = createMongoRunnerDeps(controlDriver, MongoDriverImpl.fromDb(extractDb(controlDriver)), family);
413
+
414
+ // after
415
+ import { MongoControlAdapterImpl } from '@internal/adapter-mongo/control';
416
+ const deps = new MongoControlAdapterImpl().createRunnerDependencies(controlDriver);
417
+ ```
418
+
419
+ Drop imports that are now unused and any family instance built only to pass as the third argument.
420
+
421
+ ## `mongo-runner-dependency-types-move-to-family`
422
+
423
+ Change the import of `MongoRunnerDependencies` or `MarkerOperations` to `@internal/family-mongo/control-adapter`. The shapes are unchanged.
424
+
425
+ ## `mongo-control-adapter-creates-runner-dependencies`
426
+
427
+ Add the method to a custom control adapter. It returns the `MongoRunnerDependencies` the migration runner uses for that driver:
428
+
429
+ ```ts
430
+ createRunnerDependencies(driver: ControlDriverInstance<'mongo', 'mongo'>): MongoRunnerDependencies
431
+ ```
432
+
433
+ A custom family instance forwards `createRunnerDependencies({ driver })` to the control adapter it resolves from the stack.
434
+
435
+ ## `mongo-create-runner-needs-adapter-on-stack`
436
+
437
+ Create the family instance from a control stack that includes the Mongo adapter:
438
+
439
+ ```ts
440
+ createMongoFamilyInstance(
441
+ createControlStack({ family: mongoFamilyDescriptor, target: mongoTargetDescriptor, adapter: mongoAdapterDescriptor }),
442
+ );
443
+ ```
444
+
445
+ ## `mongo-execution-context-applies-mutation-defaults`
446
+
447
+ Mongo contracts can now carry execution defaults (`temporal.createdAt()`, `temporal.updatedAt()`), and the execution context applies them. The ORM calls `context.applyMutationDefaults(...)` on every create and update, so `MongoExecutionContext` has a new required method.
448
+
449
+ Build contexts with `createMongoExecutionContext({ contract, stack })` and nothing changes. A hand-written context, typically a test double, adds a method that applies nothing:
450
+
451
+ ```ts
452
+ // before
453
+ const context: MongoExecutionContext = { contract, codecs, stack };
454
+
455
+ // after
456
+ const context: MongoExecutionContext = { contract, codecs, stack, applyMutationDefaults: () => [] };
457
+ ```
458
+
459
+ ## `mongo-field-builder-execution-defaults-parameter`
460
+
461
+ Code that constrains on a bare `FieldBuilder` (for example `Fields extends Record<string, FieldBuilder>`) accepts only builders without execution defaults; pass `ExecutionMutationDefaultPhases | undefined` as the fifth type argument to accept preset fields. There is no detection pattern: a bare `FieldBuilder` reference is too common to tell which uses need the wider type.
462
+
463
+ ## `mongo-bson-codec-added`
464
+
465
+ The Mongo target adds the codec `mongo/bson@1` (data type `mongo/bson`, PSL `Bson`, `field.bson()`), whose `CodecTypes` entry reads `BsonValue` and writes `BsonInputValue` (a `BsonValue`, or a `Uint8Array` at any depth), both exported from `@internal/target-mongo/codec-types` (declared in `@internal/mongo-value`, where the Mongo TypeScript builder reads them). `BsonValue` covers every value the driver returns with its default settings, `Code`, `MinKey`, `MaxKey`, `BSONSymbol` and a native `RegExp` (what a stored regex reads back as) included; `BSONRegExp` appears only with a driver configured with `bsonRegExp: true`. A `DBRef` is never returned, because decode turns it back into its `{ $ref, $id[, $db], ...fields }` document. It declares an empty `targetTypes`, so the validator does not constrain its value: a single field gets `{}`, and a list field still must be an array, `{ bsonType: 'array', items: {} }`. An extension that lists every Mongo codec id, or keys a map by `CodecTypes`, adds `mongo/bson@1`. An extension that stores arbitrary BSON in a field of its own contract types it `mongo/bson@1`, because `mongo/json@1` now refuses non-JSON values on encode and decode.
466
+
467
+ The detection pattern finds a descriptor whose `targetTypes` lists two or more entries, quoted strings or named constants, on one line or several. It does not see a list built elsewhere and referenced by name (`targetTypes: TYPES`); check those descriptors by hand.
468
+
469
+ The Mongo validator derivation reads every entry of a codec descriptor's `targetTypes`. For a list field the list applies to `items`, and a nullable field prepends `'null'` unless the list already has it. An empty list leaves the value's type unconstrained but not a list field's cardinality: a single field gets `{}`, and a list field gets `{ bsonType: 'array', items: {} }`.
470
+
471
+ For each Mongo codec descriptor in the extension whose `targetTypes` has more than one entry, check that every entry is a BSON type the codec's `encode` can produce, because each one is now admitted by the validator. Remove any entry the codec does not write. Then re-emit the extension's contracts and any test fixtures; their validators list every entry.
472
+
473
+ An enum's `@@type` codec must declare exactly one entry: the Mongo enum factory reports `enum "<name>" @@type codec "<id>" declares <n> BSON types; an enum needs exactly one` otherwise.