@drzl/cli 4.18.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/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-I4KMDCQR.js";
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";
@@ -420,7 +530,7 @@ var shownThisProcess = false;
420
530
  var tips = [
421
531
  "Pair DRZL watch mode with drizzle-kit to keep schema & API synced.",
422
532
  "Templatize your ORPC routers to roll out new endpoints safely.",
423
- "Need typed validators? Enable the zod, valibot, arktype, or typebox generators.",
533
+ "Need typed validators? Enable the zod, valibot, arktype, typebox, or effect generators.",
424
534
  "Need JSON Schema or OpenAPI? The json-schema generator emits both, with no runtime dependency.",
425
535
  "Use output headers to track generated files and trim noisy diffs."
426
536
  ];
@@ -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-TRJLPIWT.js")
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, { schemaTypes: true })
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`));
@@ -805,6 +921,25 @@ program.command("generate").description("Run configured generators (drzl.config.
805
921
  reportGeneratorFailure(g.kind, e);
806
922
  process.exit(1);
807
923
  }
924
+ } else if (g.kind === "effect") {
925
+ try {
926
+ const { EffectGenerator } = await loadGenerator(
927
+ "@drzl/generator-effect",
928
+ () => import("./dist-WHAW3AMQ.js")
929
+ );
930
+ const gen = new EffectGenerator(analysis);
931
+ const target = g.path ?? "src/validators/effect";
932
+ const files = await gen.generate(
933
+ validationOptions(g, cfg, target, { schemaTypes: true })
934
+ );
935
+ progress.stop();
936
+ ora().succeed(chalk3.green(`Generated (effect): ${files.length} files`));
937
+ files.forEach((f) => console.log(" -", chalk3.cyan(f)));
938
+ } catch (e) {
939
+ progress.stop();
940
+ reportGeneratorFailure(g.kind, e);
941
+ process.exit(1);
942
+ }
808
943
  }
809
944
  }
810
945
  if (driftBefore) {
@@ -972,7 +1107,11 @@ program.command("watch").description("Watch schema and regenerate on changes").o
972
1107
  validateConstraints: cfg.analyzer.validateConstraints,
973
1108
  includeHeuristicRelations: cfg.analyzer.includeHeuristicRelations
974
1109
  });
975
- analysis.tables = filterTables(analysis.tables, cfg);
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));
976
1115
  if (!opts.json) reportWideColumns(analysis.issues);
977
1116
  if (opts.pipeline === "analyze") {
978
1117
  if (opts.json) {
@@ -1071,7 +1210,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
1071
1210
  const gen = new ZodGenerator(analysis);
1072
1211
  const target = g.path ?? "src/validators/zod";
1073
1212
  const files = await gen.generate(
1074
- validationOptions(g, cfg, target, { schemaTypes: true })
1213
+ validationOptions(g, cfg, target, { schemaTypes: true, meta: true })
1075
1214
  );
1076
1215
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
1077
1216
  chalk3.green(`Generated (zod): ${files.length} files`),
@@ -1131,7 +1270,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
1131
1270
  const gen = new TypeBoxGenerator(analysis);
1132
1271
  const target = g.path ?? "src/validators/typebox";
1133
1272
  const files = await gen.generate(
1134
- validationOptions(g, cfg, target, { schemaTypes: true })
1273
+ validationOptions(g, cfg, target, {
1274
+ schemaTypes: true,
1275
+ standardSchema: true
1276
+ })
1135
1277
  );
1136
1278
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
1137
1279
  chalk3.green(`Generated (typebox): ${files.length} files`),
@@ -1142,11 +1284,31 @@ program.command("watch").description("Watch schema and regenerate on changes").o
1142
1284
  reportGeneratorFailure(g.kind, e);
1143
1285
  return;
1144
1286
  }
1287
+ } else if (g.kind === "effect") {
1288
+ try {
1289
+ const { EffectGenerator } = await loadGenerator(
1290
+ "@drzl/generator-effect",
1291
+ () => import("./dist-WHAW3AMQ.js")
1292
+ );
1293
+ const gen = new EffectGenerator(analysis);
1294
+ const target = g.path ?? "src/validators/effect";
1295
+ const files = await gen.generate(
1296
+ validationOptions(g, cfg, target, { schemaTypes: true })
1297
+ );
1298
+ opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
1299
+ chalk3.green(`Generated (effect): ${files.length} files`),
1300
+ files.map((f) => chalk3.cyan(f)).join(", ")
1301
+ );
1302
+ newFiles.push(...files);
1303
+ } catch (e) {
1304
+ reportGeneratorFailure(g.kind, e);
1305
+ return;
1306
+ }
1145
1307
  } else if (g.kind === "json-schema") {
1146
1308
  try {
1147
1309
  const { JsonSchemaGenerator } = await loadGenerator(
1148
1310
  "@drzl/generator-json-schema",
1149
- () => import("./dist-TRJLPIWT.js")
1311
+ () => import("./dist-K6N4F3XW.js")
1150
1312
  );
1151
1313
  const gen = new JsonSchemaGenerator(analysis);
1152
1314
  const target = g.path ?? "src/validators/json-schema";