@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,24 @@
1
+ import ts from "typescript";
2
+ import type { PrismaCapabilitySurface } from "./capability-surface.interface.js";
3
+ export type PrismaTypesReadResult = {
4
+ readonly surface: PrismaCapabilitySurface;
5
+ /**
6
+ * Every file under the client directory the reader actually opened. Reported
7
+ * rather than assumed: the 🛑 files are absent by refusal, not by luck, and a
8
+ * caller — or a spec — can assert the file **set** and not only the output.
9
+ */
10
+ readonly filesRead: readonly string[];
11
+ };
12
+ export declare class PrismaTypesReaderError extends Error {
13
+ readonly name = "PrismaTypesReaderError";
14
+ }
15
+ /**
16
+ * The reader's one checker question, exported because it is the distinction the
17
+ * whole reader rests on and therefore has to be testable on its own.
18
+ *
19
+ * Returns each written type argument's type as the checker renders it, so
20
+ * `FieldRef<"M0", 'String'>` answers `['"M0"', '"String"']` — including when
21
+ * `FieldRef` itself does not resolve.
22
+ */
23
+ export declare function readWrittenTypeArguments(checker: ts.TypeChecker, reference: ts.TypeReferenceNode): readonly string[];
24
+ export declare function readPrismaCapabilitySurface(clientDirectory: string): PrismaTypesReadResult;
@@ -0,0 +1,617 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import ts from "typescript";
4
+ /**
5
+ * T-3, source **TYPE** — the reader over the generated client's 🟢 public types.
6
+ *
7
+ * # Rule one: the 🟢 files only
8
+ *
9
+ * The files whose banner reads *"🟢 You can import this file directly"* are the
10
+ * only input. At 7.10.0 that is **9 per generated client** — `browser.ts`,
11
+ * `client.ts`, `commonInputTypes.ts`, `enums.ts`, `models.ts` and one
12
+ * `models/<M>.ts` per model — against **3** marked *"🛑 Under no circumstances
13
+ * should you import this file directly! 🛑"*, all three under `internal/`. Across
14
+ * the two Phase 8 clients: 18 and 6, re-counted on 2026-09-27 rather than
15
+ * inherited.
16
+ *
17
+ * The 🛑 files are excluded **by path**, before anything is opened, because
18
+ * classifying them by their banner would require opening them. The compiler host
19
+ * below refuses a read under `internal/` outright instead of quietly skipping it,
20
+ * and every 🟢 file it opens is checked to carry the 🟢 banner.
21
+ *
22
+ * # Rule two: the written type arguments, never the instantiated reference
23
+ *
24
+ * Token-shaped facts come from the **written syntax** — a
25
+ * `TypeReferenceNode`'s `typeArguments` — resolved one node at a time with
26
+ * `getTypeFromTypeNode`.
27
+ *
28
+ * The shorthand "from the AST rather than from the checker" is
29
+ * self-contradictory, since `getTypeFromTypeNode` **is** a `ts.TypeChecker`
30
+ * method. What matters is *which* checker question is asked. On a string-literal
31
+ * type argument, `getTypeFromTypeNode` answers from that node's own syntax and
32
+ * does not depend on `@prisma/client/runtime/client` resolving. Asking for the
33
+ * type *at* the reference (`getTypeAtLocation` on `FieldRef<"M0", 'String'>`)
34
+ * silently answers with the uninstantiated form when the runtime is unresolvable
35
+ * — and `// @ts-nocheck`, which the generator writes into every file, means a
36
+ * program over a broken install reports **zero** diagnostics while doing it
37
+ * (§1.2 item 16). This reader asks the first question only.
38
+ *
39
+ * This program is deliberately built with `noResolve` and `noLib`, so the
40
+ * broken-install condition is not merely survived, it is **the normal case**:
41
+ * nothing outside the 🟢 files is ever resolved, and the reader still answers.
42
+ *
43
+ * # What this reader does not do
44
+ *
45
+ * It states no provider conditionality of its own. `filterOperands` carries each
46
+ * operator's operand **as written** precisely so no later module has to branch on
47
+ * `provider` to learn a fact the types already state (T-3.1 invariant 6).
48
+ *
49
+ * It also does not decide anything: `resources` is the existence gate's evidence,
50
+ * not the gate. A PSL-only entity is a generate-time error, and the generate step
51
+ * owns that error.
52
+ *
53
+ * # Rule three: a scope is a presence question, never a rule reproduced
54
+ *
55
+ * The bulk-returning projection ceiling and the flat-bulk write stage are read the
56
+ * same way everything else here is read — **is the member in the type?** —
57
+ * against `<M>Select{Create,Update}ManyAndReturn`, their `Include` counterparts and
58
+ * the two bulk write inputs. The *explanation* for which relations survive is
59
+ * foreign-key ownership rather than cardinality; reproducing that explanation as a
60
+ * rule would be a tabulated provider fact (D-802, §18.4) that goes wrong the first
61
+ * time Prisma admits a different set. See `PrismaRelationSurface.bulkSelect`.
62
+ */
63
+ /** The banner that marks a file as public. */
64
+ const GREEN_BANNER = "You can import this file directly";
65
+ /** The banner that marks a file as private. Never expected to be read. */
66
+ const RED_BANNER = "Under no circumstances should you import this file directly";
67
+ /** The generated tree's private directory, excluded by path. */
68
+ const PRIVATE_DIRECTORY = "internal";
69
+ /** Edge-argument keys that other surface fields already own, so pagination is the residual. */
70
+ const NON_PAGINATION_EDGE_ARGUMENTS = new Set([
71
+ "select",
72
+ "omit",
73
+ "include",
74
+ "where",
75
+ ]);
76
+ const MANY_WRITE_OPERATION = /^(?:create|update|delete)Many(?:AndReturn)?$/;
77
+ export class PrismaTypesReaderError extends Error {
78
+ name = "PrismaTypesReaderError";
79
+ }
80
+ /**
81
+ * The reader's one checker question, exported because it is the distinction the
82
+ * whole reader rests on and therefore has to be testable on its own.
83
+ *
84
+ * Returns each written type argument's type as the checker renders it, so
85
+ * `FieldRef<"M0", 'String'>` answers `['"M0"', '"String"']` — including when
86
+ * `FieldRef` itself does not resolve.
87
+ */
88
+ export function readWrittenTypeArguments(checker, reference) {
89
+ return (reference.typeArguments ?? []).map((argument) => checker.typeToString(checker.getTypeFromTypeNode(argument)));
90
+ }
91
+ export function readPrismaCapabilitySurface(clientDirectory) {
92
+ const root = path.resolve(clientDirectory);
93
+ const publicFiles = listPublicFiles(root);
94
+ if (publicFiles.length === 0) {
95
+ throw new PrismaTypesReaderError(`no generated 🟢 file was found under ${root}`);
96
+ }
97
+ const filesRead = new Set();
98
+ const host = createPublicOnlyHost(root, filesRead);
99
+ const program = ts.createProgram({
100
+ rootNames: publicFiles,
101
+ // `noResolve` and `noLib` are the point, not an optimisation: nothing
102
+ // outside the 🟢 files can be pulled in, so the reader's answers cannot
103
+ // depend on a resolvable `@prisma/client/runtime/client`.
104
+ options: {
105
+ noResolve: true,
106
+ noLib: true,
107
+ target: ts.ScriptTarget.ESNext,
108
+ moduleDetection: ts.ModuleDetectionKind.Force,
109
+ },
110
+ host,
111
+ });
112
+ const checker = program.getTypeChecker();
113
+ const index = indexDeclarations(program, publicFiles);
114
+ return {
115
+ surface: {
116
+ resources: readResources(checker, index),
117
+ enums: readEnums(program, root),
118
+ },
119
+ filesRead: [...filesRead],
120
+ };
121
+ }
122
+ /* ------------------------------------------------------------------ *
123
+ * Input: the 🟢 files, and a host that refuses the 🛑 ones *
124
+ * ------------------------------------------------------------------ */
125
+ function listPublicFiles(root) {
126
+ return readdirSync(root, { recursive: true, withFileTypes: true })
127
+ .filter((entry) => entry.isFile() &&
128
+ entry.name.endsWith(".ts") &&
129
+ !isPrivatePath(root, path.join(entry.parentPath, entry.name)))
130
+ .map((entry) => path.join(entry.parentPath, entry.name))
131
+ .sort();
132
+ }
133
+ function isPrivatePath(root, file) {
134
+ return path.relative(root, file).split(path.sep).includes(PRIVATE_DIRECTORY);
135
+ }
136
+ function createPublicOnlyHost(root, filesRead) {
137
+ const readPublicFile = (fileName) => {
138
+ const resolved = path.resolve(fileName);
139
+ if (!resolved.startsWith(root + path.sep)) {
140
+ // Nothing outside the generated tree is served, so a stray specifier
141
+ // fails loudly rather than widening the input.
142
+ return undefined;
143
+ }
144
+ if (isPrivatePath(root, resolved)) {
145
+ throw new PrismaTypesReaderError(`the types reader asked for ${path.relative(root, resolved)}, which is a 🛑 file`);
146
+ }
147
+ const text = readFileSync(resolved, "utf8");
148
+ if (text.includes(RED_BANNER) || !text.includes(GREEN_BANNER)) {
149
+ throw new PrismaTypesReaderError(`${path.relative(root, resolved)} does not carry the 🟢 banner`);
150
+ }
151
+ filesRead.add(resolved);
152
+ return text;
153
+ };
154
+ return {
155
+ getSourceFile: (fileName, languageVersionOrOptions) => {
156
+ const text = readPublicFile(fileName);
157
+ return text === undefined
158
+ ? undefined
159
+ : ts.createSourceFile(fileName, text, languageVersionOrOptions, true);
160
+ },
161
+ getDefaultLibFileName: () => "lib.d.ts",
162
+ writeFile: () => undefined,
163
+ getCurrentDirectory: () => root,
164
+ getCanonicalFileName: (fileName) => fileName,
165
+ useCaseSensitiveFileNames: () => true,
166
+ getNewLine: () => "\n",
167
+ // Existence is the file system's answer, and only for what this host would
168
+ // serve: a public `.ts` file under the root. TypeScript 6 probes
169
+ // `package.json` files under the root even with `noResolve` (plan B4); a
170
+ // host that answered `true` for any path there sent it to `readFile`, which
171
+ // threw `ENOENT`. TypeScript 5.9 never asked, so the old answer was never
172
+ // tested.
173
+ fileExists: (fileName) => {
174
+ const resolved = path.resolve(fileName);
175
+ return (resolved.startsWith(root + path.sep) &&
176
+ !isPrivatePath(root, resolved) &&
177
+ resolved.endsWith(".ts") &&
178
+ statSync(resolved, { throwIfNoEntry: false })?.isFile() === true);
179
+ },
180
+ readFile: readPublicFile,
181
+ };
182
+ }
183
+ function indexDeclarations(program, publicFiles) {
184
+ const index = new Map();
185
+ for (const file of publicFiles) {
186
+ const source = program.getSourceFile(file);
187
+ if (source === undefined) {
188
+ throw new PrismaTypesReaderError(`${file} did not enter the program`);
189
+ }
190
+ for (const statement of source.statements) {
191
+ if (ts.isTypeAliasDeclaration(statement) ||
192
+ ts.isInterfaceDeclaration(statement)) {
193
+ index.set(statement.name.text, statement);
194
+ }
195
+ }
196
+ }
197
+ return index;
198
+ }
199
+ /** The two bulk-returning write calls, in the spelling the generated type names use. */
200
+ const BULK_RETURNING_WRITES = ["CreateManyAndReturn", "UpdateManyAndReturn"];
201
+ /**
202
+ * Whether every one of the declared member sets lists `member`.
203
+ *
204
+ * **Zero sets answer `false`.** `Array.prototype.every` is vacuously true on an
205
+ * empty array, and taking that answer would advertise a scope on a client that
206
+ * declares no bulk-returning projection type at all — over-advertisement by
207
+ * arithmetic, which is C-855's failure mode arriving through a language default.
208
+ */
209
+ function admittedByEvery(sets, member) {
210
+ return sets.length > 0 && sets.every((names) => names.has(member));
211
+ }
212
+ /**
213
+ * The member names of a projection type, read off the **written** object literal.
214
+ *
215
+ * Two shapes carry one meaning at 7.10.0 and both have to be read: `<M>Select…` is
216
+ * written `GetSelect<{ … }, ExtArgs["result"]["m"]>`, so its members live in the
217
+ * first written type argument, while `<M>Include…` is written as a plain object
218
+ * type. `null` means the client declares no such type — the honest answer for a
219
+ * generator that emits no `*AndReturn` projection, and not the same as a type that
220
+ * is declared and empty (`UserIncludeCreateManyAndReturn` is literally `{}` in
221
+ * this repository, which is a measured `false` per edge rather than an absence).
222
+ */
223
+ function projectionMembers(index, name) {
224
+ const declaration = index.get(name);
225
+ if (declaration === undefined) {
226
+ return null;
227
+ }
228
+ if (ts.isInterfaceDeclaration(declaration)) {
229
+ return new Set(memberNames(declaration.members.filter(ts.isPropertySignature)));
230
+ }
231
+ const written = declaration.type;
232
+ const literal = ts.isTypeReferenceNode(written)
233
+ ? written.typeArguments?.[0]
234
+ : written;
235
+ if (literal === undefined || !ts.isTypeLiteralNode(literal)) {
236
+ throw new PrismaTypesReaderError(`${name} is declared by the 🟢 types and is not a written object type, so its projection members cannot be read`);
237
+ }
238
+ return new Set(memberNames(literal.members.filter(ts.isPropertySignature)));
239
+ }
240
+ function readBulkSurface(index, model) {
241
+ const declared = (names) => names
242
+ .map((name) => projectionMembers(index, name))
243
+ .filter((members) => members !== null);
244
+ return {
245
+ select: declared(BULK_RETURNING_WRITES.map((call) => `${model}Select${call}`)),
246
+ include: declared(BULK_RETURNING_WRITES.map((call) => `${model}Include${call}`)),
247
+ create: new Set(memberNames(membersOf(index, `${model}CreateManyInput`) ?? [])),
248
+ update: new Set([
249
+ ...memberNames(membersOf(index, `${model}UpdateManyMutationInput`) ?? []),
250
+ ...memberNames(membersOf(index, `${model}UncheckedUpdateManyInput`) ?? []),
251
+ ]),
252
+ };
253
+ }
254
+ /** The members of a named declaration, following Prisma's one `…Base` indirection. */
255
+ function membersOf(index, name) {
256
+ const declaration = index.get(name);
257
+ if (declaration === undefined) {
258
+ return null;
259
+ }
260
+ if (ts.isInterfaceDeclaration(declaration)) {
261
+ return declaration.members.filter(ts.isPropertySignature);
262
+ }
263
+ if (ts.isTypeLiteralNode(declaration.type)) {
264
+ return declaration.type.members.filter(ts.isPropertySignature);
265
+ }
266
+ // `JsonFilter` is a conditional over `JsonFilterBase`, which is where the
267
+ // operators are actually written. One documented indirection, not a search.
268
+ return membersOf(index, `${name}Base`);
269
+ }
270
+ function memberName(member) {
271
+ return ts.isIdentifier(member.name) || ts.isStringLiteral(member.name)
272
+ ? member.name.text
273
+ : member.name.getText();
274
+ }
275
+ function memberNames(members) {
276
+ return members.map(memberName);
277
+ }
278
+ /** The rightmost identifier of a type reference's name: `Prisma.StringFilter` → `StringFilter`. */
279
+ function referencedName(reference) {
280
+ return ts.isQualifiedName(reference.typeName)
281
+ ? reference.typeName.right.text
282
+ : reference.typeName.text;
283
+ }
284
+ /** The first type reference, at any depth, whose name satisfies `accept`. */
285
+ function findReference(node, accept) {
286
+ if (node === undefined) {
287
+ return null;
288
+ }
289
+ let found = null;
290
+ const visit = (current) => {
291
+ if (found !== null) {
292
+ return;
293
+ }
294
+ if (ts.isTypeReferenceNode(current) && accept(referencedName(current))) {
295
+ found = current;
296
+ return;
297
+ }
298
+ ts.forEachChild(current, visit);
299
+ };
300
+ visit(node);
301
+ return found;
302
+ }
303
+ function keysOfReferenced(index, node, accept) {
304
+ const reference = findReference(node, accept);
305
+ if (reference === null) {
306
+ return [];
307
+ }
308
+ const members = membersOf(index, referencedName(reference));
309
+ if (members === null) {
310
+ throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by the 🟢 types but declared nowhere in them`);
311
+ }
312
+ return memberNames(members);
313
+ }
314
+ /* ------------------------------------------------------------------ *
315
+ * Enums *
316
+ * ------------------------------------------------------------------ */
317
+ function readEnums(program, root) {
318
+ const source = program.getSourceFile(path.join(root, "enums.ts"));
319
+ if (source === undefined) {
320
+ throw new PrismaTypesReaderError(`enums.ts is absent from ${root}`);
321
+ }
322
+ const enums = {};
323
+ for (const statement of source.statements) {
324
+ if (!ts.isVariableStatement(statement)) {
325
+ continue;
326
+ }
327
+ for (const declaration of statement.declarationList.declarations) {
328
+ const initializer = declaration.initializer;
329
+ if (initializer === undefined ||
330
+ !ts.isAsExpression(initializer) ||
331
+ !ts.isObjectLiteralExpression(initializer.expression) ||
332
+ !ts.isIdentifier(declaration.name)) {
333
+ continue;
334
+ }
335
+ // Property order is the member order. `as const` is what makes it an
336
+ // enum projection rather than a mutable object.
337
+ enums[declaration.name.text] = initializer.expression.properties
338
+ .filter(ts.isPropertyAssignment)
339
+ .map((property) => ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)
340
+ ? property.name.text
341
+ : property.name.getText());
342
+ }
343
+ }
344
+ return enums;
345
+ }
346
+ /* ------------------------------------------------------------------ *
347
+ * Resources *
348
+ * ------------------------------------------------------------------ */
349
+ function readResources(checker, index) {
350
+ const resources = {};
351
+ for (const name of [...index.keys()].sort()) {
352
+ const model = /^\$(.+)Payload$/.exec(name)?.[1];
353
+ if (model === undefined) {
354
+ continue;
355
+ }
356
+ resources[model] = readResource(checker, index, model);
357
+ }
358
+ return resources;
359
+ }
360
+ function readResource(checker, index, model) {
361
+ const payload = membersOf(index, `$${model}Payload`);
362
+ if (payload === null) {
363
+ throw new PrismaTypesReaderError(`$${model}Payload has no written members`);
364
+ }
365
+ const declaredName = payloadName(checker, payload);
366
+ if (declaredName !== model) {
367
+ throw new PrismaTypesReaderError(`$${model}Payload's own name literal is ${declaredName}`);
368
+ }
369
+ const where = membersOf(index, `${model}WhereInput`) ?? [];
370
+ // The CHECKED create/update inputs, because the unchecked halves carry no
371
+ // relation edge at all — the foreign-key scalar stands in for it there. Both
372
+ // stages are read, separately: see `PrismaRelationSurface.createMutations`.
373
+ const create = membersOf(index, `${model}CreateInput`) ?? [];
374
+ const update = membersOf(index, `${model}UpdateInput`) ?? [];
375
+ // The UNCHECKED inputs, because they are what the delegate accepts: `data` is
376
+ // `XOR<<M>UpdateInput, <M>UncheckedUpdateInput>`, and only the unchecked half
377
+ // carries the foreign-key scalars and the generated identifiers.
378
+ const uncheckedCreate = membersOf(index, `${model}UncheckedCreateInput`) ?? [];
379
+ const uncheckedUpdate = membersOf(index, `${model}UncheckedUpdateInput`) ?? [];
380
+ const orderBy = membersOf(index, `${model}OrderByWithRelationInput`) ?? [];
381
+ const include = membersOf(index, `${model}Include`) ?? [];
382
+ const counted = new Set(memberNames(membersOf(index, `${model}CountOutputType`) ?? []));
383
+ const byName = (members) => new Map(members.map((member) => [memberName(member), member]));
384
+ const operations = readOperations(index, model);
385
+ return {
386
+ scalarFields: readScalarFields(checker, index, model, payload, byName(where), byName(uncheckedCreate), byName(uncheckedUpdate), byName(orderBy)),
387
+ relationFields: readRelationFields(checker, index, payload, byName(where), byName(create), byName(update), byName(include), byName(orderBy), counted, readBulkSurface(index, model)),
388
+ uniqueKeys: readUniqueKeys(checker, index, model),
389
+ operations,
390
+ manyWriteArguments: readManyWriteArguments(index, model, operations),
391
+ };
392
+ }
393
+ /** The payload's own `name: "M"` literal — the only authority on a relation's target. */
394
+ function payloadName(checker, payload) {
395
+ const member = payload.find((m) => memberName(m) === "name");
396
+ if (member?.type === undefined) {
397
+ throw new PrismaTypesReaderError("a payload carries no `name` member");
398
+ }
399
+ return unquote(checker.typeToString(checker.getTypeFromTypeNode(member.type)));
400
+ }
401
+ function readScalarFields(checker, index, model, payload, where, uncheckedCreate, uncheckedUpdate, orderBy) {
402
+ const tokens = readFieldRefTokens(checker, index, model);
403
+ const declared = payload.find((m) => memberName(m) === "scalars");
404
+ // `scalars` is written as `GetPayloadResult<{ … }, …>`; the first written type
405
+ // argument is the object, and that is read, not the instantiated result.
406
+ const written = declared?.type;
407
+ const literal = written !== undefined && ts.isTypeReferenceNode(written)
408
+ ? written.typeArguments?.[0]
409
+ : undefined;
410
+ if (literal === undefined || !ts.isTypeLiteralNode(literal)) {
411
+ throw new PrismaTypesReaderError(`$${model}Payload.scalars is not a written object type`);
412
+ }
413
+ const names = [
414
+ ...new Set([
415
+ ...memberNames(literal.members.filter(ts.isPropertySignature)),
416
+ ...tokens.keys(),
417
+ ]),
418
+ ];
419
+ const fields = {};
420
+ for (const name of names) {
421
+ const token = tokens.get(name);
422
+ if (token === undefined) {
423
+ throw new PrismaTypesReaderError(`${model}.${name} is a payload scalar with no ${model}FieldRefs entry`);
424
+ }
425
+ const filter = findReference(where.get(name)?.type, (n) => n.endsWith("Filter"));
426
+ const filterMembers = filter === null ? [] : (membersOf(index, referencedName(filter)) ?? []);
427
+ const order = orderBy.get(name);
428
+ fields[name] = {
429
+ token,
430
+ filters: memberNames(filterMembers),
431
+ filterOperands: Object.fromEntries(filterMembers.map((member) => [
432
+ memberName(member),
433
+ // As WRITTEN. Two engines carry `path` with different operands, and
434
+ // a name set cannot express that (T-3.1 invariant 6).
435
+ member.type?.getText() ?? "",
436
+ ])),
437
+ createOperations: readMutationOperations(index, model, "Create", name, uncheckedCreate.get(name)),
438
+ // The question mark, read from the member the line above read its
439
+ // directives from — one member, two facts, so they cannot disagree about
440
+ // whether the column is creatable at all.
441
+ createOptional: uncheckedCreate.get(name)?.questionToken !== undefined,
442
+ updateOperations: readMutationOperations(index, model, "Update", name, uncheckedUpdate.get(name)),
443
+ orderable: order !== undefined,
444
+ nullsOrderable: findReference(order?.type, (n) => n === "SortOrderInput") !== null,
445
+ };
446
+ }
447
+ return fields;
448
+ }
449
+ /**
450
+ * One mutation input entry, read as the keys of the `…Input` it references.
451
+ *
452
+ * Two reference spellings carry operations and no others do:
453
+ * `<Kind>FieldUpdateOperationsInput` (`set`, and the four arithmetic directives on
454
+ * the numeric kinds) and `<M><Stage><field>Input` (a scalar list's own `set` and
455
+ * `push`). Everything else is a plain value — `Json`'s
456
+ * `JsonNullValueInput | InputJsonValue`, and every create-stage scalar — and
457
+ * answers `[]`.
458
+ *
459
+ * `undefined` in, `null` out: the client exposes no entry for this column at this
460
+ * stage, which is the ONLY evidence of a column that cannot be written. Matching
461
+ * the two spellings by name rather than taking the first `…Input` reference is
462
+ * load-bearing: `JsonNullValueInput` is an `…Input` whose members are null
463
+ * sentinels, and reading those as operations would advertise `DbNull` as a
464
+ * mutation directive.
465
+ */
466
+ function readMutationOperations(index, model, stage, field, member) {
467
+ if (member === undefined) {
468
+ return null;
469
+ }
470
+ const listInput = `${model}${stage}${field}Input`;
471
+ const reference = findReference(member.type, (name) => name === listInput || name.endsWith("FieldUpdateOperationsInput"));
472
+ if (reference === null) {
473
+ return [];
474
+ }
475
+ const members = membersOf(index, referencedName(reference));
476
+ if (members === null) {
477
+ throw new PrismaTypesReaderError(`${referencedName(reference)} is referenced by ${model}Unchecked${stage}Input.${field} and declared nowhere in the 🟢 types`);
478
+ }
479
+ return memberNames(members);
480
+ }
481
+ /** `<M>FieldRefs` → field name to scalar token, read from the written type arguments. */
482
+ function readFieldRefTokens(checker, index, model) {
483
+ const members = membersOf(index, `${model}FieldRefs`);
484
+ if (members === null) {
485
+ throw new PrismaTypesReaderError(`${model}FieldRefs is absent`);
486
+ }
487
+ const tokens = new Map();
488
+ for (const member of members) {
489
+ const reference = findReference(member.type, (n) => n === "FieldRef");
490
+ if (reference === null) {
491
+ throw new PrismaTypesReaderError(`${model}FieldRefs.${memberName(member)} is not a FieldRef`);
492
+ }
493
+ const written = readWrittenTypeArguments(checker, reference);
494
+ const token = written[1];
495
+ if (token === undefined) {
496
+ throw new PrismaTypesReaderError(`${model}FieldRefs.${memberName(member)} carries no scalar token`);
497
+ }
498
+ tokens.set(memberName(member), unquote(token));
499
+ }
500
+ return tokens;
501
+ }
502
+ function readRelationFields(checker, index, payload, where, create, update, include, orderBy, counted, bulk) {
503
+ const objects = payload.find((m) => memberName(m) === "objects");
504
+ if (objects?.type === undefined || !ts.isTypeLiteralNode(objects.type)) {
505
+ return {};
506
+ }
507
+ const fields = {};
508
+ for (const member of objects.type.members.filter(ts.isPropertySignature)) {
509
+ const name = memberName(member);
510
+ const written = member.type;
511
+ if (written === undefined) {
512
+ continue;
513
+ }
514
+ const parts = ts.isUnionTypeNode(written) ? written.types : [written];
515
+ const many = parts.some(ts.isArrayTypeNode);
516
+ const nullable = parts.some((part) => ts.isLiteralTypeNode(part) &&
517
+ part.literal.kind === ts.SyntaxKind.NullKeyword);
518
+ const targetPayload = findReference(written, (n) => n.startsWith("$"));
519
+ if (targetPayload === null) {
520
+ throw new PrismaTypesReaderError(`a relation edge "${name}" references no payload`);
521
+ }
522
+ const targetMembers = membersOf(index, referencedName(targetPayload));
523
+ if (targetMembers === null) {
524
+ throw new PrismaTypesReaderError(`${referencedName(targetPayload)} is declared nowhere in the 🟢 types`);
525
+ }
526
+ // `<M>Include`'s entry for this edge names the edge's own argument type, and
527
+ // there are TWO spellings at 7.10.0 — measured 2026-10-02, because matching
528
+ // only the `$` one silently gave every REQUIRED to-one edge an empty argument
529
+ // list: a nullable to-one and a to-many name `<M>$<edge>Args`, while a
530
+ // required to-one names the target's `<T>DefaultArgs`. The difference is real
531
+ // and semantic — `<T>DefaultArgs` carries no `where`, so a required to-one
532
+ // genuinely admits no nested filtering of the returned edge — but
533
+ // `select`/`omit`/`include` are on both, so both have to be read.
534
+ const edge = findReference(include.get(name)?.type, (n) => n.endsWith("Args"));
535
+ const edgeArguments = edge === null
536
+ ? []
537
+ : memberNames(membersOf(index, referencedName(edge)) ?? []);
538
+ fields[name] = {
539
+ // The TARGET payload's own `name` literal, not the alias's spelling.
540
+ target: payloadName(checker, targetMembers),
541
+ many,
542
+ nullable,
543
+ filters: keysOfReferenced(index, where.get(name)?.type, (n) => n.endsWith("RelationFilter")),
544
+ // The two stages name their nested inputs differently — the create-stage
545
+ // one is `<T>CreateNested{One,Many}Without<M>Input`, which does NOT end in
546
+ // `NestedInput` — and that is why each predicate is written out rather
547
+ // than one being reused: the stage is identified by the input the client
548
+ // itself references at that stage, not by a tabulated key set.
549
+ createMutations: keysOfReferenced(index, create.get(name)?.type, (n) => CREATE_NESTED_INPUT.test(n)),
550
+ updateMutations: keysOfReferenced(index, update.get(name)?.type, (n) => n.endsWith("NestedInput")),
551
+ edgeArguments,
552
+ // Pagination is the residual of the edge's own args: what neither
553
+ // projection nor predicate already owns. A to-one edge leaves nothing.
554
+ pagination: edgeArguments.filter((argument) => !NON_PAGINATION_EDGE_ARGUMENTS.has(argument)),
555
+ orderable: orderBy.has(name),
556
+ reducers: counted.has(name) ? ["_count"] : [],
557
+ // PRESENCE in the four `*AndReturn` projection types and the two bulk
558
+ // write inputs. No foreign key is looked at and no cardinality is
559
+ // consulted: `many` sits three lines up and is deliberately not read here
560
+ // — a to-one that does not own its FK is absent from these types, and that
561
+ // is a fact about the types, not a rule this reader reproduces.
562
+ bulkSelect: admittedByEvery(bulk.select, name),
563
+ bulkInclude: admittedByEvery(bulk.include, name),
564
+ bulkReducers: admittedByEvery([...bulk.select, ...bulk.include], "_count"),
565
+ bulkCreatable: bulk.create.has(name),
566
+ bulkUpdatable: bulk.update.has(name),
567
+ };
568
+ }
569
+ return fields;
570
+ }
571
+ /**
572
+ * The create-stage nested-write input's name, as 7.10.0 writes it:
573
+ * `<T>CreateNested{One,Many}Without<M>Input`. The update-stage spellings all end
574
+ * in `NestedInput`, so the two never collide.
575
+ */
576
+ const CREATE_NESTED_INPUT = /CreateNested(?:One|Many)Without.*Input$/;
577
+ function readUniqueKeys(checker, index, model) {
578
+ const declaration = index.get(`${model}WhereUniqueInput`);
579
+ if (declaration === undefined || !ts.isTypeAliasDeclaration(declaration)) {
580
+ return [];
581
+ }
582
+ const atLeast = findReference(declaration.type, (n) => n === "AtLeast");
583
+ const keys = atLeast?.typeArguments?.[1];
584
+ if (keys === undefined) {
585
+ return [];
586
+ }
587
+ const nodes = ts.isUnionTypeNode(keys) ? keys.types : [keys];
588
+ return nodes.map((node) => unquote(checker.typeToString(checker.getTypeFromTypeNode(node))));
589
+ }
590
+ /** `<M>Delegate`'s own methods, in declaration order. */
591
+ function readOperations(index, model) {
592
+ const declaration = index.get(`${model}Delegate`);
593
+ if (declaration === undefined || !ts.isInterfaceDeclaration(declaration)) {
594
+ throw new PrismaTypesReaderError(`${model}Delegate is absent`);
595
+ }
596
+ return declaration.members
597
+ .filter(ts.isMethodSignature)
598
+ .map((member) => ts.isIdentifier(member.name) ? member.name.text : member.name.getText());
599
+ }
600
+ function readManyWriteArguments(index, model, operations) {
601
+ const arguments_ = {};
602
+ for (const operation of operations) {
603
+ if (!MANY_WRITE_OPERATION.test(operation)) {
604
+ continue;
605
+ }
606
+ const capitalised = operation[0]?.toUpperCase() + operation.slice(1);
607
+ const members = membersOf(index, `${model}${capitalised}Args`);
608
+ if (members === null) {
609
+ throw new PrismaTypesReaderError(`${model}${capitalised}Args is absent although ${model}Delegate declares ${operation}`);
610
+ }
611
+ arguments_[operation] = memberNames(members);
612
+ }
613
+ return arguments_;
614
+ }
615
+ function unquote(value) {
616
+ return value.replace(/^['"]|['"]$/g, "");
617
+ }