@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +44 -29
  3. package/dist/discovery/artifact.loader.d.ts +2 -7
  4. package/dist/discovery/artifact.loader.js +8 -1
  5. package/dist/discovery/capability-surface.interface.d.ts +7 -64
  6. package/dist/discovery/capability.deriver.d.ts +2 -47
  7. package/dist/discovery/capability.deriver.js +3 -0
  8. package/dist/discovery/discovery-artifact.interface.d.ts +50 -159
  9. package/dist/discovery/discovery-artifact.interface.js +1 -1
  10. package/dist/discovery/enum.mapper.d.ts +0 -24
  11. package/dist/discovery/field.mapper.d.ts +4 -115
  12. package/dist/discovery/field.mapper.js +7 -7
  13. package/dist/discovery/identifier.mapper.d.ts +4 -84
  14. package/dist/discovery/model.discoverer.d.ts +3 -70
  15. package/dist/discovery/model.discoverer.js +5 -4
  16. package/dist/discovery/operations.deriver.d.ts +2 -37
  17. package/dist/discovery/scalar.mapper.d.ts +1 -32
  18. package/dist/discovery/scalar.mapper.js +2 -2
  19. package/dist/discovery/schema-facts.interface.d.ts +3 -83
  20. package/dist/discovery/schema.reader.d.ts +0 -133
  21. package/dist/discovery/types.reader.d.ts +1 -9
  22. package/dist/discovery/types.reader.js +18 -7
  23. package/dist/discovery/vocabulary.dictionary.d.ts +8 -134
  24. package/dist/discovery/vocabulary.dictionary.js +2 -2
  25. package/dist/errors/error.normalizer.d.ts +5 -168
  26. package/dist/errors/prisma-error.reader.d.ts +1 -93
  27. package/dist/execution/first-variant.emulator.d.ts +5 -28
  28. package/dist/execution/operation.executor.d.ts +3 -63
  29. package/dist/execution/result.shaper.d.ts +0 -56
  30. package/dist/execution/transaction.executor.d.ts +2 -99
  31. package/dist/generate/artifact.emitter.d.ts +1 -35
  32. package/dist/generate/artifact.emitter.js +31 -47
  33. package/dist/generate/declared-support.reader.d.ts +0 -20
  34. package/dist/generate/discovery.cli.d.ts +0 -4
  35. package/dist/generate/existence.gate.d.ts +3 -37
  36. package/dist/generate/index.d.ts +4 -57
  37. package/dist/generate/prisma-cli.resolver.d.ts +0 -22
  38. package/dist/generate/prisma.generate.d.ts +14 -83
  39. package/dist/generate/reader-crosscheck.d.ts +3 -88
  40. package/dist/generate/reader-crosscheck.js +4 -4
  41. package/dist/prisma-client.interface.d.ts +5 -25
  42. package/dist/prisma.adapter.d.ts +21 -22
  43. package/dist/prisma.adapter.js +1 -0
  44. package/dist/provider/provider-profile.d.ts +6 -14
  45. package/dist/translation/identifier.addressing.d.ts +0 -38
  46. package/dist/translation/mutation.translator.d.ts +6 -52
  47. package/dist/translation/mutation.translator.js +1 -1
  48. package/dist/translation/operation.translator.d.ts +3 -103
  49. package/dist/translation/order-pagination.translator.d.ts +2 -48
  50. package/dist/translation/order-pagination.translator.js +1 -1
  51. package/dist/translation/projection.translator.d.ts +1 -46
  52. package/dist/translation/scalar.conversion.d.ts +2 -65
  53. package/dist/translation/scalar.conversion.js +1 -1
  54. package/dist/translation/where.translator.d.ts +0 -52
  55. 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 🟢 file was found under ${root}`);
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 a 🛑 file`);
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 the 🟢 banner`);
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 🟢 types and is not a written object type, so its projection members cannot be read`);
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 🟢 types but declared nowhere in them`);
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 🟢 types`);
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 🟢 types`);
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 { AdapterLifecycleTrait, BuiltInScalar, PaginationCapability, RelationFilterCapability, RelationMutationDirective, ScalarFilterOperator, ScalarFormat, ScalarMutationDirective } from "@aventara/core";
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
- * The closed set of ways a mapper refuses. It is dictionary data because a
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 (invariant 4): `JsonValue` contains `null` because JSON null is a value, so a types-derived nullable is wrong for every Json field.",
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 on the 🟢 surface.',
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 matched
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. */