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