@aventara/prisma7-adapter 0.1.0-pilot.1 → 0.1.0-pilot.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +33 -20
  2. package/dist/discovery/artifact.loader.d.ts +6 -2
  3. package/dist/discovery/artifact.loader.js +12 -57
  4. package/dist/discovery/capability-surface.interface.d.ts +29 -40
  5. package/dist/discovery/capability-surface.interface.js +0 -12
  6. package/dist/discovery/capability.deriver.d.ts +10 -40
  7. package/dist/discovery/capability.deriver.js +0 -179
  8. package/dist/discovery/client-shape.verifier.d.ts +13 -0
  9. package/dist/discovery/client-shape.verifier.js +93 -0
  10. package/dist/discovery/discovery-artifact.interface.d.ts +55 -30
  11. package/dist/discovery/enum.mapper.d.ts +6 -14
  12. package/dist/discovery/enum.mapper.js +1 -2
  13. package/dist/discovery/field.mapper.d.ts +49 -63
  14. package/dist/discovery/field.mapper.js +0 -161
  15. package/dist/discovery/identifier.mapper.d.ts +38 -77
  16. package/dist/discovery/identifier.mapper.js +0 -64
  17. package/dist/discovery/model.discoverer.d.ts +18 -35
  18. package/dist/discovery/model.discoverer.js +66 -141
  19. package/dist/discovery/operations.deriver.d.ts +10 -38
  20. package/dist/discovery/operations.deriver.js +0 -40
  21. package/dist/discovery/scalar.mapper.d.ts +12 -35
  22. package/dist/discovery/scalar.mapper.js +0 -8
  23. package/dist/discovery/schema-facts.interface.d.ts +26 -25
  24. package/dist/discovery/schema-facts.interface.js +0 -10
  25. package/dist/discovery/schema.reader.d.ts +53 -64
  26. package/dist/discovery/schema.reader.js +0 -174
  27. package/dist/discovery/types.reader.d.ts +0 -4
  28. package/dist/discovery/types.reader.js +2 -177
  29. package/dist/discovery/vocabulary.dictionary.d.ts +36 -75
  30. package/dist/discovery/vocabulary.dictionary.js +2 -152
  31. package/dist/errors/error.normalizer.d.ts +85 -135
  32. package/dist/errors/error.normalizer.js +1 -70
  33. package/dist/errors/prisma-error.reader.d.ts +53 -68
  34. package/dist/errors/prisma-error.reader.js +0 -91
  35. package/dist/execution/first-variant.emulator.d.ts +11 -22
  36. package/dist/execution/first-variant.emulator.js +0 -142
  37. package/dist/execution/operation.executor.d.ts +12 -19
  38. package/dist/execution/operation.executor.js +0 -92
  39. package/dist/execution/result.shaper.d.ts +22 -31
  40. package/dist/execution/result.shaper.js +0 -24
  41. package/dist/execution/transaction.executor.d.ts +36 -73
  42. package/dist/generate/artifact.emitter.d.ts +27 -27
  43. package/dist/generate/artifact.emitter.js +30 -13
  44. package/dist/generate/declared-support.reader.d.ts +11 -11
  45. package/dist/generate/declared-support.reader.js +0 -12
  46. package/dist/generate/discovery.bin.js +0 -10
  47. package/dist/generate/discovery.cli.js +0 -55
  48. package/dist/generate/existence.gate.d.ts +26 -32
  49. package/dist/generate/existence.gate.js +0 -6
  50. package/dist/generate/index.d.ts +10 -12
  51. package/dist/generate/index.js +0 -42
  52. package/dist/generate/node-version.guard.js +0 -12
  53. package/dist/generate/prisma-cli.resolver.d.ts +9 -11
  54. package/dist/generate/prisma-cli.resolver.js +0 -13
  55. package/dist/generate/prisma.generate.d.ts +49 -65
  56. package/dist/generate/prisma.generate.js +13 -123
  57. package/dist/generate/reader-crosscheck.d.ts +36 -60
  58. package/dist/generate/reader-crosscheck.js +5 -41
  59. package/dist/prisma-client.interface.d.ts +14 -19
  60. package/dist/prisma-client.interface.js +0 -6
  61. package/dist/prisma.adapter.d.ts +9 -17
  62. package/dist/prisma.adapter.js +11 -53
  63. package/dist/provider/provider-profile.d.ts +6 -23
  64. package/dist/provider/provider-profile.js +0 -64
  65. package/dist/translation/identifier.addressing.d.ts +15 -22
  66. package/dist/translation/identifier.addressing.js +0 -10
  67. package/dist/translation/mutation.translator.d.ts +19 -43
  68. package/dist/translation/mutation.translator.js +1 -209
  69. package/dist/translation/operation.translator.d.ts +36 -63
  70. package/dist/translation/operation.translator.js +1 -51
  71. package/dist/translation/order-pagination.translator.d.ts +24 -31
  72. package/dist/translation/order-pagination.translator.js +0 -43
  73. package/dist/translation/projection.translator.d.ts +25 -58
  74. package/dist/translation/projection.translator.js +0 -27
  75. package/dist/translation/scalar.conversion.d.ts +15 -26
  76. package/dist/translation/scalar.conversion.js +1 -129
  77. package/dist/translation/where.translator.d.ts +17 -28
  78. package/dist/translation/where.translator.js +0 -83
  79. package/package.json +3 -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
- * 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
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 (C-846). */
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 (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
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
- * sides, and the mapper has nothing to resurrect.
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
- * 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.
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.2) and enters the Contract hash, so this is a stale generated client, not a cosmetic difference.`,
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
  }