@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.
Files changed (86) hide show
  1. package/LICENSE +91 -0
  2. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  3. package/README.md +564 -0
  4. package/dist/discovery/artifact.loader.d.ts +26 -0
  5. package/dist/discovery/artifact.loader.js +124 -0
  6. package/dist/discovery/capability-surface.interface.d.ts +191 -0
  7. package/dist/discovery/capability-surface.interface.js +13 -0
  8. package/dist/discovery/capability.deriver.d.ts +92 -0
  9. package/dist/discovery/capability.deriver.js +350 -0
  10. package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
  11. package/dist/discovery/discovery-artifact.interface.js +1 -0
  12. package/dist/discovery/enum.mapper.d.ts +59 -0
  13. package/dist/discovery/enum.mapper.js +32 -0
  14. package/dist/discovery/field.mapper.d.ts +244 -0
  15. package/dist/discovery/field.mapper.js +387 -0
  16. package/dist/discovery/identifier.mapper.d.ts +188 -0
  17. package/dist/discovery/identifier.mapper.js +132 -0
  18. package/dist/discovery/model.discoverer.d.ts +108 -0
  19. package/dist/discovery/model.discoverer.js +341 -0
  20. package/dist/discovery/operations.deriver.d.ts +75 -0
  21. package/dist/discovery/operations.deriver.js +92 -0
  22. package/dist/discovery/scalar.mapper.d.ts +81 -0
  23. package/dist/discovery/scalar.mapper.js +51 -0
  24. package/dist/discovery/schema-facts.interface.d.ts +174 -0
  25. package/dist/discovery/schema-facts.interface.js +11 -0
  26. package/dist/discovery/schema.reader.d.ts +162 -0
  27. package/dist/discovery/schema.reader.js +692 -0
  28. package/dist/discovery/types.reader.d.ts +24 -0
  29. package/dist/discovery/types.reader.js +617 -0
  30. package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
  31. package/dist/discovery/vocabulary.dictionary.js +429 -0
  32. package/dist/errors/error.normalizer.d.ts +250 -0
  33. package/dist/errors/error.normalizer.js +135 -0
  34. package/dist/errors/prisma-error.reader.d.ts +144 -0
  35. package/dist/errors/prisma-error.reader.js +167 -0
  36. package/dist/execution/first-variant.emulator.d.ts +64 -0
  37. package/dist/execution/first-variant.emulator.js +205 -0
  38. package/dist/execution/operation.executor.d.ts +87 -0
  39. package/dist/execution/operation.executor.js +210 -0
  40. package/dist/execution/result.shaper.d.ts +103 -0
  41. package/dist/execution/result.shaper.js +141 -0
  42. package/dist/execution/transaction.executor.d.ts +147 -0
  43. package/dist/execution/transaction.executor.js +13 -0
  44. package/dist/generate/artifact.emitter.d.ts +50 -0
  45. package/dist/generate/artifact.emitter.js +105 -0
  46. package/dist/generate/declared-support.reader.d.ts +33 -0
  47. package/dist/generate/declared-support.reader.js +74 -0
  48. package/dist/generate/discovery.bin.d.ts +2 -0
  49. package/dist/generate/discovery.bin.js +15 -0
  50. package/dist/generate/discovery.cli.d.ts +24 -0
  51. package/dist/generate/discovery.cli.js +163 -0
  52. package/dist/generate/existence.gate.d.ts +73 -0
  53. package/dist/generate/existence.gate.js +91 -0
  54. package/dist/generate/index.d.ts +81 -0
  55. package/dist/generate/index.js +65 -0
  56. package/dist/generate/node-version.guard.d.ts +8 -0
  57. package/dist/generate/node-version.guard.js +59 -0
  58. package/dist/generate/prisma-cli.resolver.d.ts +48 -0
  59. package/dist/generate/prisma-cli.resolver.js +95 -0
  60. package/dist/generate/prisma.generate.d.ts +164 -0
  61. package/dist/generate/prisma.generate.js +278 -0
  62. package/dist/generate/reader-crosscheck.d.ts +156 -0
  63. package/dist/generate/reader-crosscheck.js +202 -0
  64. package/dist/index.d.ts +5 -0
  65. package/dist/index.js +1 -0
  66. package/dist/prisma-client.interface.d.ts +48 -0
  67. package/dist/prisma-client.interface.js +9 -0
  68. package/dist/prisma.adapter.d.ts +55 -0
  69. package/dist/prisma.adapter.js +105 -0
  70. package/dist/provider/provider-profile.d.ts +53 -0
  71. package/dist/provider/provider-profile.js +91 -0
  72. package/dist/translation/identifier.addressing.d.ts +80 -0
  73. package/dist/translation/identifier.addressing.js +30 -0
  74. package/dist/translation/mutation.translator.d.ts +115 -0
  75. package/dist/translation/mutation.translator.js +580 -0
  76. package/dist/translation/operation.translator.d.ts +273 -0
  77. package/dist/translation/operation.translator.js +377 -0
  78. package/dist/translation/order-pagination.translator.d.ts +86 -0
  79. package/dist/translation/order-pagination.translator.js +159 -0
  80. package/dist/translation/projection.translator.d.ts +115 -0
  81. package/dist/translation/projection.translator.js +151 -0
  82. package/dist/translation/scalar.conversion.d.ts +101 -0
  83. package/dist/translation/scalar.conversion.js +221 -0
  84. package/dist/translation/where.translator.d.ts +79 -0
  85. package/dist/translation/where.translator.js +254 -0
  86. package/package.json +102 -0
@@ -0,0 +1,244 @@
1
+ import { type AdapterLifecycleTrait, type AdapterRelationField, type AdapterScalarField } from "@aventara/core";
2
+ import type { PrismaResourceSurface } from "./capability-surface.interface.js";
3
+ import type { PrismaAdapterModel, PrismaReferentialAction } from "./discovery-artifact.interface.js";
4
+ import type { PrismaSchemaField, PrismaSchemaUnknown } from "./schema-facts.interface.js";
5
+ import { type PrismaDiscoveryDiagnostic, type PrismaEvidence } from "./vocabulary.dictionary.js";
6
+ /**
7
+ * Task 4 — one PSL field, answered as an `AdapterScalarField` or an
8
+ * `AdapterRelationField` (C-808, C-811, C-813).
9
+ *
10
+ * # Nullability comes from PSL, and the required `Json` column is why
11
+ *
12
+ * `nullable` is `PrismaSchemaField.optional` — PSL's `?` — and **nothing else**
13
+ * (T-3.1 invariant 4). The type surface cannot answer the question, and the
14
+ * damage is not hypothetical: `$MetricPayload.scalars.sJson` resolves to
15
+ * `null | string | number | false | true | JsonObject | JsonArray` while PSL says
16
+ * `sJson Json`, required, because JSON null is a *value*. The reader's own facts
17
+ * point the same wrong way — `JsonFilterBase.array_contains` admits `| null` and
18
+ * `equals` admits `JsonNullValueFilter` on that same required column — so a
19
+ * plausible "the operands mention null, therefore the column is nullable" rule
20
+ * is wrong for **every** `Json` field while being right everywhere else. That is
21
+ * the whole reason this rule is stated as a rule rather than left to taste.
22
+ *
23
+ * # The create-optional marker is a cross-check, never a source
24
+ *
25
+ * {@link crossCheckCreateOptional} is a separate function that *consumes* this
26
+ * projection, and the data flows one way only: the marker never reaches
27
+ * {@link projectPrismaField}. `<M>UncheckedCreateInput`'s `?` is **one bit over
28
+ * several causes** — `@default(uuid())`, `@default(BRONZE)`, `@default(now())`,
29
+ * `@updatedAt`, plain nullability, and a scalar list, which needs no value
30
+ * because the empty list is one — and nothing on the 🟢 surface tells them
31
+ * apart. Deriving a trait *from* the marker would invert the check into the bug
32
+ * it exists to catch: it would make the two readers agree by construction and
33
+ * detect nothing.
34
+ *
35
+ * # `@default` is matched, never parsed
36
+ *
37
+ * {@link matchPrismaDefault} walks `PRISMA_DEFAULT_TABLE` over the raw argument
38
+ * text the PSL reader carried across unchanged. B1 deferred this matcher to this
39
+ * task deliberately: the dictionary owns the data, and the lookup belongs where
40
+ * a red test can drive it.
41
+ *
42
+ * The matcher adds **one** discriminant the dictionary's table does not state,
43
+ * and it is a shape test rather than a parse: an argument that begins with an
44
+ * identifier followed by `(` is a *generator form*, and anything else is a
45
+ * *static value*. Both are `DEFAULTED`; only an unrecognised generator form
46
+ * records an unknown. Without that split, `tier Tier @default(BRONZE)` — a field
47
+ * of the Phase 8 schemas — would record an unknown and fail the generate step,
48
+ * while C-811's own acceptance criteria say `@default("x")` is `["DEFAULTED"]`
49
+ * with nothing recorded. The test never reads *inside* the argument, so
50
+ * `dbgenerated("gen_random_uuid()")` remains a key and `"a, b // not a comment"`
51
+ * remains opaque text.
52
+ *
53
+ * # A relation carries four members, and the FK is not one of them
54
+ *
55
+ * `target`, `cardinality`, `nullable`, and (from Task 6) `capabilities`. The
56
+ * `@relation(fields: […], references: […])` scalars are read, used to identify
57
+ * the FK column and check it against the edge's own optionality, and then
58
+ * **discarded** (C-813). `@relation`'s *name* is not an Aventara concept either,
59
+ * and its referential actions are not core's yet, so both travel beside the
60
+ * projection as {@link PrismaRelationEdge} rather than inside it: pairing is
61
+ * discovery-internal, and it is **PSL-only** — the name
62
+ * appears nowhere on the 🟢 surface, so from the types alone `Author` has two
63
+ * `Author` edges with nothing to pair them.
64
+ *
65
+ * # Two failure vocabularies, kept apart
66
+ *
67
+ * A **diagnostic** is about the schema: PSL declares a column the 🟢 types do
68
+ * not expose (C-808's existence gate, whose fatality the generate step decides),
69
+ * or a Prisma type name in no table. A **throw** is about the readers: the two
70
+ * sources disagree about a fact they both state, which no schema can cause and
71
+ * no diagnostic vocabulary describes. The types reader draws the same line.
72
+ */
73
+ /** A reader-versus-reader disagreement: not a schema problem, and not recoverable. */
74
+ export declare class PrismaFieldProjectionError extends Error {
75
+ readonly name = "PrismaFieldProjectionError";
76
+ }
77
+ /**
78
+ * The projection minus `capabilities`, which is Task 6's and is derived from the
79
+ * capability surface rather than from PSL. `Omit` rather than a restatement, so
80
+ * the composed field is the core type by construction.
81
+ */
82
+ export type ProjectedScalarField = Omit<AdapterScalarField<PrismaAdapterModel>, "capabilities">;
83
+ export type ProjectedRelationField = Omit<AdapterRelationField<PrismaAdapterModel>, "capabilities">;
84
+ /**
85
+ * One relation edge as PSL states it — the pairing evidence and the referential
86
+ * actions, neither of which the projected field carries.
87
+ *
88
+ * # Why the actions ride here and not on `AdapterRelationField`
89
+ *
90
+ * `AdapterRelationField` is core's type and its members are C-813's (`target`,
91
+ * `cardinality`, `nullable`, `capabilities`). Wiring a referential action into
92
+ * the Contract is a core change and belongs to the task that owns core's
93
+ * transaction planner, not to discovery. Until then the fact lives where
94
+ * discovery can state it truthfully.
95
+ *
96
+ * # The lookup is inverted, and the edge is shaped so a consumer need not invert
97
+ *
98
+ * `onDelete` is declared on the CHILD — `Kid.parent @relation(…, onDelete:
99
+ * Cascade)` — and fires when the PARENT is deleted. So an edge reads:
100
+ * **deleting a `target` does `onDelete` to rows of `resource`.** A consumer
101
+ * asking "what does deleting `X` remove?" filters `target === X && onDelete ===
102
+ * "cascade"` and reads off `resource`; it does not have to re-derive which side
103
+ * of a pair declared what.
104
+ *
105
+ * `null` on both members means this edge is not the owning side (it declares no
106
+ * `fields:`), so the actions are on its partner — never that no action applies.
107
+ */
108
+ export type PrismaRelationEdge = {
109
+ /** The model DECLARING the edge — the child, where an owning edge is concerned. */
110
+ readonly resource: string;
111
+ readonly field: string;
112
+ /** The model the edge points AT — the parent, whose deletion fires `onDelete`. */
113
+ readonly target: string;
114
+ readonly cardinality: "one" | "many";
115
+ /** `@relation("Name")`, or `null` where PSL wrote none. PSL-only. */
116
+ readonly relationName: string | null;
117
+ /**
118
+ * What deleting a `target` row does to this `resource` row, resolved (an
119
+ * absent PSL argument already carries Prisma's measured default). `null` on a
120
+ * non-owning edge.
121
+ */
122
+ readonly onDelete: PrismaReferentialAction | null;
123
+ /** The same for an update of the referenced key. `null` on a non-owning edge. */
124
+ readonly onUpdate: PrismaReferentialAction | null;
125
+ };
126
+ export type FieldProjectionInput = {
127
+ readonly resource: string;
128
+ /** PSL — what the field IS. */
129
+ readonly field: PrismaSchemaField;
130
+ /** The model's other fields: the only way to identify an FK scalar by name. */
131
+ readonly siblings: readonly PrismaSchemaField[];
132
+ /** TYPE — what this client exposes. The existence gate (C-808). */
133
+ readonly surface: PrismaResourceSurface;
134
+ /** TYPE — `enums.ts`, which is what makes an enum name an enum name. */
135
+ readonly enums: Readonly<Record<string, readonly string[]>>;
136
+ /** PSL — the model names, which is what makes a written type name a relation. */
137
+ readonly models: ReadonlySet<string>;
138
+ };
139
+ export type FieldProjection = {
140
+ readonly outcome: "scalar";
141
+ readonly name: string;
142
+ readonly field: ProjectedScalarField;
143
+ /** Recorded unknowns; a non-empty list FAILS the generate step (T-3.1 invariant 5). */
144
+ readonly unknowns: readonly PrismaSchemaUnknown[];
145
+ } | {
146
+ readonly outcome: "relation";
147
+ readonly name: string;
148
+ readonly field: ProjectedRelationField;
149
+ readonly edge: PrismaRelationEdge;
150
+ } | {
151
+ readonly outcome: "excluded";
152
+ readonly diagnostic: PrismaDiscoveryDiagnostic;
153
+ } | {
154
+ readonly outcome: "unmapped";
155
+ readonly diagnostic: PrismaDiscoveryDiagnostic;
156
+ };
157
+ export declare function projectPrismaField(input: FieldProjectionInput): FieldProjection;
158
+ export type PrismaDefaultOutcome =
159
+ /** A row of `PRISMA_DEFAULT_TABLE` matched, and its evidence travels with it. */
160
+ {
161
+ readonly form: "table";
162
+ readonly traits: readonly AdapterLifecycleTrait[];
163
+ readonly evidence: PrismaEvidence;
164
+ }
165
+ /** A static value — a literal, a number, a boolean, an enum member, a list. */
166
+ | {
167
+ readonly form: "static";
168
+ readonly traits: readonly AdapterLifecycleTrait[];
169
+ }
170
+ /** A generator form the table does not carry: `DEFAULTED`, plus a recorded unknown. */
171
+ | {
172
+ readonly form: "unrecognised-generator";
173
+ readonly traits: readonly AdapterLifecycleTrait[];
174
+ };
175
+ /**
176
+ * T-3.1's `@default` table, looked up — never evaluated. The argument arrives as
177
+ * opaque text precisely so that this function can be a lookup.
178
+ */
179
+ export declare function matchPrismaDefault(argument: string): PrismaDefaultOutcome;
180
+ /** Why a field is legitimately optional in `<M>UncheckedCreateInput`. */
181
+ export type CreateOptionalCause = AdapterLifecycleTrait | "nullable"
182
+ /** A scalar list needs no value: the empty list is one. */
183
+ | "list";
184
+ export type CreateOptionalCrossCheckInput = {
185
+ readonly resource: string;
186
+ /**
187
+ * The **projected** scalar fields — PSL's answers, already computed. Relation
188
+ * edges are not passed: every edge is optional in a create input regardless of
189
+ * anything PSL says, so they carry no information for this check.
190
+ */
191
+ readonly fields: readonly {
192
+ readonly name: string;
193
+ readonly nullable: boolean;
194
+ readonly list: boolean;
195
+ readonly lifecycle: readonly AdapterLifecycleTrait[];
196
+ }[];
197
+ /** TYPE — `<M>UncheckedCreateInput`'s optional members. */
198
+ readonly createOptional: readonly string[];
199
+ };
200
+ export type CreateOptionalCrossCheck = {
201
+ readonly explained: readonly {
202
+ readonly name: string;
203
+ readonly cause: CreateOptionalCause;
204
+ }[];
205
+ /** Marked optional by the types with nothing in PSL to explain it. */
206
+ readonly unexplained: readonly string[];
207
+ readonly messages: readonly string[];
208
+ };
209
+ /**
210
+ * TYPE's only contribution to lifecycle: a check, run in one direction.
211
+ *
212
+ * Every field the 🟢 `<M>UncheckedCreateInput` marks optional must carry a PSL
213
+ * cause. A field the types mark optional with no cause means **one of the two
214
+ * readers is wrong** — that is the entire signal, and it is why the marker is
215
+ * never read as a trait. Names the types mark that this model does not project
216
+ * as a scalar (relation edges) are skipped rather than reported: their
217
+ * optionality is structural.
218
+ */
219
+ export declare function crossCheckCreateOptional(input: CreateOptionalCrossCheckInput): CreateOptionalCrossCheck;
220
+ export type PrismaRelationPair = readonly [
221
+ PrismaRelationEdge,
222
+ PrismaRelationEdge
223
+ ];
224
+ export type PrismaRelationPairing = {
225
+ readonly pairs: readonly PrismaRelationPair[];
226
+ /** Edges nothing pairs: a back-reference the schema never declared, or an ambiguity. */
227
+ readonly unpaired: readonly PrismaRelationEdge[];
228
+ };
229
+ /**
230
+ * Pairs each edge with its back-reference, which **only PSL can do**.
231
+ *
232
+ * An explicit `@relation("Name")` is the pairing evidence, and for the two
233
+ * `Author` → `Author` edges it is the *only* evidence: the name appears nowhere
234
+ * on the 🟢 surface, so from the types alone there are two self-edges and
235
+ * nothing to say they are one relation.
236
+ *
237
+ * Unnamed edges are paired by the unordered model pair, which is unambiguous
238
+ * because PSL requires a name as soon as two relations connect the same two
239
+ * models. Where an unordered pair still has more than two edges, they are left
240
+ * unpaired rather than guessed: the implicit name Prisma would compute lives in
241
+ * the 🛑 runtime data model, and re-deriving it here would be this module
242
+ * reimplementing the generator.
243
+ */
244
+ export declare function pairPrismaRelationEdges(edges: readonly PrismaRelationEdge[]): PrismaRelationPairing;
@@ -0,0 +1,387 @@
1
+ import { LIFECYCLE_TRAITS, } from "@aventara/core";
2
+ import { projectEnumBackedColumn } from "./enum.mapper.js";
3
+ import { mapPrismaScalarField } from "./scalar.mapper.js";
4
+ import { PRISMA_DEFAULT_TABLE, PRISMA_UNMATCHED_DEFAULT_TRAITS, } from "./vocabulary.dictionary.js";
5
+ /**
6
+ * Task 4 — one PSL field, answered as an `AdapterScalarField` or an
7
+ * `AdapterRelationField` (C-808, C-811, C-813).
8
+ *
9
+ * # Nullability comes from PSL, and the required `Json` column is why
10
+ *
11
+ * `nullable` is `PrismaSchemaField.optional` — PSL's `?` — and **nothing else**
12
+ * (T-3.1 invariant 4). The type surface cannot answer the question, and the
13
+ * damage is not hypothetical: `$MetricPayload.scalars.sJson` resolves to
14
+ * `null | string | number | false | true | JsonObject | JsonArray` while PSL says
15
+ * `sJson Json`, required, because JSON null is a *value*. The reader's own facts
16
+ * point the same wrong way — `JsonFilterBase.array_contains` admits `| null` and
17
+ * `equals` admits `JsonNullValueFilter` on that same required column — so a
18
+ * plausible "the operands mention null, therefore the column is nullable" rule
19
+ * is wrong for **every** `Json` field while being right everywhere else. That is
20
+ * the whole reason this rule is stated as a rule rather than left to taste.
21
+ *
22
+ * # The create-optional marker is a cross-check, never a source
23
+ *
24
+ * {@link crossCheckCreateOptional} is a separate function that *consumes* this
25
+ * projection, and the data flows one way only: the marker never reaches
26
+ * {@link projectPrismaField}. `<M>UncheckedCreateInput`'s `?` is **one bit over
27
+ * several causes** — `@default(uuid())`, `@default(BRONZE)`, `@default(now())`,
28
+ * `@updatedAt`, plain nullability, and a scalar list, which needs no value
29
+ * because the empty list is one — and nothing on the 🟢 surface tells them
30
+ * apart. Deriving a trait *from* the marker would invert the check into the bug
31
+ * it exists to catch: it would make the two readers agree by construction and
32
+ * detect nothing.
33
+ *
34
+ * # `@default` is matched, never parsed
35
+ *
36
+ * {@link matchPrismaDefault} walks `PRISMA_DEFAULT_TABLE` over the raw argument
37
+ * text the PSL reader carried across unchanged. B1 deferred this matcher to this
38
+ * task deliberately: the dictionary owns the data, and the lookup belongs where
39
+ * a red test can drive it.
40
+ *
41
+ * The matcher adds **one** discriminant the dictionary's table does not state,
42
+ * and it is a shape test rather than a parse: an argument that begins with an
43
+ * identifier followed by `(` is a *generator form*, and anything else is a
44
+ * *static value*. Both are `DEFAULTED`; only an unrecognised generator form
45
+ * records an unknown. Without that split, `tier Tier @default(BRONZE)` — a field
46
+ * of the Phase 8 schemas — would record an unknown and fail the generate step,
47
+ * while C-811's own acceptance criteria say `@default("x")` is `["DEFAULTED"]`
48
+ * with nothing recorded. The test never reads *inside* the argument, so
49
+ * `dbgenerated("gen_random_uuid()")` remains a key and `"a, b // not a comment"`
50
+ * remains opaque text.
51
+ *
52
+ * # A relation carries four members, and the FK is not one of them
53
+ *
54
+ * `target`, `cardinality`, `nullable`, and (from Task 6) `capabilities`. The
55
+ * `@relation(fields: […], references: […])` scalars are read, used to identify
56
+ * the FK column and check it against the edge's own optionality, and then
57
+ * **discarded** (C-813). `@relation`'s *name* is not an Aventara concept either,
58
+ * and its referential actions are not core's yet, so both travel beside the
59
+ * projection as {@link PrismaRelationEdge} rather than inside it: pairing is
60
+ * discovery-internal, and it is **PSL-only** — the name
61
+ * appears nowhere on the 🟢 surface, so from the types alone `Author` has two
62
+ * `Author` edges with nothing to pair them.
63
+ *
64
+ * # Two failure vocabularies, kept apart
65
+ *
66
+ * A **diagnostic** is about the schema: PSL declares a column the 🟢 types do
67
+ * not expose (C-808's existence gate, whose fatality the generate step decides),
68
+ * or a Prisma type name in no table. A **throw** is about the readers: the two
69
+ * sources disagree about a fact they both state, which no schema can cause and
70
+ * no diagnostic vocabulary describes. The types reader draws the same line.
71
+ */
72
+ /** A reader-versus-reader disagreement: not a schema problem, and not recoverable. */
73
+ export class PrismaFieldProjectionError extends Error {
74
+ name = "PrismaFieldProjectionError";
75
+ }
76
+ export function projectPrismaField(input) {
77
+ return input.models.has(input.field.type)
78
+ ? projectRelationField(input)
79
+ : projectScalarField(input);
80
+ }
81
+ /* ------------------------------------------------------------------ *
82
+ * Scalars *
83
+ * ------------------------------------------------------------------ */
84
+ function projectScalarField(input) {
85
+ const { resource, field, surface, enums } = input;
86
+ const mapped = mapPrismaScalarField({ resource, field, surface, enums });
87
+ if (mapped.outcome === "excluded" || mapped.outcome === "unmapped") {
88
+ // The existence gate and the unmapped-type diagnostic are the scalar
89
+ // mapper's own answers, forwarded whole: restating them here would be a
90
+ // second wording of one decision.
91
+ return mapped;
92
+ }
93
+ const exposed = surface.scalarFields[field.name];
94
+ if (exposed === undefined) {
95
+ throw new PrismaFieldProjectionError(`${resource}.${field.name} passed the existence gate and has no scalar surface`);
96
+ }
97
+ assertTokenAgrees(resource, field, exposed.token);
98
+ const lifecycle = projectLifecycle(resource, field);
99
+ const type = mapped.outcome === "enum"
100
+ ? projectEnumBackedColumn(mapped.enum).type
101
+ : { scalar: mapped.mapping.scalar };
102
+ const format = mapped.outcome === "scalar" ? mapped.mapping.format : undefined;
103
+ return {
104
+ outcome: "scalar",
105
+ name: field.name,
106
+ field: {
107
+ kind: "scalar",
108
+ type,
109
+ ...(format === undefined ? {} : { format }),
110
+ // PSL's `?`, and nothing else (T-3.1 invariant 4).
111
+ nullable: field.optional,
112
+ list: field.list,
113
+ lifecycle: lifecycle.traits,
114
+ },
115
+ unknowns: lifecycle.unknowns,
116
+ };
117
+ }
118
+ /**
119
+ * The `FieldRef` token against the written PSL type.
120
+ *
121
+ * **A token carries its list suffix** — `Post.tags` reads as `String[]`, not
122
+ * `String` — so the suffix is split off before the names are compared and is
123
+ * then the TYPE half of `list`. Comparing the raw token to the PSL type name
124
+ * would make every list field look like a type mismatch, and a provider-drop
125
+ * check built on that would fire on a field that is present.
126
+ */
127
+ function assertTokenAgrees(resource, field, token) {
128
+ const listed = token.endsWith("[]");
129
+ const base = listed ? token.slice(0, -2) : token;
130
+ if (base !== field.type) {
131
+ throw new PrismaFieldProjectionError(`${resource}.${field.name} is written \`${field.type}\` in schema.prisma and the 🟢 types carry the FieldRef token \`${token}\`. The two readers describe one instant, so this is a stale generated client or a reader defect, not a schema the adapter can translate.`);
132
+ }
133
+ if (listed !== field.list) {
134
+ throw new PrismaFieldProjectionError(`${resource}.${field.name} is ${field.list ? "a list" : "not a list"} in schema.prisma and the 🟢 types carry the FieldRef token \`${token}\`, which says the opposite.`);
135
+ }
136
+ }
137
+ /* ------------------------------------------------------------------ *
138
+ * Lifecycle (C-811) *
139
+ * ------------------------------------------------------------------ */
140
+ /**
141
+ * The three adapter traits in core's own declaration order, so a trait list has
142
+ * one canonical order and it is not this module's invention. Order is semantic:
143
+ * it enters the Contract hash.
144
+ */
145
+ const ADAPTER_TRAIT_ORDER = LIFECYCLE_TRAITS.filter((trait) => trait === "DEFAULTED" ||
146
+ trait === "GENERATED_ON_CREATE" ||
147
+ trait === "GENERATED_ON_UPDATE");
148
+ /** An argument that begins with an identifier and an open paren: a generator form. */
149
+ const GENERATOR_FORM = /^[A-Za-z_][A-Za-z0-9_]*\(/;
150
+ /**
151
+ * T-3.1's `@default` table, looked up — never evaluated. The argument arrives as
152
+ * opaque text precisely so that this function can be a lookup.
153
+ */
154
+ export function matchPrismaDefault(argument) {
155
+ for (const row of PRISMA_DEFAULT_TABLE) {
156
+ const matched = "literal" in row.match
157
+ ? row.match.literal === argument
158
+ : row.match.pattern.test(argument);
159
+ if (matched) {
160
+ return { form: "table", traits: row.traits, evidence: row.evidence };
161
+ }
162
+ }
163
+ return GENERATOR_FORM.test(argument)
164
+ ? {
165
+ form: "unrecognised-generator",
166
+ traits: PRISMA_UNMATCHED_DEFAULT_TRAITS,
167
+ }
168
+ : { form: "static", traits: PRISMA_UNMATCHED_DEFAULT_TRAITS };
169
+ }
170
+ function projectLifecycle(resource, field) {
171
+ const present = new Set();
172
+ const unknowns = [];
173
+ if (field.defaultArgument !== null) {
174
+ const outcome = matchPrismaDefault(field.defaultArgument);
175
+ for (const trait of outcome.traits) {
176
+ present.add(trait);
177
+ }
178
+ if (outcome.form === "unrecognised-generator") {
179
+ // Recorded as the same `PrismaSchemaUnknown` the PSL reader records, so
180
+ // it fails the generate step by construction rather than through a second
181
+ // rule written for it. An unrecognised generator function is a build stop,
182
+ // not a column Aventara quietly decides the client must supply.
183
+ unknowns.push({
184
+ kind: "attribute-argument",
185
+ spelling: "@default",
186
+ text: field.defaultArgument,
187
+ block: resource,
188
+ site: field.site,
189
+ });
190
+ }
191
+ }
192
+ if (field.updatedAt) {
193
+ present.add("GENERATED_ON_UPDATE");
194
+ }
195
+ return {
196
+ traits: ADAPTER_TRAIT_ORDER.filter((trait) => present.has(trait)),
197
+ unknowns,
198
+ };
199
+ }
200
+ /**
201
+ * TYPE's only contribution to lifecycle: a check, run in one direction.
202
+ *
203
+ * Every field the 🟢 `<M>UncheckedCreateInput` marks optional must carry a PSL
204
+ * cause. A field the types mark optional with no cause means **one of the two
205
+ * readers is wrong** — that is the entire signal, and it is why the marker is
206
+ * never read as a trait. Names the types mark that this model does not project
207
+ * as a scalar (relation edges) are skipped rather than reported: their
208
+ * optionality is structural.
209
+ */
210
+ export function crossCheckCreateOptional(input) {
211
+ const byName = new Map(input.fields.map((field) => [field.name, field]));
212
+ const explained = [];
213
+ const unexplained = [];
214
+ const messages = [];
215
+ for (const name of input.createOptional) {
216
+ const projected = byName.get(name);
217
+ if (projected === undefined) {
218
+ continue;
219
+ }
220
+ const cause = causeOf(projected);
221
+ if (cause === null) {
222
+ unexplained.push(name);
223
+ messages.push(`\`${input.resource}.${name}\` is optional in the generated \`${input.resource}UncheckedCreateInput\` and schema.prisma gives no reason: it carries no \`@default\`, no \`@updatedAt\`, no \`?\` and no \`[]\`. The marker is one bit over several causes and is never read as a trait, so a field optional in the types with none of them in PSL means one of the two readers is wrong.`);
224
+ continue;
225
+ }
226
+ explained.push({ name, cause });
227
+ }
228
+ return { explained, unexplained, messages };
229
+ }
230
+ function causeOf(projected) {
231
+ // Lifecycle first, in core's order, so a field with both a default and a
232
+ // nullable column reports the stronger cause.
233
+ const trait = ADAPTER_TRAIT_ORDER.filter((candidate) => projected.lifecycle.includes(candidate));
234
+ // `GENERATED_ON_CREATE` outranks the `DEFAULTED` that always accompanies it:
235
+ // it is the more specific reason the client may omit the value.
236
+ const strongest = trait.includes("GENERATED_ON_CREATE")
237
+ ? "GENERATED_ON_CREATE"
238
+ : trait[0];
239
+ if (strongest !== undefined) {
240
+ return strongest;
241
+ }
242
+ if (projected.nullable) {
243
+ return "nullable";
244
+ }
245
+ return projected.list ? "list" : null;
246
+ }
247
+ /* ------------------------------------------------------------------ *
248
+ * Relations (C-813) *
249
+ * ------------------------------------------------------------------ */
250
+ function projectRelationField(input) {
251
+ const { resource, field, siblings, surface } = input;
252
+ const column = `${resource}.${field.name}`;
253
+ const exposed = surface.relationFields[field.name];
254
+ if (exposed === undefined) {
255
+ return {
256
+ outcome: "excluded",
257
+ diagnostic: {
258
+ code: "not-exposed",
259
+ message: `${column} is declared in schema.prisma as an edge to \`${field.type}\` and absent from the generated 🟢 types, so it is omitted: ${field.ignored ? "it carries `@ignore`" : "no schema attribute explains it — a provider drop or a preview-feature exclusion"}.`,
260
+ },
261
+ };
262
+ }
263
+ const cardinality = field.list ? "many" : "one";
264
+ // A to-many edge is a collection, and an absent collection is an empty one:
265
+ // never nullable. A to-one edge is nullable exactly where PSL wrote `?`.
266
+ const nullable = cardinality === "many" ? false : field.optional;
267
+ // TYPE is the cross-check, from `$<M>Payload.objects`: `mentor:
268
+ // $AuthorPayload | null`, `mentees: $AuthorPayload[]`.
269
+ if (exposed.many !== field.list) {
270
+ throw new PrismaFieldProjectionError(`${column} is ${field.list ? "a list" : "a single edge"} in schema.prisma and the 🟢 payload says the opposite.`);
271
+ }
272
+ if (exposed.nullable !== nullable) {
273
+ throw new PrismaFieldProjectionError(`${column} is ${nullable ? "optional" : "required"} in schema.prisma and the 🟢 payload ${exposed.nullable ? "admits" : "refuses"} \`null\`.`);
274
+ }
275
+ if (exposed.target !== field.type) {
276
+ throw new PrismaFieldProjectionError(`${column} targets \`${field.type}\` in schema.prisma and \`${exposed.target}\` on the 🟢 payload.`);
277
+ }
278
+ assertForeignKeyAgrees(column, field, siblings, nullable);
279
+ return {
280
+ outcome: "relation",
281
+ name: field.name,
282
+ // Four members. No target field schema, no FK, no join-table name, no
283
+ // owning-side metadata (C-813).
284
+ field: {
285
+ kind: "relation",
286
+ target: field.type,
287
+ cardinality,
288
+ nullable,
289
+ },
290
+ edge: {
291
+ resource,
292
+ field: field.name,
293
+ target: field.type,
294
+ cardinality,
295
+ relationName: field.relation?.name ?? null,
296
+ // Carried, not projected: the action changes what a delete DOES, so it
297
+ // is evidence a transaction planner needs, and it is not a member of
298
+ // `AdapterRelationField` (see the edge's own doc).
299
+ onDelete: field.relation?.onDelete ?? null,
300
+ onUpdate: field.relation?.onUpdate ?? null,
301
+ },
302
+ };
303
+ }
304
+ /**
305
+ * The FK, read and then discarded.
306
+ *
307
+ * `fields: […]` is used for exactly two things, both of them checks: each named
308
+ * scalar must be a column this model declares, and an **optional FK forces an
309
+ * optional edge**. PSL enforces the second itself — `prisma validate` 7.10.0
310
+ * answers *"The relation field `author` uses the scalar fields authorId. At
311
+ * least one of those fields is optional. Hence the relation field must be
312
+ * optional as well."* — so a violation can only come from a reader defect.
313
+ *
314
+ * The converse does **not** hold, and assuming it would be wrong: `mentor
315
+ * Author? @relation(fields: [mentorId])` with `mentorId String` **required** is
316
+ * valid PSL at 7.10.0 and its client still types `mentor: $AuthorPayload |
317
+ * null`. So the FK is not the source of nullability — the edge's own `?` is.
318
+ *
319
+ * Nothing about the FK enters the projection.
320
+ */
321
+ function assertForeignKeyAgrees(column, field, siblings, nullable) {
322
+ for (const name of field.relation?.fields ?? []) {
323
+ const scalar = siblings.find((sibling) => sibling.name === name);
324
+ if (scalar === undefined) {
325
+ throw new PrismaFieldProjectionError(`${column} names \`${name}\` in \`@relation(fields: …)\` and the model declares no such column.`);
326
+ }
327
+ if (scalar.optional && !nullable) {
328
+ throw new PrismaFieldProjectionError(`${column} is required and its foreign key \`${name}\` is optional. PSL forbids this combination, so the schema cannot have said it: one of the readers is wrong.`);
329
+ }
330
+ }
331
+ }
332
+ /**
333
+ * Pairs each edge with its back-reference, which **only PSL can do**.
334
+ *
335
+ * An explicit `@relation("Name")` is the pairing evidence, and for the two
336
+ * `Author` → `Author` edges it is the *only* evidence: the name appears nowhere
337
+ * on the 🟢 surface, so from the types alone there are two self-edges and
338
+ * nothing to say they are one relation.
339
+ *
340
+ * Unnamed edges are paired by the unordered model pair, which is unambiguous
341
+ * because PSL requires a name as soon as two relations connect the same two
342
+ * models. Where an unordered pair still has more than two edges, they are left
343
+ * unpaired rather than guessed: the implicit name Prisma would compute lives in
344
+ * the 🛑 runtime data model, and re-deriving it here would be this module
345
+ * reimplementing the generator.
346
+ */
347
+ export function pairPrismaRelationEdges(edges) {
348
+ const groups = new Map();
349
+ for (const edge of edges) {
350
+ const key = edge.relationName === null
351
+ ? `implicit:${[edge.resource, edge.target].sort().join("<->")}`
352
+ : `named:${edge.relationName}`;
353
+ const group = groups.get(key);
354
+ if (group === undefined) {
355
+ groups.set(key, [edge]);
356
+ }
357
+ else {
358
+ group.push(edge);
359
+ }
360
+ }
361
+ const pairs = [];
362
+ const unpaired = [];
363
+ for (const group of [...groups.keys()].sort().map((key) => groups.get(key))) {
364
+ const [first, second, ...rest] = group ?? [];
365
+ if (first === undefined) {
366
+ continue;
367
+ }
368
+ if (second === undefined || rest.length > 0) {
369
+ unpaired.push(...(group ?? []));
370
+ continue;
371
+ }
372
+ if (first.resource !== second.target || first.target !== second.resource) {
373
+ unpaired.push(first, second);
374
+ continue;
375
+ }
376
+ pairs.push(sortKey(first) <= sortKey(second) ? [first, second] : [second, first]);
377
+ }
378
+ // Sorted by the first edge, so the answer's order is a property of the model
379
+ // and not of how the groups happened to be keyed.
380
+ return {
381
+ pairs: [...pairs].sort((left, right) => sortKey(left[0]).localeCompare(sortKey(right[0]))),
382
+ unpaired,
383
+ };
384
+ }
385
+ function sortKey(edge) {
386
+ return `${edge.resource}.${edge.field}`;
387
+ }