@vibeorm/generator 2.0.0-alpha.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,6 +7,9 @@ import { buildComputedManifest, validateComputedConfig } from "@vibeorm/schema";
7
7
  // packages/generator/src/emit-dts.ts
8
8
  import { VibeError as VibeError3 } from "@vibeorm/schema";
9
9
 
10
+ // packages/generator/src/emit-js.ts
11
+ import { internalError } from "@vibeorm/schema";
12
+
10
13
  // packages/generator/src/extension-bake.ts
11
14
  import {
12
15
  VibeError,
@@ -128,6 +131,235 @@ function bakeExtensions(params) {
128
131
  };
129
132
  }
130
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
+
131
363
  // packages/generator/src/type-names.ts
132
364
  var PAYLOAD_TYPES = {
133
365
  String: "string",
@@ -198,71 +430,6 @@ function updateOpsName(params) {
198
430
  return field.isOptional ? `Nullable${prefix}FieldUpdateOperationsInput` : `${prefix}FieldUpdateOperationsInput`;
199
431
  }
200
432
 
201
- // packages/generator/src/util.ts
202
- import { VibeError as VibeError2 } from "@vibeorm/schema";
203
- function pascal(params) {
204
- const { name } = params;
205
- if (name.length === 0)
206
- return name;
207
- return name.charAt(0).toUpperCase() + name.slice(1);
208
- }
209
- function clientNameOf(params) {
210
- const { name } = params;
211
- if (name.length === 0)
212
- return name;
213
- return name.charAt(0).toLowerCase() + name.slice(1);
214
- }
215
- var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
216
- function propKey(params) {
217
- const { name } = params;
218
- return IDENTIFIER_RE.test(name) ? name : JSON.stringify(name);
219
- }
220
- function tsString(params) {
221
- return JSON.stringify(params.value);
222
- }
223
- function maybeParens(params) {
224
- const { type } = params;
225
- return type.includes("|") ? `(${type})` : type;
226
- }
227
- function stableStringify(params) {
228
- return renderValue({ value: params.value, indent: "" });
229
- }
230
- function renderValue(params) {
231
- const { value, indent: indent2 } = params;
232
- if (value === null)
233
- return "null";
234
- const kind = typeof value;
235
- if (kind === "string" || kind === "number" || kind === "boolean")
236
- return JSON.stringify(value);
237
- if (Array.isArray(value)) {
238
- if (value.length === 0)
239
- return "[]";
240
- const inner = `${indent2} `;
241
- const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
242
- return `[
243
- ${items.join(`,
244
- `)}
245
- ${indent2}]`;
246
- }
247
- if (kind === "object") {
248
- const record = value;
249
- const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort();
250
- if (keys.length === 0)
251
- return "{}";
252
- const inner = `${indent2} `;
253
- const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
254
- return `{
255
- ${items.join(`,
256
- `)}
257
- ${indent2}}`;
258
- }
259
- throw new VibeError2({
260
- code: "VIBE_INTERNAL",
261
- message: `internal: cannot serialize a ${kind} value into the generated client`,
262
- meta: { kind }
263
- });
264
- }
265
-
266
433
  // packages/generator/src/emit-dts.ts
267
434
  function banner(params) {
268
435
  return `// \u2500\u2500\u2500 ${params.title} \u2500\u2500\u2500`;
@@ -276,6 +443,23 @@ ${lines.join(`
276
443
  `)}
277
444
  };`;
278
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
+ }
279
463
  function orMany(params) {
280
464
  const { type } = params;
281
465
  return `${type} | readonly ${maybeParens({ type })}[]`;
@@ -321,11 +505,66 @@ export type SortOrderWithNulls = SortOrder | { readonly sort: SortOrder; readonl
321
505
  /** How relations are loaded: batched WHERE-IN queries, or a single JOIN (pg/pglite). */
322
506
  export type RelationStrategy = "query" | "join";
323
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
+
324
528
  /** Result of createMany/updateMany/deleteMany. */
325
529
  export type BatchPayload = { readonly count: number };
326
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
+
327
549
  /** Keys of an omit object whose value is exactly true \u2014 the scalars the result drops. */
328
- type OmittedKeys<O> = keyof { [K in keyof O as O[K] extends true ? K : never]: 0 };`;
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;`;
329
568
  var SCALAR_FILTER_CONFIGS = [
330
569
  { prefix: "String", value: "string", comparable: true, text: true },
331
570
  { prefix: "Int", value: "number", comparable: true, text: false },
@@ -360,13 +599,13 @@ function scalarFilterAlias(params) {
360
599
  lines.push(` readonly mode?: QueryMode;`);
361
600
  }
362
601
  lines.push(` readonly not?: ${value} | ${name}${nullable ? " | null" : ""};`);
363
- return objectAlias({ name, lines });
602
+ return inputAlias({ name, lines });
364
603
  }
365
604
  function scalarListFilterAlias(params) {
366
605
  const { config } = params;
367
606
  const value = config.value;
368
607
  const array = `readonly ${maybeParens({ type: value })}[]`;
369
- return objectAlias({
608
+ return inputAlias({
370
609
  name: `${config.prefix}ListFilter`,
371
610
  lines: [
372
611
  ` readonly equals?: ${array} | null;`,
@@ -386,7 +625,7 @@ var UPDATE_OPS_CONFIGS = [
386
625
  function updateOpsAlias(params) {
387
626
  const { prefix, value, nullable } = params;
388
627
  const name = nullable ? `Nullable${prefix}FieldUpdateOperationsInput` : `${prefix}FieldUpdateOperationsInput`;
389
- return objectAlias({
628
+ return inputAlias({
390
629
  name,
391
630
  lines: [
392
631
  ` readonly set?: ${nullable ? `${value} | null` : value};`,
@@ -397,6 +636,25 @@ function updateOpsAlias(params) {
397
636
  ]
398
637
  });
399
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
+ }
400
658
  function filterSection() {
401
659
  const parts = [banner({ title: "Scalar filters" })];
402
660
  for (const config of SCALAR_FILTER_CONFIGS) {
@@ -404,15 +662,9 @@ function filterSection() {
404
662
  parts.push(scalarFilterAlias({ config, nullable: true }));
405
663
  parts.push(scalarListFilterAlias({ config }));
406
664
  }
407
- parts.push(objectAlias({
408
- name: "JsonFilter",
409
- lines: [` readonly equals?: InputJsonValue;`, ` readonly not?: InputJsonValue;`]
410
- }));
411
- parts.push(objectAlias({
412
- name: "JsonNullableFilter",
413
- lines: [` readonly equals?: InputJsonValue | null;`, ` readonly not?: InputJsonValue | null;`]
414
- }));
415
- parts.push(objectAlias({
665
+ parts.push(jsonFilterAlias({ nullable: false }));
666
+ parts.push(jsonFilterAlias({ nullable: true }));
667
+ parts.push(inputAlias({
416
668
  name: "JsonListFilter",
417
669
  lines: [
418
670
  ` readonly equals?: readonly InputJsonValue[] | null;`,
@@ -429,18 +681,55 @@ function filterSection() {
429
681
  }
430
682
  return parts;
431
683
  }
432
- function emitEnum(params) {
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) {
433
687
  const { declared } = params;
434
688
  const name = declared.name;
435
- const parts = [banner({ title: `Enum ${name}` })];
436
689
  const valueLines = declared.values.map((value) => ` readonly ${propKey({ name: value })}: ${tsString({ value })};`);
437
- parts.push(`export declare const ${name}: {
690
+ return [
691
+ banner({ title: `Enum ${name}` }),
692
+ `export declare const ${name}: {
438
693
  ${valueLines.join(`
439
694
  `)}
440
- };`);
441
- parts.push(`export type ${name} = ${declared.values.map((value) => tsString({ value })).join(" | ")};`);
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` })];
442
731
  const array = `readonly ${name}[]`;
443
- parts.push(objectAlias({
732
+ parts.push(inputAlias({
444
733
  name: `${name}Filter`,
445
734
  lines: [
446
735
  ` readonly equals?: ${name};`,
@@ -449,7 +738,7 @@ ${valueLines.join(`
449
738
  ` readonly not?: ${name} | ${name}Filter;`
450
739
  ]
451
740
  }));
452
- parts.push(objectAlias({
741
+ parts.push(inputAlias({
453
742
  name: `${name}NullableFilter`,
454
743
  lines: [
455
744
  ` readonly equals?: ${name} | null;`,
@@ -458,7 +747,7 @@ ${valueLines.join(`
458
747
  ` readonly not?: ${name} | ${name}NullableFilter | null;`
459
748
  ]
460
749
  }));
461
- parts.push(objectAlias({
750
+ parts.push(inputAlias({
462
751
  name: `${name}ListFilter`,
463
752
  lines: [
464
753
  ` readonly equals?: ${array} | null;`,
@@ -575,7 +864,7 @@ function registerNestedContext(params) {
575
864
  register({
576
865
  registry,
577
866
  name: `${t}Create${suffix}Input`,
578
- text: objectAlias({
867
+ text: inputAlias({
579
868
  name: `${t}Create${suffix}Input`,
580
869
  lines: checkedCreateLines({ model: target, omitRelation: inverse })
581
870
  })
@@ -583,7 +872,7 @@ function registerNestedContext(params) {
583
872
  register({
584
873
  registry,
585
874
  name: `${t}UncheckedCreate${suffix}Input`,
586
- text: objectAlias({
875
+ text: inputAlias({
587
876
  name: `${t}UncheckedCreate${suffix}Input`,
588
877
  lines: uncheckedCreateLines({ model: target, omitRelation: inverse })
589
878
  })
@@ -591,7 +880,7 @@ function registerNestedContext(params) {
591
880
  register({
592
881
  registry,
593
882
  name: `${t}Update${suffix}Input`,
594
- text: objectAlias({
883
+ text: inputAlias({
595
884
  name: `${t}Update${suffix}Input`,
596
885
  lines: checkedUpdateLines({ model: target, omitRelation: inverse })
597
886
  })
@@ -599,7 +888,7 @@ function registerNestedContext(params) {
599
888
  register({
600
889
  registry,
601
890
  name: `${t}UncheckedUpdate${suffix}Input`,
602
- text: objectAlias({
891
+ text: inputAlias({
603
892
  name: `${t}UncheckedUpdate${suffix}Input`,
604
893
  lines: uncheckedUpdateLines({ model: target, omitRelation: inverse })
605
894
  })
@@ -609,7 +898,7 @@ function registerNestedContext(params) {
609
898
  register({
610
899
  registry,
611
900
  name: connectOrCreateName,
612
- text: objectAlias({
901
+ text: inputAlias({
613
902
  name: connectOrCreateName,
614
903
  lines: [` readonly where: ${t}WhereUniqueInput;`, ` readonly create: ${createUnion};`]
615
904
  })
@@ -619,7 +908,7 @@ function registerNestedContext(params) {
619
908
  register({
620
909
  registry,
621
910
  name: `${t}CreateNestedMany${suffix}Input`,
622
- text: objectAlias({
911
+ text: inputAlias({
623
912
  name: `${t}CreateNestedMany${suffix}Input`,
624
913
  lines: [
625
914
  ` readonly create?: ${orMany({ type: createUnion })};`,
@@ -633,7 +922,7 @@ function registerNestedContext(params) {
633
922
  register({
634
923
  registry,
635
924
  name: updateWithWhere,
636
- text: objectAlias({
925
+ text: inputAlias({
637
926
  name: updateWithWhere,
638
927
  lines: [` readonly where: ${t}WhereUniqueInput;`, ` readonly data: ${updateUnion};`]
639
928
  })
@@ -642,7 +931,7 @@ function registerNestedContext(params) {
642
931
  register({
643
932
  registry,
644
933
  name: updateManyWithWhere,
645
- text: objectAlias({
934
+ text: inputAlias({
646
935
  name: updateManyWithWhere,
647
936
  lines: [` readonly where: ${t}WhereInput;`, ` readonly data: ${t}UpdateManyInput;`]
648
937
  })
@@ -651,7 +940,7 @@ function registerNestedContext(params) {
651
940
  register({
652
941
  registry,
653
942
  name: upsertWithWhere,
654
- text: objectAlias({
943
+ text: inputAlias({
655
944
  name: upsertWithWhere,
656
945
  lines: [
657
946
  ` readonly where: ${t}WhereUniqueInput;`,
@@ -663,7 +952,7 @@ function registerNestedContext(params) {
663
952
  register({
664
953
  registry,
665
954
  name: `${t}UpdateNestedMany${suffix}Input`,
666
- text: objectAlias({
955
+ text: inputAlias({
667
956
  name: `${t}UpdateNestedMany${suffix}Input`,
668
957
  lines: [
669
958
  ` readonly create?: ${orMany({ type: createUnion })};`,
@@ -685,7 +974,7 @@ function registerNestedContext(params) {
685
974
  register({
686
975
  registry,
687
976
  name: `${t}CreateNestedOne${suffix}Input`,
688
- text: objectAlias({
977
+ text: inputAlias({
689
978
  name: `${t}CreateNestedOne${suffix}Input`,
690
979
  lines: [
691
980
  ` readonly create?: ${createUnion};`,
@@ -697,7 +986,7 @@ function registerNestedContext(params) {
697
986
  register({
698
987
  registry,
699
988
  name: `${t}UpdateNestedOne${suffix}Input`,
700
- text: objectAlias({
989
+ text: inputAlias({
701
990
  name: `${t}UpdateNestedOne${suffix}Input`,
702
991
  lines: [
703
992
  ` readonly create?: ${createUnion};`,
@@ -711,7 +1000,6 @@ function registerNestedContext(params) {
711
1000
  });
712
1001
  }
713
1002
  }
714
- var AGGREGATE_OPS_UNION = `"_count" | "_avg" | "_sum" | "_min" | "_max"`;
715
1003
  function emitModel(params) {
716
1004
  const { model, view, registry } = params;
717
1005
  const computedEntries = params.computed ?? [];
@@ -744,12 +1032,12 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
744
1032
  const countLine = ` readonly _count?: boolean | { readonly select: ${n}CountSelect };`;
745
1033
  const relationSelectValue = (relation) => `boolean | ${relation.target}${relation.isList ? "FindManyArgs" : "Args"}`;
746
1034
  if (hasCounts) {
747
- parts.push(objectAlias({
1035
+ parts.push(inputAlias({
748
1036
  name: `${n}CountSelect`,
749
- lines: listRelations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: boolean;`)
1037
+ lines: listRelations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: boolean | { readonly where?: ${relation.target}WhereInput };`)
750
1038
  }));
751
1039
  }
752
- parts.push(objectAlias({
1040
+ parts.push(inputAlias({
753
1041
  name: `${n}Select`,
754
1042
  lines: [
755
1043
  ...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`),
@@ -758,21 +1046,21 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
758
1046
  ...hasCounts ? [countLine] : []
759
1047
  ]
760
1048
  }));
761
- parts.push(objectAlias({
1049
+ parts.push(inputAlias({
762
1050
  name: `${n}Include`,
763
1051
  lines: [
764
1052
  ...model.relations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: ${relationSelectValue(relation)};`),
765
1053
  ...hasCounts ? [countLine] : []
766
1054
  ]
767
1055
  }));
768
- parts.push(objectAlias({
1056
+ parts.push(inputAlias({
769
1057
  name: `${n}OmitInput`,
770
1058
  lines: [
771
1059
  ...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`),
772
1060
  ...computedEntries.map((entry) => ` readonly ${propKey({ name: entry.field })}?: boolean;`)
773
1061
  ]
774
1062
  }));
775
- parts.push(objectAlias({
1063
+ parts.push(inputAlias({
776
1064
  name: `${n}Args`,
777
1065
  lines: [
778
1066
  ` readonly select?: ${n}Select;`,
@@ -781,7 +1069,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
781
1069
  ` readonly relationStrategy?: RelationStrategy;`
782
1070
  ]
783
1071
  }));
784
- parts.push(objectAlias({
1072
+ parts.push(inputAlias({
785
1073
  name: `${n}ListRelationFilter`,
786
1074
  lines: [
787
1075
  ` readonly some?: ${n}WhereInput;`,
@@ -789,7 +1077,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
789
1077
  ` readonly none?: ${n}WhereInput;`
790
1078
  ]
791
1079
  }));
792
- parts.push(objectAlias({
1080
+ parts.push(inputAlias({
793
1081
  name: `${n}RelationFilter`,
794
1082
  lines: [` readonly is?: ${n}WhereInput | null;`, ` readonly isNot?: ${n}WhereInput | null;`]
795
1083
  }));
@@ -815,7 +1103,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
815
1103
  whereLines.push(` readonly ${key}?: ${relation.target}RelationFilter | ${relation.target}WhereInput${relation.isOptional ? " | null" : ""};`);
816
1104
  }
817
1105
  }
818
- parts.push(objectAlias({ name: `${n}WhereInput`, lines: whereLines }));
1106
+ parts.push(inputAlias({ name: `${n}WhereInput`, lines: whereLines }));
819
1107
  if (model.uniques.length === 0) {
820
1108
  parts.push(`export type ${n}WhereUniqueInput = never;`);
821
1109
  } else {
@@ -831,19 +1119,19 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
831
1119
  | ${members.join(`
832
1120
  | `)};`);
833
1121
  }
834
- parts.push(objectAlias({
1122
+ parts.push(inputAlias({
835
1123
  name: `${n}OrderByInput`,
836
1124
  lines: model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: ${field.isOptional ? "SortOrderWithNulls" : "SortOrder"};`)
837
1125
  }));
838
- parts.push(objectAlias({ name: `${n}CreateInput`, lines: checkedCreateLines({ model }) }));
839
- parts.push(objectAlias({ name: `${n}UncheckedCreateInput`, lines: uncheckedCreateLines({ model }) }));
1126
+ parts.push(inputAlias({ name: `${n}CreateInput`, lines: checkedCreateLines({ model }) }));
1127
+ parts.push(inputAlias({ name: `${n}UncheckedCreateInput`, lines: uncheckedCreateLines({ model }) }));
840
1128
  parts.push(`export type ${n}CreateManyInput = ${n}UncheckedCreateInput;`);
841
- parts.push(objectAlias({ name: `${n}UpdateInput`, lines: checkedUpdateLines({ model }) }));
842
- parts.push(objectAlias({ name: `${n}UncheckedUpdateInput`, lines: uncheckedUpdateLines({ model }) }));
1129
+ parts.push(inputAlias({ name: `${n}UpdateInput`, lines: checkedUpdateLines({ model }) }));
1130
+ parts.push(inputAlias({ name: `${n}UncheckedUpdateInput`, lines: uncheckedUpdateLines({ model }) }));
843
1131
  parts.push(`export type ${n}UpdateManyInput = ${n}UncheckedUpdateInput;`);
844
1132
  const numericFields = model.fields.filter((field) => !field.isList && field.scalar !== null && NUMERIC_SCALARS.has(field.scalar));
845
1133
  const minmaxFields = model.fields.filter((field) => !field.isList && field.scalar !== null && MINMAX_SCALARS.has(field.scalar));
846
- parts.push(objectAlias({
1134
+ parts.push(inputAlias({
847
1135
  name: `${n}CountAggregateInput`,
848
1136
  lines: [
849
1137
  ...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`),
@@ -852,7 +1140,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
852
1140
  }));
853
1141
  const aggregateOpProps = [` readonly _count?: true | ${n}CountAggregateInput;`];
854
1142
  if (numericFields.length > 0) {
855
- parts.push(objectAlias({
1143
+ parts.push(inputAlias({
856
1144
  name: `${n}AvgAggregateInput`,
857
1145
  lines: numericFields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
858
1146
  }));
@@ -861,7 +1149,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
861
1149
  aggregateOpProps.push(` readonly _sum?: ${n}SumAggregateInput;`);
862
1150
  }
863
1151
  if (minmaxFields.length > 0) {
864
- parts.push(objectAlias({
1152
+ parts.push(inputAlias({
865
1153
  name: `${n}MinAggregateInput`,
866
1154
  lines: minmaxFields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
867
1155
  }));
@@ -869,7 +1157,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
869
1157
  aggregateOpProps.push(` readonly _min?: ${n}MinAggregateInput;`);
870
1158
  aggregateOpProps.push(` readonly _max?: ${n}MaxAggregateInput;`);
871
1159
  }
872
- parts.push(objectAlias({
1160
+ parts.push(inputAlias({
873
1161
  name: `${n}FindManyArgs`,
874
1162
  lines: [
875
1163
  ` readonly where?: ${n}WhereInput;`,
@@ -878,6 +1166,8 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
878
1166
  ` readonly include?: ${n}Include;`,
879
1167
  ` readonly omit?: ${n}OmitInput;`,
880
1168
  ` readonly cursor?: ${n}WhereUniqueInput;`,
1169
+ ` readonly after?: KeysetToken;`,
1170
+ ` readonly keyset?: KeysetOptions;`,
881
1171
  ` readonly take?: number;`,
882
1172
  ` readonly skip?: number;`,
883
1173
  ` readonly distinct?: ${orMany({ type: `${n}ScalarFieldEnum` })};`,
@@ -886,7 +1176,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
886
1176
  ]
887
1177
  }));
888
1178
  parts.push(`export type ${n}FindFirstArgs = ${n}FindManyArgs;`);
889
- parts.push(objectAlias({
1179
+ parts.push(inputAlias({
890
1180
  name: `${n}FindUniqueArgs`,
891
1181
  lines: [
892
1182
  ` readonly where: ${n}WhereUniqueInput;`,
@@ -897,7 +1187,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
897
1187
  ` readonly lock?: "update";`
898
1188
  ]
899
1189
  }));
900
- parts.push(objectAlias({
1190
+ parts.push(inputAlias({
901
1191
  name: `${n}CreateArgs`,
902
1192
  lines: [
903
1193
  ` readonly data: ${n}CreateInput | ${n}UncheckedCreateInput;`,
@@ -907,7 +1197,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
907
1197
  ]
908
1198
  }));
909
1199
  parts.push(`export type ${n}ConflictTarget = readonly ${n}ScalarFieldEnum[] | { readonly constraint: string };`);
910
- parts.push(objectAlias({
1200
+ parts.push(inputAlias({
911
1201
  name: `${n}CreateOnConflictInput`,
912
1202
  lines: [
913
1203
  ` readonly action: "nothing";`,
@@ -915,7 +1205,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
915
1205
  ` readonly where?: string;`
916
1206
  ]
917
1207
  }));
918
- parts.push(objectAlias({
1208
+ parts.push(inputAlias({
919
1209
  name: `${n}CreateOrSkipArgs`,
920
1210
  lines: [
921
1211
  ` readonly data: ${n}CreateInput | ${n}UncheckedCreateInput;`,
@@ -925,14 +1215,14 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
925
1215
  ` readonly omit?: ${n}OmitInput;`
926
1216
  ]
927
1217
  }));
928
- parts.push(objectAlias({
1218
+ parts.push(inputAlias({
929
1219
  name: `${n}CreateManyArgs`,
930
1220
  lines: [
931
1221
  ` readonly data: ${orMany({ type: `${n}CreateManyInput` })};`,
932
1222
  ` readonly skipDuplicates?: boolean | { readonly target?: ${n}ConflictTarget; readonly where?: string };`
933
1223
  ]
934
1224
  }));
935
- parts.push(objectAlias({
1225
+ parts.push(inputAlias({
936
1226
  name: `${n}CreateManyAndReturnArgs`,
937
1227
  lines: [
938
1228
  ` readonly data: ${orMany({ type: `${n}CreateManyInput` })};`,
@@ -941,10 +1231,21 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
941
1231
  ` readonly omit?: ${n}OmitInput;`
942
1232
  ]
943
1233
  }));
944
- parts.push(objectAlias({
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({
945
1246
  name: `${n}UpdateArgs`,
946
1247
  lines: [
947
- ` readonly where: ${n}WhereUniqueInput;`,
1248
+ ` readonly where: ${n}UpdateWhereInput;`,
948
1249
  ` readonly data: ${n}UpdateInput | ${n}UncheckedUpdateInput;`,
949
1250
  ` readonly select?: ${n}Select;`,
950
1251
  ` readonly include?: ${n}Include;`,
@@ -952,7 +1253,9 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
952
1253
  ` readonly skipUpdatedAt?: boolean;`
953
1254
  ]
954
1255
  }));
955
- parts.push(objectAlias({
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({
956
1259
  name: `${n}UpdateManyArgs`,
957
1260
  lines: [
958
1261
  ` readonly where?: ${n}WhereInput;`,
@@ -960,7 +1263,18 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
960
1263
  ` readonly skipUpdatedAt?: boolean;`
961
1264
  ]
962
1265
  }));
963
- parts.push(objectAlias({
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({
964
1278
  name: `${n}UpsertArgs`,
965
1279
  lines: [
966
1280
  ` readonly where: ${n}WhereUniqueInput;`,
@@ -972,7 +1286,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
972
1286
  ` readonly omit?: ${n}OmitInput;`
973
1287
  ]
974
1288
  }));
975
- parts.push(objectAlias({
1289
+ parts.push(inputAlias({
976
1290
  name: `${n}DeleteArgs`,
977
1291
  lines: [
978
1292
  ` readonly where: ${n}WhereUniqueInput;`,
@@ -981,10 +1295,11 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
981
1295
  ` readonly omit?: ${n}OmitInput;`
982
1296
  ]
983
1297
  }));
984
- parts.push(objectAlias({ name: `${n}DeleteManyArgs`, lines: [` readonly where?: ${n}WhereInput;`] }));
985
- parts.push(objectAlias({
1298
+ parts.push(inputAlias({ name: `${n}DeleteManyArgs`, lines: [` readonly where?: ${n}WhereInput;`] }));
1299
+ parts.push(inputAlias({
986
1300
  name: `${n}CountArgs`,
987
1301
  lines: [
1302
+ ` readonly select?: ${n}CountAggregateInput;`,
988
1303
  ` readonly where?: ${n}WhereInput;`,
989
1304
  ` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
990
1305
  ` readonly cursor?: ${n}WhereUniqueInput;`,
@@ -992,7 +1307,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
992
1307
  ` readonly skip?: number;`
993
1308
  ]
994
1309
  }));
995
- parts.push(objectAlias({
1310
+ parts.push(inputAlias({
996
1311
  name: `${n}AggregateArgs`,
997
1312
  lines: [
998
1313
  ` readonly where?: ${n}WhereInput;`,
@@ -1003,7 +1318,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
1003
1318
  ...aggregateOpProps
1004
1319
  ]
1005
1320
  }));
1006
- parts.push(objectAlias({
1321
+ parts.push(inputAlias({
1007
1322
  name: `${n}GroupByArgs`,
1008
1323
  lines: [
1009
1324
  ` readonly by: readonly ${n}ScalarFieldEnum[];`,
@@ -1015,20 +1330,26 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
1015
1330
  ...aggregateOpProps
1016
1331
  ]
1017
1332
  }));
1018
- const falseFilter = (source) => `${source}[K] extends false | undefined ? never : K`;
1019
- parts.push(`type ${n}OmitBase<A> = A extends { readonly omit: infer O extends object } ? Omit<${n}, OmittedKeys<O>> : ${n};`);
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};`);
1020
1338
  if (hasCounts) {
1021
1339
  const listUnion = listRelations.map((relation) => tsString({ value: relation.name })).join(" | ");
1022
1340
  const allCounts = listRelations.map((relation) => `${propKey({ name: relation.name })}: number`).join("; ");
1023
1341
  parts.push(`type ${n}CountResult<C> = C extends { readonly select: infer CS extends object }
1024
- ` + ` ? { [K in keyof CS & (${listUnion}) as ${falseFilter("CS")}]: number }
1342
+ ` + ` ? { [K in keyof CS & (${listUnion}) as ${exactPart("CS")}]: number } &
1343
+ ` + ` { [K in keyof CS & (${listUnion}) as ${uncertainPart("CS")}]?: number }
1025
1344
  ` + ` : { ${allCounts} };`);
1026
- parts.push(`type ${n}CountPayload<S> = S extends { readonly _count: infer C extends true | object } ? { _count: ${n}CountResult<C> } : unknown;`);
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;`);
1027
1348
  }
1028
1349
  if (hasRelations) {
1029
1350
  const relationCases = model.relations.map((relation) => {
1030
1351
  const nested = `${relation.target}GetPayload<A>`;
1031
- const guard = `A extends { readonly select: unknown } | { readonly include: unknown } | { readonly omit: unknown }`;
1352
+ const guard = `A extends object`;
1032
1353
  let expression;
1033
1354
  if (relation.isList) {
1034
1355
  expression = `${guard} ? ${nested}[] : ${relation.target}[]`;
@@ -1044,30 +1365,48 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
1044
1365
  ${relationCases}
1045
1366
  }[K];`);
1046
1367
  parts.push(`type ${n}IncludePayload<A> = A extends { readonly include: infer I extends object }
1047
- ` + ` ? { [K in keyof I & keyof ${n}Relations as ${falseFilter("I")}]: ${n}RelationPayload<K, I[K]> }${hasCounts ? ` & ${n}CountPayload<I>` : ""}
1048
- ` + ` : unknown;`);
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;`);
1049
1372
  parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1050
- ` + ` ? { [K in keyof S & ${selectableEnum} as ${falseFilter("S")}]: ${n}[K & keyof ${n}] } &
1051
- ` + ` { [K in keyof S & keyof ${n}Relations as ${falseFilter("S")}]: ${n}RelationPayload<K, S[K]> }${hasCounts ? ` &
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 ? ` &
1052
1377
  ${n}CountPayload<S>` : ""}
1378
+ ` + ` : "select" extends keyof A ? Partial<${n}>
1053
1379
  ` + ` : ${n}OmitBase<A> & ${n}IncludePayload<A>;`);
1054
1380
  } else {
1055
1381
  parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1056
- ` + ` ? { [K in keyof S & ${selectableEnum} as ${falseFilter("S")}]: ${n}[K & keyof ${n}] }
1057
- ` + ` : ${n}OmitBase<A>;`);
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>;`);
1058
1385
  }
1059
- parts.push(`export type ${n}AggregateResult<A> = {
1060
- ` + ` [K in keyof A & (${AGGREGATE_OPS_UNION})]:
1061
- ` + ` K extends "_count" ? (A[K] extends true ? number : { [F in keyof A[K]]: number })
1062
- ` + ` : K extends "_avg" ? { [F in keyof A[K]]: number | null }
1063
- ` + ` : { [F in keyof A[K] & keyof ${n}]: ${n}[F & keyof ${n}] | null };
1064
- ` + `};`);
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]>> };`);
1065
1404
  parts.push(`export type ${n}GroupByResult<A extends ${n}GroupByArgs> =
1066
- ` + ` { [K in A["by"][number]]: ${n}[K & keyof ${n}] } & ${n}AggregateResult<A>;`);
1405
+ ` + ` (number extends A["by"]["length"] ? Partial<Pick<${n}, A["by"][number]>> : Pick<${n}, A["by"][number]>) & ${n}AggregateResult<A>;`);
1067
1406
  const readMethods = [
1068
- ` findMany<A extends ${n}FindManyArgs = ${n}FindManyArgs>(args?: A): Promise<${n}GetPayload<A>[]>;`,
1069
- ` findFirst<A extends ${n}FindFirstArgs = ${n}FindFirstArgs>(args?: A): Promise<${n}GetPayload<A> | null>;`,
1070
- ` findFirstOrThrow<A extends ${n}FindFirstArgs = ${n}FindFirstArgs>(args?: A): Promise<${n}GetPayload<A>>;`,
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>>;`,
1071
1410
  ` findUnique<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A> | null>;`,
1072
1411
  ` findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A>>;`
1073
1412
  ];
@@ -1078,14 +1417,16 @@ ${relationCases}
1078
1417
  ` createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
1079
1418
  ` update<A extends ${n}UpdateArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1080
1419
  ` updateMany(args: ${n}UpdateManyArgs): Promise<BatchPayload>;`,
1420
+ ` updateManyAndReturn<A extends ${n}UpdateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
1081
1421
  ` upsert<A extends ${n}UpsertArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1422
+ ` upsertMany(args: ${n}UpsertManyArgs): Promise<UpsertManyPayload>;`,
1082
1423
  ` delete<A extends ${n}DeleteArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1083
1424
  ` deleteMany(args?: ${n}DeleteManyArgs): Promise<BatchPayload>;`
1084
1425
  ];
1085
1426
  const aggregateMethods = [
1086
- ` count(args?: ${n}CountArgs): Promise<number>;`,
1427
+ ` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;`,
1087
1428
  ` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;`,
1088
- ` groupBy<A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;`
1429
+ ` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;`
1089
1430
  ];
1090
1431
  const delegateMethods = model.isView ? [...readMethods, ...aggregateMethods] : [...readMethods, ...writeMethods, ...aggregateMethods];
1091
1432
  delegateMethods.push(...params.extensionLines ?? []);
@@ -1154,49 +1495,54 @@ function emitViewTypesForModel(params) {
1154
1495
  ` readonly relationStrategy?: RelationStrategy;`
1155
1496
  ]
1156
1497
  }));
1157
- const falseFilter = (source) => `${source}[K] extends false | undefined ? never : K`;
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`;
1158
1500
  parts.push(`type ${n}ViewSelectScalars<F, A> = A extends { readonly select: infer S extends object }
1159
- ` + ` ? { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${falseFilter("S")}]: ${n}[K & keyof ${n}] }
1160
- ` + ` : ${n}ViewScalars<F>;`);
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>;`);
1161
1504
  parts.push(`type ${n}ViewReveal<A> = A extends { readonly reveal: infer R extends object }
1162
- ` + ` ? { [K in keyof R & ${n}ScalarFieldEnum as ${falseFilter("R")}]: ${n}[K & keyof ${n}] }
1163
- ` + ` : unknown;`);
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;`);
1164
1508
  if (hasRelations) {
1165
1509
  const relationCases = model.relations.map((relation) => {
1166
1510
  const target = relation.target;
1167
1511
  const targetClientKey = view.modelByName.get(target)?.clientName ?? target;
1168
1512
  const guard = `D extends { readonly ${propKey({ name: targetClientKey })}: infer RF extends object }`;
1169
- const masked = `ViewRow<${target}ViewScalars<RF>>`;
1513
+ const masked = `ViewRow<${target}ViewSelectScalars<RF, A> & ${target}ViewReveal<A>>`;
1514
+ const unmasked = `(A extends object ? ${target}GetPayload<A> : ${target})`;
1170
1515
  let expression;
1171
1516
  if (relation.isList) {
1172
- expression = `${guard} ? ${masked}[] : ${target}[]`;
1517
+ expression = `${guard} ? ${masked}[] : ${unmasked}[]`;
1173
1518
  } else if (relation.isOptional) {
1174
- expression = `(${guard} ? ${masked} : ${target}) | null`;
1519
+ expression = `(${guard} ? ${masked} : ${unmasked}) | null`;
1175
1520
  } else {
1176
- expression = `${guard} ? ${masked} : ${target}`;
1521
+ expression = `${guard} ? ${masked} : ${unmasked}`;
1177
1522
  }
1178
1523
  return ` ${propKey({ name: relation.name })}: ${expression};`;
1179
1524
  }).join(`
1180
1525
  `);
1181
- parts.push(`type ${n}ViewRelationResult<K extends keyof ${n}Relations, D> = {
1526
+ parts.push(`type ${n}ViewRelationResult<K extends keyof ${n}Relations, D, A> = {
1182
1527
  ${relationCases}
1183
1528
  }[K];`);
1184
1529
  parts.push(`type ${n}ViewIncludeResult<A, D> = A extends { readonly include: infer I extends object }
1185
- ` + ` ? { [K in keyof I & keyof ${n}Relations as ${falseFilter("I")}]: ${n}ViewRelationResult<K, D> }
1186
- ` + ` : unknown;`);
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;`);
1187
1533
  parts.push(`export type ${n}ViewResult<F, A, D> = ViewRow<${n}ViewSelectScalars<F, A> & ${n}ViewReveal<A> & ${n}ViewIncludeResult<A, D>>;`);
1188
1534
  } else {
1189
1535
  parts.push(`export type ${n}ViewResult<F, A, D> = ViewRow<${n}ViewSelectScalars<F, A> & ${n}ViewReveal<A>>;`);
1190
1536
  }
1191
1537
  parts.push(`export type ${n}ViewDelegate<F, D extends ViewDefinitionInput = ViewDefinitionInput> = {
1192
- ` + ` findMany<A extends ${n}ViewFindManyArgs = ${n}ViewFindManyArgs>(args?: A): Promise<${n}ViewResult<F, A, D>[]>;
1193
- ` + ` findFirst<A extends ${n}ViewFindManyArgs = ${n}ViewFindManyArgs>(args?: A): Promise<${n}ViewResult<F, A, D> | null>;
1194
- ` + ` findFirstOrThrow<A extends ${n}ViewFindManyArgs = ${n}ViewFindManyArgs>(args?: A): Promise<${n}ViewResult<F, A, D>>;
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>>;
1195
1541
  ` + ` findUnique<A extends ${n}ViewFindUniqueArgs>(args: A): Promise<${n}ViewResult<F, A, D> | null>;
1196
1542
  ` + ` findUniqueOrThrow<A extends ${n}ViewFindUniqueArgs>(args: A): Promise<${n}ViewResult<F, A, D>>;
1197
- ` + ` count(args?: ${n}CountArgs): Promise<number>;
1543
+ ` + ` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;
1198
1544
  ` + ` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;
1199
- ` + ` groupBy<A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;
1545
+ ` + ` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;
1200
1546
  ` + `};`);
1201
1547
  return parts.join(`
1202
1548
 
@@ -1243,9 +1589,176 @@ function jsonTypeImportLines(params) {
1243
1589
  }
1244
1590
  return [...bySpecifier.keys()].sort().map((from) => `import type { ${[...bySpecifier.get(from)].sort().join(", ")} } from ${tsString({ value: from })};`);
1245
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
+ }
1246
1757
  function emitClient(params) {
1247
1758
  const { view, baked, hasComputed } = params;
1759
+ const nativeRls = hasNativeRls({ view });
1248
1760
  const parts = [banner({ title: "Client" })];
1761
+ parts.push(telemetryDeclarations());
1249
1762
  parts.push(objectAlias({
1250
1763
  name: "VibeClientOptions",
1251
1764
  lines: [
@@ -1256,6 +1769,10 @@ function emitClient(params) {
1256
1769
  ` readonly defaultOrderByPk?: boolean;`,
1257
1770
  ` /** Append the primary key as a tie-breaker to \`distinct\` ordering (default true) \u2014 pins WHICH row represents each distinct key. */`,
1258
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;`,
1259
1776
  ...baked.empty ? [] : [
1260
1777
  ` /** The live extension instances \u2014 verified against the baked manifest. */`,
1261
1778
  ` readonly extensions?: readonly VibeExtension[];`
@@ -1263,18 +1780,79 @@ function emitClient(params) {
1263
1780
  ...hasComputed ? [
1264
1781
  ` /** The live computed-field config \u2014 verified against the baked manifest (VIBE_STALE_CLIENT). */`,
1265
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 };`
1266
1787
  ] : []
1267
1788
  ]
1268
1789
  }));
1269
- const delegateProps = view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`);
1270
- const modelsProps = view.models.flatMap((model) => {
1271
- const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`;
1272
- return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${model.name}Delegate;`];
1273
- });
1274
1790
  const clientExtensionLines = baked.clientDts.length === 0 ? "" : `${baked.clientDts.join(`
1275
1791
  `)}
1276
1792
  `;
1277
- parts.push(`export type VibeClientInstance = {
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 = {
1278
1856
  ` + `${delegateProps.join(`
1279
1857
  `)}
1280
1858
  ` + ` /** Delegates keyed by BOTH camelCase client name and model name \u2014 the same objects as the top-level properties (dynamic access). */
@@ -1282,29 +1860,29 @@ function emitClient(params) {
1282
1860
  ${modelsProps.join(`
1283
1861
  `)}
1284
1862
  };
1285
- ` + ` /** Subscribe to client events ("query" fires after every statement, with the same payload as onQuery \u2014 both fire). Returns an unsubscribe function. */
1863
+ ` + ` /** Subscribe to client events \u2014 shared with the base client. */
1286
1864
  ` + ` readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
1287
- ` + ` /** 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. */
1865
+ ` + ` /** ONE transaction; the callback receives a handle that INHERITS the scope. The array form takes un-awaited scoped delegate calls. */
1288
1866
  ` + ` readonly $transaction: {
1289
- ` + ` <T>(fn: (tx: VibeClientInstance) => Promise<T>, options?: TransactionOptions): Promise<T>;
1867
+ ` + ` <T>(fn: (tx: ScopedClientInstance) => Promise<T>, options?: TransactionOptions): Promise<T>;
1290
1868
  ` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
1291
1869
  ` + ` };
1292
- ` + ` readonly $queryRaw: <T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]) => Promise<T[]>;
1293
- ` + ` readonly $executeRaw: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
1294
- ` + ` readonly $queryRawUnsafe: <T = Record<string, unknown>>(text: string, ...values: unknown[]) => Promise<T[]>;
1295
- ` + ` readonly $executeRawUnsafe: (text: string, ...values: unknown[]) => Promise<number>;
1296
1870
  ` + ` readonly $connect: () => Promise<void>;
1297
1871
  ` + ` readonly $disconnect: () => Promise<void>;
1298
- ` + ` /** Field-masking views (v1 defineView parity): a read-only masked projection of models. */
1872
+ ` + ` /** Field-masking views compose over the scoped handle (columns \xD7 rows). */
1299
1873
  ` + ` readonly $defineView: <D extends ViewDefinitionInput>(options: { readonly name: string; readonly definition: D }) => ViewClient<D>;
1300
- ` + policyClientLines({ view }) + clientExtensionLines + `};`);
1301
- parts.push(`/** Build a typed client over a database adapter. */
1302
- export declare function VibeClient(options: VibeClientOptions): VibeClientInstance;`);
1303
- parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
1304
- export declare const schema: SchemaIR;`);
1305
- return parts.join(`
1306
-
1307
- `);
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
+ `;
1308
1886
  }
1309
1887
  function policyClientLines(params) {
1310
1888
  const policied = params.view.models.filter((model) => model.policy !== undefined);
@@ -1336,24 +1914,27 @@ function emitDts(params) {
1336
1914
  computedByModel.set(entry.model, list);
1337
1915
  }
1338
1916
  const parts = [];
1339
- parts.push(`// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
1340
- // Regenerate with \`vibeorm generate\` after every schema change.`);
1917
+ parts.push(GENERATED_HEADER);
1341
1918
  const schemaTypeImports = [
1342
1919
  "SchemaIR",
1343
1920
  ...baked.empty ? [] : ["VibeExtension"],
1344
1921
  ...hasComputed ? ["ComputedConfig"] : []
1345
1922
  ].sort();
1346
1923
  const importLines = [
1347
- `import type { DatabaseAdapter, DbNow, QueryEvent, TransactionOptions } from "@vibeorm/runtime";`,
1924
+ `import type { DatabaseAdapter, DbNow, QueryEvent, Skip, TransactionOptions, WriteValidators } from "@vibeorm/runtime";`,
1348
1925
  `import type { ${schemaTypeImports.join(", ")} } from "@vibeorm/schema";`,
1349
1926
  ...jsonTypeImportLines({ view }),
1350
- ...baked.dtsImports
1927
+ ...baked.dtsImports,
1928
+ `import { ${enumEntryExports({ view }).join(", ")} } from "./enums.js";`
1351
1929
  ];
1352
1930
  parts.push(`${importLines.join(`
1353
1931
  `)}
1354
1932
 
1355
1933
  ` + `export { VibeError } from "@vibeorm/schema";
1356
- export type { VibeErrorCode } 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(", ")} };`);
1357
1938
  parts.push(PREAMBLE);
1358
1939
  parts.push(...filterSection());
1359
1940
  for (const declared of view.enums)
@@ -1383,6 +1964,8 @@ export type { VibeErrorCode } from "@vibeorm/schema";`);
1383
1964
  parts.push(...baked.moduleDts);
1384
1965
  }
1385
1966
  parts.push(...emitViewSection({ view }));
1967
+ parts.push(...emitScopedSection({ view }));
1968
+ parts.push(...emitRlsSection({ view }));
1386
1969
  parts.push(emitClient({ view, baked, hasComputed }));
1387
1970
  return `${parts.join(`
1388
1971
 
@@ -1390,82 +1973,9 @@ export type { VibeErrorCode } from "@vibeorm/schema";`);
1390
1973
  `;
1391
1974
  }
1392
1975
 
1393
- // packages/generator/src/emit-js.ts
1394
- function emitJs(params) {
1395
- const { view } = params;
1396
- const baked = params.baked ?? EMPTY_BAKED_EXTENSIONS;
1397
- const computed = params.computed ?? [];
1398
- const hasComputed = computed.length > 0;
1399
- const parts = [];
1400
- parts.push(`// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
1401
- // Regenerate with \`vibeorm generate\` after every schema change.`);
1402
- parts.push(`import { createClient } from "@vibeorm/runtime";`);
1403
- if (baked.jsImports.length > 0)
1404
- parts.push(baked.jsImports.join(`
1405
- `));
1406
- parts.push(`export { VibeError } from "@vibeorm/schema";`);
1407
- parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
1408
- export const schema = ${stableStringify({ value: view.schema })};`);
1409
- for (const declared of view.enums) {
1410
- const entries = declared.values.map((value) => ` ${tsString({ value })}: ${tsString({ value })},`).join(`
1411
- `);
1412
- parts.push(`export const ${declared.name} = Object.freeze({
1413
- ${entries}
1414
- });`);
1415
- }
1416
- if (baked.empty && !hasComputed) {
1417
- parts.push(`/** Build a typed client over a database adapter. */
1418
- ` + `export function VibeClient(options) {
1419
- ` + ` const { adapter, ...clientOptions } = options;
1420
- ` + ` return createClient({ schema, adapter, options: clientOptions });
1421
- ` + `}`);
1422
- return `${parts.join(`
1423
-
1424
- `)}
1425
- `;
1426
- }
1427
- if (!baked.empty) {
1428
- parts.push(`/** Extension manifest \u2014 verified against the live extension list (VIBE_STALE_CLIENT). */
1429
- ` + `const extensionManifest = ${stableStringify({ value: baked.manifest })};`);
1430
- parts.push(`/** Baked extension surfaces (extensions.md D9). */
1431
- ` + `const extensionMounts = [
1432
- ${baked.mounts.map((mount) => ` ${mount},`).join(`
1433
- `)}
1434
- ];`);
1435
- }
1436
- if (hasComputed) {
1437
- parts.push(`/** Computed-field manifest \u2014 verified against the live computed config (VIBE_STALE_CLIENT). */
1438
- ` + `const computedManifest = ${stableStringify({ value: computed })};`);
1439
- }
1440
- const destructured = [
1441
- "adapter",
1442
- ...baked.empty ? [] : ["extensions"],
1443
- ...hasComputed ? ["computed"] : [],
1444
- "...clientOptions"
1445
- ].join(", ");
1446
- const createLines = [
1447
- ` schema,`,
1448
- ` adapter,`,
1449
- ` options: clientOptions,`,
1450
- ...baked.empty ? [] : [` extensions: { manifest: extensionManifest, mounts: extensionMounts, instances: extensions ?? [] },`],
1451
- ...hasComputed ? [` computed: { manifest: computedManifest, specs: computed },`] : []
1452
- ];
1453
- parts.push(`/** Build a typed client over a database adapter. */
1454
- ` + `export function VibeClient(options) {
1455
- ` + ` const { ${destructured} } = options;
1456
- ` + ` return createClient({
1457
- ${createLines.join(`
1458
- `)}
1459
- });
1460
- ` + `}`);
1461
- return `${parts.join(`
1462
-
1463
- `)}
1464
- `;
1465
- }
1466
-
1467
1976
  // packages/generator/src/schema-view.ts
1468
- import { VibeError as VibeError4, assertValidSchemaIR, isEnumType } from "@vibeorm/schema";
1977
+ import { VibeError as VibeError4, assertValidSchemaIR, enumStorageOf, isEnumType } from "@vibeorm/schema";
1978
+ import { postgresDialect, renderRlsPolicy } from "@vibeorm/sql";
1469
1979
  var JSON_TYPE_RE = /@type\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+"([^"]+)"\s*\)/;
1470
1980
  function parseJsonTypeAnnotation(params) {
1471
1981
  if (params.comment === undefined)
@@ -1573,20 +2083,34 @@ function buildModelView(params) {
1573
2083
  ...model.policy === undefined ? {} : { policy: model.policy }
1574
2084
  };
1575
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
+ }
1576
2093
  function buildSchemaView(params) {
1577
2094
  const { schema } = params;
1578
2095
  assertValidSchemaIR({ schema });
2096
+ if (schema.rls !== undefined)
2097
+ assertRlsRenderable({ schema });
1579
2098
  const normalized = {
1580
2099
  datasource: schema.datasource,
1581
2100
  models: [...schema.models].sort(byName),
1582
2101
  enums: [...schema.enums].sort(byName),
1583
- extensions: [...schema.extensions].sort(byName)
2102
+ extensions: [...schema.extensions].sort(byName),
2103
+ ...schema.rls === undefined ? {} : { rls: schema.rls }
1584
2104
  };
1585
2105
  const modelIRByName = new Map(normalized.models.map((model) => [model.name, model]));
1586
2106
  const models = normalized.models.map((model) => buildModelView({ model, modelIRByName }));
1587
2107
  return {
1588
2108
  schema: normalized,
1589
- enums: normalized.enums.map((declared) => ({ name: declared.name, values: declared.values })),
2109
+ enums: normalized.enums.map((declared) => ({
2110
+ name: declared.name,
2111
+ values: declared.values,
2112
+ storage: enumStorageOf({ enumDef: declared })
2113
+ })),
1590
2114
  models,
1591
2115
  modelByName: new Map(models.map((model) => [model.name, model]))
1592
2116
  };
@@ -1609,6 +2133,8 @@ async function generateClient(params) {
1609
2133
  const outputs = [
1610
2134
  { name: "index.js", content: emitJs({ view, baked, computed }) },
1611
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 }) },
1612
2138
  { name: "README.md", content: README }
1613
2139
  ];
1614
2140
  await mkdir(outDir, { recursive: true });
@@ -1625,4 +2151,4 @@ export {
1625
2151
  bakeExtensions
1626
2152
  };
1627
2153
 
1628
- //# debugId=45D379E2CD1AA90564756E2164756E21
2154
+ //# debugId=AC114FAAB4DBE66364756E2164756E21