@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.
- package/README.md +33 -20
- package/dist/discovery/artifact.loader.d.ts +6 -2
- package/dist/discovery/artifact.loader.js +12 -57
- package/dist/discovery/capability-surface.interface.d.ts +29 -40
- package/dist/discovery/capability-surface.interface.js +0 -12
- package/dist/discovery/capability.deriver.d.ts +10 -40
- package/dist/discovery/capability.deriver.js +0 -179
- package/dist/discovery/client-shape.verifier.d.ts +13 -0
- package/dist/discovery/client-shape.verifier.js +93 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +55 -30
- package/dist/discovery/enum.mapper.d.ts +6 -14
- package/dist/discovery/enum.mapper.js +1 -2
- package/dist/discovery/field.mapper.d.ts +49 -63
- package/dist/discovery/field.mapper.js +0 -161
- package/dist/discovery/identifier.mapper.d.ts +38 -77
- package/dist/discovery/identifier.mapper.js +0 -64
- package/dist/discovery/model.discoverer.d.ts +18 -35
- package/dist/discovery/model.discoverer.js +66 -141
- package/dist/discovery/operations.deriver.d.ts +10 -38
- package/dist/discovery/operations.deriver.js +0 -40
- package/dist/discovery/scalar.mapper.d.ts +12 -35
- package/dist/discovery/scalar.mapper.js +0 -8
- package/dist/discovery/schema-facts.interface.d.ts +26 -25
- package/dist/discovery/schema-facts.interface.js +0 -10
- package/dist/discovery/schema.reader.d.ts +53 -64
- package/dist/discovery/schema.reader.js +0 -174
- package/dist/discovery/types.reader.d.ts +0 -4
- package/dist/discovery/types.reader.js +2 -177
- package/dist/discovery/vocabulary.dictionary.d.ts +36 -75
- package/dist/discovery/vocabulary.dictionary.js +2 -152
- package/dist/errors/error.normalizer.d.ts +85 -135
- package/dist/errors/error.normalizer.js +1 -70
- package/dist/errors/prisma-error.reader.d.ts +53 -68
- package/dist/errors/prisma-error.reader.js +0 -91
- package/dist/execution/first-variant.emulator.d.ts +11 -22
- package/dist/execution/first-variant.emulator.js +0 -142
- package/dist/execution/operation.executor.d.ts +12 -19
- package/dist/execution/operation.executor.js +0 -92
- package/dist/execution/result.shaper.d.ts +22 -31
- package/dist/execution/result.shaper.js +0 -24
- package/dist/execution/transaction.executor.d.ts +36 -73
- package/dist/generate/artifact.emitter.d.ts +27 -27
- package/dist/generate/artifact.emitter.js +30 -13
- package/dist/generate/declared-support.reader.d.ts +11 -11
- package/dist/generate/declared-support.reader.js +0 -12
- package/dist/generate/discovery.bin.js +0 -10
- package/dist/generate/discovery.cli.js +0 -55
- package/dist/generate/existence.gate.d.ts +26 -32
- package/dist/generate/existence.gate.js +0 -6
- package/dist/generate/index.d.ts +10 -12
- package/dist/generate/index.js +0 -42
- package/dist/generate/node-version.guard.js +0 -12
- package/dist/generate/prisma-cli.resolver.d.ts +9 -11
- package/dist/generate/prisma-cli.resolver.js +0 -13
- package/dist/generate/prisma.generate.d.ts +49 -65
- package/dist/generate/prisma.generate.js +13 -123
- package/dist/generate/reader-crosscheck.d.ts +36 -60
- package/dist/generate/reader-crosscheck.js +5 -41
- package/dist/prisma-client.interface.d.ts +14 -19
- package/dist/prisma-client.interface.js +0 -6
- package/dist/prisma.adapter.d.ts +9 -17
- package/dist/prisma.adapter.js +11 -53
- package/dist/provider/provider-profile.d.ts +6 -23
- package/dist/provider/provider-profile.js +0 -64
- package/dist/translation/identifier.addressing.d.ts +15 -22
- package/dist/translation/identifier.addressing.js +0 -10
- package/dist/translation/mutation.translator.d.ts +19 -43
- package/dist/translation/mutation.translator.js +1 -209
- package/dist/translation/operation.translator.d.ts +36 -63
- package/dist/translation/operation.translator.js +1 -51
- package/dist/translation/order-pagination.translator.d.ts +24 -31
- package/dist/translation/order-pagination.translator.js +0 -43
- package/dist/translation/projection.translator.d.ts +25 -58
- package/dist/translation/projection.translator.js +0 -27
- package/dist/translation/scalar.conversion.d.ts +15 -26
- package/dist/translation/scalar.conversion.js +1 -129
- package/dist/translation/where.translator.d.ts +17 -28
- package/dist/translation/where.translator.js +0 -83
- package/package.json +3 -3
|
@@ -4,70 +4,59 @@ import type { PrismaAdapterModel, PrismaReferentialAction } from "./discovery-ar
|
|
|
4
4
|
import type { PrismaSchemaField, PrismaSchemaUnknown } from "./schema-facts.interface.js";
|
|
5
5
|
import { type PrismaDiscoveryDiagnostic, type PrismaEvidence } from "./vocabulary.dictionary.js";
|
|
6
6
|
/**
|
|
7
|
-
* Task 4 — one PSL field, answered as an `AdapterScalarField` or an
|
|
8
|
-
* `AdapterRelationField` (C-808, C-811, C-813).
|
|
9
|
-
*
|
|
10
7
|
* # Nullability comes from PSL, and the required `Json` column is why
|
|
11
8
|
*
|
|
12
|
-
* `nullable` is `PrismaSchemaField.optional` — PSL's `?` — and **nothing else
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
9
|
+
* `nullable` is `PrismaSchemaField.optional` — PSL's `?` — and **nothing else**.
|
|
10
|
+
* The type surface cannot answer the question, and the damage is not hypothetical:
|
|
11
|
+
* `$MetricPayload.scalars.sJson` resolves to `null | string | number | false |
|
|
12
|
+
* true | JsonObject | JsonArray` while PSL says `sJson Json`, required, because
|
|
13
|
+
* JSON null is a *value*. The reader's own facts point the same wrong way —
|
|
14
|
+
* `JsonFilterBase.array_contains` admits `| null` and `equals` admits
|
|
15
|
+
* `JsonNullValueFilter` on that same required column — so a plausible "the
|
|
16
|
+
* operands mention null, therefore the column is nullable" rule is wrong for
|
|
17
|
+
* **every** `Json` field while being right everywhere else. That is the whole
|
|
18
|
+
* reason this rule is stated as a rule rather than left to taste.
|
|
22
19
|
*
|
|
23
20
|
* # The create-optional marker is a cross-check, never a source
|
|
24
21
|
*
|
|
25
22
|
* {@link crossCheckCreateOptional} is a separate function that *consumes* this
|
|
26
|
-
* projection, and the data flows one way only: the marker never reaches
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `@updatedAt`, plain nullability, and a scalar list, which needs no value
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* detect nothing.
|
|
23
|
+
* projection, and the data flows one way only: the marker never reaches {@link
|
|
24
|
+
* projectPrismaField}. `<M>UncheckedCreateInput`'s `?` is **one bit over several
|
|
25
|
+
* causes** — `@default(uuid())`, `@default(BRONZE)`, `@default(now())`,
|
|
26
|
+
* `@updatedAt`, plain nullability, and a scalar list, which needs no value because
|
|
27
|
+
* the empty list is one — and nothing on the 🟢 surface tells them apart. Deriving
|
|
28
|
+
* a trait *from* the marker would invert the check into the bug it exists to
|
|
29
|
+
* catch: it would make the two readers agree by construction and detect nothing.
|
|
34
30
|
*
|
|
35
31
|
* # `@default` is matched, never parsed
|
|
36
32
|
*
|
|
37
33
|
* {@link matchPrismaDefault} walks `PRISMA_DEFAULT_TABLE` over the raw argument
|
|
38
|
-
* text the PSL reader carried across unchanged.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
|
34
|
+
* text the PSL reader carried across unchanged.
|
|
35
|
+
*
|
|
36
|
+
* The matcher adds **one** discriminant the dictionary's table does not state, and
|
|
37
|
+
* it is a shape test rather than a parse: an argument that begins with an
|
|
38
|
+
* identifier followed by `(` is a *generator form*, and anything else is a *static
|
|
39
|
+
* value*. Both are `DEFAULTED`; only an unrecognised generator form records an
|
|
40
|
+
* unknown. The test never reads *inside* the argument, so
|
|
50
41
|
* `dbgenerated("gen_random_uuid()")` remains a key and `"a, b // not a comment"`
|
|
51
42
|
* remains opaque text.
|
|
52
43
|
*
|
|
53
44
|
* # A relation carries four members, and the FK is not one of them
|
|
54
45
|
*
|
|
55
|
-
* `target`, `cardinality`, `nullable`, and
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* `Author` edges with nothing to pair them.
|
|
46
|
+
* `target`, `cardinality`, `nullable`, and `capabilities`. The `@relation(fields:
|
|
47
|
+
* […], references: […])` scalars are read, used to identify the FK column and
|
|
48
|
+
* check it against the edge's own optionality, and then **discarded**.
|
|
49
|
+
* `@relation`'s *name* is not an Aventara concept either, and its referential
|
|
50
|
+
* actions are not core's yet, so both travel beside the projection as {@link
|
|
51
|
+
* PrismaRelationEdge} rather than inside it: pairing is discovery-internal, and it
|
|
52
|
+
* is **PSL-only** — the name appears nowhere on the 🟢 surface, so from the types
|
|
53
|
+
* alone `Author` has two `Author` edges with nothing to pair them.
|
|
64
54
|
*
|
|
65
55
|
* # Two failure vocabularies, kept apart
|
|
66
56
|
*
|
|
67
|
-
* A **diagnostic** is about the schema: PSL declares a column the 🟢 types do
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* sources disagree about a fact they both state, which no schema can cause and
|
|
57
|
+
* A **diagnostic** is about the schema: PSL declares a column the 🟢 types do not
|
|
58
|
+
* expose, or a Prisma type name in no table. A **throw** is about the readers: the
|
|
59
|
+
* two sources disagree about a fact they both state, which no schema can cause and
|
|
71
60
|
* no diagnostic vocabulary describes. The types reader draws the same line.
|
|
72
61
|
*/
|
|
73
62
|
/** A reader-versus-reader disagreement: not a schema problem, and not recoverable. */
|
|
@@ -75,9 +64,8 @@ export declare class PrismaFieldProjectionError extends Error {
|
|
|
75
64
|
readonly name = "PrismaFieldProjectionError";
|
|
76
65
|
}
|
|
77
66
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* the composed field is the core type by construction.
|
|
67
|
+
* `Omit` rather than a restatement, so the composed field is the core type by
|
|
68
|
+
* construction.
|
|
81
69
|
*/
|
|
82
70
|
export type ProjectedScalarField = Omit<AdapterScalarField<PrismaAdapterModel>, "capabilities">;
|
|
83
71
|
export type ProjectedRelationField = Omit<AdapterRelationField<PrismaAdapterModel>, "capabilities">;
|
|
@@ -87,20 +75,18 @@ export type ProjectedRelationField = Omit<AdapterRelationField<PrismaAdapterMode
|
|
|
87
75
|
*
|
|
88
76
|
* # Why the actions ride here and not on `AdapterRelationField`
|
|
89
77
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* transaction planner, not to discovery. Until then the fact lives where
|
|
94
|
-
* discovery can state it truthfully.
|
|
78
|
+
* Wiring a referential action into the Contract is a core change and belongs to
|
|
79
|
+
* the task that owns core's transaction planner, not to discovery. Until then the
|
|
80
|
+
* fact lives where discovery can state it truthfully.
|
|
95
81
|
*
|
|
96
82
|
* # The lookup is inverted, and the edge is shaped so a consumer need not invert
|
|
97
83
|
*
|
|
98
84
|
* `onDelete` is declared on the CHILD — `Kid.parent @relation(…, onDelete:
|
|
99
|
-
* Cascade)` — and fires when the PARENT is deleted. So an edge reads:
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
85
|
+
* Cascade)` — and fires when the PARENT is deleted. So an edge reads: **deleting a
|
|
86
|
+
* `target` does `onDelete` to rows of `resource`.** A consumer asking "what does
|
|
87
|
+
* deleting `X` remove?" filters `target === X && onDelete === "cascade"` and reads
|
|
88
|
+
* off `resource`; it does not have to re-derive which side of a pair declared
|
|
89
|
+
* what.
|
|
104
90
|
*
|
|
105
91
|
* `null` on both members means this edge is not the owning side (it declares no
|
|
106
92
|
* `fields:`), so the actions are on its partner — never that no action applies.
|
|
@@ -129,7 +115,7 @@ export type FieldProjectionInput = {
|
|
|
129
115
|
readonly field: PrismaSchemaField;
|
|
130
116
|
/** The model's other fields: the only way to identify an FK scalar by name. */
|
|
131
117
|
readonly siblings: readonly PrismaSchemaField[];
|
|
132
|
-
/** TYPE — what this client exposes. The existence gate
|
|
118
|
+
/** TYPE — what this client exposes. The existence gate. */
|
|
133
119
|
readonly surface: PrismaResourceSurface;
|
|
134
120
|
/** TYPE — `enums.ts`, which is what makes an enum name an enum name. */
|
|
135
121
|
readonly enums: Readonly<Record<string, readonly string[]>>;
|
|
@@ -140,7 +126,7 @@ export type FieldProjection = {
|
|
|
140
126
|
readonly outcome: "scalar";
|
|
141
127
|
readonly name: string;
|
|
142
128
|
readonly field: ProjectedScalarField;
|
|
143
|
-
/** Recorded unknowns; a non-empty list FAILS the generate step
|
|
129
|
+
/** Recorded unknowns; a non-empty list FAILS the generate step. */
|
|
144
130
|
readonly unknowns: readonly PrismaSchemaUnknown[];
|
|
145
131
|
} | {
|
|
146
132
|
readonly outcome: "relation";
|
|
@@ -173,8 +159,8 @@ export type PrismaDefaultOutcome =
|
|
|
173
159
|
readonly traits: readonly AdapterLifecycleTrait[];
|
|
174
160
|
};
|
|
175
161
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
162
|
+
* The argument arrives as opaque text precisely so that this function can be a
|
|
163
|
+
* lookup.
|
|
178
164
|
*/
|
|
179
165
|
export declare function matchPrismaDefault(argument: string): PrismaDefaultOutcome;
|
|
180
166
|
/** Why a field is legitimately optional in `<M>UncheckedCreateInput`. */
|
|
@@ -2,74 +2,6 @@ import { LIFECYCLE_TRAITS, } from "@aventara/core";
|
|
|
2
2
|
import { projectEnumBackedColumn } from "./enum.mapper.js";
|
|
3
3
|
import { mapPrismaScalarField } from "./scalar.mapper.js";
|
|
4
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
5
|
export class PrismaFieldProjectionError extends Error {
|
|
74
6
|
name = "PrismaFieldProjectionError";
|
|
75
7
|
}
|
|
@@ -78,16 +10,10 @@ export function projectPrismaField(input) {
|
|
|
78
10
|
? projectRelationField(input)
|
|
79
11
|
: projectScalarField(input);
|
|
80
12
|
}
|
|
81
|
-
/* ------------------------------------------------------------------ *
|
|
82
|
-
* Scalars *
|
|
83
|
-
* ------------------------------------------------------------------ */
|
|
84
13
|
function projectScalarField(input) {
|
|
85
14
|
const { resource, field, surface, enums } = input;
|
|
86
15
|
const mapped = mapPrismaScalarField({ resource, field, surface, enums });
|
|
87
16
|
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
17
|
return mapped;
|
|
92
18
|
}
|
|
93
19
|
const exposed = surface.scalarFields[field.name];
|
|
@@ -107,7 +33,6 @@ function projectScalarField(input) {
|
|
|
107
33
|
kind: "scalar",
|
|
108
34
|
type,
|
|
109
35
|
...(format === undefined ? {} : { format }),
|
|
110
|
-
// PSL's `?`, and nothing else (T-3.1 invariant 4).
|
|
111
36
|
nullable: field.optional,
|
|
112
37
|
list: field.list,
|
|
113
38
|
lifecycle: lifecycle.traits,
|
|
@@ -115,15 +40,6 @@ function projectScalarField(input) {
|
|
|
115
40
|
unknowns: lifecycle.unknowns,
|
|
116
41
|
};
|
|
117
42
|
}
|
|
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
43
|
function assertTokenAgrees(resource, field, token) {
|
|
128
44
|
const listed = token.endsWith("[]");
|
|
129
45
|
const base = listed ? token.slice(0, -2) : token;
|
|
@@ -134,23 +50,10 @@ function assertTokenAgrees(resource, field, token) {
|
|
|
134
50
|
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
51
|
}
|
|
136
52
|
}
|
|
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
53
|
const ADAPTER_TRAIT_ORDER = LIFECYCLE_TRAITS.filter((trait) => trait === "DEFAULTED" ||
|
|
146
54
|
trait === "GENERATED_ON_CREATE" ||
|
|
147
55
|
trait === "GENERATED_ON_UPDATE");
|
|
148
|
-
/** An argument that begins with an identifier and an open paren: a generator form. */
|
|
149
56
|
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
57
|
export function matchPrismaDefault(argument) {
|
|
155
58
|
for (const row of PRISMA_DEFAULT_TABLE) {
|
|
156
59
|
const matched = "literal" in row.match
|
|
@@ -176,10 +79,6 @@ function projectLifecycle(resource, field) {
|
|
|
176
79
|
present.add(trait);
|
|
177
80
|
}
|
|
178
81
|
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
82
|
unknowns.push({
|
|
184
83
|
kind: "attribute-argument",
|
|
185
84
|
spelling: "@default",
|
|
@@ -197,16 +96,6 @@ function projectLifecycle(resource, field) {
|
|
|
197
96
|
unknowns,
|
|
198
97
|
};
|
|
199
98
|
}
|
|
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
99
|
export function crossCheckCreateOptional(input) {
|
|
211
100
|
const byName = new Map(input.fields.map((field) => [field.name, field]));
|
|
212
101
|
const explained = [];
|
|
@@ -228,11 +117,7 @@ export function crossCheckCreateOptional(input) {
|
|
|
228
117
|
return { explained, unexplained, messages };
|
|
229
118
|
}
|
|
230
119
|
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
120
|
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
121
|
const strongest = trait.includes("GENERATED_ON_CREATE")
|
|
237
122
|
? "GENERATED_ON_CREATE"
|
|
238
123
|
: trait[0];
|
|
@@ -244,9 +129,6 @@ function causeOf(projected) {
|
|
|
244
129
|
}
|
|
245
130
|
return projected.list ? "list" : null;
|
|
246
131
|
}
|
|
247
|
-
/* ------------------------------------------------------------------ *
|
|
248
|
-
* Relations (C-813) *
|
|
249
|
-
* ------------------------------------------------------------------ */
|
|
250
132
|
function projectRelationField(input) {
|
|
251
133
|
const { resource, field, siblings, surface } = input;
|
|
252
134
|
const column = `${resource}.${field.name}`;
|
|
@@ -261,11 +143,7 @@ function projectRelationField(input) {
|
|
|
261
143
|
};
|
|
262
144
|
}
|
|
263
145
|
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
146
|
const nullable = cardinality === "many" ? false : field.optional;
|
|
267
|
-
// TYPE is the cross-check, from `$<M>Payload.objects`: `mentor:
|
|
268
|
-
// $AuthorPayload | null`, `mentees: $AuthorPayload[]`.
|
|
269
147
|
if (exposed.many !== field.list) {
|
|
270
148
|
throw new PrismaFieldProjectionError(`${column} is ${field.list ? "a list" : "a single edge"} in schema.prisma and the 🟢 payload says the opposite.`);
|
|
271
149
|
}
|
|
@@ -279,8 +157,6 @@ function projectRelationField(input) {
|
|
|
279
157
|
return {
|
|
280
158
|
outcome: "relation",
|
|
281
159
|
name: field.name,
|
|
282
|
-
// Four members. No target field schema, no FK, no join-table name, no
|
|
283
|
-
// owning-side metadata (C-813).
|
|
284
160
|
field: {
|
|
285
161
|
kind: "relation",
|
|
286
162
|
target: field.type,
|
|
@@ -293,31 +169,11 @@ function projectRelationField(input) {
|
|
|
293
169
|
target: field.type,
|
|
294
170
|
cardinality,
|
|
295
171
|
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
172
|
onDelete: field.relation?.onDelete ?? null,
|
|
300
173
|
onUpdate: field.relation?.onUpdate ?? null,
|
|
301
174
|
},
|
|
302
175
|
};
|
|
303
176
|
}
|
|
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
177
|
function assertForeignKeyAgrees(column, field, siblings, nullable) {
|
|
322
178
|
for (const name of field.relation?.fields ?? []) {
|
|
323
179
|
const scalar = siblings.find((sibling) => sibling.name === name);
|
|
@@ -329,21 +185,6 @@ function assertForeignKeyAgrees(column, field, siblings, nullable) {
|
|
|
329
185
|
}
|
|
330
186
|
}
|
|
331
187
|
}
|
|
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
188
|
export function pairPrismaRelationEdges(edges) {
|
|
348
189
|
const groups = new Map();
|
|
349
190
|
for (const edge of edges) {
|
|
@@ -375,8 +216,6 @@ export function pairPrismaRelationEdges(edges) {
|
|
|
375
216
|
}
|
|
376
217
|
pairs.push(sortKey(first) <= sortKey(second) ? [first, second] : [second, first]);
|
|
377
218
|
}
|
|
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
219
|
return {
|
|
381
220
|
pairs: [...pairs].sort((left, right) => sortKey(left[0]).localeCompare(sortKey(right[0]))),
|
|
382
221
|
unpaired,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { PrismaIdentifierAddress } from "./discovery-artifact.interface.js";
|
|
2
2
|
import type { PrismaSchemaBlockIdentifier, PrismaSchemaModel } from "./schema-facts.interface.js";
|
|
3
3
|
/**
|
|
4
|
-
* PSL uniqueness → `AdapterIdentifier` tuples **[CORE]**: C-814 and C-815.
|
|
5
|
-
*
|
|
6
4
|
* # Why identity comes from PSL
|
|
7
5
|
*
|
|
8
6
|
* Because the generated types cannot state it. Measured on 2026-09-28 against
|
|
@@ -13,100 +11,70 @@ import type { PrismaSchemaBlockIdentifier, PrismaSchemaModel } from "./schema-fa
|
|
|
13
11
|
* model Epsilon { code String @id name String }
|
|
14
12
|
* ```
|
|
15
13
|
*
|
|
16
|
-
* `models/Delta.ts` and `models/Epsilon.ts` are **byte-identical** — equal
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* the 🟢 files of that client and of **both** Phase 8 clients is empty — not
|
|
23
|
-
* only over the 🟢 files: over the whole generated tree, `internal/`
|
|
24
|
-
* included.
|
|
14
|
+
* `models/Delta.ts` and `models/Epsilon.ts` are **byte-identical** — equal md5
|
|
15
|
+
* after a case-insensitive `Delta|delta → MODEL|model` substitution and after
|
|
16
|
+
* removing one 4-line `StringFieldUpdateOperationsInput` block, which is the
|
|
17
|
+
* generator's first-file-wins dedup of a shared input type and lands in whichever
|
|
18
|
+
* model file sorts first. Both declare their `AtLeast` key set as `}, "code">` on
|
|
19
|
+
* the same line.
|
|
25
20
|
*
|
|
26
|
-
* So `@id` and `@unique` are the same fact to TYPE, and a compound `@@id` is
|
|
27
|
-
*
|
|
28
|
-
*
|
|
21
|
+
* So `@id` and `@unique` are the same fact to TYPE, and a compound `@@id` is the
|
|
22
|
+
* same fact as a compound `@@unique`. PSL is the only source that distinguishes
|
|
23
|
+
* them.
|
|
29
24
|
*
|
|
30
25
|
* # Why that negative costs the model nothing
|
|
31
26
|
*
|
|
32
27
|
* `AdapterResource.identifiers` is `readonly AdapterIdentifier<TFields>[]`
|
|
33
28
|
* **[CORE]** — an **unmarked set** of non-empty scalar field tuples with no
|
|
34
29
|
* primary/secondary distinction. Aventara never asks which identifier is the
|
|
35
|
-
* primary key, so a source that cannot tell `@id` from `@unique` withholds
|
|
36
|
-
*
|
|
30
|
+
* primary key, so a source that cannot tell `@id` from `@unique` withholds nothing
|
|
31
|
+
* this mapper needs.
|
|
37
32
|
*
|
|
38
33
|
* # The types are a cross-check, and a one-sided one
|
|
39
34
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* not re-derived: matching a key computed as `members.join("_")` is what used to
|
|
45
|
-
* refuse a schema carrying `@@unique(…, name: "X")`, whose surface key is `X`.
|
|
46
|
-
* Equality is unsatisfiable by construction, because C-815 omits a criterion
|
|
47
|
-
* the generator still emits a key for. Measured: `Author` has three keys
|
|
48
|
-
* (`"id" | "email" | "tenantId_handle"`, identical on both engines) and two
|
|
49
|
-
* identifiers.
|
|
50
|
-
*
|
|
51
|
-
* What the two directions do **not** catch is this mapper keeping a criterion
|
|
52
|
-
* C-815 rejects: an extra identifier only removes a key from the explained
|
|
53
|
-
* set, so both directions still pass. Measured on 2026-09-28 — a build of this
|
|
54
|
-
* mapper without the C-815 branch passes the cross-check on all eight
|
|
55
|
-
* model/engine pairs. The cross-check guards the two readers against each
|
|
56
|
-
* other; C-815 is pinned by its own cases.
|
|
35
|
+
* Stored, not re-derived: matching a key computed as `members.join("_")` is what
|
|
36
|
+
* used to refuse a schema carrying `@@unique(…, name: "X")`, whose surface key is
|
|
37
|
+
* `X`. Measured: `Author` has three keys (`"id" | "email" | "tenantId_handle"`,
|
|
38
|
+
* identical on both engines) and two identifiers.
|
|
57
39
|
*
|
|
58
40
|
* Its key **ORDERING is not used at all**. It appears to list the primary key
|
|
59
|
-
* first, but that is an undocumented generator artifact, it is ordering inside
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
41
|
+
* first, but that is an undocumented generator artifact, it is ordering inside a
|
|
42
|
+
* union — which TypeScript does not preserve semantically — and it degenerates
|
|
43
|
+
* completely when a model has exactly one unique criterion, the common case.
|
|
44
|
+
* Nothing here may start depending on it.
|
|
63
45
|
*
|
|
64
46
|
* # Nothing here sorts
|
|
65
47
|
*
|
|
66
48
|
* Member order is the attribute's list as written, never the field declaration
|
|
67
|
-
* order (`@@unique([y, x])` is `["y", "x"]` even where `x` is declared first),
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
49
|
+
* order (`@@unique([y, x])` is `["y", "x"]` even where `x` is declared first), and
|
|
50
|
+
* the identifiers themselves come out in PSL's own order: field-level criteria in
|
|
51
|
+
* field declaration order, then `@@id`, then `@@unique` as written. Order enters
|
|
52
|
+
* the Contract hash, so a deterministic order derived from PSL is the point, not a
|
|
53
|
+
* convenience.
|
|
72
54
|
*/
|
|
73
55
|
/** Which PSL spelling a criterion came from. Reported, and never used to rank one. */
|
|
74
56
|
export type PrismaIdentifierSource = "@id" | "@unique" | "@@id" | "@@unique";
|
|
75
|
-
/** One uniqueness criterion as PSL states it, before C-815 is applied. */
|
|
76
57
|
export type PrismaUniquenessCriterion = {
|
|
77
58
|
readonly source: PrismaIdentifierSource;
|
|
78
59
|
/** Member order as written in the attribute's list. Never sorted. */
|
|
79
60
|
readonly members: readonly string[];
|
|
80
61
|
/**
|
|
81
|
-
* The Prisma addressing key for this criterion — {@link prismaUniqueInputKey}
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* Carried by the OMISSIONS too, and not only by the usable identifiers,
|
|
85
|
-
* because the TYPE cross-check's second direction matches an `AtLeast` key
|
|
86
|
-
* against the omission that explains it: a named criterion C-815 rejects is
|
|
87
|
-
* keyed by its name on the generated surface, so an omission without its key
|
|
88
|
-
* would leave that key unexplained and fail a schema Prisma accepts.
|
|
62
|
+
* The Prisma addressing key for this criterion — {@link prismaUniqueInputKey} over
|
|
63
|
+
* the PSL facts, computed once here.
|
|
89
64
|
*/
|
|
90
65
|
readonly key: string;
|
|
91
66
|
};
|
|
92
67
|
/**
|
|
93
68
|
* A criterion that is NOT a usable identifier, recorded with its reason.
|
|
94
69
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* omission would leave that key unexplained and indistinguishable from a
|
|
99
|
-
* reader bug.
|
|
100
|
-
*
|
|
101
|
-
* A discriminated union, because the arms carry different evidence: which
|
|
102
|
-
* members are nullable is not the same fact as which members are not scalar
|
|
103
|
-
* columns at all.
|
|
70
|
+
* A discriminated union, because the arms carry different evidence: which members
|
|
71
|
+
* are nullable is not the same fact as which members are not scalar columns at
|
|
72
|
+
* all.
|
|
104
73
|
*
|
|
105
|
-
* These are not `PrismaDiscoveryDiagnostic`s. A diagnostic carries prose for
|
|
106
|
-
*
|
|
74
|
+
* These are not `PrismaDiscoveryDiagnostic`s. A diagnostic carries prose for an
|
|
75
|
+
* engineer; the cross-check needs the member names as data.
|
|
107
76
|
*/
|
|
108
77
|
export type PrismaIdentifierOmission = PrismaUniquenessCriterion & ({
|
|
109
|
-
/** C-815: SQL uniqueness does not constrain NULLs. */
|
|
110
78
|
readonly reason: "nullable-participant";
|
|
111
79
|
readonly nullableMembers: readonly string[];
|
|
112
80
|
} | {
|
|
@@ -151,20 +119,13 @@ export type PrismaIdentifierMapping = {
|
|
|
151
119
|
* `WhereUniqueInput` accepts, and the key the generated `AtLeast<…, K>` union
|
|
152
120
|
* lists.
|
|
153
121
|
*
|
|
154
|
-
* Computed **once**, here, at generate time — the architect's decision on
|
|
155
|
-
* 2026-09-29: *"store it, less processing and less room for drifting."* Runtime
|
|
156
|
-
* matches the stored key and never re-derives it.
|
|
157
|
-
*
|
|
158
122
|
* The rule, measured at 7.10.0 (regenerated 2026-09-29 over five models):
|
|
159
123
|
*
|
|
160
|
-
* | criterion
|
|
161
|
-
* |
|
|
162
|
-
* |
|
|
163
|
-
* | `@@
|
|
164
|
-
*
|
|
165
|
-
* | `@@unique([b, a], name: "altKey")` | `altKey` | yes |
|
|
166
|
-
* | `@@id([a, b], name: "pk")` | `pk` | yes |
|
|
167
|
-
* | any of the above, `map: "…"` | unchanged | — |
|
|
124
|
+
* | criterion | key | wraps | | ---------------------------------- | --------- |
|
|
125
|
+
* ----- | | `id String @id` / `@unique` | `id` | no | | `@@unique([b, a])` | `b_a`
|
|
126
|
+
* | yes | | `@@id([a, b])` | `a_b` | yes | | `@@unique([b, a], name: "altKey")` |
|
|
127
|
+
* `altKey` | yes | | `@@id([a, b], name: "pk")` | `pk` | yes | | any of the above,
|
|
128
|
+
* `map: "…"` | unchanged | — |
|
|
168
129
|
*
|
|
169
130
|
* Two of those rows are why this is a function of the PSL facts and not of the
|
|
170
131
|
* members alone. A custom `name:` **replaces** the positional alias rather than
|
|
@@ -174,8 +135,8 @@ export type PrismaIdentifierMapping = {
|
|
|
174
135
|
* 2026-09-29; `map:` stays invisible and stays discarded).
|
|
175
136
|
*
|
|
176
137
|
* What this key is **not** is part of the identifier. `AdapterIdentifier` is a
|
|
177
|
-
* tuple of field names with nowhere to put a name
|
|
178
|
-
*
|
|
138
|
+
* tuple of field names with nowhere to put a name, and this key lives in the
|
|
139
|
+
* artifact's translation table beside the model, never in it.
|
|
179
140
|
*/
|
|
180
141
|
export declare function prismaUniqueInputKey(uniqueness: PrismaSchemaBlockIdentifier): string;
|
|
181
142
|
/**
|