@prisma/orm-mongo 8.0.0-rc.18-dev.46 → 8.0.0-rc.19
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/dist/{mongo-static-Be-tE2Ra-a5WLdGKy.mjs → mongo-static-DNXbLhsJ-C9Jn5l2_.mjs} +3 -3
- package/dist/mongo-static-DNXbLhsJ-C9Jn5l2_.mjs.map +1 -0
- package/dist/runtime.mjs +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/static.mjs +1 -1
- package/package.json +8 -8
- package/skills/prisma-8/SKILL.md +1 -1
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/instructions.md +1826 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/client-takes-contract-value/contract-from-json.ts +382 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/data-type-values/data-type-values.ts +1117 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/default-generators-to-on-create/rewrite-default-generators.mjs +86 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/mongo-field-storage-names/mongo-field-storage-names.ts +250 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/remove-nocheck/remove-nocheck.ts +813 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/instructions.md +2000 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/client-takes-contract-value/contract-from-json.ts +382 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/scripts/remove-nocheck/remove-nocheck.ts +813 -0
- package/dist/mongo-static-Be-tE2Ra-a5WLdGKy.mjs.map +0 -1
package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.18-to-8.0.0-rc.19/instructions.md
ADDED
|
@@ -0,0 +1,2000 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.18"
|
|
3
|
+
to: "8.0.0-rc.19"
|
|
4
|
+
changes:
|
|
5
|
+
- id: client-takes-contract-value
|
|
6
|
+
summary: |
|
|
7
|
+
Every client and static entry takes the contract only as a value, `{ contract }`. The `contractJson` option is removed, and so is the type argument on the client call: `postgres<Contract>({ contractJson, url })` no longer compiles. Turn the emitted JSON into a typed value once with `contractFromJson<Contract>(contractJson)` from the facade's `/static` entry (`@prisma/orm-postgres/static`, `@prisma/orm-sqlite/static`, `@prisma/orm-mongo/static`; Supabase uses the Postgres one), export it from the module that creates the client, and pass `{ contract }`. This applies to `postgres()`, `postgresServerless()`, `sqlite()`, `supabase()`, `mongo()`, `postgresStatic()`, `sqliteStatic()`, `mongoStatic()` and `mongoQuery()`. Extension packages that build a client, static entry or `mongoQuery` in their code, examples or tests move the same way. The colocated script moves source files; move test files by hand.
|
|
8
|
+
detection:
|
|
9
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
10
|
+
matches:
|
|
11
|
+
- '(?<![\w$.])(?:postgres|postgresServerless|sqlite|supabase|mongo|postgresStatic|sqliteStatic|mongoStatic|mongoQuery)(?:<[^>()]*(?:<[^>()]*>[^>()]*)*>)?\(\s*\{(?:(?:[^{}]|\{[^{}]*\})*?,)?\s*contractJson\s*[,}:]'
|
|
12
|
+
script: ./scripts/client-takes-contract-value/contract-from-json.ts
|
|
13
|
+
- id: collections-on-the-client
|
|
14
|
+
summary: |
|
|
15
|
+
Not a breaking change. Every SQL client takes your custom collection classes as an option, `collections`, and uses them on `db.orm`, on the ORM of every transaction (`tx.orm`), on every connection a serverless client opens, and on every Supabase role-bound client: `postgres({ contract, url, collections: { Post: PostCollection } })`, then `db.orm.public.Post.published()`. Extension code, examples and tests that build their own ORM client with `orm({ runtime, context, collections })` to reach custom classes still work; replace them with the option and use the client's ORM. The detection matches a call to `orm({ ... })`, or to `orm` imported under another name, that passes `collections`.
|
|
16
|
+
detection:
|
|
17
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
18
|
+
matches:
|
|
19
|
+
- '(?<![\w$.])orm\s*(?:<[^>()]*(?:<[^>()]*>[^>()]*)*>)?\(\s*\{(?:(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*?,)?\s*collections\s*[,}:]'
|
|
20
|
+
- 'import\s*\{[^}]*\borm\s+as\s+([\w$]+)[^}]*\}\s*from\s*[''\"][^''\"]+[''\"][\s\S]*(?<![\w$.])\1\s*(?:<[^>()]*(?:<[^>()]*>[^>()]*)*>)?\(\s*\{(?:(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*?,)?\s*collections\s*[,}:]'
|
|
21
|
+
- id: client-query-and-to-sql
|
|
22
|
+
summary: |
|
|
23
|
+
Additive for code that only uses these types. Every SQL client gets `query(plan)` and `execute(plan)`, with the types a transaction's have, and `toSql(plan)`, which returns `{ sql, params }` with no database. A serverless connection gets `query` and `execute` too; the serverless client, `postgresStatic()`, `sqliteStatic()` and every transaction get `toSql`. `postgres()`, `postgresServerless()`, `sqlite()` and `supabase()` take `mode: 'strict' | 'permissive'`. `supabase()` creates its pool on the first query instead of when it is called. Extension code, examples and tests can move from `db.runtime().query(plan)` to `db.query(plan)`; code that builds its own object of one of the changed types, such as a test double, must add the new members. The detection matches a file that names one of the changed types, or calls `runtime().query(` or `runtime().execute(`.
|
|
24
|
+
detection:
|
|
25
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
26
|
+
matches:
|
|
27
|
+
- '(?<![\w$])(?:PostgresClient|PostgresServerlessClient|PostgresServerlessConnection|PostgresStaticContext|PostgresStaticMembers|PostgresQueryMembers|SqliteClient|SqliteStaticContext|SqliteQueryMembers|RoleBoundDb|ServiceRoleDb|SupabaseInternalDb|PostgresTransactionContext|SqliteTransactionContext|SupabaseTransactionContext|SqliteStaticMembers)(?![\w$])'
|
|
28
|
+
- '\.runtime\(\)\s*\.(?:query|execute)\('
|
|
29
|
+
- id: transaction-query-members
|
|
30
|
+
summary: |
|
|
31
|
+
Additive for code that only uses these types: a transaction now offers the client's query members (`sql`, `orm`, `raw`, `prepare`, `query`, `execute`) on every SQL client, a Supabase role-bound client gains `prepare`, and SQLite's `db.orm` gains `fragment`. Code that builds its own object of one of the changed types must add the new members. Changed: `PostgresTransactionContext` (adds `raw` and `prepare`), `SqliteTransactionContext` (adds `raw` and `prepare`; `orm` is now `SqliteOrm`), `RoleBoundDb` (adds `prepare`; `transaction` passes a `SupabaseTransactionContext`). New: `PostgresQueryMembers` and `SqliteQueryMembers` (the query members a client and its transactions share), `SupabaseTransactionContext`, `SqliteOrm`, and from the ORM client `OrmClientRootMembers` and `PrepareMember`. The detection matches a file that names one of these types, or `SqliteClient`, whose `orm` changed.
|
|
32
|
+
detection:
|
|
33
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
34
|
+
matches:
|
|
35
|
+
- '(?<![\w$])(?:PostgresTransactionContext|SqliteTransactionContext|SqliteClient|RoleBoundDb|SupabaseTransactionContext|PostgresQueryMembers|SqliteQueryMembers|SqliteOrm|OrmClientRootMembers)(?![\w$])'
|
|
36
|
+
- id: runtime-activity-is-activity
|
|
37
|
+
summary: |
|
|
38
|
+
`RuntimeActivity` is renamed `Activity`, with no alias. It is the type of the `activity` parameter of `activityStarted`, `activityEnded` and `activityFinalized`.
|
|
39
|
+
detection:
|
|
40
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
41
|
+
matches:
|
|
42
|
+
- '\bRuntimeActivity\b'
|
|
43
|
+
- id: activity-includes-client-activities
|
|
44
|
+
summary: |
|
|
45
|
+
`Activity` is `StatementActivity | TransactionActivity | ClientActivity`, and `ActivityEndedResult` includes `ClientActivityEnded`, in every program. A client activity's kind contains a hyphen, such as `orm-call`. A hook that narrows by ruling out `'transaction'` and then reads statement fields no longer compiles. Narrow by the kind you handle, such as `if (result.kind !== 'statement') return;`, and ignore kinds you do not know.
|
|
46
|
+
detection:
|
|
47
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
48
|
+
matches:
|
|
49
|
+
- '\.kind\s*===\s*[''"]transaction[''"]'
|
|
50
|
+
- id: start-a-client-activity
|
|
51
|
+
summary: |
|
|
52
|
+
An extension can start an activity of its own kind with `startActivityOn(queryable, { kind, contentHash, annotations? })`, where `contentHash` is a function returning a promise of the digest, for example `() => planContentHashOn(queryable, plan)`; middleware read it with `activity.contentHash()`, which computes it once per activity and only when asked. The extension sends statements through the returned handle and must end every handle with `end({ outcome, result? })`, in a `finally`; an open handle keeps `close()` waiting. A middleware can supply the activity's result through `intercept.onActivity(activity, ctx)`. Registering the kind in `ClientActivityRegistry` types the handle's `intercepted.result` and `end({ result })`; in middleware hooks the result stays `unknown`.
|
|
53
|
+
- id: runtime-connections-carry-an-activity-source
|
|
54
|
+
summary: |
|
|
55
|
+
The SQL runtime's queryables start client activities through an `activitySource` member, keyed by a symbol so the public queryable types do not show it, which `startActivityOn` and `planContentHashOn` read. A connection-like object that a `SqlRuntimeBase` subclass builds over a raw connection adds `[activitySource]: this.connectionActivitySource(connection, () => session)`, and `ActivitySourceTarget` to the object's declared type, so activities can start on it. The public `RuntimeConnection` type does not require it.
|
|
56
|
+
detection:
|
|
57
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
58
|
+
matches:
|
|
59
|
+
- '\[transactionStart\]\s*:'
|
|
60
|
+
- id: begin-transaction-on-takes-a-parent
|
|
61
|
+
summary: |
|
|
62
|
+
`SqlRuntimeBase.beginTransactionOn(connection, owner)` takes a third argument, `parent`: the activity the transaction starts beneath, or `undefined`. Pass `undefined` where you began a transaction before.
|
|
63
|
+
detection:
|
|
64
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
65
|
+
matches:
|
|
66
|
+
- '\bbeginTransactionOn\s*\(\s*[^,()]+,\s*[^,()]+\)'
|
|
67
|
+
- id: transaction-activity-takes-a-parent
|
|
68
|
+
summary: |
|
|
69
|
+
`startTransactionActivity` requires `parent` (the activity the transaction starts beneath, or `undefined`). An `ActivityParent` has `acceptsChildren()`, and its `adopt(child)` returns an `ActivityChildSlot` (`statementFailed()`, `ended(own)`, `transactionAcceptsChildren()`) instead of an end callback; `ActivityParent.activity` is an `ActivityBase`. A transaction stops accepting children with `stopAcceptingChildren()`, and adopting beneath a parent that no longer accepts children throws `RUNTIME.ACTIVITY_NOT_ACCEPTING_CHILDREN` instead of an internal error.
|
|
70
|
+
detection:
|
|
71
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
72
|
+
matches:
|
|
73
|
+
- '\bstartTransactionActivity\s*\('
|
|
74
|
+
- '\.adopt\s*\('
|
|
75
|
+
- id: orm-runtime-objects-carry-an-activity-source
|
|
76
|
+
summary: |
|
|
77
|
+
The ORM reaches activities only through `startActivityOn` on the queryable it holds, and every ORM terminal method on a queryable without an `activitySource` member fails with `ORM.ACTIVITY_SOURCE_MISSING` (see `queryable-carries-the-activity-source`). `db.runtime()` passed straight to `orm()` carries one. A facade, such as `postgres()`, that builds its own ORM runtime object, such as one that looks up its runtime on every call, adds `[activitySource]: delegatingActivitySource(() => runtime)` from `@prisma/orm-family-sql/runtime`, for the runtime and for the context of `db.transaction(fn)`. Where the facade's statements run through sessions of its own, such as role sessions, give an activity source whose statements go the same way.
|
|
78
|
+
detection:
|
|
79
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
80
|
+
matches:
|
|
81
|
+
- '\bormBuilder\s*\(|\borm\s*\(\s*\{\s*runtime\s*:'
|
|
82
|
+
- id: orm-annotations-on-the-call
|
|
83
|
+
summary: |
|
|
84
|
+
Every SQL ORM terminal method runs as one `orm-call` client activity, and the annotations a `configure` callback records live on that activity. The statements the call sends carry only the reserved keys the ORM sets, so a middleware that reads an ORM annotation from a statement plan (`handle.read(plan)` or `plan.meta.annotations`) finds nothing. Read it from the call: in `activityStarted(activity)` or `intercept.onActivity(activity)`, `if (activity.kind === 'orm-call') handle.read(activity)`. A middleware can answer the call through `intercept.onActivity`; the value must have the shape the terminal returns, or the ORM rejects with `ORM.INTERCEPTED_RESULT_INVALID`.
|
|
85
|
+
detection:
|
|
86
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
87
|
+
matches:
|
|
88
|
+
- '\.read\(\s*(?:plan|exec|draft)\b'
|
|
89
|
+
- 'meta\.annotations\b'
|
|
90
|
+
- id: cache-store-holds-cached-values
|
|
91
|
+
summary: |
|
|
92
|
+
A cache store for `@internal/middleware-cache` holds `CachedValue`, `{ kind: 'statement-rows', rows } | { kind: 'call-result', result }`, not `CachedRows`; `CacheStore` and `CacheEntry` default their value type to `CachedValue`, and `createCacheMiddleware` accepts only a `CacheStore<TMeta, CachedValue>`. Type a custom store with `CachedValue`, and make `get` give back a value equivalent to the one `set` was given, `kind` included. An ORM call's result holds decoded values such as `Date`, `bigint`, Temporal values and extension class instances, so a store that serializes needs a serializer that rebuilds them; JSON does not. `createInMemoryCacheStore` takes only the meta type now and always holds `CachedValue`: drop its second type argument.
|
|
93
|
+
detection:
|
|
94
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
95
|
+
matches:
|
|
96
|
+
- '\bCachedRows\b'
|
|
97
|
+
- 'createInMemoryCacheStore<[^>(]*,'
|
|
98
|
+
- id: queryable-carries-the-activity-source
|
|
99
|
+
summary: |
|
|
100
|
+
The SQL ORM starts its `orm-call` activity on the queryable it was given, through the queryable's `activitySource` member, and sends every statement through the activity's handle. A wrapper that copies a runtime, connection or transaction to intercept its `query` and `execute` (for example to record plans) also copies the `[activitySource]` member, so ORM statements bypass the wrapper. Wrap the activity source too: return a source whose `startActivity` wraps each handle the same way, including the transaction its `transaction()` returns. A custom queryable passed to the ORM must carry an activity source: without one, every terminal method fails with `ORM.ACTIVITY_SOURCE_MISSING` (streaming terminals throw when called, the others reject). A test stub can use `delegatingActivitySource(() => realRuntime)`, or a source whose handles send their statements to the stub.
|
|
101
|
+
detection:
|
|
102
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
103
|
+
matches:
|
|
104
|
+
- '\.\.\.\s*[\w.]+\s*,\s*(?:query|execute)\s*[:(]'
|
|
105
|
+
- id: aggregate-operation-using-reserved
|
|
106
|
+
summary: |
|
|
107
|
+
Every ORM collection has a `using` member, so `orm()` refuses a contributed aggregate operation named `using` with `ORM.AGGREGATE_OPERATION_RESERVED`, as it refuses `with` and `fragment`. An extension that contributes an aggregate operation named `using` renames it. The detection matches a file that names an aggregate operation `using`.
|
|
108
|
+
detection:
|
|
109
|
+
glob: "**/*.{ts,mts,cts}"
|
|
110
|
+
matches:
|
|
111
|
+
- '\boperation\s*:\s*[''"`]using[''"`]'
|
|
112
|
+
- id: codecs-read-wire-and-data-type-values
|
|
113
|
+
summary: |
|
|
114
|
+
A codec implements `toWire`, `fromWire`, `toDataTypeValue` and `fromDataTypeValue` instead of `encode`, `decode`, `encodeJson` and `decodeJson`. Its constructor names its data type and parameters (`super(descriptor, dataType, params)`), and it builds the value it hands over with `this.dataTypeValueOf(json)`. A Mongo codec made with `mongoCodec()` takes the same four options in place of `encode`, `decode`, `encodeJson` and `decodeJson`, and a required `dataType`.
|
|
115
|
+
detection:
|
|
116
|
+
glob: "**/*.{ts,mts,cts}"
|
|
117
|
+
matches:
|
|
118
|
+
- '\b(?:encodeJson|decodeJson)\s*\('
|
|
119
|
+
- '\basync\s+(?:encode|decode)\s*\('
|
|
120
|
+
- '\b(?:encode|decode)\s*:\s*(?:async\s*)?\('
|
|
121
|
+
- '\b(?:encodeJson|decodeJson)\s*:'
|
|
122
|
+
- id: data-types-own-parameter-limits
|
|
123
|
+
summary: |
|
|
124
|
+
Every data type declares a `read` function, which reads and checks each stored value under the column's parameters. It replaces the `toCanonicalForm` hook of a data type or codec descriptor, and limits a parameter sets, such as a vector's length or a geometry's SRID, move from the codec to the data type.
|
|
125
|
+
detection:
|
|
126
|
+
glob: "**/*.{ts,mts,cts}"
|
|
127
|
+
matches:
|
|
128
|
+
- '\btoCanonicalForm\b'
|
|
129
|
+
- '\bsqlDataType\('
|
|
130
|
+
- id: json-helpers-renamed
|
|
131
|
+
summary: |
|
|
132
|
+
The JSON helpers in `@prisma/orm-framework/components/codec` are renamed from `decodeJson*` to `readJson*` (`readJsonString`, `readJsonMatching`, `readJsonInteger`, `readJsonIntegerText`, `readJsonFloat`, `readJsonBoolean`), and `encodeJsonFloat` to `floatToJson`. `ToCanonicalForm` and `canonicalFormOf` are removed.
|
|
133
|
+
detection:
|
|
134
|
+
glob: "**/*.{ts,mts,cts}"
|
|
135
|
+
matches:
|
|
136
|
+
- '\b(?:decodeJson(?:String|Matching|Integer|IntegerText|Float|Boolean)|encodeJsonFloat|canonicalFormOf|ToCanonicalForm)\b'
|
|
137
|
+
- id: stored-form-helpers-renamed
|
|
138
|
+
summary: |
|
|
139
|
+
The SQL helpers that read or write a value's stored text say "stored" instead of "canonical": `canonicalDateTime` is `dateTimeStoredText`, `CanonicalDateTimeOptions` is `DateTimeStoredTextOptions`, `canonicalNumeralText` is `storedNumeralText`, `sqlTextFromCanonical` is `sqlTextFromStored`, and `canonicalSqlText` is `storedSqlText`.
|
|
140
|
+
detection:
|
|
141
|
+
glob: "**/*.{ts,mts,cts}"
|
|
142
|
+
matches:
|
|
143
|
+
- '\b(?:canonicalDateTime|CanonicalDateTimeOptions|canonicalNumeralText|sqlTextFromCanonical|canonicalSqlText)\b'
|
|
144
|
+
- id: projections-read-by-from-wire
|
|
145
|
+
summary: |
|
|
146
|
+
An include and an aggregate read a column's JSON projection with the codec's `fromWire`, as a row is read, so a descriptor's `jsonProjection` writes the value the driver returns for a row, often `CAST(x AS text)`, instead of the codec's JSON form.
|
|
147
|
+
detection:
|
|
148
|
+
glob: "**/*.{ts,mts,cts}"
|
|
149
|
+
matches:
|
|
150
|
+
- '\bjsonProjection\s*\('
|
|
151
|
+
- id: empty-result-json-renamed-empty-result-wire
|
|
152
|
+
summary: |
|
|
153
|
+
An aggregate descriptor's `emptyResultJson` is renamed `emptyResultWire`, and holds the value the driver would return, which the result codec reads with `fromWire`.
|
|
154
|
+
detection:
|
|
155
|
+
glob: "**/*.{ts,mts,cts}"
|
|
156
|
+
matches:
|
|
157
|
+
- '\bemptyResultJson\b'
|
|
158
|
+
- id: family-codec-templates-render-no-literals
|
|
159
|
+
summary: |
|
|
160
|
+
`postgresCodec` and `sqliteCodec` now throw an `InternalError` when the family codec template they adapt declares `renderValueLiteral`. Earlier versions copied the template's renderer. A family template renders no literal, because only the target knows the form its data type stores a value in. Remove `renderValueLiteral` from the template and pass it in the adapter's options instead; the framework exports `renderTsLiteral`, `floatNumberLiteral`, `decimalTextNumberLiteral` and `decimalTextBigintLiteral` from `@internal/framework-components/codec` for the common stored forms.
|
|
161
|
+
detection:
|
|
162
|
+
glob: "**/*.{ts,mts,cts}"
|
|
163
|
+
matches:
|
|
164
|
+
- '(?<![\s\S])(?=[\s\S]*\bextends\s+CodecDescriptorTemplateImpl\b)(?=[\s\S]*\brenderValueLiteral\s*\()'
|
|
165
|
+
- id: enum-block-written-values
|
|
166
|
+
summary: |
|
|
167
|
+
A parsed PSL extension block now records each literal entry as it is written in `writtenValues`, a `WrittenScalar` by key, in place of `numberTexts`, which held only the source text of number entries. `AuthoringEntityContext.readWrittenNumber` is replaced by `readWrittenValueForCodec`, which takes a `WrittenScalar` as `written` in place of the number's `text`, and reads any written value, not only a number, by the cast rule. A refused `WrittenValueReading` now carries the diagnostic `code` with its `message`. `enumMemberValue()` yields a tagged member as its `WrittenScalar` in place of `null`.
|
|
168
|
+
detection:
|
|
169
|
+
glob: "**/*.{ts,mts,cts}"
|
|
170
|
+
matches:
|
|
171
|
+
- '\b(?:numberTexts|readWrittenNumber|WrittenValueReading)\b'
|
|
172
|
+
- id: type-lookups-one-framework-type
|
|
173
|
+
summary: |
|
|
174
|
+
`SqlTypeLookups` (from `@prisma/orm-family-sql/contract/data-type`) and `MongoTypeLookups` (from `@prisma/orm-family-mongo/contract/data-type`) are removed. Use `TypeLookups` from `@prisma/orm-framework/components/codec`, the same pair of `codecLookup` and `dataTypeLookup`.
|
|
175
|
+
detection:
|
|
176
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
177
|
+
matches:
|
|
178
|
+
- '\b(?:SqlTypeLookups|MongoTypeLookups)\b'
|
|
179
|
+
- id: type-name-normalisers-removed
|
|
180
|
+
summary: |
|
|
181
|
+
`normalizeSchemaNativeType` (from `@prisma/orm-postgres/target/native-type-normalizer` and `@prisma/orm-postgres/adapter/control`) and `normalizeSqliteNativeType` (from `@prisma/orm-sqlite/adapter/control`) are removed. Columns are compared by their data type and its parameters, so no type name needs normalising.
|
|
182
|
+
detection:
|
|
183
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
184
|
+
matches:
|
|
185
|
+
- '\b(?:normalizeSchemaNativeType|normalizeSqliteNativeType)\b'
|
|
186
|
+
- id: introspected-type-text
|
|
187
|
+
summary: |
|
|
188
|
+
In `@prisma/orm-postgres/target/native-type-normalizer`, `introspectedNativeType` is renamed `introspectedTypeText` and `IntrospectedNativeType` is renamed `IntrospectedTypeText`; its result holds `typeText` (the type the database reports) and `many`, and no `resolvedNativeType`. `CatalogColumnType.dataType` is renamed `informationSchemaType`.
|
|
189
|
+
detection:
|
|
190
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
191
|
+
matches:
|
|
192
|
+
- '\b(?:introspectedNativeType|IntrospectedNativeType|CatalogColumnType)\b'
|
|
193
|
+
- id: schema-ir-column-type-text
|
|
194
|
+
summary: |
|
|
195
|
+
`SqlColumnIR` and `SqlColumnIRInput` (`@prisma/orm-family-sql/schema-ir/types`): `nativeType` is renamed `typeText`, and `resolvedNativeType` and `codecBaseNativeType` are removed. `dataType` is removed: a column's type is its `storedType` and `storedTypeParams`, and nothing compares `typeText`. A column built by hand compares equal to another only when it has `storedType` and `storedTypeParams`.
|
|
196
|
+
detection:
|
|
197
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
198
|
+
matches:
|
|
199
|
+
- '\b(?:resolvedNativeType|codecBaseNativeType)\b'
|
|
200
|
+
- '(?<![\s\S])(?=[\s\S]*\bSqlColumnIR)[\s\S]*\bnativeType\b'
|
|
201
|
+
- id: default-resolver-takes-the-column
|
|
202
|
+
summary: |
|
|
203
|
+
A `DefaultResolver` (`@prisma/orm-family-sql/family/control`) receives the column, `{ typeText, many }`, as its second argument instead of a type name with a `[]` suffix for a list, and `readPostgresDefault(raw, many)` (`@prisma/orm-postgres/target/default-normalizer`) takes whether the column is a list instead of a type name.
|
|
204
|
+
detection:
|
|
205
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
206
|
+
matches:
|
|
207
|
+
- '\bDefaultResolver\b'
|
|
208
|
+
- '\breadPostgresDefault\s*\('
|
|
209
|
+
- id: alter-type-postcheck-by-data-type
|
|
210
|
+
summary: |
|
|
211
|
+
In `@prisma/orm-postgres/target/planner-sql-checks`, `buildExpectedFormatType` and `alterTypeCheckText` are removed. `alterTypeCheck(type, dataTypes, where)` returns how the `ALTER COLUMN TYPE` postcheck recognises the new type, a `ColumnTypeCheck`: `{ kind: 'catalog-text', text }`, the text `format_type` prints, or for an enum `{ kind: 'identity', typeName, many }`, checked with `to_regtype` whatever schema the enum is in. The `checkType` field of `AddColumnCall`, `AlterColumnTypeCall`, `AddRequiredColumnToEmptyTableCall` and `AddColumnWithTemporaryDefaultCall` (`@prisma/orm-postgres/target/op-factory-call`) is now a `ColumnTypeCheck` instead of a string.
|
|
212
|
+
detection:
|
|
213
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
214
|
+
matches:
|
|
215
|
+
- '\b(?:buildExpectedFormatType|alterTypeCheckText)\b'
|
|
216
|
+
- id: control-adapters-take-declared-data-types
|
|
217
|
+
summary: |
|
|
218
|
+
`PostgresControlAdapter` (`@prisma/orm-postgres/adapter/control`) and `SqliteControlAdapter` (`@prisma/orm-sqlite/adapter/control`) take the stack's declared data types as a new last constructor argument, because introspection resolves each column's reported type over them. Pass the target's `postgresDataTypes` or `sqliteDataTypes` (from `/target/data-types`), or, when the stack has extensions, the stack's types: `stack.declaredDataTypes.map(({ type }) => type)`. Code that leaves it out fails to compile, and untyped JavaScript fails at the first introspection.
|
|
219
|
+
detection:
|
|
220
|
+
glob: "**/*.{ts,mts,cts,tsx,js,mjs,cjs}"
|
|
221
|
+
matches:
|
|
222
|
+
- '\bnew\s+(?:Postgres|Sqlite)ControlAdapter\s*\('
|
|
223
|
+
- id: resolved-sql-type-carries-the-data-type
|
|
224
|
+
summary: |
|
|
225
|
+
`resolveReportedSqlType` and `resolveWrittenSqlTypes` (`@prisma/orm-family-sql/contract/data-type`) return the data type they found in `dataType`, a `SqlDataType`, instead of its id. Read `resolved.dataType.id` where you read `resolved.dataType`.
|
|
226
|
+
detection:
|
|
227
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
228
|
+
matches:
|
|
229
|
+
- '(?<![\s\S])(?=[\s\S]*\bresolve(?:ReportedSqlType|WrittenSqlTypes)\b)[\s\S]*\.dataType\b(?!\s*\.)'
|
|
230
|
+
- id: value-arguments-name-a-data-type
|
|
231
|
+
summary: |
|
|
232
|
+
A type constructor's argument that is a value is declared `{ kind: 'value', dataType }`, naming the data type the argument's value belongs to, such as `pg/int4` for a Postgres length, precision, scale or SRID. The argument kinds `string`, `boolean` and `stringArray` are removed, and a type constructor's `number` argument becomes a `value` argument. Drop `integer`, `minimum` and `maximum`: the data type's parameter schema checks the value. Assembly refuses a type constructor argument of any other kind, `option` and `object` included. A field preset keeps its `number`, `option` and `object` arguments, so a match inside a field preset needs no change.
|
|
233
|
+
detection:
|
|
234
|
+
glob: "**/*.{ts,mts,cts}"
|
|
235
|
+
matches:
|
|
236
|
+
- 'kind:\s*[''"](?:number|string|boolean|stringArray)[''"](?=[^}]*\b(?:name|integer|minimum|maximum|optional)\s*:)'
|
|
237
|
+
- '\b(?:name|integer|minimum|maximum|optional)\s*:[^{}]*\bkind:\s*[''"](?:number|string|boolean|stringArray)[''"]'
|
|
238
|
+
- '\{\s*kind:\s*[''"](?:number|string|boolean|stringArray)[''"]\s*\}'
|
|
239
|
+
- id: value-argument-types-in-typescript
|
|
240
|
+
summary: |
|
|
241
|
+
The TypeScript builder types a `value` argument through `__dataTypeArguments`, a map a pack declares in its type from each data type it registers to `{ argument, parameter }`: the TypeScript value an author passes, and the type parameter it gives. A pack that registers a data type its own type constructors take must declare it, or the argument types as `never`. The TypeScript builder reads value arguments only with the data types and authoring entries the composed packs register, so every data type an argument takes must be registered, with a writable `authoring.dataTypes` entry, by some pack in the stack: the family, the target or an extension. An extension whose arguments take a target's data type, such as `pg/int4`, registers nothing; registering it again is refused as a duplicate.
|
|
242
|
+
- id: psl-and-print-helpers-take-data-types
|
|
243
|
+
summary: |
|
|
244
|
+
`mapPslHelperArgs` and `instantiatePslFieldPreset` take the stack's `dataTypes`; `parsePslPositionalArgs` and `pslTypeArgumentText` are removed. `inferColumnTypes` takes `dataTypes`, and its `PslTypeCall` carries the argument `values`. `writtenTypeArguments` writes a call's arguments as literals of their data types. `sql.String` is no longer in the SQL family pack: a SQL target contributes it with `sqlTargetTypeConstructors({ sizeType })` from `family/pack`, and `temporalCodecPresetWithPrecision` takes `sizeType` too. `temporalCodecPresetWithPrecision` and `temporalStringAuthoringPresets` move from `family/control` to `family/pack`, so a target pack does not load the family's control entry point. `assembleAuthoringDataTypes` moves from `components/control` to `components/authoring`, beside `assembleDataTypeSupport`, which builds a `DataTypeSupport` from a list of components and checks their value arguments.
|
|
245
|
+
detection:
|
|
246
|
+
glob: "**/*.{ts,mts,cts}"
|
|
247
|
+
matches:
|
|
248
|
+
- 'parsePslPositionalArgs|pslTypeArgumentText'
|
|
249
|
+
- 'import\s*(?:type\s*)?\{[^}]*\b(?:assembleAuthoringDataTypes|temporalCodecPresetWithPrecision|temporalStringAuthoringPresets)\b[^}]*\}\s*from\s*[''"][^''"]*/(?:components|family|family-sql)/control[''"]'
|
|
250
|
+
- id: composed-helpers-take-no-data-type-lookup
|
|
251
|
+
summary: |
|
|
252
|
+
`createComposedAuthoringHelpers` no longer takes `dataTypeLookup`. It builds its data types from the family, target and extension packs it is given, with `assembleDataTypeSupport`. Remove the option. Every data type an argument takes must be registered, with an `authoring.dataTypes` entry, by some pack in the stack; an extension whose arguments take a target's data type, such as `pg/int4`, registers nothing.
|
|
253
|
+
detection:
|
|
254
|
+
glob: "**/*.{ts,mts,cts}"
|
|
255
|
+
matches:
|
|
256
|
+
- 'createComposedAuthoringHelpers\(\{[^}]*\bdataTypeLookup\b'
|
|
257
|
+
- id: helper-codec-invariants
|
|
258
|
+
summary: |
|
|
259
|
+
`enforceDataTypeInvariants` is renamed `enforceHelperCodecInvariants`, and `DataTypeInvariantInput` is renamed `HelperCodecInvariantInput`. It now checks only type constructors and field presets against the stack's codecs, and takes `lookup`, `helpers` (formerly `constructors`) and `codecDescriptorFor`. `declaredTypes`, `codecs` and `authoringEntries` are removed: `assembleDataTypeSupport` checks data types, their entries, casts and written forms, through `enforceDataTypeAssemblyInvariants`.
|
|
260
|
+
detection:
|
|
261
|
+
glob: "**/*.{ts,mts,cts}"
|
|
262
|
+
matches:
|
|
263
|
+
- 'enforceDataTypeInvariants|DataTypeInvariantInput'
|
|
264
|
+
- id: default-functions-lower-to-database-defaults-only
|
|
265
|
+
summary: |
|
|
266
|
+
A default function in `controlMutationDefaults.defaultFunctionRegistry` can no longer lower to a client generator: `LoweredDefaultValue` has only its `{ kind: 'storage' }` arm. A function that generated a value in the client, such as `slugid()`, becomes a generator function: contribute a `GeneratorFunctionContribution` in `generatorFunctions` on the pack ref and the control descriptor, with a binding for each codec it fills, and keep the generator descriptor. Users then write `@onCreate(slugid())` instead of `@default(slugid())`. The detection matches files that lower a default function to a generator.
|
|
267
|
+
detection:
|
|
268
|
+
glob: "**/*.{ts,mts,cts}"
|
|
269
|
+
matches:
|
|
270
|
+
- "kind:\\s*'execution'"
|
|
271
|
+
- id: applicable-codec-ids-and-build-phases-removed
|
|
272
|
+
summary: |
|
|
273
|
+
`MutationDefaultGeneratorDescriptor.applicableCodecIds` and `buildPhases` are removed. A binding of a generator function declares the codecs it fills and the arguments `contract print` writes for each generator it produces. Delete the removed fields. The detection matches files that set them.
|
|
274
|
+
detection:
|
|
275
|
+
glob: "**/*.{ts,mts,cts}"
|
|
276
|
+
matches:
|
|
277
|
+
- 'applicableCodecIds\s*:'
|
|
278
|
+
- 'buildPhases\s*:'
|
|
279
|
+
- id: sql-family-registers-identifier-generators
|
|
280
|
+
summary: |
|
|
281
|
+
The SQL family now registers the identifier generators `uuidv4`, `uuidv7`, `cuid2`, `ulid`, `nanoid` and `ksuid`. A target, adapter or extension that registers one of them in `generatorDescriptors` must stop: two components that register one generator fail with `CONTRACT.PACK_CONTRIBUTION_INVALID`. An extension that binds `uuid` or another identifier function to its own codec keeps the binding and drops the registration. `builtinGeneratorRegistryMetadata` from `@prisma/orm-framework/ids` is removed; delete its use. The detection matches files that register the built-in identifier generators.
|
|
282
|
+
detection:
|
|
283
|
+
glob: "**/*.{ts,mts,cts}"
|
|
284
|
+
matches:
|
|
285
|
+
- 'builtinGeneratorRegistryMetadata'
|
|
286
|
+
- "\\{\\s*id:\\s*['\"](?:uuidv4|uuidv7|cuid2|ulid|nanoid|ksuid)['\"]\\s*\\}"
|
|
287
|
+
- id: sql-target-declares-integer-argument-generators
|
|
288
|
+
summary: |
|
|
289
|
+
The SQL family no longer declares the signatures of `uuid`, `cuid` and `nanoid`, because their arguments are now values of the target's integer data type, which only the target knows. A SQL target declares them by spreading `sqlIntegerArgumentGeneratorFunctions({ integer })` from `@prisma/orm-family-sql/family/pack` into its `generatorFunctions`, on both its pack ref and its control descriptor. Without it, composing a stack fails with `CONTRACT.PACK_CONTRIBUTION_INVALID`: `"sql" binds generator function "uuid", which no component declares.` The detection matches files that bind the identifier generator functions.
|
|
290
|
+
detection:
|
|
291
|
+
glob: "**/*.{ts,mts,cts}"
|
|
292
|
+
matches:
|
|
293
|
+
- '(?<![\s\S])(?![\s\S]*\bsqlIntegerArgumentGeneratorFunctions\b)[\s\S]*\bidGeneratorFunctionBindings\('
|
|
294
|
+
- id: generator-signature-from-data-types
|
|
295
|
+
summary: |
|
|
296
|
+
The `signature` of a `GeneratorFunctionContribution`, like that of a default function in a `defaultFunctionRegistry` entry, may now be a function of the stack's data types that returns the `FuncCallSig`. Code that reads a contribution's `signature` as a `FuncCallSig` builds the call with `generatorFunctionCall(name, signature, dataTypes)` from `@prisma/orm-framework/psl-parser`, and a default function's with `defaultFunctionCall(name, entry, dataTypes)` from `@prisma/orm-family-sql/contract-psl/attribute-specs`. A plain object still works.
|
|
297
|
+
detection:
|
|
298
|
+
glob: "**/*.{ts,mts,cts,tsx,js,mjs,cjs}"
|
|
299
|
+
matches:
|
|
300
|
+
- '(?<![\s\S])(?=[\s\S]*\b(?:defaultFunctionRegistry|generatorFunctions)\b)[\s\S]*\.signature\b'
|
|
301
|
+
- id: generator-function-field-length
|
|
302
|
+
summary: |
|
|
303
|
+
The column a generator function binding resolves for, `GeneratorFunctionField` (`@prisma/orm-framework/components/components`), has a required `length` key: the column's length in its data type's normal form, so a bare `Char` gives 1, or `undefined` for a column with no length or one whose database does not limit it, such as SQLite's. Code that calls `resolve` or `callFor` on a generator function registry passes it; for a SQL column, `sqlCodecColumnLength(dataTypes, codecId, typeParams)` from `@prisma/orm-family-sql/contract/data-type` computes it. The built-in identifier bindings refuse a column shorter than what they write, and a binding of your own can read `length` to do the same. The detection matches files that call the registry's `resolve` or `callFor`.
|
|
304
|
+
detection:
|
|
305
|
+
glob: "**/*.{ts,mts,cts}"
|
|
306
|
+
matches:
|
|
307
|
+
- '\bgeneratorFunctions\.(?:resolve|callFor)\('
|
|
308
|
+
- id: build-contract-definition-takes-lookups
|
|
309
|
+
summary: |
|
|
310
|
+
`buildContractDefinition` (`@prisma/orm-family-sql/contract-ts/contract-builder`) now requires `codecLookup` and `dataTypeLookup`, the same lookups `buildSqlContractFromDefinition` takes, so that each `.onCreate(...)` and `.onUpdate(...)` call is checked against its column's length. Pass them in the definition. The detection matches files that call `buildContractDefinition`.
|
|
311
|
+
detection:
|
|
312
|
+
glob: "**/*.{ts,mts,cts}"
|
|
313
|
+
matches:
|
|
314
|
+
- '\bbuildContractDefinition\('
|
|
315
|
+
- id: arg-binding-spec-label
|
|
316
|
+
summary: |
|
|
317
|
+
`ArgBindingSpec` (`@prisma/orm-framework/psl-parser`), the second argument of `interpretArgs`, names its callee with `label`, the whole phrase such as `Attribute "rel"`, instead of `name`. `funcCall(name, signature, { label })` takes the phrase its binding diagnostics use; without it they say `Attribute "<name>"` as before.
|
|
318
|
+
detection:
|
|
319
|
+
glob: "**/*.{ts,mts,cts,tsx,js,mjs,cjs}"
|
|
320
|
+
matches:
|
|
321
|
+
- '\binterpretArgs\s*\('
|
|
322
|
+
- '\bArgBindingSpec\b'
|
|
323
|
+
- id: one-of-refuses-data-type-value
|
|
324
|
+
summary: |
|
|
325
|
+
`oneOf` (`@prisma/orm-framework/psl-parser`) throws an `InternalError` when one of its arms is a `dataTypeValue`. Give the value its own parameter, or use it as a parameter of a `funcCall` arm.
|
|
326
|
+
detection:
|
|
327
|
+
glob: "**/*.{ts,mts,cts,tsx,js,mjs,cjs}"
|
|
328
|
+
matches:
|
|
329
|
+
- '\boneOf\([^;]*\bdataTypeValue\('
|
|
330
|
+
- id: text-data-types
|
|
331
|
+
summary: |
|
|
332
|
+
A component's authoring contributions may name `textDataTypes`, the data types whose values are text; a SQL target names its own. A SQL `@@discriminator` field must be stored in one of them. An extension that adds a text type names it there to make it a discriminator.
|
|
333
|
+
- id: typescript-contract-pack-type-slots
|
|
334
|
+
summary: |
|
|
335
|
+
A contract built with `defineContract` reads its types from the packs in it. A third-party SQL pack is typed fully for such a contract only when it declares what an emitted `contract.d.ts` would get from its codecs and descriptors: `__queryOperationTypes` for its query operations, `__parameterizedOutputTypes` for codecs whose output type depends on their type parameters, literal-typed `types.aggregateDescriptors` for its aggregates, and exports from its `/pack` entry for the types an application's exported contract names. Nothing breaks for a pack that does none of this; its operations, refined output types and aggregates are missing from a `defineContract` contract's types, as before. The detection matches SQL pack metadata that declares codec types.
|
|
336
|
+
detection:
|
|
337
|
+
glob: "**/*.{ts,mts,cts}"
|
|
338
|
+
matches:
|
|
339
|
+
- '__codecTypes\?:'
|
|
340
|
+
- id: parameterized-output-types-declared-once
|
|
341
|
+
summary: |
|
|
342
|
+
A codec whose output type depends only on its column's type parameters, such as `Vector<3>` for `vector(3)`, declares that type once, as a type function the pack exports, and no longer implements `renderOutputType`. The emitted `contract.d.ts` applies the type function, and so does a contract built with `defineContract`. A pack must name the function twice: in its codec types' metadata as `types.codecTypes.parameterizedOutputTypes`, an import spec like `types.codecTypes.import`, which the emitter reads, and in its pack metadata's type as `__parameterizedOutputTypes`, which `defineContract` reads; if one is missing, that path types the column by the codec's plain output type without an error. `ParameterizedOutputTypesFunction` comes from `@prisma/orm-framework/contract/types`. A `renderOutputType` that writes such a type still works for the emitted file but is ignored by `defineContract`; remove it, with the `typeImports` entry it needed, once the type function exists. Keep `renderOutputType` only for a type no type function can compute, such as an arktype schema's text. The detection matches codec descriptors that implement `renderOutputType`.
|
|
343
|
+
detection:
|
|
344
|
+
glob: "**/*.{ts,mts,cts}"
|
|
345
|
+
matches:
|
|
346
|
+
- 'renderOutputType\('
|
|
347
|
+
- 'renderOutputType:'
|
|
348
|
+
- id: psl-interpreter-contract-capabilities
|
|
349
|
+
summary: |
|
|
350
|
+
`interpretPslDocumentToSqlContract` now writes the enabled capabilities it is given in `capabilities` into the contract it returns, joined with the target's and the extensions', so a contract read from PSL carries its adapter's capabilities as one built with `defineContract` does. Before, it used them only to check the schema and left them out of the contract. Code that called it directly and compared the contract's `capabilities` to the target's and extensions' alone now also sees the ones it passed in. The detection matches calls of the interpreter.
|
|
351
|
+
detection:
|
|
352
|
+
glob: "**/*.{ts,mts,cts}"
|
|
353
|
+
matches:
|
|
354
|
+
- 'interpretPslDocumentToSqlContract\('
|
|
355
|
+
- id: field-types-expression-removed
|
|
356
|
+
summary: |
|
|
357
|
+
The emitter now writes `FieldOutputTypes` and `FieldInputTypes` for every family as `FieldTypesFromDomain` applied to the emitted contract, so the `fieldTypesExpression` hook of `EmissionSpi` is removed. A family emitter that implemented it must delete it; the framework declares `FieldTypeOverrides` and `ParameterizedOutputTypeFunctions` itself. The detection matches implementations of the hook.
|
|
358
|
+
detection:
|
|
359
|
+
glob: "**/*.{ts,mts,cts}"
|
|
360
|
+
matches:
|
|
361
|
+
- '\bfieldTypesExpression\b'
|
|
362
|
+
- id: field-types-generic-moved
|
|
363
|
+
summary: |
|
|
364
|
+
`FieldTypesFromDomain` and `ValueTypeSide` moved from the SQL family contract types entry to `@prisma/orm-framework/contract/types`, and the generic now types value object and union fields itself, from the namespace's `valueObjects`. Its arguments are `FieldTypesFromDomain<DomainNamespaces, CodecTypes, ParameterizedOutputs, Overrides, Side>`, where `Overrides` is one map keyed like the domain, `[namespace]['models' | 'valueObjects'][owner][field]`, each entry `{ output?, input? }`; `ValueObjectTypesFromDomain` takes the same map. A storage type map can type one entry with `StoredValueType` from the same entry. `ColumnTypesFromStorage` stays in the SQL family. Update imports. The detection matches imports of the generic from the SQL family.
|
|
365
|
+
detection:
|
|
366
|
+
glob: "**/*.{ts,mts,cts}"
|
|
367
|
+
matches:
|
|
368
|
+
- 'import type \{[^}]*\b(FieldTypesFromDomain|ValueTypeSide)\b[^}]*\} from [''"][^''"]*(sql-contract|family-sql/contract|family-contract)/types[''"]'
|
|
369
|
+
- id: resolve-field-value-set-renamed
|
|
370
|
+
summary: |
|
|
371
|
+
`EmissionSpi.resolveFieldValueSet` is renamed `resolveStorageValueSet`. The framework emitter now reads a field's value set itself when the field references a domain enum (`valueSet.plane` is `'domain'`), for model and value object fields alike, and no longer calls the hook for those fields. It calls `resolveStorageValueSet` only for other model fields, whose values the family keeps in its storage, such as a SQL column's value set. Rename the hook in a family emitter, and drop any handling of domain enum references from it. The detection matches implementations of the old hook.
|
|
372
|
+
detection:
|
|
373
|
+
glob: "**/*.{ts,mts,cts}"
|
|
374
|
+
matches:
|
|
375
|
+
- '\bresolveFieldValueSet\s*[(:?]'
|
|
376
|
+
- id: mongo-parameterized-output-types
|
|
377
|
+
summary: |
|
|
378
|
+
A Mongo-family pack whose codec output type depends only on a field's type parameters, such as `Vector<3>` for a vector of length 3, declares it once as a type function, as SQL packs do: export a `ParameterizedOutputTypesForTypeParams` interface extending `ParameterizedOutputTypesFunction` from its `codec-types` entry, name it in `types.codecTypes.parameterizedOutputTypes` where the codec types import is declared, and add `__parameterizedOutputTypes` to the pack metadata's type. `renderOutputType` is no longer used for such codecs: Mongo's vector codec no longer implements it, and a Mongo `defineContract` contract ignores it. Export the types the pack metadata names from its `/pack` entry, so an application that exports a contract can build declarations. The detection matches files that import a Mongo package and implement `renderOutputType`.
|
|
379
|
+
detection:
|
|
380
|
+
glob: "**/*.{ts,mts,cts}"
|
|
381
|
+
matches:
|
|
382
|
+
- '(?<![\s\S])(?=[\s\S]*@prisma/orm-(mongo|family-mongo|target-mongo)\b)[\s\S]*renderOutputType'
|
|
383
|
+
- id: mongo-model-storage-fields
|
|
384
|
+
summary: |
|
|
385
|
+
A MongoDB model's `fields` are keyed by field name, and `model.storage.fields` records the key each field is stored under: `MongoModelStorage` gains a required `fields: Record<string, { field: string }>`, with an entry for every field. `on.localFields`, `on.targetFields` and `discriminator.field` name fields; indexes, validators and execution default refs name stored names. Code that read `model.fields` keys as document keys reads stored names through the helpers `@internal/mongo-contract` exports: `ownStoredNamesOf`, `ownStoredNameOf`, `ownFieldStoredAs`, `hasOwnFieldStoredAs`, `ownEmbedStoredNamesOf`, `ownDocumentIdFieldOf`, `storedNamesOf`, `modelAndBasesOf`, `valueObjectStoredNamesOf` and `mongoModelsOf`, and at the type level `OwnStoredNameOf` and `OwnFieldStoredAs`. An embed relation is stored under its `storage.relations` entry, or its own name. A hand-built MongoDB contract or model storage type adds `fields`.
|
|
386
|
+
detection:
|
|
387
|
+
glob: "**/*.{ts,mts,cts,tsx,json}"
|
|
388
|
+
matches:
|
|
389
|
+
- 'MongoModelStorage|MongoModelDefinition'
|
|
390
|
+
- '"storage"\s*:\s*\{\s*(?:"collection"\s*:\s*"[^"]*"\s*)?\}'
|
|
391
|
+
- '(?<![\w$])storage\s*:\s*\{\s*(?:readonly\s+)?collection\s*:\s*[''"][^''"]*[''"]\s*;?\s*\}'
|
|
392
|
+
- id: mongo-validator-derivation-moves-to-mongo-contract
|
|
393
|
+
summary: |
|
|
394
|
+
The `FieldValueSets` type moves from `@internal/mongo-contract-psl` (`@prisma/orm-family-mongo/contract-psl`) to `@internal/mongo-contract` (`@prisma/orm-family-mongo/contract`). Extensions that author Mongo contracts through `defineContract(...)` or PSL need no change.
|
|
395
|
+
detection:
|
|
396
|
+
glob: "**/*.{ts,mts,cts}"
|
|
397
|
+
matches:
|
|
398
|
+
- 'import[^;]*\bFieldValueSets\b[^;]*from\s*[''"](?:@internal/mongo-contract-psl|@prisma/orm-family-mongo/contract-psl)[''"]'
|
|
399
|
+
- id: mongo-derive-json-schema-replaced-by-derive-collection-validators
|
|
400
|
+
summary: |
|
|
401
|
+
`deriveJsonSchema` and `derivePolymorphicJsonSchema` are removed. Call `deriveCollectionValidators` from `@internal/mongo-contract` (`@prisma/orm-family-mongo/contract`) instead. It returns the validator of each collection, keyed by collection name, and it is the validator a contract emits, including embedded models and variants.
|
|
402
|
+
detection:
|
|
403
|
+
glob: "**/*.{ts,mts,cts}"
|
|
404
|
+
matches:
|
|
405
|
+
- '\bderive(?:Polymorphic)?JsonSchema\b'
|
|
406
|
+
- id: psl-nocheck-removed
|
|
407
|
+
summary: |
|
|
408
|
+
The PSL attribute `@noCheck` no longer exists, and writing it in an extension's contract source is an unknown-attribute error. A list that allows null elements is now written `T?[]`. Replace `T[] @noCheck(elementNotNull)` with `T?[]`, delete `@noCheck(membership)`, and replace a bare `@noCheck` with both. Then re-emit the extension's bundled contract. The detection matches PSL files that use the attribute.
|
|
409
|
+
detection:
|
|
410
|
+
glob: "**/*.prisma"
|
|
411
|
+
matches:
|
|
412
|
+
- '@noCheck\b'
|
|
413
|
+
- id: ts-nocheck-method-removed
|
|
414
|
+
summary: |
|
|
415
|
+
The TypeScript field builder no longer has a `noCheck()` method. Replace `.many().noCheck('elementNotNull')` with `.many({ elementsNullable: true })`, delete `.noCheck('membership')`, and replace a bare `.noCheck()` with both. Then re-emit the extension's bundled contract. The detection matches TypeScript files that call the method.
|
|
416
|
+
detection:
|
|
417
|
+
glob: "**/*.{ts,mts,cts,tsx}"
|
|
418
|
+
matches:
|
|
419
|
+
- '\.noCheck\('
|
|
420
|
+
- id: stored-contracts-drop-nocheck
|
|
421
|
+
summary: |
|
|
422
|
+
A stored SQL contract that has a `noCheck` key on a column no longer loads. Run `scripts/remove-nocheck/remove-nocheck.ts` from the extension's package root. It removes the key from every stored contract the extension ships, its migration snapshots included, and rewrites the hashes and references that change. Then re-emit the bundled contract.
|
|
423
|
+
detection:
|
|
424
|
+
glob: "**/contract.json"
|
|
425
|
+
matches:
|
|
426
|
+
- '"noCheck"\s*:'
|
|
427
|
+
script: scripts/remove-nocheck/remove-nocheck.ts
|
|
428
|
+
- id: setdefault-operation-class-derived
|
|
429
|
+
summary: |
|
|
430
|
+
On PostgreSQL, `this.setDefault` in `migration.ts` no longer takes `operationClass`. The operation is widening when the migration's start contract gives the column a default, and additive otherwise. `migration plan` no longer writes `operationClass` into the call. A `migration.ts` that passes it no longer type-checks; delete the option.
|
|
431
|
+
detection:
|
|
432
|
+
glob: "**/migration.ts"
|
|
433
|
+
matches:
|
|
434
|
+
- '\bsetDefault\(\{(?:(?!\bthis\.|\brawSql\s*\()[\s\S])*?\boperationClass\s*:'
|
|
435
|
+
- id: setdefault-has-no-postcheck
|
|
436
|
+
summary: |
|
|
437
|
+
On PostgreSQL, a `setDefault` operation no longer checks afterwards that the column has a default, so the runner no longer skips it when the column already has a different default. A package planned by 8.0.0-rc.15 or a later release before this one whose `migration.ts` has a hand-written `setDefault` that changes an existing default still carries the old check in `ops.json`; write its `ops.json` again before you apply it.
|
|
438
|
+
detection:
|
|
439
|
+
glob: "**/ops.json"
|
|
440
|
+
matches:
|
|
441
|
+
- 'verify column \\"(?:[^"\\]|\\.)*?\\" has a default'
|
|
442
|
+
- id: postgres-postcheck-create-table
|
|
443
|
+
summary: |
|
|
444
|
+
On PostgreSQL, the check that runs after `createTable` checks that the relation is a table, not a view, sequence or index of the same name. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
445
|
+
detection:
|
|
446
|
+
glob: "**/ops.json"
|
|
447
|
+
matches:
|
|
448
|
+
- '"description": "create table \\"(?:[^"\\]|\\.)*?\\""(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify table \\"(?:[^"\\]|\\.)*?\\" exists",\s*"sql": "SELECT \(?to_regclass\('
|
|
449
|
+
- id: postgres-postcheck-add-column
|
|
450
|
+
summary: |
|
|
451
|
+
On PostgreSQL, the check that runs after `addColumn` checks the column's type, and that it is NOT NULL when the column is declared NOT NULL. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
452
|
+
detection:
|
|
453
|
+
glob: "**/ops.json"
|
|
454
|
+
matches:
|
|
455
|
+
- '"description": "verify column \\"(?:[^"\\]|\\.)*?\\" exists",[^{}]*\}\s*\]'
|
|
456
|
+
- id: postgres-postcheck-add-column-with-temporary-default
|
|
457
|
+
summary: |
|
|
458
|
+
On PostgreSQL, the check that runs after `addColumnWithTemporaryDefault` checks the column's type. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
459
|
+
detection:
|
|
460
|
+
glob: "**/ops.json"
|
|
461
|
+
matches:
|
|
462
|
+
- 'has no default after temporary default removal",[^{}]*\}\s*\]'
|
|
463
|
+
- id: postgres-postcheck-add-required-column-to-empty-table
|
|
464
|
+
summary: |
|
|
465
|
+
On PostgreSQL, the check that runs after `addRequiredColumnToEmptyTable` checks the column's type. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
466
|
+
detection:
|
|
467
|
+
glob: "**/ops.json"
|
|
468
|
+
matches:
|
|
469
|
+
- '"description": "verify column \\"(?:[^"\\]|\\.)*?\\" exists",[^{}]*\},\s*\{\s*"description": "verify column \\"(?:[^"\\]|\\.)*?\\" is NOT NULL",[^{}]*\}\s*\]'
|
|
470
|
+
- id: postgres-postcheck-create-index
|
|
471
|
+
summary: |
|
|
472
|
+
On PostgreSQL, the check that runs after `createIndex` checks that the relation is an index on the planned table, with the planned uniqueness, access method and columns in order, and with an expression and a predicate only where the call has them. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
473
|
+
detection:
|
|
474
|
+
glob: "**/ops.json"
|
|
475
|
+
matches:
|
|
476
|
+
- '"description": "create index \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify index \\"(?:[^"\\]|\\.)*?\\" exists",'
|
|
477
|
+
- id: postgres-postcheck-rename-index
|
|
478
|
+
summary: |
|
|
479
|
+
On PostgreSQL, the check that runs after `renameIndex` also checks that no index has the old name any more. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
480
|
+
detection:
|
|
481
|
+
glob: "**/ops.json"
|
|
482
|
+
matches:
|
|
483
|
+
- '"description": "rename index \\"(?:[^"\\]|\\.)*?\\" to \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify index \\"(?:[^"\\]|\\.)*?\\" exists",[^{}]*\}\s*\]'
|
|
484
|
+
- id: postgres-postcheck-add-primary-key
|
|
485
|
+
summary: |
|
|
486
|
+
On PostgreSQL, the check that runs after `addPrimaryKey` checks that the constraint is a primary key on the planned columns in order. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
487
|
+
detection:
|
|
488
|
+
glob: "**/ops.json"
|
|
489
|
+
matches:
|
|
490
|
+
- '"description": "verify primary key \\"(?:[^"\\]|\\.)*?\\" exists"'
|
|
491
|
+
- id: postgres-postcheck-add-unique
|
|
492
|
+
summary: |
|
|
493
|
+
On PostgreSQL, the check that runs after `addUnique` checks that the constraint is a unique constraint on the planned columns in order. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
494
|
+
detection:
|
|
495
|
+
glob: "**/ops.json"
|
|
496
|
+
matches:
|
|
497
|
+
- '"description": "add unique constraint \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify constraint \\"(?:[^"\\]|\\.)*?\\" exists",'
|
|
498
|
+
- id: postgres-postcheck-add-foreign-key
|
|
499
|
+
summary: |
|
|
500
|
+
On PostgreSQL, the check that runs after `addForeignKey` checks the constraint's columns, referenced table and columns in order, and its delete and update actions. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
501
|
+
detection:
|
|
502
|
+
glob: "**/ops.json"
|
|
503
|
+
matches:
|
|
504
|
+
- '"description": "verify FK \\"(?:[^"\\]|\\.)*?\\" exists"'
|
|
505
|
+
- id: postgres-postcheck-add-check-constraint
|
|
506
|
+
summary: |
|
|
507
|
+
On PostgreSQL, the check that runs after `addCheckConstraint` checks that the constraint is a check constraint. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
508
|
+
detection:
|
|
509
|
+
glob: "**/ops.json"
|
|
510
|
+
matches:
|
|
511
|
+
- '"description": "add check constraint \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify constraint \\"(?:[^"\\]|\\.)*?\\" exists",'
|
|
512
|
+
- id: postgres-postcheck-rename-constraint
|
|
513
|
+
summary: |
|
|
514
|
+
On PostgreSQL, the check that runs after `renameConstraint` also checks that no constraint has the old name any more. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
515
|
+
detection:
|
|
516
|
+
glob: "**/ops.json"
|
|
517
|
+
matches:
|
|
518
|
+
- '"description": "rename (?:primary key|unique constraint|foreign key|check constraint) \\"(?:[^"\\]|\\.)*?\\" to \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify constraint \\"(?:[^"\\]|\\.)*?\\" exists",[^{}]*\}\s*\]'
|
|
519
|
+
- id: postgres-postcheck-create-rls-policy
|
|
520
|
+
summary: |
|
|
521
|
+
On PostgreSQL, the check that runs after `createRlsPolicy` checks the policy's command, roles and whether it is permissive. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
522
|
+
detection:
|
|
523
|
+
glob: "**/ops.json"
|
|
524
|
+
matches:
|
|
525
|
+
- '"description": "create RLS policy \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify RLS policy \\"(?:[^"\\]|\\.)*?\\" exists",'
|
|
526
|
+
- id: postgres-postcheck-rename-rls-policy
|
|
527
|
+
summary: |
|
|
528
|
+
On PostgreSQL, the check that runs after `renameRlsPolicy` also checks that no policy has the old name any more; its precheck now also refuses a policy that already has the new name. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
529
|
+
detection:
|
|
530
|
+
glob: "**/ops.json"
|
|
531
|
+
matches:
|
|
532
|
+
- '"description": "rename RLS policy \\"(?:[^"\\]|\\.)*?\\" to \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify RLS policy \\"(?:[^"\\]|\\.)*?\\" exists",[^{}]*\}\s*\]'
|
|
533
|
+
- id: postgres-postcheck-autoincrement-default
|
|
534
|
+
summary: |
|
|
535
|
+
On PostgreSQL, the check that runs after `setDefault` with `autoincrement()` also checks that the column owns the sequence of the planned name and that the sequence has the column's integer width. An `ops.json` written before this change keeps its old check, which the runner can find already true for an object of the same name with another definition, and then skip the operation. Run `migration.ts` again for each migration that is not applied yet.
|
|
536
|
+
detection:
|
|
537
|
+
glob: "**/ops.json"
|
|
538
|
+
matches:
|
|
539
|
+
- 'takes its default from an attached sequence",[^{}]*\}\s*\]'
|
|
540
|
+
- id: postgres-alter-column-type-unchanged-refused
|
|
541
|
+
summary: |
|
|
542
|
+
On PostgreSQL, `this.alterColumnType` with `using` now refuses, with `MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`, a column the start contract, after the earlier renames in the migration, already gives the new type. Its check compares only the type, so the runner skipped the conversion. Write the conversion as a `dataTransform`.
|
|
543
|
+
detection:
|
|
544
|
+
glob: "**/migration.ts"
|
|
545
|
+
matches:
|
|
546
|
+
- '\balterColumnType\(\{(?:(?!\bthis\.)[\s\S])*?\busing\s*:'
|
|
547
|
+
- id: postgres-autoincrement-sequence-follows-column-type
|
|
548
|
+
summary: |
|
|
549
|
+
On PostgreSQL, changing the integer type of an `autoincrement()` column now also changes the sequence the column owns to the new width, and checks both afterwards. Before, a column widened from `int4` to `int8` kept an `integer` sequence, which stops at 2147483647. An `ops.json` written before this change has an `alterColumnType` without the sequence step, and an `alter type of "<column>"` step that does not name the new type; run `migration.ts` again for each migration that is not applied yet. For a database where such a migration is already applied, run `ALTER SEQUENCE … AS bigint` on the sequence `pg_get_serial_sequence` names yourself.
|
|
550
|
+
detection:
|
|
551
|
+
glob: "**/ops.json"
|
|
552
|
+
matches:
|
|
553
|
+
- '"description": "alter type of \\"(?:[^"\\]|\\.)*?\\"",(?:(?!"postcheck")[\s\S])*?"postcheck": \[\s*\{\s*"description": "verify column \\"(?:[^"\\]|\\.)*?\\" has type \\"(?:smallint|integer|bigint)\\""'
|
|
554
|
+
- id: sqlite-postchecks-check-full-definition
|
|
555
|
+
summary: |
|
|
556
|
+
On SQLite, the checks that let the runner skip an operation whose effect is already in place now check the whole definition. `createTable` checks every column's name, type, nullability and primary key position; `addColumn` checks the column's type and nullability; `createIndex` checks the index's table, ordered columns and uniqueness; `recreateTable` checks foreign key actions and `autoincrement()`. An `ops.json` written by an earlier release still carries the old checks; write it again before you apply it.
|
|
557
|
+
detection:
|
|
558
|
+
glob: "**/ops.json"
|
|
559
|
+
matches:
|
|
560
|
+
- '(?<![\s\S])(?![\s\S]*has exactly its columns)[\s\S]*"description":\s*"verify table \\"(?:[^"\\]|\\.)*\\" exists",\s*"sql":\s*"(?:[^"\\]|\\.)*sqlite_master'
|
|
561
|
+
- '(?<![\s\S])(?![\s\S]*\\" has type )[\s\S]*"description":\s*"verify column \\"(?:[^"\\]|\\.)*\\" exists",\s*"sql":\s*"(?:[^"\\]|\\.)*pragma_table_info'
|
|
562
|
+
- '(?<![\s\S])(?![\s\S]*\\" covers \()[\s\S]*"description":\s*"verify index \\"(?:[^"\\]|\\.)*\\" exists",\s*"sql":\s*"(?:[^"\\]|\\.)*sqlite_master'
|
|
563
|
+
- 'pragma_foreign_key_list\(''(?:[^''\\]|\\.)*''\) f WHERE f\.\\"table\\" = ''(?:[^''\\]|\\.)*'' GROUP BY'
|
|
564
|
+
- id: mongo-postchecks-check-full-definition
|
|
565
|
+
summary: |
|
|
566
|
+
On MongoDB, the checks that let the runner skip an operation whose effect is already in place now check the whole definition. `createIndex` checks every index option the planner tells indexes apart by, not only the keys and `unique: true`. `collMod` with a validator that is not only a `$jsonSchema` compares the whole validator. An `ops.json` written by an earlier release still carries the old checks; write it again before you apply it.
|
|
567
|
+
detection:
|
|
568
|
+
glob: "**/ops.json"
|
|
569
|
+
matches:
|
|
570
|
+
- '(?<![\s\S])(?![\s\S]*"field":\s*"sparse")[\s\S]*"description":\s*"index exists on '
|
|
571
|
+
- '(?<![\s\S])(?![\s\S]*"field":\s*"options\.validator)[\s\S]*"description":\s*"validator applied on '
|
|
572
|
+
- id: control-client-apply-takes-invocation
|
|
573
|
+
summary: |
|
|
574
|
+
`dbInit`, `dbUpdate` and `migrate` on a control client take a required `invocation` option: `{ cwd, configPath, dbGiven }`, where the commands an error's advice prints are run from, the `--config` they repeat, and whether they repeat `--db`. Pass `{ cwd: process.cwd(), configPath: undefined, dbGiven: false }` when nothing is run from a terminal. Their failures are now `CliStructuredError`s, so `DbInitFailure`, `DbUpdateFailure` and `MigrateFailure` are gone from `control-api`.
|
|
575
|
+
detection:
|
|
576
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
577
|
+
matches:
|
|
578
|
+
- '\.(?:dbInit|dbUpdate|migrate)\(\s*\{'
|
|
579
|
+
- '\b(?:DbInitFailure|DbUpdateFailure|MigrateFailure)(?:Code)?\b'
|
|
580
|
+
- id: runner-failures-report-own-codes
|
|
581
|
+
summary: |
|
|
582
|
+
A failed migration apply reports the runner's own code, such as `MIGRATION.SCHEMA_VERIFY_FAILED`, `MIGRATION.PRECHECK_FAILED`, `MIGRATION.POSTCHECK_FAILED` or `MIGRATION.EXECUTION_FAILED`, instead of `MIGRATION.RUNNER_FAILED` with `meta.runnerErrorCode`. `MIGRATION.RUNNER_FAILED` no longer exists. The facts move into typed `meta` keys: a failed schema check lists its issues in `meta.schemaIssues` (was `meta.issues`), and a failed check is `meta.failedCheck` (`operationId`, `phase`, `description`; was `operationId`, `phase`, `stepDescription`).
|
|
583
|
+
detection:
|
|
584
|
+
glob: "**/*"
|
|
585
|
+
matches:
|
|
586
|
+
- 'RUNNER_FAILED|runnerErrorCode'
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
# 8.0.0-rc.18 → 8.0.0-rc.19 — Extension author upgrade instructions
|
|
590
|
+
|
|
591
|
+
Two changes come with scripts. Run `client-takes-contract-value` first, on source files. Run `stored-contracts-drop-nocheck` before you re-emit the extension's bundled contract. Make the other changes, then re-emit the bundled contract once.
|
|
592
|
+
|
|
593
|
+
## Clients
|
|
594
|
+
|
|
595
|
+
### `client-takes-contract-value`
|
|
596
|
+
|
|
597
|
+
A client, static entry or `mongoQuery()` no longer takes `contractJson` or a type argument. This applies to `postgres()`, `postgresServerless()`, `sqlite()`, `supabase()`, `mongo()`, `postgresStatic()`, `sqliteStatic()`, `mongoStatic()` and `mongoQuery()`. If your extension's code, examples or tests build one from emitted JSON, turn the JSON into a typed value with `contractFromJson<Contract>(json)` from the facade's `/static` entry (`@prisma/orm-postgres/static`, `@prisma/orm-sqlite/static`, `@prisma/orm-mongo/static`; Supabase uses the Postgres one) and pass `{ contract }`:
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
// Before
|
|
601
|
+
const db = postgres<Contract>({ contractJson, extensions: [myPack] });
|
|
602
|
+
|
|
603
|
+
// After
|
|
604
|
+
import { contractFromJson } from '@prisma/orm-postgres/static';
|
|
605
|
+
|
|
606
|
+
const db = postgres({ contract: contractFromJson<Contract>(contractJson), extensions: [myPack] });
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
A value is validated once however many clients use it, so build it once per module and share it. A contract written with `defineContract` is already a value: keep passing it as `{ contract }`.
|
|
610
|
+
|
|
611
|
+
`mongoQuery()` takes `{ contract }`, a value typed as the contract, and infers the type from it: `mongoQuery({ contract }).from('orders')`. A test that only needs the types can declare the value: `declare const contract: Contract;`.
|
|
612
|
+
|
|
613
|
+
#### Run the script
|
|
614
|
+
|
|
615
|
+
Run `node <path to this guide>/scripts/client-takes-contract-value/contract-from-json.ts <package root>`. The script reads source files with the TypeScript compiler from the package's `typescript`; if there is none, it changes nothing, says so and exits 1. It moves each source file that passes `contractJson` to a client, static entry or `mongoQuery`: it adds the `contractFromJson` import, declares `export const contract = contractFromJson<Contract>(contractJson);` after the imports (`const contract` without `export` in a module with a default export, with a note), removes the type argument and passes `{ contract }`. It prints each file it moved. It prints to stderr, with the reason, each file it did not move, and then exits 1: a test file, a file that already has a binding named `contract`, a file whose clients use different contracts, a file whose `contractJson` value uses a binding declared after the imports, or a call it cannot read.
|
|
616
|
+
|
|
617
|
+
#### Then, by hand
|
|
618
|
+
|
|
619
|
+
1. **Tests.** Move each test that passes `contractJson` the same way: `postgres({ contract: contractFromJson<Contract>(contractJson) })`, or one `const contract = contractFromJson<Contract>(contractJson);` per file when several clients share it.
|
|
620
|
+
2. **Types.** The option types `PostgresOptionsWithContract`, `PostgresOptionsWithContractJson`, `PostgresServerlessOptionsWithContract`, `PostgresServerlessOptionsWithContractJson`, `SqliteOptionsWithContract`, `SqliteOptionsWithContractJson`, `MongoOptionsWithContract`, `MongoOptionsWithContractJson`, `SupabaseOptionsWithContract` and `SupabaseOptionsWithContractJson` are removed. Use `PostgresOptions<Contract>`, `PostgresServerlessOptions<Contract>`, `SqliteOptions<Contract>`, `MongoOptions<Contract>` or `SupabaseOptions<Contract>`.
|
|
621
|
+
3. **READMEs and JSDoc** that show a client called with `contractJson`: rewrite them to the new form.
|
|
622
|
+
|
|
623
|
+
### `collections-on-the-client`
|
|
624
|
+
|
|
625
|
+
This concerns extension packages whose code, examples or tests build a client and then an ORM by hand to reach custom collection classes. The client types (`PostgresClient`, `PostgresTransactionContext`, `PostgresServerlessClient`, `PostgresServerlessConnection`, `SqliteClient`, `SqliteTransactionContext`, `SupabaseDb`, `RoleBoundDb`, `ServiceRoleDb`) gained a second type parameter for the classes. It defaults to no classes, so existing references with one type argument keep their meaning.
|
|
626
|
+
|
|
627
|
+
`postgres()`, `postgresServerless()`, `sqlite()` and `supabase()` take an option, `collections`, with the same type `orm({ collections })` takes. The client types are inferred from it, and the classes reach every ORM the client offers:
|
|
628
|
+
|
|
629
|
+
```ts
|
|
630
|
+
// Before
|
|
631
|
+
import { orm } from '@prisma/orm-postgres/orm-client';
|
|
632
|
+
import { db } from './prisma/db';
|
|
633
|
+
|
|
634
|
+
export const posts = orm({
|
|
635
|
+
runtime: db.runtime(),
|
|
636
|
+
context: db.context,
|
|
637
|
+
collections: { Post: PostCollection },
|
|
638
|
+
}).public.Post;
|
|
639
|
+
|
|
640
|
+
// After, in the module that creates the client
|
|
641
|
+
export const db = postgres({ contract, url, collections: { Post: PostCollection } });
|
|
642
|
+
|
|
643
|
+
await db.orm.public.Post.published().all();
|
|
644
|
+
await db.transaction(async (tx) => tx.orm.public.Post.published().forUpdate().all());
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
1. Pass the classes as `collections` where the client is created. If the module that defines the classes imports the client's module, import only the `Contract` type there (`import type { Contract } from './contract.d'`), so the two modules do not import each other.
|
|
648
|
+
2. Delete the hand-built ORM client (for example a `createOrmClient(runtime)` helper) and every `orm({ runtime, context, collections })` that only existed to reach the classes. Use `db.orm` instead. On SQLite, `db.orm` is the model collections themselves: `db.orm.Post`.
|
|
649
|
+
3. A function that took a `runtime` only to build an ORM no longer needs it. Inside a transaction, pass `tx.orm`, which has the classes too.
|
|
650
|
+
4. On a serverless client, the classes are on every connection: `await using db = await postgres.connect({ url }); db.orm.public.Post.published()`.
|
|
651
|
+
5. On Supabase, the classes are on `asUser`, `asAnon` and `asServiceRole` clients. Supabase's internal `.supabase` ORM does not take them.
|
|
652
|
+
|
|
653
|
+
A type written with only the contract, such as `PostgresClient<Contract>`, still describes a client without classes and accepts a client created with them. To name a client with classes, give the second type parameter: `PostgresClient<Contract, { Post: typeof PostCollection }>`.
|
|
654
|
+
|
|
655
|
+
### `client-query-and-to-sql`
|
|
656
|
+
|
|
657
|
+
Every SQL client now runs a plan itself and renders the SQL a plan would send:
|
|
658
|
+
|
|
659
|
+
```ts
|
|
660
|
+
const plan = db.sql.public.post.select('id', 'title').limit(10).build();
|
|
661
|
+
console.log(db.toSql(plan)); // { sql: 'SELECT … LIMIT 10', params: [] }
|
|
662
|
+
for await (const row of db.query(plan)) console.log(row);
|
|
663
|
+
await db.execute(db.raw.sql`UPDATE post SET pinned = false`.affectedCount().build());
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
`db.query(plan)` and `db.execute(plan)` behave as `db.runtime().query(plan)` and `db.runtime().execute(plan)` do, and have the types `tx.query` and `tx.execute` have. Replace `db.runtime().query(` with `db.query(` and `db.runtime().execute(` with `db.execute(`. Keep `db.runtime()` where the code needs `connection()`, `close()` or `telemetry()`. On Supabase, call them on a role-bound client, as before.
|
|
667
|
+
|
|
668
|
+
`toSql(plan)` needs only the adapter and the contract, so it works with no database. `params` are the plan's values before codecs encode them. Middleware rewrites (`beforeCompile`) are not applied. A plan with `prepare` bind sites throws `RUNTIME.PREPARE_BIND_ON_ADHOC`, and a plan built for another contract throws `PLAN.HASH_MISMATCH`, as running it would.
|
|
669
|
+
|
|
670
|
+
The message of `RUNTIME.PREPARE_BIND_ON_ADHOC` changed; its code did not. Code that matches the old message text ("Ad-hoc execute received a bind-site slot") must match the code instead. The details of `PLAN.TARGET_MISMATCH` and `PLAN.HASH_MISMATCH` rename `runtimeTarget` to `contractTarget` and `runtimeStorageHash` to `contractStorageHash`, and their messages changed; match the codes.
|
|
671
|
+
|
|
672
|
+
`supabase()` no longer creates its `pg.Pool`, driver or runtimes when it is called. It creates them on the first query, so creating a client with a URL opens nothing. It is still `async` and still requires `jwtSecret` or `jwksUrl`. `close()` now ends only a pool the client created from `url`; a `pg.Pool` passed in with the `pg` option stays open, as with `postgres()`, and the caller ends it.
|
|
673
|
+
|
|
674
|
+
`postgres()`, `postgresServerless()`, `sqlite()` and `supabase()` take a new `mode` option, typed `RuntimeMode`. It is `'strict'` by default, which is how every client ran before. Under `'permissive'`, a `budgets()` violation whose severity is `'warn'` goes to `log.warn` and the query runs.
|
|
675
|
+
|
|
676
|
+
### `transaction-query-members`
|
|
677
|
+
|
|
678
|
+
Inside `db.transaction(async (tx) => …)`, `tx` now has the client's query members with the same types: `sql`, `orm` (with the client's custom collection classes), `raw`, `prepare`, `query`, `execute` and `toSql`. This holds for `postgres()`, every serverless connection, `sqlite()` and every Supabase role-bound client. A Supabase role-bound client gains `prepare`, and SQLite's `db.orm` gains `fragment`. A prepared statement runs on whatever it is given, so `ps.query(tx, params)` runs inside the transaction; `tx.prepare` lets code inside a transaction prepare without the outer client.
|
|
679
|
+
|
|
680
|
+
### Test doubles of client types
|
|
681
|
+
|
|
682
|
+
Code that only reads the client types needs no change for `client-query-and-to-sql` and `transaction-query-members`. Code that creates an object of one of them, for example a test double typed `PostgresClient<Contract>` or `PostgresTransactionContext<Contract>`, must add the new members:
|
|
683
|
+
|
|
684
|
+
| Type | New or changed members |
|
|
685
|
+
| --- | --- |
|
|
686
|
+
| `PostgresClient`, `PostgresServerlessConnection` (`@prisma/orm-postgres/runtime`, `/serverless`) | `query`, `execute`, `toSql` |
|
|
687
|
+
| `PostgresServerlessClient`, `PostgresStaticContext`, `PostgresStaticMembers` | `toSql` |
|
|
688
|
+
| `PostgresQueryMembers` | new: the query members a client and its transactions share, including `query`, `execute` and `toSql`; `PostgresClient`, `PostgresServerlessConnection` and `PostgresTransactionContext` extend it |
|
|
689
|
+
| `PostgresTransactionContext` (`@prisma/orm-postgres/runtime`) | `raw`, `prepare`, `toSql` |
|
|
690
|
+
| `SqliteClient` (`@prisma/orm-sqlite/runtime`) | `query`, `execute`, `toSql`; `orm` is `SqliteOrm`, which adds `fragment` |
|
|
691
|
+
| `SqliteStaticContext` | `toSql` |
|
|
692
|
+
| `SqliteStaticMembers` (`@prisma/orm-sqlite/runtime`) | new: the static context plus `stack`, which `SqliteClient` now extends |
|
|
693
|
+
| `SqliteQueryMembers` | new: the query members a client and its transactions share, including `query`, `execute` and `toSql`; `SqliteClient` and `SqliteTransactionContext` extend it |
|
|
694
|
+
| `SqliteTransactionContext` (`@prisma/orm-sqlite/runtime`) | `raw`, `prepare`, `toSql`; `orm` is `SqliteOrm`, the namespace's models plus `fragment` |
|
|
695
|
+
| `RoleBoundDb` (`@prisma/orm-extension-supabase/runtime`) | `prepare`, `toSql`, and the two prepared-statement entry points a statement's `query` and `execute` call; `query` and `execute` take the plan type every SQL client shares; it extends `PostgresQueryMembers`; `transaction` passes a `SupabaseTransactionContext` instead of a plain `TransactionContext` |
|
|
696
|
+
| `ServiceRoleDb` (`@prisma/orm-extension-supabase/runtime`) | `toSql`; `query` and `execute` take the plan type every SQL client shares |
|
|
697
|
+
| `SupabaseTransactionContext` | new; has `toSql` |
|
|
698
|
+
| `SupabaseInternalDb` (the service role's `supabase` member) | `toSql`, over the Supabase contract |
|
|
699
|
+
| `OrmClientRootMembers` (ORM client) | newly exported: the members of the ORM client beside its namespaces, today `fragment` |
|
|
700
|
+
| `PrepareMember` (ORM client) | newly exported: the `prepare` member a client and its transactions offer |
|
|
701
|
+
|
|
702
|
+
A hand-built `RoleBoundDb`, such as a test double, must also implement the two prepared-statement entry points, keyed by the symbols `preparedStatementQuery` and `preparedStatementExecute`. Import them from `@prisma/orm-postgres/family-runtime/internal/prepared-query` (or `@prisma/orm-family-sql/runtime/internal/prepared-query`). Like every `internal/` entry, it can change without notice.
|
|
703
|
+
|
|
704
|
+
A callback typed to take a plain `TransactionContext` still compiles where a transaction context is passed, because each new context type extends it.
|
|
705
|
+
|
|
706
|
+
## Clients start their own activities, and ORM calls are activities
|
|
707
|
+
|
|
708
|
+
The runtime's middleware tree now has a third kind of activity: one a client starts for an act the runtime would not otherwise see. A client starts it with `startActivityOn(queryable, activity)` and sends statements through the returned handle. A middleware can supply such an activity's result through the new `intercept.onActivity` handler. Each SQL ORM terminal method is one such activity, of kind `orm-call`. Its statements, its own transaction (`handle.transaction()`) and the read-back after a nested write are its children, and the annotations a `configure` callback records are on the call.
|
|
709
|
+
|
|
710
|
+
### `runtime-activity-is-activity`
|
|
711
|
+
|
|
712
|
+
Replace every `RuntimeActivity` with `Activity`, imported from the same module. There is no alias:
|
|
713
|
+
|
|
714
|
+
```ts
|
|
715
|
+
// before
|
|
716
|
+
import type { RuntimeActivity } from '@prisma/orm-framework/components/runtime';
|
|
717
|
+
async activityStarted(activity: RuntimeActivity, ctx) {}
|
|
718
|
+
|
|
719
|
+
// after
|
|
720
|
+
import type { Activity } from '@prisma/orm-framework/components/runtime';
|
|
721
|
+
async activityStarted(activity: Activity, ctx) {}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### `activity-includes-client-activities`
|
|
725
|
+
|
|
726
|
+
`Activity` always includes `ClientActivity`, whose kind contains a hyphen. A hook that rules out `'transaction'` and then reads statement fields stops compiling. Test for the kind you handle instead:
|
|
727
|
+
|
|
728
|
+
```ts
|
|
729
|
+
// before
|
|
730
|
+
if (result.kind === 'transaction') return;
|
|
731
|
+
record(result.latencyMs);
|
|
732
|
+
|
|
733
|
+
// after
|
|
734
|
+
if (result.kind !== 'statement') return;
|
|
735
|
+
record(result.latencyMs);
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
A middleware that sees a kind it does not know should ignore it rather than fail.
|
|
739
|
+
|
|
740
|
+
### `start-a-client-activity`
|
|
741
|
+
|
|
742
|
+
An extension can start an activity for an act of its own, such as a report that sends several statements, so that middleware observe it, finalize it and can serve it from a cache.
|
|
743
|
+
|
|
744
|
+
**Dependencies.** `startActivityOn`, `planContentHashOn` and the types their signatures name, such as `StartActivity`, `ClientActivity`, `ClientActivityKind` and `ActivityInterceptResult`, are in `@prisma/orm-family-sql/runtime`. The registry, `ClientActivityRegistry`, is in `@prisma/orm-framework/components/runtime`, which also has the types. An application or extension that uses `postgres()` already depends on `@prisma/orm-postgres`, which re-exports both as `@prisma/orm-postgres/components/runtime` and `@prisma/orm-postgres/family-runtime`. Augment `ClientActivityRegistry` through the entry that exports it, `@prisma/orm-framework/components/runtime` or `@prisma/orm-postgres/components/runtime`. Augmenting it through `@prisma/orm-family-sql/runtime` or `@prisma/orm-postgres/family-runtime` registers nothing, and TypeScript reports no error.
|
|
745
|
+
|
|
746
|
+
**Register the kind.** A client kind contains a hyphen; a kind without one is rejected with `RUNTIME.ACTIVITY_KIND_RESERVED`. Registering the result type is optional. The file that augments the entry must also import from it, or TypeScript reports that the module cannot be found:
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
import type { ClientActivityResult } from '@prisma/orm-postgres/components/runtime';
|
|
750
|
+
|
|
751
|
+
declare module '@prisma/orm-postgres/components/runtime' {
|
|
752
|
+
interface ClientActivityRegistry {
|
|
753
|
+
'report-run': { result: { readonly emails: readonly string[] } };
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export type ReportResult = ClientActivityResult<'report-run'>;
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
The registry types the handle side only: `call.intercepted.result` and `call.end({ result })`. In middleware hooks the result of a client activity is `unknown`, even after `result.kind === 'report-run'`, because the hook types must accept unregistered kinds; check or cast the value there.
|
|
761
|
+
|
|
762
|
+
**Start, use and end it.** Pass a function that derives the content hash from the plan with `planContentHashOn`. The runtime calls it only when a middleware calls `activity.contentHash()`, and once per activity. It equals the digest `ctx.contentHash(exec)` returns for the statement's execution plan only when `beforeCompile` gives the same result for the plan in both places, no `beforeEncode` hook changes the parameters, and the codecs encode deterministically. (For `planContentHashOn`, `beforeCompile` runs with the scope and parent of the queryable it is called on and with fresh state, so on the runtime itself it has no parent. For the statement, it runs beneath the handle, with the state its `activityStarted` hooks wrote.) A cache should key on the activity's own content hash and not rely on the two being equal. End every handle, in a `finally`, or `close()` waits for it forever:
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
import { planContentHashOn, startActivityOn } from '@prisma/orm-postgres/family-runtime';
|
|
766
|
+
import type { SqlQueryPlan } from '@prisma/orm-postgres/relational-core/plan';
|
|
767
|
+
import type { PostgresClient } from '@prisma/orm-postgres/runtime';
|
|
768
|
+
|
|
769
|
+
async function runReport(db: PostgresClient<Contract>, plan: SqlQueryPlan<{ email: string }>) {
|
|
770
|
+
const runtime = db.runtime();
|
|
771
|
+
const call = await startActivityOn(runtime, {
|
|
772
|
+
kind: 'report-run',
|
|
773
|
+
contentHash: () => planContentHashOn(runtime, plan),
|
|
774
|
+
});
|
|
775
|
+
let outcome: 'succeeded' | 'failed' = 'failed';
|
|
776
|
+
let result: { readonly emails: readonly string[] } | undefined;
|
|
777
|
+
try {
|
|
778
|
+
if (call.intercepted) {
|
|
779
|
+
outcome = 'succeeded';
|
|
780
|
+
return call.intercepted.result.emails;
|
|
781
|
+
}
|
|
782
|
+
const rows = await call.query(plan).toArray();
|
|
783
|
+
result = { emails: rows.map((row) => row.email) };
|
|
784
|
+
outcome = 'succeeded';
|
|
785
|
+
return result.emails;
|
|
786
|
+
} finally {
|
|
787
|
+
await call.end({ outcome, ...(result === undefined ? {} : { result }) });
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
Statements sent through `call` are the activity's children. `call.transaction()` starts a transaction beneath it, on a connection it owns; it is absent when the handle was opened under a transaction. `startActivity` on the handle nests another activity.
|
|
793
|
+
|
|
794
|
+
**Serve it from middleware.** `intercept.onActivity(activity: ClientActivity, ctx)` runs after `activityStarted`. The first handler that returns `{ result }` wins, and the handle's `intercepted` is set. The ended result carries `source: 'middleware'` when the client then ends without a result:
|
|
795
|
+
|
|
796
|
+
```ts
|
|
797
|
+
import type { SqlMiddleware } from '@prisma/orm-postgres/family-runtime';
|
|
798
|
+
|
|
799
|
+
const served = new Map<string, unknown>();
|
|
800
|
+
|
|
801
|
+
const reportCache: SqlMiddleware = {
|
|
802
|
+
name: 'report-cache',
|
|
803
|
+
familyId: 'sql',
|
|
804
|
+
intercept: {
|
|
805
|
+
async onActivity(activity) {
|
|
806
|
+
if (activity.kind !== 'report-run') return undefined;
|
|
807
|
+
const hit = served.get(await activity.contentHash());
|
|
808
|
+
return hit === undefined ? undefined : { result: hit };
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
async activityEnded(activity, result) {
|
|
812
|
+
if (activity.kind !== 'report-run' || result.kind !== 'report-run') return;
|
|
813
|
+
if (result.outcome === 'succeeded' && result.source === 'client' && result.result !== undefined) {
|
|
814
|
+
served.set(await activity.contentHash(), result.result);
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
};
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### `orm-annotations-on-the-call`
|
|
821
|
+
|
|
822
|
+
The statements an ORM call sends carry only the reserved keys the ORM sets. A middleware that reads an ORM annotation from a statement plan (`handle.read(plan)` or `plan.meta.annotations`) finds nothing. Read it from the call, in `activityStarted(activity)` or `intercept.onActivity(activity)`:
|
|
823
|
+
|
|
824
|
+
```ts
|
|
825
|
+
const audit: SqlMiddleware = {
|
|
826
|
+
name: 'audit',
|
|
827
|
+
familyId: 'sql',
|
|
828
|
+
async activityStarted(activity) {
|
|
829
|
+
if (activity.kind !== 'orm-call') return;
|
|
830
|
+
const actor = auditAnnotation.read(activity);
|
|
831
|
+
if (actor) log(actor);
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
A middleware can answer the call through `intercept.onActivity`. The value must have the shape the terminal method returns, or the ORM rejects with `ORM.INTERCEPTED_RESULT_INVALID`.
|
|
837
|
+
|
|
838
|
+
### `cache-store-holds-cached-values`
|
|
839
|
+
|
|
840
|
+
A cache store for `@internal/middleware-cache` holds `CachedValue`, which is `{ kind: 'statement-rows', rows }` or `{ kind: 'call-result', result }`, instead of `CachedRows`. `CacheStore` and `CacheEntry` default their value type to `CachedValue`, and `createCacheMiddleware` accepts only a `CacheStore<TMeta, CachedValue>`:
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
import type { CachedValue, CacheStore } from '@internal/middleware-cache';
|
|
844
|
+
|
|
845
|
+
const store: CacheStore<unknown, CachedValue> = {
|
|
846
|
+
// get, set and unset as before; value is now { kind: 'statement-rows', rows } or { kind: 'call-result', result }
|
|
847
|
+
};
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
`get` must give back a value equivalent to the one `set` was given, `kind` included. An ORM call's result holds decoded values such as `Date`, `bigint`, Temporal values and extension class instances, so a store that serializes needs a serializer that rebuilds them; JSON does not. `createInMemoryCacheStore` takes only the meta type now and always holds `CachedValue`: drop its second type argument.
|
|
851
|
+
|
|
852
|
+
### Activity sources on connections, runtimes and queryables
|
|
853
|
+
|
|
854
|
+
Activities start through an `activitySource` member of a queryable. The member is keyed by a symbol, so the public queryable types do not show it, and `startActivityOn` and `planContentHashOn` read it. The SQL ORM starts its `orm-call` activity on the queryable it holds, through this member only, and sends every statement through the activity's handle. A terminal method on a queryable without an activity source fails with `ORM.ACTIVITY_SOURCE_MISSING`: streaming terminals throw when called, and the others reject.
|
|
855
|
+
|
|
856
|
+
#### `runtime-connections-carry-an-activity-source`
|
|
857
|
+
|
|
858
|
+
Inside a `SqlRuntimeBase` subclass, a connection-like object over a raw connection gets its activity members from the base class. Add `ActivitySourceTarget` to the object's declared type. The public `RuntimeConnection` type does not require the member.
|
|
859
|
+
|
|
860
|
+
```ts
|
|
861
|
+
const session: RoleSession & TransactionStartTarget & ActivitySourceTarget = {
|
|
862
|
+
[transactionStart]: createTransactionStart(),
|
|
863
|
+
[activitySource]: self.connectionActivitySource(conn, () => session),
|
|
864
|
+
// query, execute, transaction, release, destroy as before
|
|
865
|
+
};
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
`activitySource` and `ActivitySourceTarget` come from `@prisma/orm-family-sql/runtime`. `transactionStart`, `createTransactionStart` and `TransactionStartTarget` still come from `@prisma/orm-family-sql/runtime/internal/prepared-query`.
|
|
869
|
+
|
|
870
|
+
#### `orm-runtime-objects-carry-an-activity-source`
|
|
871
|
+
|
|
872
|
+
`db.runtime()` passed straight to `orm()` already carries an activity source. A facade, such as `postgres()`, that builds its own runtime object for the ORM, such as one that looks up its runtime on every call, gives it the member:
|
|
873
|
+
|
|
874
|
+
```ts
|
|
875
|
+
import { activitySource, delegatingActivitySource } from '@prisma/orm-family-sql/runtime';
|
|
876
|
+
|
|
877
|
+
const orm = ormBuilder({
|
|
878
|
+
runtime: {
|
|
879
|
+
query: (plan) => getRuntime().query(plan),
|
|
880
|
+
execute: (plan) => getRuntime().execute(plan),
|
|
881
|
+
connection: () => getRuntime().connection(),
|
|
882
|
+
[activitySource]: delegatingActivitySource(() => getRuntime()),
|
|
883
|
+
},
|
|
884
|
+
context,
|
|
885
|
+
});
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Do the same for the ORM object inside `db.transaction(fn)`, with `delegatingActivitySource(() => txCtx)`. Do not add plain `startActivity` or `planContentHash` members instead: `findActivitySource` and `startActivityOn` read only the `activitySource` member, so an object with only the plain members starts no activity.
|
|
889
|
+
|
|
890
|
+
If the facade sends its statements through sessions of its own, such as role-bound sessions, the activity's statements and transaction must go through them too. Give an activity source that does so. The plain runtime's source would run the activity's statements without the session.
|
|
891
|
+
|
|
892
|
+
#### `queryable-carries-the-activity-source`
|
|
893
|
+
|
|
894
|
+
A wrapper that copies a runtime, connection or transaction to intercept its `query` and `execute`, for example to record plans, also copies the `[activitySource]` member, so ORM statements bypass the wrapper. Wrap the activity source too: return a source whose `startActivity` wraps each handle the same way, including the transaction its `transaction()` returns. A custom queryable passed to the ORM must carry an activity source. A test stub can use `delegatingActivitySource(() => realRuntime)`, or a source whose handles send their statements to the stub.
|
|
895
|
+
|
|
896
|
+
### Pass a parent when beginning or starting a transaction
|
|
897
|
+
|
|
898
|
+
#### `begin-transaction-on-takes-a-parent`
|
|
899
|
+
|
|
900
|
+
`SqlRuntimeBase.beginTransactionOn(connection, owner)` takes a third argument, `parent`: the activity the transaction starts beneath, or `undefined`. Pass `undefined` where you began a transaction before:
|
|
901
|
+
|
|
902
|
+
```ts
|
|
903
|
+
// before
|
|
904
|
+
return self.beginTransactionOn(conn, session);
|
|
905
|
+
// after
|
|
906
|
+
return self.beginTransactionOn(conn, session, undefined);
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
#### `transaction-activity-takes-a-parent`
|
|
910
|
+
|
|
911
|
+
`startTransactionActivity` requires `parent`:
|
|
912
|
+
|
|
913
|
+
```ts
|
|
914
|
+
// before
|
|
915
|
+
await startTransactionActivity({ middleware, context, holdFinishedChildren });
|
|
916
|
+
// after
|
|
917
|
+
await startTransactionActivity({ middleware, context, parent: undefined, holdFinishedChildren });
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
An `ActivityParent` you implement yourself has `acceptsChildren()`, and its `adopt(child)` returns an `ActivityChildSlot` (`statementFailed()`, `ended(own)`, `transactionAcceptsChildren()`) instead of an end callback. `ActivityParent.activity` is an `ActivityBase`. A transaction stops accepting children with `stopAcceptingChildren()`, and adopting beneath a parent that no longer accepts children throws `RUNTIME.ACTIVITY_NOT_ACCEPTING_CHILDREN` instead of an internal error. Most runtimes only pass the parent they were given to `streamStatement`, `executeStatement`, `startTransactionActivity` or `startClientActivity` and need no change.
|
|
921
|
+
|
|
922
|
+
## `aggregate-operation-using-reserved`
|
|
923
|
+
|
|
924
|
+
A collection's new `using` member holds the collection methods that packages contribute for the model. Aggregate operations become reducer methods on the same collection, so a contributed aggregate operation named `using` would collide with it. `orm()` refuses such an operation when the client is built, with `ORM.AGGREGATE_OPERATION_RESERVED`, as it refuses `with` and `fragment`. Rename the operation in the extension's aggregate descriptors, and in the operation types the extension's contracts reference.
|
|
925
|
+
|
|
926
|
+
## Codecs read wire values and data type values
|
|
927
|
+
|
|
928
|
+
### `codecs-read-wire-and-data-type-values`
|
|
929
|
+
|
|
930
|
+
A codec converts between three forms of a value: the application's value, the value the database driver sends and returns (the wire value), and the value a contract stores, now a `DataTypeValue` of the codec's data type. Four methods replace the old four:
|
|
931
|
+
|
|
932
|
+
| Before | After | Converts |
|
|
933
|
+
| --- | --- | --- |
|
|
934
|
+
| `encode(value, ctx)` | `toWire(value, ctx)` | application value to wire value |
|
|
935
|
+
| `decode(wire, ctx)` | `fromWire(wire, ctx)` | wire value to application value |
|
|
936
|
+
| `encodeJson(value)` | `toDataTypeValue(value)` | application value to stored value |
|
|
937
|
+
| `decodeJson(json)` | `fromDataTypeValue(value)` | stored value to application value |
|
|
938
|
+
|
|
939
|
+
`toDataTypeValue` returns a `DataTypeValue`, and `fromDataTypeValue` receives one; its `value` is the JSON the contract stores. The data type has already read that JSON with its `read` function under the column's parameters, so `fromDataTypeValue` does not check it again. A codec builds the value it hands over with `this.dataTypeValueOf(json)`, which calls the data type's `valueFrom` and so refuses JSON the type does not hold. To make that possible, the codec's constructor passes its data type and its parameters to `CodecImpl`:
|
|
940
|
+
|
|
941
|
+
```ts
|
|
942
|
+
// before
|
|
943
|
+
export class PgVectorCodec extends CodecImpl</* … */> {
|
|
944
|
+
constructor(descriptor: AnyCodecDescriptor, length: number) {
|
|
945
|
+
super(descriptor);
|
|
946
|
+
this.length = length;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
async encode(value: number[], _ctx: CodecCallContext): Promise<string> {
|
|
950
|
+
this.assertVector(value, 'RUNTIME.ENCODE_FAILED');
|
|
951
|
+
return `[${value.join(',')}]`;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
async decode(wire: string, _ctx: CodecCallContext): Promise<number[]> {
|
|
955
|
+
return parseVector(wire);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
encodeJson(value: number[]): JsonValue {
|
|
959
|
+
this.assertVector(value, 'RUNTIME.ENCODE_FAILED');
|
|
960
|
+
return [...value];
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
decodeJson(json: JsonValue): number[] {
|
|
964
|
+
if (!Array.isArray(json) || json.length !== this.length) return this.refuseJson(json);
|
|
965
|
+
return json.map(Number);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// after
|
|
970
|
+
import type { DataTypeValue } from '@prisma/orm-framework/components/codec';
|
|
971
|
+
import { pgvectorVector } from './data-types';
|
|
972
|
+
|
|
973
|
+
export class PgVectorCodec extends CodecImpl</* … */> {
|
|
974
|
+
constructor(descriptor: AnyCodecDescriptor, length: number) {
|
|
975
|
+
super(descriptor, pgvectorVector, { length });
|
|
976
|
+
this.length = length;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
async toWire(value: number[], _ctx: CodecCallContext): Promise<string> {
|
|
980
|
+
this.assertVector(value, 'RUNTIME.ENCODE_FAILED');
|
|
981
|
+
return `[${value.join(',')}]`;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
async fromWire(wire: string, _ctx: CodecCallContext): Promise<number[]> {
|
|
985
|
+
return parseVector(wire);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
fromDataTypeValue(value: DataTypeValue<readonly number[]>): number[] {
|
|
989
|
+
return [...value.value];
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
toDataTypeValue(input: number[]): DataTypeValue {
|
|
993
|
+
this.assertVector(input, 'RUNTIME.ENCODE_FAILED');
|
|
994
|
+
return this.dataTypeValueOf([...input]);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
A codec without parameters passes only its data type: `super(descriptor, pgJsonb)`. A descriptor whose codec takes parameters passes them from `factory`:
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
// before
|
|
1003
|
+
override factory(_params: GeometryParams) {
|
|
1004
|
+
return () => new PostgisGeometryCodec(this);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// after
|
|
1008
|
+
override factory(params: GeometryParams) {
|
|
1009
|
+
return () => new PostgisGeometryCodec(this, params ?? {});
|
|
1010
|
+
}
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
`DataTypeValue`, `DataTypeReader` and `refuseJsonValue` are exported from `@prisma/orm-framework/components/codec`, beside `CodecImpl`.
|
|
1014
|
+
|
|
1015
|
+
A Mongo codec made with `mongoCodec()` from `@prisma/orm-family-mongo/codec` takes the four options under the new names, and names its data type in a required `dataType` option, with the column's parameters in an optional `params`. Its `toDataTypeValue` returns a `DataTypeValue` too, which it builds with `dataType.valueFrom(json, params)`. It may be left out when the application value is JSON, and `fromDataTypeValue` may be left out only when the application type is exactly `JsonValue`. Declare the data type with `mongoDataType` from `@prisma/orm-family-mongo/contract/data-type`, and register it in the pack's `dataTypes` beside the codec:
|
|
1016
|
+
|
|
1017
|
+
```ts
|
|
1018
|
+
// before
|
|
1019
|
+
import { Long } from 'bson';
|
|
1020
|
+
import { mongoCodec } from '@prisma/orm-family-mongo/codec';
|
|
1021
|
+
import { decodeJsonString } from '@prisma/orm-framework/components/codec';
|
|
1022
|
+
import type { JsonValue } from '@prisma/orm-framework/contract/types';
|
|
1023
|
+
|
|
1024
|
+
export const moneyCodec = mongoCodec({
|
|
1025
|
+
typeId: 'acme/money@1',
|
|
1026
|
+
encode: (value: bigint) => Long.fromBigInt(value),
|
|
1027
|
+
decode: (wire: Long) => wire.toBigInt(),
|
|
1028
|
+
encodeJson: (value: bigint) => value.toString(),
|
|
1029
|
+
decodeJson: (json: JsonValue) => BigInt(decodeJsonString('acme/money@1', json)),
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
// after
|
|
1033
|
+
import { Long } from 'bson';
|
|
1034
|
+
import { mongoCodec } from '@prisma/orm-family-mongo/codec';
|
|
1035
|
+
import { mongoDataType } from '@prisma/orm-family-mongo/contract/data-type';
|
|
1036
|
+
import { type DataTypeValue, readJsonMatching } from '@prisma/orm-framework/components/codec';
|
|
1037
|
+
|
|
1038
|
+
export const acmeMoney = mongoDataType('acme/money', {
|
|
1039
|
+
bsonTypes: ['long'],
|
|
1040
|
+
read: (json) => readJsonMatching('acme/money', json, /^-?\d+$/, 'decimal integer text'),
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
export const moneyCodec = mongoCodec({
|
|
1044
|
+
typeId: 'acme/money@1',
|
|
1045
|
+
dataType: acmeMoney,
|
|
1046
|
+
toWire: (value: bigint) => Long.fromBigInt(value),
|
|
1047
|
+
fromWire: (wire: Long) => wire.toBigInt(),
|
|
1048
|
+
toDataTypeValue: (value: bigint) => acmeMoney.valueFrom(value.toString(), {}),
|
|
1049
|
+
fromDataTypeValue: (value: DataTypeValue<string>) => BigInt(value.value),
|
|
1050
|
+
});
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### `data-types-own-parameter-limits`
|
|
1054
|
+
|
|
1055
|
+
A data type now reads every value a contract stores, with a `read` function that receives the JSON and the column's parameters. It returns the JSON when the type holds it and refuses it with `refuseJsonValue` when it does not. `read` is required: a data type that declared none before declares one now. It replaces `toCanonicalForm`, on the data type or on a codec descriptor, and the checks a codec made in `decodeJson` against its parameters move into it:
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
// before
|
|
1059
|
+
export const pgvectorVector = sqlDataType('pgvector/vector', {
|
|
1060
|
+
params: pgvectorVectorParams,
|
|
1061
|
+
texts: [{ text: 'vector({length})', written: true, catalog: true }],
|
|
1062
|
+
toCanonicalForm: vectorCanonicalForm,
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
// after
|
|
1066
|
+
const readVector: DataTypeReader = (json, params) => {
|
|
1067
|
+
const length = typeof params['length'] === 'number' ? params['length'] : undefined;
|
|
1068
|
+
const expected =
|
|
1069
|
+
length === undefined ? 'an array of finite numbers' : `an array of ${length} finite numbers`;
|
|
1070
|
+
if (!Array.isArray(json) || (length !== undefined && json.length !== length)) {
|
|
1071
|
+
return refuseJsonValue('pgvector/vector', expected, json);
|
|
1072
|
+
}
|
|
1073
|
+
if (!json.every(isFiniteNumber)) return refuseJsonValue('pgvector/vector', expected, json);
|
|
1074
|
+
return json;
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
export const pgvectorVector = sqlDataType('pgvector/vector', {
|
|
1078
|
+
params: pgvectorVectorParams,
|
|
1079
|
+
read: readVector,
|
|
1080
|
+
texts: [{ text: 'vector({length})', written: true, catalog: true }],
|
|
1081
|
+
});
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
A data type that had neither `toCanonicalForm` nor parameter checks gets a `read` that accepts the JSON it stores, as PostGIS's geometry now reads HEXEWKB text:
|
|
1085
|
+
|
|
1086
|
+
```ts
|
|
1087
|
+
// before
|
|
1088
|
+
export const postgisGeometry = sqlDataType('postgis/geometry', { params, texts });
|
|
1089
|
+
|
|
1090
|
+
// after
|
|
1091
|
+
const readGeometry: DataTypeReader = (json, params) => {
|
|
1092
|
+
const hex = readJsonMatching('postgis/geometry', json, HEX_TEXT, 'a HEXEWKB string');
|
|
1093
|
+
const srid = params['srid'];
|
|
1094
|
+
if (typeof srid !== 'number' || sridOf(hex) === srid) return json;
|
|
1095
|
+
return refuseJsonValue('postgis/geometry', `a HEXEWKB string of a geometry with SRID ${srid}`, json);
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
export const postgisGeometry = sqlDataType('postgis/geometry', { params, texts, read: readGeometry });
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
A parameter that limits values, such as a geometry column's SRID, is checked the same way, in the data type's `read`. A contract that stores a value the type refuses no longer loads, so a stored default that broke the limit is reported when the contract is read, not when a query runs.
|
|
1102
|
+
|
|
1103
|
+
### `json-helpers-renamed`
|
|
1104
|
+
|
|
1105
|
+
The helpers that read a value from JSON now say so, and the one that writes a float says what it returns. Rename each call:
|
|
1106
|
+
|
|
1107
|
+
| Before | After |
|
|
1108
|
+
| --- | --- |
|
|
1109
|
+
| `decodeJsonString` | `readJsonString` |
|
|
1110
|
+
| `decodeJsonMatching` | `readJsonMatching` |
|
|
1111
|
+
| `decodeJsonInteger` | `readJsonInteger` |
|
|
1112
|
+
| `decodeJsonIntegerText` | `readJsonIntegerText` |
|
|
1113
|
+
| `decodeJsonFloat` | `readJsonFloat` |
|
|
1114
|
+
| `decodeJsonBoolean` | `readJsonBoolean` |
|
|
1115
|
+
| `encodeJsonFloat` | `floatToJson` |
|
|
1116
|
+
|
|
1117
|
+
They take the same arguments. `ToCanonicalForm` and `canonicalFormOf` are removed; a data type's `read` (`data-types-own-parameter-limits`) does their work.
|
|
1118
|
+
|
|
1119
|
+
### `stored-form-helpers-renamed`
|
|
1120
|
+
|
|
1121
|
+
The text a contract stores for a value is its stored form, and the helpers that read or write it now say so. They are exported from `@prisma/orm-family-sql/contract/data-type-support` and `@prisma/orm-family-sql/contract/sql-expression` as before. Rename each use:
|
|
1122
|
+
|
|
1123
|
+
| Before | After |
|
|
1124
|
+
| --- | --- |
|
|
1125
|
+
| `canonicalDateTime` | `dateTimeStoredText` |
|
|
1126
|
+
| `CanonicalDateTimeOptions` | `DateTimeStoredTextOptions` |
|
|
1127
|
+
| `canonicalNumeralText` | `storedNumeralText` |
|
|
1128
|
+
| `sqlTextFromCanonical` | `sqlTextFromStored` |
|
|
1129
|
+
| `canonicalSqlText` | `storedSqlText` |
|
|
1130
|
+
|
|
1131
|
+
They take the same arguments and return the same values.
|
|
1132
|
+
|
|
1133
|
+
### `projections-read-by-from-wire`
|
|
1134
|
+
|
|
1135
|
+
An include and an aggregate now read each column of their JSON result with the codec's `fromWire`, as a plain read does. A descriptor's `jsonProjection` therefore writes the value the driver returns for an ordinary row, not the codec's stored JSON form. For a type the database prints as text, that is usually the text:
|
|
1136
|
+
|
|
1137
|
+
```ts
|
|
1138
|
+
// before
|
|
1139
|
+
protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr {
|
|
1140
|
+
return FunctionCallExpr.of('array_to_json', [
|
|
1141
|
+
CastExpr.as(CastExpr.as(expression, 'real[]'), 'float8[]'),
|
|
1142
|
+
]);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// after
|
|
1146
|
+
protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr {
|
|
1147
|
+
return CastExpr.as(expression, 'text');
|
|
1148
|
+
}
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
A projection that returned the column unchanged may need the cast too: a JSON constructor given a `jsonb` document that is a string, or a PostGIS geometry, writes something other than the text a row carries.
|
|
1152
|
+
|
|
1153
|
+
### `empty-result-json-renamed-empty-result-wire`
|
|
1154
|
+
|
|
1155
|
+
An aggregate descriptor that declares a value for an empty input names it `emptyResultWire` instead of `emptyResultJson`. It holds the value the driver would return for that result, and the result codec reads it with `fromWire`. Only the name changes where the value was already what the driver returns:
|
|
1156
|
+
|
|
1157
|
+
```ts
|
|
1158
|
+
// before
|
|
1159
|
+
{ ...sumIntegers, nullable: false as const, emptyResultJson: '0' }
|
|
1160
|
+
|
|
1161
|
+
// after
|
|
1162
|
+
{ ...sumIntegers, nullable: false as const, emptyResultWire: '0' }
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
## Family codec templates and enum blocks
|
|
1166
|
+
|
|
1167
|
+
### `family-codec-templates-render-no-literals`
|
|
1168
|
+
|
|
1169
|
+
```ts
|
|
1170
|
+
// before: the template renders, and postgresCodec copied it
|
|
1171
|
+
export class MyTextTemplate extends CodecDescriptorTemplateImpl<void> {
|
|
1172
|
+
override renderValueLiteral(value: JsonValue): string | undefined {
|
|
1173
|
+
return renderTsLiteral(value);
|
|
1174
|
+
}
|
|
1175
|
+
// ...
|
|
1176
|
+
}
|
|
1177
|
+
export const myTextDescriptor = postgresCodec(myTextTemplate, {
|
|
1178
|
+
dataType: pgText,
|
|
1179
|
+
jsonProjection: (expression) => expression,
|
|
1180
|
+
factory: (descriptor, dataType) => () => new MyTextCodec(descriptor, dataType),
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// after: the template renders nothing, and the target passes the renderer
|
|
1184
|
+
export class MyTextTemplate extends CodecDescriptorTemplateImpl<void> {
|
|
1185
|
+
// ...
|
|
1186
|
+
}
|
|
1187
|
+
export const myTextDescriptor = postgresCodec(myTextTemplate, {
|
|
1188
|
+
dataType: pgText,
|
|
1189
|
+
renderValueLiteral: renderTsLiteral,
|
|
1190
|
+
jsonProjection: (expression) => expression,
|
|
1191
|
+
factory: (descriptor, dataType) => () => new MyTextCodec(descriptor, dataType),
|
|
1192
|
+
});
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
Without the option, the adapted codec renders no literal, so an enum or value set on it is typed as the codec's whole output type.
|
|
1196
|
+
|
|
1197
|
+
### `enum-block-written-values`
|
|
1198
|
+
|
|
1199
|
+
1. Read `block.writtenValues?.[key]` in place of `block.numberTexts?.[key]`. A number entry is `{ kind: 'number', text }`, with the text as written.
|
|
1200
|
+
2. Rename a `readWrittenNumber` hook to `readWrittenValueForCodec`, and read `written` in place of `text`.
|
|
1201
|
+
3. Give every refused `WrittenValueReading` a `code`, such as `PSL_VALUE_TYPE_INCOMPATIBLE`.
|
|
1202
|
+
|
|
1203
|
+
```ts
|
|
1204
|
+
// before
|
|
1205
|
+
readWrittenNumber: ({ text, codecId, subject }) => ({ ok: false, message: `${subject}: refused` }),
|
|
1206
|
+
|
|
1207
|
+
// after
|
|
1208
|
+
readWrittenValueForCodec: ({ written, codecId, subject }) => ({
|
|
1209
|
+
ok: false,
|
|
1210
|
+
code: 'PSL_VALUE_TYPE_INCOMPATIBLE',
|
|
1211
|
+
message: `${subject}: refused`,
|
|
1212
|
+
}),
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
The detection matches every file that names `WrittenValueReading`, including one that already sets `code`. Check each refused reading.
|
|
1216
|
+
|
|
1217
|
+
## Columns are compared by their data type
|
|
1218
|
+
|
|
1219
|
+
### `type-lookups-one-framework-type`
|
|
1220
|
+
|
|
1221
|
+
The pair of a codec lookup and a data type lookup has one type in the framework. Replace `SqlTypeLookups` and `MongoTypeLookups` with `TypeLookups`:
|
|
1222
|
+
|
|
1223
|
+
```ts
|
|
1224
|
+
// Before
|
|
1225
|
+
import type { SqlTypeLookups } from '@prisma/orm-family-sql/contract/data-type';
|
|
1226
|
+
function storageTypeOf(codecId: string, lookups: SqlTypeLookups) {}
|
|
1227
|
+
|
|
1228
|
+
// After
|
|
1229
|
+
import type { TypeLookups } from '@prisma/orm-framework/components/codec';
|
|
1230
|
+
function storageTypeOf(codecId: string, lookups: TypeLookups) {}
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
`TypeLookups` asks only for `codecLookup.descriptorFor` and `dataTypeLookup.get`. Where your code needs the whole lookups, name them as type arguments: `TypeLookups<CodecLookupWithDescriptors, DataTypeLookup>`.
|
|
1234
|
+
|
|
1235
|
+
### `type-name-normalisers-removed`
|
|
1236
|
+
|
|
1237
|
+
`normalizeSchemaNativeType` and `normalizeSqliteNativeType` are gone. If you used them to decide whether two columns have the same type, compare the columns' data types instead, with `sqlColumnTypesEqual(a, b)` from `@prisma/orm-family-sql/schema-ir/types`: two columns have the same type when both have a `storedType`, the two are one data type, their `storedTypeParams` are equal, and both or neither are lists.
|
|
1238
|
+
|
|
1239
|
+
### `introspected-type-text`
|
|
1240
|
+
|
|
1241
|
+
```ts
|
|
1242
|
+
// Before
|
|
1243
|
+
const { nativeType, many, resolvedNativeType } = introspectedNativeType({
|
|
1244
|
+
formattedType, dataType: row.data_type, udtName, characterMaximumLength, numericPrecision, numericScale,
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// After
|
|
1248
|
+
const { typeText, many } = introspectedTypeText({
|
|
1249
|
+
formattedType, informationSchemaType: row.data_type, udtName, characterMaximumLength, numericPrecision, numericScale,
|
|
1250
|
+
});
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
On the database side, `typeText` is the type as introspection reads it, such as `integer` or `timestamp(3) with time zone`, not a normalised name. On Postgres an enum is named as a contract names it (`mood`, or `app.status` outside `public`) and a type off the search path keeps its schema (`extensions.vector(3)`); on SQLite the declared type is lower-cased. On the contract side it is the name a migration writes.
|
|
1254
|
+
|
|
1255
|
+
### `schema-ir-column-type-text`
|
|
1256
|
+
|
|
1257
|
+
```ts
|
|
1258
|
+
// Before
|
|
1259
|
+
new SqlColumnIR({ name: 'id', nativeType: 'int4', nullable: false, resolvedNativeType: 'int4' });
|
|
1260
|
+
|
|
1261
|
+
// After
|
|
1262
|
+
import { pgInt4 } from '@prisma/orm-postgres/target/data-types';
|
|
1263
|
+
|
|
1264
|
+
new SqlColumnIR({
|
|
1265
|
+
name: 'id',
|
|
1266
|
+
typeText: 'int4',
|
|
1267
|
+
nullable: false,
|
|
1268
|
+
storedType: pgInt4,
|
|
1269
|
+
storedTypeParams: {},
|
|
1270
|
+
});
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
A column with no `storedType` is one no data type claims, and it never compares equal to another, so a column a test builds by hand to compare needs `storedType` and `storedTypeParams`: the data type the column is stored as (the type its codec represents, unless that type is stored as another), and that type's parameters in normal form, such as `{ precision: 10, scale: 0 }` for `numeric(10)`. `storedTypeParams` is only for comparing; DDL writes the parameters as written, from `codecRef.typeParams`.
|
|
1274
|
+
|
|
1275
|
+
Read `column.typeText` where you read `column.nativeType`, and `column.storedType` where you read `column.dataType`; a reader that needs the type the codec represents gets it from `column.codecRef`. Code that compared `resolvedNativeType` compares types with `sqlColumnTypesEqual` instead. Code that read `codecBaseNativeType` to write DDL writes the type from `column.storedType` and the parameters in `column.codecRef.typeParams`, with `renderSqlTypeName` from `@prisma/orm-family-sql/contract/data-type`.
|
|
1276
|
+
|
|
1277
|
+
### `default-resolver-takes-the-column`
|
|
1278
|
+
|
|
1279
|
+
```ts
|
|
1280
|
+
// Before
|
|
1281
|
+
const resolve: DefaultResolver = (def, resolvedNativeType) =>
|
|
1282
|
+
read(def, resolvedNativeType.endsWith('[]'));
|
|
1283
|
+
readPostgresDefault(raw, 'text[]');
|
|
1284
|
+
|
|
1285
|
+
// After
|
|
1286
|
+
const resolve: DefaultResolver = (def, column) => read(def, column.many);
|
|
1287
|
+
readPostgresDefault(raw, true);
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
### `control-adapters-take-declared-data-types`
|
|
1291
|
+
|
|
1292
|
+
```ts
|
|
1293
|
+
// Before
|
|
1294
|
+
import { PostgresControlAdapter } from '@prisma/orm-postgres/adapter/control';
|
|
1295
|
+
|
|
1296
|
+
const adapter = new PostgresControlAdapter(
|
|
1297
|
+
createPostgresBuiltinCodecLookup(),
|
|
1298
|
+
createPostgresBuiltinDataTypeLookup(),
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
// After
|
|
1302
|
+
import { PostgresControlAdapter } from '@prisma/orm-postgres/adapter/control';
|
|
1303
|
+
import { postgresDataTypes } from '@prisma/orm-postgres/target/data-types';
|
|
1304
|
+
|
|
1305
|
+
const adapter = new PostgresControlAdapter(
|
|
1306
|
+
createPostgresBuiltinCodecLookup(),
|
|
1307
|
+
createPostgresBuiltinDataTypeLookup(),
|
|
1308
|
+
postgresDataTypes,
|
|
1309
|
+
);
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
```ts
|
|
1313
|
+
// Before
|
|
1314
|
+
import { SqliteControlAdapter } from '@prisma/orm-sqlite/adapter/control';
|
|
1315
|
+
|
|
1316
|
+
const adapter = new SqliteControlAdapter(createSqliteBuiltinCodecLookup());
|
|
1317
|
+
|
|
1318
|
+
// After
|
|
1319
|
+
import { SqliteControlAdapter } from '@prisma/orm-sqlite/adapter/control';
|
|
1320
|
+
import { sqliteDataTypes } from '@prisma/orm-sqlite/target/data-types';
|
|
1321
|
+
|
|
1322
|
+
const adapter = new SqliteControlAdapter(createSqliteBuiltinCodecLookup(), sqliteDataTypes);
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
The adapter resolves each column the database reports to the first of these data types that claims it, in the order given. When your stack has extensions that declare data types, such as pgvector, pass `stack.declaredDataTypes.map(({ type }) => type)`, or build the adapter with the adapter descriptor's `create(stack)`, which does that for you. With only the target's types, a column of an extension's type is reported as one no data type declares.
|
|
1326
|
+
|
|
1327
|
+
### `resolved-sql-type-carries-the-data-type`
|
|
1328
|
+
|
|
1329
|
+
```ts
|
|
1330
|
+
// Before
|
|
1331
|
+
const resolved = resolveReportedSqlType(reported, dataTypes);
|
|
1332
|
+
const type = dataTypes.find(({ id }) => id === resolved?.dataType);
|
|
1333
|
+
|
|
1334
|
+
// After
|
|
1335
|
+
const resolved = resolveReportedSqlType(reported, dataTypes);
|
|
1336
|
+
const type = resolved?.dataType;
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
The resolver already found the data type, so there is no second lookup, and no case where the id names no type in the list.
|
|
1340
|
+
|
|
1341
|
+
### `alter-type-postcheck-by-data-type`
|
|
1342
|
+
|
|
1343
|
+
```ts
|
|
1344
|
+
// Before
|
|
1345
|
+
const checkText = alterTypeCheckText('"app"."status"', dataTypes, where); // 'app.status'
|
|
1346
|
+
|
|
1347
|
+
// After
|
|
1348
|
+
const check = alterTypeCheck('"app"."status"', dataTypes, where);
|
|
1349
|
+
// { kind: 'identity', typeName: '"app"."status"', many: false }
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
A check of kind `catalog-text` holds the text `buildExpectedFormatType` gave for the same type. Code that read `call.checkType` as a string reads `call.checkType.text` for a `catalog-text` check and `call.checkType.typeName` for an `identity` check.
|
|
1353
|
+
|
|
1354
|
+
## Type-constructor arguments are values of data types
|
|
1355
|
+
|
|
1356
|
+
### `value-arguments-name-a-data-type`
|
|
1357
|
+
|
|
1358
|
+
An argument is read as a value of its data type, through the same read and cast as an attribute argument (ADR 254), in PSL and in the TypeScript builder. Name the data type the argument takes:
|
|
1359
|
+
|
|
1360
|
+
```ts
|
|
1361
|
+
// Before
|
|
1362
|
+
Vector: {
|
|
1363
|
+
kind: 'typeConstructor',
|
|
1364
|
+
args: [{ kind: 'number', name: 'length', integer: true, minimum: 1, maximum: 16000 }],
|
|
1365
|
+
output: { codecId: 'pg/vector@1', typeParams: { length: { kind: 'arg', index: 0 } } },
|
|
1366
|
+
},
|
|
1367
|
+
|
|
1368
|
+
// After
|
|
1369
|
+
Vector: {
|
|
1370
|
+
kind: 'typeConstructor',
|
|
1371
|
+
args: [{ kind: 'value', dataType: 'pg/int4', name: 'length' }],
|
|
1372
|
+
output: { codecId: 'pg/vector@1', typeParams: { length: { kind: 'arg', index: 0 } } },
|
|
1373
|
+
},
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
Move any bound the argument carried to the data type's `params` schema, which the codec's `paramsSchema` references. A string argument becomes a value of the data type it names; an argument that holds SQL, such as a type name, is a value of `sql/expression`, written ``sql`...` ``. The data type must be registered in the stack and written by some authoring entry, itself or through a type it casts from: assembly refuses a value argument otherwise, with `CONTRACT.DATA_TYPE_UNREGISTERED` or `CONTRACT.DATA_TYPE_NOT_WRITABLE`. A type constructor argument still declared with another kind, `option` and `object` included, is refused when the stack is assembled, with `CONTRACT.PACK_CONTRIBUTION_INVALID` naming the constructor and the argument.
|
|
1377
|
+
|
|
1378
|
+
A field preset's `number`, `option` and `object` arguments are unchanged; convert only a type constructor's arguments. Detection cannot tell the two apart, so it also reports a field preset's arguments: leave those as they are, including the properties of an `object` argument.
|
|
1379
|
+
|
|
1380
|
+
### `value-argument-types-in-typescript`
|
|
1381
|
+
|
|
1382
|
+
A target or extension that registers a data type its arguments take declares the TypeScript value in its pack's type, beside `__codecTypes`:
|
|
1383
|
+
|
|
1384
|
+
```ts
|
|
1385
|
+
export const myTargetPack: typeof myTargetPackBase & {
|
|
1386
|
+
readonly __codecTypes?: CodecTypes;
|
|
1387
|
+
readonly __dataTypeArguments?: {
|
|
1388
|
+
readonly 'my/int': { readonly argument: number; readonly parameter: number };
|
|
1389
|
+
};
|
|
1390
|
+
} = myTargetPackBase;
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
`argument` is what an author passes to the helper, and `parameter` is the type parameter the data type's reader gives for it, which the column's type is built from. An extension whose arguments take the target's types, such as `pg/int4`, declares nothing.
|
|
1394
|
+
|
|
1395
|
+
To type a descriptor of a value argument on its own, use `AuthoringValueArgument` from `components/authoring`; it now includes `name` and `optional`.
|
|
1396
|
+
|
|
1397
|
+
### `psl-and-print-helpers-take-data-types`
|
|
1398
|
+
|
|
1399
|
+
Pass the stack's `DataTypeSupport` to `mapPslHelperArgs({ ..., dataTypes })` and `instantiatePslFieldPreset({ ..., dataTypes })`. Replace `parsePslPositionalArgs` with the values of the call you built, and `pslTypeArgumentText` with `writtenTypeArguments(descriptor, args, dataTypes)`. A SQL target spreads `sqlTargetTypeConstructors({ sizeType: '<its size data type>' })` from `@prisma/orm-family-sql/family/pack` into its type namespace, and passes the same `sizeType` to `temporalCodecPresetWithPrecision`. Import `temporalCodecPresetWithPrecision` and `temporalStringAuthoringPresets` from `family/pack` too; `family/control` no longer exports them. The size type is the data type of a length or a precision: `pg/int4` on Postgres.
|
|
1400
|
+
|
|
1401
|
+
Import `assembleAuthoringDataTypes` from `components/authoring` instead of `components/control`. To build the data types a set of components reads values with, call `assembleDataTypeSupport(components, dataTypeOfCodec)`; it refuses a duplicate data type or entry, and a value argument whose data type is unregistered or that nothing writes.
|
|
1402
|
+
|
|
1403
|
+
### `composed-helpers-take-no-data-type-lookup`
|
|
1404
|
+
|
|
1405
|
+
Drop `dataTypeLookup` from the options of `createComposedAuthoringHelpers`:
|
|
1406
|
+
|
|
1407
|
+
```ts
|
|
1408
|
+
// Before
|
|
1409
|
+
createComposedAuthoringHelpers({ family, target, extensions, codecLookup, dataTypeLookup });
|
|
1410
|
+
|
|
1411
|
+
// After
|
|
1412
|
+
createComposedAuthoringHelpers({ family, target, extensions, codecLookup });
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
The helpers read value arguments, and build entity helpers, with the data types the packs register. An argument whose data type no pack in the stack registers now fails with `CONTRACT.DATA_TYPE_UNREGISTERED`. Register it, with an `authoring.dataTypes` entry, in the pack that owns it. An extension whose arguments take a target's data type, such as `pg/int4`, registers nothing: the target already does, and a second registration is refused as a duplicate.
|
|
1416
|
+
|
|
1417
|
+
### `helper-codec-invariants`
|
|
1418
|
+
|
|
1419
|
+
Rename the call and pass the type constructors and field presets as `helpers`:
|
|
1420
|
+
|
|
1421
|
+
```ts
|
|
1422
|
+
// Before
|
|
1423
|
+
enforceDataTypeInvariants({ lookup, declaredTypes, codecs, authoringEntries, constructors, codecDescriptorFor });
|
|
1424
|
+
|
|
1425
|
+
// After
|
|
1426
|
+
enforceHelperCodecInvariants({ lookup, helpers, codecDescriptorFor });
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
The checks on data types, entries, casts and written forms now run when you build data types with `assembleDataTypeSupport(components, dataTypeOfCodec)` from `components/authoring`; call it with the same components instead of passing `declaredTypes`, `codecs` and `authoringEntries`.
|
|
1430
|
+
|
|
1431
|
+
## Client generators are generator functions
|
|
1432
|
+
|
|
1433
|
+
`@default(fn())` now means a database default only. A value the client generates is written `@onCreate(fn())` or `@onUpdate(fn())`, and in TypeScript `.onCreate(gen.fn())`. The function resolves through the stack's generator function registry, by the column's codec.
|
|
1434
|
+
|
|
1435
|
+
### `default-functions-lower-to-database-defaults-only`
|
|
1436
|
+
|
|
1437
|
+
A default function whose `lower` returned `{ kind: 'execution', generated }` no longer compiles. Move it to a generator function on the pack ref (`src/exports/pack.ts`) and the control descriptor of the extension. One component declares the function's `signature`, a `FuncCallSig` as for a default function, or a function of the stack's data types that returns one (`generator-signature-from-data-types`). A binding maps codec ids to a `resolve` that turns the call's arguments and the column into a generator, and to `printArguments`, the reverse that `contract print` uses. `singleGeneratorBinding` from `@prisma/orm-framework/components/components` builds a binding for a function that takes no arguments:
|
|
1438
|
+
|
|
1439
|
+
```ts
|
|
1440
|
+
// Before
|
|
1441
|
+
const slugidEntry: ControlMutationDefaultEntry = {
|
|
1442
|
+
signature: { documentation: 'Generates a slug identifier when a field value is omitted.' },
|
|
1443
|
+
lower: () => ({
|
|
1444
|
+
ok: true,
|
|
1445
|
+
value: { kind: 'execution', generated: { kind: 'generator', id: 'slugid' } },
|
|
1446
|
+
}),
|
|
1447
|
+
usageSignatures: ['slugid()'],
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
const slugidPack = {
|
|
1451
|
+
// ...
|
|
1452
|
+
controlMutationDefaults: {
|
|
1453
|
+
defaultFunctionRegistry: new Map([['slugid', slugidEntry]]),
|
|
1454
|
+
generatorDescriptors: [{ id: 'slugid', applicableCodecIds: ['pg/text@1'] }],
|
|
1455
|
+
},
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
// After
|
|
1459
|
+
import {
|
|
1460
|
+
type ExtensionPackRef,
|
|
1461
|
+
singleGeneratorBinding,
|
|
1462
|
+
} from '@prisma/orm-framework/components/components';
|
|
1463
|
+
|
|
1464
|
+
const slugidPack = {
|
|
1465
|
+
// ...
|
|
1466
|
+
controlMutationDefaults: {
|
|
1467
|
+
defaultFunctionRegistry: new Map(),
|
|
1468
|
+
generatorDescriptors: [{ id: 'slugid' }],
|
|
1469
|
+
},
|
|
1470
|
+
generatorFunctions: [
|
|
1471
|
+
{
|
|
1472
|
+
name: 'slugid',
|
|
1473
|
+
signature: { documentation: 'Generates a slug identifier when a field value is omitted.' },
|
|
1474
|
+
bindings: [singleGeneratorBinding({ codecIds: ['pg/text@1'], generatorId: 'slugid' })],
|
|
1475
|
+
},
|
|
1476
|
+
],
|
|
1477
|
+
} as const satisfies ExtensionPackRef<'sql', 'postgres'>;
|
|
1478
|
+
```
|
|
1479
|
+
|
|
1480
|
+
Put `generatorFunctions` and the generator descriptors on both the pack ref, which the TypeScript builder reads, and the control descriptor, which `contract emit`, `contract print` and the language server read. Every stack, the TypeScript builders included, checks that each binding produces a generator a descriptor registers. Declare the pack ref with `as const satisfies`, not a type annotation: the `gen` of a `defineContract` callback is built from the packs' declared functions and their signatures, so `gen.slugid()` exists only when the pack ref keeps its literal type. A binding's `resolve` may return only a generator id the binding lists in `generatorIds`; any other id throws `InternalError`. To bind a function another component declares, such as `uuid`, to a codec your extension adds, contribute a binding without a `signature`. Composing a stack fails with `CONTRACT.PACK_CONTRIBUTION_INVALID` when two components declare one function, when a binding names a function no component declares, when two bindings cover one function and codec, when two functions write one generator for one codec, when a binding produces a generator no generator descriptor registers, or when two components register one generator id.
|
|
1481
|
+
|
|
1482
|
+
### `sql-family-registers-identifier-generators`
|
|
1483
|
+
|
|
1484
|
+
The component that defines a generator registers it, and the SQL family now defines and registers `uuidv4`, `uuidv7`, `cuid2`, `ulid`, `nanoid` and `ksuid`. A SQL target, adapter or extension that registers any of them, for example by spreading `builtinGeneratorRegistryMetadata` into `generatorDescriptors`, must remove those entries; `builtinGeneratorRegistryMetadata` is removed. Two components that register one generator id make every stack fail with `CONTRACT.PACK_CONTRIBUTION_INVALID`: `Generator "uuidv4" is registered by both "sql" and "<your component>". Register each generator once, in the component that defines it.` An extension that binds `uuid` to a codec it adds keeps the binding and registers nothing for it. Register only the generators your component defines, such as `slugid`.
|
|
1485
|
+
|
|
1486
|
+
A default function that lowers to a database default, such as one that writes a SQL expression, does not change.
|
|
1487
|
+
|
|
1488
|
+
### `applicable-codec-ids-and-build-phases-removed`
|
|
1489
|
+
|
|
1490
|
+
`applicableCodecIds` on a generator descriptor is removed: a binding's `codecIds` say which codecs a generator fills, and `contract print` reads the `printArguments` of the binding. `buildPhases` had no caller. Delete both.
|
|
1491
|
+
|
|
1492
|
+
## Generator function arguments and column length
|
|
1493
|
+
|
|
1494
|
+
### `sql-target-declares-integer-argument-generators`
|
|
1495
|
+
|
|
1496
|
+
```ts
|
|
1497
|
+
// Before
|
|
1498
|
+
generatorFunctions: [
|
|
1499
|
+
...idGeneratorFunctionBindings({ text: [MY_TEXT_CODEC_ID] }),
|
|
1500
|
+
],
|
|
1501
|
+
|
|
1502
|
+
// After
|
|
1503
|
+
import {
|
|
1504
|
+
idGeneratorFunctionBindings,
|
|
1505
|
+
sqlIntegerArgumentGeneratorFunctions,
|
|
1506
|
+
} from '@prisma/orm-family-sql/family/pack';
|
|
1507
|
+
|
|
1508
|
+
generatorFunctions: [
|
|
1509
|
+
...sqlIntegerArgumentGeneratorFunctions({ integer: myInt4.id }),
|
|
1510
|
+
...idGeneratorFunctionBindings({ text: [MY_TEXT_CODEC_ID] }),
|
|
1511
|
+
],
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
`integer` is the id of your target's 32-bit integer data type. An argument such as the `7` of `uuid(7)` is read and cast as a value of that type, as a column default is, then checked by the function: `uuid` takes 4 or 7, `cuid` takes 2, and `nanoid` takes 2 through 255. The `gen` helpers of a TypeScript contract keep their argument types.
|
|
1515
|
+
|
|
1516
|
+
### `generator-signature-from-data-types`
|
|
1517
|
+
|
|
1518
|
+
```ts
|
|
1519
|
+
// Before
|
|
1520
|
+
const arm = funcCall(contribution.name, contribution.signature as FuncCallSig);
|
|
1521
|
+
|
|
1522
|
+
// After
|
|
1523
|
+
import { generatorFunctionCall } from '@prisma/orm-framework/psl-parser';
|
|
1524
|
+
|
|
1525
|
+
const arm = generatorFunctionCall(contribution.name, contribution.signature, dataTypes);
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
A function you declare can keep a plain `FuncCallSig`. Write it as `(dataTypes) => FuncCallSig` when an argument is a value of a data type, read with `dataTypeValue(type, dataTypes)`.
|
|
1529
|
+
|
|
1530
|
+
### `generator-function-field-length`
|
|
1531
|
+
|
|
1532
|
+
```ts
|
|
1533
|
+
// Before
|
|
1534
|
+
generatorFunctions.resolve(call, { codecId, typeParams });
|
|
1535
|
+
|
|
1536
|
+
// After
|
|
1537
|
+
import { sqlCodecColumnLength } from '@prisma/orm-family-sql/contract/data-type';
|
|
1538
|
+
|
|
1539
|
+
generatorFunctions.resolve(call, {
|
|
1540
|
+
codecId,
|
|
1541
|
+
typeParams,
|
|
1542
|
+
length: sqlCodecColumnLength(dataTypes, codecId, typeParams),
|
|
1543
|
+
});
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
A binding of your own reads the length:
|
|
1547
|
+
|
|
1548
|
+
```ts
|
|
1549
|
+
const binding: GeneratorFunctionBinding = {
|
|
1550
|
+
codecIds: ['pg/text@1', 'sql/char@1'],
|
|
1551
|
+
generatorIds: ['slugid'],
|
|
1552
|
+
resolve: ({ field }) =>
|
|
1553
|
+
field.length !== undefined && field.length < 22
|
|
1554
|
+
? { ok: false, message: `slugid() writes 22 characters, which does not fit a column of ${field.length} characters.` }
|
|
1555
|
+
: { ok: true, generator: { kind: 'generator', id: 'slugid' } },
|
|
1556
|
+
printArguments: () => ({ args: {}, text: '' }),
|
|
1557
|
+
};
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
### `build-contract-definition-takes-lookups`
|
|
1561
|
+
|
|
1562
|
+
```ts
|
|
1563
|
+
// Before
|
|
1564
|
+
const definition = buildContractDefinition({ family, target, createNamespace, models });
|
|
1565
|
+
|
|
1566
|
+
// After
|
|
1567
|
+
const definition = buildContractDefinition({
|
|
1568
|
+
family,
|
|
1569
|
+
target,
|
|
1570
|
+
createNamespace,
|
|
1571
|
+
codecLookup,
|
|
1572
|
+
dataTypeLookup,
|
|
1573
|
+
models,
|
|
1574
|
+
});
|
|
1575
|
+
```
|
|
1576
|
+
|
|
1577
|
+
### `arg-binding-spec-label`
|
|
1578
|
+
|
|
1579
|
+
```ts
|
|
1580
|
+
// Before
|
|
1581
|
+
interpretArgs(args, { name: 'rel', positional, named }, ctx, span, node);
|
|
1582
|
+
|
|
1583
|
+
// After
|
|
1584
|
+
interpretArgs(args, { label: 'Attribute "rel"', positional, named }, ctx, span, node);
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
### `one-of-refuses-data-type-value`
|
|
1588
|
+
|
|
1589
|
+
```ts
|
|
1590
|
+
// Before
|
|
1591
|
+
type: oneOf(str(), dataTypeValue(pgInt4.id, dataTypes)),
|
|
1592
|
+
|
|
1593
|
+
// After: a value of a data type is its own parameter
|
|
1594
|
+
type: dataTypeValue(pgInt4.id, dataTypes),
|
|
1595
|
+
```
|
|
1596
|
+
|
|
1597
|
+
### `text-data-types`
|
|
1598
|
+
|
|
1599
|
+
```ts
|
|
1600
|
+
authoring: {
|
|
1601
|
+
textDataTypes: [citext.id],
|
|
1602
|
+
},
|
|
1603
|
+
```
|
|
1604
|
+
|
|
1605
|
+
## Contract types: one field type generic, and packs typed for `defineContract`
|
|
1606
|
+
|
|
1607
|
+
The emitted `contract.d.ts` of every family, and a contract built with `defineContract` in either family, now type their fields with one generic, `FieldTypesFromDomain` from `@prisma/orm-framework/contract/types`. A contract built with `defineContract` now has the same types as the `contract.d.ts` that `prisma contract emit` writes for it. The emitter reads a pack's codecs and descriptors at run time. A contract written in TypeScript has only the pack's types, so a pack states as types what the emitter derives. `QueryOperationTypesFunction` is exported from `@prisma/orm-family-sql/contract/types`, and `ParameterizedOutputTypesFunction` from `@prisma/orm-framework/contract/types`.
|
|
1608
|
+
|
|
1609
|
+
### `field-types-expression-removed`
|
|
1610
|
+
|
|
1611
|
+
Delete `fieldTypesExpression` from your family emitter's `EmissionSpi`. The framework declares `FieldTypeOverrides` and `ParameterizedOutputTypeFunctions` itself and writes:
|
|
1612
|
+
|
|
1613
|
+
```ts
|
|
1614
|
+
type FieldTypeOverrides = {
|
|
1615
|
+
readonly public: {
|
|
1616
|
+
readonly models: { readonly User: { readonly role: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' } } };
|
|
1617
|
+
readonly valueObjects: { readonly Address: { readonly kind: { readonly output: 'home' | 'work'; readonly input: 'home' | 'work' } } };
|
|
1618
|
+
};
|
|
1619
|
+
};
|
|
1620
|
+
export type FieldOutputTypes = FieldTypesFromDomain<ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output'>;
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
### `field-types-generic-moved`
|
|
1624
|
+
|
|
1625
|
+
Import `FieldTypesFromDomain` and `ValueTypeSide` from `@prisma/orm-framework/contract/types` instead of the SQL family's contract types entry. The generic now types value object and union fields itself, from the namespace's `valueObjects`. Its arguments are `FieldTypesFromDomain<DomainNamespaces, CodecTypes, ParameterizedOutputs, Overrides, Side>`, where `Overrides` is one map keyed like the domain, `[namespace]['models' | 'valueObjects'][owner][field]`, each entry `{ output?, input? }`; `ValueObjectTypesFromDomain` takes the same map. `ColumnTypesFromStorage` stays in the SQL family. For a storage map, apply `StoredValueType` from `@prisma/orm-framework/contract/types` to each entry, as `ColumnTypesFromStorage` does.
|
|
1626
|
+
|
|
1627
|
+
### `resolve-field-value-set-renamed`
|
|
1628
|
+
|
|
1629
|
+
Rename `EmissionSpi.resolveFieldValueSet` to `resolveStorageValueSet` in a family emitter, and drop any handling of domain enum references from it. The framework emitter now reads a field's value set itself when the field references a domain enum (`valueSet.plane` is `'domain'`), for model and value object fields alike, and no longer calls the hook for those fields. It calls `resolveStorageValueSet` only for other model fields, whose values the family keeps in its storage, such as a SQL column's value set.
|
|
1630
|
+
|
|
1631
|
+
### `typescript-contract-pack-type-slots`
|
|
1632
|
+
|
|
1633
|
+
A third-party SQL pack is typed fully for a `defineContract` contract only when it declares what an emitted `contract.d.ts` would get from its codecs and descriptors. Nothing breaks for a pack that does none of this; its operations, refined output types and aggregates are missing from a `defineContract` contract's types, as before.
|
|
1634
|
+
|
|
1635
|
+
**Query operations: `__queryOperationTypes`.** If your pack contributes query operations and exports a `QueryOperationTypes<CodecTypes>` type for its emitted `contract.d.ts`, declare it as a type-level function of the contract's codec types and add it to the pack metadata's type:
|
|
1636
|
+
|
|
1637
|
+
```ts
|
|
1638
|
+
import type { QueryOperationTypesFunction } from '@prisma/orm-family-sql/contract/types';
|
|
1639
|
+
|
|
1640
|
+
export interface QueryOperationTypesForCodecs extends QueryOperationTypesFunction {
|
|
1641
|
+
readonly queryOperationTypes: QueryOperationTypes<this['codecTypes']>;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
export const myPackMeta: typeof myPackMetaBase & {
|
|
1645
|
+
readonly __codecTypes?: CodecTypes;
|
|
1646
|
+
readonly __queryOperationTypes?: QueryOperationTypesForCodecs;
|
|
1647
|
+
} = myPackMetaBase;
|
|
1648
|
+
```
|
|
1649
|
+
|
|
1650
|
+
**Aggregates: literal `types.aggregateDescriptors`.** The aggregate result types of a `defineContract` contract are settled from the `types.aggregateDescriptors` of the target, the adapter and every extension, against the contract's codecs, as emission settles them. Only descriptors whose `operation`, input codec ids or traits, output codec id and `nullable` are literal types take part. Declare the list with `as const` and no annotation that widens it:
|
|
1651
|
+
|
|
1652
|
+
```ts
|
|
1653
|
+
export const myAggregateDescriptors = [
|
|
1654
|
+
{
|
|
1655
|
+
operation: 'bitOr',
|
|
1656
|
+
input: { kind: 'codec', codecId: 'pg/int4@1' },
|
|
1657
|
+
output: { kind: 'codec', codecId: 'pg/int8@1' },
|
|
1658
|
+
nullable: true,
|
|
1659
|
+
},
|
|
1660
|
+
] as const;
|
|
1661
|
+
```
|
|
1662
|
+
|
|
1663
|
+
If you build descriptors with helper functions, make the helpers generic with `const` type parameters and do not use `satisfies` inside them: TypeScript widens a generic helper's result to its constraint when its returned object has a `satisfies` clause. Check the list against the descriptor type where you use it, for example `types: { aggregateDescriptors: myAggregateDescriptors satisfies ReadonlyArray<SqlAggregateDescriptor> }`.
|
|
1664
|
+
|
|
1665
|
+
**Index helpers: say what the build writes.** A contract built with `defineContract` types each index from the `IndexConstraint` a helper returns, so code reads it by name and passes it to query operations. `IndexConstraint` takes a third type parameter, the index's `map` name, type and options as the build writes them. If your pack adds an index helper, return `IndexConstraint<FieldNames, Name, { readonly map: Map; readonly type: 'yourType'; readonly options: Options }>` with literal types. When the options name fields that the build turns into column names, give an interface extending `IndexOptionsFunction` from `@prisma/orm-family-sql/contract-ts/contract-builder`, whose `options` reads `this['columnOf']`, the model's field-to-column map. See `fullTextIndex` in the Postgres pack.
|
|
1666
|
+
|
|
1667
|
+
**Declaration output: export what a contract names.** An application that exports a contract and builds with `declaration: true` needs TypeScript to name every type the contract's inferred type mentions. Export from your pack's `/pack` entry, as types, the types that end up there: your `CodecTypes`, the output types your type function gives (such as `Vector`), `QueryOperationTypesForCodecs`, `ParameterizedOutputTypesForTypeParams`, and the value types your codec types name (such as a GeoJSON `Geometry`). Naming `CodecTypes` also keeps TypeScript from inlining codec classes with private members, which fails with TS4094. Compile a small application that imports only your published package and the public `@prisma/orm-postgres` with `declaration: true` to check.
|
|
1668
|
+
|
|
1669
|
+
### Output types that depend on type parameters: one type function
|
|
1670
|
+
|
|
1671
|
+
This covers `parameterized-output-types-declared-once` (SQL packs) and `mongo-parameterized-output-types` (Mongo-family packs). If a codec's output type depends only on the column's type parameters, such as `Vector<3>` for `vector(3)`, write that mapping once, as a type-level function of the type parameters, and export it from your pack's `codec-types` entry:
|
|
1672
|
+
|
|
1673
|
+
```ts
|
|
1674
|
+
import type { ParameterizedOutputTypesFunction } from '@prisma/orm-framework/contract/types';
|
|
1675
|
+
|
|
1676
|
+
type OutputTypeMap<TypeParams> = {
|
|
1677
|
+
readonly 'my/vector@1': TypeParams extends { readonly length: infer N extends number }
|
|
1678
|
+
? Vector<N>
|
|
1679
|
+
: never;
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
export interface ParameterizedOutputTypesForTypeParams extends ParameterizedOutputTypesFunction {
|
|
1683
|
+
readonly outputTypes: OutputTypeMap<this['typeParams']>;
|
|
1684
|
+
}
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
Name it in two places:
|
|
1688
|
+
|
|
1689
|
+
- In the pack metadata's type, as `readonly __parameterizedOutputTypes?: ParameterizedOutputTypesForTypeParams`. A contract built with `defineContract` reads it there.
|
|
1690
|
+
- In the metadata of the component that contributes the codecs, next to the codec types import, as `types.codecTypes.parameterizedOutputTypes`. The emitted `contract.d.ts` imports it and types each column with type parameters through it:
|
|
1691
|
+
|
|
1692
|
+
```ts
|
|
1693
|
+
types: {
|
|
1694
|
+
codecTypes: {
|
|
1695
|
+
import: { package: 'my-pack/codec-types', named: 'CodecTypes', alias: 'MyTypes' },
|
|
1696
|
+
parameterizedOutputTypes: {
|
|
1697
|
+
package: 'my-pack/codec-types',
|
|
1698
|
+
named: 'ParameterizedOutputTypesForTypeParams',
|
|
1699
|
+
alias: 'MyParameterizedOutputTypes',
|
|
1700
|
+
},
|
|
1701
|
+
},
|
|
1702
|
+
},
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1705
|
+
Declare both. With only `__parameterizedOutputTypes`, the emitted `contract.d.ts` types such a column by the codec's plain output type; with only `types.codecTypes.parameterizedOutputTypes`, a contract built with `defineContract` does. Nothing reports the gap, so check a column on both paths.
|
|
1706
|
+
|
|
1707
|
+
Then delete the codec's `renderOutputType`, and the `typeImports` entry that existed only so the emitted file could name its result (such as `Vector`). A codec the map leaves out, or maps to `never` for some type parameters, keeps its plain output type. On SQL, a `renderOutputType` that writes such a type still works for the emitted file but is ignored by `defineContract`; keep `renderOutputType` only for a type that no type function can compute from the type parameters, such as the text of an arktype schema. On Mongo, `renderOutputType` is no longer used for such codecs: Mongo's vector codec no longer implements it, and a Mongo `defineContract` contract ignores it.
|
|
1708
|
+
|
|
1709
|
+
```diff
|
|
1710
|
+
class MyVectorDescriptor extends PostgresCodecDescriptor<{ readonly length: number }> {
|
|
1711
|
+
override readonly codecId = 'my/vector@1' as const;
|
|
1712
|
+
- override renderOutputType({ length }: { length: number }) {
|
|
1713
|
+
- return `Vector<${length}>`;
|
|
1714
|
+
- }
|
|
1715
|
+
}
|
|
1716
|
+
```
|
|
1717
|
+
|
|
1718
|
+
A column helper that returns a column spec, built with `column(factory, codecId, typeParams)`, needs no entry: a `defineContract` contract reads the field's output type and codec id from the codec the factory builds.
|
|
1719
|
+
|
|
1720
|
+
Export `CodecTypes`, `ParameterizedOutputTypesForTypeParams` and the types they name from the pack's `/pack` entry, so an application that exports a contract can build declarations.
|
|
1721
|
+
|
|
1722
|
+
### `psl-interpreter-contract-capabilities`
|
|
1723
|
+
|
|
1724
|
+
`interpretPslDocumentToSqlContract` now puts the enabled capabilities you pass in `capabilities`, usually the composed stack's, into the contract's `capabilities`, together with the target's and the extensions'. Before, it used them only to check the schema and left them out of the contract. If you call it directly, expect the adapter's capabilities in the result, and pass the stack's capabilities if you build a contract to compare with one written in TypeScript or emitted by `prisma contract emit`.
|
|
1725
|
+
|
|
1726
|
+
## MongoDB fields have stored names
|
|
1727
|
+
|
|
1728
|
+
### `mongo-model-storage-fields`
|
|
1729
|
+
|
|
1730
|
+
```ts
|
|
1731
|
+
// before
|
|
1732
|
+
const keys = Object.keys(model.fields);
|
|
1733
|
+
const hasId = Object.hasOwn(model.fields, '_id');
|
|
1734
|
+
|
|
1735
|
+
// after
|
|
1736
|
+
import { ownDocumentIdFieldOf, ownStoredNamesOf } from '@internal/mongo-contract';
|
|
1737
|
+
|
|
1738
|
+
const keys = Object.values(ownStoredNamesOf(model));
|
|
1739
|
+
const hasId = ownDocumentIdFieldOf(model) !== undefined;
|
|
1740
|
+
```
|
|
1741
|
+
|
|
1742
|
+
`ownStoredNamesOf(model)` maps each of a model's own fields to its stored name; `valueObjectStoredNamesOf(valueObject)` maps each field of a value object, which has no storage block, to its own name. `storedNamesOf(models, name)` adds the fields a variant inherits from its base, and `modelAndBasesOf(models, name)` walks a model and its bases. A model built by hand, for example in a test, adds `fields` to its storage:
|
|
1743
|
+
|
|
1744
|
+
```ts
|
|
1745
|
+
// before
|
|
1746
|
+
{ fields: { _id: objectIdField, email: stringField }, relations: {}, storage: { collection: 'users' } }
|
|
1747
|
+
|
|
1748
|
+
// after
|
|
1749
|
+
{
|
|
1750
|
+
fields: { _id: objectIdField, email: stringField },
|
|
1751
|
+
relations: {},
|
|
1752
|
+
storage: { collection: 'users', fields: { _id: { field: '_id' }, email: { field: 'email' } } },
|
|
1753
|
+
}
|
|
1754
|
+
```
|
|
1755
|
+
|
|
1756
|
+
## MongoDB validator derivation moves to the contract package
|
|
1757
|
+
|
|
1758
|
+
### `mongo-validator-derivation-moves-to-mongo-contract`
|
|
1759
|
+
|
|
1760
|
+
```ts
|
|
1761
|
+
// before
|
|
1762
|
+
import type { FieldValueSets } from '@internal/mongo-contract-psl';
|
|
1763
|
+
|
|
1764
|
+
// after
|
|
1765
|
+
import type { FieldValueSets } from '@internal/mongo-contract';
|
|
1766
|
+
```
|
|
1767
|
+
|
|
1768
|
+
Through the public package, import from `@prisma/orm-family-mongo/contract` instead of `@prisma/orm-family-mongo/contract-psl`.
|
|
1769
|
+
|
|
1770
|
+
### `mongo-derive-json-schema-replaced-by-derive-collection-validators`
|
|
1771
|
+
|
|
1772
|
+
`deriveJsonSchema` took the fields of one model and ignored its relations, so its validator could differ from the one a contract emits. `deriveCollectionValidators` takes the contract's models and derives the validator of every collection that stores a root model, with each variant as a `oneOf` branch.
|
|
1773
|
+
|
|
1774
|
+
```ts
|
|
1775
|
+
// before
|
|
1776
|
+
import { deriveJsonSchema } from '@internal/mongo-contract-psl';
|
|
1777
|
+
|
|
1778
|
+
const validator = deriveJsonSchema(
|
|
1779
|
+
ns.models['Account'].fields,
|
|
1780
|
+
{ codecLookup, dataTypeLookup },
|
|
1781
|
+
ns.valueObjects,
|
|
1782
|
+
valueSets,
|
|
1783
|
+
);
|
|
1784
|
+
|
|
1785
|
+
// after
|
|
1786
|
+
import { deriveCollectionValidators } from '@internal/mongo-contract';
|
|
1787
|
+
|
|
1788
|
+
const validator = deriveCollectionValidators({
|
|
1789
|
+
models: ns.models,
|
|
1790
|
+
lookups: { codecLookup, dataTypeLookup },
|
|
1791
|
+
valueObjects: ns.valueObjects ?? {},
|
|
1792
|
+
valueSets,
|
|
1793
|
+
})['accounts'];
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
`ns` is `contract.domain.namespaces[namespaceId]`, and `valueSets` is the storage namespace's `entries.valueSet`, as before. A call to `derivePolymorphicJsonSchema` becomes the same call: the validator of a root model with variants already has one `oneOf` branch per variant.
|
|
1797
|
+
|
|
1798
|
+
## Generated checks can no longer be waived
|
|
1799
|
+
|
|
1800
|
+
Prisma 8 adds a check to a list column of a managed table so that its elements cannot be null, and a check to an enum-typed column so that it holds only the enum's values. `@noCheck` in PSL and `.noCheck()` in TypeScript used to skip these checks while the field kept its non-null element type. The client type then said the elements could not be null while the database accepted null. Both are removed, from an extension's contract source as from an application's.
|
|
1801
|
+
|
|
1802
|
+
### `psl-nocheck-removed`
|
|
1803
|
+
|
|
1804
|
+
Write a list whose elements may be null as `T?[]`:
|
|
1805
|
+
|
|
1806
|
+
```prisma
|
|
1807
|
+
// Before
|
|
1808
|
+
scopes String[] @default([]) @noCheck(elementNotNull)
|
|
1809
|
+
|
|
1810
|
+
// After
|
|
1811
|
+
scopes String?[] @default([])
|
|
1812
|
+
```
|
|
1813
|
+
|
|
1814
|
+
The database does not change: neither form has a check. The element type in the extension's `contract.d.ts` gains `| null`, so code in the extension and in applications that read the list must handle null elements.
|
|
1815
|
+
|
|
1816
|
+
If you want the check instead, delete the attribute and keep `T[]`. An application that installs the extension then gets the check in its next migration of the extension's contract space. Applying it fails if a stored list already holds a null element.
|
|
1817
|
+
|
|
1818
|
+
Delete `@noCheck(membership)`. The next migration adds the membership check to the enum column. Applying it fails if a stored value is not one of the enum's values.
|
|
1819
|
+
|
|
1820
|
+
A bare `@noCheck` waived every check the column had. Apply both rules above.
|
|
1821
|
+
|
|
1822
|
+
On a table the extension does not manage (`@@control(external)` or a contract-level policy other than `managed`), no check is derived either way. Only the element type changes.
|
|
1823
|
+
|
|
1824
|
+
Re-emit the bundled contract with the extension's own emit script, and ship the new `contract.json` and `contract.d.ts` together.
|
|
1825
|
+
|
|
1826
|
+
### `ts-nocheck-method-removed`
|
|
1827
|
+
|
|
1828
|
+
The same rules apply to the TypeScript builder:
|
|
1829
|
+
|
|
1830
|
+
```ts
|
|
1831
|
+
// Before
|
|
1832
|
+
scopes: field.text().many().noCheck('elementNotNull'),
|
|
1833
|
+
|
|
1834
|
+
// After
|
|
1835
|
+
scopes: field.text().many({ elementsNullable: true }),
|
|
1836
|
+
```
|
|
1837
|
+
|
|
1838
|
+
Delete `.noCheck('membership')` the same way. Re-emit the bundled contract.
|
|
1839
|
+
|
|
1840
|
+
### `stored-contracts-drop-nocheck`
|
|
1841
|
+
|
|
1842
|
+
Migration snapshots that an extension ships, and any other stored `contract.json` written by an earlier release, may have a `noCheck` key on a column. This release refuses such a contract and says to re-emit it. A historical snapshot cannot be re-emitted from today's source, so run the script instead. Do this before you re-emit the bundled contract, and commit your work first so the script's changes can be reviewed and undone with git. Run it from the extension's package root:
|
|
1843
|
+
|
|
1844
|
+
```sh
|
|
1845
|
+
node <guide>/scripts/remove-nocheck/remove-nocheck.ts
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
`<guide>` is the directory that holds this guide. Node 24 or later runs the TypeScript script directly; it needs no `tsx`. The script reads and writes files only and needs no database. It removes the `noCheck` key from every column of every SQL contract under the root, skipping `node_modules`, `.git`, `dist` and `build`. That includes a test fixture of an old contract: if you keep one on purpose, restore it with git afterwards. Where the key waived the element check of a list, the script also makes the column, the field that maps it and their types in `contract.d.ts` lists whose elements may be null, which is what the source says after `psl-nocheck-removed`. The script does not change which checks a contract declares.
|
|
1849
|
+
|
|
1850
|
+
The storage hash changes, so the script also renames each changed snapshot directory to its new hash. It rewrites every `migration.json`, ref, `migration.ts` and `contract.d.ts` that names an old hash, and gives each changed migration a new `migrationHash`.
|
|
1851
|
+
|
|
1852
|
+
When it finishes, it prints how many files it rewrote, how many snapshot directories it renamed, and each storage hash it replaced (`<old> -> <new>`). A package with no `noCheck` key is left unchanged, and the script says so. It changes no file and exits 1, naming the file, when a `contract.json`, `migration.json` or ref is not valid JSON, when a contract's stored storage hash or snapshot directory name does not match its content (restore that file from version control, or re-emit it, and run the script again), or when a renamed snapshot directory already exists with different content. If it stops partway, for example on a full disk, it says so; run it again to finish.
|
|
1853
|
+
|
|
1854
|
+
Then re-emit the bundled contract. The extension's storage hash changes, so tell the applications that use it, in your release notes, to run `prisma db sign` against every database after they upgrade, and name the old and new hash. The app upgrade guide for this release has such a note for the Supabase extension, `supabase-contract-lists-allow-null-elements`. Tell them too that `prisma migration status` no longer marks the extension's migrations applied before the upgrade as applied, because each rewritten migration has a new hash and their databases' migration history keeps the old ones. `prisma db migrate` runs none of them again, and `prisma db verify` passes.
|
|
1855
|
+
|
|
1856
|
+
## Migrations
|
|
1857
|
+
|
|
1858
|
+
### `setdefault-operation-class-derived`
|
|
1859
|
+
|
|
1860
|
+
`this.setDefault` on the PostgreSQL `Migration` class used to take `operationClass`, and `migration plan` wrote `operationClass: 'widening'` when a migration changed an existing default. The option is gone. The method reads the migration's start contract: the operation is widening when the start contract gives the column a default, and additive otherwise, including in a migration with no start contract. A `migration.ts` that passes the option no longer type-checks. Delete the option:
|
|
1861
|
+
|
|
1862
|
+
```typescript
|
|
1863
|
+
// before
|
|
1864
|
+
this.setDefault({ table: 'user', column: col('role', 'text', { default: lit('member'), codecRef: { codecId: 'pg/text@1' } }), operationClass: 'widening' })
|
|
1865
|
+
// after
|
|
1866
|
+
this.setDefault({ table: 'user', column: col('role', 'text', { default: lit('member'), codecRef: { codecId: 'pg/text@1' } }) })
|
|
1867
|
+
```
|
|
1868
|
+
|
|
1869
|
+
`node migration.ts` does not check types, so a file that still passes `operationClass` runs and ignores it. An applied migration needs nothing: `prisma db migrate` applies `ops.json`.
|
|
1870
|
+
|
|
1871
|
+
### Postchecks check the whole definition
|
|
1872
|
+
|
|
1873
|
+
The runner checks an operation's postchecks before it runs the operation. When they all hold, it records the operation as already applied and skips it. On MongoDB, which has no transaction, a run that fails part way relies on this skip when it runs again. Several postchecks held for an object of the planned name with another definition, so the runner skipped an operation whose effect was not in place, and the run failed later or left the object as it was. On PostgreSQL, SQLite and MongoDB these checks now check the whole definition.
|
|
1874
|
+
|
|
1875
|
+
The checks are written into `ops.json` when the migration package is written, so a package written by an earlier release still carries the old checks. Each detection below matches the old check of one operation. A matched `ops.json` needs action only when its migration is not applied yet: write its `ops.json` again before you apply it. Run its `migration.ts` (`node migration.ts`), or delete the package and run `prisma migration plan` again. An applied migration needs nothing; its operations do not run again. This applies to the migration packages an extension ships for its contract space as well.
|
|
1876
|
+
|
|
1877
|
+
#### PostgreSQL: the `postgres-postcheck-*` entries
|
|
1878
|
+
|
|
1879
|
+
Each of these operations now checks the object's definition in the catalog: kind, table, columns in order, types, referenced table and actions, access method, command and roles. It never compares text that PostgreSQL prints in its own form, such as expressions, predicates, check and policy bodies, or defaults. Renames now also check that the old name is gone.
|
|
1880
|
+
|
|
1881
|
+
- `postgres-postcheck-create-table`: `createTable` checks that the relation is a table, not a view, sequence or index of the same name.
|
|
1882
|
+
- `postgres-postcheck-add-column`: `addColumn` checks the column's type, and that it is NOT NULL when the column is declared NOT NULL.
|
|
1883
|
+
- `postgres-postcheck-add-column-with-temporary-default` and `postgres-postcheck-add-required-column-to-empty-table`: `addColumnWithTemporaryDefault` and `addRequiredColumnToEmptyTable` check the column's type.
|
|
1884
|
+
- `postgres-postcheck-create-index`: `createIndex` checks that the relation is an index on the planned table, with the planned uniqueness, access method and columns in order, and with an expression and a predicate only where the call has them.
|
|
1885
|
+
- `postgres-postcheck-rename-index`: `renameIndex` also checks that no index has the old name any more.
|
|
1886
|
+
- `postgres-postcheck-add-primary-key` and `postgres-postcheck-add-unique`: `addPrimaryKey` and `addUnique` check that the constraint is a primary key or a unique constraint on the planned columns in order.
|
|
1887
|
+
- `postgres-postcheck-add-foreign-key`: `addForeignKey` checks the constraint's columns, referenced table and columns in order, and its delete and update actions.
|
|
1888
|
+
- `postgres-postcheck-add-check-constraint`: `addCheckConstraint` checks that the constraint is a check constraint.
|
|
1889
|
+
- `postgres-postcheck-rename-constraint`: `renameConstraint` also checks that no constraint has the old name any more.
|
|
1890
|
+
- `postgres-postcheck-create-rls-policy`: `createRlsPolicy` checks the policy's command, roles and whether it is permissive.
|
|
1891
|
+
- `postgres-postcheck-rename-rls-policy`: `renameRlsPolicy` also checks that no policy has the old name any more, and its precheck now also refuses a policy that already has the new name.
|
|
1892
|
+
- `postgres-postcheck-autoincrement-default`: `setDefault` with `autoincrement()` also checks that the column owns the sequence of the planned name and that the sequence has the column's integer width.
|
|
1893
|
+
|
|
1894
|
+
#### `postgres-autoincrement-sequence-follows-column-type`
|
|
1895
|
+
|
|
1896
|
+
An `autoincrement()` column takes its values from a sequence it owns. `ALTER COLUMN … TYPE` does not change that sequence, so a column widened from `int4` to `int8` kept a sequence `AS integer`, and inserts failed once it reached 2147483647. An `alterColumnType` on such a column, planned or hand-written in `migration.ts`, now also runs `ALTER SEQUENCE … AS <new width>`. It first refuses, before any step, when the sequence has already handed out a value past a narrower width. Its postcheck asks for both the column type and the sequence width. A column whose `nextval(...)` default reads a sequence it does not own changes type as before, and that sequence is left alone.
|
|
1897
|
+
|
|
1898
|
+
The detection matches every integer `alterColumnType` written before this change, and none written after it. Only those on an `autoincrement()` column need action:
|
|
1899
|
+
|
|
1900
|
+
- Migration not applied yet: write its `ops.json` again before you apply it, as above.
|
|
1901
|
+
- Migration already applied: the sequence still has the old width. Change it to the column's width yourself, for example `ALTER SEQUENCE "public"."user_id_seq" AS bigint`. `pg_get_serial_sequence('"public"."user"', 'id')` names the sequence.
|
|
1902
|
+
|
|
1903
|
+
#### `setdefault-has-no-postcheck`
|
|
1904
|
+
|
|
1905
|
+
A `setDefault` operation without `autoincrement()` checked afterwards that the column has a default. The old default passes that check, so the runner skipped the operation. From 8.0.0-rc.15 until this release, only an additive `setDefault` carried the check, and a hand-written `this.setDefault` was additive unless it passed `operationClass: 'widening'`. Such a call on a column that already had a different default was skipped, and `prisma db migrate` then failed with `MIGRATION.SCHEMA_VERIFY_FAILED`. Such a `setDefault` operation now carries no check afterwards; the final schema verification compares the default's value, and setting a default twice changes nothing. A `setDefault` with `autoincrement()` keeps its checks (`postgres-postcheck-autoincrement-default`).
|
|
1906
|
+
|
|
1907
|
+
A matched `ops.json` needs action only when its migration is not applied yet and a hand-written `setDefault` in its `migration.ts` changes a default the column already has. A package the planner wrote for a column with no default needs nothing.
|
|
1908
|
+
|
|
1909
|
+
#### `postgres-alter-column-type-unchanged-refused`
|
|
1910
|
+
|
|
1911
|
+
`this.alterColumnType({ schema, table, column, type, using })` to the type the migration's start contract, after its earlier renames, already gives the column, used to build an operation whose check, the column's type, already held. The runner skipped it and `using` never ran. `migration.ts` now refuses the call with `MIGRATION.ALTER_COLUMN_TYPE_USING_WITHOUT_TYPE_CHANGE`. Write the conversion as a `dataTransform`, which checks the rows themselves:
|
|
1912
|
+
|
|
1913
|
+
```typescript
|
|
1914
|
+
// before
|
|
1915
|
+
this.alterColumnType({ schema: 'public', table: 'user', column: 'email', type: 'text', using: 'lower(email)' })
|
|
1916
|
+
// after
|
|
1917
|
+
this.dataTransform(endContract, 'Lower-case user emails', {
|
|
1918
|
+
check: () => /* a query for the users whose email is not lower-case */,
|
|
1919
|
+
run: () => /* an update that lower-cases those emails */,
|
|
1920
|
+
})
|
|
1921
|
+
```
|
|
1922
|
+
|
|
1923
|
+
A matched `migration.ts` needs action only when the call converts a column to the type the start contract already gives it.
|
|
1924
|
+
|
|
1925
|
+
#### `sqlite-postchecks-check-full-definition`
|
|
1926
|
+
|
|
1927
|
+
- `createTable` checked only that a table of that name exists, whatever its columns. It now checks every column's name, type, nullability and primary key position.
|
|
1928
|
+
- `addColumn` checked only that a column of that name exists. It now checks the column's type and nullability.
|
|
1929
|
+
- `createIndex` checked only that an index of that name exists. It now checks the index's table, ordered columns and uniqueness.
|
|
1930
|
+
- `recreateTable` did not check foreign key actions (`onDelete`, `onUpdate`) or `autoincrement()`. A migration that changed only those was skipped, and `prisma db migrate` then failed with `MIGRATION.SCHEMA_VERIFY_FAILED`. It now checks them.
|
|
1931
|
+
|
|
1932
|
+
A `recreateTable` written for an `autoincrement()` change has no text to detect; write it again as well when its migration is not applied yet.
|
|
1933
|
+
|
|
1934
|
+
#### `mongo-postchecks-check-full-definition`
|
|
1935
|
+
|
|
1936
|
+
- `createIndex` checked only the index keys, and `unique: true` when the index is unique. An index on the same keys with other options, such as `sparse`, a TTL, a partial filter, a collation, a wildcard projection, or other text weights or languages, passed the check. It now checks `unique` and `sparse` both ways, `expireAfterSeconds`, `partialFilterExpression`, `wildcardProjection`, the collation fields the call gives (MongoDB fills in the others) or no collation when it gives none, and a text index's fields, `weights`, `default_language` and `language_override`.
|
|
1937
|
+
- `collMod` with a validator that is not only a `$jsonSchema`, such as `{ status: { $in: [...] } }`, checked only the collection's validation level and action. It now compares the whole validator.
|
|
1938
|
+
|
|
1939
|
+
### Migration apply failures report the runner's own code
|
|
1940
|
+
|
|
1941
|
+
#### `control-client-apply-takes-invocation`
|
|
1942
|
+
|
|
1943
|
+
`dbInit`, `dbUpdate` and `migrate` on a control client write advice that names commands to run, and that advice needs to know where it will be run from. Pass it as the required `invocation` option, `{ cwd, configPath, dbGiven }`:
|
|
1944
|
+
|
|
1945
|
+
```ts
|
|
1946
|
+
// Before
|
|
1947
|
+
await control.dbUpdate({ contract, mode: 'apply', migrationsDir: 'migrations' });
|
|
1948
|
+
|
|
1949
|
+
// After
|
|
1950
|
+
await control.dbUpdate({
|
|
1951
|
+
contract,
|
|
1952
|
+
mode: 'apply',
|
|
1953
|
+
migrationsDir: 'migrations',
|
|
1954
|
+
invocation: { cwd: process.cwd(), configPath: undefined, dbGiven: false },
|
|
1955
|
+
});
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
Add the same `invocation` to every `dbInit`, `dbUpdate` and `migrate` call. Set `configPath` to the config path your tool passes to `prisma`, and `dbGiven` to `true` when it passes `--db`. Pass `{ cwd: process.cwd(), configPath: undefined, dbGiven: false }` when nothing is run from a terminal.
|
|
1959
|
+
|
|
1960
|
+
A failure of these three operations is a `CliStructuredError`, so `DbInitFailure`, `DbUpdateFailure` and `MigrateFailure` are gone from `control-api`. Replace any use of them or their code types with `CliStructuredError`, and branch on `failure.code`, a dotted code such as `MIGRATION.PLANNING_FAILED`, instead of `'PLANNING_FAILED'`, `'RUNNER_FAILED'` or `'MIGRATION_PATH_NOT_FOUND'`.
|
|
1961
|
+
|
|
1962
|
+
The failure's fields change too:
|
|
1963
|
+
|
|
1964
|
+
| Before | After |
|
|
1965
|
+
| --- | --- |
|
|
1966
|
+
| `failure.summary` | `failure.message` |
|
|
1967
|
+
| `failure.code === 'PLANNING_FAILED'`, `failure.conflicts` | `failure.code === 'MIGRATION.PLANNING_FAILED'`, `failure.meta.conflicts` |
|
|
1968
|
+
| `failure.warnings` on a failed `dbInit` or `dbUpdate`, and `value.warnings` on a successful one | the `onPlannerWarnings(warnings)` option, called once per run when planning raised warnings, whether the apply then succeeds or fails |
|
|
1969
|
+
| `failure.schemaIssues`, `failure.rowsWithoutValue`, `failure.failedTypeChange`, `failure.failedPrecheck` | the runner's facts in `failure.meta`: `schemaIssues`, `rowsWithoutValue`, `failedTypeChange`, `failedCheck`. A field in them is a subject object, not text such as `"User.age"` |
|
|
1970
|
+
| `failure.failedMigration`, `failure.databaseStorageHash` | removed; the advice in `failure.fix` names the migration directory and the `--from` hash |
|
|
1971
|
+
|
|
1972
|
+
In `--json` output, `db init` and `db update` print the planner's warnings of a failed apply as `warn` messages before the error, instead of in the error's `meta.plannerWarnings`.
|
|
1973
|
+
|
|
1974
|
+
#### `runner-failures-report-own-codes`
|
|
1975
|
+
|
|
1976
|
+
`MIGRATION.RUNNER_FAILED` no longer exists. Code that reads `prisma db migrate`, `db init` or `db update` output with `--json`, or a control client's failure, and matches `MIGRATION.RUNNER_FAILED` or `meta.runnerErrorCode`, now matches the runner's code itself:
|
|
1977
|
+
|
|
1978
|
+
```ts
|
|
1979
|
+
// Before
|
|
1980
|
+
if (error.code === 'MIGRATION.RUNNER_FAILED' && error.meta.runnerErrorCode === 'MIGRATION.SCHEMA_VERIFY_FAILED') {
|
|
1981
|
+
report(error.meta.issues);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// After
|
|
1985
|
+
if (error.code === 'MIGRATION.SCHEMA_VERIFY_FAILED') {
|
|
1986
|
+
report(error.meta.schemaIssues);
|
|
1987
|
+
}
|
|
1988
|
+
```
|
|
1989
|
+
|
|
1990
|
+
A check that matched `MIGRATION.RUNNER_FAILED` alone, meaning "the apply failed", matches every code the runner reports: `MIGRATION.SCHEMA_VERIFY_FAILED`, `MIGRATION.PRECHECK_FAILED`, `MIGRATION.POSTCHECK_FAILED`, `MIGRATION.EXECUTION_FAILED`, `MIGRATION.POLICY_VIOLATION`, `MIGRATION.MARKER_ORIGIN_MISMATCH`, `MIGRATION.MARKER_CAS_FAILURE`, `MIGRATION.DESTINATION_CONTRACT_MISMATCH`, `MIGRATION.FOREIGN_KEY_VIOLATION` and, on MongoDB, `MIGRATION.DATA_TRANSFORM_CHECK_NOT_READ_ONLY`. `db init` and `db update` also report an extension space they cannot plan as `MIGRATION.PATH_UNREACHABLE` or `MIGRATION.NO_INVARIANT_PATH`, with `meta.spaceId`.
|
|
1991
|
+
|
|
1992
|
+
Read the facts from these `meta` keys:
|
|
1993
|
+
|
|
1994
|
+
| Before | After |
|
|
1995
|
+
| --- | --- |
|
|
1996
|
+
| `meta.issues` | `meta.schemaIssues` |
|
|
1997
|
+
| `meta.operationId`, `meta.phase`, `meta.stepDescription` of a failed check | `meta.failedCheck.operationId`, `meta.failedCheck.phase`, `meta.failedCheck.description` |
|
|
1998
|
+
| `meta.runnerErrorCode` | `code` |
|
|
1999
|
+
|
|
2000
|
+
`meta.failingSpace` is unchanged. `MIGRATION.PATH_UNREACHABLE` for a database with no marker now has `'empty'`, the empty contract's hash, in `meta.fromHash`, where it had `'<empty>'`. A failure also carries `meta.failedTypeChange` and `meta.rowsWithoutValue` when the runner found them.
|