@aventara/prisma7-adapter 0.1.0-pilot.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 (86) hide show
  1. package/LICENSE +91 -0
  2. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  3. package/README.md +564 -0
  4. package/dist/discovery/artifact.loader.d.ts +26 -0
  5. package/dist/discovery/artifact.loader.js +124 -0
  6. package/dist/discovery/capability-surface.interface.d.ts +191 -0
  7. package/dist/discovery/capability-surface.interface.js +13 -0
  8. package/dist/discovery/capability.deriver.d.ts +92 -0
  9. package/dist/discovery/capability.deriver.js +350 -0
  10. package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
  11. package/dist/discovery/discovery-artifact.interface.js +1 -0
  12. package/dist/discovery/enum.mapper.d.ts +59 -0
  13. package/dist/discovery/enum.mapper.js +32 -0
  14. package/dist/discovery/field.mapper.d.ts +244 -0
  15. package/dist/discovery/field.mapper.js +387 -0
  16. package/dist/discovery/identifier.mapper.d.ts +188 -0
  17. package/dist/discovery/identifier.mapper.js +132 -0
  18. package/dist/discovery/model.discoverer.d.ts +108 -0
  19. package/dist/discovery/model.discoverer.js +341 -0
  20. package/dist/discovery/operations.deriver.d.ts +75 -0
  21. package/dist/discovery/operations.deriver.js +92 -0
  22. package/dist/discovery/scalar.mapper.d.ts +81 -0
  23. package/dist/discovery/scalar.mapper.js +51 -0
  24. package/dist/discovery/schema-facts.interface.d.ts +174 -0
  25. package/dist/discovery/schema-facts.interface.js +11 -0
  26. package/dist/discovery/schema.reader.d.ts +162 -0
  27. package/dist/discovery/schema.reader.js +692 -0
  28. package/dist/discovery/types.reader.d.ts +24 -0
  29. package/dist/discovery/types.reader.js +617 -0
  30. package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
  31. package/dist/discovery/vocabulary.dictionary.js +429 -0
  32. package/dist/errors/error.normalizer.d.ts +250 -0
  33. package/dist/errors/error.normalizer.js +135 -0
  34. package/dist/errors/prisma-error.reader.d.ts +144 -0
  35. package/dist/errors/prisma-error.reader.js +167 -0
  36. package/dist/execution/first-variant.emulator.d.ts +64 -0
  37. package/dist/execution/first-variant.emulator.js +205 -0
  38. package/dist/execution/operation.executor.d.ts +87 -0
  39. package/dist/execution/operation.executor.js +210 -0
  40. package/dist/execution/result.shaper.d.ts +103 -0
  41. package/dist/execution/result.shaper.js +141 -0
  42. package/dist/execution/transaction.executor.d.ts +147 -0
  43. package/dist/execution/transaction.executor.js +13 -0
  44. package/dist/generate/artifact.emitter.d.ts +50 -0
  45. package/dist/generate/artifact.emitter.js +105 -0
  46. package/dist/generate/declared-support.reader.d.ts +33 -0
  47. package/dist/generate/declared-support.reader.js +74 -0
  48. package/dist/generate/discovery.bin.d.ts +2 -0
  49. package/dist/generate/discovery.bin.js +15 -0
  50. package/dist/generate/discovery.cli.d.ts +24 -0
  51. package/dist/generate/discovery.cli.js +163 -0
  52. package/dist/generate/existence.gate.d.ts +73 -0
  53. package/dist/generate/existence.gate.js +91 -0
  54. package/dist/generate/index.d.ts +81 -0
  55. package/dist/generate/index.js +65 -0
  56. package/dist/generate/node-version.guard.d.ts +8 -0
  57. package/dist/generate/node-version.guard.js +59 -0
  58. package/dist/generate/prisma-cli.resolver.d.ts +48 -0
  59. package/dist/generate/prisma-cli.resolver.js +95 -0
  60. package/dist/generate/prisma.generate.d.ts +164 -0
  61. package/dist/generate/prisma.generate.js +278 -0
  62. package/dist/generate/reader-crosscheck.d.ts +156 -0
  63. package/dist/generate/reader-crosscheck.js +202 -0
  64. package/dist/index.d.ts +5 -0
  65. package/dist/index.js +1 -0
  66. package/dist/prisma-client.interface.d.ts +48 -0
  67. package/dist/prisma-client.interface.js +9 -0
  68. package/dist/prisma.adapter.d.ts +55 -0
  69. package/dist/prisma.adapter.js +105 -0
  70. package/dist/provider/provider-profile.d.ts +53 -0
  71. package/dist/provider/provider-profile.js +91 -0
  72. package/dist/translation/identifier.addressing.d.ts +80 -0
  73. package/dist/translation/identifier.addressing.js +30 -0
  74. package/dist/translation/mutation.translator.d.ts +115 -0
  75. package/dist/translation/mutation.translator.js +580 -0
  76. package/dist/translation/operation.translator.d.ts +273 -0
  77. package/dist/translation/operation.translator.js +377 -0
  78. package/dist/translation/order-pagination.translator.d.ts +86 -0
  79. package/dist/translation/order-pagination.translator.js +159 -0
  80. package/dist/translation/projection.translator.d.ts +115 -0
  81. package/dist/translation/projection.translator.js +151 -0
  82. package/dist/translation/scalar.conversion.d.ts +101 -0
  83. package/dist/translation/scalar.conversion.js +221 -0
  84. package/dist/translation/where.translator.d.ts +79 -0
  85. package/dist/translation/where.translator.js +254 -0
  86. package/package.json +102 -0
@@ -0,0 +1,250 @@
1
+ import { FrameworkError, type OperationErrorCode, type OperationIdentity } from "@aventara/core";
2
+ import { type PrismaConstraintSubject } from "./prisma-error.reader.js";
3
+ /**
4
+ * T-10 — error normalization: one closed table from `@prisma/client` 7.x
5
+ * P-codes to Aventara outcomes, and nothing else.
6
+ *
7
+ * ## Version bound (C-846)
8
+ *
9
+ * The table's keys are P-codes, which are **7.x's** error surface. Prisma 8
10
+ * replaced them with dotted `NAMESPACE.SUBCODE` codes, so a Prisma 8
11
+ * adapter re-maps this module rather than patching it.
12
+ * `error.normalizer.spec.ts` reads `@prisma/client`'s own `package.json`
13
+ * and fails if the installed major is not `7`, so the bound is enforced
14
+ * rather than described.
15
+ *
16
+ * ## Every code in the table was induced, not inherited
17
+ *
18
+ * Nothing here rests on documentation. Each rule was produced by throwing a
19
+ * real statement at a real engine on this repository's own schema, on
20
+ * `@prisma/adapter-better-sqlite3` and `@prisma/adapter-pg`, both 7.10.0.
21
+ * The measured payloads, inlined because the source measurement lives
22
+ * outside the repository:
23
+ *
24
+ * ```text
25
+ * P2002 author.create duplicate @unique email
26
+ * sqlite meta = { driverAdapterError: { name: "DriverAdapterError", cause: {
27
+ * originalCode: "SQLITE_CONSTRAINT_UNIQUE",
28
+ * originalMessage: "UNIQUE constraint failed: Author.email",
29
+ * kind: "UniqueConstraintViolation",
30
+ * constraint: { fields: ["email"] }, table: "Author" } },
31
+ * modelName: "Author" }
32
+ * pg meta = { driverAdapterError: { name: "DriverAdapterError", cause: {
33
+ * originalCode: "23505",
34
+ * originalMessage: "duplicate key value violates unique constraint \"Author_email_key\"",
35
+ * kind: "UniqueConstraintViolation",
36
+ * constraint: { index: "Author_email_key" }, table: "Author" } },
37
+ * modelName: "Author" }
38
+ *
39
+ * P2002 author.create violating @@unique([tenantId, handle])
40
+ * sqlite constraint: { fields: ["tenantId", "handle"] }
41
+ * pg constraint: { index: "Author_tenantId_handle_key" }
42
+ *
43
+ * P2003 post.create with an authorId that does not exist
44
+ * sqlite meta = { modelName: "Post", driverAdapterError: { cause: {
45
+ * originalCode: "SQLITE_CONSTRAINT_FOREIGNKEY",
46
+ * originalMessage: "FOREIGN KEY constraint failed",
47
+ * kind: "ForeignKeyConstraintViolation",
48
+ * constraint: { foreignKey: {} } } } }
49
+ * pg constraint: { index: "Post_authorId_fkey" }, originalCode "23503"
50
+ *
51
+ * P2025 author.update / author.delete on a missing unique selector
52
+ * both meta = { modelName: "Author", operation: "an update" }
53
+ * both meta = { modelName: "Author", operation: "a delete" }
54
+ *
55
+ * P2025 nested connect whose target does not exist (create, update, upsert)
56
+ * both meta = { modelName: "Post", model: "Author",
57
+ * relation: "AuthorToPost", relationType: "one-to-many",
58
+ * operation: "a nested connect",
59
+ * neededFor: "inline the relation on 'Post' record(s)" }
60
+ *
61
+ * P2018 author.update with posts.connect to a missing post
62
+ * sqlite meta = { modelName: "Author", expectedRows: 1,
63
+ * relation: "AuthorToPost", relationType: "one-to-many" }
64
+ *
65
+ * P2034 two SERIALIZABLE transactions, both reading then both writing
66
+ * pg meta = { modelName: "Author", driverAdapterError: { cause: {
67
+ * originalCode: "40001",
68
+ * originalMessage: "could not serialize access due to concurrent update",
69
+ * kind: "TransactionWriteConflict" } } }
70
+ * ```
71
+ *
72
+ * ## Two inherited claims that the measurement refutes
73
+ *
74
+ * 1. **`meta.errorIdentifier` does not exist.** D-811 and the error-code map
75
+ * both state that P2025's cases are told apart by
76
+ * `meta.errorIdentifier` (`MISSING_RECORD` / `MISSING_RELATED_RECORD` /
77
+ * `INCOMPLETE_CONNECT_INPUT`), and both marked it unverified at the meta
78
+ * level. It is **absent from every payload above**; the identifiers are
79
+ * internal rule names, not a `meta` key. What 7.10.0 actually emits is
80
+ * two different *key sets*, and the discriminant is the presence of
81
+ * `meta.relation` (with `meta.model`): present → the failure is about a
82
+ * relation reference; absent → the record the call selected was not
83
+ * there. That is structural, which is what ADR 0006 requires — the
84
+ * alternative, `meta.operation`, is English prose.
85
+ * 2. **`meta.target` is never emitted.** RC-9's open question is answered:
86
+ * the v7 docs are stale (see `PrismaErrorFacts.legacyTarget`).
87
+ *
88
+ * Also unobserved: the `{}` meta that `expectNonEmpty` was expected to
89
+ * carry. `findUniqueOrThrow` and `findFirstOrThrow` both report
90
+ * `{ modelName, operation: "a query" }`, so no empty-meta P2025 was
91
+ * produced by any statement tried here. Either arm classifies identically
92
+ * under the rule above (no `relation` → root record), so the rule is safe
93
+ * either way, but the shape itself stays unverified.
94
+ *
95
+ * ## RC-8 is decided, and this measurement is what decided it
96
+ *
97
+ * Three distinct situations used to answer the one `A2008 CONFLICT`. Coarse
98
+ * but correct, so nothing provisional ever reached the wire and the
99
+ * discriminant was purely additive (RC-8, D-831). The architect assented
100
+ * (2026-10-02) to the discrimination this measurement showed to be available:
101
+ * `A2008` now means the **constraint** case alone, a write conflict or
102
+ * deadlock answers **`A2013 CONCURRENT_MODIFICATION`**, and the lost-row race
103
+ * answers **`A2014 STALE_SELECTION`** from the emulator's own seam. The
104
+ * observed surface each decision rests on is this:
105
+ *
106
+ * | Situation | Code | `meta` keys observed | Structurally separable? |
107
+ * |---|---|---|---|
108
+ * | Uniqueness violation | `P2002` | `modelName`, `driverAdapterError` | **Yes** — distinct P-code; `cause.kind` is `"UniqueConstraintViolation"`; `cause.constraint` is `{fields}` (SQLite) or `{index}` (PostgreSQL) |
109
+ * | Write conflict / deadlock | `P2034` | `modelName`, `driverAdapterError` | **Yes** from a constraint violation — distinct P-code; `cause.kind` is `"TransactionWriteConflict"`; **no** `constraint` at all. **No** from each other: `cause.originalCode` is `40001` for a write conflict and `40P01` for a deadlock, and that field is provider-owned and never crosses the boundary |
110
+ * | Lost-row race | *none* | *none* | **Yes, trivially** — there is no provider error behind it: it is a zero-row count in a sequence the framework manufactured, and it is raised in `execution/first-variant.emulator.ts`, never here |
111
+ *
112
+ * So all three are separable **without reading any message text**, which is
113
+ * what ADR 0006 requires and what C-821 warns `P2028` cannot offer, and that
114
+ * is why the discrimination is implemented by P-code and by call site alone.
115
+ * The two provider-backed ones differ by P-code; `driverAdapterError.cause.kind`
116
+ * is a second, independent witness.
117
+ *
118
+ * **A write conflict and a deadlock share `A2013` deliberately.** Both arrive
119
+ * as `P2034` with identical `meta` keys, differing only in a provider-owned
120
+ * `originalCode` (`40001` versus `40P01`) that never crosses the boundary.
121
+ * They are not separable at this seam, so one outcome is the only honest
122
+ * answer; splitting them later would need a discriminant that does not exist. `describePrismaConflict` is the recorded
123
+ * surface, and `PrismaConstraintSubject` records that the two engines do not
124
+ * agree on *which* identifier they can name — a uniqueness discriminant that
125
+ * promised the caller the constraint's name would be unsatisfiable on SQLite.
126
+ *
127
+ * ## F-608 / D-816 — what was observed about non-`Error` throws
128
+ *
129
+ * **No Prisma path observed here threw a non-`Error`.** Every induced failure
130
+ * arrived as a `PrismaClientKnownRequestError`, which extends `Error`
131
+ * (`error instanceof Error` was asserted true for all of them), and the one
132
+ * non-Prisma failure the engines produced — `better-sqlite3` refusing a
133
+ * missing directory — was a `TypeError`. So the primitive-throw ambiguity
134
+ * F-608 describes did not arise for the only real adapter that exists, and
135
+ * the general fix (boxing primitive Adapter throws at core's seam) has no
136
+ * demonstrated urgency. That observation, not a guess, is what Task 14 (§14a)
137
+ * judges the row on. The adapter half is discharged here:
138
+ * `prismaOutcomeFailure` returns a `FrameworkError`, so every failure this
139
+ * module originates is an `Error` subclass, enforced by a spec.
140
+ *
141
+ * ## F-723 — why nothing leaks today
142
+ *
143
+ * A deliberate `FrameworkError`'s top-level `cause.message` is forwarded
144
+ * verbatim; that channel is still open and is Phase 13's. What keeps it from
145
+ * leaking is this module's own rule (C-819): the cause is framework text, and
146
+ * the reader never reads `message` at all, so there is no provider string in
147
+ * scope to forward. If that rule is ever relaxed, the leak is immediate —
148
+ * which is the input Phase 13 should receive.
149
+ */
150
+ /** The three answers a Prisma failure can get. T-10, verbatim. */
151
+ export type PrismaErrorClassification = {
152
+ readonly kind: "absence";
153
+ } | {
154
+ readonly kind: "outcome";
155
+ readonly code: OperationErrorCode;
156
+ } | {
157
+ readonly kind: "unclassified";
158
+ };
159
+ /**
160
+ * When a code is a `null` result rather than a failure (D-811).
161
+ *
162
+ * `variants` is read literally from D-811: Prisma's `update`/`delete` throw
163
+ * P2025 when the selector matches nothing, and core's outcome mapper turns a
164
+ * returned `null` into `A2003 NOT_FOUND` for exactly those two variants, so
165
+ * the adapter hands back `null` instead of inventing a failure.
166
+ *
167
+ * `subject` is the measured `meta` shape that may be forgiven. The same
168
+ * P2025 reaches `update.unique` for a second reason — a nested `connect`
169
+ * whose target is missing — and that one is a caller error about the
170
+ * *arguments*, not an absent record. `"root-record"` means `meta` carries no
171
+ * relation reference; the single-member union says that is the one shape
172
+ * measured, and leaves room for a second if one is ever observed.
173
+ */
174
+ type PrismaAbsenceRule = {
175
+ readonly variants: readonly `${string}.${string}`[];
176
+ readonly subject: "root-record";
177
+ };
178
+ type PrismaErrorRule = {
179
+ /**
180
+ * The outcome this code answers. Typed as core's own
181
+ * `OperationErrorCode`, so the adapter cannot widen the code space —
182
+ * a code the table does not contain has no rule at all (C-838).
183
+ */
184
+ readonly outcome: OperationErrorCode;
185
+ readonly absence?: PrismaAbsenceRule;
186
+ };
187
+ /** One closed table, scoped to `@prisma/client` 7.x (C-846). */
188
+ export type PrismaErrorTable = Readonly<Record<string, PrismaErrorRule>>;
189
+ /**
190
+ * The installed-major this table is written against. The spec asserts the
191
+ * installed `@prisma/client` agrees.
192
+ */
193
+ export declare const PRISMA_ERROR_TABLE_MAJOR = "7";
194
+ /**
195
+ * Every code here was induced above. Codes that 7.10.0 *can* raise but that
196
+ * no statement here produced — `P2000`, `P2011`, `P2014`, `P2017`, `P2020`,
197
+ * `P2023`, `P2010`, `P2028`, `P2029`, `P2030`, `P2036`, `P2037`, `P2039`,
198
+ * `P1008`, `P1017`, `P1018` — are **deliberately absent**: an unmeasured
199
+ * rule is a guess, and D-812's answer for anything unrecognised is to leave
200
+ * it to core's `AdapterBoundary`, which reports `A3001 ADAPTER_ERROR` with
201
+ * core's own text and sends the raw error to diagnostics. Adding a row
202
+ * later is additive and costs nothing; shipping a wrong one is not
203
+ * recoverable.
204
+ */
205
+ export declare const PRISMA_ERROR_TABLE: PrismaErrorTable;
206
+ /**
207
+ * Classifies one failed Prisma call.
208
+ *
209
+ * `error` is `unknown` and stays `unknown`: the reader is the only thing
210
+ * that touches it, and the rules see nothing but measured facts. The
211
+ * operation is needed because D-811's answer depends on the variant the
212
+ * caller asked for, which is framework knowledge and not in the error.
213
+ */
214
+ export declare function classifyPrismaError(error: unknown, operation: Pick<OperationIdentity, "family" | "variant">): PrismaErrorClassification;
215
+ /**
216
+ * The deliberate failure an `outcome` classification stands for.
217
+ *
218
+ * It is a `FrameworkError`, so every failure this adapter originates is an
219
+ * `Error` subclass — D-816's adapter half, which needs no core change.
220
+ *
221
+ * The cause carries framework text and nothing else: no issues, and that is
222
+ * now the **rule** rather than an absence (C-839, ADR 0008 decision 4). A
223
+ * conflict is not a Framework Validation Layer failure, ADR 0008 decision 1
224
+ * makes a V-code *determine* the A-code, and `validationCode` rolls up to only
225
+ * `A2001`/`A2002`/`A2004`/`A2007`/`A2009` — so a conflict V-code would have no
226
+ * legal rollup, which is why `A2008`, `A2013` and `A2014` pair with none and
227
+ * carry `cause.issues` absent. And no provider text, because a deliberate
228
+ * outcome's top-level `cause.message` is forwarded verbatim (C-819, F-723).
229
+ * Nothing read off the Prisma error reaches this value.
230
+ */
231
+ export declare function prismaOutcomeFailure(code: OperationErrorCode): FrameworkError;
232
+ /**
233
+ * What RC-8 will discriminate on, recorded rather than re-measured.
234
+ *
235
+ * **Server-side only.** Every field is provider-owned, so this value never
236
+ * reaches a `cause` — which is why it is a separate function and not a field
237
+ * on `PrismaErrorClassification`: nothing can carry it onto the wire by
238
+ * accident. It is the measurement that answered §14c — whether the three
239
+ * situations once collapsed into `A2008` are *structurally* separable, which
240
+ * they are — and it remains the record of what a diagnostics sink could
241
+ * usefully be told.
242
+ */
243
+ export type PrismaConflictSubject = {
244
+ readonly code: string;
245
+ readonly model: string | undefined;
246
+ readonly constraint: PrismaConstraintSubject | undefined;
247
+ readonly metaKeys: readonly string[];
248
+ };
249
+ export declare function describePrismaConflict(error: unknown): PrismaConflictSubject | undefined;
250
+ export {};
@@ -0,0 +1,135 @@
1
+ import { FrameworkError, } from "@aventara/core";
2
+ import { readPrismaError, } from "./prisma-error.reader.js";
3
+ /**
4
+ * The installed-major this table is written against. The spec asserts the
5
+ * installed `@prisma/client` agrees.
6
+ */
7
+ export const PRISMA_ERROR_TABLE_MAJOR = "7";
8
+ /**
9
+ * Every code here was induced above. Codes that 7.10.0 *can* raise but that
10
+ * no statement here produced — `P2000`, `P2011`, `P2014`, `P2017`, `P2020`,
11
+ * `P2023`, `P2010`, `P2028`, `P2029`, `P2030`, `P2036`, `P2037`, `P2039`,
12
+ * `P1008`, `P1017`, `P1018` — are **deliberately absent**: an unmeasured
13
+ * rule is a guess, and D-812's answer for anything unrecognised is to leave
14
+ * it to core's `AdapterBoundary`, which reports `A3001 ADAPTER_ERROR` with
15
+ * core's own text and sends the raw error to diagnostics. Adding a row
16
+ * later is additive and costs nothing; shipping a wrong one is not
17
+ * recoverable.
18
+ */
19
+ export const PRISMA_ERROR_TABLE = {
20
+ // Uniqueness conflict — caller-actionable state conflict, HTTP 409 (D-810, C-820).
21
+ P2002: { outcome: "A2008" },
22
+ // Foreign-key violation — an invalid reference is the same class of
23
+ // conflict, and D-810 maps it to the same code. These two are what
24
+ // `A2008` now means, and the whole of it: re-running either request
25
+ // unchanged fails the same way, which is what separates them from
26
+ // `A2013`/`A2014`.
27
+ P2003: { outcome: "A2008" },
28
+ // Write conflict or deadlock: another operation reached the same state
29
+ // first, so nothing this one asked for was applied. Both arrive as P2034
30
+ // and are not separable at this seam, so they share one outcome. Prisma
31
+ // performs no automatic retry and this adapter adds none (C-822, D-809,
32
+ // F-705): a retried plan would re-run developer callbacks whose side
33
+ // effects are not rollback-safe. A retry is the caller's to make.
34
+ P2034: { outcome: "A2013" },
35
+ // The required connected records were not found: measured only with a
36
+ // relation reference in `meta`, and always about the arguments.
37
+ P2018: { outcome: "A2004" },
38
+ // Depends on records that were required but not found. A caller error
39
+ // about the arguments by default; a `null` result on the two strict
40
+ // unique variants, and only when the missing record is the one the call
41
+ // selected rather than a relation target (D-811).
42
+ P2025: {
43
+ outcome: "A2004",
44
+ absence: {
45
+ variants: ["update.unique", "delete.unique"],
46
+ subject: "root-record",
47
+ },
48
+ },
49
+ };
50
+ /** Framework-owned text, one line per code it may emit. */
51
+ const OUTCOME_MESSAGES = {
52
+ // `A2008` now means the constraint case alone, and its text is
53
+ // UNCHANGED on purpose. §13.2's narrowed meaning — "would violate a
54
+ // uniqueness or reference constraint the Resource declares" — is
55
+ // documentation, NOT a legal wire message: C-819's measured sweeps ban
56
+ // `constraint`, `Unique` and `UNIQUE` from a provider-originated
57
+ // envelope, because those are the provider's own words for this very
58
+ // failure. Narrowing the code's meaning does not license naming the
59
+ // constraint to the caller, and this line is what keeps the two apart.
60
+ A2008: "The operation conflicts with the current state of the resource.",
61
+ A2013: "Another operation modified the same state concurrently, so this operation was not applied and nothing was written.",
62
+ A2004: "The operation's arguments referenced a record that does not exist.",
63
+ };
64
+ /**
65
+ * `"root-record"`: the failure is about the record the call selected, not
66
+ * about a relation target. Measured discriminant, not message text.
67
+ */
68
+ function matchesAbsenceSubject(subject, facts) {
69
+ switch (subject) {
70
+ case "root-record":
71
+ return facts.relation === undefined;
72
+ }
73
+ }
74
+ /**
75
+ * Classifies one failed Prisma call.
76
+ *
77
+ * `error` is `unknown` and stays `unknown`: the reader is the only thing
78
+ * that touches it, and the rules see nothing but measured facts. The
79
+ * operation is needed because D-811's answer depends on the variant the
80
+ * caller asked for, which is framework knowledge and not in the error.
81
+ */
82
+ export function classifyPrismaError(error, operation) {
83
+ const facts = readPrismaError(error);
84
+ if (facts.code === undefined) {
85
+ return { kind: "unclassified" };
86
+ }
87
+ const rule = Object.hasOwn(PRISMA_ERROR_TABLE, facts.code)
88
+ ? PRISMA_ERROR_TABLE[facts.code]
89
+ : undefined;
90
+ if (rule === undefined) {
91
+ return { kind: "unclassified" };
92
+ }
93
+ const { absence } = rule;
94
+ if (absence === undefined) {
95
+ return { kind: "outcome", code: rule.outcome };
96
+ }
97
+ const forgiven = absence.variants.includes(`${operation.family}.${operation.variant}`) &&
98
+ matchesAbsenceSubject(absence.subject, facts);
99
+ return forgiven
100
+ ? { kind: "absence" }
101
+ : { kind: "outcome", code: rule.outcome };
102
+ }
103
+ /**
104
+ * The deliberate failure an `outcome` classification stands for.
105
+ *
106
+ * It is a `FrameworkError`, so every failure this adapter originates is an
107
+ * `Error` subclass — D-816's adapter half, which needs no core change.
108
+ *
109
+ * The cause carries framework text and nothing else: no issues, and that is
110
+ * now the **rule** rather than an absence (C-839, ADR 0008 decision 4). A
111
+ * conflict is not a Framework Validation Layer failure, ADR 0008 decision 1
112
+ * makes a V-code *determine* the A-code, and `validationCode` rolls up to only
113
+ * `A2001`/`A2002`/`A2004`/`A2007`/`A2009` — so a conflict V-code would have no
114
+ * legal rollup, which is why `A2008`, `A2013` and `A2014` pair with none and
115
+ * carry `cause.issues` absent. And no provider text, because a deliberate
116
+ * outcome's top-level `cause.message` is forwarded verbatim (C-819, F-723).
117
+ * Nothing read off the Prisma error reaches this value.
118
+ */
119
+ export function prismaOutcomeFailure(code) {
120
+ return new FrameworkError(code, {
121
+ message: OUTCOME_MESSAGES[code] ?? "The operation could not be completed.",
122
+ });
123
+ }
124
+ export function describePrismaConflict(error) {
125
+ const facts = readPrismaError(error);
126
+ if (facts.code === undefined) {
127
+ return undefined;
128
+ }
129
+ return {
130
+ code: facts.code,
131
+ model: facts.modelName,
132
+ constraint: facts.constraint,
133
+ metaKeys: facts.metaKeys,
134
+ };
135
+ }
@@ -0,0 +1,144 @@
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
+ /**
60
+ * The violated constraint as 7.10.0's driver adapters actually report it,
61
+ * at `meta.driverAdapterError.cause.constraint` (D-810).
62
+ *
63
+ * The arms are **provider-shaped, measured on both engines**, not guessed:
64
+ *
65
+ * | Engine | Unique (P2002) | Foreign key (P2003) |
66
+ * |-----------------------------------|-------------------------------|-------------------------|
67
+ * | `@prisma/adapter-better-sqlite3` | `{ fields: ["email"] }` | `{ foreignKey: {} }` |
68
+ * | `@prisma/adapter-pg` | `{ index: "Author_email_key" }` | `{ index: "Post_authorId_fkey" }` |
69
+ *
70
+ * It is therefore **not a string**, and the two engines do not agree on
71
+ * which identifier they can name — SQLite names columns, PostgreSQL names
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.
75
+ */
76
+ export type PrismaConstraintSubject = {
77
+ readonly kind: "fields";
78
+ readonly fields: readonly string[];
79
+ } | {
80
+ readonly kind: "index";
81
+ readonly index: string;
82
+ } | {
83
+ readonly kind: "foreign-key";
84
+ } | {
85
+ readonly kind: "unreadable";
86
+ };
87
+ /**
88
+ * Everything the normalizer is allowed to know about one failed call.
89
+ *
90
+ * Narrow and flat on purpose: a classification rule reads these fields and
91
+ * nothing else, so no rule can reach back into the raw value and no rule
92
+ * can depend on a shape this module has not measured.
93
+ */
94
+ export type PrismaErrorFacts = {
95
+ /**
96
+ * The P-code, when it is an own string property. A missing or non-string
97
+ * `code` leaves this `undefined`, which the table answers as
98
+ * `unclassified` — never as a guess (D-812).
99
+ */
100
+ readonly code: string | undefined;
101
+ /**
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"`).
107
+ */
108
+ readonly metaKeys: readonly string[];
109
+ /** `meta.modelName` — the Prisma model the failure occurred on. */
110
+ readonly modelName: string | undefined;
111
+ /**
112
+ * `meta.relation` — present exactly when the failure is about a
113
+ * *relation reference* rather than about the record the call selected.
114
+ * This is the measured discriminant behind D-811 (see the normalizer).
115
+ */
116
+ readonly relation: string | undefined;
117
+ /** `meta.model` — the model at the far end of `relation`. */
118
+ readonly relatedModel: string | undefined;
119
+ /** `meta.driverAdapterError.cause.constraint`, read structurally. */
120
+ readonly constraint: PrismaConstraintSubject | undefined;
121
+ /**
122
+ * `meta.target` — **read so that it is measured, depended on by
123
+ * nothing** (C-820, RC-9).
124
+ *
125
+ * The v7 error-reference page still documents `{ target: ['email'] }`,
126
+ * and RC-9 records that whether 7.10.0 ever emits it could not be
127
+ * determined from the package. It was induced on both engines here and
128
+ * **`target` was absent from every payload** — P2002 (single and
129
+ * compound unique), P2003, P2025 (three shapes), P2018 and P2034, on
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.
133
+ */
134
+ readonly legacyTarget: readonly string[] | undefined;
135
+ };
136
+ /**
137
+ * Reads one hostile value into the facts a classification may use.
138
+ *
139
+ * Total: every input produces a `PrismaErrorFacts`. `readPrismaError(null)`
140
+ * and `readPrismaError(Object.create(null))` are as valid as a real
141
+ * `PrismaClientKnownRequestError` — they simply report nothing, which the
142
+ * table answers as `unclassified`.
143
+ */
144
+ export declare function readPrismaError(error: unknown): PrismaErrorFacts;