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