@aventara/prisma7-adapter 0.1.0-pilot.1 → 0.1.0-pilot.3
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/README.md +33 -20
- package/dist/discovery/artifact.loader.d.ts +6 -2
- package/dist/discovery/artifact.loader.js +12 -57
- package/dist/discovery/capability-surface.interface.d.ts +29 -40
- package/dist/discovery/capability-surface.interface.js +0 -12
- package/dist/discovery/capability.deriver.d.ts +10 -40
- package/dist/discovery/capability.deriver.js +0 -179
- package/dist/discovery/client-shape.verifier.d.ts +13 -0
- package/dist/discovery/client-shape.verifier.js +93 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +55 -30
- package/dist/discovery/enum.mapper.d.ts +6 -14
- package/dist/discovery/enum.mapper.js +1 -2
- package/dist/discovery/field.mapper.d.ts +49 -63
- package/dist/discovery/field.mapper.js +0 -161
- package/dist/discovery/identifier.mapper.d.ts +38 -77
- package/dist/discovery/identifier.mapper.js +0 -64
- package/dist/discovery/model.discoverer.d.ts +18 -35
- package/dist/discovery/model.discoverer.js +66 -141
- package/dist/discovery/operations.deriver.d.ts +10 -38
- package/dist/discovery/operations.deriver.js +0 -40
- package/dist/discovery/scalar.mapper.d.ts +12 -35
- package/dist/discovery/scalar.mapper.js +0 -8
- package/dist/discovery/schema-facts.interface.d.ts +26 -25
- package/dist/discovery/schema-facts.interface.js +0 -10
- package/dist/discovery/schema.reader.d.ts +53 -64
- package/dist/discovery/schema.reader.js +0 -174
- package/dist/discovery/types.reader.d.ts +0 -4
- package/dist/discovery/types.reader.js +2 -177
- package/dist/discovery/vocabulary.dictionary.d.ts +36 -75
- package/dist/discovery/vocabulary.dictionary.js +2 -152
- package/dist/errors/error.normalizer.d.ts +85 -135
- package/dist/errors/error.normalizer.js +1 -70
- package/dist/errors/prisma-error.reader.d.ts +53 -68
- package/dist/errors/prisma-error.reader.js +0 -91
- package/dist/execution/first-variant.emulator.d.ts +11 -22
- package/dist/execution/first-variant.emulator.js +0 -142
- package/dist/execution/operation.executor.d.ts +12 -19
- package/dist/execution/operation.executor.js +0 -92
- package/dist/execution/result.shaper.d.ts +22 -31
- package/dist/execution/result.shaper.js +0 -24
- package/dist/execution/transaction.executor.d.ts +36 -73
- package/dist/generate/artifact.emitter.d.ts +27 -27
- package/dist/generate/artifact.emitter.js +30 -13
- package/dist/generate/declared-support.reader.d.ts +11 -11
- package/dist/generate/declared-support.reader.js +0 -12
- package/dist/generate/discovery.bin.js +0 -10
- package/dist/generate/discovery.cli.js +0 -55
- package/dist/generate/existence.gate.d.ts +26 -32
- package/dist/generate/existence.gate.js +0 -6
- package/dist/generate/index.d.ts +10 -12
- package/dist/generate/index.js +0 -42
- package/dist/generate/node-version.guard.js +0 -12
- package/dist/generate/prisma-cli.resolver.d.ts +9 -11
- package/dist/generate/prisma-cli.resolver.js +0 -13
- package/dist/generate/prisma.generate.d.ts +49 -65
- package/dist/generate/prisma.generate.js +13 -123
- package/dist/generate/reader-crosscheck.d.ts +36 -60
- package/dist/generate/reader-crosscheck.js +5 -41
- package/dist/prisma-client.interface.d.ts +14 -19
- package/dist/prisma-client.interface.js +0 -6
- package/dist/prisma.adapter.d.ts +9 -17
- package/dist/prisma.adapter.js +11 -53
- package/dist/provider/provider-profile.d.ts +6 -23
- package/dist/provider/provider-profile.js +0 -64
- package/dist/translation/identifier.addressing.d.ts +15 -22
- package/dist/translation/identifier.addressing.js +0 -10
- package/dist/translation/mutation.translator.d.ts +19 -43
- package/dist/translation/mutation.translator.js +1 -209
- package/dist/translation/operation.translator.d.ts +36 -63
- package/dist/translation/operation.translator.js +1 -51
- package/dist/translation/order-pagination.translator.d.ts +24 -31
- package/dist/translation/order-pagination.translator.js +0 -43
- package/dist/translation/projection.translator.d.ts +25 -58
- package/dist/translation/projection.translator.js +0 -27
- package/dist/translation/scalar.conversion.d.ts +15 -26
- package/dist/translation/scalar.conversion.js +1 -129
- package/dist/translation/where.translator.d.ts +17 -28
- package/dist/translation/where.translator.js +0 -83
- package/package.json +4 -3
|
@@ -1,56 +1,7 @@
|
|
|
1
1
|
import { PRISMA_EDGE_ARGUMENTS, PRISMA_FILTER_OPERATORS, PRISMA_REDUCERS, PRISMA_RELATION_DIRECTIVES, PRISMA_RELATION_FILTERS, PRISMA_SCALAR_DIRECTIVES, } from "./vocabulary.dictionary.js";
|
|
2
|
-
/**
|
|
3
|
-
* T-6 — field capabilities, derived from the generated client's own types.
|
|
4
|
-
*
|
|
5
|
-
* # The input is narrower than T-6 writes it, and the narrowing IS the invariant
|
|
6
|
-
*
|
|
7
|
-
* T-6 spells `ScalarCapabilityInput` as `{ field, surface, mapping, profile }`.
|
|
8
|
-
* This module accepts `surface` alone, and that is deliberate: dictionary
|
|
9
|
-
* invariant 6 says a capability is read from the client's own types and never
|
|
10
|
-
* tabulated from a provider row, and the strongest available mechanism for that is
|
|
11
|
-
* to make the provider **unrepresentable** rather than merely unread. A
|
|
12
|
-
* `PrismaProviderProfile` in this input is a `provider` member one keystroke from
|
|
13
|
-
* a branch; with no such member, `if (provider === …)` is a compile error, and the
|
|
14
|
-
* three per-engine differences this pass exists to prove — `mode`, `JsonFilter`'s
|
|
15
|
-
* ordering operators, and the `path` / `in` / `notIn` operand shapes — have to come
|
|
16
|
-
* from the surface because nothing else is in scope.
|
|
17
|
-
*
|
|
18
|
-
* Dropping `field` has the same effect on C-812: a lifecycle trait cannot grant a
|
|
19
|
-
* capability if no lifecycle trait can be passed. `mapping` went with them because
|
|
20
|
-
* nothing here reads an Aventara scalar identity to answer a capability question.
|
|
21
|
-
*
|
|
22
|
-
* **This is a deviation from T-6 and is reported as one.** It predicted that the
|
|
23
|
-
* `path` question — omit, or translate, where the operand is a JSONPath string —
|
|
24
|
-
* would need `mapping` back. **Task 7 settled it and did not:** see
|
|
25
|
-
* {@link translatable}. The answer is read off the recorded operand shape, which
|
|
26
|
-
* is already on the surface; the Aventara scalar identity would have been a
|
|
27
|
-
* second, weaker source for the same fact. `mapping` stays out.
|
|
28
|
-
*
|
|
29
|
-
* # Order is canonical, not incidental
|
|
30
|
-
*
|
|
31
|
-
* Every list is emitted in **core's own union declaration order**, held below as a
|
|
32
|
-
* closed ordered array. Prisma's declaration order is an input, not a semantic
|
|
33
|
-
* fact, and C-843 wants two generate runs byte-identical; ordering by the Aventara
|
|
34
|
-
* union makes the output independent of how a generator happens to write a member
|
|
35
|
-
* list, while keeping the array reviewable against `contracts/capability.ts`.
|
|
36
|
-
*
|
|
37
|
-
* # Absent and empty are different answers
|
|
38
|
-
*
|
|
39
|
-
* `create: []` / `update: []` mean **writable with no directive** — plain
|
|
40
|
-
* assignment, which is what `set` alone is. An absent `create` / `update` means
|
|
41
|
-
* **not writable**, and core enforces exactly that: `validateScalarMutation`
|
|
42
|
-
* answers `V1006` *"is not writable during <stage>"* when the member is
|
|
43
|
-
* `undefined` **[CORE, operations/operation-validator-core.ts]**. So the two must
|
|
44
|
-
* not be conflated, and the surface's `null` (the client exposes no entry for this
|
|
45
|
-
* column at this stage) is the only thing that produces the absent form.
|
|
46
|
-
*/
|
|
47
|
-
/** A Prisma spelling that no dictionary table recognises: a build stop, never a drop. */
|
|
48
2
|
export class PrismaCapabilityDerivationError extends Error {
|
|
49
3
|
name = "PrismaCapabilityDerivationError";
|
|
50
4
|
}
|
|
51
|
-
/* ------------------------------------------------------------------ *
|
|
52
|
-
* Canonical orders — core's own union declaration order [CORE] *
|
|
53
|
-
* ------------------------------------------------------------------ */
|
|
54
5
|
const FILTER_OPERATOR_ORDER = [
|
|
55
6
|
"equals",
|
|
56
7
|
"not",
|
|
@@ -107,9 +58,6 @@ const PAGINATION_ORDER = [
|
|
|
107
58
|
"offset",
|
|
108
59
|
"cursor",
|
|
109
60
|
];
|
|
110
|
-
/* ------------------------------------------------------------------ *
|
|
111
|
-
* Scalars *
|
|
112
|
-
* ------------------------------------------------------------------ */
|
|
113
61
|
export function deriveScalarCapabilities(input) {
|
|
114
62
|
const { surface } = input;
|
|
115
63
|
const operands = deriveScalarFilterOperands(input);
|
|
@@ -117,34 +65,14 @@ export function deriveScalarCapabilities(input) {
|
|
|
117
65
|
.map((key) => look(PRISMA_FILTER_OPERATORS, key, "a scalar filter operator"))
|
|
118
66
|
.filter((operator) => translatable(operator, operands)));
|
|
119
67
|
return {
|
|
120
|
-
// The column is in `$<M>Payload.scalars`, which is the set the client
|
|
121
|
-
// actually returns — the existence gate's own evidence (T-3.1 invariant 3).
|
|
122
68
|
select: true,
|
|
123
69
|
...(filter.length > 0 ? { filter } : {}),
|
|
124
70
|
order: surface.orderable,
|
|
125
|
-
// C-836: only where `SortOrderInput` is admitted for THIS field. Absent, not
|
|
126
|
-
// `false`, elsewhere — core reads `orderNulls !== true` either way, and an
|
|
127
|
-
// absent member keeps the canonical form free of negative facts.
|
|
128
71
|
...(surface.nullsOrderable ? { orderNulls: true } : {}),
|
|
129
72
|
...directives("create", surface.createOperations),
|
|
130
73
|
...directives("update", surface.updateOperations),
|
|
131
74
|
};
|
|
132
75
|
}
|
|
133
|
-
/**
|
|
134
|
-
* The Prisma operand for each ADVERTISED operator, keyed by the Aventara name.
|
|
135
|
-
*
|
|
136
|
-
* T-6's three signatures return core types unchanged, and `ScalarFieldCapabilities`
|
|
137
|
-
* has nowhere to put an operand shape — yet the shape is exactly where two engines
|
|
138
|
-
* differ on keys they both carry (`JsonFilter.path` is `string[]` on one and
|
|
139
|
-
* `string` on the other; `in`/`notIn` admit `List<Kind>FieldRefInput` on one only).
|
|
140
|
-
* A deriver that answered operator NAMES alone would report the two engines
|
|
141
|
-
* identical where they are not, so the shape is carried beside the capability
|
|
142
|
-
* rather than discarded.
|
|
143
|
-
*
|
|
144
|
-
* Keyed by the Aventara operator so Task 7 reads a shape without re-running the
|
|
145
|
-
* rename — and so a provider branch has nothing to be for (§Open question,
|
|
146
|
-
* "How the `path` filter operator is emitted per provider").
|
|
147
|
-
*/
|
|
148
76
|
export function deriveScalarFilterOperands(input) {
|
|
149
77
|
const operands = {};
|
|
150
78
|
for (const operator of FILTER_OPERATOR_ORDER) {
|
|
@@ -157,41 +85,12 @@ export function deriveScalarFilterOperands(input) {
|
|
|
157
85
|
}
|
|
158
86
|
return operands;
|
|
159
87
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Whether an advertised operator's OPERAND can be honoured — Task 7's half of
|
|
162
|
-
* §9.2's rule that *"JSON path/content operators are exposed only where provider
|
|
163
|
-
* behavior can be mapped reliably"*.
|
|
164
|
-
*
|
|
165
|
-
* Exactly one operator reaches this test today, and it is the one the plan left
|
|
166
|
-
* open (§12, "How the `path` filter operator is emitted per provider"):
|
|
167
|
-
* `JsonFilterBase.path` is `string[]` on one engine and a JSONPath `string` on
|
|
168
|
-
* the other, measured 2026-10-02 at 7.10.0 — and the engines enforce it at
|
|
169
|
-
* RUNTIME, not only in the types:
|
|
170
|
-
*
|
|
171
|
-
* - PostgreSQL: *"Argument `path`: Invalid value provided. Expected String[], provided String."*
|
|
172
|
-
* - SQLite: *"Argument `path`: Invalid value provided. Expected String, provided (String)."*
|
|
173
|
-
*
|
|
174
|
-
* So one canonical Aventara operand cannot be carried to both, and the choice is
|
|
175
|
-
* §9.2's other ending: **the operator is not advertised where the operand is the
|
|
176
|
-
* JSONPath string**. Translating instead would mean synthesizing a JSONPath from
|
|
177
|
-
* a segment list — an escaping and array-index dialect nobody has measured — and
|
|
178
|
-
* a translation that is wrong on `{"a.b": …}` is the silent behaviour change
|
|
179
|
-
* C-855 exists to prevent. Omission is expressible in
|
|
180
|
-
* `ScalarFieldCapabilities.filter` exactly as it stands, so nothing escalates to
|
|
181
|
-
* core.
|
|
182
|
-
*
|
|
183
|
-
* The decision is read off the recorded operand SHAPE and nothing else. No
|
|
184
|
-
* `provider` is in scope here, by construction (see this module's header), and no
|
|
185
|
-
* Aventara scalar identity is needed either: T-6's `mapping` would be a second,
|
|
186
|
-
* weaker source for a fact the operand already states.
|
|
187
|
-
*/
|
|
188
88
|
function translatable(operator, operands) {
|
|
189
89
|
if (operator !== "path") {
|
|
190
90
|
return true;
|
|
191
91
|
}
|
|
192
92
|
return admitsStringList(operands.path);
|
|
193
93
|
}
|
|
194
|
-
/** Whether a written operand type admits a list of strings. */
|
|
195
94
|
function admitsStringList(written) {
|
|
196
95
|
if (written === undefined) {
|
|
197
96
|
return false;
|
|
@@ -203,7 +102,6 @@ function admitsStringList(written) {
|
|
|
203
102
|
}
|
|
204
103
|
function directives(stage, operations) {
|
|
205
104
|
if (operations === null) {
|
|
206
|
-
// The client exposes no entry for this column at this stage: not writable.
|
|
207
105
|
return {};
|
|
208
106
|
}
|
|
209
107
|
return {
|
|
@@ -212,35 +110,6 @@ function directives(stage, operations) {
|
|
|
212
110
|
.filter((directive) => directive !== null)),
|
|
213
111
|
};
|
|
214
112
|
}
|
|
215
|
-
/* ------------------------------------------------------------------ *
|
|
216
|
-
* Relations *
|
|
217
|
-
* ------------------------------------------------------------------ */
|
|
218
|
-
/* ------------------------------------------------------------------ *
|
|
219
|
-
* The scope arms — the narrowest encoding that is still honest *
|
|
220
|
-
* ------------------------------------------------------------------ */
|
|
221
|
-
/**
|
|
222
|
-
* The projection answer for one member: the **unqualified** arm where the
|
|
223
|
-
* capability holds in every scope, the qualified arm only where it is genuinely
|
|
224
|
-
* narrower, and `false` where it holds nowhere.
|
|
225
|
-
*
|
|
226
|
-
* # Why the full list is never emitted
|
|
227
|
-
*
|
|
228
|
-
* `RelationProjectionCapability` is a union, and a list naming every
|
|
229
|
-
* `ProjectionScope` says exactly what `true` says. Emitting it would put one
|
|
230
|
-
* semantic fact into two encodings — one fact with two hashes, which is C-843 and
|
|
231
|
-
* `AGENT.md`'s one-source-of-truth rule, and the ground on which the architect
|
|
232
|
-
* rejected the flat-or-per-variant option. So the degenerate list is not produced
|
|
233
|
-
* here at all. S4's canonicalizing collapse is the safety net for a *different*
|
|
234
|
-
* producer; this one does not need it.
|
|
235
|
-
*
|
|
236
|
-
* # Non-emptiness
|
|
237
|
-
*
|
|
238
|
-
* `ProjectionScopes` is a non-empty tuple, so "holds nowhere" cannot be spelled as
|
|
239
|
-
* `[]`. It is `false` — *the capability is absent*, which is the answer this module
|
|
240
|
-
* already gave before any scope existed and the answer `absent ≠ empty` requires.
|
|
241
|
-
* Every array below is a literal with a written member, so the tuple's lower bound
|
|
242
|
-
* is discharged by construction rather than by an assertion.
|
|
243
|
-
*/
|
|
244
113
|
function projectableIn(unrestricted, bulk) {
|
|
245
114
|
if (unrestricted && bulk) {
|
|
246
115
|
return true;
|
|
@@ -253,25 +122,9 @@ function projectableIn(unrestricted, bulk) {
|
|
|
253
122
|
}
|
|
254
123
|
return false;
|
|
255
124
|
}
|
|
256
|
-
/**
|
|
257
|
-
* The reducer answer: the bare reducer list where the reducers hold in every
|
|
258
|
-
* projection scope, the scoped form where they are narrower.
|
|
259
|
-
*
|
|
260
|
-
* The reducer list and the scope list stay separate facts, so `[]` keeps meaning
|
|
261
|
-
* what it means and the scope says only where the reducers may be asked for.
|
|
262
|
-
*/
|
|
263
125
|
function reducersIn(reducers, bulk) {
|
|
264
126
|
return bulk ? reducers : { reducers, scopes: ["record"] };
|
|
265
127
|
}
|
|
266
|
-
/**
|
|
267
|
-
* The write answer for one stage, on the same rule: the bare directive list where
|
|
268
|
-
* the stage holds in every write scope, the scoped form where it is narrower.
|
|
269
|
-
*
|
|
270
|
-
* The directive list is carried through rather than replaced, because `[]` already
|
|
271
|
-
* means *"writable with no directive"* and an absent member means *"not writable
|
|
272
|
-
* during this stage"* — two live answers core distinguishes (`V1006`), and a scope
|
|
273
|
-
* qualifier must not collapse them into a third.
|
|
274
|
-
*/
|
|
275
128
|
function writableIn(directives, bulk) {
|
|
276
129
|
return bulk ? directives : { directives, scopes: ["nested"] };
|
|
277
130
|
}
|
|
@@ -282,12 +135,8 @@ export function deriveRelationCapabilities(input) {
|
|
|
282
135
|
look(PRISMA_EDGE_ARGUMENTS, argument, "an edge argument");
|
|
283
136
|
}
|
|
284
137
|
const filter = canonical(RELATION_FILTER_ORDER, [
|
|
285
|
-
// The edge's own `where` argument is what core calls `nested`: filtering the
|
|
286
|
-
// RETURNED edge **[CORE, operations/operation-validator-core.ts]**.
|
|
287
138
|
...(edgeArguments.has("where") ? ["nested"] : []),
|
|
288
139
|
...surface.filters.map((key) => look(PRISMA_RELATION_FILTERS, key, "a relation filter")),
|
|
289
|
-
// A null absence predicate needs a nullable edge; `[Nullable]ScalarRelationFilter`
|
|
290
|
-
// carries the same two keys either way, so nullability is the discriminant.
|
|
291
140
|
...(surface.nullable ? ["null"] : []),
|
|
292
141
|
]);
|
|
293
142
|
const pagination = canonical(PAGINATION_ORDER, surface.pagination
|
|
@@ -298,10 +147,6 @@ export function deriveRelationCapabilities(input) {
|
|
|
298
147
|
.map((key) => look(PRISMA_RELATION_DIRECTIVES, key, "a relation mutation directive"))
|
|
299
148
|
.filter((directive) => directive !== null));
|
|
300
149
|
return {
|
|
301
|
-
// The edge's own `select` / `include` arguments answer the UNRESTRICTED
|
|
302
|
-
// position; the surface's `bulkSelect` / `bulkInclude` answer the bulk one.
|
|
303
|
-
// Both are presence reads: see `PrismaRelationSurface.bulkSelect` for why the
|
|
304
|
-
// foreign-key rule is the explanation and not the mechanism.
|
|
305
150
|
select: projectableIn(edgeArguments.has("select"), surface.bulkSelect),
|
|
306
151
|
include: projectableIn(edgeArguments.has("include"), surface.bulkInclude),
|
|
307
152
|
...(filter.length > 0 ? { filter } : {}),
|
|
@@ -310,40 +155,16 @@ export function deriveRelationCapabilities(input) {
|
|
|
310
155
|
...(reducers.length > 0
|
|
311
156
|
? { reducers: reducersIn(reducers, surface.bulkReducers) }
|
|
312
157
|
: {}),
|
|
313
|
-
// C-818, now EXPRESSIBLE rather than recorded as a conflict: the bulk create
|
|
314
|
-
// input carries no entry for this edge, so the create stage is advertised at
|
|
315
|
-
// the `nested` scope and nowhere else. Composition no longer has to choose
|
|
316
|
-
// between the two create variants' answers, and nothing is advertised to
|
|
317
|
-
// `create.many` that Prisma has no key for.
|
|
318
|
-
//
|
|
319
|
-
// C-855: each stage is still derived from ITS OWN nested input. The
|
|
320
|
-
// create-stage input admits `create` / `connectOrCreate` / `createMany` /
|
|
321
|
-
// `connect` and nothing more, so reusing the update-stage list here advertised
|
|
322
|
-
// five directives per to-many edge — and two per to-one edge — that Prisma
|
|
323
|
-
// rejects outright and `mutation.translator.ts` therefore refuses.
|
|
324
158
|
create: writableIn(directives(surface.createMutations), surface.bulkCreatable),
|
|
325
159
|
update: writableIn(directives(surface.updateMutations), surface.bulkUpdatable),
|
|
326
160
|
};
|
|
327
161
|
}
|
|
328
|
-
/* ------------------------------------------------------------------ *
|
|
329
|
-
* The two shared mechanics *
|
|
330
|
-
* ------------------------------------------------------------------ */
|
|
331
|
-
/**
|
|
332
|
-
* A dictionary lookup that refuses rather than returning `undefined`.
|
|
333
|
-
*
|
|
334
|
-
* A Prisma key no table recognises is a vocabulary gap — a 7.11 spelling, a
|
|
335
|
-
* provider feature nobody measured — and dropping one silently is precisely
|
|
336
|
-
* C-855's failure mode seen from the discovery side: a capability that quietly
|
|
337
|
-
* does not exist. The dictionary's `null` is the deliberate "no Aventara word"
|
|
338
|
-
* answer and passes through.
|
|
339
|
-
*/
|
|
340
162
|
function look(table, key, what) {
|
|
341
163
|
if (!(key in table)) {
|
|
342
164
|
throw new PrismaCapabilityDerivationError(`the generated types offer "${key}" as ${what} and the dictionary has no row for it`);
|
|
343
165
|
}
|
|
344
166
|
return table[key];
|
|
345
167
|
}
|
|
346
|
-
/** The members present, in the canonical order, deduplicated. */
|
|
347
168
|
function canonical(order, present) {
|
|
348
169
|
const seen = new Set(present);
|
|
349
170
|
return order.filter((member) => seen.has(member));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PrismaClientShape } from "./discovery-artifact.interface.js";
|
|
2
|
+
/**
|
|
3
|
+
* What differs between the client the artifact recorded and the one in hand, as
|
|
4
|
+
* short phrases — empty when they agree.
|
|
5
|
+
*/
|
|
6
|
+
export declare function describeClientDrift(recorded: PrismaClientShape, client: unknown): readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Throws when the client in hand is not the one the artifact was generated
|
|
9
|
+
* against — one sentence, naming what differs and `aventara:prepare`.
|
|
10
|
+
*
|
|
11
|
+
* @throws PrismaDiscoveryArtifactError
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertClientMatchesArtifact(recorded: PrismaClientShape, client: unknown): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { delegateKeyOf } from "../execution/operation.executor.js";
|
|
2
|
+
import { PrismaDiscoveryArtifactError } from "./artifact.loader.js";
|
|
3
|
+
const SHOWN = 3;
|
|
4
|
+
function readMember(value, key) {
|
|
5
|
+
if ((typeof value !== "object" && typeof value !== "function") ||
|
|
6
|
+
value === null) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
return value[key];
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function ownKeys(value) {
|
|
17
|
+
if (typeof value !== "object" || value === null) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return Reflect.ownKeys(value).filter((key) => typeof key === "string");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function delegateModelName(candidate) {
|
|
28
|
+
const name = readMember(candidate, "$name");
|
|
29
|
+
return typeof name === "string" &&
|
|
30
|
+
typeof readMember(candidate, "findMany") === "function"
|
|
31
|
+
? name
|
|
32
|
+
: undefined;
|
|
33
|
+
}
|
|
34
|
+
function observedModels(client) {
|
|
35
|
+
const models = new Set();
|
|
36
|
+
for (const key of ownKeys(client)) {
|
|
37
|
+
if (key.startsWith("$") || key.startsWith("_") || key === "constructor") {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const name = delegateModelName(readMember(client, key));
|
|
41
|
+
if (name !== undefined) {
|
|
42
|
+
models.add(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return models;
|
|
46
|
+
}
|
|
47
|
+
export function describeClientDrift(recorded, client) {
|
|
48
|
+
const drift = [];
|
|
49
|
+
const live = observedModels(client);
|
|
50
|
+
const recognised = Object.keys(recorded).filter((model) => delegateModelName(readMember(client, delegateKeyOf(model))) === model);
|
|
51
|
+
if (live.size === 0 && recognised.length === 0) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
if (live.size > 0) {
|
|
55
|
+
for (const model of [...live].sort()) {
|
|
56
|
+
if (!Object.hasOwn(recorded, model)) {
|
|
57
|
+
drift.push(`model \`${model}\` is in the client and not in the artifact`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const model of Object.keys(recorded).sort()) {
|
|
62
|
+
const delegate = readMember(client, delegateKeyOf(model));
|
|
63
|
+
if (delegateModelName(delegate) !== model) {
|
|
64
|
+
drift.push(`model \`${model}\` is in the artifact and not in the client`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const fields = new Set(ownKeys(readMember(delegate, "fields")));
|
|
68
|
+
if (fields.size === 0) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const expected = new Set(recorded[model]);
|
|
72
|
+
for (const field of [...fields].sort()) {
|
|
73
|
+
if (!expected.has(field)) {
|
|
74
|
+
drift.push(`field \`${model}.${field}\` is in the client and not in the artifact`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const field of [...expected].sort()) {
|
|
78
|
+
if (!fields.has(field)) {
|
|
79
|
+
drift.push(`field \`${model}.${field}\` is in the artifact and not in the client`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return drift;
|
|
84
|
+
}
|
|
85
|
+
export function assertClientMatchesArtifact(recorded, client) {
|
|
86
|
+
const drift = describeClientDrift(recorded, client);
|
|
87
|
+
if (drift.length === 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const shown = drift.slice(0, SHOWN).join("; ");
|
|
91
|
+
const more = drift.length > SHOWN ? `; and ${drift.length - SHOWN} more` : "";
|
|
92
|
+
throw new PrismaDiscoveryArtifactError(`The Prisma client has changed since the discovery artifact was generated (${shown}${more}):`, "run `npm run aventara:prepare` (the adapter's `aventara-prisma7-generate`) to regenerate it, then restart.");
|
|
93
|
+
}
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import type { AdapterResource, EnumDefinition, ScalarRegistry, TransactionCapability } from "@aventara/core";
|
|
1
|
+
import type { Adapter, AdapterResource, EnumDefinition, ScalarRegistry, TransactionCapability } from "@aventara/core";
|
|
2
2
|
import type { PrismaProvider } from "../provider/provider-profile.js";
|
|
3
3
|
/**
|
|
4
|
-
* T-4 — the discovery output, produced and never redefined.
|
|
5
|
-
*
|
|
6
4
|
* A plain `type`, not `interface … extends`: `AdapterModel.resources` is
|
|
7
5
|
* self-typed `AdapterResource<this>`, and a nominal `extends` hits TS2430.
|
|
8
|
-
* Phase 8 adds no member.
|
|
9
|
-
*
|
|
10
|
-
* (§9 of the plan names no owning file for this alias. It lives here because
|
|
11
|
-
* this is the Task 2 file that needs it — the artifact is what carries it.)
|
|
12
6
|
*/
|
|
13
7
|
export type PrismaAdapterModel = {
|
|
14
8
|
readonly scalars: ScalarRegistry;
|
|
@@ -152,33 +146,54 @@ export type PrismaResourceReferentialActions = {
|
|
|
152
146
|
* part of the model. Putting it inside would push referential-action vocabulary
|
|
153
147
|
* into the compiled Contract and the ClientContract, where core has no word for
|
|
154
148
|
* it, and would move the Contract identity of every schema that has a relation.
|
|
155
|
-
*
|
|
156
|
-
* Phase 8 owns the half that makes the facts exist and readable; the core seam
|
|
157
|
-
* that plans a cascade from them is Phase 9's (S10): `createPrismaAdapter`
|
|
158
|
-
* translates this table into core's words and states it as the adapter's
|
|
159
|
-
* `referentialActions`, and core builds the closure from that.
|
|
160
149
|
*/
|
|
161
150
|
export type PrismaReferentialActionTable<M extends PrismaAdapterModel = PrismaAdapterModel> = {
|
|
162
151
|
readonly [R in keyof M["resources"]]: PrismaResourceReferentialActions;
|
|
163
152
|
};
|
|
164
153
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
154
|
+
* What the generated Prisma client exposed when the artifact was generated: each
|
|
155
|
+
* model it has a delegate for, keyed by the model's name, and that model's field
|
|
156
|
+
* references (`prisma.<model>.fields`) in code-unit order.
|
|
157
|
+
*
|
|
158
|
+
* It is the artifact's record of the client it was read from, and the runtime
|
|
159
|
+
* reads the same two facts off the client it is handed — the delegates the client
|
|
160
|
+
* carries, and each one's public `fields`. When they disagree, the schema changed
|
|
161
|
+
* and the client was regenerated without the artifact, and the adapter refuses to
|
|
162
|
+
* start rather than serve a model that no longer matches. Prisma excludes
|
|
163
|
+
* `Unsupported(…)` and `@ignore`d fields and `@@ignore`d models from both the
|
|
164
|
+
* generated types and the runtime client, so the two readings agree by
|
|
165
|
+
* construction on an unchanged schema.
|
|
166
|
+
*
|
|
167
|
+
* Beside the model and outside `modelHash`, like the translation table: it
|
|
168
|
+
* describes the provider client, not the semantic model.
|
|
169
|
+
*/
|
|
170
|
+
export type PrismaClientShape = Readonly<Record<string, readonly string[]>>;
|
|
171
|
+
/**
|
|
172
|
+
* What a create of each Resource cannot do without, already in core's words
|
|
173
|
+
* (`Adapter["createRequirements"]`): a column the client must write, or a
|
|
174
|
+
* required relation that may instead be written through the scalar fields that
|
|
175
|
+
* hold its reference — and the relation's other end, through which a nested
|
|
176
|
+
* create supplies it.
|
|
177
|
+
*
|
|
178
|
+
* Read at generate time from the generated client's create inputs — the
|
|
179
|
+
* question marks on `<M>UncheckedCreateInput` and `<M>CreateInput` — and from the
|
|
180
|
+
* schema's `@relation(fields: …)` pairing. Beside the model and outside
|
|
181
|
+
* `modelHash`: a requirement is a fact about the store, not about the semantic
|
|
182
|
+
* model, and the Contract carries no foreign-key detail.
|
|
183
|
+
*/
|
|
184
|
+
export type PrismaCreateRequirementTable = NonNullable<Adapter["createRequirements"]>;
|
|
185
|
+
/**
|
|
186
|
+
* One module written `as const satisfies AdapterModel` makes value and type the
|
|
187
|
+
* same literal, and makes drift unrepresentable rather than merely detectable.
|
|
188
|
+
*
|
|
189
|
+
* It is still passive, serializable, introspectable, hashable and free of runtime
|
|
190
|
+
* callbacks: a `.ts` module whose only expression is a frozen object literal is
|
|
191
|
+
* JSON that carries its own type.
|
|
192
|
+
*
|
|
193
|
+
* `M` is the reason this type is generic at all. The emitted module's `model` is a
|
|
194
|
+
* literal type; if this alias pinned `model` to the general {@link
|
|
195
|
+
* PrismaAdapterModel}, every resource key and field key would be erased to
|
|
196
|
+
* `string` on the way to `createPrismaAdapter`, `EffectiveFieldKey` would
|
|
182
197
|
* degenerate, and core's whole config-typing workstream would buy nothing —
|
|
183
198
|
* silently, with no compile error anywhere. `model: M` is a bare property
|
|
184
199
|
* position, which is what makes `M` actually infer at the call site;
|
|
@@ -188,7 +203,7 @@ export type PrismaReferentialActionTable<M extends PrismaAdapterModel = PrismaAd
|
|
|
188
203
|
* "some Prisma adapter model".
|
|
189
204
|
*/
|
|
190
205
|
export type PrismaDiscoveryArtifact<M extends PrismaAdapterModel = PrismaAdapterModel> = {
|
|
191
|
-
/** e.g. `"7.10.0"`; only the major is fenced
|
|
206
|
+
/** e.g. `"7.10.0"`; only the major is fenced. */
|
|
192
207
|
readonly prismaVersion: string;
|
|
193
208
|
readonly provider: PrismaProvider;
|
|
194
209
|
/** `sha256:<hex>` of the canonicalized `model` below — and of nothing else. */
|
|
@@ -207,4 +222,14 @@ export type PrismaDiscoveryArtifact<M extends PrismaAdapterModel = PrismaAdapter
|
|
|
207
222
|
* `R` says **deleting a `target` row does `onDelete` to rows of `R`**.
|
|
208
223
|
*/
|
|
209
224
|
readonly referentialActions: PrismaReferentialActionTable<M>;
|
|
225
|
+
/**
|
|
226
|
+
* The client this artifact was generated against, beside the model and
|
|
227
|
+
* outside `modelHash`: see {@link PrismaClientShape}.
|
|
228
|
+
*/
|
|
229
|
+
readonly client: PrismaClientShape;
|
|
230
|
+
/**
|
|
231
|
+
* The create requirements, beside the model and outside `modelHash`: see
|
|
232
|
+
* {@link PrismaCreateRequirementTable}.
|
|
233
|
+
*/
|
|
234
|
+
readonly createRequirements: PrismaCreateRequirementTable;
|
|
210
235
|
};
|
|
@@ -2,20 +2,13 @@ import type { EnumDefinition } from "@aventara/core";
|
|
|
2
2
|
import type { PrismaSchemaEnum } from "./schema-facts.interface.js";
|
|
3
3
|
import type { PrismaDiscoveryDiagnostic } from "./vocabulary.dictionary.js";
|
|
4
4
|
/**
|
|
5
|
-
* PSL enums → `EnumDefinition` **[CORE]**, and the one line of C-810.
|
|
6
|
-
*
|
|
7
5
|
* # Declaration order is semantic, so nothing here sorts
|
|
8
6
|
*
|
|
9
|
-
* §19.2 puts enum declaration order among the arrays whose order survives
|
|
10
|
-
* canonicalization and enters the Contract hash. `definition.values` is therefore
|
|
11
|
-
* PSL's order, copied — the types are a **cross-check**, not the source, exactly
|
|
12
|
-
* as T-3.1's `enum values` row says.
|
|
13
|
-
*
|
|
14
7
|
* # What a cross-check failure means
|
|
15
8
|
*
|
|
16
9
|
* `enums.ts` and `schema.prisma` were read in the same instant by the same
|
|
17
|
-
* generate step
|
|
18
|
-
*
|
|
10
|
+
* generate step. If they disagree, one of them is stale, and the answer is
|
|
11
|
+
* `diverged` — a build stop — rather than a preference for either. The message
|
|
19
12
|
* prints both orders because "the enum diverged" without them costs the reader a
|
|
20
13
|
* second investigation.
|
|
21
14
|
*
|
|
@@ -24,8 +17,8 @@ import type { PrismaDiscoveryDiagnostic } from "./vocabulary.dictionary.js";
|
|
|
24
17
|
* PSL's reader discards it as bucket 2 (a database name, which Aventara has no
|
|
25
18
|
* word for), and the generated `enums.ts` writes the member name on *both* sides
|
|
26
19
|
* of a mapped member — `GAMMA: 'GAMMA'`, not `GAMMA: 'g'` **[RUN8 — 2026-09-28,
|
|
27
|
-
* prisma 7.10.0]**. So the cross-check compares Aventara identities on both
|
|
28
|
-
*
|
|
20
|
+
* prisma 7.10.0]**. So the cross-check compares Aventara identities on both sides,
|
|
21
|
+
* and the mapper has nothing to resurrect.
|
|
29
22
|
*/
|
|
30
23
|
export type EnumMappingInput = {
|
|
31
24
|
/** PSL — the source of the values and their order. */
|
|
@@ -45,9 +38,8 @@ export type EnumMappingOutcome = {
|
|
|
45
38
|
readonly diagnostic: PrismaDiscoveryDiagnostic;
|
|
46
39
|
};
|
|
47
40
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* structural kind cannot be introduced here without the type changing.
|
|
41
|
+
* The `kind` is a literal `"scalar"`, so a third structural kind cannot be
|
|
42
|
+
* introduced here without the type changing.
|
|
51
43
|
*/
|
|
52
44
|
export type EnumBackedColumn = {
|
|
53
45
|
readonly kind: "scalar";
|
|
@@ -16,14 +16,13 @@ export function mapPrismaEnum(input) {
|
|
|
16
16
|
outcome: "diverged",
|
|
17
17
|
diagnostic: {
|
|
18
18
|
code: "enum-diverged",
|
|
19
|
-
message: `enum \`${declared.name}\` reads [${declaredOrder}] in ${declared.site.file} and [${exposedOrder}] in the generated \`enums.ts\`. Declaration order is semantic
|
|
19
|
+
message: `enum \`${declared.name}\` reads [${declaredOrder}] in ${declared.site.file} and [${exposedOrder}] in the generated \`enums.ts\`. Declaration order is semantic and enters the Contract hash, so this is a stale generated client, not a cosmetic difference.`,
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
return {
|
|
24
24
|
outcome: "enum",
|
|
25
25
|
name: declared.name,
|
|
26
|
-
// PSL's order, copied. Never sorted, never taken from the cross-check.
|
|
27
26
|
definition: { values: [...declared.values] },
|
|
28
27
|
};
|
|
29
28
|
}
|