@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.
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 +4 -3
@@ -1,42 +1,6 @@
1
- /**
2
- * The Prisma addressing key for a uniqueness criterion: the key a
3
- * `WhereUniqueInput` accepts, and the key the generated `AtLeast<…, K>` union
4
- * lists.
5
- *
6
- * Computed **once**, here, at generate time — the architect's decision on
7
- * 2026-09-29: *"store it, less processing and less room for drifting."* Runtime
8
- * matches the stored key and never re-derives it.
9
- *
10
- * The rule, measured at 7.10.0 (regenerated 2026-09-29 over five models):
11
- *
12
- * | criterion | key | wraps |
13
- * | ---------------------------------- | --------- | ----- |
14
- * | `id String @id` / `@unique` | `id` | no |
15
- * | `@@unique([b, a])` | `b_a` | yes |
16
- * | `@@id([a, b])` | `a_b` | yes |
17
- * | `@@unique([b, a], name: "altKey")` | `altKey` | yes |
18
- * | `@@id([a, b], name: "pk")` | `pk` | yes |
19
- * | any of the above, `map: "…"` | unchanged | — |
20
- *
21
- * Two of those rows are why this is a function of the PSL facts and not of the
22
- * members alone. A custom `name:` **replaces** the positional alias rather than
23
- * adding to it, so `members.join("_")` is simply wrong for a named criterion —
24
- * which is precisely what made the cross-check refuse a schema Prisma accepts,
25
- * reporting a correctly-discovered identifier as an invented constraint (fixed
26
- * 2026-09-29; `map:` stays invisible and stays discarded).
27
- *
28
- * What this key is **not** is part of the identifier. `AdapterIdentifier` is a
29
- * tuple of field names with nowhere to put a name (C-814), and this key lives in
30
- * the artifact's translation table beside the model, never in it.
31
- */
32
1
  export function prismaUniqueInputKey(uniqueness) {
33
2
  return uniqueness.name ?? uniqueness.members.join("_");
34
3
  }
35
- /**
36
- * Every uniqueness criterion PSL states, in PSL's own order. A field spelled
37
- * `@id @unique` yields two criteria with the same key; the caller keeps the
38
- * first, so `@id` wins and one column does not become two identifiers.
39
- */
40
4
  function* uniquenessCriteria(model) {
41
5
  const criterion = (source, uniqueness) => ({
42
6
  source,
@@ -44,8 +8,6 @@ function* uniquenessCriteria(model) {
44
8
  key: prismaUniqueInputKey(uniqueness),
45
9
  });
46
10
  for (const field of model.fields) {
47
- // A field-level criterion has no `name:` to carry: PSL gives `@id` and
48
- // `@unique` no such argument, and Prisma keys them by the column.
49
11
  if (field.id) {
50
12
  yield criterion("@id", { members: [field.name], name: null });
51
13
  }
@@ -60,16 +22,7 @@ function* uniquenessCriteria(model) {
60
22
  yield criterion("@@unique", blockUnique);
61
23
  }
62
24
  }
63
- /**
64
- * One model's identity, read from PSL alone.
65
- *
66
- * A pure function of plain data: no generated client, no `@prisma/client`
67
- * import, and no TYPE input — which is what lets the cross-check be an
68
- * independent second opinion rather than a tautology.
69
- */
70
25
  export function mapPrismaIdentifiers(model) {
71
- // The scalar columns this adapter can actually name: a relation field, a list
72
- // and an `@ignore`d column are all keys no `AdapterFieldMap` will carry.
73
26
  const columns = new Map();
74
27
  for (const field of model.fields) {
75
28
  if (field.relation !== null || field.list || field.ignored) {
@@ -79,10 +32,6 @@ export function mapPrismaIdentifiers(model) {
79
32
  }
80
33
  const identifiers = [];
81
34
  const omitted = [];
82
- // Deduplication is on the MEMBER TUPLE, not on the addressing key: the tuple
83
- // is what `@id @unique` duplicates, and it identifies a criterion injectively,
84
- // while a key does not — a custom `name:` can collide with a field name, and
85
- // keying the seen-set would then silently drop a different criterion.
86
35
  const seen = new Set();
87
36
  for (const criterion of uniquenessCriteria(model)) {
88
37
  const tuple = JSON.stringify(criterion.members);
@@ -104,19 +53,6 @@ export function mapPrismaIdentifiers(model) {
104
53
  });
105
54
  continue;
106
55
  }
107
- // C-815. SQL uniqueness does not constrain NULLs, so a criterion with any
108
- // nullable participant does not guarantee AT MOST ONE row and is not an
109
- // `Identifier`. Recorded, never dropped: the generator still emits an
110
- // `AtLeast` key for it, and the TYPE cross-check needs the omission to
111
- // explain that key.
112
- //
113
- // C-815 is typed a Derived Decision because no source states it. Prisma
114
- // states the same rule from its own side, which is the nearest thing to a
115
- // source there is: a model whose ONLY criterion has a nullable participant
116
- // is rejected at validate time with P1012, "Each model must have at least
117
- // one unique criteria that has only required fields … The following unique
118
- // criterias were not considered as they contain fields that are not
119
- // required: - a, b" (7.10.0, measured 2026-09-28).
120
56
  const nullableMembers = criterion.members.filter((member) => columns.get(member)?.optional === true);
121
57
  if (nullableMembers.length > 0) {
122
58
  omitted.push({
@@ -3,16 +3,8 @@ import type { PrismaComposedArtifact, PrismaModelComposer } from "../generate/pr
3
3
  import type { PrismaCapabilitySurface } from "./capability-surface.interface.js";
4
4
  import type { PrismaSchemaFacts } from "./schema-facts.interface.js";
5
5
  /**
6
- * Task 6 — **composition**: the one place the two readers' facts become the two
7
- * halves of the discovery artifact.
8
- *
9
- * Every rule it applies belongs to somebody else. That is the design, not a
10
- * shortcut: the scalar table is `scalar.mapper.ts`'s, the field projection and the
11
- * lifecycle traits are `field.mapper.ts`'s, identity is `identifier.mapper.ts`'s,
12
- * capabilities are `capability.deriver.ts`'s, the §8.3 variant gate is
13
- * `operations.deriver.ts`'s and nowhere else, and the enum comparison is
14
- * `enum.mapper.ts`'s. This module **orders and assembles**, and it owns exactly
15
- * two judgements of its own:
6
+ * Every rule it applies belongs to somebody else. This module **orders and
7
+ * assembles**, and it owns exactly two judgements of its own:
16
8
  *
17
9
  * 1. **Which diagnostics are fatal.** A mapper answers with a diagnostic; whether
18
10
  * the build stops is composition's call, because only composition knows that a
@@ -49,31 +41,22 @@ import type { PrismaSchemaFacts } from "./schema-facts.interface.js";
49
41
  *
50
42
  * The composer is created with a {@link TransactionCapability} and nothing else.
51
43
  * `provider` is not a member anywhere on the path, so `if (provider === …)` is a
52
- * compile error rather than a convention (dictionary invariant 6, D-802). The
53
- * generate step keeps `provider` only to stamp the artifact, which is a recorded
54
- * fact and not a branch.
55
- *
56
- * `transactions` is a **driver** fact (T-2) and the driver is a runtime choice, so
57
- * it is declared to the composer at generate time. The two cannot silently
58
- * disagree: an artifact that says `"interactive"` loaded by an adapter whose
59
- * declared driver says `"none"` mounts no `transaction`, and core refuses
60
- * construction with `DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH` **[CORE,
61
- * framework.factory.ts]** — a hard, legible failure rather than a silent one.
62
- *
63
- * # The C-818 conflict this header used to record is closed
44
+ * compile error rather than a convention. The generate step keeps `provider` only
45
+ * to stamp the artifact, which is a recorded fact and not a branch.
64
46
  *
65
- * It read: a relation carries create directives for `create.one` and none for
66
- * `create.many` **[DOC7]**, `AdapterRelationField.capabilities` is one record per
67
- * field with a single `create` member, so composition had to ask
68
- * `deriveRelationCapabilities` for the `"create.one"` answer — the permissive one —
69
- * and C-818's restriction was unrepresentable in the Contract.
47
+ * `transactions` is a **driver** fact and the driver is a runtime choice, so it is
48
+ * declared to the composer at generate time. The two cannot silently disagree: an
49
+ * artifact that says `"interactive"` loaded by an adapter whose declared driver
50
+ * says `"none"` mounts no `transaction`, and core refuses construction with
51
+ * `DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH` **[CORE, framework.factory.ts]** — a
52
+ * hard, legible failure rather than a silent one.
70
53
  *
71
54
  * The capability members are **scope-qualified** now, so the one record holds both
72
- * answers: the create stage is advertised at the `nested` write scope and not at the
73
- * bulk one. Composition asks for no variant, passes no `operation`, and advertises
74
- * nothing it cannot honour. The mutation translator keeps its refusal — it is the
75
- * belt to core's braces, not the only guard (C-855) — but it is no longer the ONLY
76
- * place the restriction exists.
55
+ * answers: the create stage is advertised at the `nested` write scope and not at
56
+ * the bulk one. Composition asks for no variant, passes no `operation`, and
57
+ * advertises nothing it cannot honour. The mutation translator keeps its refusal —
58
+ * it is the belt to core's braces, not the only guard — but it is no longer the
59
+ * ONLY place the restriction exists.
77
60
  */
78
61
  /** Composition refuses: the collected diagnostics, named and reported together. */
79
62
  export declare class PrismaModelCompositionError extends Error {
@@ -83,9 +66,9 @@ export declare class PrismaModelCompositionError extends Error {
83
66
  }
84
67
  export type PrismaModelCompositionOptions = {
85
68
  /**
86
- * The driver's transaction capability (T-2). The ONLY declared fact
87
- * composition accepts, and deliberately not the whole profile: a
88
- * `PrismaProviderProfile` carries `provider`.
69
+ * The driver's transaction capability. The ONLY declared fact composition accepts,
70
+ * and deliberately not the whole profile: a `PrismaProviderProfile` carries
71
+ * `provider`.
89
72
  */
90
73
  readonly transactions: TransactionCapability;
91
74
  };
@@ -4,80 +4,6 @@ import { mapPrismaEnum } from "./enum.mapper.js";
4
4
  import { crossCheckCreateOptional, projectPrismaField, } from "./field.mapper.js";
5
5
  import { mapPrismaIdentifiers } from "./identifier.mapper.js";
6
6
  import { deriveResourceOperations } from "./operations.deriver.js";
7
- /**
8
- * Task 6 — **composition**: the one place the two readers' facts become the two
9
- * halves of the discovery artifact.
10
- *
11
- * Every rule it applies belongs to somebody else. That is the design, not a
12
- * shortcut: the scalar table is `scalar.mapper.ts`'s, the field projection and the
13
- * lifecycle traits are `field.mapper.ts`'s, identity is `identifier.mapper.ts`'s,
14
- * capabilities are `capability.deriver.ts`'s, the §8.3 variant gate is
15
- * `operations.deriver.ts`'s and nowhere else, and the enum comparison is
16
- * `enum.mapper.ts`'s. This module **orders and assembles**, and it owns exactly
17
- * two judgements of its own:
18
- *
19
- * 1. **Which diagnostics are fatal.** A mapper answers with a diagnostic; whether
20
- * the build stops is composition's call, because only composition knows that a
21
- * model with an unmapped column cannot produce a Contract at all.
22
- * 2. **The scalar registry.** `ScalarRegistry` is the set of Aventara scalar
23
- * identities the discovered fields actually use — a fact no single field can
24
- * state and only the whole pass can.
25
- *
26
- * # Three halves, from one pass over one field list
27
- *
28
- * `model` is core's: canonicalized, hashed, compiled into the Contract.
29
- * `translation` is the adapter's Prisma addressing, and `referentialActions` is
30
- * what the resolved `@relation(onDelete:/onUpdate:)` arguments say. All three are
31
- * built from the SAME pass: the translation table from the same
32
- * `mapPrismaIdentifiers` answer the model's `identifiers` come from — `.members`
33
- * into the model, the whole address into the table — and the action table from the
34
- * same `projectPrismaField` answer the model's relation fields come from. Two
35
- * parallel passes would have to be kept in step; one pass cannot drift.
36
- *
37
- * `PrismaTranslationTable<M>` and `PrismaReferentialActionTable<M>` are mapped
38
- * types over the model's resources, so a resource this module adds to the model
39
- * and forgets to address — or forgets to answer the actions for — is a **compile
40
- * error** rather than a runtime `undefined` at the first unique read or the first
41
- * cascade question.
42
- *
43
- * The action table carries **only owning edges**, because only an owning edge has
44
- * an action: PSL forbids the back-reference from declaring one, so the
45
- * back-reference's answer is its partner's. Its direction is the one documented on
46
- * `PrismaReferentialEdge` and on `field.mapper.ts`'s `PrismaRelationEdge` —
47
- * *deleting a `target` does `onDelete` to rows of the enclosing resource* — and it
48
- * is carried out of field projection unchanged rather than re-derived here.
49
- *
50
- * # No provider reaches this module, and that is enforced by the input
51
- *
52
- * The composer is created with a {@link TransactionCapability} and nothing else.
53
- * `provider` is not a member anywhere on the path, so `if (provider === …)` is a
54
- * compile error rather than a convention (dictionary invariant 6, D-802). The
55
- * generate step keeps `provider` only to stamp the artifact, which is a recorded
56
- * fact and not a branch.
57
- *
58
- * `transactions` is a **driver** fact (T-2) and the driver is a runtime choice, so
59
- * it is declared to the composer at generate time. The two cannot silently
60
- * disagree: an artifact that says `"interactive"` loaded by an adapter whose
61
- * declared driver says `"none"` mounts no `transaction`, and core refuses
62
- * construction with `DISCOVERY_TRANSACTION_CAPABILITY_MISMATCH` **[CORE,
63
- * framework.factory.ts]** — a hard, legible failure rather than a silent one.
64
- *
65
- * # The C-818 conflict this header used to record is closed
66
- *
67
- * It read: a relation carries create directives for `create.one` and none for
68
- * `create.many` **[DOC7]**, `AdapterRelationField.capabilities` is one record per
69
- * field with a single `create` member, so composition had to ask
70
- * `deriveRelationCapabilities` for the `"create.one"` answer — the permissive one —
71
- * and C-818's restriction was unrepresentable in the Contract.
72
- *
73
- * The capability members are **scope-qualified** now, so the one record holds both
74
- * answers: the create stage is advertised at the `nested` write scope and not at the
75
- * bulk one. Composition asks for no variant, passes no `operation`, and advertises
76
- * nothing it cannot honour. The mutation translator keeps its refusal — it is the
77
- * belt to core's braces, not the only guard (C-855) — but it is no longer the ONLY
78
- * place the restriction exists.
79
- */
80
- /** Composition refuses: the collected diagnostics, named and reported together. */
81
7
  export class PrismaModelCompositionError extends Error {
82
8
  name = "PrismaModelCompositionError";
83
9
  diagnostics;
@@ -89,13 +15,6 @@ export class PrismaModelCompositionError extends Error {
89
15
  this.diagnostics = diagnostics;
90
16
  }
91
17
  }
92
- /**
93
- * The generate step's `composeModel` seam, filled.
94
- *
95
- * A factory rather than a bare function because the step's seam takes only the
96
- * readers' output — order and refusal over external effects is the step's job, and
97
- * the declared driver fact is the caller's.
98
- */
99
18
  export function createPrismaModelComposer(options) {
100
19
  return (input) => discoverPrismaModel({ ...input, ...options });
101
20
  }
@@ -113,8 +32,6 @@ export function discoverPrismaModel(input) {
113
32
  const model = declared.get(name);
114
33
  const exposed = input.surface.resources[name];
115
34
  if (model === undefined || exposed === undefined) {
116
- // The existence gate admitted it, so both halves must be there. A
117
- // diagnostic rather than a throw: the caller gets the whole list.
118
35
  diagnostics.push(`\`${name}\` was admitted by the existence gate and is absent from ${model === undefined ? "the PSL facts" : "the capability surface"}, which the gate cannot produce — this is a reader defect.`);
119
36
  continue;
120
37
  }
@@ -144,17 +61,77 @@ export function discoverPrismaModel(input) {
144
61
  },
145
62
  translation,
146
63
  referentialActions,
64
+ createRequirements: composeCreateRequirements(declared, input.surface, resources),
147
65
  };
148
66
  }
149
- /* ------------------------------------------------------------------ *
150
- * Enums *
151
- * ------------------------------------------------------------------ */
67
+ function composeCreateRequirements(declared, surface, resources) {
68
+ const table = {};
69
+ for (const [name, resource] of Object.entries(resources)) {
70
+ const model = declared.get(name);
71
+ const exposed = surface.resources[name];
72
+ if (model === undefined || exposed === undefined) {
73
+ continue;
74
+ }
75
+ const requiredScalars = new Set(Object.entries(resource.fields)
76
+ .filter(([field, shape]) => {
77
+ const scalar = exposed.scalarFields[field];
78
+ return (shape.kind === "scalar" &&
79
+ scalar !== undefined &&
80
+ scalar.createOperations !== null &&
81
+ !scalar.createOptional);
82
+ })
83
+ .map(([field]) => field));
84
+ const requirements = [];
85
+ const references = new Set();
86
+ for (const field of model.fields) {
87
+ const shape = resource.fields[field.name];
88
+ if (shape?.kind !== "relation") {
89
+ continue;
90
+ }
91
+ const owned = field.relation?.fields ?? [];
92
+ for (const scalar of owned) {
93
+ references.add(scalar);
94
+ }
95
+ if (exposed.relationFields[field.name]?.createRequired !== true) {
96
+ continue;
97
+ }
98
+ const orFields = owned.filter((scalar) => requiredScalars.has(scalar));
99
+ if (orFields.length === 0) {
100
+ continue;
101
+ }
102
+ const inverse = inverseOf(declared, name, field);
103
+ requirements.push({
104
+ kind: "relation",
105
+ relation: field.name,
106
+ orFields,
107
+ ...(inverse === undefined ? {} : { inverse }),
108
+ });
109
+ }
110
+ const fields = model.fields
111
+ .map((field) => field.name)
112
+ .filter((field) => requiredScalars.has(field) && !references.has(field))
113
+ .map((field) => ({ kind: "field", field }));
114
+ table[name] = [...fields, ...requirements];
115
+ }
116
+ return table;
117
+ }
118
+ function inverseOf(declared, model, field) {
119
+ const target = declared.get(field.type);
120
+ if (target === undefined) {
121
+ return undefined;
122
+ }
123
+ const name = field.relation?.name ?? null;
124
+ const partners = target.fields.filter((candidate) => candidate.type === model &&
125
+ (candidate.relation?.name ?? null) === name &&
126
+ !(target.name === model && candidate.name === field.name));
127
+ const [partner] = partners;
128
+ return partners.length === 1 && partner !== undefined
129
+ ? { resource: target.name, relation: partner.name }
130
+ : undefined;
131
+ }
152
132
  function composeEnums(input, diagnostics) {
153
133
  const enums = {};
154
134
  for (const declared of input.facts.enums) {
155
- // The mapper's judgement, forwarded. Declaration order is semantic and
156
- // enters the Contract hash, so "diverged" is fatal here for the same reason
157
- // `reader-crosscheck.ts` reports it: the client is from another schema.
158
135
  const mapped = mapPrismaEnum({
159
136
  declared,
160
137
  exposed: input.surface.enums[declared.name],
@@ -166,24 +143,14 @@ function composeEnums(input, diagnostics) {
166
143
  if (mapped.outcome === "diverged") {
167
144
  diagnostics.push(mapped.diagnostic.message);
168
145
  }
169
- // `excluded` is the existence rule applied to an enum: declared in PSL and
170
- // absent from the generated `enums.ts`. It is NOT fatal, exactly as an
171
- // `@ignore`d model is not: an enum the client cannot name has no place in a
172
- // Contract, and any column typed by it is itself absent from the 🟢 surface
173
- // and therefore already excluded by the field-level gate.
174
146
  }
175
147
  return enums;
176
148
  }
177
- /* ------------------------------------------------------------------ *
178
- * One resource *
179
- * ------------------------------------------------------------------ */
180
149
  function composeResource(input) {
181
150
  const { model, exposed, diagnostics } = input;
182
151
  const fields = {};
183
- /** Owning edges only, in PSL field-declaration order (see this module's header). */
184
152
  const owningEdges = [];
185
153
  const unknowns = [];
186
- /** What the create-optional cross-check consumes: PSL's own answers. */
187
154
  const projectedScalars = [];
188
155
  for (const field of model.fields) {
189
156
  const projection = projectPrismaField({
@@ -196,8 +163,6 @@ function composeResource(input) {
196
163
  });
197
164
  switch (projection.outcome) {
198
165
  case "excluded":
199
- // The existence gate has already decided this absence is explained;
200
- // re-deciding it here would be a second owner for C-808.
201
166
  continue;
202
167
  case "unmapped":
203
168
  diagnostics.push(projection.diagnostic.message);
@@ -211,9 +176,6 @@ function composeResource(input) {
211
176
  }
212
177
  const type = projection.field.type;
213
178
  if ("scalar" in type) {
214
- // Narrowed by membership rather than asserted: `AdapterScalarField`'s
215
- // type ref is `keyof M["scalars"]`, which is `string` on the general
216
- // model, and the registry must carry built-in identities only.
217
179
  const scalar = BUILT_IN_SCALARS.find((candidate) => candidate === type.scalar);
218
180
  if (scalar === undefined) {
219
181
  diagnostics.push(`\`${model.name}.${field.name}\` mapped to the scalar identity \`${type.scalar}\`, which is not one of Aventara's built-in scalars (${BUILT_IN_SCALARS.join(", ")}).`);
@@ -240,31 +202,19 @@ function composeResource(input) {
240
202
  continue;
241
203
  }
242
204
  if (!input.admitted.has(projection.field.target)) {
243
- // An edge to a resource the model does not carry would compile into a
244
- // Contract that references a Resource that is not there.
245
205
  diagnostics.push(`\`${model.name}.${field.name}\` targets \`${projection.field.target}\`, which the existence gate did not admit, so the edge would point at a Resource the model does not carry.`);
246
206
  continue;
247
207
  }
248
208
  fields[projection.name] = {
249
209
  ...projection.field,
250
- // No variant is named: the scope-qualified members carry both create
251
- // answers at once. See this module's header for the conflict that
252
- // closed.
253
210
  capabilities: deriveRelationCapabilities({
254
211
  surface: relationSurface,
255
212
  }),
256
213
  };
257
- // The edge's resolved actions, carried out of the SAME projection the
258
- // field above came from. Both members are non-null exactly on an owning
259
- // edge, and PSL forbids a back-reference from declaring either — so the
260
- // pair is tested together rather than one member at a time, and a
261
- // back-reference contributes nothing instead of contributing two nulls.
262
214
  if (projection.edge.onDelete !== null &&
263
215
  projection.edge.onUpdate !== null) {
264
216
  owningEdges.push({
265
217
  field: projection.edge.field,
266
- // The PARENT. Deleting one of these does `onDelete` to rows of
267
- // `model.name`, which is the key this list hangs under.
268
218
  target: projection.edge.target,
269
219
  cardinality: projection.edge.cardinality,
270
220
  onDelete: projection.edge.onDelete,
@@ -276,15 +226,10 @@ function composeResource(input) {
276
226
  }
277
227
  }
278
228
  if (unknowns.length > 0) {
279
- // T-3.1 invariant 5: an unrecognised construct is a build stop, never a
280
- // column Aventara quietly decides the client must supply.
281
229
  for (const unknown of unknowns) {
282
230
  diagnostics.push(`${unknown.spelling} (${unknown.kind}) in \`${unknown.block ?? "<top level>"}\` at ${unknown.site.file}:${unknown.site.line}: ${unknown.text} — the reader does not model it, and a silently dropped construct is how a grammar change becomes wrong output instead of a build stop.`);
283
231
  }
284
232
  }
285
- // Task 4's cross-check, run from the SURFACE's own `createOptional` bit. The
286
- // marker is one bit over several causes and is never read as a trait; this is
287
- // the one direction it is read in.
288
233
  const createOptional = Object.entries(exposed.scalarFields)
289
234
  .filter(([, scalar]) => scalar.createOptional)
290
235
  .map(([name]) => name);
@@ -300,36 +245,16 @@ function composeResource(input) {
300
245
  resource: {
301
246
  fields,
302
247
  identifiers: members,
303
- // The §8.3 gate's single home. Composition supplies its three inputs and
304
- // omits nothing itself.
305
248
  operations: deriveResourceOperations({
306
249
  operations: exposed.operations,
307
250
  identifiers: members,
308
251
  transactions: input.transactions,
309
252
  }),
310
253
  },
311
- // The same list's other projection: `members` above, the whole address here.
312
254
  translation: { identifierKeys: identifiers.identifiers },
313
255
  referentialActions: { owningEdges },
314
256
  };
315
257
  }
316
- /* ------------------------------------------------------------------ *
317
- * The scalar registry *
318
- * ------------------------------------------------------------------ */
319
- /**
320
- * The Aventara scalar identities the discovered fields actually use, in core's
321
- * own `BUILT_IN_SCALARS` declaration order.
322
- *
323
- * Ordered by the union rather than by field declaration order for the reason
324
- * `capability.deriver.ts` orders its lists that way: the canonical form must be a
325
- * property of the model and not of how a schema happened to be written. (The
326
- * emitted artifact is canonicalized again before hashing, so this order is for
327
- * readers of the model in memory; it costs nothing and removes a question.)
328
- *
329
- * An enum-backed column contributes NO scalar: C-810 makes it a ScalarField whose
330
- * type ref takes the `enum` arm, and the enum registry is where its identity
331
- * lives.
332
- */
333
258
  function composeScalarRegistry(used) {
334
259
  const scalars = {};
335
260
  for (const scalar of BUILT_IN_SCALARS) {
@@ -1,22 +1,11 @@
1
1
  import type { AdapterResource, ResourceOperations, TransactionCapability } from "@aventara/core";
2
2
  import type { PrismaAdapterModel } from "./discovery-artifact.interface.js";
3
3
  /**
4
- * T-6 — `deriveResourceOperations`, **the single home of the §8.3 gate**.
5
- *
6
- * §8.3: *"an adapter … MUST advertise the variant only when it can preserve
7
- * framework semantics atomically enough for the provider… If the provider cannot
8
- * guarantee the required semantics, the variant is omitted."* Every omission in
9
- * Phase 8 is decided here. No mapper, no discoverer, no emitter and no executor
10
- * may drop a variant, because an omission in two places is an omission nobody can
11
- * audit.
4
+ * No mapper, no discoverer, no emitter and no executor may drop a variant, because
5
+ * an omission in two places is an omission nobody can audit.
12
6
  *
13
7
  * # Three inputs, and `provider` is not one of them
14
8
  *
15
- * The available operation set is read from the client's own `<M>Delegate` (T-3) —
16
- * at 7.10.0, identically on both Phase 8 engines and on all six models (four
17
- * when this was first measured; re-measured over `User` and `Profile` too when
18
- * the one-to-one fixture landed, and the set is unchanged):
19
- *
20
9
  * ```text
21
10
  * findUnique, findUniqueOrThrow, findFirst, findFirstOrThrow, findMany, create,
22
11
  * createMany, createManyAndReturn, delete, update, deleteMany, updateMany,
@@ -29,46 +18,29 @@ import type { PrismaAdapterModel } from "./discovery-artifact.interface.js";
29
18
  * needs. The whole `PrismaProviderProfile` is deliberately NOT accepted: it
30
19
  * carries `provider`, and a module that cannot be handed a provider cannot branch
31
20
  * on one (dictionary invariant 6). `isolationLevels` is not a precondition of any
32
- * variant (C-830: Phase 8 escalates no isolation).
21
+ * variant.
33
22
  *
34
23
  * `supportsEmulatedFirstVariants` is **derived, exactly `transactions !== "none"`,
35
- * and stored nowhere** (T-2's amendment, D-824). A stored copy would be a second
36
- * source of truth for a one-line derivation.
24
+ * and stored nowhere**. A stored copy would be a second source of truth for a
25
+ * one-line derivation.
37
26
  *
38
27
  * # What is NOT gated here, and why that is deliberate
39
28
  *
40
- * `delete.many` is absent on **every** profile (C-829, D-826): 7.10.0 has no
41
- * `deleteManyAndReturn`, the architect ruled the variant omitted rather than
42
- * emulated, and the omission does **not** vary with `transactions`. Pairing it
43
- * with the emulation gate would re-teach the read-capability-depends-on-
44
- * transaction-capability coupling C-829 records as no longer existing.
45
- *
46
- * The first-style variants' preconditions are the **read**, an identifier, a
47
- * transaction — the three `emulatedFirst` carries, because all three are shared —
48
- * and then **the chosen route's own write call**, which each family contributes
49
- * itself. §14e's route is **Route A**: the read selects the identifier and the
50
- * write is Prisma's own `update`/`delete` narrowed to it with the caller's
51
- * predicate re-asserted under `AND`. Task 10 measured both routes on the real
52
- * 7.10.0 client on both engines, every row came out for Route A, and the choice is
53
- * propagated across the 21 rows of `translation/operation.translator.ts`'s table;
54
- * `FIRST_VARIANT_WRITE_ROUTE` there is the one cell that names it.
55
- *
56
29
  * So `update.first` reads `emulatedFirst && exposes("update")` and `delete.first`
57
30
  * reads `emulatedFirst && exposes("delete")`. Under Route B those cells would read
58
31
  * `updateManyAndReturn` and `deleteMany` instead — which is exactly why the write
59
- * call is NOT folded into `emulatedFirst`: a shared precondition naming one route's
60
- * call would assert a gate the other route does not have, an assertion that cannot
61
- * fail for the right reason (C-817, T-7.1.1). A reversal of §14e is an edit to
62
- * these two cells and to the translator's one, and to nothing else here.
32
+ * call is NOT folded into `emulatedFirst`: a shared precondition naming one
33
+ * route's call would assert a gate the other route does not have, an assertion
34
+ * that cannot fail for the right reason.
63
35
  */
64
36
  /** Core's own identifier tuples for a discovered Resource. */
65
37
  type DiscoveredIdentifiers = AdapterResource<PrismaAdapterModel>["identifiers"];
66
38
  export type ResourceOperationsInput = {
67
- /** `<M>Delegate`'s own methods, as the types reader read them (T-3). */
39
+ /** `<M>Delegate`'s own methods, as the types reader read them. */
68
40
  readonly operations: readonly string[];
69
41
  /** The model's usable identifiers; empty means no unique addressing exists. */
70
42
  readonly identifiers: DiscoveredIdentifiers;
71
- /** The one declared profile row this gate reads (T-2's amendment). */
43
+ /** The one declared profile row this gate reads. */
72
44
  readonly transactions: TransactionCapability;
73
45
  };
74
46
  export declare function deriveResourceOperations(input: ResourceOperationsInput): ResourceOperations;
@@ -1,50 +1,14 @@
1
- /* ------------------------------------------------------------------ *
2
- * The scopes each advertised variant qualifies its arguments with *
3
- * ------------------------------------------------------------------ */
4
- /**
5
- * The scope follows the **Prisma call the gate below routes the variant to**, and
6
- * those calls fall into four argument shapes. The scope therefore sits beside the
7
- * gate that chose the call, where a reversal of §14e's route is visible as an edit
8
- * to one cell — not in a constant in core, which would be a tabulated provider fact
9
- * and wrong for the next adapter (D-802, §4 Q2).
10
- *
11
- * **The narrowest honest encoding (F-821).** Two of the four shapes are the
12
- * unrestricted positions — the `"record"` projection and the `"nested"` data
13
- * argument — which is exactly what core's readers (`projectionScopeOf`,
14
- * `writeScopeOf`) answer for `true`. A descriptor stating them carries no
15
- * information: core's canonicalizer collapsed it before any Contract value or
16
- * hash, but it lengthened the artifact and moved its `modelHash`. So those
17
- * variants are `true`, and a descriptor is stated only where it narrows: the two
18
- * bulk shapes. `true` also stays the answer for the variants with no
19
- * scope-qualified argument at all (`count` / `deleteMany` return a number, and the
20
- * delete family has no data argument) — `OperationVariantScopes` requires at least
21
- * one scope, so nothing else is representable there.
22
- */
23
- /**
24
- * A row-returning read (`findFirst`, `findUnique`, `findMany`), and a
25
- * row-returning write or delete of ONE addressed record (`create`, `update`,
26
- * `upsert`, `delete`, and the emulated first-style variants, whose §14e Route A
27
- * write is Prisma's own `update` / `delete` narrowed to the identifier): the
28
- * unrestricted projection and, for a write, the nested data argument — what
29
- * `true` already means.
30
- */
31
1
  const SINGLE_RECORD = true;
32
- /** `createManyAndReturn` / `updateManyAndReturn`: both axes are the bulk ones. */
33
2
  const WRITES_MANY_AND_RETURNS = {
34
3
  projection: "bulkWrite",
35
4
  write: "bulkWrite",
36
5
  };
37
- /** `createMany` / `updateMany`: the same flat data argument, and no projection. */
38
6
  const WRITES_MANY_AND_COUNTS = { write: "bulkWrite" };
39
- /** `count` / `deleteMany`: no projection argument and no data argument. */
40
7
  const QUALIFIES_NO_ARGUMENT = true;
41
8
  export function deriveResourceOperations(input) {
42
9
  const exposes = (method) => input.operations.includes(method);
43
10
  const addressable = input.identifiers.length > 0;
44
- // Derived, never stored: the emulated read-then-write sequence runs inside one
45
- // interactive transaction (D-807), so a driver without one cannot carry it.
46
11
  const emulates = input.transactions !== "none";
47
- /** A first-style variant is a read, an identifier to write by, and a transaction. */
48
12
  const emulatedFirst = exposes("findFirst") && addressable && emulates;
49
13
  const find = {
50
14
  ...(exposes("findFirst") ? { first: SINGLE_RECORD } : {}),
@@ -54,7 +18,6 @@ export function deriveResourceOperations(input) {
54
18
  };
55
19
  const create = {
56
20
  ...(exposes("create") ? { one: SINGLE_RECORD } : {}),
57
- // C-817: the row-returning bulk write is what `create.many` owes its caller.
58
21
  ...(exposes("createManyAndReturn")
59
22
  ? { many: WRITES_MANY_AND_RETURNS }
60
23
  : {}),
@@ -71,8 +34,6 @@ export function deriveResourceOperations(input) {
71
34
  const remove = {
72
35
  ...(emulatedFirst && exposes("delete") ? { first: SINGLE_RECORD } : {}),
73
36
  ...(exposes("delete") && addressable ? { unique: SINGLE_RECORD } : {}),
74
- // `many` is ABSENT unconditionally (C-829). `delete.count` is the supported
75
- // way to delete a filtered set.
76
37
  ...(exposes("deleteMany") ? { count: QUALIFIES_NO_ARGUMENT } : {}),
77
38
  };
78
39
  const upsert = {
@@ -86,7 +47,6 @@ export function deriveResourceOperations(input) {
86
47
  ...(nonEmpty(upsert) ? { upsert } : {}),
87
48
  };
88
49
  }
89
- /** A family with no available variant is absent, not an empty object. */
90
50
  function nonEmpty(family) {
91
51
  return Object.keys(family).length > 0;
92
52
  }