@vibeorm/generator 2.1.1 → 2.2.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/emit-dts.d.ts.map +1 -1
- package/dist/emit-module.d.ts +11 -0
- package/dist/emit-module.d.ts.map +1 -0
- package/dist/generate.d.ts +3 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/index.js +203 -135
- package/dist/index.js.map +10 -9
- package/dist/schema-view.d.ts.map +1 -1
- package/dist/type-names.d.ts.map +1 -1
- package/package.json +3 -3
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
|
|
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
|
|
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 =
|
|
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
|
|
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.`;
|
|
@@ -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[
|
|
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
|
|
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
|
|
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):
|
|
1478
|
-
` findFirst<A extends ${n}FindFirstArgs = {}>(args?: A):
|
|
1479
|
-
` findFirstOrThrow<A extends ${n}FindFirstArgs = {}>(args?: A):
|
|
1480
|
-
` findUnique<A extends ${n}FindUniqueArgs>(args: A):
|
|
1481
|
-
` findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: 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):
|
|
1485
|
-
` create<A extends ${n}CreateArgs>(args: A):
|
|
1486
|
-
` createMany(args: ${n}CreateManyArgs):
|
|
1487
|
-
` createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A):
|
|
1488
|
-
` update<A extends ${n}UpdateArgs>(args: A):
|
|
1489
|
-
` updateMany(args: ${n}UpdateManyArgs):
|
|
1490
|
-
` updateManyAndReturn<A extends ${n}UpdateManyAndReturnArgs>(args: A):
|
|
1491
|
-
` upsert<A extends ${n}UpsertArgs>(args: A):
|
|
1492
|
-
` upsertMany(args: ${n}UpsertManyArgs):
|
|
1493
|
-
` delete<A extends ${n}DeleteArgs>(args: A):
|
|
1494
|
-
` deleteMany(args?: ${n}DeleteManyArgs):
|
|
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):
|
|
1498
|
-
` aggregate<A extends ${n}AggregateArgs>(args: A):
|
|
1499
|
-
` groupBy<const A extends ${n}GroupByArgs>(args: 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
|
|
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) =>
|
|
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
|
|
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
|
|
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 = [];
|
|
@@ -2417,4 +2485,4 @@ export {
|
|
|
2417
2485
|
bakeExtensions
|
|
2418
2486
|
};
|
|
2419
2487
|
|
|
2420
|
-
//# debugId=
|
|
2488
|
+
//# debugId=732A6C5C8F7D397B64756E2164756E21
|