@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,273 @@
1
+ import type { AdapterModel, AdapterOperation } from "@aventara/core";
2
+ import type { PrismaResourceTranslation } from "../discovery/discovery-artifact.interface.js";
3
+ import { type PrismaProjectionPlan } from "./projection.translator.js";
4
+ /**
5
+ * T-7 — **one validated Aventara operation → one provider call description**,
6
+ * and the module that makes the whole sixteen-variant grammar assertable as pure
7
+ * data with no database in sight.
8
+ *
9
+ * Splitting translation from execution is the plan's single largest testability
10
+ * decision (T-7): everything below is a pure function of the discovered model,
11
+ * the generate-time translation table and the arguments core validated. The
12
+ * database is needed only for the *other* half, `execution/operation.executor.ts`.
13
+ *
14
+ * # The dispatch table is the module
15
+ *
16
+ * {@link PRISMA_VARIANT_DISPATCH} is total over core's own sixteen
17
+ * `family.variant` keys by `satisfies`, so a seventeenth standard operation added
18
+ * to core is a compile error here rather than a variant that quietly reaches no
19
+ * provider call. Reading it answers, in one place, every question of the form
20
+ * "what does Aventara's X actually issue".
21
+ *
22
+ * # The negative half of the table is the load-bearing half
23
+ *
24
+ * {@link PrismaAction} deliberately **excludes** `findUniqueOrThrow` and
25
+ * `findFirstOrThrow` (RC-2: core's outcome mapper owns the absence decision, and
26
+ * an `OrThrow` call would move it into the adapter), `groupBy` and `aggregate`
27
+ * (spec §18.5), and **every raw action** — the `findRaw`/`aggregateRaw` model
28
+ * actions and the client's own raw methods — which C-853 forbids outright and
29
+ * permanently. Aventara is not an ORM and reaches the database only through the
30
+ * adapter's ORM; a raw statement would forfeit the ORM's own injection
31
+ * protection and oblige Aventara to build its own. Because the exclusion lives in
32
+ * the *type*, a raw call is not representable in a {@link PrismaExecutionPlan} at
33
+ * all: the rule is enforced by the compiler, not by a reviewer's memory.
34
+ * `operation.translator.spec.ts` asserts the same thing from the value side, as
35
+ * the belt to that type's braces.
36
+ *
37
+ * # `delete.many` is a hole in the table, and the hole is the decision
38
+ *
39
+ * 7.10.0 has no `deleteManyAndReturn` **[PKG]**, and the architect ruled the
40
+ * variant **omitted rather than emulated** (C-829, D-826). `operations.deriver.ts`
41
+ * therefore never advertises it, so core rejects the request with `A2002
42
+ * UNKNOWN_OPERATION` before any adapter call. The table still carries the key —
43
+ * it is total over core's grammar — with an `"unavailable"` entry, so the
44
+ * omission is a fact a reader can find rather than an absence they have to infer.
45
+ *
46
+ * # First-style variants, and the write route
47
+ *
48
+ * `update.first` and `delete.first` are **framework-manufactured** (D-828): a
49
+ * cross-ORM survey found the four-part shape — general filter, ordering decides
50
+ * "first", effect limited to one row, the affected row is returned, all in one
51
+ * call — essentially absent across ORM design, and Prisma 7.10.0 has no
52
+ * `updateFirst`/`deleteFirst` **[PKG]**. So the plan is an ordinary read then
53
+ * write, and its two steps are {@link PrismaExecutionPlan}'s `"emulated"` arm.
54
+ *
55
+ * **The write route is Route A — select-first + unique mutation — and Task 10
56
+ * measured it rather than inheriting it.** T-7.1.1 recorded two routes and §14e
57
+ * reserved the choice; §1.0 forbids an implementation task from blocking on an
58
+ * owner decision, so Task 10 ran both against the real 7.10.0 client on **both**
59
+ * engines. Every row came out for Route A, and two of them came out *against*
60
+ * Route B:
61
+ *
62
+ * ```text
63
+ * measured 2026-10-02, @prisma/client 7.10.0, SQLite + PostgreSQL 18, identical
64
+ *
65
+ * A update({ where: { id, AND: […] }, data, select }) -> returned the row
66
+ * A …with a non-matching AND -> threw P2025, row untouched
67
+ * A …select reaching a to-many relation AND _count -> returned both
68
+ * A delete({ where: { id, AND: […] }, select }) -> returned the deleted row
69
+ * A …with a non-matching AND -> threw P2025, row still present
70
+ * A findFirst + update inside $transaction, non-matching AND -> threw P2025 at the emulator's own call site
71
+ * B updateManyAndReturn({ where: { id, AND: […] }, … }) -> returned [row]
72
+ * B …with a non-matching AND -> resolved []
73
+ * B …select: { posts: { … } } -> REJECTED: "Unknown field `posts` for select
74
+ * statement on model `UpdateManyAuthorAndReturnOutputType`"
75
+ * B …select: { _count: { … } } -> REJECTED: same, for `_count`
76
+ * B deleteMany({ where: { id, AND: […] } }) -> { count: 1 } — carries no row at all
77
+ * ```
78
+ *
79
+ * So Route A is the mechanism spec §8.3's own worked example names, it satisfies
80
+ * §8.2's return contract for **every** projection core can plan, and it needs no
81
+ * pre-write projection read and no identifier strip. Route B cannot serve a
82
+ * caller projection that reaches a to-many relation or `$count` at all, and its
83
+ * `delete.first` must read before it writes. The route is **one entry** in the
84
+ * table below — {@link FIRST_VARIANT_WRITE_ROUTE} — which is the seam D-801/§9
85
+ * put here precisely so §14e's ratification (or reversal) is one edit.
86
+ *
87
+ * # The write step of an emulated plan is a template, and says so
88
+ *
89
+ * A `"single"` plan's call is issuable as written. An `"emulated"` plan's write
90
+ * step is **not**, by construction: its `where` carries the caller's re-asserted
91
+ * predicate under `AND` and the identifier is added by
92
+ * `execution/first-variant.emulator.ts` from the row the read returned. That is
93
+ * what "emulated" means — the second statement is not knowable until the first
94
+ * has run — and it is why the emulator, not this module, issues it.
95
+ *
96
+ * # Nothing is emitted that the framework did not send (C-855, D-830)
97
+ *
98
+ * Every key below is added only when the corresponding Aventara argument is
99
+ * **present**. An absent argument and an `undefined` one are different arguments
100
+ * to a provider, so `"take" in args` is never made true by accident. An argument
101
+ * this module does not recognise raises rather than being dropped: an argument
102
+ * that typechecks and is silently ignored is worse than an absent one, because
103
+ * the absent one fails loudly at the caller (D-830's MikroORM evidence).
104
+ */
105
+ /** The `ModelAction` subset this adapter uses. See the header for the exclusions. */
106
+ export type PrismaAction = "findUnique" | "findFirst" | "findMany" | "count" | "create" | "createMany" | "createManyAndReturn" | "update" | "updateMany" | "updateManyAndReturn" | "delete" | "deleteMany" | "upsert";
107
+ /** One provider call description, as plain data. */
108
+ export type PrismaCall = {
109
+ /**
110
+ * The Prisma **model** name. Discovery's existence gate keys a Resource on the
111
+ * PSL model name, so the Resource key *is* the model name; the delegate
112
+ * property that reaches it is the executor's concern, not translation's.
113
+ */
114
+ readonly model: string;
115
+ readonly action: PrismaAction;
116
+ readonly args: Readonly<Record<string, unknown>>;
117
+ };
118
+ /** A variant is either one call, or an emulated sequence (§8.3, D-807). */
119
+ export type PrismaExecutionPlan = {
120
+ readonly kind: "single";
121
+ readonly call: PrismaCall;
122
+ } | {
123
+ readonly kind: "emulated";
124
+ readonly steps: readonly PrismaCall[];
125
+ readonly atomicity: "outer-tx" | "own-tx";
126
+ };
127
+ /**
128
+ * What the variant owes its caller, and therefore which shaper runs (T-8).
129
+ *
130
+ * It is a property of the *variant*, not of the provider answer, so it belongs
131
+ * in the dispatch table beside the action rather than in a second table the
132
+ * executor would have to keep in step.
133
+ */
134
+ export type PrismaResultKind = "row" | "rows" | "count";
135
+ export type PrismaVariantDispatch = {
136
+ readonly kind: "single";
137
+ readonly action: PrismaAction;
138
+ readonly result: PrismaResultKind;
139
+ } | {
140
+ readonly kind: "emulated";
141
+ readonly read: PrismaAction;
142
+ readonly write: PrismaAction;
143
+ readonly result: PrismaResultKind;
144
+ } | {
145
+ readonly kind: "unavailable";
146
+ readonly reason: string;
147
+ };
148
+ /** The sixteen-variant dispatch table (T-7, §18.4), total over core's grammar. */
149
+ export declare const PRISMA_VARIANT_DISPATCH: {
150
+ readonly "find.first": {
151
+ readonly kind: "single";
152
+ readonly action: "findFirst";
153
+ readonly result: "row";
154
+ };
155
+ readonly "find.unique": {
156
+ readonly kind: "single";
157
+ readonly action: "findUnique";
158
+ readonly result: "row";
159
+ };
160
+ readonly "find.many": {
161
+ readonly kind: "single";
162
+ readonly action: "findMany";
163
+ readonly result: "rows";
164
+ };
165
+ readonly "find.count": {
166
+ readonly kind: "single";
167
+ readonly action: "count";
168
+ readonly result: "count";
169
+ };
170
+ readonly "create.one": {
171
+ readonly kind: "single";
172
+ readonly action: "create";
173
+ readonly result: "row";
174
+ };
175
+ readonly "create.many": {
176
+ readonly kind: "single";
177
+ readonly action: "createManyAndReturn";
178
+ readonly result: "rows";
179
+ };
180
+ readonly "create.count": {
181
+ readonly kind: "single";
182
+ readonly action: "createMany";
183
+ readonly result: "count";
184
+ };
185
+ readonly "update.first": {
186
+ readonly kind: "emulated";
187
+ readonly read: "findFirst";
188
+ readonly write: "update";
189
+ readonly result: "row";
190
+ };
191
+ readonly "update.unique": {
192
+ readonly kind: "single";
193
+ readonly action: "update";
194
+ readonly result: "row";
195
+ };
196
+ readonly "update.many": {
197
+ readonly kind: "single";
198
+ readonly action: "updateManyAndReturn";
199
+ readonly result: "rows";
200
+ };
201
+ readonly "update.count": {
202
+ readonly kind: "single";
203
+ readonly action: "updateMany";
204
+ readonly result: "count";
205
+ };
206
+ readonly "delete.first": {
207
+ readonly kind: "emulated";
208
+ readonly read: "findFirst";
209
+ readonly write: "delete";
210
+ readonly result: "row";
211
+ };
212
+ readonly "delete.unique": {
213
+ readonly kind: "single";
214
+ readonly action: "delete";
215
+ readonly result: "row";
216
+ };
217
+ readonly "delete.many": {
218
+ readonly kind: "unavailable";
219
+ readonly reason: "@prisma/client 7.10.0 has no deleteManyAndReturn, so delete.many is omitted rather than emulated (C-829, D-826); delete.count deletes a filtered set";
220
+ };
221
+ readonly "delete.count": {
222
+ readonly kind: "single";
223
+ readonly action: "deleteMany";
224
+ readonly result: "count";
225
+ };
226
+ readonly "upsert.unique": {
227
+ readonly kind: "single";
228
+ readonly action: "upsert";
229
+ readonly result: "row";
230
+ };
231
+ };
232
+ /** An operation the adapter will not translate. */
233
+ export declare class PrismaOperationTranslationError extends Error {
234
+ readonly name = "PrismaOperationTranslationError";
235
+ }
236
+ /**
237
+ * The translation of one operation: the plan, plus the projection plan the result
238
+ * shaper needs.
239
+ *
240
+ * T-7 names only `PrismaExecutionPlan`, and that is not enough on its own:
241
+ * `AdapterOperation` carries no read plan **[CORE]**, so Task 8's
242
+ * {@link PrismaProjectionPlan} — the reconstruction of where every `$count`
243
+ * envelope sits — is produced *here*, while translating the projection, and
244
+ * cannot be recovered from the emitted Prisma arguments afterwards (Prisma's
245
+ * `_count` is a sibling key, not a position). Returning it beside the plan is
246
+ * what keeps `result.shaper.ts` free of a second, drifting reconstruction.
247
+ */
248
+ export type PrismaOperationTranslation = {
249
+ readonly plan: PrismaExecutionPlan;
250
+ readonly projection: PrismaProjectionPlan;
251
+ /**
252
+ * The caller's `where`, translated, or `undefined` when they sent none.
253
+ *
254
+ * Recorded for the emulator, which re-asserts it on the write (C-856, D-829).
255
+ * It is the same value the read step carries, by identity, so the two halves
256
+ * of the manufacture cannot assert different predicates.
257
+ */
258
+ readonly predicate: Readonly<Record<string, unknown>> | undefined;
259
+ };
260
+ export type PrismaOperationTranslationInput = {
261
+ readonly model: AdapterModel;
262
+ /** The generate-time translation table: Prisma addressing per Resource. */
263
+ readonly translation: Readonly<Record<string, PrismaResourceTranslation>>;
264
+ readonly operation: AdapterOperation;
265
+ };
266
+ /**
267
+ * Translates one validated Aventara operation.
268
+ *
269
+ * @throws {PrismaOperationTranslationError} for a variant the adapter advertises
270
+ * no call for, an argument it does not recognise, or a unique selector that
271
+ * addresses no row — never a silently narrowed or widened call.
272
+ */
273
+ export declare function translateOperation(input: PrismaOperationTranslationInput): PrismaOperationTranslation;
@@ -0,0 +1,377 @@
1
+ import { addressIdentifier } from "./identifier.addressing.js";
2
+ import { translateMutationData } from "./mutation.translator.js";
3
+ import { translateOrderBy, translatePagination, } from "./order-pagination.translator.js";
4
+ import { translateProjection, } from "./projection.translator.js";
5
+ import { translateWhere } from "./where.translator.js";
6
+ /**
7
+ * The chosen first-variant write route, in one place (§14e, T-7.1.1).
8
+ *
9
+ * Route A: the read selects the identifier, and the write is Prisma's own
10
+ * `update`/`delete` narrowed to that identifier with the caller's predicate
11
+ * re-asserted. Both are projection-carrying — their result maps through
12
+ * `GetFindResult`, the same helper `findFirst` uses **[PKG,
13
+ * `runtime/client.d.ts:1352`, `:1356`, `:1144`]** — and Task 10 measured both on
14
+ * both engines (see the header). Switching to Route B is an edit to these two
15
+ * cells and to `first-variant.emulator.ts`'s lost-row detection, and nothing else.
16
+ */
17
+ const FIRST_VARIANT_WRITE_ROUTE = {
18
+ update: "update",
19
+ delete: "delete",
20
+ };
21
+ /** The sixteen-variant dispatch table (T-7, §18.4), total over core's grammar. */
22
+ export const PRISMA_VARIANT_DISPATCH = {
23
+ "find.first": { kind: "single", action: "findFirst", result: "row" },
24
+ // RC-2: `findUnique` and a returned `null`, never `findUniqueOrThrow` — core's
25
+ // `OUTCOME_TABLE` owns the absence decision and maps the `null` to `A2003`.
26
+ "find.unique": { kind: "single", action: "findUnique", result: "row" },
27
+ "find.many": { kind: "single", action: "findMany", result: "rows" },
28
+ "find.count": { kind: "single", action: "count", result: "count" },
29
+ "create.one": { kind: "single", action: "create", result: "row" },
30
+ // C-817: the row-returning bulk write is what `create.many` owes its caller;
31
+ // `createMany` answers `{ count }` and is `create.count`'s call instead.
32
+ "create.many": {
33
+ kind: "single",
34
+ action: "createManyAndReturn",
35
+ result: "rows",
36
+ },
37
+ "create.count": { kind: "single", action: "createMany", result: "count" },
38
+ "update.first": {
39
+ kind: "emulated",
40
+ read: "findFirst",
41
+ write: FIRST_VARIANT_WRITE_ROUTE.update,
42
+ result: "row",
43
+ },
44
+ "update.unique": { kind: "single", action: "update", result: "row" },
45
+ "update.many": {
46
+ kind: "single",
47
+ action: "updateManyAndReturn",
48
+ result: "rows",
49
+ },
50
+ "update.count": { kind: "single", action: "updateMany", result: "count" },
51
+ "delete.first": {
52
+ kind: "emulated",
53
+ read: "findFirst",
54
+ write: FIRST_VARIANT_WRITE_ROUTE.delete,
55
+ result: "row",
56
+ },
57
+ "delete.unique": { kind: "single", action: "delete", result: "row" },
58
+ "delete.many": {
59
+ kind: "unavailable",
60
+ reason: "@prisma/client 7.10.0 has no deleteManyAndReturn, so delete.many is omitted rather than emulated (C-829, D-826); delete.count deletes a filtered set",
61
+ },
62
+ "delete.count": { kind: "single", action: "deleteMany", result: "count" },
63
+ "upsert.unique": { kind: "single", action: "upsert", result: "row" },
64
+ };
65
+ /** An operation the adapter will not translate. */
66
+ export class PrismaOperationTranslationError extends Error {
67
+ name = "PrismaOperationTranslationError";
68
+ }
69
+ /**
70
+ * Translates one validated Aventara operation.
71
+ *
72
+ * @throws {PrismaOperationTranslationError} for a variant the adapter advertises
73
+ * no call for, an argument it does not recognise, or a unique selector that
74
+ * addresses no row — never a silently narrowed or widened call.
75
+ */
76
+ export function translateOperation(input) {
77
+ const key = `${input.operation.family}.${input.operation.variant}`;
78
+ const dispatch = Object.hasOwn(PRISMA_VARIANT_DISPATCH, key)
79
+ ? PRISMA_VARIANT_DISPATCH[key]
80
+ : undefined;
81
+ if (dispatch === undefined) {
82
+ throw new PrismaOperationTranslationError(`"${key}" is not a standard Aventara operation`);
83
+ }
84
+ if (dispatch.kind === "unavailable") {
85
+ throw new PrismaOperationTranslationError(`"${key}" is not available on the Prisma adapter: ${dispatch.reason}`);
86
+ }
87
+ const context = {
88
+ model: input.model,
89
+ translation: input.translation,
90
+ operation: input.operation,
91
+ resource: input.operation.resource,
92
+ args: input.operation.arguments,
93
+ };
94
+ const projection = translateProjection(context.args, {
95
+ translateRelationArguments: relationArgumentTranslator(context),
96
+ });
97
+ const predicate = context.args.where === undefined || isUniqueSelectorVariant(key)
98
+ ? undefined
99
+ : translateWhere({
100
+ model: context.model,
101
+ resource: context.resource,
102
+ where: context.args.where,
103
+ });
104
+ if (dispatch.kind === "single") {
105
+ return {
106
+ plan: {
107
+ kind: "single",
108
+ call: {
109
+ model: context.resource,
110
+ action: dispatch.action,
111
+ args: singleCallArguments(context, key, predicate, projection.args),
112
+ },
113
+ },
114
+ projection: projection.plan,
115
+ predicate,
116
+ };
117
+ }
118
+ return {
119
+ plan: {
120
+ kind: "emulated",
121
+ steps: [
122
+ {
123
+ model: context.resource,
124
+ action: dispatch.read,
125
+ args: {
126
+ ...(predicate === undefined ? {} : { where: predicate }),
127
+ ...orderByArgument(context),
128
+ // The read selects the identifier and nothing else: under Route A
129
+ // the WRITE carries the caller's projection, so reading it twice
130
+ // would be a statement the contract does not need.
131
+ select: identifierSelect(context),
132
+ },
133
+ },
134
+ {
135
+ model: context.resource,
136
+ action: dispatch.write,
137
+ args: {
138
+ // A template: the identifier is added by the emulator from the row
139
+ // the read returned. `AND` carries the re-assertion (C-856, D-829).
140
+ ...(predicate === undefined ? {} : { where: { AND: predicate } }),
141
+ ...(context.operation.family === "update"
142
+ ? { data: mutationData(context, "update", "data") }
143
+ : {}),
144
+ ...projection.args,
145
+ },
146
+ },
147
+ ],
148
+ atomicity: "own-tx",
149
+ },
150
+ projection: projection.plan,
151
+ predicate,
152
+ };
153
+ }
154
+ /**
155
+ * The four variants whose `where` is core's FLAT unique selector rather than a
156
+ * general predicate, read from core's own rule: `hasGeneralRootWhere` is false
157
+ * for exactly `find.unique`, `update.unique`, `delete.unique` and `upsert.unique`
158
+ * **[CORE, operations/operation-validator.ts]**.
159
+ */
160
+ function isUniqueSelectorVariant(key) {
161
+ return (key === "find.unique" ||
162
+ key === "update.unique" ||
163
+ key === "delete.unique" ||
164
+ key === "upsert.unique");
165
+ }
166
+ function singleCallArguments(context, key, predicate, projectionArgs) {
167
+ const unique = isUniqueSelectorVariant(key);
168
+ const where = unique
169
+ ? { where: uniqueWhere(context) }
170
+ : predicate === undefined
171
+ ? {}
172
+ : { where: predicate };
173
+ switch (key) {
174
+ case "find.first":
175
+ return { ...where, ...orderByArgument(context), ...projectionArgs };
176
+ case "find.unique":
177
+ return { ...where, ...projectionArgs };
178
+ case "find.many":
179
+ return {
180
+ ...where,
181
+ ...orderByArgument(context),
182
+ ...paginationArguments(context),
183
+ ...projectionArgs,
184
+ };
185
+ case "find.count":
186
+ case "delete.count":
187
+ return where;
188
+ case "create.one":
189
+ return {
190
+ data: mutationData(context, "create", "data"),
191
+ ...projectionArgs,
192
+ };
193
+ case "create.many":
194
+ return {
195
+ data: bulkMutationData(context, key, "create"),
196
+ ...projectionArgs,
197
+ };
198
+ case "create.count":
199
+ return { data: bulkMutationData(context, key, "create") };
200
+ case "update.unique":
201
+ return {
202
+ ...where,
203
+ data: mutationData(context, "update", "data"),
204
+ ...projectionArgs,
205
+ };
206
+ case "update.many":
207
+ return {
208
+ ...where,
209
+ data: mutationData(context, "update", "data"),
210
+ ...projectionArgs,
211
+ };
212
+ case "update.count":
213
+ return { ...where, data: mutationData(context, "update", "data") };
214
+ case "delete.unique":
215
+ return { ...where, ...projectionArgs };
216
+ case "upsert.unique":
217
+ return {
218
+ ...where,
219
+ create: mutationData(context, "create", "create"),
220
+ update: mutationData(context, "update", "update"),
221
+ ...projectionArgs,
222
+ };
223
+ default:
224
+ throw new PrismaOperationTranslationError(`"${key}" has a dispatch entry and no argument shape, which is a translator defect`);
225
+ }
226
+ }
227
+ /** Core's flat unique selector → `<M>WhereUniqueInput`, at the top level. */
228
+ function uniqueWhere(context) {
229
+ const selector = context.args.where;
230
+ if (!isRecord(selector)) {
231
+ throw new PrismaOperationTranslationError("where must be a unique identifier object for a unique variant");
232
+ }
233
+ const addressed = addressIdentifier({
234
+ addresses: identifiersOf(context, context.resource),
235
+ values: selector,
236
+ match: "exact",
237
+ // A unique selector's values are scalars core has already normalized. The
238
+ // one conversion C-834 names — a core `Decimal`, which Prisma refuses — is
239
+ // NOT applied here, and that is a recorded gap rather than an omission: see
240
+ // `operation.executor.ts`'s header.
241
+ emit: (_member, value) => value,
242
+ });
243
+ if (addressed === undefined) {
244
+ throw new PrismaOperationTranslationError(`where: { ${Object.keys(selector).join(", ")} } is no identifier of Resource "${context.resource}", so it addresses no row`);
245
+ }
246
+ return addressed.where;
247
+ }
248
+ /** The identifier the first-style read selects: the first recorded criterion. */
249
+ function identifierSelect(context) {
250
+ const [address] = identifiersOf(context, context.resource);
251
+ if (address === undefined) {
252
+ throw new PrismaOperationTranslationError(`Resource "${context.resource}" has no identifier, so a first-style variant has nothing to write by`);
253
+ }
254
+ return Object.fromEntries(address.members.map((member) => [member, true]));
255
+ }
256
+ function identifiersOf(context, resource) {
257
+ const entry = context.translation[resource];
258
+ if (entry === undefined) {
259
+ throw new PrismaOperationTranslationError(`Resource "${resource}" has no Prisma addressing in the translation table`);
260
+ }
261
+ return entry.identifierKeys;
262
+ }
263
+ function orderByArgument(context) {
264
+ if (context.args.orderBy === undefined) {
265
+ return {};
266
+ }
267
+ return {
268
+ orderBy: translateOrderBy({
269
+ model: context.model,
270
+ resource: context.resource,
271
+ orderBy: context.args.orderBy,
272
+ }),
273
+ };
274
+ }
275
+ function paginationArguments(context) {
276
+ return translatePagination({
277
+ ...(typeof context.args.limit === "number"
278
+ ? { limit: context.args.limit }
279
+ : {}),
280
+ ...(typeof context.args.offset === "number"
281
+ ? { offset: context.args.offset }
282
+ : {}),
283
+ ...(isRecord(context.args.cursor) ? { cursor: context.args.cursor } : {}),
284
+ identifiers: identifiersOf(context, context.resource),
285
+ });
286
+ }
287
+ function mutationData(context, stage, argument) {
288
+ return translateMutationData({
289
+ model: context.model,
290
+ resource: context.resource,
291
+ operation: context.operation,
292
+ stage,
293
+ data: context.args[argument],
294
+ }, mutationDependencies(context));
295
+ }
296
+ /** `create.many`/`create.count` carry an ARRAY of records **[CORE]**. */
297
+ function bulkMutationData(context, key, stage) {
298
+ const data = context.args.data;
299
+ if (!Array.isArray(data)) {
300
+ throw new PrismaOperationTranslationError(`"${key}" carries an array of records in \`data\``);
301
+ }
302
+ const dependencies = mutationDependencies(context);
303
+ return data.map((entry) => translateMutationData({
304
+ model: context.model,
305
+ resource: context.resource,
306
+ operation: context.operation,
307
+ stage,
308
+ data: entry,
309
+ }, dependencies));
310
+ }
311
+ function mutationDependencies(context) {
312
+ return {
313
+ translateWhere: (resource, where) => translateWhere({ model: context.model, resource, where }),
314
+ identifiers: Object.fromEntries(Object.entries(context.translation).map(([resource, entry]) => [
315
+ resource,
316
+ entry.identifierKeys,
317
+ ])),
318
+ };
319
+ }
320
+ /**
321
+ * Task 7's vocabulary, as the projection translator's one dependency.
322
+ *
323
+ * The nested arguments are translated against the **target** Resource, which the
324
+ * projection path names: `projection.translator.ts` owns where a relation sits
325
+ * and this closure owns what its arguments mean, which is the seam that keeps the
326
+ * `_count` filter the *same* translated `where` the relation read uses.
327
+ */
328
+ function relationArgumentTranslator(context) {
329
+ return (args, { path }) => {
330
+ const target = resolveRelationTarget(context, path);
331
+ const emitted = {};
332
+ for (const key of Object.keys(args)) {
333
+ if (key !== "where" &&
334
+ key !== "orderBy" &&
335
+ key !== "limit" &&
336
+ key !== "offset" &&
337
+ key !== "cursor") {
338
+ throw new PrismaOperationTranslationError(`select.${path.join(".")}.${key} is not an Aventara relation argument, so the adapter will not emit it`);
339
+ }
340
+ }
341
+ if (args.where !== undefined) {
342
+ emitted.where = translateWhere({
343
+ model: context.model,
344
+ resource: target,
345
+ where: args.where,
346
+ });
347
+ }
348
+ if (args.orderBy !== undefined) {
349
+ emitted.orderBy = translateOrderBy({
350
+ model: context.model,
351
+ resource: target,
352
+ orderBy: args.orderBy,
353
+ });
354
+ }
355
+ Object.assign(emitted, translatePagination({
356
+ ...(typeof args.limit === "number" ? { limit: args.limit } : {}),
357
+ ...(typeof args.offset === "number" ? { offset: args.offset } : {}),
358
+ ...(isRecord(args.cursor) ? { cursor: args.cursor } : {}),
359
+ identifiers: identifiersOf(context, target),
360
+ }));
361
+ return emitted;
362
+ };
363
+ }
364
+ function resolveRelationTarget(context, path) {
365
+ let resource = context.resource;
366
+ for (const segment of path) {
367
+ const field = context.model.resources[resource]?.fields[segment];
368
+ if (field === undefined || field.kind !== "relation") {
369
+ throw new PrismaOperationTranslationError(`select.${path.join(".")}: "${segment}" is not a relation of Resource "${resource}"`);
370
+ }
371
+ resource = field.target;
372
+ }
373
+ return resource;
374
+ }
375
+ function isRecord(value) {
376
+ return typeof value === "object" && value !== null && !Array.isArray(value);
377
+ }