@drzl/cli 4.23.0 → 4.24.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/cli.cjs CHANGED
@@ -7,4298 +7,25 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __getProtoOf = Object.getPrototypeOf;
9
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
- var __esm = (fn, res) => function __init() {
11
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
- };
13
- var __export = (target, all) => {
14
- for (var name in all)
15
- __defProp(target, name, { get: all[name], enumerable: true });
16
- };
17
- var __copyProps = (to, from, except, desc) => {
18
- if (from && typeof from === "object" || typeof from === "function") {
19
- for (let key of __getOwnPropNames(from))
20
- if (!__hasOwnProp.call(to, key) && key !== except)
21
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
- }
23
- return to;
24
- };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
- // If the importer is in node compatibility mode or this is not an ESM
27
- // file that has been converted to a CommonJS file using a Babel-
28
- // compatible transform (i.e. "__esModule" has not been set), then set
29
- // "default" to the CommonJS "module.exports" for node compatibility.
30
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
- mod
32
- ));
33
-
34
- // ../generator-trpc/dist/index.js
35
- var dist_exports = {};
36
- __export(dist_exports, {
37
- BASE_MODULE: () => BASE_MODULE,
38
- TRPCGenerator: () => TRPCGenerator,
39
- TRPC_MAJOR: () => TRPC_MAJOR,
40
- default: () => index_default
41
- });
42
- function isWide(column) {
43
- if (column.enumValues && column.enumValues.length) return false;
44
- if (column.shape?.kind === "tuple" || column.shape?.kind === "numberObject") return false;
45
- return !["number", "string", "boolean", "Date"].includes(column.tsType);
46
- }
47
- function mapExpr(column, lib, mode) {
48
- const d = LIBS[lib];
49
- let base = (() => {
50
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
51
- if (column.shape?.kind === "tuple" && d.tuple) return d.tuple(column.shape.length);
52
- if (column.shape?.kind === "numberObject" && d.numberObject) {
53
- return d.numberObject(column.shape.fields);
54
- }
55
- switch (column.tsType) {
56
- case "number":
57
- return d.number;
58
- case "string":
59
- return d.string;
60
- case "boolean":
61
- return d.boolean;
62
- case "Date":
63
- return d.date;
64
- default:
65
- return d.unknown;
66
- }
67
- })();
68
- if (column.nullable) base = d.nullable(base);
69
- if (mode !== "select") {
70
- const optional = mode === "update" || column.nullable || column.hasDefault;
71
- if (optional) base = d.optional(base);
72
- }
73
- return base;
74
- }
75
- function field(column, lib, mode) {
76
- const d = LIBS[lib];
77
- const expr = mapExpr(column, lib, mode);
78
- return `${objectKey(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
79
- }
80
- function objectKey(name) {
81
- return isIdent(name) ? name : JSON.stringify(name);
82
- }
83
- function renderSchema(table, lib, mode) {
84
- const d = LIBS[lib];
85
- const cols = table.columns.filter((c) => mode === "select" ? true : !c.isGenerated);
86
- const body = cols.map((c) => ` ${field(c, lib, mode)},`).join("\n");
87
- const schema = d.object(body);
88
- return mode === "update" && d.partialUpdate ? d.partialUpdate(schema) : schema;
89
- }
90
- function toCase(s, c) {
91
- if (!c) return s;
92
- const parts = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/);
93
- if (c === "camel") {
94
- return parts.map(
95
- (p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()
96
- ).join("");
97
- }
98
- if (c === "kebab") return parts.map((p) => p.toLowerCase()).join("-");
99
- if (c === "snake") return parts.map((p) => p.toLowerCase()).join("_");
100
- return s;
101
- }
102
- function keyColumns(table) {
103
- const names = table.primaryKey?.columns ?? [];
104
- if (!names.length) return null;
105
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
106
- if (cols.some((c) => !c)) return null;
107
- return cols;
108
- }
109
- function renderBase(opts) {
110
- const injection = opts.databaseInjection?.enabled === true;
111
- const dbType = opts.databaseInjection?.databaseType ?? "unknown";
112
- const typeImport = opts.databaseInjection?.databaseTypeImport ? `import type { ${opts.databaseInjection.databaseTypeImport.name} } from '${opts.databaseInjection.databaseTypeImport.from}';
113
- ` : "";
114
- const trpcImport = injection ? `import { initTRPC, TRPCError } from '@trpc/server';` : `import { initTRPC } from '@trpc/server';`;
115
- const context = injection ? `/**
116
- * What your \`createContext\` hands every procedure.
117
- *
118
- * \`db\` is optional here and required by \`dbProcedure\` below. That split is what lets an adapter
119
- * build a context without a handle, for a health check or a public route, while every generated
120
- * procedure still sees one that is present.
121
- */
122
- export interface Context {
123
- db?: ${dbType};
124
- }` : `/**
125
- * What your \`createContext\` hands every procedure. Nothing generated reads it, so it is left
126
- * open; narrow it to the shape your own context really has.
127
- */
128
- export type Context = Record<string, unknown>;`;
129
- const middleware = injection ? `
130
- /**
131
- * The builder every generated procedure is built from: it refuses to run without a database
132
- * handle, and narrows \`ctx.db\` from optional to present for everything downstream.
133
- */
134
- export const dbProcedure = t.procedure.use(async ({ ctx, next }) => {
135
- if (!ctx.db) {
136
- throw new TRPCError({
137
- code: 'INTERNAL_SERVER_ERROR',
138
- message: 'No database handle on the tRPC context. Provide one from createContext.',
139
- });
140
- }
141
- return next({ ctx: { db: ctx.db } });
142
- });
143
- ` : "";
144
- return `// Generated by @drzl/generator-trpc
145
- // The shared tRPC base. Every generated router imports from here.
146
- ${trpcImport}
147
- ${typeImport}
148
- ${context}
149
-
150
- const t = initTRPC.context<Context>().create();
151
-
152
- export const router = t.router;
153
- export const mergeRouters = t.mergeRouters;
154
- export const middleware = t.middleware;
155
- /** Needed to call this router in-process, from a test or from SSR. */
156
- export const createCallerFactory = t.createCallerFactory;
157
- export const publicProcedure = t.procedure;
158
- ${middleware}`;
159
- }
160
- function renderRouter(table, opts, ctx) {
161
- const lib = opts.validation?.library ?? "zod";
162
- const d = LIBS[lib];
163
- const service = opts.template === "service";
164
- const injection = opts.databaseInjection?.enabled === true;
165
- const builder = injection ? "dbProcedure" : "publicProcedure";
166
- const insertName = `Insert${table.tsName}Schema`;
167
- const updateName = `Update${table.tsName}Schema`;
168
- const selectName = `Select${table.tsName}Schema`;
169
- const writable = !table.readOnly;
170
- const key = keyColumns(table);
171
- const Service = `${cap(singularize(table.tsName))}Service`;
172
- const serviceKeyable = !!key && key.every(serviceKeyExpressible);
173
- const keyArg = key ? key.map((c) => `input.${c.name}`).join(", ") : "";
174
- const dbArg = injection ? "ctx.db, " : "";
175
- const wiredParams = injection ? "{ ctx, input }" : "{ input }";
176
- const procedures = [];
177
- const notImplemented = (what) => `throw new Error('Not implemented: ${what} ${table.tsName}.');`;
178
- procedures.push({
179
- name: "list",
180
- kind: "query",
181
- output: d.arrayOf(selectName),
182
- params: service && injection ? "{ ctx }" : "",
183
- body: service ? [`return await ${Service}.getAll(${injection ? "ctx.db" : ""});`] : ["return [];"]
184
- });
185
- const keyInput = key ? d.objectInline(key.map((c) => field(c, lib, "select")).join(", ")) : void 0;
186
- if (key && keyInput) {
187
- const wired = service && serviceKeyable;
188
- procedures.push({
189
- name: "byId",
190
- kind: "query",
191
- input: keyInput,
192
- output: d.nullableOf(selectName),
193
- params: wired ? wiredParams : "{ input: _input }",
194
- body: wired ? [`return await ${Service}.getById(${dbArg}${keyArg});`] : service ? [serviceKeyNote(table), notImplemented("byId")] : ["return null;"]
195
- });
196
- if (writable) {
197
- const updateInput = d.objectInline(
198
- [...key.map((c) => field(c, lib, "select")), `data: ${updateName}`].join(", ")
199
- );
200
- procedures.push({
201
- name: "update",
202
- kind: "mutation",
203
- input: updateInput,
204
- output: selectName,
205
- params: wired ? wiredParams : "{ input: _input }",
206
- body: wired ? [`return await ${Service}.update(${dbArg}${keyArg}, input.data);`] : service ? [serviceKeyNote(table), notImplemented("update")] : [notImplemented("update")]
207
- });
208
- procedures.push({
209
- name: "delete",
210
- kind: "mutation",
211
- input: keyInput,
212
- output: d.booleanSchema,
213
- params: wired ? wiredParams : "{ input: _input }",
214
- body: wired ? [`return await ${Service}.delete(${dbArg}${keyArg});`] : service ? [serviceKeyNote(table), notImplemented("delete")] : ["return true;"]
215
- });
216
- }
217
- }
218
- if (writable) {
219
- procedures.push({
220
- name: "create",
221
- kind: "mutation",
222
- input: insertName,
223
- output: selectName,
224
- params: service ? wiredParams : "{ input: _input }",
225
- body: service ? [`return await ${Service}.create(${dbArg}input);`] : [notImplemented("create")]
226
- });
227
- }
228
- if (opts.includeRelations) {
229
- const taken = new Set(procedures.map((p) => p.name));
230
- procedures.push(...relationProcedures(table, lib, selectName, taken, service));
231
- }
232
- const order = ["list", "byId", "create", "update", "delete"];
233
- const rank = (n) => order.indexOf(n) === -1 ? order.length : order.indexOf(n);
234
- procedures.sort((a, b) => rank(a.name) - rank(b.name));
235
- const routerName = routerExportName(table, opts.naming);
236
- const entries = procedures.map((p) => {
237
- const rawKey = toCase(p.name, opts.naming?.procedureCase);
238
- const propKey = isIdent(rawKey) ? rawKey : JSON.stringify(rawKey);
239
- return [
240
- ` ${propKey}: ${builder}`,
241
- ...p.input ? [` .input(${p.input})`] : [],
242
- ` .output(${p.output})`,
243
- ` .${p.kind}(async (${p.params}) => {`,
244
- ...p.body.map((line) => ` ${line}`),
245
- ` }),`
246
- ].join("\n");
247
- }).join("\n");
248
- const body = `export const ${routerName} = router({
249
- ${entries}
250
- });
251
- `;
252
- const useShared = !!opts.validation?.useShared && !!opts.validation?.importPath;
253
- const declared = [];
254
- if (!useShared) {
255
- if (writable) {
256
- declared.push(`export const ${insertName} = ${renderSchema(table, lib, "insert")};`);
257
- declared.push(`export const ${updateName} = ${renderSchema(table, lib, "update")};`);
258
- }
259
- declared.push(`export const ${selectName} = ${renderSchema(table, lib, "select")};`);
260
- }
261
- const decided = [...declared, body].join("\n\n");
262
- const imports = [];
263
- if (useShared) {
264
- const sharedAffix = (0, import_validation_core3.resolveAffix)({
265
- affix: opts.validation?.affix,
266
- schemaSuffix: opts.validation?.schemaSuffix
267
- });
268
- const wanted = [
269
- ["insert", insertName],
270
- ["update", updateName],
271
- ["select", selectName]
272
- ].filter(([, local]) => decided.includes(local));
273
- if (wanted.length) {
274
- const spec = (0, import_validation_core3.resolveConfiguredImport)(
275
- opts.validation.importPath,
276
- ctx.out,
277
- process.cwd(),
278
- opts.importExtension
279
- );
280
- const names = wanted.map(([mode, local]) => {
281
- const exported = (0, import_validation_core3.schemaName)(mode, table.tsName, sharedAffix);
282
- return exported === local ? local : `${exported} as ${local}`;
283
- }).join(", ");
284
- imports.push(`import { ${names} } from '${spec}';`);
285
- }
286
- }
287
- imports.push(
288
- `import { ${[builder, "router"].sort().join(", ")} } from '${(0, import_validation_core3.importSpecifier)(
289
- `./${BASE_MODULE}.ts`,
290
- opts.importExtension
291
- )}';`
292
- );
293
- if (service) {
294
- imports.push(`import { ${Service} } from '${serviceImportSpecifier(table, ctx, opts)}';`);
295
- }
296
- if (LIB_USAGE[lib].test(decided)) imports.unshift(LIB_IMPORTS[lib]);
297
- const wide = table.columns.filter(isWide).map((c) => c.name);
298
- const wideNote = wide.length ? `// No validated type for ${wide.length === 1 ? "this column" : "these columns"}: ${wide.join(", ")}.
299
- // DRZL could not derive one from the schema, so the router accepts any value there.
300
- ` : "";
301
- return `// Generated by @drzl/generator-trpc
302
- // Router for table: ${table.name}
303
- ${wideNote}${imports.join("\n")}
304
-
305
- ${decided}`;
306
- }
307
- function relationProcedures(table, lib, selectSchemaName, taken, service) {
308
- const d = LIBS[lib];
309
- const out = [];
310
- for (const fk of table.foreignKeys ?? []) {
311
- if (fk.columns.length !== 1) continue;
312
- const colName = fk.columns[0];
313
- const column = table.columns.find((c) => c.name === colName);
314
- if (!column) continue;
315
- const name = `listBy${cap(colName)}`;
316
- if (taken.has(name)) continue;
317
- taken.add(name);
318
- out.push({
319
- name,
320
- kind: "query",
321
- input: d.objectInline(field(column, lib, "select")),
322
- output: d.arrayOf(selectSchemaName),
323
- params: "{ input: _input }",
324
- body: [
325
- `// Rows of ${table.name} whose ${JSON.stringify(colName)} matches _input.${colName}.`,
326
- // In `service` mode every other procedure really does reach the database, so a lookup
327
- // quietly answering with an empty array would read as "no matching rows". There is no
328
- // generated service method for it, so it says so instead. In `standard` mode everything
329
- // is a stub and `[]` is consistent with `list`.
330
- service ? `throw new Error('Not implemented: ${name} ${table.tsName}.');` : "return [];"
331
- ]
332
- });
333
- }
334
- return out;
335
- }
336
- function renderBarrel(routers, ctx, path9, opts) {
337
- const baseSpec = (0, import_validation_core3.importSpecifier)(`./${BASE_MODULE}.ts`, opts.importExtension);
338
- const reExports = `export { createCallerFactory, publicProcedure, router } from '${baseSpec}';
339
- ` + (opts.databaseInjection?.enabled === true ? `export { dbProcedure } from '${baseSpec}';
340
- ` : "") + `export type { Context } from '${baseSpec}';
341
- `;
342
- if (!routers.length) {
343
- return `// Generated by @drzl/generator-trpc
344
- // No tables detected in analysis. Add tables to your schema and regenerate.
345
- import { router } from '${baseSpec}';
346
-
347
- export const appRouter = router({});
348
-
349
- /** The type a tRPC client is parameterised by: \`createTRPCClient<AppRouter>()\`. */
350
- export type AppRouter = typeof appRouter;
351
-
352
- ${reExports}`;
353
- }
354
- const entries = routers.map(({ filePath, exportName, table }) => ({
355
- rel: (0, import_validation_core3.importSpecifier)(
356
- "./" + path9.relative(ctx.out, filePath).replace(/\\/g, "/"),
357
- opts.importExtension
358
- ),
359
- exportName,
360
- // The namespace a client reaches this table's procedures through: `trpc.userProfiles.list`.
361
- // `tsName` verbatim, because it is already a valid identifier and it is the name the user
362
- // wrote in their schema. The oRPC barrel lowercases this key, turning `userProfiles` into
363
- // `userprofiles`: harmless in an object literal nobody reads, and not harmless when the key
364
- // is the public API of a typed client.
365
- key: table.tsName
366
- }));
367
- const importLines = entries.map(({ rel, exportName }) => `import { ${exportName} } from '${rel}';`).join("\n");
368
- const bodyLines = entries.map(({ key, exportName }) => ` ${isIdent(key) ? key : JSON.stringify(key)}: ${exportName},`).join("\n");
369
- return `// Generated by @drzl/generator-trpc
370
- import { router } from '${baseSpec}';
371
- ${importLines}
372
-
373
- export const appRouter = router({
374
- ${bodyLines}
375
- });
376
-
377
- /** The type a tRPC client is parameterised by: \`createTRPCClient<AppRouter>()\`. */
378
- export type AppRouter = typeof appRouter;
379
-
380
- ${reExports}`;
381
- }
382
- function serviceKeyExpressible(c) {
383
- if (c.enumValues && c.enumValues.length) return true;
384
- return ["number", "string", "boolean", "Date"].includes(c.tsType);
385
- }
386
- function serviceKeyNote(table) {
387
- const cols = table.primaryKey?.columns ?? [];
388
- const untyped = cols.filter((n) => {
389
- const c = table.columns.find((x) => x.name === n);
390
- return !c || !serviceKeyExpressible(c);
391
- });
392
- const what = untyped.length === 1 ? `its column ${untyped[0]}` : `its columns ${untyped.join(", ")}`;
393
- return `// ${table.name} is keyed on (${cols.join(", ")}) and DRZL cannot type ${what}: the input
394
- // schema carries unknown there, which the service's typed key parameter does not accept.
395
- // Wire this to your own lookup.`;
396
- }
397
- function routerExportName(table, naming) {
398
- const base = `${table.tsName}${naming?.routerSuffix ?? "Router"}`;
399
- const c = naming?.procedureCase;
400
- return toCase(base, c === "kebab" ? "camel" : c);
401
- }
402
- function serviceImportSpecifier(table, ctx, opts) {
403
- const rel = relativePosix(ctx.out, ctx.services);
404
- const dir = !rel ? "." : rel.startsWith(".") ? rel : `./${rel}`;
405
- return (0, import_validation_core3.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
406
- }
407
- function relativePosix(from, to) {
408
- const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
409
- const a = norm(from).split("/");
410
- const b = norm(to).split("/");
411
- let i = 0;
412
- while (i < a.length && i < b.length && a[i] === b[i]) i++;
413
- return [...Array.from({ length: a.length - i }, () => ".."), ...b.slice(i)].join("/");
414
- }
415
- function buildHeader(h) {
416
- if (h && h.enabled === false) return "";
417
- const text = h?.text?.trim();
418
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
419
- "// Generated by DRZL (@drzl/*)",
420
- "// Generated output is granted to you under your project's license.",
421
- "// You may use, copy, modify, and distribute without attribution."
422
- ];
423
- return lines.join("\n") + "\n\n";
424
- }
425
- var import_validation_core2, import_validation_core3, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
426
- var init_dist = __esm({
427
- "../generator-trpc/dist/index.js"() {
428
- "use strict";
429
- import_validation_core2 = require("@drzl/validation-core");
430
- import_validation_core3 = require("@drzl/validation-core");
431
- TRPC_MAJOR = 11;
432
- q = (v) => JSON.stringify(v);
433
- LIB_IMPORTS = {
434
- zod: "import { z } from 'zod';",
435
- valibot: "import * as v from 'valibot';",
436
- arktype: "import { type } from 'arktype';"
437
- };
438
- LIB_USAGE = {
439
- zod: /\bz\./,
440
- valibot: /\bv\./,
441
- arktype: /\btype\(/
442
- };
443
- LIBS = {
444
- zod: {
445
- number: "z.number()",
446
- string: "z.string()",
447
- boolean: "z.boolean()",
448
- date: "z.date()",
449
- unknown: "z.unknown()",
450
- tuple: (n) => `z.tuple([${Array.from({ length: n }, () => "z.number()").join(", ")}])`,
451
- numberObject: (fields) => `z.object({ ${fields.map((f) => `${f}: z.number()`).join(", ")} })`,
452
- enum: (vals) => `z.enum([${vals.map(q).join(", ")}] as const)`,
453
- nullable: (b) => `${b}.nullable()`,
454
- optional: (b) => `${b}.optional()`,
455
- object: (body) => `z.object({
456
- ${body}
457
- })`,
458
- objectInline: (body) => `z.object({ ${body} })`,
459
- partialUpdate: (s) => `${s}.partial()`,
460
- arrayOf: (s) => `z.array(${s})`,
461
- nullableOf: (s) => `${s}.nullable()`,
462
- booleanSchema: "z.boolean()"
463
- },
464
- valibot: {
465
- number: "v.number()",
466
- string: "v.string()",
467
- boolean: "v.boolean()",
468
- date: "v.date()",
469
- unknown: "v.unknown()",
470
- tuple: (n) => `v.tuple([${Array.from({ length: n }, () => "v.number()").join(", ")}])`,
471
- numberObject: (fields) => `v.object({ ${fields.map((f) => `${f}: v.number()`).join(", ")} })`,
472
- enum: (vals) => `v.picklist([${vals.map(q).join(", ")}] as const)`,
473
- nullable: (b) => `v.nullable(${b})`,
474
- optional: (b) => `v.optional(${b})`,
475
- object: (body) => `v.object({
476
- ${body}
477
- })`,
478
- objectInline: (body) => `v.object({ ${body} })`,
479
- arrayOf: (s) => `v.array(${s})`,
480
- nullableOf: (s) => `v.nullable(${s})`,
481
- booleanSchema: "v.boolean()"
482
- },
483
- arktype: {
484
- number: "number",
485
- string: "string",
486
- boolean: "boolean",
487
- date: "Date",
488
- unknown: "unknown",
489
- // The surrounding encode adds the quotes, so the union is built with the inner quoting
490
- // ArkType expects. Emitting `'${...}'` here produces `''admin' | 'user''`, which does not parse.
491
- enum: (vals) => vals.map((x) => `'${x.replace(/'/g, "\\'")}'`).join(" | "),
492
- nullable: (b) => `(${b} | null)`,
493
- optional: (b) => `${b}?`,
494
- object: (body) => `type({
495
- ${body}
496
- })`,
497
- objectInline: (body) => `type({ ${body} })`,
498
- fieldIsString: true,
499
- arrayOf: (s) => `${s}.array()`,
500
- nullableOf: (s) => `${s}.or('null')`,
501
- booleanSchema: `type('boolean')`
502
- }
503
- };
504
- cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
505
- singularize = (s) => s.endsWith("ies") ? s.slice(0, -3) + "y" : s.endsWith("s") ? s.slice(0, -1) : s;
506
- isIdent = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
507
- BASE_MODULE = "trpc";
508
- TRPCGenerator = class {
509
- constructor(analysis) {
510
- this.analysis = analysis;
511
- }
512
- async generate(opts) {
513
- const fs6 = (0, import_validation_core2.fileWriter)(opts.fileSink);
514
- const path9 = await import("path");
515
- const out = path9.resolve(process.cwd(), opts.outputDir);
516
- const ctx = {
517
- out,
518
- services: path9.resolve(process.cwd(), opts.servicesDir ?? "src/services")
519
- };
520
- await fs6.mkdir(out, { recursive: true });
521
- const files = [];
522
- const write = async (filePath, content) => {
523
- const formatted = await (0, import_validation_core3.formatCode)(
524
- buildHeader(opts.outputHeader) + content,
525
- filePath,
526
- opts.format
527
- );
528
- await fs6.writeFile(filePath, formatted, "utf8");
529
- files.push(filePath);
530
- };
531
- const basePath = path9.join(out, `${BASE_MODULE}.ts`);
532
- await write(basePath, renderBase(opts));
533
- const routers = [];
534
- const total = this.analysis.tables.length;
535
- let index = 0;
536
- for (const table of this.analysis.tables) {
537
- const base = `${table.tsName}${opts.naming?.routerSuffix ?? ""}`;
538
- const filePath = path9.join(out, `${toCase(base, opts.naming?.procedureCase)}.ts`);
539
- if (filePath === basePath) {
540
- throw new Error(
541
- `@drzl/generator-trpc: the router for table "${table.name}" would be written to ${filePath}, which is the shared tRPC base module this generator also writes. Set naming.routerSuffix to move it out of the way.`
542
- );
543
- }
544
- await write(filePath, renderRouter(table, opts, ctx));
545
- routers.push({ table, filePath, exportName: routerExportName(table, opts.naming) });
546
- index++;
547
- opts.onProgress?.({ index, total, table: table.name, filePath });
548
- }
549
- await write(path9.join(out, "index.ts"), renderBarrel(routers, ctx, path9, opts));
550
- return { files };
551
- }
552
- };
553
- index_default = TRPCGenerator;
554
- }
555
- });
556
-
557
- // ../generator-hono/dist/index.js
558
- var dist_exports2 = {};
559
- __export(dist_exports2, {
560
- APP_MODULE: () => APP_MODULE,
561
- HonoGenerator: () => HonoGenerator,
562
- default: () => index_default2
563
- });
564
- function keyColumns2(table) {
565
- const names = table.primaryKey?.columns ?? [];
566
- if (!names.length) return null;
567
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
568
- if (cols.some((c) => !c)) return null;
569
- return cols;
570
- }
571
- function isWide2(column) {
572
- if (column.enumValues && column.enumValues.length) return false;
573
- if (column.shape?.kind === "tuple" || column.shape?.kind === "numberObject") return false;
574
- return !["number", "string", "boolean", "Date"].includes(column.tsType);
575
- }
576
- function mapExpr2(column, lib, mode) {
577
- const d = LIBS2[lib];
578
- let base = (() => {
579
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
580
- switch (column.tsType) {
581
- case "number":
582
- return d.number;
583
- case "string":
584
- return d.string;
585
- case "boolean":
586
- return d.boolean;
587
- case "Date":
588
- return d.date;
589
- default:
590
- return d.unknown;
591
- }
592
- })();
593
- if (column.nullable) base = d.nullable(base);
594
- if (mode !== "select") {
595
- const optional = mode === "update" || column.nullable || column.hasDefault;
596
- if (optional) base = d.optional(base);
597
- }
598
- return base;
599
- }
600
- function objectKey2(name) {
601
- return isIdent2(name) ? name : JSON.stringify(name);
602
- }
603
- function field2(column, lib, mode) {
604
- const d = LIBS2[lib];
605
- const expr = mapExpr2(column, lib, mode);
606
- return `${objectKey2(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
607
- }
608
- function paramField(column, lib) {
609
- const d = LIBS2[lib];
610
- const expr = (() => {
611
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
612
- return d.coerce(column.tsType) ?? d.string;
613
- })();
614
- return `${objectKey2(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
615
- }
616
- function renderSchema2(table, lib, mode) {
617
- const d = LIBS2[lib];
618
- const cols = table.columns.filter((c) => mode === "select" ? true : !c.isGenerated);
619
- const body = cols.map((c) => ` ${field2(c, lib, mode)},`).join("\n");
620
- const schema = d.object(body);
621
- return mode === "update" && d.partialUpdate ? d.partialUpdate(schema) : schema;
622
- }
623
- function toCase2(s, c) {
624
- if (!c) return s;
625
- const parts = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/);
626
- if (c === "camel") {
627
- return parts.map(
628
- (p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()
629
- ).join("");
630
- }
631
- if (c === "kebab") return parts.map((p) => p.toLowerCase()).join("-");
632
- if (c === "snake") return parts.map((p) => p.toLowerCase()).join("_");
633
- return s;
634
- }
635
- function routesExportName(table, naming) {
636
- const base = `${table.tsName}${naming?.routerSuffix ?? "Routes"}`;
637
- const c = naming?.procedureCase;
638
- return toCase2(base, c === "kebab" ? "camel" : c);
639
- }
640
- function mountPath(table, naming) {
641
- return `/${toCase2(table.tsName, naming?.procedureCase)}`;
642
- }
643
- function buildHeader2(h) {
644
- if (h && h.enabled === false) return "";
645
- const text = h?.text?.trim();
646
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
647
- "// Generated by DRZL (@drzl/*)",
648
- "// Generated output is granted to you under your project's license.",
649
- "// You may use, copy, modify, and distribute without attribution."
650
- ];
651
- return lines.join("\n") + "\n\n";
652
- }
653
- function renderRoutes(table, opts, ctx) {
654
- const lib = opts.validation?.library ?? "zod";
655
- const d = LIBS2[lib];
656
- const validator = VALIDATORS[opts.validator ?? "standard"];
657
- const insertName = `Insert${table.tsName}Schema`;
658
- const updateName = `Update${table.tsName}Schema`;
659
- const selectName = `Select${table.tsName}Schema`;
660
- const paramsName = `${cap2(table.tsName)}ParamsSchema`;
661
- const rowType = `Select${table.tsName}Row`;
662
- const writable = !table.readOnly;
663
- const key = keyColumns2(table);
664
- const routes = [];
665
- const notImplemented = (what) => `throw new Error('Not implemented: ${what} ${table.tsName}.');`;
666
- routes.push({
667
- name: "list",
668
- method: "get",
669
- path: "/",
670
- middleware: [],
671
- // The stub states its own contract. tRPC has `.output()` and Hono has nothing like it: what a
672
- // Hono client infers is the *handler's return type*, so the only place an output schema can be
673
- // honoured is the value handed to `c.json`. Annotating the local is what puts the select shape
674
- // into `hc<AppType>()` rather than `never[]`.
675
- body: [`const rows: ${rowType}[] = [];`, "return c.json(rows);"]
676
- });
677
- if (key) {
678
- const keyPath = "/" + key.map((c) => `:${c.name}`).join("/");
679
- routes.push({
680
- name: "byId",
681
- method: "get",
682
- path: keyPath,
683
- middleware: [`${validator.fn}('param', ${paramsName})`],
684
- body: [VALID_PARAM_HINT, `const row: ${rowType} | null = null;`, "return c.json(row);"]
685
- });
686
- if (writable) {
687
- routes.push({
688
- name: "update",
689
- method: "patch",
690
- path: keyPath,
691
- middleware: [
692
- `${validator.fn}('param', ${paramsName})`,
693
- `${validator.fn}('json', ${updateName})`
694
- ],
695
- body: [notImplemented("update")]
696
- });
697
- routes.push({
698
- name: "delete",
699
- method: "delete",
700
- path: keyPath,
701
- middleware: [`${validator.fn}('param', ${paramsName})`],
702
- body: [VALID_PARAM_HINT, "return c.json(true);"]
703
- });
704
- }
705
- }
706
- if (writable) {
707
- routes.push({
708
- name: "create",
709
- method: "post",
710
- path: "/",
711
- middleware: [`${validator.fn}('json', ${insertName})`],
712
- body: [notImplemented("create")]
713
- });
714
- }
715
- if (opts.includeRelations) {
716
- routes.push(...relationRoutes(table, lib, rowType, validator.fn, opts));
717
- }
718
- const order = ["list", "byId", "create", "update", "delete"];
719
- const rank = (n) => order.indexOf(n) === -1 ? order.length : order.indexOf(n);
720
- routes.sort((a, b) => rank(a.name) - rank(b.name));
721
- const exportName = routesExportName(table, opts.naming);
722
- const chain = routes.map((r) => {
723
- const args = [lit(r.path), ...r.middleware].join(", ");
724
- const ctxParam = r.body.some((line) => /\bc\./.test(line)) ? "c" : "_c";
725
- return [
726
- ` .${r.method}(${args}, async (${ctxParam}) => {`,
727
- ...r.body.map((line) => ` ${line}`),
728
- ` })`
729
- ].join("\n");
730
- }).join("\n");
731
- const body = `export const ${exportName} = new Hono()
732
- ${chain};
733
- `;
734
- const useShared = !!opts.validation?.useShared && !!opts.validation?.importPath;
735
- const declared = [];
736
- if (!useShared) {
737
- if (writable) {
738
- declared.push(`export const ${insertName} = ${renderSchema2(table, lib, "insert")};`);
739
- declared.push(`export const ${updateName} = ${renderSchema2(table, lib, "update")};`);
740
- }
741
- declared.push(`export const ${selectName} = ${renderSchema2(table, lib, "select")};`);
742
- }
743
- if (key) {
744
- declared.push(
745
- `export const ${paramsName} = ${d.objectInline(
746
- key.map((c) => paramField(c, lib)).join(", ")
747
- )};`
748
- );
749
- }
750
- declared.push(`export type ${rowType} = ${d.infer(selectName)};`);
751
- const decided = [...declared, body].join("\n\n");
752
- const imports = [];
753
- if (useShared) {
754
- const sharedAffix = (0, import_validation_core5.resolveAffix)({
755
- affix: opts.validation?.affix,
756
- schemaSuffix: opts.validation?.schemaSuffix
757
- });
758
- const wanted = [
759
- ["insert", insertName],
760
- ["update", updateName],
761
- ["select", selectName]
762
- ].filter(([, local]) => decided.includes(local));
763
- if (wanted.length) {
764
- const spec = (0, import_validation_core5.resolveConfiguredImport)(
765
- opts.validation.importPath,
766
- ctx.out,
767
- process.cwd(),
768
- opts.importExtension
769
- );
770
- const names = wanted.map(([mode, local]) => {
771
- const exported = (0, import_validation_core5.schemaName)(mode, table.tsName, sharedAffix);
772
- return exported === local ? local : `${exported} as ${local}`;
773
- }).join(", ");
774
- imports.push(`import { ${names} } from '${spec}';`);
775
- }
776
- }
777
- imports.push(`import { Hono } from 'hono';`);
778
- if (decided.includes(`${validator.fn}(`)) {
779
- imports.push(`import { ${validator.fn} } from '${validator.from}';`);
780
- }
781
- if (LIB_USAGE2[lib].test(decided)) imports.unshift(LIB_IMPORTS2[lib]);
782
- const wide = table.columns.filter(isWide2).map((c) => c.name);
783
- const wideNote = wide.length ? `// No validated type for ${wide.length === 1 ? "this column" : "these columns"}: ${wide.join(", ")}.
784
- // DRZL could not derive one from the schema, so these routes accept any value there.
785
- ` : "";
786
- return `// Generated by @drzl/generator-hono
787
- // Routes for table: ${table.name}
788
- ${wideNote}${imports.join("\n")}
789
-
790
- ${decided}`;
791
- }
792
- function relationRoutes(table, lib, rowType, validatorFn, opts) {
793
- const d = LIBS2[lib];
794
- const out = [];
795
- const taken = /* @__PURE__ */ new Set();
796
- for (const fk of table.foreignKeys ?? []) {
797
- if (fk.columns.length !== 1) continue;
798
- const colName = fk.columns[0];
799
- const column = table.columns.find((c) => c.name === colName);
800
- if (!column) continue;
801
- const segment = toCase2(`by-${colName}`, opts.naming?.procedureCase ?? "kebab");
802
- if (taken.has(segment)) continue;
803
- taken.add(segment);
804
- const inline = d.objectInline(paramField(column, lib));
805
- out.push({
806
- name: `listBy${cap2(colName)}`,
807
- method: "get",
808
- path: `/${segment}/:${colName}`,
809
- middleware: [`${validatorFn}('param', ${inline})`],
810
- body: [VALID_PARAM_HINT, `const rows: ${rowType}[] = [];`, "return c.json(rows);"]
811
- });
812
- }
813
- return out;
814
- }
815
- function renderBarrel2(modules, ctx, path9, opts) {
816
- if (!modules.length) {
817
- return `// Generated by @drzl/generator-hono
818
- // No tables detected in analysis. Add tables to your schema and regenerate.
819
- import { Hono } from 'hono';
820
-
821
- export const app = new Hono();
822
-
823
- /** The type a Hono client is parameterised by: \`hc<AppType>('/')\`. */
824
- export type AppType = typeof app;
825
- `;
826
- }
827
- const entries = modules.map(({ filePath, exportName, table }) => ({
828
- rel: (0, import_validation_core5.importSpecifier)(
829
- "./" + path9.relative(ctx.out, filePath).replace(/\\/g, "/"),
830
- opts.importExtension
831
- ),
832
- exportName,
833
- mount: mountPath(table, opts.naming)
834
- }));
835
- const imports = entries.map((e) => `import { ${e.exportName} } from '${e.rel}';`).join("\n");
836
- const chain = entries.map((e) => ` .route(${lit(e.mount)}, ${e.exportName})`).join("\n");
837
- const reExports = entries.map((e) => `export * from '${e.rel}';`).join("\n");
838
- return `// Generated by @drzl/generator-hono
839
- import { Hono } from 'hono';
840
- ${imports}
841
-
842
- export const app = new Hono()
843
- ${chain};
844
-
845
- /** The type a Hono client is parameterised by: \`hc<AppType>('/')\`. */
846
- export type AppType = typeof app;
847
-
848
- ${reExports}
849
- `;
850
- }
851
- var import_validation_core4, import_validation_core5, APP_MODULE, q2, lit, NUMERIC_SEGMENT, LIB_IMPORTS2, LIB_USAGE2, LIBS2, cap2, isIdent2, HonoGenerator, index_default2, VALIDATORS, VALID_PARAM_HINT;
852
- var init_dist2 = __esm({
853
- "../generator-hono/dist/index.js"() {
854
- "use strict";
855
- import_validation_core4 = require("@drzl/validation-core");
856
- import_validation_core5 = require("@drzl/validation-core");
857
- APP_MODULE = "index";
858
- q2 = (v) => JSON.stringify(v);
859
- lit = (v) => /['\\]/.test(v) ? JSON.stringify(v) : `'${v}'`;
860
- NUMERIC_SEGMENT = String.raw`/^-?\d+(\.\d+)?$/`;
861
- LIB_IMPORTS2 = {
862
- zod: "import { z } from 'zod';",
863
- valibot: "import * as v from 'valibot';",
864
- arktype: "import { type } from 'arktype';"
865
- };
866
- LIB_USAGE2 = {
867
- zod: /\bz\./,
868
- valibot: /\bv\./,
869
- arktype: /\btype\(|\.infer\b/
870
- };
871
- LIBS2 = {
872
- zod: {
873
- number: "z.number()",
874
- string: "z.string()",
875
- boolean: "z.boolean()",
876
- date: "z.date()",
877
- unknown: "z.unknown()",
878
- enum: (vals) => `z.enum([${vals.map(q2).join(", ")}] as const)`,
879
- nullable: (b) => `${b}.nullable()`,
880
- optional: (b) => `${b}.optional()`,
881
- object: (body) => `z.object({
882
- ${body}
883
- })`,
884
- objectInline: (body) => `z.object({ ${body} })`,
885
- partialUpdate: (s) => `${s}.partial()`,
886
- // Not `z.coerce.number()`, and not `z.coerce.date()`: both accept far more than a path
887
- // segment addressing a row should. See the measured grid on `LibDialect.coerce`.
888
- coerce: (t) => t === "number" ? `z.string().regex(${NUMERIC_SEGMENT}).transform(Number)` : t === "Date" ? "z.iso.datetime().transform((s) => new Date(s))" : null,
889
- infer: (s) => `z.output<typeof ${s}>`
890
- },
891
- valibot: {
892
- number: "v.number()",
893
- string: "v.string()",
894
- boolean: "v.boolean()",
895
- date: "v.date()",
896
- unknown: "v.unknown()",
897
- enum: (vals) => `v.picklist([${vals.map(q2).join(", ")}] as const)`,
898
- nullable: (b) => `v.nullable(${b})`,
899
- optional: (b) => `v.optional(${b})`,
900
- object: (body) => `v.object({
901
- ${body}
902
- })`,
903
- objectInline: (body) => `v.object({ ${body} })`,
904
- // A valibot pipe step sees the previous step's *output*, so the check has to happen while the
905
- // value is still the string: by the time a `v.transform(Number)` has run there is no string
906
- // left to look at. See the measured grid on `LibDialect.coerce`.
907
- coerce: (t) => t === "number" ? `v.pipe(v.string(), v.regex(${NUMERIC_SEGMENT}), v.transform(Number))` : t === "Date" ? "v.pipe(v.string(), v.isoTimestamp(), v.transform((s) => new Date(s)))" : null,
908
- infer: (s) => `v.InferOutput<typeof ${s}>`
909
- },
910
- arktype: {
911
- number: "number",
912
- string: "string",
913
- boolean: "boolean",
914
- date: "Date",
915
- unknown: "unknown",
916
- // The surrounding encode adds the quotes, so the union is built with the inner quoting
917
- // ArkType expects.
918
- enum: (vals) => vals.map((x) => `'${x.replace(/'/g, "\\'")}'`).join(" | "),
919
- nullable: (b) => `(${b} | null)`,
920
- optional: (b) => `${b}?`,
921
- object: (body) => `type({
922
- ${body}
923
- })`,
924
- objectInline: (body) => `type({ ${body} })`,
925
- fieldIsString: true,
926
- // ArkType ships these as keywords, and they are *morphs*: the declared output type is
927
- // `number`, not `string`. Returned bare, because `fieldIsString` quotes every expression this
928
- // dialect produces and a keyword returned pre-quoted arrives as `"'string.numeric.parse'"`,
929
- // which ArkType reads as a string *literal* type matching nothing but that sentence.
930
- //
931
- // `string.date.parse` and not `string.date.iso.parse` was the first draft, and it accepts
932
- // `"1"` as the year 2001, which is the same over-permissiveness the coercing spellings have.
933
- coerce: (t) => t === "number" ? "string.numeric.parse" : t === "Date" ? "string.date.iso.parse" : null,
934
- infer: (s) => `typeof ${s}.infer`
935
- }
936
- };
937
- cap2 = (s) => s.charAt(0).toUpperCase() + s.slice(1);
938
- isIdent2 = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
939
- HonoGenerator = class {
940
- constructor(analysis) {
941
- this.analysis = analysis;
942
- }
943
- async generate(opts) {
944
- const fs6 = (0, import_validation_core4.fileWriter)(opts.fileSink);
945
- const path9 = await import("path");
946
- const out = path9.resolve(process.cwd(), opts.outputDir);
947
- const ctx = { out };
948
- await fs6.mkdir(out, { recursive: true });
949
- const files = [];
950
- const write = async (filePath, content) => {
951
- const formatted = await (0, import_validation_core5.formatCode)(
952
- buildHeader2(opts.outputHeader) + content,
953
- filePath,
954
- opts.format
955
- );
956
- await fs6.writeFile(filePath, formatted, "utf8");
957
- files.push(filePath);
958
- };
959
- const barrelPath = path9.join(out, `${APP_MODULE}.ts`);
960
- const modules = [];
961
- const total = this.analysis.tables.length;
962
- let index = 0;
963
- for (const table of this.analysis.tables) {
964
- const base = `${table.tsName}${opts.naming?.routerSuffix ?? ""}`;
965
- const filePath = path9.join(out, `${toCase2(base, opts.naming?.procedureCase)}.ts`);
966
- if (filePath === barrelPath) {
967
- throw new Error(
968
- `@drzl/generator-hono: the routes for table "${table.name}" would be written to ${filePath}, which is the barrel this generator also writes. Set naming.routerSuffix to move it out of the way.`
969
- );
970
- }
971
- await write(filePath, renderRoutes(table, opts, ctx));
972
- modules.push({ table, filePath, exportName: routesExportName(table, opts.naming) });
973
- index++;
974
- opts.onProgress?.({ index, total, table: table.name, filePath });
975
- }
976
- await write(barrelPath, renderBarrel2(modules, ctx, path9, opts));
977
- return { files };
978
- }
979
- };
980
- index_default2 = HonoGenerator;
981
- VALIDATORS = {
982
- standard: { fn: "sValidator", from: "@hono/standard-validator" },
983
- zod: { fn: "zValidator", from: "@hono/zod-validator" }
984
- };
985
- VALID_PARAM_HINT = "// The validated path parameters are at c.req.valid('param').";
986
- }
987
- });
988
-
989
- // ../generator-express/dist/index.js
990
- var dist_exports3 = {};
991
- __export(dist_exports3, {
992
- APP_MODULE: () => APP_MODULE2,
993
- ExpressGenerator: () => ExpressGenerator,
994
- VALIDATION_MODULE: () => VALIDATION_MODULE,
995
- default: () => index_default3
996
- });
997
- function keyColumns3(table) {
998
- const names = table.primaryKey?.columns ?? [];
999
- if (!names.length) return null;
1000
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
1001
- if (cols.some((c) => !c)) return null;
1002
- return cols;
1003
- }
1004
- function isWide3(column) {
1005
- if (column.enumValues && column.enumValues.length) return false;
1006
- if (column.shape?.kind === "tuple" || column.shape?.kind === "numberObject") return false;
1007
- return !["number", "string", "boolean", "Date"].includes(column.tsType);
1008
- }
1009
- function mapExpr3(column, lib, mode) {
1010
- const d = LIBS3[lib];
1011
- let base = (() => {
1012
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
1013
- switch (column.tsType) {
1014
- case "number":
1015
- return d.number;
1016
- case "string":
1017
- return d.string;
1018
- case "boolean":
1019
- return d.boolean;
1020
- case "Date":
1021
- return d.date;
1022
- default:
1023
- return d.unknown;
1024
- }
1025
- })();
1026
- if (column.nullable) base = d.nullable(base);
1027
- if (mode !== "select") {
1028
- const optional = mode === "update" || column.nullable || column.hasDefault;
1029
- if (optional) base = d.optional(base);
1030
- }
1031
- return base;
1032
- }
1033
- function objectKey3(name) {
1034
- return isIdent3(name) ? name : JSON.stringify(name);
1035
- }
1036
- function field3(column, lib, mode) {
1037
- const d = LIBS3[lib];
1038
- const expr = mapExpr3(column, lib, mode);
1039
- return `${objectKey3(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
1040
- }
1041
- function paramField2(column, lib) {
1042
- const d = LIBS3[lib];
1043
- const expr = (() => {
1044
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
1045
- return d.coerce(column.tsType) ?? d.string;
1046
- })();
1047
- return `${objectKey3(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
1048
- }
1049
- function renderSchema3(table, lib, mode) {
1050
- const d = LIBS3[lib];
1051
- const cols = table.columns.filter((c) => mode === "select" ? true : !c.isGenerated);
1052
- const body = cols.map((c) => ` ${field3(c, lib, mode)},`).join("\n");
1053
- const schema = d.object(body);
1054
- return mode === "update" && d.partialUpdate ? d.partialUpdate(schema) : schema;
1055
- }
1056
- function toCase3(s, c) {
1057
- if (!c) return s;
1058
- const parts = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/);
1059
- if (c === "camel") {
1060
- return parts.map(
1061
- (p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()
1062
- ).join("");
1063
- }
1064
- if (c === "kebab") return parts.map((p) => p.toLowerCase()).join("-");
1065
- if (c === "snake") return parts.map((p) => p.toLowerCase()).join("_");
1066
- return s;
1067
- }
1068
- function routesExportName2(table, naming) {
1069
- const base = `${table.tsName}${naming?.routerSuffix ?? "Routes"}`;
1070
- const c = naming?.procedureCase;
1071
- return toCase3(base, c === "kebab" ? "camel" : c);
1072
- }
1073
- function mountPath2(table, naming) {
1074
- return `/${toCase3(table.tsName, naming?.procedureCase)}`;
1075
- }
1076
- function buildHeader3(h) {
1077
- if (h && h.enabled === false) return "";
1078
- const text = h?.text?.trim();
1079
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
1080
- "// Generated by DRZL (@drzl/*)",
1081
- "// Generated output is granted to you under your project's license.",
1082
- "// You may use, copy, modify, and distribute without attribution."
1083
- ];
1084
- return lines.join("\n") + "\n\n";
1085
- }
1086
- function renderValidationModule() {
1087
- return `// Generated by @drzl/generator-express
1088
- // A validation middleware over Standard Schema v1, so the same routes accept zod, valibot or
1089
- // arktype schemas. On failure it answers 400 with { error, slot, issues: [{ message, path }] };
1090
- // on success it replaces req[slot] with the parsed output and calls next().
1091
- import type { RequestHandler } from 'express';
1092
-
1093
- interface StandardIssue {
1094
- readonly message: string;
1095
- readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }> | undefined;
1096
- }
1097
-
1098
- type StandardResult<Output> =
1099
- | { readonly value: Output; readonly issues?: undefined }
1100
- | { readonly issues: ReadonlyArray<StandardIssue> };
1101
-
1102
- export interface StandardSchema<Output = unknown> {
1103
- readonly '~standard': {
1104
- readonly version: 1;
1105
- readonly vendor: string;
1106
- readonly validate: (
1107
- value: unknown
1108
- ) => StandardResult<Output> | Promise<StandardResult<Output>>;
1109
- };
1110
- }
1111
-
1112
- export function ${VALIDATE_FN}(slot: 'params' | 'body', schema: StandardSchema): RequestHandler {
1113
- return async (req, res, next) => {
1114
- const result = await schema['~standard'].validate(req[slot]);
1115
- if (result.issues) {
1116
- res.status(400).json({
1117
- error: 'Validation failed',
1118
- slot,
1119
- issues: result.issues.map((issue) => ({
1120
- message: issue.message,
1121
- path: (issue.path ?? []).map((p) => (typeof p === 'object' && p !== null ? p.key : p)),
1122
- })),
1123
- });
1124
- return;
1125
- }
1126
- req[slot] = result.value as never;
1127
- next();
1128
- };
1129
- }
1130
- `;
1131
- }
1132
- function renderRoutes2(table, opts, ctx) {
1133
- const lib = opts.validation?.library ?? "zod";
1134
- const d = LIBS3[lib];
1135
- const insertName = `Insert${table.tsName}Schema`;
1136
- const updateName = `Update${table.tsName}Schema`;
1137
- const selectName = `Select${table.tsName}Schema`;
1138
- const paramsName = `${cap3(table.tsName)}ParamsSchema`;
1139
- const rowType = `Select${table.tsName}Row`;
1140
- const writable = !table.readOnly;
1141
- const key = keyColumns3(table);
1142
- const routes = [];
1143
- const notImplemented = (what) => `throw new Error('Not implemented: ${what} ${table.tsName}.');`;
1144
- routes.push({
1145
- name: "list",
1146
- method: "get",
1147
- path: "/",
1148
- middleware: [],
1149
- resType: `${rowType}[]`,
1150
- // The stub states its own contract twice over: the annotated local is what a reader sees,
1151
- // and the annotated res parameter is what Express's own types hold the handler to. Nothing
1152
- // infers a client from either, which the docs say plainly; the types are for the person
1153
- // filling the stub in.
1154
- body: [`const rows: ${rowType}[] = [];`, "res.json(rows);"]
1155
- });
1156
- if (key) {
1157
- const keyPath = "/" + key.map((c) => `:${c.name}`).join("/");
1158
- routes.push({
1159
- name: "byId",
1160
- method: "get",
1161
- path: keyPath,
1162
- middleware: [`${VALIDATE_FN}('params', ${paramsName})`],
1163
- resType: `${rowType} | null`,
1164
- body: [VALID_PARAM_HINT2, `const row: ${rowType} | null = null;`, "res.json(row);"]
1165
- });
1166
- if (writable) {
1167
- routes.push({
1168
- name: "update",
1169
- method: "patch",
1170
- path: keyPath,
1171
- middleware: [
1172
- `${VALIDATE_FN}('params', ${paramsName})`,
1173
- "json()",
1174
- `${VALIDATE_FN}('body', ${updateName})`
1175
- ],
1176
- resType: rowType,
1177
- body: [notImplemented("update")]
1178
- });
1179
- routes.push({
1180
- name: "delete",
1181
- method: "delete",
1182
- path: keyPath,
1183
- middleware: [`${VALIDATE_FN}('params', ${paramsName})`],
1184
- resType: "boolean",
1185
- body: [VALID_PARAM_HINT2, "res.json(true);"]
1186
- });
1187
- }
1188
- }
1189
- if (writable) {
1190
- routes.push({
1191
- name: "create",
1192
- method: "post",
1193
- path: "/",
1194
- middleware: ["json()", `${VALIDATE_FN}('body', ${insertName})`],
1195
- resType: rowType,
1196
- body: [notImplemented("create")]
1197
- });
1198
- }
1199
- if (opts.includeRelations) {
1200
- routes.push(...relationRoutes2(table, lib, rowType, opts));
1201
- }
1202
- const order = ["list", "byId", "create", "update", "delete"];
1203
- const rank = (n) => order.indexOf(n) === -1 ? order.length : order.indexOf(n);
1204
- routes.sort((a, b) => rank(a.name) - rank(b.name));
1205
- const exportName = routesExportName2(table, opts.naming);
1206
- const statements = routes.map((r) => {
1207
- const args = [lit2(r.path), ...r.middleware].join(", ");
1208
- const reads = (what) => r.body.some((line) => !line.startsWith("//") && what.test(line));
1209
- const reqParam = reads(/\breq\./) ? "req" : "_req";
1210
- const resParam = reads(/\bres\./) ? "res" : "_res";
1211
- return [
1212
- `${exportName}.${r.method}(${args}, async (${reqParam}, ${resParam}: Response<${r.resType}>) => {`,
1213
- ...r.body.map((line) => ` ${line}`),
1214
- `});`
1215
- ].join("\n");
1216
- }).join("\n\n");
1217
- const body = `export const ${exportName} = Router();
1218
-
1219
- ${statements}
1220
- `;
1221
- const useShared = !!opts.validation?.useShared && !!opts.validation?.importPath;
1222
- const declared = [];
1223
- if (!useShared) {
1224
- if (writable) {
1225
- declared.push(`export const ${insertName} = ${renderSchema3(table, lib, "insert")};`);
1226
- declared.push(`export const ${updateName} = ${renderSchema3(table, lib, "update")};`);
1227
- }
1228
- declared.push(`export const ${selectName} = ${renderSchema3(table, lib, "select")};`);
1229
- }
1230
- if (key) {
1231
- declared.push(
1232
- `export const ${paramsName} = ${d.objectInline(
1233
- key.map((c) => paramField2(c, lib)).join(", ")
1234
- )};`
1235
- );
1236
- }
1237
- declared.push(`export type ${rowType} = ${d.infer(selectName)};`);
1238
- const decided = [...declared, body].join("\n\n");
1239
- const imports = [];
1240
- if (useShared) {
1241
- const sharedAffix = (0, import_validation_core7.resolveAffix)({
1242
- affix: opts.validation?.affix,
1243
- schemaSuffix: opts.validation?.schemaSuffix
1244
- });
1245
- const wanted = [
1246
- ["insert", insertName],
1247
- ["update", updateName],
1248
- ["select", selectName]
1249
- ].filter(([, local]) => decided.includes(local));
1250
- if (wanted.length) {
1251
- const spec = (0, import_validation_core7.resolveConfiguredImport)(
1252
- opts.validation.importPath,
1253
- ctx.out,
1254
- process.cwd(),
1255
- opts.importExtension
1256
- );
1257
- const names = wanted.map(([mode, local]) => {
1258
- const exported = (0, import_validation_core7.schemaName)(mode, table.tsName, sharedAffix);
1259
- return exported === local ? local : `${exported} as ${local}`;
1260
- }).join(", ");
1261
- imports.push(`import { ${names} } from '${spec}';`);
1262
- }
1263
- }
1264
- const expressNames = ["Router"];
1265
- if (decided.includes("json()")) expressNames.push("json");
1266
- expressNames.push("type Response");
1267
- imports.push(`import { ${expressNames.join(", ")} } from 'express';`);
1268
- if (decided.includes(`${VALIDATE_FN}('`)) {
1269
- const spec = (0, import_validation_core7.importSpecifier)(`./${VALIDATION_MODULE}.ts`, opts.importExtension);
1270
- imports.push(`import { ${VALIDATE_FN} } from '${spec}';`);
1271
- }
1272
- if (LIB_USAGE3[lib].test(decided)) imports.unshift(LIB_IMPORTS3[lib]);
1273
- const wide = table.columns.filter(isWide3).map((c) => c.name);
1274
- const wideNote = wide.length ? `// No validated type for ${wide.length === 1 ? "this column" : "these columns"}: ${wide.join(", ")}.
1275
- // DRZL could not derive one from the schema, so these routes accept any value there.
1276
- ` : "";
1277
- return `// Generated by @drzl/generator-express
1278
- // Routes for table: ${table.name}
1279
- ${wideNote}${imports.join("\n")}
1280
-
1281
- ${decided}`;
1282
- }
1283
- function relationRoutes2(table, lib, rowType, opts) {
1284
- const d = LIBS3[lib];
1285
- const out = [];
1286
- const taken = /* @__PURE__ */ new Set();
1287
- for (const fk of table.foreignKeys ?? []) {
1288
- if (fk.columns.length !== 1) continue;
1289
- const colName = fk.columns[0];
1290
- const column = table.columns.find((c) => c.name === colName);
1291
- if (!column) continue;
1292
- const segment = toCase3(`by-${colName}`, opts.naming?.procedureCase ?? "kebab");
1293
- if (taken.has(segment)) continue;
1294
- taken.add(segment);
1295
- const inline = d.objectInline(paramField2(column, lib));
1296
- out.push({
1297
- name: `listBy${cap3(colName)}`,
1298
- method: "get",
1299
- path: `/${segment}/:${colName}`,
1300
- middleware: [`${VALIDATE_FN}('params', ${inline})`],
1301
- resType: `${rowType}[]`,
1302
- body: [VALID_PARAM_HINT2, `const rows: ${rowType}[] = [];`, "res.json(rows);"]
1303
- });
1304
- }
1305
- return out;
1306
- }
1307
- function renderBarrel3(modules, ctx, path9, opts) {
1308
- if (!modules.length) {
1309
- return `// Generated by @drzl/generator-express
1310
- // No tables detected in analysis. Add tables to your schema and regenerate.
1311
- import express from 'express';
1312
-
1313
- export const app = express();
1314
- `;
1315
- }
1316
- const entries = modules.map(({ filePath, exportName, table }) => ({
1317
- rel: (0, import_validation_core7.importSpecifier)(
1318
- "./" + path9.relative(ctx.out, filePath).replace(/\\/g, "/"),
1319
- opts.importExtension
1320
- ),
1321
- exportName,
1322
- mount: mountPath2(table, opts.naming)
1323
- }));
1324
- const imports = entries.map((e) => `import { ${e.exportName} } from '${e.rel}';`).join("\n");
1325
- const mounts = entries.map((e) => `app.use(${lit2(e.mount)}, ${e.exportName});`).join("\n");
1326
- const reExports = entries.map((e) => `export * from '${e.rel}';`).join("\n");
1327
- return `// Generated by @drzl/generator-express
1328
- import express from 'express';
1329
- ${imports}
1330
-
1331
- export const app = express();
1332
-
1333
- ${mounts}
1334
-
1335
- ${reExports}
1336
- `;
1337
- }
1338
- var import_validation_core6, import_validation_core7, APP_MODULE2, VALIDATION_MODULE, q3, lit2, NUMERIC_SEGMENT2, LIB_IMPORTS3, LIB_USAGE3, LIBS3, cap3, isIdent3, ExpressGenerator, index_default3, VALIDATE_FN, VALID_PARAM_HINT2;
1339
- var init_dist3 = __esm({
1340
- "../generator-express/dist/index.js"() {
1341
- "use strict";
1342
- import_validation_core6 = require("@drzl/validation-core");
1343
- import_validation_core7 = require("@drzl/validation-core");
1344
- APP_MODULE2 = "index";
1345
- VALIDATION_MODULE = "validation";
1346
- q3 = (v) => JSON.stringify(v);
1347
- lit2 = (v) => /['\\]/.test(v) ? JSON.stringify(v) : `'${v}'`;
1348
- NUMERIC_SEGMENT2 = String.raw`/^-?\d+(\.\d+)?$/`;
1349
- LIB_IMPORTS3 = {
1350
- zod: "import { z } from 'zod';",
1351
- valibot: "import * as v from 'valibot';",
1352
- arktype: "import { type } from 'arktype';"
1353
- };
1354
- LIB_USAGE3 = {
1355
- zod: /\bz\./,
1356
- valibot: /\bv\./,
1357
- arktype: /\btype\(|\.infer\b/
1358
- };
1359
- LIBS3 = {
1360
- zod: {
1361
- number: "z.number()",
1362
- string: "z.string()",
1363
- boolean: "z.boolean()",
1364
- date: "z.date()",
1365
- unknown: "z.unknown()",
1366
- enum: (vals) => `z.enum([${vals.map(q3).join(", ")}] as const)`,
1367
- nullable: (b) => `${b}.nullable()`,
1368
- optional: (b) => `${b}.optional()`,
1369
- object: (body) => `z.object({
1370
- ${body}
1371
- })`,
1372
- objectInline: (body) => `z.object({ ${body} })`,
1373
- partialUpdate: (s) => `${s}.partial()`,
1374
- // Not `z.coerce.number()`, and not `z.coerce.date()`: both accept far more than a path
1375
- // segment addressing a row should. See the grid referenced on `LibDialect.coerce`.
1376
- coerce: (t) => t === "number" ? `z.string().regex(${NUMERIC_SEGMENT2}).transform(Number)` : t === "Date" ? "z.iso.datetime().transform((s) => new Date(s))" : null,
1377
- infer: (s) => `z.output<typeof ${s}>`
1378
- },
1379
- valibot: {
1380
- number: "v.number()",
1381
- string: "v.string()",
1382
- boolean: "v.boolean()",
1383
- date: "v.date()",
1384
- unknown: "v.unknown()",
1385
- enum: (vals) => `v.picklist([${vals.map(q3).join(", ")}] as const)`,
1386
- nullable: (b) => `v.nullable(${b})`,
1387
- optional: (b) => `v.optional(${b})`,
1388
- object: (body) => `v.object({
1389
- ${body}
1390
- })`,
1391
- objectInline: (body) => `v.object({ ${body} })`,
1392
- // A valibot pipe step sees the previous step's *output*, so the check has to happen while the
1393
- // value is still the string: after a `v.transform(Number)` there is no string left to look at.
1394
- coerce: (t) => t === "number" ? `v.pipe(v.string(), v.regex(${NUMERIC_SEGMENT2}), v.transform(Number))` : t === "Date" ? "v.pipe(v.string(), v.isoTimestamp(), v.transform((s) => new Date(s)))" : null,
1395
- infer: (s) => `v.InferOutput<typeof ${s}>`
1396
- },
1397
- arktype: {
1398
- number: "number",
1399
- string: "string",
1400
- boolean: "boolean",
1401
- date: "Date",
1402
- unknown: "unknown",
1403
- // The surrounding encode adds the quotes, so the union is built with the inner quoting
1404
- // ArkType expects.
1405
- enum: (vals) => vals.map((x) => `'${x.replace(/'/g, "\\'")}'`).join(" | "),
1406
- nullable: (b) => `(${b} | null)`,
1407
- optional: (b) => `${b}?`,
1408
- object: (body) => `type({
1409
- ${body}
1410
- })`,
1411
- objectInline: (body) => `type({ ${body} })`,
1412
- fieldIsString: true,
1413
- // ArkType ships these as keywords, and they are morphs: the declared output type is `number`,
1414
- // not `string`. Returned bare, because `fieldIsString` quotes every expression this dialect
1415
- // produces, and a keyword returned pre-quoted arrives as a string *literal* type matching
1416
- // nothing but that sentence. `string.date.iso.parse` and not `string.date.parse`, which
1417
- // accepts `"1"` as the year 2001.
1418
- coerce: (t) => t === "number" ? "string.numeric.parse" : t === "Date" ? "string.date.iso.parse" : null,
1419
- infer: (s) => `typeof ${s}.infer`
1420
- }
1421
- };
1422
- cap3 = (s) => s.charAt(0).toUpperCase() + s.slice(1);
1423
- isIdent3 = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
1424
- ExpressGenerator = class {
1425
- constructor(analysis) {
1426
- this.analysis = analysis;
1427
- }
1428
- async generate(opts) {
1429
- const fs6 = (0, import_validation_core6.fileWriter)(opts.fileSink);
1430
- const path9 = await import("path");
1431
- const out = path9.resolve(process.cwd(), opts.outputDir);
1432
- const ctx = { out };
1433
- await fs6.mkdir(out, { recursive: true });
1434
- const files = [];
1435
- const write = async (filePath, content) => {
1436
- const formatted = await (0, import_validation_core7.formatCode)(
1437
- buildHeader3(opts.outputHeader) + content,
1438
- filePath,
1439
- opts.format
1440
- );
1441
- await fs6.writeFile(filePath, formatted, "utf8");
1442
- files.push(filePath);
1443
- };
1444
- const barrelPath = path9.join(out, `${APP_MODULE2}.ts`);
1445
- const validationPath = path9.join(out, `${VALIDATION_MODULE}.ts`);
1446
- const modules = [];
1447
- const total = this.analysis.tables.length;
1448
- let index = 0;
1449
- let anyValidates = false;
1450
- for (const table of this.analysis.tables) {
1451
- const base = `${table.tsName}${opts.naming?.routerSuffix ?? ""}`;
1452
- const filePath = path9.join(out, `${toCase3(base, opts.naming?.procedureCase)}.ts`);
1453
- if (filePath === barrelPath || filePath === validationPath) {
1454
- const which = filePath === barrelPath ? "the barrel" : "the validation middleware module";
1455
- throw new Error(
1456
- `@drzl/generator-express: the routes for table "${table.name}" would be written to ${filePath}, which is ${which} this generator also writes. Set naming.routerSuffix to move it out of the way.`
1457
- );
1458
- }
1459
- const source = renderRoutes2(table, opts, ctx);
1460
- if (source.includes(`${VALIDATE_FN}('`)) anyValidates = true;
1461
- await write(filePath, source);
1462
- modules.push({ table, filePath, exportName: routesExportName2(table, opts.naming) });
1463
- index++;
1464
- opts.onProgress?.({ index, total, table: table.name, filePath });
1465
- }
1466
- if (anyValidates) {
1467
- await write(validationPath, renderValidationModule());
1468
- }
1469
- await write(barrelPath, renderBarrel3(modules, ctx, path9, opts));
1470
- return { files };
1471
- }
1472
- };
1473
- index_default3 = ExpressGenerator;
1474
- VALIDATE_FN = "validate";
1475
- VALID_PARAM_HINT2 = "// validate() has already replaced req.params with the parsed values.";
1476
- }
1477
- });
1478
-
1479
- // ../generator-json-schema/dist/index.js
1480
- var dist_exports4 = {};
1481
- __export(dist_exports4, {
1482
- DRAFT: () => DRAFT,
1483
- JsonSchemaGenerator: () => JsonSchemaGenerator,
1484
- allColumns: () => allColumns,
1485
- componentSchemaName: () => componentSchemaName,
1486
- componentsDocument: () => componentsDocument,
1487
- default: () => index_default4,
1488
- documentSchemas: () => documentSchemas,
1489
- enumKey: () => enumKey,
1490
- openApiDocument: () => openApiDocument,
1491
- planSharedEnums: () => planSharedEnums,
1492
- tableSchemas: () => tableSchemas
1493
- });
1494
- function enumKey(name) {
1495
- const safe = name.replace(/[^A-Za-z0-9.\-_]/g, "_").replace(/^_+|_+$/g, "");
1496
- return safe.length ? safe : void 0;
1497
- }
1498
- function declaredEnumColumns(columns) {
1499
- return columns.filter((c) => !c.shape && c.enumValues && c.enumValues.length);
1500
- }
1501
- function planSharedEnums(columns, enums, ref2, reserved = /* @__PURE__ */ new Set()) {
1502
- if (!enums?.length) return void 0;
1503
- const uses = /* @__PURE__ */ new Map();
1504
- for (const c of declaredEnumColumns(columns)) {
1505
- const id = identity(c.enumValues);
1506
- uses.set(id, (uses.get(id) ?? 0) + 1);
1507
- }
1508
- const keyed = /* @__PURE__ */ new Map();
1509
- const taken = new Set(reserved);
1510
- for (const e of enums) {
1511
- const id = identity(e.values);
1512
- if ((uses.get(id) ?? 0) < 2) continue;
1513
- if (keyed.has(id)) continue;
1514
- const key = enumKey(e.name);
1515
- if (!key || taken.has(key)) continue;
1516
- taken.add(key);
1517
- keyed.set(id, { key, values: [...e.values] });
1518
- }
1519
- if (!keyed.size) return void 0;
1520
- const used = /* @__PURE__ */ new Set();
1521
- return {
1522
- resolve(values) {
1523
- const hit = keyed.get(identity(values));
1524
- if (!hit) return void 0;
1525
- used.add(hit.key);
1526
- return ref2(hit.key);
1527
- },
1528
- definitions() {
1529
- const out = {};
1530
- for (const { key, values } of keyed.values()) {
1531
- if (used.has(key)) out[key] = { enum: [...values] };
1532
- }
1533
- return out;
1534
- }
1535
- };
1536
- }
1537
- function canonicalSetPattern(values, integerOnly) {
1538
- const branches = (0, import_validation_core10.canonicalMembers)(values).map((member) => {
1539
- if (member === "0") return integerOnly ? "[+-]?0+" : "[+-]?(?:0+(?:\\.0*)?|0*\\.0+)";
1540
- const sign = member.startsWith("-") ? "-" : "\\+?";
1541
- const body = member.startsWith("-") ? member.slice(1) : member;
1542
- const [int = "", frac = ""] = body.split(".");
1543
- if (integerOnly) return `${sign}0*${int}`;
1544
- if (!frac) return `${sign}0*${int}(?:\\.0*)?`;
1545
- return int === "0" ? `${sign}0*\\.${frac}0*` : `${sign}0*${int}\\.${frac}0*`;
1546
- });
1547
- return `^(?:${branches.join("|")})$`;
1548
- }
1549
- function baseSchema(c, mode, target, checks, sets, lengths, enumRef) {
1550
- const s = c.shape;
1551
- if (s) {
1552
- switch (s.kind) {
1553
- case "json":
1554
- return {};
1555
- case "custom":
1556
- return {};
1557
- case "buffer": {
1558
- const bin = base64(target);
1559
- applyBinaryLengths(bin, c, lengths);
1560
- return bin;
1561
- }
1562
- case "tuple":
1563
- return target === "openapi-3.0" ? { type: "array", items: { type: "number" }, minItems: s.length, maxItems: s.length } : {
1564
- type: "array",
1565
- prefixItems: Array.from({ length: s.length }, () => ({ type: "number" })),
1566
- minItems: s.length,
1567
- maxItems: s.length
1568
- };
1569
- case "numberObject":
1570
- return {
1571
- type: "object",
1572
- properties: Object.fromEntries(s.fields.map((f) => [f, { type: "number" }])),
1573
- required: [...s.fields]
1574
- };
1575
- case "numberVector":
1576
- return {
1577
- type: "array",
1578
- items: { type: "number" },
1579
- ...s.length ? { minItems: s.length, maxItems: s.length } : {}
1580
- };
1581
- case "bitstring":
1582
- return {
1583
- type: "string",
1584
- pattern: "^[01]*$",
1585
- ...s.length ? s.exact ? { minLength: s.length, maxLength: s.length } : { maxLength: s.length } : {}
1586
- };
1587
- case "byteString":
1588
- return { type: "string", ...s.length ? { maxLength: s.length } : {} };
1589
- }
1590
- }
1591
- const set = sets.find((x) => x.column === c.name);
1592
- if (set) {
1593
- if ((0, import_validation_core10.comparisonWire)(c) === "numeric-string") {
1594
- return { type: "string", pattern: canonicalSetPattern(set.values, c.dbType === "BIGINT") };
1595
- }
1596
- return {
1597
- enum: set.values.map(
1598
- (v) => set.kind === "string" || c.tsType === "bigint" ? v : Number(v)
1599
- )
1600
- };
1601
- }
1602
- if (c.enumValues && c.enumValues.length) {
1603
- const ref2 = enumRef?.(c.enumValues);
1604
- return ref2 ? { $ref: ref2 } : { enum: [...c.enumValues] };
1605
- }
1606
- const mine = c.arrayDimensions ? [] : checks.filter((k) => k.column === c.name);
1607
- const eq = mine.find((k) => k.operator === "=");
1608
- if (eq) {
1609
- if ((0, import_validation_core10.comparisonWire)(c) === "numeric-string") {
1610
- return { type: "string", pattern: canonicalSetPattern([eq.value], c.dbType === "BIGINT") };
1611
- }
1612
- const only = eq.kind === "string" || c.tsType === "bigint" ? eq.value : Number(eq.value);
1613
- return target === "openapi-3.0" ? { enum: [only] } : { const: only };
1614
- }
1615
- switch (c.tsType) {
1616
- case "string": {
1617
- const out = { type: "string" };
1618
- if (c.format === "uuid") out.format = UUID_FORMAT;
1619
- else if (c.format && import_validation_core10.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core10.COLUMN_FORMATS[c.format];
1620
- if (c.maxLength !== void 0) out.maxLength = c.maxLength;
1621
- applyByteCap(out, c, lengths);
1622
- applyLengths(out, c, lengths);
1623
- return out;
1624
- }
1625
- case "number": {
1626
- const out = { type: (0, import_validation_core10.isIntegerColumn)(c) ? "integer" : "number" };
1627
- if (!c.arrayDimensions) applyNumericBounds(out, c, checks, target);
1628
- return out;
1629
- }
1630
- case "bigint":
1631
- return {
1632
- type: "string",
1633
- pattern: typeof c.min === "string" && !c.min.startsWith("-") ? "^\\d+$" : "^-?\\d+$"
1634
- };
1635
- case "boolean":
1636
- return { type: "boolean" };
1637
- case "Date":
1638
- return { type: "string", format: "date-time" };
1639
- case "Uint8Array":
1640
- return base64(target);
1641
- default:
1642
- return {};
1643
- }
1644
- }
1645
- function applyByteCap(out, c, lengths) {
1646
- const budget = byteBudget(c, lengths);
1647
- if (budget === void 0) return;
1648
- out.maxLength = Math.min(Number(out.maxLength ?? Infinity), budget);
1649
- out.description = BYTE_BUDGET_NOTE(budget);
1650
- }
1651
- function ceilingOf(k) {
1652
- if (k.operator === "<=" || k.operator === "=") return Number(k.value);
1653
- if (k.operator === "<") return Number(k.value) - 1;
1654
- return void 0;
1655
- }
1656
- function byteBudget(c, lengths) {
1657
- const bounds = [
1658
- ...c.maxBytes ? [c.maxBytes] : [],
1659
- ...lengths.filter((k) => k.column === c.name && k.unit === "bytes").map(ceilingOf).filter((n) => n !== void 0)
1660
- ];
1661
- return bounds.length ? Math.min(...bounds) : void 0;
1662
- }
1663
- function applyLengths(out, c, lengths) {
1664
- for (const k of lengths.filter((x) => x.column === c.name && x.unit !== "bytes")) {
1665
- const n = Number(k.value);
1666
- if (k.operator === ">=") out.minLength = Math.max(Number(out.minLength ?? 0), n);
1667
- else if (k.operator === ">") out.minLength = Math.max(Number(out.minLength ?? 0), n + 1);
1668
- else if (k.operator === "<=") out.maxLength = Math.min(Number(out.maxLength ?? Infinity), n);
1669
- else if (k.operator === "<") out.maxLength = Math.min(Number(out.maxLength ?? Infinity), n - 1);
1670
- else if (k.operator === "=") {
1671
- out.minLength = n;
1672
- out.maxLength = n;
1673
- }
1674
- }
1675
- }
1676
- function applyBinaryLengths(out, c, lengths) {
1677
- const budget = byteBudget(c, lengths);
1678
- if (budget === void 0) return;
1679
- out.maxLength = 4 * Math.ceil(budget / 3);
1680
- out.description = `At most ${budget} bytes, which JSON Schema has no keyword for. The value travels as base64, and maxLength counts the characters of that encoding: it refuses nothing the column accepts, and a value one or two bytes over the limit encodes to the same number of characters as one inside it.`;
1681
- }
1682
- function applyNumericBounds(out, c, checks, target) {
1683
- let min = c.min !== void 0 ? { value: Number(c.min), exclusive: false } : void 0;
1684
- let max = c.max !== void 0 ? { value: Number(c.max), exclusive: false } : void 0;
1685
- for (const k of checks.filter((x) => x.column === c.name && x.kind === "number")) {
1686
- if (k.operator === ">=") min = { value: Number(k.value), exclusive: false };
1687
- else if (k.operator === ">") min = { value: Number(k.value), exclusive: true };
1688
- else if (k.operator === "<=") max = { value: Number(k.value), exclusive: false };
1689
- else if (k.operator === "<") max = { value: Number(k.value), exclusive: true };
1690
- }
1691
- const old = target === "openapi-3.0";
1692
- if (min) {
1693
- if (min.exclusive && !old) out.exclusiveMinimum = min.value;
1694
- else {
1695
- out.minimum = min.value;
1696
- if (min.exclusive) out.exclusiveMinimum = true;
1697
- }
1698
- }
1699
- if (max) {
1700
- if (max.exclusive && !old) out.exclusiveMaximum = max.value;
1701
- else {
1702
- out.maximum = max.value;
1703
- if (max.exclusive) out.exclusiveMaximum = true;
1704
- }
1705
- }
1706
- }
1707
- function cardinalityBounds(c, cardinalities) {
1708
- if (!c.arrayDimensions) return {};
1709
- const out = {};
1710
- for (const k of cardinalities.filter((x) => x.column === c.name)) {
1711
- const n = Number(k.value);
1712
- if (k.operator === ">=") out.minItems = n;
1713
- else if (k.operator === ">") out.minItems = n + 1;
1714
- else if (k.operator === "<=") out.maxItems = n;
1715
- else if (k.operator === "<") out.maxItems = n - 1;
1716
- else if (k.operator === "=") {
1717
- out.minItems = n;
1718
- out.maxItems = n;
1719
- }
1720
- }
1721
- return out;
1722
- }
1723
- function makeNullable(s, target) {
1724
- if (target === "openapi-3.0") return { ...s, nullable: true };
1725
- if ("$ref" in s) return { anyOf: [s, { type: "null" }] };
1726
- if (s.type === void 0) {
1727
- if (Array.isArray(s.enum)) return { ...s, enum: [...s.enum, null] };
1728
- if ("const" in s) {
1729
- const { const: k, ...rest } = s;
1730
- return { ...rest, enum: [k, null] };
1731
- }
1732
- return s;
1733
- }
1734
- return { ...s, type: [s.type, "null"] };
1735
- }
1736
- function columnSchema(c, mode, target, checks, sets, lengths, cardinalities, applyDefault, enumRef) {
1737
- const wantsDefault = mode === "insert" && applyDefault && c.defaultValue !== void 0;
1738
- const refBlockedBy30 = target === "openapi-3.0" && !c.arrayDimensions && (c.nullable || wantsDefault);
1739
- let s = baseSchema(c, mode, target, checks, sets, lengths, refBlockedBy30 ? void 0 : enumRef);
1740
- const dims = c.arrayDimensions ?? 0;
1741
- for (let i = 0; i < dims; i++) {
1742
- s = { type: "array", items: s, ...i === dims - 1 ? cardinalityBounds(c, cardinalities) : {} };
1743
- }
1744
- if (c.nullable) s = makeNullable(s, target);
1745
- if (mode === "insert" && applyDefault && c.defaultValue !== void 0) {
1746
- s = { ...s, default: c.defaultValue };
1747
- }
1748
- return s;
1749
- }
1750
- function rowDescription(rows, cols) {
1751
- const present = new Set(cols.map((c) => c.name));
1752
- const applicable = rows.filter((r) => present.has(r.left) && present.has(r.right));
1753
- if (!applicable.length) return void 0;
1754
- const list = applicable.map((r) => `${r.name ? `${r.name}: ` : ""}${r.left} ${r.operator} ${r.right}`).join("; ");
1755
- return `Row constraints not expressible in JSON Schema: ${list}`;
1756
- }
1757
- function tableSchema(table, cols, mode, target, applyDefaults, parsed, enums, localDefs) {
1758
- const properties = {};
1759
- const required = [];
1760
- for (const c of cols) {
1761
- properties[c.name] = columnSchema(
1762
- c,
1763
- mode,
1764
- target,
1765
- parsed.checks,
1766
- parsed.sets,
1767
- parsed.lengths,
1768
- parsed.cardinalities,
1769
- applyDefaults,
1770
- enums?.resolve
1771
- );
1772
- const suppliedOnInsert = c.hasDefault || applyDefaults && c.defaultValue !== void 0 || c.isGenerated;
1773
- const optional = mode === "update" || mode === "insert" && suppliedOnInsert;
1774
- if (!optional) required.push(c.name);
1775
- }
1776
- const desc = rowDescription(parsed.rows, cols);
1777
- const defs = localDefs ? enums?.definitions() ?? {} : {};
1778
- return {
1779
- ...target === "draft-2020-12" ? { $schema: DRAFT } : {},
1780
- $id: `${table.tsName}.${mode}`,
1781
- title: `${mode} ${table.tsName}`,
1782
- ...desc ? { description: desc } : {},
1783
- type: "object",
1784
- properties,
1785
- ...required.length ? { required } : {},
1786
- additionalProperties: false,
1787
- ...Object.keys(defs).length ? { $defs: defs } : {}
1788
- };
1789
- }
1790
- function collect(table) {
1791
- const parsed = (table.checks ?? []).map((k) => (0, import_validation_core10.parseCheck)(k.expression, k.name));
1792
- const { checks, sets } = (0, import_validation_core10.applyWirePolicy)(
1793
- table.columns,
1794
- parsed.flatMap((p) => p.ok ? p.checks : []),
1795
- parsed.flatMap((p) => p.ok ? p.sets ?? [] : [])
1796
- );
1797
- return {
1798
- checks,
1799
- sets,
1800
- rows: parsed.flatMap((p) => p.ok ? p.rows ?? [] : []),
1801
- lengths: parsed.flatMap((p) => p.ok ? p.lengths ?? [] : []),
1802
- cardinalities: parsed.flatMap((p) => p.ok ? p.cardinalities ?? [] : [])
1803
- };
1804
- }
1805
- function tableSchemas(table, opts = {}) {
1806
- const target = opts.target ?? "draft-2020-12";
1807
- const parsed = collect(table);
1808
- const localDefs = target === "draft-2020-12";
1809
- const build2 = (cols, mode) => {
1810
- const plan = localDefs ? planSharedEnums(cols, opts.enums, (key) => `#/$defs/${key}`) : void 0;
1811
- return tableSchema(table, cols, mode, target, !!opts.applyDefaults, parsed, plan, localDefs);
1812
- };
1813
- return {
1814
- insert: build2((0, import_validation_core10.insertColumns)(table), "insert"),
1815
- update: build2((0, import_validation_core10.updateColumns)(table), "update"),
1816
- select: build2((0, import_validation_core10.selectColumns)(table), "select")
1817
- };
1818
- }
1819
- function tableSchemasWith(table, target, applyDefaults, plan, modes = MODES) {
1820
- const parsed = collect(table);
1821
- const columns = {
1822
- insert: () => (0, import_validation_core10.insertColumns)(table),
1823
- update: () => (0, import_validation_core10.updateColumns)(table),
1824
- select: () => (0, import_validation_core10.selectColumns)(table)
1825
- };
1826
- const out = {};
1827
- for (const mode of modes) {
1828
- out[mode] = tableSchema(
1829
- table,
1830
- columns[mode](),
1831
- mode,
1832
- target,
1833
- applyDefaults,
1834
- parsed,
1835
- plan,
1836
- false
1837
- );
1838
- }
1839
- return out;
1840
- }
1841
- function componentsDocument(tables, opts = {}) {
1842
- const target = opts.target ?? "draft-2020-12";
1843
- const schemas = {};
1844
- for (const table of tables) {
1845
- const built = tableSchemasWith(table, target, !!opts.applyDefaults, void 0);
1846
- for (const mode of MODES) {
1847
- const { $schema: _dialect, $id: _id, ...rest } = built[mode];
1848
- schemas[componentSchemaName(table, mode)] = rest;
1849
- }
1850
- }
1851
- return { schemas };
1852
- }
1853
- function documentSchemas(tables, opts) {
1854
- const plan = planSharedEnums(
1855
- tables.flatMap((t) => t.columns),
1856
- opts.enums,
1857
- (key) => `#/components/schemas/${key}`,
1858
- opts.reserved
1859
- );
1860
- const built = /* @__PURE__ */ new Map();
1861
- for (const table of tables) {
1862
- const all = tableSchemasWith(table, opts.target, opts.applyDefaults, plan, opts.modes(table));
1863
- built.set(table, all);
1864
- }
1865
- return { built, definitions: () => plan?.definitions() ?? {} };
1866
- }
1867
- function keyColumns4(table) {
1868
- const names = table.primaryKey?.columns ?? [];
1869
- if (!names.length) return null;
1870
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
1871
- if (cols.some((c) => !c)) return null;
1872
- return cols;
1873
- }
1874
- function foreignKeysOf(table) {
1875
- if (table.foreignKeys?.length) return table.foreignKeys;
1876
- return table.columns.filter((c) => c.references).map((c) => ({
1877
- columns: [c.name],
1878
- foreignTable: c.references.table,
1879
- ...c.references.schema ? { foreignSchema: c.references.schema } : {},
1880
- foreignColumns: [c.references.column]
1881
- }));
1882
- }
1883
- function build(tables, opts) {
1884
- const target = opts.target ?? "draft-2020-12";
1885
- const schemaTarget = target === "openapi-3.0" ? "openapi-3.0" : "openapi-3.1";
1886
- const failure = String(opts.validationStatus ?? 400);
1887
- const paths = {};
1888
- const schemas = {};
1889
- const tags = [];
1890
- const operationIds = /* @__PURE__ */ new Map();
1891
- const owner = /* @__PURE__ */ new Map();
1892
- const claim = (path9, by, label) => {
1893
- const taken = owner.get(path9);
1894
- if (taken !== void 0 && taken.by !== by) {
1895
- throw new Error(
1896
- `@drzl/generator-json-schema: the OpenAPI path "${path9}" is claimed twice: by table "${taken.label}" (exported as ${taken.by}) and by table "${label}" (exported as ${by}). A path names one resource, so one of the two has to be left out of this generator with the config's "exclude" list.`
1897
- );
1898
- }
1899
- owner.set(path9, { by, label });
1900
- };
1901
- const operation = (id, table, rest) => {
1902
- const clash = operationIds.get(id);
1903
- if (clash !== void 0) {
1904
- throw new Error(
1905
- `@drzl/generator-json-schema: the operationId "${id}" would be emitted for both "${clash}" and "${(0, import_analyzer.qualifiedTableName)(table)}". An operationId is the method name a client generator derives, and the specification requires it to be unique across the document.`
1906
- );
1907
- }
1908
- operationIds.set(id, (0, import_analyzer.qualifiedTableName)(table));
1909
- return { operationId: id, tags: [(0, import_analyzer.qualifiedTableName)(table)], ...rest };
1910
- };
1911
- const keys = new Map(tables.map((t) => [t, keyColumns4(t)]));
1912
- const carried = new Map(tables.map((t) => [t, modesFor(t, keys.get(t))]));
1913
- const reserved = /* @__PURE__ */ new Set([
1914
- ERROR_SCHEMA,
1915
- ...tables.flatMap((t) => carried.get(t).map((m) => componentName(t, m)))
1916
- ]);
1917
- const shared = documentSchemas(tables, {
1918
- target: schemaTarget,
1919
- applyDefaults: !!opts.applyDefaults,
1920
- reserved,
1921
- modes: (t) => carried.get(t),
1922
- ...opts.enums ? { enums: opts.enums } : {}
1923
- });
1924
- const built = tables.map((table) => ({
1925
- table,
1926
- key: keys.get(table),
1927
- segment: resourceSegment(table),
1928
- schemas: shared.built.get(table)
1929
- }));
1930
- for (const { table, key, segment, schemas: built3 } of built) {
1931
- for (const mode of carried.get(table)) {
1932
- const { $schema: _dialect, $id: _id, ...rest } = built3[mode];
1933
- schemas[componentName(table, mode)] = rest;
1934
- }
1935
- const notes = [];
1936
- if (!key) notes.push("It has no primary key, so no path addresses a single row.");
1937
- if (table.readOnly) {
1938
- notes.push("It refuses every write, so only reads are described.");
1939
- }
1940
- tags.push({
1941
- name: (0, import_analyzer.qualifiedTableName)(table),
1942
- description: [`Table "${(0, import_analyzer.qualifiedTableName)(table)}".`, ...notes].join(" ")
1943
- });
1944
- const T = pascal(table.tsName);
1945
- const select = ref(componentName(table, "select"));
1946
- const validationFailed = {
1947
- description: "The request does not match the schema for this operation.",
1948
- ...jsonBody(ref(ERROR_SCHEMA))
1949
- };
1950
- const collidable = [
1951
- ...table.primaryKey ? [`primary key (${table.primaryKey.columns.join(", ")})`] : [],
1952
- ...table.unique.map((u) => `${u.name ? `${u.name} ` : ""}(${u.columns.join(", ")})`)
1953
- ];
1954
- const conflict = (constraints) => ({
1955
- description: `The row collides with an existing one on ${constraints.join("; ")}.`,
1956
- ...jsonBody(ref(ERROR_SCHEMA))
1957
- });
1958
- const collection = `/${segment}`;
1959
- claim(collection, table.tsName, (0, import_analyzer.qualifiedTableName)(table));
1960
- const item = {
1961
- get: operation(`list${T}`, table, {
1962
- summary: `List every ${table.name} row.`,
1963
- // No pagination parameters. Whether the server implements a limit, an offset or a cursor is
1964
- // not something a Drizzle schema states, and a declared parameter nothing honours is worse
1965
- // than an undeclared one.
1966
- responses: {
1967
- "200": {
1968
- description: `Every ${table.name} row.`,
1969
- ...jsonBody({ type: "array", items: select })
1970
- }
1971
- }
1972
- })
1973
- };
1974
- if (!table.readOnly) {
1975
- item.post = operation(`create${T}`, table, {
1976
- summary: `Create a ${table.name} row.`,
1977
- requestBody: { required: true, ...jsonBody(ref(componentName(table, "insert"))) },
1978
- responses: {
1979
- "201": { description: `The ${table.name} row that was created.`, ...jsonBody(select) },
1980
- [failure]: validationFailed,
1981
- ...collidable.length ? { "409": conflict(collidable) } : {}
1982
- }
1983
- });
1984
- }
1985
- paths[collection] = item;
1986
- if (!key) continue;
1987
- const itemPath = `${collection}/${key.map((c) => `{${c.name}}`).join("/")}`;
1988
- claim(itemPath, table.tsName, (0, import_analyzer.qualifiedTableName)(table));
1989
- const parameters = key.map((c) => ({
1990
- name: c.name,
1991
- in: "path",
1992
- required: true,
1993
- description: `${c.name}, from the primary key of ${table.name}.`,
1994
- // The column's own schema rather than a string, so an integer key is declared as one and a
1995
- // uuid key carries its format. This is the whole point of reading the real key.
1996
- schema: built3.select.properties[c.name] ?? {}
1997
- }));
1998
- const missing = {
1999
- description: `No ${table.name} row has that ${key.map((c) => c.name).join(" and ")}.`,
2000
- ...jsonBody(ref(ERROR_SCHEMA))
2001
- };
2002
- const byId = {
2003
- parameters,
2004
- get: operation(`get${T}`, table, {
2005
- summary: `Read one ${table.name} row.`,
2006
- responses: {
2007
- "200": { description: `The requested ${table.name} row.`, ...jsonBody(select) },
2008
- [failure]: validationFailed,
2009
- "404": missing
2010
- }
2011
- })
2012
- };
2013
- if (!table.readOnly) {
2014
- byId.patch = operation(`update${T}`, table, {
2015
- summary: `Patch one ${table.name} row.`,
2016
- requestBody: { required: true, ...jsonBody(ref(componentName(table, "update"))) },
2017
- responses: {
2018
- "200": { description: `The ${table.name} row after the patch.`, ...jsonBody(select) },
2019
- [failure]: validationFailed,
2020
- "404": missing,
2021
- // The primary key is not in the update schema, so a patch cannot collide on it. Only a
2022
- // unique constraint over other columns can.
2023
- ...table.unique.length ? {
2024
- "409": conflict(
2025
- table.unique.map((u) => `${u.name ? `${u.name} ` : ""}(${u.columns.join(", ")})`)
2026
- )
2027
- } : {}
2028
- }
2029
- });
2030
- byId.delete = operation(`delete${T}`, table, {
2031
- summary: `Delete one ${table.name} row.`,
2032
- responses: {
2033
- // No body. Handing back the deleted row is the alternative and it is not a true statement
2034
- // on every dialect DRZL supports: RETURNING is Postgres and SQLite, and MySQL has no such
2035
- // clause, so an implementation there has nothing to send.
2036
- "204": { description: `The ${table.name} row was deleted. No content is returned.` },
2037
- [failure]: validationFailed,
2038
- "404": missing
2039
- }
2040
- });
2041
- }
2042
- paths[itemPath] = byId;
2043
- if (!opts.includeRelations) continue;
2044
- for (const child of built) {
2045
- if (child.table === table) continue;
2046
- const matching = foreignKeysOf(child.table).filter(
2047
- (fk) => (
2048
- // Qualified on both sides. On the bare name a key pointing at `reporting.users` also
2049
- // answered for `public.users`, so the child was hung under a parent in another schema.
2050
- (0, import_analyzer.qualifiedForeignTable)(fk) === (0, import_analyzer.qualifiedTableName)(table) && fk.foreignColumns.length === key.length && fk.foreignColumns.every((c, i) => c === key[i].name)
2051
- )
2052
- );
2053
- if (matching.length !== 1) continue;
2054
- const subPath = `${itemPath}/${child.segment}`;
2055
- claim(
2056
- subPath,
2057
- `${table.tsName} -> ${child.table.tsName}`,
2058
- `${(0, import_analyzer.qualifiedTableName)(table)} -> ${(0, import_analyzer.qualifiedTableName)(child.table)}`
2059
- );
2060
- paths[subPath] = {
2061
- parameters,
2062
- get: operation(`list${T}${pascal(child.table.tsName)}`, child.table, {
2063
- summary: `List the ${child.table.name} rows belonging to one ${table.name} row.`,
2064
- responses: {
2065
- "200": {
2066
- description: `The ${child.table.name} rows whose ${matching[0].columns.join(", ")} names this ${table.name} row.`,
2067
- ...jsonBody({ type: "array", items: ref(componentName(child.table, "select")) })
2068
- },
2069
- [failure]: validationFailed,
2070
- "404": missing
2071
- }
2072
- })
2073
- };
2074
- }
2075
- }
2076
- return { paths, schemas: { ...schemas, ...shared.definitions() }, tags };
2077
- }
2078
- function openApiDocument(tables, opts = {}) {
2079
- const target = opts.target ?? "draft-2020-12";
2080
- const { paths, schemas, tags } = build(tables, opts);
2081
- if (ERROR_SCHEMA in schemas) {
2082
- throw new Error(
2083
- `@drzl/generator-json-schema: a table produced the component schema name "${ERROR_SCHEMA}", which the document already uses for its error responses.`
2084
- );
2085
- }
2086
- return {
2087
- openapi: target === "openapi-3.0" ? "3.0.3" : "3.1.1",
2088
- info: {
2089
- title: opts.info?.title ?? "API",
2090
- version: opts.info?.version ?? "0.0.0",
2091
- description: opts.info?.description ?? "Generated by DRZL from a Drizzle schema. Paths, request bodies and response bodies are derived from the schema alone; nothing here has been checked against a running server."
2092
- },
2093
- ...opts.servers?.length ? { servers: opts.servers } : {},
2094
- paths,
2095
- components: { schemas: { ...schemas, [ERROR_SCHEMA]: errorSchema() } },
2096
- tags
2097
- };
2098
- }
2099
- function renderTableModule(table, affix, target, applyDefaults, enums) {
2100
- const T = table.tsName;
2101
- const schemas = tableSchemas(table, { target, applyDefaults, ...enums ? { enums } : {} });
2102
- const decl = (mode) => `export const ${(0, import_validation_core9.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
2103
-
2104
- export type ${(0, import_validation_core9.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core9.schemaName)(mode, T, affix)};`;
2105
- return [decl("insert"), decl("update"), decl("select")].join("\n\n") + "\n";
2106
- }
2107
- function resolveDocument(opt) {
2108
- if (!opt) return null;
2109
- const o = opt === true ? {} : opt;
2110
- if (o.enabled === false) return null;
2111
- return { ...o, format: o.format ?? "ts" };
2112
- }
2113
- function buildHeader4(h) {
2114
- if (h?.enabled === false) return "";
2115
- const text = h?.text ?? "// Generated by DRZL. Do not edit by hand.";
2116
- return `${text}
2117
-
2118
- `;
2119
- }
2120
- var import_validation_core8, import_validation_core9, import_analyzer, import_validation_core10, identity, allColumns, DRAFT, UUID_FORMAT, base64, BYTE_BUDGET_NOTE, MODES, componentSchemaName, ERROR_SCHEMA, componentName, ref, pascal, modesFor, resourceSegment, jsonBody, errorSchema, DEFAULT_FILE_SUFFIX, JsonSchemaGenerator, index_default4;
2121
- var init_dist4 = __esm({
2122
- "../generator-json-schema/dist/index.js"() {
2123
- "use strict";
2124
- import_validation_core8 = require("@drzl/validation-core");
2125
- import_validation_core9 = require("@drzl/validation-core");
2126
- import_analyzer = require("@drzl/analyzer");
2127
- import_validation_core10 = require("@drzl/validation-core");
2128
- identity = (values) => JSON.stringify([...values]);
2129
- allColumns = (tables) => tables.flatMap((t) => t.columns);
2130
- DRAFT = "https://json-schema.org/draft/2020-12/schema";
2131
- UUID_FORMAT = "uuid";
2132
- base64 = (target) => target === "openapi-3.0" ? { type: "string", format: "byte" } : { type: "string", contentEncoding: "base64" };
2133
- BYTE_BUDGET_NOTE = (n) => `At most ${n} bytes of UTF-8, which JSON Schema has no keyword for. maxLength counts characters: it refuses nothing the column accepts, and a string of multi-byte characters can satisfy it and still be too long for the column.`;
2134
- MODES = ["insert", "update", "select"];
2135
- componentSchemaName = (table, mode) => `${table.tsName}${mode[0].toUpperCase()}${mode.slice(1)}`;
2136
- ERROR_SCHEMA = "Error";
2137
- componentName = componentSchemaName;
2138
- ref = (name) => ({ $ref: `#/components/schemas/${name}` });
2139
- pascal = (s) => s.charAt(0).toUpperCase() + s.slice(1);
2140
- modesFor = (table, key) => [
2141
- ...table.readOnly ? [] : ["insert"],
2142
- ...table.readOnly || !key ? [] : ["update"],
2143
- "select"
2144
- ];
2145
- resourceSegment = (table) => table.schema ? `${encodeURIComponent(table.schema)}/${encodeURIComponent(table.name)}` : encodeURIComponent(table.name);
2146
- jsonBody = (schema) => ({ content: { "application/json": { schema } } });
2147
- errorSchema = () => ({
2148
- title: "error",
2149
- description: "What an operation returns when it does not return the row.",
2150
- type: "object",
2151
- properties: {
2152
- message: { type: "string" },
2153
- code: { type: "string" }
2154
- },
2155
- required: ["message"],
2156
- additionalProperties: true
2157
- });
2158
- DEFAULT_FILE_SUFFIX = ".schema.ts";
2159
- JsonSchemaGenerator = class {
2160
- constructor(analysis) {
2161
- this.analysis = analysis;
2162
- this.library = "json-schema";
2163
- }
2164
- async generate(opts) {
2165
- const fs6 = (0, import_validation_core8.fileWriter)(opts.fileSink);
2166
- const path9 = await import("path");
2167
- const out = path9.resolve(process.cwd(), opts.outDir);
2168
- const files = [];
2169
- await fs6.mkdir(out, { recursive: true });
2170
- const affix = (0, import_validation_core9.resolveAffix)(opts);
2171
- const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX;
2172
- const target = opts.target ?? "draft-2020-12";
2173
- const document = resolveDocument(opts.document);
2174
- for (const table of this.analysis.tables) {
2175
- const filePath = path9.join(out, (0, import_validation_core9.moduleFileName)(table.tsName, fileSuffix));
2176
- const code = renderTableModule(
2177
- table,
2178
- affix,
2179
- target,
2180
- !!opts.applyDefaults,
2181
- opts.sharedEnums ? this.analysis.enums : void 0
2182
- );
2183
- const formatted = await (0, import_validation_core9.formatCode)(
2184
- buildHeader4(opts.outputHeader) + code,
2185
- filePath,
2186
- opts.format
2187
- );
2188
- await fs6.writeFile(filePath, formatted, "utf8");
2189
- files.push(filePath);
2190
- }
2191
- if (opts.components) {
2192
- const doc = componentsDocument(this.analysis.tables, {
2193
- target,
2194
- applyDefaults: !!opts.applyDefaults
2195
- });
2196
- const componentsPath = path9.join(out, "components.ts");
2197
- const code = `export const components = ${JSON.stringify(doc, null, 2)} as const;
2198
- `;
2199
- await fs6.writeFile(
2200
- componentsPath,
2201
- await (0, import_validation_core9.formatCode)(buildHeader4(opts.outputHeader) + code, componentsPath, opts.format),
2202
- "utf8"
2203
- );
2204
- files.push(componentsPath);
2205
- }
2206
- if (document) {
2207
- const built = openApiDocument(this.analysis.tables, {
2208
- target,
2209
- applyDefaults: !!opts.applyDefaults,
2210
- includeRelations: !!opts.includeRelations,
2211
- enums: this.analysis.enums,
2212
- info: document.info,
2213
- servers: document.servers,
2214
- validationStatus: document.validationStatus
2215
- });
2216
- const body = JSON.stringify(built, null, 2);
2217
- if (document.format !== "json") {
2218
- const tsPath = path9.join(out, "openapi.ts");
2219
- const code = `export const openapi = ${body} as const;
2220
- `;
2221
- await fs6.writeFile(
2222
- tsPath,
2223
- await (0, import_validation_core9.formatCode)(buildHeader4(opts.outputHeader) + code, tsPath, opts.format),
2224
- "utf8"
2225
- );
2226
- files.push(tsPath);
2227
- }
2228
- if (document.format !== "ts") {
2229
- const jsonPath = path9.join(out, "openapi.json");
2230
- await fs6.writeFile(jsonPath, body + "\n", "utf8");
2231
- files.push(jsonPath);
2232
- }
2233
- }
2234
- const ext = opts.importExtension === "none" ? "" : ".js";
2235
- const indexPath = path9.join(out, "index.ts");
2236
- const index = this.analysis.tables.map(
2237
- (t) => `export * from '${(0, import_validation_core9.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
2238
- ).concat(opts.components ? [`export * from './components${ext}';`] : []).concat(document && document.format !== "json" ? [`export * from './openapi${ext}';`] : []).join("\n") + "\n";
2239
- const indexFormatted = await (0, import_validation_core9.formatCode)(
2240
- buildHeader4(opts.outputHeader) + index,
2241
- indexPath,
2242
- opts.format
2243
- );
2244
- await fs6.writeFile(indexPath, indexFormatted, "utf8");
2245
- files.push(indexPath);
2246
- return files;
2247
- }
2248
- renderTable(table, opts) {
2249
- return renderTableModule(
2250
- table,
2251
- (0, import_validation_core9.resolveAffix)(opts),
2252
- opts?.target ?? "draft-2020-12",
2253
- !!opts?.applyDefaults,
2254
- opts?.sharedEnums ? this.analysis.enums : void 0
2255
- );
2256
- }
2257
- };
2258
- index_default4 = JsonSchemaGenerator;
2259
- }
2260
- });
2261
-
2262
- // ../generator-fastify/dist/index.js
2263
- var dist_exports5 = {};
2264
- __export(dist_exports5, {
2265
- APP_MODULE: () => APP_MODULE3,
2266
- FastifyGenerator: () => FastifyGenerator,
2267
- default: () => index_default5
2268
- });
2269
- function keyColumns5(table) {
2270
- const names = table.primaryKey?.columns ?? [];
2271
- if (!names.length) return null;
2272
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
2273
- if (cols.some((c) => !c)) return null;
2274
- return cols;
2275
- }
2276
- function segmentSchema(column) {
2277
- if (column.enumValues && column.enumValues.length) return { enum: [...column.enumValues] };
2278
- switch (column.tsType) {
2279
- case "number":
2280
- return { type: "string", pattern: NUMERIC_SEGMENT_PATTERN };
2281
- case "bigint":
2282
- return { type: "string", pattern: BIGINT_SEGMENT_PATTERN };
2283
- case "Date":
2284
- return { type: "string", format: "date-time" };
2285
- default:
2286
- return { type: "string" };
2287
- }
2288
- }
2289
- function paramsSchema(cols) {
2290
- return {
2291
- type: "object",
2292
- properties: Object.fromEntries(cols.map((c) => [c.name, segmentSchema(c)])),
2293
- required: cols.map((c) => c.name),
2294
- additionalProperties: false
2295
- };
2296
- }
2297
- function adaptForFastify(schema) {
2298
- const { $schema: _dialect, $id: _id, ...rest } = walk(schema);
2299
- return rest;
2300
- }
2301
- function walk(value) {
2302
- if (Array.isArray(value)) return value.map(walk);
2303
- if (value && typeof value === "object") {
2304
- const out = {};
2305
- for (const [k, v] of Object.entries(value)) {
2306
- if (k === "prefixItems") continue;
2307
- out[k] = walk(v);
2308
- }
2309
- const prefix = value.prefixItems;
2310
- if (Array.isArray(prefix) && prefix.length) out.items = walk(prefix[0]);
2311
- return out;
2312
- }
2313
- return value;
2314
- }
2315
- function rowFieldType(column) {
2316
- if (column.enumValues && column.enumValues.length) {
2317
- return column.enumValues.map((v) => `'${v.replace(/'/g, "\\'")}'`).join(" | ");
2318
- }
2319
- const s = column.shape;
2320
- if (s) {
2321
- switch (s.kind) {
2322
- case "tuple":
2323
- return `[${Array.from({ length: s.length }, () => "number").join(", ")}]`;
2324
- case "numberObject":
2325
- return `{ ${s.fields.map((f) => `${f}: number`).join("; ")} }`;
2326
- case "numberVector":
2327
- return "number[]";
2328
- default:
2329
- return "unknown";
2330
- }
2331
- }
2332
- switch (column.tsType) {
2333
- case "number":
2334
- return "number";
2335
- case "string":
2336
- return "string";
2337
- case "boolean":
2338
- return "boolean";
2339
- case "Date":
2340
- return "Date";
2341
- case "bigint":
2342
- return "bigint";
2343
- default:
2344
- return "unknown";
2345
- }
2346
- }
2347
- function objectKey4(name) {
2348
- return isIdent4(name) ? name : JSON.stringify(name);
2349
- }
2350
- function toCase4(s, c) {
2351
- if (!c) return s;
2352
- const parts = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/);
2353
- if (c === "camel") {
2354
- return parts.map(
2355
- (p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()
2356
- ).join("");
2357
- }
2358
- if (c === "kebab") return parts.map((p) => p.toLowerCase()).join("-");
2359
- if (c === "snake") return parts.map((p) => p.toLowerCase()).join("_");
2360
- return s;
2361
- }
2362
- function routesExportName3(table, naming) {
2363
- const base = `${table.tsName}${naming?.routerSuffix ?? "Routes"}`;
2364
- const c = naming?.procedureCase;
2365
- return toCase4(base, c === "kebab" ? "camel" : c);
2366
- }
2367
- function mountPath3(table, naming) {
2368
- return `/${toCase4(table.tsName, naming?.procedureCase)}`;
2369
- }
2370
- function buildHeader5(h) {
2371
- if (h && h.enabled === false) return "";
2372
- const text = h?.text?.trim();
2373
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
2374
- "// Generated by DRZL (@drzl/*)",
2375
- "// Generated output is granted to you under your project's license.",
2376
- "// You may use, copy, modify, and distribute without attribution."
2377
- ];
2378
- return lines.join("\n") + "\n\n";
2379
- }
2380
- function renderRoutes3(table, opts) {
2381
- const insertName = `Insert${table.tsName}Schema`;
2382
- const updateName = `Update${table.tsName}Schema`;
2383
- const selectName = `Select${table.tsName}Schema`;
2384
- const paramsName = `${cap4(table.tsName)}ParamsSchema`;
2385
- const rowType = `Select${table.tsName}Row`;
2386
- const writable = !table.readOnly;
2387
- const key = keyColumns5(table);
2388
- const routes = [];
2389
- const notImplemented = (what) => `throw new Error('Not implemented: ${what} ${table.tsName}.');`;
2390
- const paramHint = `// Fastify has already validated req.params against ${paramsName}; numeric key segments stay strings here.`;
2391
- routes.push({
2392
- name: "list",
2393
- method: "get",
2394
- path: "/",
2395
- schema: [`response: { 200: { type: 'array', items: ${selectName} } }`],
2396
- replyType: `${rowType}[]`,
2397
- // The stub states its contract twice over: the annotated local is what a reader sees, and the
2398
- // Reply generic is what Fastify's own types hold the handler to. The response schema is what
2399
- // the serializer runs; nothing infers a client from any of them, which the docs say plainly.
2400
- body: [`const rows: ${rowType}[] = [];`, "return rows;"]
2401
- });
2402
- if (key) {
2403
- const keyPath = "/" + key.map((c) => `:${c.name}`).join("/");
2404
- routes.push({
2405
- name: "byId",
2406
- method: "get",
2407
- path: keyPath,
2408
- schema: [
2409
- `params: ${paramsName}`,
2410
- `response: { 200: ${selectName}, 404: ${NOT_FOUND_SCHEMA} }`
2411
- ],
2412
- replyType: `${rowType} | { message: string }`,
2413
- body: [
2414
- paramHint,
2415
- `const row: ${rowType} | null = null;`,
2416
- "if (row !== null) return row;",
2417
- `return reply.code(404).send({ message: ${lit3(`${table.tsName} row not found`)} });`
2418
- ]
2419
- });
2420
- if (writable) {
2421
- routes.push({
2422
- name: "update",
2423
- method: "patch",
2424
- path: keyPath,
2425
- schema: [
2426
- `params: ${paramsName}`,
2427
- `body: ${updateName}`,
2428
- `response: { 200: ${selectName} }`
2429
- ],
2430
- replyType: rowType,
2431
- body: [notImplemented("update")]
2432
- });
2433
- routes.push({
2434
- name: "delete",
2435
- method: "delete",
2436
- path: keyPath,
2437
- schema: [`params: ${paramsName}`, `response: { 200: { type: 'boolean' } }`],
2438
- replyType: "boolean",
2439
- body: [paramHint, "return true;"]
2440
- });
2441
- }
2442
- }
2443
- if (writable) {
2444
- routes.push({
2445
- name: "create",
2446
- method: "post",
2447
- path: "/",
2448
- schema: [`body: ${insertName}`, `response: { 200: ${selectName} }`],
2449
- replyType: rowType,
2450
- body: [notImplemented("create")]
2451
- });
2452
- }
2453
- if (opts.includeRelations) {
2454
- routes.push(...relationRoutes3(table, rowType, selectName, opts));
2455
- }
2456
- const order = ["list", "byId", "create", "update", "delete"];
2457
- const rank = (n) => order.indexOf(n) === -1 ? order.length : order.indexOf(n);
2458
- routes.sort((a, b) => rank(a.name) - rank(b.name));
2459
- const exportName = routesExportName3(table, opts.naming);
2460
- const statements = routes.map((r) => {
2461
- const reads = (what) => r.body.some((line) => !line.startsWith("//") && what.test(line));
2462
- const params = reads(/\breply\./) ? reads(/\breq\./) ? "(req, reply)" : "(_req, reply)" : reads(/\breq\./) ? "(req)" : "()";
2463
- return [
2464
- ` app.${r.method}<{ Reply: ${r.replyType} }>(${lit3(r.path)}, {`,
2465
- ` schema: { ${r.schema.join(", ")} },`,
2466
- ` }, async ${params} => {`,
2467
- ...r.body.map((line) => ` ${line}`),
2468
- ` });`
2469
- ].join("\n");
2470
- }).join("\n\n");
2471
- const plugin = `export const ${exportName}: FastifyPluginAsync = async (app) => {
2472
- ${statements}
2473
- };
2474
- `;
2475
- const schemas = tableSchemas(table);
2476
- const declared = [];
2477
- const emit = (name, schema) => `export const ${name} = ${JSON.stringify(adaptForFastify(schema), null, 2)} as const;`;
2478
- if (writable) {
2479
- declared.push(emit(insertName, schemas.insert));
2480
- declared.push(emit(updateName, schemas.update));
2481
- }
2482
- declared.push(emit(selectName, schemas.select));
2483
- if (key) {
2484
- declared.push(emit(paramsName, paramsSchema(key)));
2485
- }
2486
- const rowFields = (0, import_validation_core12.selectColumns)(table).map((c) => ` ${objectKey4(c.name)}: ${rowFieldType(c)}${c.nullable ? " | null" : ""};`).join("\n");
2487
- declared.push(`export interface ${rowType} {
2488
- ${rowFields}
2489
- }`);
2490
- const wide = (0, import_validation_core12.selectColumns)(table).filter((c) => rowFieldType(c) === "unknown").map((c) => c.name);
2491
- const wideNote = wide.length ? `// No precise type for ${wide.length === 1 ? "this column" : "these columns"}: ${wide.join(", ")}.
2492
- // DRZL could not derive one from the schema, so these routes carry it as unknown and its
2493
- // schema constrains only what the builder could state.
2494
- ` : "";
2495
- return `// Generated by @drzl/generator-fastify
2496
- // Routes for table: ${table.name}
2497
- ${wideNote}import type { FastifyPluginAsync } from 'fastify';
2498
-
2499
- ${declared.join("\n\n")}
2500
-
2501
- ${plugin}`;
2502
- }
2503
- function relationRoutes3(table, rowType, selectName, opts) {
2504
- const out = [];
2505
- const taken = /* @__PURE__ */ new Set();
2506
- for (const fk of table.foreignKeys ?? []) {
2507
- if (fk.columns.length !== 1) continue;
2508
- const colName = fk.columns[0];
2509
- const column = table.columns.find((c) => c.name === colName);
2510
- if (!column) continue;
2511
- const segment = toCase4(`by-${colName}`, opts.naming?.procedureCase ?? "kebab");
2512
- if (taken.has(segment)) continue;
2513
- taken.add(segment);
2514
- const params = JSON.stringify(paramsSchema([column]));
2515
- out.push({
2516
- name: `listBy${cap4(colName)}`,
2517
- method: "get",
2518
- path: `/${segment}/:${colName}`,
2519
- schema: [`params: ${params}`, `response: { 200: { type: 'array', items: ${selectName} } }`],
2520
- replyType: `${rowType}[]`,
2521
- body: [`const rows: ${rowType}[] = [];`, "return rows;"]
2522
- });
2523
- }
2524
- return out;
2525
- }
2526
- function renderBarrel4(modules, ctx, path9, opts) {
2527
- if (!modules.length) {
2528
- return `// Generated by @drzl/generator-fastify
2529
- // No tables detected in analysis. Add tables to your schema and regenerate.
2530
- import type { FastifyPluginAsync } from 'fastify';
2531
-
2532
- export const routes: FastifyPluginAsync = async () => {};
2533
- `;
2534
- }
2535
- const entries = modules.map(({ filePath, exportName, table }) => ({
2536
- rel: (0, import_validation_core12.importSpecifier)(
2537
- "./" + path9.relative(ctx.out, filePath).replace(/\\/g, "/"),
2538
- opts.importExtension
2539
- ),
2540
- exportName,
2541
- mount: mountPath3(table, opts.naming)
2542
- }));
2543
- const imports = entries.map((e) => `import { ${e.exportName} } from '${e.rel}';`).join("\n");
2544
- const registrations = entries.map((e) => ` app.register(${e.exportName}, { prefix: ${lit3(e.mount)} });`).join("\n");
2545
- const reExports = entries.map((e) => `export * from '${e.rel}';`).join("\n");
2546
- return `// Generated by @drzl/generator-fastify
2547
- import type { FastifyPluginAsync } from 'fastify';
2548
- ${imports}
2549
-
2550
- export const routes: FastifyPluginAsync = async (app) => {
2551
- ${registrations}
2552
- };
2553
-
2554
- ${reExports}
2555
- `;
2556
- }
2557
- var import_validation_core11, import_validation_core12, APP_MODULE3, lit3, NUMERIC_SEGMENT_PATTERN, BIGINT_SEGMENT_PATTERN, NOT_FOUND_SCHEMA, cap4, isIdent4, FastifyGenerator, index_default5;
2558
- var init_dist5 = __esm({
2559
- "../generator-fastify/dist/index.js"() {
2560
- "use strict";
2561
- import_validation_core11 = require("@drzl/validation-core");
2562
- init_dist4();
2563
- import_validation_core12 = require("@drzl/validation-core");
2564
- APP_MODULE3 = "index";
2565
- lit3 = (v) => /['\\]/.test(v) ? JSON.stringify(v) : `'${v}'`;
2566
- NUMERIC_SEGMENT_PATTERN = "^-?\\d+(\\.\\d+)?$";
2567
- BIGINT_SEGMENT_PATTERN = "^-?\\d+$";
2568
- NOT_FOUND_SCHEMA = "{ type: 'object', properties: { message: { type: 'string' } }, required: ['message'], additionalProperties: false }";
2569
- cap4 = (s) => s.charAt(0).toUpperCase() + s.slice(1);
2570
- isIdent4 = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
2571
- FastifyGenerator = class {
2572
- constructor(analysis) {
2573
- this.analysis = analysis;
2574
- }
2575
- async generate(opts) {
2576
- const fs6 = (0, import_validation_core11.fileWriter)(opts.fileSink);
2577
- const path9 = await import("path");
2578
- const out = path9.resolve(process.cwd(), opts.outputDir);
2579
- const ctx = { out };
2580
- await fs6.mkdir(out, { recursive: true });
2581
- const files = [];
2582
- const write = async (filePath, content) => {
2583
- const formatted = await (0, import_validation_core12.formatCode)(
2584
- buildHeader5(opts.outputHeader) + content,
2585
- filePath,
2586
- opts.format
2587
- );
2588
- await fs6.writeFile(filePath, formatted, "utf8");
2589
- files.push(filePath);
2590
- };
2591
- const barrelPath = path9.join(out, `${APP_MODULE3}.ts`);
2592
- const modules = [];
2593
- const total = this.analysis.tables.length;
2594
- let index = 0;
2595
- for (const table of this.analysis.tables) {
2596
- const base = `${table.tsName}${opts.naming?.routerSuffix ?? ""}`;
2597
- const filePath = path9.join(out, `${toCase4(base, opts.naming?.procedureCase)}.ts`);
2598
- if (filePath === barrelPath) {
2599
- throw new Error(
2600
- `@drzl/generator-fastify: the routes for table "${table.name}" would be written to ${filePath}, which is the barrel this generator also writes. Set naming.routerSuffix to move it out of the way.`
2601
- );
2602
- }
2603
- await write(filePath, renderRoutes3(table, opts));
2604
- modules.push({ table, filePath, exportName: routesExportName3(table, opts.naming) });
2605
- index++;
2606
- opts.onProgress?.({ index, total, table: table.name, filePath });
2607
- }
2608
- await write(barrelPath, renderBarrel4(modules, ctx, path9, opts));
2609
- return { files };
2610
- }
2611
- };
2612
- index_default5 = FastifyGenerator;
2613
- }
2614
- });
2615
-
2616
- // ../generator-nestjs/dist/index.js
2617
- var dist_exports6 = {};
2618
- __export(dist_exports6, {
2619
- APP_MODULE: () => APP_MODULE4,
2620
- NestJSGenerator: () => NestJSGenerator,
2621
- VALIDATION_MODULE: () => VALIDATION_MODULE2,
2622
- default: () => index_default6
2623
- });
2624
- function keyColumns6(table) {
2625
- const names = table.primaryKey?.columns ?? [];
2626
- if (!names.length) return null;
2627
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
2628
- if (cols.some((c) => !c)) return null;
2629
- return cols;
2630
- }
2631
- function isWide4(column) {
2632
- if (column.enumValues && column.enumValues.length) return false;
2633
- if (column.shape?.kind === "tuple" || column.shape?.kind === "numberObject") return false;
2634
- return !["number", "string", "boolean", "Date", "bigint"].includes(column.tsType);
2635
- }
2636
- function baseExpr(column, d, mode) {
2637
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
2638
- switch (column.tsType) {
2639
- case "number":
2640
- return d.number;
2641
- case "string":
2642
- return d.string;
2643
- case "boolean":
2644
- return d.boolean;
2645
- case "Date":
2646
- return mode === "select" ? d.date : d.dateInput;
2647
- case "bigint":
2648
- return d.bigint;
2649
- default:
2650
- return d.unknown;
2651
- }
2652
- }
2653
- function mapExpr4(column, lib, mode) {
2654
- const d = LIBS4[lib];
2655
- let expr = baseExpr(column, d, mode);
2656
- if (column.nullable) expr = d.nullable(expr);
2657
- if (mode === "update" || mode === "insert" && column.hasDefault) expr = d.optional(expr);
2658
- return expr;
2659
- }
2660
- function objectKey5(name) {
2661
- return isIdent5(name) ? name : JSON.stringify(name);
2662
- }
2663
- function field4(column, lib, mode) {
2664
- const d = LIBS4[lib];
2665
- const expr = mapExpr4(column, lib, mode);
2666
- return `${objectKey5(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
2667
- }
2668
- function paramField3(column, lib) {
2669
- const d = LIBS4[lib];
2670
- const expr = (() => {
2671
- if (column.enumValues && column.enumValues.length) return d.enum(column.enumValues);
2672
- return d.coerce(column.tsType) ?? d.string;
2673
- })();
2674
- return `${objectKey5(column.name)}: ${d.fieldIsString ? JSON.stringify(expr) : expr}`;
2675
- }
2676
- function renderSchema4(cols, lib, mode) {
2677
- const d = LIBS4[lib];
2678
- const body = cols.map((c) => ` ${field4(c, lib, mode)},`).join("\n");
2679
- return d.object(body);
2680
- }
2681
- function fieldType(column) {
2682
- if (column.enumValues && column.enumValues.length) {
2683
- return column.enumValues.map((x) => `'${x.replace(/'/g, "\\'")}'`).join(" | ");
2684
- }
2685
- switch (column.tsType) {
2686
- case "number":
2687
- return "number";
2688
- case "string":
2689
- return "string";
2690
- case "boolean":
2691
- return "boolean";
2692
- case "Date":
2693
- return "Date";
2694
- case "bigint":
2695
- return "string";
2696
- default:
2697
- return "unknown";
2698
- }
2699
- }
2700
- function paramFieldType(column) {
2701
- if (column.enumValues && column.enumValues.length) return fieldType(column);
2702
- switch (column.tsType) {
2703
- case "number":
2704
- return "number";
2705
- case "Date":
2706
- return "Date";
2707
- default:
2708
- return "string";
2709
- }
2710
- }
2711
- function classField(column, mode) {
2712
- const key = isIdent5(column.name) ? column.name : `'${column.name.replace(/'/g, "\\'")}'`;
2713
- if (mode === "params") return ` ${key}!: ${paramFieldType(column)};`;
2714
- const optional = mode === "update" || mode === "insert" && column.hasDefault;
2715
- const type = `${fieldType(column)}${column.nullable ? " | null" : ""}`;
2716
- return optional ? ` ${key}?: ${type};` : ` ${key}!: ${type};`;
2717
- }
2718
- function toCase5(s, c) {
2719
- if (!c) return s;
2720
- const parts = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/);
2721
- if (c === "camel") {
2722
- return parts.map(
2723
- (p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()
2724
- ).join("");
2725
- }
2726
- if (c === "kebab") return parts.map((p) => p.toLowerCase()).join("-");
2727
- if (c === "snake") return parts.map((p) => p.toLowerCase()).join("_");
2728
- return s;
2729
- }
2730
- function buildHeader6(h) {
2731
- if (h && h.enabled === false) return "";
2732
- const text = h?.text?.trim();
2733
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
2734
- "// Generated by DRZL (@drzl/*)",
2735
- "// Generated output is granted to you under your project's license.",
2736
- "// You may use, copy, modify, and distribute without attribution."
2737
- ];
2738
- return lines.join("\n") + "\n\n";
2739
- }
2740
- function renderValidationModule2() {
2741
- return `// Generated by @drzl/generator-nestjs
2742
- // A validation pipe over Standard Schema v1. Bind it globally
2743
- // (app.useGlobalPipes(new SchemaValidationPipe())) or per handler with @UsePipes. A parameter
2744
- // whose class carries a static Standard Schema (every DTO in this directory) is validated and
2745
- // replaced by the parsed output; anything else, primitives and foreign DTOs alike, passes
2746
- // through untouched. On failure it throws BadRequestException with
2747
- // { error, slot, issues: [{ message, path }] }, which Nest answers as a 400.
2748
- //
2749
- // These DTOs do not use Nest's class-validator ValidationPipe and assume nothing about its
2750
- // options. If your app also binds one globally, note one measured interaction: with
2751
- // whitelist: true it strips every property of these classes before this pipe runs, because
2752
- // they carry no class-validator metadata.
2753
- import { BadRequestException, type ArgumentMetadata, type PipeTransform } from '@nestjs/common';
2754
-
2755
- interface StandardIssue {
2756
- readonly message: string;
2757
- readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }> | undefined;
2758
- }
2759
-
2760
- type StandardResult<Output> =
2761
- | { readonly value: Output; readonly issues?: undefined }
2762
- | { readonly issues: ReadonlyArray<StandardIssue> };
2763
-
2764
- export interface StandardSchema<Output = unknown> {
2765
- readonly '~standard': {
2766
- readonly version: 1;
2767
- readonly vendor: string;
2768
- readonly validate: (
2769
- value: unknown
2770
- ) => StandardResult<Output> | Promise<StandardResult<Output>>;
2771
- };
2772
- }
2773
-
2774
- interface CarriesSchema {
2775
- readonly schema: StandardSchema;
2776
- }
2777
-
2778
- function carriesSchema(metatype: unknown): metatype is CarriesSchema {
2779
- return (
2780
- typeof metatype === 'function' &&
2781
- typeof (metatype as { schema?: { '~standard'?: { validate?: unknown } } }).schema?.[
2782
- '~standard'
2783
- ]?.validate === 'function'
2784
- );
2785
- }
2786
-
2787
- export class SchemaValidationPipe implements PipeTransform {
2788
- async transform(value: unknown, metadata: ArgumentMetadata): Promise<unknown> {
2789
- const { metatype } = metadata;
2790
- if (!carriesSchema(metatype)) return value;
2791
- const result = await metatype.schema['~standard'].validate(value);
2792
- if (result.issues) {
2793
- throw new BadRequestException({
2794
- error: 'Validation failed',
2795
- slot: metadata.type,
2796
- issues: result.issues.map((issue) => ({
2797
- message: issue.message,
2798
- path: (issue.path ?? []).map((p) => (typeof p === 'object' && p !== null ? p.key : p)),
2799
- })),
2800
- });
2801
- }
2802
- return result.value;
2803
- }
2804
- }
2805
- `;
2806
- }
2807
- function renderTable(table, opts) {
2808
- const lib = opts.validation?.library ?? "zod";
2809
- const d = LIBS4[lib];
2810
- const insertName = `Insert${table.tsName}Schema`;
2811
- const updateName = `Update${table.tsName}Schema`;
2812
- const selectName = `Select${table.tsName}Schema`;
2813
- const paramsName = `${cap5(table.tsName)}ParamsSchema`;
2814
- const createDto = `Create${cap5(table.tsName)}Dto`;
2815
- const updateDto = `Update${cap5(table.tsName)}Dto`;
2816
- const paramsDto = `${cap5(table.tsName)}ParamsDto`;
2817
- const entity = `${cap5(table.tsName)}Entity`;
2818
- const writable = !table.readOnly;
2819
- const key = keyColumns6(table);
2820
- const declared = [];
2821
- const insertCols = (0, import_validation_core14.insertColumns)(table);
2822
- const updateCols = (0, import_validation_core14.updateColumns)(table);
2823
- const selectCols = (0, import_validation_core14.selectColumns)(table);
2824
- if (writable) {
2825
- declared.push(`export const ${insertName} = ${renderSchema4(insertCols, lib, "insert")};`);
2826
- declared.push(`export const ${updateName} = ${renderSchema4(updateCols, lib, "update")};`);
2827
- }
2828
- declared.push(`export const ${selectName} = ${renderSchema4(selectCols, lib, "select")};`);
2829
- if (key) {
2830
- declared.push(
2831
- `export const ${paramsName} = ${d.objectInline(key.map((c) => paramField3(c, lib)).join(", "))};`
2832
- );
2833
- }
2834
- const dtoClass = (name, doc, cols, mode, schema) => {
2835
- const fields = cols.map((c) => classField(c, mode)).join("\n");
2836
- return `/**
2837
- * ${doc}
2838
- */
2839
- export class ${name} {
2840
- ${fields}
2841
-
2842
- static readonly schema: StandardSchema<${name}> = ${schema};
2843
- }`;
2844
- };
2845
- if (writable) {
2846
- declared.push(
2847
- dtoClass(
2848
- createDto,
2849
- `The insert shape of ${table.name}. Fields state what the pipe hands your controller; a nullable column with no default is required, null spelled out.`,
2850
- insertCols,
2851
- "insert",
2852
- insertName
2853
- )
2854
- );
2855
- declared.push(
2856
- dtoClass(
2857
- updateDto,
2858
- `The update shape of ${table.name}: every field optional, primary key excluded.`,
2859
- updateCols,
2860
- "update",
2861
- updateName
2862
- )
2863
- );
2864
- }
2865
- if (key) {
2866
- declared.push(
2867
- dtoClass(
2868
- paramsDto,
2869
- `The path parameters addressing one ${table.name} row, parsed strictly from their string segments.`,
2870
- key,
2871
- "params",
2872
- paramsName
2873
- )
2874
- );
2875
- }
2876
- declared.push(
2877
- dtoClass(
2878
- entity,
2879
- `One ${table.name} row, at its select shape.`,
2880
- selectCols,
2881
- "select",
2882
- selectName
2883
- )
2884
- );
2885
- const decided = declared.join("\n\n");
2886
- const imports = [];
2887
- imports.push(LIB_IMPORTS4[lib]);
2888
- const spec = (0, import_validation_core14.importSpecifier)(`./${VALIDATION_MODULE2}.ts`, opts.importExtension);
2889
- imports.push(`import type { StandardSchema } from '${spec}';`);
2890
- const wide = table.columns.filter(isWide4).map((c) => c.name);
2891
- const wideNote = wide.length ? `// No validated type for ${wide.length === 1 ? "this column" : "these columns"}: ${wide.join(", ")}.
2892
- // DRZL could not derive one from the schema, so these DTOs accept any value there.
2893
- ` : "";
2894
- return `// Generated by @drzl/generator-nestjs
2895
- // DTOs for table: ${table.name}
2896
- ${wideNote}${imports.join("\n")}
2897
-
2898
- ${decided}
2899
- `;
2900
- }
2901
- function renderBarrel5(modules, ctx, path9, opts) {
2902
- const validationSpec = (0, import_validation_core14.importSpecifier)(`./${VALIDATION_MODULE2}.ts`, opts.importExtension);
2903
- if (!modules.length) {
2904
- return `// Generated by @drzl/generator-nestjs
2905
- // No tables detected in analysis. Add tables to your schema and regenerate.
2906
- export * from '${validationSpec}';
2907
- `;
2908
- }
2909
- const reExports = modules.map(({ filePath }) => {
2910
- const rel = (0, import_validation_core14.importSpecifier)(
2911
- "./" + path9.relative(ctx.out, filePath).replace(/\\/g, "/"),
2912
- opts.importExtension
2913
- );
2914
- return `export * from '${rel}';`;
2915
- }).join("\n");
2916
- return `// Generated by @drzl/generator-nestjs
2917
- ${reExports}
2918
- export * from '${validationSpec}';
2919
- `;
2920
- }
2921
- var import_validation_core13, import_validation_core14, APP_MODULE4, VALIDATION_MODULE2, q4, NUMERIC_SEGMENT3, BIGINT_DIGITS, LIB_IMPORTS4, LIBS4, cap5, isIdent5, NestJSGenerator, index_default6;
2922
- var init_dist6 = __esm({
2923
- "../generator-nestjs/dist/index.js"() {
2924
- "use strict";
2925
- import_validation_core13 = require("@drzl/validation-core");
2926
- import_validation_core14 = require("@drzl/validation-core");
2927
- APP_MODULE4 = "index";
2928
- VALIDATION_MODULE2 = "validation";
2929
- q4 = (v) => JSON.stringify(v);
2930
- NUMERIC_SEGMENT3 = String.raw`/^-?\d+(\.\d+)?$/`;
2931
- BIGINT_DIGITS = String.raw`/^-?\d+$/`;
2932
- LIB_IMPORTS4 = {
2933
- zod: "import { z } from 'zod';",
2934
- valibot: "import * as v from 'valibot';",
2935
- arktype: "import { type } from 'arktype';"
2936
- };
2937
- LIBS4 = {
2938
- zod: {
2939
- number: "z.number()",
2940
- string: "z.string()",
2941
- boolean: "z.boolean()",
2942
- date: "z.date()",
2943
- dateInput: "z.iso.datetime().transform((s) => new Date(s))",
2944
- bigint: `z.string().regex(${BIGINT_DIGITS})`,
2945
- unknown: "z.unknown()",
2946
- enum: (vals) => `z.enum([${vals.map(q4).join(", ")}] as const)`,
2947
- nullable: (b) => `${b}.nullable()`,
2948
- optional: (b) => `${b}.optional()`,
2949
- object: (body) => `z.object({
2950
- ${body}
2951
- })`,
2952
- objectInline: (body) => `z.object({ ${body} })`,
2953
- coerce: (t) => t === "number" ? `z.string().regex(${NUMERIC_SEGMENT3}).transform(Number)` : t === "Date" ? "z.iso.datetime().transform((s) => new Date(s))" : t === "bigint" ? `z.string().regex(${BIGINT_DIGITS})` : null
2954
- },
2955
- valibot: {
2956
- number: "v.number()",
2957
- string: "v.string()",
2958
- boolean: "v.boolean()",
2959
- date: "v.date()",
2960
- dateInput: "v.pipe(v.string(), v.isoTimestamp(), v.transform((s) => new Date(s)))",
2961
- bigint: `v.pipe(v.string(), v.regex(${BIGINT_DIGITS}))`,
2962
- unknown: "v.unknown()",
2963
- enum: (vals) => `v.picklist([${vals.map(q4).join(", ")}] as const)`,
2964
- nullable: (b) => `v.nullable(${b})`,
2965
- optional: (b) => `v.optional(${b})`,
2966
- object: (body) => `v.object({
2967
- ${body}
2968
- })`,
2969
- objectInline: (body) => `v.object({ ${body} })`,
2970
- // A valibot pipe step sees the previous step's *output*, so the check has to happen while
2971
- // the value is still the string: after a `v.transform(Number)` there is no string left.
2972
- coerce: (t) => t === "number" ? `v.pipe(v.string(), v.regex(${NUMERIC_SEGMENT3}), v.transform(Number))` : t === "Date" ? "v.pipe(v.string(), v.isoTimestamp(), v.transform((s) => new Date(s)))" : t === "bigint" ? `v.pipe(v.string(), v.regex(${BIGINT_DIGITS}))` : null
2973
- },
2974
- arktype: {
2975
- number: "number",
2976
- string: "string",
2977
- boolean: "boolean",
2978
- date: "Date",
2979
- dateInput: "string.date.iso.parse",
2980
- bigint: BIGINT_DIGITS,
2981
- unknown: "unknown",
2982
- // The surrounding encode adds the quotes, so the union is built with the inner quoting
2983
- // ArkType expects.
2984
- enum: (vals) => vals.map((x) => `'${x.replace(/'/g, "\\'")}'`).join(" | "),
2985
- nullable: (b) => `(${b} | null)`,
2986
- optional: (b) => `${b}?`,
2987
- // `.onUndeclaredKey('delete')` because ArkType's default keeps undeclared keys (measured on
2988
- // 2.2.3), where zod and valibot strip them. A DTO's whole point is that the controller
2989
- // receives the declared shape, so the three libraries are aligned on the strict side.
2990
- object: (body) => `type({
2991
- ${body}
2992
- }).onUndeclaredKey('delete')`,
2993
- objectInline: (body) => `type({ ${body} }).onUndeclaredKey('delete')`,
2994
- fieldIsString: true,
2995
- // ArkType ships these as keywords, and they are morphs: the declared output type is the
2996
- // parsed one. `string.date.iso.parse` and not `string.date.parse`, which accepts `"1"` as
2997
- // the year 2001.
2998
- coerce: (t) => t === "number" ? "string.numeric.parse" : t === "Date" ? "string.date.iso.parse" : t === "bigint" ? BIGINT_DIGITS : null
2999
- }
3000
- };
3001
- cap5 = (s) => s.charAt(0).toUpperCase() + s.slice(1);
3002
- isIdent5 = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
3003
- NestJSGenerator = class {
3004
- constructor(analysis) {
3005
- this.analysis = analysis;
3006
- }
3007
- async generate(opts) {
3008
- const fs6 = (0, import_validation_core13.fileWriter)(opts.fileSink);
3009
- const path9 = await import("path");
3010
- const out = path9.resolve(process.cwd(), opts.outputDir);
3011
- const ctx = { out };
3012
- await fs6.mkdir(out, { recursive: true });
3013
- const files = [];
3014
- const write = async (filePath, content) => {
3015
- const formatted = await (0, import_validation_core14.formatCode)(
3016
- buildHeader6(opts.outputHeader) + content,
3017
- filePath,
3018
- opts.format
3019
- );
3020
- await fs6.writeFile(filePath, formatted, "utf8");
3021
- files.push(filePath);
3022
- };
3023
- const barrelPath = path9.join(out, `${APP_MODULE4}.ts`);
3024
- const validationPath = path9.join(out, `${VALIDATION_MODULE2}.ts`);
3025
- const modules = [];
3026
- const total = this.analysis.tables.length;
3027
- let index = 0;
3028
- for (const table of this.analysis.tables) {
3029
- const base = `${table.tsName}${opts.naming?.routerSuffix ?? ""}`;
3030
- const filePath = path9.join(out, `${toCase5(base, opts.naming?.procedureCase)}.ts`);
3031
- if (filePath === barrelPath || filePath === validationPath) {
3032
- const which = filePath === barrelPath ? "the barrel" : "the validation pipe module";
3033
- throw new Error(
3034
- `@drzl/generator-nestjs: the DTOs for table "${table.name}" would be written to ${filePath}, which is ${which} this generator also writes. Set naming.routerSuffix to move it out of the way.`
3035
- );
3036
- }
3037
- await write(filePath, renderTable(table, opts));
3038
- modules.push({ filePath });
3039
- index++;
3040
- opts.onProgress?.({ index, total, table: table.name, filePath });
3041
- }
3042
- await write(validationPath, renderValidationModule2());
3043
- await write(barrelPath, renderBarrel5(modules, ctx, path9, opts));
3044
- return { files };
3045
- }
3046
- };
3047
- index_default6 = NestJSGenerator;
3048
- }
3049
- });
3050
-
3051
- // ../generator-graphql/dist/index.js
3052
- var dist_exports7 = {};
3053
- __export(dist_exports7, {
3054
- APP_MODULE: () => APP_MODULE5,
3055
- GraphQLGenerator: () => GraphQLGenerator,
3056
- SCALARS_MODULE: () => SCALARS_MODULE,
3057
- default: () => index_default7
3058
- });
3059
- function gqlIdent(s) {
3060
- const cleaned = s.replace(/[^_0-9A-Za-z]+/g, "_");
3061
- return /^[0-9]/.test(cleaned) ? `_${cleaned}` : cleaned;
3062
- }
3063
- function tpl(s) {
3064
- return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
3065
- }
3066
- function keyColumns7(table) {
3067
- const names = table.primaryKey?.columns ?? [];
3068
- if (!names.length) return null;
3069
- const cols = names.map((n) => table.columns.find((c) => c.name === n));
3070
- if (cols.some((c) => !c)) return null;
3071
- return cols;
3072
- }
3073
- function fitsInt32(c) {
3074
- if (c.min === void 0 || c.max === void 0) return false;
3075
- try {
3076
- return BigInt(c.min) >= INT32_MIN && BigInt(c.max) <= INT32_MAX;
3077
- } catch {
3078
- return false;
3079
- }
3080
- }
3081
- function planEnum(typeName3, values) {
3082
- const members = values.map((value) => {
3083
- const verbatimOk = GRAPHQL_NAME.test(value) && !RESERVED_ENUM_VALUES.has(value) && !value.startsWith("__");
3084
- if (verbatimOk) return { name: value, value, renamed: false };
3085
- let name = gqlIdent(value.toUpperCase());
3086
- name = name.replace(/^_+/, "_");
3087
- if (!GRAPHQL_NAME.test(name) || RESERVED_ENUM_VALUES.has(name) || name.startsWith("__")) {
3088
- return { name: "", value, renamed: true };
3089
- }
3090
- return { name, value, renamed: true };
3091
- });
3092
- if (members.some((m) => !m.name)) return null;
3093
- const seen = /* @__PURE__ */ new Set();
3094
- for (const m of members) {
3095
- if (seen.has(m.name)) return null;
3096
- seen.add(m.name);
3097
- }
3098
- return { typeName: typeName3, members };
3099
- }
3100
- function mapped(sdl, rowTs, inputTs = rowTs, scalars = []) {
3101
- return { sdl, rowTs, inputTs, scalars };
3102
- }
3103
- function planColumn(table, c) {
3104
- const field5 = GRAPHQL_NAME.test(c.name) ? c.name : gqlIdent(c.name);
3105
- const base = (() => {
3106
- if (c.enumValues && c.enumValues.length) {
3107
- const typeName3 = `${cap6(gqlIdent(table.tsName))}${cap6(gqlIdent(c.name))}Enum`;
3108
- const plan = planEnum(typeName3, c.enumValues);
3109
- if (!plan) {
3110
- return {
3111
- ...mapped("String", "string"),
3112
- note: `Column "${c.name}": the enum values ${c.enumValues.join(", ")} cannot all be spelled as distinct GraphQL enum members, so the column is exposed as String carrying the database values verbatim.`
3113
- };
3114
- }
3115
- const union = c.enumValues.map(q5).join(" | ");
3116
- return { ...mapped(plan.typeName, union), enumPlan: plan };
3117
- }
3118
- switch (c.shape?.kind) {
3119
- case "tuple":
3120
- return mapped("[Float!]", "number[]");
3121
- case "numberObject":
3122
- return mapped("JSON", "unknown", "unknown", ["JSON"]);
3123
- case "buffer":
3124
- return {
3125
- ...mapped("JSON", "unknown", "unknown", ["JSON"]),
3126
- note: `Column "${c.name}" is binary; GraphQL has no binary type, so it rides the JSON scalar and your resolver picks an encoding.`
3127
- };
3128
- case "json":
3129
- return mapped("JSON", "unknown", "unknown", ["JSON"]);
3130
- case "bitstring":
3131
- case "byteString":
3132
- return mapped("String", "string");
3133
- default:
3134
- break;
3135
- }
3136
- if (c.dbType === "VECTOR") return mapped("[Float!]", "number[]");
3137
- switch (c.tsType) {
3138
- case "number":
3139
- return mapped((0, import_validation_core16.isIntegerColumn)(c) && fitsInt32(c) ? "Int" : "Float", "number");
3140
- case "string":
3141
- return mapped(c.format === "uuid" ? "ID" : "String", "string");
3142
- case "boolean":
3143
- return mapped("Boolean", "boolean");
3144
- case "Date":
3145
- return mapped("DateTime", "Date", "Date", ["DateTime"]);
3146
- case "bigint":
3147
- return mapped("BigInt", "string | bigint", "string", ["BigInt"]);
3148
- default:
3149
- return {
3150
- ...mapped("JSON", "unknown", "unknown", ["JSON"]),
3151
- note: `No GraphQL type for column "${c.name}": DRZL could not derive one from the schema, so it is exposed through the JSON scalar and accepts any value there.`
3152
- };
3153
- }
3154
- })();
3155
- let { sdl, rowTs, inputTs } = base;
3156
- const dims = c.arrayDimensions ?? 0;
3157
- for (let i = 0; i < dims; i++) {
3158
- sdl = `[${sdl}]`;
3159
- rowTs = `(${rowTs} | null)[]`;
3160
- inputTs = `(${inputTs} | null)[]`;
3161
- }
3162
- return {
3163
- column: c,
3164
- field: field5,
3165
- renamed: field5 !== c.name,
3166
- sdl,
3167
- rowTs,
3168
- inputTs,
3169
- scalars: base.scalars,
3170
- enumPlan: base.enumPlan,
3171
- note: base.note
3172
- };
3173
- }
3174
- function planTable(table) {
3175
- const typeName3 = cap6(gqlIdent(table.tsName));
3176
- const fieldBase = gqlIdent(table.tsName);
3177
- const select = (0, import_validation_core16.selectColumns)(table).map((c) => planColumn(table, c));
3178
- const writable = !table.readOnly;
3179
- const insert = writable ? (0, import_validation_core16.insertColumns)(table).map((c) => planColumn(table, c)) : [];
3180
- const update = writable ? (0, import_validation_core16.updateColumns)(table).map((c) => planColumn(table, c)) : [];
3181
- const keyCols = keyColumns7(table);
3182
- const key = keyCols ? keyCols.map((c) => planColumn(table, c)) : null;
3183
- const scalars = /* @__PURE__ */ new Set();
3184
- const enums = [];
3185
- const notes = [];
3186
- for (const p of select) {
3187
- for (const s of p.scalars) scalars.add(s);
3188
- if (p.enumPlan) enums.push(p.enumPlan);
3189
- if (p.note) notes.push(p.note);
3190
- }
3191
- const createInput = `Create${typeName3}Input`;
3192
- const updateInput = `Update${typeName3}Input`;
3193
- const hasCreate = writable && insert.length > 0;
3194
- const hasUpdate = writable && key !== null && update.length > 0;
3195
- const keyArgsSdl = key ? key.map((p) => `${p.field}: ${p.sdl}!`).join(", ") : "";
3196
- const keyArgsTs = key ? key.map((p) => `${p.field}: ${p.inputTs}`).join("; ") : "";
3197
- const ops = [];
3198
- ops.push({
3199
- parent: "Query",
3200
- name: fieldBase,
3201
- argsSdl: "",
3202
- resultSdl: `[${typeName3}!]!`,
3203
- argsTs: "Record<string, never>",
3204
- resultTs: `${typeName3}[]`
3205
- });
3206
- if (key) {
3207
- ops.push({
3208
- parent: "Query",
3209
- name: `${fieldBase}ById`,
3210
- argsSdl: keyArgsSdl,
3211
- resultSdl: typeName3,
3212
- argsTs: `{ ${keyArgsTs} }`,
3213
- resultTs: `${typeName3} | null`
3214
- });
3215
- }
3216
- if (hasCreate) {
3217
- ops.push({
3218
- parent: "Mutation",
3219
- name: `create${typeName3}`,
3220
- argsSdl: `input: ${createInput}!`,
3221
- resultSdl: `${typeName3}!`,
3222
- argsTs: `{ input: ${createInput} }`,
3223
- resultTs: typeName3
3224
- });
3225
- }
3226
- if (hasUpdate) {
3227
- ops.push({
3228
- parent: "Mutation",
3229
- name: `update${typeName3}`,
3230
- argsSdl: `${keyArgsSdl}, input: ${updateInput}!`,
3231
- resultSdl: `${typeName3}!`,
3232
- argsTs: `{ ${keyArgsTs}; input: ${updateInput} }`,
3233
- resultTs: typeName3
3234
- });
3235
- }
3236
- if (writable && key) {
3237
- ops.push({
3238
- parent: "Mutation",
3239
- name: `delete${typeName3}`,
3240
- argsSdl: keyArgsSdl,
3241
- resultSdl: "Boolean!",
3242
- argsTs: `{ ${keyArgsTs} }`,
3243
- resultTs: "boolean"
3244
- });
3245
- }
3246
- const renamedSelect = select.filter((p) => p.renamed);
3247
- for (const p of renamedSelect) {
3248
- notes.push(
3249
- `Column "${p.column.name}" is not a valid GraphQL field name, so it is exposed as "${p.field}": output is mapped back by the emitted field resolver, and on inputs the value arrives under "${p.field}" for your resolver to write back.`
3250
- );
3251
- }
3252
- return {
3253
- table,
3254
- typeName: typeName3,
3255
- createInput,
3256
- updateInput,
3257
- select,
3258
- insert,
3259
- update,
3260
- key,
3261
- writable,
3262
- hasCreate,
3263
- hasUpdate,
3264
- ops,
3265
- scalars,
3266
- enums,
3267
- notes,
3268
- renamedSelect
3269
- };
3270
- }
3271
- function renderEnumSdl(plan) {
3272
- const lines = [`enum ${plan.typeName} {`];
3273
- for (const m of plan.members) {
3274
- if (m.renamed) lines.push(` ${JSON.stringify(`Database value: ${m.value}`)}`);
3275
- lines.push(` ${m.name}`);
3276
- }
3277
- lines.push("}");
3278
- return lines.join("\n");
3279
- }
3280
- function renderTypeSdl(plan) {
3281
- const parts = [];
3282
- for (const e of plan.enums) parts.push(renderEnumSdl(e));
3283
- parts.push(
3284
- [
3285
- `type ${plan.typeName} {`,
3286
- ...plan.select.map((p) => ` ${p.field}: ${p.sdl}${p.column.nullable ? "" : "!"}`),
3287
- "}"
3288
- ].join("\n")
3289
- );
3290
- if (plan.hasCreate) {
3291
- parts.push(
3292
- [
3293
- `input ${plan.createInput} {`,
3294
- ...plan.insert.map((p) => {
3295
- const required = !p.column.nullable && !p.column.hasDefault;
3296
- return ` ${p.field}: ${p.sdl}${required ? "!" : ""}`;
3297
- }),
3298
- "}"
3299
- ].join("\n")
3300
- );
3301
- }
3302
- if (plan.hasUpdate) {
3303
- parts.push(
3304
- [
3305
- `input ${plan.updateInput} {`,
3306
- ...plan.update.map((p) => ` ${p.field}: ${p.sdl}`),
3307
- "}"
3308
- ].join("\n")
3309
- );
3310
- }
3311
- return parts.join("\n\n");
3312
- }
3313
- function rowField(p) {
3314
- const key = isIdent6(p.column.name) ? p.column.name : q5(p.column.name);
3315
- return ` ${key}: ${p.rowTs}${p.column.nullable ? " | null" : ""};`;
3316
- }
3317
- function inputField(p, mode) {
3318
- const required = mode === "insert" && !p.column.nullable && !p.column.hasDefault;
3319
- return required ? ` ${p.field}: ${p.inputTs};` : ` ${p.field}?: ${p.inputTs} | null;`;
3320
- }
3321
- function renderResolvers(plan) {
3322
- const lines = ["{"];
3323
- const byParent = (parent) => plan.ops.filter((o) => o.parent === parent);
3324
- const renderOps = (parent) => {
3325
- lines.push(` ${parent}: {`);
3326
- for (const op of byParent(parent)) {
3327
- lines.push(
3328
- ` ${op.name}: (_parent: unknown, _args: ${op.argsTs}): ${op.resultTs} => {`,
3329
- ` ${stubBody(parent, op.name)}`,
3330
- ` },`
3331
- );
3332
- }
3333
- lines.push(" },");
3334
- };
3335
- renderOps("Query");
3336
- if (byParent("Mutation").length) renderOps("Mutation");
3337
- for (const e of plan.enums) {
3338
- const mapped2 = e.members.filter((m) => m.renamed);
3339
- if (!mapped2.length) continue;
3340
- lines.push(
3341
- ` ${e.typeName}: { ${mapped2.map((m) => `${m.name}: ${q5(m.value)}`).join(", ")} },`
3342
- );
3343
- }
3344
- if (plan.renamedSelect.length) {
3345
- lines.push(` ${plan.typeName}: {`);
3346
- for (const p of plan.renamedSelect) {
3347
- const type = `${p.rowTs}${p.column.nullable ? " | null" : ""}`;
3348
- lines.push(` ${p.field}: (parent: ${plan.typeName}): ${type} => parent[${q5(p.column.name)}],`);
3349
- }
3350
- lines.push(" },");
3351
- }
3352
- lines.push("}");
3353
- return lines.join("\n");
3354
- }
3355
- function renderTable2(plan) {
3356
- const t = plan.table;
3357
- const declared = [];
3358
- declared.push(
3359
- `/** One ${t.name} row, as your resolvers return it: database values, database spellings. */`,
3360
- `export interface ${plan.typeName} {`,
3361
- ...plan.select.map(rowField),
3362
- "}"
3363
- );
3364
- if (plan.hasCreate) {
3365
- declared.push(
3366
- "",
3367
- `/** The create${plan.typeName} input, as GraphQL hands it to your resolver. */`,
3368
- `export interface ${plan.createInput} {`,
3369
- ...plan.insert.map((p) => inputField(p, "insert")),
3370
- "}"
3371
- );
3372
- }
3373
- if (plan.hasUpdate) {
3374
- declared.push(
3375
- "",
3376
- `/** The update${plan.typeName} patch: every field optional, primary key excluded. */`,
3377
- `export interface ${plan.updateInput} {`,
3378
- ...plan.update.map((p) => inputField(p, "update")),
3379
- "}"
3380
- );
3381
- }
3382
- const tsName = gqlIdent(t.tsName);
3383
- const notes = plan.notes.length ? plan.notes.map((n) => `// ${n.replace(/\n/g, " ")}`).join("\n") + "\n" : "";
3384
- return `// Generated by @drzl/generator-graphql
3385
- // GraphQL SDL and resolver stubs for table: ${t.name}
3386
- ${notes}${declared.join("\n")}
3387
-
3388
- /** The SDL for this table's types. The Query and Mutation fields live in the barrel. */
3389
- export const ${tsName}TypeDefs = \`${tpl(renderTypeSdl(plan))}\`;
3390
-
3391
- /** Stubs that throw until replaced, plus the enum value maps and field resolvers the schema needs. */
3392
- export const ${tsName}Resolvers = ${renderResolvers(plan)};
3393
- `;
3394
- }
3395
- function renderScalarsModule() {
3396
- return `// Generated by @drzl/generator-graphql
3397
- // Dependency-free scalar configs for the resolvers map. Each hook is named twice because
3398
- // graphql 17 renamed serialize/parseValue/parseLiteral to coerceOutputValue/coerceInputValue/
3399
- // coerceInputLiteral, and the schema builder assigns whichever names the running graphql reads.
3400
-
3401
- /** The literal AST shape the literal hooks read, structurally, so nothing is imported. */
3402
- export interface LiteralNode {
3403
- kind: string;
3404
- value?: unknown;
3405
- values?: LiteralNode[];
3406
- fields?: { name: { value: string }; value: LiteralNode }[];
3407
- name?: { value: string };
3408
- }
3409
-
3410
- // Strict ISO 8601 datetime with seconds and an offset: new Date('1') is the year 2001.
3411
- const ISO_DATETIME = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$/;
3412
-
3413
- const toIso = (value: unknown): string => {
3414
- if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
3415
- throw new Error('DateTime.serialize: expected a Date');
3416
- };
3417
- const fromIso = (value: unknown): Date => {
3418
- if (typeof value !== 'string' || !ISO_DATETIME.test(value)) {
3419
- throw new Error('DateTime: expected a strict ISO 8601 datetime string');
3420
- }
3421
- const parsed = new Date(value);
3422
- if (Number.isNaN(parsed.getTime())) throw new Error('DateTime: unreadable datetime string');
3423
- return parsed;
3424
- };
3425
- const fromIsoLiteral = (ast: LiteralNode): Date => {
3426
- if (ast.kind !== 'StringValue') throw new Error('DateTime: expected a string literal');
3427
- return fromIso(ast.value);
3428
- };
3429
-
3430
- /** ISO 8601 datetime string in, real Date to the resolver, toISOString() out. */
3431
- export const DateTimeScalar = {
3432
- name: 'DateTime',
3433
- description: 'A strict ISO 8601 datetime string, e.g. 2026-01-02T03:04:05.000Z.',
3434
- serialize: toIso,
3435
- parseValue: fromIso,
3436
- parseLiteral: fromIsoLiteral,
3437
- coerceOutputValue: toIso,
3438
- coerceInputValue: fromIso,
3439
- coerceInputLiteral: fromIsoLiteral,
3440
- };
3441
-
3442
- const DIGITS = /^-?\\d+$/;
3443
-
3444
- const toDigits = (value: unknown): string => {
3445
- if (typeof value === 'bigint') return value.toString();
3446
- if (typeof value === 'string' && DIGITS.test(value)) return value;
3447
- throw new Error('BigInt.serialize: expected a bigint or a decimal digit string');
3448
- };
3449
- // Variables take the digit string only: a JSON number was already rounded by JSON.parse.
3450
- const fromDigits = (value: unknown): string => {
3451
- if (typeof value !== 'string' || !DIGITS.test(value)) {
3452
- throw new Error('BigInt: expected a string of decimal digits');
3453
- }
3454
- return value;
3455
- };
3456
- // An inline integer literal is lossless: the AST carries its raw digits as a string.
3457
- const fromDigitsLiteral = (ast: LiteralNode): string => {
3458
- if (ast.kind === 'IntValue') return String(ast.value);
3459
- if (ast.kind === 'StringValue' && typeof ast.value === 'string' && DIGITS.test(ast.value)) {
3460
- return ast.value;
3461
- }
3462
- throw new Error('BigInt: expected an integer literal or a string of decimal digits');
3463
- };
3464
-
3465
- /** A 64-bit-safe integer crossing the wire as its decimal digits. */
3466
- export const BigIntScalar = {
3467
- name: 'BigInt',
3468
- description: 'An arbitrary-precision integer as a string of decimal digits.',
3469
- serialize: toDigits,
3470
- parseValue: fromDigits,
3471
- parseLiteral: fromDigitsLiteral,
3472
- coerceOutputValue: toDigits,
3473
- coerceInputValue: fromDigits,
3474
- coerceInputLiteral: fromDigitsLiteral,
3475
- };
3476
-
3477
- const identity = (value: unknown): unknown => value;
3478
- const fromJSONLiteral = (ast: LiteralNode, variables?: Record<string, unknown> | null): unknown => {
3479
- switch (ast.kind) {
3480
- case 'StringValue':
3481
- case 'BooleanValue':
3482
- case 'EnumValue':
3483
- return ast.value;
3484
- case 'IntValue':
3485
- case 'FloatValue':
3486
- return Number(ast.value);
3487
- case 'NullValue':
3488
- return null;
3489
- case 'ListValue':
3490
- return (ast.values ?? []).map((v) => fromJSONLiteral(v, variables));
3491
- case 'ObjectValue': {
3492
- const out: Record<string, unknown> = {};
3493
- for (const f of ast.fields ?? []) out[f.name.value] = fromJSONLiteral(f.value, variables);
3494
- return out;
3495
- }
3496
- case 'Variable':
3497
- return variables ? variables[ast.name?.value ?? ''] : undefined;
3498
- default:
3499
- throw new Error('JSON: unsupported literal kind ' + ast.kind);
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
3500
15
  }
16
+ return to;
3501
17
  };
3502
-
3503
- /** Any JSON value, untouched on both value paths, rebuilt from the AST for inline literals. */
3504
- export const JSONScalar = {
3505
- name: 'JSON',
3506
- description: 'Any JSON value, passed through as-is.',
3507
- serialize: identity,
3508
- parseValue: identity,
3509
- parseLiteral: fromJSONLiteral,
3510
- coerceOutputValue: identity,
3511
- coerceInputValue: identity,
3512
- coerceInputLiteral: fromJSONLiteral,
3513
- };
3514
- `;
3515
- }
3516
- function renderBarrel6(plans, modules, usedScalars, scalarsSpec) {
3517
- if (!plans.length) {
3518
- return `// Generated by @drzl/generator-graphql
3519
- // No tables detected in analysis. Add tables to your schema and regenerate.
3520
- // A GraphQL schema needs a Query type with at least one field, so no typeDefs are composed.
3521
- export * from '${scalarsSpec}';
3522
-
3523
- export const typeDefs = '';
3524
-
3525
- export const resolvers = {};
3526
- `;
3527
- }
3528
- const imports = [];
3529
- if (usedScalars.length) {
3530
- const names = usedScalars.map((s) => SCALAR_EXPORTS[s]).sort();
3531
- imports.push(`import { ${names.join(", ")} } from '${scalarsSpec}';`);
3532
- }
3533
- for (const plan of plans) {
3534
- const tsName = gqlIdent(plan.table.tsName);
3535
- imports.push(
3536
- `import { ${tsName}Resolvers, ${tsName}TypeDefs } from '${modules.get(plan)}';`
3537
- );
3538
- }
3539
- const reExports = [
3540
- `export * from '${scalarsSpec}';`,
3541
- ...plans.map((p) => `export * from '${modules.get(p)}';`)
3542
- ];
3543
- const queryLines = plans.flatMap(
3544
- (p) => p.ops.filter((o) => o.parent === "Query").map((o) => ` ${o.name}${o.argsSdl ? `(${o.argsSdl})` : ""}: ${o.resultSdl}`)
3545
- );
3546
- const mutationLines = plans.flatMap(
3547
- (p) => p.ops.filter((o) => o.parent === "Mutation").map((o) => ` ${o.name}(${o.argsSdl}): ${o.resultSdl}`)
3548
- );
3549
- const typeDefParts = [
3550
- ...usedScalars.map((s) => `'scalar ${s}'`),
3551
- ...plans.map((p) => `${gqlIdent(p.table.tsName)}TypeDefs`),
3552
- `\`${tpl(["type Query {", ...queryLines, "}"].join("\n"))}\``
3553
- ];
3554
- if (mutationLines.length) {
3555
- typeDefParts.push(`\`${tpl(["type Mutation {", ...mutationLines, "}"].join("\n"))}\``);
3556
- }
3557
- const resolverLines = ["export const resolvers = {"];
3558
- for (const s of usedScalars) resolverLines.push(` ${s}: ${SCALAR_EXPORTS[s]},`);
3559
- for (const p of plans) {
3560
- const tsName = gqlIdent(p.table.tsName);
3561
- for (const e of p.enums) {
3562
- if (e.members.some((m) => m.renamed)) {
3563
- resolverLines.push(` ${e.typeName}: ${tsName}Resolvers.${e.typeName},`);
3564
- }
3565
- }
3566
- if (p.renamedSelect.length) {
3567
- resolverLines.push(` ${p.typeName}: ${tsName}Resolvers.${p.typeName},`);
3568
- }
3569
- }
3570
- resolverLines.push(" Query: {");
3571
- for (const p of plans) resolverLines.push(` ...${gqlIdent(p.table.tsName)}Resolvers.Query,`);
3572
- resolverLines.push(" },");
3573
- const mutating = plans.filter((p) => p.ops.some((o) => o.parent === "Mutation"));
3574
- if (mutating.length) {
3575
- resolverLines.push(" Mutation: {");
3576
- for (const p of mutating) {
3577
- resolverLines.push(` ...${gqlIdent(p.table.tsName)}Resolvers.Mutation,`);
3578
- }
3579
- resolverLines.push(" },");
3580
- }
3581
- resolverLines.push("};");
3582
- return `// Generated by @drzl/generator-graphql
3583
- // The whole schema in one pair: hand typeDefs and resolvers to makeExecutableSchema,
3584
- // createSchema (graphql-yoga) or new ApolloServer(...). Plain buildSchema(typeDefs) accepts
3585
- // the SDL too, but takes no resolvers, so scalar and enum behaviour will not attach there.
3586
- ${imports.join("\n")}
3587
-
3588
- ${reExports.join("\n")}
3589
-
3590
- /** The whole schema's SDL. */
3591
- export const typeDefs = [
3592
- ${typeDefParts.map((p) => ` ${p},`).join("\n")}
3593
- ].join('\\n\\n');
3594
-
3595
- /** Everything merged. Override per field: { ...resolvers, Query: { ...resolvers.Query, users: yours } } */
3596
- ${resolverLines.join("\n")}
3597
- `;
3598
- }
3599
- function buildHeader7(h) {
3600
- if (h && h.enabled === false) return "";
3601
- const text = h?.text?.trim();
3602
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
3603
- "// Generated by DRZL (@drzl/*)",
3604
- "// Generated output is granted to you under your project's license.",
3605
- "// You may use, copy, modify, and distribute without attribution."
3606
- ];
3607
- return lines.join("\n") + "\n\n";
3608
- }
3609
- function toCase6(s, c) {
3610
- if (!c) return s;
3611
- const parts = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/);
3612
- if (c === "camel") {
3613
- return parts.map(
3614
- (p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()
3615
- ).join("");
3616
- }
3617
- if (c === "kebab") return parts.map((p) => p.toLowerCase()).join("-");
3618
- if (c === "snake") return parts.map((p) => p.toLowerCase()).join("_");
3619
- return s;
3620
- }
3621
- var import_validation_core15, import_validation_core16, APP_MODULE5, SCALARS_MODULE, GRAPHQL_NAME, RESERVED_ENUM_VALUES, INT32_MIN, INT32_MAX, cap6, isIdent6, q5, stubBody, SCALAR_EXPORTS, GraphQLGenerator, index_default7;
3622
- var init_dist7 = __esm({
3623
- "../generator-graphql/dist/index.js"() {
3624
- "use strict";
3625
- import_validation_core15 = require("@drzl/validation-core");
3626
- import_validation_core16 = require("@drzl/validation-core");
3627
- APP_MODULE5 = "index";
3628
- SCALARS_MODULE = "scalars";
3629
- GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
3630
- RESERVED_ENUM_VALUES = /* @__PURE__ */ new Set(["true", "false", "null"]);
3631
- INT32_MIN = -2147483648n;
3632
- INT32_MAX = 2147483647n;
3633
- cap6 = (s) => s.charAt(0).toUpperCase() + s.slice(1);
3634
- isIdent6 = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
3635
- q5 = (v) => `'${v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
3636
- stubBody = (parent, name) => `throw new Error('Not implemented: ${parent}.${name}. Replace this stub with your data layer.');`;
3637
- SCALAR_EXPORTS = {
3638
- DateTime: "DateTimeScalar",
3639
- BigInt: "BigIntScalar",
3640
- JSON: "JSONScalar"
3641
- };
3642
- GraphQLGenerator = class {
3643
- constructor(analysis) {
3644
- this.analysis = analysis;
3645
- }
3646
- async generate(opts) {
3647
- const fs6 = (0, import_validation_core15.fileWriter)(opts.fileSink);
3648
- const path9 = await import("path");
3649
- const out = path9.resolve(process.cwd(), opts.outputDir);
3650
- await fs6.mkdir(out, { recursive: true });
3651
- const files = [];
3652
- const write = async (filePath, content) => {
3653
- const formatted = await (0, import_validation_core16.formatCode)(
3654
- buildHeader7(opts.outputHeader) + content,
3655
- filePath,
3656
- opts.format
3657
- );
3658
- await fs6.writeFile(filePath, formatted, "utf8");
3659
- files.push(filePath);
3660
- };
3661
- const barrelPath = path9.join(out, `${APP_MODULE5}.ts`);
3662
- const scalarsPath = path9.join(out, `${SCALARS_MODULE}.ts`);
3663
- const plans = this.analysis.tables.map(planTable);
3664
- const modules = /* @__PURE__ */ new Map();
3665
- const total = plans.length;
3666
- let index = 0;
3667
- for (const plan of plans) {
3668
- const base = `${plan.table.tsName}${opts.naming?.routerSuffix ?? ""}`;
3669
- const filePath = path9.join(out, `${toCase6(base, opts.naming?.procedureCase)}.ts`);
3670
- if (filePath === barrelPath || filePath === scalarsPath) {
3671
- const which = filePath === barrelPath ? "the barrel" : "the scalars module";
3672
- throw new Error(
3673
- `@drzl/generator-graphql: the module for table "${plan.table.name}" would be written to ${filePath}, which is ${which} this generator also writes. Set naming.routerSuffix to move it out of the way.`
3674
- );
3675
- }
3676
- await write(filePath, renderTable2(plan));
3677
- modules.set(
3678
- plan,
3679
- (0, import_validation_core16.importSpecifier)("./" + path9.relative(out, filePath).replace(/\\/g, "/"), opts.importExtension)
3680
- );
3681
- index++;
3682
- opts.onProgress?.({ index, total, table: plan.table.name, filePath });
3683
- }
3684
- const usedScalars = ["DateTime", "BigInt", "JSON"].filter(
3685
- (s) => plans.some((p) => p.scalars.has(s))
3686
- );
3687
- const scalarsSpec = (0, import_validation_core16.importSpecifier)(`./${SCALARS_MODULE}.ts`, opts.importExtension);
3688
- await write(scalarsPath, renderScalarsModule());
3689
- await write(barrelPath, renderBarrel6(plans, modules, usedScalars, scalarsSpec));
3690
- return { files };
3691
- }
3692
- };
3693
- index_default7 = GraphQLGenerator;
3694
- }
3695
- });
3696
-
3697
- // ../generator-effect/dist/index.js
3698
- var dist_exports8 = {};
3699
- __export(dist_exports8, {
3700
- EffectGenerator: () => EffectGenerator,
3701
- default: () => index_default8
3702
- });
3703
- function filter(expr, description) {
3704
- return `${NS}.filter((v) => ${expr}, { description: ${JSON.stringify(description)} })`;
3705
- }
3706
- function piped(base, steps) {
3707
- return steps.length ? `${base}.pipe(${steps.join(", ")})` : base;
3708
- }
3709
- function isUnknownExpr(expr) {
3710
- return expr === UNKNOWN_EXPR;
3711
- }
3712
- function withNarrowedType(expr, ref2) {
3713
- return `${expr} as unknown as ${NS}.Schema<${ref2}>`;
3714
- }
3715
- function numericBounds(c, checks) {
3716
- let lo = c.min !== void 0 ? { fn: "greaterThanOrEqualTo", value: c.min } : void 0;
3717
- let hi = c.max !== void 0 ? { fn: "lessThanOrEqualTo", value: c.max } : void 0;
3718
- for (const k of checks.filter((x) => x.column === c.name && x.kind === "number")) {
3719
- if (k.operator === ">=") lo = { fn: "greaterThanOrEqualTo", value: k.value };
3720
- else if (k.operator === ">") lo = { fn: "greaterThan", value: k.value };
3721
- else if (k.operator === "<=") hi = { fn: "lessThanOrEqualTo", value: k.value };
3722
- else if (k.operator === "<") hi = { fn: "lessThan", value: k.value };
3723
- }
3724
- return [lo, hi].filter(Boolean).map((x) => `${NS}.${x.fn}(${x.value})`);
3725
- }
3726
- function foldedIntoBounds(c, checks) {
3727
- if (c.arrayDimensions || c.shape) return /* @__PURE__ */ new Set();
3728
- if (c.tsType !== "number" && c.tsType !== "bigint") return /* @__PURE__ */ new Set();
3729
- return new Set(
3730
- checks.filter(
3731
- (k) => k.column === c.name && k.kind === "number" && k.operator !== "=" && k.operator !== "<>"
3732
- )
3733
- );
3734
- }
3735
- function nonFiniteBranches(c) {
3736
- const { nan, infinity } = (0, import_validation_core18.nonFiniteAccepted)(c);
3737
- return [
3738
- ...nan ? [`${NS}.Number.pipe(${filter("Number.isNaN(v)", "NaN, which this column stores")})`] : [],
3739
- ...infinity ? [`${NS}.Literal(Infinity, -Infinity)`] : []
3740
- ];
3741
- }
3742
- function withNonFinite(c, base) {
3743
- const branches = nonFiniteBranches(c);
3744
- return branches.length ? `${NS}.Union(${base}, ${branches.join(", ")})` : base;
3745
- }
3746
- function dateExpr(mode, coerceDates) {
3747
- const plain = `${NS}.ValidDateFromSelf`;
3748
- if (coerceDates === "none") return plain;
3749
- const fromString = piped(`${NS}.String`, [
3750
- `${NS}.pattern(new RegExp(${JSON.stringify(import_validation_core18.COERCIBLE_DATE_STRING)}))`,
3751
- filter((0, import_validation_core18.parsesToADate)("new Date(v)"), "a date the runtime can parse")
3752
- ]);
3753
- const fromNumber = piped(`${NS}.Number`, [
3754
- filter((0, import_validation_core18.parsesToADate)("new Date(v)"), "a date the runtime can parse")
3755
- ]);
3756
- const union = `${NS}.Union(${plain}, ${fromString}, ${fromNumber})`;
3757
- if (coerceDates === "all") return union;
3758
- return mode === "select" ? plain : union;
3759
- }
3760
- function capSteps(c, mode) {
3761
- const steps = [];
3762
- if (c.shape?.kind === "byteString") {
3763
- const n = c.shape.length;
3764
- if (!n) return steps;
3765
- return mode === "select" ? [filter(`${import_validation_core18.CODEPOINT_LENGTH} <= ${n}`, `at most ${n} characters`)] : [filter(`new TextEncoder().encode(v).length <= ${n}`, `at most ${n} bytes`)];
3766
- }
3767
- if (c.maxLength) {
3768
- steps.push(
3769
- filter(`${import_validation_core18.CODEPOINT_LENGTH} <= ${c.maxLength}`, `at most ${c.maxLength} characters`)
3770
- );
3771
- }
3772
- if (c.maxBytes) {
3773
- steps.push(
3774
- filter(`new TextEncoder().encode(v).length <= ${c.maxBytes}`, `at most ${c.maxBytes} bytes`)
3775
- );
3776
- }
3777
- return steps;
3778
- }
3779
- function lengthSteps(c, lengths) {
3780
- return lengths.filter((k) => k.column === c.name).flatMap((k) => {
3781
- const measure = (0, import_validation_core18.lengthMeasure)(c, k);
3782
- if (!measure) return [];
3783
- return [
3784
- filter(
3785
- `${(0, import_validation_core18.measureExpression)(measure, "v")} ${OPS[k.operator]} ${k.value}`,
3786
- (0, import_validation_core18.lengthCheckLabel)(k)
3787
- )
3788
- ];
3789
- });
3790
- }
3791
- function cardinalitySteps(c, cardinalities) {
3792
- if (!c.arrayDimensions) return [];
3793
- return cardinalities.filter((k) => k.column === c.name).map(
3794
- (k) => filter(
3795
- `v.length ${OPS[k.operator]} ${k.value}`,
3796
- `${k.name ? `${k.name}: ` : ""}cardinality(${c.name}) ${k.operator} ${k.value}`
3797
- )
3798
- );
3799
- }
3800
- function checkSteps(c, checks) {
3801
- if (c.arrayDimensions || c.shape) return [];
3802
- const folded2 = foldedIntoBounds(c, checks);
3803
- const numericWire = (0, import_validation_core18.comparisonWire)(c) === "numeric-string";
3804
- return checks.filter((k) => k.column === c.name && !folded2.has(k)).map((k) => {
3805
- const label = `${k.name ? `${k.name}: ` : ""}${c.name} ${k.operator} ${k.value}`;
3806
- if (numericWire) {
3807
- if (k.operator === "=" || k.operator === "<>") {
3808
- const canon = JSON.stringify((0, import_validation_core18.canonicalNumericText)(k.value));
3809
- const op = k.operator === "=" ? "===" : "!==";
3810
- return filter(`${import_validation_core18.NUMERIC_CANON_NAME}(v) ${op} ${canon}`, label);
3811
- }
3812
- return filter(`Number(v) ${OPS[k.operator]} ${k.value}`, label);
3813
- }
3814
- const literal = k.kind === "string" ? JSON.stringify(k.value) : (0, import_validation_core18.wireNumberLiteral)(c, k.value);
3815
- return filter(`v ${OPS[k.operator]} ${literal}`, label);
3816
- });
3817
- }
3818
- function hasNoRuntimeType(c) {
3819
- return c.tsType === "any" || c.shape?.kind === "custom" || c.shape?.kind === "json";
3820
- }
3821
- function shapeExpr(c, mode, replaced = false) {
3822
- const s = c.shape;
3823
- if (!s) return void 0;
3824
- switch (s.kind) {
3825
- case "json":
3826
- return replaced ? UNKNOWN_EXPR : JSON_CONST;
3827
- case "custom":
3828
- return UNKNOWN_EXPR;
3829
- case "buffer":
3830
- return `${NS}.Uint8ArrayFromSelf`;
3831
- case "tuple":
3832
- return `${NS}.Tuple(${Array.from({ length: s.length }, () => `${NS}.Number`).join(", ")})`;
3833
- case "numberObject":
3834
- return `${NS}.Struct({ ${s.fields.map((f) => `${f}: ${NS}.Number`).join(", ")} })`;
3835
- case "numberVector":
3836
- return piped(
3837
- `${NS}.Array(${NS}.Number)`,
3838
- s.length ? [filter(`v.length === ${s.length}`, `exactly ${s.length} elements`)] : []
3839
- );
3840
- case "bitstring":
3841
- return piped(`${NS}.String`, [
3842
- `${NS}.pattern(/^[01]*$/)`,
3843
- ...s.length ? [
3844
- s.exact ? filter(`v.length === ${s.length}`, `exactly ${s.length} binary digits`) : filter(`v.length <= ${s.length}`, `at most ${s.length} binary digits`)
3845
- ] : []
3846
- ]);
3847
- case "byteString":
3848
- return piped(`${NS}.String`, capSteps(c, mode));
3849
- }
3850
- }
3851
- function exprForColumn(c, mode, coerceDates, checks, sets, lengths, replaced) {
3852
- const shaped = shapeExpr(c, mode, replaced);
3853
- if (shaped) return piped(shaped, lengthSteps(c, lengths));
3854
- const set = sets.find((x) => x.column === c.name);
3855
- if (set) {
3856
- if ((0, import_validation_core18.comparisonWire)(c) === "numeric-string") {
3857
- const members = (0, import_validation_core18.canonicalMembers)(set.values);
3858
- const test = members.map((m) => `canon === ${JSON.stringify(m)}`).join(" || ");
3859
- return piped(`${NS}.String`, [
3860
- filter(`((canon) => ${test})(${import_validation_core18.NUMERIC_CANON_NAME}(v))`, (0, import_validation_core18.describeSet)(set))
3861
- ]);
3862
- }
3863
- const values = set.values.map(
3864
- (v) => set.kind === "string" ? JSON.stringify(v) : (0, import_validation_core18.wireNumberLiteral)(c, v)
3865
- );
3866
- return `${NS}.Literal(${values.join(", ")})`;
3867
- }
3868
- if (c.arrayDimensions) checks = [];
3869
- if (c.enumValues && c.enumValues.length) {
3870
- return `${NS}.Literal(${c.enumValues.map((v) => JSON.stringify(v)).join(", ")})`;
3871
- }
3872
- const eq = checks.find((k) => k.column === c.name && k.operator === "=");
3873
- if (eq && !c.shape && (0, import_validation_core18.comparisonWire)(c) !== "numeric-string") {
3874
- return `${NS}.Literal(${eq.kind === "string" ? JSON.stringify(eq.value) : (0, import_validation_core18.wireNumberLiteral)(c, eq.value)})`;
3875
- }
3876
- const rest = [...checkSteps(c, checks), ...lengthSteps(c, lengths)];
3877
- switch (c.tsType) {
3878
- case "string": {
3879
- const base = c.format === "uuid" ? `${NS}.UUID` : `${NS}.String`;
3880
- const pattern = c.format && c.format !== "uuid" && import_validation_core18.COLUMN_FORMATS[c.format] ? [`${NS}.pattern(new RegExp(${JSON.stringify(import_validation_core18.COLUMN_FORMATS[c.format])}))`] : [];
3881
- return piped(base, [...pattern, ...capSteps(c, mode), ...rest]);
3882
- }
3883
- case "number": {
3884
- const base = (0, import_validation_core18.isIntegerColumn)(c) ? `${NS}.Int` : `${NS}.Finite`;
3885
- return withNonFinite(c, piped(base, [...numericBounds(c, checks), ...rest]));
3886
- }
3887
- case "bigint":
3888
- return piped(`${NS}.BigIntFromSelf`, [
3889
- ...c.min !== void 0 ? [`${NS}.greaterThanOrEqualToBigInt(${c.min}n)`] : [],
3890
- ...c.max !== void 0 ? [`${NS}.lessThanOrEqualToBigInt(${c.max}n)`] : [],
3891
- ...rest
3892
- ]);
3893
- case "boolean":
3894
- return `${NS}.Boolean`;
3895
- case "Date":
3896
- return dateExpr(mode, coerceDates);
3897
- case "Uint8Array":
3898
- return `${NS}.Uint8ArrayFromSelf`;
3899
- case "any":
3900
- return UNKNOWN_EXPR;
3901
- default:
3902
- return UNKNOWN_EXPR;
3903
- }
3904
- }
3905
- function renderField(c, mode, coerceDates, checks, sets, lengths, cardinalities, applyDefault, narrowRef, brand) {
3906
- let expr = exprForColumn(
3907
- c,
3908
- mode,
3909
- coerceDates,
3910
- checks,
3911
- sets,
3912
- lengths,
3913
- !!narrowRef && hasNoRuntimeType(c)
3914
- );
3915
- const dims = c.arrayDimensions ?? 0;
3916
- for (let i = 0; i < dims; i++) {
3917
- expr = `${NS}.Array(${expr})`;
3918
- if (i === dims - 1) expr = piped(expr, cardinalitySteps(c, cardinalities));
3919
- }
3920
- if (brand) expr = piped(expr, [`${NS}.brand(${JSON.stringify(brand)})`]);
3921
- if (c.nullable && !isUnknownExpr(expr)) expr = `${NS}.NullOr(${expr})`;
3922
- if (narrowRef && !brand) expr = withNarrowedType(expr, narrowRef);
3923
- if (mode === "select") return expr;
3924
- const wantsDefault = mode === "insert" && applyDefault && c.defaultValue !== void 0;
3925
- if (wantsDefault) {
3926
- return `${NS}.optionalWith(${expr}, { default: () => ${JSON.stringify(c.defaultValue)} })`;
3927
- }
3928
- if (mode === "update" || c.nullable || c.hasDefault) return `${NS}.optional(${expr})`;
3929
- return expr;
3930
- }
3931
- function wantsRef(c, allColumns2) {
3932
- return allColumns2 || hasNoRuntimeType(c);
3933
- }
3934
- function renderObjectShape(cols, mode, coerceDates, checks, sets, lengths, cardinalities, typedJson, applyDefaults, brands) {
3935
- return cols.map((c) => {
3936
- const ref2 = typedJson && wantsRef(c, !!typedJson.allColumns) ? `(typeof ${typedJson.table}.$infer${typedJson.mode === "insert" ? "Insert" : "Select"})[${JSON.stringify(c.name)}]` : void 0;
3937
- const field5 = renderField(
3938
- c,
3939
- mode,
3940
- coerceDates,
3941
- checks,
3942
- sets,
3943
- lengths,
3944
- cardinalities,
3945
- applyDefaults,
3946
- ref2,
3947
- brands?.plan.brandOf(brands.tsName, c.name)
3948
- );
3949
- return ` ${JSON.stringify(c.name)}: ${field5},`;
3950
- }).join("\n");
3951
- }
3952
- function rowSteps(rows, cols) {
3953
- const present = new Set(cols.map((c) => c.name));
3954
- return rows.filter((r) => present.has(r.left) && present.has(r.right)).map((r) => {
3955
- const l = `o[${JSON.stringify(r.left)}]`;
3956
- const rt = `o[${JSON.stringify(r.right)}]`;
3957
- const msg = `${r.name ? `${r.name}: ` : ""}${r.left} ${r.operator} ${r.right}`;
3958
- return `${NS}.filter((o) => ${l} == null || ${rt} == null || ${l} ${OPS[r.operator]} ${rt}, { description: ${JSON.stringify(msg)} })`;
3959
- });
3960
- }
3961
- function indentBlock(code, by = " ") {
3962
- return code.split("\n").map((line) => line ? by + line : line).join("\n");
3963
- }
3964
- function parsedChecksFor(table) {
3965
- const parsed = (table.checks ?? []).map((k) => (0, import_validation_core18.parseCheck)(k.expression, k.name));
3966
- const { checks, sets } = (0, import_validation_core18.applyWirePolicy)(
3967
- table.columns,
3968
- parsed.flatMap((p) => p.ok ? p.checks : []),
3969
- parsed.flatMap((p) => p.ok ? p.sets ?? [] : [])
3970
- );
3971
- return {
3972
- checks,
3973
- sets,
3974
- rows: parsed.flatMap((p) => p.ok ? p.rows ?? [] : []),
3975
- lengths: parsed.flatMap((p) => p.ok ? p.lengths ?? [] : []),
3976
- cardinalities: parsed.flatMap((p) => p.ok ? p.cardinalities ?? [] : [])
3977
- };
3978
- }
3979
- function nestedNodeCols(node, mode) {
3980
- const all = mode === "insert" ? (0, import_validation_core18.insertColumns)(node.table) : (0, import_validation_core18.selectColumns)(node.table);
3981
- return (0, import_validation_core18.nestedNodeColumns)(all, node);
3982
- }
3983
- function nestedNodes(node, into = []) {
3984
- into.push(node);
3985
- for (const arm of node.arms) nestedNodes(arm.child, into);
3986
- return into;
3987
- }
3988
- function renderNestedObject(node, mode, coerceDates, typedJson, applyDefaults, brands) {
3989
- const cols = nestedNodeCols(node, mode);
3990
- const { checks, sets, rows, lengths, cardinalities } = parsedChecksFor(node.table);
3991
- const tj = typedJson ? { table: node.table.tsName, mode, allColumns: typedJson.allColumns } : void 0;
3992
- const fields = renderObjectShape(
3993
- cols,
3994
- mode,
3995
- coerceDates,
3996
- checks,
3997
- sets,
3998
- lengths,
3999
- cardinalities,
4000
- tj,
4001
- applyDefaults,
4002
- brands ? { plan: brands, tsName: node.table.tsName } : void 0
4003
- );
4004
- const arms = node.arms.map((arm) => {
4005
- const notes = (0, import_validation_core18.nestedArmNotes)(arm).map((n) => ` // ${n}
4006
- `).join("");
4007
- const child = renderNestedObject(
4008
- arm.child,
4009
- mode,
4010
- coerceDates,
4011
- typedJson,
4012
- applyDefaults,
4013
- brands
4014
- );
4015
- const inner = arm.single ? `${NS}.NullOr(
4016
- ${indentBlock(indentBlock(child))}
4017
- )` : `${NS}.Array(
4018
- ${indentBlock(indentBlock(child))}
4019
- )`;
4020
- return `${notes} ${JSON.stringify(arm.key)}: ${NS}.optional(${inner}),`;
4021
- });
4022
- const body = [fields, ...arms].filter(Boolean).join("\n");
4023
- return piped(`${NS}.Struct({
4024
- ${body}
4025
- })`, rowSteps(rows, cols));
4026
- }
4027
- function renderNestedSchemas(table, affix, coerceDates, typedJson, applyDefaults, plans, brands) {
4028
- const out = [];
4029
- for (const mode of ["insert", "select"]) {
4030
- const plan = plans[mode];
4031
- if (!plan) continue;
4032
- const name = (0, import_validation_core18.nestedSchemaName)(mode, table.tsName, affix);
4033
- const tname = (0, import_validation_core18.nestedTypeName)(mode, table.tsName, affix);
4034
- const expr = renderNestedObject(plan, mode, coerceDates, typedJson, applyDefaults, brands);
4035
- out.push(
4036
- `export const ${name} = ${expr};
4037
-
4038
- export type ${tname} = ${NS}.Schema.Type<typeof ${name}>;
4039
-
4040
- export const ${STANDARD_PREFIX}${name} = ${NS}.standardSchemaV1(${name});`
4041
- );
4042
- }
4043
- return out.length ? `
4044
- ${out.join("\n\n")}
4045
- ` : "";
4046
- }
4047
- function nestedPlansFor(table, analysis, depth) {
4048
- const out = {};
4049
- for (const mode of ["insert", "select"]) {
4050
- if (mode === "insert" && table.readOnly) continue;
4051
- const plan = (0, import_validation_core18.buildNestedPlan)(table, analysis.tables, analysis.relations ?? [], mode, depth);
4052
- if (plan) out[mode] = plan;
4053
- }
4054
- return out;
4055
- }
4056
- function renderTableSchemas(table, affix, coerceDates, typedJson, applyDefaults = false, wantsDuplicateFinder = false, nested = {}, brands) {
4057
- const T = table.tsName;
4058
- const insertCols = (0, import_validation_core18.insertColumns)(table);
4059
- const updateCols = (0, import_validation_core18.updateColumns)(table);
4060
- const selectCols = (0, import_validation_core18.selectColumns)(table);
4061
- const { checks, sets, rows, lengths, cardinalities } = parsedChecksFor(table);
4062
- const tj = typedJson ? { table: T, allColumns: typedJson.allColumns } : void 0;
4063
- const modes = [
4064
- ["insert", insertCols],
4065
- ["update", updateCols],
4066
- ["select", selectCols]
4067
- ];
4068
- const blocks = modes.map(([mode, cols]) => {
4069
- const name = (0, import_validation_core18.schemaName)(mode, T, affix);
4070
- const tname = (0, import_validation_core18.typeName)(mode, T, affix);
4071
- const body = renderObjectShape(
4072
- cols,
4073
- mode,
4074
- coerceDates,
4075
- checks,
4076
- sets,
4077
- lengths,
4078
- cardinalities,
4079
- // The update schema references the insert-side inferred types: both describe a value going
4080
- // in, and `$inferSelect` would name the post-default type for a column a write may omit.
4081
- tj ? { ...tj, mode: mode === "select" ? "select" : "insert" } : void 0,
4082
- applyDefaults,
4083
- brands ? { plan: brands, tsName: T } : void 0
4084
- );
4085
- const expr = piped(`${NS}.Struct({
4086
- ${body}
4087
- })`, rowSteps(rows, cols));
4088
- return `export const ${name} = ${expr};
4089
-
4090
- export type ${tname} = ${NS}.Schema.Type<typeof ${name}>;
4091
-
4092
- export const ${STANDARD_PREFIX}${name} = ${NS}.standardSchemaV1(${name});`;
4093
- });
4094
- const nestedByTable = ["insert", "select"].flatMap((m) => {
4095
- const plan = nested[m];
4096
- return plan ? nestedNodes(plan).map((n) => [n.table.tsName, nestedNodeCols(n, m)]) : [];
4097
- });
4098
- const nestedCols = nestedByTable.flatMap(([, cs]) => cs);
4099
- const referenced = /* @__PURE__ */ new Set();
4100
- if (typedJson) {
4101
- const all = !!typedJson.allColumns;
4102
- if ([...insertCols, ...updateCols, ...selectCols].some((c) => wantsRef(c, all))) {
4103
- referenced.add(T);
4104
- }
4105
- for (const [name, cs] of nestedByTable) {
4106
- if (cs.some((c) => wantsRef(c, all))) referenced.add(name);
4107
- }
4108
- }
4109
- const schemaImport = referenced.size ? `import type { ${[...referenced].join(", ")} } from '${typedJson.schemaSpecifier}';
4110
- ` : "";
4111
- const needsJson = !typedJson && [...insertCols, ...updateCols, ...selectCols, ...nestedCols].some(
4112
- (c) => c.shape?.kind === "json"
4113
- );
4114
- const finder = wantsDuplicateFinder ? (0, import_validation_core18.renderDuplicateFinder)(table, `findDuplicate${T}`, (0, import_validation_core18.typeName)("insert", T, affix)) : void 0;
4115
- const duplicates = finder ? `
4116
- ${finder}
4117
- ` : "";
4118
- const nestedCode = renderNestedSchemas(
4119
- table,
4120
- affix,
4121
- coerceDates,
4122
- typedJson,
4123
- applyDefaults,
4124
- nested,
4125
- brands
4126
- );
4127
- const selectName = (0, import_validation_core18.schemaName)("select", T, affix);
4128
- const brandAliases = (brands?.aliasesFor(T) ?? []).map(
4129
- (a) => `/** The nominal type of ${T}.${a.column}. */
4130
- export type ${a.alias} = ${NS}.Schema.Type<typeof ${selectName}>[${JSON.stringify(a.column)}];`
4131
- ).join("\n\n");
4132
- const brandCode = brandAliases ? `
4133
- ${brandAliases}
4134
- ` : "";
4135
- const involved = [
4136
- table,
4137
- ...["insert", "select"].flatMap((m) => {
4138
- const plan = nested[m];
4139
- return plan ? nestedNodes(plan).map((n) => n.table) : [];
4140
- })
4141
- ];
4142
- const canonPreamble = involved.some((t) => {
4143
- const own = parsedChecksFor(t);
4144
- return (0, import_validation_core18.needsNumericCanon)(t.columns, own.checks, own.sets);
4145
- }) ? `
4146
- ${import_validation_core18.NUMERIC_CANON_SOURCE}` : "";
4147
- return `import * as ${NS} from 'effect/Schema';
4148
- ${schemaImport}${needsJson ? `
4149
- ${JSON_PREAMBLE}` : ""}${canonPreamble}
4150
- ${blocks.join("\n\n")}
4151
- ${brandCode}${nestedCode}${duplicates}`;
4152
- }
4153
- function buildHeader8(h) {
4154
- if (h && h.enabled === false) return "";
4155
- const text = h?.text?.trim();
4156
- const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
4157
- "// Generated by DRZL (@drzl/*)",
4158
- "// Generated output is granted to you under your project's license.",
4159
- "// You may use, copy, modify, and distribute without attribution."
4160
- ];
4161
- return lines.join("\n") + "\n\n";
4162
- }
4163
- var import_validation_core17, import_validation_core18, DEFAULT_FILE_SUFFIX2, STANDARD_PREFIX, NS, OPS, JSON_CONST, JSON_PREAMBLE, UNKNOWN_EXPR, EffectGenerator, index_default8;
4164
- var init_dist8 = __esm({
4165
- "../generator-effect/dist/index.js"() {
4166
- "use strict";
4167
- import_validation_core17 = require("@drzl/validation-core");
4168
- import_validation_core18 = require("@drzl/validation-core");
4169
- DEFAULT_FILE_SUFFIX2 = ".effect.ts";
4170
- STANDARD_PREFIX = "Standard";
4171
- NS = "Schema";
4172
- OPS = {
4173
- ">=": ">=",
4174
- ">": ">",
4175
- "<=": "<=",
4176
- "<": "<",
4177
- "=": "===",
4178
- "<>": "!=="
4179
- };
4180
- JSON_CONST = "DrzlJsonValue";
4181
- JSON_PREAMBLE = `type ${JSON_CONST}Type =
4182
- | string
4183
- | number
4184
- | boolean
4185
- | null
4186
- | readonly ${JSON_CONST}Type[]
4187
- | { readonly [key: string]: ${JSON_CONST}Type };
4188
-
4189
- const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(() =>
4190
- ${NS}.Union(
4191
- ${NS}.String,
4192
- ${NS}.Finite,
4193
- ${NS}.Boolean,
4194
- ${NS}.Null,
4195
- ${NS}.Array(${JSON_CONST}),
4196
- // The plain-object test comes before the record, not after it. \`Schema.Record\` rebuilds its
4197
- // output, so a check placed after it inspects that new object and reports every input as
4198
- // plain. A Date sailed through: it has no own enumerable keys, so the record accepted it and
4199
- // rebuilt it as \`{}\`.
4200
- ${NS}.Unknown.pipe(
4201
- ${NS}.filter(
4202
- (o) => {
4203
- if (typeof o !== 'object' || o === null || Array.isArray(o)) return false;
4204
- const p = Object.getPrototypeOf(o);
4205
- return p === Object.prototype || p === null;
4206
- },
4207
- { description: 'a plain object' }
4208
- ),
4209
- ${NS}.compose(${NS}.Record({ key: ${NS}.String, value: ${JSON_CONST} }), { strict: false })
4210
- )
4211
- )
4212
- );
4213
- `;
4214
- UNKNOWN_EXPR = `${NS}.Unknown`;
4215
- EffectGenerator = class {
4216
- constructor(analysis) {
4217
- this.analysis = analysis;
4218
- this.library = "effect";
4219
- }
4220
- async generate(opts) {
4221
- const fs6 = (0, import_validation_core17.fileWriter)(opts.fileSink);
4222
- const path9 = await import("path");
4223
- const out = path9.resolve(process.cwd(), opts.outDir);
4224
- const files = [];
4225
- await fs6.mkdir(out, { recursive: true });
4226
- const affix = (0, import_validation_core18.resolveAffix)(opts);
4227
- const coerceDates = opts.coerceDates ?? "input";
4228
- const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX2;
4229
- const wantsTypes = opts.typedJson || opts.typedColumns;
4230
- const typedJson = wantsTypes && opts.schemaPath ? {
4231
- schemaSpecifier: (0, import_validation_core18.resolveConfiguredImport)(
4232
- opts.schemaPath,
4233
- out,
4234
- process.cwd(),
4235
- opts.importExtension
4236
- ),
4237
- allColumns: !!opts.typedColumns
4238
- } : void 0;
4239
- if (wantsTypes && !opts.schemaPath) {
4240
- console.warn(
4241
- "[drzl] typedJson was requested but the schema path is unknown, so json columns keep their wide type."
4242
- );
4243
- }
4244
- const nestedDepth = opts.nestedSchemas ? (0, import_validation_core18.resolveNestedDepth)(opts.nestedDepth, (m) => console.warn(m)) : 0;
4245
- const brands = (0, import_validation_core18.buildBrandPlan)(this.analysis.tables, opts.branded);
4246
- for (const note of brands?.notes ?? []) console.warn(`[drzl] ${note}`);
4247
- for (const table of this.analysis.tables) {
4248
- const filePath = path9.join(out, (0, import_validation_core18.moduleFileName)(table.tsName, fileSuffix));
4249
- const code = renderTableSchemas(
4250
- table,
4251
- affix,
4252
- coerceDates,
4253
- typedJson,
4254
- !!opts.applyDefaults,
4255
- !!opts?.duplicateFinder,
4256
- opts.nestedSchemas ? nestedPlansFor(table, this.analysis, nestedDepth) : {},
4257
- brands
4258
- );
4259
- const formatted = await (0, import_validation_core18.formatCode)(
4260
- buildHeader8(opts.outputHeader) + code,
4261
- filePath,
4262
- opts.format
4263
- );
4264
- await fs6.writeFile(filePath, formatted, "utf8");
4265
- files.push(filePath);
4266
- }
4267
- const indexPath = path9.join(out, "index.ts");
4268
- const indexFormatted = await (0, import_validation_core18.formatCode)(
4269
- buildHeader8(opts.outputHeader) + this.defaultIndex(this.analysis, opts),
4270
- indexPath,
4271
- opts.format
4272
- );
4273
- await fs6.writeFile(indexPath, indexFormatted, "utf8");
4274
- files.push(indexPath);
4275
- return files;
4276
- }
4277
- renderTable(table, opts) {
4278
- return renderTableSchemas(
4279
- table,
4280
- (0, import_validation_core18.resolveAffix)(opts),
4281
- opts?.coerceDates ?? "input",
4282
- void 0,
4283
- !!opts?.applyDefaults,
4284
- !!opts?.duplicateFinder,
4285
- {},
4286
- (0, import_validation_core18.buildBrandPlan)(this.analysis.tables, opts?.branded)
4287
- );
4288
- }
4289
- defaultIndex(analysis, opts) {
4290
- const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX2;
4291
- return analysis.tables.map(
4292
- (t) => `export * from '${(0, import_validation_core18.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
4293
- ).join("\n") + "\n";
4294
- }
4295
- };
4296
- index_default8 = EffectGenerator;
4297
- }
4298
- });
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
4299
26
 
4300
27
  // src/cli.ts
4301
- var import_analyzer4 = require("@drzl/analyzer");
28
+ var import_analyzer3 = require("@drzl/analyzer");
4302
29
  var import_chokidar = __toESM(require("chokidar"), 1);
4303
30
  var import_commander = require("commander");
4304
31
  var path8 = __toESM(require("path"), 1);
@@ -5677,14 +1404,20 @@ var GENERATORS = [
5677
1404
  },
5678
1405
  {
5679
1406
  kind: "trpc",
5680
- // An optional dependency, like seven others below. A package that has never been published
5681
- // cannot publish through npm's trusted-publisher OIDC flow, so its first version has to go out
5682
- // by hand, and naming it as a hard dependency of the CLI in the same release breaks
5683
- // `npm i @drzl/cli` for everyone until it exists. A missing optional dependency is skipped by
5684
- // the installer rather than failing it, which is why these really can be absent on an ordinary
5685
- // install, and why `loadGenerator` tells absence apart from failure.
1407
+ // This one and seven others were `optionalDependencies` until every one of them had been
1408
+ // published: a package that has never existed cannot publish through npm's trusted-publisher
1409
+ // OIDC flow, so its first version goes out by hand, and naming it as a hard dependency in the
1410
+ // same release breaks `npm i @drzl/cli` for everyone until it does exist. An optional
1411
+ // dependency is skipped by the installer instead, which made that release safe.
1412
+ //
1413
+ // The side effect was invisible and lasted longer than the reason: tsup externalises
1414
+ // `dependencies` and `peerDependencies` and bundles everything else, so those eight travelled
1415
+ // inside `dist` while the other six were resolved from `node_modules`. All fourteen are on the
1416
+ // registry now and all fourteen are `dependencies`, which is what makes every one of them a
1417
+ // package that can genuinely be absent, and `loadGenerator` tell absence apart from failure
1418
+ // for every kind rather than for six of them.
5686
1419
  specifier: "@drzl/generator-trpc",
5687
- load: () => Promise.resolve().then(() => (init_dist(), dist_exports)),
1420
+ load: () => import("@drzl/generator-trpc"),
5688
1421
  construct: (m, analysis) => new m.TRPCGenerator(analysis),
5689
1422
  outputDir: (g, cfg) => trpcOutDir(g, cfg),
5690
1423
  options: (g, cfg, ctx) => trpcOptions(g, cfg, ctx.servicesDir)
@@ -5692,7 +1425,7 @@ var GENERATORS = [
5692
1425
  {
5693
1426
  kind: "hono",
5694
1427
  specifier: "@drzl/generator-hono",
5695
- load: () => Promise.resolve().then(() => (init_dist2(), dist_exports2)),
1428
+ load: () => import("@drzl/generator-hono"),
5696
1429
  construct: (m, analysis) => new m.HonoGenerator(analysis),
5697
1430
  outputDir: (g, cfg) => honoOutDir(g, cfg),
5698
1431
  options: (g, cfg) => honoOptions(g, cfg)
@@ -5700,7 +1433,7 @@ var GENERATORS = [
5700
1433
  {
5701
1434
  kind: "express",
5702
1435
  specifier: "@drzl/generator-express",
5703
- load: () => Promise.resolve().then(() => (init_dist3(), dist_exports3)),
1436
+ load: () => import("@drzl/generator-express"),
5704
1437
  construct: (m, analysis) => new m.ExpressGenerator(analysis),
5705
1438
  outputDir: (g, cfg) => expressOutDir(g, cfg),
5706
1439
  options: (g, cfg) => expressOptions(g, cfg)
@@ -5708,7 +1441,7 @@ var GENERATORS = [
5708
1441
  {
5709
1442
  kind: "fastify",
5710
1443
  specifier: "@drzl/generator-fastify",
5711
- load: () => Promise.resolve().then(() => (init_dist5(), dist_exports5)),
1444
+ load: () => import("@drzl/generator-fastify"),
5712
1445
  construct: (m, analysis) => new m.FastifyGenerator(analysis),
5713
1446
  outputDir: (g, cfg) => fastifyOutDir(g, cfg),
5714
1447
  options: (g, cfg) => fastifyOptions(g, cfg)
@@ -5716,7 +1449,7 @@ var GENERATORS = [
5716
1449
  {
5717
1450
  kind: "nestjs",
5718
1451
  specifier: "@drzl/generator-nestjs",
5719
- load: () => Promise.resolve().then(() => (init_dist6(), dist_exports6)),
1452
+ load: () => import("@drzl/generator-nestjs"),
5720
1453
  construct: (m, analysis) => new m.NestJSGenerator(analysis),
5721
1454
  outputDir: (g, cfg) => nestjsOutDir(g, cfg),
5722
1455
  options: (g, cfg) => nestjsOptions(g, cfg)
@@ -5724,7 +1457,7 @@ var GENERATORS = [
5724
1457
  {
5725
1458
  kind: "graphql",
5726
1459
  specifier: "@drzl/generator-graphql",
5727
- load: () => Promise.resolve().then(() => (init_dist7(), dist_exports7)),
1460
+ load: () => import("@drzl/generator-graphql"),
5728
1461
  construct: (m, analysis) => new m.GraphQLGenerator(analysis),
5729
1462
  outputDir: (g, cfg) => graphqlOutDir(g, cfg),
5730
1463
  options: (g, cfg) => graphqlOptions(g, cfg)
@@ -5778,7 +1511,7 @@ var GENERATORS = [
5778
1511
  {
5779
1512
  kind: "effect",
5780
1513
  specifier: "@drzl/generator-effect",
5781
- load: () => Promise.resolve().then(() => (init_dist8(), dist_exports8)),
1514
+ load: () => import("@drzl/generator-effect"),
5782
1515
  construct: (m, analysis) => new m.EffectGenerator(analysis),
5783
1516
  outputDir: (g) => g.path ?? VALIDATOR_DEFAULT_DIRS.effect,
5784
1517
  options: (g, cfg, ctx) => validationOptions(g, cfg, ctx.outDir, { schemaTypes: true })
@@ -5786,7 +1519,7 @@ var GENERATORS = [
5786
1519
  {
5787
1520
  kind: "json-schema",
5788
1521
  specifier: "@drzl/generator-json-schema",
5789
- load: () => Promise.resolve().then(() => (init_dist4(), dist_exports4)),
1522
+ load: () => import("@drzl/generator-json-schema"),
5790
1523
  construct: (m, analysis) => new m.JsonSchemaGenerator(analysis),
5791
1524
  outputDir: (g) => g.path ?? VALIDATOR_DEFAULT_DIRS["json-schema"],
5792
1525
  options: (g, cfg, ctx) => jsonSchemaOptions(g, cfg, ctx.outDir)
@@ -5968,10 +1701,10 @@ function nothingToGenerate(opts) {
5968
1701
  }
5969
1702
 
5970
1703
  // src/column-filter.ts
5971
- var import_validation_core20 = require("@drzl/validation-core");
1704
+ var import_validation_core3 = require("@drzl/validation-core");
5972
1705
 
5973
1706
  // src/doctor.ts
5974
- var import_validation_core19 = require("@drzl/validation-core");
1707
+ var import_validation_core2 = require("@drzl/validation-core");
5975
1708
  var import_chalk2 = require("chalk");
5976
1709
  var PLAIN = new import_chalk2.Chalk({ level: 0 });
5977
1710
  var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
@@ -6036,7 +1769,7 @@ function checkFindings(table) {
6036
1769
  const label = k.name ? `"${k.name}"` : "an unnamed constraint";
6037
1770
  const raw = k.expression ?? "";
6038
1771
  const expr = raw.trim() ? raw : "(empty)";
6039
- const parsed = (0, import_validation_core19.parseCheck)(raw, k.name);
1772
+ const parsed = (0, import_validation_core2.parseCheck)(raw, k.name);
6040
1773
  if (!parsed.ok) {
6041
1774
  out.push({
6042
1775
  kind: "check-declined",
@@ -6061,7 +1794,7 @@ function checkFindings(table) {
6061
1794
  }
6062
1795
  for (const l of parsed.lengths ?? []) {
6063
1796
  const col = byName.get(l.column);
6064
- if (!col || (0, import_validation_core19.lengthMeasure)(col, l)) continue;
1797
+ if (!col || (0, import_validation_core2.lengthMeasure)(col, l)) continue;
6065
1798
  out.push({
6066
1799
  kind: "check-uncountable",
6067
1800
  level: "warn",
@@ -6270,7 +2003,7 @@ function renderDoctorReport(report, style = PLAIN) {
6270
2003
 
6271
2004
  // src/column-filter.ts
6272
2005
  function checkedColumns(expression, name) {
6273
- const parsed = (0, import_validation_core20.parseCheck)(expression, name);
2006
+ const parsed = (0, import_validation_core3.parseCheck)(expression, name);
6274
2007
  if (!parsed.ok) return [];
6275
2008
  return [...new Set(namedColumns(parsed).map((n) => n.column))];
6276
2009
  }
@@ -6362,8 +2095,8 @@ function filterColumns(tables, spec) {
6362
2095
  }
6363
2096
 
6364
2097
  // src/explain.ts
6365
- var import_analyzer2 = require("@drzl/analyzer");
6366
- var import_validation_core21 = require("@drzl/validation-core");
2098
+ var import_analyzer = require("@drzl/analyzer");
2099
+ var import_validation_core4 = require("@drzl/validation-core");
6367
2100
  var import_chalk3 = require("chalk");
6368
2101
  var PLAIN2 = new import_chalk3.Chalk({ level: 0 });
6369
2102
  function namesOf(table) {
@@ -6555,8 +2288,8 @@ function gapsFor(table, constraints, issues) {
6555
2288
  }
6556
2289
  function explainTable(analysis, match, options = {}) {
6557
2290
  const table = match.table;
6558
- const qualified = (0, import_analyzer2.qualifiedTableName)(table);
6559
- const constraints = (0, import_validation_core21.tableConstraints)(table).constraints;
2291
+ const qualified = (0, import_analyzer.qualifiedTableName)(table);
2292
+ const constraints = (0, import_validation_core4.tableConstraints)(table).constraints;
6560
2293
  const capped = new Set(
6561
2294
  constraints.filter((c) => c.kind === "maxLength" || c.kind === "maxBytes").flatMap((c) => c.columns)
6562
2295
  );
@@ -6588,11 +2321,11 @@ function explainTable(analysis, match, options = {}) {
6588
2321
  })
6589
2322
  }));
6590
2323
  const relations = analysis.relations.filter((r) => r.from === qualified || r.to === qualified || r.via === qualified).map((r) => ({ ...r, outgoing: r.from === qualified }));
6591
- const keyColumns8 = table.columns.filter((c) => primaryKeyColumns.has(c.name));
2324
+ const keyColumns = table.columns.filter((c) => primaryKeyColumns.has(c.name));
6592
2325
  const primaryKey = table.primaryKey?.columns.length ? {
6593
2326
  ...table.primaryKey.name ? { name: table.primaryKey.name } : {},
6594
2327
  columns: [...table.primaryKey.columns],
6595
- generated: keyColumns8.length > 0 && keyColumns8.every((c) => c.isGenerated || c.hasDefault)
2328
+ generated: keyColumns.length > 0 && keyColumns.every((c) => c.isGenerated || c.hasDefault)
6596
2329
  } : null;
6597
2330
  const removed = options.keptColumns ? table.columns.map((c) => c.name).filter((name) => !options.keptColumns.includes(name)) : [];
6598
2331
  return {
@@ -6619,7 +2352,7 @@ function explainTable(analysis, match, options = {}) {
6619
2352
  foreignKeys: (table.foreignKeys ?? []).map((fk) => ({
6620
2353
  ...fk.name ? { name: fk.name } : {},
6621
2354
  columns: [...fk.columns],
6622
- references: { table: (0, import_analyzer2.qualifiedForeignTable)(fk), columns: [...fk.foreignColumns] },
2355
+ references: { table: (0, import_analyzer.qualifiedForeignTable)(fk), columns: [...fk.foreignColumns] },
6623
2356
  ...fk.onDelete ? { onDelete: fk.onDelete } : {},
6624
2357
  ...fk.onUpdate ? { onUpdate: fk.onUpdate } : {}
6625
2358
  })),
@@ -6633,10 +2366,10 @@ function summarize(analysis) {
6633
2366
  name: table.name,
6634
2367
  tsName: table.tsName,
6635
2368
  ...table.schema ? { schema: table.schema } : {},
6636
- qualified: (0, import_analyzer2.qualifiedTableName)(table),
2369
+ qualified: (0, import_analyzer.qualifiedTableName)(table),
6637
2370
  columns: table.columns.length,
6638
2371
  checks: table.checks?.length ?? 0,
6639
- gaps: gapsFor(table, (0, import_validation_core21.tableConstraints)(table).constraints, analysis.issues).length
2372
+ gaps: gapsFor(table, (0, import_validation_core4.tableConstraints)(table).constraints, analysis.issues).length
6640
2373
  }));
6641
2374
  }
6642
2375
  var WIDTH = 80;
@@ -6685,14 +2418,14 @@ function renderExplanation(explanation, context, style = PLAIN2) {
6685
2418
  const out = [];
6686
2419
  const plural = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
6687
2420
  out.push(style.bold(explanation.qualified) + style.dim(` ${context.schema}`));
6688
- const identity2 = [
2421
+ const identity = [
6689
2422
  context.dialect,
6690
2423
  `table "${explanation.name}"`,
6691
2424
  `export "${explanation.tsName}"`,
6692
2425
  plural(explanation.columns.length, "column")
6693
2426
  ];
6694
- if (explanation.readOnly) identity2.push("read-only, so no insert or update schema is emitted");
6695
- out.push(style.dim(" " + identity2.join(", ")));
2427
+ if (explanation.readOnly) identity.push("read-only, so no insert or update schema is emitted");
2428
+ out.push(style.dim(" " + identity.join(", ")));
6696
2429
  if (!explanation.matchedExactly) {
6697
2430
  out.push(style.dim(` matched on ${MATCH_LABELS[explanation.matchedOn]}, ignoring case`));
6698
2431
  }
@@ -7075,7 +2808,7 @@ var import_node_fs = require("fs");
7075
2808
  var import_node_path = __toESM(require("path"), 1);
7076
2809
  async function snapshotDir(dir) {
7077
2810
  const out = /* @__PURE__ */ new Map();
7078
- async function walk2(current) {
2811
+ async function walk(current) {
7079
2812
  let entries;
7080
2813
  try {
7081
2814
  entries = await import_node_fs.promises.readdir(current, { withFileTypes: true });
@@ -7084,11 +2817,11 @@ async function snapshotDir(dir) {
7084
2817
  }
7085
2818
  for (const e of entries) {
7086
2819
  const full = import_node_path.default.join(current, e.name);
7087
- if (e.isDirectory()) await walk2(full);
2820
+ if (e.isDirectory()) await walk(full);
7088
2821
  else out.set(import_node_path.default.relative(dir, full), await import_node_fs.promises.readFile(full, "utf8"));
7089
2822
  }
7090
2823
  }
7091
- await walk2(dir);
2824
+ await walk(dir);
7092
2825
  return out;
7093
2826
  }
7094
2827
  async function snapshotAll(dirs) {
@@ -7450,7 +3183,7 @@ function createRebuildScheduler(options) {
7450
3183
  }
7451
3184
 
7452
3185
  // src/init.ts
7453
- var import_analyzer3 = require("@drzl/analyzer");
3186
+ var import_analyzer2 = require("@drzl/analyzer");
7454
3187
  var fs5 = __toESM(require("fs"), 1);
7455
3188
  var path5 = __toESM(require("path"), 1);
7456
3189
  var INIT_GENERATOR_CHOICES = [
@@ -7489,7 +3222,7 @@ function schemaCandidates() {
7489
3222
  async function classifySchemaCandidate(target) {
7490
3223
  let analysis;
7491
3224
  try {
7492
- analysis = await new import_analyzer3.SchemaAnalyzer(target).analyze({
3225
+ analysis = await new import_analyzer2.SchemaAnalyzer(target).analyze({
7493
3226
  includeRelations: false,
7494
3227
  validateConstraints: false
7495
3228
  });
@@ -7692,7 +3425,7 @@ function normalizeGenerators(kinds) {
7692
3425
  for (const k of kinds) {
7693
3426
  if (!known.has(k)) {
7694
3427
  throw new Error(
7695
- `drzl init: "${k}" is not a generator init can scaffold. Choose from ${[...known].join(", ")}. Every other kind is documented in drzl.config; the route generators are optional dependencies and may not be installed.`
3428
+ `drzl init: "${k}" is not a generator init can scaffold. Choose from ${[...known].join(", ")}. Every other kind is installed and works; add it to drzl.config by hand, following the entry for it in the docs.`
7696
3429
  );
7697
3430
  }
7698
3431
  }
@@ -7952,7 +3685,7 @@ withOutputFlags(
7952
3685
  ).action(async (schema, opts) => {
7953
3686
  const out = outputFor(opts);
7954
3687
  try {
7955
- const analyzer = new import_analyzer4.SchemaAnalyzer(schema);
3688
+ const analyzer = new import_analyzer3.SchemaAnalyzer(schema);
7956
3689
  const spinner = out.spinner("Analyzing schema...");
7957
3690
  const start = Date.now();
7958
3691
  const res = await analyzer.analyze({
@@ -8003,7 +3736,7 @@ withOutputFlags(
8003
3736
  process.exit(EXIT_FAILED);
8004
3737
  return;
8005
3738
  }
8006
- const analyzer = new import_analyzer4.SchemaAnalyzer(target);
3739
+ const analyzer = new import_analyzer3.SchemaAnalyzer(target);
8007
3740
  const analysis = await analyzer.analyze({
8008
3741
  includeRelations: true,
8009
3742
  validateConstraints: true
@@ -8080,7 +3813,7 @@ withOutputFlags(
8080
3813
  }
8081
3814
  if (source.note) out.note(out.errStyle.gray(source.note));
8082
3815
  const spinner = out.spinner("Reading the schema...");
8083
- const analysis = await new import_analyzer4.SchemaAnalyzer(source.schema).analyze({
3816
+ const analysis = await new import_analyzer3.SchemaAnalyzer(source.schema).analyze({
8084
3817
  // Both on, for the reason `doctor` turns both on: this command's job is to say everything
8085
3818
  // that is known, and a relation that appears only under a flag is one a reader would be
8086
3819
  // told is absent.
@@ -8111,7 +3844,7 @@ withOutputFlags(
8111
3844
  let keptTables;
8112
3845
  let keptColumns;
8113
3846
  if (cfg) {
8114
- keptTables = filterTables(analysis.tables, cfg).map((t) => (0, import_analyzer4.qualifiedTableName)(t));
3847
+ keptTables = filterTables(analysis.tables, cfg).map((t) => (0, import_analyzer3.qualifiedTableName)(t));
8115
3848
  try {
8116
3849
  const narrowed = filterColumns(
8117
3850
  [match.table],
@@ -8206,7 +3939,7 @@ withOutputFlags(
8206
3939
  )
8207
3940
  );
8208
3941
  }
8209
- const analyzer = new import_analyzer4.SchemaAnalyzer(source.schema);
3942
+ const analyzer = new import_analyzer3.SchemaAnalyzer(source.schema);
8210
3943
  const spinner = out.spinner("Analyzing...");
8211
3944
  const t0 = Date.now();
8212
3945
  const analysis = await analyzer.analyze({
@@ -8421,7 +4154,7 @@ withOutputFlags(
8421
4154
  const out = outputFor(opts);
8422
4155
  out.warn(deprecationNotice("generate:orpc", "orpc", schema, cmd));
8423
4156
  try {
8424
- const analyzer = new import_analyzer4.SchemaAnalyzer(schema);
4157
+ const analyzer = new import_analyzer3.SchemaAnalyzer(schema);
8425
4158
  const analysis = await analyzer.analyze({
8426
4159
  includeRelations: !!opts.includeRelations,
8427
4160
  validateConstraints: true
@@ -8464,7 +4197,7 @@ withOutputFlags(
8464
4197
  const out = outputFor(opts);
8465
4198
  out.warn(deprecationNotice("generate:trpc", "trpc", schema, cmd));
8466
4199
  try {
8467
- const analyzer = new import_analyzer4.SchemaAnalyzer(schema);
4200
+ const analyzer = new import_analyzer3.SchemaAnalyzer(schema);
8468
4201
  const analysis = await analyzer.analyze({
8469
4202
  includeRelations: !!opts.includeRelations,
8470
4203
  validateConstraints: true
@@ -8645,7 +4378,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
8645
4378
  });
8646
4379
  }
8647
4380
  for (const w of source.warnings) out.warn(w);
8648
- const analyzer = new import_analyzer4.SchemaAnalyzer(source.schema);
4381
+ const analyzer = new import_analyzer3.SchemaAnalyzer(source.schema);
8649
4382
  const analysis = await analyzer.analyze({
8650
4383
  includeRelations: cfg.analyzer.includeRelations,
8651
4384
  validateConstraints: cfg.analyzer.validateConstraints,