@aventara/prisma7-adapter 0.1.0-pilot.0 → 0.1.0-pilot.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/README.md +39 -28
- package/dist/discovery/artifact.loader.d.ts +6 -2
- package/dist/discovery/artifact.loader.js +12 -57
- package/dist/discovery/capability-surface.interface.d.ts +29 -40
- package/dist/discovery/capability-surface.interface.js +0 -12
- package/dist/discovery/capability.deriver.d.ts +10 -40
- package/dist/discovery/capability.deriver.js +0 -179
- package/dist/discovery/client-shape.verifier.d.ts +13 -0
- package/dist/discovery/client-shape.verifier.js +93 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +55 -30
- package/dist/discovery/enum.mapper.d.ts +6 -14
- package/dist/discovery/enum.mapper.js +1 -2
- package/dist/discovery/field.mapper.d.ts +49 -63
- package/dist/discovery/field.mapper.js +0 -161
- package/dist/discovery/identifier.mapper.d.ts +38 -77
- package/dist/discovery/identifier.mapper.js +0 -64
- package/dist/discovery/model.discoverer.d.ts +18 -35
- package/dist/discovery/model.discoverer.js +66 -141
- package/dist/discovery/operations.deriver.d.ts +10 -38
- package/dist/discovery/operations.deriver.js +0 -40
- package/dist/discovery/scalar.mapper.d.ts +12 -35
- package/dist/discovery/scalar.mapper.js +0 -8
- package/dist/discovery/schema-facts.interface.d.ts +26 -25
- package/dist/discovery/schema-facts.interface.js +0 -10
- package/dist/discovery/schema.reader.d.ts +53 -64
- package/dist/discovery/schema.reader.js +0 -174
- package/dist/discovery/types.reader.d.ts +0 -4
- package/dist/discovery/types.reader.js +2 -177
- package/dist/discovery/vocabulary.dictionary.d.ts +36 -75
- package/dist/discovery/vocabulary.dictionary.js +2 -152
- package/dist/errors/error.normalizer.d.ts +85 -135
- package/dist/errors/error.normalizer.js +1 -70
- package/dist/errors/prisma-error.reader.d.ts +53 -68
- package/dist/errors/prisma-error.reader.js +0 -91
- package/dist/execution/first-variant.emulator.d.ts +11 -22
- package/dist/execution/first-variant.emulator.js +0 -142
- package/dist/execution/operation.executor.d.ts +12 -19
- package/dist/execution/operation.executor.js +0 -92
- package/dist/execution/result.shaper.d.ts +22 -31
- package/dist/execution/result.shaper.js +0 -24
- package/dist/execution/transaction.executor.d.ts +36 -73
- package/dist/generate/artifact.emitter.d.ts +27 -27
- package/dist/generate/artifact.emitter.js +30 -13
- package/dist/generate/declared-support.reader.d.ts +11 -11
- package/dist/generate/declared-support.reader.js +0 -12
- package/dist/generate/discovery.bin.js +0 -10
- package/dist/generate/discovery.cli.js +0 -55
- package/dist/generate/existence.gate.d.ts +26 -32
- package/dist/generate/existence.gate.js +0 -6
- package/dist/generate/index.d.ts +10 -12
- package/dist/generate/index.js +0 -42
- package/dist/generate/node-version.guard.js +0 -12
- package/dist/generate/prisma-cli.resolver.d.ts +9 -11
- package/dist/generate/prisma-cli.resolver.js +0 -13
- package/dist/generate/prisma.generate.d.ts +49 -65
- package/dist/generate/prisma.generate.js +13 -123
- package/dist/generate/reader-crosscheck.d.ts +36 -60
- package/dist/generate/reader-crosscheck.js +5 -41
- package/dist/prisma-client.interface.d.ts +14 -19
- package/dist/prisma-client.interface.js +0 -6
- package/dist/prisma.adapter.d.ts +9 -17
- package/dist/prisma.adapter.js +11 -53
- package/dist/provider/provider-profile.d.ts +6 -23
- package/dist/provider/provider-profile.js +0 -64
- package/dist/translation/identifier.addressing.d.ts +15 -22
- package/dist/translation/identifier.addressing.js +0 -10
- package/dist/translation/mutation.translator.d.ts +19 -43
- package/dist/translation/mutation.translator.js +1 -209
- package/dist/translation/operation.translator.d.ts +36 -63
- package/dist/translation/operation.translator.js +1 -51
- package/dist/translation/order-pagination.translator.d.ts +24 -31
- package/dist/translation/order-pagination.translator.js +0 -43
- package/dist/translation/projection.translator.d.ts +25 -58
- package/dist/translation/projection.translator.js +0 -27
- package/dist/translation/scalar.conversion.d.ts +15 -26
- package/dist/translation/scalar.conversion.js +1 -129
- package/dist/translation/where.translator.d.ts +17 -28
- package/dist/translation/where.translator.js +0 -83
- package/package.json +3 -3
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The total read of one hostile value
|
|
2
|
+
* The total read of one hostile value.
|
|
3
3
|
*
|
|
4
|
-
* Everything on the failure path arrives as `unknown`. `@prisma/client`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Everything on the failure path arrives as `unknown`. `@prisma/client` 7.10.0
|
|
5
|
+
* raises `PrismaClientKnownRequestError`, whose `meta` is typed `Record<string,
|
|
6
|
+
* unknown>` and is **not** a discriminated union, so a reader that trusts the type
|
|
7
|
+
* would be reading a shape nobody promised. Worse, the value need not come from
|
|
8
|
+
* Prisma at all: a developer callback, a driver, or a proxy can hand the adapter
|
|
9
|
+
* anything.
|
|
10
10
|
*
|
|
11
|
-
* So this module reads structurally and defensively, under two rules it
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* So this module reads structurally and defensively, under two rules it never
|
|
12
|
+
* relaxes — the same totality discipline as core's diagnostics reporter, which
|
|
13
|
+
* also observes values it cannot vouch for:
|
|
14
14
|
*
|
|
15
15
|
* 1. **No accessor is ever invoked.** Every read goes through
|
|
16
|
-
* `Object.getOwnPropertyDescriptor` and takes `descriptor.value`. An
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 2. **Nothing escapes.** Every reflective call is guarded, because a Proxy
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Reading only *own* properties is deliberate rather than incidental:
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
16
|
+
* `Object.getOwnPropertyDescriptor` and takes `descriptor.value`. An accessor
|
|
17
|
+
* descriptor yields `undefined` and its getter is never called, so a
|
|
18
|
+
* booby-trapped property cannot run code, mutate state, or throw from inside
|
|
19
|
+
* the adapter's failure path.
|
|
20
|
+
* 2. **Nothing escapes.** Every reflective call is guarded, because a Proxy may
|
|
21
|
+
* throw from its `getOwnPropertyDescriptor` or `ownKeys` trap, and a reader
|
|
22
|
+
* that throws while classifying a failure would replace a legible outcome with
|
|
23
|
+
* an illegible one.
|
|
24
|
+
*
|
|
25
|
+
* Reading only *own* properties is deliberate rather than incidental: 7.10.0
|
|
26
|
+
* carries `code` and `meta` as own data properties (observed own keys:
|
|
27
|
+
* `["code","meta","clientVersion","name"]`), so the own read is sufficient for
|
|
28
|
+
* every real error, and a `code` that exists only as an inherited accessor is
|
|
29
|
+
* precisely the case rule 1 refuses to touch.
|
|
30
30
|
*
|
|
31
31
|
* ## What this module does NOT read
|
|
32
32
|
*
|
|
33
|
-
* `message` is never read. At 7.10.0 a `PrismaClientKnownRequestError`
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* `message` is never read. At 7.10.0 a `PrismaClientKnownRequestError` message is
|
|
34
|
+
* not a sentence — it is a rendered call site. Measured verbatim on this
|
|
35
|
+
* repository's own schema:
|
|
36
36
|
*
|
|
37
37
|
* ```text
|
|
38
38
|
* Invalid `client.author.create()` invocation in
|
|
@@ -43,35 +43,29 @@
|
|
|
43
43
|
* Unique constraint failed on the fields: (`email`)
|
|
44
44
|
* ```
|
|
45
45
|
*
|
|
46
|
-
* That single string carries an absolute file path, a source excerpt and
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* read the message at all, so the leak has no source; the raw error still
|
|
51
|
-
* reaches core's diagnostics sink, which is server-side and may carry it.
|
|
52
|
-
*
|
|
53
|
-
* ## Version bound (C-846)
|
|
46
|
+
* That single string carries an absolute file path, a source excerpt and the
|
|
47
|
+
* violated constraint. The cheapest guarantee is not to read the message at all,
|
|
48
|
+
* so the leak has no source; the raw error still reaches core's diagnostics sink,
|
|
49
|
+
* which is server-side and may carry it.
|
|
54
50
|
*
|
|
55
51
|
* Everything below is scoped to `@prisma/client` **7.x**. Prisma 8 replaced
|
|
56
|
-
* P-codes with dotted `NAMESPACE.SUBCODE` codes, which makes a Prisma 8
|
|
57
|
-
*
|
|
52
|
+
* P-codes with dotted `NAMESPACE.SUBCODE` codes, which makes a Prisma 8 adapter a
|
|
53
|
+
* re-map of this module rather than a patch to it.
|
|
58
54
|
*/
|
|
59
55
|
/**
|
|
60
|
-
* The violated constraint as 7.10.0's driver adapters actually report it,
|
|
61
|
-
*
|
|
56
|
+
* The violated constraint as 7.10.0's driver adapters actually report it, at
|
|
57
|
+
* `meta.driverAdapterError.cause.constraint`.
|
|
62
58
|
*
|
|
63
59
|
* The arms are **provider-shaped, measured on both engines**, not guessed:
|
|
64
60
|
*
|
|
65
|
-
* | Engine
|
|
61
|
+
* | Engine | Unique (P2002) | Foreign key (P2003) |
|
|
66
62
|
* |-----------------------------------|-------------------------------|-------------------------|
|
|
67
|
-
* | `@prisma/adapter-better-sqlite3`
|
|
68
|
-
* | `@prisma/adapter-pg`
|
|
63
|
+
* | `@prisma/adapter-better-sqlite3` | `{ fields: ["email"] }` | `{ foreignKey: {}
|
|
64
|
+
* }` | | `@prisma/adapter-pg` | `{ index: "Author_email_key" }` | `{ index:
|
|
65
|
+
* "Post_authorId_fkey" }` |
|
|
69
66
|
*
|
|
70
|
-
* It is therefore **not a string**, and the two engines do not agree on
|
|
71
|
-
*
|
|
72
|
-
* the index. Every arm's payload is provider-owned text and never crosses
|
|
73
|
-
* the boundary (C-819); the *shape* is kept because it is structurally
|
|
74
|
-
* separable, which is what RC-8 needs from Task 12.
|
|
67
|
+
* It is therefore **not a string**, and the two engines do not agree on which
|
|
68
|
+
* identifier they can name — SQLite names columns, PostgreSQL names the index.
|
|
75
69
|
*/
|
|
76
70
|
export type PrismaConstraintSubject = {
|
|
77
71
|
readonly kind: "fields";
|
|
@@ -93,25 +87,20 @@ export type PrismaConstraintSubject = {
|
|
|
93
87
|
*/
|
|
94
88
|
export type PrismaErrorFacts = {
|
|
95
89
|
/**
|
|
96
|
-
* The P-code, when it is an own string property. A missing or non-string
|
|
97
|
-
*
|
|
98
|
-
*
|
|
90
|
+
* The P-code, when it is an own string property. A missing or non-string `code`
|
|
91
|
+
* leaves this `undefined`, which the table answers as `unclassified` — never as a
|
|
92
|
+
* guess.
|
|
99
93
|
*/
|
|
100
94
|
readonly code: string | undefined;
|
|
101
95
|
/**
|
|
102
|
-
* The own, enumerable key names of `meta`, in the order 7.10.0 wrote
|
|
103
|
-
* them. This is the *structural* signature of a failure, and the only
|
|
104
|
-
* discriminant ADR 0006 permits: it forbids inferring an outcome from
|
|
105
|
-
* error text, and `meta.operation` is English prose (`"an update"`,
|
|
106
|
-
* `"a delete"`, `"a query"`, `"a nested connect"`).
|
|
96
|
+
* The own, enumerable key names of `meta`, in the order 7.10.0 wrote them.
|
|
107
97
|
*/
|
|
108
98
|
readonly metaKeys: readonly string[];
|
|
109
99
|
/** `meta.modelName` — the Prisma model the failure occurred on. */
|
|
110
100
|
readonly modelName: string | undefined;
|
|
111
101
|
/**
|
|
112
|
-
* `meta.relation` — present exactly when the failure is about a
|
|
113
|
-
*
|
|
114
|
-
* This is the measured discriminant behind D-811 (see the normalizer).
|
|
102
|
+
* `meta.relation` — present exactly when the failure is about a *relation
|
|
103
|
+
* reference* rather than about the record the call selected.
|
|
115
104
|
*/
|
|
116
105
|
readonly relation: string | undefined;
|
|
117
106
|
/** `meta.model` — the model at the far end of `relation`. */
|
|
@@ -119,17 +108,13 @@ export type PrismaErrorFacts = {
|
|
|
119
108
|
/** `meta.driverAdapterError.cause.constraint`, read structurally. */
|
|
120
109
|
readonly constraint: PrismaConstraintSubject | undefined;
|
|
121
110
|
/**
|
|
122
|
-
* `meta.target` — **read so that it is measured, depended on by
|
|
123
|
-
* nothing** (C-820, RC-9).
|
|
111
|
+
* `meta.target` — **read so that it is measured, depended on by nothing**.
|
|
124
112
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* SQLite and on PostgreSQL. The docs are stale; the installed package
|
|
131
|
-
* governs (spec §18.1). This field exists so the next reader sees that
|
|
132
|
-
* measured, and does not "fix" the code to match the documentation.
|
|
113
|
+
* It was induced on both engines here and **`target` was absent from every
|
|
114
|
+
* payload** — P2002 (single and compound unique), P2003, P2025 (three shapes),
|
|
115
|
+
* P2018 and P2034, on SQLite and on PostgreSQL. The docs are stale; the installed
|
|
116
|
+
* package governs. This field exists so the next reader sees that measured, and
|
|
117
|
+
* does not "fix" the code to match the documentation.
|
|
133
118
|
*/
|
|
134
119
|
readonly legacyTarget: readonly string[] | undefined;
|
|
135
120
|
};
|
|
@@ -1,73 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The total read of one hostile value (T-10's first half).
|
|
3
|
-
*
|
|
4
|
-
* Everything on the failure path arrives as `unknown`. `@prisma/client`
|
|
5
|
-
* 7.10.0 raises `PrismaClientKnownRequestError`, whose `meta` is typed
|
|
6
|
-
* `Record<string, unknown>` and is **not** a discriminated union, so a
|
|
7
|
-
* reader that trusts the type would be reading a shape nobody promised.
|
|
8
|
-
* Worse, the value need not come from Prisma at all: a developer callback,
|
|
9
|
-
* a driver, or a proxy can hand the adapter anything.
|
|
10
|
-
*
|
|
11
|
-
* So this module reads structurally and defensively, under two rules it
|
|
12
|
-
* never relaxes — the same totality discipline as core's diagnostics
|
|
13
|
-
* reporter, which also observes values it cannot vouch for:
|
|
14
|
-
*
|
|
15
|
-
* 1. **No accessor is ever invoked.** Every read goes through
|
|
16
|
-
* `Object.getOwnPropertyDescriptor` and takes `descriptor.value`. An
|
|
17
|
-
* accessor descriptor yields `undefined` and its getter is never called,
|
|
18
|
-
* so a booby-trapped property cannot run code, mutate state, or throw
|
|
19
|
-
* from inside the adapter's failure path.
|
|
20
|
-
* 2. **Nothing escapes.** Every reflective call is guarded, because a Proxy
|
|
21
|
-
* may throw from its `getOwnPropertyDescriptor` or `ownKeys` trap, and a
|
|
22
|
-
* reader that throws while classifying a failure would replace a legible
|
|
23
|
-
* outcome with an illegible one.
|
|
24
|
-
*
|
|
25
|
-
* Reading only *own* properties is deliberate rather than incidental:
|
|
26
|
-
* 7.10.0 carries `code` and `meta` as own data properties (observed own
|
|
27
|
-
* keys: `["code","meta","clientVersion","name"]`), so the own read is
|
|
28
|
-
* sufficient for every real error, and a `code` that exists only as an
|
|
29
|
-
* inherited accessor is precisely the case rule 1 refuses to touch.
|
|
30
|
-
*
|
|
31
|
-
* ## What this module does NOT read
|
|
32
|
-
*
|
|
33
|
-
* `message` is never read. At 7.10.0 a `PrismaClientKnownRequestError`
|
|
34
|
-
* message is not a sentence — it is a rendered call site. Measured
|
|
35
|
-
* verbatim on this repository's own schema:
|
|
36
|
-
*
|
|
37
|
-
* ```text
|
|
38
|
-
* Invalid `client.author.create()` invocation in
|
|
39
|
-
* /home/.../packages/prisma7-adapter/src/errors/__probe__.spec.ts:50:18
|
|
40
|
-
*
|
|
41
|
-
* 49 await capture("P2002 duplicate email", () =>
|
|
42
|
-
* → 50 client.author.create(
|
|
43
|
-
* Unique constraint failed on the fields: (`email`)
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* That single string carries an absolute file path, a source excerpt and
|
|
47
|
-
* the violated constraint. C-819 forbids all three from reaching `cause`,
|
|
48
|
-
* and a *deliberate* outcome's top-level `cause.message` is still forwarded
|
|
49
|
-
* verbatim (register row F-723, open). The cheapest guarantee is not to
|
|
50
|
-
* read the message at all, so the leak has no source; the raw error still
|
|
51
|
-
* reaches core's diagnostics sink, which is server-side and may carry it.
|
|
52
|
-
*
|
|
53
|
-
* ## Version bound (C-846)
|
|
54
|
-
*
|
|
55
|
-
* Everything below is scoped to `@prisma/client` **7.x**. Prisma 8 replaced
|
|
56
|
-
* P-codes with dotted `NAMESPACE.SUBCODE` codes, which makes a Prisma 8
|
|
57
|
-
* adapter a re-map of this module rather than a patch to it.
|
|
58
|
-
*/
|
|
59
|
-
/** Only objects and functions can carry own properties. */
|
|
60
1
|
function canCarryProperties(value) {
|
|
61
2
|
return ((typeof value === "object" && value !== null) || typeof value === "function");
|
|
62
3
|
}
|
|
63
|
-
/**
|
|
64
|
-
* One own *data* property, or `undefined`.
|
|
65
|
-
*
|
|
66
|
-
* `"value" in descriptor` is what separates a data descriptor from an
|
|
67
|
-
* accessor one: an accessor descriptor has `get`/`set` and no `value`, so
|
|
68
|
-
* it falls through and the getter is never invoked. The `try` is for a
|
|
69
|
-
* Proxy whose `getOwnPropertyDescriptor` trap throws.
|
|
70
|
-
*/
|
|
71
4
|
function ownValue(target, key) {
|
|
72
5
|
if (!canCarryProperties(target)) {
|
|
73
6
|
return undefined;
|
|
@@ -86,7 +19,6 @@ function ownString(target, key) {
|
|
|
86
19
|
const value = ownValue(target, key);
|
|
87
20
|
return typeof value === "string" ? value : undefined;
|
|
88
21
|
}
|
|
89
|
-
/** Own enumerable key names, or none. Guarded for a hostile `ownKeys` trap. */
|
|
90
22
|
function ownKeys(target) {
|
|
91
23
|
if (!canCarryProperties(target)) {
|
|
92
24
|
return [];
|
|
@@ -98,11 +30,6 @@ function ownKeys(target) {
|
|
|
98
30
|
return [];
|
|
99
31
|
}
|
|
100
32
|
}
|
|
101
|
-
/**
|
|
102
|
-
* A string list read element by element: a sparse array, a nested array or
|
|
103
|
-
* a list of objects yields only the strings it does contain, and anything
|
|
104
|
-
* that is not an array at all yields `undefined`.
|
|
105
|
-
*/
|
|
106
33
|
function ownStringList(target, key) {
|
|
107
34
|
const value = ownValue(target, key);
|
|
108
35
|
if (!Array.isArray(value)) {
|
|
@@ -116,16 +43,6 @@ function ownStringList(target, key) {
|
|
|
116
43
|
}
|
|
117
44
|
return strings;
|
|
118
45
|
}
|
|
119
|
-
/**
|
|
120
|
-
* `meta.driverAdapterError.cause.constraint`, as the engines shape it.
|
|
121
|
-
*
|
|
122
|
-
* The chain is walked one accessor-free step at a time, so a `meta` that is
|
|
123
|
-
* a string, a `driverAdapterError` that is `null`, or a `cause` with a
|
|
124
|
-
* throwing trap all end as `undefined` rather than as a throw. A
|
|
125
|
-
* `constraint` that is present but matches no measured arm reports
|
|
126
|
-
* `"unreadable"` — a recorded unknown, which is not the same answer as "no
|
|
127
|
-
* constraint was reported".
|
|
128
|
-
*/
|
|
129
46
|
function readConstraint(meta) {
|
|
130
47
|
const cause = ownValue(ownValue(meta, "driverAdapterError"), "cause");
|
|
131
48
|
const constraint = ownValue(cause, "constraint");
|
|
@@ -145,14 +62,6 @@ function readConstraint(meta) {
|
|
|
145
62
|
}
|
|
146
63
|
return { kind: "unreadable" };
|
|
147
64
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Reads one hostile value into the facts a classification may use.
|
|
150
|
-
*
|
|
151
|
-
* Total: every input produces a `PrismaErrorFacts`. `readPrismaError(null)`
|
|
152
|
-
* and `readPrismaError(Object.create(null))` are as valid as a real
|
|
153
|
-
* `PrismaClientKnownRequestError` — they simply report nothing, which the
|
|
154
|
-
* table answers as `unclassified`.
|
|
155
|
-
*/
|
|
156
65
|
export function readPrismaError(error) {
|
|
157
66
|
const meta = ownValue(error, "meta");
|
|
158
67
|
return {
|
|
@@ -2,35 +2,25 @@ import { FrameworkError } from "@aventara/core";
|
|
|
2
2
|
import type { PrismaIdentifierAddress } from "../discovery/discovery-artifact.interface.js";
|
|
3
3
|
import type { PrismaCall } from "../translation/operation.translator.js";
|
|
4
4
|
/**
|
|
5
|
-
* The lost-row conflict: the read selected a row and the re-asserted write
|
|
6
|
-
*
|
|
5
|
+
* The lost-row conflict: the read selected a row and the re-asserted write matched
|
|
6
|
+
* none, so **nothing was written** and a retry may succeed.
|
|
7
7
|
*
|
|
8
|
-
* `A2014 STALE_SELECTION`, and this is the only place that names it.
|
|
9
|
-
* carries framework text and no issues: like `A2008` and `A2013` it pairs with
|
|
10
|
-
* **no** V-code, because a conflict is not a Framework Validation Layer failure
|
|
11
|
-
* and ADR 0008 decision 1 would give a conflict V-code no legal rollup (C-839).
|
|
8
|
+
* `A2014 STALE_SELECTION`, and this is the only place that names it.
|
|
12
9
|
*/
|
|
13
10
|
export declare function lostRowConflict(): FrameworkError;
|
|
14
11
|
/**
|
|
15
12
|
* Issues one of the emulation's two statements.
|
|
16
|
-
*
|
|
17
|
-
* `step` is passed because the two are classified **differently** and the
|
|
18
|
-
* discrimination is by call site: the read's failures go through the closed P-code
|
|
19
|
-
* table like any other call, while the write's `P2025` is this module's lost row
|
|
20
|
-
* and must not reach D-811's rule.
|
|
21
13
|
*/
|
|
22
14
|
export type FirstVariantIssuer = (call: PrismaCall, step: "read" | "write") => Promise<unknown>;
|
|
23
15
|
/**
|
|
24
16
|
* How the emulation's two statements are issued, and against which client.
|
|
25
17
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* assertion about the issued `where` would have noticed. Making the unit the thing
|
|
33
|
-
* that hands out the issuer removes the gap rather than documenting it.
|
|
18
|
+
* An earlier draft of this module took the two separately — a `unit` that opened a
|
|
19
|
+
* transaction and an `issue` already bound to a client — and the standalone case
|
|
20
|
+
* then opened a transaction whose two statements ran on the **root** client,
|
|
21
|
+
* outside it: the emulation would have looked atomic and been nothing of the kind,
|
|
22
|
+
* and no assertion about the issued `where` would have noticed. Making the unit
|
|
23
|
+
* the thing that hands out the issuer removes the gap rather than documenting it.
|
|
34
24
|
*
|
|
35
25
|
* This module therefore never sees a Prisma client, a provider name or a
|
|
36
26
|
* transaction; it sees one issuer, already in the right unit.
|
|
@@ -44,9 +34,8 @@ export type FirstVariantEmulation = {
|
|
|
44
34
|
* Opens the one adapter-internal unit and runs `work` inside it.
|
|
45
35
|
*
|
|
46
36
|
* Standalone this opens an interactive transaction and the issuer it yields is
|
|
47
|
-
* scoped to it; inside a plan it runs `work` against the outer transaction
|
|
48
|
-
*
|
|
49
|
-
* independently (§14.5).
|
|
37
|
+
* scoped to it; inside a plan it runs `work` against the outer transaction client,
|
|
38
|
+
* so the emulated steps roll back **with** the plan rather than independently.
|
|
50
39
|
*/
|
|
51
40
|
readonly inUnit: <T>(work: (issue: FirstVariantIssuer) => Promise<T>) => Promise<T>;
|
|
52
41
|
};
|
|
@@ -1,145 +1,14 @@
|
|
|
1
1
|
import { FrameworkError } from "@aventara/core";
|
|
2
2
|
import { readPrismaError } from "../errors/prisma-error.reader.js";
|
|
3
3
|
import { addressIdentifier } from "../translation/identifier.addressing.js";
|
|
4
|
-
/**
|
|
5
|
-
* `update.first` and `delete.first` — the two **framework-manufactured**
|
|
6
|
-
* variants, and the one adapter-internal unit that manufactures them (D-807,
|
|
7
|
-
* D-828, C-856; closes F-704).
|
|
8
|
-
*
|
|
9
|
-
* # What is manufactured, and why that word
|
|
10
|
-
*
|
|
11
|
-
* The shape Aventara advertises — *general filter · ordering decides "first" ·
|
|
12
|
-
* effect limited to one row · the affected row is returned, in one call* — is
|
|
13
|
-
* essentially absent across ORM design, because SQL splits the ingredients along
|
|
14
|
-
* a dialect fault line: `UPDATE … ORDER BY … LIMIT` is a MySQL/MariaDB extension
|
|
15
|
-
* while `RETURNING` is Postgres/SQLite/MSSQL, so whichever dialect grants the
|
|
16
|
-
* limit denies the returning **[SURVEY]**. Prisma 7.10.0's `Operation` union has
|
|
17
|
-
* no `updateFirst` and no `deleteFirst` **[PKG]**. So these are not a Prisma-
|
|
18
|
-
* shaped gap an adapter might omit; they are an operation the framework
|
|
19
|
-
* manufactures, and the honest framing — the one the plan, C-857 and the package
|
|
20
|
-
* README all use — is *framework-manufactured, non-atomic, with a defined
|
|
21
|
-
* conflict outcome*, **not** "dangerous".
|
|
22
|
-
*
|
|
23
|
-
* # Two statements, and the firstness lives in the first one
|
|
24
|
-
*
|
|
25
|
-
* 1. `findFirst({ where, orderBy, select: <identifier> })`. The `orderBy` the
|
|
26
|
-
* caller sent is the **selector for which row**, not a limit: ordering and
|
|
27
|
-
* single-row selection are natural on a read. No match → `null`, which core's
|
|
28
|
-
* outcome mapper turns into `A1001 NO_MATCH` (200, `data: null`) (C-826).
|
|
29
|
-
* 2. `update`/`delete` narrowed to the identifier the read returned **with the
|
|
30
|
-
* caller's original predicate re-asserted** — `{ <identifier>, AND: <original
|
|
31
|
-
* where> }` — carrying the caller's own projection, so the row the contract
|
|
32
|
-
* owes comes back from the write itself (§8.2, §8.4). `WhereUniqueInput`'s
|
|
33
|
-
* extended form admits exactly this, and `extendedWhereUnique` is GA **[RUN8]**.
|
|
34
|
-
*
|
|
35
|
-
* Prisma's `limit` on `updateMany`/`deleteMany` is **not** used. That is not a
|
|
36
|
-
* principle — D-827 rules the opposite, an argument the ORM offers is not hidden —
|
|
37
|
-
* it is that the `limit` cannot express this operation: neither `updateMany` nor
|
|
38
|
-
* `deleteMany` carries an `orderBy` in 7.10.0 **[RUN8]**, so `limit: 1` selects an
|
|
39
|
-
* **arbitrary** row and two identical calls can hit different rows. The identifier
|
|
40
|
-
* from the read is what makes the write deterministic.
|
|
41
|
-
*
|
|
42
|
-
* # What re-assertion closes, stated precisely
|
|
43
|
-
*
|
|
44
|
-
* It closes the **wrong-row race**: without it the write is by identifier alone
|
|
45
|
-
* and can mutate a row that no longer matches what the caller asked for; with it
|
|
46
|
-
* the write can only ever touch a row the read selected, and a row that moved out
|
|
47
|
-
* of the predicate is **detected** rather than mis-mutated.
|
|
48
|
-
*
|
|
49
|
-
* It does **not** close **stale firstness**. Between the read and the write
|
|
50
|
-
* another writer can insert or update a row that sorts *ahead* of the selected one
|
|
51
|
-
* and still matches the original predicate. Both steps are blind to it — the read
|
|
52
|
-
* never saw it, and the re-asserted write succeeds against the row it was given —
|
|
53
|
-
* so `update.first` can mutate a row that is no longer the first ordered match and
|
|
54
|
-
* report ordinary success. Nothing detects it and no re-assertion can, because the
|
|
55
|
-
* missing fact is about a row that was never read. Closing it needs either
|
|
56
|
-
* `Serializable` isolation, which core's `Adapter.transaction(work)` seam cannot
|
|
57
|
-
* express (RC-3), so it is **accepted as a documented property**: C-830 has Phase 8
|
|
58
|
-
* escalate no isolation, and `first-variant.emulator.spec.ts` carries a case that
|
|
59
|
-
* *demonstrates* the residual rather than asserting it away.
|
|
60
|
-
*
|
|
61
|
-
* # The lost row is discriminated BY CALL SITE, never by `meta`
|
|
62
|
-
*
|
|
63
|
-
* A write that matches nothing after a successful read is the **lost-row race**: a
|
|
64
|
-
* distinct semantic outcome the caller may retry. Under the chosen Route A it
|
|
65
|
-
* arrives as a thrown `P2025` — measured at the emulator's own call site, inside
|
|
66
|
-
* an interactive transaction, on both engines (2026-10-02):
|
|
67
|
-
*
|
|
68
|
-
* ```text
|
|
69
|
-
* tx.author.update({ where: { id: <read>, AND: [{ tenantId: "gone" }] }, data })
|
|
70
|
-
* -> P2025, meta = { modelName: "Author", operation: "an update" }
|
|
71
|
-
* ```
|
|
72
|
-
*
|
|
73
|
-
* That meta is **identical** to the one D-811 forgives into a `null` for
|
|
74
|
-
* `update.unique`/`delete.unique`, so the two rules cannot be told apart by the
|
|
75
|
-
* error at all. They are told apart by **where the call was made**: a `P2025`
|
|
76
|
-
* caught by {@link runFirstVariantEmulation}'s write step is the lost row, and
|
|
77
|
-
* nothing else in this adapter forgives a `P2025` into a `null`. This is why the
|
|
78
|
-
* emulator does not consult `classifyPrismaError` for its write step and why
|
|
79
|
-
* D-811's rule is not shared.
|
|
80
|
-
*
|
|
81
|
-
* {@link lostRowConflict} is the one seam that names the outcome. It lives here
|
|
82
|
-
* rather than in `error.normalizer.ts` (ownership corrected 2026-09-23) because
|
|
83
|
-
* the normalizer owns the closed P-code table and this condition has **no provider
|
|
84
|
-
* error behind it at all** under Route B, and under Route A the provider error is
|
|
85
|
-
* only the messenger. It answers **`A2014 STALE_SELECTION`** (409) — its own
|
|
86
|
-
* discriminated code since the architect's assent of 2026-10-02 (RC-8, D-831),
|
|
87
|
-
* replacing the coarse `A2008` it shared with the constraint cases and with
|
|
88
|
-
* `A2013`'s concurrent write. The code is reached from here and from nowhere
|
|
89
|
-
* else: the closed P-code table has no row that can emit it, because the
|
|
90
|
-
* discriminant is the call site and not anything in the error.
|
|
91
|
-
*
|
|
92
|
-
* # One adapter-internal unit — F-704's answer
|
|
93
|
-
*
|
|
94
|
-
* Inside a plan the emulation reuses the **outer transaction client**; standalone
|
|
95
|
-
* it opens **its own interactive transaction** (D-807, C-830). The nested option
|
|
96
|
-
* is real — `$transaction` is not in the interactive-client deny list, which is
|
|
97
|
-
* exactly `["$connect","$disconnect","$on","$use","$extends"]`, and
|
|
98
|
-
* `Transaction.Options.newTxId` exists for nested interactive transactions
|
|
99
|
-
* **[PKG]** — and it is rejected on semantics, not on SQL: a nested savepoint
|
|
100
|
-
* would let an emulated variant roll back *independently* of the plan, which
|
|
101
|
-
* contradicts §14.5 ("if any operation fails, all transaction writes roll back")
|
|
102
|
-
* and core's single-sentinel model. The literal `SAVEPOINT` statement is not in
|
|
103
|
-
* the JS bundle, so the decision deliberately rests on that semantic argument
|
|
104
|
-
* rather than on an unverifiable claim about the SQL issued.
|
|
105
|
-
*
|
|
106
|
-
* No isolation level is escalated: RC-3's readings are unadopted, safety comes
|
|
107
|
-
* from re-assertion, and the spec asserts the transaction **boundary**, not a
|
|
108
|
-
* level.
|
|
109
|
-
*/
|
|
110
|
-
/**
|
|
111
|
-
* The framework-owned text of the lost-row outcome, which is `A2014`'s published
|
|
112
|
-
* meaning verbatim. It holds for **any** adapter that reaches this state,
|
|
113
|
-
* including one that reaches it in a single statement with optimistic
|
|
114
|
-
* concurrency: it names no provider, no emulation, no savepoint, no retry loop
|
|
115
|
-
* and no statement count. Carries no provider string (C-819).
|
|
116
|
-
*/
|
|
117
4
|
const LOST_ROW_MESSAGE = "The state this operation had selected changed before its effect could be applied, so nothing was written.";
|
|
118
|
-
/**
|
|
119
|
-
* The lost-row conflict: the read selected a row and the re-asserted write
|
|
120
|
-
* matched none, so **nothing was written** and a retry may succeed.
|
|
121
|
-
*
|
|
122
|
-
* `A2014 STALE_SELECTION`, and this is the only place that names it. The cause
|
|
123
|
-
* carries framework text and no issues: like `A2008` and `A2013` it pairs with
|
|
124
|
-
* **no** V-code, because a conflict is not a Framework Validation Layer failure
|
|
125
|
-
* and ADR 0008 decision 1 would give a conflict V-code no legal rollup (C-839).
|
|
126
|
-
*/
|
|
127
5
|
export function lostRowConflict() {
|
|
128
6
|
return new FrameworkError("A2014", { message: LOST_ROW_MESSAGE });
|
|
129
7
|
}
|
|
130
|
-
/** Prisma's code for "the records required for this operation were not found". */
|
|
131
8
|
const MISSING_RECORD_CODE = "P2025";
|
|
132
|
-
/** A plan that is not the two-step shape this module manufactures. */
|
|
133
9
|
export class PrismaEmulationError extends Error {
|
|
134
10
|
name = "PrismaEmulationError";
|
|
135
11
|
}
|
|
136
|
-
/**
|
|
137
|
-
* Runs one first-style variant.
|
|
138
|
-
*
|
|
139
|
-
* @returns the affected row, shaped to the caller's `select`/`include` by the
|
|
140
|
-
* write's own projection, or `null` when the read matched nothing.
|
|
141
|
-
* @throws the lost-row conflict when the re-asserted write matches no row.
|
|
142
|
-
*/
|
|
143
12
|
export async function runFirstVariantEmulation(emulation) {
|
|
144
13
|
const [read, write] = emulation.steps;
|
|
145
14
|
if (read === undefined || write === undefined) {
|
|
@@ -148,7 +17,6 @@ export async function runFirstVariantEmulation(emulation) {
|
|
|
148
17
|
return emulation.inUnit(async (issue) => {
|
|
149
18
|
const selected = await issue(read, "read");
|
|
150
19
|
if (selected === null || selected === undefined) {
|
|
151
|
-
// C-826: the adapter returns `null` and core assigns `A1001 NO_MATCH`.
|
|
152
20
|
return null;
|
|
153
21
|
}
|
|
154
22
|
if (!isRecord(selected)) {
|
|
@@ -157,9 +25,6 @@ export async function runFirstVariantEmulation(emulation) {
|
|
|
157
25
|
const addressed = addressIdentifier({
|
|
158
26
|
addresses: emulation.identifiers,
|
|
159
27
|
values: selected,
|
|
160
|
-
// `"subset"`: the read selects the identifier today, and a future route
|
|
161
|
-
// may select the caller's projection beside it. Either way the criterion
|
|
162
|
-
// is complete, which is all the write needs.
|
|
163
28
|
match: "subset",
|
|
164
29
|
emit: (_member, value) => value,
|
|
165
30
|
});
|
|
@@ -169,12 +34,6 @@ export async function runFirstVariantEmulation(emulation) {
|
|
|
169
34
|
try {
|
|
170
35
|
return await issue({
|
|
171
36
|
...write,
|
|
172
|
-
// The invariant, and the whole point of the step: the issued `where`
|
|
173
|
-
// carries BOTH the identifier and the caller's original predicate
|
|
174
|
-
// (C-856, D-829). The predicate arrives already under `AND` from the
|
|
175
|
-
// translator, so merging the identifier in cannot reshape it — and when
|
|
176
|
-
// the caller sent no `where` there is no `AND` to carry, because
|
|
177
|
-
// inventing an empty one would emit a token the request never did.
|
|
178
37
|
args: {
|
|
179
38
|
...write.args,
|
|
180
39
|
where: { ...addressed.where, ...writeWhere(write) },
|
|
@@ -189,7 +48,6 @@ export async function runFirstVariantEmulation(emulation) {
|
|
|
189
48
|
}
|
|
190
49
|
});
|
|
191
50
|
}
|
|
192
|
-
/** The re-asserted predicate the translator placed on the write template. */
|
|
193
51
|
function writeWhere(write) {
|
|
194
52
|
const where = write.args.where;
|
|
195
53
|
if (where === undefined) {
|
|
@@ -2,28 +2,23 @@ import type { AdapterModel, AdapterOperationExecutor } from "@aventara/core";
|
|
|
2
2
|
import type { PrismaResourceTranslation } from "../discovery/discovery-artifact.interface.js";
|
|
3
3
|
import { type PrismaDelegateHost, type PrismaTransactionOptions } from "../prisma-client.interface.js";
|
|
4
4
|
/**
|
|
5
|
-
* The execution half of T-7: a translated {@link PrismaCall} → a provider call,
|
|
6
|
-
* and `Adapter.execute` made real.
|
|
7
|
-
*
|
|
8
5
|
* Translation is pure and lives in `translation/operation.translator.ts`; this
|
|
9
6
|
* module owns the three things that need a client and nothing else:
|
|
10
7
|
*
|
|
11
8
|
* 1. **Reaching the delegate.** See {@link delegateKeyOf} — the one derivation in
|
|
12
9
|
* this package, and the one place it is made.
|
|
13
10
|
* 2. **Classifying a failure.** The closed P-code table in
|
|
14
|
-
* `errors/error.normalizer.ts` answers `absence` → `null`
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
11
|
+
* `errors/error.normalizer.ts` answers `absence` → `null`, `outcome` → a
|
|
12
|
+
* deliberate `FrameworkError`, or `unclassified` → propagate, so core's
|
|
13
|
+
* `AdapterBoundary` reports `A3001` with core's own text and sends the raw
|
|
14
|
+
* error to diagnostics. The adapter never guesses a code it did not measure.
|
|
18
15
|
* 3. **Shaping the answer.** `execution/result.shaper.ts` places every `$count`
|
|
19
16
|
* envelope, converts every returned value to the runtime form core's output
|
|
20
|
-
* validator accepts
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* # One executor type, two scopes (D-807, C-804)
|
|
17
|
+
* validator accepts and reads the number a count variant owes; which of its
|
|
18
|
+
* three entry points runs is the *variant's* `result` row in the dispatch
|
|
19
|
+
* table, not a guess from the shape Prisma returned. The shaper is handed the
|
|
20
|
+
* model and the Resource as well as the plan, because the scalar a returned
|
|
21
|
+
* value belongs to is a fact of the model and not of the value.
|
|
27
22
|
*
|
|
28
23
|
* `scope: "root"` is the adapter's own `execute`. `scope: "transaction"` is what
|
|
29
24
|
* `Adapter.transaction`'s callback hands core: the same code over a transaction
|
|
@@ -32,10 +27,8 @@ import { type PrismaDelegateHost, type PrismaTransactionOptions } from "../prism
|
|
|
32
27
|
* transaction-scoped executor closes over the transaction client only, so the root
|
|
33
28
|
* client is unreachable from inside it.
|
|
34
29
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* All three value crossings read ONE table, `translation/scalar.conversion.ts`:
|
|
38
|
-
* a write's data through `mutation.translator.ts`, a filter operand through
|
|
30
|
+
* All three value crossings read ONE table, `translation/scalar.conversion.ts`: a
|
|
31
|
+
* write's data through `mutation.translator.ts`, a filter operand through
|
|
39
32
|
* `where.translator.ts`, and a returned row through `execution/result.shaper.ts`.
|
|
40
33
|
* None of them is converted here. Converting at this seam would have put the
|
|
41
34
|
* scalar table in a fourth place and hidden the gap from the module that owns it.
|
|
@@ -63,7 +56,7 @@ export type PrismaOperationExecutorDependencies = {
|
|
|
63
56
|
/** The generate-time translation table (Prisma addressing per Resource). */
|
|
64
57
|
readonly translation: Readonly<Record<string, PrismaResourceTranslation>>;
|
|
65
58
|
readonly scope: PrismaExecutionScope;
|
|
66
|
-
/** Adapter-owned, never Aventara public configuration
|
|
59
|
+
/** Adapter-owned, never Aventara public configuration. */
|
|
67
60
|
readonly transactionOptions?: PrismaTransactionOptions;
|
|
68
61
|
};
|
|
69
62
|
/**
|