@aventara/prisma7-adapter 0.1.0-pilot.3 → 1.0.0-rc.0
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 +190 -0
- package/README.md +44 -29
- package/dist/discovery/artifact.loader.d.ts +2 -7
- package/dist/discovery/artifact.loader.js +8 -1
- package/dist/discovery/capability-surface.interface.d.ts +7 -64
- package/dist/discovery/capability.deriver.d.ts +2 -47
- package/dist/discovery/capability.deriver.js +3 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +50 -159
- package/dist/discovery/discovery-artifact.interface.js +1 -1
- package/dist/discovery/enum.mapper.d.ts +0 -24
- package/dist/discovery/field.mapper.d.ts +4 -115
- package/dist/discovery/field.mapper.js +7 -7
- package/dist/discovery/identifier.mapper.d.ts +4 -84
- package/dist/discovery/model.discoverer.d.ts +3 -70
- package/dist/discovery/model.discoverer.js +5 -4
- package/dist/discovery/operations.deriver.d.ts +2 -37
- package/dist/discovery/scalar.mapper.d.ts +1 -32
- package/dist/discovery/scalar.mapper.js +2 -2
- package/dist/discovery/schema-facts.interface.d.ts +3 -83
- package/dist/discovery/schema.reader.d.ts +0 -133
- package/dist/discovery/types.reader.d.ts +1 -9
- package/dist/discovery/types.reader.js +18 -7
- package/dist/discovery/vocabulary.dictionary.d.ts +8 -134
- package/dist/discovery/vocabulary.dictionary.js +2 -2
- package/dist/errors/error.normalizer.d.ts +5 -168
- package/dist/errors/prisma-error.reader.d.ts +1 -93
- package/dist/execution/first-variant.emulator.d.ts +5 -28
- package/dist/execution/operation.executor.d.ts +3 -63
- package/dist/execution/result.shaper.d.ts +0 -56
- package/dist/execution/transaction.executor.d.ts +2 -99
- package/dist/generate/artifact.emitter.d.ts +1 -35
- package/dist/generate/artifact.emitter.js +31 -47
- package/dist/generate/declared-support.reader.d.ts +0 -20
- package/dist/generate/discovery.cli.d.ts +0 -4
- package/dist/generate/existence.gate.d.ts +3 -37
- package/dist/generate/index.d.ts +4 -57
- package/dist/generate/prisma-cli.resolver.d.ts +0 -22
- package/dist/generate/prisma.generate.d.ts +14 -83
- package/dist/generate/reader-crosscheck.d.ts +3 -88
- package/dist/generate/reader-crosscheck.js +4 -4
- package/dist/prisma-client.interface.d.ts +5 -25
- package/dist/prisma.adapter.d.ts +21 -22
- package/dist/prisma.adapter.js +1 -0
- package/dist/provider/provider-profile.d.ts +6 -14
- package/dist/translation/identifier.addressing.d.ts +0 -38
- package/dist/translation/mutation.translator.d.ts +6 -52
- package/dist/translation/mutation.translator.js +1 -1
- package/dist/translation/operation.translator.d.ts +3 -103
- package/dist/translation/order-pagination.translator.d.ts +2 -48
- package/dist/translation/order-pagination.translator.js +1 -1
- package/dist/translation/projection.translator.d.ts +1 -46
- package/dist/translation/scalar.conversion.d.ts +2 -65
- package/dist/translation/scalar.conversion.js +1 -1
- package/dist/translation/where.translator.d.ts +0 -52
- package/package.json +5 -4
|
@@ -2,19 +2,11 @@ import ts from "typescript";
|
|
|
2
2
|
import type { PrismaCapabilitySurface } from "./capability-surface.interface.js";
|
|
3
3
|
export type PrismaTypesReadResult = {
|
|
4
4
|
readonly surface: PrismaCapabilitySurface;
|
|
5
|
-
/**
|
|
6
|
-
* Every file under the client directory the reader actually opened. Reported
|
|
7
|
-
* rather than assumed: the 🛑 files are absent by refusal, not by luck, and a
|
|
8
|
-
* caller — or a spec — can assert the file **set** and not only the output.
|
|
9
|
-
*/
|
|
5
|
+
/** Every file under the client directory the reader actually opened. */
|
|
10
6
|
readonly filesRead: readonly string[];
|
|
11
7
|
};
|
|
12
8
|
export declare class PrismaTypesReaderError extends Error {
|
|
13
9
|
readonly name = "PrismaTypesReaderError";
|
|
14
10
|
}
|
|
15
|
-
/**
|
|
16
|
-
* The reader's one checker question, exported because it is the distinction the
|
|
17
|
-
* whole reader rests on and therefore has to be testable on its own.
|
|
18
|
-
*/
|
|
19
11
|
export declare function readWrittenTypeArguments(checker: ts.TypeChecker, reference: ts.TypeReferenceNode): readonly string[];
|
|
20
12
|
export declare function readPrismaCapabilitySurface(clientDirectory: string): PrismaTypesReadResult;
|
|
@@ -21,7 +21,7 @@ export function readPrismaCapabilitySurface(clientDirectory) {
|
|
|
21
21
|
const root = path.resolve(clientDirectory);
|
|
22
22
|
const publicFiles = listPublicFiles(root);
|
|
23
23
|
if (publicFiles.length === 0) {
|
|
24
|
-
throw new PrismaTypesReaderError(`no generated
|
|
24
|
+
throw new PrismaTypesReaderError(`no generated Prisma client file was found under ${root}`);
|
|
25
25
|
}
|
|
26
26
|
const filesRead = new Set();
|
|
27
27
|
const host = createPublicOnlyHost(root, filesRead);
|
|
@@ -63,11 +63,11 @@ function createPublicOnlyHost(root, filesRead) {
|
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
65
|
if (isPrivatePath(root, resolved)) {
|
|
66
|
-
throw new PrismaTypesReaderError(`the types reader asked for ${path.relative(root, resolved)}, which is
|
|
66
|
+
throw new PrismaTypesReaderError(`the types reader asked for ${path.relative(root, resolved)}, which is an internal file of the generated client that must not be imported`);
|
|
67
67
|
}
|
|
68
68
|
const text = readFileSync(resolved, "utf8");
|
|
69
69
|
if (text.includes(RED_BANNER) || !text.includes(GREEN_BANNER)) {
|
|
70
|
-
throw new PrismaTypesReaderError(`${path.relative(root, resolved)} does not carry
|
|
70
|
+
throw new PrismaTypesReaderError(`${path.relative(root, resolved)} does not carry Prisma's "${GREEN_BANNER}" banner`);
|
|
71
71
|
}
|
|
72
72
|
filesRead.add(resolved);
|
|
73
73
|
return text;
|
|
@@ -128,7 +128,7 @@ function projectionMembers(index, name) {
|
|
|
128
128
|
? written.typeArguments?.[0]
|
|
129
129
|
: written;
|
|
130
130
|
if (literal === undefined || !ts.isTypeLiteralNode(literal)) {
|
|
131
|
-
throw new PrismaTypesReaderError(`${name} is declared by the
|
|
131
|
+
throw new PrismaTypesReaderError(`${name} is declared by the generated client's types and is not a written object type, so its projection members cannot be read`);
|
|
132
132
|
}
|
|
133
133
|
return new Set(memberNames(literal.members.filter(ts.isPropertySignature)));
|
|
134
134
|
}
|
|
@@ -197,10 +197,20 @@ function keysOfReferenced(index, node, accept) {
|
|
|
197
197
|
}
|
|
198
198
|
const members = membersOf(index, referencedName(reference));
|
|
199
199
|
if (members === null) {
|
|
200
|
-
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by the
|
|
200
|
+
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by the generated client's types but declared nowhere in them`);
|
|
201
201
|
}
|
|
202
202
|
return memberNames(members);
|
|
203
203
|
}
|
|
204
|
+
function deleteManyTakesScalarWhere(index, node) {
|
|
205
|
+
const input = findReference(node, (n) => n.endsWith("NestedInput"));
|
|
206
|
+
if (input === null) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const deleteMany = (membersOf(index, referencedName(input)) ?? []).find((member) => memberName(member) === "deleteMany");
|
|
210
|
+
return (deleteMany !== undefined &&
|
|
211
|
+
findReference(deleteMany.type, (n) => n.endsWith("ScalarWhereInput")) !==
|
|
212
|
+
null);
|
|
213
|
+
}
|
|
204
214
|
function readEnums(program, root) {
|
|
205
215
|
const source = program.getSourceFile(path.join(root, "enums.ts"));
|
|
206
216
|
if (source === undefined) {
|
|
@@ -325,7 +335,7 @@ function readMutationOperations(index, model, stage, field, member) {
|
|
|
325
335
|
}
|
|
326
336
|
const members = membersOf(index, referencedName(reference));
|
|
327
337
|
if (members === null) {
|
|
328
|
-
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by ${model}Unchecked${stage}Input.${field} and declared nowhere in the
|
|
338
|
+
throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by ${model}Unchecked${stage}Input.${field} and declared nowhere in the generated client's types`);
|
|
329
339
|
}
|
|
330
340
|
return memberNames(members);
|
|
331
341
|
}
|
|
@@ -371,7 +381,7 @@ function readRelationFields(checker, index, payload, where, create, update, incl
|
|
|
371
381
|
}
|
|
372
382
|
const targetMembers = membersOf(index, referencedName(targetPayload));
|
|
373
383
|
if (targetMembers === null) {
|
|
374
|
-
throw new PrismaTypesReaderError(`${referencedName(targetPayload)} is declared nowhere in the
|
|
384
|
+
throw new PrismaTypesReaderError(`${referencedName(targetPayload)} is declared nowhere in the generated client's types`);
|
|
375
385
|
}
|
|
376
386
|
const edge = findReference(include.get(name)?.type, (n) => n.endsWith("Args"));
|
|
377
387
|
const edgeArguments = edge === null
|
|
@@ -386,6 +396,7 @@ function readRelationFields(checker, index, payload, where, create, update, incl
|
|
|
386
396
|
createRequired: create.get(name) !== undefined &&
|
|
387
397
|
create.get(name)?.questionToken === undefined,
|
|
388
398
|
updateMutations: keysOfReferenced(index, update.get(name)?.type, (n) => n.endsWith("NestedInput")),
|
|
399
|
+
scalarNestedWriteFilter: deleteManyTakesScalarWhere(index, update.get(name)?.type),
|
|
389
400
|
edgeArguments,
|
|
390
401
|
pagination: edgeArguments.filter((argument) => !NON_PAGINATION_EDGE_ARGUMENTS.has(argument)),
|
|
391
402
|
orderable: orderBy.has(name),
|
|
@@ -1,66 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
* # It is data, not conditionals
|
|
4
|
-
*
|
|
5
|
-
* # Every entry names its source class (invariant 1)
|
|
6
|
-
*
|
|
7
|
-
* `VOCABULARY` carries one row per Aventara fact with its source code — `PSL`,
|
|
8
|
-
* `TYPE`, `RUN`, `DRIVER` or `CFG`. `VocabularyEntry.sources` is a **non-empty**
|
|
9
|
-
* tuple, so "an entry with no source" is a compile error rather than a silent
|
|
10
|
-
* default.
|
|
11
|
-
*
|
|
12
|
-
* # `@default` is matched, never parsed (invariant 5)
|
|
13
|
-
*
|
|
14
|
-
* `PRISMA_DEFAULT_TABLE` is looked up with the raw text the PSL reader carried
|
|
15
|
-
* across unchanged. `dbgenerated("gen_random_uuid()")` is a **key**, not an
|
|
16
|
-
* expression: the day this module evaluates one, the reader behind it has become a
|
|
17
|
-
* parser and needs a different decision. An unmatched form falls back to
|
|
18
|
-
* `PRISMA_UNMATCHED_DEFAULT_TRAITS` **plus** a recorded unknown that fails the
|
|
19
|
-
* generate step — never an inferred trait.
|
|
20
|
-
*
|
|
21
|
-
* `@db.*` is matched the same way, by its **exact written spelling**
|
|
22
|
-
* (`PRISMA_NATIVE_FORMATS`). `@db.Decimal(10, 2)` matches no key and therefore
|
|
23
|
-
* proves no format, which is the right answer arrived at without splitting an
|
|
24
|
-
* argument list.
|
|
25
|
-
*
|
|
26
|
-
* # This dictionary proposes no `CONTEXT.md` term
|
|
27
|
-
*
|
|
28
|
-
* It is adapter-internal data, not framework vocabulary. Its job is the opposite:
|
|
29
|
-
* no Prisma term reaches `AdapterModel` (invariant 2) — `AtLeast`,
|
|
30
|
-
* `WhereUniqueInput`, `FieldRef`, `@@map`, `take`/`skip` all die here.
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* `PSL` — `schema.prisma` · `TYPE` — the 🟢 generated types ·
|
|
34
|
-
* `RUN` — observed runtime behaviour · `DRIVER` — the configured Prisma driver
|
|
35
|
-
* adapter · `CFG` — framework configuration, which is policy and not discovery.
|
|
36
|
-
*/
|
|
1
|
+
import type { BuiltInScalar, PaginationCapability, RelationFilterCapability, RelationMutationDirective, ScalarFilterOperator, ScalarFormat, ScalarMutationDirective } from "@aventara/core";
|
|
2
|
+
import type { AdapterLifecycleTrait } from "@aventara/core/internal";
|
|
37
3
|
export type VocabularySource = "PSL" | "TYPE" | "RUN" | "DRIVER" | "CFG";
|
|
38
4
|
export type VocabularyEntry = {
|
|
39
5
|
readonly fact: string;
|
|
40
|
-
/** Non-empty by construction: invariant 1 is enforced by the type, not by a comment. */
|
|
41
6
|
readonly sources: readonly [VocabularySource, ...VocabularySource[]];
|
|
42
7
|
readonly rule: string;
|
|
43
8
|
};
|
|
44
|
-
/**
|
|
45
|
-
* So the source is `PSL` **and** `CFG`: discovery emits what the provider's own
|
|
46
|
-
* column type proves, configuration owns everything else. `scalar.mapper.ts`'s
|
|
47
|
-
* header carries the full argument.
|
|
48
|
-
*/
|
|
49
9
|
export declare const VOCABULARY: readonly VocabularyEntry[];
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
* refusal is the dictionary's own answer: a construct it has no word for.
|
|
53
|
-
*/
|
|
54
|
-
export type PrismaDiscoveryDiagnosticCode =
|
|
55
|
-
/** A Prisma type name in neither PRISMA_SCALAR_TABLE nor `enums.ts`. */
|
|
56
|
-
"unmapped-prisma-type"
|
|
57
|
-
/**
|
|
58
|
-
* PSL declares it, the 🟢 types do not expose it: omitted, with the reason said
|
|
59
|
-
* out loud.
|
|
60
|
-
*/
|
|
61
|
-
| "not-exposed"
|
|
62
|
-
/** PSL and `enums.ts` disagree about an enum's members or their order. */
|
|
63
|
-
| "enum-diverged";
|
|
10
|
+
/** The closed set of ways a mapper refuses. */
|
|
11
|
+
export type PrismaDiscoveryDiagnosticCode = "unmapped-prisma-type" | "not-exposed" | "enum-diverged";
|
|
64
12
|
export type PrismaDiscoveryDiagnostic = {
|
|
65
13
|
readonly code: PrismaDiscoveryDiagnosticCode;
|
|
66
14
|
/** Written for the engineer who has to fix the schema, naming the entity it is about. */
|
|
@@ -71,39 +19,13 @@ export type PrismaScalarMapping = {
|
|
|
71
19
|
readonly format?: ScalarFormat;
|
|
72
20
|
};
|
|
73
21
|
export type PrismaScalarTable = Readonly<Record<string, PrismaScalarMapping>>;
|
|
74
|
-
/**
|
|
75
|
-
* Total and 1:1 over `BUILT_IN_SCALARS` **[CORE]**. There is no default arm: a
|
|
76
|
-
* Prisma type name absent from this table is a diagnostic, not a guess.
|
|
77
|
-
*
|
|
78
|
-
* **No row carries a `format`**, and that is the point: a format is a property of
|
|
79
|
-
* a *column*, not of a Prisma *type name*. `String` alone proves nothing, so the
|
|
80
|
-
* one thing that can prove something — the native column type — is a separate
|
|
81
|
-
* lookup (`PRISMA_NATIVE_FORMATS`).
|
|
82
|
-
*/
|
|
22
|
+
/** Total and 1:1 over `BUILT_IN_SCALARS` **[CORE]**. */
|
|
83
23
|
export declare const PRISMA_SCALAR_TABLE: PrismaScalarTable;
|
|
84
24
|
/**
|
|
85
25
|
* The only `@db.*` spellings that **prove** a format, matched by exact written
|
|
86
26
|
* spelling and never parsed.
|
|
87
|
-
*
|
|
88
|
-
* The bar for a row here is not "the name suggests a format". It is: **the column
|
|
89
|
-
* type itself rejects the values `format` would later reject.** `format` is
|
|
90
|
-
* enforced, not decorative — `normalizeScalarFieldValue` turns `format: "uuid"`
|
|
91
|
-
* into a `V1003` rejection of request input **[CORE,
|
|
92
|
-
* `runtime/scalar-runtime.ts`]** — so a row that is merely probable would have
|
|
93
|
-
* Aventara refuse writes the database accepts.
|
|
94
|
-
*
|
|
95
|
-
* `@db.Uuid` clears that bar: PostgreSQL's `uuid` column rejects `'not-a-uuid'`
|
|
96
|
-
* (`ERROR: invalid input syntax for type uuid`) where a `text` column takes it
|
|
97
|
-
* **[RUN8 — 2026-09-28, `aventara-postgres`, postgres:18]**.
|
|
98
|
-
*
|
|
99
|
-
* That intent belongs in `FrameworkScalarFieldConfig.format` **[CORE]**, which the
|
|
100
|
-
* Contract compiler already applies over the adapter's value.
|
|
101
|
-
*
|
|
102
|
-
* `@db.Decimal(10, 2)` and `@db.VarChar(255)` are absent because they prove no
|
|
103
|
-
* *format*; they carry precision and width, which Aventara has no word for.
|
|
104
27
|
*/
|
|
105
28
|
export declare const PRISMA_NATIVE_FORMATS: Readonly<Record<string, ScalarFormat>>;
|
|
106
|
-
/** Whether a row was exercised against 7.10.0 or is carried forward unproven. */
|
|
107
29
|
export type PrismaEvidence = "RUN8" | "UNVERIFIED";
|
|
108
30
|
export type PrismaDefaultRow = {
|
|
109
31
|
/**
|
|
@@ -119,13 +41,6 @@ export type PrismaDefaultRow = {
|
|
|
119
41
|
readonly traits: readonly AdapterLifecycleTrait[];
|
|
120
42
|
readonly evidence: PrismaEvidence;
|
|
121
43
|
};
|
|
122
|
-
/**
|
|
123
|
-
* This table states the answer rather than half of it, so no consumer has to
|
|
124
|
-
* remember to add the other half.
|
|
125
|
-
*
|
|
126
|
-
* The lookup that walks this table lives with the lifecycle projection, where it
|
|
127
|
-
* can be driven red; this pass owns the data only.
|
|
128
|
-
*/
|
|
129
44
|
export declare const PRISMA_DEFAULT_TABLE: readonly PrismaDefaultRow[];
|
|
130
45
|
/**
|
|
131
46
|
* Any other text — `"BRONZE"`, `"x"`, `sequence()`, `nanoid()`, a spelling added
|
|
@@ -138,42 +53,12 @@ export declare const PRISMA_UNMATCHED_DEFAULT_TRAITS: readonly AdapterLifecycleT
|
|
|
138
53
|
/**
|
|
139
54
|
* The dictionary's other half: the point at which Prisma's spelling stops and
|
|
140
55
|
* Aventara's begins.
|
|
141
|
-
*
|
|
142
|
-
* **One untyped `PRISMA_RENAMES` became six typed tables.** The single
|
|
143
|
-
* `Record<string, string>` could not make a mistake a compile error: a rename to a
|
|
144
|
-
* word core does not have, or a Prisma key nobody decided about, both typechecked.
|
|
145
|
-
* Each table below is keyed on the Prisma spelling and valued in the ONE Aventara
|
|
146
|
-
* vocabulary that spelling can land in, so `"arrayContains"` misspelt fails to
|
|
147
|
-
* compile. The table had no consumer, so nothing was broken by splitting it.
|
|
148
|
-
*
|
|
149
|
-
* `null` is a value, not an omission: it means **recognised, and Aventara has no
|
|
150
|
-
* word for it** — the nested bulk writes, `distinct`, Prisma's `set` assignment.
|
|
151
|
-
*
|
|
152
|
-
* This is the vocabulary half only.
|
|
153
|
-
*/
|
|
154
|
-
/**
|
|
155
|
-
* `<Kind>Filter` / `<Kind>NullableFilter` / `Json[Nullable]Filter` keys.
|
|
156
|
-
*
|
|
157
|
-
* Total and 1:1 over `ScalarFilterOperator` **[CORE]** at 7.10.0, measured over
|
|
158
|
-
* both generated clients: 23 Prisma keys, 23 Aventara operators, no survivor on
|
|
159
|
-
* either side.
|
|
160
56
|
*/
|
|
57
|
+
/** `<Kind>Filter` / `<Kind>NullableFilter` / `Json[Nullable]Filter` keys. */
|
|
161
58
|
export declare const PRISMA_FILTER_OPERATORS: Readonly<Record<string, ScalarFilterOperator>>;
|
|
162
|
-
/**
|
|
163
|
-
* `[Nullable]ScalarRelationFilter` and `ListRelationFilter` keys.
|
|
164
|
-
*
|
|
165
|
-
* `nested` and `null` are **not** keys of any filter input — they are read off the
|
|
166
|
-
* edge's own `where` argument and off the edge's nullability — so they are absent
|
|
167
|
-
* here by construction rather than by omission.
|
|
168
|
-
*/
|
|
59
|
+
/** `[Nullable]ScalarRelationFilter` and `ListRelationFilter` keys. */
|
|
169
60
|
export declare const PRISMA_RELATION_FILTERS: Readonly<Record<string, RelationFilterCapability>>;
|
|
170
|
-
/**
|
|
171
|
-
* `<Kind>FieldUpdateOperationsInput` and `<M><Stage><field>Input` keys.
|
|
172
|
-
*
|
|
173
|
-
* `set` is `null` because plain assignment is not a directive in Aventara: the
|
|
174
|
-
* value is written directly, and `ScalarMutationDirective` has no `$set`
|
|
175
|
-
* **[CORE]**.
|
|
176
|
-
*/
|
|
61
|
+
/** `<Kind>FieldUpdateOperationsInput` and `<M><Stage><field>Input` keys. */
|
|
177
62
|
export declare const PRISMA_SCALAR_DIRECTIVES: Readonly<Record<string, ScalarMutationDirective | null>>;
|
|
178
63
|
/**
|
|
179
64
|
* `<M>Update<edge>Without<M>NestedInput` / `<M>Create<edge>…NestedInput` keys.
|
|
@@ -205,15 +90,4 @@ export type ObservedValueShape = {
|
|
|
205
90
|
readonly engines: readonly string[];
|
|
206
91
|
readonly evidence: PrismaEvidence;
|
|
207
92
|
};
|
|
208
|
-
/**
|
|
209
|
-
* Identical through both driver adapters, so the representation is not
|
|
210
|
-
* driver-dependent *between these two engines* — which is a narrower claim than
|
|
211
|
-
* "not driver-dependent", and the narrower one is what was measured.
|
|
212
|
-
*
|
|
213
|
-
* A row is `RUN8` only where a read-back value was actually asserted. `String`,
|
|
214
|
-
* `Boolean`, `Int` and `Float` were written into a row and never read back under
|
|
215
|
-
* assertion, so they stay `[UNVERIFIED]`: a plausible value shape is not an
|
|
216
|
-
* observed one, and this table exists precisely so that nobody has to guess which
|
|
217
|
-
* is which.
|
|
218
|
-
*/
|
|
219
93
|
export declare const OBSERVED_VALUE_SHAPES: readonly ObservedValueShape[];
|
|
@@ -7,7 +7,7 @@ export const VOCABULARY = [
|
|
|
7
7
|
{
|
|
8
8
|
fact: "nullable",
|
|
9
9
|
sources: ["PSL"],
|
|
10
|
-
rule: "Never TYPE
|
|
10
|
+
rule: "Never TYPE: `JsonValue` contains `null` because JSON null is a value, so a types-derived nullable is wrong for every Json field.",
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
fact: "list",
|
|
@@ -32,7 +32,7 @@ export const VOCABULARY = [
|
|
|
32
32
|
{
|
|
33
33
|
fact: "relation pairing",
|
|
34
34
|
sources: ["PSL"],
|
|
35
|
-
rule: '`@relation("Mentorship")` on both sides is the only pairing evidence; the name appears nowhere
|
|
35
|
+
rule: '`@relation("Mentorship")` on both sides is the only pairing evidence; the name appears nowhere in the generated client.',
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
fact: "lifecycle traits",
|
|
@@ -1,130 +1,5 @@
|
|
|
1
1
|
import { FrameworkError, type OperationErrorCode, type OperationIdentity } from "@aventara/core";
|
|
2
2
|
import { type PrismaConstraintSubject } from "./prisma-error.reader.js";
|
|
3
|
-
/**
|
|
4
|
-
* The table's keys are P-codes, which are **7.x's** error surface. Prisma 8
|
|
5
|
-
* replaced them with dotted `NAMESPACE.SUBCODE` codes, so a Prisma 8 adapter
|
|
6
|
-
* re-maps this module rather than patching it. `error.normalizer.spec.ts` reads
|
|
7
|
-
* `@prisma/client`'s own `package.json` and fails if the installed major is not
|
|
8
|
-
* `7`, so the bound is enforced rather than described.
|
|
9
|
-
*
|
|
10
|
-
* ## Every code in the table was induced, not inherited
|
|
11
|
-
*
|
|
12
|
-
* Nothing here rests on documentation. Each rule was produced by throwing a real
|
|
13
|
-
* statement at a real engine on this repository's own schema, on
|
|
14
|
-
* `@prisma/adapter-better-sqlite3` and `@prisma/adapter-pg`, both 7.10.0. The
|
|
15
|
-
* measured payloads, inlined because the source measurement lives outside the
|
|
16
|
-
* repository:
|
|
17
|
-
*
|
|
18
|
-
* ```text
|
|
19
|
-
* P2002 author.create duplicate @unique email
|
|
20
|
-
* sqlite meta = { driverAdapterError: { name: "DriverAdapterError", cause: {
|
|
21
|
-
* originalCode: "SQLITE_CONSTRAINT_UNIQUE",
|
|
22
|
-
* originalMessage: "UNIQUE constraint failed: Author.email",
|
|
23
|
-
* kind: "UniqueConstraintViolation",
|
|
24
|
-
* constraint: { fields: ["email"] }, table: "Author" } },
|
|
25
|
-
* modelName: "Author" }
|
|
26
|
-
* pg meta = { driverAdapterError: { name: "DriverAdapterError", cause: {
|
|
27
|
-
* originalCode: "23505",
|
|
28
|
-
* originalMessage: "duplicate key value violates unique constraint \"Author_email_key\"",
|
|
29
|
-
* kind: "UniqueConstraintViolation",
|
|
30
|
-
* constraint: { index: "Author_email_key" }, table: "Author" } },
|
|
31
|
-
* modelName: "Author" }
|
|
32
|
-
*
|
|
33
|
-
* P2002 author.create violating @@unique([tenantId, handle])
|
|
34
|
-
* sqlite constraint: { fields: ["tenantId", "handle"] }
|
|
35
|
-
* pg constraint: { index: "Author_tenantId_handle_key" }
|
|
36
|
-
*
|
|
37
|
-
* P2003 post.create with an authorId that does not exist
|
|
38
|
-
* sqlite meta = { modelName: "Post", driverAdapterError: { cause: {
|
|
39
|
-
* originalCode: "SQLITE_CONSTRAINT_FOREIGNKEY",
|
|
40
|
-
* originalMessage: "FOREIGN KEY constraint failed",
|
|
41
|
-
* kind: "ForeignKeyConstraintViolation",
|
|
42
|
-
* constraint: { foreignKey: {} } } } }
|
|
43
|
-
* pg constraint: { index: "Post_authorId_fkey" }, originalCode "23503"
|
|
44
|
-
*
|
|
45
|
-
* P2025 author.update / author.delete on a missing unique selector
|
|
46
|
-
* both meta = { modelName: "Author", operation: "an update" }
|
|
47
|
-
* both meta = { modelName: "Author", operation: "a delete" }
|
|
48
|
-
*
|
|
49
|
-
* P2025 nested connect whose target does not exist (create, update, upsert)
|
|
50
|
-
* both meta = { modelName: "Post", model: "Author",
|
|
51
|
-
* relation: "AuthorToPost", relationType: "one-to-many",
|
|
52
|
-
* operation: "a nested connect",
|
|
53
|
-
* neededFor: "inline the relation on 'Post' record(s)" }
|
|
54
|
-
*
|
|
55
|
-
* P2014 author.update with posts.set [] (or posts.disconnect) while a post
|
|
56
|
-
* still requires that author
|
|
57
|
-
* both meta = { modelName: "Author", relation: "AuthorToPost",
|
|
58
|
-
* modelA: "Post", modelB: "Author" }
|
|
59
|
-
*
|
|
60
|
-
* P2018 author.update with posts.connect to a missing post
|
|
61
|
-
* sqlite meta = { modelName: "Author", expectedRows: 1,
|
|
62
|
-
* relation: "AuthorToPost", relationType: "one-to-many" }
|
|
63
|
-
*
|
|
64
|
-
* P2034 two SERIALIZABLE transactions, both reading then both writing
|
|
65
|
-
* pg meta = { modelName: "Author", driverAdapterError: { cause: {
|
|
66
|
-
* originalCode: "40001",
|
|
67
|
-
* originalMessage: "could not serialize access due to concurrent update",
|
|
68
|
-
* kind: "TransactionWriteConflict" } } }
|
|
69
|
-
* ```
|
|
70
|
-
*
|
|
71
|
-
* ## Two inherited claims that the measurement refutes
|
|
72
|
-
*
|
|
73
|
-
* 1. It is **absent from every payload above**; the identifiers are internal rule
|
|
74
|
-
* names, not a `meta` key. What 7.10.0 actually emits is two different *key
|
|
75
|
-
* sets*, and the discriminant is the presence of `meta.relation` (with
|
|
76
|
-
* `meta.model`): present → the failure is about a relation reference; absent →
|
|
77
|
-
* the record the call selected was not there.
|
|
78
|
-
*
|
|
79
|
-
* Also unobserved: the `{}` meta that `expectNonEmpty` was expected to carry.
|
|
80
|
-
* `findUniqueOrThrow` and `findFirstOrThrow` both report `{ modelName, operation:
|
|
81
|
-
* "a query" }`, so no empty-meta P2025 was produced by any statement tried here.
|
|
82
|
-
* Either arm classifies identically under the rule above (no `relation` → root
|
|
83
|
-
* record), so the rule is safe either way, but the shape itself stays unverified.
|
|
84
|
-
*
|
|
85
|
-
* Three distinct situations used to answer the one `A2008 CONFLICT`. Coarse but
|
|
86
|
-
* correct, so nothing provisional ever reached the wire and the discriminant was
|
|
87
|
-
* purely additive. The observed surface each decision rests on is this:
|
|
88
|
-
*
|
|
89
|
-
* | Situation | Code | `meta` keys observed | Structurally separable? |
|
|
90
|
-
* |---|---|---|---| | Uniqueness violation | `P2002` | `modelName`,
|
|
91
|
-
* `driverAdapterError` | **Yes** — distinct P-code; `cause.kind` is
|
|
92
|
-
* `"UniqueConstraintViolation"`; `cause.constraint` is `{fields}` (SQLite) or
|
|
93
|
-
* `{index}` (PostgreSQL) | | Write conflict / deadlock | `P2034` | `modelName`,
|
|
94
|
-
* `driverAdapterError` | **Yes** from a constraint violation — distinct P-code;
|
|
95
|
-
* `cause.kind` is `"TransactionWriteConflict"`; **no** `constraint` at all. **No**
|
|
96
|
-
* from each other: `cause.originalCode` is `40001` for a write conflict and
|
|
97
|
-
* `40P01` for a deadlock, and that field is provider-owned and never crosses the
|
|
98
|
-
* boundary | | Lost-row race | *none* | *none* | **Yes, trivially** — there is no
|
|
99
|
-
* provider error behind it: it is a zero-row count in a sequence the framework
|
|
100
|
-
* manufactured, and it is raised in `execution/first-variant.emulator.ts`, never
|
|
101
|
-
* here |
|
|
102
|
-
*
|
|
103
|
-
* The two provider-backed ones differ by P-code; `driverAdapterError.cause.kind`
|
|
104
|
-
* is a second, independent witness.
|
|
105
|
-
*
|
|
106
|
-
* **A write conflict and a deadlock share `A2013` deliberately.** Both arrive as
|
|
107
|
-
* `P2034` with identical `meta` keys, differing only in a provider-owned
|
|
108
|
-
* `originalCode` (`40001` versus `40P01`) that never crosses the boundary. They
|
|
109
|
-
* are not separable at this seam, so one outcome is the only honest answer;
|
|
110
|
-
* splitting them later would need a discriminant that does not exist.
|
|
111
|
-
* `describePrismaConflict` is the recorded surface, and `PrismaConstraintSubject`
|
|
112
|
-
* records that the two engines do not agree on *which* identifier they can name —
|
|
113
|
-
* a uniqueness discriminant that promised the caller the constraint's name would
|
|
114
|
-
* be unsatisfiable on SQLite.
|
|
115
|
-
*
|
|
116
|
-
* **No Prisma path observed here threw a non-`Error`.** Every induced failure
|
|
117
|
-
* arrived as a `PrismaClientKnownRequestError`, which extends `Error` (`error
|
|
118
|
-
* instanceof Error` was asserted true for all of them), and the one non-Prisma
|
|
119
|
-
* failure the engines produced — `better-sqlite3` refusing a missing directory —
|
|
120
|
-
* was a `TypeError`. The adapter half is discharged here: `prismaOutcomeFailure`
|
|
121
|
-
* returns a `FrameworkError`, so every failure this module originates is an
|
|
122
|
-
* `Error` subclass, enforced by a spec.
|
|
123
|
-
*
|
|
124
|
-
* What keeps it from leaking is this module's own rule: the cause is framework
|
|
125
|
-
* text, and the reader never reads `message` at all, so there is no provider
|
|
126
|
-
* string in scope to forward.
|
|
127
|
-
*/
|
|
128
3
|
/** The three answers a Prisma failure can get. */
|
|
129
4
|
export type PrismaErrorClassification = {
|
|
130
5
|
readonly kind: "absence";
|
|
@@ -134,62 +9,24 @@ export type PrismaErrorClassification = {
|
|
|
134
9
|
} | {
|
|
135
10
|
readonly kind: "unclassified";
|
|
136
11
|
};
|
|
137
|
-
/**
|
|
138
|
-
* When a code is a `null` result rather than a failure.
|
|
139
|
-
*
|
|
140
|
-
* `subject` is the measured `meta` shape that may be forgiven. The same P2025
|
|
141
|
-
* reaches `update.unique` for a second reason — a nested `connect` whose target is
|
|
142
|
-
* missing — and that one is a caller error about the *arguments*, not an absent
|
|
143
|
-
* record. `"root-record"` means `meta` carries no relation reference; the
|
|
144
|
-
* single-member union says that is the one shape measured, and leaves room for a
|
|
145
|
-
* second if one is ever observed.
|
|
146
|
-
*/
|
|
147
12
|
type PrismaAbsenceRule = {
|
|
148
13
|
readonly variants: readonly `${string}.${string}`[];
|
|
149
14
|
readonly subject: "root-record";
|
|
150
15
|
};
|
|
151
16
|
type PrismaErrorRule = {
|
|
152
|
-
/**
|
|
153
|
-
* The outcome this code answers. Typed as core's own `OperationErrorCode`, so the
|
|
154
|
-
* adapter cannot widen the code space — a code the table does not contain has no
|
|
155
|
-
* rule at all.
|
|
156
|
-
*/
|
|
17
|
+
/** The outcome this code answers. */
|
|
157
18
|
readonly outcome: OperationErrorCode;
|
|
158
19
|
readonly absence?: PrismaAbsenceRule;
|
|
159
20
|
};
|
|
160
|
-
/** One closed table, scoped to `@prisma/client` 7.x. */
|
|
161
21
|
export type PrismaErrorTable = Readonly<Record<string, PrismaErrorRule>>;
|
|
162
|
-
/**
|
|
163
|
-
* The installed-major this table is written against. The spec asserts the
|
|
164
|
-
* installed `@prisma/client` agrees.
|
|
165
|
-
*/
|
|
22
|
+
/** The installed-major this table is written against. */
|
|
166
23
|
export declare const PRISMA_ERROR_TABLE_MAJOR = "7";
|
|
167
|
-
/**
|
|
168
|
-
* Every code here was induced above. Adding a row later is additive and costs
|
|
169
|
-
* nothing; shipping a wrong one is not recoverable.
|
|
170
|
-
*/
|
|
24
|
+
/** Every code here was induced above. */
|
|
171
25
|
export declare const PRISMA_ERROR_TABLE: PrismaErrorTable;
|
|
172
|
-
/**
|
|
173
|
-
* Classifies one failed Prisma call.
|
|
174
|
-
*
|
|
175
|
-
* `error` is `unknown` and stays `unknown`: the reader is the only thing that
|
|
176
|
-
* touches it, and the rules see nothing but measured facts.
|
|
177
|
-
*/
|
|
26
|
+
/** Classifies one failed Prisma call. */
|
|
178
27
|
export declare function classifyPrismaError(error: unknown, operation: Pick<OperationIdentity, "family" | "variant">): PrismaErrorClassification;
|
|
179
|
-
/**
|
|
180
|
-
* The deliberate failure an `outcome` classification stands for.
|
|
181
|
-
*
|
|
182
|
-
* The cause carries framework text and nothing else: no issues, and that is now
|
|
183
|
-
* the **rule** rather than an absence. And no provider text, because a deliberate
|
|
184
|
-
* outcome's top-level `cause.message` is forwarded verbatim. Nothing read off the
|
|
185
|
-
* Prisma error reaches this value.
|
|
186
|
-
*/
|
|
28
|
+
/** The deliberate failure an `outcome` classification stands for. */
|
|
187
29
|
export declare function prismaOutcomeFailure(code: OperationErrorCode): FrameworkError;
|
|
188
|
-
/**
|
|
189
|
-
* **Server-side only.** Every field is provider-owned, so this value never reaches
|
|
190
|
-
* a `cause` — which is why it is a separate function and not a field on
|
|
191
|
-
* `PrismaErrorClassification`: nothing can carry it onto the wire by accident.
|
|
192
|
-
*/
|
|
193
30
|
export type PrismaConflictSubject = {
|
|
194
31
|
readonly code: string;
|
|
195
32
|
readonly model: string | undefined;
|
|
@@ -1,72 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The total read of one hostile value.
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
15
|
-
* 1. **No accessor is ever invoked.** Every read goes through
|
|
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
|
-
*
|
|
31
|
-
* ## What this module does NOT read
|
|
32
|
-
*
|
|
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
|
-
*
|
|
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 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.
|
|
50
|
-
*
|
|
51
|
-
* Everything below is scoped to `@prisma/client` **7.x**. Prisma 8 replaced
|
|
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.
|
|
54
|
-
*/
|
|
55
|
-
/**
|
|
56
|
-
* The violated constraint as 7.10.0's driver adapters actually report it, at
|
|
57
|
-
* `meta.driverAdapterError.cause.constraint`.
|
|
58
|
-
*
|
|
59
|
-
* The arms are **provider-shaped, measured on both engines**, not guessed:
|
|
60
|
-
*
|
|
61
|
-
* | Engine | Unique (P2002) | Foreign key (P2003) |
|
|
62
|
-
* |-----------------------------------|-------------------------------|-------------------------|
|
|
63
|
-
* | `@prisma/adapter-better-sqlite3` | `{ fields: ["email"] }` | `{ foreignKey: {}
|
|
64
|
-
* }` | | `@prisma/adapter-pg` | `{ index: "Author_email_key" }` | `{ index:
|
|
65
|
-
* "Post_authorId_fkey" }` |
|
|
66
|
-
*
|
|
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.
|
|
69
|
-
*/
|
|
70
1
|
export type PrismaConstraintSubject = {
|
|
71
2
|
readonly kind: "fields";
|
|
72
3
|
readonly fields: readonly string[];
|
|
@@ -78,23 +9,9 @@ export type PrismaConstraintSubject = {
|
|
|
78
9
|
} | {
|
|
79
10
|
readonly kind: "unreadable";
|
|
80
11
|
};
|
|
81
|
-
/**
|
|
82
|
-
* Everything the normalizer is allowed to know about one failed call.
|
|
83
|
-
*
|
|
84
|
-
* Narrow and flat on purpose: a classification rule reads these fields and
|
|
85
|
-
* nothing else, so no rule can reach back into the raw value and no rule
|
|
86
|
-
* can depend on a shape this module has not measured.
|
|
87
|
-
*/
|
|
12
|
+
/** Everything the normalizer is allowed to know about one failed call. */
|
|
88
13
|
export type PrismaErrorFacts = {
|
|
89
|
-
/**
|
|
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.
|
|
93
|
-
*/
|
|
94
14
|
readonly code: string | undefined;
|
|
95
|
-
/**
|
|
96
|
-
* The own, enumerable key names of `meta`, in the order 7.10.0 wrote them.
|
|
97
|
-
*/
|
|
98
15
|
readonly metaKeys: readonly string[];
|
|
99
16
|
/** `meta.modelName` — the Prisma model the failure occurred on. */
|
|
100
17
|
readonly modelName: string | undefined;
|
|
@@ -107,15 +24,6 @@ export type PrismaErrorFacts = {
|
|
|
107
24
|
readonly relatedModel: string | undefined;
|
|
108
25
|
/** `meta.driverAdapterError.cause.constraint`, read structurally. */
|
|
109
26
|
readonly constraint: PrismaConstraintSubject | undefined;
|
|
110
|
-
/**
|
|
111
|
-
* `meta.target` — **read so that it is measured, depended on by nothing**.
|
|
112
|
-
*
|
|
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.
|
|
118
|
-
*/
|
|
119
27
|
readonly legacyTarget: readonly string[] | undefined;
|
|
120
28
|
};
|
|
121
29
|
/**
|
|
@@ -2,41 +2,18 @@ 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
|
-
* none, so **nothing was written** and a retry may succeed.
|
|
7
|
-
*
|
|
8
|
-
* `A2014 STALE_SELECTION`, and this is the only place that names it.
|
|
5
|
+
* The lost-row conflict: the read selected a row and the re-asserted write
|
|
6
|
+
* matched none, so **nothing was written** and a retry may succeed.
|
|
9
7
|
*/
|
|
10
8
|
export declare function lostRowConflict(): FrameworkError;
|
|
11
|
-
/**
|
|
12
|
-
* Issues one of the emulation's two statements.
|
|
13
|
-
*/
|
|
9
|
+
/** Issues one of the emulation's two statements. */
|
|
14
10
|
export type FirstVariantIssuer = (call: PrismaCall, step: "read" | "write") => Promise<unknown>;
|
|
15
|
-
/**
|
|
16
|
-
* How the emulation's two statements are issued, and against which client.
|
|
17
|
-
*
|
|
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.
|
|
24
|
-
*
|
|
25
|
-
* This module therefore never sees a Prisma client, a provider name or a
|
|
26
|
-
* transaction; it sees one issuer, already in the right unit.
|
|
27
|
-
*/
|
|
11
|
+
/** How the emulation's two statements are issued, and against which client. */
|
|
28
12
|
export type FirstVariantEmulation = {
|
|
29
|
-
/** The translated plan's two steps: the read, then the write template. */
|
|
30
13
|
readonly steps: readonly PrismaCall[];
|
|
31
14
|
/** The Resource's recorded Prisma addressing, for the write's identifier. */
|
|
32
15
|
readonly identifiers: readonly PrismaIdentifierAddress[];
|
|
33
|
-
/**
|
|
34
|
-
* Opens the one adapter-internal unit and runs `work` inside it.
|
|
35
|
-
*
|
|
36
|
-
* Standalone this opens an interactive transaction and the issuer it yields is
|
|
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.
|
|
39
|
-
*/
|
|
16
|
+
/** Opens the one adapter-internal unit and runs `work` inside it. */
|
|
40
17
|
readonly inUnit: <T>(work: (issue: FirstVariantIssuer) => Promise<T>) => Promise<T>;
|
|
41
18
|
};
|
|
42
19
|
/** A plan that is not the two-step shape this module manufactures. */
|