@vibeorm/generator 2.3.0 → 3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VibeORM contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -23,10 +23,21 @@ import type { SchemaView } from "./schema-view.ts";
23
23
  export declare function emitEnumsDts(params: {
24
24
  view: SchemaView;
25
25
  }): string;
26
+ /** Refuse compiler-proven logical input names without invalidating SchemaIR. */
27
+ export declare function assertGeneratedInputNames(params: {
28
+ view: SchemaView;
29
+ computed?: readonly ComputedManifestEntry[];
30
+ }): void;
26
31
  /** Emit the full index.d.ts for a resolved schema view. */
27
32
  export declare function emitDts(params: {
28
33
  view: SchemaView;
29
34
  baked?: BakedExtensions;
30
35
  computed?: readonly ComputedManifestEntry[];
31
36
  }): string;
37
+ /** @internal Admit before other emitters, then render in the existing output order. */
38
+ export declare function prepareDts(params: {
39
+ view: SchemaView;
40
+ baked?: BakedExtensions;
41
+ computed?: readonly ComputedManifestEntry[];
42
+ }): () => string;
32
43
  //# sourceMappingURL=emit-dts.d.ts.map
@@ -17,6 +17,7 @@ export type GenerateResult = {
17
17
  * Generate the client module for a schema into `outDir` (created if missing).
18
18
  *
19
19
  * @throws VibeError `VIBE_SCHEMA_INVALID` when the IR fails validation.
20
+ * @throws VibeError `VIBE_UNSUPPORTED_CAPABILITY` for logical members that cannot be represented by generated inputs.
20
21
  */
21
22
  export declare function generateClient(params: {
22
23
  schema: SchemaIR;
package/dist/index.js CHANGED
@@ -32,10 +32,13 @@ function maybeParens(params) {
32
32
  return type.includes("|") ? `(${type})` : type;
33
33
  }
34
34
  function stableStringify(params) {
35
- return renderValue({ value: params.value, indent: "" });
35
+ return renderValue({ value: params.value, indent: "", javascript: false });
36
+ }
37
+ function stableJs(params) {
38
+ return renderValue({ value: params.value, indent: "", javascript: true });
36
39
  }
37
40
  function renderValue(params) {
38
- const { value, indent } = params;
41
+ const { value, indent, javascript } = params;
39
42
  if (value === null)
40
43
  return "null";
41
44
  const kind = typeof value;
@@ -45,7 +48,7 @@ function renderValue(params) {
45
48
  if (value.length === 0)
46
49
  return "[]";
47
50
  const inner = `${indent} `;
48
- const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
51
+ const items = value.map((item) => inner + renderValue({ value: item, indent: inner, javascript }));
49
52
  return `[
50
53
  ${items.join(`,
51
54
  `)}
@@ -57,7 +60,7 @@ ${indent}]`;
57
60
  if (keys.length === 0)
58
61
  return "{}";
59
62
  const inner = `${indent} `;
60
- const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
63
+ const items = keys.map((key) => `${inner}${javascript && key === "__proto__" ? `[${JSON.stringify(key)}]` : JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner, javascript })}`);
61
64
  return `{
62
65
  ${items.join(`,
63
66
  `)}
@@ -119,7 +122,7 @@ export { sql, decode, sqlModels, sqlProjection } from "./index.js";
119
122
  export const databaseModule = defineDatabaseModule({ ...${JSON.stringify(module)}, schema, manifest: ${JSON.stringify(manifest)} });
120
123
  ` },
121
124
  { name: "schema.js", content: `// Generated data-only schema and runtime manifest.
122
- export const schema = ${serialized};
125
+ export const schema = ${stableJs({ value: view.schema })};
123
126
  export const moduleManifest = ${JSON.stringify(manifest)};
124
127
  ` },
125
128
  { name: "schema.d.ts", content: `import type { SchemaIR, DatabaseModuleManifest } from "@vibeorm/schema";
@@ -371,7 +374,7 @@ ${validatorLines}
371
374
  function serializeSchema(params) {
372
375
  const { schema } = params;
373
376
  if (schema.rls === undefined)
374
- return stableStringify({ value: schema });
377
+ return stableJs({ value: schema });
375
378
  return renderIr({ value: schema, indent: "", ordered: false });
376
379
  }
377
380
  function renderIr(params) {
@@ -400,7 +403,7 @@ ${indent}]`;
400
403
  if (keys.length === 0)
401
404
  return "{}";
402
405
  const inner = `${indent} `;
403
- const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderIr({ value: record[key], indent: inner, ordered: ordered || key === "rls" })}`);
406
+ const items = keys.map((key) => `${inner}${key === "__proto__" ? `[${JSON.stringify(key)}]` : JSON.stringify(key)}: ${renderIr({ value: record[key], indent: inner, ordered: ordered || key === "rls" })}`);
404
407
  return `{
405
408
  ${items.join(`,
406
409
  `)}
@@ -441,7 +444,7 @@ ${view.models.map((model) => ` [${tsString({ value: model.clientName })}]: (opt
441
444
  }
442
445
  if (!baked.empty) {
443
446
  parts.push(`/** Extension manifest \u2014 verified against the live extension list (VIBE_STALE_CLIENT). */
444
- ` + `const extensionManifest = ${stableStringify({ value: baked.manifest })};`);
447
+ ` + `const extensionManifest = ${stableJs({ value: baked.manifest })};`);
445
448
  parts.push(`/** Baked extension surfaces (extensions.md D9). */
446
449
  ` + `const extensionMounts = [
447
450
  ${baked.mounts.map((mount) => ` ${mount},`).join(`
@@ -450,7 +453,7 @@ ${baked.mounts.map((mount) => ` ${mount},`).join(`
450
453
  }
451
454
  if (hasComputed) {
452
455
  parts.push(`/** Computed-field manifest \u2014 verified against the live computed config (VIBE_STALE_CLIENT). */
453
- ` + `const computedManifest = ${stableStringify({ value: computed })};`);
456
+ ` + `const computedManifest = ${stableJs({ value: computed })};`);
454
457
  }
455
458
  const destructured = [
456
459
  "adapter",
@@ -536,6 +539,15 @@ function inputBaseType(params) {
536
539
  return "Date | string";
537
540
  return field.enumName ?? INPUT_TYPES[field.scalar];
538
541
  }
542
+ function aggregateOperandType(params) {
543
+ const { field, key } = params;
544
+ if (key === "_count")
545
+ return "number";
546
+ if (key === "_avg")
547
+ return "number";
548
+ const value = field.scalar === "DateTime" ? "Date | string" : inputBaseType({ field });
549
+ return value;
550
+ }
539
551
  function filterName(params) {
540
552
  const { field } = params;
541
553
  const prefix = field.enumName ?? FILTER_PREFIX[field.scalar];
@@ -591,6 +603,12 @@ function orMany(params) {
591
603
  const { type } = params;
592
604
  return `${type} | readonly ${maybeParens({ type })}[]`;
593
605
  }
606
+ function aggregatePredicateType(params) {
607
+ const { value } = params;
608
+ const skip = params.skippable === false ? "" : " | Skip";
609
+ const operators = ["equals", "not", "gt", "gte", "lt", "lte"];
610
+ return `${value} | { ${operators.map((operator) => `readonly ${operator}?: ${value}${skip}`).join("; ")} }`;
611
+ }
594
612
  function payloadFieldType(params) {
595
613
  const { field } = params;
596
614
  const base = payloadBaseType({ field });
@@ -647,11 +665,18 @@ export type KeysetPage<T> = { readonly items: T[]; readonly nextCursor: KeysetTo
647
665
  export type KeysetOptions = {
648
666
  /**
649
667
  * Asserts that every DateTime ordering column in this query holds
650
- * millisecond-exact values. Required on postgres and mysql, whose timestamps
651
- * may be finer than a JavaScript Date; NOT safe for \`@default(now())\`
652
- * columns on postgres, which are microsecond-precise.
668
+ * millisecond-exact values. Needed on postgres and mysql only for a column
669
+ * that may be finer than a JavaScript Date \u2014 one without a declared
670
+ * precision of 0-3; a column declared \`@db.Timestamptz(3)\` needs none. NOT
671
+ * safe for a microsecond \`@default(now())\` column.
653
672
  */
654
673
  readonly timestampPrecision?: "milliseconds";
674
+ /**
675
+ * The owner a page token belongs to (a user or tenant id). Mixed into the
676
+ * token's query binding beside the filters, so a token minted for one scope
677
+ * is refused for another. Encoded, not secret.
678
+ */
679
+ readonly scope?: string;
655
680
  };
656
681
 
657
682
  /** Result of createMany/updateMany/deleteMany. */
@@ -679,6 +704,21 @@ export type UpsertManyPayload = { readonly count: number; readonly affectedRows:
679
704
  */
680
705
  type Provided<T> = Exclude<T, Skip | undefined>;
681
706
 
707
+ /** An absent projection member supplies the inactive fallback value. */
708
+ type SelectionMember<S, K> = K extends keyof S ? S[K] : undefined;
709
+
710
+ /** Object-bag keys only; primitive SKIP contributes no Symbol members. */
711
+ type BagKeys<S> = S extends object ? keyof S : never;
712
+
713
+ /** Read only object bags, preserving unions and optional bag omission. */
714
+ type SelectionValue<S, K> = S extends object ? SelectionMember<S, K> : undefined;
715
+
716
+ /** Only an active included relation replaces the selected relation. */
717
+ type IncludedRelation<S, I> = I extends false | undefined | Skip ? S : I;
718
+
719
+ /** Count include overrides even with false; omitted/nullish values fall back. */
720
+ type IncludedCount<S, I> = I extends null | undefined | Skip ? S : I;
721
+
682
722
  /** Keys of an omit object whose value is exactly true \u2014 the scalars the result drops. */
683
723
  type OmittedKeys<O> = keyof { [K in keyof O as O[K] extends true ? K : never]: 0 };
684
724
 
@@ -1139,7 +1179,7 @@ function registerNestedContext(params) {
1139
1179
  ` readonly connectOrCreate?: ${connectOrCreateName};`,
1140
1180
  ` readonly update?: ${updateUnion};`,
1141
1181
  ` readonly upsert?: { readonly create: ${createUnion}; readonly update: ${updateUnion} };`,
1142
- ...allowDetach ? [` readonly disconnect?: boolean;`, ` readonly delete?: boolean;`] : []
1182
+ ...allowDetach ? [` readonly disconnect?: boolean | ${t}WhereInput;`, ` readonly delete?: boolean | ${t}WhereInput;`] : []
1143
1183
  ]
1144
1184
  })
1145
1185
  });
@@ -1161,7 +1201,7 @@ ${[
1161
1201
  ].join(`
1162
1202
  `)}
1163
1203
  };`);
1164
- parts.push(`export type ${n}ScalarFieldEnum = ${model.fields.map((field) => tsString({ value: field.name })).join(" | ")};`);
1204
+ parts.push(`export type ${n}ScalarFieldEnum = ${literalUnion({ members: model.fields.map((field) => field.name) }) || "never"};`);
1165
1205
  if (computedEntries.length > 0) {
1166
1206
  parts.push(`export type ${n}SelectableFieldEnum = ${n}ScalarFieldEnum | ${computedEntries.map((entry) => tsString({ value: entry.field })).join(" | ")};`);
1167
1207
  }
@@ -1286,7 +1326,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
1286
1326
  parts.push(inputAlias({ name: `${n}UncheckedUpdateInput`, lines: uncheckedUpdateLines({ model }) }));
1287
1327
  parts.push(inputAlias({ name: `${n}UpdateManyInput`, lines: uncheckedUpdateLines({ model, scalarOnly: true }) }));
1288
1328
  const numericFields = model.fields.filter((field) => !field.isList && field.scalar !== null && NUMERIC_SCALARS.has(field.scalar));
1289
- const minmaxFields = model.fields.filter((field) => !field.isList && field.scalar !== null && MINMAX_SCALARS.has(field.scalar));
1329
+ const minmaxFields = model.fields.filter((field) => !field.isList && (field.enumName !== null || field.scalar !== null && MINMAX_SCALARS.has(field.scalar)));
1290
1330
  parts.push(inputAlias({
1291
1331
  name: `${n}CountAggregateInput`,
1292
1332
  lines: [
@@ -1313,6 +1353,44 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
1313
1353
  aggregateOpProps.push(` readonly _min?: ${n}MinAggregateInput;`);
1314
1354
  aggregateOpProps.push(` readonly _max?: ${n}MaxAggregateInput;`);
1315
1355
  }
1356
+ const groupAggregates = [
1357
+ { key: "_count", prefix: "Count", fields: model.fields },
1358
+ ...numericFields.length > 0 ? [
1359
+ { key: "_avg", prefix: "Avg", fields: numericFields },
1360
+ { key: "_sum", prefix: "Sum", fields: numericFields }
1361
+ ] : [],
1362
+ ...minmaxFields.length > 0 ? [
1363
+ { key: "_min", prefix: "Min", fields: minmaxFields },
1364
+ { key: "_max", prefix: "Max", fields: minmaxFields }
1365
+ ] : []
1366
+ ];
1367
+ const groupAggregateFields = new Map(groupAggregates.map((aggregate) => [aggregate, new Set(aggregate.fields)]));
1368
+ const havingLines = [
1369
+ ` readonly AND?: ${orMany({ type: `${n}HavingInput` })};`,
1370
+ ` readonly OR?: ${orMany({ type: `${n}HavingInput` })};`,
1371
+ ` readonly NOT?: ${orMany({ type: `${n}HavingInput` })};`
1372
+ ];
1373
+ const groupOrderLines = [...scalarOrderLines];
1374
+ for (const aggregate of groupAggregates) {
1375
+ const havingFields = aggregate.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: ${aggregatePredicateType({ value: aggregateOperandType({ field, key: aggregate.key }) })};`);
1376
+ const orderFields = aggregate.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: SortOrder;`);
1377
+ if (aggregate.key === "_count") {
1378
+ havingFields.push(` readonly _all?: ${aggregatePredicateType({ value: "number" })};`);
1379
+ orderFields.push(` readonly _all?: SortOrder;`);
1380
+ }
1381
+ parts.push(inputAlias({ name: `${n}${aggregate.prefix}AggregateHavingInput`, lines: havingFields }));
1382
+ parts.push(inputAlias({ name: `${n}${aggregate.prefix}AggregateOrderByInput`, lines: orderFields }));
1383
+ havingLines.push(` readonly ${aggregate.key}?: ${n}${aggregate.prefix}AggregateHavingInput;`);
1384
+ groupOrderLines.push(` readonly ${aggregate.key}?: ${n}${aggregate.prefix}AggregateOrderByInput;`);
1385
+ }
1386
+ for (const field of model.fields) {
1387
+ const aggregates = groupAggregates.filter((aggregate) => groupAggregateFields.get(aggregate).has(field)).map((aggregate) => `readonly ${aggregate.key}?: ${aggregatePredicateType({ value: aggregateOperandType({ field, key: aggregate.key }), skippable: field.scalar !== "Json" })} | Skip`);
1388
+ const filter = `(${filterName({ field })} & { ${aggregates.join("; ")} })`;
1389
+ const value = field.isList || field.scalar === "Json" ? filter : `${inputBaseType({ field })} | ${filter}${field.isOptional ? " | null" : ""}`;
1390
+ havingLines.push(` readonly ${propKey({ name: field.name })}?: ${value};`);
1391
+ }
1392
+ parts.push(inputAlias({ name: `${n}HavingInput`, lines: havingLines }));
1393
+ parts.push(inputAlias({ name: `${n}GroupByOrderByInput`, lines: groupOrderLines }));
1316
1394
  for (const nested of [false, true]) {
1317
1395
  parts.push(inputAlias({
1318
1396
  name: `${n}${nested ? "RelationFindManyArgs" : "FindManyArgs"}`,
@@ -1438,13 +1516,19 @@ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1438
1516
  ` readonly skipUpdatedAt?: boolean;`
1439
1517
  ]
1440
1518
  }));
1519
+ const returningSelect = [
1520
+ ...model.fields.map((field) => `readonly ${propKey({ name: field.name })}?: boolean | Skip`),
1521
+ ...computedEntries.map((entry) => `readonly ${propKey({ name: entry.field })}?: boolean | Skip`),
1522
+ ...model.relations.map((relation) => `readonly ${propKey({ name: relation.name })}?: false | Skip`),
1523
+ ...hasCounts ? ["readonly _count?: false | Skip"] : []
1524
+ ].join("; ");
1441
1525
  parts.push(inputAlias({
1442
1526
  name: `${n}UpdateManyAndReturnArgs`,
1443
1527
  lines: [
1444
1528
  ` readonly include?: never;`,
1445
1529
  ` readonly where?: ${n}WhereInput;`,
1446
1530
  ` readonly data: ${n}UpdateManyInput;`,
1447
- ` readonly select?: ${n}Select;`,
1531
+ ` readonly select?: { ${returningSelect} };`,
1448
1532
  ` readonly omit?: ${n}OmitInput;`,
1449
1533
  ` readonly skipUpdatedAt?: boolean;`
1450
1534
  ]
@@ -1496,8 +1580,8 @@ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1496
1580
  lines: [
1497
1581
  ` readonly by: readonly ${n}ScalarFieldEnum[];`,
1498
1582
  ` readonly where?: ${n}WhereInput;`,
1499
- ` readonly orderBy?: ${orMany({ type: `${n}ScalarOrderByInput` })};`,
1500
- ` readonly having?: ${n}WhereInput;`,
1583
+ ` readonly orderBy?: ${orMany({ type: `${n}GroupByOrderByInput` })};`,
1584
+ ` readonly having?: ${n}HavingInput;`,
1501
1585
  ` readonly take?: number;`,
1502
1586
  ` readonly skip?: number;`,
1503
1587
  ...aggregateOpProps
@@ -1516,9 +1600,9 @@ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1516
1600
  ` + ` { [K in keyof CS & (${listUnion}) as ${uncertainPart("CS")}]?: number }
1517
1601
  ` + ` : C extends { readonly select: Skip } ? { ${allCounts} }
1518
1602
  ` + ` : "select" extends keyof C ? Partial<{ ${allCounts} }> : { ${allCounts} };`);
1519
- parts.push(`type ${n}CountPayload<S> = S extends { readonly _count: infer C extends true | object } ? { _count: ${n}CountResult<C> }
1520
- ` + ` : S extends { readonly _count: false | undefined | Skip } ? unknown
1521
- ` + ` : S extends { readonly _count?: infer C extends boolean | object | Skip } ? { _count?: ${n}CountResult<Provided<C>> } : unknown;`);
1603
+ parts.push(`type ${n}CountPayload<C> = [C] extends [true | object] ? { _count: ${n}CountResult<C> }
1604
+ ` + ` : [C] extends [false | undefined | Skip] ? unknown
1605
+ ` + ` : { _count?: ${n}CountResult<Exclude<C, false | undefined | Skip>> };`);
1522
1606
  }
1523
1607
  if (hasRelations) {
1524
1608
  const relationCases = model.relations.map((relation) => {
@@ -1538,20 +1622,23 @@ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1538
1622
  parts.push(`type ${n}RelationPayload<K extends keyof ${n}Relations, A> = {
1539
1623
  ${relationCases}
1540
1624
  }[K];`);
1541
- parts.push(`type ${n}IncludePayload<A> = A extends { readonly include: infer I extends object }
1542
- ` + ` ? { [K in keyof I & keyof ${n}Relations as ${exactPart("I")}]: ${n}RelationPayload<K, I[K]> } &
1543
- ` + ` { [K in keyof I & keyof ${n}Relations as ${uncertainPart("I")}]?: ${n}RelationPayload<K, I[K]> }${hasCounts ? ` &
1544
- ${n}CountPayload<I>` : ""}
1545
- ` + ` : "include" extends keyof A ? Partial<${n}IncludePayload<{ include: Provided<A["include"]> }>> : unknown;`);
1625
+ parts.push(`type ${n}ResolvedSelection<A, S = SelectionValue<A, "select">, I = SelectionValue<A, "include">> = {
1626
+ ` + ` [K in (BagKeys<S> | BagKeys<I>) & (${hasCounts ? `keyof ${n}Relations | "_count"` : `keyof ${n}Relations`})]: ${hasCounts ? `K extends "_count" ? IncludedCount<SelectionValue<S, K>, SelectionValue<I, K>> : ` : ""}IncludedRelation<SelectionValue<S, K>, SelectionValue<I, K>>;
1627
+ ` + `};`);
1628
+ parts.push(`type ${n}IncludePayload<A, I = "include" extends keyof A ? "select" extends keyof A ? ${n}ResolvedSelection<A> : SelectionValue<A, "include"> : SelectionValue<A, "select">> =
1629
+ ` + ` [Exclude<SelectionValue<I, BagKeys<I> & (${hasCounts ? `keyof ${n}Relations | "_count"` : `keyof ${n}Relations`})>, false | undefined | Skip>] extends [never] ? unknown :
1630
+ ` + ` { [K in BagKeys<I> & keyof ${n}Relations as ${exactPart("I").replaceAll("I[K]", "SelectionValue<I, K>")}]: ${n}RelationPayload<K, Exclude<SelectionValue<I, K>, false | undefined | Skip>> } &
1631
+ ` + ` { [K in BagKeys<I> & keyof ${n}Relations as ${uncertainPart("I").replaceAll("I[K]", "SelectionValue<I, K>")}]?: ${n}RelationPayload<K, Exclude<SelectionValue<I, K>, false | undefined | Skip>> }${hasCounts ? ` &
1632
+ ${n}CountPayload<SelectionValue<I, "_count">>` : ""};`);
1546
1633
  parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1547
1634
  ` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
1548
1635
  ` + ` { [K in keyof S & ${selectableEnum} as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] } &
1549
- ` + ` { [K in keyof S & keyof ${n}Relations as ${exactPart("S")}]: ${n}RelationPayload<K, S[K]> } &
1550
- ` + ` { [K in keyof S & keyof ${n}Relations as ${uncertainPart("S")}]?: ${n}RelationPayload<K, S[K]> }${hasCounts ? ` &
1551
- ${n}CountPayload<S>` : ""}
1636
+ ` + ` ("include" extends keyof A ? ${n}IncludePayload<A> :
1637
+ ` + ` [Exclude<SelectionValue<S, BagKeys<S> & (${hasCounts ? `keyof ${n}Relations | "_count"` : `keyof ${n}Relations`})>, false | undefined | Skip>] extends [never] ? unknown : ${n}IncludePayload<A>)
1552
1638
  ` + ` : A extends { readonly select: Skip } ? ${n}OmitBase<A> & ${n}IncludePayload<A>
1553
- ` + ` : "select" extends keyof A ? Partial<${n}>
1554
- ` + ` : ${n}OmitBase<A> & ${n}IncludePayload<A>;`);
1639
+ ` + ` : "select" extends keyof A ? Partial<${n}> & ${n}IncludePayload<A>
1640
+ ` + ` : "include" extends keyof A ? ${n}OmitBase<A> & ${n}IncludePayload<A>
1641
+ ` + ` : ${n}OmitBase<A>;`);
1555
1642
  } else {
1556
1643
  parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1557
1644
  ` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
@@ -1560,7 +1647,7 @@ ${relationCases}
1560
1647
  ` + ` : "select" extends keyof A ? Partial<${n}> : ${n}OmitBase<A>;`);
1561
1648
  }
1562
1649
  const aggregateFields = (fields, value) => `{ ${fields.map((field) => `${propKey({ name: field.name })}: ${value(field)}`).join("; ")} }`;
1563
- const countValues = aggregateFields(model.fields, () => "number").replace(" }", "; _all: number }");
1650
+ const countValues = `{ ${[...model.fields.map((field) => `${propKey({ name: field.name })}: number`), "_all: number"].join("; ")} }`;
1564
1651
  parts.push(`type ${n}AggregateValues = {
1565
1652
  ` + ` _count: ${countValues};
1566
1653
  ` + ` _avg: ${aggregateFields(numericFields, () => "number | null")};
@@ -1692,7 +1779,7 @@ function emitViewTypesForModel(params) {
1692
1779
  const target = relation.target;
1693
1780
  const targetClientKey = view.modelByName.get(target)?.clientName ?? target;
1694
1781
  const guard = `D extends { readonly ${propKey({ name: targetClientKey })}: infer RF extends object }`;
1695
- const masked = `ViewRow<${target}ViewSelectScalars<RF, A> & ${target}ViewReveal<A>>`;
1782
+ const masked = `${target}ViewResult<RF, Exclude<A, false | undefined | Skip>, D>`;
1696
1783
  const unmasked = `(A extends object ? ${target}GetPayload<A> : ${target})`;
1697
1784
  let expression;
1698
1785
  if (relation.isList) {
@@ -2247,17 +2334,59 @@ ${scopeLines}} : {}) & `;
2247
2334
  ` + `};`
2248
2335
  ];
2249
2336
  }
2250
- function emitDts(params) {
2337
+ var UNSUPPORTED_INPUT_MEMBER_NAMES = new Set([
2338
+ "toString",
2339
+ "valueOf",
2340
+ "constructor",
2341
+ "toLocaleString",
2342
+ "hasOwnProperty",
2343
+ "isPrototypeOf",
2344
+ "propertyIsEnumerable"
2345
+ ]);
2346
+ function groupComputed(params) {
2347
+ const byModel = new Map;
2348
+ for (const entry of params.computed) {
2349
+ const model = entry.model;
2350
+ const list = byModel.get(model);
2351
+ if (list === undefined)
2352
+ byModel.set(model, [entry]);
2353
+ else
2354
+ list.push(entry);
2355
+ }
2356
+ return byModel;
2357
+ }
2358
+ function assertGeneratedInputNames(params) {
2359
+ const check = (member) => {
2360
+ if (!UNSUPPORTED_INPUT_MEMBER_NAMES.has(member.name))
2361
+ return;
2362
+ const hint = member.kind === "field" ? "Rename the logical field and preserve its database column with @map or DSL .map()." : member.kind === "relation" ? "Rename the logical relation while keeping its FK fields, references, relation pairing name and constraint mapping. Relation fields do not have a column @map." : "Rename the computed alias in the computed configuration; it has no database column to map.";
2363
+ throw new VibeError4({
2364
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
2365
+ message: `Generated TypeScript inputs cannot represent logical ${member.kind} "${member.model}.${member.name}" safely because it conflicts with an inherited Object member. ${hint} SchemaIR and the dynamic client still accept this name.`,
2366
+ meta: { reason: "generated_input_member", model: member.model, member: member.name, kind: member.kind }
2367
+ });
2368
+ };
2369
+ const computedByModel = groupComputed({ computed: params.computed ?? [] });
2370
+ for (const model of params.view.models) {
2371
+ for (const field of model.fields)
2372
+ check({ model: model.name, name: field.name, kind: "field" });
2373
+ for (const relation of model.relations)
2374
+ check({ model: model.name, name: relation.name, kind: "relation" });
2375
+ for (const entry of computedByModel.get(model.name) ?? []) {
2376
+ check({ model: model.name, name: entry.field, kind: "computed" });
2377
+ }
2378
+ }
2379
+ }
2380
+ function prepareDts(params) {
2381
+ assertGeneratedInputNames({ view: params.view, computed: params.computed ?? [] });
2382
+ return () => emitPreparedDts(params);
2383
+ }
2384
+ function emitPreparedDts(params) {
2251
2385
  const { view } = params;
2252
2386
  const baked = params.baked ?? EMPTY_BAKED_EXTENSIONS;
2253
2387
  const computed = params.computed ?? [];
2254
2388
  const hasComputed = computed.length > 0;
2255
- const computedByModel = new Map;
2256
- for (const entry of computed) {
2257
- const list = computedByModel.get(entry.model) ?? [];
2258
- list.push(entry);
2259
- computedByModel.set(entry.model, list);
2260
- }
2389
+ const computedByModel = groupComputed({ computed });
2261
2390
  const parts = [];
2262
2391
  parts.push(GENERATED_HEADER);
2263
2392
  const schemaTypeImports = [
@@ -2501,11 +2630,12 @@ async function generateClient(params) {
2501
2630
  validateComputedConfig({ schema, computed: params.computed });
2502
2631
  computed = buildComputedManifest({ computed: params.computed });
2503
2632
  }
2633
+ const declarations = prepareDts({ view, baked, computed });
2504
2634
  if (params.module !== undefined && (!baked.empty || computed.length > 0))
2505
2635
  throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Package modules do not support computed fields or extension mounts in this protocol", meta: { reason: "module_schema_feature", moduleId: params.module.id } });
2506
2636
  const outputs = [
2507
2637
  { name: "index.js", content: emitJs({ view, baked, computed }) },
2508
- { name: "index.d.ts", content: emitDts({ view, baked, computed }) },
2638
+ { name: "index.d.ts", content: declarations() },
2509
2639
  { name: "enums.js", content: emitEnumsJs({ view }) },
2510
2640
  { name: "enums.d.ts", content: emitEnumsDts({ view }) },
2511
2641
  { name: "README.md", content: README },
@@ -2526,4 +2656,4 @@ export {
2526
2656
  prepareDatabaseModuleSchema
2527
2657
  };
2528
2658
 
2529
- //# debugId=BD8C77FCC6EC9CEF64756E2164756E21
2659
+ //# debugId=0EF8A4E0C9CC959A64756E2164756E21