@drzl/cli 4.19.0 → 4.23.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-54E2IO7N.js +1030 -0
- package/dist/chunk-54E2IO7N.js.map +1 -0
- package/dist/{dist-TRJLPIWT.js → chunk-KKPDOZOD.js} +239 -48
- package/dist/chunk-KKPDOZOD.js.map +1 -0
- package/dist/cli.cjs +6850 -1434
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2958 -679
- package/dist/cli.js.map +1 -1
- package/dist/config.cjs +632 -70
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +314 -6
- package/dist/config.d.ts +314 -6
- package/dist/config.js +29 -1
- package/dist/dist-KQMPKOFK.js +350 -0
- package/dist/dist-KQMPKOFK.js.map +1 -0
- package/dist/{dist-CZDVFYFW.js → dist-KX62ETKK.js} +112 -37
- package/dist/dist-KX62ETKK.js.map +1 -0
- package/dist/dist-P24ILO5N.js +431 -0
- package/dist/dist-P24ILO5N.js.map +1 -0
- package/dist/dist-QYH7DRFY.js +27 -0
- package/dist/dist-QYH7DRFY.js.map +1 -0
- package/dist/dist-SGI2I53L.js +434 -0
- package/dist/dist-SGI2I53L.js.map +1 -0
- package/dist/dist-T5376MW7.js +489 -0
- package/dist/dist-T5376MW7.js.map +1 -0
- package/dist/dist-UVP6B4XJ.js +646 -0
- package/dist/dist-UVP6B4XJ.js.map +1 -0
- package/dist/{dist-XBGVORL3.js → dist-ZIHNXQ7U.js} +16 -6
- package/dist/dist-ZIHNXQ7U.js.map +1 -0
- package/dist/drzl.config.schema.json +717 -0
- package/package.json +19 -13
- package/dist/chunk-V2IXXAC2.js +0 -465
- package/dist/chunk-V2IXXAC2.js.map +0 -1
- package/dist/dist-CZDVFYFW.js.map +0 -1
- package/dist/dist-TRJLPIWT.js.map +0 -1
- package/dist/dist-XBGVORL3.js.map +0 -1
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import {
|
|
3
|
+
AFFIX_PREFIX_PATTERN,
|
|
4
|
+
AFFIX_PROBE_TABLE,
|
|
5
|
+
AFFIX_SUFFIX_PATTERN,
|
|
6
|
+
DEFAULT_IMPORT_EXTENSION,
|
|
7
|
+
IMPORT_EXTENSIONS,
|
|
8
|
+
NAME_MODES,
|
|
9
|
+
resolveAffix,
|
|
10
|
+
schemaName,
|
|
11
|
+
validateAffix
|
|
12
|
+
} from "@drzl/validation-core";
|
|
13
|
+
import * as fs from "fs";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
// src/config-errors.ts
|
|
19
|
+
function hasOwn(object, key) {
|
|
20
|
+
return Object.prototype.hasOwnProperty.call(object, key);
|
|
21
|
+
}
|
|
22
|
+
var CONFIG_INVALID_CODE = "DRZL_CFG_002";
|
|
23
|
+
var ConfigValidationError = class extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = CONFIG_INVALID_CODE;
|
|
27
|
+
this.name = "ConfigValidationError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var MAX_LISTED_PROBLEMS = 8;
|
|
31
|
+
var MAX_VALUE_CHARS = 60;
|
|
32
|
+
function renderConfigPath(segments) {
|
|
33
|
+
if (!segments.length) return "(root)";
|
|
34
|
+
let out = "";
|
|
35
|
+
for (const segment of segments) {
|
|
36
|
+
if (typeof segment === "number") {
|
|
37
|
+
out += `[${segment}]`;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const key = String(segment);
|
|
41
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) out += out ? `.${key}` : key;
|
|
42
|
+
else out += `[${JSON.stringify(key)}]`;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function describeValue(value) {
|
|
47
|
+
if (value === void 0) return "undefined";
|
|
48
|
+
if (value === null) return "null";
|
|
49
|
+
switch (typeof value) {
|
|
50
|
+
case "boolean":
|
|
51
|
+
return String(value);
|
|
52
|
+
case "number":
|
|
53
|
+
return String(value);
|
|
54
|
+
case "bigint":
|
|
55
|
+
return `${value}n`;
|
|
56
|
+
case "function":
|
|
57
|
+
return "[function]";
|
|
58
|
+
case "symbol":
|
|
59
|
+
return String(value);
|
|
60
|
+
case "string":
|
|
61
|
+
return value.length <= MAX_VALUE_CHARS ? JSON.stringify(value) : `${JSON.stringify(value.slice(0, MAX_VALUE_CHARS))} ... (${value.length} characters)`;
|
|
62
|
+
}
|
|
63
|
+
let json;
|
|
64
|
+
try {
|
|
65
|
+
json = JSON.stringify(value);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (typeof json !== "string") return null;
|
|
70
|
+
return json.length <= MAX_VALUE_CHARS ? json : null;
|
|
71
|
+
}
|
|
72
|
+
function valueAt(raw, segments) {
|
|
73
|
+
let current = raw;
|
|
74
|
+
for (const segment of segments) {
|
|
75
|
+
if (current === null || typeof current !== "object") return { found: false, value: void 0 };
|
|
76
|
+
if (!hasOwn(current, String(segment))) return { found: false, value: void 0 };
|
|
77
|
+
current = current[segment];
|
|
78
|
+
}
|
|
79
|
+
return { found: true, value: current };
|
|
80
|
+
}
|
|
81
|
+
function editDistance(a, b) {
|
|
82
|
+
if (a === b) return 0;
|
|
83
|
+
if (!a.length) return b.length;
|
|
84
|
+
if (!b.length) return a.length;
|
|
85
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
86
|
+
for (let i = 1; i <= a.length; i++) {
|
|
87
|
+
const current = new Array(b.length + 1);
|
|
88
|
+
current[0] = i;
|
|
89
|
+
for (let j = 1; j <= b.length; j++) {
|
|
90
|
+
const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
91
|
+
current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, substitution);
|
|
92
|
+
}
|
|
93
|
+
previous = current;
|
|
94
|
+
}
|
|
95
|
+
return previous[b.length];
|
|
96
|
+
}
|
|
97
|
+
function nearestKey(key, known) {
|
|
98
|
+
const budget = key.length >= 5 ? 2 : 1;
|
|
99
|
+
let best;
|
|
100
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
101
|
+
for (const candidate of known) {
|
|
102
|
+
if (candidate === key) return void 0;
|
|
103
|
+
const distance = editDistance(key, candidate);
|
|
104
|
+
if (distance <= budget && distance < bestDistance) {
|
|
105
|
+
best = candidate;
|
|
106
|
+
bestDistance = distance;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return best;
|
|
110
|
+
}
|
|
111
|
+
function objectNodeFor(node) {
|
|
112
|
+
if (!node || typeof node !== "object") return void 0;
|
|
113
|
+
const record = node;
|
|
114
|
+
const anyOf = record.anyOf;
|
|
115
|
+
if (Array.isArray(anyOf)) {
|
|
116
|
+
const branches = anyOf.filter(
|
|
117
|
+
(branch) => branch && typeof branch === "object" && (branch.properties !== void 0 || typeof branch.additionalProperties === "object")
|
|
118
|
+
);
|
|
119
|
+
return branches.length === 1 ? branches[0] : void 0;
|
|
120
|
+
}
|
|
121
|
+
if (record.properties !== void 0 || record.additionalProperties !== void 0) return record;
|
|
122
|
+
return void 0;
|
|
123
|
+
}
|
|
124
|
+
function unknownConfigKeys(raw, schema) {
|
|
125
|
+
const found = [];
|
|
126
|
+
walkForUnknownKeys(raw, schema, [], found);
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
function walkForUnknownKeys(value, node, segments, found) {
|
|
130
|
+
if (value === null || typeof value !== "object") return;
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
const items = node?.items;
|
|
133
|
+
if (!items) return;
|
|
134
|
+
value.forEach((entry, index) => walkForUnknownKeys(entry, items, [...segments, index], found));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const object = objectNodeFor(node);
|
|
138
|
+
if (!object) return;
|
|
139
|
+
const properties = object.properties;
|
|
140
|
+
const additional = object.additionalProperties;
|
|
141
|
+
if (!properties) {
|
|
142
|
+
if (additional && typeof additional === "object") {
|
|
143
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
144
|
+
walkForUnknownKeys(entry, additional, [...segments, key], found);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const known = Object.keys(properties);
|
|
150
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
151
|
+
if (hasOwn(properties, key)) {
|
|
152
|
+
walkForUnknownKeys(entry, properties[key], [...segments, key], found);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (additional === false) continue;
|
|
156
|
+
found.push({ path: [...segments], key, suggestion: nearestKey(key, known) });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function unknownKeyWarnings(raw, schema) {
|
|
160
|
+
return unknownConfigKeys(raw, schema).map((unknown) => {
|
|
161
|
+
const where = unknown.path.length ? `in ${renderConfigPath(unknown.path)}` : "at the top level";
|
|
162
|
+
const suggestion = unknown.suggestion ? ` Did you mean "${unknown.suggestion}"?` : "";
|
|
163
|
+
return `drzl config: unknown key "${unknown.key}" ${where}; it is ignored.${suggestion}`;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function nodeAt(schema, segments) {
|
|
167
|
+
let node = schema;
|
|
168
|
+
for (const segment of segments) {
|
|
169
|
+
if (typeof segment === "number") {
|
|
170
|
+
node = node?.items;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const object = objectNodeFor(node);
|
|
174
|
+
if (!object) return void 0;
|
|
175
|
+
const properties = object.properties;
|
|
176
|
+
if (properties && hasOwn(properties, String(segment))) {
|
|
177
|
+
node = properties[String(segment)];
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const additional = object.additionalProperties;
|
|
181
|
+
if (additional && typeof additional === "object") {
|
|
182
|
+
node = additional;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
return objectNodeFor(node);
|
|
188
|
+
}
|
|
189
|
+
function knownKeysAt(schema, segments) {
|
|
190
|
+
const node = nodeAt(schema, segments);
|
|
191
|
+
const properties = node?.properties;
|
|
192
|
+
return properties ? Object.keys(properties) : [];
|
|
193
|
+
}
|
|
194
|
+
function renderIssue(issue, raw, schema) {
|
|
195
|
+
const segments = issue.path ?? [];
|
|
196
|
+
const where = renderConfigPath(segments);
|
|
197
|
+
if (issue.code === "unrecognized_keys" && issue.keys?.length) {
|
|
198
|
+
const known = knownKeysAt(schema, segments);
|
|
199
|
+
return issue.keys.map((key) => {
|
|
200
|
+
const suggestion = nearestKey(key, known);
|
|
201
|
+
return `${where}: unrecognized key "${key}".${suggestion ? ` Did you mean "${suggestion}"?` : ""}`;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const what = String(issue.message ?? "is not valid").replace(/^Invalid input:\s*/, "");
|
|
205
|
+
const { found, value } = valueAt(raw, segments);
|
|
206
|
+
const shown = found ? describeValue(value) : null;
|
|
207
|
+
return [`${where}: ${what}${shown === null ? "" : ` (found ${shown})`}`];
|
|
208
|
+
}
|
|
209
|
+
function formatConfigProblems(file, issues, raw, schema) {
|
|
210
|
+
const lines = issues.flatMap((issue) => renderIssue(issue, raw, schema));
|
|
211
|
+
const shown = lines.slice(0, MAX_LISTED_PROBLEMS);
|
|
212
|
+
const rest = lines.length - shown.length;
|
|
213
|
+
const header = `${file} is not valid (${CONFIG_INVALID_CODE}). ${lines.length} problem${lines.length === 1 ? "" : "s"}:`;
|
|
214
|
+
const body = shown.map((line) => ` - ${line}`);
|
|
215
|
+
if (rest > 0) body.push(` ... and ${rest} more`);
|
|
216
|
+
return [header, ...body].join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/patterns.ts
|
|
220
|
+
function patternToRegExp(pattern) {
|
|
221
|
+
return new RegExp(
|
|
222
|
+
"^" + pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
function matchesAny(patterns, name) {
|
|
226
|
+
return patterns.some((p) => patternToRegExp(p).test(name));
|
|
227
|
+
}
|
|
228
|
+
var DEFAULT_SCHEMA_ALIAS = "public";
|
|
229
|
+
function tableAliases(table) {
|
|
230
|
+
return [table.name, `${table.schema ?? DEFAULT_SCHEMA_ALIAS}.${table.name}`];
|
|
231
|
+
}
|
|
232
|
+
function matchesTable(patterns, table) {
|
|
233
|
+
return tableAliases(table).some((alias) => matchesAny(patterns, alias));
|
|
234
|
+
}
|
|
235
|
+
function displayTableName(table) {
|
|
236
|
+
return table.schema ? `${table.schema}.${table.name}` : table.name;
|
|
237
|
+
}
|
|
238
|
+
function addressableName(table) {
|
|
239
|
+
return `${table.schema ?? DEFAULT_SCHEMA_ALIAS}.${table.name}`;
|
|
240
|
+
}
|
|
241
|
+
function hasNamedSchemas(tables) {
|
|
242
|
+
return tables.some((t) => t.schema);
|
|
243
|
+
}
|
|
244
|
+
function ambiguousPatternWarnings(patterns, tables, option) {
|
|
245
|
+
const out = [];
|
|
246
|
+
for (const pattern of patterns) {
|
|
247
|
+
if (pattern.includes(".")) continue;
|
|
248
|
+
const matched = tables.filter((t) => matchesTable([pattern], t));
|
|
249
|
+
const schemas = new Set(matched.map((t) => t.schema ?? DEFAULT_SCHEMA_ALIAS));
|
|
250
|
+
if (schemas.size < 2) continue;
|
|
251
|
+
out.push(
|
|
252
|
+
`drzl config: ${option} pattern ${JSON.stringify(pattern)} matches tables in more than one schema, and every one of them is affected: ${matched.map(addressableName).sort().join(", ")}. Write the schema to mean one of them, for example ${JSON.stringify(addressableName(matched[0]))}.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/config.ts
|
|
259
|
+
var NamingSchema = z.object({
|
|
260
|
+
routerSuffix: z.string().default("Router"),
|
|
261
|
+
procedureCase: z.enum(["camel", "kebab", "snake"]).default("camel")
|
|
262
|
+
}).partial();
|
|
263
|
+
var affixValueSchema = (pattern) => z.union(
|
|
264
|
+
[
|
|
265
|
+
z.string().meta({ pattern }),
|
|
266
|
+
z.object({
|
|
267
|
+
insert: z.string().meta({ pattern }).optional(),
|
|
268
|
+
update: z.string().meta({ pattern }).optional(),
|
|
269
|
+
select: z.string().meta({ pattern }).optional()
|
|
270
|
+
}).strict()
|
|
271
|
+
],
|
|
272
|
+
{
|
|
273
|
+
error: 'Expected a string to use for every mode, or an object with any of the keys "insert", "update" and "select". Those keys are lowercase, matching the mode names drzl uses everywhere else.'
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
var AffixPartSchema = z.object({
|
|
277
|
+
prefix: affixValueSchema(AFFIX_PREFIX_PATTERN).optional(),
|
|
278
|
+
suffix: affixValueSchema(AFFIX_SUFFIX_PATTERN).optional()
|
|
279
|
+
}).strict();
|
|
280
|
+
var AffixSchema = z.object({
|
|
281
|
+
/**
|
|
282
|
+
* `preserve` (default) keeps today's output: the Drizzle export name goes into the
|
|
283
|
+
* identifier verbatim, so `export const users` yields `InsertusersSchema`. `pascal`
|
|
284
|
+
* upper-camels it first, yielding `InsertUsersSchema`.
|
|
285
|
+
*/
|
|
286
|
+
tableCase: z.enum(["preserve", "pascal"]).optional(),
|
|
287
|
+
schema: AffixPartSchema.optional(),
|
|
288
|
+
type: AffixPartSchema.optional()
|
|
289
|
+
}).strict();
|
|
290
|
+
var ImportExtensionSchema = z.enum(IMPORT_EXTENSIONS);
|
|
291
|
+
var GeneratorKindSchema = z.enum([
|
|
292
|
+
"orpc",
|
|
293
|
+
"trpc",
|
|
294
|
+
"hono",
|
|
295
|
+
"express",
|
|
296
|
+
"fastify",
|
|
297
|
+
"nestjs",
|
|
298
|
+
"graphql",
|
|
299
|
+
"service",
|
|
300
|
+
"zod",
|
|
301
|
+
"valibot",
|
|
302
|
+
"arktype",
|
|
303
|
+
"typebox",
|
|
304
|
+
"effect",
|
|
305
|
+
"json-schema"
|
|
306
|
+
]);
|
|
307
|
+
var GENERATOR_KINDS = GeneratorKindSchema.options;
|
|
308
|
+
var GeneratorSchema = z.object({
|
|
309
|
+
kind: GeneratorKindSchema,
|
|
310
|
+
/**
|
|
311
|
+
* Which of Hono's two official validator middlewares the emitted routes carry, and therefore
|
|
312
|
+
* which package they import. `hono` only.
|
|
313
|
+
*
|
|
314
|
+
* `standard` is `sValidator` from `@hono/standard-validator`, which takes any Standard Schema
|
|
315
|
+
* and so works with every library `validation.library` can name. `zod` is `zValidator` from
|
|
316
|
+
* `@hono/zod-validator`, which is zod-specific.
|
|
317
|
+
*/
|
|
318
|
+
validator: z.enum(["standard", "zod"]).optional(),
|
|
319
|
+
/**
|
|
320
|
+
* Overrides the top-level `importExtension` for this generator alone, for a project whose
|
|
321
|
+
* generated directories are compiled by different tsconfigs.
|
|
322
|
+
*/
|
|
323
|
+
importExtension: ImportExtensionSchema.optional(),
|
|
324
|
+
template: z.string().optional(),
|
|
325
|
+
includeRelations: z.boolean().optional(),
|
|
326
|
+
/**
|
|
327
|
+
* Write an enum used by two or more columns once under `$defs` in the `json-schema` per-table
|
|
328
|
+
* modules, and `$ref` it at each use.
|
|
329
|
+
*
|
|
330
|
+
* Off by default, and the reason is a consumer pattern rather than a doubt about the keyword. A
|
|
331
|
+
* per-table schema is used whole and one property at a time, and a `$ref` cannot survive being
|
|
332
|
+
* pulled out with its property: `properties[col]` compiled on its own is a dangling reference
|
|
333
|
+
* that ajv refuses outright. The OpenAPI document shares regardless, because a document is only
|
|
334
|
+
* ever read whole.
|
|
335
|
+
*/
|
|
336
|
+
sharedEnums: z.boolean().optional(),
|
|
337
|
+
/**
|
|
338
|
+
* Type `json` and `jsonb` columns from the schema rather than leaving them wide.
|
|
339
|
+
*
|
|
340
|
+
* `.$type<T>()` is a compile-time cast, so no runtime-derived validator can see it and
|
|
341
|
+
* `drizzle-orm/zod` types every json column as its generic `Json`. A generator can reference
|
|
342
|
+
* `typeof <table>.$inferSelect['<column>']` instead, which is the declared type resolved by
|
|
343
|
+
* TypeScript itself, so generics, unions and imported interfaces all work.
|
|
344
|
+
*
|
|
345
|
+
* Off by default because it makes the generated file import your schema module, as a
|
|
346
|
+
* type-only import that disappears at build time.
|
|
347
|
+
*/
|
|
348
|
+
// What a date column accepts. Documented on the zod generator and, until now, accepted by the
|
|
349
|
+
// config parser and then dropped on the floor: the generators default it to 'input' themselves,
|
|
350
|
+
// so setting it here changed nothing.
|
|
351
|
+
coerceDates: z.enum(["input", "all", "none"]).optional(),
|
|
352
|
+
typedJson: z.boolean().optional(),
|
|
353
|
+
// The wider form: every column's static type comes from Drizzle, not just the untyped ones.
|
|
354
|
+
typedColumns: z.boolean().optional(),
|
|
355
|
+
// Reproduce literal column defaults in the insert schema, so parsing fills them in.
|
|
356
|
+
applyDefaults: z.boolean().optional(),
|
|
357
|
+
/**
|
|
358
|
+
* Emit `findDuplicate<Table>` beside the schemas: the rows in a batch that collide with an
|
|
359
|
+
* earlier row on a unique constraint.
|
|
360
|
+
*
|
|
361
|
+
* Uniqueness is the one constraint a per-row validator structurally cannot see, since it is a
|
|
362
|
+
* fact about the table rather than the row. This checks the half that needs no database.
|
|
363
|
+
*/
|
|
364
|
+
duplicateFinder: z.boolean().optional(),
|
|
365
|
+
/**
|
|
366
|
+
* zod and valibot. Also emit `constraints.ts`: every CHECK, unique constraint, primary and
|
|
367
|
+
* foreign key on each table as plain data, plus `constraintForIssue`, which maps a validation
|
|
368
|
+
* issue back to the constraint that caused it.
|
|
369
|
+
*
|
|
370
|
+
* For building forms. A schema states what a value must look like and never says which
|
|
371
|
+
* constraint said so, so a failed parse hands a form a message and no way to attribute it; and
|
|
372
|
+
* uniqueness and foreign keys, the two constraints no per-row schema can check, are absent from
|
|
373
|
+
* the emitted schemas in every form.
|
|
374
|
+
*
|
|
375
|
+
* Not `meta` written to a second file. `meta` describes a *field* and travels with the schema
|
|
376
|
+
* into `z.toJSONSchema`; this describes the table's *constraints*, carries their names, states
|
|
377
|
+
* each operand as data rather than inside a sentence, and is read without holding a schema.
|
|
378
|
+
*
|
|
379
|
+
* `true` is the shorthand for `{ enabled: true }`. `{ errorMap: false }` emits the data alone,
|
|
380
|
+
* without the matcher.
|
|
381
|
+
*/
|
|
382
|
+
constraints: z.union([
|
|
383
|
+
z.boolean(),
|
|
384
|
+
z.object({ enabled: z.boolean().optional(), errorMap: z.boolean().optional() }).strict()
|
|
385
|
+
]).optional(),
|
|
386
|
+
/**
|
|
387
|
+
* zod only. Attach the facts the analyzer knows and a zod schema cannot state, as `.meta()` on
|
|
388
|
+
* every field and every table schema: the declared SQL type, the primary key, the unique
|
|
389
|
+
* constraints, whether the database generates or defaults the value, and the CHECK constraints,
|
|
390
|
+
* including the ones DRZL declined to enforce.
|
|
391
|
+
*
|
|
392
|
+
* `z.toJSONSchema` copies these through, so they are also how an OpenAPI document built from the
|
|
393
|
+
* emitted schemas gets the declared width back: DRZL enforces one as a `.refine()`, and
|
|
394
|
+
* `toJSONSchema` drops every refinement in silence.
|
|
395
|
+
*
|
|
396
|
+
* `true` is the shorthand for `{ enabled: true }`. `{ description: true }` additionally writes a
|
|
397
|
+
* `description`, which is what an OpenAPI viewer renders to a human.
|
|
398
|
+
*/
|
|
399
|
+
meta: z.union([
|
|
400
|
+
z.boolean(),
|
|
401
|
+
z.object({ enabled: z.boolean().optional(), description: z.boolean().optional() }).strict()
|
|
402
|
+
]).optional(),
|
|
403
|
+
/**
|
|
404
|
+
* TypeBox only. Give every emitted schema a `~standard` key, so it can be handed to a tRPC or
|
|
405
|
+
* oRPC route.
|
|
406
|
+
*
|
|
407
|
+
* TypeBox is the one validator DRZL emits that carries none of its own: measured on 0.34.52, a
|
|
408
|
+
* bare `Type.Object()` has no `~standard` and the package exports nothing matching
|
|
409
|
+
* `/standard/i`. zod, valibot and arktype all put one on every schema they build, so the option
|
|
410
|
+
* does nothing for them and is not passed through.
|
|
411
|
+
*
|
|
412
|
+
* The property is non-enumerable, so the schema stays a TypeBox schema in every respect that was
|
|
413
|
+
* already observable, including the JSON Schema `JSON.stringify` produces.
|
|
414
|
+
*/
|
|
415
|
+
standardSchema: z.boolean().optional(),
|
|
416
|
+
/**
|
|
417
|
+
* Emit `NestedInsert<Table>` and `NestedSelect<Table>` beside the flat schemas: the table plus
|
|
418
|
+
* one key per relation, so `{ ...user, posts: [...] }` can be validated whole.
|
|
419
|
+
*
|
|
420
|
+
* Nothing in the Drizzle validator ecosystem describes that payload, and `db.insert` drops the
|
|
421
|
+
* relation key silently rather than refusing it, so the children are never written and nothing
|
|
422
|
+
* says so.
|
|
423
|
+
*/
|
|
424
|
+
nestedSchemas: z.boolean().optional(),
|
|
425
|
+
/**
|
|
426
|
+
* How many levels of children a nested schema describes. Defaults to 1, capped at 3.
|
|
427
|
+
*
|
|
428
|
+
* Nesting is expanded inline rather than by reference, so this multiplies the emitted size, and
|
|
429
|
+
* it is also what terminates a cycle: `users -> posts -> users` stops here.
|
|
430
|
+
*/
|
|
431
|
+
nestedDepth: z.number().int().optional(),
|
|
432
|
+
/**
|
|
433
|
+
* Give every primary key, and every foreign key pointing at one, a nominal type, so a
|
|
434
|
+
* `users.id` cannot be passed where a `posts.id` is wanted.
|
|
435
|
+
*
|
|
436
|
+
* Type level only, in all five validators. Measured on zod 4.4.3, `.brand()` returns the same
|
|
437
|
+
* schema object it was called on and the parsed value of `1` is `1`, so nothing about what a
|
|
438
|
+
* schema accepts changes and no bytes are added to the bundle. TypeBox has no brand of its own
|
|
439
|
+
* and gets a `TUnsafe` cast, which leaves the schema object identical.
|
|
440
|
+
*
|
|
441
|
+
* Off by default: it changes the inferred type of every consumer of the select schemas, which
|
|
442
|
+
* is the point, but it is a change to existing call sites rather than an addition.
|
|
443
|
+
*
|
|
444
|
+
* `true` is the shorthand for `{ enabled: true }`. `{ foreignKeys: false }` brands only the
|
|
445
|
+
* keys themselves, and `{ aliases: false }` stops the `export type UsersId = ...` lines.
|
|
446
|
+
*/
|
|
447
|
+
branded: z.union([
|
|
448
|
+
z.boolean(),
|
|
449
|
+
z.object({
|
|
450
|
+
enabled: z.boolean().optional(),
|
|
451
|
+
foreignKeys: z.boolean().optional(),
|
|
452
|
+
aliases: z.boolean().optional()
|
|
453
|
+
}).strict()
|
|
454
|
+
]).optional(),
|
|
455
|
+
naming: NamingSchema.optional(),
|
|
456
|
+
outputHeader: z.object({
|
|
457
|
+
enabled: z.boolean().default(true).optional(),
|
|
458
|
+
text: z.string().optional()
|
|
459
|
+
}).optional(),
|
|
460
|
+
format: z.object({
|
|
461
|
+
enabled: z.boolean().default(true).optional(),
|
|
462
|
+
engine: z.enum(["auto", "prettier", "biome"]).default("auto").optional(),
|
|
463
|
+
configPath: z.string().optional()
|
|
464
|
+
}).optional(),
|
|
465
|
+
/**
|
|
466
|
+
* Which spelling of JSON Schema the `json-schema` generator emits.
|
|
467
|
+
*
|
|
468
|
+
* OpenAPI 3.0 is not an older superset of the 2020-12 draft, it is a different dialect: a
|
|
469
|
+
* nullable type is `nullable: true` rather than a type array, and an exclusive bound is a
|
|
470
|
+
* boolean flag beside the bound rather than its own keyword. An unknown keyword is not an error
|
|
471
|
+
* in JSON Schema, it is ignored, so emitting the wrong dialect produces a document that
|
|
472
|
+
* validates and then accepts the values the constraints exist to reject.
|
|
473
|
+
*/
|
|
474
|
+
target: z.enum(["draft-2020-12", "openapi-3.1", "openapi-3.0"]).optional(),
|
|
475
|
+
/** Also emit `components.ts` for the `json-schema` generator, ready for an OpenAPI document. */
|
|
476
|
+
components: z.boolean().optional(),
|
|
477
|
+
/**
|
|
478
|
+
* Also emit the whole OpenAPI document for the `json-schema` generator: paths, verbs, request and
|
|
479
|
+
* response bodies per table, with `components.schemas` embedded so the file stands alone.
|
|
480
|
+
*
|
|
481
|
+
* `true` is the short form. The object form carries the three things a Drizzle schema genuinely
|
|
482
|
+
* cannot say: what the API is called, where it is served, and which status code that particular
|
|
483
|
+
* server answers a request that fails its schema with.
|
|
484
|
+
*/
|
|
485
|
+
document: z.union([
|
|
486
|
+
z.boolean(),
|
|
487
|
+
z.object({
|
|
488
|
+
enabled: z.boolean().optional(),
|
|
489
|
+
/** `ts` (default) writes a module, `json` the file OpenAPI tooling reads directly. */
|
|
490
|
+
format: z.enum(["ts", "json", "both"]).optional(),
|
|
491
|
+
info: z.object({
|
|
492
|
+
title: z.string().optional(),
|
|
493
|
+
version: z.string().optional(),
|
|
494
|
+
description: z.string().optional()
|
|
495
|
+
}).strict().optional(),
|
|
496
|
+
/**
|
|
497
|
+
* Omitted by default, which the specification reads as a single server at `/`: the
|
|
498
|
+
* document describes whatever is serving it. A placeholder host would be a fabrication
|
|
499
|
+
* that tooling then follows.
|
|
500
|
+
*/
|
|
501
|
+
servers: z.array(z.object({ url: z.string(), description: z.string().optional() }).strict()).optional(),
|
|
502
|
+
/** 400 by default. 422 is the other defensible reading; exactly one is emitted. */
|
|
503
|
+
validationStatus: z.union([z.literal(400), z.literal(422)]).optional()
|
|
504
|
+
}).strict()
|
|
505
|
+
]).optional(),
|
|
506
|
+
// service generator specific options
|
|
507
|
+
path: z.string().optional(),
|
|
508
|
+
dataAccess: z.enum(["stub", "drizzle"]).default("stub").optional(),
|
|
509
|
+
dbImportPath: z.string().optional(),
|
|
510
|
+
schemaImportPath: z.string().optional(),
|
|
511
|
+
// zod/valibot/arktype generator specific options
|
|
512
|
+
schemaSuffix: z.string().optional(),
|
|
513
|
+
fileSuffix: z.string().optional(),
|
|
514
|
+
/**
|
|
515
|
+
* Prefixes, suffixes and table casing for generated identifiers (zod/valibot/arktype).
|
|
516
|
+
* Omitting it reproduces the output of every previous release exactly.
|
|
517
|
+
*/
|
|
518
|
+
affix: AffixSchema.optional(),
|
|
519
|
+
/**
|
|
520
|
+
* How the router generators reach a database handle: through the request context, rather than
|
|
521
|
+
* through a module-level import in the service layer.
|
|
522
|
+
*
|
|
523
|
+
* Documented on the oRPC generator since it was added and, until now, absent from this schema
|
|
524
|
+
* entirely. `GeneratorSchema` is not strict, so zod stripped the key without a word and the
|
|
525
|
+
* option did nothing at all when set from a config file. It was only ever reachable by calling
|
|
526
|
+
* the generator's API directly.
|
|
527
|
+
*/
|
|
528
|
+
databaseInjection: z.object({
|
|
529
|
+
enabled: z.boolean().optional(),
|
|
530
|
+
/** The type annotation for the injected handle, e.g. `DrizzleD1Database`. */
|
|
531
|
+
databaseType: z.string().optional(),
|
|
532
|
+
databaseTypeImport: z.object({ name: z.string(), from: z.string() }).optional()
|
|
533
|
+
}).optional(),
|
|
534
|
+
// router validation sharing (orpc, trpc)
|
|
535
|
+
validation: z.object({
|
|
536
|
+
useShared: z.boolean().default(false).optional(),
|
|
537
|
+
library: z.enum(["zod", "valibot", "arktype"]).default("zod").optional(),
|
|
538
|
+
importPath: z.string().optional(),
|
|
539
|
+
schemaSuffix: z.string().optional(),
|
|
540
|
+
/**
|
|
541
|
+
* How the validation generator named its exports. Usually left unset: the CLI copies
|
|
542
|
+
* it from the sibling generator whose `kind` matches `library`.
|
|
543
|
+
*/
|
|
544
|
+
affix: AffixSchema.optional()
|
|
545
|
+
}).optional(),
|
|
546
|
+
// template options
|
|
547
|
+
templateOptions: z.record(z.string(), z.any()).optional()
|
|
548
|
+
});
|
|
549
|
+
var ColumnRulesSchema = z.object({
|
|
550
|
+
omit: z.array(z.string()).optional(),
|
|
551
|
+
pick: z.array(z.string()).optional()
|
|
552
|
+
}).strict();
|
|
553
|
+
var AnalyzerSchema = z.object({
|
|
554
|
+
includeRelations: z.boolean().default(true),
|
|
555
|
+
validateConstraints: z.boolean().default(true),
|
|
556
|
+
includeHeuristicRelations: z.boolean().default(false)
|
|
557
|
+
});
|
|
558
|
+
var ConfigSchema = z.object({
|
|
559
|
+
/**
|
|
560
|
+
* Path to the Drizzle schema module. Optional since drizzle-kit interop: a project that
|
|
561
|
+
* already names its schema in `drizzle.config.ts` should not have to say it twice, so an
|
|
562
|
+
* omitted `schema` falls back to reading the drizzle-kit config (see `drizzleKit`). The
|
|
563
|
+
* "neither file names a schema" error is raised at resolution time, where it can name both
|
|
564
|
+
* files, rather than here, where "Required" could name only this one.
|
|
565
|
+
*/
|
|
566
|
+
schema: z.string().optional(),
|
|
567
|
+
/**
|
|
568
|
+
* Read the schema path from drizzle-kit's own config instead of `schema`.
|
|
569
|
+
*
|
|
570
|
+
* `true` reads `drizzle.config.ts`, then `.js`, then `.json`, the same candidates in the
|
|
571
|
+
* same order drizzle-kit's CLI uses (measured on drizzle-kit 0.31.10). A string reads that
|
|
572
|
+
* file, wherever it is, like kit's own `--config` flag. `false` disables the fallback, so
|
|
573
|
+
* an omitted `schema` is an error even beside a drizzle.config. Unset behaves like `true`
|
|
574
|
+
* whenever `schema` is omitted, and does nothing when `schema` is set: `schema` always
|
|
575
|
+
* wins, with a warning when both are stated.
|
|
576
|
+
*
|
|
577
|
+
* Only kit's `schema` (string or array, entries may be globs) and `dialect` (cross-checked
|
|
578
|
+
* against what the analyzer detects) are read. Everything else in that file describes
|
|
579
|
+
* migrations and database credentials, which DRZL has no use for.
|
|
580
|
+
*/
|
|
581
|
+
drizzleKit: z.union([z.boolean(), z.string()], {
|
|
582
|
+
error: "Expected true (read drizzle.config.ts/.js/.json, the same candidates drizzle-kit uses), false (never read one), or a path to the drizzle-kit config file."
|
|
583
|
+
}).optional(),
|
|
584
|
+
outDir: z.string().default("src/api"),
|
|
585
|
+
/**
|
|
586
|
+
* Which tables to generate for, matched against the database table name.
|
|
587
|
+
*
|
|
588
|
+
* There was no way to say this, and every generator loops over every table it finds, so
|
|
589
|
+
* DRZL emitted unauthenticated CRUD over whatever shared the schema file. That is noise for
|
|
590
|
+
* a migrations table and a genuine leak for an auth one: Better Auth puts `user`, `session`,
|
|
591
|
+
* `account` and `verification` alongside your own tables, and `account` holds
|
|
592
|
+
* `accessToken`, `refreshToken`, `idToken` and `password`.
|
|
593
|
+
*
|
|
594
|
+
* Deliberately name-based and explicit rather than detecting any particular library. Auth
|
|
595
|
+
* table names are all renameable, so a built-in list would miss renamed tables and, worse,
|
|
596
|
+
* silently skip an ordinary table that happened to be called `user`, which is usually the
|
|
597
|
+
* application's main entity.
|
|
598
|
+
*
|
|
599
|
+
* `exclude` wins over `include`. Patterns support `*`, matching within a name.
|
|
600
|
+
*/
|
|
601
|
+
include: z.array(z.string()).optional(),
|
|
602
|
+
exclude: z.array(z.string()).optional(),
|
|
603
|
+
/**
|
|
604
|
+
* Which columns of those tables to generate for, keyed by table.
|
|
605
|
+
*
|
|
606
|
+
* `include`/`exclude` is all or nothing per table, and the column that should not be in a
|
|
607
|
+
* generated schema is usually sitting in a table you do want: `passwordHash` on `users`, an
|
|
608
|
+
* internal note beside the public fields, a `tenantId` the server sets from the session and a
|
|
609
|
+
* request body must not carry. Editing the emitted file is not an answer, because the next
|
|
610
|
+
* `drzl generate` overwrites it.
|
|
611
|
+
*
|
|
612
|
+
* ```ts
|
|
613
|
+
* columns: {
|
|
614
|
+
* users: { omit: ['passwordHash'] },
|
|
615
|
+
* 'app_*': { omit: ['deleted_at'] },
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* The key is a table pattern in the same language `include`/`exclude` uses: the database table
|
|
620
|
+
* name, anchored, with `*` as the only metacharacter. Column patterns are the same language
|
|
621
|
+
* again. Every matching entry applies, in the order written; within one entry `pick` narrows
|
|
622
|
+
* first and `omit` then removes, so `omit` wins, exactly as `exclude` wins over `include`.
|
|
623
|
+
*
|
|
624
|
+
* Applies to every mode and every generator at once, because it narrows the analysis rather
|
|
625
|
+
* than any one generator's output. A column cannot be kept in `select` and dropped from
|
|
626
|
+
* `insert`: see the docs for why that form was not taken on.
|
|
627
|
+
*
|
|
628
|
+
* A pattern that matches nothing is an error rather than a no-op, because a typo in `omit`
|
|
629
|
+
* that silently does nothing leaves the column exactly where it was while reading like a fix.
|
|
630
|
+
* Dropping a primary key column is an error too; dropping a NOT NULL column with no default is
|
|
631
|
+
* a warning.
|
|
632
|
+
*/
|
|
633
|
+
columns: z.record(z.string(), ColumnRulesSchema).optional(),
|
|
634
|
+
/**
|
|
635
|
+
* How every relative specifier drzl invents spells its extension, for every generator.
|
|
636
|
+
* A generator may override it. Defaults to `js`, which is the only form that resolves
|
|
637
|
+
* under every `moduleResolution` without a compiler flag.
|
|
638
|
+
*/
|
|
639
|
+
importExtension: ImportExtensionSchema.default(DEFAULT_IMPORT_EXTENSION),
|
|
640
|
+
analyzer: AnalyzerSchema.default({
|
|
641
|
+
includeRelations: true,
|
|
642
|
+
validateConstraints: true,
|
|
643
|
+
includeHeuristicRelations: false
|
|
644
|
+
}),
|
|
645
|
+
generators: z.array(GeneratorSchema).min(1).default([{ kind: "orpc" }])
|
|
646
|
+
}).superRefine((cfg, ctx) => {
|
|
647
|
+
cfg.generators.forEach((g, i) => {
|
|
648
|
+
const report = (base, affix, schemaSuffix) => {
|
|
649
|
+
for (const issue of validateAffix(affix, schemaSuffix)) {
|
|
650
|
+
ctx.addIssue({
|
|
651
|
+
code: "custom",
|
|
652
|
+
path: ["generators", i, ...base, ...issue.path],
|
|
653
|
+
message: issue.message
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
report(["affix"], g.affix, g.schemaSuffix);
|
|
658
|
+
report(
|
|
659
|
+
["validation", "affix"],
|
|
660
|
+
g.validation?.affix,
|
|
661
|
+
g.validation?.schemaSuffix
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
function defineConfig(cfg) {
|
|
666
|
+
return cfg;
|
|
667
|
+
}
|
|
668
|
+
var CONFIG_FILE_NAMES = [
|
|
669
|
+
"drzl.config.ts",
|
|
670
|
+
"drzl.config.mjs",
|
|
671
|
+
"drzl.config.js",
|
|
672
|
+
"drzl.config.cjs",
|
|
673
|
+
"drzl.config.json"
|
|
674
|
+
];
|
|
675
|
+
var CONFIG_SCHEMA_ID = "https://use-drzl.github.io/drzl/drzl.config.schema.json";
|
|
676
|
+
function buildConfigJsonSchema() {
|
|
677
|
+
const generated = z.toJSONSchema(ConfigSchema, {
|
|
678
|
+
io: "input",
|
|
679
|
+
target: "draft-7"
|
|
680
|
+
});
|
|
681
|
+
const properties = {
|
|
682
|
+
// Declared so an editor suggests it and does not report the pointer a reader was told to
|
|
683
|
+
// add as an unknown key. `ConfigSchema` is not strict, so the CLI strips it and never sees it.
|
|
684
|
+
$schema: {
|
|
685
|
+
type: "string",
|
|
686
|
+
description: "Path or URL of this schema, for editor completion. Ignored by drzl."
|
|
687
|
+
},
|
|
688
|
+
...generated.properties
|
|
689
|
+
};
|
|
690
|
+
const { $schema, properties: _dropped, ...rest } = generated;
|
|
691
|
+
return {
|
|
692
|
+
$schema,
|
|
693
|
+
$id: CONFIG_SCHEMA_ID,
|
|
694
|
+
title: "DRZL configuration",
|
|
695
|
+
description: "Configuration for the drzl CLI. Also describes drzl.config.ts, which gets the same shape from the defineConfig export of @drzl/cli/config.",
|
|
696
|
+
...rest,
|
|
697
|
+
properties
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
var ROUTER_KINDS = /* @__PURE__ */ new Set(["orpc", "trpc", "hono", "express"]);
|
|
701
|
+
var INJECTION_KINDS = /* @__PURE__ */ new Set(["orpc", "trpc"]);
|
|
702
|
+
function trpcOutDir(g, cfg) {
|
|
703
|
+
return g.path ?? cfg.outDir;
|
|
704
|
+
}
|
|
705
|
+
function honoOutDir(g, cfg) {
|
|
706
|
+
return g.path ?? cfg.outDir;
|
|
707
|
+
}
|
|
708
|
+
function expressOutDir(g, cfg) {
|
|
709
|
+
return g.path ?? cfg.outDir;
|
|
710
|
+
}
|
|
711
|
+
function fastifyOutDir(g, cfg) {
|
|
712
|
+
return g.path ?? cfg.outDir;
|
|
713
|
+
}
|
|
714
|
+
function nestjsOutDir(g, cfg) {
|
|
715
|
+
return g.path ?? cfg.outDir;
|
|
716
|
+
}
|
|
717
|
+
function graphqlOutDir(g, cfg) {
|
|
718
|
+
return g.path ?? cfg.outDir;
|
|
719
|
+
}
|
|
720
|
+
function sharedSchemaNames(opts) {
|
|
721
|
+
const resolved = resolveAffix(opts);
|
|
722
|
+
return NAME_MODES.map((mode) => schemaName(mode, AFFIX_PROBE_TABLE, resolved));
|
|
723
|
+
}
|
|
724
|
+
function resolveConfig(cfg) {
|
|
725
|
+
const warnings = [];
|
|
726
|
+
const generators = cfg.generators.map((g) => ({
|
|
727
|
+
...g,
|
|
728
|
+
importExtension: g.importExtension ?? cfg.importExtension
|
|
729
|
+
}));
|
|
730
|
+
for (const g of generators) {
|
|
731
|
+
if (g.kind === "fastify") {
|
|
732
|
+
if (g.databaseInjection?.enabled) {
|
|
733
|
+
warnings.push(
|
|
734
|
+
`drzl config: the "fastify" generator sets databaseInjection.enabled, which it does not support. Its handlers are stubs and never call a service, so nothing reads the injected handle. Reach your database from inside the handler bodies you fill in, or use the "trpc" or "orpc" generator, which do delegate to @drzl/generator-service.`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
if (g.validation) {
|
|
738
|
+
warnings.push(
|
|
739
|
+
`drzl config: the "fastify" generator sets "validation", which it does not read. Its route schemas are JSON Schema produced by the same builder as the "json-schema" generator and inlined into the routes, so there is no library to choose and no shared schema module to import. Remove the block.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (g.kind === "nestjs") {
|
|
745
|
+
if (g.databaseInjection?.enabled) {
|
|
746
|
+
warnings.push(
|
|
747
|
+
`drzl config: the "nestjs" generator sets databaseInjection.enabled, which it does not support. It emits DTO classes with no handlers at all, so nothing reads the injected handle. Reach your database from the controllers you write around these DTOs, or use the "trpc" or "orpc" generator, which do delegate to @drzl/generator-service.`
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
if (g.includeRelations) {
|
|
751
|
+
warnings.push(
|
|
752
|
+
`drzl config: the "nestjs" generator sets includeRelations, which it does not read. Relation lookups are routes, and this generator emits DTO classes for your own controllers rather than routes. Remove the flag.`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
if (g.validation?.useShared || g.validation?.importPath) {
|
|
756
|
+
warnings.push(
|
|
757
|
+
`drzl config: the "nestjs" generator sets validation.useShared or validation.importPath, which it does not read. Its DTO modules are self-contained: the class fields and the schema are generated from the same columns, and wrapping a schema another generator wrote would let the two drift. Only validation.library is read on this kind.`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
if (g.validation?.schemaSuffix || g.validation?.affix) {
|
|
761
|
+
warnings.push(
|
|
762
|
+
`drzl config: the "nestjs" generator sets validation.schemaSuffix or validation.affix, which it does not read. Those options spell the names of shared schema modules, and this generator imports none. Only validation.library is read on this kind.`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (g.kind === "graphql") {
|
|
768
|
+
if (g.databaseInjection?.enabled) {
|
|
769
|
+
warnings.push(
|
|
770
|
+
`drzl config: the "graphql" generator sets databaseInjection.enabled, which it does not support. It emits SDL and resolver stubs with no handlers at all, so nothing reads the injected handle. Reach your database from the resolvers you write in place of the stubs, or use the "trpc" or "orpc" generator, which do delegate to @drzl/generator-service.`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
if (g.includeRelations) {
|
|
774
|
+
warnings.push(
|
|
775
|
+
`drzl config: the "graphql" generator sets includeRelations, which it does not read. Relation lookups are routes on the router generators, and relation fields on a GraphQL type are resolvers you write against your own data layer. Remove the flag.`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
if (g.validation) {
|
|
779
|
+
warnings.push(
|
|
780
|
+
`drzl config: the "graphql" generator sets "validation", which it does not read. Its schema is GraphQL SDL, GraphQL's own type language, so there is no library to choose and no shared schema module to import. Remove the block.`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (!ROUTER_KINDS.has(g.kind)) continue;
|
|
786
|
+
if (g.databaseInjection?.enabled && !INJECTION_KINDS.has(g.kind)) {
|
|
787
|
+
warnings.push(
|
|
788
|
+
`drzl config: the "${g.kind}" generator sets databaseInjection.enabled, which it does not support. Its handlers are stubs and never call a service, so nothing reads the injected handle. Reach your database from inside the handler bodies you fill in, or use the "trpc" or "orpc" generator, which do delegate to @drzl/generator-service.`
|
|
789
|
+
);
|
|
790
|
+
} else if (g.databaseInjection?.enabled) {
|
|
791
|
+
for (const s of generators.filter((x) => x.kind === "service")) {
|
|
792
|
+
if (!s.databaseInjection) {
|
|
793
|
+
s.databaseInjection = g.databaseInjection;
|
|
794
|
+
} else if (!s.databaseInjection.enabled) {
|
|
795
|
+
warnings.push(
|
|
796
|
+
`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.`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
if ((s.dataAccess ?? "stub") === "stub") {
|
|
800
|
+
warnings.push(
|
|
801
|
+
`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.`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const v = g.validation;
|
|
807
|
+
if (!v?.useShared) continue;
|
|
808
|
+
const library = v.library ?? "zod";
|
|
809
|
+
const siblings = generators.filter((s) => s.kind === library);
|
|
810
|
+
if (siblings.length !== 1) continue;
|
|
811
|
+
const sibling = siblings[0];
|
|
812
|
+
const theirs = sharedSchemaNames({
|
|
813
|
+
affix: sibling.affix,
|
|
814
|
+
schemaSuffix: sibling.schemaSuffix
|
|
815
|
+
});
|
|
816
|
+
if (!v.affix) {
|
|
817
|
+
if (sibling.affix) {
|
|
818
|
+
g.validation = {
|
|
819
|
+
...v,
|
|
820
|
+
affix: resolveAffix({
|
|
821
|
+
affix: sibling.affix,
|
|
822
|
+
schemaSuffix: sibling.schemaSuffix
|
|
823
|
+
})
|
|
824
|
+
};
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
const mine2 = sharedSchemaNames({ schemaSuffix: v.schemaSuffix });
|
|
828
|
+
if (mine2.join(",") !== theirs.join(",")) {
|
|
829
|
+
warnings.push(
|
|
830
|
+
`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.`
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const mine = sharedSchemaNames({
|
|
836
|
+
affix: v.affix,
|
|
837
|
+
schemaSuffix: v.schemaSuffix
|
|
838
|
+
});
|
|
839
|
+
if (mine.join(",") !== theirs.join(",")) {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`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.`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return { config: { ...cfg, generators }, warnings };
|
|
846
|
+
}
|
|
847
|
+
var configShape = null;
|
|
848
|
+
function configShapeForReading() {
|
|
849
|
+
return configShape ??= buildConfigJsonSchema();
|
|
850
|
+
}
|
|
851
|
+
function displayConfigPath(file, cwd = process.cwd()) {
|
|
852
|
+
const relative2 = path.relative(cwd, file);
|
|
853
|
+
return relative2 && !relative2.startsWith("..") ? relative2 : file;
|
|
854
|
+
}
|
|
855
|
+
function finalize(raw, file, onWarn) {
|
|
856
|
+
const parsed = ConfigSchema.safeParse(raw);
|
|
857
|
+
if (!parsed.success) {
|
|
858
|
+
throw new ConfigValidationError(
|
|
859
|
+
formatConfigProblems(
|
|
860
|
+
displayConfigPath(file),
|
|
861
|
+
parsed.error.issues,
|
|
862
|
+
raw,
|
|
863
|
+
configShapeForReading()
|
|
864
|
+
)
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
for (const w of unknownKeyWarnings(raw, configShapeForReading())) onWarn(w);
|
|
868
|
+
const { config, warnings } = resolveConfig(parsed.data);
|
|
869
|
+
for (const w of warnings) onWarn(w);
|
|
870
|
+
return config;
|
|
871
|
+
}
|
|
872
|
+
async function importFreshConfigModule(p) {
|
|
873
|
+
const fsp = await import("fs/promises");
|
|
874
|
+
const ext = path.extname(p).toLowerCase();
|
|
875
|
+
if (ext === ".json") {
|
|
876
|
+
return JSON.parse(await fsp.readFile(p, "utf8"));
|
|
877
|
+
}
|
|
878
|
+
const { createJiti } = await import("jiti");
|
|
879
|
+
const stat = await fsp.stat(p);
|
|
880
|
+
const base = typeof __filename !== "undefined" ? __filename : path.join(process.cwd(), "index.js");
|
|
881
|
+
const jiti = createJiti(base, {
|
|
882
|
+
moduleCache: false,
|
|
883
|
+
// re-evaluate each time
|
|
884
|
+
fsCache: true,
|
|
885
|
+
// keep transform cache
|
|
886
|
+
cacheVersion: String(stat.mtimeMs),
|
|
887
|
+
// bump on edit
|
|
888
|
+
interopDefault: true,
|
|
889
|
+
tryNative: false
|
|
890
|
+
// <-- prevent native import of .ts
|
|
891
|
+
// debug: true,
|
|
892
|
+
});
|
|
893
|
+
const mod = await jiti.import(p);
|
|
894
|
+
return mod?.default ?? mod;
|
|
895
|
+
}
|
|
896
|
+
async function loadConfig(customPath, onWarn = (warning) => console.warn(warning)) {
|
|
897
|
+
const fsp = await import("fs/promises");
|
|
898
|
+
const candidates = customPath ? [customPath] : [...CONFIG_FILE_NAMES];
|
|
899
|
+
for (const c of candidates) {
|
|
900
|
+
const p = path.resolve(process.cwd(), c);
|
|
901
|
+
try {
|
|
902
|
+
await fsp.access(p);
|
|
903
|
+
} catch {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
return finalize(await importFreshConfigModule(p), p, onWarn);
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
function configFromKinds(kinds, schema, onWarn = () => {
|
|
911
|
+
}) {
|
|
912
|
+
const parsed = ConfigSchema.parse({
|
|
913
|
+
...schema ? { schema } : {},
|
|
914
|
+
generators: kinds.map((kind) => ({ kind }))
|
|
915
|
+
});
|
|
916
|
+
const { config, warnings } = resolveConfig(parsed);
|
|
917
|
+
for (const w of warnings) onWarn(w);
|
|
918
|
+
return config;
|
|
919
|
+
}
|
|
920
|
+
function computeGeneratorOutputDirs(cfg, cwd = process.cwd()) {
|
|
921
|
+
const abs = (p) => path.resolve(cwd, p);
|
|
922
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
923
|
+
dirs.add(abs(cfg.outDir));
|
|
924
|
+
for (const g of cfg.generators) {
|
|
925
|
+
if (g.kind === "trpc") dirs.add(abs(trpcOutDir(g, cfg)));
|
|
926
|
+
if (g.kind === "hono") dirs.add(abs(honoOutDir(g, cfg)));
|
|
927
|
+
if (g.kind === "express") dirs.add(abs(expressOutDir(g, cfg)));
|
|
928
|
+
if (g.kind === "fastify") dirs.add(abs(fastifyOutDir(g, cfg)));
|
|
929
|
+
if (g.kind === "nestjs") dirs.add(abs(nestjsOutDir(g, cfg)));
|
|
930
|
+
if (g.kind === "graphql") dirs.add(abs(graphqlOutDir(g, cfg)));
|
|
931
|
+
if (g.kind === "service") dirs.add(abs(g.path ?? "src/services"));
|
|
932
|
+
if (g.kind === "zod") dirs.add(abs(g.path ?? "src/validators/zod"));
|
|
933
|
+
if (g.kind === "valibot") dirs.add(abs(g.path ?? "src/validators/valibot"));
|
|
934
|
+
if (g.kind === "arktype") dirs.add(abs(g.path ?? "src/validators/arktype"));
|
|
935
|
+
if (g.kind === "typebox") dirs.add(abs(g.path ?? "src/validators/typebox"));
|
|
936
|
+
if (g.kind === "effect") dirs.add(abs(g.path ?? "src/validators/effect"));
|
|
937
|
+
if (g.kind === "json-schema") dirs.add(abs(g.path ?? "src/validators/json-schema"));
|
|
938
|
+
}
|
|
939
|
+
return [...dirs];
|
|
940
|
+
}
|
|
941
|
+
function resolveTemplateDirsSync(cfg, cwd = process.cwd()) {
|
|
942
|
+
const results = [];
|
|
943
|
+
const req = createRequire(
|
|
944
|
+
typeof __filename !== "undefined" ? __filename : path.join(process.cwd(), "index.js")
|
|
945
|
+
);
|
|
946
|
+
for (const g of cfg.generators) {
|
|
947
|
+
const t = g.template;
|
|
948
|
+
if (!t || t === "standard" || t === "minimal" || t === "service") continue;
|
|
949
|
+
let pkgDir = null;
|
|
950
|
+
try {
|
|
951
|
+
const pkg = req.resolve(`${t}/package.json`, { paths: [cwd] });
|
|
952
|
+
pkgDir = path.dirname(pkg);
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
if (pkgDir) {
|
|
956
|
+
results.push(pkgDir);
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (/[./\\]/.test(t)) {
|
|
960
|
+
const abs = path.resolve(cwd, t);
|
|
961
|
+
if (fs.existsSync(abs)) results.push(abs);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return Array.from(new Set(results));
|
|
965
|
+
}
|
|
966
|
+
function filterTables(tables, opts) {
|
|
967
|
+
let out = tables;
|
|
968
|
+
if (opts.include?.length) out = out.filter((t) => matchesTable(opts.include, t));
|
|
969
|
+
if (opts.exclude?.length) out = out.filter((t) => !matchesTable(opts.exclude, t));
|
|
970
|
+
return out;
|
|
971
|
+
}
|
|
972
|
+
function tableFilterWarnings(tables, opts) {
|
|
973
|
+
return [
|
|
974
|
+
...ambiguousPatternWarnings(opts.include ?? [], tables, "include"),
|
|
975
|
+
...ambiguousPatternWarnings(opts.exclude ?? [], tables, "exclude")
|
|
976
|
+
];
|
|
977
|
+
}
|
|
978
|
+
function computeWatchTargets(cfg, cwd = process.cwd(), source) {
|
|
979
|
+
const abs = (p) => path.resolve(cwd, p);
|
|
980
|
+
const targets = new Set(CONFIG_FILE_NAMES.map(abs));
|
|
981
|
+
if (source) {
|
|
982
|
+
for (const d of source.watchDirs) targets.add(abs(d));
|
|
983
|
+
if (source.drizzleKitConfigPath) targets.add(abs(source.drizzleKitConfigPath));
|
|
984
|
+
} else if (cfg.schema) {
|
|
985
|
+
targets.add(path.dirname(abs(cfg.schema)));
|
|
986
|
+
}
|
|
987
|
+
for (const t of resolveTemplateDirsSync(cfg, cwd)) targets.add(t);
|
|
988
|
+
return [...targets];
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
export {
|
|
992
|
+
ConfigValidationError,
|
|
993
|
+
nearestKey,
|
|
994
|
+
matchesAny,
|
|
995
|
+
tableAliases,
|
|
996
|
+
matchesTable,
|
|
997
|
+
displayTableName,
|
|
998
|
+
addressableName,
|
|
999
|
+
hasNamedSchemas,
|
|
1000
|
+
ambiguousPatternWarnings,
|
|
1001
|
+
NamingSchema,
|
|
1002
|
+
AffixSchema,
|
|
1003
|
+
ImportExtensionSchema,
|
|
1004
|
+
GeneratorKindSchema,
|
|
1005
|
+
GENERATOR_KINDS,
|
|
1006
|
+
GeneratorSchema,
|
|
1007
|
+
ColumnRulesSchema,
|
|
1008
|
+
AnalyzerSchema,
|
|
1009
|
+
ConfigSchema,
|
|
1010
|
+
defineConfig,
|
|
1011
|
+
CONFIG_FILE_NAMES,
|
|
1012
|
+
CONFIG_SCHEMA_ID,
|
|
1013
|
+
buildConfigJsonSchema,
|
|
1014
|
+
trpcOutDir,
|
|
1015
|
+
honoOutDir,
|
|
1016
|
+
expressOutDir,
|
|
1017
|
+
fastifyOutDir,
|
|
1018
|
+
nestjsOutDir,
|
|
1019
|
+
graphqlOutDir,
|
|
1020
|
+
resolveConfig,
|
|
1021
|
+
importFreshConfigModule,
|
|
1022
|
+
loadConfig,
|
|
1023
|
+
configFromKinds,
|
|
1024
|
+
computeGeneratorOutputDirs,
|
|
1025
|
+
resolveTemplateDirsSync,
|
|
1026
|
+
filterTables,
|
|
1027
|
+
tableFilterWarnings,
|
|
1028
|
+
computeWatchTargets
|
|
1029
|
+
};
|
|
1030
|
+
//# sourceMappingURL=chunk-54E2IO7N.js.map
|