@prisma/orm-mongo 8.0.0-rc.4-dev.17 → 8.0.0-rc.5-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -10
- package/skills/prisma-8/SKILL.md +84 -0
- package/skills/prisma-8/references/build.md +142 -0
- package/skills/prisma-8/references/contract.md +417 -0
- package/skills/prisma-8/references/debug.md +141 -0
- package/skills/prisma-8/references/feedback.md +251 -0
- package/skills/prisma-8/references/migration-review.md +224 -0
- package/skills/prisma-8/references/migrations.md +519 -0
- package/skills/prisma-8/references/queries-mongo.md +236 -0
- package/skills/prisma-8/references/queries-postgres.md +415 -0
- package/skills/prisma-8/references/queries.md +168 -0
- package/skills/prisma-8/references/quickstart.md +326 -0
- package/skills/prisma-8/references/runtime.md +344 -0
- package/skills/prisma-8/references/supabase.md +244 -0
- package/skills/prisma-8/references/upgrade-app.md +101 -0
- package/skills/prisma-8/references/upgrade-extension.md +105 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md +56 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +381 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +202 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts +201 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts +198 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +339 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts +229 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +543 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +359 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md +173 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +805 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md +72 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md +36 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md +86 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +588 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +158 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +42 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md +276 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +738 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts +97 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts +223 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +266 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +522 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +803 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md +219 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +731 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +194 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md +57 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md +150 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +746 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +137 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +129 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.1"
|
|
3
|
+
to: "8.0.0-rc.2"
|
|
4
|
+
changes:
|
|
5
|
+
- id: check-constraint-ir-carries-an-opaque-expression
|
|
6
|
+
summary: |
|
|
7
|
+
`CheckConstraint` (contract IR) and `SqlCheckConstraintIR` (schema IR) changed from
|
|
8
|
+
`{ name, column, valueSet }` / `{ name, column, permittedValues }` to
|
|
9
|
+
`{ name, prefix?, expression }`. Both are constructed from an `SqlObjectNaming` rather
|
|
10
|
+
than a bare name, exactly like `Index` / `SqlIndexIR`: pass
|
|
11
|
+
`{ naming: { kind: 'wire', prefix, hash }, expression }` for a managed check, or
|
|
12
|
+
`{ naming: { kind: 'exact', name }, expression }` for one adopted verbatim. Compute the
|
|
13
|
+
hash with `computeCheckContentHash(expression)` from `@internal/sql-schema-ir/naming`.
|
|
14
|
+
Reading a check off a built node is unchanged (`check.name`), and `check.prefix` tells
|
|
15
|
+
you whether it is wire-named. Contract JSON hydrates through
|
|
16
|
+
`checkConstraintInputFromSerialized`, which rejects a `prefix` that does not parse out of
|
|
17
|
+
the `name`. `resolveValueSetValues` is gone — a check no longer references a value set,
|
|
18
|
+
so there is nothing to resolve; the members are already baked into the predicate text.
|
|
19
|
+
detection:
|
|
20
|
+
glob: "**/*.{ts,mts,cts}"
|
|
21
|
+
contains:
|
|
22
|
+
- 'CheckConstraint'
|
|
23
|
+
- 'permittedValues'
|
|
24
|
+
- 'resolveValueSetValues'
|
|
25
|
+
anyMatch: true
|
|
26
|
+
- id: add-check-constraint-call-takes-an-expression
|
|
27
|
+
summary: |
|
|
28
|
+
`AddCheckConstraintCall` is now constructed as
|
|
29
|
+
`(schemaName, tableName, constraintName, expression)` — the `column` and `values`
|
|
30
|
+
parameters are gone, and the rendered DDL is
|
|
31
|
+
`ALTER TABLE … ADD CONSTRAINT "x" CHECK (<expression>)` with the predicate emitted
|
|
32
|
+
verbatim. The matching migration-class method takes
|
|
33
|
+
`{ schema, table, constraint, expression }`. `DropCheckConstraintCall` is unchanged.
|
|
34
|
+
There is no compatibility overload — update every construction site.
|
|
35
|
+
detection:
|
|
36
|
+
glob: "**/*.{ts,mts,cts}"
|
|
37
|
+
contains:
|
|
38
|
+
- 'AddCheckConstraintCall'
|
|
39
|
+
- 'addCheckConstraint'
|
|
40
|
+
anyMatch: true
|
|
41
|
+
- id: specifier-default-control-policy-requires-create-namespace
|
|
42
|
+
summary: |
|
|
43
|
+
The options bag on `typescriptContract` / `typescriptContractFromPath` now requires
|
|
44
|
+
`createNamespace` alongside `defaultControlPolicy`. Stamping a specifier default carries
|
|
45
|
+
a consequence — derived CHECK constraints are stripped from tables the stamped policy
|
|
46
|
+
leaves non-managed — and the strip rebuilds storage namespaces through the target's
|
|
47
|
+
factory, so the two options travel together. Pass the same factory the PSL specifier
|
|
48
|
+
already takes:
|
|
49
|
+
`typescriptContract(contract, output, { defaultControlPolicy: 'external' })` becomes
|
|
50
|
+
`typescriptContract(contract, output, { defaultControlPolicy: 'external',
|
|
51
|
+
createNamespace: postgresCreateNamespace })`, with `postgresCreateNamespace` imported
|
|
52
|
+
from the Postgres target's types entrypoint (`@internal/target-postgres/types`).
|
|
53
|
+
Calls without an options bag are unchanged, and `emptyContract` already took
|
|
54
|
+
`createNamespace`.
|
|
55
|
+
detection:
|
|
56
|
+
glob: "**/*.{ts,mts,cts}"
|
|
57
|
+
contains:
|
|
58
|
+
- 'typescriptContract'
|
|
59
|
+
- 'defaultControlPolicy'
|
|
60
|
+
anyMatch: false
|
|
61
|
+
- id: re-emit-extension-contract-spaces
|
|
62
|
+
summary: |
|
|
63
|
+
Run your extension's `contract emit` (the `build:contract-space` script, if you have one)
|
|
64
|
+
to regenerate its committed `contract.json` / `contract.d.ts`. Two things change: any
|
|
65
|
+
enum CHECK is re-serialized into the new shape with a wire name, and every list (`many`)
|
|
66
|
+
column gains a declared element-non-null CHECK that the Postgres planner used to
|
|
67
|
+
synthesize without ever declaring. Prefixes derived from long table and column names are
|
|
68
|
+
truncated to 54 UTF-8 bytes so the wire name fits Postgres's 63-byte identifier limit;
|
|
69
|
+
identity lives in the hash, so truncated prefixes still yield distinct names.
|
|
70
|
+
Postgres introspection also stopped parsing predicates and now captures every CHECK
|
|
71
|
+
constraint verbatim, so any hand-written or platform-installed check on a table your
|
|
72
|
+
extension manages is visible for the first time: it verifies as an undeclared extra under
|
|
73
|
+
`--strict` and becomes a `dropCheckConstraint` under a policy that allows `destructive`.
|
|
74
|
+
If your extension installs checks out of band — through a raw-SQL migration step rather
|
|
75
|
+
than through the contract — declare them instead: `@@check(expression: "…", map: "<physical
|
|
76
|
+
name>")` adopts a constraint under the name it already carries. Until they are declared,
|
|
77
|
+
keep the tables carrying them under an additive-only policy — the checks survive, and only
|
|
78
|
+
`--strict` verify reports them — or expect the first destructive plan against an upgraded
|
|
79
|
+
database to offer to drop them. Declaring them is the durable fix; see
|
|
80
|
+
`authored-check-constraints` in this transition.
|
|
81
|
+
detection:
|
|
82
|
+
glob: "**/contract.json"
|
|
83
|
+
contains:
|
|
84
|
+
- '"many": true'
|
|
85
|
+
- '"valueSet"'
|
|
86
|
+
anyMatch: true
|
|
87
|
+
- id: stub-execution-contexts-must-carry-an-aggregate-registry
|
|
88
|
+
summary: |
|
|
89
|
+
A pack that hand-rolls an `ExecutionContext` in its tests must give it an
|
|
90
|
+
`aggregateDescriptors` registry whose `values()` yields the descriptors the test exercises.
|
|
91
|
+
`orm(...)` and every `Collection` construction enumerate that registry to build their method
|
|
92
|
+
sets, so a stub context without the field raises
|
|
93
|
+
`TypeError: Cannot read properties of undefined (reading 'values')` at construction, and a
|
|
94
|
+
registry whose `values()` yields nothing produces a collection and a `sql()` function bag
|
|
95
|
+
with no aggregate methods at all — `TypeError: aggregate.count is not a function` at the
|
|
96
|
+
first call. A `resolve()` that answers `count` is no longer sufficient on its own: `resolve`
|
|
97
|
+
settles one `(operation, input)` pair, `values` declares the vocabulary. Prefer
|
|
98
|
+
`buildSqlAggregateDescriptorRegistry(descriptors, codecDescriptors)` from
|
|
99
|
+
`@internal/sql-relational-core/aggregate-descriptor-registry` over a hand-written object —
|
|
100
|
+
it settles the same registry the runtime does.
|
|
101
|
+
detection:
|
|
102
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
103
|
+
contains:
|
|
104
|
+
- "aggregateDescriptors"
|
|
105
|
+
- "ExecutionContext"
|
|
106
|
+
- "SqlAggregateDescriptorRegistry"
|
|
107
|
+
anyMatch: true
|
|
108
|
+
- id: aggregate-surfaces-derive-from-the-contract-s-operation-map
|
|
109
|
+
summary: |
|
|
110
|
+
The ORM's `aggregate()` / `groupBy().aggregate()` / `groupBy().having()` builders, the
|
|
111
|
+
include reducers on a collection, and the SQL builder's aggregate functions no longer
|
|
112
|
+
declare `count` / `sum` / `avg` / `min` / `max` outright. Each surface is a mapped type over
|
|
113
|
+
the operation names in the contract's emitted `AggregateTypes` block. Deriving the surface
|
|
114
|
+
neither adds nor removes a method by itself, but the block a re-emit produces is not the
|
|
115
|
+
list it was: PostgreSQL now declares eight operations and SQLite seven, and the bare results
|
|
116
|
+
over integer columns moved — `count`, `sum`, and `avg`. What stayed: `min` / `max`, `sum`
|
|
117
|
+
and `avg` over a float, `numeric`, `interval`, or `time` column, and `sum` over an
|
|
118
|
+
`UnboundedInt` column, each still in its own family — see
|
|
119
|
+
`count-over-a-field-counts-that-field` and `aggregate-defaults-are-js-native-numbers`.
|
|
120
|
+
A contract whose block is unknown — an in-code `defineContract(...)` value, or a
|
|
121
|
+
contract emitted before `AggregateTypes` existed — resolves every one of those surfaces to
|
|
122
|
+
`AggregateOperationsUnavailable`, an empty type, so the call fails with
|
|
123
|
+
`Property 'count' does not exist` rather than offering selector types the declaration
|
|
124
|
+
cannot supply. This is a compile-time change only: the runtime builds its methods from the
|
|
125
|
+
composed registry and dispatches exactly as before. Either emit the contract and type the
|
|
126
|
+
client from the emitted `Contract`, or cast the builder to a dynamic record — see the body
|
|
127
|
+
for the shape.
|
|
128
|
+
detection:
|
|
129
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
130
|
+
contains:
|
|
131
|
+
- "defineContract"
|
|
132
|
+
- ".aggregate("
|
|
133
|
+
- "AggregateBuilder"
|
|
134
|
+
- "HavingBuilder"
|
|
135
|
+
anyMatch: true
|
|
136
|
+
- id: count-over-a-field-counts-that-field
|
|
137
|
+
summary: |
|
|
138
|
+
`count(field)` renders `COUNT(<column>)` in the ORM. It used to drop the argument and render
|
|
139
|
+
`COUNT(*)`. PostgreSQL declares `count` with `input: { kind: 'any' }`, which settles into
|
|
140
|
+
both a `withoutInput` row and an `anyInput` row, so the derived method carries both arities
|
|
141
|
+
honestly: `count()` counts rows, `count(field)` counts that field's non-null values. Typed
|
|
142
|
+
call sites are unaffected, because `count` took no argument before and no such call
|
|
143
|
+
compiled. Reachable through a call that bypassed the types — a `@ts-expect-error`, an
|
|
144
|
+
`as never` argument, or dynamic invocation. Those call sites change result whenever the
|
|
145
|
+
column holds NULLs. Sweep them and drop the argument wherever `COUNT(*)` was what you meant.
|
|
146
|
+
detection:
|
|
147
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
148
|
+
contains:
|
|
149
|
+
- "count("
|
|
150
|
+
- ".aggregate("
|
|
151
|
+
anyMatch: true
|
|
152
|
+
- id: contributed-aggregate-operations-carry-a-lowering-hook
|
|
153
|
+
summary: |
|
|
154
|
+
An aggregate descriptor whose `operation` is not one of `count`, `sum`, `avg`, `min`, `max`
|
|
155
|
+
must declare a `lower` hook. `operation` has been a `string` for a release, but a name
|
|
156
|
+
outside that alphabet was inert — no consumer surface offered it. It is live now: a
|
|
157
|
+
contributed operation surfaces as a method on the ORM and SQL-builder aggregate surfaces
|
|
158
|
+
under its own name, so it must be able to build an expression, and the closed AST alphabet
|
|
159
|
+
gives it none. Registry assembly rejects a hook-less descriptor with
|
|
160
|
+
`RUNTIME.AGGREGATE_LOWERING_MISSING` while the execution context composes. Two further
|
|
161
|
+
rules follow. An out-of-alphabet operation is projection-only: HAVING, ORDER BY, and
|
|
162
|
+
comparison operands refuse it with `ORM.AGGREGATE_PROJECTION_ONLY`, and its typed HAVING
|
|
163
|
+
method does not exist. And its name may not shadow an ORM collection member (`select`,
|
|
164
|
+
`where`, `include`, `combine`, `state`, …), because reducers install into that same
|
|
165
|
+
namespace; `orm(...)` rejects a collision with `ORM.AGGREGATE_OPERATION_RESERVED`.
|
|
166
|
+
detection:
|
|
167
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
168
|
+
contains:
|
|
169
|
+
- "aggregateDescriptors"
|
|
170
|
+
- "SqlAggregateDescriptor"
|
|
171
|
+
anyMatch: true
|
|
172
|
+
- id: aggregate-defaults-are-js-native-numbers
|
|
173
|
+
summary: |
|
|
174
|
+
Both built-in targets split their aggregate vocabulary. `count()`, `sum()` over an integer
|
|
175
|
+
input, and `avg()` over an integer input read as `number` — where they read as a `bigint`,
|
|
176
|
+
a `bigint`-or-decimal-string, and a decimal string. Three new operations carry the lossless
|
|
177
|
+
results: `countBigInt` → `bigint`, `sumBigInt` → `bigint` (over `pg/int8@1`,
|
|
178
|
+
`pg/int8number@1`, and `pg/unboundedint@1` it resolves to `pg/unboundedint@1`, exact past
|
|
179
|
+
2^63), and `avgDecimal` → `pg/numeric@1`, PostgreSQL only. `count`'s empty-input answer is
|
|
180
|
+
`0`, not `0n`. `count`, and `sum` over an integer input, raise `RUNTIME.DECODE_FAILED` past
|
|
181
|
+
±(2^53 − 1) rather than rounding, on the JSON/include path as well as the wire path; no
|
|
182
|
+
other result carries that guard, `avg` included. Unchanged: `min`/`max`,
|
|
183
|
+
`sum`/`avg` over float codecs, `sum` over `pg/numeric@1` and `pg/unboundedint@1`, and the
|
|
184
|
+
ORM's `having(...)` operands, which its typed surface fixes at `number`. The SQL builder's
|
|
185
|
+
comparison operands do move: `fns.gt` types both sides from one codec, so
|
|
186
|
+
`fns.gt(fns.count(), 1n)` becomes `fns.gt(fns.count(), 1)`.
|
|
187
|
+
Two things to do. Re-run your contract space's `contract emit` —
|
|
188
|
+
the `AggregateTypes` block gains the three operations and the changed result codecs. Then
|
|
189
|
+
fix pack tests that assert aggregate values or rendered SQL: `2n` and decimal-string
|
|
190
|
+
expectations become plain numbers, and PostgreSQL's integer `avg` renders
|
|
191
|
+
`CAST(avg(…) AS float8)` where it rendered a plain `avg(…)`. SQLite's transport cast to
|
|
192
|
+
text is unchanged, but `sqlite/bigintnumber@1` now carries a JSON projection
|
|
193
|
+
(`CAST(… AS INTEGER)`), so a SQLite include aggregate arrives inside `json_object` as a
|
|
194
|
+
JSON number rather than a JSON string.
|
|
195
|
+
detection:
|
|
196
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
197
|
+
contains:
|
|
198
|
+
- ".aggregate("
|
|
199
|
+
- "aggregateDescriptors"
|
|
200
|
+
- "AggregateTypes"
|
|
201
|
+
anyMatch: true
|
|
202
|
+
- id: non-nullable-aggregate-descriptors-declare-an-empty-result
|
|
203
|
+
summary: |
|
|
204
|
+
A descriptor with `nullable: false` must also declare `emptyResultJson` — the value the
|
|
205
|
+
operation answers with when no result row reaches the client at all. `AggregateResultNullability`
|
|
206
|
+
(exported from `@internal/framework-components/components`) is a discriminated union, so
|
|
207
|
+
`{ nullable: false }` on its own does not compile, and registry assembly rejects it at
|
|
208
|
+
runtime with `RUNTIME.AGGREGATE_DESCRIPTOR_INVALID` (or `CONTRACT.AGGREGATE_DESCRIPTOR_INVALID`
|
|
209
|
+
during emit). State the value in the **result codec's canonical JSON**, not as an
|
|
210
|
+
application value: `emptyResultJson: 0` under `pg/int8number@1`, `emptyResultJson: '0'`
|
|
211
|
+
under `pg/int8@1`. The empty-input answer belongs to the operation rather than to the codec
|
|
212
|
+
— `count`'s identity element is zero, an `every()`'s would be `true` — which is why it sits
|
|
213
|
+
on the descriptor. `ResolvedSqlAggregate` (`@internal/sql-relational-core/query-lane-context`)
|
|
214
|
+
follows the same union, so a consumer that constructs one spreads the nullability rather
|
|
215
|
+
than assigning `nullable: boolean`; reading `resolved.nullable` still narrows as before.
|
|
216
|
+
Nullable descriptors are unchanged.
|
|
217
|
+
detection:
|
|
218
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
219
|
+
contains:
|
|
220
|
+
- "nullable: false"
|
|
221
|
+
- "aggregateDescriptors"
|
|
222
|
+
- "ResolvedSqlAggregate"
|
|
223
|
+
anyMatch: true
|
|
224
|
+
- id: integer-codecs-check-the-js-type-they-are-given
|
|
225
|
+
summary: |
|
|
226
|
+
`pg/int8@1`, `pg/unboundedint@1`, and `sqlite/bigint@1` refuse a JS `number` on `encode`,
|
|
227
|
+
and `pg/int8number@1` and `sqlite/bigintnumber@1` refuse a `bigint`, with
|
|
228
|
+
`RUNTIME.ENCODE_FAILED` and a message naming the type that arrived
|
|
229
|
+
(`pg/int8@1 value must be a bigint, got number 9`) plus `meta.received`. The bigint codecs
|
|
230
|
+
used to accept a number and stringify it, which let a fractional value through to an
|
|
231
|
+
integer column. `encodeJson` is deliberately wider on the exact codecs: it accepts a
|
|
232
|
+
safe-integer `number`, because a schema language writes no `bigint` literal and
|
|
233
|
+
`BigInt @default(0)` arrives as the JSON number `0`; a non-integral or unsafe number raises
|
|
234
|
+
`<codec> number literal must be an integer within the safe integer range`. Two consequences
|
|
235
|
+
for a pack. Any place you hand a codec a value read out of a contract, JSON, or PSL must
|
|
236
|
+
pick the matching method — `encode` takes the application value, `encodeJson` takes and
|
|
237
|
+
returns canonical JSON. And if your pack renders DDL literal defaults itself, read the
|
|
238
|
+
stored value back first: `await codec.encode(codec.decodeJson(stored), {})`, which is the
|
|
239
|
+
two declared conversions in their declared order, rather than passing canonical JSON
|
|
240
|
+
straight to `encode`.
|
|
241
|
+
detection:
|
|
242
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
243
|
+
contains:
|
|
244
|
+
- "encodeJson"
|
|
245
|
+
- "decodeJson"
|
|
246
|
+
- "codec.encode"
|
|
247
|
+
anyMatch: true
|
|
248
|
+
- id: authored-check-constraints
|
|
249
|
+
summary: |
|
|
250
|
+
Two things change for packs. `sql.checkConstraint` is a new adapter-reported capability: an
|
|
251
|
+
adapter whose target implements CHECK constraint DDL reports it, and the `@@check` authoring
|
|
252
|
+
surface is gated on it. And a check is no longer "derived" merely by being wire-named —
|
|
253
|
+
user-authored checks are wire-named too. Derivation is now decided by whether the wire prefix
|
|
254
|
+
is one derivation would produce for a column of that table. A pack that read
|
|
255
|
+
`check.prefix !== undefined` to mean "Prisma Next generated this" must use the same
|
|
256
|
+
prefix-shape test, `derivedCheckPrefixes` from `@internal/sql-schema-ir/naming`. An authored
|
|
257
|
+
name that collides with a derived prefix shape is rejected at authoring with
|
|
258
|
+
`CONTRACT.CHECK_NAME_RESERVED`.
|
|
259
|
+
detection:
|
|
260
|
+
glob: "**/*.{ts,tsx}"
|
|
261
|
+
contains:
|
|
262
|
+
- 'checkConstraint'
|
|
263
|
+
- 'derivedCheckPrefixes'
|
|
264
|
+
anyMatch: true
|
|
265
|
+
- id: runtime-query-execute-hard-cut
|
|
266
|
+
summary: |
|
|
267
|
+
Runtime and scope implementations expose `query()` for rows, while rc.1 prepared rows use `target.queryPrepared(prepared, params, options?)` and rc.2 uses `prepared.query(target, params, options?)`; statistics-returning `execute()` remains for non-returning statements. Classify callers and helpers by the result they consume; do not globally rename `execute`. Route row plans to `query`, prepared row plans to `prepared.query(target, params, options?)`, and non-returning writes to `execute`, reading `stats.affectedRows` when a count is needed. Preserve the bound connection, row-result laziness, and eager statistics result. Middleware uses `beforeQuery` → `interceptQuery` → driver query → `onRow` → `afterQuery` for rows and `beforeExecute` → `interceptExecute` → driver execute → `afterExecute` for statistics. Query interception returns `{ rows }`; execute interception returns `{ stats }`. There is no operation discriminator, compatibility alias, or generic fallback hook. The Mongo facade keeps static `db.query` and removes row-execution `db.execute`; execute a built row plan through `(await db.runtime()).query(plan)`.
|
|
268
|
+
detection:
|
|
269
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
270
|
+
contains:
|
|
271
|
+
- "beforeQuery"
|
|
272
|
+
- "interceptExecute"
|
|
273
|
+
- ".queryPrepared("
|
|
274
|
+
- ".execute("
|
|
275
|
+
anyMatch: true
|
|
276
|
+
- id: config-file-is-prisma-config-with-an-orm-section
|
|
277
|
+
summary: |
|
|
278
|
+
The CLI config file is `prisma.config.ts` — the `prisma-next.config.ts` name is
|
|
279
|
+
deprecated — and the config value is engine-shaped: `defineConfig` from
|
|
280
|
+
`@prisma/cli-engine` wraps the whole ORM config as its `orm` section. Rename the file,
|
|
281
|
+
then wrap the existing export: alias the current `defineConfig` import (from the target
|
|
282
|
+
facade or CLI config-types) as `ormConfig` and write
|
|
283
|
+
`export default defineConfig({ orm: ormConfig({ ...existing config... }) })`, adding
|
|
284
|
+
`@prisma/cli-engine` to devDependencies. Both the deprecated filename and the flat shape
|
|
285
|
+
still load, each printing a deprecation warning on stderr, so the two steps can land
|
|
286
|
+
separately; anything asserting clean stderr around CLI invocations sees the warning
|
|
287
|
+
until both are done.
|
|
288
|
+
detection:
|
|
289
|
+
glob: "**/prisma*.config.*"
|
|
290
|
+
contains:
|
|
291
|
+
- "defineConfig"
|
|
292
|
+
anyMatch: true
|
|
293
|
+
- id: published-prisma-next-bin-retired
|
|
294
|
+
summary: |
|
|
295
|
+
Nothing published ships a `prisma-next` bin anymore: `@prisma/orm-toolchain` publishes
|
|
296
|
+
the `orm` command family at `@prisma/orm-toolchain/cli` and no bin, and the database
|
|
297
|
+
facades forward no launcher. The only user-facing binary is the unified `prisma` CLI
|
|
298
|
+
(the prisma-cli distribution), which mounts the same commands. Replace
|
|
299
|
+
`prisma-next <command>` invocations in package scripts and CI with the unified CLI's
|
|
300
|
+
equivalent, and drop any dependency that was taken only to put the bin on PATH.
|
|
301
|
+
detection:
|
|
302
|
+
glob: "**/package.json"
|
|
303
|
+
contains:
|
|
304
|
+
- "prisma-next"
|
|
305
|
+
anyMatch: true
|
|
306
|
+
- id: prepared-statements-split-by-declared-result
|
|
307
|
+
summary: |
|
|
308
|
+
`runtime.prepare()` now returns one of two handles, chosen from the plan the callback
|
|
309
|
+
builds: a rows plan gives the `PreparedStatement<Params, Row>` you already have,
|
|
310
|
+
consumed with `.query(target, params)`; a plan whose declared result is an affected-row
|
|
311
|
+
count gives a `PreparedExecution<Params>`, consumed with `.execute(target, params)` and
|
|
312
|
+
resolving `SqlStatementStats`. Two things follow for an extension. A facade that
|
|
313
|
+
redeclares `prepare()` changes its return type from
|
|
314
|
+
`Promise<PreparedStatement<ParamsFromDeclaration<D, CT>, Row>>` to
|
|
315
|
+
`Promise<PreparedFor<ParamsFromDeclaration<D, CT>, Row>>`, importing `PreparedFor` from
|
|
316
|
+
`@internal/sql-runtime` — no logic changes. A scope of your own that installs the
|
|
317
|
+
prepared-query bridge (`preparedStatementQuery`) must also install the execute bridge
|
|
318
|
+
(`preparedStatementExecute`, from `@internal/sql-runtime/internal/prepared-query`),
|
|
319
|
+
routing it to your bound queryable exactly as the query bridge does; without it,
|
|
320
|
+
`prepared.execute(yourScope, ...)` throws on the bridge invariant.
|
|
321
|
+
detection:
|
|
322
|
+
glob: "**/*.{ts,mts,cts}"
|
|
323
|
+
contains:
|
|
324
|
+
- "preparedStatementQuery"
|
|
325
|
+
- "PreparedStatement<ParamsFromDeclaration"
|
|
326
|
+
anyMatch: true
|
|
327
|
+
- id: prepare-defaults-to-the-contract-codec-map
|
|
328
|
+
summary: |
|
|
329
|
+
A facade that redeclares `prepare()` should default its codec-map parameter to
|
|
330
|
+
`ExtractCodecTypes<TContract>` alone. Intersecting it with `CodecTypesBase` — the shape
|
|
331
|
+
the in-tree facades used — collapses `keyof CT` to `string` through that type's index
|
|
332
|
+
signature, which costs a caller every codec-id completion in the declaration and lets an
|
|
333
|
+
id no registry carries typecheck. Drop the intersection; the `CT extends CodecTypesBase`
|
|
334
|
+
constraint stays and nothing else in the signature changes. If your facade exposes a
|
|
335
|
+
contract-bound raw tag, its `.returns()` now narrows to the contract's ids the same way,
|
|
336
|
+
with no change on your side; a contract-free tag keeps accepting any string.
|
|
337
|
+
detection:
|
|
338
|
+
glob: "**/*.{ts,mts,cts}"
|
|
339
|
+
contains:
|
|
340
|
+
- "ExtractCodecTypes<TContract> & CodecTypesBase"
|
|
341
|
+
- "CodecTypesBase ="
|
|
342
|
+
anyMatch: true
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
# 8.0.0-rc.1 → 8.0.0-rc.2 — Extension-author upgrade instructions
|
|
346
|
+
|
|
347
|
+
## `stub-execution-contexts-must-carry-an-aggregate-registry`
|
|
348
|
+
|
|
349
|
+
The aggregate method sets are built by enumerating the composed registry, once per `Collection` construction and once at `orm(...)`. A test that fabricates an `ExecutionContext` therefore has to supply one:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
import { buildSqlAggregateDescriptorRegistry } from '@internal/sql-relational-core/aggregate-descriptor-registry';
|
|
353
|
+
|
|
354
|
+
const context = {
|
|
355
|
+
contract,
|
|
356
|
+
codecDescriptors,
|
|
357
|
+
aggregateDescriptors: buildSqlAggregateDescriptorRegistry(descriptors, codecDescriptors),
|
|
358
|
+
// …
|
|
359
|
+
};
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Two distinct failures tell you which half is missing:
|
|
363
|
+
|
|
364
|
+
- **`TypeError: Cannot read properties of undefined (reading 'values')`**, thrown from `new Collection(...)` or `orm(...)` — the stub has no `aggregateDescriptors` field at all.
|
|
365
|
+
- **`TypeError: aggregate.count is not a function`** (or `posts.count is not a function`, or `fns.sum is not a function`) — the stub has a registry, but its `values()` yields nothing, so no method was installed. `resolve()` alone no longer describes the surface: `resolve` settles one `(operation, input)` pair on demand, `values` declares which operations exist.
|
|
366
|
+
|
|
367
|
+
A minimal hand-written stub that keeps `count` available:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
const aggregateDescriptors = {
|
|
371
|
+
resolve: (operation: string) =>
|
|
372
|
+
operation === 'count'
|
|
373
|
+
? {
|
|
374
|
+
operation,
|
|
375
|
+
output: { codecId: 'pg/int8@1' },
|
|
376
|
+
nullable: false as const,
|
|
377
|
+
emptyResultJson: '0',
|
|
378
|
+
lower: undefined,
|
|
379
|
+
}
|
|
380
|
+
: undefined,
|
|
381
|
+
values: function* () {
|
|
382
|
+
yield {
|
|
383
|
+
operation: 'count',
|
|
384
|
+
input: { kind: 'any' as const },
|
|
385
|
+
output: { kind: 'codec' as const, codecId: 'pg/int8@1' },
|
|
386
|
+
nullable: false as const,
|
|
387
|
+
emptyResultJson: '0',
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
`emptyResultJson` is required on any non-nullable row — see [`non-nullable-aggregate-descriptors-declare-an-empty-result`](#non-nullable-aggregate-descriptors-declare-an-empty-result) for what the value means and which form to state it in.
|
|
394
|
+
|
|
395
|
+
Prefer the real builder where the test can afford it — it applies the same validation the runtime does, including the lowering rule below.
|
|
396
|
+
|
|
397
|
+
## `aggregate-surfaces-derive-from-the-contract-s-operation-map`
|
|
398
|
+
|
|
399
|
+
Every aggregate surface is now a mapped type keyed by the operation names in the contract's emitted `AggregateTypes` block:
|
|
400
|
+
|
|
401
|
+
| Surface | Type |
|
|
402
|
+
| --- | --- |
|
|
403
|
+
| `collection.aggregate(fn)` | `AggregateBuilder<TContract, ModelName, NsId>` |
|
|
404
|
+
| `groupBy(...).aggregate(fn)` | the same |
|
|
405
|
+
| `groupBy(...).having(fn)` | `HavingBuilder<…>` — keyed by the map *intersected with* the SQL alphabet |
|
|
406
|
+
| `include('rel', (rel) => …)` | `AggregateIncludeReducers<…>` on the collection |
|
|
407
|
+
| `sql().select((f, fns) => …)` | `AggregateOnlyFunctions<QC>` |
|
|
408
|
+
|
|
409
|
+
For a contract emitted by `prisma-next contract emit` on 8.0.0-rc.1 or later, the derivation itself takes nothing away — the block names whatever the composed stack declares. It does not name the same list it did, though: PostgreSQL now declares eight operations and SQLite seven, and the bare results over integer columns moved — `count`, `sum`, and `avg`. `min` / `max` did not, nor did `sum` and `avg` over a float, `numeric`, `interval`, or `time` column, nor `sum` over an `UnboundedInt` column; each of those stays in its own family. Re-emit, then work the two entries that carry those changes — [`count-over-a-field-counts-that-field`](#count-over-a-field-counts-that-field) and [`aggregate-defaults-are-js-native-numbers`](#aggregate-defaults-are-js-native-numbers).
|
|
410
|
+
|
|
411
|
+
For a contract whose block is unknown, all five surfaces resolve to `AggregateOperationsUnavailable` — an empty interface carrying one optional symbol-keyed brand that names the reason on hover. Two populations reach it:
|
|
412
|
+
|
|
413
|
+
- a contract built in code with `defineContract(...)` and used without emission (fixtures, integration tests, the no-emit authoring flow);
|
|
414
|
+
- a contract emitted before `AggregateTypes` existed, i.e. before 8.0.0-rc.1.
|
|
415
|
+
|
|
416
|
+
The symptom is a compile error at the call site:
|
|
417
|
+
|
|
418
|
+
```text
|
|
419
|
+
Property 'count' does not exist on type 'AggregateOperationsUnavailable'.
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Runtime behaviour is unchanged — the methods are installed from the composed registry either way — so a cast is a legitimate fix where the contract is deliberately un-emitted:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import type { AggregateSpec } from '@internal/sql-orm-client';
|
|
426
|
+
|
|
427
|
+
type DynamicAggregates = Record<string, (field?: string) => AggregateSpec[string]>;
|
|
428
|
+
|
|
429
|
+
const stats = await readings.aggregate((aggregate) => {
|
|
430
|
+
const dynamic = aggregate as DynamicAggregates;
|
|
431
|
+
return { total: dynamic['sum']!('counter'), peak: dynamic['max']!('counter') };
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
The same shape works for an include reducer, whose callback receives the refinement collection:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
const reduceToTotal = (related: unknown): unknown =>
|
|
439
|
+
(related as DynamicAggregates)['sum']!('counter');
|
|
440
|
+
|
|
441
|
+
await readings.select('id').include('samples', (samples) => reduceToTotal(samples) as never).all();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Where an argument was previously widened past the types — `aggregate.sum('counter' as never)` compiled because `AggregateFieldNames` was already `never` for such a contract — the cast moves from the argument to the builder, and the field name goes back to being a plain string.
|
|
445
|
+
|
|
446
|
+
The better fix, wherever the pack can emit, is to emit: run `prisma-next contract emit` and type the client from the emitted `Contract`. That restores full typing — arities, admitted field names, and per-codec result types — instead of erasing it.
|
|
447
|
+
|
|
448
|
+
## `count-over-a-field-counts-that-field`
|
|
449
|
+
|
|
450
|
+
`count(field)` counts that field:
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
await db.orm.User.aggregate((aggregate) => ({ all: aggregate.count() }));
|
|
454
|
+
// SELECT COUNT(*) …
|
|
455
|
+
|
|
456
|
+
await db.orm.User.aggregate((aggregate) => ({ named: aggregate.count('email') }));
|
|
457
|
+
// SELECT COUNT("email") … — rows whose email is NULL are not counted
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
The second call used to render `COUNT(*)`: the argument was accepted and discarded. It is honoured now, because both arities are read off `count`'s rows rather than special-cased — PostgreSQL declares `count` with `input: { kind: 'any' }`, which settles into a `withoutInput` row (the zero-argument call) and an `anyInput` row (the field-taking call).
|
|
461
|
+
|
|
462
|
+
No typed call site changes meaning, because the field-taking overload did not exist before. What to sweep for is a call that got past the types:
|
|
463
|
+
|
|
464
|
+
- `// @ts-expect-error` immediately above a `count(...)` call. Where the argument is a field the contract admits, the suppression is now unused and TypeScript reports the unused directive. Where it is not — a relation name, say — the directive still holds and the call still fails.
|
|
465
|
+
- `count(x as never)` or `count(x as any)`.
|
|
466
|
+
- dynamic dispatch through a `Record<string, …>` cast.
|
|
467
|
+
|
|
468
|
+
For each, decide which count you meant. `COUNT(*)` is `count()`; `COUNT(col)` skips NULLs.
|
|
469
|
+
|
|
470
|
+
## `contributed-aggregate-operations-carry-a-lowering-hook`
|
|
471
|
+
|
|
472
|
+
Aggregate operation names are an open vocabulary, and they now reach the consumer surfaces. The SQL alphabet holds exactly five names (`count`, `sum`, `avg`, `min`, `max`), and a pack that contributes a descriptor named outside it — `bitOr`, say — gets a method under that name on the ORM's `aggregate()`, on `groupBy().aggregate()`, and on the include reducers, plus a matching function on the SQL builder. Three rules come with that.
|
|
473
|
+
|
|
474
|
+
**Declare a `lower` hook.** The AST's `AggregateExpr` carries only alphabet names, so a novel operation has no default form; the hook builds its whole expression from existing nodes:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
import type { SqlAggregateDescriptor } from '@internal/sql-relational-core/aggregate-descriptor-registry';
|
|
478
|
+
import { FunctionCallExpr } from '@internal/sql-relational-core/ast';
|
|
479
|
+
|
|
480
|
+
const bitOr: SqlAggregateDescriptor = {
|
|
481
|
+
operation: 'bitOr',
|
|
482
|
+
input: { kind: 'codec', codecId: 'pg/int8@1' },
|
|
483
|
+
output: { kind: 'codec', codecId: 'pg/int8@1' },
|
|
484
|
+
nullable: true,
|
|
485
|
+
lower: ({ expr }) => FunctionCallExpr.of('bit_or', expr === undefined ? [] : [expr]),
|
|
486
|
+
};
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Without it, the execution context refuses to compose:
|
|
490
|
+
|
|
491
|
+
```text
|
|
492
|
+
RUNTIME.AGGREGATE_LOWERING_MISSING: Aggregate descriptor 'bitOr:codec:pg/int8@1' declares
|
|
493
|
+
operation 'bitOr', which is outside the SQL aggregate alphabet (count, sum, avg, min, max)
|
|
494
|
+
and carries no lowering hook.
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Use it in projections only.** The lowered form is a rendering for the driver boundary. HAVING, ORDER BY, and comparison operands compare inside the database, where that rendering would change what the comparison means, so both consumers refuse those positions at authoring time with `ORM.AGGREGATE_PROJECTION_ONLY`. The typed surface agrees: `HavingBuilder` is keyed by the map intersected with the alphabet, so a contributed operation has no HAVING method to call. Project it in a select and filter or order on the projected value.
|
|
498
|
+
|
|
499
|
+
**Pick a name no collection member owns.** Include reducers install into the ORM collection's own namespace, beside `select`, `where`, `include`, `combine`, `aggregate`, and the collection's instance fields. `orm(...)` rejects a collision with `ORM.AGGREGATE_OPERATION_RESERVED` and names the operation in `meta.operation`; rename the operation.
|
|
500
|
+
|
|
501
|
+
Reference: [the aggregate descriptor guide](https://github.com/prisma/prisma/blob/main/docs/reference/aggregate-descriptor-guide.md).
|
|
502
|
+
|
|
503
|
+
## `aggregate-defaults-are-js-native-numbers`
|
|
504
|
+
|
|
505
|
+
Both built-in targets now split their aggregate vocabulary: the bare operations answer in the JS-native type, three new suffixed operations answer losslessly.
|
|
506
|
+
|
|
507
|
+
| Operation | Input | Result codec | Was |
|
|
508
|
+
| --- | --- | --- | --- |
|
|
509
|
+
| `count` | none or any | `pg/int8number@1` / `sqlite/bigintnumber@1` | `pg/int8@1` / `sqlite/bigint@1` |
|
|
510
|
+
| `countBigInt` | none or any | `pg/int8@1` / `sqlite/bigint@1` | — (new) |
|
|
511
|
+
| `sum` | `pg/int2@1`, `pg/int4@1`, `pg/int@1`, `sql/int@1` | `pg/int8number@1` | `pg/int8@1` |
|
|
512
|
+
| `sum` | `pg/int8@1`, `pg/int8number@1` | `pg/int8number@1` | `pg/numeric@1` |
|
|
513
|
+
| `sum` | SQLite's integer codecs | `sqlite/bigintnumber@1` | `sqlite/bigint@1` |
|
|
514
|
+
| `sumBigInt` | `pg/int2@1`, `pg/int4@1`, `pg/int@1`, `sql/int@1` | `pg/int8@1` | — (new) |
|
|
515
|
+
| `sumBigInt` | `pg/int8@1`, `pg/int8number@1`, `pg/unboundedint@1` | `pg/unboundedint@1` | — (new) |
|
|
516
|
+
| `sumBigInt` | SQLite's integer codecs | `sqlite/bigint@1` | — (new) |
|
|
517
|
+
| `avg` | every PostgreSQL integer codec | `pg/float8@1`, through a result cast | `pg/numeric@1` |
|
|
518
|
+
| `avgDecimal` | every PostgreSQL integer codec, plus `pg/numeric@1` | `pg/numeric@1` | — (new) |
|
|
519
|
+
|
|
520
|
+
Everything else keeps its row: `min` / `max`, `sum` and `avg` over the float codecs, `sum` over `pg/numeric@1` and `pg/unboundedint@1`, `avg` over `pg/numeric@1` and `pg/interval@1`, and SQLite's `avg`, which was already `sqlite/real@1`.
|
|
521
|
+
|
|
522
|
+
`sumBigInt` over a 64-bit input resolves to `pg/unboundedint@1` rather than `pg/int8@1`, deliberately: PostgreSQL computes that total as a `numeric`, and casting it back to `int8` would raise `bigint out of range` past 2^63. On SQLite, `sumBigInt` is offered inside SQLite's own bound — a 64-bit `SUM` overflow raises `integer overflow` in the database rather than promoting to a float.
|
|
523
|
+
|
|
524
|
+
### What to do
|
|
525
|
+
|
|
526
|
+
1. **Re-run your contract space's `contract emit`.** The `AggregateTypes` block in the committed `contract.d.ts` gains `countBigInt`, `sumBigInt`, and `avgDecimal`, and the changed result codecs on `count` / `sum` / `avg`.
|
|
527
|
+
2. **Fix value assertions in pack tests.** `expect(stats.total).toBe(2n)` becomes `toBe(2)`; a decimal-string average expectation becomes a number. Where the test was proving exactness, change the *method* to the suffixed variant rather than the expectation.
|
|
528
|
+
3. **Fix rendered-SQL assertions on PostgreSQL `avg`.** An integer `avg` renders `CAST(avg("t"."c") AS float8)` where it rendered a plain `avg("t"."c")`. The cast is on the **result**, so the exact `numeric` mean is computed first and rounded once.
|
|
529
|
+
4. **Expect a JSON number from a SQLite include aggregate.** `sqlite/bigintnumber@1` carries a JSON projection (`CAST(… AS INTEGER)`), so an included `count` or `sum` arrives inside `json_object` as a JSON number rather than a JSON string. The transport cast to text on the flat path is unchanged.
|
|
530
|
+
5. **Retype SQL-builder comparison literals against an aggregate.** `fns.gt(a, b)` types both operands from one codec, so a literal compared against `fns.count()` or an integer `fns.sum(...)` follows the aggregate's new result codec: `fns.gt(fns.count(), 1n)` becomes `fns.gt(fns.count(), 1)`. The ORM's `having(...)` is not this case — its comparand is typed `number` outright.
|
|
531
|
+
|
|
532
|
+
`count`, and `sum` over an integer input, raise `RUNTIME.DECODE_FAILED` past ±(2^53 − 1) rather than rounding, on the JSON path as well as the wire path — the guard runs after `JSON.parse`, and rounding is monotone, so a value outside the range cannot parse back inside it. They are the two results a guarded integer codec produces; `sum` over a float, `numeric`, or unbounded-integer input keeps that input's own family, and `avg` resolves to a float codec that rounds as any double does.
|
|
533
|
+
|
|
534
|
+
## `non-nullable-aggregate-descriptors-declare-an-empty-result`
|
|
535
|
+
|
|
536
|
+
A descriptor that declares `nullable: false` must declare `emptyResultJson` beside it:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
import type { SqlAggregateDescriptor } from '@internal/sql-relational-core/aggregate-descriptor-registry';
|
|
540
|
+
|
|
541
|
+
const count: SqlAggregateDescriptor = {
|
|
542
|
+
operation: 'count',
|
|
543
|
+
input: { kind: 'any' },
|
|
544
|
+
output: { kind: 'codec', codecId: 'pg/int8number@1' },
|
|
545
|
+
nullable: false,
|
|
546
|
+
emptyResultJson: 0,
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
`AggregateResultNullability` — exported from `@internal/framework-components/components` — is a discriminated union (`{ nullable: true } | { nullable: false; emptyResultJson: JsonValue }`), so `{ nullable: false }` alone is a type error. The runtime check agrees: registry assembly raises `RUNTIME.AGGREGATE_DESCRIPTOR_INVALID`, and `contract emit` raises `CONTRACT.AGGREGATE_DESCRIPTOR_INVALID`.
|
|
551
|
+
|
|
552
|
+
**State the value in the result codec's canonical JSON, not as an application value.** The client decodes it through the codec the same row declares, so the two must agree — and the wrong form only fails at the one moment a populated table never reaches. `count`'s zero is `0` under `pg/int8number@1` and `'0'` under `pg/int8@1`.
|
|
553
|
+
|
|
554
|
+
**Why it lives on the descriptor.** The empty-input answer is a property of the operation, not of the type its result carries: `count`'s identity element is zero, an `every()`'s would be `true`, a `product()`'s would be one. A codec has no way to know which.
|
|
555
|
+
|
|
556
|
+
The value is read only where no result row reaches the client at all — an absent aggregate alias, or an include whose envelope never arrived. SQL answers an ordinary empty input set itself.
|
|
557
|
+
|
|
558
|
+
If your pack consumes a resolution rather than contributing one, `ResolvedSqlAggregate` (`@internal/sql-relational-core/query-lane-context`) follows the same union. Reading `resolved.nullable` narrows as it always did; constructing one spreads the nullability instead of assigning a boolean:
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
const nullability = descriptor.nullable
|
|
562
|
+
? ({ nullable: true } as const)
|
|
563
|
+
: ({ nullable: false, emptyResultJson: descriptor.emptyResultJson } as const);
|
|
564
|
+
|
|
565
|
+
return { operation, output, ...nullability, lower };
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## `integer-codecs-check-the-js-type-they-are-given`
|
|
569
|
+
|
|
570
|
+
The integer codecs answer for the JS type before the range, so a wrong type reads as a wrong type:
|
|
571
|
+
|
|
572
|
+
```text
|
|
573
|
+
RUNTIME.ENCODE_FAILED: pg/int8@1 value must be a bigint, got number 9
|
|
574
|
+
RUNTIME.ENCODE_FAILED: pg/int8number@1 value must be a number, got bigint 9
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
`meta.received` names the type that arrived. `pg/int8@1`, `pg/unboundedint@1`, and `sqlite/bigint@1` read a `bigint`; `pg/int8number@1` and `sqlite/bigintnumber@1` read a `number`. The bigint codecs used to accept a number and stringify it, which meant `1.5` reached an integer column as valid decimal text.
|
|
578
|
+
|
|
579
|
+
**`encodeJson` is wider on the exact codecs, and only there.** It also accepts a safe-integer `number`, because a schema language writes no `bigint` literal — `BigInt @default(0)` arrives as the JSON number `0`. A non-integral or unsafe number is refused:
|
|
580
|
+
|
|
581
|
+
```text
|
|
582
|
+
RUNTIME.ENCODE_FAILED: pg/int8@1 number literal must be an integer within
|
|
583
|
+
the safe integer range, got 9007199254740992
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
Past that range the literal was already rounded before the codec saw it, so its digits no longer name the value that was written.
|
|
587
|
+
|
|
588
|
+
Two consequences for a pack:
|
|
589
|
+
|
|
590
|
+
- **Pick the method that matches the value you hold.** `encode` takes the application value and produces a wire value; `encodeJson` takes and returns canonical JSON. Handing canonical JSON to `encode` used to work for codecs whose two forms coincide and now fails loudly for the ones whose forms differ.
|
|
591
|
+
- **If your pack renders DDL literal defaults itself, read the stored value back first.** A contract stores a default in the codec's canonical JSON, so the pair is `decodeJson` then `encode`, in that order:
|
|
592
|
+
|
|
593
|
+
```ts
|
|
594
|
+
const value = stored instanceof Date ? stored : codec.decodeJson(stored);
|
|
595
|
+
const wire = await codec.encode(value, {});
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
A `Date` is the one authored value JSON has no notation for, so it is the one that arrives as itself.
|
|
599
|
+
|
|
600
|
+
<!--
|
|
601
|
+
PR #30049: `changes: []`. The 8.0.0-rc.2 release cut. The extension-package diff is the lockstep version bump, the `@prisma/cli-engine` peer moving to 0.1.1, and the supabase contract fixtures re-emitted so their embedded extension version matches. The engine peer is a devDependency in every extension package here, so it reaches no downstream extension author; the consumer-facing changes this release ships are the entries above, recorded by the PRs that made them.
|
|
602
|
+
|
|
603
|
+
PR #29920: `changes: []`. Adds prepared-statement test coverage to the Supabase runtime suite (test-fixture codec registration only) and fixes a postgres direct-driver transaction defect; neither requires downstream extension source translation. The SPI split itself is recorded as `driver-spi-splits-query-and-execute` in the 0.17-to-8.0.0-rc.1 transition.
|
|
604
|
+
|
|
605
|
+
PR #29902: `changes: []`. Generated contracts gain additive aggregate rows for new opt-in integer representation codecs, but existing extension schemas and source require no migration; extension authors re-emit only when adopting the new target-scoped types.
|
|
606
|
+
PR #29940: `changes: []`. Dependabot's weekly dev-dependency bumps, which move `@biomejs/biome` to 2.5.6 and realign every `biome.jsonc` `$schema` URL to match. The extension-package diff is that URL and nothing else, so it requires no downstream extension source translation.
|
|
607
|
+
PR #29965: `changes: []`. The same shape again: `@biomejs/biome` moves to 2.5.7 and every `biome.jsonc` `$schema` URL is realigned to match. The extension-package diff is that URL and nothing else, so it requires no downstream extension source translation.
|
|
608
|
+
-->
|
|
609
|
+
|
|
610
|
+
## Why checks stopped being structured
|
|
611
|
+
|
|
612
|
+
A check is now one opaque SQL string that nothing parses. Postgres reprints predicates in its
|
|
613
|
+
own normalized form — a `varchar` membership test comes back as
|
|
614
|
+
`((col)::text = ANY ((ARRAY[…])::text[]))` — so any structured reading of a live predicate
|
|
615
|
+
drifts against the authored text. Equality for a wire-named check is name equality, because the
|
|
616
|
+
hash already commits to the predicate; only an exact-named check compares its body, and then
|
|
617
|
+
byte-for-byte.
|
|
618
|
+
## If your target pack authors checks
|
|
619
|
+
|
|
620
|
+
Check emission is driven by a duck-typed `renderCheckExpressions` hook on the pack's
|
|
621
|
+
`authoring` contributions, resolved the same way `qualifyColumnType` is. It receives one
|
|
622
|
+
column's shape (`tableName`, `columnName`, `many`, and `memberValues` — the last present only
|
|
623
|
+
for a value set the toolchain owns) and returns `{ kind, columnName, expression }` candidates,
|
|
624
|
+
where `kind` is `'membership'` or `'elementNotNull'`. A pack without the hook emits no checks
|
|
625
|
+
at all, which is how SQLite keeps its no-CHECK stance. Nothing in the return value is a name:
|
|
626
|
+
the contract builder composes the prefix from the table, the column, and the kind, truncates it
|
|
627
|
+
to 54 UTF-8 bytes, and appends the content hash.
|
|
628
|
+
## Hand-written checks are visible now
|
|
629
|
+
|
|
630
|
+
Postgres introspection reads `pg_get_expr(c.conbin, c.conrelid)` and stores the predicate
|
|
631
|
+
verbatim; it no longer recognises only the two shapes the old parser could parse. Every CHECK
|
|
632
|
+
constraint on a managed table therefore reaches the differ, and one the contract does not
|
|
633
|
+
declare is an ordinary undeclared extra: reported by `db verify --strict`, and dropped by a plan
|
|
634
|
+
whose control policy allows `destructive`.
|
|
635
|
+
|
|
636
|
+
For an extension this matters in one specific case — a check your extension installs through a
|
|
637
|
+
raw-SQL migration step rather than deriving in its contract space. That constraint used to be
|
|
638
|
+
invisible and is now drop-eligible against any database the extension manages. Declare it:
|
|
639
|
+
`@@check(expression: "…", map: "<its physical name>")` in the contract space adopts the
|
|
640
|
+
constraint under the name it already carries, after which it is owned rather than extra and no
|
|
641
|
+
plan drops it — see `authored-check-constraints` in this transition. Until you do, keep the
|
|
642
|
+
tables carrying it under an additive-only policy: the check survives, plain `db verify`
|
|
643
|
+
tolerates it, and only `--strict` reports it.
|
|
644
|
+
|
|
645
|
+
## `runtime-query-execute-hard-cut`
|
|
646
|
+
|
|
647
|
+
The runtime SPI separates row streams from statement statistics. Inspect what each caller consumes rather than applying a global `execute` → `query` replacement: selects, returning writes, Mongo command-result plans, and other iterated or decoded results use `query`; non-returning DML uses eager `execute` and returns `{ affectedRows: number }`. Prepared row callers move from `target.queryPrepared(prepared, params, options?)` to `prepared.query(target, params, options?)`.
|
|
648
|
+
|
|
649
|
+
Connection, transaction, and role-bound scopes preserve their existing bound resource for both operations. Keep row results lazy and fully consume them inside a scope when required; statistics execution is eager. A count terminal must use `stats.affectedRows` from the write and never derive it from a row array.
|
|
650
|
+
|
|
651
|
+
Middleware hooks are operation-specific: query uses `beforeQuery`, `interceptQuery`, `onRow`, and `afterQuery`; statistics uses `beforeExecute`, `interceptExecute`, and `afterExecute`; `beforeCompile` remains shared. `interceptQuery` returns `{ rows }`, `interceptExecute` returns `{ stats }`, and hook selection carries the operation distinction, so contexts and results have no operation discriminator. Do not add compatibility aliases or generic fallback hooks. Split row and statistics fakes and spies so tests detect an incorrect route. Behavior intended for both operations assigns one private implementation to both corresponding hook names.
|
|
652
|
+
|
|
653
|
+
The Mongo facade keeps static `db.query`; it has no row-execution method named `query` and no compatibility `db.execute`. Build with `db.query`, obtain the connected runtime, and call `(await db.runtime()).query(plan)`. Leave genuine statistics calls, migration runners, and unrelated APIs named `execute` unchanged.
|
|
654
|
+
|
|
655
|
+
## `config-file-is-prisma-config-with-an-orm-section`
|
|
656
|
+
|
|
657
|
+
Two mechanical steps, in either order:
|
|
658
|
+
|
|
659
|
+
1. `git mv prisma-next.config.ts prisma.config.ts` (same for `.mts` / `.mjs` variants).
|
|
660
|
+
2. Wrap the flat export in the engine shape:
|
|
661
|
+
|
|
662
|
+
```ts
|
|
663
|
+
// before
|
|
664
|
+
import { defineConfig } from '@prisma/orm-postgres/config';
|
|
665
|
+
export default defineConfig({ ... });
|
|
666
|
+
|
|
667
|
+
// after
|
|
668
|
+
import { defineConfig } from '@prisma/cli-engine';
|
|
669
|
+
import { defineConfig as ormConfig } from '@prisma/orm-postgres/config';
|
|
670
|
+
export default defineConfig({ orm: ormConfig({ ... }) });
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Add `@prisma/cli-engine` to `devDependencies`. The inner config is unchanged — only the file
|
|
674
|
+
name and the outer wrapper move. The loader still discovers the deprecated filename and still
|
|
675
|
+
accepts the flat shape, each with a stderr deprecation warning, so nothing breaks mid-rename;
|
|
676
|
+
finish both steps to silence the warnings.
|
|
677
|
+
|
|
678
|
+
## `published-prisma-next-bin-retired`
|
|
679
|
+
|
|
680
|
+
`prisma-next ...` in a package script resolved through a bin the database facades forwarded
|
|
681
|
+
from the toolchain. That chain is gone: the published toolchain is bin-less and exports the
|
|
682
|
+
`orm` command family at `@prisma/orm-toolchain/cli` for the unified `prisma` CLI (the
|
|
683
|
+
prisma-cli distribution) to mount. Point scripts and CI at the unified CLI, which serves the
|
|
684
|
+
same command paths.
|
|
685
|
+
|
|
686
|
+
## `prepared-statements-split-by-declared-result`
|
|
687
|
+
|
|
688
|
+
The prepared surface answers what the plan declared. A rows plan still prepares into a
|
|
689
|
+
`PreparedStatement` you consume by querying it; a plan built from a statement that reports an
|
|
690
|
+
affected-row count prepares into a `PreparedExecution` you consume by executing it, which
|
|
691
|
+
resolves `SqlStatementStats` rather than streaming rows. The two handles share no consumption
|
|
692
|
+
method, so neither can be read the wrong way round.
|
|
693
|
+
|
|
694
|
+
Extensions meet this in two places.
|
|
695
|
+
|
|
696
|
+
**A facade that redeclares `prepare()`** — as the in-tree database facades do — swaps the
|
|
697
|
+
declared return type and nothing else:
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
// Before
|
|
701
|
+
): Promise<PreparedStatement<ParamsFromDeclaration<D, CT>, Row>> {
|
|
702
|
+
// After
|
|
703
|
+
): Promise<PreparedFor<ParamsFromDeclaration<D, CT>, Row>> {
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
`PreparedFor` comes from `@internal/sql-runtime` and resolves to whichever handle the plan
|
|
707
|
+
earns.
|
|
708
|
+
|
|
709
|
+
**A scope of your own** — a session, a role context, any object you hand to
|
|
710
|
+
`prepared.query(...)` — carries the prepared bridges. If it installs `preparedStatementQuery`,
|
|
711
|
+
it must now also install `preparedStatementExecute` beside it, routing to the same queryable:
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
[preparedStatementExecute]<Params>(
|
|
715
|
+
prepared: PreparedExecution<Params>,
|
|
716
|
+
params: Params,
|
|
717
|
+
options?: RuntimeExecuteOptions,
|
|
718
|
+
): Promise<SqlStatementStats> {
|
|
719
|
+
return runPreparedExecuteAgainstYourQueryable(prepared, params, yourQueryable, options);
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Both symbols come from `@internal/sql-runtime/internal/prepared-query`. A scope missing the
|
|
724
|
+
execute bridge typechecks but throws on the bridge invariant the first time someone executes a
|
|
725
|
+
prepared statement against it.
|
|
726
|
+
|
|
727
|
+
## `prepare-defaults-to-the-contract-codec-map`
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
// Before — the index signature on CodecTypesBase widens keyof CT to string
|
|
731
|
+
CT extends CodecTypesBase = ExtractCodecTypes<TContract> & CodecTypesBase,
|
|
732
|
+
|
|
733
|
+
// After
|
|
734
|
+
CT extends CodecTypesBase = ExtractCodecTypes<TContract>,
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
Only the default changes. The constraint, `Declaration<CT>`, `ParamsFromDeclaration`, and the
|
|
738
|
+
returned prepared handle are all as they were, and what infers at existing call sites is
|
|
739
|
+
unchanged — this is about what the caller is offered and what the compiler refuses. With the
|
|
740
|
+
intersection in place, `prepare({ id: 'pg/int4' }, ...)` typechecks and then fails at
|
|
741
|
+
execution with `RUNTIME.PARAM_REF_MISSING_CODEC`; without it, the wrong id is a compile
|
|
742
|
+
error and the right ones complete.
|
|
743
|
+
|
|
744
|
+
The same narrowing reaches `.returns()` on a contract-bound raw tag through
|
|
745
|
+
`@internal/sql-builder`, so a facade that re-exposes that tag inherits it without doing
|
|
746
|
+
anything. Tags built for a contract-free lane are deliberately left open.
|