@vibeorm/generator 1.3.1 → 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2154 @@
1
+ // @bun
2
+ // packages/generator/src/generate.ts
3
+ import { mkdir, writeFile } from "fs/promises";
4
+ import { join } from "path";
5
+ import { buildComputedManifest, validateComputedConfig } from "@vibeorm/schema";
6
+
7
+ // packages/generator/src/emit-dts.ts
8
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
9
+
10
+ // packages/generator/src/emit-js.ts
11
+ import { internalError } from "@vibeorm/schema";
12
+
13
+ // packages/generator/src/extension-bake.ts
14
+ import {
15
+ VibeError,
16
+ buildExtensionRegistry,
17
+ extensionDeclarations,
18
+ extensionManifestEntry,
19
+ providerToDialect
20
+ } from "@vibeorm/schema";
21
+ var EMPTY_BAKED_EXTENSIONS = {
22
+ jsImports: [],
23
+ mounts: [],
24
+ manifest: [],
25
+ dtsImports: [],
26
+ delegateDts: new Map,
27
+ moduleDts: [],
28
+ clientDts: [],
29
+ empty: true
30
+ };
31
+
32
+ class ImportCollector {
33
+ byModule = new Map;
34
+ add(specs) {
35
+ for (const spec of specs ?? []) {
36
+ const names = this.byModule.get(spec.from) ?? new Set;
37
+ for (const name of spec.names)
38
+ names.add(name);
39
+ this.byModule.set(spec.from, names);
40
+ }
41
+ }
42
+ lines(params) {
43
+ return [...this.byModule.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([from, names]) => `import ${params.typeOnly ? "type " : ""}{ ${[...names].sort().join(", ")} } from ${JSON.stringify(from)};`);
44
+ }
45
+ }
46
+ function indent(lines) {
47
+ return lines.map((line) => line.length === 0 ? line : ` ${line}`);
48
+ }
49
+ function bakeExtensions(params) {
50
+ const { schema } = params;
51
+ if (params.extensions.length === 0)
52
+ return EMPTY_BAKED_EXTENSIONS;
53
+ buildExtensionRegistry({ extensions: params.extensions });
54
+ const dialect = providerToDialect({ provider: schema.datasource.provider });
55
+ const provider = schema.datasource.provider;
56
+ const sorted = [...params.extensions].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
57
+ const jsImports = new ImportCollector;
58
+ const dtsImports = new ImportCollector;
59
+ const mounts = [];
60
+ const delegateDts = new Map;
61
+ const moduleDts = [];
62
+ const clientDts = [];
63
+ const useContribution = (paramsInner) => {
64
+ const { extension, contribution, scope, model } = paramsInner;
65
+ const property = scope === "model" ? extension.name : `$${extension.name}`;
66
+ jsImports.add(contribution.imports);
67
+ dtsImports.add(contribution.dtsImports);
68
+ moduleDts.push(...contribution.dtsTypes ?? []);
69
+ mounts.push(`{ extension: ${JSON.stringify(extension.name)}, scope: ${JSON.stringify(scope)}, ${model === undefined ? "" : `model: ${JSON.stringify(model)}, `}property: ${JSON.stringify(property)}, create: ${contribution.runtimeGlue} }`);
70
+ if (scope === "model" && model !== undefined) {
71
+ const lines = delegateDts.get(model) ?? [];
72
+ lines.push(...indent(contribution.dts));
73
+ delegateDts.set(model, lines);
74
+ } else {
75
+ clientDts.push(...indent(contribution.dts));
76
+ }
77
+ };
78
+ for (const extension of sorted) {
79
+ const declarations = extensionDeclarations({ schema, name: extension.name });
80
+ const modelHook = extension.client?.model;
81
+ if (modelHook !== undefined) {
82
+ for (const model of schema.models) {
83
+ const declaration = declarations[model.name];
84
+ if (declaration === undefined)
85
+ continue;
86
+ const contribution = modelHook({
87
+ model,
88
+ schema,
89
+ dialect,
90
+ provider,
91
+ options: extension.options,
92
+ declaration,
93
+ typeNames: { payload: model.name, whereInput: `${model.name}WhereInput` }
94
+ });
95
+ if (contribution === null)
96
+ continue;
97
+ const clash = model.fields.some((field) => field.name === extension.name) || model.relations.some((relation) => relation.name === extension.name);
98
+ if (clash) {
99
+ throw new VibeError({
100
+ code: "VIBE_CONFIG",
101
+ message: `extension "${extension.name}" collides with a field/relation named "${extension.name}" on model "${model.name}" \u2014 rename one of them`,
102
+ meta: { extension: extension.name, model: model.name }
103
+ });
104
+ }
105
+ useContribution({ extension, contribution, scope: "model", model: model.name });
106
+ }
107
+ }
108
+ const rootHook = extension.client?.root;
109
+ if (rootHook !== undefined) {
110
+ const contribution = rootHook({
111
+ schema,
112
+ dialect,
113
+ provider,
114
+ options: extension.options,
115
+ declarations
116
+ });
117
+ if (contribution !== null) {
118
+ useContribution({ extension, contribution, scope: "client" });
119
+ }
120
+ }
121
+ }
122
+ return {
123
+ jsImports: jsImports.lines({ typeOnly: false }),
124
+ mounts,
125
+ manifest: sorted.map((extension) => extensionManifestEntry({ extension })),
126
+ dtsImports: dtsImports.lines({ typeOnly: true }),
127
+ delegateDts,
128
+ moduleDts,
129
+ clientDts,
130
+ empty: false
131
+ };
132
+ }
133
+
134
+ // packages/generator/src/util.ts
135
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
136
+ function pascal(params) {
137
+ const { name } = params;
138
+ if (name.length === 0)
139
+ return name;
140
+ return name.charAt(0).toUpperCase() + name.slice(1);
141
+ }
142
+ function clientNameOf(params) {
143
+ const { name } = params;
144
+ if (name.length === 0)
145
+ return name;
146
+ return name.charAt(0).toLowerCase() + name.slice(1);
147
+ }
148
+ var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
149
+ function propKey(params) {
150
+ const { name } = params;
151
+ return IDENTIFIER_RE.test(name) ? name : JSON.stringify(name);
152
+ }
153
+ function tsString(params) {
154
+ return JSON.stringify(params.value);
155
+ }
156
+ function maybeParens(params) {
157
+ const { type } = params;
158
+ return type.includes("|") ? `(${type})` : type;
159
+ }
160
+ function stableStringify(params) {
161
+ return renderValue({ value: params.value, indent: "" });
162
+ }
163
+ function renderValue(params) {
164
+ const { value, indent: indent2 } = params;
165
+ if (value === null)
166
+ return "null";
167
+ const kind = typeof value;
168
+ if (kind === "string" || kind === "number" || kind === "boolean")
169
+ return JSON.stringify(value);
170
+ if (Array.isArray(value)) {
171
+ if (value.length === 0)
172
+ return "[]";
173
+ const inner = `${indent2} `;
174
+ const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
175
+ return `[
176
+ ${items.join(`,
177
+ `)}
178
+ ${indent2}]`;
179
+ }
180
+ if (kind === "object") {
181
+ const record = value;
182
+ const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort();
183
+ if (keys.length === 0)
184
+ return "{}";
185
+ const inner = `${indent2} `;
186
+ const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
187
+ return `{
188
+ ${items.join(`,
189
+ `)}
190
+ ${indent2}}`;
191
+ }
192
+ throw new VibeError2({
193
+ code: "VIBE_INTERNAL",
194
+ message: `internal: cannot serialize a ${kind} value into the generated client`,
195
+ meta: { kind }
196
+ });
197
+ }
198
+
199
+ // packages/generator/src/emit-js.ts
200
+ var HEADER = `// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
201
+ // Regenerate with \`vibeorm generate\` after every schema change.`;
202
+ function enumEntryExports(params) {
203
+ return [...params.view.enums.map((declared) => declared.name), "enumValidators"];
204
+ }
205
+ function emitEnumsJs(params) {
206
+ const { view } = params;
207
+ const parts = [
208
+ `${HEADER}
209
+ // Lightweight entry: enum constants + boundary parsers. No runtime client, no schema module.`
210
+ ];
211
+ if (view.enums.length === 0) {
212
+ parts.push(`/** Boundary parsers grouped by enum name \u2014 this schema declares no enums. */
213
+ ` + `export const enumValidators = Object.freeze({});`);
214
+ return `${parts.join(`
215
+
216
+ `)}
217
+ `;
218
+ }
219
+ parts.push(`import { VibeError } from "@vibeorm/schema";`);
220
+ for (const declared of view.enums) {
221
+ const entries = declared.values.map((value) => ` ${value === "__proto__" ? `[${tsString({ value })}]` : tsString({ value })}: ${tsString({ value })},`).join(`
222
+ `);
223
+ parts.push(`export const ${declared.name} = Object.freeze({
224
+ ${entries}
225
+ });`);
226
+ }
227
+ parts.push(`/** Membership validator for one enum: \`values\`, \`is\`, \`parse\` (VIBE_VALIDATION on a non-member). */
228
+ ` + `function enumValidator(params) {
229
+ ` + ` const { name, values } = params;
230
+ ` + ` const members = new Set(values);
231
+ ` + ` const is = (value) => typeof value === "string" && members.has(value);
232
+ ` + ` return Object.freeze({
233
+ ` + ` values: Object.freeze([...values]),
234
+ ` + ` is,
235
+ ` + ` parse(input) {
236
+ ` + ` const { value } = input;
237
+ ` + ` if (is(value)) return value;
238
+ ` + ` const received = typeof value === "string" ? value.slice(0, 80) : value === null ? "null" : typeof value;
239
+ ` + ` throw new VibeError({
240
+ ` + ` code: "VIBE_VALIDATION",
241
+ ` + ` message: \`Invalid \${name} value \${JSON.stringify(received)} \u2014 expected one of \${values.join(", ")}\`,
242
+ ` + ` meta: { enum: name, received },
243
+ ` + ` });
244
+ ` + ` },
245
+ ` + ` });
246
+ ` + `}`);
247
+ const validatorLines = view.enums.map((declared) => ` ${declared.name === "__proto__" ? `[${tsString({ value: declared.name })}]` : declared.name}: enumValidator({ name: ${tsString({ value: declared.name })}, values: [${declared.values.map((value) => tsString({ value })).join(", ")}] }),`).join(`
248
+ `);
249
+ parts.push(`/** Boundary parsers grouped by enum name, so an enum member named \`parse\` cannot collide. */
250
+ ` + `export const enumValidators = Object.freeze({
251
+ ${validatorLines}
252
+ });`);
253
+ return `${parts.join(`
254
+
255
+ `)}
256
+ `;
257
+ }
258
+ function serializeSchema(params) {
259
+ const { schema } = params;
260
+ if (schema.rls === undefined)
261
+ return stableStringify({ value: schema });
262
+ return renderIr({ value: schema, indent: "", ordered: false });
263
+ }
264
+ function renderIr(params) {
265
+ const { value, indent: indent2, ordered } = params;
266
+ if (value === null)
267
+ return "null";
268
+ const kind = typeof value;
269
+ if (kind === "string" || kind === "number" || kind === "boolean")
270
+ return JSON.stringify(value);
271
+ if (Array.isArray(value)) {
272
+ if (value.length === 0)
273
+ return "[]";
274
+ const inner2 = `${indent2} `;
275
+ const items2 = value.map((item) => inner2 + renderIr({ value: item, indent: inner2, ordered }));
276
+ return `[
277
+ ${items2.join(`,
278
+ `)}
279
+ ${indent2}]`;
280
+ }
281
+ if (kind !== "object") {
282
+ throw internalError({ message: `internal: cannot serialize a ${kind} value into the generated client`, meta: { kind } });
283
+ }
284
+ const record = value;
285
+ const defined = Object.keys(record).filter((key) => record[key] !== undefined);
286
+ const keys = ordered ? defined : defined.sort();
287
+ if (keys.length === 0)
288
+ return "{}";
289
+ const inner = `${indent2} `;
290
+ const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderIr({ value: record[key], indent: inner, ordered: ordered || key === "rls" })}`);
291
+ return `{
292
+ ${items.join(`,
293
+ `)}
294
+ ${indent2}}`;
295
+ }
296
+ function emitJs(params) {
297
+ const { view } = params;
298
+ const baked = params.baked ?? EMPTY_BAKED_EXTENSIONS;
299
+ const computed = params.computed ?? [];
300
+ const hasComputed = computed.length > 0;
301
+ const parts = [];
302
+ parts.push(HEADER);
303
+ parts.push(`import { createClient } from "@vibeorm/runtime";`);
304
+ if (baked.jsImports.length > 0)
305
+ parts.push(baked.jsImports.join(`
306
+ `));
307
+ parts.push(`export { VibeError } from "@vibeorm/schema";`);
308
+ parts.push(`export { SKIP } from "@vibeorm/runtime";`);
309
+ parts.push(`export { ${enumEntryExports({ view }).join(", ")} } from "./enums.js";`);
310
+ parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
311
+ export const schema = ${serializeSchema({ schema: view.schema })};`);
312
+ if (baked.empty && !hasComputed) {
313
+ parts.push(`/** Build a typed client over a database adapter. */
314
+ ` + `export function VibeClient(options) {
315
+ ` + ` const { adapter, ...clientOptions } = options;
316
+ ` + ` return createClient({ schema, adapter, options: clientOptions });
317
+ ` + `}`);
318
+ return `${parts.join(`
319
+
320
+ `)}
321
+ `;
322
+ }
323
+ if (!baked.empty) {
324
+ parts.push(`/** Extension manifest \u2014 verified against the live extension list (VIBE_STALE_CLIENT). */
325
+ ` + `const extensionManifest = ${stableStringify({ value: baked.manifest })};`);
326
+ parts.push(`/** Baked extension surfaces (extensions.md D9). */
327
+ ` + `const extensionMounts = [
328
+ ${baked.mounts.map((mount) => ` ${mount},`).join(`
329
+ `)}
330
+ ];`);
331
+ }
332
+ if (hasComputed) {
333
+ parts.push(`/** Computed-field manifest \u2014 verified against the live computed config (VIBE_STALE_CLIENT). */
334
+ ` + `const computedManifest = ${stableStringify({ value: computed })};`);
335
+ }
336
+ const destructured = [
337
+ "adapter",
338
+ ...baked.empty ? [] : ["extensions"],
339
+ ...hasComputed ? ["computed"] : [],
340
+ "...clientOptions"
341
+ ].join(", ");
342
+ const createLines = [
343
+ ` schema,`,
344
+ ` adapter,`,
345
+ ` options: clientOptions,`,
346
+ ...baked.empty ? [] : [` extensions: { manifest: extensionManifest, mounts: extensionMounts, instances: extensions ?? [] },`],
347
+ ...hasComputed ? [` computed: { manifest: computedManifest, specs: computed },`] : []
348
+ ];
349
+ parts.push(`/** Build a typed client over a database adapter. */
350
+ ` + `export function VibeClient(options) {
351
+ ` + ` const { ${destructured} } = options;
352
+ ` + ` return createClient({
353
+ ${createLines.join(`
354
+ `)}
355
+ });
356
+ ` + `}`);
357
+ return `${parts.join(`
358
+
359
+ `)}
360
+ `;
361
+ }
362
+
363
+ // packages/generator/src/type-names.ts
364
+ var PAYLOAD_TYPES = {
365
+ String: "string",
366
+ Boolean: "boolean",
367
+ Int: "number",
368
+ BigInt: "bigint",
369
+ Float: "number",
370
+ Decimal: "string",
371
+ DateTime: "Date",
372
+ Json: "JsonValue",
373
+ Bytes: "Uint8Array"
374
+ };
375
+ var INPUT_TYPES = {
376
+ String: "string",
377
+ Boolean: "boolean",
378
+ Int: "number",
379
+ BigInt: "bigint | number",
380
+ Float: "number",
381
+ Decimal: "string | number",
382
+ DateTime: "Date | string | DbNow",
383
+ Json: "InputJsonValue",
384
+ Bytes: "Uint8Array"
385
+ };
386
+ var FILTER_PREFIX = {
387
+ String: "String",
388
+ Boolean: "Bool",
389
+ Int: "Int",
390
+ BigInt: "BigInt",
391
+ Float: "Float",
392
+ Decimal: "Decimal",
393
+ DateTime: "DateTime",
394
+ Json: "Json",
395
+ Bytes: "Bytes"
396
+ };
397
+ var NUMERIC_SCALARS = new Set(["Int", "BigInt", "Float", "Decimal"]);
398
+ var MINMAX_SCALARS = new Set([
399
+ "String",
400
+ "Int",
401
+ "BigInt",
402
+ "Float",
403
+ "Decimal",
404
+ "DateTime"
405
+ ]);
406
+ function payloadBaseType(params) {
407
+ const { field } = params;
408
+ if (field.jsonType !== undefined)
409
+ return field.jsonType.name;
410
+ return field.enumName ?? PAYLOAD_TYPES[field.scalar];
411
+ }
412
+ function inputBaseType(params) {
413
+ const { field } = params;
414
+ if (field.jsonType !== undefined)
415
+ return field.jsonType.name;
416
+ return field.enumName ?? INPUT_TYPES[field.scalar];
417
+ }
418
+ function filterName(params) {
419
+ const { field } = params;
420
+ const prefix = field.enumName ?? FILTER_PREFIX[field.scalar];
421
+ if (field.isList)
422
+ return `${prefix}ListFilter`;
423
+ return field.isOptional ? `${prefix}NullableFilter` : `${prefix}Filter`;
424
+ }
425
+ function updateOpsName(params) {
426
+ const { field } = params;
427
+ if (field.isList || field.scalar === null || !NUMERIC_SCALARS.has(field.scalar))
428
+ return null;
429
+ const prefix = FILTER_PREFIX[field.scalar];
430
+ return field.isOptional ? `Nullable${prefix}FieldUpdateOperationsInput` : `${prefix}FieldUpdateOperationsInput`;
431
+ }
432
+
433
+ // packages/generator/src/emit-dts.ts
434
+ function banner(params) {
435
+ return `// \u2500\u2500\u2500 ${params.title} \u2500\u2500\u2500`;
436
+ }
437
+ function objectAlias(params) {
438
+ const { name, lines } = params;
439
+ if (lines.length === 0)
440
+ return `export type ${name} = Record<string, never>;`;
441
+ return `export type ${name} = {
442
+ ${lines.join(`
443
+ `)}
444
+ };`;
445
+ }
446
+ var OPTIONAL_MEMBER = /^(\s*(?:readonly\s+)?(?:[A-Za-z_$][\w$]*|"(?:[^"\\]|\\.)*")\?:\s*)(.+);$/;
447
+ function skippableMembers(params) {
448
+ return params.block.split(`
449
+ `).map((line) => {
450
+ const match = OPTIONAL_MEMBER.exec(line);
451
+ const head = match?.[1];
452
+ const type = match?.[2];
453
+ if (head === undefined || type === undefined)
454
+ return line;
455
+ return `${head}${type.includes("=>") ? `(${type})` : type} | Skip;`;
456
+ }).join(`
457
+ `);
458
+ }
459
+ function inputAlias(params) {
460
+ const { name, lines } = params;
461
+ return objectAlias({ name, lines: lines.map((block) => skippableMembers({ block })) });
462
+ }
463
+ function orMany(params) {
464
+ const { type } = params;
465
+ return `${type} | readonly ${maybeParens({ type })}[]`;
466
+ }
467
+ function payloadFieldType(params) {
468
+ const { field } = params;
469
+ const base = payloadBaseType({ field });
470
+ if (field.isList)
471
+ return `${maybeParens({ type: base })}[]`;
472
+ return field.isOptional ? `${base} | null` : base;
473
+ }
474
+ function relationPayloadType(params) {
475
+ const { relation } = params;
476
+ if (relation.isList)
477
+ return `${relation.target}[]`;
478
+ return relation.isOptional ? `${relation.target} | null` : relation.target;
479
+ }
480
+ var PREAMBLE = `${banner({ title: "Shared scalar & helper types" })}
481
+
482
+ /** JSON as stored/returned by Json columns (decoded payload shape). */
483
+ export type JsonValue = string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[];
484
+
485
+ /** Object form accepted as Json INPUT \u2014 readonly, and undefined-valued keys are dropped on serialize. */
486
+ export type InputJsonObject = { readonly [key: string]: InputJsonValue | null | undefined };
487
+
488
+ /** Array form accepted as Json INPUT \u2014 readonly arrays are welcome (v1 lesson, LEARNINGS bug 7). */
489
+ export type InputJsonArray = ReadonlyArray<InputJsonValue | null>;
490
+
491
+ /**
492
+ * JSON as ACCEPTED for Json input positions \u2014 deliberately wider than
493
+ * JsonValue: readonly containers and toJSON-bearing values (Date) serialize
494
+ * fine, the runtime stringifies with JSON.stringify (v1 lessons, bugs 7+8).
495
+ */
496
+ export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown };
497
+
498
+ /** \`strict\` (board #20) forces case-SENSITIVE matching where the engine folds (sqlite/mysql); a no-op on postgres. */
499
+ export type QueryMode = "default" | "insensitive" | "strict";
500
+
501
+ export type SortOrder = "asc" | "desc";
502
+
503
+ export type SortOrderWithNulls = SortOrder | { readonly sort: SortOrder; readonly nulls?: "first" | "last" };
504
+
505
+ /** How relations are loaded: batched WHERE-IN queries, or a single JOIN (pg/pglite). */
506
+ export type RelationStrategy = "query" | "join";
507
+
508
+ /**
509
+ * An opaque, versioned keyset position (\`after\`) \u2014 ordering values plus a
510
+ * unique tie-breaker, minted by a previous page. It is ENCODED, not encrypted:
511
+ * whoever holds it can read the ordering values inside. It is also NOT an
512
+ * authorization credential \u2014 it selects a position, never a row, and every
513
+ * query still applies its own where/scoped/policy/RLS filters.
514
+ */
515
+ export type KeysetToken = string;
516
+
517
+ /** Per-query keyset options. */
518
+ export type KeysetOptions = {
519
+ /**
520
+ * Asserts that every DateTime ordering column in this query holds
521
+ * millisecond-exact values. Required on postgres and mysql, whose timestamps
522
+ * may be finer than a JavaScript Date; NOT safe for \`@default(now())\`
523
+ * columns on postgres, which are microsecond-precise.
524
+ */
525
+ readonly timestampPrecision?: "milliseconds";
526
+ };
527
+
528
+ /** Result of createMany/updateMany/deleteMany. */
529
+ export type BatchPayload = { readonly count: number };
530
+
531
+ /**
532
+ * What upsertMany reports. \`count\` is the rows submitted after duplicate-key
533
+ * validation \u2014 every one of them was inserted or updated, since the statement
534
+ * has no skip arm and the whole operation is atomic. \`affectedRows\` is the
535
+ * driver's own number, and only where that number IS a row count: it is null on
536
+ * mysql, whose ON DUPLICATE KEY scores an insert 1, a changed update 2 and an
537
+ * unchanged update 0.
538
+ */
539
+ export type UpsertManyPayload = { readonly count: number; readonly affectedRows: number | null };
540
+
541
+ /**
542
+ * An argument member as the RESULT machinery must read it. Absent and the
543
+ * \`SKIP\` sentinel both mean "not provided", so neither may narrow a payload:
544
+ * without this, \`{ omit?: UserOmitInput | Skip }\` would collapse the key
545
+ * helpers below to \`never\` and promise fields the query may still have dropped.
546
+ */
547
+ type Provided<T> = Exclude<T, Skip | undefined>;
548
+
549
+ /** Keys of an omit object whose value is exactly true \u2014 the scalars the result drops. */
550
+ type OmittedKeys<O> = keyof { [K in keyof O as O[K] extends true ? K : never]: 0 };
551
+
552
+ /** Keys a selection has not RULED OUT \u2014 everything except a literal false/undefined. */
553
+ type PossibleKeys<S> = keyof { [K in keyof S as S[K] extends false | undefined ? never : K]: 0 };
554
+
555
+ /**
556
+ * Keys whose flag can STILL be false or missing when the query runs \u2014 a widened
557
+ * \`boolean\`, or an optional nested selection. They are emitted OPTIONAL: the
558
+ * result type must not promise a field a run-time flag can switch off.
559
+ */
560
+ type UncertainKeys<S> = PossibleKeys<S> &
561
+ keyof { [K in keyof S as [false] extends [S[K]] ? K : [undefined] extends [S[K]] ? K : never]: 0 };
562
+
563
+ /** Keys the selection definitely turns on: a literal \`true\`, or a nested selection object. */
564
+ type ExactKeys<S> = Exclude<PossibleKeys<S>, UncertainKeys<S>>;
565
+
566
+ /** An aggregate object is absent when none of its selectors is enabled. */
567
+ type HasSelectedField<S> = [S] extends [true] ? true : [S] extends [object] ? ExactKeys<S> extends never ? false : true : false;`;
568
+ var SCALAR_FILTER_CONFIGS = [
569
+ { prefix: "String", value: "string", comparable: true, text: true },
570
+ { prefix: "Int", value: "number", comparable: true, text: false },
571
+ { prefix: "Float", value: "number", comparable: true, text: false },
572
+ { prefix: "BigInt", value: "bigint | number", comparable: true, text: false },
573
+ { prefix: "Decimal", value: "string | number", comparable: true, text: false },
574
+ { prefix: "DateTime", value: "Date | string", comparable: true, text: false, clock: true },
575
+ { prefix: "Bool", value: "boolean", comparable: false, text: false },
576
+ { prefix: "Bytes", value: "Uint8Array", comparable: false, text: false }
577
+ ];
578
+ function scalarFilterAlias(params) {
579
+ const { config, nullable } = params;
580
+ const name = nullable ? `${config.prefix}NullableFilter` : `${config.prefix}Filter`;
581
+ const value = config.value;
582
+ const point = config.clock === true ? `${value} | DbNow` : value;
583
+ const valueOrNull = nullable ? `${point} | null` : point;
584
+ const array = `readonly ${maybeParens({ type: value })}[]`;
585
+ const lines = [];
586
+ lines.push(` readonly equals?: ${valueOrNull};`);
587
+ lines.push(` readonly in?: ${array};`);
588
+ lines.push(` readonly notIn?: ${array};`);
589
+ if (config.comparable) {
590
+ lines.push(` readonly lt?: ${point};`);
591
+ lines.push(` readonly lte?: ${point};`);
592
+ lines.push(` readonly gt?: ${point};`);
593
+ lines.push(` readonly gte?: ${point};`);
594
+ }
595
+ if (config.text) {
596
+ lines.push(` readonly contains?: ${value};`);
597
+ lines.push(` readonly startsWith?: ${value};`);
598
+ lines.push(` readonly endsWith?: ${value};`);
599
+ lines.push(` readonly mode?: QueryMode;`);
600
+ }
601
+ lines.push(` readonly not?: ${value} | ${name}${nullable ? " | null" : ""};`);
602
+ return inputAlias({ name, lines });
603
+ }
604
+ function scalarListFilterAlias(params) {
605
+ const { config } = params;
606
+ const value = config.value;
607
+ const array = `readonly ${maybeParens({ type: value })}[]`;
608
+ return inputAlias({
609
+ name: `${config.prefix}ListFilter`,
610
+ lines: [
611
+ ` readonly equals?: ${array} | null;`,
612
+ ` readonly has?: ${value} | null;`,
613
+ ` readonly hasEvery?: ${array};`,
614
+ ` readonly hasSome?: ${array};`,
615
+ ` readonly isEmpty?: boolean;`
616
+ ]
617
+ });
618
+ }
619
+ var UPDATE_OPS_CONFIGS = [
620
+ { prefix: "Int", value: "number" },
621
+ { prefix: "Float", value: "number" },
622
+ { prefix: "BigInt", value: "bigint | number" },
623
+ { prefix: "Decimal", value: "string | number" }
624
+ ];
625
+ function updateOpsAlias(params) {
626
+ const { prefix, value, nullable } = params;
627
+ const name = nullable ? `Nullable${prefix}FieldUpdateOperationsInput` : `${prefix}FieldUpdateOperationsInput`;
628
+ return inputAlias({
629
+ name,
630
+ lines: [
631
+ ` readonly set?: ${nullable ? `${value} | null` : value};`,
632
+ ` readonly increment?: ${value};`,
633
+ ` readonly decrement?: ${value};`,
634
+ ` readonly multiply?: ${value};`,
635
+ ` readonly divide?: ${value};`
636
+ ]
637
+ });
638
+ }
639
+ function jsonFilterAlias(params) {
640
+ const { nullable } = params;
641
+ const document = nullable ? "InputJsonValue | null" : "InputJsonValue";
642
+ return inputAlias({
643
+ name: nullable ? "JsonNullableFilter" : "JsonFilter",
644
+ lines: [
645
+ ` readonly path?: readonly (string | number)[];`,
646
+ ` readonly mode?: "default" | "insensitive";`,
647
+ ` readonly equals?: ${document};`,
648
+ ` readonly not?: ${document};`,
649
+ ` readonly string_contains?: string;`,
650
+ ` readonly string_starts_with?: string;`,
651
+ ` readonly string_ends_with?: string;`,
652
+ ` readonly array_contains?: InputJsonValue;`,
653
+ ` readonly array_starts_with?: InputJsonValue;`,
654
+ ` readonly array_ends_with?: InputJsonValue;`
655
+ ]
656
+ });
657
+ }
658
+ function filterSection() {
659
+ const parts = [banner({ title: "Scalar filters" })];
660
+ for (const config of SCALAR_FILTER_CONFIGS) {
661
+ parts.push(scalarFilterAlias({ config, nullable: false }));
662
+ parts.push(scalarFilterAlias({ config, nullable: true }));
663
+ parts.push(scalarListFilterAlias({ config }));
664
+ }
665
+ parts.push(jsonFilterAlias({ nullable: false }));
666
+ parts.push(jsonFilterAlias({ nullable: true }));
667
+ parts.push(inputAlias({
668
+ name: "JsonListFilter",
669
+ lines: [
670
+ ` readonly equals?: readonly InputJsonValue[] | null;`,
671
+ ` readonly has?: InputJsonValue | null;`,
672
+ ` readonly hasEvery?: readonly InputJsonValue[];`,
673
+ ` readonly hasSome?: readonly InputJsonValue[];`,
674
+ ` readonly isEmpty?: boolean;`
675
+ ]
676
+ }));
677
+ parts.push(banner({ title: "Atomic update operations" }));
678
+ for (const config of UPDATE_OPS_CONFIGS) {
679
+ parts.push(updateOpsAlias({ ...config, nullable: false }));
680
+ parts.push(updateOpsAlias({ ...config, nullable: true }));
681
+ }
682
+ return parts;
683
+ }
684
+ var GENERATED_HEADER = `// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
685
+ // Regenerate with \`vibeorm generate\` after every schema change.`;
686
+ function emitEnumDeclaration(params) {
687
+ const { declared } = params;
688
+ const name = declared.name;
689
+ const valueLines = declared.values.map((value) => ` readonly ${propKey({ name: value })}: ${tsString({ value })};`);
690
+ return [
691
+ banner({ title: `Enum ${name}` }),
692
+ `export declare const ${name}: {
693
+ ${valueLines.join(`
694
+ `)}
695
+ };`,
696
+ `export type ${name} = ${declared.values.map((value) => tsString({ value })).join(" | ")};`,
697
+ `/** Boundary validator for ${name}: \`values\`, \`is\`, \`parse\` (throws VIBE_VALIDATION on a non-member). */
698
+ ` + `export type ${name}Validator = {
699
+ ` + ` readonly values: readonly ${name}[];
700
+ ` + ` is(value: unknown): value is ${name};
701
+ ` + ` parse(params: { readonly value: unknown }): ${name};
702
+ ` + `};`
703
+ ].join(`
704
+
705
+ `);
706
+ }
707
+ function emitEnumsDts(params) {
708
+ const { view } = params;
709
+ const parts = [
710
+ `${GENERATED_HEADER}
711
+ // Lightweight entry: enum constants + boundary parsers. No runtime client, no schema module.`
712
+ ];
713
+ for (const declared of view.enums)
714
+ parts.push(emitEnumDeclaration({ declared }));
715
+ parts.push(banner({ title: "Enum validators" }));
716
+ const validatorLines = view.enums.map((declared) => ` readonly ${declared.name}: ${declared.name}Validator;`);
717
+ parts.push(`/** Boundary parsers grouped by enum name, so an enum member named \`parse\` cannot collide. */
718
+ ` + (validatorLines.length === 0 ? `export declare const enumValidators: Record<string, never>;` : `export declare const enumValidators: {
719
+ ${validatorLines.join(`
720
+ `)}
721
+ };`));
722
+ return `${parts.join(`
723
+
724
+ `)}
725
+ `;
726
+ }
727
+ function emitEnum(params) {
728
+ const { declared } = params;
729
+ const name = declared.name;
730
+ const parts = [banner({ title: `Enum ${name} filters` })];
731
+ const array = `readonly ${name}[]`;
732
+ parts.push(inputAlias({
733
+ name: `${name}Filter`,
734
+ lines: [
735
+ ` readonly equals?: ${name};`,
736
+ ` readonly in?: ${array};`,
737
+ ` readonly notIn?: ${array};`,
738
+ ` readonly not?: ${name} | ${name}Filter;`
739
+ ]
740
+ }));
741
+ parts.push(inputAlias({
742
+ name: `${name}NullableFilter`,
743
+ lines: [
744
+ ` readonly equals?: ${name} | null;`,
745
+ ` readonly in?: ${array};`,
746
+ ` readonly notIn?: ${array};`,
747
+ ` readonly not?: ${name} | ${name}NullableFilter | null;`
748
+ ]
749
+ }));
750
+ parts.push(inputAlias({
751
+ name: `${name}ListFilter`,
752
+ lines: [
753
+ ` readonly equals?: ${array} | null;`,
754
+ ` readonly has?: ${name} | null;`,
755
+ ` readonly hasEvery?: ${array};`,
756
+ ` readonly hasSome?: ${array};`,
757
+ ` readonly isEmpty?: boolean;`
758
+ ]
759
+ }));
760
+ return parts.join(`
761
+
762
+ `);
763
+ }
764
+ function createFieldProp(params) {
765
+ const { field } = params;
766
+ const optional = field.isOptional || field.hasDefault || field.isUpdatedAt || field.isList;
767
+ const base = inputBaseType({ field });
768
+ const value = field.isList ? `readonly ${maybeParens({ type: base })}[]` : field.isOptional ? `${base} | null` : base;
769
+ return ` readonly ${propKey({ name: field.name })}${optional ? "?" : ""}: ${value};`;
770
+ }
771
+ function updateFieldProp(params) {
772
+ const { field } = params;
773
+ const base = inputBaseType({ field });
774
+ const key = propKey({ name: field.name });
775
+ if (field.isList) {
776
+ const element = maybeParens({ type: base });
777
+ return ` readonly ${key}?: readonly ${element}[] | { readonly set?: readonly ${element}[]; readonly push?: ${base} | readonly ${element}[] };`;
778
+ }
779
+ const ops = updateOpsName({ field });
780
+ const withNull = field.isOptional ? `${base} | null` : base;
781
+ if (ops !== null)
782
+ return ` readonly ${key}?: ${withNull} | ${ops};`;
783
+ return ` readonly ${key}?: ${withNull} | { readonly set: ${withNull} };`;
784
+ }
785
+ function nestedRefName(params) {
786
+ const { relation, verb } = params;
787
+ const suffix = relation.inverseName === null ? "" : `Without${pascal({ name: relation.inverseName })}`;
788
+ return `${relation.target}${verb}Nested${relation.isList ? "Many" : "One"}${suffix}Input`;
789
+ }
790
+ function checkedCreateLines(params) {
791
+ const { model, omitRelation } = params;
792
+ const lines = [];
793
+ for (const field of model.fields) {
794
+ if (model.fkFieldNames.has(field.name))
795
+ continue;
796
+ lines.push(createFieldProp({ field }));
797
+ }
798
+ for (const relation of model.relations) {
799
+ if (relation.name === omitRelation)
800
+ continue;
801
+ const required = !relation.isList && !relation.isOptional;
802
+ lines.push(` readonly ${propKey({ name: relation.name })}${required ? "" : "?"}: ${nestedRefName({ relation, verb: "Create" })};`);
803
+ }
804
+ return lines;
805
+ }
806
+ function uncheckedCreateLines(params) {
807
+ const { model, omitRelation } = params;
808
+ const omitted = new Set(omitRelation === undefined ? [] : model.relations.find((relation) => relation.name === omitRelation)?.fkFields ?? []);
809
+ return model.fields.filter((field) => !omitted.has(field.name)).map((field) => createFieldProp({ field }));
810
+ }
811
+ function checkedUpdateLines(params) {
812
+ const { model, omitRelation } = params;
813
+ const lines = [];
814
+ for (const field of model.fields) {
815
+ if (model.fkFieldNames.has(field.name))
816
+ continue;
817
+ lines.push(updateFieldProp({ field }));
818
+ }
819
+ for (const relation of model.relations) {
820
+ if (relation.name === omitRelation)
821
+ continue;
822
+ lines.push(` readonly ${propKey({ name: relation.name })}?: ${nestedRefName({ relation, verb: "Update" })};`);
823
+ }
824
+ return lines;
825
+ }
826
+ function uncheckedUpdateLines(params) {
827
+ const { model, omitRelation } = params;
828
+ const omitted = new Set(omitRelation === undefined ? [] : model.relations.find((relation) => relation.name === omitRelation)?.fkFields ?? []);
829
+ return model.fields.filter((field) => !omitted.has(field.name)).map((field) => updateFieldProp({ field }));
830
+ }
831
+ function register(params) {
832
+ const { registry, name, text } = params;
833
+ const existing = registry.get(name);
834
+ if (existing !== undefined) {
835
+ if (existing !== text) {
836
+ throw new VibeError3({
837
+ code: "VIBE_INTERNAL",
838
+ message: `internal: nested input type "${name}" generated with two different bodies`,
839
+ meta: { name }
840
+ });
841
+ }
842
+ return;
843
+ }
844
+ registry.set(name, text);
845
+ }
846
+ function registerNestedContext(params) {
847
+ const { registry, view, relation } = params;
848
+ const target = view.modelByName.get(relation.target);
849
+ if (target === undefined)
850
+ return;
851
+ const t = target.name;
852
+ const inverse = relation.inverseName;
853
+ const suffix = inverse === null ? "" : `Without${pascal({ name: inverse })}`;
854
+ const many = relation.isList;
855
+ const m2m = relation.type === "manyToMany";
856
+ let createUnion;
857
+ let updateUnion;
858
+ if (inverse === null) {
859
+ createUnion = `${t}CreateInput | ${t}UncheckedCreateInput`;
860
+ updateUnion = `${t}UpdateInput | ${t}UncheckedUpdateInput`;
861
+ } else {
862
+ createUnion = `${t}Create${suffix}Input | ${t}UncheckedCreate${suffix}Input`;
863
+ updateUnion = `${t}Update${suffix}Input | ${t}UncheckedUpdate${suffix}Input`;
864
+ register({
865
+ registry,
866
+ name: `${t}Create${suffix}Input`,
867
+ text: inputAlias({
868
+ name: `${t}Create${suffix}Input`,
869
+ lines: checkedCreateLines({ model: target, omitRelation: inverse })
870
+ })
871
+ });
872
+ register({
873
+ registry,
874
+ name: `${t}UncheckedCreate${suffix}Input`,
875
+ text: inputAlias({
876
+ name: `${t}UncheckedCreate${suffix}Input`,
877
+ lines: uncheckedCreateLines({ model: target, omitRelation: inverse })
878
+ })
879
+ });
880
+ register({
881
+ registry,
882
+ name: `${t}Update${suffix}Input`,
883
+ text: inputAlias({
884
+ name: `${t}Update${suffix}Input`,
885
+ lines: checkedUpdateLines({ model: target, omitRelation: inverse })
886
+ })
887
+ });
888
+ register({
889
+ registry,
890
+ name: `${t}UncheckedUpdate${suffix}Input`,
891
+ text: inputAlias({
892
+ name: `${t}UncheckedUpdate${suffix}Input`,
893
+ lines: uncheckedUpdateLines({ model: target, omitRelation: inverse })
894
+ })
895
+ });
896
+ }
897
+ const connectOrCreateName = `${t}ConnectOrCreate${suffix}Input`;
898
+ register({
899
+ registry,
900
+ name: connectOrCreateName,
901
+ text: inputAlias({
902
+ name: connectOrCreateName,
903
+ lines: [` readonly where: ${t}WhereUniqueInput;`, ` readonly create: ${createUnion};`]
904
+ })
905
+ });
906
+ const createManyData = inverse === null ? `readonly ${t}CreateManyInput[]` : `readonly ${t}UncheckedCreate${suffix}Input[]`;
907
+ if (many) {
908
+ register({
909
+ registry,
910
+ name: `${t}CreateNestedMany${suffix}Input`,
911
+ text: inputAlias({
912
+ name: `${t}CreateNestedMany${suffix}Input`,
913
+ lines: [
914
+ ` readonly create?: ${orMany({ type: createUnion })};`,
915
+ ` readonly connect?: ${orMany({ type: `${t}WhereUniqueInput` })};`,
916
+ ` readonly connectOrCreate?: ${orMany({ type: connectOrCreateName })};`,
917
+ ...m2m ? [] : [` readonly createMany?: { readonly data: ${createManyData}; readonly skipDuplicates?: boolean };`]
918
+ ]
919
+ })
920
+ });
921
+ const updateWithWhere = `${t}UpdateWithWhereUnique${suffix}Input`;
922
+ register({
923
+ registry,
924
+ name: updateWithWhere,
925
+ text: inputAlias({
926
+ name: updateWithWhere,
927
+ lines: [` readonly where: ${t}WhereUniqueInput;`, ` readonly data: ${updateUnion};`]
928
+ })
929
+ });
930
+ const updateManyWithWhere = `${t}UpdateManyWithWhere${suffix}Input`;
931
+ register({
932
+ registry,
933
+ name: updateManyWithWhere,
934
+ text: inputAlias({
935
+ name: updateManyWithWhere,
936
+ lines: [` readonly where: ${t}WhereInput;`, ` readonly data: ${t}UpdateManyInput;`]
937
+ })
938
+ });
939
+ const upsertWithWhere = `${t}UpsertWithWhereUnique${suffix}Input`;
940
+ register({
941
+ registry,
942
+ name: upsertWithWhere,
943
+ text: inputAlias({
944
+ name: upsertWithWhere,
945
+ lines: [
946
+ ` readonly where: ${t}WhereUniqueInput;`,
947
+ ` readonly create: ${createUnion};`,
948
+ ` readonly update: ${updateUnion};`
949
+ ]
950
+ })
951
+ });
952
+ register({
953
+ registry,
954
+ name: `${t}UpdateNestedMany${suffix}Input`,
955
+ text: inputAlias({
956
+ name: `${t}UpdateNestedMany${suffix}Input`,
957
+ lines: [
958
+ ` readonly create?: ${orMany({ type: createUnion })};`,
959
+ ` readonly connect?: ${orMany({ type: `${t}WhereUniqueInput` })};`,
960
+ ` readonly connectOrCreate?: ${orMany({ type: connectOrCreateName })};`,
961
+ ...m2m ? [] : [` readonly createMany?: { readonly data: ${createManyData}; readonly skipDuplicates?: boolean };`],
962
+ ` readonly set?: ${orMany({ type: `${t}WhereUniqueInput` })};`,
963
+ ` readonly disconnect?: ${orMany({ type: `${t}WhereUniqueInput` })};`,
964
+ ` readonly delete?: ${orMany({ type: `${t}WhereUniqueInput` })};`,
965
+ ` readonly update?: ${orMany({ type: updateWithWhere })};`,
966
+ ` readonly updateMany?: ${orMany({ type: updateManyWithWhere })};`,
967
+ ` readonly deleteMany?: ${orMany({ type: `${t}WhereInput` })};`,
968
+ ` readonly upsert?: ${orMany({ type: upsertWithWhere })};`
969
+ ]
970
+ })
971
+ });
972
+ } else {
973
+ const allowDetach = inverse === null || relation.isOptional;
974
+ register({
975
+ registry,
976
+ name: `${t}CreateNestedOne${suffix}Input`,
977
+ text: inputAlias({
978
+ name: `${t}CreateNestedOne${suffix}Input`,
979
+ lines: [
980
+ ` readonly create?: ${createUnion};`,
981
+ ` readonly connect?: ${t}WhereUniqueInput;`,
982
+ ` readonly connectOrCreate?: ${connectOrCreateName};`
983
+ ]
984
+ })
985
+ });
986
+ register({
987
+ registry,
988
+ name: `${t}UpdateNestedOne${suffix}Input`,
989
+ text: inputAlias({
990
+ name: `${t}UpdateNestedOne${suffix}Input`,
991
+ lines: [
992
+ ` readonly create?: ${createUnion};`,
993
+ ` readonly connect?: ${t}WhereUniqueInput;`,
994
+ ` readonly connectOrCreate?: ${connectOrCreateName};`,
995
+ ` readonly update?: ${updateUnion};`,
996
+ ` readonly upsert?: { readonly create: ${createUnion}; readonly update: ${updateUnion} };`,
997
+ ...allowDetach ? [` readonly disconnect?: boolean;`, ` readonly delete?: boolean;`] : []
998
+ ]
999
+ })
1000
+ });
1001
+ }
1002
+ }
1003
+ function emitModel(params) {
1004
+ const { model, view, registry } = params;
1005
+ const computedEntries = params.computed ?? [];
1006
+ const n = model.name;
1007
+ const hasRelations = model.relations.length > 0;
1008
+ const parts = [banner({ title: `Model ${n}` })];
1009
+ for (const relation of model.relations) {
1010
+ registerNestedContext({ registry, view, relation });
1011
+ }
1012
+ parts.push(`export type ${n} = {
1013
+ ${[
1014
+ ...model.fields.map((field) => ` ${propKey({ name: field.name })}: ${payloadFieldType({ field })};`),
1015
+ ...computedEntries.map((entry) => ` ${propKey({ name: entry.field })}: ${entry.type};`)
1016
+ ].join(`
1017
+ `)}
1018
+ };`);
1019
+ parts.push(`export type ${n}ScalarFieldEnum = ${model.fields.map((field) => tsString({ value: field.name })).join(" | ")};`);
1020
+ if (computedEntries.length > 0) {
1021
+ parts.push(`export type ${n}SelectableFieldEnum = ${n}ScalarFieldEnum | ${computedEntries.map((entry) => tsString({ value: entry.field })).join(" | ")};`);
1022
+ }
1023
+ const selectableEnum = computedEntries.length > 0 ? `${n}SelectableFieldEnum` : `${n}ScalarFieldEnum`;
1024
+ if (hasRelations) {
1025
+ parts.push(`export type ${n}Relations = {
1026
+ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${relationPayloadType({ relation })};`).join(`
1027
+ `)}
1028
+ };`);
1029
+ }
1030
+ const listRelations = model.relations.filter((relation) => relation.isList);
1031
+ const hasCounts = listRelations.length > 0;
1032
+ const countLine = ` readonly _count?: boolean | { readonly select: ${n}CountSelect };`;
1033
+ const relationSelectValue = (relation) => `boolean | ${relation.target}${relation.isList ? "FindManyArgs" : "Args"}`;
1034
+ if (hasCounts) {
1035
+ parts.push(inputAlias({
1036
+ name: `${n}CountSelect`,
1037
+ lines: listRelations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: boolean | { readonly where?: ${relation.target}WhereInput };`)
1038
+ }));
1039
+ }
1040
+ parts.push(inputAlias({
1041
+ name: `${n}Select`,
1042
+ lines: [
1043
+ ...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`),
1044
+ ...computedEntries.map((entry) => ` readonly ${propKey({ name: entry.field })}?: boolean;`),
1045
+ ...model.relations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: ${relationSelectValue(relation)};`),
1046
+ ...hasCounts ? [countLine] : []
1047
+ ]
1048
+ }));
1049
+ parts.push(inputAlias({
1050
+ name: `${n}Include`,
1051
+ lines: [
1052
+ ...model.relations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: ${relationSelectValue(relation)};`),
1053
+ ...hasCounts ? [countLine] : []
1054
+ ]
1055
+ }));
1056
+ parts.push(inputAlias({
1057
+ name: `${n}OmitInput`,
1058
+ lines: [
1059
+ ...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`),
1060
+ ...computedEntries.map((entry) => ` readonly ${propKey({ name: entry.field })}?: boolean;`)
1061
+ ]
1062
+ }));
1063
+ parts.push(inputAlias({
1064
+ name: `${n}Args`,
1065
+ lines: [
1066
+ ` readonly select?: ${n}Select;`,
1067
+ ` readonly include?: ${n}Include;`,
1068
+ ` readonly omit?: ${n}OmitInput;`,
1069
+ ` readonly relationStrategy?: RelationStrategy;`
1070
+ ]
1071
+ }));
1072
+ parts.push(inputAlias({
1073
+ name: `${n}ListRelationFilter`,
1074
+ lines: [
1075
+ ` readonly some?: ${n}WhereInput;`,
1076
+ ` readonly every?: ${n}WhereInput;`,
1077
+ ` readonly none?: ${n}WhereInput;`
1078
+ ]
1079
+ }));
1080
+ parts.push(inputAlias({
1081
+ name: `${n}RelationFilter`,
1082
+ lines: [` readonly is?: ${n}WhereInput | null;`, ` readonly isNot?: ${n}WhereInput | null;`]
1083
+ }));
1084
+ const whereLines = [
1085
+ ` readonly AND?: ${orMany({ type: `${n}WhereInput` })};`,
1086
+ ` readonly OR?: readonly ${n}WhereInput[];`,
1087
+ ` readonly NOT?: ${orMany({ type: `${n}WhereInput` })};`
1088
+ ];
1089
+ for (const field of model.fields) {
1090
+ const key = propKey({ name: field.name });
1091
+ if (field.isList || field.scalar === "Json") {
1092
+ whereLines.push(` readonly ${key}?: ${filterName({ field })};`);
1093
+ continue;
1094
+ }
1095
+ const nullable = field.isOptional ? " | null" : "";
1096
+ whereLines.push(` readonly ${key}?: ${inputBaseType({ field })} | ${filterName({ field })}${nullable};`);
1097
+ }
1098
+ for (const relation of model.relations) {
1099
+ const key = propKey({ name: relation.name });
1100
+ if (relation.isList) {
1101
+ whereLines.push(` readonly ${key}?: ${relation.target}ListRelationFilter;`);
1102
+ } else {
1103
+ whereLines.push(` readonly ${key}?: ${relation.target}RelationFilter | ${relation.target}WhereInput${relation.isOptional ? " | null" : ""};`);
1104
+ }
1105
+ }
1106
+ parts.push(inputAlias({ name: `${n}WhereInput`, lines: whereLines }));
1107
+ if (model.uniques.length === 0) {
1108
+ parts.push(`export type ${n}WhereUniqueInput = never;`);
1109
+ } else {
1110
+ const members = model.uniques.map((member) => {
1111
+ const first = member.fields[0];
1112
+ if (member.fields.length === 1 && first !== undefined) {
1113
+ return `{ readonly ${propKey({ name: first.name })}: ${inputBaseType({ field: first })}${member.nullsNotDistinct && first.isOptional ? " | null" : ""} }`;
1114
+ }
1115
+ const inner = member.fields.map((field) => `readonly ${propKey({ name: field.name })}: ${inputBaseType({ field })}${member.nullsNotDistinct && field.isOptional ? " | null" : ""}`).join("; ");
1116
+ return `{ readonly ${propKey({ name: member.key })}: { ${inner} } }`;
1117
+ });
1118
+ parts.push(`export type ${n}WhereUniqueInput =
1119
+ | ${members.join(`
1120
+ | `)};`);
1121
+ }
1122
+ parts.push(inputAlias({
1123
+ name: `${n}OrderByInput`,
1124
+ lines: model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: ${field.isOptional ? "SortOrderWithNulls" : "SortOrder"};`)
1125
+ }));
1126
+ parts.push(inputAlias({ name: `${n}CreateInput`, lines: checkedCreateLines({ model }) }));
1127
+ parts.push(inputAlias({ name: `${n}UncheckedCreateInput`, lines: uncheckedCreateLines({ model }) }));
1128
+ parts.push(`export type ${n}CreateManyInput = ${n}UncheckedCreateInput;`);
1129
+ parts.push(inputAlias({ name: `${n}UpdateInput`, lines: checkedUpdateLines({ model }) }));
1130
+ parts.push(inputAlias({ name: `${n}UncheckedUpdateInput`, lines: uncheckedUpdateLines({ model }) }));
1131
+ parts.push(`export type ${n}UpdateManyInput = ${n}UncheckedUpdateInput;`);
1132
+ const numericFields = model.fields.filter((field) => !field.isList && field.scalar !== null && NUMERIC_SCALARS.has(field.scalar));
1133
+ const minmaxFields = model.fields.filter((field) => !field.isList && field.scalar !== null && MINMAX_SCALARS.has(field.scalar));
1134
+ parts.push(inputAlias({
1135
+ name: `${n}CountAggregateInput`,
1136
+ lines: [
1137
+ ...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`),
1138
+ ` readonly _all?: true;`
1139
+ ]
1140
+ }));
1141
+ const aggregateOpProps = [` readonly _count?: true | ${n}CountAggregateInput;`];
1142
+ if (numericFields.length > 0) {
1143
+ parts.push(inputAlias({
1144
+ name: `${n}AvgAggregateInput`,
1145
+ lines: numericFields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
1146
+ }));
1147
+ parts.push(`export type ${n}SumAggregateInput = ${n}AvgAggregateInput;`);
1148
+ aggregateOpProps.push(` readonly _avg?: ${n}AvgAggregateInput;`);
1149
+ aggregateOpProps.push(` readonly _sum?: ${n}SumAggregateInput;`);
1150
+ }
1151
+ if (minmaxFields.length > 0) {
1152
+ parts.push(inputAlias({
1153
+ name: `${n}MinAggregateInput`,
1154
+ lines: minmaxFields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
1155
+ }));
1156
+ parts.push(`export type ${n}MaxAggregateInput = ${n}MinAggregateInput;`);
1157
+ aggregateOpProps.push(` readonly _min?: ${n}MinAggregateInput;`);
1158
+ aggregateOpProps.push(` readonly _max?: ${n}MaxAggregateInput;`);
1159
+ }
1160
+ parts.push(inputAlias({
1161
+ name: `${n}FindManyArgs`,
1162
+ lines: [
1163
+ ` readonly where?: ${n}WhereInput;`,
1164
+ ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
1165
+ ` readonly select?: ${n}Select;`,
1166
+ ` readonly include?: ${n}Include;`,
1167
+ ` readonly omit?: ${n}OmitInput;`,
1168
+ ` readonly cursor?: ${n}WhereUniqueInput;`,
1169
+ ` readonly after?: KeysetToken;`,
1170
+ ` readonly keyset?: KeysetOptions;`,
1171
+ ` readonly take?: number;`,
1172
+ ` readonly skip?: number;`,
1173
+ ` readonly distinct?: ${orMany({ type: `${n}ScalarFieldEnum` })};`,
1174
+ ` readonly relationStrategy?: RelationStrategy;`,
1175
+ ` readonly lock?: "update";`
1176
+ ]
1177
+ }));
1178
+ parts.push(`export type ${n}FindFirstArgs = ${n}FindManyArgs;`);
1179
+ parts.push(inputAlias({
1180
+ name: `${n}FindUniqueArgs`,
1181
+ lines: [
1182
+ ` readonly where: ${n}WhereUniqueInput;`,
1183
+ ` readonly select?: ${n}Select;`,
1184
+ ` readonly include?: ${n}Include;`,
1185
+ ` readonly omit?: ${n}OmitInput;`,
1186
+ ` readonly relationStrategy?: RelationStrategy;`,
1187
+ ` readonly lock?: "update";`
1188
+ ]
1189
+ }));
1190
+ parts.push(inputAlias({
1191
+ name: `${n}CreateArgs`,
1192
+ lines: [
1193
+ ` readonly data: ${n}CreateInput | ${n}UncheckedCreateInput;`,
1194
+ ` readonly select?: ${n}Select;`,
1195
+ ` readonly include?: ${n}Include;`,
1196
+ ` readonly omit?: ${n}OmitInput;`
1197
+ ]
1198
+ }));
1199
+ parts.push(`export type ${n}ConflictTarget = readonly ${n}ScalarFieldEnum[] | { readonly constraint: string };`);
1200
+ parts.push(inputAlias({
1201
+ name: `${n}CreateOnConflictInput`,
1202
+ lines: [
1203
+ ` readonly action: "nothing";`,
1204
+ ` readonly target?: ${n}ConflictTarget;`,
1205
+ ` readonly where?: string;`
1206
+ ]
1207
+ }));
1208
+ parts.push(inputAlias({
1209
+ name: `${n}CreateOrSkipArgs`,
1210
+ lines: [
1211
+ ` readonly data: ${n}CreateInput | ${n}UncheckedCreateInput;`,
1212
+ ` readonly onConflict: ${n}CreateOnConflictInput;`,
1213
+ ` readonly select?: ${n}Select;`,
1214
+ ` readonly include?: ${n}Include;`,
1215
+ ` readonly omit?: ${n}OmitInput;`
1216
+ ]
1217
+ }));
1218
+ parts.push(inputAlias({
1219
+ name: `${n}CreateManyArgs`,
1220
+ lines: [
1221
+ ` readonly data: ${orMany({ type: `${n}CreateManyInput` })};`,
1222
+ ` readonly skipDuplicates?: boolean | { readonly target?: ${n}ConflictTarget; readonly where?: string };`
1223
+ ]
1224
+ }));
1225
+ parts.push(inputAlias({
1226
+ name: `${n}CreateManyAndReturnArgs`,
1227
+ lines: [
1228
+ ` readonly data: ${orMany({ type: `${n}CreateManyInput` })};`,
1229
+ ` readonly skipDuplicates?: boolean | { readonly target?: ${n}ConflictTarget; readonly where?: string };`,
1230
+ ` readonly select?: ${n}Select;`,
1231
+ ` readonly omit?: ${n}OmitInput;`
1232
+ ]
1233
+ }));
1234
+ parts.push(inputAlias({
1235
+ name: `${n}UpsertManyArgs`,
1236
+ lines: [
1237
+ ` readonly data: readonly ${n}CreateManyInput[];`,
1238
+ ` readonly conflictTarget: ${n}ConflictTarget;`,
1239
+ ` readonly conflictWhere?: string;`,
1240
+ ` readonly update: readonly ${n}ScalarFieldEnum[];`,
1241
+ ` readonly skipUpdatedAt?: boolean;`,
1242
+ ` readonly allowAnyUniqueConflict?: boolean;`
1243
+ ]
1244
+ }));
1245
+ parts.push(inputAlias({
1246
+ name: `${n}UpdateArgs`,
1247
+ lines: [
1248
+ ` readonly where: ${n}UpdateWhereInput;`,
1249
+ ` readonly data: ${n}UpdateInput | ${n}UncheckedUpdateInput;`,
1250
+ ` readonly select?: ${n}Select;`,
1251
+ ` readonly include?: ${n}Include;`,
1252
+ ` readonly omit?: ${n}OmitInput;`,
1253
+ ` readonly skipUpdatedAt?: boolean;`
1254
+ ]
1255
+ }));
1256
+ parts.push(`/** update()'s where: a concrete unique selector AND any additional predicates (optimistic-concurrency guards such as \`{ id, version }\`). */
1257
+ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1258
+ parts.push(inputAlias({
1259
+ name: `${n}UpdateManyArgs`,
1260
+ lines: [
1261
+ ` readonly where?: ${n}WhereInput;`,
1262
+ ` readonly data: ${n}UpdateManyInput;`,
1263
+ ` readonly skipUpdatedAt?: boolean;`
1264
+ ]
1265
+ }));
1266
+ parts.push(inputAlias({
1267
+ name: `${n}UpdateManyAndReturnArgs`,
1268
+ lines: [
1269
+ ` readonly include?: never;`,
1270
+ ` readonly where?: ${n}WhereInput;`,
1271
+ ` readonly data: ${n}UpdateManyInput;`,
1272
+ ` readonly select?: ${n}Select;`,
1273
+ ` readonly omit?: ${n}OmitInput;`,
1274
+ ` readonly skipUpdatedAt?: boolean;`
1275
+ ]
1276
+ }));
1277
+ parts.push(inputAlias({
1278
+ name: `${n}UpsertArgs`,
1279
+ lines: [
1280
+ ` readonly where: ${n}WhereUniqueInput;`,
1281
+ ` readonly create: ${n}CreateInput | ${n}UncheckedCreateInput;`,
1282
+ ` readonly update: ${n}UpdateInput | ${n}UncheckedUpdateInput;`,
1283
+ ` readonly onConflict?: { readonly constraint: string };`,
1284
+ ` readonly select?: ${n}Select;`,
1285
+ ` readonly include?: ${n}Include;`,
1286
+ ` readonly omit?: ${n}OmitInput;`
1287
+ ]
1288
+ }));
1289
+ parts.push(inputAlias({
1290
+ name: `${n}DeleteArgs`,
1291
+ lines: [
1292
+ ` readonly where: ${n}WhereUniqueInput;`,
1293
+ ` readonly select?: ${n}Select;`,
1294
+ ` readonly include?: ${n}Include;`,
1295
+ ` readonly omit?: ${n}OmitInput;`
1296
+ ]
1297
+ }));
1298
+ parts.push(inputAlias({ name: `${n}DeleteManyArgs`, lines: [` readonly where?: ${n}WhereInput;`] }));
1299
+ parts.push(inputAlias({
1300
+ name: `${n}CountArgs`,
1301
+ lines: [
1302
+ ` readonly select?: ${n}CountAggregateInput;`,
1303
+ ` readonly where?: ${n}WhereInput;`,
1304
+ ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
1305
+ ` readonly cursor?: ${n}WhereUniqueInput;`,
1306
+ ` readonly take?: number;`,
1307
+ ` readonly skip?: number;`
1308
+ ]
1309
+ }));
1310
+ parts.push(inputAlias({
1311
+ name: `${n}AggregateArgs`,
1312
+ lines: [
1313
+ ` readonly where?: ${n}WhereInput;`,
1314
+ ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
1315
+ ` readonly cursor?: ${n}WhereUniqueInput;`,
1316
+ ` readonly take?: number;`,
1317
+ ` readonly skip?: number;`,
1318
+ ...aggregateOpProps
1319
+ ]
1320
+ }));
1321
+ parts.push(inputAlias({
1322
+ name: `${n}GroupByArgs`,
1323
+ lines: [
1324
+ ` readonly by: readonly ${n}ScalarFieldEnum[];`,
1325
+ ` readonly where?: ${n}WhereInput;`,
1326
+ ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
1327
+ ` readonly having?: ${n}WhereInput;`,
1328
+ ` readonly take?: number;`,
1329
+ ` readonly skip?: number;`,
1330
+ ...aggregateOpProps
1331
+ ]
1332
+ }));
1333
+ const exactPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : K`;
1334
+ const uncertainPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : never`;
1335
+ parts.push(`type ${n}OmitBase<A> = A extends { readonly omit: infer O extends object }
1336
+ ` + ` ? Omit<${n}, PossibleKeys<O> & keyof ${n}> & { [K in UncertainKeys<O> & keyof ${n}]?: ${n}[K] }
1337
+ ` + ` : "omit" extends keyof A ? Omit<${n}, PossibleKeys<Provided<A["omit"]>> & keyof ${n}> & Partial<Pick<${n}, PossibleKeys<Provided<A["omit"]>> & keyof ${n}>> : ${n};`);
1338
+ if (hasCounts) {
1339
+ const listUnion = listRelations.map((relation) => tsString({ value: relation.name })).join(" | ");
1340
+ const allCounts = listRelations.map((relation) => `${propKey({ name: relation.name })}: number`).join("; ");
1341
+ parts.push(`type ${n}CountResult<C> = C extends { readonly select: infer CS extends object }
1342
+ ` + ` ? { [K in keyof CS & (${listUnion}) as ${exactPart("CS")}]: number } &
1343
+ ` + ` { [K in keyof CS & (${listUnion}) as ${uncertainPart("CS")}]?: number }
1344
+ ` + ` : { ${allCounts} };`);
1345
+ parts.push(`type ${n}CountPayload<S> = S extends { readonly _count: infer C extends true | object } ? { _count: ${n}CountResult<C> }
1346
+ ` + ` : S extends { readonly _count: false | undefined } ? unknown
1347
+ ` + ` : S extends { readonly _count?: infer C extends boolean | object } ? { _count?: ${n}CountResult<C> } : unknown;`);
1348
+ }
1349
+ if (hasRelations) {
1350
+ const relationCases = model.relations.map((relation) => {
1351
+ const nested = `${relation.target}GetPayload<A>`;
1352
+ const guard = `A extends object`;
1353
+ let expression;
1354
+ if (relation.isList) {
1355
+ expression = `${guard} ? ${nested}[] : ${relation.target}[]`;
1356
+ } else if (relation.isOptional) {
1357
+ expression = `(${guard} ? ${nested} : ${relation.target}) | null`;
1358
+ } else {
1359
+ expression = `${guard} ? ${nested} : ${relation.target}`;
1360
+ }
1361
+ return ` ${propKey({ name: relation.name })}: ${expression};`;
1362
+ }).join(`
1363
+ `);
1364
+ parts.push(`type ${n}RelationPayload<K extends keyof ${n}Relations, A> = {
1365
+ ${relationCases}
1366
+ }[K];`);
1367
+ parts.push(`type ${n}IncludePayload<A> = A extends { readonly include: infer I extends object }
1368
+ ` + ` ? { [K in keyof I & keyof ${n}Relations as ${exactPart("I")}]: ${n}RelationPayload<K, I[K]> } &
1369
+ ` + ` { [K in keyof I & keyof ${n}Relations as ${uncertainPart("I")}]?: ${n}RelationPayload<K, I[K]> }${hasCounts ? ` &
1370
+ ${n}CountPayload<I>` : ""}
1371
+ ` + ` : "include" extends keyof A ? Partial<${n}IncludePayload<{ include: Provided<A["include"]> }>> : unknown;`);
1372
+ parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1373
+ ` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
1374
+ ` + ` { [K in keyof S & ${selectableEnum} as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] } &
1375
+ ` + ` { [K in keyof S & keyof ${n}Relations as ${exactPart("S")}]: ${n}RelationPayload<K, S[K]> } &
1376
+ ` + ` { [K in keyof S & keyof ${n}Relations as ${uncertainPart("S")}]?: ${n}RelationPayload<K, S[K]> }${hasCounts ? ` &
1377
+ ${n}CountPayload<S>` : ""}
1378
+ ` + ` : "select" extends keyof A ? Partial<${n}>
1379
+ ` + ` : ${n}OmitBase<A> & ${n}IncludePayload<A>;`);
1380
+ } else {
1381
+ parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1382
+ ` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
1383
+ ` + ` { [K in keyof S & ${selectableEnum} as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] }
1384
+ ` + ` : "select" extends keyof A ? Partial<${n}> : ${n}OmitBase<A>;`);
1385
+ }
1386
+ const aggregateFields = (fields, value) => `{ ${fields.map((field) => `${propKey({ name: field.name })}: ${value(field)}`).join("; ")} }`;
1387
+ const countValues = aggregateFields(model.fields, () => "number").replace(" }", "; _all: number }");
1388
+ parts.push(`type ${n}AggregateValues = {
1389
+ ` + ` _count: ${countValues};
1390
+ ` + ` _avg: ${aggregateFields(numericFields, () => "number | null")};
1391
+ ` + ` _sum: ${aggregateFields(numericFields, (field) => `${n}[${tsString({ value: field.name })}] | null`)};
1392
+ ` + ` _min: ${aggregateFields(minmaxFields, (field) => `${n}[${tsString({ value: field.name })}] | null`)};
1393
+ ` + ` _max: ${aggregateFields(minmaxFields, (field) => `${n}[${tsString({ value: field.name })}] | null`)};
1394
+ };`);
1395
+ parts.push(`type ${n}AggregateSelection<V, S> = S extends true ? number : S extends object
1396
+ ` + ` ? { [K in keyof S & keyof V as ${exactPart("S")}]: V[K] } &
1397
+ ` + ` { [K in keyof S & keyof V as ${uncertainPart("S")}]?: V[K] } : never;`);
1398
+ parts.push(`type ${n}CountOutput<A> = A extends { readonly select: infer S extends object }
1399
+ ` + ` ? ${n}AggregateSelection<${n}AggregateValues["_count"], S>
1400
+ ` + ` : "select" extends keyof A ? number | ${n}AggregateSelection<${n}AggregateValues["_count"], Provided<A["select"]>> : number;`);
1401
+ parts.push(`export type ${n}AggregateResult<A> =
1402
+ ` + ` { [K in keyof A & keyof ${n}AggregateValues as HasSelectedField<A[K]> extends true ? K : never]: ${n}AggregateSelection<${n}AggregateValues[K], A[K]> } &
1403
+ ` + ` { [K in keyof A & keyof ${n}AggregateValues as HasSelectedField<A[K]> extends true ? never : K]?: ${n}AggregateSelection<${n}AggregateValues[K], Provided<A[K]>> };`);
1404
+ parts.push(`export type ${n}GroupByResult<A extends ${n}GroupByArgs> =
1405
+ ` + ` (number extends A["by"]["length"] ? Partial<Pick<${n}, A["by"][number]>> : Pick<${n}, A["by"][number]>) & ${n}AggregateResult<A>;`);
1406
+ const readMethods = [
1407
+ ` findMany<A extends ${n}FindManyArgs = {}>(args?: A): Promise<${n}GetPayload<A>[]>;`,
1408
+ ` findFirst<A extends ${n}FindFirstArgs = {}>(args?: A): Promise<${n}GetPayload<A> | null>;`,
1409
+ ` findFirstOrThrow<A extends ${n}FindFirstArgs = {}>(args?: A): Promise<${n}GetPayload<A>>;`,
1410
+ ` findUnique<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A> | null>;`,
1411
+ ` findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A>>;`
1412
+ ];
1413
+ const writeMethods = [
1414
+ ` create<A extends ${n}CreateOrSkipArgs>(args: A): Promise<${n}GetPayload<A> | null>;`,
1415
+ ` create<A extends ${n}CreateArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1416
+ ` createMany(args: ${n}CreateManyArgs): Promise<BatchPayload>;`,
1417
+ ` createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
1418
+ ` update<A extends ${n}UpdateArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1419
+ ` updateMany(args: ${n}UpdateManyArgs): Promise<BatchPayload>;`,
1420
+ ` updateManyAndReturn<A extends ${n}UpdateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
1421
+ ` upsert<A extends ${n}UpsertArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1422
+ ` upsertMany(args: ${n}UpsertManyArgs): Promise<UpsertManyPayload>;`,
1423
+ ` delete<A extends ${n}DeleteArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1424
+ ` deleteMany(args?: ${n}DeleteManyArgs): Promise<BatchPayload>;`
1425
+ ];
1426
+ const aggregateMethods = [
1427
+ ` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;`,
1428
+ ` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;`,
1429
+ ` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;`
1430
+ ];
1431
+ const delegateMethods = model.isView ? [...readMethods, ...aggregateMethods] : [...readMethods, ...writeMethods, ...aggregateMethods];
1432
+ delegateMethods.push(...params.extensionLines ?? []);
1433
+ parts.push(`export type ${n}Delegate = {
1434
+ ${delegateMethods.join(`
1435
+ `)}
1436
+ };`);
1437
+ return parts.join(`
1438
+
1439
+ `);
1440
+ }
1441
+ function emitViewTypesForModel(params) {
1442
+ const { model, view } = params;
1443
+ const n = model.name;
1444
+ const hasRelations = model.relations.length > 0;
1445
+ const parts = [];
1446
+ parts.push(objectAlias({
1447
+ name: `${n}ViewFields`,
1448
+ lines: model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
1449
+ }));
1450
+ parts.push(objectAlias({
1451
+ name: `${n}ViewSelect`,
1452
+ lines: model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`)
1453
+ }));
1454
+ parts.push(`type ${n}ViewScalars<F> = { [K in keyof F & ${n}ScalarFieldEnum]: ${n}[K & keyof ${n}] };`);
1455
+ if (hasRelations) {
1456
+ parts.push(objectAlias({
1457
+ name: `${n}ViewInclude`,
1458
+ lines: model.relations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: boolean | ${relation.target}ViewNestedArgs;`)
1459
+ }));
1460
+ }
1461
+ parts.push(objectAlias({
1462
+ name: `${n}ViewNestedArgs`,
1463
+ lines: [
1464
+ ` readonly where?: ${n}WhereInput;`,
1465
+ ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
1466
+ ` readonly take?: number;`,
1467
+ ` readonly skip?: number;`,
1468
+ ` readonly select?: ${n}ViewSelect;`,
1469
+ ` readonly reveal?: ${n}ViewSelect;`,
1470
+ ...hasRelations ? [` readonly include?: ${n}ViewInclude;`] : []
1471
+ ]
1472
+ }));
1473
+ parts.push(objectAlias({
1474
+ name: `${n}ViewFindManyArgs`,
1475
+ lines: [
1476
+ ` readonly where?: ${n}WhereInput;`,
1477
+ ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
1478
+ ` readonly select?: ${n}ViewSelect;`,
1479
+ ` readonly reveal?: ${n}ViewSelect;`,
1480
+ ...hasRelations ? [` readonly include?: ${n}ViewInclude;`] : [],
1481
+ ` readonly cursor?: ${n}WhereUniqueInput;`,
1482
+ ` readonly take?: number;`,
1483
+ ` readonly skip?: number;`,
1484
+ ` readonly distinct?: ${orMany({ type: `${n}ScalarFieldEnum` })};`,
1485
+ ` readonly relationStrategy?: RelationStrategy;`
1486
+ ]
1487
+ }));
1488
+ parts.push(objectAlias({
1489
+ name: `${n}ViewFindUniqueArgs`,
1490
+ lines: [
1491
+ ` readonly where: ${n}WhereUniqueInput;`,
1492
+ ` readonly select?: ${n}ViewSelect;`,
1493
+ ` readonly reveal?: ${n}ViewSelect;`,
1494
+ ...hasRelations ? [` readonly include?: ${n}ViewInclude;`] : [],
1495
+ ` readonly relationStrategy?: RelationStrategy;`
1496
+ ]
1497
+ }));
1498
+ const exactPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : K`;
1499
+ const uncertainPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : never`;
1500
+ parts.push(`type ${n}ViewSelectScalars<F, A> = A extends { readonly select: infer S extends object }
1501
+ ` + ` ? { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
1502
+ ` + ` { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] }
1503
+ ` + ` : "select" extends keyof A ? Partial<${n}ViewScalars<F>> : ${n}ViewScalars<F>;`);
1504
+ parts.push(`type ${n}ViewReveal<A> = A extends { readonly reveal: infer R extends object }
1505
+ ` + ` ? { [K in keyof R & ${n}ScalarFieldEnum as ${exactPart("R")}]: ${n}[K & keyof ${n}] } &
1506
+ ` + ` { [K in keyof R & ${n}ScalarFieldEnum as ${uncertainPart("R")}]?: ${n}[K & keyof ${n}] }
1507
+ ` + ` : "reveal" extends keyof A ? Partial<${n}ViewReveal<{ reveal: NonNullable<A["reveal"]> }>> : unknown;`);
1508
+ if (hasRelations) {
1509
+ const relationCases = model.relations.map((relation) => {
1510
+ const target = relation.target;
1511
+ const targetClientKey = view.modelByName.get(target)?.clientName ?? target;
1512
+ const guard = `D extends { readonly ${propKey({ name: targetClientKey })}: infer RF extends object }`;
1513
+ const masked = `ViewRow<${target}ViewSelectScalars<RF, A> & ${target}ViewReveal<A>>`;
1514
+ const unmasked = `(A extends object ? ${target}GetPayload<A> : ${target})`;
1515
+ let expression;
1516
+ if (relation.isList) {
1517
+ expression = `${guard} ? ${masked}[] : ${unmasked}[]`;
1518
+ } else if (relation.isOptional) {
1519
+ expression = `(${guard} ? ${masked} : ${unmasked}) | null`;
1520
+ } else {
1521
+ expression = `${guard} ? ${masked} : ${unmasked}`;
1522
+ }
1523
+ return ` ${propKey({ name: relation.name })}: ${expression};`;
1524
+ }).join(`
1525
+ `);
1526
+ parts.push(`type ${n}ViewRelationResult<K extends keyof ${n}Relations, D, A> = {
1527
+ ${relationCases}
1528
+ }[K];`);
1529
+ parts.push(`type ${n}ViewIncludeResult<A, D> = A extends { readonly include: infer I extends object }
1530
+ ` + ` ? { [K in keyof I & keyof ${n}Relations as ${exactPart("I")}]: ${n}ViewRelationResult<K, D, I[K]> } &
1531
+ ` + ` { [K in keyof I & keyof ${n}Relations as ${uncertainPart("I")}]?: ${n}ViewRelationResult<K, D, I[K]> }
1532
+ ` + ` : "include" extends keyof A ? Partial<${n}ViewIncludeResult<{ include: NonNullable<A["include"]> }, D>> : unknown;`);
1533
+ parts.push(`export type ${n}ViewResult<F, A, D> = ViewRow<${n}ViewSelectScalars<F, A> & ${n}ViewReveal<A> & ${n}ViewIncludeResult<A, D>>;`);
1534
+ } else {
1535
+ parts.push(`export type ${n}ViewResult<F, A, D> = ViewRow<${n}ViewSelectScalars<F, A> & ${n}ViewReveal<A>>;`);
1536
+ }
1537
+ parts.push(`export type ${n}ViewDelegate<F, D extends ViewDefinitionInput = ViewDefinitionInput> = {
1538
+ ` + ` findMany<A extends ${n}ViewFindManyArgs = {}>(args?: A): Promise<${n}ViewResult<F, A, D>[]>;
1539
+ ` + ` findFirst<A extends ${n}ViewFindManyArgs = {}>(args?: A): Promise<${n}ViewResult<F, A, D> | null>;
1540
+ ` + ` findFirstOrThrow<A extends ${n}ViewFindManyArgs = {}>(args?: A): Promise<${n}ViewResult<F, A, D>>;
1541
+ ` + ` findUnique<A extends ${n}ViewFindUniqueArgs>(args: A): Promise<${n}ViewResult<F, A, D> | null>;
1542
+ ` + ` findUniqueOrThrow<A extends ${n}ViewFindUniqueArgs>(args: A): Promise<${n}ViewResult<F, A, D>>;
1543
+ ` + ` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;
1544
+ ` + ` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;
1545
+ ` + ` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;
1546
+ ` + `};`);
1547
+ return parts.join(`
1548
+
1549
+ `);
1550
+ }
1551
+ function emitViewSection(params) {
1552
+ const { view } = params;
1553
+ const parts = [banner({ title: "Field-masking views ($defineView)" })];
1554
+ parts.push(`/**
1555
+ ` + ` * A masked row: definition fields are properties, JSON.stringify drops
1556
+ ` + ` * revealed fields (toJSON), $unsafe() returns everything as a plain object.
1557
+ ` + ` */
1558
+ ` + `export type ViewRow<T> = T & {
1559
+ ` + ` toJSON(): Partial<T>;
1560
+ ` + ` $unsafe(): Record<string, unknown>;
1561
+ ` + `};`);
1562
+ parts.push(objectAlias({
1563
+ name: "ViewDefinitionInput",
1564
+ lines: view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}?: ${model.name}ViewFields;`)
1565
+ }));
1566
+ parts.push(`type ViewDelegateMap<D extends ViewDefinitionInput> = {
1567
+ ${view.models.map((model) => ` ${propKey({ name: model.clientName })}: ${model.name}ViewDelegate<NonNullable<D[${tsString({ value: model.clientName })}]>, D>;`).join(`
1568
+ `)}
1569
+ };`);
1570
+ parts.push(`/** A read-only scoped client over the definition's models \u2014 what $defineView returns. */
1571
+ ` + `export type ViewClient<D extends ViewDefinitionInput> = {
1572
+ ` + ` readonly [K in keyof D & keyof ViewDelegateMap<D>]: ViewDelegateMap<D>[K];
1573
+ ` + `};`);
1574
+ for (const model of view.models) {
1575
+ parts.push(emitViewTypesForModel({ model, view }));
1576
+ }
1577
+ return parts;
1578
+ }
1579
+ function jsonTypeImportLines(params) {
1580
+ const bySpecifier = new Map;
1581
+ for (const model of params.view.models) {
1582
+ for (const field of model.fields) {
1583
+ if (field.jsonType === undefined)
1584
+ continue;
1585
+ const names = bySpecifier.get(field.jsonType.from) ?? new Set;
1586
+ names.add(field.jsonType.name);
1587
+ bySpecifier.set(field.jsonType.from, names);
1588
+ }
1589
+ }
1590
+ return [...bySpecifier.keys()].sort().map((from) => `import type { ${[...bySpecifier.get(from)].sort().join(", ")} } from ${tsString({ value: from })};`);
1591
+ }
1592
+ function hasNativeRls(params) {
1593
+ return params.view.schema.rls !== undefined;
1594
+ }
1595
+ var RLS_CONTEXT_TYPES = {
1596
+ String: "string",
1597
+ Boolean: "boolean",
1598
+ Int: "number",
1599
+ BigInt: "bigint",
1600
+ Float: "number",
1601
+ Decimal: "string",
1602
+ DateTime: "Date"
1603
+ };
1604
+ function emitRlsSection(params) {
1605
+ const rls = params.view.schema.rls;
1606
+ if (rls === undefined)
1607
+ return [];
1608
+ const lines = Object.entries(rls.context).map(([key, slot]) => ` readonly ${propKey({ name: key })}: ${RLS_CONTEXT_TYPES[slot.type]}${slot.nullable ? " | null" : ""};`);
1609
+ const doc = `/** The identity context every query on this schema must be bound to \u2014 all slots required, an explicit null included. */`;
1610
+ return [
1611
+ banner({ title: "Native row-level security" }),
1612
+ lines.length === 0 ? `${doc}
1613
+ export type RlsContext = Record<string, never>;` : `${doc}
1614
+ export type RlsContext = {
1615
+ ${lines.join(`
1616
+ `)}
1617
+ };`
1618
+ ];
1619
+ }
1620
+ function instanceSurfaceLines(params) {
1621
+ const { view, selfType } = params;
1622
+ const delegateProps = view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`);
1623
+ const modelsProps = view.models.flatMap((model) => {
1624
+ const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`;
1625
+ return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${model.name}Delegate;`];
1626
+ });
1627
+ return `${delegateProps.join(`
1628
+ `)}
1629
+ ` + ` /** Delegates keyed by BOTH camelCase client name and model name \u2014 the same objects as the top-level properties (dynamic access). */
1630
+ ` + ` readonly $models: {
1631
+ ${modelsProps.join(`
1632
+ `)}
1633
+ };
1634
+ ` + ` /** Subscribe to client events ("query" fires after every statement, with the same payload as onQuery \u2014 both fire). Returns an unsubscribe function. */
1635
+ ` + ` readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
1636
+ ` + ` /** ONE transaction. Callback form: everything on \`tx\` rides the same BEGIN/COMMIT; a throw rolls back. Array form (Prisma parity): UN-AWAITED delegate calls run sequentially in one transaction, results returned positionally. */
1637
+ ` + ` readonly $transaction: {
1638
+ ` + ` <T>(fn: (tx: ${selfType}) => Promise<T>, options?: TransactionOptions): Promise<T>;
1639
+ ` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
1640
+ ` + ` };
1641
+ ` + ` readonly $queryRaw: <T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]) => Promise<T[]>;
1642
+ ` + ` readonly $executeRaw: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
1643
+ ` + ` readonly $queryRawUnsafe: <T = Record<string, unknown>>(text: string, ...values: unknown[]) => Promise<T[]>;
1644
+ ` + ` readonly $executeRawUnsafe: (text: string, ...values: unknown[]) => Promise<number>;
1645
+ ` + ` readonly $connect: () => Promise<void>;
1646
+ ` + ` readonly $disconnect: () => Promise<void>;
1647
+ ` + ` /** Field-masking views (v1 defineView parity): a read-only masked projection of models. */
1648
+ ` + ` readonly $defineView: <D extends ViewDefinitionInput>(options: { readonly name: string; readonly definition: D }) => ViewClient<D>;
1649
+ `;
1650
+ }
1651
+ function telemetryDeclarations() {
1652
+ return [
1653
+ `/** One SQL execution inside an operation. Parameter values are redacted; \`sql\` and \`parameters\` appear only with the matching capture option. */`,
1654
+ `export type VibeTelemetryStatementEvent = {`,
1655
+ ` readonly operationId: string;`,
1656
+ ` readonly index: number;`,
1657
+ ` readonly kind: "rows" | "affected" | "raw" | "extension";`,
1658
+ ` readonly model: string | null;`,
1659
+ ` readonly method: string;`,
1660
+ ` readonly dialect: string;`,
1661
+ ` readonly provider: string;`,
1662
+ ` /** Stable normalized grouping key \u2014 bounded and literal-free, safe as a metric label. */`,
1663
+ ` readonly fingerprint: string;`,
1664
+ ` readonly outcome: "success" | "failure";`,
1665
+ ` /** Adapter-call elapsed time: networking and possibly pool wait included \u2014 not pure server execution time. */`,
1666
+ ` readonly durationMs: number;`,
1667
+ ` readonly parameterCount: number;`,
1668
+ ` readonly inTransaction: boolean;`,
1669
+ ` readonly rowCount?: number;`,
1670
+ ` readonly errorCode?: string;`,
1671
+ ` readonly slow?: boolean;`,
1672
+ ` readonly sql?: string;`,
1673
+ ` readonly parameters?: readonly unknown[];`,
1674
+ `};`,
1675
+ ``,
1676
+ `/** One public ORM call, start to terminal outcome. Emitted exactly once. Unknown values are ABSENT, never zero. */`,
1677
+ `export type VibeTelemetryOperationEvent = {`,
1678
+ ` readonly operationId: string;`,
1679
+ ` readonly parentOperationId?: string;`,
1680
+ ` readonly context?: unknown;`,
1681
+ ` readonly kind: "model" | "raw" | "transaction" | "extension";`,
1682
+ ` readonly model: string | null;`,
1683
+ ` readonly method: string;`,
1684
+ ` readonly dialect: string;`,
1685
+ ` readonly provider: string;`,
1686
+ ` readonly outcome: "success" | "failure";`,
1687
+ ` readonly stage: "refused" | "validation" | "statement" | "decode" | "transaction" | "completed" | "unknown";`,
1688
+ ` readonly dispatched: boolean;`,
1689
+ ` readonly startedAt?: number;`,
1690
+ ` readonly durationMs?: number;`,
1691
+ ` /** Creation to dispatch \u2014 labelled separately, never folded into durationMs. */`,
1692
+ ` readonly queueWaitMs?: number;`,
1693
+ ` readonly statementCount: number;`,
1694
+ ` readonly statementDurationMs?: number;`,
1695
+ ` readonly rowCount?: number;`,
1696
+ ` readonly errorCode?: string;`,
1697
+ ` readonly detail?: { readonly renderMs?: number; readonly adapterMs?: number; readonly processingMs?: number };`,
1698
+ ` readonly statements: readonly VibeTelemetryStatementEvent[];`,
1699
+ ` readonly droppedStatements?: number;`,
1700
+ ` readonly slow?: boolean;`,
1701
+ ` readonly sampled: true;`,
1702
+ `};`,
1703
+ ``,
1704
+ `/** Where events go. A throw from any callback is contained: it can never change a database outcome. */`,
1705
+ `export type VibeTelemetryExporter = {`,
1706
+ ` readonly onOperation?: (event: VibeTelemetryOperationEvent) => void;`,
1707
+ ` readonly onStatement?: (event: VibeTelemetryStatementEvent) => void;`,
1708
+ ` readonly onDrop?: (event: { readonly reason: "buffer_full" | "statement_limit"; readonly count: number }) => void;`,
1709
+ ` readonly flush?: () => void | Promise<void>;`,
1710
+ ` /** NEVER called by the client \u2014 \`$disconnect()\` flushes only, because the provider is yours. */`,
1711
+ ` readonly shutdown?: () => void | Promise<void>;`,
1712
+ `};`,
1713
+ ``,
1714
+ `/** Complete counters (spans may be sampled, these are not) and explicit lifecycle. */`,
1715
+ `export type VibeTelemetryHandle = {`,
1716
+ ` readonly metrics: () => {`,
1717
+ ` readonly countsRepresent: "all";`,
1718
+ ` readonly sampleRate: number;`,
1719
+ ` readonly sampledOperations: number;`,
1720
+ ` readonly operations: { readonly total: number; readonly failed: number; readonly refused: number };`,
1721
+ ` readonly statements: { readonly total: number; readonly failed: number };`,
1722
+ ` readonly rows: { readonly known: number; readonly total: number };`,
1723
+ ` readonly operationLatencyMs: { readonly bounds: readonly number[]; readonly counts: readonly number[]; readonly sum: number; readonly count: number };`,
1724
+ ` readonly statementLatencyMs: { readonly bounds: readonly number[]; readonly counts: readonly number[]; readonly sum: number; readonly count: number };`,
1725
+ ` readonly dropped: { readonly operations: number; readonly statements: number };`,
1726
+ ` };`,
1727
+ ` readonly flush: () => Promise<void>;`,
1728
+ ` readonly shutdown: () => Promise<void>;`,
1729
+ `};`,
1730
+ ``,
1731
+ `/** Opt-in operation telemetry. Defaults are conservative: no SQL text, no parameter values, no sampling. */`,
1732
+ `export type VibeTelemetryOptions = {`,
1733
+ ` readonly exporter: VibeTelemetryExporter;`,
1734
+ ` /** Attach rendered SQL text (default false \u2014 raw text can embed secrets). */`,
1735
+ ` readonly captureSql?: boolean;`,
1736
+ ` /** Attach parameter VALUES (default false \u2014 only the count is reported). */`,
1737
+ ` readonly captureParameterValues?: boolean;`,
1738
+ ` /** Split render / adapter / processing time on the operation event (default false). */`,
1739
+ ` readonly detailedTimings?: boolean;`,
1740
+ ` /** Span sampling probability, 0..1 (default 1). Counters still count every execution. */`,
1741
+ ` readonly sampleRate?: number;`,
1742
+ ` readonly sampler?: (input: { readonly kind: "model" | "raw" | "transaction" | "extension"; readonly model: string | null; readonly method: string; readonly context?: unknown }) => boolean;`,
1743
+ ` /** Read your current parent context SYNCHRONOUSLY at call time, so an application span survives the async boundary. */`,
1744
+ ` readonly context?: () => unknown;`,
1745
+ ` readonly delivery?: "immediate" | "buffered";`,
1746
+ ` readonly maxBufferedOperations?: number;`,
1747
+ ` readonly maxStatementsPerOperation?: number;`,
1748
+ ` readonly slowStatementMs?: number;`,
1749
+ ` readonly slowOperationMs?: number;`,
1750
+ ` readonly onExporterError?: (params: { readonly error: unknown; readonly phase: "operation" | "statement" | "drop" | "flush" | "shutdown" | "sampler" | "context" }) => void;`,
1751
+ ` /** Called once while the client is built, with the handle that owns metrics and lifecycle. */`,
1752
+ ` readonly onReady?: (handle: VibeTelemetryHandle) => void;`,
1753
+ `};`
1754
+ ].join(`
1755
+ `);
1756
+ }
1757
+ function emitClient(params) {
1758
+ const { view, baked, hasComputed } = params;
1759
+ const nativeRls = hasNativeRls({ view });
1760
+ const parts = [banner({ title: "Client" })];
1761
+ parts.push(telemetryDeclarations());
1762
+ parts.push(objectAlias({
1763
+ name: "VibeClientOptions",
1764
+ lines: [
1765
+ ` readonly adapter: DatabaseAdapter;`,
1766
+ ` readonly onQuery?: (event: QueryEvent) => void;`,
1767
+ ` readonly relationStrategy?: RelationStrategy;`,
1768
+ ` /** Append the primary key to ORDER BY when a find has no explicit orderBy (default false) \u2014 deterministic row order. */`,
1769
+ ` readonly defaultOrderByPk?: boolean;`,
1770
+ ` /** Append the primary key as a tie-breaker to \`distinct\` ordering (default true) \u2014 pins WHICH row represents each distinct key. */`,
1771
+ ` readonly distinctOrderByPk?: boolean;`,
1772
+ ` /** Opt-in write-boundary validators, per model \xD7 field: \`{ User: { email: zodSchema } }\`. Any Standard Schema validator (zod, valibot, arktype) works; they run on every write path before encoding. */`,
1773
+ ` readonly validate?: WriteValidators;`,
1774
+ ` /** Opt-in operation telemetry: one event per public ORM call, with the statements it ran. Parameter values are redacted by default and the exporter can never change a database outcome. */`,
1775
+ ` readonly telemetry?: VibeTelemetryOptions;`,
1776
+ ...baked.empty ? [] : [
1777
+ ` /** The live extension instances \u2014 verified against the baked manifest. */`,
1778
+ ` readonly extensions?: readonly VibeExtension[];`
1779
+ ],
1780
+ ...hasComputed ? [
1781
+ ` /** The live computed-field config \u2014 verified against the baked manifest (VIBE_STALE_CLIENT). */`,
1782
+ ` readonly computed: ComputedConfig;`
1783
+ ] : [],
1784
+ ...nativeRls ? [
1785
+ ` /** Native row-level security: the non-bypassing database role the runtime binds each transaction to (required on PGlite; server connections log in as it). */`,
1786
+ ` readonly nativeRls?: { readonly runtimeRole?: string };`
1787
+ ] : []
1788
+ ]
1789
+ }));
1790
+ const clientExtensionLines = baked.clientDts.length === 0 ? "" : `${baked.clientDts.join(`
1791
+ `)}
1792
+ `;
1793
+ if (nativeRls) {
1794
+ parts.push(`export type BoundVibeClientInstance = {
1795
+ ` + instanceSurfaceLines({ view, selfType: "BoundVibeClientInstance" }) + clientExtensionLines + `};`);
1796
+ parts.push(`export type VibeClientInstance = {
1797
+ ` + ` /** Bind the request's identity context \u2014 ALL of it, every declared slot \u2014 and get the client that can actually query. */
1798
+ ` + ` readonly $withContext: (context: RlsContext) => BoundVibeClientInstance;
1799
+ ` + ` /** Subscribe to client events ("query" fires after every statement, with the same payload as onQuery \u2014 both fire). Returns an unsubscribe function. */
1800
+ ` + ` readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
1801
+ ` + ` readonly $connect: () => Promise<void>;
1802
+ ` + ` readonly $disconnect: () => Promise<void>;
1803
+ ` + ` // Deliberately ABSENT: model delegates, $models, $transaction, $queryRaw / $executeRaw / $queryRawUnsafe / $executeRawUnsafe, $defineView and every extension mount \u2014 an unbound client has no identity, so it cannot read or write a row. Bind one with $withContext.
1804
+ ` + `};`);
1805
+ } else {
1806
+ parts.push(`export type VibeClientInstance = {
1807
+ ` + instanceSurfaceLines({ view, selfType: "VibeClientInstance" }) + policyClientLines({ view }) + scopedClientLines({ view }) + clientExtensionLines + `};`);
1808
+ }
1809
+ parts.push(`/** Build a typed client over a database adapter. */
1810
+ export declare function VibeClient(options: VibeClientOptions): VibeClientInstance;`);
1811
+ parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
1812
+ export declare const schema: SchemaIR;`);
1813
+ return parts.join(`
1814
+
1815
+ `);
1816
+ }
1817
+ function scopedColumnCandidates(params) {
1818
+ return params.model.fields.filter((field) => !field.isList && (field.scalar === "String" || field.scalar === "Int" || field.scalar === "BigInt")).map((field) => field.name);
1819
+ }
1820
+ function emitScopedSection(params) {
1821
+ const { view } = params;
1822
+ if (hasNativeRls({ view }))
1823
+ return [];
1824
+ if (view.models.some((model) => model.policy !== undefined))
1825
+ return [];
1826
+ const entries = view.models.map((model) => {
1827
+ const arms = [`"none"`];
1828
+ const columns = scopedColumnCandidates({ model });
1829
+ if (columns.length > 0) {
1830
+ const union = columns.map((name) => `"${name}"`).join(" | ");
1831
+ arms.push(`{ readonly column: ${union}; readonly nested?: ScopedNestedMode }`);
1832
+ }
1833
+ const throughs = model.relations.filter((relation) => !relation.isList).map((relation) => relation.name);
1834
+ if (throughs.length > 0) {
1835
+ const union = throughs.map((name) => `"${name}"`).join(" | ");
1836
+ arms.push(`{ readonly through: ${union}; readonly nested?: ScopedNestedMode }`);
1837
+ }
1838
+ return ` readonly ${propKey({ name: model.name })}: ${arms.join(" | ")};`;
1839
+ });
1840
+ const scopedDelegateType = (model) => `${model.name}Delegate`;
1841
+ const delegateProps = view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}: ${scopedDelegateType(model)};`);
1842
+ const modelsProps = view.models.flatMap((model) => {
1843
+ const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${scopedDelegateType(model)};`;
1844
+ return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${scopedDelegateType(model)};`];
1845
+ });
1846
+ return [
1847
+ banner({ title: "Tenant scoping ($scoped)" }),
1848
+ `/** What a scoped handle does when this model appears INSIDE another model's nested write: "verify" (default) rewrites reference verbs into tenant-verified lookups; "refuse" rejects them. */
1849
+ export type ScopedNestedMode = "verify" | "refuse";`,
1850
+ `/** Per-model tenancy classification \u2014 TOTAL: every model must be named ("none" = global, passes through untouched). */
1851
+ export type ScopedModelsConfig = {
1852
+ ${entries.join(`
1853
+ `)}
1854
+ };`,
1855
+ `export type ScopedClientInstance = {
1856
+ ` + `${delegateProps.join(`
1857
+ `)}
1858
+ ` + ` /** Delegates keyed by BOTH camelCase client name and model name \u2014 the same objects as the top-level properties (dynamic access). */
1859
+ ` + ` readonly $models: {
1860
+ ${modelsProps.join(`
1861
+ `)}
1862
+ };
1863
+ ` + ` /** Subscribe to client events \u2014 shared with the base client. */
1864
+ ` + ` readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
1865
+ ` + ` /** ONE transaction; the callback receives a handle that INHERITS the scope. The array form takes un-awaited scoped delegate calls. */
1866
+ ` + ` readonly $transaction: {
1867
+ ` + ` <T>(fn: (tx: ScopedClientInstance) => Promise<T>, options?: TransactionOptions): Promise<T>;
1868
+ ` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
1869
+ ` + ` };
1870
+ ` + ` readonly $connect: () => Promise<void>;
1871
+ ` + ` readonly $disconnect: () => Promise<void>;
1872
+ ` + ` /** Field-masking views compose over the scoped handle (columns \xD7 rows). */
1873
+ ` + ` readonly $defineView: <D extends ViewDefinitionInput>(options: { readonly name: string; readonly definition: D }) => ViewClient<D>;
1874
+ ` + ` // Deliberately ABSENT: $queryRaw / $executeRaw / $queryRawUnsafe / $executeRawUnsafe (raw SQL cannot be tenant-verified \u2014 use the base client) and $scoped (scope once, from the base client).
1875
+ ` + `};`
1876
+ ];
1877
+ }
1878
+ function scopedClientLines(params) {
1879
+ if (hasNativeRls(params))
1880
+ return "";
1881
+ if (params.view.models.some((model) => model.policy !== undefined))
1882
+ return "";
1883
+ return ` /** Tenant scoping: a total per-model tenancy map + a scope value \u2192 a NEW handle whose every call is confined to that tenant. scope: null builds the handle but refuses calls on scoped models. */
1884
+ ` + ` readonly $scoped: (config: { readonly scope: string | number | bigint | null; readonly models: ScopedModelsConfig }) => ScopedClientInstance;
1885
+ `;
1886
+ }
1887
+ function policyClientLines(params) {
1888
+ const policied = params.view.models.filter((model) => model.policy !== undefined);
1889
+ if (policied.length === 0)
1890
+ return "";
1891
+ const CONTEXT_SCALAR_TYPES = { String: "string", Int: "number", BigInt: "bigint" };
1892
+ const contextKeys = new Map;
1893
+ for (const model of policied) {
1894
+ const policy = model.policy;
1895
+ const field = model.fields.find((entry) => entry.name === policy.field);
1896
+ contextKeys.set(policy.context, field?.scalar == null ? "string" : CONTEXT_SCALAR_TYPES[field.scalar] ?? "string");
1897
+ }
1898
+ const contextType = `{ ${[...contextKeys.entries()].map(([key, type]) => `readonly ${propKey({ name: key })}: ${type};`).join(" ")} }`;
1899
+ return ` /** Row scoping (@@policy): bind a per-request context; policied models fail closed without one. */
1900
+ ` + ` readonly $withPolicy: (context: ${contextType}) => VibeClientInstance;
1901
+ ` + ` /** Deliberate, greppable full-access escape from policy scoping. */
1902
+ ` + ` readonly $bypassPolicy: () => VibeClientInstance;
1903
+ `;
1904
+ }
1905
+ function emitDts(params) {
1906
+ const { view } = params;
1907
+ const baked = params.baked ?? EMPTY_BAKED_EXTENSIONS;
1908
+ const computed = params.computed ?? [];
1909
+ const hasComputed = computed.length > 0;
1910
+ const computedByModel = new Map;
1911
+ for (const entry of computed) {
1912
+ const list = computedByModel.get(entry.model) ?? [];
1913
+ list.push(entry);
1914
+ computedByModel.set(entry.model, list);
1915
+ }
1916
+ const parts = [];
1917
+ parts.push(GENERATED_HEADER);
1918
+ const schemaTypeImports = [
1919
+ "SchemaIR",
1920
+ ...baked.empty ? [] : ["VibeExtension"],
1921
+ ...hasComputed ? ["ComputedConfig"] : []
1922
+ ].sort();
1923
+ const importLines = [
1924
+ `import type { DatabaseAdapter, DbNow, QueryEvent, Skip, TransactionOptions, WriteValidators } from "@vibeorm/runtime";`,
1925
+ `import type { ${schemaTypeImports.join(", ")} } from "@vibeorm/schema";`,
1926
+ ...jsonTypeImportLines({ view }),
1927
+ ...baked.dtsImports,
1928
+ `import { ${enumEntryExports({ view }).join(", ")} } from "./enums.js";`
1929
+ ];
1930
+ parts.push(`${importLines.join(`
1931
+ `)}
1932
+
1933
+ ` + `export { VibeError } from "@vibeorm/schema";
1934
+ export type { VibeErrorCode } from "@vibeorm/schema";
1935
+ ` + `export { SKIP } from "@vibeorm/runtime";
1936
+ export type { Skip } from "@vibeorm/runtime";
1937
+ ` + `export { ${enumEntryExports({ view }).join(", ")} };`);
1938
+ parts.push(PREAMBLE);
1939
+ parts.push(...filterSection());
1940
+ for (const declared of view.enums)
1941
+ parts.push(emitEnum({ declared }));
1942
+ const registry = new Map;
1943
+ const modelParts = [];
1944
+ for (const model of view.models) {
1945
+ const extensionLines = baked.delegateDts.get(model.name);
1946
+ const modelComputed = computedByModel.get(model.name);
1947
+ modelParts.push(emitModel({
1948
+ model,
1949
+ view,
1950
+ registry,
1951
+ ...extensionLines === undefined ? {} : { extensionLines },
1952
+ ...modelComputed === undefined ? {} : { computed: modelComputed }
1953
+ }));
1954
+ }
1955
+ parts.push(...modelParts);
1956
+ if (registry.size > 0) {
1957
+ parts.push(banner({ title: "Nested write inputs" }));
1958
+ for (const name of [...registry.keys()].sort()) {
1959
+ parts.push(registry.get(name));
1960
+ }
1961
+ }
1962
+ if (baked.moduleDts.length > 0) {
1963
+ parts.push(banner({ title: "Extensions" }));
1964
+ parts.push(...baked.moduleDts);
1965
+ }
1966
+ parts.push(...emitViewSection({ view }));
1967
+ parts.push(...emitScopedSection({ view }));
1968
+ parts.push(...emitRlsSection({ view }));
1969
+ parts.push(emitClient({ view, baked, hasComputed }));
1970
+ return `${parts.join(`
1971
+
1972
+ `)}
1973
+ `;
1974
+ }
1975
+
1976
+ // packages/generator/src/schema-view.ts
1977
+ import { VibeError as VibeError4, assertValidSchemaIR, enumStorageOf, isEnumType } from "@vibeorm/schema";
1978
+ import { postgresDialect, renderRlsPolicy } from "@vibeorm/sql";
1979
+ var JSON_TYPE_RE = /@type\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+"([^"]+)"\s*\)/;
1980
+ function parseJsonTypeAnnotation(params) {
1981
+ if (params.comment === undefined)
1982
+ return null;
1983
+ const match = JSON_TYPE_RE.exec(params.comment);
1984
+ if (match === null)
1985
+ return null;
1986
+ return { name: match[1], from: match[2] };
1987
+ }
1988
+ function byName(left, right) {
1989
+ return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
1990
+ }
1991
+ function buildFieldView(params) {
1992
+ const { field, model } = params;
1993
+ const enumRef = isEnumType(field.type);
1994
+ const annotation = parseJsonTypeAnnotation({ comment: field.documentation });
1995
+ if (annotation !== null && (enumRef || field.type !== "Json")) {
1996
+ throw new VibeError4({
1997
+ code: "VIBE_SCHEMA_INVALID",
1998
+ message: `@type(...) annotation on ${model}.${field.name} \u2014 typed JSON is only valid on Json fields`,
1999
+ meta: { model, field: field.name }
2000
+ });
2001
+ }
2002
+ return {
2003
+ name: field.name,
2004
+ scalar: enumRef ? null : field.type,
2005
+ enumName: enumRef ? field.type.enum : null,
2006
+ isList: field.isList,
2007
+ isOptional: field.isOptional,
2008
+ isId: field.isId,
2009
+ isUnique: field.isUnique,
2010
+ hasDefault: field.default !== undefined,
2011
+ isUpdatedAt: field.isUpdatedAt,
2012
+ ...annotation === null ? {} : { jsonType: annotation }
2013
+ };
2014
+ }
2015
+ function resolveInverse(params) {
2016
+ const { relation, target } = params;
2017
+ const candidates = target.relations.filter((candidate) => candidate.targetModel === relation.model && (candidate.relationName ?? null) === (relation.relationName ?? null) && !(candidate.model === relation.model && candidate.name === relation.name));
2018
+ const first = candidates[0];
2019
+ return candidates.length === 1 && first !== undefined ? first.name : null;
2020
+ }
2021
+ function buildUniques(params) {
2022
+ const { model, fields } = params;
2023
+ const fieldByName = new Map(fields.map((field) => [field.name, field]));
2024
+ const members = [];
2025
+ const seen = new Set;
2026
+ const add = (names, nullsNotDistinct, clientName) => {
2027
+ const key = clientName ?? names.join("_");
2028
+ if (seen.has(key))
2029
+ return;
2030
+ const resolved = [];
2031
+ for (const name of names) {
2032
+ const field = fieldByName.get(name);
2033
+ if (field === undefined)
2034
+ return;
2035
+ resolved.push(field);
2036
+ }
2037
+ seen.add(key);
2038
+ members.push({ key, fields: resolved, nullsNotDistinct });
2039
+ };
2040
+ for (const field of fields) {
2041
+ if (field.isId || field.isUnique)
2042
+ add([field.name], false);
2043
+ }
2044
+ if (model.primaryKey !== undefined) {
2045
+ if (model.primaryKey.fields.length === 1)
2046
+ add(model.primaryKey.fields, false);
2047
+ else
2048
+ add(model.primaryKey.fields, false, model.primaryKey.clientName);
2049
+ }
2050
+ for (const index of model.indexes) {
2051
+ if (index.kind === "unique" && index.where === undefined) {
2052
+ add(index.fields.map((field) => field.name), index.nullsNotDistinct === true, index.clientName);
2053
+ }
2054
+ }
2055
+ return members;
2056
+ }
2057
+ function buildModelView(params) {
2058
+ const { model, modelIRByName } = params;
2059
+ const fields = model.fields.filter((field) => field.isUnsupported !== true).map((field) => buildFieldView({ field, model: model.name }));
2060
+ const fkFieldNames = new Set;
2061
+ const relations = model.relations.map((relation) => {
2062
+ for (const fk of relation.fields ?? [])
2063
+ fkFieldNames.add(fk);
2064
+ const target = modelIRByName.get(relation.targetModel);
2065
+ return {
2066
+ name: relation.name,
2067
+ target: relation.targetModel,
2068
+ type: relation.type,
2069
+ isList: relation.isList,
2070
+ isOptional: relation.isOptional,
2071
+ fkFields: relation.fields ?? [],
2072
+ inverseName: target === undefined ? null : resolveInverse({ relation, target })
2073
+ };
2074
+ });
2075
+ return {
2076
+ name: model.name,
2077
+ clientName: clientNameOf({ name: model.name }),
2078
+ fields,
2079
+ relations,
2080
+ uniques: buildUniques({ model, fields }),
2081
+ fkFieldNames,
2082
+ isView: model.isView,
2083
+ ...model.policy === undefined ? {} : { policy: model.policy }
2084
+ };
2085
+ }
2086
+ function assertRlsRenderable(params) {
2087
+ for (const model of params.schema.models) {
2088
+ if (model.rls === undefined || model.rls === "unprotected")
2089
+ continue;
2090
+ renderRlsPolicy({ dialect: postgresDialect, schema: params.schema, model: model.name });
2091
+ }
2092
+ }
2093
+ function buildSchemaView(params) {
2094
+ const { schema } = params;
2095
+ assertValidSchemaIR({ schema });
2096
+ if (schema.rls !== undefined)
2097
+ assertRlsRenderable({ schema });
2098
+ const normalized = {
2099
+ datasource: schema.datasource,
2100
+ models: [...schema.models].sort(byName),
2101
+ enums: [...schema.enums].sort(byName),
2102
+ extensions: [...schema.extensions].sort(byName),
2103
+ ...schema.rls === undefined ? {} : { rls: schema.rls }
2104
+ };
2105
+ const modelIRByName = new Map(normalized.models.map((model) => [model.name, model]));
2106
+ const models = normalized.models.map((model) => buildModelView({ model, modelIRByName }));
2107
+ return {
2108
+ schema: normalized,
2109
+ enums: normalized.enums.map((declared) => ({
2110
+ name: declared.name,
2111
+ values: declared.values,
2112
+ storage: enumStorageOf({ enumDef: declared })
2113
+ })),
2114
+ models,
2115
+ modelByName: new Map(models.map((model) => [model.name, model]))
2116
+ };
2117
+ }
2118
+
2119
+ // packages/generator/src/generate.ts
2120
+ var README = `# Generated VibeORM client
2121
+
2122
+ This directory was generated by \`@vibeorm/generator\` from your VibeORM schema \u2014 do not edit it by hand, any change is overwritten on the next generate. After every schema change, regenerate with \`vibeorm generate\` (or programmatically via \`generateClient({ schema, outDir })\` from \`@vibeorm/generator\`).
2123
+ `;
2124
+ async function generateClient(params) {
2125
+ const { schema, outDir } = params;
2126
+ const view = buildSchemaView({ schema });
2127
+ const baked = bakeExtensions({ schema: view.schema, extensions: params.extensions ?? [] });
2128
+ let computed = [];
2129
+ if (params.computed !== undefined && Object.keys(params.computed).length > 0) {
2130
+ validateComputedConfig({ schema, computed: params.computed });
2131
+ computed = buildComputedManifest({ computed: params.computed });
2132
+ }
2133
+ const outputs = [
2134
+ { name: "index.js", content: emitJs({ view, baked, computed }) },
2135
+ { name: "index.d.ts", content: emitDts({ view, baked, computed }) },
2136
+ { name: "enums.js", content: emitEnumsJs({ view }) },
2137
+ { name: "enums.d.ts", content: emitEnumsDts({ view }) },
2138
+ { name: "README.md", content: README }
2139
+ ];
2140
+ await mkdir(outDir, { recursive: true });
2141
+ const files = [];
2142
+ for (const output of outputs) {
2143
+ const path = join(outDir, output.name);
2144
+ await writeFile(path, output.content, "utf-8");
2145
+ files.push(path);
2146
+ }
2147
+ return { files };
2148
+ }
2149
+ export {
2150
+ generateClient,
2151
+ bakeExtensions
2152
+ };
2153
+
2154
+ //# debugId=AC114FAAB4DBE66364756E2164756E21