@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,580 @@
|
|
|
1
|
+
import { advertisedDirectives, } from "@aventara/core";
|
|
2
|
+
import { addressIdentifier } from "./identifier.addressing.js";
|
|
3
|
+
import { emitScalarValue } from "./scalar.conversion.js";
|
|
4
|
+
/**
|
|
5
|
+
* T-7's mutation half — one validated Aventara `data` in, one Prisma write
|
|
6
|
+
* argument out.
|
|
7
|
+
*
|
|
8
|
+
* # Pure, and the tables are the module
|
|
9
|
+
*
|
|
10
|
+
* No client, no database, no provider name. Three tables carry everything this
|
|
11
|
+
* module knows, each one TOTAL over a core union by `satisfies`, so a member
|
|
12
|
+
* added to core with no row here is a compile error rather than a silently
|
|
13
|
+
* dropped argument (C-855's emission half, C-837's translator table — the one
|
|
14
|
+
* `vocabulary.dictionary.ts` explicitly left to this task):
|
|
15
|
+
*
|
|
16
|
+
* - {@link SCALAR_DIRECTIVE_TOKENS} over `ScalarMutationDirective`;
|
|
17
|
+
* - {@link RELATION_DIRECTIVE_EMISSIONS} over `RelationMutationDirective`;
|
|
18
|
+
* - {@link MUTATION_DATA_SITES} over every standard operation key, which is
|
|
19
|
+
* C-818's enforcement point.
|
|
20
|
+
*
|
|
21
|
+
* C-834's own table is NOT one of them any more: the scalar value conversion was
|
|
22
|
+
* born here, and `where.translator.ts` and `execution/result.shaper.ts` need the
|
|
23
|
+
* same answers, so it moved to `scalar.conversion.ts` and all three read it
|
|
24
|
+
* (Task 10's `identifier.addressing.ts` is the precedent). This module's
|
|
25
|
+
* emission site is {@link emitValue}, which is now a call rather than a copy.
|
|
26
|
+
*
|
|
27
|
+
* Phase 7's `RELATION_MUTATION_RECORD_SITES` is the pattern
|
|
28
|
+
* (`packages/core/src/runtime/mutation-planner.ts`): `as const satisfies
|
|
29
|
+
* Readonly<Record<Union, …>>` keeps every cell a literal while making the union
|
|
30
|
+
* coverage a compile-time obligation. Two of core's passes are already keyed
|
|
31
|
+
* that way; this is the third, and the only one that names Prisma.
|
|
32
|
+
*
|
|
33
|
+
* # Refusing is part of honouring (the `where.translator.ts` stance)
|
|
34
|
+
*
|
|
35
|
+
* The adapter assumes validated input (C-845). It still refuses to EMIT a
|
|
36
|
+
* directive the discovered model does not advertise for that field and stage,
|
|
37
|
+
* and refuses one for which Prisma's nested input at that stage and cardinality
|
|
38
|
+
* has no key at all. Both refusals are C-855 seen from the emission side: an
|
|
39
|
+
* argument is honoured or it is not sent. A directive quietly dropped here would
|
|
40
|
+
* surface as a write that silently did less than it was asked to.
|
|
41
|
+
*
|
|
42
|
+
* # C-818 cannot reach the Contract, so it is enforced here
|
|
43
|
+
*
|
|
44
|
+
* `RelationFieldCapabilities` is one record per field with a single `create`
|
|
45
|
+
* member **[CORE, adapters/adapter-field.ts]**, so "relation create directives
|
|
46
|
+
* for `create.one` and none for `create.many`" is unrepresentable in the model;
|
|
47
|
+
* `model.discoverer.ts` composes the permissive `create.one` answer and says so.
|
|
48
|
+
* The restriction therefore lives in {@link MUTATION_DATA_SITES}, which is keyed
|
|
49
|
+
* by the operation the data belongs to. Measured at 7.10.0 on both engines
|
|
50
|
+
* (2026-10-02), a nested relation key in a bulk write is a hard failure, not a
|
|
51
|
+
* no-op:
|
|
52
|
+
*
|
|
53
|
+
* - `post.createMany({ data: [{ title, author: { connect: … } }] })` →
|
|
54
|
+
* *"Argument `authorId` is missing."*
|
|
55
|
+
* - `post.updateMany({ where, data: { category: { connect: … } } })` →
|
|
56
|
+
* *"Unknown argument `category`. Available options are marked with ?."*
|
|
57
|
+
*
|
|
58
|
+
* # Nothing in the caller's arguments is touched
|
|
59
|
+
*
|
|
60
|
+
* Every object and array returned is new. The input is read, never written: a
|
|
61
|
+
* translator that mutated its argument would corrupt the second attempt of a
|
|
62
|
+
* retried operation, which is the one place the bug would not show up in a
|
|
63
|
+
* single-shot test.
|
|
64
|
+
*/
|
|
65
|
+
/** A mutation the model, the stage or the active operation does not license. */
|
|
66
|
+
export class PrismaMutationTranslationError extends Error {
|
|
67
|
+
name = "PrismaMutationTranslationError";
|
|
68
|
+
}
|
|
69
|
+
/* ------------------------------------------------------------------ *
|
|
70
|
+
* C-837 — the scalar directive table *
|
|
71
|
+
* ------------------------------------------------------------------ */
|
|
72
|
+
/**
|
|
73
|
+
* The Prisma operand key for each of core's scalar mutation directives.
|
|
74
|
+
*
|
|
75
|
+
* The inverse of discovery's `PRISMA_SCALAR_DIRECTIVES` rename table, written
|
|
76
|
+
* here rather than imported from it for the reason `where.translator.ts` gives:
|
|
77
|
+
* that table is generate-time and `vocabulary.dictionary.ts` is excluded from
|
|
78
|
+
* the published build, while this module is runtime. `mutation.translator.spec.ts`
|
|
79
|
+
* round-trips every emitted token back through the dictionary, so the two cannot
|
|
80
|
+
* drift.
|
|
81
|
+
*
|
|
82
|
+
* Prisma's `set` has no row because it is not a directive: plain assignment is
|
|
83
|
+
* how core spells it, and the bare value is what this module emits — wrapping it
|
|
84
|
+
* in `{ set: … }` would add a token the request never carried.
|
|
85
|
+
*/
|
|
86
|
+
const SCALAR_DIRECTIVE_TOKENS = {
|
|
87
|
+
$increment: "increment",
|
|
88
|
+
$decrement: "decrement",
|
|
89
|
+
$multiply: "multiply",
|
|
90
|
+
$divide: "divide",
|
|
91
|
+
$push: "push",
|
|
92
|
+
$unset: "unset",
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* §18.5's normalization table, forward: one Aventara directive → one Prisma
|
|
96
|
+
* nested-write key and operand, per stage and cardinality.
|
|
97
|
+
*
|
|
98
|
+
* Total over `RelationMutationDirective`, so a ninth directive fails to compile
|
|
99
|
+
* — the same obligation core's two tables carry. Every cell below was read off
|
|
100
|
+
* the generated types and then RUN on both engines (2026-10-02); the three that
|
|
101
|
+
* matter are not key-for-key renames and would each have been wrong the obvious
|
|
102
|
+
* way.
|
|
103
|
+
*
|
|
104
|
+
* **To-many `$update` emits `updateMany`, not `update`.** Core validates a
|
|
105
|
+
* to-many `$update` entry's `where` with `validateWhere` — a GENERAL filter, not
|
|
106
|
+
* a unique selector **[CORE, operations/operation-validator-core.ts]** — and
|
|
107
|
+
* §18.5 says so in as many words: *"Prisma nested update/updateMany normalize to
|
|
108
|
+
* $update (general where → update every relation match)"*. Prisma's nested
|
|
109
|
+
* `update` takes `<T>UpdateWithWhereUnique…Input`, which refuses a general
|
|
110
|
+
* filter: *"Argument `where` of type PostWhereUniqueInput needs at least one of
|
|
111
|
+
* `id` arguments."* `updateMany` takes `{ where: <T>ScalarWhereInput, data }`,
|
|
112
|
+
* which is the shape core actually hands over.
|
|
113
|
+
*
|
|
114
|
+
* **To-many `$delete` emits `deleteMany`, for the same reason**, and its operand
|
|
115
|
+
* is the where UNWRAPPED: `deleteMany?: PostScalarWhereInput`. The singular
|
|
116
|
+
* `delete` is a `<T>WhereUniqueInput[]` and refuses the entry: *"Argument
|
|
117
|
+
* `delete`: Invalid value provided. Expected PostWhereUniqueInput[], provided
|
|
118
|
+
* Object."*
|
|
119
|
+
*
|
|
120
|
+
* **A `<T>ScalarWhereInput` carries scalars only**, so the two bulk forms refuse
|
|
121
|
+
* a relation predicate rather than emit one Prisma will reject: *"Unknown
|
|
122
|
+
* argument `category`. Did you mean `categoryId`?"* — and `updateMany`'s `data`
|
|
123
|
+
* is an `UpdateMany` input, so it admits no nested relation write either:
|
|
124
|
+
* *"Unknown argument `category`."*
|
|
125
|
+
*
|
|
126
|
+
* **The create stage admits four directives, not eight.**
|
|
127
|
+
* `<T>Create…NestedInput` carries `create`, `connectOrCreate`, `createMany` and
|
|
128
|
+
* `connect` and nothing else, on both cardinalities — *"Unknown argument
|
|
129
|
+
* `disconnect`. Did you mean `connect`?"*, *"Unknown argument `set`."* A to-one
|
|
130
|
+
* edge has no `set` at EITHER stage: *"Unknown argument `set`. Did you mean
|
|
131
|
+
* `upsert`?"* The `create: null` cells are therefore measured facts.
|
|
132
|
+
*
|
|
133
|
+
* They were also where discovery over-advertised: `PrismaRelationSurface` carried
|
|
134
|
+
* ONE mutation list, read off the UPDATE-stage nested input and reused for both
|
|
135
|
+
* stages, so the model offered `$set`/`$disconnect`/`$update`/`$delete`/`$upsert`
|
|
136
|
+
* at the create stage, which Prisma has no key for. This table refused them
|
|
137
|
+
* loudly — which is C-855's whole point — and the deriver now reads each stage
|
|
138
|
+
* from its own nested input (`createMutations` / `updateMutations`), so the
|
|
139
|
+
* Contract no longer advertises what these cells refuse.
|
|
140
|
+
*/
|
|
141
|
+
const RELATION_DIRECTIVE_EMISSIONS = {
|
|
142
|
+
$create: {
|
|
143
|
+
create: {
|
|
144
|
+
one: { key: "create", operand: "create-data" },
|
|
145
|
+
many: { key: "create", operand: "create-data" },
|
|
146
|
+
},
|
|
147
|
+
update: {
|
|
148
|
+
one: { key: "create", operand: "create-data" },
|
|
149
|
+
many: { key: "create", operand: "create-data" },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
$connect: {
|
|
153
|
+
create: {
|
|
154
|
+
one: { key: "connect", operand: "unique" },
|
|
155
|
+
many: { key: "connect", operand: "unique" },
|
|
156
|
+
},
|
|
157
|
+
update: {
|
|
158
|
+
one: { key: "connect", operand: "unique" },
|
|
159
|
+
many: { key: "connect", operand: "unique" },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
$connectOrCreate: {
|
|
163
|
+
create: {
|
|
164
|
+
one: { key: "connectOrCreate", operand: "connect-or-create" },
|
|
165
|
+
many: { key: "connectOrCreate", operand: "connect-or-create" },
|
|
166
|
+
},
|
|
167
|
+
update: {
|
|
168
|
+
one: { key: "connectOrCreate", operand: "connect-or-create" },
|
|
169
|
+
many: { key: "connectOrCreate", operand: "connect-or-create" },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
$disconnect: {
|
|
173
|
+
create: { one: null, many: null },
|
|
174
|
+
update: {
|
|
175
|
+
one: { key: "disconnect", operand: "disconnect-true" },
|
|
176
|
+
many: { key: "disconnect", operand: "unique" },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
$set: {
|
|
180
|
+
create: { one: null, many: null },
|
|
181
|
+
update: { one: null, many: { key: "set", operand: "unique" } },
|
|
182
|
+
},
|
|
183
|
+
$update: {
|
|
184
|
+
create: { one: null, many: null },
|
|
185
|
+
update: {
|
|
186
|
+
one: { key: "update", operand: "to-one-update" },
|
|
187
|
+
many: { key: "updateMany", operand: "to-many-update-many" },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
$delete: {
|
|
191
|
+
create: { one: null, many: null },
|
|
192
|
+
update: {
|
|
193
|
+
one: { key: "delete", operand: "to-one-delete" },
|
|
194
|
+
many: { key: "deleteMany", operand: "to-many-delete-many" },
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
$upsert: {
|
|
198
|
+
create: { one: null, many: null },
|
|
199
|
+
update: {
|
|
200
|
+
one: { key: "upsert", operand: "to-one-upsert" },
|
|
201
|
+
many: { key: "upsert", operand: "to-many-upsert" },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Every standard operation, and the mutation data it carries — `null` for the
|
|
207
|
+
* ten that carry none.
|
|
208
|
+
*
|
|
209
|
+
* Total over the whole operation set rather than over the mutating subset, so a
|
|
210
|
+
* variant added to `ResourceOperations` fails to compile here instead of
|
|
211
|
+
* reaching the `forbidden`/`nested` question by default. The read and delete
|
|
212
|
+
* families are `null` for the same reason the dictionary spells a missing
|
|
213
|
+
* Aventara word `null`: *"no data"* is an answer, and an absent row is not.
|
|
214
|
+
*
|
|
215
|
+
* `create.many`, `create.count`, `update.many` and `update.count` are the
|
|
216
|
+
* `forbidden` four: they become `createMany`/`updateMany`, whose inputs carry
|
|
217
|
+
* scalars and foreign keys only. `update.first` is `nested` because the variant
|
|
218
|
+
* is emulated as a read followed by a singular `update` (T-7.1), and that write
|
|
219
|
+
* step is an `<M>UpdateInput`.
|
|
220
|
+
*/
|
|
221
|
+
const MUTATION_DATA_SITES = {
|
|
222
|
+
"find.first": null,
|
|
223
|
+
"find.unique": null,
|
|
224
|
+
"find.many": null,
|
|
225
|
+
"find.count": null,
|
|
226
|
+
"create.one": { stages: ["create"], relations: "nested" },
|
|
227
|
+
"create.many": { stages: ["create"], relations: "forbidden" },
|
|
228
|
+
"create.count": { stages: ["create"], relations: "forbidden" },
|
|
229
|
+
"update.first": { stages: ["update"], relations: "nested" },
|
|
230
|
+
"update.unique": { stages: ["update"], relations: "nested" },
|
|
231
|
+
"update.many": { stages: ["update"], relations: "forbidden" },
|
|
232
|
+
"update.count": { stages: ["update"], relations: "forbidden" },
|
|
233
|
+
"delete.first": null,
|
|
234
|
+
"delete.unique": null,
|
|
235
|
+
"delete.many": null,
|
|
236
|
+
"delete.count": null,
|
|
237
|
+
"upsert.unique": { stages: ["create", "update"], relations: "nested" },
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Translates one validated Aventara mutation `data` into the Prisma write
|
|
241
|
+
* argument for the named operation and stage.
|
|
242
|
+
*
|
|
243
|
+
* @throws {PrismaMutationTranslationError} rather than emitting a directive the
|
|
244
|
+
* model does not advertise, one Prisma has no key for at this stage and
|
|
245
|
+
* cardinality, or a nested relation write inside a bulk argument (C-818).
|
|
246
|
+
*/
|
|
247
|
+
export function translateMutationData(input, dependencies) {
|
|
248
|
+
const key = `${input.operation.family}.${input.operation.variant}`;
|
|
249
|
+
if (!isOperationKey(key)) {
|
|
250
|
+
throw new PrismaMutationTranslationError(`data: "${key}" is not a standard Aventara operation`);
|
|
251
|
+
}
|
|
252
|
+
const site = MUTATION_DATA_SITES[key];
|
|
253
|
+
if (site === null) {
|
|
254
|
+
throw new PrismaMutationTranslationError(`data: "${key}" carries no mutation data`);
|
|
255
|
+
}
|
|
256
|
+
if (!site.stages.includes(input.stage)) {
|
|
257
|
+
throw new PrismaMutationTranslationError(`data: "${key}" carries no "${input.stage}" data`);
|
|
258
|
+
}
|
|
259
|
+
return translateRecord({
|
|
260
|
+
model: input.model,
|
|
261
|
+
dependencies,
|
|
262
|
+
relations: site.relations,
|
|
263
|
+
}, input.resource, input.data, input.stage, "data");
|
|
264
|
+
}
|
|
265
|
+
function translateRecord(pass, resourceKey, data, stage, path) {
|
|
266
|
+
const resource = pass.model.resources[resourceKey];
|
|
267
|
+
if (resource === undefined) {
|
|
268
|
+
throw new PrismaMutationTranslationError(`${path}: Resource "${resourceKey}" is not in the discovered model`);
|
|
269
|
+
}
|
|
270
|
+
if (!isRecord(data)) {
|
|
271
|
+
throw new PrismaMutationTranslationError(`${path} must be an object`);
|
|
272
|
+
}
|
|
273
|
+
const emitted = {};
|
|
274
|
+
for (const [key, value] of Object.entries(data)) {
|
|
275
|
+
const field = resource.fields[key];
|
|
276
|
+
if (field === undefined) {
|
|
277
|
+
throw new PrismaMutationTranslationError(`${path}.${key} is not a field of Resource "${resourceKey}"`);
|
|
278
|
+
}
|
|
279
|
+
if (field.kind === "scalar") {
|
|
280
|
+
emitted[key] = translateScalarWrite(pass, field, value, stage, `${path}.${key}`);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (pass.relations === "forbidden") {
|
|
284
|
+
// C-818, and the only place it can be enforced: the Contract cannot say
|
|
285
|
+
// which variant this data belongs to.
|
|
286
|
+
throw new PrismaMutationTranslationError(`${path}.${key}: a bulk write carries no nested relation directive; Prisma's createMany/updateMany inputs admit scalars and foreign keys only (C-818)`);
|
|
287
|
+
}
|
|
288
|
+
emitted[key] = translateRelationWrite(pass, field, value, stage, `${path}.${key}`);
|
|
289
|
+
}
|
|
290
|
+
return emitted;
|
|
291
|
+
}
|
|
292
|
+
function translateScalarWrite(pass, field, value, stage, path) {
|
|
293
|
+
const advertised = field.capabilities[stage];
|
|
294
|
+
if (advertised === undefined) {
|
|
295
|
+
throw new PrismaMutationTranslationError(`${path} is not writable during ${stage}`);
|
|
296
|
+
}
|
|
297
|
+
const directives = directiveKeysOf(value, path);
|
|
298
|
+
if (directives === undefined) {
|
|
299
|
+
// Core's own rule: a value that is not a directive object IS plain
|
|
300
|
+
// assignment, and Prisma spells that the same way. Wrapping it in
|
|
301
|
+
// `{ set: … }` would emit a token the request never carried.
|
|
302
|
+
return emitValue(pass, field, value, path);
|
|
303
|
+
}
|
|
304
|
+
const operand = {};
|
|
305
|
+
for (const directive of directives) {
|
|
306
|
+
if (!isScalarMutationDirective(directive)) {
|
|
307
|
+
throw new PrismaMutationTranslationError(`${path}.${directive} is not an Aventara scalar mutation directive`);
|
|
308
|
+
}
|
|
309
|
+
if (!advertised.includes(directive)) {
|
|
310
|
+
throw new PrismaMutationTranslationError(`${path}: "${directive}" is not advertised on this field for ${stage}, so the adapter will not emit it`);
|
|
311
|
+
}
|
|
312
|
+
operand[SCALAR_DIRECTIVE_TOKENS[directive]] = emitValue(pass, field, value[directive], `${path}.${directive}`);
|
|
313
|
+
}
|
|
314
|
+
return operand;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The directive keys of a scalar write, or `undefined` when the value is a
|
|
318
|
+
* value.
|
|
319
|
+
*
|
|
320
|
+
* The discriminant is the `$` prefix, which is core's own
|
|
321
|
+
* (`validateScalarMutation` rejects a mixed object with `V1001` *"Scalar
|
|
322
|
+
* mutation action objects may contain only $-prefixed directive keys"*). It is
|
|
323
|
+
* mirrored rather than improved on: a `json` column's value is a plain object
|
|
324
|
+
* too, and any second discriminant would disagree with core's on some input.
|
|
325
|
+
*/
|
|
326
|
+
function directiveKeysOf(value, path) {
|
|
327
|
+
if (!isRecord(value)) {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
const keys = Object.keys(value);
|
|
331
|
+
const directives = keys.filter((key) => key.startsWith("$"));
|
|
332
|
+
if (directives.length === 0) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
if (directives.length !== keys.length) {
|
|
336
|
+
throw new PrismaMutationTranslationError(`${path}: a scalar mutation action carries only $-prefixed directive keys`);
|
|
337
|
+
}
|
|
338
|
+
return directives;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* C-834's emission site: one Aventara runtime value, one Prisma operand.
|
|
342
|
+
*
|
|
343
|
+
* The conversion itself belongs to `scalar.conversion.ts`, which owns both
|
|
344
|
+
* directions for every scalar; this module owns only *where* a value sits in a
|
|
345
|
+
* write argument.
|
|
346
|
+
*/
|
|
347
|
+
function emitValue(pass, field, value, path) {
|
|
348
|
+
return emitScalarValue(pass.model, field, value, path);
|
|
349
|
+
}
|
|
350
|
+
function translateRelationWrite(pass, field, value, stage, path) {
|
|
351
|
+
const advertised = advertisedDirectives(field.capabilities[stage]);
|
|
352
|
+
if (advertised === undefined) {
|
|
353
|
+
throw new PrismaMutationTranslationError(`${path} is not writable during ${stage}`);
|
|
354
|
+
}
|
|
355
|
+
if (!isRecord(value)) {
|
|
356
|
+
throw new PrismaMutationTranslationError(`${path} must be a directive object`);
|
|
357
|
+
}
|
|
358
|
+
const emitted = {};
|
|
359
|
+
for (const [directive, operand] of Object.entries(value)) {
|
|
360
|
+
if (!isRelationMutationDirective(directive)) {
|
|
361
|
+
throw new PrismaMutationTranslationError(`${path}.${directive} is not an Aventara relation mutation directive`);
|
|
362
|
+
}
|
|
363
|
+
if (!advertised.includes(directive)) {
|
|
364
|
+
throw new PrismaMutationTranslationError(`${path}: "${directive}" is not advertised on this edge for ${stage}, so the adapter will not emit it`);
|
|
365
|
+
}
|
|
366
|
+
const emission = RELATION_DIRECTIVE_EMISSIONS[directive][stage][field.cardinality];
|
|
367
|
+
if (emission === null) {
|
|
368
|
+
throw new PrismaMutationTranslationError(`${path}: Prisma's nested ${stage} input for a to-${field.cardinality} edge has no operand for "${directive}", so the adapter will not emit one`);
|
|
369
|
+
}
|
|
370
|
+
if (emission.key in emitted) {
|
|
371
|
+
throw new PrismaMutationTranslationError(`${path}.${directive} would overwrite the "${emission.key}" operand another directive already emitted`);
|
|
372
|
+
}
|
|
373
|
+
emitted[emission.key] = emitRelationOperand(pass, field.target, field.cardinality, emission.operand, operand, `${path}.${directive}`);
|
|
374
|
+
}
|
|
375
|
+
return emitted;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* One directive's operand, with its ARITY preserved: core admits a single entry
|
|
379
|
+
* or an array on a to-many edge, Prisma admits both, and collapsing or
|
|
380
|
+
* expanding one would change the shape the request carried.
|
|
381
|
+
*/
|
|
382
|
+
function emitRelationOperand(pass, target, cardinality, form, operand, path) {
|
|
383
|
+
if (cardinality === "many" && Array.isArray(operand)) {
|
|
384
|
+
return operand.map((entry, index) => emitRelationEntry(pass, target, form, entry, `${path}[${index}]`));
|
|
385
|
+
}
|
|
386
|
+
return emitRelationEntry(pass, target, form, operand, path);
|
|
387
|
+
}
|
|
388
|
+
function emitRelationEntry(pass, target, form, entry, path) {
|
|
389
|
+
switch (form) {
|
|
390
|
+
case "create-data":
|
|
391
|
+
return translateRecord(nested(pass), target, entry, "create", path);
|
|
392
|
+
case "unique":
|
|
393
|
+
return addressUnique(pass, target, entry, path);
|
|
394
|
+
case "connect-or-create": {
|
|
395
|
+
const { where, create } = requireMembers(entry, ["where", "create"], path);
|
|
396
|
+
return {
|
|
397
|
+
where: addressUnique(pass, target, where, `${path}.where`),
|
|
398
|
+
create: translateRecord(nested(pass), target, create, "create", `${path}.create`),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
case "disconnect-true":
|
|
402
|
+
if (entry !== true) {
|
|
403
|
+
throw new PrismaMutationTranslationError(`${path}: a to-one $disconnect is true`);
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
case "to-one-update": {
|
|
407
|
+
const { data } = requireMembers(entry, ["data"], path);
|
|
408
|
+
return {
|
|
409
|
+
...optionalWhere(pass, target, entry, path),
|
|
410
|
+
data: translateRecord(nested(pass), target, data, "update", `${path}.data`),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
case "to-one-delete": {
|
|
414
|
+
if (entry === true) {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
if (!isRecord(entry)) {
|
|
418
|
+
throw new PrismaMutationTranslationError(`${path}: a to-one $delete is true or an optional where object`);
|
|
419
|
+
}
|
|
420
|
+
if (!Object.hasOwn(entry, "where")) {
|
|
421
|
+
// `{}` is "delete the related row", which Prisma spells `true`. The
|
|
422
|
+
// empty object is not a Prisma where: `delete: {}` would match nothing.
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return pass.dependencies.translateWhere(target, entry.where, `${path}.where`);
|
|
426
|
+
}
|
|
427
|
+
case "to-one-upsert": {
|
|
428
|
+
const { create, update } = requireMembers(entry, ["create", "update"], path);
|
|
429
|
+
return {
|
|
430
|
+
...optionalWhere(pass, target, entry, path),
|
|
431
|
+
create: translateRecord(nested(pass), target, create, "create", `${path}.create`),
|
|
432
|
+
update: translateRecord(nested(pass), target, update, "update", `${path}.update`),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
case "to-many-update-many": {
|
|
436
|
+
const { where, data } = requireMembers(entry, ["where", "data"], path);
|
|
437
|
+
return {
|
|
438
|
+
where: scalarWhere(pass, target, where, `${path}.where`),
|
|
439
|
+
// `<T>UpdateManyWithWhere…Input.data` is an UpdateMany input: scalars
|
|
440
|
+
// and foreign keys only, exactly as a top-level `updateMany`.
|
|
441
|
+
data: translateRecord(bulk(pass), target, data, "update", `${path}.data`),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
case "to-many-delete-many": {
|
|
445
|
+
const { where } = requireMembers(entry, ["where"], path);
|
|
446
|
+
// `deleteMany` takes the scalar where itself, not `{ where }`.
|
|
447
|
+
return scalarWhere(pass, target, where, `${path}.where`);
|
|
448
|
+
}
|
|
449
|
+
case "to-many-upsert": {
|
|
450
|
+
const { where, create, update } = requireMembers(entry, ["where", "create", "update"], path);
|
|
451
|
+
return {
|
|
452
|
+
where: addressUnique(pass, target, where, `${path}.where`),
|
|
453
|
+
create: translateRecord(nested(pass), target, create, "create", `${path}.create`),
|
|
454
|
+
update: translateRecord(nested(pass), target, update, "update", `${path}.update`),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function nested(pass) {
|
|
460
|
+
return pass.relations === "nested" ? pass : { ...pass, relations: "nested" };
|
|
461
|
+
}
|
|
462
|
+
function bulk(pass) {
|
|
463
|
+
return pass.relations === "forbidden"
|
|
464
|
+
? pass
|
|
465
|
+
: { ...pass, relations: "forbidden" };
|
|
466
|
+
}
|
|
467
|
+
/** A to-one `$update`/`$upsert` `where` is optional, and absent is not `undefined`. */
|
|
468
|
+
function optionalWhere(pass, target, entry, path) {
|
|
469
|
+
if (!isRecord(entry) || !Object.hasOwn(entry, "where")) {
|
|
470
|
+
return {};
|
|
471
|
+
}
|
|
472
|
+
// Prisma's to-one `update`/`upsert` `where` is a general `<T>WhereInput`, so
|
|
473
|
+
// core's flat unique selector needs no addressing here — it is already a valid
|
|
474
|
+
// filter. Measured on both engines (2026-10-02).
|
|
475
|
+
return {
|
|
476
|
+
where: pass.dependencies.translateWhere(target, entry.where, `${path}.where`),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* A `<T>ScalarWhereInput`: the nested bulk forms' filter, which carries scalar
|
|
481
|
+
* columns only.
|
|
482
|
+
*
|
|
483
|
+
* Core's `validateWhere` admits a relation predicate here, so this is a real
|
|
484
|
+
* narrowing and it refuses rather than emits: Prisma answers *"Unknown argument
|
|
485
|
+
* `category`. Did you mean `categoryId`?"* on both engines. Reported as a
|
|
486
|
+
* C-855 gap in the advertised surface rather than silently dropped.
|
|
487
|
+
*/
|
|
488
|
+
function scalarWhere(pass, target, where, path) {
|
|
489
|
+
requireScalarPredicate(pass, target, where, path);
|
|
490
|
+
return pass.dependencies.translateWhere(target, where, path);
|
|
491
|
+
}
|
|
492
|
+
function requireScalarPredicate(pass, target, where, path) {
|
|
493
|
+
if (Array.isArray(where)) {
|
|
494
|
+
for (const [index, member] of where.entries()) {
|
|
495
|
+
requireScalarPredicate(pass, target, member, `${path}[${index}]`);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (!isRecord(where)) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const resource = pass.model.resources[target];
|
|
503
|
+
if (resource === undefined) {
|
|
504
|
+
throw new PrismaMutationTranslationError(`${path}: Resource "${target}" is not in the discovered model`);
|
|
505
|
+
}
|
|
506
|
+
for (const [key, value] of Object.entries(where)) {
|
|
507
|
+
if (key === "AND" || key === "OR" || key === "NOT") {
|
|
508
|
+
requireScalarPredicate(pass, target, value, `${path}.${key}`);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (resource.fields[key]?.kind === "relation") {
|
|
512
|
+
throw new PrismaMutationTranslationError(`${path}.${key}: a nested bulk write filters on scalar fields only, so the adapter will not emit a relation predicate`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Core's FLAT unique selector → Prisma's `<T>WhereUniqueInput`, for a NESTED
|
|
518
|
+
* `connect`/`disconnect`/`set`/`update`/`upsert` target.
|
|
519
|
+
*
|
|
520
|
+
* The addressing rule itself lives in `identifier.addressing.ts`, which Task 10
|
|
521
|
+
* extracted when the top-level where-unique made it the third copy. What stays
|
|
522
|
+
* here is this site's two decisions: the match is **exact**, and each member's
|
|
523
|
+
* value goes out through C-834's scalar emission (`scalar.conversion.ts`).
|
|
524
|
+
*/
|
|
525
|
+
function addressUnique(pass, target, selector, path) {
|
|
526
|
+
if (!isRecord(selector)) {
|
|
527
|
+
throw new PrismaMutationTranslationError(`${path} must be a unique identifier object`);
|
|
528
|
+
}
|
|
529
|
+
const resource = pass.model.resources[target];
|
|
530
|
+
if (resource === undefined) {
|
|
531
|
+
throw new PrismaMutationTranslationError(`${path}: Resource "${target}" is not in the discovered model`);
|
|
532
|
+
}
|
|
533
|
+
const addresses = pass.dependencies.identifiers[target];
|
|
534
|
+
if (addresses === undefined) {
|
|
535
|
+
throw new PrismaMutationTranslationError(`${path}: Resource "${target}" has no Prisma addressing in the translation table`);
|
|
536
|
+
}
|
|
537
|
+
// `"exact"`: core's unique selector carries one identifier and nothing else,
|
|
538
|
+
// so a spare key means it is not that identifier. Each member goes out through
|
|
539
|
+
// C-834's scalar emission, which is why the conversion is passed in rather
|
|
540
|
+
// than the table imported by the addressing module.
|
|
541
|
+
const addressed = addressIdentifier({
|
|
542
|
+
addresses,
|
|
543
|
+
values: selector,
|
|
544
|
+
match: "exact",
|
|
545
|
+
emit: (member, value) => emitSelectorMember(pass, resource.fields[member], value, `${path}.${member}`),
|
|
546
|
+
});
|
|
547
|
+
if (addressed === undefined) {
|
|
548
|
+
throw new PrismaMutationTranslationError(`${path}: { ${Object.keys(selector).join(", ")} } is no identifier of Resource "${target}", so it addresses no row`);
|
|
549
|
+
}
|
|
550
|
+
return addressed.where;
|
|
551
|
+
}
|
|
552
|
+
function emitSelectorMember(pass, field, value, path) {
|
|
553
|
+
if (field === undefined || field.kind !== "scalar") {
|
|
554
|
+
throw new PrismaMutationTranslationError(`${path} is not a scalar field of the target Resource`);
|
|
555
|
+
}
|
|
556
|
+
return emitValue(pass, field, value, path);
|
|
557
|
+
}
|
|
558
|
+
function requireMembers(entry, members, path) {
|
|
559
|
+
if (!isRecord(entry)) {
|
|
560
|
+
throw new PrismaMutationTranslationError(`${path} must be an object carrying ${members.join(" and ")}`);
|
|
561
|
+
}
|
|
562
|
+
for (const member of members) {
|
|
563
|
+
if (!Object.hasOwn(entry, member)) {
|
|
564
|
+
throw new PrismaMutationTranslationError(`${path}.${member} is required by this directive`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return entry;
|
|
568
|
+
}
|
|
569
|
+
function isOperationKey(key) {
|
|
570
|
+
return Object.hasOwn(MUTATION_DATA_SITES, key);
|
|
571
|
+
}
|
|
572
|
+
function isScalarMutationDirective(key) {
|
|
573
|
+
return Object.hasOwn(SCALAR_DIRECTIVE_TOKENS, key);
|
|
574
|
+
}
|
|
575
|
+
function isRelationMutationDirective(key) {
|
|
576
|
+
return Object.hasOwn(RELATION_DIRECTIVE_EMISSIONS, key);
|
|
577
|
+
}
|
|
578
|
+
function isRecord(value) {
|
|
579
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
580
|
+
}
|