@prisma/orm-mongo 8.0.0-rc.14-dev.46 → 8.0.0-rc.15
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/upgrading/app/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/instructions.md +1365 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/scripts/data-type-in-contract/data-type-in-contract.ts +1492 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/instructions.md +2168 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/scripts/data-type-in-contract/data-type-in-contract.ts +1492 -0
package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/instructions.md
ADDED
|
@@ -0,0 +1,2168 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.14"
|
|
3
|
+
to: "8.0.0-rc.15"
|
|
4
|
+
changes:
|
|
5
|
+
- id: contract-stores-data-type
|
|
6
|
+
summary: |
|
|
7
|
+
A SQL contract names each column's data type in `dataType` (for example `pg/int4`) instead of
|
|
8
|
+
its database type name in `nativeType`. Run the colocated script on the extension's contract
|
|
9
|
+
space (it also writes lists in this release's new form), release the extension against the
|
|
10
|
+
framework version that contains this change, and raise its peer dependency floor to that
|
|
11
|
+
version: the old framework refuses a rewritten contract space and the new framework refuses
|
|
12
|
+
an old one, so your users upgrade the framework and the extension in one step. Publish a
|
|
13
|
+
`--data-type` line for each codec the extension owns.
|
|
14
|
+
detection:
|
|
15
|
+
glob: "**/*.json"
|
|
16
|
+
matches:
|
|
17
|
+
- '"nativeType"\s*:\s*"'
|
|
18
|
+
script: ./scripts/data-type-in-contract/data-type-in-contract.ts
|
|
19
|
+
- id: reemit-extension-list-contracts
|
|
20
|
+
summary: Re-emit bundled extension contracts and reconcile changed snapshot hashes and references; for SQL contract spaces, `contract-stores-data-type` rewrites the historical snapshots.
|
|
21
|
+
- id: consume-nested-list-cardinality
|
|
22
|
+
summary: Update contract producers and consumers for explicit cardinality and nested element nullability.
|
|
23
|
+
- id: sql-data-type-declares-names
|
|
24
|
+
summary: |
|
|
25
|
+
A SQL data type now declares how the database writes and reports it. Declare an extension's
|
|
26
|
+
SQL data types with `sqlDataType(id, { params, texts, ... })` from
|
|
27
|
+
`@internal/sql-contract/data-type` instead of `dataType(id, ...)`. Migrations, schema
|
|
28
|
+
verification and PostgreSQL parameter casts read the column type's name from this
|
|
29
|
+
declaration only.
|
|
30
|
+
detection:
|
|
31
|
+
glob: "**/*.{ts,mts,cts}"
|
|
32
|
+
matches:
|
|
33
|
+
- '(?<![\w$.])dataType\(\s*[''"][a-z0-9-]+/'
|
|
34
|
+
- id: codec-target-types-removed
|
|
35
|
+
summary: |
|
|
36
|
+
`targetTypes` is removed from codec descriptors and templates, with `CodecLookup.targetTypesFor`
|
|
37
|
+
and `CodecDescriptorRegistry.byTargetType`. A SQL codec's type names move to its data type's
|
|
38
|
+
`texts`; a Mongo codec's BSON types move to its data type, declared with
|
|
39
|
+
`mongoDataType(id, { bsonTypes })` from `@internal/mongo-contract/data-type`.
|
|
40
|
+
detection:
|
|
41
|
+
glob: "**/*.{ts,mts,cts}"
|
|
42
|
+
matches:
|
|
43
|
+
- '\btargetTypes\b'
|
|
44
|
+
- '\btargetTypesFor\b'
|
|
45
|
+
- '\bbyTargetType\b'
|
|
46
|
+
- id: native-type-rendering-hooks-removed
|
|
47
|
+
summary: |
|
|
48
|
+
The rendering hooks are removed: `CodecControlHooks.expandNativeType`, the protected
|
|
49
|
+
`nativeType(params)` method of `PostgresCodecDescriptor` and its public `nativeTypeFor(ref)`,
|
|
50
|
+
`NativeTypeExpander`, and the control adapter's `normalizeNativeType`. The data type's
|
|
51
|
+
`texts` write the column type instead.
|
|
52
|
+
detection:
|
|
53
|
+
glob: "**/*.{ts,mts,cts}"
|
|
54
|
+
matches:
|
|
55
|
+
- '\bexpandNativeType\b'
|
|
56
|
+
- '\bprotected\s+override\s+nativeType\s*\('
|
|
57
|
+
- '\bnativeTypeFor\b'
|
|
58
|
+
- '\bNativeTypeExpander\b'
|
|
59
|
+
- '\bnormalizeNativeType\b'
|
|
60
|
+
- id: comments-name-removed-apis
|
|
61
|
+
summary: |
|
|
62
|
+
Comments that describe `expandNativeType`, `targetTypes` or the `nativeType()` hook describe
|
|
63
|
+
code that no longer exists. Delete each such sentence, and say that a codec descriptor
|
|
64
|
+
carries its data type where a comment lists target types or a native type among its parts.
|
|
65
|
+
detection:
|
|
66
|
+
glob: "**/*.{ts,mts,cts}"
|
|
67
|
+
matches:
|
|
68
|
+
- '(?://|\*).*\b(?:expandNativeType|targetTypes)\b'
|
|
69
|
+
- '(?://|\*).*\b(?:target types|native type|bare `nativeType)'
|
|
70
|
+
- id: postgres-codec-takes-data-type
|
|
71
|
+
summary: |
|
|
72
|
+
`postgresCodec(template, options)` and `sqliteCodec(template, options)` take the data type
|
|
73
|
+
object in `options.dataType`, not its id, and `postgresCodec` no longer takes a `nativeType`
|
|
74
|
+
option. The adapted codec's `paramsSchema` is the data type's `params`.
|
|
75
|
+
detection:
|
|
76
|
+
glob: "**/*.{ts,mts,cts}"
|
|
77
|
+
matches:
|
|
78
|
+
- '\b(?:postgresCodec|sqliteCodec)\s*\([^;]*?\bdataType\s*:\s*[\w$]+\.id\b'
|
|
79
|
+
- '\bpostgresCodec\s*\([^;]*?\bnativeType\s*:'
|
|
80
|
+
- id: codec-params-schema-is-data-type-params
|
|
81
|
+
summary: |
|
|
82
|
+
A codec's `paramsSchema` is its data type's `params`, referenced and not restated. Move the
|
|
83
|
+
parameter schema and its bounds onto the data type, and point the codec at it.
|
|
84
|
+
detection:
|
|
85
|
+
glob: "**/*.{ts,mts,cts}"
|
|
86
|
+
matches:
|
|
87
|
+
- '\bparamsSchema\b[^;=]*=\s*(?:arktype|type)\s*\('
|
|
88
|
+
- '\bconst\s+[\w$]*[pP]aramsSchema\s*=\s*(?:arktype|type)\s*\('
|
|
89
|
+
- id: column-helper-checks-data-type-params
|
|
90
|
+
summary: |
|
|
91
|
+
A column helper no longer checks its arguments against bounds of its own. Delete the check:
|
|
92
|
+
building the contract checks every column's parameters against its data type and raises
|
|
93
|
+
`CONTRACT.TYPE_PARAMS_INVALID`.
|
|
94
|
+
detection:
|
|
95
|
+
glob: "**/*.{ts,mts,cts}"
|
|
96
|
+
matches:
|
|
97
|
+
- '[''"]CONTRACT\.ARGUMENT_INVALID[''"][^;]*\b(?:must be|non-negative|in the range|in \[)'
|
|
98
|
+
- id: type-constructor-templates-lose-native-type
|
|
99
|
+
summary: |
|
|
100
|
+
A type constructor's or field preset's `output` no longer takes `nativeType`; it is
|
|
101
|
+
`{ codecId, typeParams? }`. An argument mapped onto a data type parameter drops its `minimum`
|
|
102
|
+
and `maximum`, because the data type's `params` checks it. A constructor may set
|
|
103
|
+
`inferred: true`; at most one per data type.
|
|
104
|
+
detection:
|
|
105
|
+
glob: "**/*.{ts,mts,cts}"
|
|
106
|
+
matches:
|
|
107
|
+
- '\boutput\s*:\s*\{[^{}]*\bnativeType\s*:'
|
|
108
|
+
- id: runtime-descriptor-registers-data-types
|
|
109
|
+
summary: |
|
|
110
|
+
A SQL runtime extension descriptor lists its `dataTypes`, the same list as its control
|
|
111
|
+
descriptor, and `createPostgresAdapter({ codecDescriptors })` takes the matching `dataTypes`.
|
|
112
|
+
The runtime writes PostgreSQL parameter casts from the data type a codec represents.
|
|
113
|
+
detection:
|
|
114
|
+
glob: "**/*.{ts,mts,cts}"
|
|
115
|
+
matches:
|
|
116
|
+
- '\bSqlRuntimeExtensionDescriptor\s*<[^>]*>\s*=\s*\{(?=[^;]*\bcodecDescriptors\b)(?![^;]*\bdataTypes\b)'
|
|
117
|
+
- '\bcreatePostgresAdapter\s*\(\s*\{(?=[^}]*\bcodecDescriptors\b)(?![^}]*\bdataTypes\b)'
|
|
118
|
+
- id: column-descriptors-drop-native-type
|
|
119
|
+
summary: |
|
|
120
|
+
`ColumnTypeDescriptor` and authored `storage.types` entries lose `nativeType`, the pack
|
|
121
|
+
metadata loses its `types.storage` list and `StorageTypeMetadata` is deleted, `column()`
|
|
122
|
+
loses its fourth argument, and a codec descriptor's
|
|
123
|
+
`columnFromEntity` returns `{ typeParams }` only. The contract takes a column's data type from
|
|
124
|
+
its codec. A `types` constraint over what `type.*` helpers return is
|
|
125
|
+
`Record<string, AuthoredStorageTypeInstance>` instead of `Record<string, StorageTypeInstance>`.
|
|
126
|
+
`StorageTypeInstanceInput` takes `dataType`, the id of the data type the codec represents,
|
|
127
|
+
in place of `nativeType`.
|
|
128
|
+
detection:
|
|
129
|
+
glob: "**/*.{ts,mts,cts}"
|
|
130
|
+
matches:
|
|
131
|
+
- '(?<![\w$])(?<!readonly\s+)nativeType\s*:'
|
|
132
|
+
- '(?<![\w$.])column\s*\((?:[^()]|\([^()]*\))*,(?:[^()]|\([^()]*\))*,(?:[^()]|\([^()]*\))*,\s*[\w''"](?:[^()]|\([^()]*\))*\)'
|
|
133
|
+
- '\bRecord\s*<\s*string\s*,\s*StorageTypeInstance\s*>'
|
|
134
|
+
- '\bStorageTypeInstanceInput\b'
|
|
135
|
+
- '\bStorageTypeMetadata\b'
|
|
136
|
+
- '\bstorage\s*:\s*\[\s*\{\s*typeId\b'
|
|
137
|
+
- id: sqlite-data-types-are-stored-types
|
|
138
|
+
summary: |
|
|
139
|
+
The SQLite target declares only the types SQLite stores, plus the two character types:
|
|
140
|
+
`sqlite/text`, `sqlite/integer`, `sqlite/real`, `sqlite/blob`, `sqlite/character` and
|
|
141
|
+
`sqlite/character-varying`. `sqlite/json`, `sqlite/datetime` and `sqlite/bigint` are deleted:
|
|
142
|
+
a codec, cast or authoring entry that names one fails assembly. The JSON and date-time codecs
|
|
143
|
+
represent `sqlite/text`, the big integer codecs `sqlite/integer`. The date-time canonical form
|
|
144
|
+
moves from `sqlite/datetime` to the codec `sqlite/datetime@1`, whose descriptor declares
|
|
145
|
+
`toCanonicalForm`.
|
|
146
|
+
detection:
|
|
147
|
+
glob: "**/*.{ts,mts,cts}"
|
|
148
|
+
matches:
|
|
149
|
+
- '[''"`]sqlite/(?:json|datetime|bigint)[''"`]'
|
|
150
|
+
- '\bsqlite(?:Json|Datetime|Bigint)\.id\b'
|
|
151
|
+
- id: canonical-form-of-a-column
|
|
152
|
+
summary: |
|
|
153
|
+
A column's canonical form comes from one function, `canonicalFormOf(codec, dataTypes)` in
|
|
154
|
+
`@internal/framework-components/codec`: the codec's `toCanonicalForm` when it declares one,
|
|
155
|
+
else its data type's. `CanonicalDateTimeOptions.dataTypeId` is renamed `ownerId`.
|
|
156
|
+
`SqlColumnDefaultIRInput` carries `toCanonicalForm` instead of `dataType`, and
|
|
157
|
+
`SqlColumnIRInput` gains `toCanonicalForm`. `DefaultMappingOptions.columnDataType` is
|
|
158
|
+
replaced by `columnCodec`, the column's codec descriptor.
|
|
159
|
+
detection:
|
|
160
|
+
glob: "**/*.{ts,mts,cts}"
|
|
161
|
+
matches:
|
|
162
|
+
- '\bCanonicalDateTimeOptions\b'
|
|
163
|
+
- '\bcanonicalDateTime\s*\('
|
|
164
|
+
- '\bcolumnDataType\b'
|
|
165
|
+
- '\bSqlColumnDefaultIR\b'
|
|
166
|
+
- id: authoring-entry-key-checked
|
|
167
|
+
summary: |
|
|
168
|
+
Assembly refuses an authoring entry filed under the wrong key with
|
|
169
|
+
`CONTRACT.DATA_TYPE_ENTRY_KEY_INVALID`: an entry whose tag names the type it yields (`type`)
|
|
170
|
+
sits under `tagEntryKey(tag)`, and an entry under a data type's id names no `type`.
|
|
171
|
+
detection:
|
|
172
|
+
glob: "**/*.{ts,mts,cts}"
|
|
173
|
+
matches:
|
|
174
|
+
- '\bkind\s*:\s*[''"]tag[''"]'
|
|
175
|
+
- id: the-sql-tag-writes-the-sql-expression-data-type
|
|
176
|
+
summary: |
|
|
177
|
+
Lowering entries are removed. Every entry in `authoring.dataTypes` is a `DataTypeAuthoringEntry`
|
|
178
|
+
keyed by a registered data type id, and `sql` is the tag of the data type `sql/expression`.
|
|
179
|
+
detection:
|
|
180
|
+
glob: "**/*.{ts,mts,cts}"
|
|
181
|
+
matches:
|
|
182
|
+
- '\b(loweringEntryKey|isLoweringEntryKey|isDataTypeLoweringEntry)\b'
|
|
183
|
+
- '\b(AuthoringDataTypeEntry|DataTypeLoweringAuthoringEntry|TaggedLiteralValue)\b'
|
|
184
|
+
- '\bsqlDefaultLiteralTagEntry\b'
|
|
185
|
+
- '\bcreate(Postgres|Sqlite)DataTypeEntries\b'
|
|
186
|
+
- '\bPSL_INVALID_DEFAULT_SQL\b'
|
|
187
|
+
- id: prefixed-sql-tags-are-removed
|
|
188
|
+
summary: |
|
|
189
|
+
The tags `pg.sql` and `sqlite.sql` are removed. Write `sql`.
|
|
190
|
+
detection:
|
|
191
|
+
glob: "**/*.{prisma,ts}"
|
|
192
|
+
matches:
|
|
193
|
+
- '(?<![\w./-])(pg|sqlite)\s*\.\s*sql\s*\\?[\x60"'']'
|
|
194
|
+
- id: default-diagnostic-codes-changed
|
|
195
|
+
summary: |
|
|
196
|
+
Four PSL diagnostic codes for written values changed: `PSL_UNKNOWN_DEFAULT_LITERAL_TAG` is now `PSL_UNKNOWN_LITERAL_TAG`, `PSL_INVALID_JSON_LITERAL` is now `PSL_INVALID_LITERAL`, `PSL_DEFAULT_TYPE_INCOMPATIBLE` is now `PSL_VALUE_TYPE_INCOMPATIBLE`, or `PSL_DEFAULT_LIST_EXPECTED` for a single value on a list column, and most cases of `PSL_INVALID_DEFAULT_LITERAL` moved to `PSL_INVALID_LITERAL`.
|
|
197
|
+
detection:
|
|
198
|
+
glob: "**/*.{ts,mts,cts,js,mjs}"
|
|
199
|
+
matches:
|
|
200
|
+
- '\bPSL_INVALID_JSON_LITERAL\b'
|
|
201
|
+
- '\bPSL_UNKNOWN_DEFAULT_LITERAL_TAG\b'
|
|
202
|
+
- '\bPSL_DEFAULT_TYPE_INCOMPATIBLE\b'
|
|
203
|
+
- '\bPSL_INVALID_DEFAULT_LITERAL\b'
|
|
204
|
+
- id: resolve-identity-value-receives-data-type
|
|
205
|
+
summary: |
|
|
206
|
+
`CodecControlHooks.resolveIdentityValue` receives `dataType`, the id of the data type the
|
|
207
|
+
column's codec represents, instead of `nativeType`.
|
|
208
|
+
detection:
|
|
209
|
+
glob: "**/*.{ts,mts,cts}"
|
|
210
|
+
matches:
|
|
211
|
+
- '\bresolveIdentityValue\s*:\s*\(\s*\{[^}]*\bnativeType\b'
|
|
212
|
+
- '\bResolveIdentityValueInput\b[^;]*\bnativeType\b'
|
|
213
|
+
- id: validate-scalar-type-codec-ids-removed
|
|
214
|
+
summary: |
|
|
215
|
+
`validateScalarTypeCodecIds` is removed. Stack assembly now refuses, with an `InternalError`,
|
|
216
|
+
a type constructor or field preset that names an unregistered codec, a constructor argument
|
|
217
|
+
mapped onto a parameter neither the data type nor the codec declares, and two constructors of
|
|
218
|
+
one data type marked `inferred`. The SQL family refuses two SQL data types that claim the same
|
|
219
|
+
reported type when it creates its control instance, so CLI commands report it and the
|
|
220
|
+
language server does not.
|
|
221
|
+
detection:
|
|
222
|
+
glob: "**/*.{ts,mts,cts}"
|
|
223
|
+
matches:
|
|
224
|
+
- '\bvalidateScalarTypeCodecIds\b'
|
|
225
|
+
- id: assemble-data-types-moved-to-codec
|
|
226
|
+
summary: |
|
|
227
|
+
`assembleDataTypes` moves from `@internal/framework-components/control` to
|
|
228
|
+
`@internal/framework-components/codec`, because the runtime plane assembles data types too.
|
|
229
|
+
detection:
|
|
230
|
+
glob: "**/*.{ts,mts,cts}"
|
|
231
|
+
matches:
|
|
232
|
+
- 'import\s*\{[^}]*\bassembleDataTypes\b[^}]*\}\s*from\s*[''"]@internal/framework-components/control[''"]'
|
|
233
|
+
- id: number-text-helpers-moved
|
|
234
|
+
summary: |
|
|
235
|
+
`numeralText` moves from `@internal/sql-relational-core/ast` to
|
|
236
|
+
`@internal/sql-contract/data-type`, beside the SQL data type declarations that use it.
|
|
237
|
+
detection:
|
|
238
|
+
glob: "**/*.{ts,mts,cts}"
|
|
239
|
+
matches:
|
|
240
|
+
- '\bnumeralText\b'
|
|
241
|
+
- id: data-type-support-moved
|
|
242
|
+
summary: |
|
|
243
|
+
The helpers SQL targets use to implement their data types and PSL entries move from
|
|
244
|
+
`@internal/sql-relational-core/ast` to `@internal/sql-contract/data-type-support`:
|
|
245
|
+
`escapePslString`, `isNumeralText`, `canonicalNumeralText`, `integerTextCanonicalForm`,
|
|
246
|
+
`signedRange`, `createNumberClassifier`, `parseJsonBody`, `printJsonBody`,
|
|
247
|
+
`canonicalDateTime`, and the types `NumberClassification`, `IntegerStep`,
|
|
248
|
+
`NumberClassifierSpec`, `DateTimeShape` and `CanonicalDateTimeOptions`.
|
|
249
|
+
detection:
|
|
250
|
+
glob: "**/*.{ts,mts,cts}"
|
|
251
|
+
matches:
|
|
252
|
+
- 'import\s+(?:type\s+)?\{[^}]*\b(?:escapePslString|isNumeralText|canonicalNumeralText|integerTextCanonicalForm|signedRange|createNumberClassifier|parseJsonBody|printJsonBody|canonicalDateTime|NumberClassification|IntegerStep|NumberClassifierSpec|DateTimeShape|CanonicalDateTimeOptions)\b[^}]*\}\s*from\s*[''"]@internal/sql-relational-core/ast[''"]'
|
|
253
|
+
- id: numeric-limits-removed
|
|
254
|
+
summary: |
|
|
255
|
+
`NUMERIC_PRECISION_RANGE` and `NUMERIC_SCALE_RANGE` are no longer exported from
|
|
256
|
+
`@internal/target-postgres/codecs`. The bounds of `numeric` are written in the `pg/numeric`
|
|
257
|
+
data type's parameter schema, `pgNumericParams` from `@internal/target-postgres/data-types`.
|
|
258
|
+
detection:
|
|
259
|
+
glob: "**/*.{ts,mts,cts}"
|
|
260
|
+
matches:
|
|
261
|
+
- '\bNUMERIC_(?:PRECISION|SCALE)_RANGE\b'
|
|
262
|
+
- id: postgres-codecs-decode-server-text
|
|
263
|
+
summary: The Postgres runtime driver now returns every column as the server's text output. A Postgres codec's `decode` receives that text for its type instead of the value `pg` used to parse, and direct `driver.query` callers receive strings.
|
|
264
|
+
detection:
|
|
265
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
266
|
+
matches:
|
|
267
|
+
- '(?<![\w$])(?:PostgresCodecDescriptor|definePostgresCodecs)(?![\w$])'
|
|
268
|
+
- '@internal/driver-postgres/runtime[\s\S]*\.query\('
|
|
269
|
+
- id: codecs-decode-json-reads-stored-forms
|
|
270
|
+
summary: |
|
|
271
|
+
A built-in codec's `decodeJson` now throws `RUNTIME.DECODE_FAILED` for JSON that is not a stored form of its type. Pass it the stored form. A codec an extension contributes should read the same way: every form the database writes for its type, and nothing else.
|
|
272
|
+
detection:
|
|
273
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
274
|
+
matches:
|
|
275
|
+
- '\bdecodeJson\b'
|
|
276
|
+
- id: mongo-codec-requires-decode-json
|
|
277
|
+
summary: |
|
|
278
|
+
`mongoCodec` now requires `decodeJson` unless the codec's application type is exactly `JsonValue`. Add a `decodeJson` that refuses JSON of another kind with `RUNTIME.DECODE_FAILED`.
|
|
279
|
+
detection:
|
|
280
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
281
|
+
matches:
|
|
282
|
+
- '\bmongoCodec\s*\('
|
|
283
|
+
- id: sql-float-json-helpers-removed
|
|
284
|
+
summary: |
|
|
285
|
+
`sqlFloatEncodeJson`, `sqlFloatDecodeJson` and `isNonFiniteText` are no longer exported from `@internal/sql-relational-core/ast`. Use `encodeJsonFloat`, `decodeJsonFloat(codecId, json)` and `isNonFiniteText` from `@internal/framework-components/codec`.
|
|
286
|
+
detection:
|
|
287
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
288
|
+
matches:
|
|
289
|
+
- '\bsqlFloat(?:En|De)codeJson\b'
|
|
290
|
+
- '\bisNonFiniteText\b'
|
|
291
|
+
- id: codec-lookup-has-no-descriptor-for
|
|
292
|
+
summary: |
|
|
293
|
+
`CodecLookup` no longer has `descriptorFor`. A lookup that builds a column's codec is a `CodecLookupWithDescriptors`, whose `descriptorFor` is required: the `codecLookup` option of `defineContract`, `ContractSourceContext.codecLookup` and `CodecRegistry`. `emptyCodecLookup` is a plain `CodecLookup`. Type such a lookup `CodecLookupWithDescriptors` and give it a `descriptorFor` that answers from the same codecs as `get`.
|
|
294
|
+
detection:
|
|
295
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
296
|
+
matches:
|
|
297
|
+
- '\bCodecLookup\b'
|
|
298
|
+
- '\bemptyCodecLookup\b'
|
|
299
|
+
- '\bcodecLookup\s*:'
|
|
300
|
+
- '\bdescriptorFor\?\.'
|
|
301
|
+
- id: mongo-codecs-check-json
|
|
302
|
+
summary: |
|
|
303
|
+
The built-in Mongo codecs now refuse a JSON value that is not the JSON form of their type, where most passed it through: a PSL enum member whose value its `@@type` codec does not take is now refused at `contract emit`, and a TypeScript `enumType` member that `mongo/objectId@1` or `mongo/int32@1` does not hold is refused when the contract is built. Correct the member.
|
|
304
|
+
detection:
|
|
305
|
+
glob: "**/*.{prisma,ts,mts,cts,tsx}"
|
|
306
|
+
matches:
|
|
307
|
+
- '@@type\(\s*"mongo/'
|
|
308
|
+
- '[''"]mongo/(?:objectId|int32)@1[''"]'
|
|
309
|
+
- '\bMONGO_(?:OBJECTID|INT32)_CODEC_ID\b'
|
|
310
|
+
- id: text-array-elements-nullable
|
|
311
|
+
summary: |
|
|
312
|
+
A `textArray()` column's elements are now typed `string | null`, because a `text[]` holds NULL elements, which it reads as `null`. Handle the `null`.
|
|
313
|
+
detection:
|
|
314
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
315
|
+
matches:
|
|
316
|
+
- '\btextArray\s*\('
|
|
317
|
+
- '[''"]pg/text-array@1[''"]'
|
|
318
|
+
- id: char-reads-drop-only-padding
|
|
319
|
+
summary: |
|
|
320
|
+
A `char(n)` column now reads the same through `.include()` as through a flat read: without the trailing spaces that pad it, where an include used to return them, and keeping a trailing tab or newline, which a flat read used to drop. Compare `char` values without their padding.
|
|
321
|
+
detection:
|
|
322
|
+
glob: "**/contract.json"
|
|
323
|
+
matches:
|
|
324
|
+
- '"codecId"\s*:\s*"(?:sql|pg)/char@1"'
|
|
325
|
+
- id: sqlite-nan-parameters-refused
|
|
326
|
+
summary: |
|
|
327
|
+
On SQLite, NaN written to a float column or used as a filter value now throws `RUNTIME.ENCODE_FAILED` naming the codec, where SQLite stored NULL or matched nothing. Write `null` for no value.
|
|
328
|
+
detection:
|
|
329
|
+
glob: "**/contract.json"
|
|
330
|
+
matches:
|
|
331
|
+
- '"target"\s*:\s*"sqlite"'
|
|
332
|
+
- id: sqlite-int-include-refuses-inexact-values
|
|
333
|
+
summary: |
|
|
334
|
+
On SQLite, an `.include()` of a row whose `sql/int@1` column holds an INTEGER past 2^53, or a REAL, now throws `RUNTIME.DECODE_FAILED`, where it read the value rounded or with a fraction. Store such values in a `BigInt` or `Float` column.
|
|
335
|
+
detection:
|
|
336
|
+
glob: "**/contract.json"
|
|
337
|
+
matches:
|
|
338
|
+
- '"codecId"\s*:\s*"sql/int@1"'
|
|
339
|
+
- id: contract-build-takes-lookups
|
|
340
|
+
summary: |
|
|
341
|
+
`buildSqlContractFromDefinition` and the `defineContract` of `@internal/sql-contract-ts`
|
|
342
|
+
require `codecLookup` and `dataTypeLookup`. The Postgres and SQLite `defineContract` facades
|
|
343
|
+
build both from the target and `extensions`, so a TypeScript contract that uses an extension's
|
|
344
|
+
codec must list that extension, or pass lookups that hold its codecs and data types.
|
|
345
|
+
detection:
|
|
346
|
+
glob: "**/*.{ts,mts,cts}"
|
|
347
|
+
matches:
|
|
348
|
+
- '\bbuildSqlContractFromDefinition\s*\('
|
|
349
|
+
- '\bdefineContract\s*\(\s*\{\s*\}'
|
|
350
|
+
- id: define-contract-wrapper-builds-data-type-lookup
|
|
351
|
+
summary: |
|
|
352
|
+
A package that wraps `buildBoundContract` in its own `defineContract` must pass a
|
|
353
|
+
`dataTypeLookup` built from its target pack and the listed extensions, next to the
|
|
354
|
+
`codecLookup` it already passes, and accept both as optional overrides.
|
|
355
|
+
detection:
|
|
356
|
+
glob: "**/*.{ts,mts,cts}"
|
|
357
|
+
matches:
|
|
358
|
+
- '\bbuildBoundContract\b[^;]*from\s*[''"]@internal/sql-contract-ts/'
|
|
359
|
+
- id: postgres-codec-registry-takes-data-type-lookup
|
|
360
|
+
summary: |
|
|
361
|
+
The Postgres codec registry no longer carries the data types. `assemblePostgresCodecRegistry`,
|
|
362
|
+
`assemblePostgresCodecRegistryWithBuiltins`, `createPostgresCodecRegistryWithBuiltins`,
|
|
363
|
+
`createPostgresAdapterWithCodecRegistry` and the `PostgresControlAdapter` constructor take a
|
|
364
|
+
data type lookup beside the codecs, and refuse a codec whose data type it lacks.
|
|
365
|
+
detection:
|
|
366
|
+
glob: "**/*.{ts,mts,cts}"
|
|
367
|
+
matches:
|
|
368
|
+
- '\b(?:assemblePostgresCodecRegistry(?:WithBuiltins)?|createPostgresCodecRegistryWithBuiltins|createPostgresAdapterWithCodecRegistry)\s*\('
|
|
369
|
+
- '\bnew\s+PostgresControlAdapter\s*\('
|
|
370
|
+
- id: contract-to-schema-takes-components
|
|
371
|
+
summary: |
|
|
372
|
+
The migrations capability's `contractToSchema(contract, frameworkComponents)` requires
|
|
373
|
+
`frameworkComponents`; the codecs and data types in them name each column's type.
|
|
374
|
+
detection:
|
|
375
|
+
glob: "**/*.{ts,mts,cts}"
|
|
376
|
+
matches:
|
|
377
|
+
- '\bcontractToSchema\s*\(\s*[^,()]+\)'
|
|
378
|
+
- id: contract-to-schema-ir-takes-lookups
|
|
379
|
+
summary: |
|
|
380
|
+
`contractToSchemaIR` options replace `expandNativeType` with the required `dataTypeLookup`
|
|
381
|
+
and `codecLookup`.
|
|
382
|
+
detection:
|
|
383
|
+
glob: "**/*.{ts,mts,cts}"
|
|
384
|
+
matches:
|
|
385
|
+
- '\bcontractToSchemaIR\s*\('
|
|
386
|
+
- id: authoring-entity-context-takes-data-type-lookup
|
|
387
|
+
summary: |
|
|
388
|
+
`AuthoringEntityContext` gains a required `dataTypeLookup`, the stack's data types, and its
|
|
389
|
+
`codecLookup` becomes a required `CodecLookupWithDescriptors`. Code that builds a context
|
|
390
|
+
passes both. The `codecLookup` input of `interpretPslDocumentToMongoContract` becomes a required
|
|
391
|
+
`CodecLookupWithDescriptors` too.
|
|
392
|
+
detection:
|
|
393
|
+
glob: "**/*.{ts,mts,cts}"
|
|
394
|
+
matches:
|
|
395
|
+
- ':\s*AuthoringEntityContext\s*=\s*\{'
|
|
396
|
+
- id: mongo-derive-json-schema-takes-lookups
|
|
397
|
+
summary: |
|
|
398
|
+
`deriveJsonSchema` and `derivePolymorphicJsonSchema` (from `@internal/mongo-contract-psl`)
|
|
399
|
+
take a required `lookups: MongoTypeLookups` (from `@internal/mongo-contract/data-type`)
|
|
400
|
+
directly after their fields: `{ codecLookup, dataTypeLookup }`. The optional `codecLookup`
|
|
401
|
+
argument is removed; the codec lookup moves into `lookups`, and the validator reads each
|
|
402
|
+
field's BSON types from the data type its codec represents. Extensions that author Mongo
|
|
403
|
+
contracts through `defineContract(...)` or PSL need no change.
|
|
404
|
+
detection:
|
|
405
|
+
glob: "**/*.{ts,mts,cts}"
|
|
406
|
+
matches:
|
|
407
|
+
- '\bderiveJsonSchema\s*\('
|
|
408
|
+
- '\bderivePolymorphicJsonSchema\s*\('
|
|
409
|
+
- id: field-type-params-come-from-the-domain-type
|
|
410
|
+
summary: |
|
|
411
|
+
A field's type parameters now come from its domain type only. `EmissionSpi.resolveFieldTypeParams` is removed, `generateFieldOutputTypesMap` from `@internal/emitter` takes `(models, codecLookup)`, and `buildSqlContractFromDefinition` reads a field's type parameters from its descriptor. Drop the hook and the resolver argument, and give a value-object field its column's descriptor.
|
|
412
|
+
detection:
|
|
413
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
414
|
+
matches:
|
|
415
|
+
- '\bresolveFieldTypeParams\b'
|
|
416
|
+
- '\bgenerateFieldOutputTypesMap\s*\('
|
|
417
|
+
- '\bbuildSqlContractFromDefinition\s*\('
|
|
418
|
+
- id: sql-infer-psl-contract-takes-build-context
|
|
419
|
+
summary: |
|
|
420
|
+
A SQL target's `inferPslContract` hook now takes the stack's PSL build context as its second parameter, `(schema, context, describedContracts?)`, as `buildPslContract` does. A target that implements it accepts the context and reads type constructors and codecs from it. Code that called the hook on a target descriptor calls it on the SQL family instance instead, which supplies the context.
|
|
421
|
+
detection:
|
|
422
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
423
|
+
matches:
|
|
424
|
+
- '\binferPslContract\b'
|
|
425
|
+
- id: imported-postgres-field-checks-defaults
|
|
426
|
+
summary: |
|
|
427
|
+
The `field` exported by the Postgres facade's `contract-builder` entry now has the Postgres presets and checks a `.default(...)` value against the Postgres target's column types, as the `defineContract` callback's `field` does; it does not know the column types an extension adds, such as pgvector's, so it does not check those. A default of the wrong type, which compiled before and failed when the contract was built, is now a compile error; give the value the column's type.
|
|
428
|
+
detection:
|
|
429
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
430
|
+
matches:
|
|
431
|
+
- 'import\s*\{[^}]*\bfield\b[^}]*\}\s*from\s*[''"]@(?:prisma/orm-|internal/)postgres/contract-builder[''"]'
|
|
432
|
+
- id: codecs-check-stored-json
|
|
433
|
+
summary: |
|
|
434
|
+
A TypeScript `.default()` value or `enumType` member that its column's codec does not take is now refused when the contract is built, with `CONTRACT.DEFAULT_INVALID` or `CONTRACT.ENUM_INVALID`. A `contract.json` that holds such a default stops `db init`, `db update` and `migration plan` with `CONTRACT.DEFAULT_INVALID`, and a `migration.ts` that holds one fails when it runs. Correct the value the error names.
|
|
435
|
+
detection:
|
|
436
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
437
|
+
matches:
|
|
438
|
+
- '\.default\(\s*(?!now\(|sql`|autoincrement\()'
|
|
439
|
+
- '\benumType\s*\('
|
|
440
|
+
- id: psl-values-checked-by-codecs
|
|
441
|
+
summary: |
|
|
442
|
+
A PSL schema whose SQL enum member its codec does not take, or whose literal default its column's type does not hold, is now refused at `contract emit`, where it used to load. Correct the member or the default.
|
|
443
|
+
detection:
|
|
444
|
+
glob: "**/*.prisma"
|
|
445
|
+
matches:
|
|
446
|
+
- '@@type\(\s*"(?:pg|sql|sqlite)/'
|
|
447
|
+
- '^\s*\w+\s*=\s*-?\d{10,}\s*$'
|
|
448
|
+
- '@default\(\s*(?:"|-?\d|\[)'
|
|
449
|
+
- id: value-object-default-matches-composite-type
|
|
450
|
+
summary: |
|
|
451
|
+
A literal default on a field typed by a composite type must now match the composite type, with each member value read by the member's codec and each enum member value one of the enum's values, or the schema is refused. Fix the default the diagnostic names.
|
|
452
|
+
detection:
|
|
453
|
+
glob: "**/*.prisma"
|
|
454
|
+
matches:
|
|
455
|
+
- '^\s*type\s+\w+\s*\{'
|
|
456
|
+
- id: composite-type-attributes-refused
|
|
457
|
+
summary: |
|
|
458
|
+
An attribute on a composite type or on one of its members is now refused, where it used to be ignored. Remove it.
|
|
459
|
+
detection:
|
|
460
|
+
glob: "**/*.prisma"
|
|
461
|
+
matches:
|
|
462
|
+
- '^\s*type\s+\w+\s*\{'
|
|
463
|
+
- id: uuid-defaults-stored-as-postgresql-writes
|
|
464
|
+
summary: |
|
|
465
|
+
A uuid default written in upper case, in braces or without hyphens, in PSL or in a TypeScript `.default()`, is now stored as PostgreSQL writes it, so emitting the contract again changes its storage hash. Earlier versions could not apply such a contract: the command that applied it failed and changed nothing. Emit the contract again, then run that command again. With migrations, first delete the migration package that never applied.
|
|
466
|
+
detection:
|
|
467
|
+
glob: "**/*.{prisma,ts,mts,cts,tsx}"
|
|
468
|
+
matches:
|
|
469
|
+
- '\bUuid\b[^\n]*@default\(\s*"(?![0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")\{?[0-9A-Fa-f]{4}'
|
|
470
|
+
- '\b(?:uuidNative|pgUuidColumn)\s*\([^\n]*\.default\(\s*[''"`](?![0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[''"`])\{?[0-9A-Fa-f]{4}'
|
|
471
|
+
- '^\s*\.default\(\s*[''"`](?![0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[''"`])(?=[^''"`\n]*[A-F{-])\{?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}\}?[''"`]'
|
|
472
|
+
- id: domain-types-match-their-columns
|
|
473
|
+
summary: |
|
|
474
|
+
The domain half of an emitted SQL contract now carries the type parameters and enum value sets the schema declares: on fields typed by a named type, on enum list fields, and on composite type members. In `contract.d.ts`, a composite type member with type parameters now has the parameterized output type. Re-emit the contract. This change leaves the storage half, every hash and migration snapshots unchanged.
|
|
475
|
+
detection:
|
|
476
|
+
glob: "**/contract.json"
|
|
477
|
+
matches:
|
|
478
|
+
- '"typeRef"\s*:'
|
|
479
|
+
- '"valueObjects"\s*:'
|
|
480
|
+
- '"valueSet"\s*:'
|
|
481
|
+
- id: default-renderer-receives-data-type
|
|
482
|
+
summary: |
|
|
483
|
+
`DefaultRenderer` receives a third argument, `{ dataType, baseTypeName }`: the id of the data
|
|
484
|
+
type the column's codec represents, and its base name (the written name without
|
|
485
|
+
parameters). The Postgres `renderDefaultLiteral` takes `{ many, baseTypeName, dataType }`
|
|
486
|
+
instead of `{ many?, nativeType, dataTypeId? }`, and `dataType` is required.
|
|
487
|
+
detection:
|
|
488
|
+
glob: "**/*.{ts,mts,cts}"
|
|
489
|
+
matches:
|
|
490
|
+
- '\bDefaultRenderer\b'
|
|
491
|
+
- '\brenderDefaultLiteral\b'
|
|
492
|
+
- '\bDefaultColumn\b'
|
|
493
|
+
- id: ddl-column-default-visitor-removed
|
|
494
|
+
summary: |
|
|
495
|
+
`DdlColumnDefaultVisitor`, `DdlColumnRenderContext` and the `accept` method of
|
|
496
|
+
`LiteralColumnDefault` and `FunctionColumnDefault` are removed. Nothing in Prisma Next called
|
|
497
|
+
them.
|
|
498
|
+
detection:
|
|
499
|
+
glob: "**/*.{ts,mts,cts}"
|
|
500
|
+
matches:
|
|
501
|
+
- '\bDdlColumnDefaultVisitor\b'
|
|
502
|
+
- '\bDdlColumnRenderContext\b'
|
|
503
|
+
- id: ddl-nodes-hold-opaque-sql
|
|
504
|
+
summary: |
|
|
505
|
+
`FunctionColumnDefault`, `CheckExpressionConstraint`, `PostgresCreatePolicy` and `PostgresCreateIndex` hold their SQL as an `OpaqueSql` value instead of a string, and `DdlIndexElements` changed with them. Wrap the string with `opaqueSql(...)` when you construct one, read `.text` where you read the SQL, and render it with `renderOpaqueSql(...)`.
|
|
506
|
+
detection:
|
|
507
|
+
glob: "**/*.{ts,mts,cts}"
|
|
508
|
+
matches:
|
|
509
|
+
- '\bnew\s+(?:[\w$]+\.)?(FunctionColumnDefault|CheckExpressionConstraint|PostgresCreatePolicy|PostgresCreateIndex)\s*\('
|
|
510
|
+
- id: adapter-writes-column-defaults
|
|
511
|
+
summary: |
|
|
512
|
+
The control adapter writes every column's `DEFAULT …` clause, through a new required method, `renderColumnDefault(column, table)`, on `ExecuteRequestLowerer` and `SqlControlAdapter` (`family/control-adapter`). An adapter, and any fake lowerer in tests, must implement it. `buildColumnDefaultSql` is removed from `target/planner-ddl-builders`: build the column and call `renderColumnDefault`. `SetDefaultCall` (`target/op-factory-call`) takes the column, `new SetDefaultCall(schema, table, column, operationClass)`, instead of its name and `defaultSql`.
|
|
513
|
+
detection:
|
|
514
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
515
|
+
matches:
|
|
516
|
+
- '\bbuildColumnDefaultSql\b'
|
|
517
|
+
- '\bnew\s+SetDefaultCall\s*\('
|
|
518
|
+
- '(?<![.\w])lowerToExecuteRequest\s*[(:]'
|
|
519
|
+
- '\bimplements\b[^{]*\b(?:ExecuteRequestLowerer|SqlControlAdapter)\b'
|
|
520
|
+
- id: migration-ts-column-defaults
|
|
521
|
+
summary: |
|
|
522
|
+
In `migration.ts`, the adapter writes every column default, reading it with the column's codec. Postgres `setDefault` takes the column as `col(name, type, { default, codecRef })` instead of `column` (the name) and `defaultSql`. A SQLite `addColumn` or `recreateTable` column carries `default` and `codecRef` instead of `defaultSql`, and a `recreateTable` postcheck for a default is `{ description, columnDefault }`. An earlier `migration.ts` that uses `defaultSql` no longer compiles, and running it with `node migration.ts` stops with `MIGRATION.OPERATION_OPTION_REMOVED`; its `ops.json` still applies.
|
|
523
|
+
detection:
|
|
524
|
+
glob: "**/migration.ts"
|
|
525
|
+
matches:
|
|
526
|
+
- '\bdefaultSql\s*:'
|
|
527
|
+
- id: rename-check-constraint-call-is-rename-constraint-call
|
|
528
|
+
summary: Replace `RenameCheckConstraintCall` with `RenameConstraintCall`, which takes the constraint kind as a new third constructor argument; pass `'checkConstraint'` for a check constraint.
|
|
529
|
+
detection:
|
|
530
|
+
glob: "**/*.{ts,mts,cts,js,mjs,cjs}"
|
|
531
|
+
matches:
|
|
532
|
+
- '(?<![\w$])RenameCheckConstraintCall(?![\w$])'
|
|
533
|
+
- 'factoryName\s*[!=]==?\s*["'']renameCheckConstraint["'']'
|
|
534
|
+
- '["'']renameCheckConstraint["'']\s*[!=]==?\s*[\w$.?]*factoryName'
|
|
535
|
+
- 'case\s+["'']renameCheckConstraint["'']\s*:'
|
|
536
|
+
- id: control-family-instance-sign-spaces
|
|
537
|
+
summary: |
|
|
538
|
+
`ControlFamilyInstance` has a new required method, `signSpaces({ driver, spaces })`, which
|
|
539
|
+
writes the marker of every contract space it is given. `db sign` calls it once for all
|
|
540
|
+
spaces, each with the marker `db sign` read before verifying it (`expected`), and
|
|
541
|
+
writes a marker only while it still holds those hashes. `SqlControlAdapter` gains
|
|
542
|
+
`lockMarker`. `ControlFamilyInstance.sign`, `ControlClient.sign`, `SignOptions` and
|
|
543
|
+
`SignDatabaseResult` are removed.
|
|
544
|
+
detection:
|
|
545
|
+
glob: "**/*.{ts,mts,cts}"
|
|
546
|
+
matches:
|
|
547
|
+
- '\bControlFamilyInstance\s*<'
|
|
548
|
+
- '\bSignDatabaseResult\b'
|
|
549
|
+
- '\bSignOptions\b'
|
|
550
|
+
- id: db-sign-signs-extension-spaces
|
|
551
|
+
summary: |
|
|
552
|
+
`db sign` now signs every contract space, the extension's included, and only a space whose
|
|
553
|
+
schema verifies. Documentation that describes `db sign` for an extension's space says so.
|
|
554
|
+
detection:
|
|
555
|
+
glob: "**/*.md"
|
|
556
|
+
matches:
|
|
557
|
+
- '\bdb sign\b'
|
|
558
|
+
- '\bfails verify and cannot repair it\b'
|
|
559
|
+
- id: cli-error-from-caught
|
|
560
|
+
summary: |
|
|
561
|
+
`mapCaughtMigrationError` is removed from `@prisma/orm-toolchain/cli/control-api` (`@internal/cli/control-api`). Use `errorFromCaught(error, why)`, which always returns an error: a CLI error as it is, any error with a structured code as itself, and anything else as `CLI.UNEXPECTED` with `why(message)`. It throws an `InternalError` again.
|
|
562
|
+
detection:
|
|
563
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
564
|
+
matches:
|
|
565
|
+
- '\bmapCaughtMigrationError\b'
|
|
566
|
+
- id: adapter-control-loads-temporal-polyfill
|
|
567
|
+
summary: |
|
|
568
|
+
The adapter's control entry, `adapter/control` of `@prisma/orm-postgres` and `@prisma/orm-target-postgres`, now loads `temporal-polyfill` too, as the target's control entry does since `temporal-polyfill-is-a-peer-dependency`. A Yarn project that added the polyfill for that change needs nothing more. An extension package that installs with Yarn and loads only the adapter's control entry in its tests or tooling must add `temporal-polyfill` (`^1.0.4`) to its `devDependencies`.
|
|
569
|
+
detection:
|
|
570
|
+
glob: "**/*.{ts,mts,cts,tsx,js,mjs,cjs}"
|
|
571
|
+
matches:
|
|
572
|
+
- '[''"]@prisma/orm-(?:target-)?postgres/adapter/control[''"]'
|
|
573
|
+
- id: parameter-casts-use-base-names
|
|
574
|
+
summary: |
|
|
575
|
+
PostgreSQL parameter casts are written with the data type's base name: `$1::int4` instead of
|
|
576
|
+
`$1::integer`, and likewise `int2`, `int8`, `float4`, `float8` and `bool` instead of
|
|
577
|
+
`smallint`, `bigint`, `real`, `double precision` and `boolean`. Tests that assert query text
|
|
578
|
+
change to match.
|
|
579
|
+
detection:
|
|
580
|
+
glob: "**/*.{ts,mts,cts,sql,json,snap}"
|
|
581
|
+
matches:
|
|
582
|
+
- '::(?:integer|smallint|bigint|real|double precision|boolean)\b'
|
|
583
|
+
- id: sql-builder-reads-codec-descriptors
|
|
584
|
+
summary: The sql-builder lane now reads `codecDescriptors` from the `ExecutionContext` passed to `sql()`. A hand-built test context must provide it.
|
|
585
|
+
detection:
|
|
586
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
587
|
+
matches:
|
|
588
|
+
- 'as unknown as ExecutionContext'
|
|
589
|
+
- id: select-ast-options-carry-locking
|
|
590
|
+
summary: "SelectAstOptions has a new required key, locking; when rebuilding a select from an existing SelectAst, carry ast.locking so a row lock is not dropped."
|
|
591
|
+
detection:
|
|
592
|
+
glob: "**/*.ts"
|
|
593
|
+
contains:
|
|
594
|
+
- "new SelectAst("
|
|
595
|
+
- id: render-lowered-sql-takes-capabilities
|
|
596
|
+
summary: "renderLoweredSql from @internal/adapter-postgres/sql-renderer takes two new required arguments after the codec descriptor registry: the data type lookup, and the capability matrix to check locking clauses against; pass postgresAdapterCapabilities from @internal/adapter-postgres/adapter."
|
|
597
|
+
detection:
|
|
598
|
+
glob: "**/*.ts"
|
|
599
|
+
contains:
|
|
600
|
+
- "renderLoweredSql("
|
|
601
|
+
- id: sql-runtime-close-refusal-option
|
|
602
|
+
summary: "The options of SqlRuntimeBase and its subclasses (RuntimeOptions, for example new PostgresRuntimeImpl({ ... })) have a new optional key closeRefusal: 'when-idle' | 'at-once'. Pass 'at-once' for a runtime that many callers share; leaving it out means 'when-idle', which suits a runtime that one request owns."
|
|
603
|
+
detection:
|
|
604
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
605
|
+
matches:
|
|
606
|
+
- 'new \w*RuntimeImpl\('
|
|
607
|
+
- 'RuntimeOptions'
|
|
608
|
+
- id: writes-on-a-conditional-collection-are-refused
|
|
609
|
+
summary: |
|
|
610
|
+
A write (`update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`) on a collection that is filtered on some code paths and not on others no longer compiles. A pattern cannot tell which collections those are: act only where the compiler reports "The 'this' context of type '...' is not assignable to method's 'this' of type 'HasWhere'". Filter on every path, or make the write only where the filter was applied.
|
|
611
|
+
detection:
|
|
612
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
613
|
+
matches:
|
|
614
|
+
- '\.(?:updateAll|updateAndCount|deleteAll|deleteAndCount)\s*\('
|
|
615
|
+
- '\.update\s*\('
|
|
616
|
+
- '\.delete\s*\(\s*(?:\)|\()'
|
|
617
|
+
- id: writes-refuse-what-they-would-ignore
|
|
618
|
+
summary: |
|
|
619
|
+
`updateAll`, `updateAndCount`, `deleteAll` and `deleteAndCount` now throw `ORM.ARGUMENT_INVALID` on a collection that has a `limit`, an `offset`, a `cursor`, `distinct` or `distinctOn`. These writes change every row that matches the filter; their statement cannot apply any of these, so they were ignored and more rows changed than the chain asked for. Remove them before the write, or read the rows first and change them by their ids. `update` with a relation callback now throws on a collection with an order, a limit, an offset, a cursor, `distinct` or `distinctOn`, which it ignored; filter it to the one row instead. `update` and `delete` without a relation callback change the row `first()` returns, as before, except after `limit(0)`: they changed one row and now change none and return `null`.
|
|
620
|
+
detection:
|
|
621
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
622
|
+
matches:
|
|
623
|
+
- '\.(?:limit|offset|cursor|distinct|distinctOn)\s*\([^)]*\)[\s\S]{0,300}?\.(?:update|updateAll|updateAndCount|delete|deleteAll|deleteAndCount)\s*\('
|
|
624
|
+
- '\.orderBy\s*\([\s\S]{0,300}?\.update\s*\('
|
|
625
|
+
- id: cursor-and-distinct-on-check-the-receiver
|
|
626
|
+
summary: |
|
|
627
|
+
`cursor` and `distinctOn` now require an order on the collection they are called on, checked on the receiver. A cast on the argument, such as `cursor({ id } as never)`, no longer bypasses the check; add the `orderBy`, or cast the collection to `Ordered<C>` where the query is meant to have no order.
|
|
628
|
+
detection:
|
|
629
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
630
|
+
matches:
|
|
631
|
+
- '\.(?:cursor|distinctOn)\s*\([^)]*\bas\s+never\b'
|
|
632
|
+
- id: apply-is-a-collection-member
|
|
633
|
+
summary: |
|
|
634
|
+
Every collection now has an `apply` method. A custom collection class that declares its own `apply` member with another signature no longer compiles; rename it.
|
|
635
|
+
detection:
|
|
636
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
637
|
+
matches:
|
|
638
|
+
- '(?:^|\n)[ \t]*(?:(?:public|protected|private|readonly|static|async|override)\s+)*apply\s*[(<:=?]'
|
|
639
|
+
- id: scope-is-a-collection-member
|
|
640
|
+
summary: |
|
|
641
|
+
Every collection now has a `scope` method. A custom collection class that declares its own `scope` member with another signature no longer compiles; rename it.
|
|
642
|
+
detection:
|
|
643
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
644
|
+
matches:
|
|
645
|
+
- '(?:^|\n)[ \t]*(?:(?:public|protected|private|readonly|static|async|override|get|set)\s+)*scope\s*[!(<:=?]'
|
|
646
|
+
- id: overriding-a-chaining-method
|
|
647
|
+
summary: |
|
|
648
|
+
In a class that extends `Collection`, an override of a chaining method (`where`, `orderBy`, `limit`, `offset`, `distinct`, `distinctOn`, `cursor`, `include`) or of a method that returns rows (`all`, `first`, `create`, `createAll`, `upsert`, `update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`) must use the new signature, which takes a `this` parameter.
|
|
649
|
+
detection:
|
|
650
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
651
|
+
matches:
|
|
652
|
+
- '(?:^|\n)[ \t]*(?:(?:public|protected|override|async)\s+)*(?:where|orderBy|limit|offset|distinct|distinctOn|cursor|include|all|first|create|createAll|upsert|update|updateAll|updateAndCount|delete|deleteAll|deleteAndCount)\s*[(<]'
|
|
653
|
+
- id: collection-state-flags-are-boolean
|
|
654
|
+
summary: |
|
|
655
|
+
In `DefaultCollectionTypeState`, `hasWhere`, `hasOrderBy` and `hasUniqueFilter` are `boolean` (not known) instead of `false`. Code that expects `false` on a collection with no filter or order must expect `boolean`.
|
|
656
|
+
detection:
|
|
657
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
658
|
+
matches:
|
|
659
|
+
- '\bhas(?:Where|OrderBy|UniqueFilter)\b[''"]?\]?\s*,\s*false\b'
|
|
660
|
+
- '\bhas(?:Where|OrderBy|UniqueFilter)\s*:\s*false\b'
|
|
661
|
+
- id: read-collection-state-and-row-with-helpers
|
|
662
|
+
summary: |
|
|
663
|
+
A collection's type state and row are read with `CollectionTypeStateOf<C>` and `CollectionRowOf<C>`, not by inferring the type arguments of `Collection`. The type arguments keep what the collection started with.
|
|
664
|
+
detection:
|
|
665
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
666
|
+
matches:
|
|
667
|
+
- '\bCollection<[^;]*?\binfer\b'
|
|
668
|
+
- id: return-type-of-a-chaining-method
|
|
669
|
+
summary: |
|
|
670
|
+
`ReturnType` of `where`, `orderBy`, `limit`, `offset`, `distinct`, `distinctOn`, `cursor` or `include` no longer gives a collection. Write `Filtered<C>` after `where`, `Ordered<C>` after `orderBy`, and `C` after the others, or take `typeof` of a value.
|
|
671
|
+
detection:
|
|
672
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
673
|
+
matches:
|
|
674
|
+
- '\bReturnType<[^>]*\[[''"](?:where|orderBy|limit|offset|distinct|distinctOn|cursor|include)[''"]\]'
|
|
675
|
+
- '\bReturnType<\s*typeof\s+[\w$.]+\.(?:where|orderBy|limit|offset|distinct|distinctOn|cursor|include)\b'
|
|
676
|
+
- id: chaining-methods-take-no-explicit-type-arguments
|
|
677
|
+
summary: |
|
|
678
|
+
`include`, `distinct` and `distinctOn` with explicit type arguments no longer compile: `posts.include<'user'>('user')` and `posts.distinct<['title']>('title')` fail, and `ReturnType<typeof posts.include<'user'>>` is `never`. Drop the type arguments; they are inferred from the arguments, so `posts.distinct('title')` needs none.
|
|
679
|
+
detection:
|
|
680
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
681
|
+
matches:
|
|
682
|
+
- '\.(?:include|distinct|distinctOn)<'
|
|
683
|
+
- id: custom-collection-methods-chain
|
|
684
|
+
summary: |
|
|
685
|
+
Optional. Custom collection methods now stay available after the built-in chaining methods. Where code repeats a class method's body inline after a chaining call, it can call the method instead.
|
|
686
|
+
detection:
|
|
687
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
688
|
+
matches:
|
|
689
|
+
- '\bextends\s+Collection<'
|
|
690
|
+
- id: variant-takes-discriminator-value
|
|
691
|
+
summary: |
|
|
692
|
+
`.variant()` on a polymorphic SQL or Mongo ORM collection takes the discriminator value a variant declares instead of the variant's model name: `db.orm.public.Task.variant('bug')` for `@@base(Task, "bug")`, where it used to be `.variant('Bug')`. A value the model does not declare, or a call on a model with no discriminator, now throws `ORM.ARGUMENT_INVALID` instead of returning the collection unchanged. `.variant()` on a collection that already has a variant selected is now rejected: select the variant from the base collection.
|
|
693
|
+
detection:
|
|
694
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
695
|
+
matches:
|
|
696
|
+
- '\.variant\('
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
# 8.0.0-rc.14 → 8.0.0-rc.15 — Extension author upgrade instructions
|
|
700
|
+
|
|
701
|
+
Apply the sections in this order:
|
|
702
|
+
|
|
703
|
+
1. Run the script of `contract-stores-data-type` first, before any step of this release that re-emits a contract. It rewrites only contracts in the old format.
|
|
704
|
+
2. Make the code changes in the sections that follow.
|
|
705
|
+
3. Before you re-emit, sign each database the extension's tests or tooling keep with `prisma db sign`, as `contract-stores-data-type` describes: `db sign` must see the contract the database was created from before a re-emit changes it again.
|
|
706
|
+
4. Re-emit the extension's contracts and test fixtures with `prisma contract emit` (`build:contract-space`) once, after the code changes. Several sections ask for it: `reemit-extension-list-contracts`, `consume-nested-list-cardinality`, `domain-types-match-their-columns` and `uuid-defaults-stored-as-postgresql-writes`. Where the re-emit changes the schema, plan and apply a migration as those sections say.
|
|
707
|
+
|
|
708
|
+
After the code changes, delete imports and constants that are no longer used, and run the package's formatter so imports are sorted.
|
|
709
|
+
|
|
710
|
+
## `contract-stores-data-type`
|
|
711
|
+
|
|
712
|
+
Do this before any other step of this release that re-emits a contract: the script changes only files in the old format, and `prisma db sign` must see the contract the database was created from before a re-emit changes it again. Commit your work first, so the script's changes can be reviewed and undone with git. The script follows links to files and directories, also outside the root, and rewrites the files there; commit or back up those too. Then run the script that sits next to this guide from the extension package's root. `<skill>` is the directory of the synced `prisma-8` skill:
|
|
713
|
+
|
|
714
|
+
```sh
|
|
715
|
+
node <skill>/upgrading/extension/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/scripts/data-type-in-contract/data-type-in-contract.ts
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
Node 24 or later runs the TypeScript script directly; it needs no `tsx`. It reads and writes files only and needs no database. It rewrites every `*.json` file under the root that parses as a SQL contract in the old format (a column or `storage.types` entry that stores `nativeType`), skipping `node_modules`, `.git`, `dist` and `build`. That includes a test fixture of an old-format contract: if you keep such a fixture on purpose, restore it with git afterwards (`git restore <file>`), or keep it outside the project root. In each contract it rewrites, it also writes lists in this release's new form (`consume-nested-list-cardinality`): a list column or field stored as `many: true` becomes `many: { elementNullable: false }`, and `contract.d.ts` declares `many` on every column. This is the SQL part of `reemit-extension-list-contracts`.
|
|
719
|
+
|
|
720
|
+
On PostgreSQL, a list column of an enum keeps the membership check its database has, because the database still holds that check. Run `prisma db sign` before you emit the contract again. The next `prisma contract emit` writes the check as `array_remove(…) <@ ARRAY[…]` under a new name; `prisma migration plan` then writes a migration that drops the old check and adds the new one, and `prisma db migrate` applies it. The plan warns that dropping the old check may lose data; it does not, because only the check changes, not the column. If you already emitted before signing, `db sign` reports the new check as missing: sign each database with the storage hash the script printed instead (`prisma db sign <new hash>`), then plan and migrate as above.
|
|
721
|
+
|
|
722
|
+
If the script stops on an error, for example on a full disk, it prints the error and `the upgrade stopped partway, run the script again to finish it`, and exits 1. After an error, Ctrl-C or a crash, run it again: it finishes the upgrade. A file the script was writing at that moment is either unchanged or complete, and it removes its own temporary files (ending in `.data-type-in-contract-tmp`) on the next run.
|
|
723
|
+
|
|
724
|
+
Run your formatter afterwards. The script replaces text in `migration.ts` and `contract.d.ts`, so the import order in `migration.ts` and the line wrapping in `contract.d.ts` can differ from what a fresh emit and your formatter produce.
|
|
725
|
+
|
|
726
|
+
When it finishes, it prints how many files it rewrote and how many snapshot directories it renamed, and each storage hash it replaced (`<old> -> <new>`). A contract already in the new format is never changed, even when its stored hash does not match its content, so a project already in the new format is left unchanged, and the script says that nothing changed. If it finds no SQL contract under the root, it says so; run it again from the project root. It prints `<file>: stored hash did not recompute; rehashed from content` for an old-format contract whose stored storage hash does not match its content, and rewrites it anyway. It changes no file and exits 1 when a column uses a codec it does not know (`<file>: unknown codec <id>; name its data type with --data-type <id>=<data type id>`) or when a renamed snapshot directory already exists with different content.
|
|
727
|
+
|
|
728
|
+
The script knows every codec that Prisma and its own extensions ship. For each codec your extension owns, run the script with `--data-type <codec id>=<data type id>`, naming the data type that codec represents, for example `--data-type acme/shape@1=acme/shape`. Publish those lines in your release notes: your users pass the same options when they run the script on their projects. The option cannot change the data type of a codec the script already knows for a contract's target, but it can name the data type of a shared `sql/*` codec on a target the script does not know.
|
|
729
|
+
|
|
730
|
+
Release the extension with the rewritten contract space against the framework version that contains this change, and raise the extension's peer dependency floor on the framework to that version. The two versions cannot be mixed: the old framework refuses a contract space that stores `dataType`, and the new framework refuses one that still stores `nativeType`. So a release published early cannot be installed before the framework upgrade. Tell your users to upgrade the framework and your extension in the same step.
|
|
731
|
+
|
|
732
|
+
## `reemit-extension-list-contracts`
|
|
733
|
+
|
|
734
|
+
Regenerate bundled `contract.json` and `contract.d.ts` together from the extension's original authoring source and configuration, including each owned contract space. Use the existing contract-space emission command rather than hand-editing generated fields or hashes. For the Supabase extension in this repository, `pnpm --filter @internal/extension-supabase build:contract-space` regenerates `src/contract/contract.json` and `src/contract/contract.d.ts`; its separate `emit` script targets test fixtures, not this bundled contract.
|
|
735
|
+
|
|
736
|
+
Preserve existing strict-element declarations and explicit `elementNotNull` waivers. In particular, a Supabase native-array column with `noCheck: ['elementNotNull']` remains `many: { elementNullable: false }`, not a nullable-element list. Scalar domain JSON continues to omit `many: false`; list descriptors change, and generated contract hashes may change. Retaining old hash literals is not a valid regeneration. Scalar domain JSON and declarations omit `many: false`; SQL storage-column declarations retain required cardinality.
|
|
737
|
+
|
|
738
|
+
For SQL contract spaces, the script of `contract-stores-data-type` already writes the list form and refreshes hashes and snapshot names, so historical SQL snapshots need no re-emission; MongoDB contract spaces still do.
|
|
739
|
+
|
|
740
|
+
For extension-owned MongoDB migration snapshots, regenerate each historical state from its own source, not the latest extension schema. Write the JSON/declaration pair through the snapshot store and regenerate dependent migration metadata in dependency order. If a snapshot hash changes, preserve the old entry while it is referenced, create the new content-addressed entry, and update both JSON/type imports, start/end contract hashes, and derived migration identifiers and parent references consistently. Preserve recorded operations unless intentionally changing the schema. Do not blanket-rename hash paths, drop migration references, or rewrite applied history. Coordinate already-applied migration identities and database contract markers through the consumer project's supported migration procedure and verify the transition on a disposable database. Stop if historical sources or the reference mapping cannot be recovered.
|
|
741
|
+
|
|
742
|
+
## `consume-nested-list-cardinality`
|
|
743
|
+
|
|
744
|
+
`ContractField.many` and `StorageColumn.many` are now `false | { elementNullable: boolean }`. `ContractField.many` is optional: omission and `false` both mean scalar. `StorageColumn.many` remains required, with `false` for non-array storage columns. Serialized model and value-object fields may omit `many` for scalar cardinality; deserialization normalizes omission to `many: false`, and canonical emission omits that scalar default again. Replace legacy `many: true` with `many: { elementNullable: false }` for existing strict-element lists, preserving nested `elementNullable: false` during emission. Move any intermediate sibling `elementNullable` property into the list descriptor and remove the sibling. Explicit malformed `many` values, including the old `true` form and descriptors without a boolean `elementNullable`, remain rejected, as does the sibling property.
|
|
745
|
+
|
|
746
|
+
For domain fields, use `if (field.many)` to detect and narrow a list descriptor before reading `elementNullable`; omitted `many` is scalar. For normalized storage columns, replace `column.many === true` with `column.many !== false`. Update conditional types that matched `{ many: true }` to match the descriptor. Do not broaden this replacement to unrelated booleans or relation cardinality. For example, the included-column decoder in an ORM extension must replace `ref.storageColumn.many === true` with `ref.storageColumn.many !== false` so native arrays still take the element-decoding path.
|
|
747
|
+
|
|
748
|
+
Preserve the independent meaning of `nullable` (the whole field value) and `many.elementNullable` (each element). Type generators must produce `ReadonlyArray<T | null>` for nullable elements and add an outer `| null` only for a nullable container. Array encoders and decoders must preserve a whole-list `null` without traversal and bypass the element codec for null elements, while processing non-null elements normally. Do not pass null elements into scalar codecs or collapse null lists into empty arrays.
|
|
749
|
+
|
|
750
|
+
For native SQL arrays, propagate element semantics to both the domain field and storage column. For JSON-backed value-object lists, retain the domain descriptor but do not add a native-array descriptor to storage. Do not add `elementNullable` to schema IR: PostgreSQL migration comparison observes the derived check constraints. Generate `elementNotNull` only for strict-element arrays, then apply explicit `noCheck` waivers. Never infer element nullability from `noCheck`, and reject an `elementNotNull` waiver on a nullable-element list as inapplicable. MongoDB array-item validators must admit null only when the domain descriptor permits it, including enum membership and value-object items.
|
|
751
|
+
|
|
752
|
+
If exposing list authoring, use `.many({ elementsNullable: true })` to request nullable elements; the option uses plural `elementsNullable`, whereas the emitted descriptor uses singular `elementNullable`. Omitted or literal `false` retains strict elements. Keep whole-list `.nullable()` independent. PSL equivalents are `T[]`, `T?[]`, `T[]?`, and `T?[]?`; this change does not enable nullable relation-list elements or scalar lists on SQLite.
|
|
753
|
+
|
|
754
|
+
## `sql-data-type-declares-names`
|
|
755
|
+
|
|
756
|
+
Declare each SQL data type the extension owns with `sqlDataType`, and list the texts the database writes and reports for it. `@internal/sql-contract/data-type` is a new dependency of the package.
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
// before
|
|
760
|
+
import { type DataType, dataType } from '@internal/framework-components/codec';
|
|
761
|
+
|
|
762
|
+
export const pgvectorVector: DataType = dataType('pgvector/vector', {
|
|
763
|
+
listCast: {
|
|
764
|
+
of: [pgInt2.id, pgInt4.id, pgInt8.id, pgNumeric.id],
|
|
765
|
+
cast: (elements) => elements.map(elementNumber),
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// after
|
|
770
|
+
import type { DataType } from '@internal/framework-components/codec';
|
|
771
|
+
import { sqlDataType } from '@internal/sql-contract/data-type';
|
|
772
|
+
import { type as arktype } from 'arktype';
|
|
773
|
+
import { VECTOR_MAX_DIM } from './constants';
|
|
774
|
+
|
|
775
|
+
export const pgvectorVectorParams = arktype({
|
|
776
|
+
length: `number.integer >= 1 & number.integer <= ${VECTOR_MAX_DIM}` as const,
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
export const pgvectorVector = sqlDataType('pgvector/vector', {
|
|
780
|
+
params: pgvectorVectorParams,
|
|
781
|
+
texts: [{ text: 'vector({length})', written: true, catalog: true }],
|
|
782
|
+
listCast: {
|
|
783
|
+
of: [pgInt2.id, pgInt4.id, pgInt8.id, pgNumeric.id],
|
|
784
|
+
cast: (elements) => elements.map(elementNumber),
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Declare `params` as a named export directly above the data type, so the codec can reference it. Each text is lower case with single spaces, and `{name}` stands for the parameter `name`. Mark the text a migration writes `written: true`, and the text the database catalog prints (`format_type` on PostgreSQL) `catalog: true`. Among texts with the same placeholders, at most one is written and one is catalog. Add `normalize` when two parameter sets name the same database type. Keep `casts` and `listCast` as they are. Drop the explicit `: DataType` annotation on the declaration so the parameter type is kept; the `dataTypes` list stays `readonly DataType[]`.
|
|
790
|
+
|
|
791
|
+
A type written with and without parameters lists a text for each, and `display` gives the letter case the database writes. The PostGIS geometry type:
|
|
792
|
+
|
|
793
|
+
```ts
|
|
794
|
+
export const postgisGeometryParams = arktype({ 'srid?': 'number.integer >= 1' });
|
|
795
|
+
|
|
796
|
+
export const postgisGeometry = sqlDataType('postgis/geometry', {
|
|
797
|
+
params: postgisGeometryParams,
|
|
798
|
+
texts: [
|
|
799
|
+
{ text: 'geometry', written: true, catalog: true },
|
|
800
|
+
{
|
|
801
|
+
text: 'geometry(geometry,{srid})',
|
|
802
|
+
written: true,
|
|
803
|
+
catalog: true,
|
|
804
|
+
display: 'geometry(Geometry,{srid})',
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
casts: { [pgText.id]: (value) => value },
|
|
808
|
+
});
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
The texts must reproduce what the removed hooks wrote: the written text for the column's parameters must equal the old `expandNativeType` result, or migration SQL changes. The bounds are the type's real ones: PostgreSQL refuses an SRID below 1 in a type modifier.
|
|
812
|
+
|
|
813
|
+
The section "Declaring a data type" of the Prisma 8 codec authoring guide (`docs/reference/codec-authoring-guide.md` in the Prisma repository) describes every field.
|
|
814
|
+
|
|
815
|
+
## `codec-target-types-removed`
|
|
816
|
+
|
|
817
|
+
Delete `targetTypes` from every codec descriptor and template:
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
// before
|
|
821
|
+
override readonly targetTypes = ['vector'] as const;
|
|
822
|
+
|
|
823
|
+
// after: the line is gone; the names are the data type's texts
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
Code that read `codecLookup.targetTypesFor(codecId)` or `registry.byTargetType(name)` reads the data type instead: `dataTypeLookup.get(codecLookup.descriptorFor(codecId).dataType)`. For a SQL type, call `sqlBaseName(type, dataTypeParams(type, typeParams))` or `renderSqlTypeName(...)` from `@internal/sql-contract/data-type`. For a Mongo codec, move the list to the data type the codec names and read it with `bsonTypesOfCodec(codecId, { codecLookup, dataTypeLookup })` from `@internal/mongo-contract/data-type`; the declared type keeps the list at `type.mongo.bsonTypes`:
|
|
827
|
+
|
|
828
|
+
```ts
|
|
829
|
+
export const myDecimal = mongoDataType('my/decimal', { bsonTypes: ['decimal'] });
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
## `native-type-rendering-hooks-removed`
|
|
833
|
+
|
|
834
|
+
Delete the `nativeType` override from each `PostgresCodecDescriptor` subclass, with the constant it returned when nothing else uses it. Delete `expandNativeType` from each entry of `controlPlaneHooks`, and keep the other hooks, such as `resolveIdentityValue`.
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
// before
|
|
838
|
+
const PG_VECTOR_NATIVE_TYPE = 'vector';
|
|
839
|
+
|
|
840
|
+
export class PgVectorDescriptor extends PostgresCodecDescriptor<VectorParams> {
|
|
841
|
+
protected override nativeType(): string {
|
|
842
|
+
return PG_VECTOR_NATIVE_TYPE;
|
|
843
|
+
}
|
|
844
|
+
// …
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const vectorControlPlaneHooks: CodecControlHooks = {
|
|
848
|
+
expandNativeType: ({ nativeType, typeParams }) => {
|
|
849
|
+
// …
|
|
850
|
+
},
|
|
851
|
+
resolveIdentityValue: ({ typeParams }) => buildVectorIdentityValue(typeParams),
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// after
|
|
855
|
+
export class PgVectorDescriptor extends PostgresCodecDescriptor<VectorParams> {
|
|
856
|
+
// …
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const vectorControlPlaneHooks: CodecControlHooks = {
|
|
860
|
+
resolveIdentityValue: ({ typeParams }) => buildVectorIdentityValue(typeParams),
|
|
861
|
+
};
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
When a hooks object held only `expandNativeType`, delete the object and the `types` override that registered it:
|
|
865
|
+
|
|
866
|
+
```ts
|
|
867
|
+
// before
|
|
868
|
+
const arktypeJsonControlPlaneHooks: CodecControlHooks = {
|
|
869
|
+
expandNativeType: ({ nativeType }) => nativeType,
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
export const arktypeJsonExtensionDescriptor: SqlControlExtensionDescriptor<'postgres'> = {
|
|
873
|
+
...arktypeJsonPackMeta,
|
|
874
|
+
types: {
|
|
875
|
+
...arktypeJsonPackMeta.types,
|
|
876
|
+
codecTypes: {
|
|
877
|
+
...arktypeJsonPackMeta.types.codecTypes,
|
|
878
|
+
controlPlaneHooks: {
|
|
879
|
+
[ARKTYPE_JSON_CODEC_ID]: arktypeJsonControlPlaneHooks,
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
create: () => ({ /* … */ }),
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
// after
|
|
887
|
+
export const arktypeJsonExtensionDescriptor: SqlControlExtensionDescriptor<'postgres'> = {
|
|
888
|
+
...arktypeJsonPackMeta,
|
|
889
|
+
create: () => ({ /* … */ }),
|
|
890
|
+
};
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
A caller of `descriptor.nativeTypeFor(ref)` uses `sqlBaseName` of the codec's data type. A custom control adapter drops `normalizeNativeType`.
|
|
894
|
+
|
|
895
|
+
## `comments-name-removed-apis`
|
|
896
|
+
|
|
897
|
+
In comments, delete each sentence that names `expandNativeType`, `targetTypes` or the `nativeType()` hook, and nothing else. When the deletion empties a paragraph, a list item or a whole comment, delete it with the blank comment line before it. Leave the rest of the comment as it is.
|
|
898
|
+
|
|
899
|
+
```ts
|
|
900
|
+
// before
|
|
901
|
+
/**
|
|
902
|
+
* Per-codec column helper for `pg/vector@1`. Generic over `N extends number` so the column site preserves the dimension literal in `typeParams` (e.g. `pgVectorColumn(1536)` packs `typeParams: { length: 1536 }`).
|
|
903
|
+
*
|
|
904
|
+
* Passes the bare `nativeType: 'vector'`; the family-layer `expandNativeType` hook renders the parameterized form (`vector(1536)`) at emit/verify time from `nativeType` + `typeParams`.
|
|
905
|
+
*/
|
|
906
|
+
|
|
907
|
+
// after
|
|
908
|
+
/**
|
|
909
|
+
* Per-codec column helper for `pg/vector@1`. Generic over `N extends number` so the column site preserves the dimension literal in `typeParams` (e.g. `pgVectorColumn(1536)` packs `typeParams: { length: 1536 }`).
|
|
910
|
+
*/
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
A codec module's summary comment that lists what the descriptor carries names the data type and its params schema in place of target types and a native type, and says the data type declares the type's name and the bounds of its parameters. A column helper no longer passes a bare `nativeType`, so that clause goes too. Write each rewritten list item on one line. The pgvector and PostGIS summaries:
|
|
914
|
+
|
|
915
|
+
```ts
|
|
916
|
+
// before
|
|
917
|
+
* 2. `PgVectorDescriptor` extends {@link PostgresCodecDescriptor} with the codec id, traits, target types, params schema (`{ length: number }`, validated against {@link VECTOR_MAX_DIM}), the postgres native type `vector`, explicit target behavior, and the emit-path `renderOutputType` producing `Vector<${length}>`.
|
|
918
|
+
* 3. `pgVectorColumn(length)` per-codec column helper invoking `descriptor.factory({ length })` directly + passing the bare `nativeType: 'vector'`. The family-layer {@link expandNativeType} hook renders the parameterized form (`vector(1536)`) at emit/verify time from `nativeType` + `typeParams`.
|
|
919
|
+
|
|
920
|
+
// after
|
|
921
|
+
* 2. `PgVectorDescriptor` extends {@link PostgresCodecDescriptor} with the codec id, traits, the `pgvector/vector` data type and its params schema, explicit target behavior, and the emit-path `renderOutputType` producing `Vector<${length}>`. The data type declares the type's name and the bounds of `length`.
|
|
922
|
+
* 3. `pgVectorColumn(length)` per-codec column helper invoking `descriptor.factory({ length })` directly.
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
```ts
|
|
926
|
+
// before
|
|
927
|
+
* 2. `PostgisGeometryDescriptor` extends {@link PostgresCodecDescriptor}
|
|
928
|
+
* with the codec id, traits, target types, params schema
|
|
929
|
+
* (`{ srid?: number }`, preserving unparameterized geometry while validating supplied SRIDs), explicit target behavior, and
|
|
930
|
+
* the emit-path `renderOutputType` producing `Geometry<${srid}>` /
|
|
931
|
+
* `Geometry` when no SRID is supplied.
|
|
932
|
+
* 3. `pgGeometryColumn({ srid })` per-codec column helper invoking
|
|
933
|
+
* `descriptor.factory({ srid })` and passing the bare
|
|
934
|
+
* `nativeType: 'geometry'`. The family-layer `expandNativeType`
|
|
935
|
+
* hook renders the parameterised form
|
|
936
|
+
* (`geometry(Geometry,${srid})`) at emit/verify time from
|
|
937
|
+
* `nativeType` + `typeParams`.
|
|
938
|
+
|
|
939
|
+
// after
|
|
940
|
+
* 2. `PostgisGeometryDescriptor` extends {@link PostgresCodecDescriptor} with the codec id, traits, the `postgis/geometry` data type and its params schema (`{ srid?: number }`), explicit target behavior, and the emit-path `renderOutputType` producing `Geometry<${srid}>` / `Geometry` when no SRID is supplied. The data type declares the type's name and the bound of `srid`.
|
|
941
|
+
* 3. `pgGeometryColumn({ srid })` per-codec column helper invoking `descriptor.factory({ srid })`.
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
## `postgres-codec-takes-data-type`
|
|
945
|
+
|
|
946
|
+
```ts
|
|
947
|
+
// before
|
|
948
|
+
const postgresSqlTextDescriptor = postgresCodec(sqlTextDescriptor, {
|
|
949
|
+
dataType: pgText.id,
|
|
950
|
+
nativeType: () => 'text',
|
|
951
|
+
jsonProjection: (expression) => expression,
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
// after
|
|
955
|
+
const postgresSqlTextDescriptor = postgresCodec(sqlTextDescriptor, {
|
|
956
|
+
dataType: pgText,
|
|
957
|
+
jsonProjection: (expression) => expression,
|
|
958
|
+
});
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Import the data type object if only its id was imported. `sqliteCodec` changes the same way, without a `nativeType` option to remove.
|
|
962
|
+
|
|
963
|
+
## `codec-params-schema-is-data-type-params`
|
|
964
|
+
|
|
965
|
+
Delete the codec's own parameter schema, which the data type's `params` now holds (see `sql-data-type-declares-names`), and point `paramsSchema` at the data type's:
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
// before, in codecs.ts
|
|
969
|
+
import { pgvectorVector } from './data-types';
|
|
970
|
+
|
|
971
|
+
const vectorParamsSchema = arktype({
|
|
972
|
+
length: 'number',
|
|
973
|
+
}).narrow((params, ctx) => {
|
|
974
|
+
// … integer and range checks …
|
|
975
|
+
}) satisfies StandardSchemaV1<VectorParams>;
|
|
976
|
+
|
|
977
|
+
override readonly paramsSchema: StandardSchemaV1<VectorParams> = vectorParamsSchema;
|
|
978
|
+
|
|
979
|
+
// after
|
|
980
|
+
import { pgvectorVector, pgvectorVectorParams } from './data-types';
|
|
981
|
+
|
|
982
|
+
override readonly paramsSchema: StandardSchemaV1<VectorParams> = pgvectorVectorParams;
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
A codec with keys of its own sets `paramsSchema` to `dataType.params.and(ownKeys)`. A codec whose data type has no `params` keeps a schema of its own keys only; `arktype/json@1` is such a codec and changes nothing here.
|
|
986
|
+
|
|
987
|
+
## `column-helper-checks-data-type-params`
|
|
988
|
+
|
|
989
|
+
Delete the helper's own range check and the `@throws` tag that describes it, with the blank comment line directly before the tag when there is one. The helper returns its descriptor unchecked:
|
|
990
|
+
|
|
991
|
+
```ts
|
|
992
|
+
// before
|
|
993
|
+
import { VECTOR_CODEC_ID, VECTOR_MAX_DIM } from '../core/constants';
|
|
994
|
+
import { pgVectorError } from '../core/errors';
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* …
|
|
998
|
+
* @returns A column type descriptor with `typeParams.length` set
|
|
999
|
+
* @throws `CONTRACT.ARGUMENT_INVALID` if length is not an integer in the range [1, VECTOR_MAX_DIM]
|
|
1000
|
+
*/
|
|
1001
|
+
export function vector<N extends number>(length: N) /* : … */ {
|
|
1002
|
+
if (!Number.isInteger(length) || length < 1 || length > VECTOR_MAX_DIM) {
|
|
1003
|
+
throw pgVectorError('CONTRACT.ARGUMENT_INVALID', /* … */);
|
|
1004
|
+
}
|
|
1005
|
+
return { /* … */ } as const;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// after
|
|
1009
|
+
import { VECTOR_CODEC_ID } from '../core/constants';
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* …
|
|
1013
|
+
* @returns A column type descriptor with `typeParams.length` set
|
|
1014
|
+
*/
|
|
1015
|
+
export function vector<N extends number>(length: N) /* : … */ {
|
|
1016
|
+
return { /* … */ } as const;
|
|
1017
|
+
}
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
PostGIS's `geometry({ srid })` and `pgGeometryColumn({ srid })` lose their `srid` checks the same way and keep `const { srid } = options;`. A contract whose column has parameters its data type refuses, such as `vector(0)` or `geometry({ srid: 0 })`, fails when it is built, with `CONTRACT.TYPE_PARAMS_INVALID` and the meta `{ dataType, parameters, modelName, fieldName }`.
|
|
1021
|
+
|
|
1022
|
+
## `type-constructor-templates-lose-native-type`
|
|
1023
|
+
|
|
1024
|
+
```ts
|
|
1025
|
+
// before
|
|
1026
|
+
Vector: {
|
|
1027
|
+
kind: 'typeConstructor',
|
|
1028
|
+
args: [{ kind: 'number', name: 'length', integer: true, minimum: 1, maximum: VECTOR_MAX_DIM }],
|
|
1029
|
+
output: {
|
|
1030
|
+
codecId: 'pg/vector@1',
|
|
1031
|
+
nativeType: 'vector',
|
|
1032
|
+
typeParams: {
|
|
1033
|
+
length: { kind: 'arg', index: 0 },
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
// after
|
|
1039
|
+
Vector: {
|
|
1040
|
+
kind: 'typeConstructor',
|
|
1041
|
+
inferred: true,
|
|
1042
|
+
args: [{ kind: 'number', name: 'length', integer: true }],
|
|
1043
|
+
output: {
|
|
1044
|
+
codecId: 'pg/vector@1',
|
|
1045
|
+
typeParams: {
|
|
1046
|
+
length: { kind: 'arg', index: 0 },
|
|
1047
|
+
},
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
Put `inferred: true` directly after `kind` on the one constructor that `contract infer` should print for the data type. Keep `minimum` and `maximum` on an argument that also feeds something other than a data type parameter. An argument mapped onto a parameter the data type declares optional becomes `optional: true`, as PostGIS's `srid` does:
|
|
1053
|
+
|
|
1054
|
+
```ts
|
|
1055
|
+
args: [{ kind: 'number', name: 'srid', integer: true, optional: true }],
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
## `runtime-descriptor-registers-data-types`
|
|
1059
|
+
|
|
1060
|
+
Add `dataTypes` directly after `version`:
|
|
1061
|
+
|
|
1062
|
+
```ts
|
|
1063
|
+
const pgvectorRuntimeDescriptor: SqlRuntimeExtensionDescriptor<'postgres'> = {
|
|
1064
|
+
kind: 'extension' as const,
|
|
1065
|
+
id: pgvectorPackMeta.id,
|
|
1066
|
+
version: pgvectorPackMeta.version,
|
|
1067
|
+
dataTypes: pgvectorPackMeta.dataTypes,
|
|
1068
|
+
// …
|
|
1069
|
+
};
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
Without it, building the runtime adapter fails with `CONTRACT.DATA_TYPE_UNREGISTERED`, because the runtime stack has no data type to write the extension codec's casts from. When building an adapter by hand, pass the same list: `createPostgresAdapter({ codecDescriptors, dataTypes: pgvectorDataTypes })`. An extension whose codecs represent only the target's data types, such as `arktype/json@1` over `pg/jsonb`, has no data types of its own and adds nothing.
|
|
1073
|
+
|
|
1074
|
+
## `column-descriptors-drop-native-type`
|
|
1075
|
+
|
|
1076
|
+
Delete `nativeType` from every column type descriptor: column type helpers, hand-written descriptors, and enum descriptors.
|
|
1077
|
+
|
|
1078
|
+
```ts
|
|
1079
|
+
// before
|
|
1080
|
+
export function vector<N extends number>(length: N) {
|
|
1081
|
+
return { codecId: VECTOR_CODEC_ID, nativeType: 'vector', typeParams: { length } } as const;
|
|
1082
|
+
}
|
|
1083
|
+
const pgText = { codecId: 'pg/text@1', nativeType: 'text' } as const;
|
|
1084
|
+
|
|
1085
|
+
// after
|
|
1086
|
+
export function vector<N extends number>(length: N) {
|
|
1087
|
+
return { codecId: VECTOR_CODEC_ID, typeParams: { length } } as const;
|
|
1088
|
+
}
|
|
1089
|
+
const pgText = { codecId: 'pg/text@1' } as const;
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Delete the fourth argument of `column()`, the type name:
|
|
1093
|
+
|
|
1094
|
+
```ts
|
|
1095
|
+
// before
|
|
1096
|
+
column(pgVectorDescriptor.factory({ length }), pgVectorDescriptor.codecId, { length }, 'vector');
|
|
1097
|
+
|
|
1098
|
+
// after
|
|
1099
|
+
column(pgVectorDescriptor.factory({ length }), pgVectorDescriptor.codecId, { length });
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
Delete the `storage` list from the pack metadata's `types`: nothing reads it, and `StorageTypeMetadata`, its type, is deleted from `@internal/sql-contract/pack-types`. Delete an import that only the list used, and the part of a doc comment that describes the list. Contracts no longer carry `extensions.<pack>.types.storage`; the script removes it from yours.
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
// before
|
|
1106
|
+
types: {
|
|
1107
|
+
codecTypes: { import: { package: '@acme/pack/codec-types', named: 'CodecTypes', alias: 'AcmeTypes' } },
|
|
1108
|
+
storage: [{ typeId: pgvectorTypeId, familyId: 'sql', targetId: 'postgres', nativeType: 'vector' }],
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
// after
|
|
1112
|
+
types: {
|
|
1113
|
+
codecTypes: { import: { package: '@acme/pack/codec-types', named: 'CodecTypes', alias: 'AcmeTypes' } },
|
|
1114
|
+
},
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
Delete `nativeType` from each `storage.types` entry of the contract space's source (`src/contract.ts`):
|
|
1118
|
+
|
|
1119
|
+
```ts
|
|
1120
|
+
// before
|
|
1121
|
+
[PGVECTOR_NATIVE_TYPE]: { kind: 'codec-instance', codecId: VECTOR_CODEC_ID, nativeType: PGVECTOR_NATIVE_TYPE, typeParams: {} },
|
|
1122
|
+
|
|
1123
|
+
// after
|
|
1124
|
+
[PGVECTOR_NATIVE_TYPE]: { kind: 'codec-instance', codecId: VECTOR_CODEC_ID, typeParams: {} },
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
A codec descriptor's `columnFromEntity` returns `{ typeParams }` and no `nativeType`; code that read `nativeType` from its result stops reading it.
|
|
1128
|
+
|
|
1129
|
+
A `defineContract` facade that constrains the contract's `types` to what `type.*` helpers return uses `AuthoredStorageTypeInstance` from `@internal/sql-contract/types`, which has no `dataType`; the contract build adds it:
|
|
1130
|
+
|
|
1131
|
+
```ts
|
|
1132
|
+
// before
|
|
1133
|
+
type TypesConstraint = Record<string, StorageTypeInstance>;
|
|
1134
|
+
|
|
1135
|
+
// after
|
|
1136
|
+
type TypesConstraint = Record<string, AuthoredStorageTypeInstance>;
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
Code that builds a stored contract's column by hand, for example a test helper, writes the data type id instead of the type name: `{ dataType: 'pg/int4', codecId: 'pg/int4@1' }` instead of `{ nativeType: 'int4', codecId: 'pg/int4@1' }`, and a contract type written by hand declares `readonly dataType: 'pg/int4'` instead of `readonly nativeType: 'int4'`.
|
|
1140
|
+
|
|
1141
|
+
Update doc comments and examples that describe what a helper produces: `// Produces: codecId: 'pg/vector@1', typeParams: { length: 1536 }` instead of `// Produces: nativeType: 'vector', typeParams: { length: 1536 }`.
|
|
1142
|
+
|
|
1143
|
+
## `sqlite-data-types-are-stored-types`
|
|
1144
|
+
|
|
1145
|
+
A codec, cast or authoring entry that named one of the deleted types names the type SQLite stores instead: `sqlite/text` for JSON and date-time values, `sqlite/integer` for big integers. The data types are exported from `@internal/target-sqlite/data-types`.
|
|
1146
|
+
|
|
1147
|
+
```ts
|
|
1148
|
+
// before
|
|
1149
|
+
override readonly dataType = sqliteJson.id; // 'sqlite/json'
|
|
1150
|
+
override readonly dataType = sqliteBigint.id; // 'sqlite/bigint'
|
|
1151
|
+
|
|
1152
|
+
// after
|
|
1153
|
+
override readonly dataType = sqliteText.id; // 'sqlite/text'
|
|
1154
|
+
override readonly dataType = sqliteInteger.id; // 'sqlite/integer'
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
The canonical forms follow the stored type: `sqlite/integer` stores digit text and `sqlite/text` a string. A JSON codec on SQLite stores the JSON text of the document, and a date-time codec its text. `sqlite/text` declares no canonical form, so a codec whose values have several written forms declares one on its descriptor: `sqlite/datetime@1` does, with `toCanonicalForm` (the function that was `sqliteDatetime.toCanonicalForm`). A contract source, `db verify` and DDL use a codec's canonical form before its data type's.
|
|
1158
|
+
|
|
1159
|
+
```ts
|
|
1160
|
+
import type { ToCanonicalForm } from '@internal/framework-components/codec';
|
|
1161
|
+
|
|
1162
|
+
// your function: written text in, the one text the contract stores out; it throws
|
|
1163
|
+
// CONTRACT.CAST_REFUSED for text the codec does not hold
|
|
1164
|
+
declare const datetimeCanonicalForm: ToCanonicalForm;
|
|
1165
|
+
|
|
1166
|
+
export class MyDatetimeDescriptor extends SqliteCodecDescriptor<void> {
|
|
1167
|
+
override readonly dataType = sqliteText.id;
|
|
1168
|
+
override readonly toCanonicalForm = datetimeCanonicalForm;
|
|
1169
|
+
// codecId, traits, the JSON projection and the factory follow
|
|
1170
|
+
}
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
## `canonical-form-of-a-column`
|
|
1174
|
+
|
|
1175
|
+
Read a column's canonical form through `canonicalFormOf`, and pass the renamed options:
|
|
1176
|
+
|
|
1177
|
+
```ts
|
|
1178
|
+
// before
|
|
1179
|
+
canonicalDateTime(text, { shape: 'instant', dataTypeId: 'acme/instant' });
|
|
1180
|
+
mapDefault(stored, { dataTypeEntries, dataTypeLookup, columnDataType: codec.dataType });
|
|
1181
|
+
const toCanonical = codec.toCanonicalForm ?? dataTypes.get(codec.dataType)?.toCanonicalForm;
|
|
1182
|
+
|
|
1183
|
+
// after
|
|
1184
|
+
canonicalDateTime(text, { shape: 'instant', ownerId: 'acme/instant' });
|
|
1185
|
+
mapDefault(stored, { dataTypeEntries, dataTypeLookup, columnCodec: codec });
|
|
1186
|
+
const toCanonical = canonicalFormOf(codec, dataTypes);
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
## `authoring-entry-key-checked`
|
|
1190
|
+
|
|
1191
|
+
An entry whose tag yields a type that another entry already holds under that type's id, as SQLite's `json` tag yields `sqlite/text`, moves under its tag's key and names the type it yields:
|
|
1192
|
+
|
|
1193
|
+
```ts
|
|
1194
|
+
import { tagEntryKey } from '@internal/framework-components/authoring';
|
|
1195
|
+
|
|
1196
|
+
// before
|
|
1197
|
+
[sqliteJson.id]: {
|
|
1198
|
+
written: { kind: 'tag', tag: 'json', parse: parseJsonBody },
|
|
1199
|
+
print: printJsonBody,
|
|
1200
|
+
documentation: 'Reads the body as a JSON document and stores it as the default value.',
|
|
1201
|
+
},
|
|
1202
|
+
|
|
1203
|
+
// after
|
|
1204
|
+
[tagEntryKey('json')]: {
|
|
1205
|
+
written: {
|
|
1206
|
+
kind: 'tag',
|
|
1207
|
+
tag: 'json',
|
|
1208
|
+
type: sqliteText.id,
|
|
1209
|
+
parse: (text) => canonicalizeJson(parseJsonBody(text)),
|
|
1210
|
+
},
|
|
1211
|
+
print: (value) => String(value),
|
|
1212
|
+
documentation: 'Reads the body as a JSON document and stores its JSON text as the default value.',
|
|
1213
|
+
},
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
An entry under a data type's id keeps naming no `type`. Any other combination fails assembly with `CONTRACT.DATA_TYPE_ENTRY_KEY_INVALID`.
|
|
1217
|
+
|
|
1218
|
+
## `the-sql-tag-writes-the-sql-expression-data-type`
|
|
1219
|
+
|
|
1220
|
+
This supersedes the section about lowering entries in the `data-types-column-defaults` extension instructions of the upgrade from 8.0.0-rc.11 to 8.0.0-rc.12.
|
|
1221
|
+
|
|
1222
|
+
`sql` used to be a tag that named no data type and lowered its own body. It is now the tag of the data type `sql/expression`, which the SQL family defines and registers. The second kind of authoring entry is gone.
|
|
1223
|
+
|
|
1224
|
+
| Removed | Replacement |
|
|
1225
|
+
| --- | --- |
|
|
1226
|
+
| `AuthoringDataTypeEntry` | `DataTypeAuthoringEntry`, from `@internal/framework-components/authoring` |
|
|
1227
|
+
| `DataTypeLoweringAuthoringEntry`, `loweringEntryKey`, `isLoweringEntryKey`, `isDataTypeLoweringEntry` | None. Remove the branch that told the two kinds of entry apart |
|
|
1228
|
+
| `TaggedLiteralValue` from `@internal/framework-components/control` | None |
|
|
1229
|
+
| `sqlDefaultLiteralTagEntry` from `@internal/family-sql/control` | Nothing to register. The SQL family descriptor registers `sqlExpressionAuthoringEntry` from `@internal/sql-contract/sql-expression` under `SQL_EXPRESSION_DATA_TYPE_ID` |
|
|
1230
|
+
| `PSL_INVALID_DEFAULT_SQL` from `@internal/family-sql/control` | The string `'PSL_INVALID_DEFAULT_SQL'`. The code itself is unchanged |
|
|
1231
|
+
| `createPostgresDataTypeEntries`, `createSqliteDataTypeEntries` in the adapters | `postgresDataTypeEntries()` from `@internal/target-postgres/data-types`, `sqliteDataTypeEntries()` from `@internal/target-sqlite/data-types` |
|
|
1232
|
+
|
|
1233
|
+
Every key of `authoring.dataTypes` must now be the id of a data type that a component in the stack registers, or the key `tagEntryKey(tag)` of an entry that names the type its tag yields (see `authoring-entry-key-checked`). A key such as `lowering:sql` fails assembly with `CONTRACT.DATA_TYPE_UNREGISTERED`.
|
|
1234
|
+
|
|
1235
|
+
The SQL family registers `sql/expression` and its entry itself. A target or an extension does not register it, and a target that registered it too fails assembly with `CONTRACT.DATA_TYPE_ENTRY_DUPLICATE` (or `CONTRACT.DATA_TYPE_DUPLICATE` if it registers only the type). Remove both from the target. No component's data types may declare a cast or a list cast from `sql/expression`. The SQL family refuses a stack that has one with `CONTRACT.DATA_TYPE_CASTS_FROM_SQL_EXPRESSION` when it creates its control instance.
|
|
1236
|
+
|
|
1237
|
+
Two types exported from `@internal/sql-contract-psl/resolution` changed:
|
|
1238
|
+
|
|
1239
|
+
- The `WrittenValue` tag arm names its text `text`, not `body`.
|
|
1240
|
+
- The `unreadable` arm of `DefaultRefusal` has no `json` field. A JSON text that is not a JSON document is an ordinary `unreadable` refusal.
|
|
1241
|
+
|
|
1242
|
+
## `prefixed-sql-tags-are-removed`
|
|
1243
|
+
|
|
1244
|
+
`pg.sql` and `sqlite.sql` were second names for the `sql` tag. They are removed, and a schema that uses one is refused with `PSL_UNKNOWN_LITERAL_TAG`: `Unknown literal tag "pg.sql". Known tags: sql, json.`
|
|
1245
|
+
|
|
1246
|
+
Replace the tag with `sql` and leave the text unchanged:
|
|
1247
|
+
|
|
1248
|
+
```diff
|
|
1249
|
+
- createdAt DateTime @default(pg.sql`(now() + interval '1 hour')`)
|
|
1250
|
+
+ createdAt DateTime @default(sql`(now() + interval '1 hour')`)
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
The stored default does not change, so no migration follows.
|
|
1254
|
+
|
|
1255
|
+
## `default-diagnostic-codes-changed`
|
|
1256
|
+
|
|
1257
|
+
This matters only to code that reads PSL diagnostic codes, such as a test that asserts one. The messages did not change, except for a `sql` literal inside a list literal and the list of known tags, both described below the table.
|
|
1258
|
+
|
|
1259
|
+
| Refusal | Old code | New code |
|
|
1260
|
+
| --- | --- | --- |
|
|
1261
|
+
| A tag no pack registered | `PSL_UNKNOWN_DEFAULT_LITERAL_TAG` | `PSL_UNKNOWN_LITERAL_TAG` |
|
|
1262
|
+
| A `json` literal whose text is not a JSON document | `PSL_INVALID_JSON_LITERAL` | `PSL_INVALID_LITERAL` |
|
|
1263
|
+
| Text an authoring entry or a cast refused | `PSL_INVALID_DEFAULT_LITERAL` | `PSL_INVALID_LITERAL` |
|
|
1264
|
+
| A value whose type the column's type has no cast from, including a list written on a column that holds one value | `PSL_DEFAULT_TYPE_INCOMPATIBLE` | `PSL_VALUE_TYPE_INCOMPATIBLE` |
|
|
1265
|
+
| A written form the target has no data type for | `PSL_DEFAULT_TYPE_INCOMPATIBLE` | `PSL_VALUE_TYPE_INCOMPATIBLE` |
|
|
1266
|
+
| A single value on a list column | `PSL_DEFAULT_TYPE_INCOMPATIBLE` | `PSL_DEFAULT_LIST_EXPECTED` |
|
|
1267
|
+
| A value the column's codec refused | `PSL_INVALID_DEFAULT_LITERAL` | unchanged |
|
|
1268
|
+
| A `sql` literal inside a list literal | `PSL_INVALID_DEFAULT_LITERAL`, at the element | `PSL_VALUE_TYPE_INCOMPATIBLE`, at the `@default` attribute |
|
|
1269
|
+
|
|
1270
|
+
`PSL_INVALID_JSON_LITERAL` no longer exists.
|
|
1271
|
+
|
|
1272
|
+
The unknown-tag message lists the known tags in the order the stack registers them. The SQL family registers `sql` before the target registers `json`, so a Postgres or SQLite stack lists `sql, json`, where it used to list `json, sql, pg.sql` or `json, sql, sqlite.sql`. The completion list and the `Expected one of` message of `@default` offer `sql` before `json` for the same reason.
|
|
1273
|
+
|
|
1274
|
+
A `sql` literal inside a list literal used to report `Literal tag "sql" produces a default of its own and cannot be an element of a list literal.` It is now refused by the cast rule, like any other value the column's type does not take: `Field "Post.tags" at element 1: pg/text has no cast from sql/expression; it casts from nothing`. Write the whole list as one `sql` literal instead, as in `` @default(sql`'{}'::text[]`) ``.
|
|
1275
|
+
|
|
1276
|
+
This supersedes the statement in the `data-types-column-defaults` extension instructions of the upgrade from 8.0.0-rc.11 to 8.0.0-rc.12 that a cast's refusal surfaces as `PSL_INVALID_DEFAULT_LITERAL`. It surfaces as `PSL_INVALID_LITERAL`.
|
|
1277
|
+
|
|
1278
|
+
## `resolve-identity-value-receives-data-type`
|
|
1279
|
+
|
|
1280
|
+
```ts
|
|
1281
|
+
// before
|
|
1282
|
+
resolveIdentityValue: ({ nativeType }) => (nativeType === 'vector' ? "'[0]'" : null),
|
|
1283
|
+
|
|
1284
|
+
// after
|
|
1285
|
+
resolveIdentityValue: ({ dataType }) => (dataType === 'pgvector/vector' ? "'[0]'" : null),
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
A list column now reaches the hook with its element's data type and `typeParams`.
|
|
1289
|
+
|
|
1290
|
+
## `validate-scalar-type-codec-ids-removed`
|
|
1291
|
+
|
|
1292
|
+
Delete calls to `validateScalarTypeCodecIds`; the control stack checks the same thing when it is assembled. Fix any assembly error the new checks report in the extension's own contributions.
|
|
1293
|
+
|
|
1294
|
+
## `assemble-data-types-moved-to-codec`
|
|
1295
|
+
|
|
1296
|
+
Import `assembleDataTypes` from `@internal/framework-components/codec`. Remove it from the `@internal/framework-components/control` import, and delete that import if nothing is left in it:
|
|
1297
|
+
|
|
1298
|
+
```ts
|
|
1299
|
+
// before
|
|
1300
|
+
import { assembleDataTypes } from '@internal/framework-components/control';
|
|
1301
|
+
|
|
1302
|
+
// after
|
|
1303
|
+
import { assembleDataTypes } from '@internal/framework-components/codec';
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
## `number-text-helpers-moved`
|
|
1307
|
+
|
|
1308
|
+
Import `numeralText` from `@internal/sql-contract/data-type`, adding it to an existing import from that module. Remove it from the `@internal/sql-relational-core/ast` import, and delete that import if nothing is left in it:
|
|
1309
|
+
|
|
1310
|
+
```ts
|
|
1311
|
+
// before
|
|
1312
|
+
import { numeralText } from '@internal/sql-relational-core/ast';
|
|
1313
|
+
|
|
1314
|
+
// after
|
|
1315
|
+
import { numeralText } from '@internal/sql-contract/data-type';
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
## `data-type-support-moved`
|
|
1319
|
+
|
|
1320
|
+
Import each helper listed in the summary from `@internal/sql-contract/data-type-support`. `CanonicalDateTimeOptions.dataTypeId` is also renamed `ownerId`; see `canonical-form-of-a-column`. Remove it from the `@internal/sql-relational-core/ast` import, and delete that import if nothing is left in it. Add `@internal/sql-contract` to the package's dependencies if it is not there:
|
|
1321
|
+
|
|
1322
|
+
```ts
|
|
1323
|
+
// before
|
|
1324
|
+
import { escapePslString, createNumberClassifier } from '@internal/sql-relational-core/ast';
|
|
1325
|
+
|
|
1326
|
+
// after
|
|
1327
|
+
import { createNumberClassifier, escapePslString } from '@internal/sql-contract/data-type-support';
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
## `numeric-limits-removed`
|
|
1331
|
+
|
|
1332
|
+
Validate `numeric` parameters against `pgNumericParams` instead of reading the two ranges:
|
|
1333
|
+
|
|
1334
|
+
```ts
|
|
1335
|
+
// before
|
|
1336
|
+
import { NUMERIC_PRECISION_RANGE } from '@internal/target-postgres/codecs';
|
|
1337
|
+
const fits = precision >= NUMERIC_PRECISION_RANGE.min && precision <= NUMERIC_PRECISION_RANGE.max;
|
|
1338
|
+
|
|
1339
|
+
// after
|
|
1340
|
+
import { pgNumericParams } from '@internal/target-postgres/data-types';
|
|
1341
|
+
import { type as arktype } from 'arktype';
|
|
1342
|
+
const fits = !(pgNumericParams({ precision }) instanceof arktype.errors);
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
## `postgres-codecs-decode-server-text`
|
|
1346
|
+
|
|
1347
|
+
In a Postgres codec, make `decode` parse the server text for its type. Do not expect a value that `pg` parsed:
|
|
1348
|
+
|
|
1349
|
+
- `bool` arrives as `t` or `f`, not a boolean.
|
|
1350
|
+
- Integer, float and `oid` values arrive as decimal text, such as `42`, `1.5`, `NaN` or `-Infinity`, not numbers. `int8` and `numeric` already arrived as text.
|
|
1351
|
+
- `bytea` arrives as `\x` hex text, such as `\x0102ff`, not a `Buffer`.
|
|
1352
|
+
- `interval` arrives as PostgreSQL interval text, such as `1 day 02:03:04`, not an object.
|
|
1353
|
+
- `point` and `circle` arrive as PostgreSQL geometric text, such as `(1,2)` and `<(1,2),3>`, not objects.
|
|
1354
|
+
- `json` and `jsonb` arrive as JSON text, so a stored JSON string keeps its quotes (`"hello"`). Parse it with `JSON.parse` before you validate it.
|
|
1355
|
+
|
|
1356
|
+
Code that reads rows from the runtime driver's `query` without a codec now receives these strings for every column. Convert each value it uses, for example with `Number(row.count)` or `row.flag === 't'`.
|
|
1357
|
+
|
|
1358
|
+
## `codecs-decode-json-reads-stored-forms`
|
|
1359
|
+
|
|
1360
|
+
Code that calls a built-in codec's `decodeJson` must pass the stored JSON form of its type; another kind throws `RUNTIME.DECODE_FAILED` with the codec id in `meta`. A codec an extension contributes should follow the same rule, stated on `Codec.decodeJson` in `@internal/framework-components/codec`: read a stored JSON form of its type, including every form the database writes for it, and throw on anything else.
|
|
1361
|
+
|
|
1362
|
+
A codec's `decodeJson` reads a value in the stored JSON form of its type: a column's literal default in `contract.json`, a member of a value-object default, and a value inside the JSON the database returns for an included relation. The text codecs (`pg/text@1`, `sql/text@1`, `sql/char@1`, `sql/varchar@1`, `sqlite/text@1`, `pg/enum@1`, `pg/uuid@1`, `pg/inet@1`, `pg/bit@1`, `pg/varbit@1`, `pg/tsquery@1`, `pg/timetz@1`, `pg/text-array@1` and the date and time codecs), the integer codecs `pg/int4@1`, `pg/int2@1` and `sql/int@1`, and `pg/bool@1` used to pass any JSON value through. Each now refuses a value of another kind with `RUNTIME.DECODE_FAILED`, naming the codec: a text codec takes a JSON string, `pg/int4@1` a JSON integer from -2147483648 to 2147483647, `pg/int2@1` one from -32768 to 32767, `sql/int@1` a safe integer, or on PostgreSQL, where its column is an int4, an integer from -2147483648 to 2147483647, `pg/bool@1` `true` or `false`, `pg/uuid@1` a UUID as PostgreSQL writes it, in lower case and hyphenated 8-4-4-4-12, and a bit string only `0` and `1`. `pg/vector@1` refuses JSON that is not an array of the column's number of finite numbers with the same shape, as in `pg/vector@1 JSON value must be an array of 3 finite numbers`, where it said `Vector length mismatch` or `Vector value must contain only numbers`. `pg/int8@1` and `sqlite/bigint@1` take decimal text in the signed 64-bit range. `pg/timestamptz-date@1` refused a bad string with a plain `RangeError`; it now raises `RUNTIME.DECODE_FAILED` like the others. Every form PostgreSQL and SQLite write for a value the application type holds is still read; see `text-array-elements-nullable` and `sqlite-int-include-refuses-inexact-values` for the two stored values that now throw instead of reading wrong.
|
|
1363
|
+
|
|
1364
|
+
The float codecs `pg/float8@1`, `pg/float4@1`, `pg/float@1`, `sql/float@1` and `sqlite/real@1` take a finite JSON number or the text `"NaN"`, `"Infinity"` or `"-Infinity"`, which PostgreSQL writes for those values in JSON, and `encodeJson` writes that text for them. SQLite writes an infinity in JSON as `9.0e+999`, so on SQLite the float codecs' JSON projection writes the text instead. `sql/float@1`, `pg/float@1` and `sqlite/real@1` used to refuse NaN and the infinities, so an `.include()` of a row holding one failed with `RUNTIME.DECODE_FAILED`; it now reads the value. SQLite cannot store NaN, so on SQLite `sqlite/real@1` and `sql/float@1` refuse it; see `sqlite-nan-parameters-refused`.
|
|
1365
|
+
|
|
1366
|
+
Helpers for following the rule:
|
|
1367
|
+
|
|
1368
|
+
- `@internal/framework-components/codec` exports the JSON readers every family's codecs share: `decodeJsonString`, `decodeJsonMatching`, `decodeJsonBoolean`, `decodeJsonInteger` (with an `IntegerRange`), `decodeJsonIntegerText` (decimal text, with an optional `BigIntRange`), `decodeJsonFloat` and `encodeJsonFloat`, and `refuseJsonValue`, which raises the refusal they all raise: `RUNTIME.DECODE_FAILED`, `<codecId> JSON value must be <what it takes>`, with `meta.codecId` and `meta.received`, the value it got as JSON text, cut to 100 characters. It also exports the ranges they take, `INT32_RANGE` and `SAFE_INTEGER_RANGE` (`IntegerRange`, which `isIntegerIn(value, range)` tests a value against) and `INT64_RANGE` and `SAFE_INTEGER_BIGINT_RANGE` (`BigIntRange`), and `isNonFiniteText`, which says whether text is `NaN`, `Infinity` or `-Infinity`. The built-in codecs' refusals no longer say `database JSON value`.
|
|
1369
|
+
- `@internal/utils/text` exports `counted`, which writes a count and its noun for a refusal, such as `3 characters`, and `withoutTrailing(text, character)`, which drops a trailing run of one character in time linear in the run's length.
|
|
1370
|
+
|
|
1371
|
+
## `mongo-codec-requires-decode-json`
|
|
1372
|
+
|
|
1373
|
+
`mongoCodec` now requires `decodeJson` when the codec's application type is narrower than `JsonValue`, such as `string` or `number`; only a codec whose application type is exactly `JsonValue` may leave it out. A codec that leaves it out no longer compiles. Supply a `decodeJson` that refuses a JSON value of another kind with `RUNTIME.DECODE_FAILED`, such as `decodeJsonString` or another reader from `@internal/framework-components/codec`.
|
|
1374
|
+
|
|
1375
|
+
## `sql-float-json-helpers-removed`
|
|
1376
|
+
|
|
1377
|
+
Replace `sqlFloatEncodeJson(value)` with `encodeJsonFloat(value)` and `sqlFloatDecodeJson(json)` with `decodeJsonFloat(codecId, json)`, and import `isNonFiniteText` from `@internal/framework-components/codec` instead of `@internal/sql-relational-core/ast`. An import of `isNonFiniteText` that already names `@internal/framework-components/codec` needs no change.
|
|
1378
|
+
|
|
1379
|
+
## `codec-lookup-has-no-descriptor-for`
|
|
1380
|
+
|
|
1381
|
+
`codecForRef(lookup, ref)` from `@internal/framework-components/codec` builds a column's codec from its descriptor, with the column's type parameters, so everything that builds one takes a `CodecLookupWithDescriptors`. `CodecLookup` no longer declares `descriptorFor`, not even as optional. Each of these no longer compiles:
|
|
1382
|
+
|
|
1383
|
+
- a `codecLookup` for `defineContract` or a `ContractSourceContext` without `descriptorFor`. Add one, as the registry `assemblePostgresCodecRegistryWithBuiltins` returns does, or leave `codecLookup` out of `defineContract` so it assembles the target's registry;
|
|
1384
|
+
- a stub built as `{ ...emptyCodecLookup, get }` where a `CodecLookupWithDescriptors` is expected, because `emptyCodecLookup` no longer has a `descriptorFor` that answers `undefined`. Add a `descriptorFor` that answers for the same codecs as `get`, so the two never disagree;
|
|
1385
|
+
- an object literal typed `CodecLookup` that sets `descriptorFor`, or a call `lookup.descriptorFor?.(id)` on a `CodecLookup`. Type the lookup `CodecLookupWithDescriptors` and call `descriptorFor(id)`.
|
|
1386
|
+
|
|
1387
|
+
## `mongo-codecs-check-json`
|
|
1388
|
+
|
|
1389
|
+
A Mongo codec's `decodeJson` reads the JSON form of its type. The Mongo runtime reads documents through `decode` and never calls it; the PSL reader calls it for each member of an enum. `mongo/string@1`, `mongo/objectId@1`, `mongo/int32@1`, `mongo/double@1`, `mongo/bool@1`, `mongo/vector@1` and `mongo/bson@1` used to return any JSON value as it was, so an enum member of the wrong kind was stored in the contract:
|
|
1390
|
+
|
|
1391
|
+
```prisma
|
|
1392
|
+
enum Priority {
|
|
1393
|
+
@@type("mongo/int32@1")
|
|
1394
|
+
Low = "low"
|
|
1395
|
+
}
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
This is now refused with `PSL_EXTENSION_INVALID_VALUE`, naming the codec's message, `mongo/int32@1 JSON value must be an integer from -2147483648 to 2147483647`. A member written without a value, such as a bare `Low`, under a codec that does not take text is `PSL_ENUM_BARE_MEMBER_NON_STRING_CODEC`. Give each member a value of the codec's type.
|
|
1399
|
+
|
|
1400
|
+
Each codec now takes: `mongo/string@1` a string; `mongo/objectId@1` 24 hexadecimal digits; `mongo/int32@1` an integer from -2147483648 to 2147483647; `mongo/double@1` a number, or the text `"NaN"`, `"Infinity"` or `"-Infinity"`, which its `encodeJson` now writes for those values instead of a number JSON cannot hold; `mongo/bool@1` a boolean; `mongo/date@1` the text `Date.toISOString()` writes; `mongo/vector@1` an array of numbers; and `mongo/bson@1` canonical Extended JSON, the form its `encodeJson` writes. Another value throws `RUNTIME.DECODE_FAILED` with the codec id in `meta`. A TypeScript `enumType` member that `mongo/objectId@1` or `mongo/int32@1` does not hold now throws `RUNTIME.ENCODE_FAILED` when the contract is built.
|
|
1401
|
+
|
|
1402
|
+
## `text-array-elements-nullable`
|
|
1403
|
+
|
|
1404
|
+
`pg/text-array@1`, the codec of a contract-free `textArray()` column, reads a `text[]` column's NULL elements as `null`, so its application type is `readonly (string | null)[]` where it was `readonly string[]`, and so is its entry in the Postgres `CodecTypes`. Code typed by a `textArray()` column, or by `min` or `max` over one, sees `string | null` elements; handle the `null`. Read through an `.include()`, a two-dimensional `text[]` value now throws `RUNTIME.DECODE_FAILED`, where it read as the text `"a,b"`.
|
|
1405
|
+
|
|
1406
|
+
## `char-reads-drop-only-padding`
|
|
1407
|
+
|
|
1408
|
+
PostgreSQL pads a `char(n)` value with spaces to its length: `'a'` in a `char(3)` column is stored as `'a '`. A flat read dropped every trailing whitespace character, so a stored `'a\t'` also read as `"a"`, while `.include()` returned the padded text, `"a "`. Both reads now return the value without the padding and nothing more: `"a"` for `'a'`, and `"a\t"` for `'a\t'`. On SQLite, which does not pad, both reads drop trailing spaces, as a flat read did. Code that compared an included `char` value with its padding, or relied on a flat read dropping a trailing tab or newline, compares the value without its padding.
|
|
1409
|
+
|
|
1410
|
+
## `sqlite-nan-parameters-refused`
|
|
1411
|
+
|
|
1412
|
+
SQLite cannot store NaN: bound as a parameter, it becomes NULL. So `create({ value: 0 / 0 })` on an optional `Float` column stored NULL, and `where((p) => p.value.eq(Number.NaN))` matched nothing. On SQLite, `sqlite/real@1` and `sql/float@1` now refuse NaN with `RUNTIME.ENCODE_FAILED`, `<codecId> value must be a number other than NaN, which SQLite cannot store`, with `meta.codecId` and `meta.received`: when they encode a value to write or filter by, and when they encode a TypeScript `.default()`, which is still refused when the contract is built with `CONTRACT.DEFAULT_INVALID`, now with this message. Their `decodeJson` refuses the text `"NaN"`. A NaN parameter no codec encoded, such as one in raw SQL, is refused by the SQLite driver with the same code: `Parameter 2 is NaN, which SQLite cannot store: it would bind it as NULL. Pass null to store no value.`, with `meta.paramIndex`, counted from 0. On a required column SQLite already refused the NULL, so only the error changes. Where a computed value can be NaN, write `null` for no value, and filter with `isNull()` for rows that have none. Infinity and -Infinity are stored and read back as before.
|
|
1413
|
+
|
|
1414
|
+
## `sqlite-int-include-refuses-inexact-values`
|
|
1415
|
+
|
|
1416
|
+
`sql/int@1` holds a JavaScript safe integer. On SQLite, an INTEGER column can hold a larger integer or a REAL. An `.include()` read such a value rounded or with a fraction; it now throws `RUNTIME.DECODE_FAILED`, naming the codec. A flat read is unchanged. Store an integer past 2^53 in a `BigInt` column and a fraction in a `Float` column.
|
|
1417
|
+
|
|
1418
|
+
## `contract-build-takes-lookups`
|
|
1419
|
+
|
|
1420
|
+
The column's stored `dataType` is the data type its codec represents (see `contract-stores-data-type`), so the build needs both lookups. Through the facades, list every extension whose codec the contract uses:
|
|
1421
|
+
|
|
1422
|
+
```ts
|
|
1423
|
+
defineContract({ extensions: { pgvector } }, ({ field, model }) => ({ /* … */ }));
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
A contract written with an empty definition (`defineContract({}, …)`) that names an extension's codec passes the lookups itself, as an extension's own contract space does:
|
|
1427
|
+
|
|
1428
|
+
```ts
|
|
1429
|
+
// before
|
|
1430
|
+
export const contract = defineContract({}, () => ({
|
|
1431
|
+
types: {
|
|
1432
|
+
[PGVECTOR_NATIVE_TYPE]: {
|
|
1433
|
+
kind: 'codec-instance',
|
|
1434
|
+
codecId: VECTOR_CODEC_ID,
|
|
1435
|
+
nativeType: PGVECTOR_NATIVE_TYPE,
|
|
1436
|
+
typeParams: {},
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
models: {},
|
|
1440
|
+
}));
|
|
1441
|
+
|
|
1442
|
+
// after
|
|
1443
|
+
import { assembleDataTypes } from '@internal/framework-components/codec';
|
|
1444
|
+
import { assemblePostgresCodecRegistryWithBuiltins } from '@internal/target-postgres/codecs';
|
|
1445
|
+
import { postgresDataTypes } from '@internal/target-postgres/data-types';
|
|
1446
|
+
import { pgvectorDataTypes } from './core/data-types';
|
|
1447
|
+
import { pgvectorCodecRegistry } from './core/registry';
|
|
1448
|
+
|
|
1449
|
+
const dataTypeLookup = assembleDataTypes([
|
|
1450
|
+
{ id: 'postgres', dataTypes: postgresDataTypes },
|
|
1451
|
+
{ id: 'pgvector', dataTypes: pgvectorDataTypes },
|
|
1452
|
+
]).lookup;
|
|
1453
|
+
const codecLookup = assemblePostgresCodecRegistryWithBuiltins(
|
|
1454
|
+
[{ types: { codecTypes: { codecDescriptors: [...pgvectorCodecRegistry.values()] } } }],
|
|
1455
|
+
dataTypeLookup,
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
export const contract = defineContract(
|
|
1459
|
+
{ codecLookup, dataTypeLookup },
|
|
1460
|
+
() => ({
|
|
1461
|
+
types: {
|
|
1462
|
+
[PGVECTOR_NATIVE_TYPE]: {
|
|
1463
|
+
kind: 'codec-instance',
|
|
1464
|
+
codecId: VECTOR_CODEC_ID,
|
|
1465
|
+
typeParams: {},
|
|
1466
|
+
},
|
|
1467
|
+
},
|
|
1468
|
+
models: {},
|
|
1469
|
+
}),
|
|
1470
|
+
);
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
The `storage.types` entry loses `nativeType` as `column-descriptors-drop-native-type` describes. A column whose codec the lookup lacks fails with `CONTRACT.CODEC_DESCRIPTOR_MISSING`; one whose codec's data type the lookup lacks fails with `CONTRACT.DATA_TYPE_UNREGISTERED`. A direct call of `buildSqlContractFromDefinition(definition, codecLookup, dataTypeLookup)` passes both.
|
|
1474
|
+
|
|
1475
|
+
## `define-contract-wrapper-builds-data-type-lookup`
|
|
1476
|
+
|
|
1477
|
+
A package that exposes its own `defineContract` over `buildBoundContract`, as the Postgres and SQLite facades do, makes four edits.
|
|
1478
|
+
|
|
1479
|
+
1. Directly after the first import, import the lookup types and the data type assembly:
|
|
1480
|
+
|
|
1481
|
+
```ts
|
|
1482
|
+
import {
|
|
1483
|
+
assembleDataTypes,
|
|
1484
|
+
type CodecLookupWithDescriptors,
|
|
1485
|
+
type DataTypeLookup,
|
|
1486
|
+
} from '@internal/framework-components/codec';
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
2. In the result type, the object passed to `ContractInput`'s build gains both lookups directly after `createNamespace`:
|
|
1490
|
+
|
|
1491
|
+
```ts
|
|
1492
|
+
readonly createNamespace: (input: SqlNamespaceInput) => SqlNamespaceBase;
|
|
1493
|
+
readonly codecLookup: CodecLookupWithDescriptors;
|
|
1494
|
+
readonly dataTypeLookup: DataTypeLookup;
|
|
1495
|
+
```
|
|
1496
|
+
|
|
1497
|
+
3. The scaffold type adds `'codecLookup' | 'dataTypeLookup'` at the end of the keys it omits from `ContractInput`, and takes both as optional overrides. When the `Omit<…>` is already intersected with an object type, the two members go at the start of that object; otherwise add `& { … }` with them:
|
|
1498
|
+
|
|
1499
|
+
```ts
|
|
1500
|
+
> & {
|
|
1501
|
+
/** Overrides the codecs of the target and the extensions. */
|
|
1502
|
+
readonly codecLookup?: CodecLookupWithDescriptors;
|
|
1503
|
+
/** Overrides the data types of the target and the extensions. */
|
|
1504
|
+
readonly dataTypeLookup?: DataTypeLookup;
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
4. In the implementation, assemble the data type lookup once, with the target pack as the first contributor, and build the codec lookup against it. On Postgres, `assemblePostgresCodecRegistryWithBuiltins` now takes the data type lookup as its second argument:
|
|
1508
|
+
|
|
1509
|
+
```ts
|
|
1510
|
+
const extensions: readonly ExtensionPackRef<'sql', string>[] = Object.values(
|
|
1511
|
+
definition.extensions ?? {},
|
|
1512
|
+
);
|
|
1513
|
+
const dataTypeLookup =
|
|
1514
|
+
definition.dataTypeLookup ?? assembleDataTypes([postgresPack, ...extensions]).lookup;
|
|
1515
|
+
const bound = {
|
|
1516
|
+
...definition,
|
|
1517
|
+
createNamespace: postgresCreateNamespace,
|
|
1518
|
+
codecLookup:
|
|
1519
|
+
definition.codecLookup ??
|
|
1520
|
+
assemblePostgresCodecRegistryWithBuiltins(extensions, dataTypeLookup),
|
|
1521
|
+
dataTypeLookup,
|
|
1522
|
+
};
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
On SQLite, the target constant is the first contributor:
|
|
1526
|
+
|
|
1527
|
+
```ts
|
|
1528
|
+
const extensionPacks: readonly ExtensionPackRef<'sql', string>[] = Object.values(
|
|
1529
|
+
definition.extensions ?? {},
|
|
1530
|
+
);
|
|
1531
|
+
const bound = {
|
|
1532
|
+
...definition,
|
|
1533
|
+
createNamespace: sqliteCreateNamespace,
|
|
1534
|
+
codecLookup: definition.codecLookup ?? assembleSqliteCodecRegistry(target, extensionPacks),
|
|
1535
|
+
dataTypeLookup:
|
|
1536
|
+
definition.dataTypeLookup ?? assembleDataTypes([target, ...extensionPacks]).lookup,
|
|
1537
|
+
};
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
Both refuse a data type id that two packs register, with `CONTRACT.DATA_TYPE_DUPLICATE`.
|
|
1541
|
+
|
|
1542
|
+
## `postgres-codec-registry-takes-data-type-lookup`
|
|
1543
|
+
|
|
1544
|
+
The Postgres codec registry no longer carries the data types. Each function that builds one takes the data type lookup its codecs are checked against, and refuses a codec whose data type the lookup lacks with `CONTRACT.DATA_TYPE_UNREGISTERED`. Build the lookup first, from the same components, with `assembleDataTypes(components).lookup` from `@internal/framework-components/codec`, or take the target's own with `createPostgresBuiltinDataTypeLookup()` from `@internal/target-postgres/data-types`:
|
|
1545
|
+
|
|
1546
|
+
```ts
|
|
1547
|
+
// before
|
|
1548
|
+
const codecRegistry = assemblePostgresCodecRegistry(components);
|
|
1549
|
+
const adapter = new PostgresControlAdapter(codecRegistry);
|
|
1550
|
+
|
|
1551
|
+
// after
|
|
1552
|
+
const dataTypeLookup = assembleDataTypes(components).lookup;
|
|
1553
|
+
const codecRegistry = assemblePostgresCodecRegistry(components, dataTypeLookup);
|
|
1554
|
+
const adapter = new PostgresControlAdapter(codecRegistry, dataTypeLookup);
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
`assemblePostgresCodecRegistryWithBuiltins(extensions, dataTypeLookup)` and `createPostgresAdapterWithCodecRegistry(codecRegistry, dataTypeLookup)` change the same way. `createPostgresCodecRegistryWithBuiltins(codecDescriptors, dataTypeLookup)` takes the lookup in place of a list of data types, and defaults to the target's own. A built-in adapter in a test is `new PostgresControlAdapter(createPostgresBuiltinCodecLookup(), createPostgresBuiltinDataTypeLookup())`.
|
|
1558
|
+
|
|
1559
|
+
## `contract-to-schema-takes-components`
|
|
1560
|
+
|
|
1561
|
+
```ts
|
|
1562
|
+
// before
|
|
1563
|
+
const fromSchema = migrations.contractToSchema(fromContract);
|
|
1564
|
+
|
|
1565
|
+
// after
|
|
1566
|
+
const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
Pass the framework components of the stack the contract was built with. A custom target's implementation passes them on to the family's `contractToSchemaIR` as `dataTypeLookup` and `codecLookup`.
|
|
1570
|
+
|
|
1571
|
+
## `contract-to-schema-ir-takes-lookups`
|
|
1572
|
+
|
|
1573
|
+
```ts
|
|
1574
|
+
// before
|
|
1575
|
+
contractToSchemaIR(contract, { annotationNamespace: 'pg', expandNativeType });
|
|
1576
|
+
|
|
1577
|
+
// after
|
|
1578
|
+
contractToSchemaIR(contract, { annotationNamespace: 'pg', dataTypeLookup, codecLookup });
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
`dataTypeLookup` and `codecLookup` come from the assembled stack: `sqlTypeLookupsOf(frameworkComponents)` from `@internal/family-sql/control` returns both. Every field or parameter that holds a `DataTypeLookup` is now named `dataTypeLookup`; `dataTypes` names only a list of data types.
|
|
1582
|
+
|
|
1583
|
+
## `authoring-entity-context-takes-data-type-lookup`
|
|
1584
|
+
|
|
1585
|
+
```ts
|
|
1586
|
+
// before
|
|
1587
|
+
const ctx: AuthoringEntityContext = { family: 'sql', target: 'postgres' };
|
|
1588
|
+
|
|
1589
|
+
// after
|
|
1590
|
+
const ctx: AuthoringEntityContext = {
|
|
1591
|
+
family: 'sql',
|
|
1592
|
+
target: 'postgres',
|
|
1593
|
+
codecLookup: createPostgresBuiltinCodecLookup(),
|
|
1594
|
+
dataTypeLookup: createDataTypeLookup(postgresDataTypes),
|
|
1595
|
+
};
|
|
1596
|
+
```
|
|
1597
|
+
|
|
1598
|
+
Pass the lookups of the stack the context serves; `createDataTypeLookup` from `@internal/framework-components/codec` builds a data type lookup from a list of data types, and `{ ...emptyCodecLookup, descriptorFor: () => undefined }`, with `emptyCodecLookup` from the same module, stands in where no codecs apply. A stub codec lookup that answers `descriptorFor` is typed `CodecLookupWithDescriptors`. A call of `interpretPslDocumentToMongoContract` passes `codecLookup` as well: the stack's codec lookup, or that stand-in.
|
|
1599
|
+
|
|
1600
|
+
## `mongo-derive-json-schema-takes-lookups`
|
|
1601
|
+
|
|
1602
|
+
```ts
|
|
1603
|
+
// before
|
|
1604
|
+
deriveJsonSchema(fields, valueObjects, codecLookup, valueSets);
|
|
1605
|
+
derivePolymorphicJsonSchema(baseFields, discriminator, variants, valueObjects, codecLookup, valueSets);
|
|
1606
|
+
|
|
1607
|
+
// after
|
|
1608
|
+
deriveJsonSchema(fields, { codecLookup, dataTypeLookup }, valueObjects, valueSets);
|
|
1609
|
+
derivePolymorphicJsonSchema(
|
|
1610
|
+
baseFields,
|
|
1611
|
+
discriminator,
|
|
1612
|
+
variants,
|
|
1613
|
+
{ codecLookup, dataTypeLookup },
|
|
1614
|
+
valueObjects,
|
|
1615
|
+
valueSets,
|
|
1616
|
+
);
|
|
1617
|
+
```
|
|
1618
|
+
|
|
1619
|
+
`codecLookup` is a `CodecLookupWithDescriptors`, the stack's codec lookup. `dataTypeLookup` holds the stack's data types: `createDataTypeLookup(mongoDataTypes)`, with `createDataTypeLookup` from `@internal/framework-components/codec` and `mongoDataTypes` from `@internal/target-mongo/data-types`, plus any data types the extension registers. A call that passed no codec lookup passes the stack's now; without it the validator left out every scalar field's BSON type.
|
|
1620
|
+
|
|
1621
|
+
## `field-type-params-come-from-the-domain-type`
|
|
1622
|
+
|
|
1623
|
+
- `buildSqlContractFromDefinition` takes a model field's domain type parameters from its `descriptor.typeParams`, or else from the named storage type its `descriptor.typeRef` names. A value-object model field carries its column's `descriptor` (the target's value-object storage type) instead of the builder assuming `jsonb`. A value-object member has no `columnName` and is typed by a codec and its type parameters only.
|
|
1624
|
+
- `EmissionSpi.resolveFieldTypeParams` is removed. A family whose domain fields do not carry their type parameters puts them there when it builds the contract.
|
|
1625
|
+
- `generateFieldOutputTypesMap` from `@internal/emitter` takes `(models, codecLookup)`: its third parameter, the type-parameter resolver, is removed with the hook.
|
|
1626
|
+
|
|
1627
|
+
## `sql-infer-psl-contract-takes-build-context`
|
|
1628
|
+
|
|
1629
|
+
The SQL family calls a target's `inferPslContract(schema, context, describedContracts?)` with the same `SqlPslBuildContext` it passes `buildPslContract`: the stack's authoring contributions, codec lookup and data types. `contract emit` reads the inferred schema with that stack, so a target reads a written type's codec and type parameters from `context.authoringContributions.type` and `context.codecLookup` instead of a table of its own. A target that implements the hook adds the parameter.
|
|
1630
|
+
|
|
1631
|
+
Code that called the hook on a target descriptor, such as a script that infers PSL from an introspected schema, has no context to pass. It calls the SQL family instance's `inferPslContract(schema)`, which builds the context from the control stack it was created with:
|
|
1632
|
+
|
|
1633
|
+
```ts
|
|
1634
|
+
// before
|
|
1635
|
+
const inferPslContract = postgresTargetDescriptor.inferPslContract;
|
|
1636
|
+
if (!inferPslContract) {
|
|
1637
|
+
throw new Error('the postgres target descriptor has no inferPslContract');
|
|
1638
|
+
}
|
|
1639
|
+
const ast = inferPslContract(rawSchemaNode);
|
|
1640
|
+
|
|
1641
|
+
// after
|
|
1642
|
+
const ast = sqlFamilyDescriptor.create(controlStack).inferPslContract(rawSchemaNode);
|
|
1643
|
+
```
|
|
1644
|
+
|
|
1645
|
+
`sqlFamilyDescriptor` is the default export of `@internal/family-sql/control`, and `controlStack` is the stack `createControlStack` from `@internal/framework-components/control` builds for the family, target, adapter and driver the script already uses.
|
|
1646
|
+
|
|
1647
|
+
## `imported-postgres-field-checks-defaults`
|
|
1648
|
+
|
|
1649
|
+
`import { field } from '@prisma/orm-postgres/contract-builder'` now gives the same field builders the `defineContract` callback receives, without the ones an extension adds: `field.text()`, `field.temporal.timestamptz()`, `field.uuidString()`, and `field.column(columnType)` as before. Its `.default(...)` now checks the value against the Postgres target's column types; a column type an extension adds, such as pgvector's `vector`, is not checked. A default of another type no longer compiles:
|
|
1650
|
+
|
|
1651
|
+
```diff
|
|
1652
|
+
- field.column(int8Column).default(1)
|
|
1653
|
+
+ field.column(int8Column).default(1n)
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
## `codecs-check-stored-json`
|
|
1657
|
+
|
|
1658
|
+
The built-in SQL, PostgreSQL and SQLite codecs now refuse a value that is not a stored form of their type, including one its type parameters rule out, where most used to pass it through. A TypeScript `.default()` given such a value is now refused when the contract is built, with `CONTRACT.DEFAULT_INVALID` naming the model and field: a value of another kind, such as a number for a text column, or one the PostgreSQL column rules in `psl-values-checked-by-codecs` refuse, such as `'toolong'` for a `varchar(3)` column. A TypeScript `enumType` member its codec does not take is refused the same way, with `CONTRACT.ENUM_INVALID` naming the enum, the member and the codec: for example a `pg/char@1` or `sql/char@1` member longer than one character on PostgreSQL, since the enum's column is `character`, which holds one. Correct the value the error names.
|
|
1659
|
+
|
|
1660
|
+
A `contract.json` with such a default, emitted by an earlier version or edited by hand, still loads. `db init`, `db update` and `migration plan` used to plan the default; they now stop with `CONTRACT.DEFAULT_INVALID`, which names the table, the column, the codec and the value. Emit the contract again with this version, and correct the default in the contract source if emit refuses it. Running a `migration.ts` that an earlier version planned with such a default fails with the same message; correct the default in that file. Every statement that writes a default reads it this way: a new table or column, a changed default, and on SQLite a rebuilt table, which used to write a changed default or a rebuilt table's defaults unread. Each element of a list default is read the same way, and the message names the element's position: `Column "post"."tags" has a default (element 2) its codec pg/text@1 refuses: pg/text@1 JSON value must be a string`. A NULL element stays NULL. The codecs that carry PostgreSQL's own date and time text, those of `DateString`, `TimeString`, `TimestampString`, `TimestamptzString` and `Timetz`, read only a date or time in ISO 8601 or as PostgreSQL writes it, so such a default as `'now'`, which PostgreSQL would read once when it creates the table, is refused the same way. An `.include()` of such a column reads the text PostgreSQL writes in the ISO DateStyle, its default; on a server set to another DateStyle it now fails with `RUNTIME.DECODE_FAILED`, where it passed the text through. Set `DateStyle` to `ISO` on that server.
|
|
1661
|
+
|
|
1662
|
+
## `psl-values-checked-by-codecs`
|
|
1663
|
+
|
|
1664
|
+
The PSL reader reads each literal default, and each member of a SQL `enum`, with the column's codec, so the stricter codecs refuse schemas that loaded before. A value of another kind is refused: a text codec takes a string, an integer codec an integer in its range, `Boolean` `true` or `false`, and `Uuid` a UUID. On PostgreSQL the codecs also check what the column stores:
|
|
1665
|
+
|
|
1666
|
+
- `VarChar(n)` and `Char(n)` take at most n characters, counted by code point, and a `Char` value's trailing spaces do not count. A `Char` without a length is `character(1)`, so it takes one character.
|
|
1667
|
+
- `Bit(n)` takes exactly n bits and `VarBit(n)` at most n, and a bit column without a length is `bit(1)`.
|
|
1668
|
+
- `Numeric(p, s)` takes a value it stores without rounding.
|
|
1669
|
+
- `Int`, `sql/int@1` and an enum whose members are integers take an integer from -2147483648 to 2147483647, and `SmallInt` one from -32768 to 32767.
|
|
1670
|
+
- `Real` takes a finite number only if float4 holds it, neither overflowing to an infinity nor becoming 0.
|
|
1671
|
+
|
|
1672
|
+
Such a default used to load, and the migration planned and applied; the first insert that used the default then failed. A `Char` or bit column without a length, which did not apply on PostgreSQL whatever its default, now applies as `character(1)` or `bit(1)`. SQLite does not enforce a declared length, so on SQLite the char and varchar codecs take text of any length. Each of these is now refused at `contract emit`:
|
|
1673
|
+
|
|
1674
|
+
| Schema | Diagnostic |
|
|
1675
|
+
| --- | --- |
|
|
1676
|
+
| `enum P { @@type("pg/int4@1") Low = "low" }` | `PSL_EXTENSION_INVALID_VALUE`: `enum "P" member "Low" was rejected by codec "pg/int4@1": pg/int4@1 JSON value must be an integer from -2147483648 to 2147483647` |
|
|
1677
|
+
| the same enum with a bare `Low` | `PSL_ENUM_BARE_MEMBER_NON_STRING_CODEC`: `enum "P" member "Low" has no value and codec "pg/int4@1" does not accept a bare name as input` |
|
|
1678
|
+
| `enum P { @@type("pg/text@1") Low = 1 }` | `PSL_EXTENSION_INVALID_VALUE`, `enum "P" member "Low": pg/text has no cast from pg/int2; it casts from nothing` |
|
|
1679
|
+
| an enum without `@@type` whose integer members include one outside -2147483648 to 2147483647, such as `Low = 3000000000` | `PSL_EXTENSION_INVALID_VALUE`, `enum "P" member "Low": pg/int4 has no cast from pg/int8; it casts from pg/int2` |
|
|
1680
|
+
| `u Uuid @default("nope")` | `PSL_INVALID_LITERAL`, `"nope" is not a UUID: PostgreSQL reads 32 hexadecimal digits, with a hyphen after any group of four and optionally in braces.` |
|
|
1681
|
+
| `s VarChar(3) @default("toolong")` | `PSL_INVALID_DEFAULT_LITERAL`, `sql/varchar@1 JSON value must be a string of at most 3 characters` |
|
|
1682
|
+
| `c Char @default("abc")` on PostgreSQL | `PSL_INVALID_DEFAULT_LITERAL`, `sql/char@1 JSON value must be a string of at most 1 character before any trailing spaces` |
|
|
1683
|
+
| `enum P { @@type("sql/int@1") Low = 3000000000 }` on PostgreSQL | `PSL_EXTENSION_INVALID_VALUE`, `enum "P" member "Low": pg/int4 has no cast from pg/int8; it casts from pg/int2` |
|
|
1684
|
+
| `n Numeric(5, 2) @default(1.555)` | `PSL_INVALID_DEFAULT_LITERAL`, `pg/numeric@1 JSON value must be a decimal string that numeric(5, 2) stores without rounding` |
|
|
1685
|
+
|
|
1686
|
+
Give each enum member a value its codec takes, and each default a value its column's type holds unchanged. A `Uuid` default is still read in any form PostgreSQL reads; see `uuid-defaults-stored-as-postgresql-writes`.
|
|
1687
|
+
|
|
1688
|
+
## `value-object-default-matches-composite-type`
|
|
1689
|
+
|
|
1690
|
+
A literal default on a field typed by a composite type used to be stored whatever its shape. It is now checked, naming the path that is wrong, as in `Field "User.home.street"`:
|
|
1691
|
+
|
|
1692
|
+
- A single value object takes a JSON object, and a list of them a JSON array: `` homes Address[] @default(json`{"street": "x"}`) `` is refused; write `@default([])` or `` @default(json`[{"street": "x"}]`) ``. JSON `null` is taken when the field is optional. `PSL_VALUE_TYPE_INCOMPATIBLE`.
|
|
1693
|
+
- A key that is not a member is refused, and so is a missing member that is not optional, and `null` for a member that is not optional. `PSL_VALUE_TYPE_INCOMPATIBLE`.
|
|
1694
|
+
- The default holds each member in the form its codec stores, so the member's codec must read the value. A `Decimal`, `Numeric(p, s)` or `BigInt` member takes a decimal string, `"1.5"`, and a number is refused; a `String` member takes a JSON string, so `"street": 1` is refused; a `DateTime` member takes a date and time string; a `Json` member takes any JSON value. `PSL_INVALID_DEFAULT_LITERAL`, with the codec's message.
|
|
1695
|
+
- A member typed by an enum takes only the enum's values: `PSL_INVALID_DEFAULT_LITERAL`, `Expected one of:` the values.
|
|
1696
|
+
- Nested value objects are checked the same way.
|
|
1697
|
+
|
|
1698
|
+
Correct the value the diagnostic names.
|
|
1699
|
+
|
|
1700
|
+
## `composite-type-attributes-refused`
|
|
1701
|
+
|
|
1702
|
+
An attribute inside a `type` block was ignored: `street String @default("x")` stored no default, and `@@map` mapped nothing. Each is now refused, `PSL_UNSUPPORTED_FIELD_ATTRIBUTE` on a member and `PSL_UNSUPPORTED_COMPOSITE_TYPE_ATTRIBUTE` on the type. Remove the attribute. To give a value object a default, write it on the model field as a whole value, such as `` home Address @default(json`{"street": "x"}`) ``.
|
|
1703
|
+
|
|
1704
|
+
## `uuid-defaults-stored-as-postgresql-writes`
|
|
1705
|
+
|
|
1706
|
+
A `Uuid` default may be written in any form PostgreSQL reads: either case, with or without a hyphen after any group of four digits, and optionally in braces. The contract now stores it as PostgreSQL writes it, in lower case and hyphenated 8-4-4-4-12, and so does a TypeScript `.default()` on a `pg/uuid@1` column, so the applied default verifies against the database with no difference.
|
|
1707
|
+
|
|
1708
|
+
Earlier versions stored such a default as written. The database stores the lower-case form, so the check that runs after the change is applied failed: `db init`, `db update` and `db migrate` stopped with `MIGRATION.RUNNER_FAILED` and rolled the change back. The database has none of the changes that contract adds, and no marker for it. With this version, a `contract.json` that still holds such a default stops `db init`, `db update` and `migration plan` with `CONTRACT.DEFAULT_INVALID`, as `codecs-check-stored-json` describes.
|
|
1709
|
+
|
|
1710
|
+
Emit the contract again with this version. The stored default changes, and with it the storage hash. Then:
|
|
1711
|
+
|
|
1712
|
+
- For a project kept with `db init` or `db update`, run the command that failed again. It applies the contract, and `db verify` then passes.
|
|
1713
|
+
- For a project with migrations, delete the migration package that never applied: its directory under `migrations/app/`, and its contract snapshot `migrations/snapshots/<hash>/`, where `<hash>` is the `to` hash in the package's `migration.json`. Then run `prisma migration plan` and `prisma db migrate`. Left in place, the package stays in the migration graph, ending at a contract no database reaches.
|
|
1714
|
+
|
|
1715
|
+
## `domain-types-match-their-columns`
|
|
1716
|
+
|
|
1717
|
+
Run `prisma contract emit`. `contract.json` and `contract.d.ts` gain these entries in the domain half:
|
|
1718
|
+
|
|
1719
|
+
| PSL | Added to the field's domain entry |
|
|
1720
|
+
| --- | --- |
|
|
1721
|
+
| `code Short`, with `types { Short = VarChar(10) }` (also `Short[]`) | `"typeParams": { "length": 10 }` on `type` |
|
|
1722
|
+
| `roles Role[]`, where `Role` is an `enum` | `"valueSet": { "plane": "domain", "entityKind": "enum", "namespaceId": "public", "entityName": "Role" }`, as `role Role` already had |
|
|
1723
|
+
| composite type member `amount Numeric(10, 2)` (also a list, or a named type) | `"typeParams": { "precision": 10, "scale": 2 }` on `type` |
|
|
1724
|
+
| composite type member `role Role` or `roles Role[]` | the same `valueSet` as a model field of that enum |
|
|
1725
|
+
|
|
1726
|
+
A named type without parameters, such as `Email = String`, adds nothing.
|
|
1727
|
+
|
|
1728
|
+
In `contract.d.ts`, a composite type's output type (`AddressOutput`) now gives a member with type parameters the parameterized output type a model field of that type has, such as `Varchar<10>` or `Numeric<10, 2>`, instead of the codec's plain output type. These are branded strings, so code that builds such an output object from plain strings, such as a test fixture or a mock, no longer type-checks. Build the value as the ORM returns it, or type it with the composite type's input type (`AddressInput`), which is unchanged.
|
|
1729
|
+
|
|
1730
|
+
Migration snapshots under `migrations/snapshots/<hash>/` need no change for this. Migration commands read only their storage half, which this change leaves unchanged. The script of `contract-stores-data-type` rewrites the storage half and the snapshots separately.
|
|
1731
|
+
|
|
1732
|
+
`prisma contract print` now expects a field typed by a parameterized named type, and an enum list field, to carry these domain entries. It refuses a contract emitted before this change that lacks them. Re-emit it first.
|
|
1733
|
+
|
|
1734
|
+
A pack that ships a contract with such fields re-emits it with `build:contract-space` (`prisma contract emit`).
|
|
1735
|
+
|
|
1736
|
+
## `default-renderer-receives-data-type`
|
|
1737
|
+
|
|
1738
|
+
```ts
|
|
1739
|
+
// before
|
|
1740
|
+
const renderDefault: DefaultRenderer = (def, column) => render(def);
|
|
1741
|
+
renderDefaultLiteral(value, { many: true, nativeType: 'text', dataTypeId: 'pg/text' });
|
|
1742
|
+
|
|
1743
|
+
// after
|
|
1744
|
+
const renderDefault: DefaultRenderer = (def, column, type) => render(def, type.baseTypeName);
|
|
1745
|
+
renderDefaultLiteral(value, { many: { elementNullable: false }, baseTypeName: 'text', dataType: 'pg/text' });
|
|
1746
|
+
```
|
|
1747
|
+
|
|
1748
|
+
`baseTypeName` is the type's written name without parameters, for example `jsonb` or `varchar`. Compare `dataType`, for example `pg/jsonb`, when the decision depends on which type the column stores.
|
|
1749
|
+
|
|
1750
|
+
## `ddl-column-default-visitor-removed`
|
|
1751
|
+
|
|
1752
|
+
Code that dispatched a column default through `accept` reads its `kind` instead:
|
|
1753
|
+
|
|
1754
|
+
```ts
|
|
1755
|
+
// before
|
|
1756
|
+
const sql = node.accept({ literal: (n, ctx) => renderLiteral(n, ctx.nativeType), function: (n) => n.expression }, { nativeType: 'jsonb' });
|
|
1757
|
+
|
|
1758
|
+
// after
|
|
1759
|
+
const sql = node.kind === 'literal' ? renderLiteral(node, 'jsonb') : node.expression;
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
## `ddl-nodes-hold-opaque-sql`
|
|
1763
|
+
|
|
1764
|
+
SQL that Prisma places inside a larger statement now travels as an `OpaqueSql` value, exported with `opaqueSql` and `renderOpaqueSql` from `@internal/sql-relational-core/ast`. These types changed:
|
|
1765
|
+
|
|
1766
|
+
| Type | Field | Was | Is |
|
|
1767
|
+
| --- | --- | --- | --- |
|
|
1768
|
+
| `FunctionColumnDefault` | `expression` | `string` | `OpaqueSql` |
|
|
1769
|
+
| `CheckExpressionConstraint` | `expression` | `string` | `OpaqueSql` |
|
|
1770
|
+
| `PostgresCreatePolicy` | `using`, `withCheck` | `string \| undefined` | `OpaqueSql \| undefined` |
|
|
1771
|
+
| `PostgresCreateIndex` | `where` | `string \| undefined` | `OpaqueSql \| undefined` |
|
|
1772
|
+
| `PostgresCreateIndex` | `elements.expression` | `string` | `OpaqueSql` |
|
|
1773
|
+
| `DdlIndexElements` (`@internal/target-postgres/ddl`) | `expression` | `string` | `OpaqueSql` |
|
|
1774
|
+
|
|
1775
|
+
Code that constructs one of these nodes directly wraps the string:
|
|
1776
|
+
|
|
1777
|
+
```diff
|
|
1778
|
+
- new FunctionColumnDefault('now()')
|
|
1779
|
+
+ new FunctionColumnDefault(opaqueSql('now()'))
|
|
1780
|
+
```
|
|
1781
|
+
|
|
1782
|
+
The factories `fn` and `checkExpression` still take strings, as do `createPolicy` and `createIndex` inside the Postgres target. Prefer a factory over a constructor where one is exported:
|
|
1783
|
+
|
|
1784
|
+
```diff
|
|
1785
|
+
- new CheckExpressionConstraint({ name: 'chk', expression: 'price > 0' })
|
|
1786
|
+
+ checkExpression('chk', 'price > 0')
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
`createIndex` now takes its element list as `CreateIndexElements`, which holds strings and is exported from `@internal/target-postgres/ddl` next to `DdlIndexElements`. Code that typed the elements it passes to `createIndex` as `DdlIndexElements` uses `CreateIndexElements` instead.
|
|
1790
|
+
|
|
1791
|
+
Code that reads `.expression`, `.using`, `.withCheck` or `.where` of these nodes now reads `.text`. The TypeScript compiler reports a read only where the value goes to a `string`, such as a comparison with a string or a `string` parameter. It does not report a read placed in a template string, which renders `[object Object]`, or passed to a function that accepts any value, such as `JSON.stringify` or a code generator. Search your code for these reads and check each one.
|
|
1792
|
+
|
|
1793
|
+
An adapter that renders these nodes into SQL renders the SQL with `renderOpaqueSql(node.expression)`, not `node.expression.text`. `renderOpaqueSql` ends text that contains `--` with a line break, so a line comment on the last line cannot comment out the rest of the statement.
|
|
1794
|
+
|
|
1795
|
+
## `adapter-writes-column-defaults`
|
|
1796
|
+
|
|
1797
|
+
The control adapter writes every column default that DDL writes, for a new table, a new column, a changed default and a rebuilt SQLite table, through one method on `ExecuteRequestLowerer`, which `SqlControlAdapter` extends (`family/control-adapter` of `@prisma/orm-postgres`, `@prisma/orm-sqlite` and `@prisma/orm-family-sql`):
|
|
1798
|
+
|
|
1799
|
+
```typescript
|
|
1800
|
+
renderColumnDefault(column: DdlColumn, table: string): Promise<string>
|
|
1801
|
+
```
|
|
1802
|
+
|
|
1803
|
+
It returns the `DEFAULT …` clause for the column, or `''` when the column has none or writes it another way, as an autoincrement column does. It reads a literal default, and each element of a list default, with the column's codec first, so a value the codec refuses is `CONTRACT.DEFAULT_INVALID` naming the table and the column.
|
|
1804
|
+
|
|
1805
|
+
- **An `ExecuteRequestLowerer` or `SqlControlAdapter` implementation** must add the method, and so must a fake lowerer in tests. An adapter returns the clause its CREATE TABLE writes for the column. To read a literal default with the column's codec, use `encodeLiteralDefault` and `encodeListLiteralDefault` from `relational-core/ast`. A fake that writes no defaults can return `''`:
|
|
1806
|
+
|
|
1807
|
+
```typescript
|
|
1808
|
+
const lowerer: ExecuteRequestLowerer = {
|
|
1809
|
+
lower: () => ({ sql: '', params: [] }),
|
|
1810
|
+
lowerToExecuteRequest: async () => ({ sql: '', params: [] }),
|
|
1811
|
+
renderColumnDefault: async () => '',
|
|
1812
|
+
};
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
- **`buildColumnDefaultSql`** is removed from `target/planner-ddl-builders` of `@prisma/orm-postgres` and `@prisma/orm-target-postgres`. Build the column with `col`, `lit` and `fn` from `relational-core/contract-free`, and ask the adapter for the clause:
|
|
1816
|
+
|
|
1817
|
+
```typescript
|
|
1818
|
+
// before
|
|
1819
|
+
const clause = buildColumnDefaultSql({ kind: 'literal', value: 'member' }, { nativeType: 'text' });
|
|
1820
|
+
// after
|
|
1821
|
+
const clause = await adapter.renderColumnDefault(
|
|
1822
|
+
col('role', 'text', { default: lit('member'), codecRef: { codecId: 'pg/text@1' } }),
|
|
1823
|
+
'user',
|
|
1824
|
+
);
|
|
1825
|
+
```
|
|
1826
|
+
|
|
1827
|
+
- **`SetDefaultCall`** (`target/op-factory-call` of `@prisma/orm-postgres` and `@prisma/orm-target-postgres`) takes the column instead of its name and the SQL text:
|
|
1828
|
+
|
|
1829
|
+
```typescript
|
|
1830
|
+
// before
|
|
1831
|
+
new SetDefaultCall('public', 'user', 'role', "DEFAULT 'member'", 'widening');
|
|
1832
|
+
// after
|
|
1833
|
+
new SetDefaultCall('public', 'user', col('role', 'text', { default: lit('member'), codecRef: { codecId: 'pg/text@1' } }), 'widening');
|
|
1834
|
+
```
|
|
1835
|
+
|
|
1836
|
+
## `migration-ts-column-defaults`
|
|
1837
|
+
|
|
1838
|
+
A `migration.ts` no longer carries a column default as SQL text. The control adapter writes the `DEFAULT …` clause for every statement, the same way for a new table, a new column, a changed default and a rebuilt SQLite table, and it reads a literal default with the column's codec first. A `migration.ts` that sets a default the codec refuses fails when it runs, with `CONTRACT.DEFAULT_INVALID` naming the table and the column.
|
|
1839
|
+
|
|
1840
|
+
On PostgreSQL, `setDefault` takes the column and its default:
|
|
1841
|
+
|
|
1842
|
+
```typescript
|
|
1843
|
+
// before
|
|
1844
|
+
this.setDefault({ table: 'user', column: 'role', defaultSql: "DEFAULT 'member'" })
|
|
1845
|
+
// after
|
|
1846
|
+
this.setDefault({ table: 'user', column: col('role', 'text', { default: lit('member'), codecRef: { codecId: 'pg/text@1' } }) })
|
|
1847
|
+
```
|
|
1848
|
+
|
|
1849
|
+
On SQLite, a column in `addColumn` or `recreateTable` carries the default and its codec, and a `recreateTable` postcheck that checks a default names the column:
|
|
1850
|
+
|
|
1851
|
+
```typescript
|
|
1852
|
+
// before
|
|
1853
|
+
{ name: 'role', typeSql: 'TEXT', defaultSql: "DEFAULT 'member'", nullable: false }
|
|
1854
|
+
{ description: 'verify "role" default on "user"', sql: "SELECT COUNT(*) > 0 FROM pragma_table_info('user') WHERE ..." }
|
|
1855
|
+
// after
|
|
1856
|
+
{ name: 'role', typeSql: 'TEXT', default: { kind: 'literal', value: 'member' }, codecRef: { codecId: 'sqlite/text@1' }, nullable: false }
|
|
1857
|
+
{ description: 'verify "role" default on "user"', columnDefault: 'role' }
|
|
1858
|
+
```
|
|
1859
|
+
|
|
1860
|
+
An applied migration needs nothing: `db migrate` applies `ops.json`, which holds the SQL. Change a `migration.ts` this way only when you run it again to write `ops.json`. `node migration.ts` does not check types, so an earlier file still runs; `setDefault`, `addColumn` and `recreateTable` then refuse a `defaultSql` with `MIGRATION.OPERATION_OPTION_REMOVED`, naming the table and the column, rather than leave the default out. Rewrite the call as shown, or, if the migration is not applied, delete its package and run `migration plan` again.
|
|
1861
|
+
|
|
1862
|
+
The `migration.ts` that `migration plan` writes for a new SQLite table now gives each column its `codecRef`, so running it writes the same `ops.json` as the plan.
|
|
1863
|
+
|
|
1864
|
+
## `rename-check-constraint-call-is-rename-constraint-call`
|
|
1865
|
+
|
|
1866
|
+
Find references to the class `RenameCheckConstraintCall`, imported from `@prisma/orm-postgres/target/op-factory-call` or `@internal/target-postgres/op-factory-call`. The class is now `RenameConstraintCall`. It also renames primary keys, unique constraints and foreign keys, so it takes the constraint kind as a new third constructor argument: `'primaryKey'`, `'unique'`, `'foreignKey'` or `'checkConstraint'`.
|
|
1867
|
+
|
|
1868
|
+
- Rename the import and every reference to `RenameConstraintCall`.
|
|
1869
|
+
- Change `new RenameCheckConstraintCall(schema, table, from, to)` to `new RenameConstraintCall(schema, table, 'checkConstraint', from, to)`.
|
|
1870
|
+
- Change a comparison of a call's `factoryName` with `'renameCheckConstraint'`, or a `case 'renameCheckConstraint':` over it, to test `factoryName === 'renameConstraint' && call.kind === 'checkConstraint'`.
|
|
1871
|
+
|
|
1872
|
+
The operation a check-constraint rename produces keeps its id, label and SQL. It now renders as `this.renameConstraint({ ..., kind: "checkConstraint", ... })`. Migration files that call `this.renameCheckConstraint({ ... })` keep working; leave them unchanged.
|
|
1873
|
+
|
|
1874
|
+
## `control-family-instance-sign-spaces`
|
|
1875
|
+
|
|
1876
|
+
A family that implements `ControlFamilyInstance` adds `signSpaces`:
|
|
1877
|
+
|
|
1878
|
+
```ts
|
|
1879
|
+
signSpaces(options: {
|
|
1880
|
+
readonly driver: ControlDriverInstance<TFamilyId, string>;
|
|
1881
|
+
readonly spaces: readonly SpaceToSign[];
|
|
1882
|
+
}): Promise<readonly SpaceSignature[]>;
|
|
1883
|
+
```
|
|
1884
|
+
|
|
1885
|
+
`SpaceToSign` is `{ space, contract, expected }`, where `expected` is the marker `db sign` read before it verified the space. The method writes each space's marker with its contract's hashes and returns one `SpaceSignature` per space: `{ status, space, contract: { storageHash, profileHash } }`, where `status` is `created`, `updated` (with `previous`, the hashes the marker held) or `unchanged`. It writes a space's marker only while the marker still holds `expected`, and returns `{ status: 'conflict', space, contract, expected, found }` for a space whose marker changed, whether it finds that on reading the marker or when its compare-and-swap write fails. It does not verify the schema; `db sign` verifies every space before it calls the method. A SQL family's control adapter implements `lockMarker(driver)`, which takes the one lock the target's migration runner holds while it reads and writes markers. The SQL family writes every marker in one transaction, so a failed write leaves every marker as it was; the Mongo family writes them one by one, and running `db sign` again finishes the job.
|
|
1886
|
+
|
|
1887
|
+
`ControlFamilyInstance.sign`, `ControlClient.sign`, `SignOptions` and `SignDatabaseResult` are removed: a family that implemented `sign` deletes it and keeps `signSpaces`, and code that called `client.sign({ contract })` calls `client.dbSign({ contract, migrationsDir })`, which verifies every contract space and signs each one that verified, as `db sign` does.
|
|
1888
|
+
|
|
1889
|
+
## `db-sign-signs-extension-spaces`
|
|
1890
|
+
|
|
1891
|
+
`db sign` signs the contract space of every extension in the project together with the application's, and signs a space only when its schema verifies; a space that does not verify is reported with its differences and the command exits with code 4. If your extension's documentation explains what happens when a user's database does not match your contract space, add that `db sign` does not sign the space either. The Supabase extension's `src/contract/CONTRACT-FIDELITY.md` adds this sentence at the end of the paragraph that says a database with a different constraint set fails verify:
|
|
1892
|
+
|
|
1893
|
+
> `db sign` signs a contract space only when its schema verifies, so such a database cannot be signed for this pack's space either: `db sign` signs the application's space, reports this one with its differences and exits 4.
|
|
1894
|
+
|
|
1895
|
+
## `cli-error-from-caught`
|
|
1896
|
+
|
|
1897
|
+
`mapCaughtMigrationError(error)`, exported from `@prisma/orm-toolchain/cli/control-api` (`@internal/cli/control-api`), returned a CLI error unchanged and `null` for anything else, which the caller wrapped as `CLI.UNEXPECTED`. `errorFromCaught(error, why)`, exported from the same place, does the whole job: it returns a CLI error unchanged, reports any other error with a structured `NAMESPACE.SUBCODE` code as itself, reports anything else as `CLI.UNEXPECTED` with `why` given the error's message, and throws an `InternalError` again. Replace `mapCaughtMigrationError(error) ?? errorUnexpected(...)` with `errorFromCaught(error, (message) => ...)`. A caller that holds a database connection string passes it as `errorFromCaught(error, why, { connection })`, which removes it from every field of the reported error.
|
|
1898
|
+
|
|
1899
|
+
## `adapter-control-loads-temporal-polyfill`
|
|
1900
|
+
|
|
1901
|
+
The adapter contributes the PostgreSQL type constructors, which it imports from the target's control entry, and that entry sets up the fallback `Temporal` from `temporal-polyfill`. So the adapter's control entry, `@prisma/orm-postgres/adapter/control` or `@prisma/orm-target-postgres/adapter/control`, now loads `temporal-polyfill` as well. Both packages declare it as a required peer dependency, and npm, pnpm and bun install it automatically.
|
|
1902
|
+
|
|
1903
|
+
For each Yarn project whose tests or tooling load the adapter's control entry, `detection` finds the import. If the project's `package.json` does not already list `temporal-polyfill`, add `"temporal-polyfill": "^1.0.4"` to its `devDependencies` and reinstall. Without it, a test or script that loads the adapter's control entry fails because Node.js cannot find the package `temporal-polyfill`.
|
|
1904
|
+
|
|
1905
|
+
## `parameter-casts-use-base-names`
|
|
1906
|
+
|
|
1907
|
+
A cast written into query text names the data type's base name and never its parameters, because an explicit cast to `varchar(n)` truncates and to `numeric(p,s)` rounds:
|
|
1908
|
+
|
|
1909
|
+
| Before | After |
|
|
1910
|
+
| --- | --- |
|
|
1911
|
+
| `$1::integer` | `$1::int4` |
|
|
1912
|
+
| `$1::smallint` | `$1::int2` |
|
|
1913
|
+
| `$1::bigint` | `$1::int8` |
|
|
1914
|
+
| `$1::real` | `$1::float4` |
|
|
1915
|
+
| `$1::double precision` | `$1::float8` |
|
|
1916
|
+
| `$1::boolean` | `$1::bool` |
|
|
1917
|
+
| `$1::integer[]` | `$1::int4[]` |
|
|
1918
|
+
|
|
1919
|
+
Update test expectations and snapshots that assert such text. Extension types keep their names (`$1::vector`, `$1::geometry`).
|
|
1920
|
+
|
|
1921
|
+
## `sql-builder-reads-codec-descriptors`
|
|
1922
|
+
|
|
1923
|
+
When a computed projection such as `fns.eq` or `fns.raw` names a codec id but no codec ref, `sql()` asks `context.codecDescriptors.descriptorFor(codecId)` whether the composed stack registers that id. A hand-built `ExecutionContext` that a test passes to `sql()` must therefore carry `codecDescriptors`. Add `codecDescriptors: { descriptorFor: () => undefined }` to the stub, or return a descriptor for the ids the test expects to decode. A context from `createExecutionContext` already has it.
|
|
1924
|
+
|
|
1925
|
+
## `select-ast-options-carry-locking`
|
|
1926
|
+
|
|
1927
|
+
`SelectAstOptions` now has a required `locking: ReadonlyArray<LockingClause> | undefined`. Where you construct `new SelectAst({ ... })` from an existing select's fields, add `locking: ast.locking` when you rebuild an existing select. Pass `locking: undefined` only for a select you build from nothing. Setting it to `undefined` while rebuilding an existing select removes the caller's `FOR UPDATE` without an error.
|
|
1928
|
+
|
|
1929
|
+
## `render-lowered-sql-takes-capabilities`
|
|
1930
|
+
|
|
1931
|
+
`renderLoweredSql(ast, contract, codecDescriptorRegistry)` is now `renderLoweredSql(ast, contract, codecDescriptorRegistry, dataTypeLookup, capabilities)`. The renderer writes PostgreSQL parameter casts from the data type lookup (see `parameter-casts-use-base-names`): pass the stack's, or the target's own from `createPostgresBuiltinDataTypeLookup()` in `@internal/target-postgres/data-types`. It refuses a locking clause whose strength or option the given capabilities do not report. To render as the Postgres adapter does, pass its capabilities:
|
|
1932
|
+
|
|
1933
|
+
```ts
|
|
1934
|
+
import { postgresAdapterCapabilities } from '@internal/adapter-postgres/adapter';
|
|
1935
|
+
import { renderLoweredSql } from '@internal/adapter-postgres/sql-renderer';
|
|
1936
|
+
|
|
1937
|
+
renderLoweredSql(ast, contract, codecDescriptorRegistry, dataTypeLookup, postgresAdapterCapabilities);
|
|
1938
|
+
```
|
|
1939
|
+
|
|
1940
|
+
## `sql-runtime-close-refusal-option`
|
|
1941
|
+
|
|
1942
|
+
`close()` on a SQL runtime waits for the work already in flight and refuses runtime-scope work that starts later with `DRIVER.NOT_CONNECTED` ("Runtime is closed"). The new `closeRefusal` option says when the refusal begins: `'at-once'` refuses from the call of `close()`, and `'when-idle'` refuses once the runtime has been idle for one turn of the event loop, so work that keeps it busy from the close onward is admitted.
|
|
1943
|
+
|
|
1944
|
+
The key is optional, so existing code compiles unchanged. In each place that constructs a runtime class that extends `SqlRuntimeBase`, or builds a `RuntimeOptions` object for one, decide whether to add it:
|
|
1945
|
+
|
|
1946
|
+
1. If the runtime belongs to a client or service that many callers share, such as a pooled client, pass `closeRefusal: 'at-once'`.
|
|
1947
|
+
2. If the runtime belongs to one request or one scope, such as a per-request connection, leave the key out or pass `closeRefusal: 'when-idle'`.
|
|
1948
|
+
|
|
1949
|
+
## `writes-on-a-conditional-collection-are-refused`
|
|
1950
|
+
|
|
1951
|
+
`where`, `orderBy`, `limit`, `offset`, `distinct`, `distinctOn`, `cursor` and `include` now return the collection they were called on, with what they establish added to its type as a fact. A custom collection class keeps its methods through the chain, so `db.Post.where({ userId }).withTitle('orm')` and `db.Post.include('user').withTitle('orm')` compile.
|
|
1952
|
+
|
|
1953
|
+
The facts have names. Write a filtered collection's type as `Filtered<C>` and an ordered one as `Ordered<C>`. `Filtered<C>` is `C & HasWhere`, and `HasWhere` is the name error messages print. Import the names from `@internal/sql-orm-client`, or from the `orm-client` entry of the facade your extension depends on.
|
|
1954
|
+
|
|
1955
|
+
The collection changes from here to `custom-collection-methods-chain`, `scope` included, apply to the SQL ORM client only. The MongoDB ORM client did not change; skip matches in code that uses it. `variant-takes-discriminator-value` applies to both.
|
|
1956
|
+
|
|
1957
|
+
A write needs a collection that is filtered on every code path. Code that filters only on some paths compiled before and no longer does:
|
|
1958
|
+
|
|
1959
|
+
```ts
|
|
1960
|
+
const posts = search ? db.Post.withTitle(search) : db.Post;
|
|
1961
|
+
await posts.deleteAll(); // error: The 'this' context of type 'PostCollection' is not assignable to method's 'this' of type 'HasWhere'
|
|
1962
|
+
```
|
|
1963
|
+
|
|
1964
|
+
The same applies to an `if` with an early return, a `switch`, a loop, and a `let` reassigned in an `if`. Make the write only where the filter was applied, or filter on every path:
|
|
1965
|
+
|
|
1966
|
+
```diff
|
|
1967
|
+
- const posts = search ? db.Post.withTitle(search) : db.Post;
|
|
1968
|
+
- await posts.deleteAll();
|
|
1969
|
+
+ if (search) {
|
|
1970
|
+
+ await db.Post.withTitle(search).deleteAll();
|
|
1971
|
+
+ }
|
|
1972
|
+
```
|
|
1973
|
+
|
|
1974
|
+
If the code relied on deleting or updating every row when there is no filter, that was the unsafe case the check now refuses. State the intent with an explicit filter instead.
|
|
1975
|
+
|
|
1976
|
+
## `writes-refuse-what-they-would-ignore`
|
|
1977
|
+
|
|
1978
|
+
`updateAll`, `updateAndCount`, `deleteAll` and `deleteAndCount` change every row that matches the filter. They ignored a `limit`, an `offset`, a `cursor`, `distinct` and `distinctOn` on the collection, so a chain such as `db.orm.public.Post.where(...).limit(10).deleteAll()` deleted every matching row, not ten, and one with `.cursor({ id })` deleted the rows before the cursor too. They now throw `ORM.ARGUMENT_INVALID` instead. An order does not change which rows they change, and they still accept it. Remove what the write would ignore, or read the rows and change them by their ids:
|
|
1979
|
+
|
|
1980
|
+
```diff
|
|
1981
|
+
- await db.orm.public.Post.where({ userId }).limit(10).deleteAll();
|
|
1982
|
+
+ const ids = (await db.orm.public.Post.where({ userId }).select('id').limit(10).all()).map((p) => p.id);
|
|
1983
|
+
+ await db.orm.public.Post.where((p) => p.id.in(ids)).deleteAll();
|
|
1984
|
+
```
|
|
1985
|
+
|
|
1986
|
+
`update` and `delete` change one row, the one `first()` returns, so the order, the offset, the cursor, `distinct` and `distinctOn` choose it; they are unchanged, except after `limit(0)`. `first()` replaces the limit with 1, so after `limit(0)` they changed one row; they now change none and return `null`. A `limit(0)` that a request can set, such as a page size of zero, no longer writes.
|
|
1987
|
+
|
|
1988
|
+
The exception is `update` with a relation callback, such as `posts: (posts) => posts.connect(...)`. It finds its row by the filter alone and ignored the order, the limit, the offset, the cursor, `distinct` and `distinctOn`, so it could change another row than the chain chose. It now throws `ORM.ARGUMENT_INVALID` on a collection with any of them. Find the row first and filter to it:
|
|
1989
|
+
|
|
1990
|
+
```diff
|
|
1991
|
+
- await users.where({ teamId }).orderBy((u) => u.createdAt.asc()).update({ posts: (posts) => posts.connect([{ id: postId }]) });
|
|
1992
|
+
+ const oldest = await users.where({ teamId }).orderBy((u) => u.createdAt.asc()).select('id').first();
|
|
1993
|
+
+ if (oldest) await users.where({ id: oldest.id }).update({ posts: (posts) => posts.connect([{ id: postId }]) });
|
|
1994
|
+
```
|
|
1995
|
+
|
|
1996
|
+
## `cursor-and-distinct-on-check-the-receiver`
|
|
1997
|
+
|
|
1998
|
+
`cursor` and `distinctOn` need an order on the collection they are called on. The check is on the receiver, so a cast on the argument no longer bypasses it:
|
|
1999
|
+
|
|
2000
|
+
```ts
|
|
2001
|
+
await db.Post.cursor({ id } as never).all(); // error: The 'this' context of type 'PostCollection' is not assignable to method's 'this' of type 'HasOrderBy'
|
|
2002
|
+
```
|
|
2003
|
+
|
|
2004
|
+
Add the order the query needs:
|
|
2005
|
+
|
|
2006
|
+
```diff
|
|
2007
|
+
- await db.Post.cursor({ id } as never).all();
|
|
2008
|
+
+ await db.Post.orderBy((post) => post.id.asc()).cursor({ id }).all();
|
|
2009
|
+
```
|
|
2010
|
+
|
|
2011
|
+
Where the query is meant to run without an order, cast the collection instead of the argument:
|
|
2012
|
+
|
|
2013
|
+
```ts
|
|
2014
|
+
import type { Ordered } from '@internal/sql-orm-client';
|
|
2015
|
+
|
|
2016
|
+
const unordered = db.Post as Ordered<typeof db.Post>;
|
|
2017
|
+
await unordered.cursor({ id }).all();
|
|
2018
|
+
```
|
|
2019
|
+
|
|
2020
|
+
## `apply-is-a-collection-member`
|
|
2021
|
+
|
|
2022
|
+
Collections have a new method, `apply(fn)`, which calls `fn` with the collection and returns the result. A custom collection class that declares its own `apply` with another signature no longer compiles. Rename that member and its call sites:
|
|
2023
|
+
|
|
2024
|
+
```diff
|
|
2025
|
+
class PostCollection extends Collection<Contract, 'Post'> {
|
|
2026
|
+
- apply(limit: number) { return this.limit(limit); }
|
|
2027
|
+
+ firstPage(limit: number) { return this.limit(limit); }
|
|
2028
|
+
}
|
|
2029
|
+
```
|
|
2030
|
+
|
|
2031
|
+
An aggregate operation named `apply` is now refused with `ORM.AGGREGATE_OPERATION_RESERVED` when the client is built; rename the operation.
|
|
2032
|
+
|
|
2033
|
+
## `scope-is-a-collection-member`
|
|
2034
|
+
|
|
2035
|
+
Collections have a new method, `scope(body)`, which defines a scope for the collection's model: `db.orm.public.Post.scope((posts) => posts.select('id', 'title'))`. A custom collection class that declares its own `scope` with another signature no longer compiles. Rename that member and its call sites:
|
|
2036
|
+
|
|
2037
|
+
```diff
|
|
2038
|
+
class PostCollection extends Collection<Contract, 'Post'> {
|
|
2039
|
+
- scope(userId: string) { return this.where({ userId }); }
|
|
2040
|
+
+ ownedBy(userId: string) { return this.where({ userId }); }
|
|
2041
|
+
}
|
|
2042
|
+
```
|
|
2043
|
+
|
|
2044
|
+
An aggregate operation named `scope` is now refused with `ORM.AGGREGATE_OPERATION_RESERVED` when the client is built; rename the operation.
|
|
2045
|
+
|
|
2046
|
+
## `overriding-a-chaining-method`
|
|
2047
|
+
|
|
2048
|
+
A class that extends `Collection` and overrides one of the chaining methods, or one of the methods that return rows (`all`, `first`, `create`, `createAll`, `upsert` and the writes), must declare the override with the new signature: a type parameter for the receiver, a `this` parameter of that type, and the result type the base method returns. Call the base method with `call` and explicit type arguments, so that the receiver type passes through:
|
|
2049
|
+
|
|
2050
|
+
```diff
|
|
2051
|
+
class PostCollection extends Collection<Contract, 'Post'> {
|
|
2052
|
+
- override limit(n: number) {
|
|
2053
|
+
- return super.limit(Math.min(n, 100));
|
|
2054
|
+
- }
|
|
2055
|
+
+ override limit<Self>(this: Self, n: number): Self {
|
|
2056
|
+
+ return super.limit.call<Self, [number], Self>(this, Math.min(n, 100));
|
|
2057
|
+
+ }
|
|
2058
|
+
}
|
|
2059
|
+
```
|
|
2060
|
+
|
|
2061
|
+
Only classes that extend the SQL `Collection` are affected; skip matches in other classes.
|
|
2062
|
+
|
|
2063
|
+
## `collection-state-flags-are-boolean`
|
|
2064
|
+
|
|
2065
|
+
`DefaultCollectionTypeState` declares `hasWhere`, `hasOrderBy` and `hasUniqueFilter` as `boolean`, meaning not known. A method that establishes a flag sets it to `true`. Where your code expects `false`, expect `boolean`:
|
|
2066
|
+
|
|
2067
|
+
```diff
|
|
2068
|
+
- type Check = Equal<CollectionTypeStateOf<typeof users>['hasOrderBy'], false>;
|
|
2069
|
+
+ type Check = Equal<CollectionTypeStateOf<typeof users>['hasOrderBy'], boolean>;
|
|
2070
|
+
```
|
|
2071
|
+
|
|
2072
|
+
A type of your own that sets a flag to `false` should set it to `boolean`. The writes still need `hasWhere: true`, and `cursor` and `distinctOn` still need `hasOrderBy: true`.
|
|
2073
|
+
|
|
2074
|
+
## `read-collection-state-and-row-with-helpers`
|
|
2075
|
+
|
|
2076
|
+
`where`, `orderBy` and `include` record what they establish in two declared properties, not in the type arguments of `Collection`. Inferring the third or fourth type argument gives the row and the state the collection started with. Read them with `CollectionRowOf` and `CollectionTypeStateOf` instead:
|
|
2077
|
+
|
|
2078
|
+
```diff
|
|
2079
|
+
- type RowOf<C> = C extends Collection<infer _C, infer _M, infer Row, infer _S> ? Row : never;
|
|
2080
|
+
- type UsersRow = RowOf<typeof users>;
|
|
2081
|
+
+ import type { CollectionRowOf } from '@internal/sql-orm-client';
|
|
2082
|
+
+ type UsersRow = CollectionRowOf<typeof users>;
|
|
2083
|
+
```
|
|
2084
|
+
|
|
2085
|
+
To keep a helper of your own, constrain its parameter, because both helpers require one:
|
|
2086
|
+
|
|
2087
|
+
```ts
|
|
2088
|
+
import type { CollectionRowOf, CollectionTypeStateOf, HasRow, HasTypeState } from '@internal/sql-orm-client';
|
|
2089
|
+
|
|
2090
|
+
type RowOf<C extends HasRow> = CollectionRowOf<C>;
|
|
2091
|
+
type StateOf<C extends HasTypeState> = CollectionTypeStateOf<C>;
|
|
2092
|
+
```
|
|
2093
|
+
|
|
2094
|
+
## `return-type-of-a-chaining-method`
|
|
2095
|
+
|
|
2096
|
+
The chaining methods are generic in their receiver, and `ReturnType` of a generic method uses the constraint of its type parameter. `ReturnType<PostCollection['where']>` is now `HasWhere`, and `ReturnType<PostCollection['limit']>` is `unknown`. Write the type with `Filtered` after `where`, `Ordered` after `orderBy`, and the collection type itself after `limit`, `offset`, `distinct`, `distinctOn` and `cursor`. You can also take `typeof` of a value:
|
|
2097
|
+
|
|
2098
|
+
```diff
|
|
2099
|
+
- type MatchingPosts = ReturnType<PostCollection['where']>;
|
|
2100
|
+
+ import type { Filtered } from '@internal/sql-orm-client';
|
|
2101
|
+
+ type MatchingPosts = Filtered<PostCollection>;
|
|
2102
|
+
```
|
|
2103
|
+
|
|
2104
|
+
`ReturnType` of a method of your own class, such as `ReturnType<PostCollection['withTitle']>`, still works.
|
|
2105
|
+
|
|
2106
|
+
## `chaining-methods-take-no-explicit-type-arguments`
|
|
2107
|
+
|
|
2108
|
+
These methods infer their receiver from the call. With explicit type arguments the receiver is not inferred: `posts.include<'user'>('user')` does not compile, `posts.distinct<['title']>('title')` and `posts.distinctOn<['title']>('title')` fail with "Expected 2 type arguments, but got 1", and `ReturnType<typeof posts.include<'user'>>` is `never`. Drop the type arguments. They are inferred from the arguments, so `distinct<['title']>('title')` becomes `distinct('title')`:
|
|
2109
|
+
|
|
2110
|
+
```diff
|
|
2111
|
+
- const titles = posts.distinct<['title']>('title');
|
|
2112
|
+
+ const titles = posts.distinct('title');
|
|
2113
|
+
```
|
|
2114
|
+
|
|
2115
|
+
To name the type of an include, call `include` on a value and take its type:
|
|
2116
|
+
|
|
2117
|
+
```diff
|
|
2118
|
+
- type WithUser = ReturnType<typeof posts.include<'user'>>;
|
|
2119
|
+
+ const withUser = posts.include('user');
|
|
2120
|
+
+ type WithUser = typeof withUser;
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
## `custom-collection-methods-chain`
|
|
2124
|
+
|
|
2125
|
+
Custom collection methods are now available after `where`, `orderBy`, `limit` and the other chaining methods. Where code on a client built with `orm({ collections })` repeats a class method's body inline after a chaining call on that class, it can call the method. A client without custom classes, such as `db.orm`, has no such methods.
|
|
2126
|
+
|
|
2127
|
+
```diff
|
|
2128
|
+
return db.Post.forUser(userId)
|
|
2129
|
+
- .orderBy((post) => post.createdAt.desc())
|
|
2130
|
+
+ .newestFirst()
|
|
2131
|
+
.limit(limit)
|
|
2132
|
+
.all();
|
|
2133
|
+
```
|
|
2134
|
+
|
|
2135
|
+
Here `newestFirst()` is a method of the application's `PostCollection` whose body is that `orderBy`.
|
|
2136
|
+
|
|
2137
|
+
## `variant-takes-discriminator-value`
|
|
2138
|
+
|
|
2139
|
+
For every `.variant(...)` call matched by `detection`, replace the variant's model name with the discriminator value that variant declares. Read the value from the contract: in PSL it is the second argument of the variant's `@@base(Base, "<value>")`; in the TypeScript builder it is the `value` under the base model's `discriminator.variants.<VariantName>`; in `contract.json` it is `domain.namespaces.<ns>.models.<Base>.variants.<VariantName>.value`.
|
|
2140
|
+
|
|
2141
|
+
```ts
|
|
2142
|
+
// before
|
|
2143
|
+
db.orm.public.Task.variant('Bug');
|
|
2144
|
+
db.orm.events.variant('ViewProductEvent');
|
|
2145
|
+
|
|
2146
|
+
// after, for @@base(Task, "bug") and a ViewProductEvent variant declaring "view-product"
|
|
2147
|
+
db.orm.public.Task.variant('bug');
|
|
2148
|
+
db.orm.events.variant('view-product');
|
|
2149
|
+
```
|
|
2150
|
+
|
|
2151
|
+
Custom collection methods that call `this.variant('<ModelName>')`, and helpers or closures that call `.variant()`, change the same way. When the argument is a variable or a forwarded parameter, follow it back to where the model name is written and replace it there, including in type annotations that list variant model names. Update code comments and READMEs that show `.variant('<ModelName>')` too.
|
|
2152
|
+
|
|
2153
|
+
The type checker catches most old call sites, because the parameter only accepts the base model's declared values. It does not catch a call whose model-name argument happens to equal a declared value, for example a variant model declared with `@@base(Base, "Admin")`. Such a call keeps compiling and now selects the variant declaring that value, so check every call site against the contract rather than relying on type errors.
|
|
2154
|
+
|
|
2155
|
+
A call that passes a value through `as never` or another cast also escapes the type checker. At runtime, a value the base model does not declare, or a `variant()` call on a model without a discriminator, throws `ORM.ARGUMENT_INVALID`. The error names the model and lists its declared values. Previously the call returned the collection unchanged, so the query read every variant. Code that relied on that fallback must stop calling `variant()` in that case.
|
|
2156
|
+
|
|
2157
|
+
A second `.variant()` call on a variant collection is rejected. It no longer replaces the first selection: it is a type error, and at runtime it throws `ORM.OPERATION_UNSUPPORTED` naming the model and the discriminator value already selected. Select each variant from the base collection instead, and keep a reference to the base collection where code needs more than one variant.
|
|
2158
|
+
|
|
2159
|
+
```ts
|
|
2160
|
+
// before
|
|
2161
|
+
const bugs = db.orm.public.Task.variant('Bug');
|
|
2162
|
+
const features = bugs.variant('Feature');
|
|
2163
|
+
|
|
2164
|
+
// after
|
|
2165
|
+
const tasks = db.orm.public.Task;
|
|
2166
|
+
const bugs = tasks.variant('bug');
|
|
2167
|
+
const features = tasks.variant('feature');
|
|
2168
|
+
```
|