@drzl/cli 4.19.0 → 4.21.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-V2IXXAC2.js → chunk-XNNKHBGV.js} +146 -7
- package/dist/chunk-XNNKHBGV.js.map +1 -0
- package/dist/cli.cjs +385 -104
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +135 -12
- package/dist/cli.js.map +1 -1
- package/dist/config.cjs +135 -6
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +57 -5
- package/dist/config.d.ts +57 -5
- package/dist/config.js +5 -1
- package/dist/{dist-TRJLPIWT.js → dist-K6N4F3XW.js} +19 -10
- package/dist/dist-K6N4F3XW.js.map +1 -0
- package/dist/{dist-CZDVFYFW.js → dist-WHAW3AMQ.js} +42 -16
- package/dist/dist-WHAW3AMQ.js.map +1 -0
- package/package.json +10 -10
- 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/cli.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
addressableName,
|
|
4
|
+
ambiguousPatternWarnings,
|
|
3
5
|
computeGeneratorOutputDirs,
|
|
4
6
|
computeWatchTargets,
|
|
7
|
+
displayTableName,
|
|
5
8
|
filterTables,
|
|
9
|
+
hasNamedSchemas,
|
|
6
10
|
loadConfig,
|
|
11
|
+
matchesAny,
|
|
12
|
+
matchesTable,
|
|
13
|
+
tableFilterWarnings,
|
|
7
14
|
trpcOutDir
|
|
8
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XNNKHBGV.js";
|
|
9
16
|
|
|
10
17
|
// src/cli.ts
|
|
11
18
|
import { SchemaAnalyzer } from "@drzl/analyzer";
|
|
@@ -32,6 +39,11 @@ function validationOptions(g, cfg, outDir, caps = {}) {
|
|
|
32
39
|
duplicateFinder: g.duplicateFinder,
|
|
33
40
|
nestedSchemas: g.nestedSchemas,
|
|
34
41
|
nestedDepth: g.nestedDepth,
|
|
42
|
+
// Every validation generator can express a brand, including TypeBox, which has no brand
|
|
43
|
+
// helper and gets one from `TUnsafe` instead. So this needs no capability flag: an option
|
|
44
|
+
// that reached only four of the five would be the class of defect this file exists to
|
|
45
|
+
// remove.
|
|
46
|
+
branded: g.branded,
|
|
35
47
|
// Only where the generator can act on them, so an unsupported option is absent rather than
|
|
36
48
|
// present and ignored.
|
|
37
49
|
...caps.schemaTypes ? {
|
|
@@ -39,7 +51,9 @@ function validationOptions(g, cfg, outDir, caps = {}) {
|
|
|
39
51
|
schemaPath: cfg.schema,
|
|
40
52
|
typedJson: g.typedJson,
|
|
41
53
|
typedColumns: g.typedColumns
|
|
42
|
-
} : {}
|
|
54
|
+
} : {},
|
|
55
|
+
...caps.standardSchema ? { standardSchema: g.standardSchema } : {},
|
|
56
|
+
...caps.meta ? { meta: g.meta } : {}
|
|
43
57
|
};
|
|
44
58
|
}
|
|
45
59
|
|
|
@@ -76,6 +90,9 @@ function trpcOptions(g, cfg, servicesDir) {
|
|
|
76
90
|
};
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
// src/column-filter.ts
|
|
94
|
+
import { parseCheck as parseCheck2 } from "@drzl/validation-core";
|
|
95
|
+
|
|
79
96
|
// src/doctor.ts
|
|
80
97
|
import { parseCheck } from "@drzl/validation-core";
|
|
81
98
|
import chalk from "chalk";
|
|
@@ -334,6 +351,99 @@ function renderDoctorReport(report) {
|
|
|
334
351
|
return out.join("\n");
|
|
335
352
|
}
|
|
336
353
|
|
|
354
|
+
// src/column-filter.ts
|
|
355
|
+
function checkedColumns(expression, name) {
|
|
356
|
+
const parsed = parseCheck2(expression, name);
|
|
357
|
+
if (!parsed.ok) return [];
|
|
358
|
+
return [...new Set(namedColumns(parsed).map((n) => n.column))];
|
|
359
|
+
}
|
|
360
|
+
function filterColumns(tables, spec) {
|
|
361
|
+
const entries = Object.entries(spec ?? {});
|
|
362
|
+
if (!entries.length) return { tables, warnings: [] };
|
|
363
|
+
const errors = [];
|
|
364
|
+
const warnings = [];
|
|
365
|
+
const nameForConfig = hasNamedSchemas(tables) ? addressableName : displayTableName;
|
|
366
|
+
for (const [tablePattern, rules] of entries) {
|
|
367
|
+
const matched = tables.filter((t) => matchesTable([tablePattern], t));
|
|
368
|
+
if (!matched.length) {
|
|
369
|
+
errors.push(
|
|
370
|
+
`columns[${JSON.stringify(tablePattern)}] matches no table. The schema declares: ${tables.map(nameForConfig).join(", ") || "(no tables)"}.`
|
|
371
|
+
);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const available = [...new Set(matched.flatMap((t) => t.columns.map((c) => c.name)))];
|
|
375
|
+
for (const which of ["pick", "omit"]) {
|
|
376
|
+
for (const pattern of rules[which] ?? []) {
|
|
377
|
+
if (available.some((name) => matchesAny([pattern], name))) continue;
|
|
378
|
+
errors.push(
|
|
379
|
+
`columns[${JSON.stringify(tablePattern)}].${which} names ${JSON.stringify(pattern)}, which matches no column of ${matched.map(nameForConfig).join(", ")}. Available: ${available.join(", ")}.`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
warnings.push(
|
|
385
|
+
...ambiguousPatternWarnings(
|
|
386
|
+
entries.map(([p]) => p),
|
|
387
|
+
tables,
|
|
388
|
+
"columns"
|
|
389
|
+
)
|
|
390
|
+
);
|
|
391
|
+
const out = tables.map((table) => {
|
|
392
|
+
const mine = entries.filter(([pattern]) => matchesTable([pattern], table));
|
|
393
|
+
if (!mine.length) return table;
|
|
394
|
+
let keep = table.columns;
|
|
395
|
+
for (const [, rules] of mine) {
|
|
396
|
+
if (rules.pick?.length) keep = keep.filter((c) => matchesAny(rules.pick, c.name));
|
|
397
|
+
if (rules.omit?.length) keep = keep.filter((c) => !matchesAny(rules.omit, c.name));
|
|
398
|
+
}
|
|
399
|
+
if (keep.length === table.columns.length) return table;
|
|
400
|
+
const kept = new Set(keep.map((c) => c.name));
|
|
401
|
+
const dropped = table.columns.filter((c) => !kept.has(c.name));
|
|
402
|
+
if (!keep.length) {
|
|
403
|
+
errors.push(
|
|
404
|
+
`columns leaves table "${displayTableName(table)}" with no columns at all. An empty schema describes no row, so this is never a narrower API. Exclude the table instead, with the top-level "exclude" option.`
|
|
405
|
+
);
|
|
406
|
+
return table;
|
|
407
|
+
}
|
|
408
|
+
const lostKey = (table.primaryKey?.columns ?? []).filter((n) => !kept.has(n));
|
|
409
|
+
if (lostKey.length) {
|
|
410
|
+
errors.push(
|
|
411
|
+
`columns drops ${lostKey.map((n) => JSON.stringify(n)).join(", ")} from table "${displayTableName(table)}", which is part of its primary key (${table.primaryKey?.columns.join(", ")}). The generated getById, update and delete address rows by that key, so the emitted schemas would describe a row nothing can address. Keep the key, or leave the whole table out with the top-level "exclude" option.`
|
|
412
|
+
);
|
|
413
|
+
return table;
|
|
414
|
+
}
|
|
415
|
+
for (const c of dropped) {
|
|
416
|
+
if (c.nullable || c.hasDefault || c.isGenerated || table.readOnly) continue;
|
|
417
|
+
warnings.push(
|
|
418
|
+
`drzl config: the "columns" option drops "${c.name}" from table "${displayTableName(table)}", and the database requires it: NOT NULL with no default. The emitted insert schema therefore describes a payload that is not a complete row, so whatever calls db.insert has to supply "${c.name}" itself.`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
for (const k of table.checks ?? []) {
|
|
422
|
+
const lost = checkedColumns(k.expression, k.name).filter(
|
|
423
|
+
(n) => !kept.has(n) && table.columns.some((c) => c.name === n)
|
|
424
|
+
);
|
|
425
|
+
if (!lost.length) continue;
|
|
426
|
+
warnings.push(
|
|
427
|
+
`drzl config: CHECK ${k.name ? `"${k.name}"` : "(unnamed)"} on table "${displayTableName(table)}" names ${lost.map((n) => JSON.stringify(n)).join(", ")}, which the "columns" option drops, so nothing DRZL emits enforces it. Your database still does.`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
...table,
|
|
432
|
+
columns: keep,
|
|
433
|
+
unique: (table.unique ?? []).filter((k) => k.columns.every((n) => kept.has(n))),
|
|
434
|
+
indexes: (table.indexes ?? []).filter((i) => i.columns.every((n) => kept.has(n))),
|
|
435
|
+
...table.foreignKeys ? { foreignKeys: table.foreignKeys.filter((f) => f.columns.every((n) => kept.has(n))) } : {}
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
if (errors.length) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`drzl config: the "columns" option cannot be honoured.
|
|
441
|
+
` + errors.map((e) => ` - ${e}`).join("\n")
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return { tables: out, warnings };
|
|
445
|
+
}
|
|
446
|
+
|
|
337
447
|
// src/drift.ts
|
|
338
448
|
import { promises as fs } from "fs";
|
|
339
449
|
import path from "path";
|
|
@@ -629,8 +739,11 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
629
739
|
validateConstraints: cfg.analyzer.validateConstraints,
|
|
630
740
|
includeHeuristicRelations: cfg.analyzer.includeHeuristicRelations
|
|
631
741
|
});
|
|
632
|
-
analysis.tables = filterTables(analysis.tables, cfg);
|
|
633
742
|
spinner.succeed(`Analysis complete in ${Date.now() - t0}ms`);
|
|
743
|
+
const narrowed = filterColumns(analysis.tables, cfg.columns);
|
|
744
|
+
const filterWarnings = tableFilterWarnings(narrowed.tables, cfg);
|
|
745
|
+
analysis.tables = filterTables(narrowed.tables, cfg);
|
|
746
|
+
for (const w of [...narrowed.warnings, ...filterWarnings]) console.warn(chalk3.yellow(w));
|
|
634
747
|
reportWideColumns(analysis.issues);
|
|
635
748
|
const driftDirs = computeGeneratorOutputDirs(cfg);
|
|
636
749
|
const driftBefore = opts.check ? await snapshotAll(driftDirs) : null;
|
|
@@ -721,7 +834,7 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
721
834
|
const gen = new ZodGenerator(analysis);
|
|
722
835
|
const target = g.path ?? "src/validators/zod";
|
|
723
836
|
const files = await gen.generate(
|
|
724
|
-
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
837
|
+
validationOptions(g, cfg, target, { schemaTypes: true, meta: true })
|
|
725
838
|
);
|
|
726
839
|
progress.stop();
|
|
727
840
|
ora().succeed(chalk3.green(`Generated (zod): ${files.length} files`));
|
|
@@ -773,7 +886,7 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
773
886
|
try {
|
|
774
887
|
const { JsonSchemaGenerator } = await loadGenerator(
|
|
775
888
|
"@drzl/generator-json-schema",
|
|
776
|
-
() => import("./dist-
|
|
889
|
+
() => import("./dist-K6N4F3XW.js")
|
|
777
890
|
);
|
|
778
891
|
const gen = new JsonSchemaGenerator(analysis);
|
|
779
892
|
const target = g.path ?? "src/validators/json-schema";
|
|
@@ -795,7 +908,10 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
795
908
|
const gen = new TypeBoxGenerator(analysis);
|
|
796
909
|
const target = g.path ?? "src/validators/typebox";
|
|
797
910
|
const files = await gen.generate(
|
|
798
|
-
validationOptions(g, cfg, target, {
|
|
911
|
+
validationOptions(g, cfg, target, {
|
|
912
|
+
schemaTypes: true,
|
|
913
|
+
standardSchema: true
|
|
914
|
+
})
|
|
799
915
|
);
|
|
800
916
|
progress.stop();
|
|
801
917
|
ora().succeed(chalk3.green(`Generated (typebox): ${files.length} files`));
|
|
@@ -809,7 +925,7 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
809
925
|
try {
|
|
810
926
|
const { EffectGenerator } = await loadGenerator(
|
|
811
927
|
"@drzl/generator-effect",
|
|
812
|
-
() => import("./dist-
|
|
928
|
+
() => import("./dist-WHAW3AMQ.js")
|
|
813
929
|
);
|
|
814
930
|
const gen = new EffectGenerator(analysis);
|
|
815
931
|
const target = g.path ?? "src/validators/effect";
|
|
@@ -991,7 +1107,11 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
991
1107
|
validateConstraints: cfg.analyzer.validateConstraints,
|
|
992
1108
|
includeHeuristicRelations: cfg.analyzer.includeHeuristicRelations
|
|
993
1109
|
});
|
|
994
|
-
|
|
1110
|
+
const narrowed = filterColumns(analysis.tables, cfg.columns);
|
|
1111
|
+
const filterWarnings = tableFilterWarnings(narrowed.tables, cfg);
|
|
1112
|
+
analysis.tables = filterTables(narrowed.tables, cfg);
|
|
1113
|
+
if (!opts.json)
|
|
1114
|
+
for (const w of [...narrowed.warnings, ...filterWarnings]) console.warn(chalk3.yellow(w));
|
|
995
1115
|
if (!opts.json) reportWideColumns(analysis.issues);
|
|
996
1116
|
if (opts.pipeline === "analyze") {
|
|
997
1117
|
if (opts.json) {
|
|
@@ -1090,7 +1210,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1090
1210
|
const gen = new ZodGenerator(analysis);
|
|
1091
1211
|
const target = g.path ?? "src/validators/zod";
|
|
1092
1212
|
const files = await gen.generate(
|
|
1093
|
-
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
1213
|
+
validationOptions(g, cfg, target, { schemaTypes: true, meta: true })
|
|
1094
1214
|
);
|
|
1095
1215
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1096
1216
|
chalk3.green(`Generated (zod): ${files.length} files`),
|
|
@@ -1150,7 +1270,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1150
1270
|
const gen = new TypeBoxGenerator(analysis);
|
|
1151
1271
|
const target = g.path ?? "src/validators/typebox";
|
|
1152
1272
|
const files = await gen.generate(
|
|
1153
|
-
validationOptions(g, cfg, target, {
|
|
1273
|
+
validationOptions(g, cfg, target, {
|
|
1274
|
+
schemaTypes: true,
|
|
1275
|
+
standardSchema: true
|
|
1276
|
+
})
|
|
1154
1277
|
);
|
|
1155
1278
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1156
1279
|
chalk3.green(`Generated (typebox): ${files.length} files`),
|
|
@@ -1165,7 +1288,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1165
1288
|
try {
|
|
1166
1289
|
const { EffectGenerator } = await loadGenerator(
|
|
1167
1290
|
"@drzl/generator-effect",
|
|
1168
|
-
() => import("./dist-
|
|
1291
|
+
() => import("./dist-WHAW3AMQ.js")
|
|
1169
1292
|
);
|
|
1170
1293
|
const gen = new EffectGenerator(analysis);
|
|
1171
1294
|
const target = g.path ?? "src/validators/effect";
|
|
@@ -1185,7 +1308,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1185
1308
|
try {
|
|
1186
1309
|
const { JsonSchemaGenerator } = await loadGenerator(
|
|
1187
1310
|
"@drzl/generator-json-schema",
|
|
1188
|
-
() => import("./dist-
|
|
1311
|
+
() => import("./dist-K6N4F3XW.js")
|
|
1189
1312
|
);
|
|
1190
1313
|
const gen = new JsonSchemaGenerator(analysis);
|
|
1191
1314
|
const target = g.path ?? "src/validators/json-schema";
|