@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.
- package/CHANGELOG.md +552 -0
- package/README.md +407 -329
- package/dist/discovery/artifact.loader.d.ts +10 -6
- package/dist/discovery/artifact.loader.js +20 -20
- package/dist/discovery/capability-surface.interface.d.ts +1 -1
- package/dist/discovery/capability.deriver.d.ts +9 -6
- package/dist/discovery/capability.deriver.js +41 -6
- package/dist/discovery/client-shape.verifier.d.ts +1 -1
- package/dist/discovery/client-shape.verifier.js +2 -2
- package/dist/discovery/discovery-artifact.interface.d.ts +38 -47
- package/dist/discovery/discovery-artifact.interface.js +1 -1
- package/dist/discovery/enum.mapper.d.ts +2 -2
- package/dist/discovery/field.mapper.d.ts +13 -13
- package/dist/discovery/field.mapper.js +10 -10
- package/dist/discovery/identifier.mapper.d.ts +7 -7
- package/dist/discovery/installed-client.locator.d.ts +13 -0
- package/dist/discovery/installed-client.locator.js +43 -0
- package/dist/discovery/model.discoverer.d.ts +7 -4
- package/dist/discovery/model.discoverer.js +61 -19
- package/dist/discovery/operations.deriver.d.ts +6 -6
- package/dist/discovery/schema-facts.interface.d.ts +3 -3
- package/dist/discovery/schema.reader.d.ts +2 -2
- package/dist/discovery/schema.reader.js +5 -5
- package/dist/discovery/types.reader.d.ts +2 -2
- package/dist/discovery/types.reader.js +22 -22
- package/dist/discovery/vocabulary.dictionary.d.ts +14 -14
- package/dist/discovery/vocabulary.dictionary.js +1 -1
- package/dist/errors/back-reference.locator.d.ts +25 -0
- package/dist/errors/back-reference.locator.js +100 -0
- package/dist/errors/bigint-binding.locator.d.ts +7 -0
- package/dist/errors/bigint-binding.locator.js +27 -0
- package/dist/errors/error.normalizer.d.ts +44 -6
- package/dist/errors/error.normalizer.js +96 -7
- package/dist/errors/prisma-error.reader.d.ts +6 -0
- package/dist/errors/prisma-error.reader.js +3 -0
- package/dist/execution/first-variant.emulator.d.ts +4 -4
- package/dist/execution/first-variant.emulator.js +13 -9
- package/dist/execution/operation.executor.d.ts +15 -9
- package/dist/execution/operation.executor.js +34 -17
- package/dist/execution/result.shaper.d.ts +3 -3
- package/dist/execution/result.shaper.js +7 -7
- package/dist/execution/sql-statement.executor.d.ts +9 -0
- package/dist/execution/sql-statement.executor.js +58 -0
- package/dist/execution/transaction.executor.d.ts +12 -8
- package/dist/execution/transaction.executor.js +11 -5
- package/dist/generate/artifact.emitter.d.ts +11 -7
- package/dist/generate/artifact.emitter.js +60 -18
- package/dist/generate/computed-fields.reader.d.ts +19 -0
- package/dist/generate/computed-fields.reader.js +210 -0
- package/dist/generate/declared-support.reader.d.ts +1 -1
- package/dist/generate/discovery.cli.d.ts +4 -3
- package/dist/generate/discovery.cli.js +31 -15
- package/dist/generate/existence.gate.d.ts +4 -4
- package/dist/generate/existence.gate.js +4 -4
- package/dist/generate/index.d.ts +13 -5
- package/dist/generate/index.js +5 -2
- package/dist/generate/prisma.generate.d.ts +24 -8
- package/dist/generate/prisma.generate.js +52 -15
- package/dist/index.d.ts +4 -4
- package/dist/prisma-client.interface.d.ts +22 -8
- package/dist/prisma-client.interface.js +7 -0
- package/dist/prisma.adapter.d.ts +16 -16
- package/dist/prisma.adapter.js +48 -5
- package/dist/provider/provider-profile.d.ts +29 -13
- package/dist/provider/provider-profile.js +21 -0
- package/dist/translation/json-null.sentinel.d.ts +7 -0
- package/dist/translation/json-null.sentinel.js +17 -0
- package/dist/translation/mutation.translator.d.ts +8 -8
- package/dist/translation/mutation.translator.js +37 -30
- package/dist/translation/operation.translator.d.ts +7 -7
- package/dist/translation/operation.translator.js +12 -12
- package/dist/translation/order-pagination.translator.d.ts +5 -5
- package/dist/translation/order-pagination.translator.js +16 -16
- package/dist/translation/projection.translator.d.ts +4 -4
- package/dist/translation/projection.translator.js +11 -11
- package/dist/translation/scalar.conversion.d.ts +9 -9
- package/dist/translation/scalar.conversion.js +11 -11
- package/dist/translation/where.translator.d.ts +5 -5
- package/dist/translation/where.translator.js +114 -31
- 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 `
|
|
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
|
|
8
|
-
does, what it
|
|
9
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
15
|
-
and `@prisma/adapter-pg` `^7.10.0` and `typescript` `>=5.
|
|
16
|
-
|
|
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
|
|
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
|
|
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({
|
|
48
|
+
const framework = await createFramework({ api: "/api", adapter });
|
|
51
49
|
```
|
|
52
50
|
|
|
53
|
-
`provider` and `driver
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
62
|
+
configuration. Prisma's own defaults apply when they are absent: `maxWait` 2000 ms, `timeout` 5000 ms.
|
|
65
63
|
|
|
66
|
-
|
|
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
|
|
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
|
|
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
|
|
91
|
-
driver decides `
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
The step reads two sources
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
`
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
a default
|
|
170
|
-
set up by `aventara` does, from `postinstall` and `aventara:prepare`), and deploy 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
|
|
178
|
-
|
|
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
|
|
182
|
-
|
|
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
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
|
197
|
-
client's own public types admit it
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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 |
|
|
208
|
-
| JSON `path` filter | advertised |
|
|
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
|
|
238
|
+
| Transactions | `"interactive"` | `"interactive"` | the driver, not the provider (see below) |
|
|
213
239
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
242
|
-
record
|
|
243
|
-
because SQL splits the ingredients
|
|
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
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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`
|
|
253
|
-
|
|
254
|
-
2. `update` / `delete` narrowed to the identifier the read returned,
|
|
255
|
-
predicate re-asserted alongside it
|
|
256
|
-
contract owes comes back from the write itself. Nothing
|
|
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
|
|
259
|
-
|
|
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
|
|
264
|
-
|
|
265
|
-
for
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
can insert or update a record that sorts
|
|
274
|
-
predicate.
|
|
275
|
-
succeeds against the record it was given
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
This is a
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
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
|
|
297
|
-
7.10.0
|
|
298
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
###
|
|
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
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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
|
|
330
|
-
| `A2013 CONCURRENT_MODIFICATION` | Another operation
|
|
331
|
-
| `A2014 STALE_SELECTION` | The state this operation had
|
|
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`
|
|
334
|
-
|
|
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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
358
|
-
transaction plan's operations run developer-authored pipeline callbacks, and
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
365
|
-
a
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
|
381
|
-
|
|
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
|
|
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 }
|
|
389
|
-
| `maxWait` acquisition timeout | `P2028` |
|
|
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"`) |
|
|
411
|
+
| An isolation level outside Prisma's own set (e.g. `"Bogus"`) | `P2023` | `{ providedIsolationLevel }`, a different code entirely |
|
|
392
412
|
|
|
393
|
-
The
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
closed code table
|
|
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
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
413
|
-
is a rewrite: no `@prisma/client`, no `prisma generate`, no driver adapters, dotted
|
|
414
|
-
P-codes yet. A Prisma 8 adapter
|
|
415
|
-
this one. A project keeping Prisma 7 beside
|
|
416
|
-
`prisma7`), which the generate step
|
|
417
|
-
bound is enforced
|
|
418
|
-
|
|
419
|
-
|
|
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
|
|
426
|
-
transaction
|
|
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
|
|
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
|
|
431
|
-
|
|
432
|
-
-
|
|
433
|
-
|
|
434
|
-
-
|
|
435
|
-
|
|
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
|
|
439
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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"
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
generated
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
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
|
|
473
|
-
applies when the PSL argument is absent
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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: […])
|
|
497
|
-
that writes a relation and one of its reference fields
|
|
498
|
-
|
|
499
|
-
at the relation, message `Set "shopId" or "shop", not both.`
|
|
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
|
|
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
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
|
518
|
-
model and outside `modelHash`, so they move no Contract value and no hash.
|
|
519
|
-
them to core through the adapter's optional `referentialActions` member,
|
|
520
|
-
five words (mirroring Prisma's and SQL's actions
|
|
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
|
|
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
|
-
|
|
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
|
|
557
|
+
`onUpdate` is not carried, because core plans the delete closure only.
|
|
535
558
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
`
|
|
543
|
-
|
|
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
|
-
##
|
|
570
|
+
## Raw SQL: developer statements only, always bound
|
|
549
571
|
|
|
550
|
-
|
|
551
|
-
`findRaw`, `aggregateRaw` and
|
|
552
|
-
and
|
|
553
|
-
|
|
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
|
-
|
|
556
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
-
|
|
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
|
|
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
|
|
579
|
-
as a recorded unknown that
|
|
580
|
-
- `@default` forms beyond `uuid()`, `cuid()`, `autoincrement()`, `now()` and `dbgenerated(…)
|
|
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
|
|
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
|
|
590
|
-
use it to build a product that competes with Aventara itself. See `LICENSE` and
|
|
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`.
|