@vibeorm/generator 2.1.1 → 2.2.1

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
@@ -1,22 +1,151 @@
1
1
  // @bun
2
+ // packages/generator/src/emit-module.ts
3
+ import { createHash } from "crypto";
4
+ import { VibeError as VibeError2, providerToDialect } from "@vibeorm/schema";
5
+
6
+ // packages/generator/src/util.ts
7
+ import { VibeError } from "@vibeorm/schema";
8
+ function pascal(params) {
9
+ const { name } = params;
10
+ if (name.length === 0)
11
+ return name;
12
+ return name.charAt(0).toUpperCase() + name.slice(1);
13
+ }
14
+ function clientNameOf(params) {
15
+ const { name } = params;
16
+ if (name.length === 0)
17
+ return name;
18
+ return name.charAt(0).toLowerCase() + name.slice(1);
19
+ }
20
+ var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
21
+ function propKey(params) {
22
+ const { name } = params;
23
+ return IDENTIFIER_RE.test(name) ? name : JSON.stringify(name);
24
+ }
25
+ function tsString(params) {
26
+ return JSON.stringify(params.value);
27
+ }
28
+ function maybeParens(params) {
29
+ const { type } = params;
30
+ return type.includes("|") ? `(${type})` : type;
31
+ }
32
+ function stableStringify(params) {
33
+ return renderValue({ value: params.value, indent: "" });
34
+ }
35
+ function renderValue(params) {
36
+ const { value, indent } = params;
37
+ if (value === null)
38
+ return "null";
39
+ const kind = typeof value;
40
+ if (kind === "string" || kind === "number" || kind === "boolean")
41
+ return JSON.stringify(value);
42
+ if (Array.isArray(value)) {
43
+ if (value.length === 0)
44
+ return "[]";
45
+ const inner = `${indent} `;
46
+ const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
47
+ return `[
48
+ ${items.join(`,
49
+ `)}
50
+ ${indent}]`;
51
+ }
52
+ if (kind === "object") {
53
+ const record = value;
54
+ const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort();
55
+ if (keys.length === 0)
56
+ return "{}";
57
+ const inner = `${indent} `;
58
+ const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
59
+ return `{
60
+ ${items.join(`,
61
+ `)}
62
+ ${indent}}`;
63
+ }
64
+ throw new VibeError({
65
+ code: "VIBE_INTERNAL",
66
+ message: `internal: cannot serialize a ${kind} value into the generated client`,
67
+ meta: { kind }
68
+ });
69
+ }
70
+
71
+ // packages/generator/src/emit-module.ts
72
+ var MODULE_DECLARATIONS = `import type { ClientContext, DatabaseAdapter, QueryOperation, TransactionOptions } from "@vibeorm/runtime";
73
+ import type { DatabaseModuleManifest, SchemaIR } from "@vibeorm/schema";
74
+ import type { VibeClientInstance, VibeTransactionClientInstance, ReadOnlyClientInstance } from "./index.js";
75
+ export { sql, decode, sqlModels, sqlProjection } from "./index.js";
76
+ export type ModuleClient = Omit<VibeClientInstance, "$connect" | "$disconnect" | "$transaction" | "$scoped" | "$withPolicy" | "$bypassPolicy" | "$readOnly"> & {
77
+ readonly $transaction: ModuleTransactionClient["$transaction"];
78
+ readonly $readOnly: () => ReadOnlyClientInstance;
79
+ };
80
+ export type ModuleTransactionClient = Omit<VibeTransactionClientInstance, "$transaction" | "$scoped" | "$withPolicy" | "$bypassPolicy"> & {
81
+ readonly $transaction: {
82
+ <T>(run: (tx: ModuleTransactionClient) => Promise<T>, options?: TransactionOptions): Promise<T>;
83
+ <T extends readonly QueryOperation<unknown, "read" | "write">[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
84
+ };
85
+ };
86
+ export declare const databaseModule: {
87
+ readonly manifest: DatabaseModuleManifest;
88
+ readonly schema: SchemaIR;
89
+ readonly create: (params: { adapter: DatabaseAdapter }) => ModuleClient;
90
+ readonly bind: {
91
+ (params: { db: ClientContext<"root"> }): ModuleClient;
92
+ (params: { db: ClientContext<"transaction"> }): ModuleTransactionClient;
93
+ (params: { db: ClientContext<"readOnly"> }): ReadOnlyClientInstance;
94
+ };
95
+ };
96
+ `;
97
+ function emitDatabaseModule(params) {
98
+ const { view, module } = params;
99
+ for (const key of ["id", "contract", "revision"]) {
100
+ if (typeof module[key] !== "string" || module[key].trim().length === 0 || module[key].includes("\x00"))
101
+ throw new VibeError2({ code: "VIBE_CONFIG", message: `Database module ${key} must be a non-empty identifier`, meta: { reason: "module_manifest", key } });
102
+ }
103
+ if (view.schema.rls !== undefined || view.schema.extensions.length > 0 || view.schema.models.some((model) => model.policy !== undefined))
104
+ throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Module ${module.id} cannot declare extensions, policy or native RLS in this module protocol`, meta: { reason: "module_schema_feature", moduleId: module.id } });
105
+ const serialized = stableStringify({ value: view.schema });
106
+ const manifest = {
107
+ ...module,
108
+ protocol: 1,
109
+ dialect: providerToDialect({ provider: view.schema.datasource.provider }),
110
+ schemaChecksum: createHash("sha256").update(JSON.stringify(JSON.parse(serialized))).digest("hex")
111
+ };
112
+ return [
113
+ { name: "module.js", content: `// Generated package module. No connection or DDL on import.
114
+ import { defineDatabaseModule } from "@vibeorm/runtime";
115
+ import { schema } from "./schema.js";
116
+ export { sql, decode, sqlModels, sqlProjection } from "./index.js";
117
+ export const databaseModule = defineDatabaseModule({ ...${JSON.stringify(module)}, schema, manifest: ${JSON.stringify(manifest)} });
118
+ ` },
119
+ { name: "schema.js", content: `// Generated data-only schema and runtime manifest.
120
+ export const schema = ${serialized};
121
+ export const moduleManifest = ${JSON.stringify(manifest)};
122
+ ` },
123
+ { name: "schema.d.ts", content: `import type { SchemaIR, DatabaseModuleManifest } from "@vibeorm/schema";
124
+ export declare const schema: SchemaIR;
125
+ export declare const moduleManifest: DatabaseModuleManifest;
126
+ ` },
127
+ { name: "module.d.ts", content: MODULE_DECLARATIONS }
128
+ ];
129
+ }
130
+
2
131
  // packages/generator/src/generate.ts
3
132
  import { mkdir, writeFile } from "fs/promises";
4
133
  import { join } from "path";
5
- import { buildComputedManifest, validateComputedConfig } from "@vibeorm/schema";
134
+ import { VibeError as VibeError6, buildComputedManifest, validateComputedConfig } from "@vibeorm/schema";
6
135
 
7
136
  // packages/generator/src/emit-dts.ts
8
- import { DIALECT_CAPABILITIES, VibeError as VibeError3, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
137
+ import { DIALECT_CAPABILITIES, VibeError as VibeError4, providerToDialect as providerToDialect3 } from "@vibeorm/schema";
9
138
 
10
139
  // packages/generator/src/emit-js.ts
11
140
  import { internalError } from "@vibeorm/schema";
12
141
 
13
142
  // packages/generator/src/extension-bake.ts
14
143
  import {
15
- VibeError,
144
+ VibeError as VibeError3,
16
145
  buildExtensionRegistry,
17
146
  extensionDeclarations,
18
147
  extensionManifestEntry,
19
- providerToDialect
148
+ providerToDialect as providerToDialect2
20
149
  } from "@vibeorm/schema";
21
150
  var EMPTY_BAKED_EXTENSIONS = {
22
151
  jsImports: [],
@@ -54,7 +183,7 @@ function readOnlyClassification(params) {
54
183
  if (readOnly === undefined)
55
184
  return;
56
185
  const refuse = (problem) => {
57
- throw new VibeError({
186
+ throw new VibeError3({
58
187
  code: "VIBE_CONFIG",
59
188
  message: `extension "${extension.name}": readOnly ${problem}`,
60
189
  meta: { extension: extension.name, kind: contribution.kind }
@@ -84,7 +213,7 @@ function bakeExtensions(params) {
84
213
  if (params.extensions.length === 0)
85
214
  return EMPTY_BAKED_EXTENSIONS;
86
215
  buildExtensionRegistry({ extensions: params.extensions });
87
- const dialect = providerToDialect({ provider: schema.datasource.provider });
216
+ const dialect = providerToDialect2({ provider: schema.datasource.provider });
88
217
  const provider = schema.datasource.provider;
89
218
  const sorted = [...params.extensions].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
90
219
  const jsImports = new ImportCollector;
@@ -115,7 +244,7 @@ function bakeExtensions(params) {
115
244
  }
116
245
  } else {
117
246
  clientDts.push(...indent(contribution.dts));
118
- const classified = readOnlyLine({ owner: "VibeClientInstance", property, readOnly });
247
+ const classified = readOnlyLine({ owner: schema.rls === undefined ? "VibeClientInstance" : "BoundVibeClientInstance", property, readOnly });
119
248
  if (classified !== undefined)
120
249
  readOnlyClientDts.push(classified);
121
250
  }
@@ -141,7 +270,7 @@ function bakeExtensions(params) {
141
270
  continue;
142
271
  const clash = model.fields.some((field) => field.name === extension.name) || model.relations.some((relation) => relation.name === extension.name);
143
272
  if (clash) {
144
- throw new VibeError({
273
+ throw new VibeError3({
145
274
  code: "VIBE_CONFIG",
146
275
  message: `extension "${extension.name}" collides with a field/relation named "${extension.name}" on model "${model.name}" \u2014 rename one of them`,
147
276
  meta: { extension: extension.name, model: model.name }
@@ -178,71 +307,6 @@ function bakeExtensions(params) {
178
307
  };
179
308
  }
180
309
 
181
- // packages/generator/src/util.ts
182
- import { VibeError as VibeError2 } from "@vibeorm/schema";
183
- function pascal(params) {
184
- const { name } = params;
185
- if (name.length === 0)
186
- return name;
187
- return name.charAt(0).toUpperCase() + name.slice(1);
188
- }
189
- function clientNameOf(params) {
190
- const { name } = params;
191
- if (name.length === 0)
192
- return name;
193
- return name.charAt(0).toLowerCase() + name.slice(1);
194
- }
195
- var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
196
- function propKey(params) {
197
- const { name } = params;
198
- return IDENTIFIER_RE.test(name) ? name : JSON.stringify(name);
199
- }
200
- function tsString(params) {
201
- return JSON.stringify(params.value);
202
- }
203
- function maybeParens(params) {
204
- const { type } = params;
205
- return type.includes("|") ? `(${type})` : type;
206
- }
207
- function stableStringify(params) {
208
- return renderValue({ value: params.value, indent: "" });
209
- }
210
- function renderValue(params) {
211
- const { value, indent: indent2 } = params;
212
- if (value === null)
213
- return "null";
214
- const kind = typeof value;
215
- if (kind === "string" || kind === "number" || kind === "boolean")
216
- return JSON.stringify(value);
217
- if (Array.isArray(value)) {
218
- if (value.length === 0)
219
- return "[]";
220
- const inner = `${indent2} `;
221
- const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
222
- return `[
223
- ${items.join(`,
224
- `)}
225
- ${indent2}]`;
226
- }
227
- if (kind === "object") {
228
- const record = value;
229
- const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort();
230
- if (keys.length === 0)
231
- return "{}";
232
- const inner = `${indent2} `;
233
- const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
234
- return `{
235
- ${items.join(`,
236
- `)}
237
- ${indent2}}`;
238
- }
239
- throw new VibeError2({
240
- code: "VIBE_INTERNAL",
241
- message: `internal: cannot serialize a ${kind} value into the generated client`,
242
- meta: { kind }
243
- });
244
- }
245
-
246
310
  // packages/generator/src/emit-js.ts
247
311
  var HEADER = `// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
248
312
  // Regenerate with \`vibeorm generate\` after every schema change.`;
@@ -309,7 +373,7 @@ function serializeSchema(params) {
309
373
  return renderIr({ value: schema, indent: "", ordered: false });
310
374
  }
311
375
  function renderIr(params) {
312
- const { value, indent: indent2, ordered } = params;
376
+ const { value, indent, ordered } = params;
313
377
  if (value === null)
314
378
  return "null";
315
379
  const kind = typeof value;
@@ -318,12 +382,12 @@ function renderIr(params) {
318
382
  if (Array.isArray(value)) {
319
383
  if (value.length === 0)
320
384
  return "[]";
321
- const inner2 = `${indent2} `;
322
- const items2 = value.map((item) => inner2 + renderIr({ value: item, indent: inner2, ordered }));
385
+ const inner = `${indent} `;
386
+ const items = value.map((item) => inner + renderIr({ value: item, indent: inner, ordered }));
323
387
  return `[
324
- ${items2.join(`,
388
+ ${items.join(`,
325
389
  `)}
326
- ${indent2}]`;
390
+ ${indent}]`;
327
391
  }
328
392
  if (kind !== "object") {
329
393
  throw internalError({ message: `internal: cannot serialize a ${kind} value into the generated client`, meta: { kind } });
@@ -333,12 +397,12 @@ ${indent2}]`;
333
397
  const keys = ordered ? defined : defined.sort();
334
398
  if (keys.length === 0)
335
399
  return "{}";
336
- const inner = `${indent2} `;
400
+ const inner = `${indent} `;
337
401
  const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderIr({ value: record[key], indent: inner, ordered: ordered || key === "rls" })}`);
338
402
  return `{
339
403
  ${items.join(`,
340
404
  `)}
341
- ${indent2}}`;
405
+ ${indent}}`;
342
406
  }
343
407
  function emitJs(params) {
344
408
  const { view } = params;
@@ -459,13 +523,15 @@ var MINMAX_SCALARS = new Set([
459
523
  function payloadBaseType(params) {
460
524
  const { field } = params;
461
525
  if (field.jsonType !== undefined)
462
- return field.jsonType.name;
526
+ return `import(${tsString({ value: field.jsonType.from })}).${field.jsonType.name}`;
463
527
  return field.enumName ?? PAYLOAD_TYPES[field.scalar];
464
528
  }
465
529
  function inputBaseType(params) {
466
530
  const { field } = params;
467
531
  if (field.jsonType !== undefined)
468
- return field.jsonType.name;
532
+ return `import(${tsString({ value: field.jsonType.from })}).${field.jsonType.name}`;
533
+ if (field.isList && field.scalar === "DateTime")
534
+ return "Date | string";
469
535
  return field.enumName ?? INPUT_TYPES[field.scalar];
470
536
  }
471
537
  function filterName(params) {
@@ -488,7 +554,7 @@ function banner(params) {
488
554
  return `// \u2500\u2500\u2500 ${params.title} \u2500\u2500\u2500`;
489
555
  }
490
556
  function capabilitiesOf(params) {
491
- return DIALECT_CAPABILITIES[providerToDialect2({ provider: params.view.schema.datasource.provider })];
557
+ return DIALECT_CAPABILITIES[providerToDialect3({ provider: params.view.schema.datasource.provider })];
492
558
  }
493
559
  function literalUnion(params) {
494
560
  return params.members.map((member) => tsString({ value: member })).join(" | ");
@@ -587,6 +653,10 @@ export type KeysetOptions = {
587
653
  /** Result of createMany/updateMany/deleteMany. */
588
654
  export type BatchPayload = { readonly count: number };
589
655
 
656
+ /** Static operation classification only; runtime verifies instance ownership and active lifetime. */
657
+ declare const vibeTransaction: unique symbol;
658
+ export type VibeQueryPromise<T, Access extends "read" | "write" = "read"> = QueryOperation<T, Access>;
659
+
590
660
  /**
591
661
  * What upsertMany reports. \`count\` is the rows submitted after duplicate-key
592
662
  * validation \u2014 every one of them was inserted or updated, since the statement
@@ -609,7 +679,7 @@ type Provided<T> = Exclude<T, Skip | undefined>;
609
679
  type OmittedKeys<O> = keyof { [K in keyof O as O[K] extends true ? K : never]: 0 };
610
680
 
611
681
  /** Keys a selection has not RULED OUT \u2014 everything except a literal false/undefined. */
612
- type PossibleKeys<S> = keyof { [K in keyof S as S[K] extends false | undefined ? never : K]: 0 };
682
+ type PossibleKeys<S> = keyof { [K in keyof S as S[K] extends false | undefined | Skip ? never : K]: 0 };
613
683
 
614
684
  /**
615
685
  * Keys whose flag can STILL be false or missing when the query runs \u2014 a widened
@@ -617,7 +687,7 @@ type PossibleKeys<S> = keyof { [K in keyof S as S[K] extends false | undefined ?
617
687
  * result type must not promise a field a run-time flag can switch off.
618
688
  */
619
689
  type UncertainKeys<S> = PossibleKeys<S> &
620
- keyof { [K in keyof S as [false] extends [S[K]] ? K : [undefined] extends [S[K]] ? K : never]: 0 };
690
+ keyof { [K in keyof S as [false] extends [S[K]] ? K : [undefined] extends [S[K]] ? K : [Skip] extends [S[K]] ? K : never]: 0 };
621
691
 
622
692
  /** Keys the selection definitely turns on: a literal \`true\`, or a nested selection object. */
623
693
  type ExactKeys<S> = Exclude<PossibleKeys<S>, UncertainKeys<S>>;
@@ -892,7 +962,7 @@ function register(params) {
892
962
  const existing = registry.get(name);
893
963
  if (existing !== undefined) {
894
964
  if (existing !== text) {
895
- throw new VibeError3({
965
+ throw new VibeError4({
896
966
  code: "VIBE_INTERNAL",
897
967
  message: `internal: nested input type "${name}" generated with two different bodies`,
898
968
  meta: { name }
@@ -1400,8 +1470,8 @@ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1400
1470
  ...aggregateOpProps
1401
1471
  ]
1402
1472
  }));
1403
- const exactPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : K`;
1404
- const uncertainPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : never`;
1473
+ const exactPart = (source) => `${source}[K] extends false | undefined | Skip ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : Skip extends ${source}[K] ? never : K`;
1474
+ const uncertainPart = (source) => `${source}[K] extends false | undefined | Skip ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : Skip extends ${source}[K] ? K : never`;
1405
1475
  parts.push(`type ${n}OmitBase<A> = A extends { readonly omit: infer O extends object }
1406
1476
  ` + ` ? Omit<${n}, PossibleKeys<O> & keyof ${n}> & { [K in UncertainKeys<O> & keyof ${n}]?: ${n}[K] }
1407
1477
  ` + ` : "omit" extends keyof A ? Omit<${n}, PossibleKeys<Provided<A["omit"]>> & keyof ${n}> & Partial<Pick<${n}, PossibleKeys<Provided<A["omit"]>> & keyof ${n}>> : ${n};`);
@@ -1411,10 +1481,11 @@ export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
1411
1481
  parts.push(`type ${n}CountResult<C> = C extends { readonly select: infer CS extends object }
1412
1482
  ` + ` ? { [K in keyof CS & (${listUnion}) as ${exactPart("CS")}]: number } &
1413
1483
  ` + ` { [K in keyof CS & (${listUnion}) as ${uncertainPart("CS")}]?: number }
1414
- ` + ` : { ${allCounts} };`);
1484
+ ` + ` : C extends { readonly select: Skip } ? { ${allCounts} }
1485
+ ` + ` : "select" extends keyof C ? Partial<{ ${allCounts} }> : { ${allCounts} };`);
1415
1486
  parts.push(`type ${n}CountPayload<S> = S extends { readonly _count: infer C extends true | object } ? { _count: ${n}CountResult<C> }
1416
- ` + ` : S extends { readonly _count: false | undefined } ? unknown
1417
- ` + ` : S extends { readonly _count?: infer C extends boolean | object } ? { _count?: ${n}CountResult<C> } : unknown;`);
1487
+ ` + ` : S extends { readonly _count: false | undefined | Skip } ? unknown
1488
+ ` + ` : S extends { readonly _count?: infer C extends boolean | object | Skip } ? { _count?: ${n}CountResult<Provided<C>> } : unknown;`);
1418
1489
  }
1419
1490
  if (hasRelations) {
1420
1491
  const relationCases = model.relations.map((relation) => {
@@ -1445,12 +1516,14 @@ ${relationCases}
1445
1516
  ` + ` { [K in keyof S & keyof ${n}Relations as ${exactPart("S")}]: ${n}RelationPayload<K, S[K]> } &
1446
1517
  ` + ` { [K in keyof S & keyof ${n}Relations as ${uncertainPart("S")}]?: ${n}RelationPayload<K, S[K]> }${hasCounts ? ` &
1447
1518
  ${n}CountPayload<S>` : ""}
1519
+ ` + ` : A extends { readonly select: Skip } ? ${n}OmitBase<A> & ${n}IncludePayload<A>
1448
1520
  ` + ` : "select" extends keyof A ? Partial<${n}>
1449
1521
  ` + ` : ${n}OmitBase<A> & ${n}IncludePayload<A>;`);
1450
1522
  } else {
1451
1523
  parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
1452
1524
  ` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
1453
1525
  ` + ` { [K in keyof S & ${selectableEnum} as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] }
1526
+ ` + ` : A extends { readonly select: Skip } ? ${n}OmitBase<A>
1454
1527
  ` + ` : "select" extends keyof A ? Partial<${n}> : ${n}OmitBase<A>;`);
1455
1528
  }
1456
1529
  const aggregateFields = (fields, value) => `{ ${fields.map((field) => `${propKey({ name: field.name })}: ${value(field)}`).join("; ")} }`;
@@ -1470,33 +1543,33 @@ ${relationCases}
1470
1543
  ` + ` : "select" extends keyof A ? number | ${n}AggregateSelection<${n}AggregateValues["_count"], Provided<A["select"]>> : number;`);
1471
1544
  parts.push(`export type ${n}AggregateResult<A> =
1472
1545
  ` + ` { [K in keyof A & keyof ${n}AggregateValues as HasSelectedField<A[K]> extends true ? K : never]: ${n}AggregateSelection<${n}AggregateValues[K], A[K]> } &
1473
- ` + ` { [K in keyof A & keyof ${n}AggregateValues as HasSelectedField<A[K]> extends true ? never : K]?: ${n}AggregateSelection<${n}AggregateValues[K], Provided<A[K]>> };`);
1546
+ ` + ` { [K in keyof A & keyof ${n}AggregateValues as A[K] extends false | undefined | Skip ? never : HasSelectedField<A[K]> extends true ? never : K]?: ${n}AggregateSelection<${n}AggregateValues[K], Provided<A[K]>> };`);
1474
1547
  parts.push(`export type ${n}GroupByResult<A extends ${n}GroupByArgs> =
1475
1548
  ` + ` (number extends A["by"]["length"] ? Partial<Pick<${n}, A["by"][number]>> : Pick<${n}, A["by"][number]>) & ${n}AggregateResult<A>;`);
1476
1549
  const readMethods = [
1477
- ` findMany<A extends ${n}FindManyArgs = {}>(args?: A): Promise<${n}GetPayload<A>[]>;`,
1478
- ` findFirst<A extends ${n}FindFirstArgs = {}>(args?: A): Promise<${n}GetPayload<A> | null>;`,
1479
- ` findFirstOrThrow<A extends ${n}FindFirstArgs = {}>(args?: A): Promise<${n}GetPayload<A>>;`,
1480
- ` findUnique<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A> | null>;`,
1481
- ` findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A>>;`
1550
+ ` findMany<A extends ${n}FindManyArgs = {}>(args?: A): VibeQueryPromise<${n}GetPayload<A>[]>;`,
1551
+ ` findFirst<A extends ${n}FindFirstArgs = {}>(args?: A): VibeQueryPromise<${n}GetPayload<A> | null>;`,
1552
+ ` findFirstOrThrow<A extends ${n}FindFirstArgs = {}>(args?: A): VibeQueryPromise<${n}GetPayload<A>>;`,
1553
+ ` findUnique<A extends ${n}FindUniqueArgs>(args: A): VibeQueryPromise<${n}GetPayload<A> | null>;`,
1554
+ ` findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>>;`
1482
1555
  ];
1483
1556
  const writeMethods = [
1484
- ` create<A extends ${n}CreateOrSkipArgs>(args: A): Promise<${n}GetPayload<A> | null>;`,
1485
- ` create<A extends ${n}CreateArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1486
- ` createMany(args: ${n}CreateManyArgs): Promise<BatchPayload>;`,
1487
- ` createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
1488
- ` update<A extends ${n}UpdateArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1489
- ` updateMany(args: ${n}UpdateManyArgs): Promise<BatchPayload>;`,
1490
- ` updateManyAndReturn<A extends ${n}UpdateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
1491
- ` upsert<A extends ${n}UpsertArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1492
- ` upsertMany(args: ${n}UpsertManyArgs): Promise<UpsertManyPayload>;`,
1493
- ` delete<A extends ${n}DeleteArgs>(args: A): Promise<${n}GetPayload<A>>;`,
1494
- ` deleteMany(args?: ${n}DeleteManyArgs): Promise<BatchPayload>;`
1557
+ ` create<A extends ${n}CreateOrSkipArgs>(args: A): VibeQueryPromise<${n}GetPayload<A> | null, "write">;`,
1558
+ ` create<A extends ${n}CreateArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>, "write">;`,
1559
+ ` createMany(args: ${n}CreateManyArgs): VibeQueryPromise<BatchPayload, "write">;`,
1560
+ ` createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>[], "write">;`,
1561
+ ` update<A extends ${n}UpdateArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>, "write">;`,
1562
+ ` updateMany(args: ${n}UpdateManyArgs): VibeQueryPromise<BatchPayload, "write">;`,
1563
+ ` updateManyAndReturn<A extends ${n}UpdateManyAndReturnArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>[], "write">;`,
1564
+ ` upsert<A extends ${n}UpsertArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>, "write">;`,
1565
+ ` upsertMany(args: ${n}UpsertManyArgs): VibeQueryPromise<UpsertManyPayload, "write">;`,
1566
+ ` delete<A extends ${n}DeleteArgs>(args: A): VibeQueryPromise<${n}GetPayload<A>, "write">;`,
1567
+ ` deleteMany(args?: ${n}DeleteManyArgs): VibeQueryPromise<BatchPayload, "write">;`
1495
1568
  ];
1496
1569
  const aggregateMethods = [
1497
- ` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;`,
1498
- ` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;`,
1499
- ` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;`
1570
+ ` count<A extends ${n}CountArgs = {}>(args?: A): VibeQueryPromise<${n}CountOutput<A>>;`,
1571
+ ` aggregate<A extends ${n}AggregateArgs>(args: A): VibeQueryPromise<${n}AggregateResult<A>>;`,
1572
+ ` groupBy<const A extends ${n}GroupByArgs>(args: A): VibeQueryPromise<${n}GroupByResult<A>[]>;`
1500
1573
  ];
1501
1574
  const delegateMethods = model.isView ? [...readMethods, ...aggregateMethods] : [...readMethods, ...writeMethods, ...aggregateMethods];
1502
1575
  delegateMethods.push(...params.extensionLines ?? []);
@@ -1570,8 +1643,8 @@ function emitViewTypesForModel(params) {
1570
1643
  ` readonly relationStrategy?: RelationStrategy;`
1571
1644
  ]
1572
1645
  }));
1573
- const exactPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : K`;
1574
- const uncertainPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : never`;
1646
+ const exactPart = (source) => `${source}[K] extends false | undefined | Skip ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : Skip extends ${source}[K] ? never : K`;
1647
+ const uncertainPart = (source) => `${source}[K] extends false | undefined | Skip ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : Skip extends ${source}[K] ? K : never`;
1575
1648
  parts.push(`type ${n}ViewSelectScalars<F, A> = A extends { readonly select: infer S extends object }
1576
1649
  ` + ` ? { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
1577
1650
  ` + ` { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] }
@@ -1651,19 +1724,6 @@ ${view.models.map((model) => ` ${propKey({ name: model.clientName })}: ${model.
1651
1724
  }
1652
1725
  return parts;
1653
1726
  }
1654
- function jsonTypeImportLines(params) {
1655
- const bySpecifier = new Map;
1656
- for (const model of params.view.models) {
1657
- for (const field of model.fields) {
1658
- if (field.jsonType === undefined)
1659
- continue;
1660
- const names = bySpecifier.get(field.jsonType.from) ?? new Set;
1661
- names.add(field.jsonType.name);
1662
- bySpecifier.set(field.jsonType.from, names);
1663
- }
1664
- }
1665
- return [...bySpecifier.keys()].sort().map((from) => `import type { ${[...bySpecifier.get(from)].sort().join(", ")} } from ${tsString({ value: from })};`);
1666
- }
1667
1727
  function hasNativeRls(params) {
1668
1728
  return params.view.schema.rls !== undefined;
1669
1729
  }
@@ -1806,7 +1866,7 @@ function transactionLines(params) {
1806
1866
  return `${doc}
1807
1867
  ` + ` readonly $transaction: {
1808
1868
  ` + ` <T>(fn: (tx: ${transactionType}) => Promise<T>, options?: TransactionOptions): Promise<T>;
1809
- ` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
1869
+ ` + ` <T extends readonly VibeQueryPromise<unknown, ${reads ? '"read"' : '"read" | "write"'}>[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
1810
1870
  ` + ` };
1811
1871
  `;
1812
1872
  }
@@ -1828,7 +1888,8 @@ var READ_ONLY_LINE = ` /** A read-only handle over this one: reads pass through
1828
1888
  `;
1829
1889
  function instanceSurfaceLines(params) {
1830
1890
  const { view, transactionType, inTransaction, selfLabel } = params;
1831
- return delegateSurfaceLines({ view, delegateSuffix: "Delegate" }) + transactionLines({ transactionType, reads: false }) + RAW_SURFACE_LINES + (inTransaction ? advisoryLockLines({ view, selfLabel }) : "") + (inTransaction ? "" : CONNECTION_SURFACE_LINES) + DEFINE_VIEW_LINE;
1891
+ return (inTransaction ? ` readonly [vibeTransaction]: true;
1892
+ ` : "") + delegateSurfaceLines({ view, delegateSuffix: "Delegate" }) + transactionLines({ transactionType, reads: false }) + RAW_SURFACE_LINES + (inTransaction ? advisoryLockLines({ view, selfLabel }) : "") + (inTransaction ? "" : CONNECTION_SURFACE_LINES) + DEFINE_VIEW_LINE;
1832
1893
  }
1833
1894
  function telemetryDeclarations() {
1834
1895
  return [
@@ -1945,6 +2006,8 @@ function emitClient(params) {
1945
2006
  name: "VibeClientOptions",
1946
2007
  lines: [
1947
2008
  ` readonly adapter: DatabaseAdapter;`,
2009
+ ` /** Strict argument validation defaults to true; false restores legacy omission of undefined values. */`,
2010
+ ` readonly strictArguments?: boolean;`,
1948
2011
  ` readonly onQuery?: (event: QueryEvent) => void;`,
1949
2012
  ` readonly relationStrategy?: RelationStrategy;`,
1950
2013
  ` /** Opt-in concurrent relation loading: how many of ONE query's relation statements (siblings, \`_count\` entries, key chunks) may be in flight at once. Default 1; ignored inside \`$transaction\`. */`,
@@ -1977,7 +2040,7 @@ function emitClient(params) {
1977
2040
  `)}
1978
2041
  `;
1979
2042
  if (nativeRls) {
1980
- parts.push(`export type BoundVibeClientInstance = {
2043
+ parts.push(`export type BoundVibeClientInstance = ClientContext<"restricted"> & {
1981
2044
  ` + instanceSurfaceLines({
1982
2045
  view,
1983
2046
  transactionType: "BoundVibeTransactionClientInstance",
@@ -1985,7 +2048,7 @@ function emitClient(params) {
1985
2048
  selfLabel: "db"
1986
2049
  }) + READ_ONLY_LINE + clientExtensionLines + `};`);
1987
2050
  parts.push(`/** The bound handle inside \`$transaction\`: same identity, plus the locks that require a transaction, minus the connection lifecycle the transaction owns. */
1988
- ` + `export type BoundVibeTransactionClientInstance = {
2051
+ ` + `export type BoundVibeTransactionClientInstance = ClientContext<"restricted"> & {
1989
2052
  ` + instanceSurfaceLines({
1990
2053
  view,
1991
2054
  transactionType: "BoundVibeTransactionClientInstance",
@@ -1993,7 +2056,7 @@ function emitClient(params) {
1993
2056
  selfLabel: "tx"
1994
2057
  }) + READ_ONLY_LINE + clientExtensionLines + ` // Deliberately ABSENT: $connect / $disconnect (the open transaction owns the connection) and $withContext (identity is bound once, from the root).
1995
2058
  ` + `};`);
1996
- parts.push(`export type VibeClientInstance = {
2059
+ parts.push(`export type VibeClientInstance = ClientContext<"restricted"> & {
1997
2060
  ` + ` /** Bind the request's identity context \u2014 ALL of it, every declared slot \u2014 and get the client that can actually query. */
1998
2061
  ` + ` readonly $withContext: (context: RlsContext) => BoundVibeClientInstance;
1999
2062
  ` + ` /** Subscribe to client events ("query" fires after every statement, with the same payload as onQuery \u2014 both fire). Returns an unsubscribe function. */
@@ -2003,7 +2066,7 @@ function emitClient(params) {
2003
2066
  ` + ` // Deliberately ABSENT: model delegates, $models, $transaction, $queryRaw / $executeRaw / $queryRawUnsafe / $executeRawUnsafe, $advisoryLock / $tryAdvisoryLock, $defineView, $readOnly and every extension mount \u2014 an unbound client has no identity, so it cannot read or write a row. Bind one with $withContext.
2004
2067
  ` + `};`);
2005
2068
  } else {
2006
- parts.push(`export type VibeClientInstance = {
2069
+ parts.push(`export type VibeClientInstance = ClientContext<"root"> & {
2007
2070
  ` + instanceSurfaceLines({
2008
2071
  view,
2009
2072
  transactionType: "VibeTransactionClientInstance",
@@ -2011,7 +2074,7 @@ function emitClient(params) {
2011
2074
  selfLabel: "db"
2012
2075
  }) + policyClientLines({ view, selfType: "VibeClientInstance" }) + scopedClientLines({ view, scopedType: "ScopedClientInstance" }) + READ_ONLY_LINE + clientExtensionLines + `};`);
2013
2076
  parts.push(`/** The handle \`$transaction\` gives its callback. A helper that REQUIRES a transaction takes this type, and an ordinary client no longer satisfies it. */
2014
- ` + `export type VibeTransactionClientInstance = {
2077
+ ` + `export type VibeTransactionClientInstance = ClientContext<"transaction"> & {
2015
2078
  ` + instanceSurfaceLines({
2016
2079
  view,
2017
2080
  transactionType: "VibeTransactionClientInstance",
@@ -2057,7 +2120,8 @@ function emitScopedSection(params) {
2057
2120
  const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${scopedDelegateType(model)};`;
2058
2121
  return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${scopedDelegateType(model)};`];
2059
2122
  });
2060
- const scopedSurface = (params2) => `${delegateProps.join(`
2123
+ const scopedSurface = (params2) => (params2.inTransaction ? ` readonly [vibeTransaction]: true;
2124
+ ` : "") + `${delegateProps.join(`
2061
2125
  `)}
2062
2126
  ` + ` /** Delegates keyed by BOTH camelCase client name and model name \u2014 the same objects as the top-level properties (dynamic access). */
2063
2127
  ` + ` readonly $models: {
@@ -2069,7 +2133,7 @@ ${modelsProps.join(`
2069
2133
  ` + ` /** ONE transaction; the callback receives a handle that INHERITS the scope. The array form takes un-awaited scoped delegate calls. */
2070
2134
  ` + ` readonly $transaction: {
2071
2135
  ` + ` <T>(fn: (tx: ${params2.transactionType}) => Promise<T>, options?: TransactionOptions): Promise<T>;
2072
- ` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
2136
+ ` + ` <T extends readonly VibeQueryPromise<unknown, "read" | "write">[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
2073
2137
  ` + ` };
2074
2138
  ` + (params2.inTransaction ? advisoryLockLines({ view, selfLabel: "tx" }) : CONNECTION_SURFACE_LINES) + ` /** Field-masking views compose over the scoped handle (columns \xD7 rows). */
2075
2139
  ` + ` readonly $defineView: <D extends ViewDefinitionInput>(options: { readonly name: string; readonly definition: D }) => ViewClient<D>;
@@ -2137,7 +2201,7 @@ function emitReadOnlySection(params) {
2137
2201
  ` + ` * await reader.user.findMany(); // fine
2138
2202
  ` + ` * await reader.user.upsertMany({ \u2026 }); // VIBE_READ_ONLY, and a compile error
2139
2203
  ` + ` */
2140
- ` + `export type ReadOnlyClientInstance = {
2204
+ ` + `export type ReadOnlyClientInstance = ClientContext<"readOnly"> & {
2141
2205
  ` + delegateSurfaceLines({ view, delegateSuffix: "ReadOnlyDelegate" }) + transactionLines({ transactionType: "ReadOnlyClientInstance", reads: true }) + DEFINE_VIEW_LINE + policyClientLines({ view, selfType: "ReadOnlyClientInstance" }) + scopedClientLines({ view, scopedType: "ReadOnlyClientInstance" }) + ` /** Idempotent \u2014 restricting an already-restricted handle returns the same handle. */
2142
2206
  ` + ` readonly $readOnly: () => ReadOnlyClientInstance;
2143
2207
  ` + extensionLines + ` // Deliberately ABSENT: every write method, $queryRaw / $executeRaw / $queryRawUnsafe / $executeRawUnsafe, $connect / $disconnect, $advisoryLock / $tryAdvisoryLock, $withContext, and every extension surface its author did not classify as a read.
@@ -2163,6 +2227,8 @@ function emitDts(params) {
2163
2227
  ...hasComputed ? ["ComputedConfig"] : []
2164
2228
  ].sort();
2165
2229
  const runtimeTypeImports = [
2230
+ "ClientContext",
2231
+ "QueryOperation",
2166
2232
  "SqlFragment",
2167
2233
  "ResultDecoder",
2168
2234
  "SqlColumn",
@@ -2178,7 +2244,6 @@ function emitDts(params) {
2178
2244
  const importLines = [
2179
2245
  `import type { ${runtimeTypeImports.join(", ")} } from "@vibeorm/runtime";`,
2180
2246
  `import type { ${schemaTypeImports.join(", ")} } from "@vibeorm/schema";`,
2181
- ...jsonTypeImportLines({ view }),
2182
2247
  ...baked.dtsImports,
2183
2248
  `import { ${enumEntryExports({ view }).join(", ")} } from "./enums.js";`
2184
2249
  ];
@@ -2240,7 +2305,7 @@ ${view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}:
2240
2305
  }
2241
2306
 
2242
2307
  // packages/generator/src/schema-view.ts
2243
- import { VibeError as VibeError4, assertValidSchemaIR, enumStorageOf, isEnumType } from "@vibeorm/schema";
2308
+ import { VibeError as VibeError5, assertValidSchemaIR, enumStorageOf, isEnumType } from "@vibeorm/schema";
2244
2309
  import { postgresDialect, renderRlsPolicy } from "@vibeorm/sql";
2245
2310
  var JSON_TYPE_RE = /@type\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+"([^"]+)"\s*\)/;
2246
2311
  function parseJsonTypeAnnotation(params) {
@@ -2259,7 +2324,7 @@ function buildFieldView(params) {
2259
2324
  const enumRef = isEnumType(field.type);
2260
2325
  const annotation = parseJsonTypeAnnotation({ comment: field.documentation });
2261
2326
  if (annotation !== null && (enumRef || field.type !== "Json")) {
2262
- throw new VibeError4({
2327
+ throw new VibeError5({
2263
2328
  code: "VIBE_SCHEMA_INVALID",
2264
2329
  message: `@type(...) annotation on ${model}.${field.name} \u2014 typed JSON is only valid on Json fields`,
2265
2330
  meta: { model, field: field.name }
@@ -2333,7 +2398,7 @@ function buildModelView(params) {
2333
2398
  target: relation.targetModel,
2334
2399
  type: relation.type,
2335
2400
  isList: relation.isList,
2336
- isOptional: relation.isOptional,
2401
+ isOptional: relation.isOptional || !relation.isList && (relation.fields?.length ?? 0) === 0,
2337
2402
  fkFields: relation.fields ?? [],
2338
2403
  inverseName: target === undefined ? null : resolveInverse({ relation, target })
2339
2404
  };
@@ -2396,12 +2461,15 @@ async function generateClient(params) {
2396
2461
  validateComputedConfig({ schema, computed: params.computed });
2397
2462
  computed = buildComputedManifest({ computed: params.computed });
2398
2463
  }
2464
+ if (params.module !== undefined && (!baked.empty || computed.length > 0))
2465
+ 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 } });
2399
2466
  const outputs = [
2400
2467
  { name: "index.js", content: emitJs({ view, baked, computed }) },
2401
2468
  { name: "index.d.ts", content: emitDts({ view, baked, computed }) },
2402
2469
  { name: "enums.js", content: emitEnumsJs({ view }) },
2403
2470
  { name: "enums.d.ts", content: emitEnumsDts({ view }) },
2404
- { name: "README.md", content: README }
2471
+ { name: "README.md", content: README },
2472
+ ...params.module === undefined ? [] : emitDatabaseModule({ view, module: params.module })
2405
2473
  ];
2406
2474
  await mkdir(outDir, { recursive: true });
2407
2475
  const files = [];
@@ -2413,8 +2481,8 @@ async function generateClient(params) {
2413
2481
  return { files };
2414
2482
  }
2415
2483
  export {
2416
- generateClient,
2417
- bakeExtensions
2484
+ bakeExtensions,
2485
+ generateClient
2418
2486
  };
2419
2487
 
2420
- //# debugId=F1C487D9764B944164756E2164756E21
2488
+ //# debugId=F8DCFFF006F16A9E64756E2164756E21