@drzl/cli 4.14.3 → 4.15.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/{chunk-HGD5CBM5.js → chunk-FZNDWYBB.js} +44 -7
- package/dist/chunk-FZNDWYBB.js.map +1 -0
- package/dist/cli.cjs +796 -123
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +196 -71
- package/dist/cli.js.map +1 -1
- package/dist/config.cjs +46 -8
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +35 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +5 -3
- package/dist/dist-XBGVORL3.js +512 -0
- package/dist/dist-XBGVORL3.js.map +1 -0
- package/package.json +9 -8
- package/dist/chunk-HGD5CBM5.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -31,12 +31,525 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
mod
|
|
32
32
|
));
|
|
33
33
|
|
|
34
|
-
// ../generator-
|
|
34
|
+
// ../generator-trpc/dist/index.js
|
|
35
35
|
var dist_exports = {};
|
|
36
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.length === 1 && key[0].tsType === "number";
|
|
173
|
+
const keyArg = key && key.length === 1 ? `input.${key[0].name}` : "";
|
|
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_core2.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_core2.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_core2.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_core2.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, path6, opts) {
|
|
337
|
+
const baseSpec = (0, import_validation_core2.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_core2.importSpecifier)(
|
|
356
|
+
"./" + path6.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 serviceKeyNote(table) {
|
|
383
|
+
const cols = table.primaryKey?.columns ?? [];
|
|
384
|
+
const shape = cols.length > 1 ? `has a composite primary key (${cols.join(", ")})` : `has a non-numeric primary key (${cols[0]})`;
|
|
385
|
+
return `// ${table.name} ${shape}, and @drzl/generator-service types its key parameter as one number.
|
|
386
|
+
// Wire this to your own lookup.`;
|
|
387
|
+
}
|
|
388
|
+
function routerExportName(table, naming) {
|
|
389
|
+
const base = `${table.tsName}${naming?.routerSuffix ?? "Router"}`;
|
|
390
|
+
const c = naming?.procedureCase;
|
|
391
|
+
return toCase(base, c === "kebab" ? "camel" : c);
|
|
392
|
+
}
|
|
393
|
+
function serviceImportSpecifier(table, ctx, opts) {
|
|
394
|
+
const rel = relativePosix(ctx.out, ctx.services);
|
|
395
|
+
const dir = !rel ? "." : rel.startsWith(".") ? rel : `./${rel}`;
|
|
396
|
+
return (0, import_validation_core2.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
|
|
397
|
+
}
|
|
398
|
+
function relativePosix(from, to) {
|
|
399
|
+
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
400
|
+
const a = norm(from).split("/");
|
|
401
|
+
const b = norm(to).split("/");
|
|
402
|
+
let i = 0;
|
|
403
|
+
while (i < a.length && i < b.length && a[i] === b[i]) i++;
|
|
404
|
+
return [...Array.from({ length: a.length - i }, () => ".."), ...b.slice(i)].join("/");
|
|
405
|
+
}
|
|
406
|
+
function buildHeader(h) {
|
|
407
|
+
if (h && h.enabled === false) return "";
|
|
408
|
+
const text = h?.text?.trim();
|
|
409
|
+
const lines = text ? text.split(/\r?\n/).map((l) => `// ${l}`) : [
|
|
410
|
+
"// Generated by DRZL (@drzl/*)",
|
|
411
|
+
"// Generated output is granted to you under your project's license.",
|
|
412
|
+
"// You may use, copy, modify, and distribute without attribution."
|
|
413
|
+
];
|
|
414
|
+
return lines.join("\n") + "\n\n";
|
|
415
|
+
}
|
|
416
|
+
var import_validation_core2, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
|
|
417
|
+
var init_dist = __esm({
|
|
418
|
+
"../generator-trpc/dist/index.js"() {
|
|
419
|
+
"use strict";
|
|
420
|
+
import_validation_core2 = require("@drzl/validation-core");
|
|
421
|
+
TRPC_MAJOR = 11;
|
|
422
|
+
q = (v) => JSON.stringify(v);
|
|
423
|
+
LIB_IMPORTS = {
|
|
424
|
+
zod: "import { z } from 'zod';",
|
|
425
|
+
valibot: "import * as v from 'valibot';",
|
|
426
|
+
arktype: "import { type } from 'arktype';"
|
|
427
|
+
};
|
|
428
|
+
LIB_USAGE = {
|
|
429
|
+
zod: /\bz\./,
|
|
430
|
+
valibot: /\bv\./,
|
|
431
|
+
arktype: /\btype\(/
|
|
432
|
+
};
|
|
433
|
+
LIBS = {
|
|
434
|
+
zod: {
|
|
435
|
+
number: "z.number()",
|
|
436
|
+
string: "z.string()",
|
|
437
|
+
boolean: "z.boolean()",
|
|
438
|
+
date: "z.date()",
|
|
439
|
+
unknown: "z.unknown()",
|
|
440
|
+
tuple: (n) => `z.tuple([${Array.from({ length: n }, () => "z.number()").join(", ")}])`,
|
|
441
|
+
numberObject: (fields) => `z.object({ ${fields.map((f) => `${f}: z.number()`).join(", ")} })`,
|
|
442
|
+
enum: (vals) => `z.enum([${vals.map(q).join(", ")}] as const)`,
|
|
443
|
+
nullable: (b) => `${b}.nullable()`,
|
|
444
|
+
optional: (b) => `${b}.optional()`,
|
|
445
|
+
object: (body) => `z.object({
|
|
446
|
+
${body}
|
|
447
|
+
})`,
|
|
448
|
+
objectInline: (body) => `z.object({ ${body} })`,
|
|
449
|
+
partialUpdate: (s) => `${s}.partial()`,
|
|
450
|
+
arrayOf: (s) => `z.array(${s})`,
|
|
451
|
+
nullableOf: (s) => `${s}.nullable()`,
|
|
452
|
+
booleanSchema: "z.boolean()"
|
|
453
|
+
},
|
|
454
|
+
valibot: {
|
|
455
|
+
number: "v.number()",
|
|
456
|
+
string: "v.string()",
|
|
457
|
+
boolean: "v.boolean()",
|
|
458
|
+
date: "v.date()",
|
|
459
|
+
unknown: "v.unknown()",
|
|
460
|
+
tuple: (n) => `v.tuple([${Array.from({ length: n }, () => "v.number()").join(", ")}])`,
|
|
461
|
+
numberObject: (fields) => `v.object({ ${fields.map((f) => `${f}: v.number()`).join(", ")} })`,
|
|
462
|
+
enum: (vals) => `v.picklist([${vals.map(q).join(", ")}] as const)`,
|
|
463
|
+
nullable: (b) => `v.nullable(${b})`,
|
|
464
|
+
optional: (b) => `v.optional(${b})`,
|
|
465
|
+
object: (body) => `v.object({
|
|
466
|
+
${body}
|
|
467
|
+
})`,
|
|
468
|
+
objectInline: (body) => `v.object({ ${body} })`,
|
|
469
|
+
arrayOf: (s) => `v.array(${s})`,
|
|
470
|
+
nullableOf: (s) => `v.nullable(${s})`,
|
|
471
|
+
booleanSchema: "v.boolean()"
|
|
472
|
+
},
|
|
473
|
+
arktype: {
|
|
474
|
+
number: "number",
|
|
475
|
+
string: "string",
|
|
476
|
+
boolean: "boolean",
|
|
477
|
+
date: "Date",
|
|
478
|
+
unknown: "unknown",
|
|
479
|
+
// The surrounding encode adds the quotes, so the union is built with the inner quoting
|
|
480
|
+
// ArkType expects. Emitting `'${...}'` here produces `''admin' | 'user''`, which does not parse.
|
|
481
|
+
enum: (vals) => vals.map((x) => `'${x.replace(/'/g, "\\'")}'`).join(" | "),
|
|
482
|
+
nullable: (b) => `(${b} | null)`,
|
|
483
|
+
optional: (b) => `${b}?`,
|
|
484
|
+
object: (body) => `type({
|
|
485
|
+
${body}
|
|
486
|
+
})`,
|
|
487
|
+
objectInline: (body) => `type({ ${body} })`,
|
|
488
|
+
fieldIsString: true,
|
|
489
|
+
arrayOf: (s) => `${s}.array()`,
|
|
490
|
+
nullableOf: (s) => `${s}.or('null')`,
|
|
491
|
+
booleanSchema: `type('boolean')`
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
495
|
+
singularize = (s) => s.endsWith("ies") ? s.slice(0, -3) + "y" : s.endsWith("s") ? s.slice(0, -1) : s;
|
|
496
|
+
isIdent = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
|
|
497
|
+
BASE_MODULE = "trpc";
|
|
498
|
+
TRPCGenerator = class {
|
|
499
|
+
constructor(analysis) {
|
|
500
|
+
this.analysis = analysis;
|
|
501
|
+
}
|
|
502
|
+
async generate(opts) {
|
|
503
|
+
const fs3 = await import("fs/promises");
|
|
504
|
+
const path6 = await import("path");
|
|
505
|
+
const out = path6.resolve(process.cwd(), opts.outputDir);
|
|
506
|
+
const ctx = {
|
|
507
|
+
out,
|
|
508
|
+
services: path6.resolve(process.cwd(), opts.servicesDir ?? "src/services")
|
|
509
|
+
};
|
|
510
|
+
await fs3.mkdir(out, { recursive: true });
|
|
511
|
+
const files = [];
|
|
512
|
+
const write = async (filePath, content) => {
|
|
513
|
+
const formatted = await (0, import_validation_core2.formatCode)(
|
|
514
|
+
buildHeader(opts.outputHeader) + content,
|
|
515
|
+
filePath,
|
|
516
|
+
opts.format
|
|
517
|
+
);
|
|
518
|
+
await fs3.writeFile(filePath, formatted, "utf8");
|
|
519
|
+
files.push(filePath);
|
|
520
|
+
};
|
|
521
|
+
const basePath = path6.join(out, `${BASE_MODULE}.ts`);
|
|
522
|
+
await write(basePath, renderBase(opts));
|
|
523
|
+
const routers = [];
|
|
524
|
+
const total = this.analysis.tables.length;
|
|
525
|
+
let index = 0;
|
|
526
|
+
for (const table of this.analysis.tables) {
|
|
527
|
+
const base = `${table.tsName}${opts.naming?.routerSuffix ?? ""}`;
|
|
528
|
+
const filePath = path6.join(out, `${toCase(base, opts.naming?.procedureCase)}.ts`);
|
|
529
|
+
if (filePath === basePath) {
|
|
530
|
+
throw new Error(
|
|
531
|
+
`@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.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
await write(filePath, renderRouter(table, opts, ctx));
|
|
535
|
+
routers.push({ table, filePath, exportName: routerExportName(table, opts.naming) });
|
|
536
|
+
index++;
|
|
537
|
+
opts.onProgress?.({ index, total, table: table.name, filePath });
|
|
538
|
+
}
|
|
539
|
+
await write(path6.join(out, "index.ts"), renderBarrel(routers, ctx, path6, opts));
|
|
540
|
+
return { files };
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
index_default = TRPCGenerator;
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// ../generator-json-schema/dist/index.js
|
|
548
|
+
var dist_exports2 = {};
|
|
549
|
+
__export(dist_exports2, {
|
|
37
550
|
JsonSchemaGenerator: () => JsonSchemaGenerator,
|
|
38
551
|
componentsDocument: () => componentsDocument,
|
|
39
|
-
default: () =>
|
|
552
|
+
default: () => index_default2,
|
|
40
553
|
tableSchemas: () => tableSchemas
|
|
41
554
|
});
|
|
42
555
|
function baseSchema(c, mode, target, checks, sets, lengths) {
|
|
@@ -88,14 +601,14 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
|
|
|
88
601
|
case "string": {
|
|
89
602
|
const out = { type: "string" };
|
|
90
603
|
if (c.format === "uuid") out.format = UUID_FORMAT;
|
|
91
|
-
else if (c.format &&
|
|
604
|
+
else if (c.format && import_validation_core3.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core3.COLUMN_FORMATS[c.format];
|
|
92
605
|
if (c.maxLength !== void 0) out.maxLength = c.maxLength;
|
|
93
606
|
applyByteCap(out, c);
|
|
94
607
|
applyLengths(out, c, lengths);
|
|
95
608
|
return out;
|
|
96
609
|
}
|
|
97
610
|
case "number": {
|
|
98
|
-
const out = { type: (0,
|
|
611
|
+
const out = { type: (0, import_validation_core3.isIntegerColumn)(c) ? "integer" : "number" };
|
|
99
612
|
if (!c.arrayDimensions) applyNumericBounds(out, c, checks, target);
|
|
100
613
|
return out;
|
|
101
614
|
}
|
|
@@ -232,7 +745,7 @@ function tableSchema(table, cols, mode, target, applyDefaults, parsed) {
|
|
|
232
745
|
};
|
|
233
746
|
}
|
|
234
747
|
function collect(table) {
|
|
235
|
-
const parsed = (table.checks ?? []).map((k) => (0,
|
|
748
|
+
const parsed = (table.checks ?? []).map((k) => (0, import_validation_core3.parseCheck)(k.expression, k.name));
|
|
236
749
|
return {
|
|
237
750
|
checks: parsed.flatMap((p) => p.ok ? p.checks : []),
|
|
238
751
|
sets: parsed.flatMap((p) => p.ok ? p.sets ?? [] : []),
|
|
@@ -246,9 +759,9 @@ function tableSchemas(table, opts = {}) {
|
|
|
246
759
|
const parsed = collect(table);
|
|
247
760
|
const build = (cols, mode) => tableSchema(table, cols, mode, target, !!opts.applyDefaults, parsed);
|
|
248
761
|
return {
|
|
249
|
-
insert: build((0,
|
|
250
|
-
update: build((0,
|
|
251
|
-
select: build((0,
|
|
762
|
+
insert: build((0, import_validation_core3.insertColumns)(table), "insert"),
|
|
763
|
+
update: build((0, import_validation_core3.updateColumns)(table), "update"),
|
|
764
|
+
select: build((0, import_validation_core3.selectColumns)(table), "select")
|
|
252
765
|
};
|
|
253
766
|
}
|
|
254
767
|
function componentsDocument(tables, opts = {}) {
|
|
@@ -266,23 +779,23 @@ function componentsDocument(tables, opts = {}) {
|
|
|
266
779
|
function renderTableModule(table, affix, target, applyDefaults) {
|
|
267
780
|
const T = table.tsName;
|
|
268
781
|
const schemas = tableSchemas(table, { target, applyDefaults });
|
|
269
|
-
const decl = (mode) => `export const ${(0,
|
|
782
|
+
const decl = (mode) => `export const ${(0, import_validation_core3.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
|
|
270
783
|
|
|
271
|
-
export type ${(0,
|
|
784
|
+
export type ${(0, import_validation_core3.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core3.schemaName)(mode, T, affix)};`;
|
|
272
785
|
return [decl("insert"), decl("update"), decl("select")].join("\n\n") + "\n";
|
|
273
786
|
}
|
|
274
|
-
function
|
|
787
|
+
function buildHeader2(h) {
|
|
275
788
|
if (h?.enabled === false) return "";
|
|
276
789
|
const text = h?.text ?? "// Generated by DRZL. Do not edit by hand.";
|
|
277
790
|
return `${text}
|
|
278
791
|
|
|
279
792
|
`;
|
|
280
793
|
}
|
|
281
|
-
var
|
|
282
|
-
var
|
|
794
|
+
var import_validation_core3, DEFAULT_FILE_SUFFIX, DRAFT, UUID_FORMAT, JsonSchemaGenerator, index_default2;
|
|
795
|
+
var init_dist2 = __esm({
|
|
283
796
|
"../generator-json-schema/dist/index.js"() {
|
|
284
797
|
"use strict";
|
|
285
|
-
|
|
798
|
+
import_validation_core3 = require("@drzl/validation-core");
|
|
286
799
|
DEFAULT_FILE_SUFFIX = ".schema.ts";
|
|
287
800
|
DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
288
801
|
UUID_FORMAT = "uuid";
|
|
@@ -297,14 +810,14 @@ var init_dist = __esm({
|
|
|
297
810
|
const out = path6.resolve(process.cwd(), opts.outDir);
|
|
298
811
|
const files = [];
|
|
299
812
|
await fs3.mkdir(out, { recursive: true });
|
|
300
|
-
const affix = (0,
|
|
813
|
+
const affix = (0, import_validation_core3.resolveAffix)(opts);
|
|
301
814
|
const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX;
|
|
302
815
|
const target = opts.target ?? "draft-2020-12";
|
|
303
816
|
for (const table of this.analysis.tables) {
|
|
304
|
-
const filePath = path6.join(out, (0,
|
|
817
|
+
const filePath = path6.join(out, (0, import_validation_core3.moduleFileName)(table.tsName, fileSuffix));
|
|
305
818
|
const code = renderTableModule(table, affix, target, !!opts.applyDefaults);
|
|
306
|
-
const formatted = await (0,
|
|
307
|
-
|
|
819
|
+
const formatted = await (0, import_validation_core3.formatCode)(
|
|
820
|
+
buildHeader2(opts.outputHeader) + code,
|
|
308
821
|
filePath,
|
|
309
822
|
opts.format
|
|
310
823
|
);
|
|
@@ -321,17 +834,17 @@ var init_dist = __esm({
|
|
|
321
834
|
`;
|
|
322
835
|
await fs3.writeFile(
|
|
323
836
|
componentsPath,
|
|
324
|
-
await (0,
|
|
837
|
+
await (0, import_validation_core3.formatCode)(buildHeader2(opts.outputHeader) + code, componentsPath, opts.format),
|
|
325
838
|
"utf8"
|
|
326
839
|
);
|
|
327
840
|
files.push(componentsPath);
|
|
328
841
|
}
|
|
329
842
|
const indexPath = path6.join(out, "index.ts");
|
|
330
|
-
const index = this.analysis.tables.map((t) => `export * from '${(0,
|
|
843
|
+
const index = this.analysis.tables.map((t) => `export * from '${(0, import_validation_core3.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`).concat(
|
|
331
844
|
opts.components ? [`export * from './components${opts.importExtension === "none" ? "" : ".js"}';`] : []
|
|
332
845
|
).join("\n") + "\n";
|
|
333
|
-
const indexFormatted = await (0,
|
|
334
|
-
|
|
846
|
+
const indexFormatted = await (0, import_validation_core3.formatCode)(
|
|
847
|
+
buildHeader2(opts.outputHeader) + index,
|
|
335
848
|
indexPath,
|
|
336
849
|
opts.format
|
|
337
850
|
);
|
|
@@ -342,13 +855,13 @@ var init_dist = __esm({
|
|
|
342
855
|
renderTable(table, opts) {
|
|
343
856
|
return renderTableModule(
|
|
344
857
|
table,
|
|
345
|
-
(0,
|
|
858
|
+
(0, import_validation_core3.resolveAffix)(opts),
|
|
346
859
|
opts?.target ?? "draft-2020-12",
|
|
347
860
|
!!opts?.applyDefaults
|
|
348
861
|
);
|
|
349
862
|
}
|
|
350
863
|
};
|
|
351
|
-
|
|
864
|
+
index_default2 = JsonSchemaGenerator;
|
|
352
865
|
}
|
|
353
866
|
});
|
|
354
867
|
|
|
@@ -362,30 +875,6 @@ var import_commander = require("commander");
|
|
|
362
875
|
var path5 = __toESM(require("path"), 1);
|
|
363
876
|
var import_ora = __toESM(require("ora"), 1);
|
|
364
877
|
|
|
365
|
-
// src/validation-options.ts
|
|
366
|
-
function validationOptions(g, cfg, outDir, caps = {}) {
|
|
367
|
-
return {
|
|
368
|
-
outDir,
|
|
369
|
-
outputHeader: g.outputHeader,
|
|
370
|
-
format: g.format,
|
|
371
|
-
schemaSuffix: g.schemaSuffix,
|
|
372
|
-
fileSuffix: g.fileSuffix,
|
|
373
|
-
importExtension: g.importExtension,
|
|
374
|
-
affix: g.affix,
|
|
375
|
-
coerceDates: g.coerceDates,
|
|
376
|
-
applyDefaults: g.applyDefaults,
|
|
377
|
-
duplicateFinder: g.duplicateFinder,
|
|
378
|
-
// Only where the generator can act on them, so an unsupported option is absent rather than
|
|
379
|
-
// present and ignored.
|
|
380
|
-
...caps.schemaTypes ? {
|
|
381
|
-
// Needed by both: the reference is resolved relative to the emitted file.
|
|
382
|
-
schemaPath: cfg.schema,
|
|
383
|
-
typedJson: g.typedJson,
|
|
384
|
-
typedColumns: g.typedColumns
|
|
385
|
-
} : {}
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
878
|
// src/config.ts
|
|
390
879
|
var import_validation_core = require("@drzl/validation-core");
|
|
391
880
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -425,7 +914,7 @@ var AffixSchema = import_zod.z.object({
|
|
|
425
914
|
}).strict();
|
|
426
915
|
var ImportExtensionSchema = import_zod.z.enum(import_validation_core.IMPORT_EXTENSIONS);
|
|
427
916
|
var GeneratorSchema = import_zod.z.object({
|
|
428
|
-
kind: import_zod.z.enum(["orpc", "service", "zod", "valibot", "arktype", "typebox", "json-schema"]),
|
|
917
|
+
kind: import_zod.z.enum(["orpc", "trpc", "service", "zod", "valibot", "arktype", "typebox", "json-schema"]),
|
|
429
918
|
/**
|
|
430
919
|
* Overrides the top-level `importExtension` for this generator alone, for a project whose
|
|
431
920
|
* generated directories are compiled by different tsconfigs.
|
|
@@ -496,7 +985,22 @@ var GeneratorSchema = import_zod.z.object({
|
|
|
496
985
|
* Omitting it reproduces the output of every previous release exactly.
|
|
497
986
|
*/
|
|
498
987
|
affix: AffixSchema.optional(),
|
|
499
|
-
|
|
988
|
+
/**
|
|
989
|
+
* How the router generators reach a database handle: through the request context, rather than
|
|
990
|
+
* through a module-level import in the service layer.
|
|
991
|
+
*
|
|
992
|
+
* Documented on the oRPC generator since it was added and, until now, absent from this schema
|
|
993
|
+
* entirely. `GeneratorSchema` is not strict, so zod stripped the key without a word and the
|
|
994
|
+
* option did nothing at all when set from a config file. It was only ever reachable by calling
|
|
995
|
+
* the generator's API directly.
|
|
996
|
+
*/
|
|
997
|
+
databaseInjection: import_zod.z.object({
|
|
998
|
+
enabled: import_zod.z.boolean().optional(),
|
|
999
|
+
/** The type annotation for the injected handle, e.g. `DrizzleD1Database`. */
|
|
1000
|
+
databaseType: import_zod.z.string().optional(),
|
|
1001
|
+
databaseTypeImport: import_zod.z.object({ name: import_zod.z.string(), from: import_zod.z.string() }).optional()
|
|
1002
|
+
}).optional(),
|
|
1003
|
+
// router validation sharing (orpc, trpc)
|
|
500
1004
|
validation: import_zod.z.object({
|
|
501
1005
|
useShared: import_zod.z.boolean().default(false).optional(),
|
|
502
1006
|
library: import_zod.z.enum(["zod", "valibot", "arktype"]).default("zod").optional(),
|
|
@@ -568,6 +1072,10 @@ var ConfigSchema = import_zod.z.object({
|
|
|
568
1072
|
);
|
|
569
1073
|
});
|
|
570
1074
|
});
|
|
1075
|
+
var ROUTER_KINDS = /* @__PURE__ */ new Set(["orpc", "trpc"]);
|
|
1076
|
+
function trpcOutDir(g, cfg) {
|
|
1077
|
+
return g.path ?? cfg.outDir;
|
|
1078
|
+
}
|
|
571
1079
|
function sharedSchemaNames(opts) {
|
|
572
1080
|
const resolved = (0, import_validation_core.resolveAffix)(opts);
|
|
573
1081
|
return import_validation_core.NAME_MODES.map((mode) => (0, import_validation_core.schemaName)(mode, import_validation_core.AFFIX_PROBE_TABLE, resolved));
|
|
@@ -579,7 +1087,23 @@ function resolveConfig(cfg) {
|
|
|
579
1087
|
importExtension: g.importExtension ?? cfg.importExtension
|
|
580
1088
|
}));
|
|
581
1089
|
for (const g of generators) {
|
|
582
|
-
if (g.kind
|
|
1090
|
+
if (!ROUTER_KINDS.has(g.kind)) continue;
|
|
1091
|
+
if (g.databaseInjection?.enabled) {
|
|
1092
|
+
for (const s of generators.filter((x) => x.kind === "service")) {
|
|
1093
|
+
if (!s.databaseInjection) {
|
|
1094
|
+
s.databaseInjection = g.databaseInjection;
|
|
1095
|
+
} else if (!s.databaseInjection.enabled) {
|
|
1096
|
+
warnings.push(
|
|
1097
|
+
`drzl config: the "${g.kind}" generator sets databaseInjection.enabled while the "service" generator sets it to false. The router will call Service.method(ctx.db, ...) against services that take no database parameter, so the generated project will not compile. Set both, or neither.`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
if ((s.dataAccess ?? "stub") === "stub") {
|
|
1101
|
+
warnings.push(
|
|
1102
|
+
`drzl config: the "${g.kind}" generator sets databaseInjection.enabled, so its handlers call Service.method(ctx.db, ...). The "service" generator emits stub bodies, which take no database parameter whatever this option says, so those calls will not compile. Set dataAccess: 'drizzle' on the "service" generator, or drop databaseInjection.`
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
583
1107
|
const v = g.validation;
|
|
584
1108
|
if (!v?.useShared) continue;
|
|
585
1109
|
const library = v.library ?? "zod";
|
|
@@ -604,7 +1128,7 @@ function resolveConfig(cfg) {
|
|
|
604
1128
|
const mine2 = sharedSchemaNames({ schemaSuffix: v.schemaSuffix });
|
|
605
1129
|
if (mine2.join(",") !== theirs.join(",")) {
|
|
606
1130
|
warnings.push(
|
|
607
|
-
`drzl config: the "
|
|
1131
|
+
`drzl config: the "${g.kind}" generator's validation.schemaSuffix (${JSON.stringify(v.schemaSuffix ?? "Schema")}) does not match the "${library}" generator's schemaSuffix (${JSON.stringify(sibling.schemaSuffix ?? "Schema")}). The router will import ${mine2.join(", ")} but the "${library}" generator exports ${theirs.join(", ")}, so the generated router will not compile. Set both to the same value, or move to "affix", which is inherited automatically.`
|
|
608
1132
|
);
|
|
609
1133
|
}
|
|
610
1134
|
continue;
|
|
@@ -615,7 +1139,7 @@ function resolveConfig(cfg) {
|
|
|
615
1139
|
});
|
|
616
1140
|
if (mine.join(",") !== theirs.join(",")) {
|
|
617
1141
|
throw new Error(
|
|
618
|
-
`drzl config: the "
|
|
1142
|
+
`drzl config: the "${g.kind}" generator imports shared ${library} schemas, but its validation.affix disagrees with the "${library}" generator's own naming. The router would import ${mine.join(", ")} while the "${library}" generator exports ${theirs.join(", ")}. Make them match, or drop validation.affix and let it be inherited from the "${library}" generator.`
|
|
619
1143
|
);
|
|
620
1144
|
}
|
|
621
1145
|
}
|
|
@@ -673,6 +1197,7 @@ function computeGeneratorOutputDirs(cfg, cwd = process.cwd()) {
|
|
|
673
1197
|
const dirs = /* @__PURE__ */ new Set();
|
|
674
1198
|
dirs.add(abs(cfg.outDir));
|
|
675
1199
|
for (const g of cfg.generators) {
|
|
1200
|
+
if (g.kind === "trpc") dirs.add(abs(trpcOutDir(g, cfg)));
|
|
676
1201
|
if (g.kind === "service") dirs.add(abs(g.path ?? "src/services"));
|
|
677
1202
|
if (g.kind === "zod") dirs.add(abs(g.path ?? "src/validators/zod"));
|
|
678
1203
|
if (g.kind === "valibot") dirs.add(abs(g.path ?? "src/validators/valibot"));
|
|
@@ -689,7 +1214,7 @@ function resolveTemplateDirsSync(cfg, cwd = process.cwd()) {
|
|
|
689
1214
|
);
|
|
690
1215
|
for (const g of cfg.generators) {
|
|
691
1216
|
const t = g.template;
|
|
692
|
-
if (!t || t === "standard" || t === "minimal") continue;
|
|
1217
|
+
if (!t || t === "standard" || t === "minimal" || t === "service") continue;
|
|
693
1218
|
let pkgDir = null;
|
|
694
1219
|
try {
|
|
695
1220
|
const pkg = req.resolve(`${t}/package.json`, { paths: [cwd] });
|
|
@@ -731,6 +1256,49 @@ function computeWatchTargets(cfg, cwd = process.cwd()) {
|
|
|
731
1256
|
return [...targets];
|
|
732
1257
|
}
|
|
733
1258
|
|
|
1259
|
+
// src/trpc-options.ts
|
|
1260
|
+
function trpcOptions(g, cfg, servicesDir) {
|
|
1261
|
+
return {
|
|
1262
|
+
outputDir: trpcOutDir(g, cfg),
|
|
1263
|
+
template: g.template,
|
|
1264
|
+
includeRelations: g.includeRelations,
|
|
1265
|
+
naming: g.naming,
|
|
1266
|
+
outputHeader: g.outputHeader,
|
|
1267
|
+
format: g.format,
|
|
1268
|
+
importExtension: g.importExtension,
|
|
1269
|
+
validation: g.validation,
|
|
1270
|
+
databaseInjection: g.databaseInjection,
|
|
1271
|
+
// Where the service generator is actually writing, so `template: 'service'` emits an import
|
|
1272
|
+
// of a module that exists. The generator defaults this to `src/services`, which is right only
|
|
1273
|
+
// by coincidence for a config that puts them elsewhere.
|
|
1274
|
+
servicesDir
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/validation-options.ts
|
|
1279
|
+
function validationOptions(g, cfg, outDir, caps = {}) {
|
|
1280
|
+
return {
|
|
1281
|
+
outDir,
|
|
1282
|
+
outputHeader: g.outputHeader,
|
|
1283
|
+
format: g.format,
|
|
1284
|
+
schemaSuffix: g.schemaSuffix,
|
|
1285
|
+
fileSuffix: g.fileSuffix,
|
|
1286
|
+
importExtension: g.importExtension,
|
|
1287
|
+
affix: g.affix,
|
|
1288
|
+
coerceDates: g.coerceDates,
|
|
1289
|
+
applyDefaults: g.applyDefaults,
|
|
1290
|
+
duplicateFinder: g.duplicateFinder,
|
|
1291
|
+
// Only where the generator can act on them, so an unsupported option is absent rather than
|
|
1292
|
+
// present and ignored.
|
|
1293
|
+
...caps.schemaTypes ? {
|
|
1294
|
+
// Needed by both: the reference is resolved relative to the emitted file.
|
|
1295
|
+
schemaPath: cfg.schema,
|
|
1296
|
+
typedJson: g.typedJson,
|
|
1297
|
+
typedColumns: g.typedColumns
|
|
1298
|
+
} : {}
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
|
|
734
1302
|
// src/drift.ts
|
|
735
1303
|
var import_node_fs = require("fs");
|
|
736
1304
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -782,6 +1350,30 @@ async function restoreSnapshot(before, after) {
|
|
|
782
1350
|
}
|
|
783
1351
|
}
|
|
784
1352
|
|
|
1353
|
+
// src/generator-loader.ts
|
|
1354
|
+
var GeneratorNotInstalledError = class extends Error {
|
|
1355
|
+
constructor(specifier, reason) {
|
|
1356
|
+
super(`${specifier} is not installed`);
|
|
1357
|
+
this.specifier = specifier;
|
|
1358
|
+
this.reason = reason;
|
|
1359
|
+
this.name = "GeneratorNotInstalledError";
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
function isPackageMissing(err, specifier) {
|
|
1363
|
+
const code = err?.code;
|
|
1364
|
+
if (code !== "ERR_MODULE_NOT_FOUND") return false;
|
|
1365
|
+
const message = err?.message;
|
|
1366
|
+
return typeof message === "string" && message.includes(`'${specifier}'`);
|
|
1367
|
+
}
|
|
1368
|
+
async function loadGenerator(specifier, load) {
|
|
1369
|
+
try {
|
|
1370
|
+
return await load();
|
|
1371
|
+
} catch (e) {
|
|
1372
|
+
if (isPackageMissing(e, specifier)) throw new GeneratorNotInstalledError(specifier, e);
|
|
1373
|
+
throw e;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
785
1377
|
// src/sponsor.ts
|
|
786
1378
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
787
1379
|
var import_node_fs2 = require("fs");
|
|
@@ -885,6 +1477,17 @@ function readCliVersion() {
|
|
|
885
1477
|
var CLI_VERSION = readCliVersion();
|
|
886
1478
|
|
|
887
1479
|
// src/cli.ts
|
|
1480
|
+
function reportGeneratorFailure(kind, e) {
|
|
1481
|
+
if (e instanceof GeneratorNotInstalledError) {
|
|
1482
|
+
console.error(
|
|
1483
|
+
import_chalk2.default.red(`The ${kind} generator is not installed.`),
|
|
1484
|
+
import_chalk2.default.yellow(`
|
|
1485
|
+
Install with: npm install ${e.specifier}`)
|
|
1486
|
+
);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
console.error(import_chalk2.default.red(`The ${kind} generator failed:`), e?.message ?? e);
|
|
1490
|
+
}
|
|
888
1491
|
var program = new import_commander.Command();
|
|
889
1492
|
program.name("drzl").description("DRZL - Drizzle Developer Toolkit").version(CLI_VERSION);
|
|
890
1493
|
program.addHelpText(
|
|
@@ -975,15 +1578,40 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
975
1578
|
templateOptions: g.templateOptions,
|
|
976
1579
|
importExtension: g.importExtension,
|
|
977
1580
|
validation: g.validation,
|
|
1581
|
+
// Documented on this generator since it was added and never reachable from a config
|
|
1582
|
+
// file, because the config schema had no such key and zod stripped it in silence.
|
|
1583
|
+
databaseInjection: g.databaseInjection,
|
|
978
1584
|
servicesDir,
|
|
979
1585
|
onProgress: ({ index }) => progress.update(index)
|
|
980
1586
|
});
|
|
981
1587
|
progress.stop();
|
|
982
1588
|
(0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (${g.kind}): ${files.length} files`));
|
|
983
1589
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1590
|
+
} else if (g.kind === "trpc") {
|
|
1591
|
+
try {
|
|
1592
|
+
const { TRPCGenerator: TRPCGenerator2 } = await loadGenerator(
|
|
1593
|
+
"@drzl/generator-trpc",
|
|
1594
|
+
() => Promise.resolve().then(() => (init_dist(), dist_exports))
|
|
1595
|
+
);
|
|
1596
|
+
const gen = new TRPCGenerator2(analysis);
|
|
1597
|
+
const { files } = await gen.generate({
|
|
1598
|
+
...trpcOptions(g, cfg, servicesDir),
|
|
1599
|
+
onProgress: ({ index }) => progress.update(index)
|
|
1600
|
+
});
|
|
1601
|
+
progress.stop();
|
|
1602
|
+
(0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (trpc): ${files.length} files`));
|
|
1603
|
+
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
progress.stop();
|
|
1606
|
+
reportGeneratorFailure(g.kind, e);
|
|
1607
|
+
process.exit(1);
|
|
1608
|
+
}
|
|
984
1609
|
} else if (g.kind === "service") {
|
|
985
1610
|
try {
|
|
986
|
-
const { ServiceGenerator } = await
|
|
1611
|
+
const { ServiceGenerator } = await loadGenerator(
|
|
1612
|
+
"@drzl/generator-service",
|
|
1613
|
+
() => import("@drzl/generator-service")
|
|
1614
|
+
);
|
|
987
1615
|
const gen = new ServiceGenerator(analysis);
|
|
988
1616
|
const target = g.path ?? "src/services";
|
|
989
1617
|
const files = await gen.generate({
|
|
@@ -993,23 +1621,27 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
993
1621
|
dataAccess: g.dataAccess,
|
|
994
1622
|
dbImportPath: g.dbImportPath,
|
|
995
1623
|
schemaImportPath: g.schemaImportPath,
|
|
996
|
-
importExtension: g.importExtension
|
|
1624
|
+
importExtension: g.importExtension,
|
|
1625
|
+
// The other half of `databaseInjection`. A router generator in injection mode
|
|
1626
|
+
// emits `Service.getById(ctx.db, id)`, and only a service generated in the same
|
|
1627
|
+
// mode has a `db` parameter to receive it. This branch never passed the option, so
|
|
1628
|
+
// the two halves of one generated project disagreed about the signature.
|
|
1629
|
+
databaseInjection: g.databaseInjection
|
|
997
1630
|
});
|
|
998
1631
|
progress.stop();
|
|
999
1632
|
(0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (service): ${files.length} files`));
|
|
1000
1633
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1001
1634
|
} catch (e) {
|
|
1002
1635
|
progress.stop();
|
|
1003
|
-
|
|
1004
|
-
import_chalk2.default.red("Service generator missing."),
|
|
1005
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-service")
|
|
1006
|
-
);
|
|
1007
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1636
|
+
reportGeneratorFailure(g.kind, e);
|
|
1008
1637
|
process.exit(1);
|
|
1009
1638
|
}
|
|
1010
1639
|
} else if (g.kind === "zod") {
|
|
1011
1640
|
try {
|
|
1012
|
-
const { ZodGenerator } = await
|
|
1641
|
+
const { ZodGenerator } = await loadGenerator(
|
|
1642
|
+
"@drzl/generator-zod",
|
|
1643
|
+
() => import("@drzl/generator-zod")
|
|
1644
|
+
);
|
|
1013
1645
|
const gen = new ZodGenerator(analysis);
|
|
1014
1646
|
const target = g.path ?? "src/validators/zod";
|
|
1015
1647
|
const files = await gen.generate(
|
|
@@ -1020,16 +1652,15 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1020
1652
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1021
1653
|
} catch (e) {
|
|
1022
1654
|
progress.stop();
|
|
1023
|
-
|
|
1024
|
-
import_chalk2.default.red("Zod generator missing."),
|
|
1025
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-zod")
|
|
1026
|
-
);
|
|
1027
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1655
|
+
reportGeneratorFailure(g.kind, e);
|
|
1028
1656
|
process.exit(1);
|
|
1029
1657
|
}
|
|
1030
1658
|
} else if (g.kind === "valibot") {
|
|
1031
1659
|
try {
|
|
1032
|
-
const { ValibotGenerator } = await
|
|
1660
|
+
const { ValibotGenerator } = await loadGenerator(
|
|
1661
|
+
"@drzl/generator-valibot",
|
|
1662
|
+
() => import("@drzl/generator-valibot")
|
|
1663
|
+
);
|
|
1033
1664
|
const gen = new ValibotGenerator(analysis);
|
|
1034
1665
|
const target = g.path ?? "src/validators/valibot";
|
|
1035
1666
|
const files = await gen.generate(
|
|
@@ -1040,16 +1671,15 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1040
1671
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1041
1672
|
} catch (e) {
|
|
1042
1673
|
progress.stop();
|
|
1043
|
-
|
|
1044
|
-
import_chalk2.default.red("Valibot generator missing."),
|
|
1045
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-valibot")
|
|
1046
|
-
);
|
|
1047
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1674
|
+
reportGeneratorFailure(g.kind, e);
|
|
1048
1675
|
process.exit(1);
|
|
1049
1676
|
}
|
|
1050
1677
|
} else if (g.kind === "arktype") {
|
|
1051
1678
|
try {
|
|
1052
|
-
const { ArkTypeGenerator } = await
|
|
1679
|
+
const { ArkTypeGenerator } = await loadGenerator(
|
|
1680
|
+
"@drzl/generator-arktype",
|
|
1681
|
+
() => import("@drzl/generator-arktype")
|
|
1682
|
+
);
|
|
1053
1683
|
const gen = new ArkTypeGenerator(analysis);
|
|
1054
1684
|
const target = g.path ?? "src/validators/arktype";
|
|
1055
1685
|
const files = await gen.generate(
|
|
@@ -1060,16 +1690,15 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1060
1690
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1061
1691
|
} catch (e) {
|
|
1062
1692
|
progress.stop();
|
|
1063
|
-
|
|
1064
|
-
import_chalk2.default.red("ArkType generator missing."),
|
|
1065
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-arktype")
|
|
1066
|
-
);
|
|
1067
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1693
|
+
reportGeneratorFailure(g.kind, e);
|
|
1068
1694
|
process.exit(1);
|
|
1069
1695
|
}
|
|
1070
1696
|
} else if (g.kind === "json-schema") {
|
|
1071
1697
|
try {
|
|
1072
|
-
const { JsonSchemaGenerator: JsonSchemaGenerator2 } = await
|
|
1698
|
+
const { JsonSchemaGenerator: JsonSchemaGenerator2 } = await loadGenerator(
|
|
1699
|
+
"@drzl/generator-json-schema",
|
|
1700
|
+
() => Promise.resolve().then(() => (init_dist2(), dist_exports2))
|
|
1701
|
+
);
|
|
1073
1702
|
const gen = new JsonSchemaGenerator2(analysis);
|
|
1074
1703
|
const target = g.path ?? "src/validators/json-schema";
|
|
1075
1704
|
const files = await gen.generate({
|
|
@@ -1083,20 +1712,15 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1083
1712
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1084
1713
|
} catch (e) {
|
|
1085
1714
|
progress.stop();
|
|
1086
|
-
|
|
1087
|
-
import_chalk2.default.red("JSON Schema generator missing."),
|
|
1088
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-json-schema"),
|
|
1089
|
-
// An optional dependency, unlike the other generators, until its npm trusted
|
|
1090
|
-
// publisher exists. A missing optional dependency is skipped rather than failing
|
|
1091
|
-
// the install, which is what keeps `npm i @drzl/cli` working meanwhile.
|
|
1092
|
-
""
|
|
1093
|
-
);
|
|
1094
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1715
|
+
reportGeneratorFailure(g.kind, e);
|
|
1095
1716
|
process.exit(1);
|
|
1096
1717
|
}
|
|
1097
1718
|
} else if (g.kind === "typebox") {
|
|
1098
1719
|
try {
|
|
1099
|
-
const { TypeBoxGenerator } = await
|
|
1720
|
+
const { TypeBoxGenerator } = await loadGenerator(
|
|
1721
|
+
"@drzl/generator-typebox",
|
|
1722
|
+
() => import("@drzl/generator-typebox")
|
|
1723
|
+
);
|
|
1100
1724
|
const gen = new TypeBoxGenerator(analysis);
|
|
1101
1725
|
const target = g.path ?? "src/validators/typebox";
|
|
1102
1726
|
const files = await gen.generate(
|
|
@@ -1107,11 +1731,7 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1107
1731
|
files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
|
|
1108
1732
|
} catch (e) {
|
|
1109
1733
|
progress.stop();
|
|
1110
|
-
|
|
1111
|
-
import_chalk2.default.red("TypeBox generator missing."),
|
|
1112
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-typebox")
|
|
1113
|
-
);
|
|
1114
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1734
|
+
reportGeneratorFailure(g.kind, e);
|
|
1115
1735
|
process.exit(1);
|
|
1116
1736
|
}
|
|
1117
1737
|
}
|
|
@@ -1171,7 +1791,37 @@ program.command("generate:orpc").argument("<schema>", "path to drizzle schema (T
|
|
|
1171
1791
|
process.exit(1);
|
|
1172
1792
|
}
|
|
1173
1793
|
});
|
|
1174
|
-
program.command("
|
|
1794
|
+
program.command("generate:trpc").argument("<schema>", "path to drizzle schema (TS)").option("-o, --outDir <dir>", "output directory", "src/api").option("--template <name>", "standard | service", "standard").option("--includeRelations", "include relation endpoints").option("--servicesDir <dir>", "where the service generator writes", "src/services").action(async (schema, opts) => {
|
|
1795
|
+
try {
|
|
1796
|
+
const analyzer = new import_analyzer.SchemaAnalyzer(schema);
|
|
1797
|
+
const analysis = await analyzer.analyze({
|
|
1798
|
+
includeRelations: !!opts.includeRelations,
|
|
1799
|
+
validateConstraints: true
|
|
1800
|
+
});
|
|
1801
|
+
const { TRPCGenerator: TRPCGenerator2 } = await loadGenerator(
|
|
1802
|
+
"@drzl/generator-trpc",
|
|
1803
|
+
() => Promise.resolve().then(() => (init_dist(), dist_exports))
|
|
1804
|
+
);
|
|
1805
|
+
const gen = new TRPCGenerator2(analysis);
|
|
1806
|
+
const { files } = await gen.generate({
|
|
1807
|
+
outputDir: opts.outDir,
|
|
1808
|
+
template: opts.template,
|
|
1809
|
+
includeRelations: !!opts.includeRelations,
|
|
1810
|
+
// Only consulted by `--template service`, and passed unconditionally so this command
|
|
1811
|
+
// cannot become the branch that forgets it.
|
|
1812
|
+
servicesDir: opts.servicesDir
|
|
1813
|
+
});
|
|
1814
|
+
console.log(
|
|
1815
|
+
import_chalk2.default.green(`Generated:`),
|
|
1816
|
+
files.map((f) => import_chalk2.default.cyan(f)).join(", ")
|
|
1817
|
+
);
|
|
1818
|
+
maybeShowSponsorMessage({ reason: "generate:trpc" });
|
|
1819
|
+
} catch (e) {
|
|
1820
|
+
reportGeneratorFailure("trpc", e);
|
|
1821
|
+
process.exit(1);
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
program.command("watch").description("Watch schema and regenerate on changes").option("-c, --config <path>", "path to drzl.config").option("--pipeline <name>", "all | analyze | generate-orpc | generate-trpc", "all").option("--debounce <ms>", "debounce ms", "200").option("--json", "emit JSON logs", false).option("--poll", "force polling (helps WSL/Docker/remote FS)", false).action(async (opts) => {
|
|
1175
1825
|
let cfg = await loadConfig(opts.config);
|
|
1176
1826
|
if (!cfg) {
|
|
1177
1827
|
console.error(import_chalk2.default.red("No config found. Create drzl.config.ts or pass --config."));
|
|
@@ -1271,8 +1921,13 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1271
1921
|
return;
|
|
1272
1922
|
}
|
|
1273
1923
|
const newFiles = [];
|
|
1924
|
+
const servicesDir = cfg.generators.find((x) => x.kind === "service")?.path ?? "src/services";
|
|
1925
|
+
const PIPELINE_KINDS = {
|
|
1926
|
+
"generate-orpc": "orpc",
|
|
1927
|
+
"generate-trpc": "trpc"
|
|
1928
|
+
};
|
|
1274
1929
|
for (const g of cfg.generators) {
|
|
1275
|
-
if (opts.pipeline !== "all" &&
|
|
1930
|
+
if (opts.pipeline !== "all" && PIPELINE_KINDS[opts.pipeline] !== g.kind) {
|
|
1276
1931
|
continue;
|
|
1277
1932
|
}
|
|
1278
1933
|
if (g.kind === "orpc") {
|
|
@@ -1286,16 +1941,38 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1286
1941
|
format: g.format,
|
|
1287
1942
|
templateOptions: g.templateOptions,
|
|
1288
1943
|
importExtension: g.importExtension,
|
|
1289
|
-
validation: g.validation
|
|
1944
|
+
validation: g.validation,
|
|
1945
|
+
databaseInjection: g.databaseInjection,
|
|
1946
|
+
servicesDir
|
|
1290
1947
|
});
|
|
1291
1948
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1292
1949
|
import_chalk2.default.green(`Generated (${g.kind}):`),
|
|
1293
1950
|
files.map((f) => import_chalk2.default.cyan(f)).join(", ")
|
|
1294
1951
|
);
|
|
1295
1952
|
newFiles.push(...files);
|
|
1953
|
+
} else if (g.kind === "trpc") {
|
|
1954
|
+
try {
|
|
1955
|
+
const { TRPCGenerator: TRPCGenerator2 } = await loadGenerator(
|
|
1956
|
+
"@drzl/generator-trpc",
|
|
1957
|
+
() => Promise.resolve().then(() => (init_dist(), dist_exports))
|
|
1958
|
+
);
|
|
1959
|
+
const gen = new TRPCGenerator2(analysis);
|
|
1960
|
+
const { files } = await gen.generate(trpcOptions(g, cfg, servicesDir));
|
|
1961
|
+
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1962
|
+
import_chalk2.default.green(`Generated (trpc): ${files.length} files`),
|
|
1963
|
+
files.map((f) => import_chalk2.default.cyan(f)).join(", ")
|
|
1964
|
+
);
|
|
1965
|
+
newFiles.push(...files);
|
|
1966
|
+
} catch (e) {
|
|
1967
|
+
reportGeneratorFailure(g.kind, e);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1296
1970
|
} else if (g.kind === "service") {
|
|
1297
1971
|
try {
|
|
1298
|
-
const { ServiceGenerator } = await
|
|
1972
|
+
const { ServiceGenerator } = await loadGenerator(
|
|
1973
|
+
"@drzl/generator-service",
|
|
1974
|
+
() => import("@drzl/generator-service")
|
|
1975
|
+
);
|
|
1299
1976
|
const gen = new ServiceGenerator(analysis);
|
|
1300
1977
|
const target = g.path ?? "src/services";
|
|
1301
1978
|
const files = await gen.generate({
|
|
@@ -1305,7 +1982,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1305
1982
|
dataAccess: g.dataAccess,
|
|
1306
1983
|
dbImportPath: g.dbImportPath,
|
|
1307
1984
|
schemaImportPath: g.schemaImportPath,
|
|
1308
|
-
importExtension: g.importExtension
|
|
1985
|
+
importExtension: g.importExtension,
|
|
1986
|
+
databaseInjection: g.databaseInjection
|
|
1309
1987
|
});
|
|
1310
1988
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1311
1989
|
import_chalk2.default.green(`Generated (service): ${files.length} files`),
|
|
@@ -1313,16 +1991,15 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1313
1991
|
);
|
|
1314
1992
|
newFiles.push(...files);
|
|
1315
1993
|
} catch (e) {
|
|
1316
|
-
|
|
1317
|
-
import_chalk2.default.red("Service generator missing."),
|
|
1318
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-service")
|
|
1319
|
-
);
|
|
1320
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
1994
|
+
reportGeneratorFailure(g.kind, e);
|
|
1321
1995
|
return;
|
|
1322
1996
|
}
|
|
1323
1997
|
} else if (g.kind === "zod") {
|
|
1324
1998
|
try {
|
|
1325
|
-
const { ZodGenerator } = await
|
|
1999
|
+
const { ZodGenerator } = await loadGenerator(
|
|
2000
|
+
"@drzl/generator-zod",
|
|
2001
|
+
() => import("@drzl/generator-zod")
|
|
2002
|
+
);
|
|
1326
2003
|
const gen = new ZodGenerator(analysis);
|
|
1327
2004
|
const target = g.path ?? "src/validators/zod";
|
|
1328
2005
|
const files = await gen.generate({
|
|
@@ -1340,16 +2017,15 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1340
2017
|
);
|
|
1341
2018
|
newFiles.push(...files);
|
|
1342
2019
|
} catch (e) {
|
|
1343
|
-
|
|
1344
|
-
import_chalk2.default.red("Zod generator missing."),
|
|
1345
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-zod")
|
|
1346
|
-
);
|
|
1347
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
2020
|
+
reportGeneratorFailure(g.kind, e);
|
|
1348
2021
|
return;
|
|
1349
2022
|
}
|
|
1350
2023
|
} else if (g.kind === "valibot") {
|
|
1351
2024
|
try {
|
|
1352
|
-
const { ValibotGenerator } = await
|
|
2025
|
+
const { ValibotGenerator } = await loadGenerator(
|
|
2026
|
+
"@drzl/generator-valibot",
|
|
2027
|
+
() => import("@drzl/generator-valibot")
|
|
2028
|
+
);
|
|
1353
2029
|
const gen = new ValibotGenerator(analysis);
|
|
1354
2030
|
const target = g.path ?? "src/validators/valibot";
|
|
1355
2031
|
const files = await gen.generate({
|
|
@@ -1367,16 +2043,15 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1367
2043
|
);
|
|
1368
2044
|
newFiles.push(...files);
|
|
1369
2045
|
} catch (e) {
|
|
1370
|
-
|
|
1371
|
-
import_chalk2.default.red("Valibot generator missing."),
|
|
1372
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-valibot")
|
|
1373
|
-
);
|
|
1374
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
2046
|
+
reportGeneratorFailure(g.kind, e);
|
|
1375
2047
|
return;
|
|
1376
2048
|
}
|
|
1377
2049
|
} else if (g.kind === "arktype") {
|
|
1378
2050
|
try {
|
|
1379
|
-
const { ArkTypeGenerator } = await
|
|
2051
|
+
const { ArkTypeGenerator } = await loadGenerator(
|
|
2052
|
+
"@drzl/generator-arktype",
|
|
2053
|
+
() => import("@drzl/generator-arktype")
|
|
2054
|
+
);
|
|
1380
2055
|
const gen = new ArkTypeGenerator(analysis);
|
|
1381
2056
|
const target = g.path ?? "src/validators/arktype";
|
|
1382
2057
|
const files = await gen.generate({
|
|
@@ -1394,11 +2069,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1394
2069
|
);
|
|
1395
2070
|
newFiles.push(...files);
|
|
1396
2071
|
} catch (e) {
|
|
1397
|
-
|
|
1398
|
-
import_chalk2.default.red("ArkType generator missing."),
|
|
1399
|
-
import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-arktype")
|
|
1400
|
-
);
|
|
1401
|
-
console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
|
|
2072
|
+
reportGeneratorFailure(g.kind, e);
|
|
1402
2073
|
return;
|
|
1403
2074
|
}
|
|
1404
2075
|
}
|
|
@@ -1457,6 +2128,8 @@ program.command("init").description("Scaffold a drzl.config.ts").option("-y, --y
|
|
|
1457
2128
|
outDir: 'src/api',
|
|
1458
2129
|
analyzer: { includeRelations: true, validateConstraints: true },
|
|
1459
2130
|
generators: [
|
|
2131
|
+
// For tRPC instead: { kind: 'trpc', template: 'standard', includeRelations: true }
|
|
2132
|
+
// To run both, give one of them its own \`path\`; they share \`outDir\` otherwise.
|
|
1460
2133
|
{ kind: 'orpc', template: 'standard', includeRelations: true }
|
|
1461
2134
|
]
|
|
1462
2135
|
} as const
|