@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.
- package/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +564 -0
- package/dist/discovery/artifact.loader.d.ts +26 -0
- package/dist/discovery/artifact.loader.js +124 -0
- package/dist/discovery/capability-surface.interface.d.ts +191 -0
- package/dist/discovery/capability-surface.interface.js +13 -0
- package/dist/discovery/capability.deriver.d.ts +92 -0
- package/dist/discovery/capability.deriver.js +350 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
- package/dist/discovery/discovery-artifact.interface.js +1 -0
- package/dist/discovery/enum.mapper.d.ts +59 -0
- package/dist/discovery/enum.mapper.js +32 -0
- package/dist/discovery/field.mapper.d.ts +244 -0
- package/dist/discovery/field.mapper.js +387 -0
- package/dist/discovery/identifier.mapper.d.ts +188 -0
- package/dist/discovery/identifier.mapper.js +132 -0
- package/dist/discovery/model.discoverer.d.ts +108 -0
- package/dist/discovery/model.discoverer.js +341 -0
- package/dist/discovery/operations.deriver.d.ts +75 -0
- package/dist/discovery/operations.deriver.js +92 -0
- package/dist/discovery/scalar.mapper.d.ts +81 -0
- package/dist/discovery/scalar.mapper.js +51 -0
- package/dist/discovery/schema-facts.interface.d.ts +174 -0
- package/dist/discovery/schema-facts.interface.js +11 -0
- package/dist/discovery/schema.reader.d.ts +162 -0
- package/dist/discovery/schema.reader.js +692 -0
- package/dist/discovery/types.reader.d.ts +24 -0
- package/dist/discovery/types.reader.js +617 -0
- package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
- package/dist/discovery/vocabulary.dictionary.js +429 -0
- package/dist/errors/error.normalizer.d.ts +250 -0
- package/dist/errors/error.normalizer.js +135 -0
- package/dist/errors/prisma-error.reader.d.ts +144 -0
- package/dist/errors/prisma-error.reader.js +167 -0
- package/dist/execution/first-variant.emulator.d.ts +64 -0
- package/dist/execution/first-variant.emulator.js +205 -0
- package/dist/execution/operation.executor.d.ts +87 -0
- package/dist/execution/operation.executor.js +210 -0
- package/dist/execution/result.shaper.d.ts +103 -0
- package/dist/execution/result.shaper.js +141 -0
- package/dist/execution/transaction.executor.d.ts +147 -0
- package/dist/execution/transaction.executor.js +13 -0
- package/dist/generate/artifact.emitter.d.ts +50 -0
- package/dist/generate/artifact.emitter.js +105 -0
- package/dist/generate/declared-support.reader.d.ts +33 -0
- package/dist/generate/declared-support.reader.js +74 -0
- package/dist/generate/discovery.bin.d.ts +2 -0
- package/dist/generate/discovery.bin.js +15 -0
- package/dist/generate/discovery.cli.d.ts +24 -0
- package/dist/generate/discovery.cli.js +163 -0
- package/dist/generate/existence.gate.d.ts +73 -0
- package/dist/generate/existence.gate.js +91 -0
- package/dist/generate/index.d.ts +81 -0
- package/dist/generate/index.js +65 -0
- package/dist/generate/node-version.guard.d.ts +8 -0
- package/dist/generate/node-version.guard.js +59 -0
- package/dist/generate/prisma-cli.resolver.d.ts +48 -0
- package/dist/generate/prisma-cli.resolver.js +95 -0
- package/dist/generate/prisma.generate.d.ts +164 -0
- package/dist/generate/prisma.generate.js +278 -0
- package/dist/generate/reader-crosscheck.d.ts +156 -0
- package/dist/generate/reader-crosscheck.js +202 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/prisma-client.interface.d.ts +48 -0
- package/dist/prisma-client.interface.js +9 -0
- package/dist/prisma.adapter.d.ts +55 -0
- package/dist/prisma.adapter.js +105 -0
- package/dist/provider/provider-profile.d.ts +53 -0
- package/dist/provider/provider-profile.js +91 -0
- package/dist/translation/identifier.addressing.d.ts +80 -0
- package/dist/translation/identifier.addressing.js +30 -0
- package/dist/translation/mutation.translator.d.ts +115 -0
- package/dist/translation/mutation.translator.js +580 -0
- package/dist/translation/operation.translator.d.ts +273 -0
- package/dist/translation/operation.translator.js +377 -0
- package/dist/translation/order-pagination.translator.d.ts +86 -0
- package/dist/translation/order-pagination.translator.js +159 -0
- package/dist/translation/projection.translator.d.ts +115 -0
- package/dist/translation/projection.translator.js +151 -0
- package/dist/translation/scalar.conversion.d.ts +101 -0
- package/dist/translation/scalar.conversion.js +221 -0
- package/dist/translation/where.translator.d.ts +79 -0
- package/dist/translation/where.translator.js +254 -0
- 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>;
|