@prisma/orm-sqlite 8.0.0-rc.11-dev.55 → 8.0.0-rc.12-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 +8 -8
- package/skills/prisma-8/SKILL.md +1 -1
- package/skills/prisma-8/references/migration-model.md +1 -1
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/instructions.md +639 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/scripts/multifile-psl/add-use-prisma-8-directive.mjs +60 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/scripts/psl-verbatim-table-names/add-model-map.mjs +256 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/instructions.md +857 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/scripts/psl-verbatim-table-names/add-model-map.mjs +256 -0
package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/instructions.md
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.11"
|
|
3
|
+
to: "8.0.0-rc.12"
|
|
4
|
+
# The Prisma 7 contract source adds `prisma7Schema` and `contract: ContractConfig` to
|
|
5
|
+
# `@prisma/orm-postgres/config`, and `diagnostics` to CliStructuredError. Both additive. It also
|
|
6
|
+
# moves the Postgres raw default parser out of the family `psl-infer` subpath (entry below). The
|
|
7
|
+
# Postgres default reader and the pinned introspection session change what users see, not any
|
|
8
|
+
# extension API; the app skill covers them.
|
|
9
|
+
# contract.d.ts now orders every collection the way contract.json does; a re-emit reorders, nothing else.
|
|
10
|
+
# Prepared include decoder specialization adds no consumer migration; retain existing entries below.
|
|
11
|
+
# The postgres/sqlite/mongo defineConfig wrappers accept a glob-shaped
|
|
12
|
+
# `contract:` string (e.g. `./prisma/**/*.prisma`) and derive the default
|
|
13
|
+
# output from its static prefix directory. Purely additive: every existing
|
|
14
|
+
# single-path `contract:` value keeps deriving its output exactly as before.
|
|
15
|
+
# Nothing for an extension author to translate.
|
|
16
|
+
changes:
|
|
17
|
+
- id: define-config-becomes-define-prisma-config
|
|
18
|
+
summary: |
|
|
19
|
+
Rename the engine config marker import in `prisma.config.ts` from `defineConfig` to
|
|
20
|
+
`definePrismaConfig`. Required: `@prisma/cli-engine@0.6.1` no longer exports the deprecated
|
|
21
|
+
`defineConfig` alias.
|
|
22
|
+
detection:
|
|
23
|
+
glob: "**/prisma.config.ts"
|
|
24
|
+
contains:
|
|
25
|
+
- "import { defineConfig } from '@prisma/cli-engine'"
|
|
26
|
+
- id: engine-pin-moves-to-0-6-1
|
|
27
|
+
summary: |
|
|
28
|
+
The toolchain now peers `@prisma/cli-engine@0.6.1` (up from 0.4.0). An extension package that pins `@prisma/cli-engine` for its tests or tooling must move the pin to `0.6.1`. A config section's `validate` now receives a second `provenance` argument naming the files that declared the section.
|
|
29
|
+
detection:
|
|
30
|
+
glob: "**/package.json"
|
|
31
|
+
contains:
|
|
32
|
+
- '"@prisma/cli-engine": "0.4.0"'
|
|
33
|
+
- id: psl-model-names-table-verbatim
|
|
34
|
+
summary: |
|
|
35
|
+
A PSL `model` with no `@@map` now names its table (or Mongo collection) after the model
|
|
36
|
+
verbatim: `model UserProfile` maps to `"UserProfile"`, where it used to map to `"userProfile"`.
|
|
37
|
+
Run the colocated codemod over every `.prisma` file, including the `contract.prisma` copies
|
|
38
|
+
under `migrations/`, so each unmapped model gets `@@map("<current table name>")` and keeps the
|
|
39
|
+
table it already has. Emitted contracts, storage hashes, and migration history are unchanged
|
|
40
|
+
after the codemod. Planning without it fails with `MIGRATION.TABLE_NAME_CASE_CHANGED`
|
|
41
|
+
instead of dropping and recreating the table.
|
|
42
|
+
detection:
|
|
43
|
+
glob: "**/*.prisma"
|
|
44
|
+
regex:
|
|
45
|
+
- '\bmodel\s+[A-Za-z_][A-Za-z0-9_]*\s*\{'
|
|
46
|
+
script: ./scripts/psl-verbatim-table-names/add-model-map.mjs
|
|
47
|
+
- id: dbgenerated-removed-from-psl
|
|
48
|
+
summary: |
|
|
49
|
+
`@default(dbgenerated("..."))` is removed from PSL. `contract emit` refuses it with
|
|
50
|
+
`PSL_UNKNOWN_DEFAULT_FUNCTION`. Write a raw SQL default as the `sql` tagged literal, and a
|
|
51
|
+
value the column's data type writes as that literal.
|
|
52
|
+
detection:
|
|
53
|
+
glob: "**/*.prisma"
|
|
54
|
+
contains:
|
|
55
|
+
- "dbgenerated("
|
|
56
|
+
- id: default-sql-method-deprecated
|
|
57
|
+
summary: |
|
|
58
|
+
`.defaultSql('...')` on the TypeScript contract builder is deprecated and is removed at
|
|
59
|
+
8.0.0. Rewrite each call to `.default(...)` with `now()`, `autoincrement()`, or the `sql`
|
|
60
|
+
template tag.
|
|
61
|
+
detection:
|
|
62
|
+
glob: "**/*.{ts,mts,cts}"
|
|
63
|
+
matches:
|
|
64
|
+
- '\.defaultSql\('
|
|
65
|
+
- id: every-codec-descriptor-names-a-data-type
|
|
66
|
+
summary: |
|
|
67
|
+
`CodecDescriptor` gained a required `dataType`: the id of the data type the codec represents.
|
|
68
|
+
A descriptor without one does not compile, and a data type no component registers is an
|
|
69
|
+
assembly error.
|
|
70
|
+
detection:
|
|
71
|
+
glob: "**/*.{ts,mts,cts}"
|
|
72
|
+
matches:
|
|
73
|
+
- '\b(CodecDescriptorImpl|PostgresCodecDescriptor|SqliteCodecDescriptor)\b'
|
|
74
|
+
- id: a-pack-registers-its-data-types
|
|
75
|
+
summary: |
|
|
76
|
+
A pack registers its data types through `dataTypes` on its component metadata — a sibling of
|
|
77
|
+
`types`, not a member of it — as an array of `dataType(...)` declarations.
|
|
78
|
+
detection:
|
|
79
|
+
glob: "**/*.{ts,mts,cts}"
|
|
80
|
+
matches:
|
|
81
|
+
- '\bcodecDescriptors:'
|
|
82
|
+
- id: casts-replace-accepted-shape-handling
|
|
83
|
+
summary: |
|
|
84
|
+
A data type declares, in `casts`, which other types' values it takes and how. Casts replace
|
|
85
|
+
every per-codec list of accepted shapes and the conversions that went with them.
|
|
86
|
+
detection:
|
|
87
|
+
glob: "**/*.{ts,mts,cts}"
|
|
88
|
+
matches:
|
|
89
|
+
- '\bliteralTypes\b'
|
|
90
|
+
- '\bLiteralTypeDeclaration\b'
|
|
91
|
+
- '\bintegerLiteralTypesUpTo\b'
|
|
92
|
+
- id: decode-json-takes-only-the-canonical-form
|
|
93
|
+
summary: |
|
|
94
|
+
`decodeJson` takes its data type's canonical form and nothing else. Remove every coercion a
|
|
95
|
+
codec did to accept another shape; the cast runs before the codec sees the value.
|
|
96
|
+
detection:
|
|
97
|
+
glob: "**/*.{ts,mts,cts}"
|
|
98
|
+
matches:
|
|
99
|
+
- '\bdecodeJson\('
|
|
100
|
+
- id: the-authoring-entry-replaces-the-tag-registry-entry
|
|
101
|
+
summary: |
|
|
102
|
+
PSL support for a data type is an authoring entry under `authoring.dataTypes`, keyed by the
|
|
103
|
+
type's id. It replaces the entry a pack used to put in the default-literal tag registry.
|
|
104
|
+
detection:
|
|
105
|
+
glob: "**/*.{ts,mts,cts}"
|
|
106
|
+
matches:
|
|
107
|
+
- '\bdefaultLiteralTagRegistry\b'
|
|
108
|
+
- '\bControlDefaultLiteralTagEntry\b'
|
|
109
|
+
- '\bjsonDefaultLiteralTagEntry\b'
|
|
110
|
+
- '\bisDefaultLiteralTagLoweringEntry\b'
|
|
111
|
+
- id: map-default-takes-data-types
|
|
112
|
+
summary: |
|
|
113
|
+
`DefaultMappingOptions` carries `dataTypeEntries`, `dataTypes` and `columnDataType` in place
|
|
114
|
+
of `literalTypes`.
|
|
115
|
+
detection:
|
|
116
|
+
glob: "**/*.{ts,mts,cts}"
|
|
117
|
+
matches:
|
|
118
|
+
- '\bmapDefault\('
|
|
119
|
+
- '\bDefaultMappingOptions\b'
|
|
120
|
+
- id: psl-and-numeral-helpers-live-in-relational-core
|
|
121
|
+
summary: |
|
|
122
|
+
`escapePslString`, `isNumeralText`, `isNonFiniteText` and `numeralText` moved from
|
|
123
|
+
`@internal/framework-components/codec` to `@internal/sql-relational-core/ast`.
|
|
124
|
+
detection:
|
|
125
|
+
glob: "**/*.{ts,mts,cts}"
|
|
126
|
+
matches:
|
|
127
|
+
- '\b(escapePslString|isNumeralText|isNonFiniteText|numeralText)\b'
|
|
128
|
+
- id: the-postgres-target-exposes-its-data-types
|
|
129
|
+
summary: |
|
|
130
|
+
The Postgres target gained a `./data-types` subpath, forwarded by the `@prisma/orm-postgres`
|
|
131
|
+
facade as `./target/data-types`. Import the Postgres types from there to declare a cast from
|
|
132
|
+
one.
|
|
133
|
+
detection:
|
|
134
|
+
glob: "**/*.{ts,mts,cts}"
|
|
135
|
+
matches:
|
|
136
|
+
- '\bdataType\('
|
|
137
|
+
- id: a-target-adapted-codec-extends-the-template
|
|
138
|
+
summary: |
|
|
139
|
+
A codec whose data type depends on the target adapting it extends `CodecDescriptorTemplateImpl`
|
|
140
|
+
and leaves `dataType` off; the target names the type when it adapts the template.
|
|
141
|
+
detection:
|
|
142
|
+
glob: "**/*.{ts,mts,cts}"
|
|
143
|
+
matches:
|
|
144
|
+
- '\bCodecDescriptorTemplateImpl\b'
|
|
145
|
+
- id: codec-without-params-has-no-params-schema
|
|
146
|
+
summary: A codec that takes no params sets `paramsSchema` to `undefined`; `voidParamsSchema` is removed.
|
|
147
|
+
detection:
|
|
148
|
+
glob: "**/*.ts"
|
|
149
|
+
contains:
|
|
150
|
+
- "voidParamsSchema"
|
|
151
|
+
- id: psl-infer-raw-default-parser-is-target-owned
|
|
152
|
+
summary: |
|
|
153
|
+
`parseRawDefault` is no longer exported from the `family/psl-infer` subpath; import `parsePostgresDefault` from `@prisma/orm-postgres/target/default-normalizer` instead.
|
|
154
|
+
- id: psl-attribute-specs-are-documented
|
|
155
|
+
summary: |
|
|
156
|
+
`fieldAttribute`, `modelAttribute` and `blockAttribute` require a `documentation` string, and so does every positional and named parameter. A named parameter is now `{ type, documentation }` instead of a bare argument type.
|
|
157
|
+
detection:
|
|
158
|
+
glob: "**/*.{ts,mts,cts}"
|
|
159
|
+
matches:
|
|
160
|
+
- '\b(?:fieldAttribute|modelAttribute|blockAttribute)\('
|
|
161
|
+
- id: psl-entity-ref-takes-a-selector
|
|
162
|
+
summary: |
|
|
163
|
+
`entityRef()` takes a selector such as `entityRef({ kind: 'model' })` and returns the resolved declaration instead of a name. Use `identifier()` for a name that is not checked. The attribute context carries the collected `symbols` table.
|
|
164
|
+
detection:
|
|
165
|
+
glob: "**/*.{ts,mts,cts}"
|
|
166
|
+
contains:
|
|
167
|
+
- "entityRef()"
|
|
168
|
+
- id: psl-parse-takes-a-file-name
|
|
169
|
+
summary: |
|
|
170
|
+
`parse(source, filename, options?)` requires the file name, and its result carries `sources` in place of `sourceFile`. The interpreter input takes `documents: [document]` in place of `document`.
|
|
171
|
+
detection:
|
|
172
|
+
glob: "**/*.{ts,mts,cts}"
|
|
173
|
+
matches:
|
|
174
|
+
- '\bparse\([^,()]*\)'
|
|
175
|
+
- '\bdocument:\s'
|
|
176
|
+
- id: emit-requires-deserialize-contract
|
|
177
|
+
summary: Pass the contract family's deserializer to emit(); contract.d.ts is now always generated from the canonical contract.json.
|
|
178
|
+
detection:
|
|
179
|
+
glob: "**/*.{ts,mts,cts}"
|
|
180
|
+
matches:
|
|
181
|
+
- 'import\s*\{[^}]*\bemit\b[^}]*\}\s*from\s*[^/\w\s]@(?:prisma/orm-toolchain|internal)/emitter[^/\w-]'
|
|
182
|
+
- id: expression-codec-on-return-type
|
|
183
|
+
summary: Move custom expression wrapper codec metadata to returnType.codec and remove the separate ExpressionImpl codec argument.
|
|
184
|
+
- id: shared-preparable-envelope
|
|
185
|
+
summary: Type ORM preparation descriptions with the shared compositional Preparable protocol and pass their contained plan to SQL runtime.
|
|
186
|
+
- id: params-only-sql-facade-prepare
|
|
187
|
+
summary: Replace injected SQL-builder preparation callbacks with params-only callbacks and lexical facade SQL access.
|
|
188
|
+
- id: preserve-prepared-reference-nullability
|
|
189
|
+
summary: Preserve declaration nullability when constructing or cloning PreparedParamRef AST nodes.
|
|
190
|
+
- id: preserve-orm-pagination-expressions
|
|
191
|
+
summary: Preserve expression-valued limit and offset when consuming ORM CollectionState.
|
|
192
|
+
- id: preserve-grouped-orm-pagination-expressions
|
|
193
|
+
summary: Preserve expression-valued limit and offset when consuming ORM GroupPagingState.
|
|
194
|
+
- id: postgres-list-element-codecs-receive-raw-strings
|
|
195
|
+
summary: |
|
|
196
|
+
PostgreSQL list decoding now parses array frames in the target and passes raw string elements to the scalar element codec; custom PostgreSQL codecs used in lists must accept those raw element spellings.
|
|
197
|
+
- id: query-operation-types-move-to-the-postgres-target
|
|
198
|
+
summary: |
|
|
199
|
+
`QueryOperationTypes` moves from `@internal/adapter-postgres/operation-types` to
|
|
200
|
+
`@internal/target-postgres/operation-types`. The adapter subpath is gone, with no
|
|
201
|
+
compatibility re-export.
|
|
202
|
+
detection:
|
|
203
|
+
glob: "**/*.{ts,mts,cts}"
|
|
204
|
+
matches:
|
|
205
|
+
- "adapter-postgres/operation-types"
|
|
206
|
+
- "orm-postgres/adapter/operation-types"
|
|
207
|
+
- id: native-enum-codec-is-not-textual
|
|
208
|
+
summary: |
|
|
209
|
+
The native enum codec `pg/enum@1` no longer declares the `textual` trait. `pgEnumDescriptor`
|
|
210
|
+
and `PgEnumCodec` declare `['equality', 'order']`, and `CodecTypes['pg/enum@1']['traits']` is
|
|
211
|
+
`'equality' | 'order'`.
|
|
212
|
+
- id: sql-lowering-spec-drops-strategy
|
|
213
|
+
summary: |
|
|
214
|
+
`SqlLoweringSpec` from `@internal/sql-operations` no longer has a `strategy` field.
|
|
215
|
+
Remove `strategy: 'infix'` and `strategy: 'function'` from every operation descriptor's
|
|
216
|
+
`lowering` object; `template` alone describes the lowering.
|
|
217
|
+
detection:
|
|
218
|
+
glob: "**/*.{ts,mts,cts}"
|
|
219
|
+
matches:
|
|
220
|
+
- "strategy: 'infix'"
|
|
221
|
+
- "strategy: 'function'"
|
|
222
|
+
- "strategy: \"infix\""
|
|
223
|
+
- "strategy: \"function\""
|
|
224
|
+
- id: share-create-default-cache-across-inserts
|
|
225
|
+
summary: Share query-stable mutation defaults across every insert in one logical create operation.
|
|
226
|
+
- id: supabase-contract-declares-nullable-list-columns
|
|
227
|
+
summary: The Supabase extension contract now declares storage.buckets.allowed_mime_types and storage.objects.path_tokens as nullable lists, so its storage hash changes; re-sign databases that were signed against the previous Supabase contract.
|
|
228
|
+
detection:
|
|
229
|
+
glob: "**/package.json"
|
|
230
|
+
contains:
|
|
231
|
+
- '"@prisma/orm-extension-supabase"'
|
|
232
|
+
- id: supabase-contract-regenerated-from-the-reference-fixture
|
|
233
|
+
summary: The Supabase extension contract is regenerated and now declares the reference build's 43 check constraints, six native-enum defaults as member literals, four jsonb defaults as JSON literals, and an element-not-null waiver on two more list columns, so its storage hash changes; re-sign databases that were signed against the previous Supabase contract, and check your own Supabase build declares the same constraints.
|
|
234
|
+
detection:
|
|
235
|
+
glob: "**/package.json"
|
|
236
|
+
contains:
|
|
237
|
+
- '"@prisma/orm-extension-supabase"'
|
|
238
|
+
- id: contract-space-restamp
|
|
239
|
+
summary: |
|
|
240
|
+
The emitted `contract.json` / `contract.d.ts` embed the toolchain version, which moves
|
|
241
|
+
to 8.0.0-rc.12. Rebuild the extension's contract space (the package's `build:contract-space`
|
|
242
|
+
script) once after upgrading so the emitted artifacts match the installed toolchain.
|
|
243
|
+
detection:
|
|
244
|
+
glob: "**/contract.json"
|
|
245
|
+
contains:
|
|
246
|
+
- '"version": "8.0.0-rc.11"'
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
# 8.0.0-rc.11 → 8.0.0-rc.12 — Extension author upgrade instructions
|
|
250
|
+
|
|
251
|
+
## `define-config-becomes-define-prisma-config`
|
|
252
|
+
|
|
253
|
+
For every `prisma.config.ts` matched by `detection`, rename the engine import and its call site from `defineConfig` to `definePrismaConfig`:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// before
|
|
257
|
+
import { defineConfig } from '@prisma/cli-engine';
|
|
258
|
+
export default defineConfig({ ... });
|
|
259
|
+
|
|
260
|
+
// after
|
|
261
|
+
import { definePrismaConfig } from '@prisma/cli-engine';
|
|
262
|
+
export default definePrismaConfig({ ... });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`definePrismaConfig` has been the engine's name for the marker since `@prisma/cli-engine@0.2.0`. `defineConfig` was a deprecated alias, and `@prisma/cli-engine@0.6.1` removes it, so this rename is required before the engine pin moves (`engine-pin-moves-to-0-6-1`, next). Leave any `defineConfig` imported from a product package (for example `@prisma/orm-postgres/config`) untouched — those helpers keep their name. The engine function's behaviour is identical; only the name changes.
|
|
266
|
+
|
|
267
|
+
## `engine-pin-moves-to-0-6-1`
|
|
268
|
+
|
|
269
|
+
For every `package.json` matched by `detection`, change the `@prisma/cli-engine` version from `0.4.0` to `0.6.1` and reinstall. The engine no longer exports the deprecated `defineConfig` alias; `define-config-becomes-define-prisma-config` (the previous entry) covers the rename.
|
|
270
|
+
|
|
271
|
+
Code that calls a config section's `validate` directly, for example in a test, must pass the provenance the engine supplies as the second argument:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
// before
|
|
275
|
+
section.validate(raw);
|
|
276
|
+
|
|
277
|
+
// after
|
|
278
|
+
section.validate(raw, { files: [configPath], keys: { contract: configPath } });
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`files` lists the config files that declared the section, nearest first. `keys` maps each top-level key of the section to the file that wrote it.
|
|
282
|
+
|
|
283
|
+
## `psl-model-names-table-verbatim`
|
|
284
|
+
|
|
285
|
+
A PSL `model` with no `@@map` used to name its table, or its Mongo collection, after the model with the first letter lowered: `model UserProfile` read and wrote `"userProfile"`. It now uses the model name verbatim, `"UserProfile"`, the same rule every other Prisma 8 authoring surface already followed. Every model without `@@map` therefore points at a table that does not exist yet, so the schema must say which table it means.
|
|
286
|
+
|
|
287
|
+
From the extension package root, run the codemod that sits next to this guide once over every schema file, including the contract-space `contract.prisma` and the copy inside each migration directory. `<skill>` is the directory of the synced `prisma-8` skill. The script exits with an error if no file matches:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
node <skill>/upgrading/extension/upgrades/8.0.0-rc.11-to-8.0.0-rc.12/scripts/psl-verbatim-table-names/add-model-map.mjs '**/*.prisma'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
It adds `@@map("<model name with its first letter lowered>")` as the last line of every `model` block that has no `@@map`, keeps the file's indentation and line endings, leaves models that already have `@@map` alone, and leaves a variant with `@@base(...)` and no `@@map` alone because it shares its base's table. It never descends into `node_modules` or `dist`, prints every model it mapped as `<file>: model <Name> -> @@map("<name>")`, and is idempotent. If a `model` block is written in a shape it cannot read it prints `<file>:<line>: model block not understood` and exits 1; add the `@@map` to that block by hand.
|
|
294
|
+
|
|
295
|
+
The codemod cannot see storage. It is for schemas written against the previous release only, where every unmapped model's table was created with its first letter lowered. Run it once, before you re-run `contract infer`, and never on a schema that was inferred or written after upgrading: such a schema already names its tables verbatim, and the codemod would point each unmapped model at a lowercase table that does not exist. Read the printed list and remove the `@@map` from any model whose table already has the verbatim name.
|
|
296
|
+
|
|
297
|
+
Then run the package's contract-space build (`build:contract-space`, or `prisma contract emit` for the package) and check that the emitted `contract.json` did not change. An unchanged `contract.json` proves the codemod was run on the right schema: if it changed, the schema was already verbatim, so revert the codemod's edits. Storage hashes, migration history, and refs are unchanged after a correct run, so applications composing the extension see no change.
|
|
298
|
+
|
|
299
|
+
In this upgrade, other entries in this guide also change the emitted `contract.json` (the `version` stamp, and the defaults some later entries name). Emit also fails while the schema still holds a form this release refuses, such as `dbgenerated(...)`, until a later entry rewrites it. In either case, run this check after those entries, and compare the table names in `contract.json` rather than the whole file.
|
|
300
|
+
|
|
301
|
+
If you plan a migration (`prisma migration plan`, `prisma db update`, `prisma migrate`) without running the codemod, planning fails instead of dropping the table:
|
|
302
|
+
|
|
303
|
+
```text
|
|
304
|
+
✘ [MIGRATION.PLANNING_FAILED] Migration planning failed
|
|
305
|
+
why: MIGRATION.TABLE_NAME_CASE_CHANGED: table "UserProfile" would be created and table "userProfile" dropped. Prisma 8 changed the default table name: a model with no @@map now names its table verbatim, so model UserProfile points at "UserProfile" instead of "userProfile".
|
|
306
|
+
→ To keep table "userProfile" and its rows, add @@map("userProfile") to model UserProfile (or run the add-model-map codemod over the schema) and plan again. Prisma 8 has no rename-table operation, so a deliberate rename is done by hand: run ALTER TABLE "userProfile" RENAME TO "UserProfile" (schema-qualified where applicable), after which the plan is empty.
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The conflict fires for each pair where the table to drop equals the table to create with its first letter lowered, in the same namespace, whatever the columns. It does not fire on an empty database or on tables the contract's control policy marks `external` or `observed`. Mongo has no planner and gives no error: an unmapped model silently reads and writes an empty `UserProfile` collection while the documents stay in `userProfile`, so run the codemod before deploying.
|
|
310
|
+
|
|
311
|
+
To adopt the verbatim names on purpose instead of mapping, rename the storage by hand and skip the codemod for those models. Postgres and SQLite: `ALTER TABLE "userProfile" RENAME TO "UserProfile"` (`ALTER TABLE "auth"."userProfile" RENAME TO "UserProfile"` inside a schema). Mongo: `db.userProfile.renameCollection("UserProfile")`. After the rename the migration plan is empty and `db verify` is clean.
|
|
312
|
+
|
|
313
|
+
`contract infer` follows the same rule: a table whose name already equals the model name (`"UserProfile"`, `"User"`) infers to a model with no `@@map` and verifies clean, where the previous output pointed the model at a lowercase table that did not exist. A snake_case table still infers with `@@map("user_profile")`. There is nothing to detect for this: the inferred text for such a table is the same as before, it is now correct.
|
|
314
|
+
|
|
315
|
+
## `dbgenerated-removed-from-psl`
|
|
316
|
+
|
|
317
|
+
`@default(dbgenerated("<expression>"))` no longer parses. Every use is reported at its span as `PSL_UNKNOWN_DEFAULT_FUNCTION` with the message `` Default function "dbgenerated" was removed. Write the SQL as a tagged literal: @default(sql`<expression>`). Supported functions: ... ``. `prisma contract infer` no longer prints it either: a raw expression prints as a `sql` tagged literal, and a value the column's data type writes prints as that literal.
|
|
318
|
+
|
|
319
|
+
In PSL, a raw SQL column default is written as a tagged literal, ``@default(sql`...`)`` or `@default(sql"...")`. Rewrite each use by what the expression is:
|
|
320
|
+
|
|
321
|
+
| You wrote | Write instead |
|
|
322
|
+
| --- | --- |
|
|
323
|
+
| `@default(dbgenerated("gen_random_uuid()"))` | `` @default(sql`gen_random_uuid()`) `` |
|
|
324
|
+
| `@default(dbgenerated("now()"))`, `@default(dbgenerated("CURRENT_TIMESTAMP"))` on Postgres | `@default(now())` |
|
|
325
|
+
| `@default(dbgenerated("autoincrement()"))`, `@default(dbgenerated("nextval('<seq>'::regclass)"))` on a serial column | `@default(autoincrement())` |
|
|
326
|
+
| `@default(dbgenerated("'<json>'::jsonb"))` on a `Json` or `Jsonb` column | `` @default(json`<json>`) `` |
|
|
327
|
+
| `@default(dbgenerated("'<member>'::<enum type>"))` on a column typed by that enum | `@default("<member>")` |
|
|
328
|
+
| `@default(dbgenerated("'<text>'::text"))` on a text column | `@default("<text>")` |
|
|
329
|
+
| `@default(dbgenerated("<anything else>"))` | `` @default(sql`<anything else>`) `` |
|
|
330
|
+
|
|
331
|
+
The `now()` and `autoincrement()` rows are required, not a matter of style: `` sql`now()` `` and `` sql`autoincrement()` `` are refused with `PSL_INVALID_DEFAULT_SQL`, because Prisma reads those two expressions as its own default functions. Every other expression, including `NOW()` written in capitals, is used exactly as written.
|
|
332
|
+
|
|
333
|
+
The mechanical rewrite for the last row is `@default(sql"<expression>")` with the argument text copied unchanged: the double-quote fence uses the same escapes as the `dbgenerated("...")` argument, so the contract cannot change. The backtick fence reads better for SQL; to use it, undo the quoted string's escaping (`\"` becomes `"`, `\n` becomes a line break), then write each backtick in the body as `` \` ``. Inside backticks every other backslash is kept as written, `\$` included, because PSL has no escape for `$`, and `${` is ordinary text. A body that contains a backtick is easiest to keep in the double-quote fence.
|
|
334
|
+
|
|
335
|
+
The JSON and enum rows change the emitted contract: the default becomes `{ kind: 'literal', value }` instead of `{ kind: 'function', expression }`, so the storage hash moves. Run `prisma contract emit`, then `prisma db verify`: the literal compares equal to the live default, so verify passes and no migration is needed. Every other row emits the same contract as before; the storage hash does not move.
|
|
336
|
+
|
|
337
|
+
Find the uses with `grep -rn "dbgenerated(" prisma/` (or wherever the schema lives).
|
|
338
|
+
|
|
339
|
+
### For a pack that ships a contract
|
|
340
|
+
|
|
341
|
+
A pack's committed `contract.prisma` (for example a reference contract regenerated from a live database with `prisma contract infer`) carries the same rewrites. Regenerate it with the pack's own generation script, or apply the table above by hand, then re-emit `contract.json` and `contract.d.ts` with `prisma contract emit`. Only the JSON and enum rows change the emitted contract and move the storage hash; the pack's verify test against its reference database still passes, because the literal compares equal to the live default.
|
|
342
|
+
|
|
343
|
+
## `default-sql-method-deprecated`
|
|
344
|
+
|
|
345
|
+
Rewrite every `.defaultSql('<expression>')` call by its expression:
|
|
346
|
+
|
|
347
|
+
| Call | Replacement | Import |
|
|
348
|
+
| --- | --- | --- |
|
|
349
|
+
| `.defaultSql('now()')` | `.default(now())` | `now` from `@internal/sql-contract-ts/contract-builder` |
|
|
350
|
+
| `.defaultSql('autoincrement()')` | `.default(autoincrement())` | `autoincrement` from `@internal/sql-contract-ts/contract-builder` |
|
|
351
|
+
| `.defaultSql('gen_random_uuid()')` | `` .default(sql`gen_random_uuid()`) `` | `sql` from `@internal/sql-contract-ts/contract-builder` |
|
|
352
|
+
| `.defaultSql('<anything else>')` | `` .default(sql`<anything else>`) `` | `sql` from `@internal/sql-contract-ts/contract-builder` |
|
|
353
|
+
|
|
354
|
+
There is no named helper for other database functions, so they use the `sql` tag, as `gen_random_uuid()` does above. `` sql`now()` `` and `` sql`autoincrement()` `` are refused in the TypeScript `sql` tag as they are in PSL, so those two must use the named form.
|
|
355
|
+
|
|
356
|
+
Copy the expression's value, not its source string: first undo the TypeScript string's own escaping, so `.defaultSql('it\'s')` contributes `it's`. Then write each backtick as `` \` ``. Write a backslash that precedes a dollar sign as `\\$`, because the tag reads `\$` as the escape for `$`. Every other backslash can be written as it is, or doubled; both give one backslash. An expression that contains `${` is written `\${` inside the `sql` tag, which resolves it back to the two characters; in PSL it is written as it is.
|
|
357
|
+
|
|
358
|
+
Every form lowers to the same `{ kind: 'function', expression }` default, so emitted contracts do not change.
|
|
359
|
+
|
|
360
|
+
Find the uses with `grep -rn "defaultSql(" src/` (or wherever the contract is defined).
|
|
361
|
+
|
|
362
|
+
## `every-codec-descriptor-names-a-data-type`
|
|
363
|
+
|
|
364
|
+
A **data type** is a stored type made first-class: `pg/int8`, `pg/jsonb`, `postgis/geometry`. Its id is `owner/name` and carries no version, because a type's identity does not change. A **codec** is one representation of a data type, and a codec id carries a version (`pg/int8@1`), so one string never names both.
|
|
365
|
+
|
|
366
|
+
Every descriptor names the type it represents:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { pgvectorVector } from './data-types';
|
|
370
|
+
|
|
371
|
+
export class PgVectorDescriptor extends PostgresCodecDescriptor<VectorParams> {
|
|
372
|
+
override readonly dataType = pgvectorVector.id;
|
|
373
|
+
override readonly codecId = VECTOR_CODEC_ID;
|
|
374
|
+
override readonly traits = ['equality'] as const;
|
|
375
|
+
override readonly targetTypes = ['vector'] as const;
|
|
376
|
+
override readonly paramsSchema: StandardSchemaV1<VectorParams> = vectorParamsSchema;
|
|
377
|
+
override factory(params: VectorParams): (ctx: CodecInstanceContext) => PgVectorCodec {
|
|
378
|
+
return () => new PgVectorCodec(this, params.length);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`dataType` is `abstract readonly dataType: DataTypeId` on `CodecDescriptorImpl`, so a descriptor that leaves it off does not compile. `DataTypeId` is a branded string: the only way to make one is `dataTypeId('owner/name')`, which `dataType()` calls for you, so reference the declaration's `.id` rather than writing the string again.
|
|
384
|
+
|
|
385
|
+
Several codecs may represent one type. `pg/int8@1` and `pg/int8number@1` both name `pg/int8`; they differ in the in-memory value they produce, and both store the type's one canonical form.
|
|
386
|
+
|
|
387
|
+
Name the target's type whenever your codec stores what one of the target's columns stores. A codec that keeps a JSON document in a `jsonb` column and validates it against a schema names `pg/jsonb` and registers nothing: `pg/jsonb` already says what the column holds and what it takes, and the schema check is the codec's, at the point the value is read. Register a type of your own only for a database type no pack describes yet, as pgvector does for `vector`.
|
|
388
|
+
|
|
389
|
+
Assembly checks the ids across packs. A codec naming a type nobody registers fails with `CONTRACT.DATA_TYPE_UNREGISTERED`, naming your component and the id.
|
|
390
|
+
|
|
391
|
+
## `a-pack-registers-its-data-types`
|
|
392
|
+
|
|
393
|
+
A pack that introduces a database type of its own declares each type with `dataType(id, spec)` and lists them on the component metadata. A pack whose codecs all represent types the target registers declares none, and has no `dataTypes` at all.
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
// data-types.ts
|
|
397
|
+
import { type DataType, dataType } from '@internal/framework-components/codec';
|
|
398
|
+
import { pgText } from '@internal/target-postgres/data-types';
|
|
399
|
+
|
|
400
|
+
export const postgisGeometry: DataType = dataType('postgis/geometry', {
|
|
401
|
+
casts: { [pgText.id]: (value) => value },
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
export const postgisDataTypes: readonly DataType[] = [postgisGeometry];
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
// descriptor-meta.ts
|
|
409
|
+
const postgisPackMetaBase = {
|
|
410
|
+
kind: 'extension',
|
|
411
|
+
id: 'postgis',
|
|
412
|
+
// …
|
|
413
|
+
dataTypes: postgisDataTypes,
|
|
414
|
+
types: {
|
|
415
|
+
codecTypes: { codecDescriptors: Array.from(postgisCodecRegistry.values()), /* … */ },
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
`dataTypes` sits beside `types`, not inside it: `types` is copied into an extension's contract space, and a cast is a function, which no contract holds.
|
|
421
|
+
|
|
422
|
+
Two components registering one id fail assembly with `CONTRACT.DATA_TYPE_DUPLICATE`, naming both.
|
|
423
|
+
|
|
424
|
+
## `casts-replace-accepted-shape-handling`
|
|
425
|
+
|
|
426
|
+
A **cast** is a pure function from another type's canonical form into this type's. Casts are declared by the type that receives, never by the source, so there is at most one for any pair and a type's owner is the only one who decides what it takes. A written value is admitted when its type is the column's type or the column's type casts from it; the cast runs before the codec sees anything.
|
|
427
|
+
|
|
428
|
+
This replaces the per-codec list of accepted shapes. Delete `literalTypes` from every descriptor, delete any import of `LiteralTypeDeclaration` or `integerLiteralTypesUpTo`, and move each conversion into the receiving type's cast:
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
const asNumeralText: Cast = (value) =>
|
|
432
|
+
typeof value === 'number' ? numeralText(value) : wrongShape(value, 'a number');
|
|
433
|
+
|
|
434
|
+
export const pgInt8: DataType = dataType('pg/int8', {
|
|
435
|
+
casts: { [pgInt2.id]: asNumeralText, [pgInt4.id]: asNumeralText },
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Declaring no cast is a decision, not an omission. `pg/int4` declares none from `pg/int8`, so a number too wide for the column is refused before anything is decoded:
|
|
440
|
+
|
|
441
|
+
```text
|
|
442
|
+
Field "N.count": pg/int4 has no cast from pg/int8; it casts from pg/int2
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
A cast may refuse the value it is handed, with a structured error carrying `why` and `fix`; the refusal surfaces as `PSL_INVALID_DEFAULT_LITERAL` at the written value.
|
|
446
|
+
|
|
447
|
+
There is no list data type. A type whose single value holds several elements declares a `listCast` instead: `of` is the set of types an element may be, and `cast` receives the elements' canonical forms in written order. This is how a vector column takes `` @default([0.1, 0.2, 0.3]) ``:
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
export const pgvectorVector: DataType = dataType('pgvector/vector', {
|
|
451
|
+
listCast: {
|
|
452
|
+
of: [pgInt2.id, pgInt4.id, pgInt8.id, pgNumeric.id],
|
|
453
|
+
cast: (elements) => elements.map(elementNumber),
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Assembly refuses a cast whose source type no contract source can write, with `CONTRACT.DATA_TYPE_NOT_WRITABLE`: such a cast could never be exercised.
|
|
459
|
+
|
|
460
|
+
## `decode-json-takes-only-the-canonical-form`
|
|
461
|
+
|
|
462
|
+
A data type names one **canonical form**: the single JSON shape `contract.json` stores for its values. `pg/int8` stores digit text, `pg/int4` a JSON number, `pg/jsonb` the document. Every codec of a type stores and reads exactly that form.
|
|
463
|
+
|
|
464
|
+
So `decodeJson` takes that form and nothing else, and `encodeJson` produces it. Remove every branch a codec had for a shape it does not itself write — the cast has already produced the canonical form by the time the codec is called:
|
|
465
|
+
|
|
466
|
+
```diff
|
|
467
|
+
decodeJson(json: JsonValue): number {
|
|
468
|
+
- if (typeof json === 'number') return decodeInt8(json);
|
|
469
|
+
if (typeof json !== 'string') {
|
|
470
|
+
- throw myError('RUNTIME.DECODE_FAILED', 'value must be decimal text or a whole number');
|
|
471
|
+
+ throw myError('RUNTIME.DECODE_FAILED', 'database JSON value must be decimal text');
|
|
472
|
+
}
|
|
473
|
+
return decodeInt8(json);
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Where two codecs of one type previously stored different shapes, they now share the type's form. `pg/int8number@1` and `sqlite/bigintnumber@1` store digit text like their `bigint`-valued siblings, and refuse text past 2^53 as a limit of their own representation.
|
|
478
|
+
|
|
479
|
+
A codec still validates what the column's parameters constrain, on the canonical form: `vector(3)` refuses four elements, `numeric(10,2)` refuses a third decimal place. A refusal is reported as `PSL_INVALID_DEFAULT_LITERAL` carrying the codec's own message, and `contract infer` calls the codec on what it is about to print, falling back to the raw expression when it throws.
|
|
480
|
+
|
|
481
|
+
## `the-authoring-entry-replaces-the-tag-registry-entry`
|
|
482
|
+
|
|
483
|
+
PSL support for a data type is an **authoring entry**, contributed by the pack that owns the type under `authoring.dataTypes` and keyed by the type's id. It replaces the entry a pack used to register in the default-literal tag registry.
|
|
484
|
+
|
|
485
|
+
An entry has a **written form**, a `print` that is the reverse of reading it, and `documentation` the language server shows:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
export function postgresDataTypeEntries(): Readonly<Record<string, AuthoringDataTypeEntry>> {
|
|
489
|
+
return {
|
|
490
|
+
[pgText.id]: {
|
|
491
|
+
written: { kind: 'plain', syntax: 'string', parse: (text) => text },
|
|
492
|
+
print: (value) => String(value),
|
|
493
|
+
documentation: 'Text.',
|
|
494
|
+
},
|
|
495
|
+
[pgJson.id]: {
|
|
496
|
+
written: { kind: 'tag', tag: 'json', parse: parseJsonBody },
|
|
497
|
+
print: printJsonBody,
|
|
498
|
+
documentation: 'Reads the body as a JSON document and stores it as the default value.',
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
There are four written forms:
|
|
505
|
+
|
|
506
|
+
- `{ kind: 'tag', tag, parse }` — a qualified name followed by a body in any of PSL's quote styles. A target may register an unprefixed tag; every other pack prefixes, as `postgis.geometry` does.
|
|
507
|
+
- `{ kind: 'plain', syntax: 'string', parse }` and `{ kind: 'plain', syntax: 'boolean', parse }` — a quoted string, and `true`/`false`.
|
|
508
|
+
- `{ kind: 'plain', syntax: 'number', types, classify }` — a written number. This is the one form that yields several types, so instead of `parse` it carries a classifier that picks the type from the digits and returns the canonical form with it, plus `types`, every type the classifier can return. Naming `types` is how assembly knows those types can be written.
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
const classifyPostgresNumber = createNumberClassifier({
|
|
512
|
+
integers: [
|
|
513
|
+
{ type: pgInt2.id, form: 'number', ...signedRange(16) },
|
|
514
|
+
{ type: pgInt4.id, form: 'number', ...signedRange(32) },
|
|
515
|
+
{ type: pgInt8.id, form: 'text', ...signedRange(64) },
|
|
516
|
+
],
|
|
517
|
+
largerWhole: { type: pgNumeric.id, form: 'text' },
|
|
518
|
+
fraction: { type: pgNumeric.id, form: 'text' },
|
|
519
|
+
words: { type: pgNumeric.id, form: 'text' },
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`createNumberClassifier`, `signedRange`, `parseJsonBody` and `printJsonBody` come from `@internal/sql-relational-core/ast`, so targets share one digit classifier and one JSON reader.
|
|
524
|
+
|
|
525
|
+
One tag names no data type: `sql` takes an expression in the database's language and lowers its own body. A **lowering entry** sits in the same map under a reserved key, because it has no type id to be keyed by:
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
export function createPostgresDataTypeEntries(): Readonly<Record<string, AuthoringDataTypeEntry>> {
|
|
529
|
+
return {
|
|
530
|
+
...postgresDataTypeEntries(),
|
|
531
|
+
[loweringEntryKey('sql')]: sqlDefaultLiteralTagEntry('sql'),
|
|
532
|
+
[loweringEntryKey('pg.sql')]: sqlDefaultLiteralTagEntry('pg.sql'),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
`loweringEntryKey`, `isLoweringEntryKey` and `isDataTypeLoweringEntry` are exported from `@internal/framework-components/authoring`; `isDataTypeLoweringEntry` is the only place the discriminating key is named, so narrow with it before reaching for `lower`.
|
|
538
|
+
|
|
539
|
+
These surfaces are gone, with no replacement beyond the above: `ControlMutationDefaults.defaultLiteralTagRegistry`, the `ControlDefaultLiteralTagEntry` and `ControlDefaultLiteralTagRegistry` types, `literalTypes` on codec descriptors, and the framework's literal-types exports (`LiteralTypeName`, `LiteralTypeDeclaration`, `integerLiteralTypesUpTo`, `jsonDefaultLiteralTagEntry`, `isDefaultLiteralTagLoweringEntry`).
|
|
540
|
+
|
|
541
|
+
Assembly refuses two entries claiming one tag or one plain form with `CONTRACT.DATA_TYPE_WRITTEN_FORM_DUPLICATE`, and an entry keyed by an unregistered id with `CONTRACT.DATA_TYPE_UNREGISTERED`.
|
|
542
|
+
|
|
543
|
+
## `map-default-takes-data-types`
|
|
544
|
+
|
|
545
|
+
`mapDefault` (`@internal/family-sql/psl-infer`) classifies the stored value with the same rules a written value uses, confirms the column's type takes it, prints it with the classified type's authoring entry, and reads the text straight back. `DefaultMappingOptions` lost `literalTypes` and gained:
|
|
546
|
+
|
|
547
|
+
- `dataTypeEntries` — the stack's authoring entries, keyed by data type id;
|
|
548
|
+
- `dataTypes` — a `DataTypeLookup` over the stack's types, whose casts say what each one takes;
|
|
549
|
+
- `columnDataType` — the data type of this column's codec;
|
|
550
|
+
- `list` — whether the column is a list, whose elements each carry the column's own type. A written list on a column that is not a list goes through that type's `listCast` instead.
|
|
551
|
+
|
|
552
|
+
A target builds the first two once:
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
export function createPostgresDefaultMapping(): DefaultMappingOptions {
|
|
556
|
+
return {
|
|
557
|
+
dataTypeEntries: postgresDataTypeEntries(),
|
|
558
|
+
dataTypes: createDataTypeLookup(postgresDataTypes),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
and adds the per-column half at each call:
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
const result = mapDefault(columnDefault, {
|
|
567
|
+
...defaultMapping,
|
|
568
|
+
...ifDefined('columnDataType', dataTypeForPrintedType(resolution.pslType.name, isEnumColumn)),
|
|
569
|
+
list: column.many === true,
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
A value no entry writes, or one that does not read back as the stored value, makes `mapDefault` return `undefined`, which is the signal to fall back to the raw database default: map it again as a function default, which prints as a named function or as `` @default(sql`<expression>`) ``. `DefaultMappingResult` is now `{ attribute }` only; the `{ comment }` result and the `fallbackFunctionAttribute` option are removed along with `dbgenerated`. `formatLiteralValue` and the per-PSL-type formatter table a target printer used to supply (`PslDefaultValueFormat`, `formatPslValue`, `formatPslListLiteralValue`) are gone; delete them.
|
|
574
|
+
|
|
575
|
+
## `psl-and-numeral-helpers-live-in-relational-core`
|
|
576
|
+
|
|
577
|
+
Four helpers moved out of `@internal/framework-components/codec`, because they are SQL-family text handling rather than framework surface:
|
|
578
|
+
|
|
579
|
+
| Helper | What it does | Now imported from |
|
|
580
|
+
| --- | --- | --- |
|
|
581
|
+
| `escapePslString` | Escapes a string for a PSL double-quoted literal | `@internal/sql-relational-core/ast` |
|
|
582
|
+
| `isNumeralText` | Whether text is a number written out | `@internal/sql-relational-core/ast` |
|
|
583
|
+
| `isNonFiniteText` | Whether text is `NaN`, `Infinity` or `-Infinity` | `@internal/sql-relational-core/ast` |
|
|
584
|
+
| `numeralText` | A JS number as digit text, with no exponent | `@internal/sql-relational-core/ast` |
|
|
585
|
+
|
|
586
|
+
```diff
|
|
587
|
+
-import { escapePslString, numeralText } from '@internal/framework-components/codec';
|
|
588
|
+
+import { escapePslString, numeralText } from '@internal/sql-relational-core/ast';
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Use them rather than a local regex, so a printed value and the reader that parses it back cannot drift.
|
|
592
|
+
|
|
593
|
+
## `the-postgres-target-exposes-its-data-types`
|
|
594
|
+
|
|
595
|
+
Declaring a cast means naming the source type by its declaration, so the Postgres target now exports its types and its authoring entries from a `./data-types` subpath:
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
import { pgInt2, pgInt4, pgInt8, pgNumeric, pgText } from '@internal/target-postgres/data-types';
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
The `@prisma/orm-postgres` facade forwards it as `@prisma/orm-postgres/target/data-types`, which is the import an out-of-repo extension uses.
|
|
602
|
+
|
|
603
|
+
The subpath carries every `pg/*` type (`pgText`, `pgBool`, `pgInt2`, `pgInt4`, `pgInt8`, `pgNumeric`, `pgFloat4`, `pgFloat8`, `pgJson`, `pgJsonb`, the text-backed types, and the temporal ones), the `postgresDataTypes` array, and `postgresDataTypeEntries()`.
|
|
604
|
+
|
|
605
|
+
A cast whose source belongs to another pack only makes sense when that pack is in the stack, which is why assembly, not the extension, checks it: `pgvector/vector` casting from `pg/numeric` is valid only when the Postgres target is composed in.
|
|
606
|
+
|
|
607
|
+
## `a-target-adapted-codec-extends-the-template`
|
|
608
|
+
|
|
609
|
+
A codec shared by several targets cannot name its data type itself, because the type differs per target. Such a descriptor extends `CodecDescriptorTemplateImpl`, which has every descriptor field except `dataType`:
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
export class SqlTextDescriptor extends CodecDescriptorTemplateImpl<void> {
|
|
613
|
+
override readonly codecId = SQL_TEXT_CODEC_ID;
|
|
614
|
+
override readonly traits = ['equality', 'order', 'textual'] as const;
|
|
615
|
+
override readonly targetTypes = ['text'] as const;
|
|
616
|
+
override readonly paramsSchema = undefined;
|
|
617
|
+
override factory(): (ctx: CodecInstanceContext) => SqlTextCodec {
|
|
618
|
+
return () => new SqlTextCodec(this);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
The target names the type when it adapts the template:
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
export const postgresSqlTextDescriptor = postgresCodec(sqlTextDescriptor, {
|
|
627
|
+
dataType: pgText.id,
|
|
628
|
+
nativeType: () => 'text',
|
|
629
|
+
jsonProjection: identityJsonProjection,
|
|
630
|
+
});
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
`dataType` is required in the adapter's options, so a target cannot adapt a template without deciding which of its types the codec represents. Every other descriptor — one written for a single target — extends `CodecDescriptorImpl` (or a target's subclass of it, such as `PostgresCodecDescriptor`) and declares `dataType` directly.
|
|
634
|
+
|
|
635
|
+
## `codec-without-params-has-no-params-schema`
|
|
636
|
+
|
|
637
|
+
`voidParamsSchema` is no longer exported from `@internal/framework-components/codec`. A codec descriptor that takes no params (`P = void`) sets `paramsSchema` to `undefined`, and `isParameterized` is `true` exactly when a descriptor has a `paramsSchema`. A codec without params still rejects any `typeParams` with `RUNTIME.TYPE_PARAMS_INVALID`.
|
|
638
|
+
|
|
639
|
+
In every file matched by `detection`:
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
// before
|
|
643
|
+
import { CodecDescriptorImpl, voidParamsSchema } from '@internal/framework-components/codec';
|
|
644
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
645
|
+
|
|
646
|
+
class MyTextDescriptor extends CodecDescriptorImpl<void> {
|
|
647
|
+
override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// after
|
|
651
|
+
import { CodecDescriptorImpl } from '@internal/framework-components/codec';
|
|
652
|
+
|
|
653
|
+
class MyTextDescriptor extends CodecDescriptorImpl<void> {
|
|
654
|
+
override readonly paramsSchema = undefined;
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
- Remove `voidParamsSchema` from the import. Remove the `StandardSchemaV1` import too if nothing else in the file uses it.
|
|
659
|
+
- In a descriptor written as a plain object, replace `paramsSchema: voidParamsSchema` with `paramsSchema: undefined`.
|
|
660
|
+
- Code that reads `descriptor.paramsSchema` now sees `StandardSchemaV1<P> | undefined`. Check `descriptor.paramsSchema !== undefined` before calling its `validate`.
|
|
661
|
+
|
|
662
|
+
Parameterized codecs are unchanged.
|
|
663
|
+
|
|
664
|
+
## `psl-infer-raw-default-parser-is-target-owned`
|
|
665
|
+
|
|
666
|
+
Find imports of `parseRawDefault` from `@prisma/orm-family-sql/family/psl-infer`, `@prisma/orm-postgres/family/psl-infer`, or `@prisma/orm-sqlite/family/psl-infer`. The function read Postgres default spellings, so it now lives in the Postgres target. Replace each import with `parsePostgresDefault` from `@prisma/orm-postgres/target/default-normalizer`. The signature is unchanged: `(rawDefault: string, nativeType?: string) => ColumnDefault | undefined`. It reads every spelling the old function read, plus the negative and cast numerals, cross-schema enum literals, zoneless `timestamp` literals, and `ARRAY[...]` lists added in this release, so a value that was `undefined` before may now be a literal.
|
|
667
|
+
|
|
668
|
+
The `parseRawDefault` option on `PslPrinterOptions` is unchanged; pass `parsePostgresDefault` there where you passed the old function.
|
|
669
|
+
|
|
670
|
+
## `psl-attribute-specs-are-documented`
|
|
671
|
+
|
|
672
|
+
The language server now shows signature help for every PSL attribute, built from the attribute's own declaration. For every file matched by `detection`, give each attribute spec a `documentation` string, give each positional parameter one, and wrap each named parameter's type in `{ type, documentation }`:
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
// before
|
|
676
|
+
const mapAttribute = blockAttribute('map', {
|
|
677
|
+
positional: [{ key: 'name', type: str() }],
|
|
678
|
+
named: { schema: optional(str()) },
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// after
|
|
682
|
+
const mapAttribute = blockAttribute('map', {
|
|
683
|
+
documentation: 'Maps this block to its database name.',
|
|
684
|
+
positional: [{ key: 'name', type: str(), documentation: 'The database name.' }],
|
|
685
|
+
named: { schema: { type: optional(str()), documentation: 'The schema that holds it.' } },
|
|
686
|
+
});
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
An attribute with no parameters still needs its own `documentation`, for example `modelAttribute('rls', { documentation: '...' })`. The documentation is editor help only; parsing, validation and the emitted contract do not change.
|
|
690
|
+
|
|
691
|
+
## `psl-entity-ref-takes-a-selector`
|
|
692
|
+
|
|
693
|
+
`entityRef()` now checks the reference against the schema's declarations. For every file matched by `detection`, pass the kind of declaration the argument must name, and read the resolved declaration from the result instead of a string:
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
// before
|
|
697
|
+
{ key: 'base', type: entityRef(), documentation: 'The base model to inherit from.' }
|
|
698
|
+
// parsed.base is the name as written
|
|
699
|
+
|
|
700
|
+
// after
|
|
701
|
+
{ key: 'base', type: entityRef({ kind: 'model' }), documentation: 'The base model to inherit from.' }
|
|
702
|
+
// parsed.base is { declaration, namespace }; the name is parsed.base.declaration.name
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
A reference to another block kind uses `entityRef({ kind: 'block', keyword: '<keyword>' })`. An argument that names something the schema does not declare, and that must not be checked, uses `identifier()` instead. A pinned literal name stays `identifier('<name>', { documentation })`. Code that builds an attribute context itself must now pass the collected symbol table as `symbols`, next to `sources`.
|
|
706
|
+
|
|
707
|
+
## `psl-parse-takes-a-file-name`
|
|
708
|
+
|
|
709
|
+
A schema can now span several files, so every parse result records which file it came from. For every file matched by `detection`:
|
|
710
|
+
|
|
711
|
+
```ts
|
|
712
|
+
// before
|
|
713
|
+
const { document, sourceFile, diagnostics } = parse(source);
|
|
714
|
+
interpret({ document, ... });
|
|
715
|
+
|
|
716
|
+
// after
|
|
717
|
+
const { document, sources, diagnostics } = parse(source, 'schema.prisma');
|
|
718
|
+
interpret({ documents: [document], ... });
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Pass the real path when there is one; a synthetic name such as `'<inline>.prisma'` is fine for source that has no file. `sources` maps each syntax root to its file and converts offsets to line and column, which `sourceFile` used to do for the single file.
|
|
722
|
+
|
|
723
|
+
## `emit-requires-deserialize-contract`
|
|
724
|
+
|
|
725
|
+
Find calls to `emit(contract, stack, emission, options)` imported from `@prisma/orm-toolchain/emitter`. Add a `deserializeContract` option that reads the canonical JSON object back into a contract through the family instance of the contract being emitted:
|
|
726
|
+
|
|
727
|
+
```ts
|
|
728
|
+
const familyInstance = family.create(stack);
|
|
729
|
+
const result = await emit(contract, stack, family.emission, {
|
|
730
|
+
serializeContract: (c) => target.contractSerializer.serializeContract(c),
|
|
731
|
+
deserializeContract: (json) => familyInstance.deserializeContract(json),
|
|
732
|
+
});
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
`emit()` generates `contract.d.ts` from that re-read contract, so models, fields and relations appear in the order `contract.json` lists them. Use the family's own deserializer rather than an identity function: the family builds the objects the declaration generator reads. Direct calls to `generateContractDts` are unchanged.
|
|
736
|
+
|
|
737
|
+
## `expression-codec-on-return-type`
|
|
738
|
+
|
|
739
|
+
For custom SQL `Expression` wrappers, move an existing top-level `codec` reference into `returnType.codec`, preserving the complete reference including `typeParams`. Read metadata through `codecOf(expression)` or `expression.returnType.codec`, not `expression.codec`. Keep the existing `returnType.codecId` and nullability; wrappers without an explicit reference continue to use the declared codec id fallback. Do not remove or relocate unrelated codec fields on AST nodes, storage declarations, runtime bindings or scope descriptors.
|
|
740
|
+
|
|
741
|
+
For direct `ExpressionImpl` construction, change `new ExpressionImpl(ast, returnType, codec, projectionAst)` to `new ExpressionImpl(ast, { ...returnType, codec }, projectionAst)`. When the removed codec argument was `undefined`, keep `returnType` unchanged and move any fourth projection argument to the third position. Preserve projection-only lowering separately from predicate and ordering ASTs.
|
|
742
|
+
|
|
743
|
+
## `shared-preparable-envelope`
|
|
744
|
+
|
|
745
|
+
Replace imports of SQL ORM client's `RowQuery` with `Preparable` from `@internal/sql-relational-core/plan`. Supply both type arguments as `Preparable<DbRow, Result>` and return `{ plan, consume }`, where `plan` is a `SqlQueryPlan<DbRow>` and `consume` returns the complete ORM result. Read AST, parameters and metadata through `description.plan`; pass `description.plan` rather than the description to SQL runtime preparation. Keep the consumer's mapping setup outside invocation-time code.
|
|
746
|
+
|
|
747
|
+
For integrations accepting both SQL and ORM callbacks, constrain the callback result with `Q extends SqlQueryPlan | Preparable<unknown, unknown>` and use SQL ORM client's `prepareQuery` and `PreparedFrom<Params, Q>`. Preserve the concrete `Q` so SQL row/statistics types and ORM all/first result types remain distinct. Do not add identity consumers to plain SQL plans.
|
|
748
|
+
|
|
749
|
+
## `params-only-sql-facade-prepare`
|
|
750
|
+
|
|
751
|
+
Find calls to `prepare(declaration, callback)` on clients created by the Postgres or SQLite facade (`@prisma/orm-postgres/runtime`, `@prisma/orm-sqlite/runtime`, or their `@internal/postgres/runtime` and `@internal/sqlite/runtime` counterparts). Resolve the receiver and callback rather than rewriting every method named `prepare`: native SQLite `database.prepare(sql)` and SQL runtime's existing params-only preparation are different APIs and must remain unchanged.
|
|
752
|
+
|
|
753
|
+
Change callbacks from `(sql, params) => ...` to `(params) => ...`. Replace references bound to the removed `sql` callback argument with the same facade receiver's lexical `.sql` property. Preserve the params argument's name, declaration, SQL chain, row selection, filters and invocation target/options. For extracted callbacks, capture the same client in the enclosing scope; do not capture an invocation target or evaluate the callback twice. Update explicit callback type annotations to accept only the placeholder-params argument.
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
// Before
|
|
757
|
+
const query = await db.prepare({ id: 'pg/int4@1' }, (sql, params) =>
|
|
758
|
+
sql.public.users.select('id').where((f, fns) => fns.eq(f.id, params.id)).build(),
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
// After
|
|
762
|
+
const query = await db.prepare({ id: 'pg/int4@1' }, (params) =>
|
|
763
|
+
db.sql.public.users.select('id').where((f, fns) => fns.eq(f.id, params.id)).build(),
|
|
764
|
+
);
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
Apply the same translation to SQLite's flat SQL facade (`sql.users` becomes `db.sql.users`), retaining its existing codec ids. Keep `.query(target, params, options?)` and SQL statistics `.execute(target, params, options?)` calls unchanged. Do not rewrite historical release notes, applied upgrade recipes, generated contracts or tests as part of this source translation.
|
|
768
|
+
|
|
769
|
+
## `preserve-prepared-reference-nullability`
|
|
770
|
+
|
|
771
|
+
Find code that constructs or clones `PreparedParamRef` from SQL relational-core's AST exports. When constructing a reference from a nullable declaration, pass its declared boolean nullability as the third argument to `PreparedParamRef.of(name, codec, nullable)` or `new PreparedParamRef(name, codec, nullable)`. When cloning an existing reference, preserve `ref.nullable`: `PreparedParamRef.of(ref.name, ref.codec, ref.nullable)`. Keep the name and complete codec reference unchanged, and keep constructing frozen class instances rather than spreading nodes into plain objects. Do not derive this flag from a column's nullability or an invocation's bound value.
|
|
772
|
+
|
|
773
|
+
## `preserve-orm-pagination-expressions`
|
|
774
|
+
|
|
775
|
+
Update extension code that reads or mirrors ORM `CollectionState.limit` and `offset`: these fields now contain relational-core `LimitOffsetValue | undefined` (`number | AnyExpression | undefined`), not just numbers. Forward them unchanged to the existing `SelectAst.withLimit` and `withOffset` methods. If processing numeric literals separately, narrow with `typeof value === 'number'`; preserve expression nodes rather than coercing, serializing or boxing them as literal parameters. Test presence against `undefined`, not truthiness, so zero limits and offsets survive. Keep grouped post-aggregation paging's separate numeric state unchanged.
|
|
776
|
+
|
|
777
|
+
## `preserve-grouped-orm-pagination-expressions`
|
|
778
|
+
|
|
779
|
+
Update extension code that reads or mirrors ORM `GroupPagingState.limit` and `offset`: these fields now contain relational-core `LimitOffsetValue | undefined` (`number | AnyExpression | undefined`), not just numbers. Forward them unchanged to the existing `SelectAst.withLimit` and `withOffset` methods. If processing numeric literals separately, narrow with `typeof value === 'number'`; preserve expression nodes rather than coercing, serializing or boxing them as literal parameters. Test presence against `undefined`, not truthiness, so zero limits and offsets survive. Keep grouped post-aggregation paging separate from pre-group collection paging, preserving expression operands at both stages. This extends the collection-pagination migration in `preserve-orm-pagination-expressions` (the previous entry) to grouped pagination; do not keep grouped paging numeric-only.
|
|
780
|
+
|
|
781
|
+
## `postgres-list-element-codecs-receive-raw-strings`
|
|
782
|
+
|
|
783
|
+
Review PostgreSQL extension codecs whose descriptors can be used by `CodecRef.many` list columns. Inbound list framing is now target-owned: the target parses the Postgres array literal and invokes the scalar element codec for each non-null raw string element. Keep scalar direct-query compatibility as needed, but make the element `decode(wire, ctx)` accept the raw text spelling Postgres emits for that scalar value; the built-in numeric, boolean, integer, and float codecs accept both raw strings and native scalar wire values for this reason. Do not add a native-array fallback at the list-frame boundary, and do not add compatibility exports or codec-id aliases. `codecId`, `typeParams`, and emitted `CodecRef.many` shapes are unchanged.
|
|
784
|
+
|
|
785
|
+
## `query-operation-types-move-to-the-postgres-target`
|
|
786
|
+
|
|
787
|
+
The Postgres adapter no longer contributes query operations; the target does. Change the import:
|
|
788
|
+
|
|
789
|
+
```ts
|
|
790
|
+
// before
|
|
791
|
+
import type { QueryOperationTypes } from '@internal/adapter-postgres/operation-types';
|
|
792
|
+
// after
|
|
793
|
+
import type { QueryOperationTypes } from '@internal/target-postgres/operation-types';
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Under the published facade, `@prisma/orm-postgres/adapter/operation-types` becomes `@prisma/orm-postgres/target/operation-types`. Emitted contracts name the target import under the alias `PgTargetQueryOps` instead of `PgAdapterQueryOps`, so a snapshot or fixture that pins emitted contract text needs regenerating.
|
|
797
|
+
|
|
798
|
+
The type's shape is otherwise unchanged. It gains `fullTextMatches`, `fullTextRank` and `fullTextHeadline` on `textual` columns, whose query argument is a `pg/tsquery@1` value (built with a parser or the `tsquery` tag). It also gains the four parser operations `websearchToTsquery`, `toTsquery`, `plaintoTsquery` and `phrasetoTsquery`, which have no `self`, so they attach to no column. None of the names collide with an existing operation, so an extension that intersects its own `QueryOperationTypes` with the Postgres one needs no other edit.
|
|
799
|
+
|
|
800
|
+
## `native-enum-codec-is-not-textual`
|
|
801
|
+
|
|
802
|
+
Postgres has no `LIKE`, `ILIKE` or `to_tsvector` for an enum type, so the native enum codec (`pgEnumDescriptor`, `PgEnumCodec` in `@internal/target-postgres/codecs`) no longer declares `textual`. Its traits are `['equality', 'order']`.
|
|
803
|
+
|
|
804
|
+
An extension operation whose `self` targets `{ traits: ['textual'] }` no longer attaches to native enum columns. That is intended when the operation passes the column where Postgres expects `text`. If an operation of yours does work on an enum, declare its `self` by codec id (`{ codecId: 'pg/enum@1' }`).
|
|
805
|
+
|
|
806
|
+
A hand-written type fixture that spells out `pg/enum@1` with `traits: 'equality' | 'order' | 'textual'` should drop `'textual'` to match the real codec.
|
|
807
|
+
|
|
808
|
+
`min` and `max` over a native enum still resolve to `pg/enum@1`: the Postgres target now lists the enum codec explicitly instead of reaching it through the `textual` fallback. Emitted `AggregateTypes` do not change.
|
|
809
|
+
|
|
810
|
+
## `sql-lowering-spec-drops-strategy`
|
|
811
|
+
|
|
812
|
+
Nothing ever read `strategy`; the template already says whether an operation lowers as an infix operator or a function call. The field is gone from `SqlLoweringSpec`, so a descriptor that still sets it fails to typecheck as an excess property. Delete the line:
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
// before
|
|
816
|
+
lowering: { targetFamily: 'sql', strategy: 'function', template: 'lower({{self}})' },
|
|
817
|
+
// after
|
|
818
|
+
lowering: { targetFamily: 'sql', template: 'lower({{self}})' },
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
The rendered SQL is unchanged.
|
|
822
|
+
|
|
823
|
+
## `share-create-default-cache-across-inserts`
|
|
824
|
+
|
|
825
|
+
If an extension orchestrates multiple `applyMutationDefaults` calls for one logical create, allocate one `Map<string, unknown>` before iterating its rows and pass it as `defaultValueCache` to every call. For multi-table inheritance, pass the same cache to both the base-table and variant-table inserts, including all rows of a bulk create.
|
|
826
|
+
|
|
827
|
+
Let helpers that apply create defaults accept that cache from their caller, with a fresh map as the default for standalone operations. Do not keep it on a reusable collection, runtime, or transaction: a subsequent create operation needs a fresh cache.
|
|
828
|
+
|
|
829
|
+
Continue letting the mutation-default registry honor generator stability. Do not cache field-stable generators yourself; generated IDs must remain independent.
|
|
830
|
+
|
|
831
|
+
## `supabase-contract-declares-nullable-list-columns`
|
|
832
|
+
|
|
833
|
+
The `@prisma/orm-extension-supabase` contract gains two fields that were previously omitted because the PSL printer could not write a nullable list: `StorageBucket.allowedMimeTypes` (`storage.buckets.allowed_mime_types`) and `StorageObject.pathTokens` (`storage.objects.path_tokens`), both `String[]?`. This is one of several changes to the Supabase contract in this release; `supabase-contract-regenerated-from-the-reference-fixture` (next) gives the storage hash they produce together.
|
|
834
|
+
|
|
835
|
+
A contract that composes the Supabase space references it by id, so your own `contract.json` and `contract.d.ts` do not change. What changes is the signature: a database that was signed against the previous Supabase contract no longer matches the new hash, so run `prisma db sign` against it once after upgrading; one sign covers this entry and the next. Both columns already exist on every Supabase database, so `prisma db verify` passes without a schema change. `path_tokens` is `GENERATED ALWAYS`: read it, do not write it.
|
|
836
|
+
|
|
837
|
+
## `supabase-contract-regenerated-from-the-reference-fixture`
|
|
838
|
+
|
|
839
|
+
The `@prisma/orm-extension-supabase` contract is now exactly what `contract:generate` produces from the reference fixture, which it had drifted away from. The Supabase space's storage hash changes from `409d9a5191d9d7d8e45a795cb55695a79edce9d8f42ff1e456bce6e79f98dff1` (the hash in 8.0.0-rc.11) to `734893bc990ae5ec14dc7ad50a7d8b5873a864f056085a7ffd2f69c866989067`. That new hash also covers the two fields added by `supabase-contract-declares-nullable-list-columns` (the previous entry).
|
|
840
|
+
|
|
841
|
+
Five things changed in the contract.
|
|
842
|
+
|
|
843
|
+
**43 check constraints are now declared.** Every `CHECK` that the pack's reference Supabase build declares on an `auth` or `storage` table — for example `users_email_change_confirm_status_check` and `one_time_tokens_token_hash_check` — is now part of the contract. The reference build is supabase/postgres 17.6.1.106 with gotrue 2.188.1 and storage-api 1.54.1. On a database at or near that version the constraints are already present, so `prisma db verify` passes without a schema change. This is the one item that can newly fail for you: the checks used to be a tolerated live extra and are now a declared shape, so if your Supabase build's constraint set differs, verify reports the missing ones. You cannot repair that with a migration, because Prisma emits no DDL against an externally controlled table; report the difference so the pack's reference fixture can be refreshed.
|
|
844
|
+
|
|
845
|
+
**Six native-enum column defaults are declared as member literals.** `auth.oauth_clients.client_type`, `auth.oauth_authorizations.response_type`, `auth.oauth_authorizations.status`, and the `type` column of `storage.buckets`, `storage.buckets_analytics` and `storage.buckets_vectors` previously carried the raw cast expression as their default, for example `{ "kind": "function", "expression": "'STANDARD'::storage.buckettype" }`. They now carry the enum member itself: `{ "kind": "literal", "value": "STANDARD" }`. This is the same live default read a more precise way, so the live databases need no change; if you read a column's declared default out of the contract, expect a literal rather than an expression.
|
|
846
|
+
|
|
847
|
+
**Four `jsonb` column defaults are declared as JSON literals.** `auth.custom_oauth_providers.attribute_mapping`, `auth.custom_oauth_providers.authorization_params` and `storage.iceberg_namespaces.metadata` (`'{}'::jsonb`), and `auth.webauthn_credentials.transports` (`'[]'::jsonb`), previously carried the raw cast expression as their default. They now carry the JSON value itself, for example `{ "kind": "literal", "value": {} }`, as the JSON row of `dbgenerated-removed-from-psl` describes. The live defaults need no change.
|
|
848
|
+
|
|
849
|
+
**Two list columns carry an element-not-null waiver.** `auth.custom_oauth_providers.acceptable_client_ids` and `auth.custom_oauth_providers.scopes` now carry `"noCheck": ["elementNotNull"]`, matching the two `storage` list columns that already did. `contract infer` writes this for any list column with no live check at the derived name, and the committed contract is the generator's output, so it carries it too. This item moves the storage hash and changes nothing else you can observe: the pack is under `external` control, so a derived check is stripped before emit whether the waiver is written or not, and `db verify` demanded no such constraint before and demands none now.
|
|
850
|
+
|
|
851
|
+
**78 timestamp columns are written as `Timestamptz` instead of `DateTime` in the PSL.** Same codec (`pg/timestamptz-temporal@1`) and same emitted column, so this is a text change only.
|
|
852
|
+
|
|
853
|
+
A contract that composes the Supabase space references it by id, so your own `contract.json` and `contract.d.ts` do not change. What changes is the signature: a database that was signed against the previous Supabase contract no longer matches the new hash, so run `prisma db sign` against it after upgrading. If you re-emit your own contract, do that first so the composed space is the new one.
|
|
854
|
+
|
|
855
|
+
## `contract-space-restamp`
|
|
856
|
+
|
|
857
|
+
For every `contract.json` matched by `detection`, run the extension package's `build:contract-space` script (or its emit command) once after upgrading. This entry accounts for the embedded `version` moving to `8.0.0-rc.12`; any other difference in the emitted files comes from an earlier entry in this guide.
|