@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,350 @@
|
|
|
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
|
+
export class PrismaCapabilityDerivationError extends Error {
|
|
49
|
+
name = "PrismaCapabilityDerivationError";
|
|
50
|
+
}
|
|
51
|
+
/* ------------------------------------------------------------------ *
|
|
52
|
+
* Canonical orders — core's own union declaration order [CORE] *
|
|
53
|
+
* ------------------------------------------------------------------ */
|
|
54
|
+
const FILTER_OPERATOR_ORDER = [
|
|
55
|
+
"equals",
|
|
56
|
+
"not",
|
|
57
|
+
"in",
|
|
58
|
+
"notIn",
|
|
59
|
+
"lt",
|
|
60
|
+
"lte",
|
|
61
|
+
"gt",
|
|
62
|
+
"gte",
|
|
63
|
+
"contains",
|
|
64
|
+
"startsWith",
|
|
65
|
+
"endsWith",
|
|
66
|
+
"mode",
|
|
67
|
+
"has",
|
|
68
|
+
"hasEvery",
|
|
69
|
+
"hasSome",
|
|
70
|
+
"isEmpty",
|
|
71
|
+
"path",
|
|
72
|
+
"stringContains",
|
|
73
|
+
"stringStartsWith",
|
|
74
|
+
"stringEndsWith",
|
|
75
|
+
"arrayContains",
|
|
76
|
+
"arrayStartsWith",
|
|
77
|
+
"arrayEndsWith",
|
|
78
|
+
];
|
|
79
|
+
const SCALAR_DIRECTIVE_ORDER = [
|
|
80
|
+
"$increment",
|
|
81
|
+
"$decrement",
|
|
82
|
+
"$multiply",
|
|
83
|
+
"$divide",
|
|
84
|
+
"$push",
|
|
85
|
+
"$unset",
|
|
86
|
+
];
|
|
87
|
+
const RELATION_FILTER_ORDER = [
|
|
88
|
+
"nested",
|
|
89
|
+
"match",
|
|
90
|
+
"null",
|
|
91
|
+
"some",
|
|
92
|
+
"every",
|
|
93
|
+
"none",
|
|
94
|
+
];
|
|
95
|
+
const RELATION_DIRECTIVE_ORDER = [
|
|
96
|
+
"$create",
|
|
97
|
+
"$connect",
|
|
98
|
+
"$connectOrCreate",
|
|
99
|
+
"$disconnect",
|
|
100
|
+
"$set",
|
|
101
|
+
"$update",
|
|
102
|
+
"$delete",
|
|
103
|
+
"$upsert",
|
|
104
|
+
];
|
|
105
|
+
const PAGINATION_ORDER = [
|
|
106
|
+
"limit",
|
|
107
|
+
"offset",
|
|
108
|
+
"cursor",
|
|
109
|
+
];
|
|
110
|
+
/* ------------------------------------------------------------------ *
|
|
111
|
+
* Scalars *
|
|
112
|
+
* ------------------------------------------------------------------ */
|
|
113
|
+
export function deriveScalarCapabilities(input) {
|
|
114
|
+
const { surface } = input;
|
|
115
|
+
const operands = deriveScalarFilterOperands(input);
|
|
116
|
+
const filter = canonical(FILTER_OPERATOR_ORDER, surface.filters
|
|
117
|
+
.map((key) => look(PRISMA_FILTER_OPERATORS, key, "a scalar filter operator"))
|
|
118
|
+
.filter((operator) => translatable(operator, operands)));
|
|
119
|
+
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
|
+
select: true,
|
|
123
|
+
...(filter.length > 0 ? { filter } : {}),
|
|
124
|
+
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
|
+
...(surface.nullsOrderable ? { orderNulls: true } : {}),
|
|
129
|
+
...directives("create", surface.createOperations),
|
|
130
|
+
...directives("update", surface.updateOperations),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
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
|
+
export function deriveScalarFilterOperands(input) {
|
|
149
|
+
const operands = {};
|
|
150
|
+
for (const operator of FILTER_OPERATOR_ORDER) {
|
|
151
|
+
for (const [key, written] of Object.entries(input.surface.filterOperands)) {
|
|
152
|
+
if (look(PRISMA_FILTER_OPERATORS, key, "a scalar filter operator") ===
|
|
153
|
+
operator) {
|
|
154
|
+
operands[operator] = written;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return operands;
|
|
159
|
+
}
|
|
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
|
+
function translatable(operator, operands) {
|
|
189
|
+
if (operator !== "path") {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return admitsStringList(operands.path);
|
|
193
|
+
}
|
|
194
|
+
/** Whether a written operand type admits a list of strings. */
|
|
195
|
+
function admitsStringList(written) {
|
|
196
|
+
if (written === undefined) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return written
|
|
200
|
+
.split("|")
|
|
201
|
+
.map((member) => member.trim())
|
|
202
|
+
.includes("string[]");
|
|
203
|
+
}
|
|
204
|
+
function directives(stage, operations) {
|
|
205
|
+
if (operations === null) {
|
|
206
|
+
// The client exposes no entry for this column at this stage: not writable.
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
[stage]: canonical(SCALAR_DIRECTIVE_ORDER, operations
|
|
211
|
+
.map((key) => look(PRISMA_SCALAR_DIRECTIVES, key, "a scalar mutation directive"))
|
|
212
|
+
.filter((directive) => directive !== null)),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
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
|
+
function projectableIn(unrestricted, bulk) {
|
|
245
|
+
if (unrestricted && bulk) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if (unrestricted) {
|
|
249
|
+
return ["record"];
|
|
250
|
+
}
|
|
251
|
+
if (bulk) {
|
|
252
|
+
return ["bulkWrite"];
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
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
|
+
function reducersIn(reducers, bulk) {
|
|
264
|
+
return bulk ? reducers : { reducers, scopes: ["record"] };
|
|
265
|
+
}
|
|
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
|
+
function writableIn(directives, bulk) {
|
|
276
|
+
return bulk ? directives : { directives, scopes: ["nested"] };
|
|
277
|
+
}
|
|
278
|
+
export function deriveRelationCapabilities(input) {
|
|
279
|
+
const { surface } = input;
|
|
280
|
+
const edgeArguments = new Set(surface.edgeArguments);
|
|
281
|
+
for (const argument of edgeArguments) {
|
|
282
|
+
look(PRISMA_EDGE_ARGUMENTS, argument, "an edge argument");
|
|
283
|
+
}
|
|
284
|
+
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
|
+
...(edgeArguments.has("where") ? ["nested"] : []),
|
|
288
|
+
...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
|
+
...(surface.nullable ? ["null"] : []),
|
|
292
|
+
]);
|
|
293
|
+
const pagination = canonical(PAGINATION_ORDER, surface.pagination
|
|
294
|
+
.map((argument) => look(PRISMA_EDGE_ARGUMENTS, argument, "an edge argument"))
|
|
295
|
+
.filter((capability) => capability !== null));
|
|
296
|
+
const reducers = surface.reducers.map((key) => look(PRISMA_REDUCERS, key, "a relation reducer"));
|
|
297
|
+
const directives = (keys) => canonical(RELATION_DIRECTIVE_ORDER, keys
|
|
298
|
+
.map((key) => look(PRISMA_RELATION_DIRECTIVES, key, "a relation mutation directive"))
|
|
299
|
+
.filter((directive) => directive !== null));
|
|
300
|
+
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
|
+
select: projectableIn(edgeArguments.has("select"), surface.bulkSelect),
|
|
306
|
+
include: projectableIn(edgeArguments.has("include"), surface.bulkInclude),
|
|
307
|
+
...(filter.length > 0 ? { filter } : {}),
|
|
308
|
+
order: surface.orderable,
|
|
309
|
+
...(pagination.length > 0 ? { pagination } : {}),
|
|
310
|
+
...(reducers.length > 0
|
|
311
|
+
? { reducers: reducersIn(reducers, surface.bulkReducers) }
|
|
312
|
+
: {}),
|
|
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
|
+
create: writableIn(directives(surface.createMutations), surface.bulkCreatable),
|
|
325
|
+
update: writableIn(directives(surface.updateMutations), surface.bulkUpdatable),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
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
|
+
function look(table, key, what) {
|
|
341
|
+
if (!(key in table)) {
|
|
342
|
+
throw new PrismaCapabilityDerivationError(`the generated types offer "${key}" as ${what} and the dictionary has no row for it`);
|
|
343
|
+
}
|
|
344
|
+
return table[key];
|
|
345
|
+
}
|
|
346
|
+
/** The members present, in the canonical order, deduplicated. */
|
|
347
|
+
function canonical(order, present) {
|
|
348
|
+
const seen = new Set(present);
|
|
349
|
+
return order.filter((member) => seen.has(member));
|
|
350
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { AdapterResource, EnumDefinition, ScalarRegistry, TransactionCapability } from "@aventara/core";
|
|
2
|
+
import type { PrismaProvider } from "../provider/provider-profile.js";
|
|
3
|
+
/**
|
|
4
|
+
* T-4 — the discovery output, produced and never redefined.
|
|
5
|
+
*
|
|
6
|
+
* A plain `type`, not `interface … extends`: `AdapterModel.resources` is
|
|
7
|
+
* 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
|
+
*/
|
|
13
|
+
export type PrismaAdapterModel = {
|
|
14
|
+
readonly scalars: ScalarRegistry;
|
|
15
|
+
readonly enums: Readonly<Record<string, EnumDefinition>>;
|
|
16
|
+
readonly resources: Readonly<Record<string, AdapterResource<PrismaAdapterModel>>>;
|
|
17
|
+
readonly transactions: TransactionCapability;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* One identifier, and the **Prisma addressing key** that reaches it.
|
|
21
|
+
*
|
|
22
|
+
* `members` is the identifier as core carries it — derived from
|
|
23
|
+
* `AdapterResource` rather than restated, so a change to core's identifier type
|
|
24
|
+
* is a compile error here. `key` is the same criterion's *Prisma* spelling: the
|
|
25
|
+
* key a `WhereUniqueInput` accepts, and the key the generated `AtLeast<…, K>`
|
|
26
|
+
* union lists.
|
|
27
|
+
*
|
|
28
|
+
* The two are different facts, and the whole reason this type exists is that
|
|
29
|
+
* they are not derivable from one another. Measured at 7.10.0 (2026-09-29):
|
|
30
|
+
*
|
|
31
|
+
* - a single-column criterion keys by the field name and does **not** wrap:
|
|
32
|
+
* `{ id: "x" }`;
|
|
33
|
+
* - a compound keys by its members joined with `_` **in attribute order** and
|
|
34
|
+
* does wrap: `@@unique([b, a])` → `{ b_a: { b, a } }`;
|
|
35
|
+
* - `name: "X"` **replaces** the positional form on both sides — `}, "id" |
|
|
36
|
+
* "altKey">` and `altKey?: Prisma.…CompoundUniqueInput`;
|
|
37
|
+
* - `map:` affects neither: it reaches only the 🛑 inline-schema blob.
|
|
38
|
+
*
|
|
39
|
+
* So `key` is stored at generate time and **matched** at runtime, never
|
|
40
|
+
* re-derived: `members.join("_")` is wrong for every named criterion, and
|
|
41
|
+
* whether it wraps is `members.length > 1`.
|
|
42
|
+
*/
|
|
43
|
+
export type PrismaIdentifierAddress = {
|
|
44
|
+
readonly members: AdapterResource<PrismaAdapterModel>["identifiers"][number];
|
|
45
|
+
readonly key: string;
|
|
46
|
+
};
|
|
47
|
+
/** One Resource's Prisma addressing, in the order the identifiers were discovered. */
|
|
48
|
+
export type PrismaResourceTranslation = {
|
|
49
|
+
readonly identifierKeys: readonly PrismaIdentifierAddress[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The **translation table**: Prisma addressing for every discovered Resource.
|
|
53
|
+
*
|
|
54
|
+
* Keyed on `M`'s own resource keys, so a table missing a Resource — or naming
|
|
55
|
+
* one the model does not have — is a compile error rather than a runtime
|
|
56
|
+
* `undefined` at the first unique read.
|
|
57
|
+
*
|
|
58
|
+
* It sits **beside** {@link PrismaDiscoveryArtifact.model}, never inside it, and
|
|
59
|
+
* that placement is the point: the model is canonicalized, hashed and compiled
|
|
60
|
+
* into the Contract, so a Prisma addressing key in it would put a DB-side
|
|
61
|
+
* rename into the Contract hash and force every client to be regenerated for a
|
|
62
|
+
* change with zero semantic content. This half is adapter-private: the executor
|
|
63
|
+
* reads it, core never sees it.
|
|
64
|
+
*/
|
|
65
|
+
export type PrismaTranslationTable<M extends PrismaAdapterModel = PrismaAdapterModel> = {
|
|
66
|
+
readonly [R in keyof M["resources"]]: PrismaResourceTranslation;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* A referential action in Aventara's own vocabulary.
|
|
70
|
+
*
|
|
71
|
+
* Prisma's set — `Cascade | Restrict | NoAction | SetNull | SetDefault` — is the
|
|
72
|
+
* SQL-standard set spelled in PascalCase, so a faithful mapping exists and no
|
|
73
|
+
* Prisma spelling has to travel with the fact. These words are SQL's own
|
|
74
|
+
* (`ON DELETE SET NULL`), which is why the mapping is a rename and not a
|
|
75
|
+
* translation: nothing about it is Prisma-shaped, and the next adapter can state
|
|
76
|
+
* the same fact without inheriting this one's vocabulary.
|
|
77
|
+
*
|
|
78
|
+
* It is declared **here**, on the artifact side, rather than beside the PSL
|
|
79
|
+
* facts that resolve it: the artifact is what carries the vocabulary off the
|
|
80
|
+
* generate step and across the package boundary, and `schema-facts.interface.ts`
|
|
81
|
+
* imports it from here so that there is one spelling and the dependency runs
|
|
82
|
+
* generate-time → artifact rather than the other way about.
|
|
83
|
+
*/
|
|
84
|
+
export type PrismaReferentialAction = "cascade" | "restrict" | "no-action" | "set-null" | "set-default";
|
|
85
|
+
/**
|
|
86
|
+
* One **owning** relation edge's resolved referential actions.
|
|
87
|
+
*
|
|
88
|
+
* # The direction, which is the whole point of this type
|
|
89
|
+
*
|
|
90
|
+
* `onDelete` is declared on the **child** — `Kid.parent @relation(…, onDelete:
|
|
91
|
+
* Cascade)` — and fires when the **parent** is deleted. PSL enforces that itself:
|
|
92
|
+
* `prisma validate` 7.10.0 refuses the action on the other edge, naming the
|
|
93
|
+
* opposite field. So an entry under resource `R` reads:
|
|
94
|
+
*
|
|
95
|
+
* > **deleting a `target` row does `onDelete` to rows of `R`.**
|
|
96
|
+
*
|
|
97
|
+
* A consumer asking *"what does deleting an `X` do?"* scans every resource's
|
|
98
|
+
* {@link PrismaResourceReferentialActions.owningEdges} for `target === "X"` and
|
|
99
|
+
* reads the enclosing resource key off the table. It never has to work out which
|
|
100
|
+
* side of a pair declared what.
|
|
101
|
+
*
|
|
102
|
+
* **Reversing that reading is silent.** Nothing in the shape distinguishes
|
|
103
|
+
* `resource` from `target`, both are resource names, and a planner built on the
|
|
104
|
+
* inverted reading cascades the wrong way with no type error and no runtime
|
|
105
|
+
* complaint. That is why the convention is restated here as well as on
|
|
106
|
+
* `field.mapper.ts`'s `PrismaRelationEdge`, which is where these values are
|
|
107
|
+
* carried out of field projection.
|
|
108
|
+
*
|
|
109
|
+
* # Both members are resolved, so neither is nullable
|
|
110
|
+
*
|
|
111
|
+
* An absent `@relation(onDelete:)` is **not** a third state: it is Prisma's
|
|
112
|
+
* effective default, measured at 7.10.0 with `prisma migrate diff` and identical
|
|
113
|
+
* on `postgresql` and `sqlite` — `SET NULL` when every scalar in `fields:` is
|
|
114
|
+
* optional, `RESTRICT` otherwise, and `CASCADE` on update
|
|
115
|
+
* (`schema.reader.ts`'s `resolvePrismaReferentialDefaults`). An action the reader
|
|
116
|
+
* cannot name is recorded in `unhandled` and fails the generate step, so no
|
|
117
|
+
* artifact ever carries an unresolved one.
|
|
118
|
+
*/
|
|
119
|
+
export type PrismaReferentialEdge = {
|
|
120
|
+
/** The edge's field name on the enclosing (child) resource. */
|
|
121
|
+
readonly field: string;
|
|
122
|
+
/** The resource the edge points AT — the parent, whose deletion fires `onDelete`. */
|
|
123
|
+
readonly target: string;
|
|
124
|
+
readonly cardinality: "one" | "many";
|
|
125
|
+
/** What deleting a `target` row does to rows of the ENCLOSING resource. */
|
|
126
|
+
readonly onDelete: PrismaReferentialAction;
|
|
127
|
+
/** The same for an update of the referenced key. Read independently of `onDelete`. */
|
|
128
|
+
readonly onUpdate: PrismaReferentialAction;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* One Resource's owning edges, in PSL field-declaration order.
|
|
132
|
+
*
|
|
133
|
+
* Only **owning** edges appear — the ones that declare `@relation(fields: …)`,
|
|
134
|
+
* which is what makes an edge the foreign-key side and the only side PSL lets
|
|
135
|
+
* carry an action. A back-reference's actions are its partner's, so an entry for
|
|
136
|
+
* it would carry two nulls and no fact; a resource with no owning edge carries an
|
|
137
|
+
* empty list, which is itself the answer that deleting anything leaves its rows
|
|
138
|
+
* alone.
|
|
139
|
+
*/
|
|
140
|
+
export type PrismaResourceReferentialActions = {
|
|
141
|
+
readonly owningEdges: readonly PrismaReferentialEdge[];
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* The **referential-action table**: every discovered Resource's owning edges,
|
|
145
|
+
* resolved.
|
|
146
|
+
*
|
|
147
|
+
* Keyed on `M`'s own resource keys for the reason {@link PrismaTranslationTable}
|
|
148
|
+
* is — a resource the model carries and the table forgets is a compile error at
|
|
149
|
+
* the generate step rather than a runtime `undefined` — and it sits **beside**
|
|
150
|
+
* {@link PrismaDiscoveryArtifact.model} and outside `modelHash` for the reason
|
|
151
|
+
* that one does: `modelHash` digests the model, and a referential action is not
|
|
152
|
+
* part of the model. Putting it inside would push referential-action vocabulary
|
|
153
|
+
* into the compiled Contract and the ClientContract, where core has no word for
|
|
154
|
+
* 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
|
+
*/
|
|
161
|
+
export type PrismaReferentialActionTable<M extends PrismaAdapterModel = PrismaAdapterModel> = {
|
|
162
|
+
readonly [R in keyof M["resources"]]: PrismaResourceReferentialActions;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* T-3.2 — what the generate step emits and the adapter loads.
|
|
166
|
+
*
|
|
167
|
+
* **ADR 0009 (Accepted) overrides the plan here: the artifact is one TypeScript
|
|
168
|
+
* module, not JSON.** `resolveJsonModule` widens `"string"` to `string`, so a
|
|
169
|
+
* JSON artifact would force a second emitted declaration purely to carry the
|
|
170
|
+
* literals, and two emissions from one source can drift. One module written
|
|
171
|
+
* `as const satisfies AdapterModel` makes value and type the same literal, and
|
|
172
|
+
* makes drift unrepresentable rather than merely detectable.
|
|
173
|
+
*
|
|
174
|
+
* It is still passive, serializable, introspectable, hashable and free of
|
|
175
|
+
* runtime callbacks (C-841): a `.ts` module whose only expression is a frozen
|
|
176
|
+
* object literal is JSON that carries its own type.
|
|
177
|
+
*
|
|
178
|
+
* `M` is the reason this type is generic at all. The emitted module's `model`
|
|
179
|
+
* is a literal type; if this alias pinned `model` to the general
|
|
180
|
+
* {@link PrismaAdapterModel}, every resource key and field key would be erased
|
|
181
|
+
* to `string` on the way to `createPrismaAdapter`, `EffectiveFieldKey` would
|
|
182
|
+
* degenerate, and core's whole config-typing workstream would buy nothing —
|
|
183
|
+
* silently, with no compile error anywhere. `model: M` is a bare property
|
|
184
|
+
* position, which is what makes `M` actually infer at the call site;
|
|
185
|
+
* `typed-artifact.test-d.ts` is what proves it still does.
|
|
186
|
+
*
|
|
187
|
+
* The default keeps the unparameterised spelling meaning what it always meant:
|
|
188
|
+
* "some Prisma adapter model".
|
|
189
|
+
*/
|
|
190
|
+
export type PrismaDiscoveryArtifact<M extends PrismaAdapterModel = PrismaAdapterModel> = {
|
|
191
|
+
/** e.g. `"7.10.0"`; only the major is fenced (C-846). */
|
|
192
|
+
readonly prismaVersion: string;
|
|
193
|
+
readonly provider: PrismaProvider;
|
|
194
|
+
/** `sha256:<hex>` of the canonicalized `model` below — and of nothing else. */
|
|
195
|
+
readonly modelHash: string;
|
|
196
|
+
readonly model: M;
|
|
197
|
+
/**
|
|
198
|
+
* Prisma addressing beside the model, and outside `modelHash` by
|
|
199
|
+
* construction: see {@link PrismaTranslationTable}.
|
|
200
|
+
*/
|
|
201
|
+
readonly translation: PrismaTranslationTable<M>;
|
|
202
|
+
/**
|
|
203
|
+
* The resolved referential actions, beside the model and outside `modelHash`
|
|
204
|
+
* on the same construction: see {@link PrismaReferentialActionTable}.
|
|
205
|
+
*
|
|
206
|
+
* Direction, because getting it backwards is silent: an entry under resource
|
|
207
|
+
* `R` says **deleting a `target` row does `onDelete` to rows of `R`**.
|
|
208
|
+
*/
|
|
209
|
+
readonly referentialActions: PrismaReferentialActionTable<M>;
|
|
210
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { EnumDefinition } from "@aventara/core";
|
|
2
|
+
import type { PrismaSchemaEnum } from "./schema-facts.interface.js";
|
|
3
|
+
import type { PrismaDiscoveryDiagnostic } from "./vocabulary.dictionary.js";
|
|
4
|
+
/**
|
|
5
|
+
* PSL enums → `EnumDefinition` **[CORE]**, and the one line of C-810.
|
|
6
|
+
*
|
|
7
|
+
* # Declaration order is semantic, so nothing here sorts
|
|
8
|
+
*
|
|
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
|
+
* # What a cross-check failure means
|
|
15
|
+
*
|
|
16
|
+
* `enums.ts` and `schema.prisma` were read in the same instant by the same
|
|
17
|
+
* generate step (D-802). If they disagree, one of them is stale, and the answer
|
|
18
|
+
* is `diverged` — a build stop — rather than a preference for either. The message
|
|
19
|
+
* prints both orders because "the enum diverged" without them costs the reader a
|
|
20
|
+
* second investigation.
|
|
21
|
+
*
|
|
22
|
+
* # A member-level `@map` never reaches here, from either side
|
|
23
|
+
*
|
|
24
|
+
* PSL's reader discards it as bucket 2 (a database name, which Aventara has no
|
|
25
|
+
* word for), and the generated `enums.ts` writes the member name on *both* sides
|
|
26
|
+
* 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
|
+
* sides, and the mapper has nothing to resurrect.
|
|
29
|
+
*/
|
|
30
|
+
export type EnumMappingInput = {
|
|
31
|
+
/** PSL — the source of the values and their order. */
|
|
32
|
+
readonly declared: PrismaSchemaEnum;
|
|
33
|
+
/** TYPE — `enums.ts`'s member list, or `undefined` when the 🟢 types omit the enum entirely. */
|
|
34
|
+
readonly exposed: readonly string[] | undefined;
|
|
35
|
+
};
|
|
36
|
+
export type EnumMappingOutcome = {
|
|
37
|
+
readonly outcome: "enum";
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly definition: EnumDefinition;
|
|
40
|
+
} | {
|
|
41
|
+
readonly outcome: "excluded";
|
|
42
|
+
readonly diagnostic: PrismaDiscoveryDiagnostic;
|
|
43
|
+
} | {
|
|
44
|
+
readonly outcome: "diverged";
|
|
45
|
+
readonly diagnostic: PrismaDiscoveryDiagnostic;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* C-810 made concrete: an enum-backed column projects to a **ScalarField** whose
|
|
49
|
+
* type ref takes the `enum` arm. The `kind` is a literal `"scalar"`, so a third
|
|
50
|
+
* structural kind cannot be introduced here without the type changing.
|
|
51
|
+
*/
|
|
52
|
+
export type EnumBackedColumn = {
|
|
53
|
+
readonly kind: "scalar";
|
|
54
|
+
readonly type: {
|
|
55
|
+
readonly enum: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export declare function mapPrismaEnum(input: EnumMappingInput): EnumMappingOutcome;
|
|
59
|
+
export declare function projectEnumBackedColumn(name: string): EnumBackedColumn;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function mapPrismaEnum(input) {
|
|
2
|
+
const { declared, exposed } = input;
|
|
3
|
+
if (exposed === undefined) {
|
|
4
|
+
return {
|
|
5
|
+
outcome: "excluded",
|
|
6
|
+
diagnostic: {
|
|
7
|
+
code: "not-exposed",
|
|
8
|
+
message: `enum \`${declared.name}\` is declared in ${declared.site.file}:${declared.site.line} and absent from the generated \`enums.ts\`, so it is omitted rather than projected into a Contract the client cannot name.`,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const declaredOrder = declared.values.join(", ");
|
|
13
|
+
const exposedOrder = exposed.join(", ");
|
|
14
|
+
if (declaredOrder !== exposedOrder) {
|
|
15
|
+
return {
|
|
16
|
+
outcome: "diverged",
|
|
17
|
+
diagnostic: {
|
|
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.2) and enters the Contract hash, so this is a stale generated client, not a cosmetic difference.`,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
outcome: "enum",
|
|
25
|
+
name: declared.name,
|
|
26
|
+
// PSL's order, copied. Never sorted, never taken from the cross-check.
|
|
27
|
+
definition: { values: [...declared.values] },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function projectEnumBackedColumn(name) {
|
|
31
|
+
return { kind: "scalar", type: { enum: name } };
|
|
32
|
+
}
|