@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,210 @@
1
+ import { classifyPrismaError, prismaOutcomeFailure, } from "../errors/error.normalizer.js";
2
+ import { prismaDelegates, } from "../prisma-client.interface.js";
3
+ import { PRISMA_VARIANT_DISPATCH, translateOperation, } from "../translation/operation.translator.js";
4
+ import { runFirstVariantEmulation } from "./first-variant.emulator.js";
5
+ import { shapeCount, shapeRow, shapeRows, } from "./result.shaper.js";
6
+ /**
7
+ * The execution half of T-7: a translated {@link PrismaCall} → a provider call,
8
+ * and `Adapter.execute` made real.
9
+ *
10
+ * Translation is pure and lives in `translation/operation.translator.ts`; this
11
+ * module owns the three things that need a client and nothing else:
12
+ *
13
+ * 1. **Reaching the delegate.** See {@link delegateKeyOf} — the one derivation in
14
+ * this package, and the one place it is made.
15
+ * 2. **Classifying a failure.** The closed P-code table in
16
+ * `errors/error.normalizer.ts` answers `absence` → `null` (D-811),
17
+ * `outcome` → a deliberate `FrameworkError`, or `unclassified` → propagate, so
18
+ * core's `AdapterBoundary` reports `A3001` with core's own text and sends the
19
+ * raw error to diagnostics. The adapter never guesses a code it did not measure.
20
+ * 3. **Shaping the answer.** `execution/result.shaper.ts` places every `$count`
21
+ * envelope, converts every returned value to the runtime form core's output
22
+ * validator accepts (C-834's inbound half) and reads the number a count variant
23
+ * owes; which of its three entry points runs is the *variant's* `result` row in
24
+ * the dispatch table, not a guess from the shape Prisma returned. The shaper is
25
+ * handed the model and the Resource as well as the plan, because the scalar a
26
+ * returned value belongs to is a fact of the model and not of the value.
27
+ *
28
+ * # One executor type, two scopes (D-807, C-804)
29
+ *
30
+ * `scope: "root"` is the adapter's own `execute`. `scope: "transaction"` is what
31
+ * `Adapter.transaction`'s callback hands core: the same code over a transaction
32
+ * client. The scope decides exactly one thing — whether a first-style emulation
33
+ * opens its own interactive transaction or reuses the one already open — and a
34
+ * transaction-scoped executor closes over the transaction client only, so the root
35
+ * client is unreachable from inside it.
36
+ *
37
+ * # C-834 is converted at its own seam, not at this one
38
+ *
39
+ * All three value crossings read ONE table, `translation/scalar.conversion.ts`:
40
+ * a write's data through `mutation.translator.ts`, a filter operand through
41
+ * `where.translator.ts`, and a returned row through `execution/result.shaper.ts`.
42
+ * None of them is converted here. Converting at this seam would have put the
43
+ * scalar table in a fourth place and hidden the gap from the module that owns it.
44
+ *
45
+ * **Core's output validation runs after this module, and strips.** For a
46
+ * projection-carrying variant `planReadOperation` sets `materialize: true` and
47
+ * `materializeRecord` ends in `Object.fromEntries(plan.requested…)` **[CORE]**, so
48
+ * a spare key the adapter returned would be removed before the validator saw it.
49
+ * That is why the emulator's own return value is asserted directly in
50
+ * `first-variant.emulator.spec.ts` as well as end-to-end here: an end-to-end
51
+ * assertion about extra keys cannot fail on that path.
52
+ */
53
+ /** A call the executor cannot issue against the client it was given. */
54
+ export class PrismaExecutionError extends Error {
55
+ name = "PrismaExecutionError";
56
+ }
57
+ /**
58
+ * Prisma's delegate property for a model.
59
+ *
60
+ * **Derived, and that is a recorded residual rather than a comfortable choice.**
61
+ * The authoritative token is readable — `generated/<engine>/models/<M>.ts`, a 🟢
62
+ * file, writes it as the extension key: `GetSelect<{ … },
63
+ * ExtArgs["result"]["author"]>` for model `Author` — but `types.reader.ts` reads
64
+ * the 🟢 files for *capability* facts and does not record it, and the client
65
+ * property itself is declared only in `internal/class.ts`, which that reader
66
+ * refuses by design. So the rule applied here is Prisma's own: lower-case the
67
+ * first character and nothing else. `operation.executor.spec.ts` asserts the
68
+ * derivation against the real generated client for **every** model in the schema,
69
+ * so it is observed for this repository's six models; a model name whose Prisma
70
+ * property does not follow the rule would be caught there and not in production.
71
+ * The clean owner of the fact is the generate-time translation table, beside
72
+ * `identifierKeys`, and that is left as a pointer rather than taken here.
73
+ */
74
+ export function delegateKeyOf(model) {
75
+ const first = model.slice(0, 1);
76
+ return `${first.toLowerCase()}${model.slice(1)}`;
77
+ }
78
+ export function createPrismaOperationExecutor(dependencies) {
79
+ const issue = async (call) => resolveDelegateCall(dependencies.client, call)(call.args);
80
+ return {
81
+ async execute(operation) {
82
+ const result = resultKindOf(dispatchOf(operation));
83
+ const translation = translateOperation({
84
+ model: dependencies.model,
85
+ translation: dependencies.translation,
86
+ operation: operation,
87
+ });
88
+ const shaping = {
89
+ model: dependencies.model,
90
+ resource: operation.resource,
91
+ plan: translation.projection,
92
+ };
93
+ if (translation.plan.kind === "single") {
94
+ const call = translation.plan.call;
95
+ return shape(result, await issueClassified(issue, call, operation), shaping);
96
+ }
97
+ const emulated = await runFirstVariantEmulation({
98
+ steps: translation.plan.steps,
99
+ identifiers: identifiersOf(dependencies, operation.resource),
100
+ inUnit: (work) => withUnit(dependencies, (client) => work((call, step) => {
101
+ // The READ goes through the same classification as any other
102
+ // call; the WRITE's own `P2025` is the emulator's to interpret,
103
+ // by call site.
104
+ const send = (one) => resolveDelegateCall(client, one)(one.args);
105
+ return step === "read"
106
+ ? issueClassified(send, call, operation)
107
+ : send(call);
108
+ })),
109
+ });
110
+ return emulated === null ? null : shape(result, emulated, shaping);
111
+ },
112
+ };
113
+ }
114
+ /**
115
+ * D-807's unit. Standalone the emulation opens its **own** interactive
116
+ * transaction and the steps run on **that** client; inside a plan it reuses the
117
+ * outer transaction client, so its steps roll back with the plan rather than
118
+ * independently (§14.5). No isolation level is escalated (C-830).
119
+ */
120
+ async function withUnit(dependencies, work) {
121
+ if (dependencies.scope === "transaction") {
122
+ // The plan's transaction is already open and `client` IS its client, so a
123
+ // nested one would let these steps roll back independently of the plan.
124
+ return work(dependencies.client);
125
+ }
126
+ const options = dependencies.transactionOptions;
127
+ // The client the callback is handed is the one the steps run on: that is what
128
+ // makes the two statements one unit rather than two statements near one.
129
+ return options === undefined
130
+ ? dependencies.client.$transaction((transactional) => work(transactional))
131
+ : dependencies.client.$transaction((transactional) => work(transactional), options);
132
+ }
133
+ /**
134
+ * What the variant owes, read off the dispatch table.
135
+ *
136
+ * An `"unavailable"` entry is refused here rather than silently shaped: core
137
+ * rejects the request with `A2002 UNKNOWN_OPERATION` long before this, because
138
+ * `operations.deriver.ts` never advertised it, so reaching this line means the
139
+ * Contract and the adapter have diverged and that is a defect, not a request.
140
+ */
141
+ function resultKindOf(dispatch) {
142
+ if (dispatch.kind === "unavailable") {
143
+ throw new PrismaExecutionError(`the Prisma adapter advertises no call for this operation: ${dispatch.reason}`);
144
+ }
145
+ return dispatch.result;
146
+ }
147
+ function dispatchOf(operation) {
148
+ const table = PRISMA_VARIANT_DISPATCH;
149
+ const entry = table[`${operation.family}.${operation.variant}`];
150
+ if (entry === undefined) {
151
+ throw new PrismaExecutionError(`"${operation.family}.${operation.variant}" is not a standard Aventara operation`);
152
+ }
153
+ return entry;
154
+ }
155
+ function identifiersOf(dependencies, resource) {
156
+ const entry = dependencies.translation[resource];
157
+ if (entry === undefined) {
158
+ throw new PrismaExecutionError(`Resource "${resource}" has no Prisma addressing in the translation table`);
159
+ }
160
+ return entry.identifierKeys;
161
+ }
162
+ function resolveDelegateCall(client, call) {
163
+ const key = delegateKeyOf(call.model);
164
+ const delegate = prismaDelegates(client)[key];
165
+ if (delegate === undefined) {
166
+ throw new PrismaExecutionError(`the client exposes no delegate "${key}" for Prisma model "${call.model}"`);
167
+ }
168
+ const method = delegate[call.action];
169
+ if (typeof method !== "function") {
170
+ throw new PrismaExecutionError(`the delegate "${key}" exposes no "${call.action}", which discovery advertised`);
171
+ }
172
+ return (args) => Promise.resolve(method.call(delegate, args));
173
+ }
174
+ /**
175
+ * Issues one call and routes its failure through the closed P-code table.
176
+ *
177
+ * `absence` returns `null`, which core's `OUTCOME_TABLE` turns into `A2003` for a
178
+ * strict-unique variant (D-811) — the adapter never names the A-code.
179
+ */
180
+ async function issueClassified(issue, call, operation) {
181
+ try {
182
+ return await issue(call);
183
+ }
184
+ catch (error) {
185
+ const classification = classifyPrismaError(error, operation);
186
+ switch (classification.kind) {
187
+ case "absence":
188
+ return null;
189
+ case "outcome":
190
+ throw prismaOutcomeFailure(classification.code);
191
+ case "unclassified":
192
+ throw error;
193
+ }
194
+ }
195
+ }
196
+ function shape(kind, result, shaping) {
197
+ switch (kind) {
198
+ case "count":
199
+ return shapeCount(result);
200
+ case "rows":
201
+ if (!Array.isArray(result)) {
202
+ throw new PrismaExecutionError("a many variant owes an array and Prisma answered something else");
203
+ }
204
+ return shapeRows(result, shaping);
205
+ case "row":
206
+ return result === null || result === undefined
207
+ ? null
208
+ : shapeRow(result, shaping);
209
+ }
210
+ }
@@ -0,0 +1,103 @@
1
+ import type { AdapterModel } from "@aventara/core";
2
+ import type { PrismaProjectionPlan } from "../translation/projection.translator.js";
3
+ /**
4
+ * Task 8 / T-8 — the shape core's output validator expects back (C-832, §10.3),
5
+ * and C-834's inbound value conversion.
6
+ *
7
+ * **The envelope is the adapter's to build, not core's.** `output-validator.ts`
8
+ * accepts exactly `["count"]` or `["data","count"]` for a relation whose plan
9
+ * carries the `$count` reducer, and rejects any key outside the projection —
10
+ * which includes Prisma's own `_count` **[CORE, runtime/output-validator.ts]**.
11
+ * So Prisma's answer `{ id, posts: [...], _count: { posts: 3 } }` must leave here
12
+ * as `{ id, posts: { data: [...], count: 3 } }`, with `_count` gone.
13
+ *
14
+ * **The three §10.3 shapes, and which one applies:**
15
+ *
16
+ * | nested select | shape | this module |
17
+ * |---|---|---|
18
+ * | `["id","title"]` | array | returns Prisma's rows, values converted |
19
+ * | `["$count"]` | `{ count }` | reads `_count.<edge>`; no rows were selected |
20
+ * | `["id","$count"]` | `{ data, count }` | pairs the rows with `_count.<edge>` |
21
+ *
22
+ * # C-834's inbound half lives here, and it is keyed by the MODEL
23
+ *
24
+ * The same validator that rejects a spare key also checks every projected
25
+ * scalar's runtime form, and a decimal must be core's own `Decimal`
26
+ * **[CORE, output-validator.ts → `normalizeScalarFieldValue`]**. Prisma returns an
27
+ * object whose constructor is `Decimal2`, so before this conversion existed every
28
+ * read of a decimal column failed core's output validation and the caller was
29
+ * told `A3000` — measured through `createFramework(...).execute(...)` on both
30
+ * engines (2026-10-02). The conversion itself belongs to
31
+ * `translation/scalar.conversion.ts`, the one table that knows how each scalar
32
+ * crosses the boundary in either direction; this module owns only *which field a
33
+ * returned value belongs to*.
34
+ *
35
+ * **It answers that from the discovered model, not from the value.** The shaper
36
+ * walks a row against `AdapterModel`: a key that names a scalar field converts
37
+ * under that field's scalar, a key that names a relation recurses into the edge's
38
+ * TARGET Resource, and a key the model does not know passes through for core to
39
+ * accept or reject. Sniffing the runtime shape instead would be wrong in both
40
+ * directions at once — a `Decimal2` and a `Json` document are both objects, and a
41
+ * `Json` column holding the string `"12.5"` is indistinguishable from a decimal
42
+ * string, so the first `Json` column holding numeric text would quietly come back
43
+ * as a `Decimal`.
44
+ *
45
+ * The plan is still what places a `$count` envelope, and only that. The two are
46
+ * complementary and neither can do the other's job: the plan records only the
47
+ * edges that need reshaping (so it cannot drive a walk that must reach every
48
+ * scalar), and the model does not know which edges requested a reducer (so it
49
+ * cannot place an envelope).
50
+ *
51
+ * **Cardinality is never assumed.** `AdapterOperation` carries no plan, and the
52
+ * arguments the plan is reconstructed from do not say whether an edge is to-one
53
+ * or to-many. So recursion is driven by the value Prisma actually returned — an
54
+ * array is shaped elementwise, a record is shaped as one row, `null` passes
55
+ * through — which is total without knowing the cardinality at all.
56
+ *
57
+ * Nothing here invents a number. A missing or non-numeric `_count` entry for a
58
+ * planned reducer is a translation/driver disagreement, and it raises rather
59
+ * than defaulting to `0`: a wrong count that validates is worse than a loud
60
+ * failure, because core would forward it to the caller as fact.
61
+ *
62
+ * **Nothing Prisma returned is written into.** A row is copied only when some
63
+ * value in it actually changed, so a result needing no conversion and no envelope
64
+ * comes back by identity.
65
+ */
66
+ /** What a Prisma result must be read against: the model, the Resource, the plan. */
67
+ export type PrismaRowShaping = {
68
+ /** The discovered model: field kinds, scalars, relation targets. */
69
+ readonly model: AdapterModel;
70
+ /** The Aventara Resource key the rows belong to. */
71
+ readonly resource: string;
72
+ /** Where the `$count` reducers sit, reconstructed by projection translation. */
73
+ readonly plan: PrismaProjectionPlan;
74
+ };
75
+ export type PrismaResultShaper = {
76
+ shapeRow(row: unknown, shaping: PrismaRowShaping): unknown;
77
+ shapeRows(rows: readonly unknown[], shaping: PrismaRowShaping): readonly unknown[];
78
+ shapeCount(result: unknown): number;
79
+ };
80
+ /** A provider result the adapter will not reshape into a claim about the data. */
81
+ export declare class PrismaResultShapeError extends Error {
82
+ constructor(reason: string);
83
+ }
84
+ /**
85
+ * Converts every scalar in one provider row and places every `$count` envelope.
86
+ *
87
+ * @returns the row unchanged, by identity, when no value needed converting and
88
+ * the plan records no edge that needs reshaping; a copy otherwise. The row Prisma
89
+ * returned is never mutated.
90
+ */
91
+ export declare function shapeRow(row: unknown, shaping: PrismaRowShaping): unknown;
92
+ /** Shapes a `many` result: one pass per row. */
93
+ export declare function shapeRows(rows: readonly unknown[], shaping: PrismaRowShaping): readonly unknown[];
94
+ /**
95
+ * Reads the number a `count` variant must return.
96
+ *
97
+ * Prisma answers a bare number from `count` and `{ count }` from `updateMany` /
98
+ * `deleteMany`; core requires a non-negative safe integer either way
99
+ * **[CORE, runtime/output-validator.ts]**.
100
+ */
101
+ export declare function shapeCount(result: unknown): number;
102
+ /** T-8's shaper, as one value the operation executor can hold. */
103
+ export declare const prismaResultShaper: PrismaResultShaper;
@@ -0,0 +1,141 @@
1
+ import { receiveScalarValue } from "../translation/scalar.conversion.js";
2
+ /** A provider result the adapter will not reshape into a claim about the data. */
3
+ export class PrismaResultShapeError extends Error {
4
+ constructor(reason) {
5
+ super(reason);
6
+ this.name = "PrismaResultShapeError";
7
+ }
8
+ }
9
+ /** Prisma's spelling of the reducer envelope on the parent row. */
10
+ const PRISMA_COUNT_KEY = "_count";
11
+ /** An edge with no reducer anywhere beneath it. */
12
+ const NO_REDUCERS = { relations: [] };
13
+ function isRecord(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+ function readCount(row, field) {
17
+ const counts = row[PRISMA_COUNT_KEY];
18
+ const count = isRecord(counts) ? counts[field] : undefined;
19
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) {
20
+ throw new PrismaResultShapeError(`Prisma returned no \`_count\` for the relation "${field}", which requested the $count reducer.`);
21
+ }
22
+ return count;
23
+ }
24
+ /** The nested shaping for one edge: its target Resource, and its own plan. */
25
+ function edgeShaping(shaping, field, node) {
26
+ return {
27
+ model: shaping.model,
28
+ resource: field.target,
29
+ plan: node?.projection ?? NO_REDUCERS,
30
+ };
31
+ }
32
+ function shapeValue(value, shaping) {
33
+ if (!Array.isArray(value)) {
34
+ return shapeRow(value, shaping);
35
+ }
36
+ let changed = false;
37
+ const shaped = value.map((entry) => {
38
+ const result = shapeRow(entry, shaping);
39
+ changed ||= result !== entry;
40
+ return result;
41
+ });
42
+ return changed ? shaped : value;
43
+ }
44
+ function shapeReducerRows(value, field, shaping) {
45
+ if (!Array.isArray(value)) {
46
+ throw new PrismaResultShapeError(`Prisma returned no row set for the relation "${field}", whose projection requested rows beside the $count reducer.`);
47
+ }
48
+ return value.map((entry) => shapeRow(entry, shaping));
49
+ }
50
+ /**
51
+ * Converts every scalar in one provider row and places every `$count` envelope.
52
+ *
53
+ * @returns the row unchanged, by identity, when no value needed converting and
54
+ * the plan records no edge that needs reshaping; a copy otherwise. The row Prisma
55
+ * returned is never mutated.
56
+ */
57
+ export function shapeRow(row, shaping) {
58
+ if (!isRecord(row)) {
59
+ return row;
60
+ }
61
+ const resource = shaping.model.resources[shaping.resource];
62
+ if (resource === undefined) {
63
+ throw new PrismaResultShapeError(`Resource "${shaping.resource}" is not in the discovered model, so the adapter cannot read the row it returned.`);
64
+ }
65
+ const nodes = new Map(shaping.plan.relations.map((relation) => [relation.field, relation]));
66
+ const envelopes = shaping.plan.relations.filter((relation) => relation.reducers.length > 0);
67
+ let changed = envelopes.length > 0;
68
+ const shaped = {};
69
+ for (const [key, value] of Object.entries(row)) {
70
+ if (key === PRISMA_COUNT_KEY && envelopes.length > 0) {
71
+ // The reducer pass below reads it; core rejects it as a spare key.
72
+ continue;
73
+ }
74
+ const field = resource.fields[key];
75
+ if (field === undefined) {
76
+ // Not a field of this Resource — Prisma's own `_count` with no reducer
77
+ // planned, or a divergence. Core's output validation owns that verdict.
78
+ shaped[key] = value;
79
+ continue;
80
+ }
81
+ if (field.kind === "scalar") {
82
+ const received = receiveScalarValue(shaping.model, field, value, `${shaping.resource}.${key}`);
83
+ changed ||= received !== value;
84
+ shaped[key] = received;
85
+ continue;
86
+ }
87
+ const node = nodes.get(key);
88
+ if (node !== undefined && node.reducers.length > 0) {
89
+ // Written by the reducer pass, from the raw row.
90
+ continue;
91
+ }
92
+ const nested = shapeValue(value, edgeShaping(shaping, field, node));
93
+ changed ||= nested !== value;
94
+ shaped[key] = nested;
95
+ }
96
+ for (const relation of envelopes) {
97
+ const field = resource.fields[relation.field];
98
+ if (field === undefined || field.kind !== "relation") {
99
+ throw new PrismaResultShapeError(`The projection requested the $count reducer on "${relation.field}", which is not a relation of Resource "${shaping.resource}".`);
100
+ }
101
+ const count = readCount(row, relation.field);
102
+ const nested = edgeShaping(shaping, field, relation);
103
+ shaped[relation.field] = relation.rows
104
+ ? {
105
+ data: shapeReducerRows(row[relation.field], relation.field, nested),
106
+ count,
107
+ }
108
+ : { count };
109
+ }
110
+ return changed ? shaped : row;
111
+ }
112
+ /** Shapes a `many` result: one pass per row. */
113
+ export function shapeRows(rows, shaping) {
114
+ let changed = false;
115
+ const shaped = rows.map((row) => {
116
+ const result = shapeRow(row, shaping);
117
+ changed ||= result !== row;
118
+ return result;
119
+ });
120
+ return changed ? shaped : rows;
121
+ }
122
+ /**
123
+ * Reads the number a `count` variant must return.
124
+ *
125
+ * Prisma answers a bare number from `count` and `{ count }` from `updateMany` /
126
+ * `deleteMany`; core requires a non-negative safe integer either way
127
+ * **[CORE, runtime/output-validator.ts]**.
128
+ */
129
+ export function shapeCount(result) {
130
+ const count = isRecord(result) ? result.count : result;
131
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) {
132
+ throw new PrismaResultShapeError("Prisma returned no count for a count operation.");
133
+ }
134
+ return count;
135
+ }
136
+ /** T-8's shaper, as one value the operation executor can hold. */
137
+ export const prismaResultShaper = {
138
+ shapeRow,
139
+ shapeRows,
140
+ shapeCount,
141
+ };
@@ -0,0 +1,147 @@
1
+ import type { AdapterModel, AdapterOperationExecutor } from "@aventara/core";
2
+ import type { PrismaResourceTranslation } from "../discovery/discovery-artifact.interface.js";
3
+ import type { PrismaDelegateHost, PrismaTransactionOptions } from "../prisma-client.interface.js";
4
+ /**
5
+ * T-9 — `Adapter.transaction`: one interactive Prisma transaction per framework
6
+ * plan, and the transaction-scoped executor core runs the plan through.
7
+ *
8
+ * The body is one `client.$transaction(async (tx) => work(scopedExecutorFor(tx)),
9
+ * options)`. Everything interesting about this module is what it deliberately
10
+ * does **not** do, so each omission is recorded here rather than left to be
11
+ * rediscovered.
12
+ *
13
+ * # The callback form, never the array form (C-803)
14
+ *
15
+ * Prisma's array form — `$transaction` applied to a list of prepared calls —
16
+ * cannot honour a plan at all. An Aventara plan is
17
+ * **sequential by contract**: operation *n* may carry a `$ref` resolved from
18
+ * operation *n − 1*'s final data, and core resolves it between calls, so the
19
+ * second call's arguments do not exist until the first has answered. The array
20
+ * form takes every call up front. It is therefore a framework invariant and not
21
+ * a preference, and it is protected twice: {@link PrismaDelegateHost} declares
22
+ * `$transaction` in its callback form only, so the array overload is not
23
+ * *representable* through the seam this adapter holds, and
24
+ * `transaction.executor.spec.ts` asserts both that every call this adapter makes
25
+ * passes a function and that the array form's own opening token occurs nowhere in
26
+ * `src` (that spec assembles the token rather than writing it, for the same
27
+ * reason this sentence does not quote it).
28
+ *
29
+ * # The root client is unreachable from inside (C-804)
30
+ *
31
+ * `scopedExecutorFor` closes over the callback's `tx` and nothing else, so no
32
+ * plan operation can reach the client this module was constructed with — there is
33
+ * no path to it from the value core is handed. `scope: "transaction"` additionally
34
+ * tells `operation.executor.ts` that an emulated first-style variant must reuse
35
+ * this transaction rather than open its own (D-807): inside a plan a nested
36
+ * savepoint would let the emulation roll back independently of the plan, which
37
+ * §14.5 forbids.
38
+ *
39
+ * # No `try`, no `catch`, no wrapper (C-805, C-806)
40
+ *
41
+ * Core throws a private frozen abort sentinel out of `work` after it has recorded
42
+ * a failing operation, and recognises the rejection **by `===` alone**. Prisma
43
+ * 7.10.0 rethrows the caught value by reference after a best-effort rollback
44
+ * **[P7R Q3]**, so the contract is met by *adding nothing*: a `catch` that
45
+ * re-threw anything — even `error` wrapped in context — would turn every plan
46
+ * failure into core's `foreign-abort` integrity failure and lose the operation's
47
+ * own code.
48
+ *
49
+ * **Prisma swallows its own rollback failures.** The rollback call's rejection is
50
+ * caught and handed to an internal debug logger — `await
51
+ * this._engine.transaction("rollback", …).catch(E => { kt("rollback attempt
52
+ * %d/%d failed: %O", …) })`, with `kt = debug("prisma:client")` **[PKG,
53
+ * `runtime/client.js`; P7R Q4]** — so a failed rollback never reaches this
54
+ * module. **Induced, not assumed** (2026-10-02, PostgreSQL 18): a write inside a
55
+ * transaction, the backend terminated from a second connection, a frozen sentinel
56
+ * thrown from the callback. Prisma's `ROLLBACK` failed — `prisma:client rollback
57
+ * attempt 1/1 failed: '{}'` appears under `DEBUG=prisma*` — and the value the
58
+ * caller received was still that sentinel, by identity, carrying nothing about the
59
+ * rollback at all. Two consequences, both deliberate: core's `A3002`-on-rollback-
60
+ * failure branch is **unreachable with this adapter** (it stays for adapters whose
61
+ * provider reports the failure), and this adapter never claims a rollback
62
+ * succeeded, because it cannot observe that it did. A commit failure is different:
63
+ * it is thrown from inside the same `try` and rejects like a callback throw.
64
+ *
65
+ * # `maxWait`, `timeout` and `isolationLevel` are adapter-owned (D-806, F-702)
66
+ *
67
+ * They are provider concepts: core's `Adapter.transaction(work)` carries no
68
+ * options and must not grow any, so they are set once at
69
+ * `createPrismaAdapter({ transactionOptions })` and are not part of Aventara's
70
+ * public configuration. Prisma's own defaults stand when they are absent —
71
+ * `maxWait` 2000 ms, `timeout` 5000 ms **[PKG, `runtime/client.d.ts`]** — and the
72
+ * supported isolation levels come from the provider profile, which discovered
73
+ * them, rather than from a table written here.
74
+ *
75
+ * The options are **not** forwarded to the scoped executor. At `"transaction"`
76
+ * scope `operation.executor.ts` never opens a transaction, so an option passed
77
+ * there would be dead configuration that reads as if it did something.
78
+ *
79
+ * # `P2028`, as measured rather than as documented (C-821, D-808)
80
+ *
81
+ * Induced against PostgreSQL 18 on `@prisma/client` 7.10.0, 2026-10-02 (the
82
+ * assertions live in `conformance/postgres.conformance.spec.ts`):
83
+ *
84
+ * ```text
85
+ * timeout 1 ms, a query issued after expiry
86
+ * P2028 meta = { modelName: "Author", operation: "query", timeout: 1, timeTaken: 66 }
87
+ * timeout 1 ms, no query after the sleep — the COMMIT is what fails
88
+ * P2028 meta = { operation: "commit", timeout: 1, timeTaken: 62 }
89
+ * maxWait 1 ms with the one connection already held by another transaction
90
+ * P2028 meta = {} <- EMPTY
91
+ * isolationLevel: "Bogus"
92
+ * P2023 meta = { providedIsolationLevel: "Bogus" } <- NOT P2028
93
+ * ```
94
+ *
95
+ * The table above is the `isolationLevel` arm only as far as a **malformed** level
96
+ * goes. A level that IS one of Prisma's five and is simply unsupported by the
97
+ * provider reaches the transaction manager and raises a `meta`-carrying `P2028` of
98
+ * its own, measured on both engines (2026-10-02):
99
+ *
100
+ * ```text
101
+ * isolationLevel: "Snapshot" (a SQL Server level; one of Prisma's five)
102
+ * P2028 meta = { isolationLevel: "SNAPSHOT" }
103
+ * ```
104
+ *
105
+ * So of the seven `P2028` classes in 7.10.0 — `TransactionManagerError` plus
106
+ * `nt`/`un`/`cn`/`fr`/`pn`/`Me`/`dn` **[PKG, `runtime/client.js`]** — the
107
+ * separable-by-`meta` set is **three**: the transaction-timeout expiry carries
108
+ * `{ operation, timeout, timeTaken }`, the unsupported isolation level carries
109
+ * `{ isolationLevel }`, and the acquisition timeout carries nothing and is
110
+ * separable *by* that emptiness from the other two. It is **not** separable from
111
+ * the four remaining classes: transaction-not-found, already-committed,
112
+ * already-rolled-back and internal-consistency all arrive as `P2028` with no
113
+ * `meta` at all, and telling those five apart needs the message text, which
114
+ * ADR 0006 forbids. C-821's claim that the invalid isolation level is a
115
+ * `meta`-carrying `P2028` subclass therefore **holds**, by a narrower route than
116
+ * C-821 states: only a level Prisma's own union does not contain is rejected by
117
+ * the query compiler first, as `P2023`. An earlier revision of this comment
118
+ * withdrew the claim outright; it was measured on the wrong input, and
119
+ * `conformance/postgres.conformance.spec.ts` and
120
+ * `conformance/sqlite.conformance.spec.ts` both assert the corrected fact.
121
+ *
122
+ * D-808's outcome — `P2028` → `A3002 TRANSACTION_ERROR` — needs no row in the
123
+ * adapter's P-code table to hold, and that is the honest reading of **where** it
124
+ * holds. A `P2028` that ends the transaction rejects out of `$transaction` itself,
125
+ * is not core's sentinel, and core's own `infrastructureFailure` answers `A3002`.
126
+ * A `P2028` raised by a *delegate call* inside the callback is a different route:
127
+ * it is unclassified by the closed table, so core's `AdapterBoundary` attributes
128
+ * `A3001` to that operation. `A3002` is therefore the answer for the failure that
129
+ * ends the transaction, not a uniform answer for the code — recorded here because
130
+ * the difference is invisible from the table alone.
131
+ */
132
+ export type PrismaTransactionExecutorDependencies = {
133
+ /** The client the transaction is opened on: the application's own. */
134
+ readonly client: PrismaDelegateHost;
135
+ readonly model: AdapterModel;
136
+ /** The generate-time translation table (Prisma addressing per Resource). */
137
+ readonly translation: Readonly<Record<string, PrismaResourceTranslation>>;
138
+ /** Adapter-owned, never Aventara public configuration (D-806). */
139
+ readonly transactionOptions?: PrismaTransactionOptions;
140
+ };
141
+ /**
142
+ * `Adapter<M>["transaction"]` without the optionality: the adapter mounts it
143
+ * exactly when the discovered model declares `transactions: "interactive"`
144
+ * (D-805, DA-12), so the function itself is total.
145
+ */
146
+ export type PrismaTransactionRunner<M extends AdapterModel> = <T>(work: (executor: AdapterOperationExecutor<M>) => Promise<T>) => Promise<T>;
147
+ export declare function createPrismaTransactionExecutor<M extends AdapterModel>(dependencies: PrismaTransactionExecutorDependencies): PrismaTransactionRunner<M>;
@@ -0,0 +1,13 @@
1
+ import { createPrismaOperationExecutor } from "./operation.executor.js";
2
+ export function createPrismaTransactionExecutor(dependencies) {
3
+ const options = dependencies.transactionOptions;
4
+ const scopedExecutorFor = (transactional) => createPrismaOperationExecutor({
5
+ client: transactional,
6
+ model: dependencies.model,
7
+ translation: dependencies.translation,
8
+ scope: "transaction",
9
+ });
10
+ return (work) => options === undefined
11
+ ? dependencies.client.$transaction(async (transactional) => work(scopedExecutorFor(transactional)))
12
+ : dependencies.client.$transaction(async (transactional) => work(scopedExecutorFor(transactional)), options);
13
+ }
@@ -0,0 +1,50 @@
1
+ import type { PrismaAdapterModel, PrismaReferentialActionTable, PrismaTranslationTable } from "../discovery/discovery-artifact.interface.js";
2
+ import type { PrismaProvider } from "../provider/provider-profile.js";
3
+ /**
4
+ * T-3.2 under **ADR 0009 (Accepted), which overrides the plan**: the discovery
5
+ * artifact is **one TypeScript module**, not JSON.
6
+ *
7
+ * The plan's T-3.2 says JSON. It predates the ADR and loses to it, for a reason
8
+ * the ADR states plainly: `resolveJsonModule` widens `"string"` to `string`, so
9
+ * a JSON model carries no literals, and carrying them would take a second
10
+ * emitted `.d.ts` beside the data — two emissions from one source, which can
11
+ * drift. One module written `as const satisfies AdapterModel` makes the value
12
+ * and the type the same expression, so drift is not merely detectable but
13
+ * unrepresentable.
14
+ *
15
+ * The emitted module is still passive, serializable, introspectable, hashable
16
+ * and free of runtime callbacks (C-841). It contains three `const` bindings and
17
+ * no executable statement; `discovery.model` and `discovery.translation`
18
+ * *reference* their bindings rather than restating them, so there is exactly one
19
+ * representation of each half in the file.
20
+ *
21
+ * **Three halves, and the seam between them is deliberate.** `model` is what
22
+ * core consumes: canonicalized, digested into `modelHash`, and compiled into the
23
+ * Contract. `translation` is Prisma addressing — which key a `WhereUniqueInput`
24
+ * answers to for each identifier. `referentialActions` is what deleting a parent
25
+ * row does to its children, resolved. Both of the latter sit BESIDE the model
26
+ * precisely because they must not reach the hash: a `@@unique(name:)` rename
27
+ * carries no semantic content, and inside the model it would change the Contract
28
+ * identity and force every generated client to be regenerated for nothing; a
29
+ * referential action is not part of the model at all, and inside it the action
30
+ * would reach the compiled Contract and the ClientContract, which have no word
31
+ * for one.
32
+ *
33
+ * **Determinism (C-843).** The model text is the canonical JSON core already
34
+ * uses for Contract identity, re-indented — not `JSON.stringify` over whatever
35
+ * key order the readers happened to build. So two generate runs over one schema
36
+ * emit byte-identical modules, and `modelHash` is a digest of exactly the bytes
37
+ * the module carries, computed by the same `computeModelHash` the runtime
38
+ * loader checks it with. One hash function, one canonicalization, both halves.
39
+ */
40
+ export type PrismaArtifactEmission = {
41
+ /** e.g. `"7.10.0"`; only the major is fenced at load (C-846). */
42
+ readonly prismaVersion: string;
43
+ readonly provider: PrismaProvider;
44
+ readonly model: PrismaAdapterModel;
45
+ /** Prisma addressing per Resource — emitted beside `model`, outside the hash. */
46
+ readonly translation: PrismaTranslationTable;
47
+ /** Resolved referential actions per Resource — also beside `model`, also outside the hash. */
48
+ readonly referentialActions: PrismaReferentialActionTable;
49
+ };
50
+ export declare function emitDiscoveryArtifactModule(emission: PrismaArtifactEmission): Promise<string>;