@aventara/prisma7-adapter 0.1.0-pilot.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +564 -0
- package/dist/discovery/artifact.loader.d.ts +26 -0
- package/dist/discovery/artifact.loader.js +124 -0
- package/dist/discovery/capability-surface.interface.d.ts +191 -0
- package/dist/discovery/capability-surface.interface.js +13 -0
- package/dist/discovery/capability.deriver.d.ts +92 -0
- package/dist/discovery/capability.deriver.js +350 -0
- package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
- package/dist/discovery/discovery-artifact.interface.js +1 -0
- package/dist/discovery/enum.mapper.d.ts +59 -0
- package/dist/discovery/enum.mapper.js +32 -0
- package/dist/discovery/field.mapper.d.ts +244 -0
- package/dist/discovery/field.mapper.js +387 -0
- package/dist/discovery/identifier.mapper.d.ts +188 -0
- package/dist/discovery/identifier.mapper.js +132 -0
- package/dist/discovery/model.discoverer.d.ts +108 -0
- package/dist/discovery/model.discoverer.js +341 -0
- package/dist/discovery/operations.deriver.d.ts +75 -0
- package/dist/discovery/operations.deriver.js +92 -0
- package/dist/discovery/scalar.mapper.d.ts +81 -0
- package/dist/discovery/scalar.mapper.js +51 -0
- package/dist/discovery/schema-facts.interface.d.ts +174 -0
- package/dist/discovery/schema-facts.interface.js +11 -0
- package/dist/discovery/schema.reader.d.ts +162 -0
- package/dist/discovery/schema.reader.js +692 -0
- package/dist/discovery/types.reader.d.ts +24 -0
- package/dist/discovery/types.reader.js +617 -0
- package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
- package/dist/discovery/vocabulary.dictionary.js +429 -0
- package/dist/errors/error.normalizer.d.ts +250 -0
- package/dist/errors/error.normalizer.js +135 -0
- package/dist/errors/prisma-error.reader.d.ts +144 -0
- package/dist/errors/prisma-error.reader.js +167 -0
- package/dist/execution/first-variant.emulator.d.ts +64 -0
- package/dist/execution/first-variant.emulator.js +205 -0
- package/dist/execution/operation.executor.d.ts +87 -0
- package/dist/execution/operation.executor.js +210 -0
- package/dist/execution/result.shaper.d.ts +103 -0
- package/dist/execution/result.shaper.js +141 -0
- package/dist/execution/transaction.executor.d.ts +147 -0
- package/dist/execution/transaction.executor.js +13 -0
- package/dist/generate/artifact.emitter.d.ts +50 -0
- package/dist/generate/artifact.emitter.js +105 -0
- package/dist/generate/declared-support.reader.d.ts +33 -0
- package/dist/generate/declared-support.reader.js +74 -0
- package/dist/generate/discovery.bin.d.ts +2 -0
- package/dist/generate/discovery.bin.js +15 -0
- package/dist/generate/discovery.cli.d.ts +24 -0
- package/dist/generate/discovery.cli.js +163 -0
- package/dist/generate/existence.gate.d.ts +73 -0
- package/dist/generate/existence.gate.js +91 -0
- package/dist/generate/index.d.ts +81 -0
- package/dist/generate/index.js +65 -0
- package/dist/generate/node-version.guard.d.ts +8 -0
- package/dist/generate/node-version.guard.js +59 -0
- package/dist/generate/prisma-cli.resolver.d.ts +48 -0
- package/dist/generate/prisma-cli.resolver.js +95 -0
- package/dist/generate/prisma.generate.d.ts +164 -0
- package/dist/generate/prisma.generate.js +278 -0
- package/dist/generate/reader-crosscheck.d.ts +156 -0
- package/dist/generate/reader-crosscheck.js +202 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/prisma-client.interface.d.ts +48 -0
- package/dist/prisma-client.interface.js +9 -0
- package/dist/prisma.adapter.d.ts +55 -0
- package/dist/prisma.adapter.js +105 -0
- package/dist/provider/provider-profile.d.ts +53 -0
- package/dist/provider/provider-profile.js +91 -0
- package/dist/translation/identifier.addressing.d.ts +80 -0
- package/dist/translation/identifier.addressing.js +30 -0
- package/dist/translation/mutation.translator.d.ts +115 -0
- package/dist/translation/mutation.translator.js +580 -0
- package/dist/translation/operation.translator.d.ts +273 -0
- package/dist/translation/operation.translator.js +377 -0
- package/dist/translation/order-pagination.translator.d.ts +86 -0
- package/dist/translation/order-pagination.translator.js +159 -0
- package/dist/translation/projection.translator.d.ts +115 -0
- package/dist/translation/projection.translator.js +151 -0
- package/dist/translation/scalar.conversion.d.ts +101 -0
- package/dist/translation/scalar.conversion.js +221 -0
- package/dist/translation/where.translator.d.ts +79 -0
- package/dist/translation/where.translator.js +254 -0
- package/package.json +102 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { PrismaIdentifierAddress } from "./discovery-artifact.interface.js";
|
|
2
|
+
import type { PrismaSchemaBlockIdentifier, PrismaSchemaModel } from "./schema-facts.interface.js";
|
|
3
|
+
/**
|
|
4
|
+
* PSL uniqueness → `AdapterIdentifier` tuples **[CORE]**: C-814 and C-815.
|
|
5
|
+
*
|
|
6
|
+
* # Why identity comes from PSL
|
|
7
|
+
*
|
|
8
|
+
* Because the generated types cannot state it. Measured on 2026-09-28 against
|
|
9
|
+
* prisma 7.10.0, on a two-model scratch client generated for this purpose:
|
|
10
|
+
*
|
|
11
|
+
* ```prisma
|
|
12
|
+
* model Delta { code String @unique name String }
|
|
13
|
+
* model Epsilon { code String @id name String }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `models/Delta.ts` and `models/Epsilon.ts` are **byte-identical** — equal
|
|
17
|
+
* md5 after a case-insensitive `Delta|delta → MODEL|model` substitution and
|
|
18
|
+
* after removing one 4-line `StringFieldUpdateOperationsInput` block, which is
|
|
19
|
+
* the generator's first-file-wins dedup of a shared input type and lands in
|
|
20
|
+
* whichever model file sorts first. Both declare their `AtLeast` key set as
|
|
21
|
+
* `}, "code">` on the same line. A grep for `primaryKey|isId|isUnique` over
|
|
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.
|
|
25
|
+
*
|
|
26
|
+
* So `@id` and `@unique` are the same fact to TYPE, and a compound `@@id` is
|
|
27
|
+
* the same fact as a compound `@@unique`. PSL is the only source that
|
|
28
|
+
* distinguishes them.
|
|
29
|
+
*
|
|
30
|
+
* # Why that negative costs the model nothing
|
|
31
|
+
*
|
|
32
|
+
* `AdapterResource.identifiers` is `readonly AdapterIdentifier<TFields>[]`
|
|
33
|
+
* **[CORE]** — an **unmarked set** of non-empty scalar field tuples with no
|
|
34
|
+
* primary/secondary distinction. Aventara never asks which identifier is the
|
|
35
|
+
* primary key, so a source that cannot tell `@id` from `@unique` withholds
|
|
36
|
+
* nothing this mapper needs.
|
|
37
|
+
*
|
|
38
|
+
* # The types are a cross-check, and a one-sided one
|
|
39
|
+
*
|
|
40
|
+
* `PrismaResourceSurface.uniqueKeys` — `AtLeast<…, K>`'s key union — is
|
|
41
|
+
* checked two-directionally and never read as a source (C-814): every
|
|
42
|
+
* identifier's **stored** key must appear among the keys, and every key with no
|
|
43
|
+
* identifier behind it must be explained by C-815 and by nothing else. Stored,
|
|
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.
|
|
57
|
+
*
|
|
58
|
+
* 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
|
+
* a union — which TypeScript does not preserve semantically — and it
|
|
61
|
+
* degenerates completely when a model has exactly one unique criterion, the
|
|
62
|
+
* common case. Nothing here may start depending on it.
|
|
63
|
+
*
|
|
64
|
+
* # Nothing here sorts
|
|
65
|
+
*
|
|
66
|
+
* 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
|
+
* and the identifiers themselves come out in PSL's own order: field-level
|
|
69
|
+
* criteria in field declaration order, then `@@id`, then `@@unique` as
|
|
70
|
+
* written. Order enters the Contract hash (§19.2), so a deterministic order
|
|
71
|
+
* derived from PSL is the point, not a convenience.
|
|
72
|
+
*/
|
|
73
|
+
/** Which PSL spelling a criterion came from. Reported, and never used to rank one. */
|
|
74
|
+
export type PrismaIdentifierSource = "@id" | "@unique" | "@@id" | "@@unique";
|
|
75
|
+
/** One uniqueness criterion as PSL states it, before C-815 is applied. */
|
|
76
|
+
export type PrismaUniquenessCriterion = {
|
|
77
|
+
readonly source: PrismaIdentifierSource;
|
|
78
|
+
/** Member order as written in the attribute's list. Never sorted. */
|
|
79
|
+
readonly members: readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* The Prisma addressing key for this criterion — {@link prismaUniqueInputKey}
|
|
82
|
+
* over the PSL facts, computed once here.
|
|
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.
|
|
89
|
+
*/
|
|
90
|
+
readonly key: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* A criterion that is NOT a usable identifier, recorded with its reason.
|
|
94
|
+
*
|
|
95
|
+
* Recorded rather than dropped for two independent reasons: C-814 requires the
|
|
96
|
+
* omission to be reported, and the TYPE cross-check needs it — the generator
|
|
97
|
+
* emits an `AtLeast` key for a criterion C-815 rejects, and an unrecorded
|
|
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.
|
|
104
|
+
*
|
|
105
|
+
* These are not `PrismaDiscoveryDiagnostic`s. A diagnostic carries prose for
|
|
106
|
+
* an engineer; the cross-check needs the member names as data.
|
|
107
|
+
*/
|
|
108
|
+
export type PrismaIdentifierOmission = PrismaUniquenessCriterion & ({
|
|
109
|
+
/** C-815: SQL uniqueness does not constrain NULLs. */
|
|
110
|
+
readonly reason: "nullable-participant";
|
|
111
|
+
readonly nullableMembers: readonly string[];
|
|
112
|
+
} | {
|
|
113
|
+
/**
|
|
114
|
+
* A member that is not a scalar column this adapter can name: absent
|
|
115
|
+
* from the model, a relation field, a list, or `@ignore`d out of the
|
|
116
|
+
* generated client. Valid PSL cannot produce the first three; `@ignore`
|
|
117
|
+
* can, and keeping it would emit an identifier over a key no
|
|
118
|
+
* `AdapterFieldMap` carries.
|
|
119
|
+
*/
|
|
120
|
+
readonly reason: "unresolved-participant";
|
|
121
|
+
readonly unresolvedMembers: readonly string[];
|
|
122
|
+
} | {
|
|
123
|
+
/** An empty member list: `AdapterIdentifier` is non-empty by type. */
|
|
124
|
+
readonly reason: "no-participants";
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* What one model's identity reads as: the usable criteria, each as the tuple
|
|
128
|
+
* core consumes **paired with** the Prisma key that addresses it, and why the
|
|
129
|
+
* rest are not usable.
|
|
130
|
+
*
|
|
131
|
+
* One list, two projections, which is the reason the pair is not split here:
|
|
132
|
+
* the composer takes `.members` for `AdapterResource.identifiers` (model
|
|
133
|
+
* content, hashed, Contract-bound) and the whole address for the artifact's
|
|
134
|
+
* translation table (adapter-private, unhashed). Two parallel lists would have
|
|
135
|
+
* to be kept in step; these cannot drift because they are one.
|
|
136
|
+
*
|
|
137
|
+
* `PrismaIdentifierAddress.members` is derived from `AdapterResource`, so a
|
|
138
|
+
* change in core's identifier type reaches this mapper as a compile error.
|
|
139
|
+
* Discovery is where the field map is being **built**, so the abstract form is
|
|
140
|
+
* what applies — `AdapterScalarFieldKey` widens to `string` through
|
|
141
|
+
* `AdapterFieldMap<M>`, making an identifier here a non-empty tuple of field
|
|
142
|
+
* names. The concrete keys are pinned later, in the emitted artifact
|
|
143
|
+
* (`PrismaDiscoveryArtifact<M>`).
|
|
144
|
+
*/
|
|
145
|
+
export type PrismaIdentifierMapping = {
|
|
146
|
+
readonly identifiers: readonly PrismaIdentifierAddress[];
|
|
147
|
+
readonly omitted: readonly PrismaIdentifierOmission[];
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The Prisma addressing key for a uniqueness criterion: the key a
|
|
151
|
+
* `WhereUniqueInput` accepts, and the key the generated `AtLeast<…, K>` union
|
|
152
|
+
* lists.
|
|
153
|
+
*
|
|
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
|
+
* The rule, measured at 7.10.0 (regenerated 2026-09-29 over five models):
|
|
159
|
+
*
|
|
160
|
+
* | criterion | key | wraps |
|
|
161
|
+
* | ---------------------------------- | --------- | ----- |
|
|
162
|
+
* | `id String @id` / `@unique` | `id` | no |
|
|
163
|
+
* | `@@unique([b, a])` | `b_a` | yes |
|
|
164
|
+
* | `@@id([a, b])` | `a_b` | yes |
|
|
165
|
+
* | `@@unique([b, a], name: "altKey")` | `altKey` | yes |
|
|
166
|
+
* | `@@id([a, b], name: "pk")` | `pk` | yes |
|
|
167
|
+
* | any of the above, `map: "…"` | unchanged | — |
|
|
168
|
+
*
|
|
169
|
+
* Two of those rows are why this is a function of the PSL facts and not of the
|
|
170
|
+
* members alone. A custom `name:` **replaces** the positional alias rather than
|
|
171
|
+
* adding to it, so `members.join("_")` is simply wrong for a named criterion —
|
|
172
|
+
* which is precisely what made the cross-check refuse a schema Prisma accepts,
|
|
173
|
+
* reporting a correctly-discovered identifier as an invented constraint (fixed
|
|
174
|
+
* 2026-09-29; `map:` stays invisible and stays discarded).
|
|
175
|
+
*
|
|
176
|
+
* What this key is **not** is part of the identifier. `AdapterIdentifier` is a
|
|
177
|
+
* tuple of field names with nowhere to put a name (C-814), and this key lives in
|
|
178
|
+
* the artifact's translation table beside the model, never in it.
|
|
179
|
+
*/
|
|
180
|
+
export declare function prismaUniqueInputKey(uniqueness: PrismaSchemaBlockIdentifier): string;
|
|
181
|
+
/**
|
|
182
|
+
* One model's identity, read from PSL alone.
|
|
183
|
+
*
|
|
184
|
+
* A pure function of plain data: no generated client, no `@prisma/client`
|
|
185
|
+
* import, and no TYPE input — which is what lets the cross-check be an
|
|
186
|
+
* independent second opinion rather than a tautology.
|
|
187
|
+
*/
|
|
188
|
+
export declare function mapPrismaIdentifiers(model: PrismaSchemaModel): PrismaIdentifierMapping;
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
export function prismaUniqueInputKey(uniqueness) {
|
|
33
|
+
return uniqueness.name ?? uniqueness.members.join("_");
|
|
34
|
+
}
|
|
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
|
+
function* uniquenessCriteria(model) {
|
|
41
|
+
const criterion = (source, uniqueness) => ({
|
|
42
|
+
source,
|
|
43
|
+
members: uniqueness.members,
|
|
44
|
+
key: prismaUniqueInputKey(uniqueness),
|
|
45
|
+
});
|
|
46
|
+
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
|
+
if (field.id) {
|
|
50
|
+
yield criterion("@id", { members: [field.name], name: null });
|
|
51
|
+
}
|
|
52
|
+
if (field.unique) {
|
|
53
|
+
yield criterion("@unique", { members: [field.name], name: null });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (model.blockId !== null) {
|
|
57
|
+
yield criterion("@@id", model.blockId);
|
|
58
|
+
}
|
|
59
|
+
for (const blockUnique of model.blockUniques) {
|
|
60
|
+
yield criterion("@@unique", blockUnique);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
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
|
+
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
|
+
const columns = new Map();
|
|
74
|
+
for (const field of model.fields) {
|
|
75
|
+
if (field.relation !== null || field.list || field.ignored) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
columns.set(field.name, field);
|
|
79
|
+
}
|
|
80
|
+
const identifiers = [];
|
|
81
|
+
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
|
+
const seen = new Set();
|
|
87
|
+
for (const criterion of uniquenessCriteria(model)) {
|
|
88
|
+
const tuple = JSON.stringify(criterion.members);
|
|
89
|
+
if (seen.has(tuple)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
seen.add(tuple);
|
|
93
|
+
const [head, ...tail] = criterion.members;
|
|
94
|
+
if (head === undefined) {
|
|
95
|
+
omitted.push({ ...criterion, reason: "no-participants" });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const unresolvedMembers = criterion.members.filter((member) => !columns.has(member));
|
|
99
|
+
if (unresolvedMembers.length > 0) {
|
|
100
|
+
omitted.push({
|
|
101
|
+
...criterion,
|
|
102
|
+
reason: "unresolved-participant",
|
|
103
|
+
unresolvedMembers,
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
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
|
+
const nullableMembers = criterion.members.filter((member) => columns.get(member)?.optional === true);
|
|
121
|
+
if (nullableMembers.length > 0) {
|
|
122
|
+
omitted.push({
|
|
123
|
+
...criterion,
|
|
124
|
+
reason: "nullable-participant",
|
|
125
|
+
nullableMembers,
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
identifiers.push({ members: [head, ...tail], key: criterion.key });
|
|
130
|
+
}
|
|
131
|
+
return { identifiers, omitted };
|
|
132
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type TransactionCapability } from "@aventara/core";
|
|
2
|
+
import type { PrismaComposedArtifact, PrismaModelComposer } from "../generate/prisma.generate.js";
|
|
3
|
+
import type { PrismaCapabilitySurface } from "./capability-surface.interface.js";
|
|
4
|
+
import type { PrismaSchemaFacts } from "./schema-facts.interface.js";
|
|
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:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Which diagnostics are fatal.** A mapper answers with a diagnostic; whether
|
|
18
|
+
* the build stops is composition's call, because only composition knows that a
|
|
19
|
+
* model with an unmapped column cannot produce a Contract at all.
|
|
20
|
+
* 2. **The scalar registry.** `ScalarRegistry` is the set of Aventara scalar
|
|
21
|
+
* identities the discovered fields actually use — a fact no single field can
|
|
22
|
+
* state and only the whole pass can.
|
|
23
|
+
*
|
|
24
|
+
* # Three halves, from one pass over one field list
|
|
25
|
+
*
|
|
26
|
+
* `model` is core's: canonicalized, hashed, compiled into the Contract.
|
|
27
|
+
* `translation` is the adapter's Prisma addressing, and `referentialActions` is
|
|
28
|
+
* what the resolved `@relation(onDelete:/onUpdate:)` arguments say. All three are
|
|
29
|
+
* built from the SAME pass: the translation table from the same
|
|
30
|
+
* `mapPrismaIdentifiers` answer the model's `identifiers` come from — `.members`
|
|
31
|
+
* into the model, the whole address into the table — and the action table from the
|
|
32
|
+
* same `projectPrismaField` answer the model's relation fields come from. Two
|
|
33
|
+
* parallel passes would have to be kept in step; one pass cannot drift.
|
|
34
|
+
*
|
|
35
|
+
* `PrismaTranslationTable<M>` and `PrismaReferentialActionTable<M>` are mapped
|
|
36
|
+
* types over the model's resources, so a resource this module adds to the model
|
|
37
|
+
* and forgets to address — or forgets to answer the actions for — is a **compile
|
|
38
|
+
* error** rather than a runtime `undefined` at the first unique read or the first
|
|
39
|
+
* cascade question.
|
|
40
|
+
*
|
|
41
|
+
* The action table carries **only owning edges**, because only an owning edge has
|
|
42
|
+
* an action: PSL forbids the back-reference from declaring one, so the
|
|
43
|
+
* back-reference's answer is its partner's. Its direction is the one documented on
|
|
44
|
+
* `PrismaReferentialEdge` and on `field.mapper.ts`'s `PrismaRelationEdge` —
|
|
45
|
+
* *deleting a `target` does `onDelete` to rows of the enclosing resource* — and it
|
|
46
|
+
* is carried out of field projection unchanged rather than re-derived here.
|
|
47
|
+
*
|
|
48
|
+
* # No provider reaches this module, and that is enforced by the input
|
|
49
|
+
*
|
|
50
|
+
* The composer is created with a {@link TransactionCapability} and nothing else.
|
|
51
|
+
* `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
|
|
64
|
+
*
|
|
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.
|
|
70
|
+
*
|
|
71
|
+
* 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.
|
|
77
|
+
*/
|
|
78
|
+
/** Composition refuses: the collected diagnostics, named and reported together. */
|
|
79
|
+
export declare class PrismaModelCompositionError extends Error {
|
|
80
|
+
readonly name = "PrismaModelCompositionError";
|
|
81
|
+
readonly diagnostics: readonly string[];
|
|
82
|
+
constructor(diagnostics: readonly string[]);
|
|
83
|
+
}
|
|
84
|
+
export type PrismaModelCompositionOptions = {
|
|
85
|
+
/**
|
|
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`.
|
|
89
|
+
*/
|
|
90
|
+
readonly transactions: TransactionCapability;
|
|
91
|
+
};
|
|
92
|
+
export type PrismaModelDiscoveryInput = PrismaModelCompositionOptions & {
|
|
93
|
+
/** PSL — what the schema says. */
|
|
94
|
+
readonly facts: PrismaSchemaFacts;
|
|
95
|
+
/** TYPE — what this client can do. */
|
|
96
|
+
readonly surface: PrismaCapabilitySurface;
|
|
97
|
+
/** Resource names the existence gate admitted, in PSL declaration order. */
|
|
98
|
+
readonly admitted: readonly string[];
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The generate step's `composeModel` seam, filled.
|
|
102
|
+
*
|
|
103
|
+
* A factory rather than a bare function because the step's seam takes only the
|
|
104
|
+
* readers' output — order and refusal over external effects is the step's job, and
|
|
105
|
+
* the declared driver fact is the caller's.
|
|
106
|
+
*/
|
|
107
|
+
export declare function createPrismaModelComposer(options: PrismaModelCompositionOptions): PrismaModelComposer;
|
|
108
|
+
export declare function discoverPrismaModel(input: PrismaModelDiscoveryInput): PrismaComposedArtifact;
|