@aventara/prisma7-adapter 0.1.0-pilot.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +91 -0
  2. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  3. package/README.md +564 -0
  4. package/dist/discovery/artifact.loader.d.ts +26 -0
  5. package/dist/discovery/artifact.loader.js +124 -0
  6. package/dist/discovery/capability-surface.interface.d.ts +191 -0
  7. package/dist/discovery/capability-surface.interface.js +13 -0
  8. package/dist/discovery/capability.deriver.d.ts +92 -0
  9. package/dist/discovery/capability.deriver.js +350 -0
  10. package/dist/discovery/discovery-artifact.interface.d.ts +210 -0
  11. package/dist/discovery/discovery-artifact.interface.js +1 -0
  12. package/dist/discovery/enum.mapper.d.ts +59 -0
  13. package/dist/discovery/enum.mapper.js +32 -0
  14. package/dist/discovery/field.mapper.d.ts +244 -0
  15. package/dist/discovery/field.mapper.js +387 -0
  16. package/dist/discovery/identifier.mapper.d.ts +188 -0
  17. package/dist/discovery/identifier.mapper.js +132 -0
  18. package/dist/discovery/model.discoverer.d.ts +108 -0
  19. package/dist/discovery/model.discoverer.js +341 -0
  20. package/dist/discovery/operations.deriver.d.ts +75 -0
  21. package/dist/discovery/operations.deriver.js +92 -0
  22. package/dist/discovery/scalar.mapper.d.ts +81 -0
  23. package/dist/discovery/scalar.mapper.js +51 -0
  24. package/dist/discovery/schema-facts.interface.d.ts +174 -0
  25. package/dist/discovery/schema-facts.interface.js +11 -0
  26. package/dist/discovery/schema.reader.d.ts +162 -0
  27. package/dist/discovery/schema.reader.js +692 -0
  28. package/dist/discovery/types.reader.d.ts +24 -0
  29. package/dist/discovery/types.reader.js +617 -0
  30. package/dist/discovery/vocabulary.dictionary.d.ts +258 -0
  31. package/dist/discovery/vocabulary.dictionary.js +429 -0
  32. package/dist/errors/error.normalizer.d.ts +250 -0
  33. package/dist/errors/error.normalizer.js +135 -0
  34. package/dist/errors/prisma-error.reader.d.ts +144 -0
  35. package/dist/errors/prisma-error.reader.js +167 -0
  36. package/dist/execution/first-variant.emulator.d.ts +64 -0
  37. package/dist/execution/first-variant.emulator.js +205 -0
  38. package/dist/execution/operation.executor.d.ts +87 -0
  39. package/dist/execution/operation.executor.js +210 -0
  40. package/dist/execution/result.shaper.d.ts +103 -0
  41. package/dist/execution/result.shaper.js +141 -0
  42. package/dist/execution/transaction.executor.d.ts +147 -0
  43. package/dist/execution/transaction.executor.js +13 -0
  44. package/dist/generate/artifact.emitter.d.ts +50 -0
  45. package/dist/generate/artifact.emitter.js +105 -0
  46. package/dist/generate/declared-support.reader.d.ts +33 -0
  47. package/dist/generate/declared-support.reader.js +74 -0
  48. package/dist/generate/discovery.bin.d.ts +2 -0
  49. package/dist/generate/discovery.bin.js +15 -0
  50. package/dist/generate/discovery.cli.d.ts +24 -0
  51. package/dist/generate/discovery.cli.js +163 -0
  52. package/dist/generate/existence.gate.d.ts +73 -0
  53. package/dist/generate/existence.gate.js +91 -0
  54. package/dist/generate/index.d.ts +81 -0
  55. package/dist/generate/index.js +65 -0
  56. package/dist/generate/node-version.guard.d.ts +8 -0
  57. package/dist/generate/node-version.guard.js +59 -0
  58. package/dist/generate/prisma-cli.resolver.d.ts +48 -0
  59. package/dist/generate/prisma-cli.resolver.js +95 -0
  60. package/dist/generate/prisma.generate.d.ts +164 -0
  61. package/dist/generate/prisma.generate.js +278 -0
  62. package/dist/generate/reader-crosscheck.d.ts +156 -0
  63. package/dist/generate/reader-crosscheck.js +202 -0
  64. package/dist/index.d.ts +5 -0
  65. package/dist/index.js +1 -0
  66. package/dist/prisma-client.interface.d.ts +48 -0
  67. package/dist/prisma-client.interface.js +9 -0
  68. package/dist/prisma.adapter.d.ts +55 -0
  69. package/dist/prisma.adapter.js +105 -0
  70. package/dist/provider/provider-profile.d.ts +53 -0
  71. package/dist/provider/provider-profile.js +91 -0
  72. package/dist/translation/identifier.addressing.d.ts +80 -0
  73. package/dist/translation/identifier.addressing.js +30 -0
  74. package/dist/translation/mutation.translator.d.ts +115 -0
  75. package/dist/translation/mutation.translator.js +580 -0
  76. package/dist/translation/operation.translator.d.ts +273 -0
  77. package/dist/translation/operation.translator.js +377 -0
  78. package/dist/translation/order-pagination.translator.d.ts +86 -0
  79. package/dist/translation/order-pagination.translator.js +159 -0
  80. package/dist/translation/projection.translator.d.ts +115 -0
  81. package/dist/translation/projection.translator.js +151 -0
  82. package/dist/translation/scalar.conversion.d.ts +101 -0
  83. package/dist/translation/scalar.conversion.js +221 -0
  84. package/dist/translation/where.translator.d.ts +79 -0
  85. package/dist/translation/where.translator.js +254 -0
  86. package/package.json +102 -0
@@ -0,0 +1,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
+ }