@aventara/prisma7-adapter 1.0.0-rc.0 → 1.0.0-rc.2

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +552 -0
  2. package/README.md +407 -329
  3. package/dist/discovery/artifact.loader.d.ts +10 -6
  4. package/dist/discovery/artifact.loader.js +20 -20
  5. package/dist/discovery/capability-surface.interface.d.ts +1 -1
  6. package/dist/discovery/capability.deriver.d.ts +9 -6
  7. package/dist/discovery/capability.deriver.js +41 -6
  8. package/dist/discovery/client-shape.verifier.d.ts +1 -1
  9. package/dist/discovery/client-shape.verifier.js +2 -2
  10. package/dist/discovery/discovery-artifact.interface.d.ts +38 -47
  11. package/dist/discovery/discovery-artifact.interface.js +1 -1
  12. package/dist/discovery/enum.mapper.d.ts +2 -2
  13. package/dist/discovery/field.mapper.d.ts +13 -13
  14. package/dist/discovery/field.mapper.js +10 -10
  15. package/dist/discovery/identifier.mapper.d.ts +7 -7
  16. package/dist/discovery/installed-client.locator.d.ts +13 -0
  17. package/dist/discovery/installed-client.locator.js +43 -0
  18. package/dist/discovery/model.discoverer.d.ts +7 -4
  19. package/dist/discovery/model.discoverer.js +61 -19
  20. package/dist/discovery/operations.deriver.d.ts +6 -6
  21. package/dist/discovery/schema-facts.interface.d.ts +3 -3
  22. package/dist/discovery/schema.reader.d.ts +2 -2
  23. package/dist/discovery/schema.reader.js +5 -5
  24. package/dist/discovery/types.reader.d.ts +2 -2
  25. package/dist/discovery/types.reader.js +22 -22
  26. package/dist/discovery/vocabulary.dictionary.d.ts +14 -14
  27. package/dist/discovery/vocabulary.dictionary.js +1 -1
  28. package/dist/errors/back-reference.locator.d.ts +25 -0
  29. package/dist/errors/back-reference.locator.js +100 -0
  30. package/dist/errors/bigint-binding.locator.d.ts +7 -0
  31. package/dist/errors/bigint-binding.locator.js +27 -0
  32. package/dist/errors/error.normalizer.d.ts +44 -6
  33. package/dist/errors/error.normalizer.js +96 -7
  34. package/dist/errors/prisma-error.reader.d.ts +6 -0
  35. package/dist/errors/prisma-error.reader.js +3 -0
  36. package/dist/execution/first-variant.emulator.d.ts +4 -4
  37. package/dist/execution/first-variant.emulator.js +13 -9
  38. package/dist/execution/operation.executor.d.ts +15 -9
  39. package/dist/execution/operation.executor.js +34 -17
  40. package/dist/execution/result.shaper.d.ts +3 -3
  41. package/dist/execution/result.shaper.js +7 -7
  42. package/dist/execution/sql-statement.executor.d.ts +9 -0
  43. package/dist/execution/sql-statement.executor.js +58 -0
  44. package/dist/execution/transaction.executor.d.ts +12 -8
  45. package/dist/execution/transaction.executor.js +11 -5
  46. package/dist/generate/artifact.emitter.d.ts +11 -7
  47. package/dist/generate/artifact.emitter.js +60 -18
  48. package/dist/generate/computed-fields.reader.d.ts +19 -0
  49. package/dist/generate/computed-fields.reader.js +210 -0
  50. package/dist/generate/declared-support.reader.d.ts +1 -1
  51. package/dist/generate/discovery.cli.d.ts +4 -3
  52. package/dist/generate/discovery.cli.js +31 -15
  53. package/dist/generate/existence.gate.d.ts +4 -4
  54. package/dist/generate/existence.gate.js +4 -4
  55. package/dist/generate/index.d.ts +13 -5
  56. package/dist/generate/index.js +5 -2
  57. package/dist/generate/prisma.generate.d.ts +24 -8
  58. package/dist/generate/prisma.generate.js +52 -15
  59. package/dist/index.d.ts +4 -4
  60. package/dist/prisma-client.interface.d.ts +22 -8
  61. package/dist/prisma-client.interface.js +7 -0
  62. package/dist/prisma.adapter.d.ts +16 -16
  63. package/dist/prisma.adapter.js +48 -5
  64. package/dist/provider/provider-profile.d.ts +29 -13
  65. package/dist/provider/provider-profile.js +21 -0
  66. package/dist/translation/json-null.sentinel.d.ts +7 -0
  67. package/dist/translation/json-null.sentinel.js +17 -0
  68. package/dist/translation/mutation.translator.d.ts +8 -8
  69. package/dist/translation/mutation.translator.js +37 -30
  70. package/dist/translation/operation.translator.d.ts +7 -7
  71. package/dist/translation/operation.translator.js +12 -12
  72. package/dist/translation/order-pagination.translator.d.ts +5 -5
  73. package/dist/translation/order-pagination.translator.js +16 -16
  74. package/dist/translation/projection.translator.d.ts +4 -4
  75. package/dist/translation/projection.translator.js +11 -11
  76. package/dist/translation/scalar.conversion.d.ts +9 -9
  77. package/dist/translation/scalar.conversion.js +11 -11
  78. package/dist/translation/where.translator.d.ts +5 -5
  79. package/dist/translation/where.translator.js +114 -31
  80. package/package.json +6 -6
package/README.md CHANGED
@@ -1,30 +1,28 @@
1
1
  # `@aventara/prisma7-adapter`
2
2
 
3
3
  The Prisma 7 Adapter for Aventara. It discovers what a Prisma schema and its generated client can do,
4
- compiles that into an Aventara `AdapterModel`, and executes validated Aventara operations against a
4
+ compiles that into an Aventara `AvAdapterModel`, and executes validated Aventara operations against a
5
5
  Prisma client the application owns.
6
6
 
7
- This document is written for the person setting it up in an application. It states what the adapter
8
- does, what it deliberately does **not** offer, and the two places where a correct Aventara operation
9
- has a weaker guarantee than its name suggests. Everything a consumer has to know is in the body of
10
- this file; nothing load-bearing is in a footnote.
7
+ This README is for the person setting the adapter up in an application. It covers what the adapter
8
+ does, what it does not offer, and the two operations whose guarantee is weaker than their names
9
+ suggest. Everything you need is in the body of this file, not in footnotes.
11
10
 
12
- - **Status:** release candidate. Two engines are proven — PostgreSQL and SQLite. Every other Prisma provider is
11
+ - Status: release candidate. Two engines are proven, PostgreSQL and SQLite. Every other Prisma provider is
13
12
  unmeasured, and this file says so where it matters.
14
- - **Peer dependencies:** `@prisma/client` `^7.10.0` (required); `prisma`, `@prisma/adapter-better-sqlite3`
15
- and `@prisma/adapter-pg` `^7.10.0` and `typescript` `>=5.5.0 <7` (optional). 7.10.0 is the version the
16
- error map targets; the adapter is bound to Prisma **7**'s error surface; see
17
- [Prisma 7 only](#prisma-7-only).
13
+ - Peer dependencies: `@prisma/client` `^7.10.0` (required); `prisma`, `@prisma/adapter-better-sqlite3`
14
+ and `@prisma/adapter-pg` `^7.10.0` and `typescript` `>=5.7.0 <7` (optional). The error map targets
15
+ 7.10.0, and the adapter is bound to Prisma 7's error surface; see [Prisma 7 only](#prisma-7-only).
18
16
 
19
17
  ---
20
18
 
21
19
  ## The application owns the Prisma client, and a driver adapter is required
22
20
 
23
- `createPrismaAdapter` takes a client that is **already constructed**. It never constructs one, never
21
+ `createPrismaAdapter` takes a client that is already constructed. It never constructs one, never
24
22
  reads a connection string, and never reads an environment variable. The application owns the client's
25
23
  lifetime, its pool, its logging, its extensions and its `$disconnect`.
26
24
 
27
- Prisma 7 reaches a database through a **driver adapter**, so the client you hand over must have been
25
+ Prisma 7 reaches a database through a driver adapter, so the client you hand over must have been
28
26
  built with one. There is no fallback path, in Prisma or here.
29
27
 
30
28
  ```ts
@@ -47,31 +45,31 @@ const adapter = await createPrismaAdapter({
47
45
  transactionOptions: { timeout: 10_000, isolationLevel: "Serializable" },
48
46
  });
49
47
 
50
- const framework = await createFramework({ entrypoint: "/api", adapter });
48
+ const framework = await createFramework({ api: "/api", adapter });
51
49
  ```
52
50
 
53
- `provider` and `driver` are **declared by the caller**. The adapter does not inspect client internals
54
- to guess them, because a guess that is wrong is worse than a declaration that is checked: the driver
55
- decides whether transactions are advertised at all.
51
+ The caller declares `provider` and `driver`. The adapter does not inspect client internals to guess
52
+ them: the driver decides whether transactions are advertised at all, and a wrong guess would be worse
53
+ than a declaration that is checked.
56
54
 
57
55
  `createPrismaAdapter` is `async`. Construction verifies the discovery artifact, and the digest it
58
- checks is a `crypto.subtle` promise — so every fence fires once, here, before an adapter exists.
56
+ checks is a `crypto.subtle` promise, so every check runs once, here, before an adapter exists.
59
57
 
60
58
  ### `maxWait`, `timeout` and `isolationLevel` are adapter-owned
61
59
 
62
60
  They are provider concepts. Core's `Adapter.transaction(work)` interface carries no options and will
63
61
  not grow any, so these live on `createPrismaAdapter({ transactionOptions })` and are not Aventara public
64
- configuration. Prisma's own defaults stand when they are absent: `maxWait` 2000 ms, `timeout` 5000 ms.
62
+ configuration. Prisma's own defaults apply when they are absent: `maxWait` 2000 ms, `timeout` 5000 ms.
65
63
 
66
- Supported isolation levels come from the declared provider: PostgreSQL admits `ReadUncommitted`,
64
+ The supported isolation levels come from the declared provider. PostgreSQL admits `ReadUncommitted`,
67
65
  `ReadCommitted`, `RepeatableRead` and `Serializable`; SQLite admits `Serializable` only. Asking for a
68
- level the provider does not support fails the transaction — see [`P2028`](#p2028-covers-seven-situations-and-three-of-them-are-separable).
66
+ level the provider does not support fails the transaction; see [`P2028`](#p2028-covers-seven-situations-and-three-of-them-are-separable).
69
67
 
70
68
  ---
71
69
 
72
70
  ## The generate step, and the artifact it emits
73
71
 
74
- Discovery runs **once, at generate time**, and writes a plain-data artifact. At runtime the adapter
72
+ Discovery runs once, at generate time, and writes a plain-data artifact. At runtime the adapter
75
73
  loads that artifact, verifies it, and re-derives nothing.
76
74
 
77
75
  The step ships with the package, as a binary and as a second export:
@@ -84,26 +82,38 @@ npx aventara-prisma7-generate \
84
82
  --client src/generated \
85
83
  --provider postgresql \
86
84
  --driver @prisma/adapter-pg \
87
- --out src/discovery.artifact.ts
85
+ --out src/discovery.artifact.ts \
86
+ --config src/aventara.config.ts
88
87
  ```
89
88
 
90
- `--provider` and `--driver` are declared, never sniffed. The provider is recorded in the artifact; the
91
- driver decides `AdapterModel.transactions`, which is a driver fact the schema cannot state. Declare the
92
- driver the application will actually construct the adapter with — the two cannot drift silently, but
93
- they can disagree, and core refuses construction when they do.
94
-
95
- **The toolchain the step runs on.** The step runs the Prisma 7 CLI itself: `@prisma/prisma7` (bin
96
- `prisma7`, the Prisma 7 CLI under its own name) when it is installed, otherwise `prisma` only when its
97
- version is in this package's declared `prisma` range. `npm i prisma` now installs a Prisma 8 release
98
- candidate, so a project that has Prisma 8 installed keeps `@prisma/prisma7` beside it; with neither
99
- usable the step refuses in one sentence naming both. TypeScript 6 is supported; TypeScript 7, which has
100
- no classic compiler API, is refused in one sentence.
101
-
102
- **`--out` must not be inside `--client`.** The types reader reads every `.ts` file under the client
103
- root and refuses one that is not generated-client output, so an artifact dropped beside the client
104
- would break the next run.
105
-
106
- The same thing programmatically, for a project that already has a build script:
89
+ `--provider` and `--driver` are declared, never sniffed. The provider is recorded in the artifact. The
90
+ driver decides `AvAdapterModel.transactions`, a driver fact the schema cannot state. Declare the driver
91
+ the application will actually construct the adapter with. The two cannot drift silently, but they can
92
+ disagree, and core refuses construction when they do.
93
+
94
+ The step runs the Prisma 7 CLI itself: `@prisma/prisma7` (bin `prisma7`, the Prisma 7 CLI under its own
95
+ name) when it is installed, otherwise `prisma` only when its version is in this package's declared
96
+ `prisma` range. `npm i prisma` now installs a Prisma 8 release candidate, so a project that has Prisma 8
97
+ installed keeps `@prisma/prisma7` beside it. With neither usable, the step refuses in one sentence naming
98
+ both. TypeScript 6 is supported; TypeScript 7, which has no classic compiler API, is refused in one
99
+ sentence.
100
+
101
+ `--config` is optional. It names your Aventara configuration module: its `aventaraConfig` export (a
102
+ function, as the `aventara` scaffold writes it, or a value) or its default export. The step reads the
103
+ configuration's TypeScript type, never runs it, and states in the artifact which fields its
104
+ `compute.create` and `compute.update` stages fill, per layer (`computedFields`). A variant's guards,
105
+ pipes and filters, which run before write compute, may then leave exactly those fields out of a create;
106
+ every other required field stays required. Without `--config`, or when the configuration is not a
107
+ literal where a compute stage is written (the step prints where), the artifact states nothing and those
108
+ stages may leave any create field out. When the framework starts, it compares the statement with the
109
+ configuration and warns (`AVENTARA_STALE_GENERATED_TYPES`) if you changed a compute stage without
110
+ running the step again.
111
+
112
+ `--out` must not be inside `--client`: Prisma's next `prisma generate` clears that directory and would
113
+ delete the artifact. The step refuses such an `--out`, and one that names a directory, in one sentence
114
+ before Prisma runs.
115
+
116
+ The same step can run from code, for a project that already has a build script:
107
117
 
108
118
  ```ts
109
119
  import { generatePrismaDiscoveryArtifact } from "@aventara/prisma7-adapter/generate";
@@ -117,120 +127,139 @@ await generatePrismaDiscoveryArtifact({
117
127
  });
118
128
  ```
119
129
 
120
- **The two entries have different dependency shapes, deliberately.** `@aventara/prisma7-adapter`
121
- depends on `@aventara/core` and your generated client. `@aventara/prisma7-adapter/generate` additionally
122
- needs `typescript` and `prisma`, which are declared as **optional peer dependencies**: they are not
123
- installed on your behalf, nothing reachable from the runtime entry imports either, and so no compiler
124
- and no Prisma tooling enters a deployed runtime. If you call the generate entry, have both in your
125
- project's `devDependencies` — if you are generating a Prisma client at all, you already do.
126
-
127
- The step reads two sources in the same instant — the `schema.prisma` text and the generated client's
128
- own public types — and cross-checks them against each other. What it emits is a TypeScript module
129
- exporting one `discovery` value with eight members: the canonical `AdapterModel` (scalars, enums,
130
- Resources with fields, identifiers and available operations, plus the `transactions` capability and the
131
- `referenceWrites` limit), the
132
- Prisma addressing each Resource is reached by, the resolved referential actions per Resource, a record
133
- of the generated client it was read from (each model and its field references), the create
134
- requirements per Resource, a `modelHash`, and the `prismaVersion` it was generated against.
135
-
136
- The create requirements are what a create cannot do without — a column with no default, or a required
137
- relation, which may be written instead through the scalar fields that hold its reference (the edge's
138
- `references`, below) — read off the question marks of the generated create inputs. The framework checks every create against them after
139
- your pipes and computed behaviours have run, and answers a missing value as `A2004` with a `V1000`
130
+ The two entries have different dependencies. `@aventara/prisma7-adapter` depends on `@aventara/core`
131
+ and your generated client. `@aventara/prisma7-adapter/generate` also needs `typescript` and `prisma`,
132
+ which are declared as optional peer dependencies. They are not installed for you, and nothing reachable
133
+ from the runtime entry imports either, so no compiler and no Prisma tooling enters a deployed runtime.
134
+ If you call the generate entry, have both in your project's `devDependencies`; a project that generates
135
+ a Prisma client already does.
136
+
137
+ The step reads two sources at the same time, the `schema.prisma` text and the generated client's own
138
+ public types, and cross-checks them against each other. It emits a TypeScript module exporting one
139
+ `discovery` value with eight members:
140
+
141
+ - the canonical `AvAdapterModel` (scalars, enums, Resources with fields, identifiers, available
142
+ operations and create requirements, plus the `transactions` capability and the `referenceWrites`
143
+ limit);
144
+ - the Prisma addressing each Resource is reached by;
145
+ - the resolved referential actions per Resource;
146
+ - a record of the generated client it was read from (each model and its field references);
147
+ - a `modelHash`;
148
+ - the `prismaVersion` and database `provider` it was generated against;
149
+ - the artifact's format (`regenerate_with_aventara_prepare`).
150
+
151
+ Each Resource's create requirements are what a create cannot do without, read off the question marks
152
+ of the generated create inputs: `required` lists the columns with no default (`["title"]`), and
153
+ `oneOf` each required relation with the fields that hold its reference (`[["author", "authorId"]]`:
154
+ write `author`, or `authorId`, not both). `parentSupplies` says which of them a nested create gets
155
+ from its parent (`{ "Author": { "posts": ["author", "authorId"] } }`). They are part of the model, so
156
+ they reach every Contract and the generated client: a create that leaves one out is a type error, and
157
+ at runtime, after your pipes and computed behaviours have run, it is answered `A2004` with a `V1000`
140
158
  issue at its path (`["arguments", "data", "title"]`, or `["arguments", "data", "author"]` for a
141
- required relation) instead of letting the database refuse it. A nested create does not need the
142
- relation it is created through.
143
-
144
- `modelHash` digests the **model** and nothing else. The addressing table, the referential actions, the
145
- client record and the create requirements sit beside it and outside the hash: a database-side rename or a foreign-key action carries no semantic
146
- content, and inside the hash either would move the Contract identity and force every generated client
147
- to be regenerated for nothing.
148
-
149
- On the runtime path there is **no** TypeScript program, **no** PSL parser and **no** Prisma tooling.
150
- The loader reads no file: the artifact arrives as a value you import.
151
-
152
- At construction — before the application listens — the adapter checks three things, and fails hard
153
- with an instruction to regenerate if any is wrong:
154
-
155
- 1. the artifact matches **itself** — the `modelHash` over its own canonicalized model;
156
- 2. the installed `@prisma/client` **major** is the one the artifact was generated against;
157
- 3. the Prisma client you pass is the one the artifact was **generated from**: the same models, and the
159
+ required relation) before the database can refuse it.
160
+
161
+ The module also exports the model's type by name, `AvModel` (`typeof model` is that type). A
162
+ project that emits declarations (Nest's `declaration: true`) writes that name wherever an exported
163
+ declaration holds the model, such as the scaffold's `aventaraConfig()`, an `AppFramework` alias or
164
+ `defineResource<typeof model>()`, so those declarations stay under a kilobyte at any schema size. Before
165
+ the name, each mention wrote the whole model out, about 0.5 MB over 50 models.
166
+
167
+ `modelHash` digests the model and nothing else. The addressing table, the referential actions and the
168
+ client record sit beside it and outside the hash. A database-side rename or a foreign-key action
169
+ changes no semantics; inside the hash, either would move the Contract identity and force every
170
+ generated client to be regenerated for nothing.
171
+
172
+ On the runtime path there is no TypeScript program, no PSL parser and no Prisma tooling. The loader
173
+ reads no file: the artifact arrives as a value you import. The adapter reads one: the installed
174
+ `@prisma/client`'s `package.json`, for its version.
175
+
176
+ At construction, before the application listens, the adapter checks three things and fails hard with
177
+ an instruction to regenerate if any is wrong:
178
+
179
+ 1. the artifact matches itself: the `modelHash` over its own canonicalized model;
180
+ 2. the installed `@prisma/client` major is the one the artifact was generated against. The version is
181
+ read from `@prisma/client/package.json`, resolved from the adapter's own location and then from the
182
+ working directory. Where it cannot be read (a runtime without Node's module loader, a bundle
183
+ deployed without `@prisma/client`), the adapter starts and emits a warning, code
184
+ `AVENTARA_PRISMA_VERSION_UNCHECKED`, saying the majors were not compared;
185
+ `installedPrismaVersion` overrides the version read;
186
+ 3. the Prisma client you pass is the one the artifact was generated from: the same models, and the
158
187
  same field references (`prisma.<model>.fields`) on each. A schema change followed by
159
- `prisma generate` but not by the generate step — a model or field added, removed or renamed — is
188
+ `prisma generate` but not by the generate step (a model or field added, removed or renamed) is
160
189
  refused in one sentence naming what differs:
161
190
 
162
191
  ```text
163
- PrismaDiscoveryArtifactError: The Prisma client has changed since the discovery artifact was generated (model `Comment` is in the client and not in the artifact): run `npm run aventara:prepare` (the adapter's `aventara-prisma7-generate`) to regenerate it, then restart.
192
+ AvPrismaDiscoveryArtifactError: The Prisma client has changed since the discovery artifact was generated (model `Comment` is in the client and not in the artifact): run `npm run aventara:prepare` (the adapter's `aventara-prisma7-generate`) to regenerate it, then restart.
164
193
  ```
165
194
 
166
195
  A value with no recognisable model delegate (a test double, a deep mock) is not compared.
167
196
 
168
- What the client check does not see: a change that leaves every model and field name in place — a type,
169
- a default, an attribute. Run the generate step in the same build that runs `prisma generate` (a project
170
- set up by `aventara` does, from `postinstall` and `aventara:prepare`), and deploy the artifact with the
171
- schema and client it came from.
197
+ The client check does not see a change that leaves every model and field name in place, such as a
198
+ type, a default or an attribute. Run the generate step in the same build that runs `prisma generate`
199
+ (a project set up by `aventara` does, from `postinstall` and `aventara:prepare`), and deploy the
200
+ artifact with the schema and client it came from.
172
201
 
173
202
  ---
174
203
 
175
204
  ## What is advertised, what is not, and why
176
205
 
177
- Operation availability is **derived**, never assumed. Every omission is decided in one place, from
178
- three facts: the generated delegate's own method set, the Resource's identifiers, and the driver's
206
+ Operation availability is derived, never assumed. Every omission is decided in one place, from three
207
+ facts: the generated delegate's own method set, the Resource's identifiers, and the driver's
179
208
  transaction capability.
180
209
 
181
- For a typical schema the compiled Contracts advertise **fifteen** distinct
182
- `family.variant` operations, identically on all three Contract scopes. `delete.many` is **not** one of
183
- them.
210
+ For a typical schema the compiled Contracts advertise fifteen distinct `family.variant` operations,
211
+ identically on all three Contract scopes. `delete.many` is not one of them.
184
212
 
185
- ### `delete.many` is not offered — use `delete.count`
213
+ ### `delete.many` is not offered: use `delete.count`
186
214
 
187
- Prisma 7.10.0 has no `deleteManyAndReturn`. `delete.many` owes its caller the deleted rows, and there
188
- is no provider call that returns them, so the variant is **omitted from every compiled Contract**
189
- rather than emulated. A request for it is refused by core with `A2002 UNKNOWN_OPERATION` before the
190
- adapter is reached — zero statements are issued.
215
+ Prisma 7.10.0 has no `deleteManyAndReturn`. `delete.many` owes its caller the deleted rows, and no
216
+ provider call returns them, so the variant is omitted from every compiled Contract instead of being
217
+ emulated. Core refuses a request for it with `A2002 UNKNOWN_OPERATION` before the adapter is reached,
218
+ and no statement is issued.
191
219
 
192
- **Use `delete.count`** to delete a filtered set. It answers a number, including zero.
220
+ To delete a filtered set, use `delete.count`. It answers a number, including zero.
193
221
 
194
222
  ### Provider-gated capabilities are read from the client's own types, never from a provider table
195
223
 
196
- No module in this package branches on `provider`. A capability is advertised when the **generated
197
- client's own public types admit it**, and the provider name is not even in scope where capabilities
198
- are derived — so `if (provider === …)` is a compile error rather than a code-review question. That is
199
- the rule because a provider table is a second source of truth that drifts, silently, one Prisma
200
- release at a time.
224
+ No module in this package branches on `provider`. A capability is advertised when the generated
225
+ client's own public types admit it. The provider name is not even in scope where capabilities are
226
+ derived, so `if (provider === …)` is a compile error. A provider table would be a second source of
227
+ truth, and it would drift one Prisma release at a time.
201
228
 
202
- Consequences on PostgreSQL and SQLite, all of them read from generated types rather than
203
- tabulated:
229
+ On PostgreSQL and SQLite, read from the generated types:
204
230
 
205
231
  | Capability | PostgreSQL | SQLite | Read from |
206
232
  |---|---|---|---|
207
- | Scalar list fields | advertised | **not advertised** — the connector rejects a list of primitives at schema validation | the schema's own validation plus the generated field set |
208
- | JSON `path` filter | advertised | **omitted** — the operand is a JSONPath `string` here and `string[]` there, and one protocol name cannot carry both | the written operand shape on `JsonFilter.path` |
233
+ | Scalar list fields | advertised | not advertised: the connector rejects a list of primitives at schema validation | the schema's own validation plus the generated field set |
234
+ | JSON `path` filter | advertised | omitted: the operand is a JSONPath `string` here and `string[]` there, and one protocol name cannot carry both | the written operand shape on `JsonFilter.path` |
209
235
  | Case-insensitive `mode`, JSON ordering operators, field-reference `in`/`notIn` operands | as the generated filter types admit | as the generated filter types admit | `<Kind>Filter`'s own members |
210
236
  | Explicit null ordering | advertised | advertised | `SortOrderInput`'s admission, per field |
211
237
  | `create.many` / `update.many` (row-returning bulk writes) | advertised | advertised | `createManyAndReturn` / `updateManyAndReturn` on the delegate |
212
- | Transactions | `"interactive"` | `"interactive"` | the **driver**, not the provider — see below |
238
+ | Transactions | `"interactive"` | `"interactive"` | the driver, not the provider (see below) |
213
239
 
214
- **Beyond PostgreSQL and SQLite nothing here is measured.** MySQL, SQL Server, MongoDB, CockroachDB
215
- and D1 were not generated against. Because every capability is read from the client you actually
216
- generated, a provider that lacks something advertises less rather than failing at runtime — but the
217
- this release makes no claim about them.
240
+ Nothing beyond PostgreSQL and SQLite is measured. Cloudflare D1 is SQLite through `@prisma/adapter-d1`:
241
+ it reads the same generated client, so its capabilities are SQLite's, and it is measured in process
242
+ through Miniflare's D1 binding, where it differs from SQLite in two ways (no transactions, and a bigint
243
+ within ±(2^53 − 1); see below). MySQL, SQL Server, MongoDB and CockroachDB were not generated against.
244
+ Because every capability is read from the client you actually generated, a provider that lacks
245
+ something advertises less instead of failing at runtime, but this release makes no claim about those
246
+ providers.
218
247
 
219
248
  ### `update.many` and `create.many` cannot project a to-many relation
220
249
 
221
- On `update.many` and `create.many`, Prisma's row-returning bulk writes reach **to-one relations
222
- only**: the generated `*SelectUpdateManyAndReturn` / `*IncludeUpdateManyAndReturn` admit the scalars
223
- plus a to-one relation and carry no to-many relation and no `$count`, where the unrestricted
224
- `*Include` carries both.
250
+ On `update.many` and `create.many`, Prisma's row-returning bulk writes reach to-one relations only.
251
+ The generated `*SelectUpdateManyAndReturn` / `*IncludeUpdateManyAndReturn` admit the scalars plus a
252
+ to-one relation and carry no to-many relation and no `$count`, where the unrestricted `*Include`
253
+ carries both.
225
254
 
226
- A to-one projection on `update.many` works. A projection reaching a **to-many** relation or `$count`
227
- on these two variants is refused before any statement runs: `A2004` (a validation error) with the
228
- issue `V1008`, nothing written. The Contract advertises each relation's projection per position, and
255
+ A to-one projection on `update.many` works. A projection reaching a to-many relation or `$count` on
256
+ these two variants is refused before any statement runs: `A2004` (a validation error) with the issue
257
+ `V1008`, and nothing is written. The Contract advertises each relation's projection per position, and
229
258
  on these two variants the position the bulk result is read in does not include to-many relations.
230
259
 
231
- **In practice:** keep `update.many`/`create.many` projections to scalars and to-one relations; read
232
- to-many relations with a separate `find`. The adapter deliberately does **not** paper over the gap
233
- with a follow-up read: that would add a statement and a new consistency window to a bulk write.
260
+ Keep `update.many`/`create.many` projections to scalars and to-one relations, and read to-many
261
+ relations with a separate `find`. The adapter does not fill the gap with a follow-up read, which would
262
+ add a statement and a new consistency window to a bulk write.
234
263
 
235
264
  ---
236
265
 
@@ -238,87 +267,84 @@ with a follow-up read: that would add a statement and a new consistency window t
238
267
 
239
268
  Read this section before using either.
240
269
 
241
- Aventara's first-style write — *general filter · ordering decides "first" · effect limited to one
242
- record · the affected record is returned, in one call* — is essentially absent across ORM design,
243
- because SQL splits the ingredients along a dialect fault line: `UPDATE … ORDER BY … LIMIT` is a
244
- MySQL/MariaDB extension while `RETURNING` is PostgreSQL/SQLite/MSSQL, so whichever dialect grants the
245
- limit denies the returning. Prisma 7.10.0's operation set has no `updateFirst` and no `deleteFirst`
246
- at all.
270
+ Aventara's first-style write takes a general filter, lets the ordering decide which record is "first",
271
+ limits the effect to that one record, and returns the affected record, all in one call. ORMs almost
272
+ never offer this, because SQL splits the ingredients between dialects: `UPDATE … ORDER BY … LIMIT` is
273
+ a MySQL/MariaDB extension while `RETURNING` is PostgreSQL/SQLite/MSSQL, so whichever dialect grants the
274
+ limit denies the returning. Prisma 7.10.0's operation set has no `updateFirst` and no `deleteFirst` at
275
+ all.
247
276
 
248
- So these two are **not** a Prisma-shaped gap an adapter might fill. They are operations the framework
249
- **manufactures**, out of two statements, inside one adapter-internal transaction:
277
+ The framework builds these two operations out of two statements, inside one adapter-internal
278
+ transaction:
250
279
 
251
280
  1. `findFirst` with the caller's `where` and `orderBy`, selecting the Resource's identifier. The
252
- `orderBy` is the selector for *which* record — ordering and single-record selection are natural on
253
- a read. No match answers `A1001 NO_MATCH` with `null` data, and nothing is written.
254
- 2. `update` / `delete` narrowed to the identifier the read returned, **with the caller's original
255
- predicate re-asserted alongside it**, carrying the caller's own projection — so the record the
256
- contract owes comes back from the write itself. Nothing spare is read, and nothing is stripped.
281
+ `orderBy` selects which record; ordering and single-record selection are natural on a read. No
282
+ match answers `A1001 NO_MATCH` with `null` data, and nothing is written.
283
+ 2. `update` / `delete` narrowed to the identifier the read returned, with the caller's original
284
+ predicate re-asserted alongside it, carrying the caller's own projection, so the record the
285
+ contract owes comes back from the write itself. Nothing extra is read, and nothing is stripped.
257
286
 
258
- They are therefore **non-atomic**, and they have a **defined conflict outcome**. The Contract's own
259
- vocabulary says so: a *manufactured*, *non-atomic* variant. Neither is ever described as atomic.
287
+ They are therefore non-atomic, and they have a defined conflict outcome. The Contract's own vocabulary
288
+ calls them manufactured, non-atomic variants, and neither is ever described as atomic.
260
289
 
261
290
  ### What re-assertion closes, which race that is, and the residual it leaves
262
291
 
263
- Re-asserting the caller's predicate on the write closes the **wrong-record race**. Without it the
264
- write is by identifier alone and could mutate a record that no longer matches what the caller asked
265
- for; with it the write can only ever reach a record the read selected, and a record that left the
266
- predicate in between is **detected** rather than mis-mutated — it answers
267
- `A2014 STALE_SELECTION` (409) with nothing written, and a retry may succeed.
268
-
269
- **The published guarantee, verbatim:** *the record written is one the read selected, or nothing is
270
- written and the caller is told.*
271
-
272
- **It does not close stale firstness, and nothing can.** Between the read and the write another writer
273
- can insert or update a record that sorts *ahead* of the selected one and still matches the original
274
- predicate. Both statements are blind to it — the read never saw it, and the re-asserted write
275
- succeeds against the record it was given — so `update.first` can mutate a record that is no longer
276
- the first ordered match and report **ordinary success**. Nothing detects it, nothing reports it, and
277
- no re-assertion can, because the missing fact is about a record that was never read.
278
-
279
- This is a **documented, permitted property** of a framework-manufactured variant, published as
280
- policy rather than discovered in practice. Serializable isolation is not the remedy: core's
281
- `Adapter.transaction(work)` interface carries no isolation parameter, so an adapter cannot set a level for
282
- a manufactured variant or for a plan, and a provider transaction's level is fixed before its first
283
- statement, so a per-operation escalation is not expressible at all. SQLite admits `Serializable`
284
- only, and the residual persists there regardless.
285
-
286
- **If your operation's correctness depends on the record still being the first ordered match at the
287
- instant of the write, do not use `update.first`/`delete.first`.** Select the record yourself and use
292
+ Re-asserting the caller's predicate on the write closes the wrong-record race. Without it, the write
293
+ would go by identifier alone and could change a record that no longer matches what the caller asked
294
+ for. With it, the write can only reach a record the read selected. A record that left the predicate in
295
+ between is detected instead of changed: it answers `A2014 STALE_SELECTION` (409) with nothing written,
296
+ and a retry may succeed.
297
+
298
+ The published guarantee is: *the record written is one the read selected, or nothing is written and
299
+ the caller is told.*
300
+
301
+ Re-assertion does not close stale firstness, and nothing can. Between the read and the write another
302
+ writer can insert or update a record that sorts ahead of the selected one and still matches the
303
+ original predicate. Neither statement sees it (the read never saw it, and the re-asserted write
304
+ succeeds against the record it was given), so `update.first` can change a record that is no longer the
305
+ first ordered match and report ordinary success. Nothing detects or reports this, and no re-assertion
306
+ can, because the missing fact is about a record that was never read.
307
+
308
+ This is a documented, permitted property of a framework-manufactured variant. Serializable isolation
309
+ does not fix it. Core's `Adapter.transaction(work)` interface carries no isolation parameter, so an
310
+ adapter cannot set a level for a manufactured variant or for a plan, and a provider transaction's
311
+ level is fixed before its first statement, so a per-operation escalation cannot be expressed at all.
312
+ SQLite admits `Serializable` only, and the residual persists there regardless.
313
+
314
+ If your operation's correctness depends on the record still being the first ordered match at the
315
+ moment of the write, do not use `update.first`/`delete.first`. Select the record yourself and use
288
316
  `update.unique`/`delete.unique` on an identifier, inside a transaction plan whose other operations
289
317
  make the invariant you need checkable.
290
318
 
291
- Wherever this document says a race is closed, it names which race. A sentence that says "the race is
292
- closed" without naming one is wrong.
319
+ Wherever this README says a race is closed, it names which race.
293
320
 
294
321
  ### Why the write is by identifier and not by Prisma's `limit`
295
322
 
296
- Prisma **does** offer a top-level `limit` on `updateMany`, `deleteMany` and `updateManyAndReturn` in
297
- 7.10.0 — present on every model on PostgreSQL and SQLite. The adapter does not use it, and the
298
- reason is not concealment: Aventara's doctrine is to **expose** what the ORM offers, not to hide it.
323
+ Prisma does offer a top-level `limit` on `updateMany`, `deleteMany` and `updateManyAndReturn` in
324
+ 7.10.0, on every model on PostgreSQL and SQLite. Aventara exposes what the ORM offers, so this is not
325
+ a choice to hide it.
299
326
 
300
- The reason is that `limit` **cannot express this operation**. None of those three argument types
301
- carries an `orderBy`. So `limit: 1` affects an **arbitrary** matching row, and two identical calls can
302
- affect different ones. The identifier from the ordered read is what makes the write deterministic.
327
+ `limit` cannot express this operation. None of those three argument types carries an `orderBy`, so
328
+ `limit: 1` affects an arbitrary matching row, and two identical calls can affect different rows. The
329
+ identifier from the ordered read is what makes the write deterministic.
303
330
 
304
- So, stated plainly rather than presented as a principle: **Aventara does not currently expose an
305
- arbitrary affected-row cap.** If it ever does, it will be named so that nobody can mistake it for an
306
- ordered window — the hazard belongs in the identifier, not in a sentence beside it.
331
+ Aventara does not currently expose an arbitrary affected-row cap. If it ever does, its name will make
332
+ clear that it is not an ordered window.
307
333
 
308
334
  ---
309
335
 
310
336
  ## Errors
311
337
 
312
- ### A caller discriminates by **code**, never by message text
338
+ ### Branch on the code, never on the message text
313
339
 
314
340
  Every failure this adapter originates carries framework-authored text and nothing read off the Prisma
315
341
  error. No table name, constraint name, column name, SQL fragment, connection string, file path or
316
- stack reaches a caller — the normalizer never reads the provider's `message` at all, so there is no
317
- provider string in scope to forward. The whole serialized envelope of every failing path is swept for
318
- provider markers by this package's own conformance gate.
342
+ stack reaches a caller. The normalizer never reads the provider's `message`, so there is no provider
343
+ string in scope to forward. This package's own conformance gate sweeps the whole serialized envelope
344
+ of every failing path for provider markers.
319
345
 
320
- The consequence is a rule, not an inconvenience: **branch on the code.** Message text is framework
321
- prose, may change, and deliberately says less than the provider did.
346
+ So branch on the code. Message text is framework prose, may change, and says less than the provider
347
+ did.
322
348
 
323
349
  ### The three conflict codes
324
350
 
@@ -326,137 +352,134 @@ Three different situations each have their own code.
326
352
 
327
353
  | Code | Means | Retry? |
328
354
  |---|---|---|
329
- | `A2008 CONFLICT` | The operation would violate a **uniqueness or reference constraint** the Resource declares. Raised from Prisma's `P2002` and `P2003`. | No — re-running the same request unchanged fails the same way. |
330
- | `A2013 CONCURRENT_MODIFICATION` | Another operation **modified the same state concurrently**, so this operation was not applied and nothing was written. Raised from Prisma's `P2034`. | Yes, the same request may succeed. The framework performs **no** automatic retry. |
331
- | `A2014 STALE_SELECTION` | The state this operation had **selected changed** before its effect could be applied, so nothing was written. Raised by the adapter's `update.first` and `delete.first`. | Yes, the same request may succeed. No automatic retry. |
355
+ | `A2008 CONFLICT` | The operation would violate a uniqueness or reference constraint the Resource declares. Raised from Prisma's `P2002` and `P2003`. | No. Re-running the same request unchanged fails the same way. |
356
+ | `A2013 CONCURRENT_MODIFICATION` | Another operation modified the same state concurrently, so this operation was not applied and nothing was written. Raised from Prisma's `P2034`. | Yes, the same request may succeed. The framework performs no automatic retry. |
357
+ | `A2014 STALE_SELECTION` | The state this operation had selected changed before its effect could be applied, so nothing was written. Raised by the adapter's `update.first` and `delete.first`. | Yes, the same request may succeed. No automatic retry. |
332
358
 
333
- `A2008` **narrowed** to the constraint case; it was not repurposed. If you previously treated `A2008`
334
- as "any conflict", the two situations that left it now have their own codes.
359
+ `A2008` was narrowed to the constraint case, not repurposed. If you previously treated `A2008` as "any
360
+ conflict", the two situations that left it now have their own codes.
335
361
 
336
- Two consequences worth stating explicitly:
362
+ `A2008`'s narrowed meaning lives in the specification, not in the wire text. The emitted message stays
363
+ *"The operation conflicts with the current state of the resource."* The words "uniqueness" and
364
+ "constraint" are the provider's own words for this failure, and the sanitization rule bans them from a
365
+ provider-originated envelope because it cannot tell a framework-authored "constraint" from Prisma's.
366
+ So this README can say `A2008` means a uniqueness or reference constraint, but the envelope does not
367
+ say so, and nothing should be parsed out of it.
337
368
 
338
- - **`A2008`'s narrowed meaning lives in the specification, not in the wire text.** The emitted message
339
- stays *"The operation conflicts with the current state of the resource."* The words "uniqueness" and
340
- "constraint" are exactly the provider's own words for this failure and are banned from a
341
- provider-originated envelope by the sanitization rule, which cannot tell a framework-authored
342
- "constraint" from Prisma's. So this README may say `A2008` *means* a uniqueness or reference
343
- constraint; the envelope does **not** say so, and nothing should be parsed out of it.
344
- - **A write conflict and a deadlock share `A2013` deliberately.** Both arrive as `P2034` with
345
- identical `meta` keys, differing only in a provider-owned code (`40001` versus `40P01`) that never
346
- crosses the boundary. They cannot be told apart here, so one outcome is the honest answer.
369
+ A write conflict and a deadlock share `A2013`. Both arrive as `P2034` with identical `meta` keys and
370
+ differ only in a provider-owned code (`40001` versus `40P01`) that never crosses the boundary. They
371
+ cannot be told apart here, so they get one outcome.
347
372
 
348
373
  ### None of the three carries `cause.issues`
349
374
 
350
- `A2008`, `A2013` and `A2014` pair with **no** validation code and carry `cause.issues` **absent** — as
351
- a rule, not as an accident. A conflict is not a Framework Validation Layer failure: validation codes
352
- *determine* the outcome code and roll up only to the validation outcomes, so a conflict validation
353
- code would have no legal rollup. Do not write a caller that expects `issues` on a conflict.
375
+ `A2008`, `A2013` and `A2014` pair with no validation code, and `cause.issues` is always absent on
376
+ them. A conflict is not a Framework Validation Layer failure: validation codes determine the outcome
377
+ code and roll up only to the validation outcomes, so a conflict validation code would have no legal
378
+ rollup. Do not write a caller that expects `issues` on a conflict.
354
379
 
355
380
  ### `P2034` is never retried automatically
356
381
 
357
- Neither Prisma nor this adapter retries a write conflict, and the adapter will not grow one. A
358
- transaction plan's operations run developer-authored pipeline callbacks, and **re-running a plan
359
- re-runs those callbacks** — including after hooks whose external side effects are not rollback-safe
360
- (see below). A retry is the caller's to make, having decided its own operations are safe to repeat.
382
+ Neither Prisma nor this adapter retries a write conflict, and the adapter will not grow a retry. A
383
+ transaction plan's operations run developer-authored pipeline callbacks, and re-running a plan re-runs
384
+ those callbacks, including after hooks whose external side effects are not rollback-safe (see below).
385
+ The caller makes a retry, after deciding its own operations are safe to repeat.
361
386
 
362
387
  ### An after hook's external side effect inside a plan runs before the commit
363
388
 
364
- State this as a pair, because the behaviour alone reads as a bug and the obligation is what makes it
365
- a property.
389
+ Inside a transaction plan, a pipeline after hook runs inside the provider transaction, and therefore
390
+ before the provider commits. If a later operation fails, the provider rolls back: the row is absent
391
+ but the external effect has already happened. An email was sent, a queue message published or a cache
392
+ written, for a record that does not exist.
366
393
 
367
- Inside a transaction plan, a pipeline after hook runs **inside** the provider transaction — therefore
368
- **before** the provider commits. If a later operation fails, the provider rolls back: the row is
369
- absent and the external effect has already happened. An email was sent, a queue message published, a
370
- cache written, for a record that does not exist.
371
-
372
- That is not a defect to be fixed by the adapter; it is the shape the specification describes, and it
373
- is asserted against a real provider commit by this package's conformance gate. **The duty is the
374
- hook author's:** an after hook SHOULD be observational or idempotent, and MUST NOT be relied on to
375
- undo a mutation. If an effect must not happen unless the plan commits, do not perform it in an after
376
- hook.
394
+ The specification describes this behaviour, and this package's conformance gate asserts it against a
395
+ real provider commit. The duty falls on the hook author: an after hook SHOULD be observational or
396
+ idempotent, and MUST NOT be relied on to undo a mutation. If an effect must not happen unless the plan
397
+ commits, do not perform it in an after hook.
377
398
 
378
399
  ### `P2028` covers seven situations, and three of them are separable
379
400
 
380
- `P2028` is Prisma 7's transaction code and it is shared by **seven** internal classes. The adapter
381
- answers `A3002 TRANSACTION_ERROR` for the failure that *ends* the transaction, and never reads the
382
- message text to decide anything.
401
+ `P2028` is Prisma 7's transaction code, and seven internal classes share it. The adapter answers
402
+ `A3002 TRANSACTION_ERROR` wherever it is met, and never reads the message text to decide anything.
383
403
 
384
- What is separable **structurally** (on PostgreSQL and SQLite, never inferred from docs):
404
+ What can be told apart from the error's structure, measured on PostgreSQL and SQLite:
385
405
 
386
406
  | Situation | Code | `meta` |
387
407
  |---|---|---|
388
- | Transaction-timeout expiry | `P2028` | `{ operation, timeout, timeTaken }` — the expiry is identifiable |
389
- | `maxWait` acquisition timeout | `P2028` | **empty** — identifiable *by* that emptiness, and by nothing finer |
408
+ | Transaction-timeout expiry | `P2028` | `{ operation, timeout, timeTaken }`: the expiry is identifiable |
409
+ | `maxWait` acquisition timeout | `P2028` | empty: identifiable by that emptiness, and by nothing finer |
390
410
  | Valid-but-unsupported isolation level (e.g. `Snapshot` on either engine) | `P2028` | `{ isolationLevel }` |
391
- | An isolation level outside Prisma's own set (e.g. `"Bogus"`) | **`P2023`** | `{ providedIsolationLevel }` — a different code entirely |
411
+ | An isolation level outside Prisma's own set (e.g. `"Bogus"`) | `P2023` | `{ providedIsolationLevel }`, a different code entirely |
392
412
 
393
- The remaining **four** classes — transaction not found, already committed, already rolled back, and
394
- an internal consistency failure — carry **no `meta` at all** and differ only in their message text.
395
- Reading error text to tell outcomes apart is forbidden here, so those four are **inseparable**, and
396
- the acquisition timeout is inseparable *from them*. That is Prisma's limit, not a gap in this
397
- adapter, and it is recorded so nobody later closes it with message parsing.
413
+ The other four classes (transaction not found, already committed, already rolled back, and an internal
414
+ consistency failure) carry no `meta` at all and differ only in their message text. Reading error text
415
+ to tell outcomes apart is not allowed here, so those four cannot be told apart, and the acquisition
416
+ timeout cannot be told apart from them. That limit is Prisma's, and it is recorded here so nobody later
417
+ works around it by parsing messages.
398
418
 
399
- One asymmetry worth knowing: a `P2028` that ends the transaction rejects out of `$transaction` and
400
- becomes `A3002`. A `P2028` raised by a *delegate call inside* the callback is unrecognised by the
401
- closed code table, so it becomes `A3001` attributed to that operation.
419
+ A `P2028` that ends the transaction rejects out of `$transaction` and becomes `A3002`. A `P2028`
420
+ raised by a delegate call (or a raw statement) inside the callback, such as a step that runs after the
421
+ `timeout` passed, is in the closed code table and also becomes `A3002`, attributed to that operation
422
+ (`cause.operation`). In rc.1 that second case was unclassified and answered `A3001`.
402
423
 
403
424
  ### Everything unrecognised becomes `A3001`
404
425
 
405
- The code table contains only codes that were **induced** against a real engine on a real schema. A
406
- Prisma code the table does not contain is deliberately left to core's adapter boundary, which answers
407
- `A3001 ADAPTER_ERROR` with core's own text and sends the raw error to the diagnostics sink. Adding a
408
- row later is additive; shipping a guessed one is not recoverable.
426
+ The code table contains only codes that were induced against a real engine on a real schema. A Prisma
427
+ code the table does not contain is left to core's adapter boundary, which answers `A3001 ADAPTER_ERROR`
428
+ with core's own text and sends the raw error to the diagnostics sink. Adding a row later is additive;
429
+ a guessed row, once shipped, cannot be taken back.
409
430
 
410
431
  ### Prisma 7 only
411
432
 
412
- The code table's keys are P-codes, which are **7.x**'s error surface. Prisma 8 (a release candidate in October 2026)
413
- is a rewrite: no `@prisma/client`, no `prisma generate`, no driver adapters, dotted `NAMESPACE.SUBCODE` errors with no
414
- P-codes yet. A Prisma 8 adapter is therefore a **new adapter package**, `@aventara/prisma8-adapter`, not a re-map of
415
- this one. A project keeping Prisma 7 beside a Prisma 8 install runs the Prisma 7 CLI as `@prisma/prisma7` (bin
416
- `prisma7`), which the generate step prefers. The
417
- bound is enforced rather than described: the package's own suite reads the installed
418
- `@prisma/client`'s version and fails if the major is not `7`, and the discovery artifact records the
419
- major it was generated against.
433
+ The code table's keys are P-codes, which are Prisma 7.x's error surface. Prisma 8 (a release candidate
434
+ in October 2026) is a rewrite: no `@prisma/client`, no `prisma generate`, no driver adapters, and dotted
435
+ `NAMESPACE.SUBCODE` errors with no P-codes yet. A Prisma 8 adapter will therefore be a new adapter
436
+ package, `@aventara/prisma8-adapter`, instead of a re-map of this one. A project keeping Prisma 7 beside
437
+ a Prisma 8 install runs the Prisma 7 CLI as `@prisma/prisma7` (bin `prisma7`), which the generate step
438
+ prefers. The bound is enforced: the package's own suite reads the installed `@prisma/client`'s version
439
+ and fails if the major is not `7`, and the discovery artifact records the major it was generated
440
+ against.
420
441
 
421
442
  ---
422
443
 
423
444
  ## Transactions
424
445
 
425
- `Adapter.transaction` is mounted **exactly when the declared driver supports an interactive
426
- transaction**. It is one `client.$transaction(async (tx) => …)` per Aventara transaction plan:
446
+ `Adapter.transaction` is mounted exactly when the declared driver supports an interactive
447
+ transaction. It is one `client.$transaction(async (tx) => …)` per Aventara transaction plan:
427
448
 
428
- - the **callback** form only — never the array form. An Aventara plan is sequential by contract, so
429
- operation *n* may consume operation *n − 1*'s result and the second call's arguments do not exist
430
- until the first has answered. The array form takes every call up front, so it cannot honour a plan
431
- at all, and it is not even representable through the interface this adapter holds;
432
- - the **root client is unreachable** from inside the callback: the executor core is handed closes over
433
- the transaction client and nothing else;
434
- - the value the callback threw is rethrown **by identity**. The adapter adds no `try`, no `catch` and
435
- no wrapper, because that is what core's transaction interface requires and wrapping would turn every plan failure into
436
- an integrity failure.
449
+ - It uses the callback form only, never the array form. An Aventara plan is sequential by contract, so
450
+ operation *n* may consume operation *n − 1*'s result, and the second call's arguments do not exist
451
+ until the first has answered. The array form takes every call up front, so it cannot carry a plan at
452
+ all, and the interface this adapter holds cannot even represent it.
453
+ - The root client is unreachable from inside the callback: the executor core is handed closes over the
454
+ transaction client and nothing else.
455
+ - The value the callback threw is rethrown by identity. The adapter adds no `try`, no `catch` and no
456
+ wrapper, because core's transaction interface requires that, and wrapping would turn every plan
457
+ failure into an integrity failure.
437
458
 
438
- A first-style variant inside a plan **reuses the plan's transaction** rather than opening a nested
439
- one, so its two statements roll back *with* the plan rather than independently.
459
+ A first-style variant inside a plan reuses the plan's transaction instead of opening a nested one, so
460
+ its two statements roll back with the plan, not independently.
440
461
 
441
- **Prisma swallows its own rollback failures** — the rollback's rejection reaches an internal debug
442
- logger and never the caller. So this adapter never claims a rollback succeeded, because it cannot
462
+ Prisma swallows its own rollback failures: the rollback's rejection reaches an internal debug logger
463
+ and never the caller. This adapter therefore never claims a rollback succeeded, because it cannot
443
464
  observe that it did. A commit failure is different: it rejects like a callback throw.
444
465
 
445
466
  ### `transactions` is a driver fact, and it reaches the ClientContract hash
446
467
 
447
- `@prisma/adapter-d1` declares `transactions: "none"` — Cloudflare D1 rejects interactive
448
- transactions, and Prisma says so itself. `@prisma/adapter-pg` and `@prisma/adapter-better-sqlite3`
449
- declare `"interactive"`, observed by running one.
468
+ `@prisma/adapter-d1` declares `transactions: "none"`: Prisma's client refuses an interactive
469
+ `$transaction` on D1 itself. Prisma still opens its own implicit transaction for a nested write (a
470
+ `$create` under a relation, a `$connect`), and the D1 driver runs it as single statements, so a nested
471
+ write is not atomic on D1: when a later statement fails, the rows the earlier ones wrote are kept.
472
+ `@prisma/adapter-pg` and `@prisma/adapter-better-sqlite3` declare `"interactive"`, observed by running
473
+ one.
450
474
 
451
- **This has a consequence consumers must plan for.** The `transactions` capability participates in the
452
- **ClientContract hash**, and a generated client **fails hard** on a hash mismatch. So one schema, one
453
- framework configuration and two deployment drivers produce two ClientContracts, two hashes and two
454
- generated clients — and a client generated against a `"none"` deployment will not run against an
455
- `"interactive"` one, or the reverse.
475
+ Plan for the consequence. The `transactions` capability is part of the ClientContract hash, and a
476
+ generated client fails hard on a hash mismatch. So one schema, one framework configuration and two
477
+ deployment drivers produce two ClientContracts, two hashes and two generated clients, and a client
478
+ generated against a `"none"` deployment will not run against an `"interactive"` one, or the reverse.
456
479
 
457
- Practically: **generate your client against the deployment's own driver**, and treat the driver as
458
- part of the deployment identity rather than as a connection detail. If you deploy the same application
459
- over D1 and over PostgreSQL, those are two deployments with two generated clients.
480
+ Generate your client against the deployment's own driver, and treat the driver as part of the
481
+ deployment's identity, not as a connection detail. If you deploy the same application over D1 and over
482
+ PostgreSQL, those are two deployments with two generated clients.
460
483
 
461
484
  An artifact and a driver cannot drift silently either: an artifact that says `"interactive"` loaded by
462
485
  an adapter whose declared driver says `"none"` makes framework construction fail with a capability
@@ -466,12 +489,12 @@ mismatch.
466
489
 
467
490
  ## Referential actions
468
491
 
469
- **The artifact carries them, the adapter states them to core, and core refuses a transaction plan that
470
- depends on rows a cascade removed.**
492
+ The artifact carries the referential actions, the adapter states them to core, and core refuses a
493
+ transaction plan that depends on rows a cascade removed.
471
494
 
472
- The generate step resolves each relation edge's referential actions — including the defaults Prisma
473
- applies when the PSL argument is absent — cross-checks them while reading the schema, and emits them as
474
- the artifact's `referentialActions` member:
495
+ The generate step resolves each relation edge's referential actions, including the defaults Prisma
496
+ applies when the PSL argument is absent. It cross-checks them while reading the schema, and emits them
497
+ as the artifact's `referentialActions` member:
475
498
 
476
499
  ```ts
477
500
  discovery.referentialActions;
@@ -486,105 +509,160 @@ discovery.referentialActions;
486
509
  // }
487
510
  ```
488
511
 
489
- **Read it in one direction.** `onDelete` is declared on the **child** and fires when the **parent** is
490
- deleted, so an entry under Resource `R` says *deleting a `target` row does `onDelete` to rows of `R`*.
491
- To answer *"what does deleting a `Shop` do?"*, scan every Resource's `owningEdges` for
492
- `target === "Shop"` and read the enclosing Resource key — above, that is `Item` (cascade) and `Clerk`
493
- (set-null). Both ends of that sentence are Resource names and nothing in the shape tells them apart, so
494
- the inverted reading produces no error at all, only a plan that reasons backwards.
512
+ Read it in one direction. `onDelete` is declared on the child and fires when the parent is deleted, so
513
+ an entry under Resource `R` says *deleting a `target` row does `onDelete` to rows of `R`*. To answer
514
+ *"what does deleting a `Shop` do?"*, scan every Resource's `owningEdges` for `target === "Shop"` and
515
+ read the enclosing Resource key: above, that is `Item` (cascade) and `Clerk` (set-null). Both ends of
516
+ that sentence are Resource names and nothing in the shape tells them apart, so reading it backwards
517
+ produces no error, only a plan that reasons backwards.
495
518
 
496
- `references` are the edge's `@relation(fields: […])` — the scalars that hold the reference. A record
497
- that writes a relation and one of its reference fields both (`shopId` and `shop: { $connect: … }`,
498
- required relation or optional) is refused before the database is reached: `A2004` with a `V1012` issue
499
- at the relation, message `Set "shopId" or "shop", not both.` They are also what a required relation may
500
- be written through instead of the relation itself.
519
+ `references` are the edge's `@relation(fields: […])`, the scalars that hold the reference. A record
520
+ that writes both a relation and one of its reference fields (`shopId` and `shop: { $connect: … }`,
521
+ whether the relation is required or optional) is refused before the database is reached: `A2004` with
522
+ a `V1012` issue at the relation, message `Set "shopId" or "shop", not both.` The reference fields are
523
+ also what a required relation may be written through instead of the relation itself.
501
524
 
502
525
  Prisma takes one record's relations either all as relation writes or all through their reference
503
- fields — its checked and unchecked inputs — so every artifact's model states `referenceWrites: "uniform"`,
526
+ fields (its checked and unchecked inputs), so every artifact's model states `referenceWrites: "uniform"`,
504
527
  and every Contract compiled from it carries the same member. A record that writes `shopId` beside
505
528
  another relation's write (`supplier: { $connect: … }`) is refused before the database is reached:
506
529
  `A2004` with a `V1012` issue at the relation write. A reference field beside a back-reference (a
507
530
  relation that holds no reference on that side, such as a shop's `items`) mixes nothing and is
508
531
  accepted.
509
532
 
510
- Only **owning** edges appear: PSL forbids the non-owning side of a relation from declaring an action,
511
- so a back-reference's answer is its partner's and carries no entry of its own. Both actions are
512
- *resolved* — an absent PSL argument is not a third state but Prisma's effective default (`set-null`
513
- where every foreign-key scalar is optional, `restrict` otherwise, `cascade` on update), on both
514
- PostgreSQL and SQLite. An action spelling the reader cannot name stops the generate step rather than reaching
515
- the artifact.
533
+ Only owning edges appear. PSL forbids the non-owning side of a relation from declaring an action, so a
534
+ back-reference's answer is its partner's and has no entry of its own. Both actions are resolved: an
535
+ absent PSL argument means Prisma's effective default (`set-null` where every foreign-key scalar is
536
+ optional, `restrict` otherwise, `cascade` on update), on both PostgreSQL and SQLite. An action spelling
537
+ the reader cannot name stops the generate step before it reaches the artifact.
516
538
 
517
- Neither the model nor the compiled Contracts carry a referential action or a reference field — the edges are beside the
518
- model and outside `modelHash`, so they move no Contract value and no hash. `createPrismaAdapter` states
519
- them to core through the adapter's optional `referentialActions` member, translated one to one into core's
520
- five words (mirroring Prisma's and SQL's actions; direction unchanged):
539
+ Neither the model nor the compiled Contracts carry a referential action or a reference field. The edges
540
+ sit beside the model and outside `modelHash`, so they move no Contract value and no hash.
541
+ `createPrismaAdapter` states them to core through the adapter's optional `referentialActions` member,
542
+ translated one to one into core's five words (mirroring Prisma's and SQL's actions, direction
543
+ unchanged):
521
544
 
522
545
  | Prisma action | core's word | deleting a `target` row does this to rows of the enclosing Resource |
523
546
  |---|---|---|
524
547
  | `cascade` | `cascade` | deletes them, and the closure recurses into them |
525
548
  | `restrict` | `restrict` | refuses the delete while they reference it |
526
- | `no-action` | `noAction` | refuses the delete while they reference it — identical to `restrict` on both engines, because `NoAction` is not `DEFERRABLE` on either |
549
+ | `no-action` | `noAction` | refuses the delete while they reference it; identical to `restrict` on both engines, because `NoAction` is not `DEFERRABLE` on either |
527
550
  | `set-null` | `setNull` | keeps them, the reference set to null |
528
551
  | `set-default` | `setDefault` | keeps them, the reference set to its column default |
529
552
 
530
553
  Only `cascade` drives core behaviour (the `V1019` closure below). `restrict`, `noAction`, `setNull` and
531
- `setDefault` are carried as informational adapter facts: the adapter states
532
- what the schema says rather than collapsing it into what core currently reads.
554
+ `setDefault` are carried as informational adapter facts: the adapter states what the schema says
555
+ instead of reducing it to what core currently reads.
533
556
 
534
- `onUpdate` is not carried: core plans the **delete** closure only.
557
+ `onUpdate` is not carried, because core plans the delete closure only.
535
558
 
536
- **Planning-phase refusal.** Core builds the transitive closure of `cascade` edges
537
- once, at framework construction, and the transaction planner refuses a step that depends on rows an
538
- earlier step's cascade removed — its own Resource, or one its arguments reach through a relation — with
539
- `A2004`/`V1019` *"Transaction step depends on a row removed by an earlier step's cascade"*, before any
540
- provider statement is issued. For example, with `Metric.author`
541
- (`onDelete: Cascade`) as the one cascade edge: deleting an `Author` and then reading its
542
- `Metric` rows is refused; deleting an `Author` and then reading `Post` (`Restrict`), or a `Category` and
543
- then `Post` (`SetNull`), is not. The refusal is per Resource — a plan cannot know which rows a delete
544
- will match — and plan-scoped: a single delete that cascades runs.
559
+ Core builds the transitive closure of `cascade` edges once, at framework construction. The transaction
560
+ planner refuses a step that depends on rows an earlier step's cascade removed (its own Resource, or one
561
+ its arguments reach through a relation) with `A2004`/`V1019` *"Transaction step depends on a row
562
+ removed by an earlier step's cascade"*, before any provider statement is issued. For example, with
563
+ `Metric.author` (`onDelete: Cascade`) as the one cascade edge, deleting an `Author` and then reading
564
+ its `Metric` rows is refused. Deleting an `Author` and then reading `Post` (`Restrict`), or a
565
+ `Category` and then `Post` (`SetNull`), is not. The refusal is per Resource, since a plan cannot know
566
+ which rows a delete will match, and it is plan-scoped: a single delete that cascades runs.
545
567
 
546
568
  ---
547
569
 
548
- ## No raw SQL
570
+ ## Raw SQL: developer statements only, always bound
549
571
 
550
- The adapter issues **no raw SQL, ever**. `$queryRaw`, `$executeRaw`, the `…Unsafe` variants,
551
- `findRaw`, `aggregateRaw` and `$runCommandRaw` appear nowhere in this package, public or internal —
552
- and a raw action is not representable in the plan the translator builds, which is pinned by a test
553
- asserting the closed set of thirteen Prisma actions the adapter may issue.
572
+ Aventara never writes raw SQL itself. Every standard operation goes through Prisma's typed delegate
573
+ surface. `$queryRawUnsafe`, `$executeRaw`, `$executeRawUnsafe`, `findRaw`, `aggregateRaw` and
574
+ `$runCommandRaw` appear nowhere in this package, and no raw action is representable in the plan the
575
+ translator builds; a test pins that plan to the closed set of thirteen Prisma actions the adapter may
576
+ issue.
554
577
 
555
- This is an architectural rule with no exception attached. Everything the adapter does, it does through
556
- Prisma's typed delegate surface.
578
+ Developer SQL reaches the database two ways, both written as a tagged template: `framework.appTx.$sql`,
579
+ a step of an application transaction, and a pipeline stage's `ctx.$sql`, run at once.
557
580
 
558
- ---
581
+ ```ts
582
+ const post = framework.appTx.Post.create.one({ data: { title, authorId }, select: ["id"] });
583
+ const job = framework.appTx.$sql<{ msg_id: bigint }>`
584
+ select pgmq.send('jobs', jsonb_build_object('postId', ${post.$ref("id")}))`;
585
+ const [created, rows] = await framework.transaction([post, job]);
586
+ // rows is { msg_id: bigint }[]
587
+ ```
559
588
 
560
- ## Also worth knowing
589
+ The adapter runs it with `$queryRaw` in its tagged-template form. Inside a transaction (an
590
+ `appTx.$sql` step, or a `ctx.$sql` in a step, a forced call or a `ctx.$transaction`) it runs on the
591
+ interactive transaction client, so the statement commits or rolls back with the transaction. A
592
+ `ctx.$sql` made outside any transaction runs on the base client as a plain statement: no `BEGIN` is
593
+ sent around it, so it needs no interactive transaction and works with `@prisma/adapter-d1` too. The
594
+ adapter declares `sqlStatements` with every driver. Every interpolated value is
595
+ a bound parameter, never SQL text, so the text is static and a table or column name cannot come from
596
+ a value. What it binds, measured on both engines with 7.10.0:
597
+
598
+ - a JSON value (a plain object or array) is bound as its JSON text: cast it, `${value}::jsonb` on
599
+ PostgreSQL, or read it with `json_extract(${value}, '$.k')` on SQLite;
600
+ - an Aventara `AvDecimal` is bound as its exact decimal text; a `Date`, `bigint`, `Uint8Array` and the
601
+ primitives are bound as Prisma binds them;
602
+ - a bare placeholder is the database's to type: PostgreSQL reads `select ${1}` as `text`, so cast where
603
+ the type matters.
604
+
605
+ The rows come back as `$queryRaw` reads them, except that a PostgreSQL `numeric` becomes Aventara's
606
+ `AvDecimal`. Prisma 7.10.0 cannot read a SQLite BLOB column out of a raw query; such a statement fails
607
+ with `A3001`.
608
+
609
+ A raw query's failure is always `P2010` on 7.10.0, a syntax error and a constraint violation alike.
610
+ The adapter tells them apart by the driver adapter's own `cause.kind`, never by message text. A
611
+ uniqueness or foreign-key violation answers `A2008`, as it does through a delegate, and everything
612
+ else (a syntax error, a missing table, a null violation) answers `A3001` with the raw error in
613
+ diagnostics. Either way the whole plan rolls back.
614
+
615
+ `appTx.$sql` exists only in process. `clientTx` has no SQL step, the ClientContract carries nothing of
616
+ it, and no `/_transactions` request can express one. A later step cannot `$ref` a SQL step's rows
617
+ (`A2007`/`V1010`). No guard, pipe, hook, interceptor or filter runs around a SQL step, since it has no
618
+ Resource or operation for one to wrap.
561
619
 
562
- - **`$count` on a paginated relation reports the pre-pagination count.** On both engines, a
563
- relation's `_count` is unaffected by `take`/`skip`/`cursor` applied to the same relation in the same
564
- query. A filter on the relation *does* affect it.
565
- - **`limit`/`offset`/`cursor` are the public names.** Prisma's `take`/`skip` never surface.
566
- - **Decimal values are converted in both directions.** Prisma's bundled decimal object is not an
567
- Aventara public type and does not reach a caller as one.
568
- - **Count variants return numbers, including zero.** `create.many` and `update.many` return arrays.
569
- - **`find.unique`, `update.unique` and `delete.unique` answer `A2003 NOT_FOUND`** on a missing target;
620
+ ---
621
+
622
+ ## Other behaviour
623
+
624
+ - `$count` on a paginated relation reports the count before pagination. On both engines, a relation's
625
+ `_count` is unaffected by `take`/`skip`/`cursor` applied to the same relation in the same query. A
626
+ filter on the relation does affect it.
627
+ - `limit`/`offset`/`cursor` are the public names. Prisma's `take`/`skip` never surface.
628
+ - Decimal values are converted in both directions. Prisma's bundled decimal object is not an Aventara
629
+ public type and does not reach a caller as one.
630
+ - Values and filter operands reach Prisma as given: a `contains`, `startsWith` or `endsWith` operand is the
631
+ engine's `LIKE` pattern (`%` and `_` included), and a column stores, rounds or widens a value as the engine does.
632
+ A value PostgreSQL refuses (too long for its column, out of its range, U+0000 in text, a `LIKE` pattern ending in
633
+ `\`) answers `A2004` with one issue at `["arguments"]` (`V1004` or `V1001`), not `A3001`.
634
+ - On D1, a `BigInt` value must lie within ±(2^53 − 1) (±9,007,199,254,740,991), not the full signed 64-bit
635
+ range: the D1 driver binds a bigint as a JavaScript number and refuses one beyond that before the
636
+ statement runs. A value beyond it, written, compared in a filter or used in an `$increment`, answers
637
+ `A2004` with one `V1004` issue at the value (`["arguments", "data", "views"]`), and the statement that
638
+ carries it writes nothing. A `ctx.$sql` statement binding such a value answers `A3001`: its SQL is
639
+ yours.
640
+ - A record written through a relation cannot write that relation, or its reference fields, back: Prisma's nested
641
+ input for it leaves them out (a post created in `author.posts` has no `author` or `authorId`; a category created in
642
+ a post's `category` has no `posts`). The artifact states each relation's other end (`inverse`, paired by relation
643
+ name), the fields that hold its reference (`referenceFields`, from `@relation(fields: …)`) and no relation
644
+ writable at the `"loopback"` position, so `@aventara/core` refuses such a write before any
645
+ guard or SQL, `A2004` with a `V1012` issue at the field written back, and the types do not offer it. Where a layer
646
+ hides the other end, Prisma's client refuses the write instead and the adapter answers that refusal the same way.
647
+ - Count variants return numbers, including zero. `create.many` and `update.many` return arrays.
648
+ - `find.unique`, `update.unique` and `delete.unique` answer `A2003 NOT_FOUND` on a missing target;
570
649
  `find.first`, `update.first` and `delete.first` answer `A1001 NO_MATCH` with `null` data. The
571
- distinction is deliberate and is core's, not the adapter's.
650
+ distinction is core's, not the adapter's.
572
651
 
573
652
  ## Not covered
574
653
 
575
- Stated so nothing here reads as a claim it is not:
576
-
577
654
  - every provider except PostgreSQL and SQLite;
578
- - implicit many-to-many relations, `type` / `view` / composite blocks and multi-schema — each arrives
579
- as a recorded unknown that **stops the generate step** rather than mapping silently;
580
- - `@default` forms beyond `uuid()`, `cuid()`, `autoincrement()`, `now()` and `dbgenerated(…)` — an
655
+ - implicit many-to-many relations, `type` / `view` / composite blocks and multi-schema. Each arrives
656
+ as a recorded unknown that stops the generate step instead of mapping silently;
657
+ - `@default` forms beyond `uuid()`, `cuid()`, `autoincrement()`, `now()` and `dbgenerated(…)`. An
581
658
  unmatched form is recorded as a defaulted field plus an unknown that fails the generate step, so an
582
659
  unconfirmed mapping cannot become a wrong model;
583
660
  - whether Neon-HTTP or PlanetScale-HTTP support interactive transactions in Prisma 7. Those drivers
584
- are **absent** from the driver set rather than declared `"none"`: an entry with no evidence is a
585
- guess.
661
+ are absent from the driver set instead of declared `"none"`, because an entry with no evidence would
662
+ be a guess.
586
663
 
587
664
  ## License
588
665
 
589
- PolyForm Shield 1.0.0 with an additional permission — free to use, including in commercial applications; you may not
590
- use it to build a product that competes with Aventara itself. See `LICENSE` and `LICENSE-ADDITIONAL-PERMISSION.md`.
666
+ PolyForm Shield 1.0.0 with an additional permission. It is free to use, including in commercial applications, but you
667
+ may not use it to build a product that competes with Aventara itself. See `LICENSE` and
668
+ `LICENSE-ADDITIONAL-PERMISSION.md`.