@vibeorm/generator 2.0.0-alpha.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/emit-dts.d.ts +8 -0
- package/dist/emit-dts.d.ts.map +1 -1
- package/dist/emit-js.d.ts +19 -3
- package/dist/emit-js.d.ts.map +1 -1
- package/dist/generate.d.ts +2 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/index.js +743 -283
- package/dist/index.js.map +8 -8
- package/dist/schema-view.d.ts +3 -1
- package/dist/schema-view.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -7,6 +7,9 @@ import { buildComputedManifest, validateComputedConfig } from "@vibeorm/schema";
|
|
|
7
7
|
// packages/generator/src/emit-dts.ts
|
|
8
8
|
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
9
9
|
|
|
10
|
+
// packages/generator/src/emit-js.ts
|
|
11
|
+
import { internalError } from "@vibeorm/schema";
|
|
12
|
+
|
|
10
13
|
// packages/generator/src/extension-bake.ts
|
|
11
14
|
import {
|
|
12
15
|
VibeError,
|
|
@@ -128,6 +131,235 @@ function bakeExtensions(params) {
|
|
|
128
131
|
};
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
// packages/generator/src/util.ts
|
|
135
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
136
|
+
function pascal(params) {
|
|
137
|
+
const { name } = params;
|
|
138
|
+
if (name.length === 0)
|
|
139
|
+
return name;
|
|
140
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
141
|
+
}
|
|
142
|
+
function clientNameOf(params) {
|
|
143
|
+
const { name } = params;
|
|
144
|
+
if (name.length === 0)
|
|
145
|
+
return name;
|
|
146
|
+
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
147
|
+
}
|
|
148
|
+
var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
149
|
+
function propKey(params) {
|
|
150
|
+
const { name } = params;
|
|
151
|
+
return IDENTIFIER_RE.test(name) ? name : JSON.stringify(name);
|
|
152
|
+
}
|
|
153
|
+
function tsString(params) {
|
|
154
|
+
return JSON.stringify(params.value);
|
|
155
|
+
}
|
|
156
|
+
function maybeParens(params) {
|
|
157
|
+
const { type } = params;
|
|
158
|
+
return type.includes("|") ? `(${type})` : type;
|
|
159
|
+
}
|
|
160
|
+
function stableStringify(params) {
|
|
161
|
+
return renderValue({ value: params.value, indent: "" });
|
|
162
|
+
}
|
|
163
|
+
function renderValue(params) {
|
|
164
|
+
const { value, indent: indent2 } = params;
|
|
165
|
+
if (value === null)
|
|
166
|
+
return "null";
|
|
167
|
+
const kind = typeof value;
|
|
168
|
+
if (kind === "string" || kind === "number" || kind === "boolean")
|
|
169
|
+
return JSON.stringify(value);
|
|
170
|
+
if (Array.isArray(value)) {
|
|
171
|
+
if (value.length === 0)
|
|
172
|
+
return "[]";
|
|
173
|
+
const inner = `${indent2} `;
|
|
174
|
+
const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
|
|
175
|
+
return `[
|
|
176
|
+
${items.join(`,
|
|
177
|
+
`)}
|
|
178
|
+
${indent2}]`;
|
|
179
|
+
}
|
|
180
|
+
if (kind === "object") {
|
|
181
|
+
const record = value;
|
|
182
|
+
const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort();
|
|
183
|
+
if (keys.length === 0)
|
|
184
|
+
return "{}";
|
|
185
|
+
const inner = `${indent2} `;
|
|
186
|
+
const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
|
|
187
|
+
return `{
|
|
188
|
+
${items.join(`,
|
|
189
|
+
`)}
|
|
190
|
+
${indent2}}`;
|
|
191
|
+
}
|
|
192
|
+
throw new VibeError2({
|
|
193
|
+
code: "VIBE_INTERNAL",
|
|
194
|
+
message: `internal: cannot serialize a ${kind} value into the generated client`,
|
|
195
|
+
meta: { kind }
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// packages/generator/src/emit-js.ts
|
|
200
|
+
var HEADER = `// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
|
|
201
|
+
// Regenerate with \`vibeorm generate\` after every schema change.`;
|
|
202
|
+
function enumEntryExports(params) {
|
|
203
|
+
return [...params.view.enums.map((declared) => declared.name), "enumValidators"];
|
|
204
|
+
}
|
|
205
|
+
function emitEnumsJs(params) {
|
|
206
|
+
const { view } = params;
|
|
207
|
+
const parts = [
|
|
208
|
+
`${HEADER}
|
|
209
|
+
// Lightweight entry: enum constants + boundary parsers. No runtime client, no schema module.`
|
|
210
|
+
];
|
|
211
|
+
if (view.enums.length === 0) {
|
|
212
|
+
parts.push(`/** Boundary parsers grouped by enum name \u2014 this schema declares no enums. */
|
|
213
|
+
` + `export const enumValidators = Object.freeze({});`);
|
|
214
|
+
return `${parts.join(`
|
|
215
|
+
|
|
216
|
+
`)}
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
parts.push(`import { VibeError } from "@vibeorm/schema";`);
|
|
220
|
+
for (const declared of view.enums) {
|
|
221
|
+
const entries = declared.values.map((value) => ` ${value === "__proto__" ? `[${tsString({ value })}]` : tsString({ value })}: ${tsString({ value })},`).join(`
|
|
222
|
+
`);
|
|
223
|
+
parts.push(`export const ${declared.name} = Object.freeze({
|
|
224
|
+
${entries}
|
|
225
|
+
});`);
|
|
226
|
+
}
|
|
227
|
+
parts.push(`/** Membership validator for one enum: \`values\`, \`is\`, \`parse\` (VIBE_VALIDATION on a non-member). */
|
|
228
|
+
` + `function enumValidator(params) {
|
|
229
|
+
` + ` const { name, values } = params;
|
|
230
|
+
` + ` const members = new Set(values);
|
|
231
|
+
` + ` const is = (value) => typeof value === "string" && members.has(value);
|
|
232
|
+
` + ` return Object.freeze({
|
|
233
|
+
` + ` values: Object.freeze([...values]),
|
|
234
|
+
` + ` is,
|
|
235
|
+
` + ` parse(input) {
|
|
236
|
+
` + ` const { value } = input;
|
|
237
|
+
` + ` if (is(value)) return value;
|
|
238
|
+
` + ` const received = typeof value === "string" ? value.slice(0, 80) : value === null ? "null" : typeof value;
|
|
239
|
+
` + ` throw new VibeError({
|
|
240
|
+
` + ` code: "VIBE_VALIDATION",
|
|
241
|
+
` + ` message: \`Invalid \${name} value \${JSON.stringify(received)} \u2014 expected one of \${values.join(", ")}\`,
|
|
242
|
+
` + ` meta: { enum: name, received },
|
|
243
|
+
` + ` });
|
|
244
|
+
` + ` },
|
|
245
|
+
` + ` });
|
|
246
|
+
` + `}`);
|
|
247
|
+
const validatorLines = view.enums.map((declared) => ` ${declared.name === "__proto__" ? `[${tsString({ value: declared.name })}]` : declared.name}: enumValidator({ name: ${tsString({ value: declared.name })}, values: [${declared.values.map((value) => tsString({ value })).join(", ")}] }),`).join(`
|
|
248
|
+
`);
|
|
249
|
+
parts.push(`/** Boundary parsers grouped by enum name, so an enum member named \`parse\` cannot collide. */
|
|
250
|
+
` + `export const enumValidators = Object.freeze({
|
|
251
|
+
${validatorLines}
|
|
252
|
+
});`);
|
|
253
|
+
return `${parts.join(`
|
|
254
|
+
|
|
255
|
+
`)}
|
|
256
|
+
`;
|
|
257
|
+
}
|
|
258
|
+
function serializeSchema(params) {
|
|
259
|
+
const { schema } = params;
|
|
260
|
+
if (schema.rls === undefined)
|
|
261
|
+
return stableStringify({ value: schema });
|
|
262
|
+
return renderIr({ value: schema, indent: "", ordered: false });
|
|
263
|
+
}
|
|
264
|
+
function renderIr(params) {
|
|
265
|
+
const { value, indent: indent2, ordered } = params;
|
|
266
|
+
if (value === null)
|
|
267
|
+
return "null";
|
|
268
|
+
const kind = typeof value;
|
|
269
|
+
if (kind === "string" || kind === "number" || kind === "boolean")
|
|
270
|
+
return JSON.stringify(value);
|
|
271
|
+
if (Array.isArray(value)) {
|
|
272
|
+
if (value.length === 0)
|
|
273
|
+
return "[]";
|
|
274
|
+
const inner2 = `${indent2} `;
|
|
275
|
+
const items2 = value.map((item) => inner2 + renderIr({ value: item, indent: inner2, ordered }));
|
|
276
|
+
return `[
|
|
277
|
+
${items2.join(`,
|
|
278
|
+
`)}
|
|
279
|
+
${indent2}]`;
|
|
280
|
+
}
|
|
281
|
+
if (kind !== "object") {
|
|
282
|
+
throw internalError({ message: `internal: cannot serialize a ${kind} value into the generated client`, meta: { kind } });
|
|
283
|
+
}
|
|
284
|
+
const record = value;
|
|
285
|
+
const defined = Object.keys(record).filter((key) => record[key] !== undefined);
|
|
286
|
+
const keys = ordered ? defined : defined.sort();
|
|
287
|
+
if (keys.length === 0)
|
|
288
|
+
return "{}";
|
|
289
|
+
const inner = `${indent2} `;
|
|
290
|
+
const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderIr({ value: record[key], indent: inner, ordered: ordered || key === "rls" })}`);
|
|
291
|
+
return `{
|
|
292
|
+
${items.join(`,
|
|
293
|
+
`)}
|
|
294
|
+
${indent2}}`;
|
|
295
|
+
}
|
|
296
|
+
function emitJs(params) {
|
|
297
|
+
const { view } = params;
|
|
298
|
+
const baked = params.baked ?? EMPTY_BAKED_EXTENSIONS;
|
|
299
|
+
const computed = params.computed ?? [];
|
|
300
|
+
const hasComputed = computed.length > 0;
|
|
301
|
+
const parts = [];
|
|
302
|
+
parts.push(HEADER);
|
|
303
|
+
parts.push(`import { createClient } from "@vibeorm/runtime";`);
|
|
304
|
+
if (baked.jsImports.length > 0)
|
|
305
|
+
parts.push(baked.jsImports.join(`
|
|
306
|
+
`));
|
|
307
|
+
parts.push(`export { VibeError } from "@vibeorm/schema";`);
|
|
308
|
+
parts.push(`export { SKIP } from "@vibeorm/runtime";`);
|
|
309
|
+
parts.push(`export { ${enumEntryExports({ view }).join(", ")} } from "./enums.js";`);
|
|
310
|
+
parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
|
|
311
|
+
export const schema = ${serializeSchema({ schema: view.schema })};`);
|
|
312
|
+
if (baked.empty && !hasComputed) {
|
|
313
|
+
parts.push(`/** Build a typed client over a database adapter. */
|
|
314
|
+
` + `export function VibeClient(options) {
|
|
315
|
+
` + ` const { adapter, ...clientOptions } = options;
|
|
316
|
+
` + ` return createClient({ schema, adapter, options: clientOptions });
|
|
317
|
+
` + `}`);
|
|
318
|
+
return `${parts.join(`
|
|
319
|
+
|
|
320
|
+
`)}
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
if (!baked.empty) {
|
|
324
|
+
parts.push(`/** Extension manifest \u2014 verified against the live extension list (VIBE_STALE_CLIENT). */
|
|
325
|
+
` + `const extensionManifest = ${stableStringify({ value: baked.manifest })};`);
|
|
326
|
+
parts.push(`/** Baked extension surfaces (extensions.md D9). */
|
|
327
|
+
` + `const extensionMounts = [
|
|
328
|
+
${baked.mounts.map((mount) => ` ${mount},`).join(`
|
|
329
|
+
`)}
|
|
330
|
+
];`);
|
|
331
|
+
}
|
|
332
|
+
if (hasComputed) {
|
|
333
|
+
parts.push(`/** Computed-field manifest \u2014 verified against the live computed config (VIBE_STALE_CLIENT). */
|
|
334
|
+
` + `const computedManifest = ${stableStringify({ value: computed })};`);
|
|
335
|
+
}
|
|
336
|
+
const destructured = [
|
|
337
|
+
"adapter",
|
|
338
|
+
...baked.empty ? [] : ["extensions"],
|
|
339
|
+
...hasComputed ? ["computed"] : [],
|
|
340
|
+
"...clientOptions"
|
|
341
|
+
].join(", ");
|
|
342
|
+
const createLines = [
|
|
343
|
+
` schema,`,
|
|
344
|
+
` adapter,`,
|
|
345
|
+
` options: clientOptions,`,
|
|
346
|
+
...baked.empty ? [] : [` extensions: { manifest: extensionManifest, mounts: extensionMounts, instances: extensions ?? [] },`],
|
|
347
|
+
...hasComputed ? [` computed: { manifest: computedManifest, specs: computed },`] : []
|
|
348
|
+
];
|
|
349
|
+
parts.push(`/** Build a typed client over a database adapter. */
|
|
350
|
+
` + `export function VibeClient(options) {
|
|
351
|
+
` + ` const { ${destructured} } = options;
|
|
352
|
+
` + ` return createClient({
|
|
353
|
+
${createLines.join(`
|
|
354
|
+
`)}
|
|
355
|
+
});
|
|
356
|
+
` + `}`);
|
|
357
|
+
return `${parts.join(`
|
|
358
|
+
|
|
359
|
+
`)}
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
|
|
131
363
|
// packages/generator/src/type-names.ts
|
|
132
364
|
var PAYLOAD_TYPES = {
|
|
133
365
|
String: "string",
|
|
@@ -198,71 +430,6 @@ function updateOpsName(params) {
|
|
|
198
430
|
return field.isOptional ? `Nullable${prefix}FieldUpdateOperationsInput` : `${prefix}FieldUpdateOperationsInput`;
|
|
199
431
|
}
|
|
200
432
|
|
|
201
|
-
// packages/generator/src/util.ts
|
|
202
|
-
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
203
|
-
function pascal(params) {
|
|
204
|
-
const { name } = params;
|
|
205
|
-
if (name.length === 0)
|
|
206
|
-
return name;
|
|
207
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
208
|
-
}
|
|
209
|
-
function clientNameOf(params) {
|
|
210
|
-
const { name } = params;
|
|
211
|
-
if (name.length === 0)
|
|
212
|
-
return name;
|
|
213
|
-
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
214
|
-
}
|
|
215
|
-
var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
216
|
-
function propKey(params) {
|
|
217
|
-
const { name } = params;
|
|
218
|
-
return IDENTIFIER_RE.test(name) ? name : JSON.stringify(name);
|
|
219
|
-
}
|
|
220
|
-
function tsString(params) {
|
|
221
|
-
return JSON.stringify(params.value);
|
|
222
|
-
}
|
|
223
|
-
function maybeParens(params) {
|
|
224
|
-
const { type } = params;
|
|
225
|
-
return type.includes("|") ? `(${type})` : type;
|
|
226
|
-
}
|
|
227
|
-
function stableStringify(params) {
|
|
228
|
-
return renderValue({ value: params.value, indent: "" });
|
|
229
|
-
}
|
|
230
|
-
function renderValue(params) {
|
|
231
|
-
const { value, indent: indent2 } = params;
|
|
232
|
-
if (value === null)
|
|
233
|
-
return "null";
|
|
234
|
-
const kind = typeof value;
|
|
235
|
-
if (kind === "string" || kind === "number" || kind === "boolean")
|
|
236
|
-
return JSON.stringify(value);
|
|
237
|
-
if (Array.isArray(value)) {
|
|
238
|
-
if (value.length === 0)
|
|
239
|
-
return "[]";
|
|
240
|
-
const inner = `${indent2} `;
|
|
241
|
-
const items = value.map((item) => inner + renderValue({ value: item, indent: inner }));
|
|
242
|
-
return `[
|
|
243
|
-
${items.join(`,
|
|
244
|
-
`)}
|
|
245
|
-
${indent2}]`;
|
|
246
|
-
}
|
|
247
|
-
if (kind === "object") {
|
|
248
|
-
const record = value;
|
|
249
|
-
const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort();
|
|
250
|
-
if (keys.length === 0)
|
|
251
|
-
return "{}";
|
|
252
|
-
const inner = `${indent2} `;
|
|
253
|
-
const items = keys.map((key) => `${inner}${JSON.stringify(key)}: ${renderValue({ value: record[key], indent: inner })}`);
|
|
254
|
-
return `{
|
|
255
|
-
${items.join(`,
|
|
256
|
-
`)}
|
|
257
|
-
${indent2}}`;
|
|
258
|
-
}
|
|
259
|
-
throw new VibeError2({
|
|
260
|
-
code: "VIBE_INTERNAL",
|
|
261
|
-
message: `internal: cannot serialize a ${kind} value into the generated client`,
|
|
262
|
-
meta: { kind }
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
433
|
// packages/generator/src/emit-dts.ts
|
|
267
434
|
function banner(params) {
|
|
268
435
|
return `// \u2500\u2500\u2500 ${params.title} \u2500\u2500\u2500`;
|
|
@@ -276,6 +443,23 @@ ${lines.join(`
|
|
|
276
443
|
`)}
|
|
277
444
|
};`;
|
|
278
445
|
}
|
|
446
|
+
var OPTIONAL_MEMBER = /^(\s*(?:readonly\s+)?(?:[A-Za-z_$][\w$]*|"(?:[^"\\]|\\.)*")\?:\s*)(.+);$/;
|
|
447
|
+
function skippableMembers(params) {
|
|
448
|
+
return params.block.split(`
|
|
449
|
+
`).map((line) => {
|
|
450
|
+
const match = OPTIONAL_MEMBER.exec(line);
|
|
451
|
+
const head = match?.[1];
|
|
452
|
+
const type = match?.[2];
|
|
453
|
+
if (head === undefined || type === undefined)
|
|
454
|
+
return line;
|
|
455
|
+
return `${head}${type.includes("=>") ? `(${type})` : type} | Skip;`;
|
|
456
|
+
}).join(`
|
|
457
|
+
`);
|
|
458
|
+
}
|
|
459
|
+
function inputAlias(params) {
|
|
460
|
+
const { name, lines } = params;
|
|
461
|
+
return objectAlias({ name, lines: lines.map((block) => skippableMembers({ block })) });
|
|
462
|
+
}
|
|
279
463
|
function orMany(params) {
|
|
280
464
|
const { type } = params;
|
|
281
465
|
return `${type} | readonly ${maybeParens({ type })}[]`;
|
|
@@ -321,11 +505,66 @@ export type SortOrderWithNulls = SortOrder | { readonly sort: SortOrder; readonl
|
|
|
321
505
|
/** How relations are loaded: batched WHERE-IN queries, or a single JOIN (pg/pglite). */
|
|
322
506
|
export type RelationStrategy = "query" | "join";
|
|
323
507
|
|
|
508
|
+
/**
|
|
509
|
+
* An opaque, versioned keyset position (\`after\`) \u2014 ordering values plus a
|
|
510
|
+
* unique tie-breaker, minted by a previous page. It is ENCODED, not encrypted:
|
|
511
|
+
* whoever holds it can read the ordering values inside. It is also NOT an
|
|
512
|
+
* authorization credential \u2014 it selects a position, never a row, and every
|
|
513
|
+
* query still applies its own where/scoped/policy/RLS filters.
|
|
514
|
+
*/
|
|
515
|
+
export type KeysetToken = string;
|
|
516
|
+
|
|
517
|
+
/** Per-query keyset options. */
|
|
518
|
+
export type KeysetOptions = {
|
|
519
|
+
/**
|
|
520
|
+
* Asserts that every DateTime ordering column in this query holds
|
|
521
|
+
* millisecond-exact values. Required on postgres and mysql, whose timestamps
|
|
522
|
+
* may be finer than a JavaScript Date; NOT safe for \`@default(now())\`
|
|
523
|
+
* columns on postgres, which are microsecond-precise.
|
|
524
|
+
*/
|
|
525
|
+
readonly timestampPrecision?: "milliseconds";
|
|
526
|
+
};
|
|
527
|
+
|
|
324
528
|
/** Result of createMany/updateMany/deleteMany. */
|
|
325
529
|
export type BatchPayload = { readonly count: number };
|
|
326
530
|
|
|
531
|
+
/**
|
|
532
|
+
* What upsertMany reports. \`count\` is the rows submitted after duplicate-key
|
|
533
|
+
* validation \u2014 every one of them was inserted or updated, since the statement
|
|
534
|
+
* has no skip arm and the whole operation is atomic. \`affectedRows\` is the
|
|
535
|
+
* driver's own number, and only where that number IS a row count: it is null on
|
|
536
|
+
* mysql, whose ON DUPLICATE KEY scores an insert 1, a changed update 2 and an
|
|
537
|
+
* unchanged update 0.
|
|
538
|
+
*/
|
|
539
|
+
export type UpsertManyPayload = { readonly count: number; readonly affectedRows: number | null };
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* An argument member as the RESULT machinery must read it. Absent and the
|
|
543
|
+
* \`SKIP\` sentinel both mean "not provided", so neither may narrow a payload:
|
|
544
|
+
* without this, \`{ omit?: UserOmitInput | Skip }\` would collapse the key
|
|
545
|
+
* helpers below to \`never\` and promise fields the query may still have dropped.
|
|
546
|
+
*/
|
|
547
|
+
type Provided<T> = Exclude<T, Skip | undefined>;
|
|
548
|
+
|
|
327
549
|
/** Keys of an omit object whose value is exactly true \u2014 the scalars the result drops. */
|
|
328
|
-
type OmittedKeys<O> = keyof { [K in keyof O as O[K] extends true ? K : never]: 0 }
|
|
550
|
+
type OmittedKeys<O> = keyof { [K in keyof O as O[K] extends true ? K : never]: 0 };
|
|
551
|
+
|
|
552
|
+
/** Keys a selection has not RULED OUT \u2014 everything except a literal false/undefined. */
|
|
553
|
+
type PossibleKeys<S> = keyof { [K in keyof S as S[K] extends false | undefined ? never : K]: 0 };
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Keys whose flag can STILL be false or missing when the query runs \u2014 a widened
|
|
557
|
+
* \`boolean\`, or an optional nested selection. They are emitted OPTIONAL: the
|
|
558
|
+
* result type must not promise a field a run-time flag can switch off.
|
|
559
|
+
*/
|
|
560
|
+
type UncertainKeys<S> = PossibleKeys<S> &
|
|
561
|
+
keyof { [K in keyof S as [false] extends [S[K]] ? K : [undefined] extends [S[K]] ? K : never]: 0 };
|
|
562
|
+
|
|
563
|
+
/** Keys the selection definitely turns on: a literal \`true\`, or a nested selection object. */
|
|
564
|
+
type ExactKeys<S> = Exclude<PossibleKeys<S>, UncertainKeys<S>>;
|
|
565
|
+
|
|
566
|
+
/** An aggregate object is absent when none of its selectors is enabled. */
|
|
567
|
+
type HasSelectedField<S> = [S] extends [true] ? true : [S] extends [object] ? ExactKeys<S> extends never ? false : true : false;`;
|
|
329
568
|
var SCALAR_FILTER_CONFIGS = [
|
|
330
569
|
{ prefix: "String", value: "string", comparable: true, text: true },
|
|
331
570
|
{ prefix: "Int", value: "number", comparable: true, text: false },
|
|
@@ -360,13 +599,13 @@ function scalarFilterAlias(params) {
|
|
|
360
599
|
lines.push(` readonly mode?: QueryMode;`);
|
|
361
600
|
}
|
|
362
601
|
lines.push(` readonly not?: ${value} | ${name}${nullable ? " | null" : ""};`);
|
|
363
|
-
return
|
|
602
|
+
return inputAlias({ name, lines });
|
|
364
603
|
}
|
|
365
604
|
function scalarListFilterAlias(params) {
|
|
366
605
|
const { config } = params;
|
|
367
606
|
const value = config.value;
|
|
368
607
|
const array = `readonly ${maybeParens({ type: value })}[]`;
|
|
369
|
-
return
|
|
608
|
+
return inputAlias({
|
|
370
609
|
name: `${config.prefix}ListFilter`,
|
|
371
610
|
lines: [
|
|
372
611
|
` readonly equals?: ${array} | null;`,
|
|
@@ -386,7 +625,7 @@ var UPDATE_OPS_CONFIGS = [
|
|
|
386
625
|
function updateOpsAlias(params) {
|
|
387
626
|
const { prefix, value, nullable } = params;
|
|
388
627
|
const name = nullable ? `Nullable${prefix}FieldUpdateOperationsInput` : `${prefix}FieldUpdateOperationsInput`;
|
|
389
|
-
return
|
|
628
|
+
return inputAlias({
|
|
390
629
|
name,
|
|
391
630
|
lines: [
|
|
392
631
|
` readonly set?: ${nullable ? `${value} | null` : value};`,
|
|
@@ -397,6 +636,25 @@ function updateOpsAlias(params) {
|
|
|
397
636
|
]
|
|
398
637
|
});
|
|
399
638
|
}
|
|
639
|
+
function jsonFilterAlias(params) {
|
|
640
|
+
const { nullable } = params;
|
|
641
|
+
const document = nullable ? "InputJsonValue | null" : "InputJsonValue";
|
|
642
|
+
return inputAlias({
|
|
643
|
+
name: nullable ? "JsonNullableFilter" : "JsonFilter",
|
|
644
|
+
lines: [
|
|
645
|
+
` readonly path?: readonly (string | number)[];`,
|
|
646
|
+
` readonly mode?: "default" | "insensitive";`,
|
|
647
|
+
` readonly equals?: ${document};`,
|
|
648
|
+
` readonly not?: ${document};`,
|
|
649
|
+
` readonly string_contains?: string;`,
|
|
650
|
+
` readonly string_starts_with?: string;`,
|
|
651
|
+
` readonly string_ends_with?: string;`,
|
|
652
|
+
` readonly array_contains?: InputJsonValue;`,
|
|
653
|
+
` readonly array_starts_with?: InputJsonValue;`,
|
|
654
|
+
` readonly array_ends_with?: InputJsonValue;`
|
|
655
|
+
]
|
|
656
|
+
});
|
|
657
|
+
}
|
|
400
658
|
function filterSection() {
|
|
401
659
|
const parts = [banner({ title: "Scalar filters" })];
|
|
402
660
|
for (const config of SCALAR_FILTER_CONFIGS) {
|
|
@@ -404,15 +662,9 @@ function filterSection() {
|
|
|
404
662
|
parts.push(scalarFilterAlias({ config, nullable: true }));
|
|
405
663
|
parts.push(scalarListFilterAlias({ config }));
|
|
406
664
|
}
|
|
407
|
-
parts.push(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}));
|
|
411
|
-
parts.push(objectAlias({
|
|
412
|
-
name: "JsonNullableFilter",
|
|
413
|
-
lines: [` readonly equals?: InputJsonValue | null;`, ` readonly not?: InputJsonValue | null;`]
|
|
414
|
-
}));
|
|
415
|
-
parts.push(objectAlias({
|
|
665
|
+
parts.push(jsonFilterAlias({ nullable: false }));
|
|
666
|
+
parts.push(jsonFilterAlias({ nullable: true }));
|
|
667
|
+
parts.push(inputAlias({
|
|
416
668
|
name: "JsonListFilter",
|
|
417
669
|
lines: [
|
|
418
670
|
` readonly equals?: readonly InputJsonValue[] | null;`,
|
|
@@ -429,18 +681,55 @@ function filterSection() {
|
|
|
429
681
|
}
|
|
430
682
|
return parts;
|
|
431
683
|
}
|
|
432
|
-
|
|
684
|
+
var GENERATED_HEADER = `// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
|
|
685
|
+
// Regenerate with \`vibeorm generate\` after every schema change.`;
|
|
686
|
+
function emitEnumDeclaration(params) {
|
|
433
687
|
const { declared } = params;
|
|
434
688
|
const name = declared.name;
|
|
435
|
-
const parts = [banner({ title: `Enum ${name}` })];
|
|
436
689
|
const valueLines = declared.values.map((value) => ` readonly ${propKey({ name: value })}: ${tsString({ value })};`);
|
|
437
|
-
|
|
690
|
+
return [
|
|
691
|
+
banner({ title: `Enum ${name}` }),
|
|
692
|
+
`export declare const ${name}: {
|
|
438
693
|
${valueLines.join(`
|
|
439
694
|
`)}
|
|
440
|
-
}
|
|
441
|
-
|
|
695
|
+
};`,
|
|
696
|
+
`export type ${name} = ${declared.values.map((value) => tsString({ value })).join(" | ")};`,
|
|
697
|
+
`/** Boundary validator for ${name}: \`values\`, \`is\`, \`parse\` (throws VIBE_VALIDATION on a non-member). */
|
|
698
|
+
` + `export type ${name}Validator = {
|
|
699
|
+
` + ` readonly values: readonly ${name}[];
|
|
700
|
+
` + ` is(value: unknown): value is ${name};
|
|
701
|
+
` + ` parse(params: { readonly value: unknown }): ${name};
|
|
702
|
+
` + `};`
|
|
703
|
+
].join(`
|
|
704
|
+
|
|
705
|
+
`);
|
|
706
|
+
}
|
|
707
|
+
function emitEnumsDts(params) {
|
|
708
|
+
const { view } = params;
|
|
709
|
+
const parts = [
|
|
710
|
+
`${GENERATED_HEADER}
|
|
711
|
+
// Lightweight entry: enum constants + boundary parsers. No runtime client, no schema module.`
|
|
712
|
+
];
|
|
713
|
+
for (const declared of view.enums)
|
|
714
|
+
parts.push(emitEnumDeclaration({ declared }));
|
|
715
|
+
parts.push(banner({ title: "Enum validators" }));
|
|
716
|
+
const validatorLines = view.enums.map((declared) => ` readonly ${declared.name}: ${declared.name}Validator;`);
|
|
717
|
+
parts.push(`/** Boundary parsers grouped by enum name, so an enum member named \`parse\` cannot collide. */
|
|
718
|
+
` + (validatorLines.length === 0 ? `export declare const enumValidators: Record<string, never>;` : `export declare const enumValidators: {
|
|
719
|
+
${validatorLines.join(`
|
|
720
|
+
`)}
|
|
721
|
+
};`));
|
|
722
|
+
return `${parts.join(`
|
|
723
|
+
|
|
724
|
+
`)}
|
|
725
|
+
`;
|
|
726
|
+
}
|
|
727
|
+
function emitEnum(params) {
|
|
728
|
+
const { declared } = params;
|
|
729
|
+
const name = declared.name;
|
|
730
|
+
const parts = [banner({ title: `Enum ${name} filters` })];
|
|
442
731
|
const array = `readonly ${name}[]`;
|
|
443
|
-
parts.push(
|
|
732
|
+
parts.push(inputAlias({
|
|
444
733
|
name: `${name}Filter`,
|
|
445
734
|
lines: [
|
|
446
735
|
` readonly equals?: ${name};`,
|
|
@@ -449,7 +738,7 @@ ${valueLines.join(`
|
|
|
449
738
|
` readonly not?: ${name} | ${name}Filter;`
|
|
450
739
|
]
|
|
451
740
|
}));
|
|
452
|
-
parts.push(
|
|
741
|
+
parts.push(inputAlias({
|
|
453
742
|
name: `${name}NullableFilter`,
|
|
454
743
|
lines: [
|
|
455
744
|
` readonly equals?: ${name} | null;`,
|
|
@@ -458,7 +747,7 @@ ${valueLines.join(`
|
|
|
458
747
|
` readonly not?: ${name} | ${name}NullableFilter | null;`
|
|
459
748
|
]
|
|
460
749
|
}));
|
|
461
|
-
parts.push(
|
|
750
|
+
parts.push(inputAlias({
|
|
462
751
|
name: `${name}ListFilter`,
|
|
463
752
|
lines: [
|
|
464
753
|
` readonly equals?: ${array} | null;`,
|
|
@@ -575,7 +864,7 @@ function registerNestedContext(params) {
|
|
|
575
864
|
register({
|
|
576
865
|
registry,
|
|
577
866
|
name: `${t}Create${suffix}Input`,
|
|
578
|
-
text:
|
|
867
|
+
text: inputAlias({
|
|
579
868
|
name: `${t}Create${suffix}Input`,
|
|
580
869
|
lines: checkedCreateLines({ model: target, omitRelation: inverse })
|
|
581
870
|
})
|
|
@@ -583,7 +872,7 @@ function registerNestedContext(params) {
|
|
|
583
872
|
register({
|
|
584
873
|
registry,
|
|
585
874
|
name: `${t}UncheckedCreate${suffix}Input`,
|
|
586
|
-
text:
|
|
875
|
+
text: inputAlias({
|
|
587
876
|
name: `${t}UncheckedCreate${suffix}Input`,
|
|
588
877
|
lines: uncheckedCreateLines({ model: target, omitRelation: inverse })
|
|
589
878
|
})
|
|
@@ -591,7 +880,7 @@ function registerNestedContext(params) {
|
|
|
591
880
|
register({
|
|
592
881
|
registry,
|
|
593
882
|
name: `${t}Update${suffix}Input`,
|
|
594
|
-
text:
|
|
883
|
+
text: inputAlias({
|
|
595
884
|
name: `${t}Update${suffix}Input`,
|
|
596
885
|
lines: checkedUpdateLines({ model: target, omitRelation: inverse })
|
|
597
886
|
})
|
|
@@ -599,7 +888,7 @@ function registerNestedContext(params) {
|
|
|
599
888
|
register({
|
|
600
889
|
registry,
|
|
601
890
|
name: `${t}UncheckedUpdate${suffix}Input`,
|
|
602
|
-
text:
|
|
891
|
+
text: inputAlias({
|
|
603
892
|
name: `${t}UncheckedUpdate${suffix}Input`,
|
|
604
893
|
lines: uncheckedUpdateLines({ model: target, omitRelation: inverse })
|
|
605
894
|
})
|
|
@@ -609,7 +898,7 @@ function registerNestedContext(params) {
|
|
|
609
898
|
register({
|
|
610
899
|
registry,
|
|
611
900
|
name: connectOrCreateName,
|
|
612
|
-
text:
|
|
901
|
+
text: inputAlias({
|
|
613
902
|
name: connectOrCreateName,
|
|
614
903
|
lines: [` readonly where: ${t}WhereUniqueInput;`, ` readonly create: ${createUnion};`]
|
|
615
904
|
})
|
|
@@ -619,7 +908,7 @@ function registerNestedContext(params) {
|
|
|
619
908
|
register({
|
|
620
909
|
registry,
|
|
621
910
|
name: `${t}CreateNestedMany${suffix}Input`,
|
|
622
|
-
text:
|
|
911
|
+
text: inputAlias({
|
|
623
912
|
name: `${t}CreateNestedMany${suffix}Input`,
|
|
624
913
|
lines: [
|
|
625
914
|
` readonly create?: ${orMany({ type: createUnion })};`,
|
|
@@ -633,7 +922,7 @@ function registerNestedContext(params) {
|
|
|
633
922
|
register({
|
|
634
923
|
registry,
|
|
635
924
|
name: updateWithWhere,
|
|
636
|
-
text:
|
|
925
|
+
text: inputAlias({
|
|
637
926
|
name: updateWithWhere,
|
|
638
927
|
lines: [` readonly where: ${t}WhereUniqueInput;`, ` readonly data: ${updateUnion};`]
|
|
639
928
|
})
|
|
@@ -642,7 +931,7 @@ function registerNestedContext(params) {
|
|
|
642
931
|
register({
|
|
643
932
|
registry,
|
|
644
933
|
name: updateManyWithWhere,
|
|
645
|
-
text:
|
|
934
|
+
text: inputAlias({
|
|
646
935
|
name: updateManyWithWhere,
|
|
647
936
|
lines: [` readonly where: ${t}WhereInput;`, ` readonly data: ${t}UpdateManyInput;`]
|
|
648
937
|
})
|
|
@@ -651,7 +940,7 @@ function registerNestedContext(params) {
|
|
|
651
940
|
register({
|
|
652
941
|
registry,
|
|
653
942
|
name: upsertWithWhere,
|
|
654
|
-
text:
|
|
943
|
+
text: inputAlias({
|
|
655
944
|
name: upsertWithWhere,
|
|
656
945
|
lines: [
|
|
657
946
|
` readonly where: ${t}WhereUniqueInput;`,
|
|
@@ -663,7 +952,7 @@ function registerNestedContext(params) {
|
|
|
663
952
|
register({
|
|
664
953
|
registry,
|
|
665
954
|
name: `${t}UpdateNestedMany${suffix}Input`,
|
|
666
|
-
text:
|
|
955
|
+
text: inputAlias({
|
|
667
956
|
name: `${t}UpdateNestedMany${suffix}Input`,
|
|
668
957
|
lines: [
|
|
669
958
|
` readonly create?: ${orMany({ type: createUnion })};`,
|
|
@@ -685,7 +974,7 @@ function registerNestedContext(params) {
|
|
|
685
974
|
register({
|
|
686
975
|
registry,
|
|
687
976
|
name: `${t}CreateNestedOne${suffix}Input`,
|
|
688
|
-
text:
|
|
977
|
+
text: inputAlias({
|
|
689
978
|
name: `${t}CreateNestedOne${suffix}Input`,
|
|
690
979
|
lines: [
|
|
691
980
|
` readonly create?: ${createUnion};`,
|
|
@@ -697,7 +986,7 @@ function registerNestedContext(params) {
|
|
|
697
986
|
register({
|
|
698
987
|
registry,
|
|
699
988
|
name: `${t}UpdateNestedOne${suffix}Input`,
|
|
700
|
-
text:
|
|
989
|
+
text: inputAlias({
|
|
701
990
|
name: `${t}UpdateNestedOne${suffix}Input`,
|
|
702
991
|
lines: [
|
|
703
992
|
` readonly create?: ${createUnion};`,
|
|
@@ -711,7 +1000,6 @@ function registerNestedContext(params) {
|
|
|
711
1000
|
});
|
|
712
1001
|
}
|
|
713
1002
|
}
|
|
714
|
-
var AGGREGATE_OPS_UNION = `"_count" | "_avg" | "_sum" | "_min" | "_max"`;
|
|
715
1003
|
function emitModel(params) {
|
|
716
1004
|
const { model, view, registry } = params;
|
|
717
1005
|
const computedEntries = params.computed ?? [];
|
|
@@ -744,12 +1032,12 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
744
1032
|
const countLine = ` readonly _count?: boolean | { readonly select: ${n}CountSelect };`;
|
|
745
1033
|
const relationSelectValue = (relation) => `boolean | ${relation.target}${relation.isList ? "FindManyArgs" : "Args"}`;
|
|
746
1034
|
if (hasCounts) {
|
|
747
|
-
parts.push(
|
|
1035
|
+
parts.push(inputAlias({
|
|
748
1036
|
name: `${n}CountSelect`,
|
|
749
|
-
lines: listRelations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: boolean;`)
|
|
1037
|
+
lines: listRelations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: boolean | { readonly where?: ${relation.target}WhereInput };`)
|
|
750
1038
|
}));
|
|
751
1039
|
}
|
|
752
|
-
parts.push(
|
|
1040
|
+
parts.push(inputAlias({
|
|
753
1041
|
name: `${n}Select`,
|
|
754
1042
|
lines: [
|
|
755
1043
|
...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`),
|
|
@@ -758,21 +1046,21 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
758
1046
|
...hasCounts ? [countLine] : []
|
|
759
1047
|
]
|
|
760
1048
|
}));
|
|
761
|
-
parts.push(
|
|
1049
|
+
parts.push(inputAlias({
|
|
762
1050
|
name: `${n}Include`,
|
|
763
1051
|
lines: [
|
|
764
1052
|
...model.relations.map((relation) => ` readonly ${propKey({ name: relation.name })}?: ${relationSelectValue(relation)};`),
|
|
765
1053
|
...hasCounts ? [countLine] : []
|
|
766
1054
|
]
|
|
767
1055
|
}));
|
|
768
|
-
parts.push(
|
|
1056
|
+
parts.push(inputAlias({
|
|
769
1057
|
name: `${n}OmitInput`,
|
|
770
1058
|
lines: [
|
|
771
1059
|
...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: boolean;`),
|
|
772
1060
|
...computedEntries.map((entry) => ` readonly ${propKey({ name: entry.field })}?: boolean;`)
|
|
773
1061
|
]
|
|
774
1062
|
}));
|
|
775
|
-
parts.push(
|
|
1063
|
+
parts.push(inputAlias({
|
|
776
1064
|
name: `${n}Args`,
|
|
777
1065
|
lines: [
|
|
778
1066
|
` readonly select?: ${n}Select;`,
|
|
@@ -781,7 +1069,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
781
1069
|
` readonly relationStrategy?: RelationStrategy;`
|
|
782
1070
|
]
|
|
783
1071
|
}));
|
|
784
|
-
parts.push(
|
|
1072
|
+
parts.push(inputAlias({
|
|
785
1073
|
name: `${n}ListRelationFilter`,
|
|
786
1074
|
lines: [
|
|
787
1075
|
` readonly some?: ${n}WhereInput;`,
|
|
@@ -789,7 +1077,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
789
1077
|
` readonly none?: ${n}WhereInput;`
|
|
790
1078
|
]
|
|
791
1079
|
}));
|
|
792
|
-
parts.push(
|
|
1080
|
+
parts.push(inputAlias({
|
|
793
1081
|
name: `${n}RelationFilter`,
|
|
794
1082
|
lines: [` readonly is?: ${n}WhereInput | null;`, ` readonly isNot?: ${n}WhereInput | null;`]
|
|
795
1083
|
}));
|
|
@@ -815,7 +1103,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
815
1103
|
whereLines.push(` readonly ${key}?: ${relation.target}RelationFilter | ${relation.target}WhereInput${relation.isOptional ? " | null" : ""};`);
|
|
816
1104
|
}
|
|
817
1105
|
}
|
|
818
|
-
parts.push(
|
|
1106
|
+
parts.push(inputAlias({ name: `${n}WhereInput`, lines: whereLines }));
|
|
819
1107
|
if (model.uniques.length === 0) {
|
|
820
1108
|
parts.push(`export type ${n}WhereUniqueInput = never;`);
|
|
821
1109
|
} else {
|
|
@@ -831,19 +1119,19 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
831
1119
|
| ${members.join(`
|
|
832
1120
|
| `)};`);
|
|
833
1121
|
}
|
|
834
|
-
parts.push(
|
|
1122
|
+
parts.push(inputAlias({
|
|
835
1123
|
name: `${n}OrderByInput`,
|
|
836
1124
|
lines: model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: ${field.isOptional ? "SortOrderWithNulls" : "SortOrder"};`)
|
|
837
1125
|
}));
|
|
838
|
-
parts.push(
|
|
839
|
-
parts.push(
|
|
1126
|
+
parts.push(inputAlias({ name: `${n}CreateInput`, lines: checkedCreateLines({ model }) }));
|
|
1127
|
+
parts.push(inputAlias({ name: `${n}UncheckedCreateInput`, lines: uncheckedCreateLines({ model }) }));
|
|
840
1128
|
parts.push(`export type ${n}CreateManyInput = ${n}UncheckedCreateInput;`);
|
|
841
|
-
parts.push(
|
|
842
|
-
parts.push(
|
|
1129
|
+
parts.push(inputAlias({ name: `${n}UpdateInput`, lines: checkedUpdateLines({ model }) }));
|
|
1130
|
+
parts.push(inputAlias({ name: `${n}UncheckedUpdateInput`, lines: uncheckedUpdateLines({ model }) }));
|
|
843
1131
|
parts.push(`export type ${n}UpdateManyInput = ${n}UncheckedUpdateInput;`);
|
|
844
1132
|
const numericFields = model.fields.filter((field) => !field.isList && field.scalar !== null && NUMERIC_SCALARS.has(field.scalar));
|
|
845
1133
|
const minmaxFields = model.fields.filter((field) => !field.isList && field.scalar !== null && MINMAX_SCALARS.has(field.scalar));
|
|
846
|
-
parts.push(
|
|
1134
|
+
parts.push(inputAlias({
|
|
847
1135
|
name: `${n}CountAggregateInput`,
|
|
848
1136
|
lines: [
|
|
849
1137
|
...model.fields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`),
|
|
@@ -852,7 +1140,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
852
1140
|
}));
|
|
853
1141
|
const aggregateOpProps = [` readonly _count?: true | ${n}CountAggregateInput;`];
|
|
854
1142
|
if (numericFields.length > 0) {
|
|
855
|
-
parts.push(
|
|
1143
|
+
parts.push(inputAlias({
|
|
856
1144
|
name: `${n}AvgAggregateInput`,
|
|
857
1145
|
lines: numericFields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
|
|
858
1146
|
}));
|
|
@@ -861,7 +1149,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
861
1149
|
aggregateOpProps.push(` readonly _sum?: ${n}SumAggregateInput;`);
|
|
862
1150
|
}
|
|
863
1151
|
if (minmaxFields.length > 0) {
|
|
864
|
-
parts.push(
|
|
1152
|
+
parts.push(inputAlias({
|
|
865
1153
|
name: `${n}MinAggregateInput`,
|
|
866
1154
|
lines: minmaxFields.map((field) => ` readonly ${propKey({ name: field.name })}?: true;`)
|
|
867
1155
|
}));
|
|
@@ -869,7 +1157,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
869
1157
|
aggregateOpProps.push(` readonly _min?: ${n}MinAggregateInput;`);
|
|
870
1158
|
aggregateOpProps.push(` readonly _max?: ${n}MaxAggregateInput;`);
|
|
871
1159
|
}
|
|
872
|
-
parts.push(
|
|
1160
|
+
parts.push(inputAlias({
|
|
873
1161
|
name: `${n}FindManyArgs`,
|
|
874
1162
|
lines: [
|
|
875
1163
|
` readonly where?: ${n}WhereInput;`,
|
|
@@ -878,6 +1166,8 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
878
1166
|
` readonly include?: ${n}Include;`,
|
|
879
1167
|
` readonly omit?: ${n}OmitInput;`,
|
|
880
1168
|
` readonly cursor?: ${n}WhereUniqueInput;`,
|
|
1169
|
+
` readonly after?: KeysetToken;`,
|
|
1170
|
+
` readonly keyset?: KeysetOptions;`,
|
|
881
1171
|
` readonly take?: number;`,
|
|
882
1172
|
` readonly skip?: number;`,
|
|
883
1173
|
` readonly distinct?: ${orMany({ type: `${n}ScalarFieldEnum` })};`,
|
|
@@ -886,7 +1176,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
886
1176
|
]
|
|
887
1177
|
}));
|
|
888
1178
|
parts.push(`export type ${n}FindFirstArgs = ${n}FindManyArgs;`);
|
|
889
|
-
parts.push(
|
|
1179
|
+
parts.push(inputAlias({
|
|
890
1180
|
name: `${n}FindUniqueArgs`,
|
|
891
1181
|
lines: [
|
|
892
1182
|
` readonly where: ${n}WhereUniqueInput;`,
|
|
@@ -897,7 +1187,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
897
1187
|
` readonly lock?: "update";`
|
|
898
1188
|
]
|
|
899
1189
|
}));
|
|
900
|
-
parts.push(
|
|
1190
|
+
parts.push(inputAlias({
|
|
901
1191
|
name: `${n}CreateArgs`,
|
|
902
1192
|
lines: [
|
|
903
1193
|
` readonly data: ${n}CreateInput | ${n}UncheckedCreateInput;`,
|
|
@@ -907,7 +1197,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
907
1197
|
]
|
|
908
1198
|
}));
|
|
909
1199
|
parts.push(`export type ${n}ConflictTarget = readonly ${n}ScalarFieldEnum[] | { readonly constraint: string };`);
|
|
910
|
-
parts.push(
|
|
1200
|
+
parts.push(inputAlias({
|
|
911
1201
|
name: `${n}CreateOnConflictInput`,
|
|
912
1202
|
lines: [
|
|
913
1203
|
` readonly action: "nothing";`,
|
|
@@ -915,7 +1205,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
915
1205
|
` readonly where?: string;`
|
|
916
1206
|
]
|
|
917
1207
|
}));
|
|
918
|
-
parts.push(
|
|
1208
|
+
parts.push(inputAlias({
|
|
919
1209
|
name: `${n}CreateOrSkipArgs`,
|
|
920
1210
|
lines: [
|
|
921
1211
|
` readonly data: ${n}CreateInput | ${n}UncheckedCreateInput;`,
|
|
@@ -925,14 +1215,14 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
925
1215
|
` readonly omit?: ${n}OmitInput;`
|
|
926
1216
|
]
|
|
927
1217
|
}));
|
|
928
|
-
parts.push(
|
|
1218
|
+
parts.push(inputAlias({
|
|
929
1219
|
name: `${n}CreateManyArgs`,
|
|
930
1220
|
lines: [
|
|
931
1221
|
` readonly data: ${orMany({ type: `${n}CreateManyInput` })};`,
|
|
932
1222
|
` readonly skipDuplicates?: boolean | { readonly target?: ${n}ConflictTarget; readonly where?: string };`
|
|
933
1223
|
]
|
|
934
1224
|
}));
|
|
935
|
-
parts.push(
|
|
1225
|
+
parts.push(inputAlias({
|
|
936
1226
|
name: `${n}CreateManyAndReturnArgs`,
|
|
937
1227
|
lines: [
|
|
938
1228
|
` readonly data: ${orMany({ type: `${n}CreateManyInput` })};`,
|
|
@@ -941,10 +1231,21 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
941
1231
|
` readonly omit?: ${n}OmitInput;`
|
|
942
1232
|
]
|
|
943
1233
|
}));
|
|
944
|
-
parts.push(
|
|
1234
|
+
parts.push(inputAlias({
|
|
1235
|
+
name: `${n}UpsertManyArgs`,
|
|
1236
|
+
lines: [
|
|
1237
|
+
` readonly data: readonly ${n}CreateManyInput[];`,
|
|
1238
|
+
` readonly conflictTarget: ${n}ConflictTarget;`,
|
|
1239
|
+
` readonly conflictWhere?: string;`,
|
|
1240
|
+
` readonly update: readonly ${n}ScalarFieldEnum[];`,
|
|
1241
|
+
` readonly skipUpdatedAt?: boolean;`,
|
|
1242
|
+
` readonly allowAnyUniqueConflict?: boolean;`
|
|
1243
|
+
]
|
|
1244
|
+
}));
|
|
1245
|
+
parts.push(inputAlias({
|
|
945
1246
|
name: `${n}UpdateArgs`,
|
|
946
1247
|
lines: [
|
|
947
|
-
` readonly where: ${n}
|
|
1248
|
+
` readonly where: ${n}UpdateWhereInput;`,
|
|
948
1249
|
` readonly data: ${n}UpdateInput | ${n}UncheckedUpdateInput;`,
|
|
949
1250
|
` readonly select?: ${n}Select;`,
|
|
950
1251
|
` readonly include?: ${n}Include;`,
|
|
@@ -952,7 +1253,9 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
952
1253
|
` readonly skipUpdatedAt?: boolean;`
|
|
953
1254
|
]
|
|
954
1255
|
}));
|
|
955
|
-
parts.push(
|
|
1256
|
+
parts.push(`/** update()'s where: a concrete unique selector AND any additional predicates (optimistic-concurrency guards such as \`{ id, version }\`). */
|
|
1257
|
+
export type ${n}UpdateWhereInput = ${n}WhereUniqueInput & ${n}WhereInput;`);
|
|
1258
|
+
parts.push(inputAlias({
|
|
956
1259
|
name: `${n}UpdateManyArgs`,
|
|
957
1260
|
lines: [
|
|
958
1261
|
` readonly where?: ${n}WhereInput;`,
|
|
@@ -960,7 +1263,18 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
960
1263
|
` readonly skipUpdatedAt?: boolean;`
|
|
961
1264
|
]
|
|
962
1265
|
}));
|
|
963
|
-
parts.push(
|
|
1266
|
+
parts.push(inputAlias({
|
|
1267
|
+
name: `${n}UpdateManyAndReturnArgs`,
|
|
1268
|
+
lines: [
|
|
1269
|
+
` readonly include?: never;`,
|
|
1270
|
+
` readonly where?: ${n}WhereInput;`,
|
|
1271
|
+
` readonly data: ${n}UpdateManyInput;`,
|
|
1272
|
+
` readonly select?: ${n}Select;`,
|
|
1273
|
+
` readonly omit?: ${n}OmitInput;`,
|
|
1274
|
+
` readonly skipUpdatedAt?: boolean;`
|
|
1275
|
+
]
|
|
1276
|
+
}));
|
|
1277
|
+
parts.push(inputAlias({
|
|
964
1278
|
name: `${n}UpsertArgs`,
|
|
965
1279
|
lines: [
|
|
966
1280
|
` readonly where: ${n}WhereUniqueInput;`,
|
|
@@ -972,7 +1286,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
972
1286
|
` readonly omit?: ${n}OmitInput;`
|
|
973
1287
|
]
|
|
974
1288
|
}));
|
|
975
|
-
parts.push(
|
|
1289
|
+
parts.push(inputAlias({
|
|
976
1290
|
name: `${n}DeleteArgs`,
|
|
977
1291
|
lines: [
|
|
978
1292
|
` readonly where: ${n}WhereUniqueInput;`,
|
|
@@ -981,10 +1295,11 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
981
1295
|
` readonly omit?: ${n}OmitInput;`
|
|
982
1296
|
]
|
|
983
1297
|
}));
|
|
984
|
-
parts.push(
|
|
985
|
-
parts.push(
|
|
1298
|
+
parts.push(inputAlias({ name: `${n}DeleteManyArgs`, lines: [` readonly where?: ${n}WhereInput;`] }));
|
|
1299
|
+
parts.push(inputAlias({
|
|
986
1300
|
name: `${n}CountArgs`,
|
|
987
1301
|
lines: [
|
|
1302
|
+
` readonly select?: ${n}CountAggregateInput;`,
|
|
988
1303
|
` readonly where?: ${n}WhereInput;`,
|
|
989
1304
|
` readonly orderBy?: ${orMany({ type: `${n}OrderByInput` })};`,
|
|
990
1305
|
` readonly cursor?: ${n}WhereUniqueInput;`,
|
|
@@ -992,7 +1307,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
992
1307
|
` readonly skip?: number;`
|
|
993
1308
|
]
|
|
994
1309
|
}));
|
|
995
|
-
parts.push(
|
|
1310
|
+
parts.push(inputAlias({
|
|
996
1311
|
name: `${n}AggregateArgs`,
|
|
997
1312
|
lines: [
|
|
998
1313
|
` readonly where?: ${n}WhereInput;`,
|
|
@@ -1003,7 +1318,7 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
1003
1318
|
...aggregateOpProps
|
|
1004
1319
|
]
|
|
1005
1320
|
}));
|
|
1006
|
-
parts.push(
|
|
1321
|
+
parts.push(inputAlias({
|
|
1007
1322
|
name: `${n}GroupByArgs`,
|
|
1008
1323
|
lines: [
|
|
1009
1324
|
` readonly by: readonly ${n}ScalarFieldEnum[];`,
|
|
@@ -1015,20 +1330,26 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
1015
1330
|
...aggregateOpProps
|
|
1016
1331
|
]
|
|
1017
1332
|
}));
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1333
|
+
const exactPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : K`;
|
|
1334
|
+
const uncertainPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : never`;
|
|
1335
|
+
parts.push(`type ${n}OmitBase<A> = A extends { readonly omit: infer O extends object }
|
|
1336
|
+
` + ` ? Omit<${n}, PossibleKeys<O> & keyof ${n}> & { [K in UncertainKeys<O> & keyof ${n}]?: ${n}[K] }
|
|
1337
|
+
` + ` : "omit" extends keyof A ? Omit<${n}, PossibleKeys<Provided<A["omit"]>> & keyof ${n}> & Partial<Pick<${n}, PossibleKeys<Provided<A["omit"]>> & keyof ${n}>> : ${n};`);
|
|
1020
1338
|
if (hasCounts) {
|
|
1021
1339
|
const listUnion = listRelations.map((relation) => tsString({ value: relation.name })).join(" | ");
|
|
1022
1340
|
const allCounts = listRelations.map((relation) => `${propKey({ name: relation.name })}: number`).join("; ");
|
|
1023
1341
|
parts.push(`type ${n}CountResult<C> = C extends { readonly select: infer CS extends object }
|
|
1024
|
-
` + ` ? { [K in keyof CS & (${listUnion}) as ${
|
|
1342
|
+
` + ` ? { [K in keyof CS & (${listUnion}) as ${exactPart("CS")}]: number } &
|
|
1343
|
+
` + ` { [K in keyof CS & (${listUnion}) as ${uncertainPart("CS")}]?: number }
|
|
1025
1344
|
` + ` : { ${allCounts} };`);
|
|
1026
|
-
parts.push(`type ${n}CountPayload<S> = S extends { readonly _count: infer C extends true | object } ? { _count: ${n}CountResult<C> }
|
|
1345
|
+
parts.push(`type ${n}CountPayload<S> = S extends { readonly _count: infer C extends true | object } ? { _count: ${n}CountResult<C> }
|
|
1346
|
+
` + ` : S extends { readonly _count: false | undefined } ? unknown
|
|
1347
|
+
` + ` : S extends { readonly _count?: infer C extends boolean | object } ? { _count?: ${n}CountResult<C> } : unknown;`);
|
|
1027
1348
|
}
|
|
1028
1349
|
if (hasRelations) {
|
|
1029
1350
|
const relationCases = model.relations.map((relation) => {
|
|
1030
1351
|
const nested = `${relation.target}GetPayload<A>`;
|
|
1031
|
-
const guard = `A extends
|
|
1352
|
+
const guard = `A extends object`;
|
|
1032
1353
|
let expression;
|
|
1033
1354
|
if (relation.isList) {
|
|
1034
1355
|
expression = `${guard} ? ${nested}[] : ${relation.target}[]`;
|
|
@@ -1044,30 +1365,48 @@ ${model.relations.map((relation) => ` ${propKey({ name: relation.name })}: ${re
|
|
|
1044
1365
|
${relationCases}
|
|
1045
1366
|
}[K];`);
|
|
1046
1367
|
parts.push(`type ${n}IncludePayload<A> = A extends { readonly include: infer I extends object }
|
|
1047
|
-
` + ` ? { [K in keyof I & keyof ${n}Relations as ${
|
|
1048
|
-
` + `
|
|
1368
|
+
` + ` ? { [K in keyof I & keyof ${n}Relations as ${exactPart("I")}]: ${n}RelationPayload<K, I[K]> } &
|
|
1369
|
+
` + ` { [K in keyof I & keyof ${n}Relations as ${uncertainPart("I")}]?: ${n}RelationPayload<K, I[K]> }${hasCounts ? ` &
|
|
1370
|
+
${n}CountPayload<I>` : ""}
|
|
1371
|
+
` + ` : "include" extends keyof A ? Partial<${n}IncludePayload<{ include: Provided<A["include"]> }>> : unknown;`);
|
|
1049
1372
|
parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
|
|
1050
|
-
` + ` ? { [K in keyof S & ${selectableEnum} as ${
|
|
1051
|
-
` + ` { [K in keyof S &
|
|
1373
|
+
` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
|
|
1374
|
+
` + ` { [K in keyof S & ${selectableEnum} as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] } &
|
|
1375
|
+
` + ` { [K in keyof S & keyof ${n}Relations as ${exactPart("S")}]: ${n}RelationPayload<K, S[K]> } &
|
|
1376
|
+
` + ` { [K in keyof S & keyof ${n}Relations as ${uncertainPart("S")}]?: ${n}RelationPayload<K, S[K]> }${hasCounts ? ` &
|
|
1052
1377
|
${n}CountPayload<S>` : ""}
|
|
1378
|
+
` + ` : "select" extends keyof A ? Partial<${n}>
|
|
1053
1379
|
` + ` : ${n}OmitBase<A> & ${n}IncludePayload<A>;`);
|
|
1054
1380
|
} else {
|
|
1055
1381
|
parts.push(`export type ${n}GetPayload<A> = A extends { readonly select: infer S extends object }
|
|
1056
|
-
` + ` ? { [K in keyof S & ${selectableEnum} as ${
|
|
1057
|
-
` + `
|
|
1382
|
+
` + ` ? { [K in keyof S & ${selectableEnum} as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
|
|
1383
|
+
` + ` { [K in keyof S & ${selectableEnum} as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] }
|
|
1384
|
+
` + ` : "select" extends keyof A ? Partial<${n}> : ${n}OmitBase<A>;`);
|
|
1058
1385
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
`
|
|
1062
|
-
` + `
|
|
1063
|
-
` + `
|
|
1064
|
-
` + `}
|
|
1386
|
+
const aggregateFields = (fields, value) => `{ ${fields.map((field) => `${propKey({ name: field.name })}: ${value(field)}`).join("; ")} }`;
|
|
1387
|
+
const countValues = aggregateFields(model.fields, () => "number").replace(" }", "; _all: number }");
|
|
1388
|
+
parts.push(`type ${n}AggregateValues = {
|
|
1389
|
+
` + ` _count: ${countValues};
|
|
1390
|
+
` + ` _avg: ${aggregateFields(numericFields, () => "number | null")};
|
|
1391
|
+
` + ` _sum: ${aggregateFields(numericFields, (field) => `${n}[${tsString({ value: field.name })}] | null`)};
|
|
1392
|
+
` + ` _min: ${aggregateFields(minmaxFields, (field) => `${n}[${tsString({ value: field.name })}] | null`)};
|
|
1393
|
+
` + ` _max: ${aggregateFields(minmaxFields, (field) => `${n}[${tsString({ value: field.name })}] | null`)};
|
|
1394
|
+
};`);
|
|
1395
|
+
parts.push(`type ${n}AggregateSelection<V, S> = S extends true ? number : S extends object
|
|
1396
|
+
` + ` ? { [K in keyof S & keyof V as ${exactPart("S")}]: V[K] } &
|
|
1397
|
+
` + ` { [K in keyof S & keyof V as ${uncertainPart("S")}]?: V[K] } : never;`);
|
|
1398
|
+
parts.push(`type ${n}CountOutput<A> = A extends { readonly select: infer S extends object }
|
|
1399
|
+
` + ` ? ${n}AggregateSelection<${n}AggregateValues["_count"], S>
|
|
1400
|
+
` + ` : "select" extends keyof A ? number | ${n}AggregateSelection<${n}AggregateValues["_count"], Provided<A["select"]>> : number;`);
|
|
1401
|
+
parts.push(`export type ${n}AggregateResult<A> =
|
|
1402
|
+
` + ` { [K in keyof A & keyof ${n}AggregateValues as HasSelectedField<A[K]> extends true ? K : never]: ${n}AggregateSelection<${n}AggregateValues[K], A[K]> } &
|
|
1403
|
+
` + ` { [K in keyof A & keyof ${n}AggregateValues as HasSelectedField<A[K]> extends true ? never : K]?: ${n}AggregateSelection<${n}AggregateValues[K], Provided<A[K]>> };`);
|
|
1065
1404
|
parts.push(`export type ${n}GroupByResult<A extends ${n}GroupByArgs> =
|
|
1066
|
-
` + `
|
|
1405
|
+
` + ` (number extends A["by"]["length"] ? Partial<Pick<${n}, A["by"][number]>> : Pick<${n}, A["by"][number]>) & ${n}AggregateResult<A>;`);
|
|
1067
1406
|
const readMethods = [
|
|
1068
|
-
` findMany<A extends ${n}FindManyArgs =
|
|
1069
|
-
` findFirst<A extends ${n}FindFirstArgs =
|
|
1070
|
-
` findFirstOrThrow<A extends ${n}FindFirstArgs =
|
|
1407
|
+
` findMany<A extends ${n}FindManyArgs = {}>(args?: A): Promise<${n}GetPayload<A>[]>;`,
|
|
1408
|
+
` findFirst<A extends ${n}FindFirstArgs = {}>(args?: A): Promise<${n}GetPayload<A> | null>;`,
|
|
1409
|
+
` findFirstOrThrow<A extends ${n}FindFirstArgs = {}>(args?: A): Promise<${n}GetPayload<A>>;`,
|
|
1071
1410
|
` findUnique<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A> | null>;`,
|
|
1072
1411
|
` findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: A): Promise<${n}GetPayload<A>>;`
|
|
1073
1412
|
];
|
|
@@ -1078,14 +1417,16 @@ ${relationCases}
|
|
|
1078
1417
|
` createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
|
|
1079
1418
|
` update<A extends ${n}UpdateArgs>(args: A): Promise<${n}GetPayload<A>>;`,
|
|
1080
1419
|
` updateMany(args: ${n}UpdateManyArgs): Promise<BatchPayload>;`,
|
|
1420
|
+
` updateManyAndReturn<A extends ${n}UpdateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;`,
|
|
1081
1421
|
` upsert<A extends ${n}UpsertArgs>(args: A): Promise<${n}GetPayload<A>>;`,
|
|
1422
|
+
` upsertMany(args: ${n}UpsertManyArgs): Promise<UpsertManyPayload>;`,
|
|
1082
1423
|
` delete<A extends ${n}DeleteArgs>(args: A): Promise<${n}GetPayload<A>>;`,
|
|
1083
1424
|
` deleteMany(args?: ${n}DeleteManyArgs): Promise<BatchPayload>;`
|
|
1084
1425
|
];
|
|
1085
1426
|
const aggregateMethods = [
|
|
1086
|
-
` count
|
|
1427
|
+
` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;`,
|
|
1087
1428
|
` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;`,
|
|
1088
|
-
` groupBy<A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;`
|
|
1429
|
+
` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;`
|
|
1089
1430
|
];
|
|
1090
1431
|
const delegateMethods = model.isView ? [...readMethods, ...aggregateMethods] : [...readMethods, ...writeMethods, ...aggregateMethods];
|
|
1091
1432
|
delegateMethods.push(...params.extensionLines ?? []);
|
|
@@ -1154,49 +1495,54 @@ function emitViewTypesForModel(params) {
|
|
|
1154
1495
|
` readonly relationStrategy?: RelationStrategy;`
|
|
1155
1496
|
]
|
|
1156
1497
|
}));
|
|
1157
|
-
const
|
|
1498
|
+
const exactPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? never : undefined extends ${source}[K] ? never : K`;
|
|
1499
|
+
const uncertainPart = (source) => `${source}[K] extends false | undefined ? never : false extends ${source}[K] ? K : undefined extends ${source}[K] ? K : never`;
|
|
1158
1500
|
parts.push(`type ${n}ViewSelectScalars<F, A> = A extends { readonly select: infer S extends object }
|
|
1159
|
-
` + ` ? { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${
|
|
1160
|
-
` + `
|
|
1501
|
+
` + ` ? { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${exactPart("S")}]: ${n}[K & keyof ${n}] } &
|
|
1502
|
+
` + ` { [K in keyof S & keyof F & ${n}ScalarFieldEnum as ${uncertainPart("S")}]?: ${n}[K & keyof ${n}] }
|
|
1503
|
+
` + ` : "select" extends keyof A ? Partial<${n}ViewScalars<F>> : ${n}ViewScalars<F>;`);
|
|
1161
1504
|
parts.push(`type ${n}ViewReveal<A> = A extends { readonly reveal: infer R extends object }
|
|
1162
|
-
` + ` ? { [K in keyof R & ${n}ScalarFieldEnum as ${
|
|
1163
|
-
` + `
|
|
1505
|
+
` + ` ? { [K in keyof R & ${n}ScalarFieldEnum as ${exactPart("R")}]: ${n}[K & keyof ${n}] } &
|
|
1506
|
+
` + ` { [K in keyof R & ${n}ScalarFieldEnum as ${uncertainPart("R")}]?: ${n}[K & keyof ${n}] }
|
|
1507
|
+
` + ` : "reveal" extends keyof A ? Partial<${n}ViewReveal<{ reveal: NonNullable<A["reveal"]> }>> : unknown;`);
|
|
1164
1508
|
if (hasRelations) {
|
|
1165
1509
|
const relationCases = model.relations.map((relation) => {
|
|
1166
1510
|
const target = relation.target;
|
|
1167
1511
|
const targetClientKey = view.modelByName.get(target)?.clientName ?? target;
|
|
1168
1512
|
const guard = `D extends { readonly ${propKey({ name: targetClientKey })}: infer RF extends object }`;
|
|
1169
|
-
const masked = `ViewRow<${target}
|
|
1513
|
+
const masked = `ViewRow<${target}ViewSelectScalars<RF, A> & ${target}ViewReveal<A>>`;
|
|
1514
|
+
const unmasked = `(A extends object ? ${target}GetPayload<A> : ${target})`;
|
|
1170
1515
|
let expression;
|
|
1171
1516
|
if (relation.isList) {
|
|
1172
|
-
expression = `${guard} ? ${masked}[] : ${
|
|
1517
|
+
expression = `${guard} ? ${masked}[] : ${unmasked}[]`;
|
|
1173
1518
|
} else if (relation.isOptional) {
|
|
1174
|
-
expression = `(${guard} ? ${masked} : ${
|
|
1519
|
+
expression = `(${guard} ? ${masked} : ${unmasked}) | null`;
|
|
1175
1520
|
} else {
|
|
1176
|
-
expression = `${guard} ? ${masked} : ${
|
|
1521
|
+
expression = `${guard} ? ${masked} : ${unmasked}`;
|
|
1177
1522
|
}
|
|
1178
1523
|
return ` ${propKey({ name: relation.name })}: ${expression};`;
|
|
1179
1524
|
}).join(`
|
|
1180
1525
|
`);
|
|
1181
|
-
parts.push(`type ${n}ViewRelationResult<K extends keyof ${n}Relations, D> = {
|
|
1526
|
+
parts.push(`type ${n}ViewRelationResult<K extends keyof ${n}Relations, D, A> = {
|
|
1182
1527
|
${relationCases}
|
|
1183
1528
|
}[K];`);
|
|
1184
1529
|
parts.push(`type ${n}ViewIncludeResult<A, D> = A extends { readonly include: infer I extends object }
|
|
1185
|
-
` + ` ? { [K in keyof I & keyof ${n}Relations as ${
|
|
1186
|
-
` + `
|
|
1530
|
+
` + ` ? { [K in keyof I & keyof ${n}Relations as ${exactPart("I")}]: ${n}ViewRelationResult<K, D, I[K]> } &
|
|
1531
|
+
` + ` { [K in keyof I & keyof ${n}Relations as ${uncertainPart("I")}]?: ${n}ViewRelationResult<K, D, I[K]> }
|
|
1532
|
+
` + ` : "include" extends keyof A ? Partial<${n}ViewIncludeResult<{ include: NonNullable<A["include"]> }, D>> : unknown;`);
|
|
1187
1533
|
parts.push(`export type ${n}ViewResult<F, A, D> = ViewRow<${n}ViewSelectScalars<F, A> & ${n}ViewReveal<A> & ${n}ViewIncludeResult<A, D>>;`);
|
|
1188
1534
|
} else {
|
|
1189
1535
|
parts.push(`export type ${n}ViewResult<F, A, D> = ViewRow<${n}ViewSelectScalars<F, A> & ${n}ViewReveal<A>>;`);
|
|
1190
1536
|
}
|
|
1191
1537
|
parts.push(`export type ${n}ViewDelegate<F, D extends ViewDefinitionInput = ViewDefinitionInput> = {
|
|
1192
|
-
` + ` findMany<A extends ${n}ViewFindManyArgs =
|
|
1193
|
-
` + ` findFirst<A extends ${n}ViewFindManyArgs =
|
|
1194
|
-
` + ` findFirstOrThrow<A extends ${n}ViewFindManyArgs =
|
|
1538
|
+
` + ` findMany<A extends ${n}ViewFindManyArgs = {}>(args?: A): Promise<${n}ViewResult<F, A, D>[]>;
|
|
1539
|
+
` + ` findFirst<A extends ${n}ViewFindManyArgs = {}>(args?: A): Promise<${n}ViewResult<F, A, D> | null>;
|
|
1540
|
+
` + ` findFirstOrThrow<A extends ${n}ViewFindManyArgs = {}>(args?: A): Promise<${n}ViewResult<F, A, D>>;
|
|
1195
1541
|
` + ` findUnique<A extends ${n}ViewFindUniqueArgs>(args: A): Promise<${n}ViewResult<F, A, D> | null>;
|
|
1196
1542
|
` + ` findUniqueOrThrow<A extends ${n}ViewFindUniqueArgs>(args: A): Promise<${n}ViewResult<F, A, D>>;
|
|
1197
|
-
` + ` count
|
|
1543
|
+
` + ` count<A extends ${n}CountArgs = {}>(args?: A): Promise<${n}CountOutput<A>>;
|
|
1198
1544
|
` + ` aggregate<A extends ${n}AggregateArgs>(args: A): Promise<${n}AggregateResult<A>>;
|
|
1199
|
-
` + ` groupBy<A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;
|
|
1545
|
+
` + ` groupBy<const A extends ${n}GroupByArgs>(args: A): Promise<${n}GroupByResult<A>[]>;
|
|
1200
1546
|
` + `};`);
|
|
1201
1547
|
return parts.join(`
|
|
1202
1548
|
|
|
@@ -1243,9 +1589,176 @@ function jsonTypeImportLines(params) {
|
|
|
1243
1589
|
}
|
|
1244
1590
|
return [...bySpecifier.keys()].sort().map((from) => `import type { ${[...bySpecifier.get(from)].sort().join(", ")} } from ${tsString({ value: from })};`);
|
|
1245
1591
|
}
|
|
1592
|
+
function hasNativeRls(params) {
|
|
1593
|
+
return params.view.schema.rls !== undefined;
|
|
1594
|
+
}
|
|
1595
|
+
var RLS_CONTEXT_TYPES = {
|
|
1596
|
+
String: "string",
|
|
1597
|
+
Boolean: "boolean",
|
|
1598
|
+
Int: "number",
|
|
1599
|
+
BigInt: "bigint",
|
|
1600
|
+
Float: "number",
|
|
1601
|
+
Decimal: "string",
|
|
1602
|
+
DateTime: "Date"
|
|
1603
|
+
};
|
|
1604
|
+
function emitRlsSection(params) {
|
|
1605
|
+
const rls = params.view.schema.rls;
|
|
1606
|
+
if (rls === undefined)
|
|
1607
|
+
return [];
|
|
1608
|
+
const lines = Object.entries(rls.context).map(([key, slot]) => ` readonly ${propKey({ name: key })}: ${RLS_CONTEXT_TYPES[slot.type]}${slot.nullable ? " | null" : ""};`);
|
|
1609
|
+
const doc = `/** The identity context every query on this schema must be bound to \u2014 all slots required, an explicit null included. */`;
|
|
1610
|
+
return [
|
|
1611
|
+
banner({ title: "Native row-level security" }),
|
|
1612
|
+
lines.length === 0 ? `${doc}
|
|
1613
|
+
export type RlsContext = Record<string, never>;` : `${doc}
|
|
1614
|
+
export type RlsContext = {
|
|
1615
|
+
${lines.join(`
|
|
1616
|
+
`)}
|
|
1617
|
+
};`
|
|
1618
|
+
];
|
|
1619
|
+
}
|
|
1620
|
+
function instanceSurfaceLines(params) {
|
|
1621
|
+
const { view, selfType } = params;
|
|
1622
|
+
const delegateProps = view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`);
|
|
1623
|
+
const modelsProps = view.models.flatMap((model) => {
|
|
1624
|
+
const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`;
|
|
1625
|
+
return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${model.name}Delegate;`];
|
|
1626
|
+
});
|
|
1627
|
+
return `${delegateProps.join(`
|
|
1628
|
+
`)}
|
|
1629
|
+
` + ` /** Delegates keyed by BOTH camelCase client name and model name \u2014 the same objects as the top-level properties (dynamic access). */
|
|
1630
|
+
` + ` readonly $models: {
|
|
1631
|
+
${modelsProps.join(`
|
|
1632
|
+
`)}
|
|
1633
|
+
};
|
|
1634
|
+
` + ` /** Subscribe to client events ("query" fires after every statement, with the same payload as onQuery \u2014 both fire). Returns an unsubscribe function. */
|
|
1635
|
+
` + ` readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
|
|
1636
|
+
` + ` /** ONE transaction. Callback form: everything on \`tx\` rides the same BEGIN/COMMIT; a throw rolls back. Array form (Prisma parity): UN-AWAITED delegate calls run sequentially in one transaction, results returned positionally. */
|
|
1637
|
+
` + ` readonly $transaction: {
|
|
1638
|
+
` + ` <T>(fn: (tx: ${selfType}) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
1639
|
+
` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
|
|
1640
|
+
` + ` };
|
|
1641
|
+
` + ` readonly $queryRaw: <T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]) => Promise<T[]>;
|
|
1642
|
+
` + ` readonly $executeRaw: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
|
|
1643
|
+
` + ` readonly $queryRawUnsafe: <T = Record<string, unknown>>(text: string, ...values: unknown[]) => Promise<T[]>;
|
|
1644
|
+
` + ` readonly $executeRawUnsafe: (text: string, ...values: unknown[]) => Promise<number>;
|
|
1645
|
+
` + ` readonly $connect: () => Promise<void>;
|
|
1646
|
+
` + ` readonly $disconnect: () => Promise<void>;
|
|
1647
|
+
` + ` /** Field-masking views (v1 defineView parity): a read-only masked projection of models. */
|
|
1648
|
+
` + ` readonly $defineView: <D extends ViewDefinitionInput>(options: { readonly name: string; readonly definition: D }) => ViewClient<D>;
|
|
1649
|
+
`;
|
|
1650
|
+
}
|
|
1651
|
+
function telemetryDeclarations() {
|
|
1652
|
+
return [
|
|
1653
|
+
`/** One SQL execution inside an operation. Parameter values are redacted; \`sql\` and \`parameters\` appear only with the matching capture option. */`,
|
|
1654
|
+
`export type VibeTelemetryStatementEvent = {`,
|
|
1655
|
+
` readonly operationId: string;`,
|
|
1656
|
+
` readonly index: number;`,
|
|
1657
|
+
` readonly kind: "rows" | "affected" | "raw" | "extension";`,
|
|
1658
|
+
` readonly model: string | null;`,
|
|
1659
|
+
` readonly method: string;`,
|
|
1660
|
+
` readonly dialect: string;`,
|
|
1661
|
+
` readonly provider: string;`,
|
|
1662
|
+
` /** Stable normalized grouping key \u2014 bounded and literal-free, safe as a metric label. */`,
|
|
1663
|
+
` readonly fingerprint: string;`,
|
|
1664
|
+
` readonly outcome: "success" | "failure";`,
|
|
1665
|
+
` /** Adapter-call elapsed time: networking and possibly pool wait included \u2014 not pure server execution time. */`,
|
|
1666
|
+
` readonly durationMs: number;`,
|
|
1667
|
+
` readonly parameterCount: number;`,
|
|
1668
|
+
` readonly inTransaction: boolean;`,
|
|
1669
|
+
` readonly rowCount?: number;`,
|
|
1670
|
+
` readonly errorCode?: string;`,
|
|
1671
|
+
` readonly slow?: boolean;`,
|
|
1672
|
+
` readonly sql?: string;`,
|
|
1673
|
+
` readonly parameters?: readonly unknown[];`,
|
|
1674
|
+
`};`,
|
|
1675
|
+
``,
|
|
1676
|
+
`/** One public ORM call, start to terminal outcome. Emitted exactly once. Unknown values are ABSENT, never zero. */`,
|
|
1677
|
+
`export type VibeTelemetryOperationEvent = {`,
|
|
1678
|
+
` readonly operationId: string;`,
|
|
1679
|
+
` readonly parentOperationId?: string;`,
|
|
1680
|
+
` readonly context?: unknown;`,
|
|
1681
|
+
` readonly kind: "model" | "raw" | "transaction" | "extension";`,
|
|
1682
|
+
` readonly model: string | null;`,
|
|
1683
|
+
` readonly method: string;`,
|
|
1684
|
+
` readonly dialect: string;`,
|
|
1685
|
+
` readonly provider: string;`,
|
|
1686
|
+
` readonly outcome: "success" | "failure";`,
|
|
1687
|
+
` readonly stage: "refused" | "validation" | "statement" | "decode" | "transaction" | "completed" | "unknown";`,
|
|
1688
|
+
` readonly dispatched: boolean;`,
|
|
1689
|
+
` readonly startedAt?: number;`,
|
|
1690
|
+
` readonly durationMs?: number;`,
|
|
1691
|
+
` /** Creation to dispatch \u2014 labelled separately, never folded into durationMs. */`,
|
|
1692
|
+
` readonly queueWaitMs?: number;`,
|
|
1693
|
+
` readonly statementCount: number;`,
|
|
1694
|
+
` readonly statementDurationMs?: number;`,
|
|
1695
|
+
` readonly rowCount?: number;`,
|
|
1696
|
+
` readonly errorCode?: string;`,
|
|
1697
|
+
` readonly detail?: { readonly renderMs?: number; readonly adapterMs?: number; readonly processingMs?: number };`,
|
|
1698
|
+
` readonly statements: readonly VibeTelemetryStatementEvent[];`,
|
|
1699
|
+
` readonly droppedStatements?: number;`,
|
|
1700
|
+
` readonly slow?: boolean;`,
|
|
1701
|
+
` readonly sampled: true;`,
|
|
1702
|
+
`};`,
|
|
1703
|
+
``,
|
|
1704
|
+
`/** Where events go. A throw from any callback is contained: it can never change a database outcome. */`,
|
|
1705
|
+
`export type VibeTelemetryExporter = {`,
|
|
1706
|
+
` readonly onOperation?: (event: VibeTelemetryOperationEvent) => void;`,
|
|
1707
|
+
` readonly onStatement?: (event: VibeTelemetryStatementEvent) => void;`,
|
|
1708
|
+
` readonly onDrop?: (event: { readonly reason: "buffer_full" | "statement_limit"; readonly count: number }) => void;`,
|
|
1709
|
+
` readonly flush?: () => void | Promise<void>;`,
|
|
1710
|
+
` /** NEVER called by the client \u2014 \`$disconnect()\` flushes only, because the provider is yours. */`,
|
|
1711
|
+
` readonly shutdown?: () => void | Promise<void>;`,
|
|
1712
|
+
`};`,
|
|
1713
|
+
``,
|
|
1714
|
+
`/** Complete counters (spans may be sampled, these are not) and explicit lifecycle. */`,
|
|
1715
|
+
`export type VibeTelemetryHandle = {`,
|
|
1716
|
+
` readonly metrics: () => {`,
|
|
1717
|
+
` readonly countsRepresent: "all";`,
|
|
1718
|
+
` readonly sampleRate: number;`,
|
|
1719
|
+
` readonly sampledOperations: number;`,
|
|
1720
|
+
` readonly operations: { readonly total: number; readonly failed: number; readonly refused: number };`,
|
|
1721
|
+
` readonly statements: { readonly total: number; readonly failed: number };`,
|
|
1722
|
+
` readonly rows: { readonly known: number; readonly total: number };`,
|
|
1723
|
+
` readonly operationLatencyMs: { readonly bounds: readonly number[]; readonly counts: readonly number[]; readonly sum: number; readonly count: number };`,
|
|
1724
|
+
` readonly statementLatencyMs: { readonly bounds: readonly number[]; readonly counts: readonly number[]; readonly sum: number; readonly count: number };`,
|
|
1725
|
+
` readonly dropped: { readonly operations: number; readonly statements: number };`,
|
|
1726
|
+
` };`,
|
|
1727
|
+
` readonly flush: () => Promise<void>;`,
|
|
1728
|
+
` readonly shutdown: () => Promise<void>;`,
|
|
1729
|
+
`};`,
|
|
1730
|
+
``,
|
|
1731
|
+
`/** Opt-in operation telemetry. Defaults are conservative: no SQL text, no parameter values, no sampling. */`,
|
|
1732
|
+
`export type VibeTelemetryOptions = {`,
|
|
1733
|
+
` readonly exporter: VibeTelemetryExporter;`,
|
|
1734
|
+
` /** Attach rendered SQL text (default false \u2014 raw text can embed secrets). */`,
|
|
1735
|
+
` readonly captureSql?: boolean;`,
|
|
1736
|
+
` /** Attach parameter VALUES (default false \u2014 only the count is reported). */`,
|
|
1737
|
+
` readonly captureParameterValues?: boolean;`,
|
|
1738
|
+
` /** Split render / adapter / processing time on the operation event (default false). */`,
|
|
1739
|
+
` readonly detailedTimings?: boolean;`,
|
|
1740
|
+
` /** Span sampling probability, 0..1 (default 1). Counters still count every execution. */`,
|
|
1741
|
+
` readonly sampleRate?: number;`,
|
|
1742
|
+
` readonly sampler?: (input: { readonly kind: "model" | "raw" | "transaction" | "extension"; readonly model: string | null; readonly method: string; readonly context?: unknown }) => boolean;`,
|
|
1743
|
+
` /** Read your current parent context SYNCHRONOUSLY at call time, so an application span survives the async boundary. */`,
|
|
1744
|
+
` readonly context?: () => unknown;`,
|
|
1745
|
+
` readonly delivery?: "immediate" | "buffered";`,
|
|
1746
|
+
` readonly maxBufferedOperations?: number;`,
|
|
1747
|
+
` readonly maxStatementsPerOperation?: number;`,
|
|
1748
|
+
` readonly slowStatementMs?: number;`,
|
|
1749
|
+
` readonly slowOperationMs?: number;`,
|
|
1750
|
+
` readonly onExporterError?: (params: { readonly error: unknown; readonly phase: "operation" | "statement" | "drop" | "flush" | "shutdown" | "sampler" | "context" }) => void;`,
|
|
1751
|
+
` /** Called once while the client is built, with the handle that owns metrics and lifecycle. */`,
|
|
1752
|
+
` readonly onReady?: (handle: VibeTelemetryHandle) => void;`,
|
|
1753
|
+
`};`
|
|
1754
|
+
].join(`
|
|
1755
|
+
`);
|
|
1756
|
+
}
|
|
1246
1757
|
function emitClient(params) {
|
|
1247
1758
|
const { view, baked, hasComputed } = params;
|
|
1759
|
+
const nativeRls = hasNativeRls({ view });
|
|
1248
1760
|
const parts = [banner({ title: "Client" })];
|
|
1761
|
+
parts.push(telemetryDeclarations());
|
|
1249
1762
|
parts.push(objectAlias({
|
|
1250
1763
|
name: "VibeClientOptions",
|
|
1251
1764
|
lines: [
|
|
@@ -1256,6 +1769,10 @@ function emitClient(params) {
|
|
|
1256
1769
|
` readonly defaultOrderByPk?: boolean;`,
|
|
1257
1770
|
` /** Append the primary key as a tie-breaker to \`distinct\` ordering (default true) \u2014 pins WHICH row represents each distinct key. */`,
|
|
1258
1771
|
` readonly distinctOrderByPk?: boolean;`,
|
|
1772
|
+
` /** Opt-in write-boundary validators, per model \xD7 field: \`{ User: { email: zodSchema } }\`. Any Standard Schema validator (zod, valibot, arktype) works; they run on every write path before encoding. */`,
|
|
1773
|
+
` readonly validate?: WriteValidators;`,
|
|
1774
|
+
` /** Opt-in operation telemetry: one event per public ORM call, with the statements it ran. Parameter values are redacted by default and the exporter can never change a database outcome. */`,
|
|
1775
|
+
` readonly telemetry?: VibeTelemetryOptions;`,
|
|
1259
1776
|
...baked.empty ? [] : [
|
|
1260
1777
|
` /** The live extension instances \u2014 verified against the baked manifest. */`,
|
|
1261
1778
|
` readonly extensions?: readonly VibeExtension[];`
|
|
@@ -1263,41 +1780,32 @@ function emitClient(params) {
|
|
|
1263
1780
|
...hasComputed ? [
|
|
1264
1781
|
` /** The live computed-field config \u2014 verified against the baked manifest (VIBE_STALE_CLIENT). */`,
|
|
1265
1782
|
` readonly computed: ComputedConfig;`
|
|
1783
|
+
] : [],
|
|
1784
|
+
...nativeRls ? [
|
|
1785
|
+
` /** Native row-level security: the non-bypassing database role the runtime binds each transaction to (required on PGlite; server connections log in as it). */`,
|
|
1786
|
+
` readonly nativeRls?: { readonly runtimeRole?: string };`
|
|
1266
1787
|
] : []
|
|
1267
1788
|
]
|
|
1268
1789
|
}));
|
|
1269
|
-
const delegateProps = view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`);
|
|
1270
|
-
const modelsProps = view.models.flatMap((model) => {
|
|
1271
|
-
const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${model.name}Delegate;`;
|
|
1272
|
-
return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${model.name}Delegate;`];
|
|
1273
|
-
});
|
|
1274
1790
|
const clientExtensionLines = baked.clientDts.length === 0 ? "" : `${baked.clientDts.join(`
|
|
1275
1791
|
`)}
|
|
1276
1792
|
`;
|
|
1277
|
-
|
|
1278
|
-
`
|
|
1279
|
-
`)}
|
|
1280
|
-
`
|
|
1281
|
-
` + `
|
|
1282
|
-
$
|
|
1283
|
-
`)}
|
|
1284
|
-
};
|
|
1793
|
+
if (nativeRls) {
|
|
1794
|
+
parts.push(`export type BoundVibeClientInstance = {
|
|
1795
|
+
` + instanceSurfaceLines({ view, selfType: "BoundVibeClientInstance" }) + clientExtensionLines + `};`);
|
|
1796
|
+
parts.push(`export type VibeClientInstance = {
|
|
1797
|
+
` + ` /** Bind the request's identity context \u2014 ALL of it, every declared slot \u2014 and get the client that can actually query. */
|
|
1798
|
+
` + ` readonly $withContext: (context: RlsContext) => BoundVibeClientInstance;
|
|
1285
1799
|
` + ` /** Subscribe to client events ("query" fires after every statement, with the same payload as onQuery \u2014 both fire). Returns an unsubscribe function. */
|
|
1286
1800
|
` + ` readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
|
|
1287
|
-
` + ` /** ONE transaction. Callback form: everything on \`tx\` rides the same BEGIN/COMMIT; a throw rolls back. Array form (Prisma parity): UN-AWAITED delegate calls run sequentially in one transaction, results returned positionally. */
|
|
1288
|
-
` + ` readonly $transaction: {
|
|
1289
|
-
` + ` <T>(fn: (tx: VibeClientInstance) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
1290
|
-
` + ` <T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
|
|
1291
|
-
` + ` };
|
|
1292
|
-
` + ` readonly $queryRaw: <T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]) => Promise<T[]>;
|
|
1293
|
-
` + ` readonly $executeRaw: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
|
|
1294
|
-
` + ` readonly $queryRawUnsafe: <T = Record<string, unknown>>(text: string, ...values: unknown[]) => Promise<T[]>;
|
|
1295
|
-
` + ` readonly $executeRawUnsafe: (text: string, ...values: unknown[]) => Promise<number>;
|
|
1296
1801
|
` + ` readonly $connect: () => Promise<void>;
|
|
1297
1802
|
` + ` readonly $disconnect: () => Promise<void>;
|
|
1298
|
-
` + `
|
|
1299
|
-
` + `
|
|
1300
|
-
|
|
1803
|
+
` + ` // Deliberately ABSENT: model delegates, $models, $transaction, $queryRaw / $executeRaw / $queryRawUnsafe / $executeRawUnsafe, $defineView and every extension mount \u2014 an unbound client has no identity, so it cannot read or write a row. Bind one with $withContext.
|
|
1804
|
+
` + `};`);
|
|
1805
|
+
} else {
|
|
1806
|
+
parts.push(`export type VibeClientInstance = {
|
|
1807
|
+
` + instanceSurfaceLines({ view, selfType: "VibeClientInstance" }) + policyClientLines({ view }) + scopedClientLines({ view }) + clientExtensionLines + `};`);
|
|
1808
|
+
}
|
|
1301
1809
|
parts.push(`/** Build a typed client over a database adapter. */
|
|
1302
1810
|
export declare function VibeClient(options: VibeClientOptions): VibeClientInstance;`);
|
|
1303
1811
|
parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
|
|
@@ -1311,6 +1819,8 @@ function scopedColumnCandidates(params) {
|
|
|
1311
1819
|
}
|
|
1312
1820
|
function emitScopedSection(params) {
|
|
1313
1821
|
const { view } = params;
|
|
1822
|
+
if (hasNativeRls({ view }))
|
|
1823
|
+
return [];
|
|
1314
1824
|
if (view.models.some((model) => model.policy !== undefined))
|
|
1315
1825
|
return [];
|
|
1316
1826
|
const entries = view.models.map((model) => {
|
|
@@ -1327,10 +1837,11 @@ function emitScopedSection(params) {
|
|
|
1327
1837
|
}
|
|
1328
1838
|
return ` readonly ${propKey({ name: model.name })}: ${arms.join(" | ")};`;
|
|
1329
1839
|
});
|
|
1330
|
-
const
|
|
1840
|
+
const scopedDelegateType = (model) => `${model.name}Delegate`;
|
|
1841
|
+
const delegateProps = view.models.map((model) => ` readonly ${propKey({ name: model.clientName })}: ${scopedDelegateType(model)};`);
|
|
1331
1842
|
const modelsProps = view.models.flatMap((model) => {
|
|
1332
|
-
const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${model
|
|
1333
|
-
return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${model
|
|
1843
|
+
const byClientName = ` readonly ${propKey({ name: model.clientName })}: ${scopedDelegateType(model)};`;
|
|
1844
|
+
return model.name === model.clientName ? [byClientName] : [byClientName, ` readonly ${propKey({ name: model.name })}: ${scopedDelegateType(model)};`];
|
|
1334
1845
|
});
|
|
1335
1846
|
return [
|
|
1336
1847
|
banner({ title: "Tenant scoping ($scoped)" }),
|
|
@@ -1365,6 +1876,8 @@ ${modelsProps.join(`
|
|
|
1365
1876
|
];
|
|
1366
1877
|
}
|
|
1367
1878
|
function scopedClientLines(params) {
|
|
1879
|
+
if (hasNativeRls(params))
|
|
1880
|
+
return "";
|
|
1368
1881
|
if (params.view.models.some((model) => model.policy !== undefined))
|
|
1369
1882
|
return "";
|
|
1370
1883
|
return ` /** Tenant scoping: a total per-model tenancy map + a scope value \u2192 a NEW handle whose every call is confined to that tenant. scope: null builds the handle but refuses calls on scoped models. */
|
|
@@ -1401,24 +1914,27 @@ function emitDts(params) {
|
|
|
1401
1914
|
computedByModel.set(entry.model, list);
|
|
1402
1915
|
}
|
|
1403
1916
|
const parts = [];
|
|
1404
|
-
parts.push(
|
|
1405
|
-
// Regenerate with \`vibeorm generate\` after every schema change.`);
|
|
1917
|
+
parts.push(GENERATED_HEADER);
|
|
1406
1918
|
const schemaTypeImports = [
|
|
1407
1919
|
"SchemaIR",
|
|
1408
1920
|
...baked.empty ? [] : ["VibeExtension"],
|
|
1409
1921
|
...hasComputed ? ["ComputedConfig"] : []
|
|
1410
1922
|
].sort();
|
|
1411
1923
|
const importLines = [
|
|
1412
|
-
`import type { DatabaseAdapter, DbNow, QueryEvent, TransactionOptions } from "@vibeorm/runtime";`,
|
|
1924
|
+
`import type { DatabaseAdapter, DbNow, QueryEvent, Skip, TransactionOptions, WriteValidators } from "@vibeorm/runtime";`,
|
|
1413
1925
|
`import type { ${schemaTypeImports.join(", ")} } from "@vibeorm/schema";`,
|
|
1414
1926
|
...jsonTypeImportLines({ view }),
|
|
1415
|
-
...baked.dtsImports
|
|
1927
|
+
...baked.dtsImports,
|
|
1928
|
+
`import { ${enumEntryExports({ view }).join(", ")} } from "./enums.js";`
|
|
1416
1929
|
];
|
|
1417
1930
|
parts.push(`${importLines.join(`
|
|
1418
1931
|
`)}
|
|
1419
1932
|
|
|
1420
1933
|
` + `export { VibeError } from "@vibeorm/schema";
|
|
1421
|
-
export type { VibeErrorCode } from "@vibeorm/schema"
|
|
1934
|
+
export type { VibeErrorCode } from "@vibeorm/schema";
|
|
1935
|
+
` + `export { SKIP } from "@vibeorm/runtime";
|
|
1936
|
+
export type { Skip } from "@vibeorm/runtime";
|
|
1937
|
+
` + `export { ${enumEntryExports({ view }).join(", ")} };`);
|
|
1422
1938
|
parts.push(PREAMBLE);
|
|
1423
1939
|
parts.push(...filterSection());
|
|
1424
1940
|
for (const declared of view.enums)
|
|
@@ -1449,6 +1965,7 @@ export type { VibeErrorCode } from "@vibeorm/schema";`);
|
|
|
1449
1965
|
}
|
|
1450
1966
|
parts.push(...emitViewSection({ view }));
|
|
1451
1967
|
parts.push(...emitScopedSection({ view }));
|
|
1968
|
+
parts.push(...emitRlsSection({ view }));
|
|
1452
1969
|
parts.push(emitClient({ view, baked, hasComputed }));
|
|
1453
1970
|
return `${parts.join(`
|
|
1454
1971
|
|
|
@@ -1456,82 +1973,9 @@ export type { VibeErrorCode } from "@vibeorm/schema";`);
|
|
|
1456
1973
|
`;
|
|
1457
1974
|
}
|
|
1458
1975
|
|
|
1459
|
-
// packages/generator/src/emit-js.ts
|
|
1460
|
-
function emitJs(params) {
|
|
1461
|
-
const { view } = params;
|
|
1462
|
-
const baked = params.baked ?? EMPTY_BAKED_EXTENSIONS;
|
|
1463
|
-
const computed = params.computed ?? [];
|
|
1464
|
-
const hasComputed = computed.length > 0;
|
|
1465
|
-
const parts = [];
|
|
1466
|
-
parts.push(`// Generated by VibeORM (@vibeorm/generator) \u2014 DO NOT EDIT.
|
|
1467
|
-
// Regenerate with \`vibeorm generate\` after every schema change.`);
|
|
1468
|
-
parts.push(`import { createClient } from "@vibeorm/runtime";`);
|
|
1469
|
-
if (baked.jsImports.length > 0)
|
|
1470
|
-
parts.push(baked.jsImports.join(`
|
|
1471
|
-
`));
|
|
1472
|
-
parts.push(`export { VibeError } from "@vibeorm/schema";`);
|
|
1473
|
-
parts.push(`/** The normalized Schema IR this client was generated from (plain data). */
|
|
1474
|
-
export const schema = ${stableStringify({ value: view.schema })};`);
|
|
1475
|
-
for (const declared of view.enums) {
|
|
1476
|
-
const entries = declared.values.map((value) => ` ${tsString({ value })}: ${tsString({ value })},`).join(`
|
|
1477
|
-
`);
|
|
1478
|
-
parts.push(`export const ${declared.name} = Object.freeze({
|
|
1479
|
-
${entries}
|
|
1480
|
-
});`);
|
|
1481
|
-
}
|
|
1482
|
-
if (baked.empty && !hasComputed) {
|
|
1483
|
-
parts.push(`/** Build a typed client over a database adapter. */
|
|
1484
|
-
` + `export function VibeClient(options) {
|
|
1485
|
-
` + ` const { adapter, ...clientOptions } = options;
|
|
1486
|
-
` + ` return createClient({ schema, adapter, options: clientOptions });
|
|
1487
|
-
` + `}`);
|
|
1488
|
-
return `${parts.join(`
|
|
1489
|
-
|
|
1490
|
-
`)}
|
|
1491
|
-
`;
|
|
1492
|
-
}
|
|
1493
|
-
if (!baked.empty) {
|
|
1494
|
-
parts.push(`/** Extension manifest \u2014 verified against the live extension list (VIBE_STALE_CLIENT). */
|
|
1495
|
-
` + `const extensionManifest = ${stableStringify({ value: baked.manifest })};`);
|
|
1496
|
-
parts.push(`/** Baked extension surfaces (extensions.md D9). */
|
|
1497
|
-
` + `const extensionMounts = [
|
|
1498
|
-
${baked.mounts.map((mount) => ` ${mount},`).join(`
|
|
1499
|
-
`)}
|
|
1500
|
-
];`);
|
|
1501
|
-
}
|
|
1502
|
-
if (hasComputed) {
|
|
1503
|
-
parts.push(`/** Computed-field manifest \u2014 verified against the live computed config (VIBE_STALE_CLIENT). */
|
|
1504
|
-
` + `const computedManifest = ${stableStringify({ value: computed })};`);
|
|
1505
|
-
}
|
|
1506
|
-
const destructured = [
|
|
1507
|
-
"adapter",
|
|
1508
|
-
...baked.empty ? [] : ["extensions"],
|
|
1509
|
-
...hasComputed ? ["computed"] : [],
|
|
1510
|
-
"...clientOptions"
|
|
1511
|
-
].join(", ");
|
|
1512
|
-
const createLines = [
|
|
1513
|
-
` schema,`,
|
|
1514
|
-
` adapter,`,
|
|
1515
|
-
` options: clientOptions,`,
|
|
1516
|
-
...baked.empty ? [] : [` extensions: { manifest: extensionManifest, mounts: extensionMounts, instances: extensions ?? [] },`],
|
|
1517
|
-
...hasComputed ? [` computed: { manifest: computedManifest, specs: computed },`] : []
|
|
1518
|
-
];
|
|
1519
|
-
parts.push(`/** Build a typed client over a database adapter. */
|
|
1520
|
-
` + `export function VibeClient(options) {
|
|
1521
|
-
` + ` const { ${destructured} } = options;
|
|
1522
|
-
` + ` return createClient({
|
|
1523
|
-
${createLines.join(`
|
|
1524
|
-
`)}
|
|
1525
|
-
});
|
|
1526
|
-
` + `}`);
|
|
1527
|
-
return `${parts.join(`
|
|
1528
|
-
|
|
1529
|
-
`)}
|
|
1530
|
-
`;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
1976
|
// packages/generator/src/schema-view.ts
|
|
1534
|
-
import { VibeError as VibeError4, assertValidSchemaIR, isEnumType } from "@vibeorm/schema";
|
|
1977
|
+
import { VibeError as VibeError4, assertValidSchemaIR, enumStorageOf, isEnumType } from "@vibeorm/schema";
|
|
1978
|
+
import { postgresDialect, renderRlsPolicy } from "@vibeorm/sql";
|
|
1535
1979
|
var JSON_TYPE_RE = /@type\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+"([^"]+)"\s*\)/;
|
|
1536
1980
|
function parseJsonTypeAnnotation(params) {
|
|
1537
1981
|
if (params.comment === undefined)
|
|
@@ -1639,20 +2083,34 @@ function buildModelView(params) {
|
|
|
1639
2083
|
...model.policy === undefined ? {} : { policy: model.policy }
|
|
1640
2084
|
};
|
|
1641
2085
|
}
|
|
2086
|
+
function assertRlsRenderable(params) {
|
|
2087
|
+
for (const model of params.schema.models) {
|
|
2088
|
+
if (model.rls === undefined || model.rls === "unprotected")
|
|
2089
|
+
continue;
|
|
2090
|
+
renderRlsPolicy({ dialect: postgresDialect, schema: params.schema, model: model.name });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
1642
2093
|
function buildSchemaView(params) {
|
|
1643
2094
|
const { schema } = params;
|
|
1644
2095
|
assertValidSchemaIR({ schema });
|
|
2096
|
+
if (schema.rls !== undefined)
|
|
2097
|
+
assertRlsRenderable({ schema });
|
|
1645
2098
|
const normalized = {
|
|
1646
2099
|
datasource: schema.datasource,
|
|
1647
2100
|
models: [...schema.models].sort(byName),
|
|
1648
2101
|
enums: [...schema.enums].sort(byName),
|
|
1649
|
-
extensions: [...schema.extensions].sort(byName)
|
|
2102
|
+
extensions: [...schema.extensions].sort(byName),
|
|
2103
|
+
...schema.rls === undefined ? {} : { rls: schema.rls }
|
|
1650
2104
|
};
|
|
1651
2105
|
const modelIRByName = new Map(normalized.models.map((model) => [model.name, model]));
|
|
1652
2106
|
const models = normalized.models.map((model) => buildModelView({ model, modelIRByName }));
|
|
1653
2107
|
return {
|
|
1654
2108
|
schema: normalized,
|
|
1655
|
-
enums: normalized.enums.map((declared) => ({
|
|
2109
|
+
enums: normalized.enums.map((declared) => ({
|
|
2110
|
+
name: declared.name,
|
|
2111
|
+
values: declared.values,
|
|
2112
|
+
storage: enumStorageOf({ enumDef: declared })
|
|
2113
|
+
})),
|
|
1656
2114
|
models,
|
|
1657
2115
|
modelByName: new Map(models.map((model) => [model.name, model]))
|
|
1658
2116
|
};
|
|
@@ -1675,6 +2133,8 @@ async function generateClient(params) {
|
|
|
1675
2133
|
const outputs = [
|
|
1676
2134
|
{ name: "index.js", content: emitJs({ view, baked, computed }) },
|
|
1677
2135
|
{ name: "index.d.ts", content: emitDts({ view, baked, computed }) },
|
|
2136
|
+
{ name: "enums.js", content: emitEnumsJs({ view }) },
|
|
2137
|
+
{ name: "enums.d.ts", content: emitEnumsDts({ view }) },
|
|
1678
2138
|
{ name: "README.md", content: README }
|
|
1679
2139
|
];
|
|
1680
2140
|
await mkdir(outDir, { recursive: true });
|
|
@@ -1691,4 +2151,4 @@ export {
|
|
|
1691
2151
|
bakeExtensions
|
|
1692
2152
|
};
|
|
1693
2153
|
|
|
1694
|
-
//# debugId=
|
|
2154
|
+
//# debugId=AC114FAAB4DBE66364756E2164756E21
|