@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/cli.cjs CHANGED
@@ -261,7 +261,7 @@ ${entries}
261
261
  const decided = [...declared, body].join("\n\n");
262
262
  const imports = [];
263
263
  if (useShared) {
264
- const sharedAffix = (0, import_validation_core3.resolveAffix)({
264
+ const sharedAffix = (0, import_validation_core4.resolveAffix)({
265
265
  affix: opts.validation?.affix,
266
266
  schemaSuffix: opts.validation?.schemaSuffix
267
267
  });
@@ -271,21 +271,21 @@ ${entries}
271
271
  ["select", selectName]
272
272
  ].filter(([, local]) => decided.includes(local));
273
273
  if (wanted.length) {
274
- const spec = (0, import_validation_core3.resolveConfiguredImport)(
274
+ const spec = (0, import_validation_core4.resolveConfiguredImport)(
275
275
  opts.validation.importPath,
276
276
  ctx.out,
277
277
  process.cwd(),
278
278
  opts.importExtension
279
279
  );
280
280
  const names = wanted.map(([mode, local]) => {
281
- const exported = (0, import_validation_core3.schemaName)(mode, table.tsName, sharedAffix);
281
+ const exported = (0, import_validation_core4.schemaName)(mode, table.tsName, sharedAffix);
282
282
  return exported === local ? local : `${exported} as ${local}`;
283
283
  }).join(", ");
284
284
  imports.push(`import { ${names} } from '${spec}';`);
285
285
  }
286
286
  }
287
287
  imports.push(
288
- `import { ${[builder, "router"].sort().join(", ")} } from '${(0, import_validation_core3.importSpecifier)(
288
+ `import { ${[builder, "router"].sort().join(", ")} } from '${(0, import_validation_core4.importSpecifier)(
289
289
  `./${BASE_MODULE}.ts`,
290
290
  opts.importExtension
291
291
  )}';`
@@ -334,7 +334,7 @@ function relationProcedures(table, lib, selectSchemaName, taken, service) {
334
334
  return out;
335
335
  }
336
336
  function renderBarrel(routers, ctx, path6, opts) {
337
- const baseSpec = (0, import_validation_core3.importSpecifier)(`./${BASE_MODULE}.ts`, opts.importExtension);
337
+ const baseSpec = (0, import_validation_core4.importSpecifier)(`./${BASE_MODULE}.ts`, opts.importExtension);
338
338
  const reExports = `export { createCallerFactory, publicProcedure, router } from '${baseSpec}';
339
339
  ` + (opts.databaseInjection?.enabled === true ? `export { dbProcedure } from '${baseSpec}';
340
340
  ` : "") + `export type { Context } from '${baseSpec}';
@@ -352,7 +352,7 @@ export type AppRouter = typeof appRouter;
352
352
  ${reExports}`;
353
353
  }
354
354
  const entries = routers.map(({ filePath, exportName, table }) => ({
355
- rel: (0, import_validation_core3.importSpecifier)(
355
+ rel: (0, import_validation_core4.importSpecifier)(
356
356
  "./" + path6.relative(ctx.out, filePath).replace(/\\/g, "/"),
357
357
  opts.importExtension
358
358
  ),
@@ -393,7 +393,7 @@ function routerExportName(table, naming) {
393
393
  function serviceImportSpecifier(table, ctx, opts) {
394
394
  const rel = relativePosix(ctx.out, ctx.services);
395
395
  const dir = !rel ? "." : rel.startsWith(".") ? rel : `./${rel}`;
396
- return (0, import_validation_core3.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
396
+ return (0, import_validation_core4.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
397
397
  }
398
398
  function relativePosix(from, to) {
399
399
  const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -413,11 +413,11 @@ function buildHeader(h) {
413
413
  ];
414
414
  return lines.join("\n") + "\n\n";
415
415
  }
416
- var import_validation_core3, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
416
+ var import_validation_core4, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
417
417
  var init_dist = __esm({
418
418
  "../generator-trpc/dist/index.js"() {
419
419
  "use strict";
420
- import_validation_core3 = require("@drzl/validation-core");
420
+ import_validation_core4 = require("@drzl/validation-core");
421
421
  TRPC_MAJOR = 11;
422
422
  q = (v) => JSON.stringify(v);
423
423
  LIB_IMPORTS = {
@@ -510,7 +510,7 @@ ${body}
510
510
  await fs3.mkdir(out, { recursive: true });
511
511
  const files = [];
512
512
  const write = async (filePath, content) => {
513
- const formatted = await (0, import_validation_core3.formatCode)(
513
+ const formatted = await (0, import_validation_core4.formatCode)(
514
514
  buildHeader(opts.outputHeader) + content,
515
515
  filePath,
516
516
  opts.format
@@ -606,14 +606,14 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
606
606
  case "string": {
607
607
  const out = { type: "string" };
608
608
  if (c.format === "uuid") out.format = UUID_FORMAT;
609
- else if (c.format && import_validation_core5.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core5.COLUMN_FORMATS[c.format];
609
+ else if (c.format && import_validation_core6.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core6.COLUMN_FORMATS[c.format];
610
610
  if (c.maxLength !== void 0) out.maxLength = c.maxLength;
611
611
  applyByteCap(out, c);
612
612
  applyLengths(out, c, lengths);
613
613
  return out;
614
614
  }
615
615
  case "number": {
616
- const out = { type: (0, import_validation_core5.isIntegerColumn)(c) ? "integer" : "number" };
616
+ const out = { type: (0, import_validation_core6.isIntegerColumn)(c) ? "integer" : "number" };
617
617
  if (!c.arrayDimensions) applyNumericBounds(out, c, checks, target);
618
618
  return out;
619
619
  }
@@ -750,7 +750,7 @@ function tableSchema(table, cols, mode, target, applyDefaults, parsed) {
750
750
  };
751
751
  }
752
752
  function collect(table) {
753
- const parsed = (table.checks ?? []).map((k) => (0, import_validation_core5.parseCheck)(k.expression, k.name));
753
+ const parsed = (table.checks ?? []).map((k) => (0, import_validation_core6.parseCheck)(k.expression, k.name));
754
754
  return {
755
755
  checks: parsed.flatMap((p) => p.ok ? p.checks : []),
756
756
  sets: parsed.flatMap((p) => p.ok ? p.sets ?? [] : []),
@@ -764,9 +764,9 @@ function tableSchemas(table, opts = {}) {
764
764
  const parsed = collect(table);
765
765
  const build2 = (cols, mode) => tableSchema(table, cols, mode, target, !!opts.applyDefaults, parsed);
766
766
  return {
767
- insert: build2((0, import_validation_core5.insertColumns)(table), "insert"),
768
- update: build2((0, import_validation_core5.updateColumns)(table), "update"),
769
- select: build2((0, import_validation_core5.selectColumns)(table), "select")
767
+ insert: build2((0, import_validation_core6.insertColumns)(table), "insert"),
768
+ update: build2((0, import_validation_core6.updateColumns)(table), "update"),
769
+ select: build2((0, import_validation_core6.selectColumns)(table), "select")
770
770
  };
771
771
  }
772
772
  function componentsDocument(tables, opts = {}) {
@@ -793,6 +793,7 @@ function foreignKeysOf(table) {
793
793
  return table.columns.filter((c) => c.references).map((c) => ({
794
794
  columns: [c.name],
795
795
  foreignTable: c.references.table,
796
+ ...c.references.schema ? { foreignSchema: c.references.schema } : {},
796
797
  foreignColumns: [c.references.column]
797
798
  }));
798
799
  }
@@ -818,11 +819,11 @@ function build(tables, opts) {
818
819
  const clash = operationIds.get(id);
819
820
  if (clash !== void 0) {
820
821
  throw new Error(
821
- `@drzl/generator-json-schema: the operationId "${id}" would be emitted for both "${clash}" and "${table.name}". An operationId is the method name a client generator derives, and the specification requires it to be unique across the document.`
822
+ `@drzl/generator-json-schema: the operationId "${id}" would be emitted for both "${clash}" and "${(0, import_analyzer.qualifiedTableName)(table)}". An operationId is the method name a client generator derives, and the specification requires it to be unique across the document.`
822
823
  );
823
824
  }
824
- operationIds.set(id, table.name);
825
- return { operationId: id, tags: [table.name], ...rest };
825
+ operationIds.set(id, (0, import_analyzer.qualifiedTableName)(table));
826
+ return { operationId: id, tags: [(0, import_analyzer.qualifiedTableName)(table)], ...rest };
826
827
  };
827
828
  const built = tables.map((table) => ({
828
829
  table,
@@ -840,7 +841,10 @@ function build(tables, opts) {
840
841
  if (table.readOnly) {
841
842
  notes.push("It refuses every write, so only reads are described.");
842
843
  }
843
- tags.push({ name: table.name, description: [`Table "${table.name}".`, ...notes].join(" ") });
844
+ tags.push({
845
+ name: (0, import_analyzer.qualifiedTableName)(table),
846
+ description: [`Table "${(0, import_analyzer.qualifiedTableName)(table)}".`, ...notes].join(" ")
847
+ });
844
848
  const T = pascal(table.tsName);
845
849
  const select = ref(componentName(table, "select"));
846
850
  const validationFailed = {
@@ -856,7 +860,7 @@ function build(tables, opts) {
856
860
  ...jsonBody(ref(ERROR_SCHEMA))
857
861
  });
858
862
  const collection = `/${segment}`;
859
- claim(collection, table.tsName, table.name);
863
+ claim(collection, table.tsName, (0, import_analyzer.qualifiedTableName)(table));
860
864
  const item = {
861
865
  get: operation(`list${T}`, table, {
862
866
  summary: `List every ${table.name} row.`,
@@ -885,7 +889,7 @@ function build(tables, opts) {
885
889
  paths[collection] = item;
886
890
  if (!key) continue;
887
891
  const itemPath = `${collection}/${key.map((c) => `{${c.name}}`).join("/")}`;
888
- claim(itemPath, table.tsName, table.name);
892
+ claim(itemPath, table.tsName, (0, import_analyzer.qualifiedTableName)(table));
889
893
  const parameters = key.map((c) => ({
890
894
  name: c.name,
891
895
  in: "path",
@@ -944,14 +948,18 @@ function build(tables, opts) {
944
948
  for (const child of built) {
945
949
  if (child.table === table) continue;
946
950
  const matching = foreignKeysOf(child.table).filter(
947
- (fk) => fk.foreignTable === table.name && fk.foreignColumns.length === key.length && fk.foreignColumns.every((c, i) => c === key[i].name)
951
+ (fk) => (
952
+ // Qualified on both sides. On the bare name a key pointing at `reporting.users` also
953
+ // answered for `public.users`, so the child was hung under a parent in another schema.
954
+ (0, import_analyzer.qualifiedForeignTable)(fk) === (0, import_analyzer.qualifiedTableName)(table) && fk.foreignColumns.length === key.length && fk.foreignColumns.every((c, i) => c === key[i].name)
955
+ )
948
956
  );
949
957
  if (matching.length !== 1) continue;
950
958
  const subPath = `${itemPath}/${child.segment}`;
951
959
  claim(
952
960
  subPath,
953
961
  `${table.tsName} -> ${child.table.tsName}`,
954
- `${table.name} -> ${child.table.name}`
962
+ `${(0, import_analyzer.qualifiedTableName)(table)} -> ${(0, import_analyzer.qualifiedTableName)(child.table)}`
955
963
  );
956
964
  paths[subPath] = {
957
965
  parameters,
@@ -995,9 +1003,9 @@ function openApiDocument(tables, opts = {}) {
995
1003
  function renderTableModule(table, affix, target, applyDefaults) {
996
1004
  const T = table.tsName;
997
1005
  const schemas = tableSchemas(table, { target, applyDefaults });
998
- const decl = (mode) => `export const ${(0, import_validation_core4.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
1006
+ const decl = (mode) => `export const ${(0, import_validation_core5.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
999
1007
 
1000
- export type ${(0, import_validation_core4.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core4.schemaName)(mode, T, affix)};`;
1008
+ export type ${(0, import_validation_core5.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core5.schemaName)(mode, T, affix)};`;
1001
1009
  return [decl("insert"), decl("update"), decl("select")].join("\n\n") + "\n";
1002
1010
  }
1003
1011
  function resolveDocument(opt) {
@@ -1013,12 +1021,13 @@ function buildHeader2(h) {
1013
1021
 
1014
1022
  `;
1015
1023
  }
1016
- var import_validation_core4, import_validation_core5, DRAFT, UUID_FORMAT, base64, ERROR_SCHEMA, componentName, ref, pascal, modesFor, resourceSegment, jsonBody, errorSchema, DEFAULT_FILE_SUFFIX, JsonSchemaGenerator, index_default2;
1024
+ var import_validation_core5, import_analyzer, import_validation_core6, DRAFT, UUID_FORMAT, base64, ERROR_SCHEMA, componentName, ref, pascal, modesFor, resourceSegment, jsonBody, errorSchema, DEFAULT_FILE_SUFFIX, JsonSchemaGenerator, index_default2;
1017
1025
  var init_dist2 = __esm({
1018
1026
  "../generator-json-schema/dist/index.js"() {
1019
1027
  "use strict";
1020
- import_validation_core4 = require("@drzl/validation-core");
1021
1028
  import_validation_core5 = require("@drzl/validation-core");
1029
+ import_analyzer = require("@drzl/analyzer");
1030
+ import_validation_core6 = require("@drzl/validation-core");
1022
1031
  DRAFT = "https://json-schema.org/draft/2020-12/schema";
1023
1032
  UUID_FORMAT = "uuid";
1024
1033
  base64 = (target) => target === "openapi-3.0" ? { type: "string", format: "byte" } : { type: "string", contentEncoding: "base64" };
@@ -1031,7 +1040,7 @@ var init_dist2 = __esm({
1031
1040
  ...table.readOnly || !key ? [] : ["update"],
1032
1041
  "select"
1033
1042
  ];
1034
- resourceSegment = (table) => encodeURIComponent(table.name);
1043
+ resourceSegment = (table) => table.schema ? `${encodeURIComponent(table.schema)}/${encodeURIComponent(table.name)}` : encodeURIComponent(table.name);
1035
1044
  jsonBody = (schema) => ({ content: { "application/json": { schema } } });
1036
1045
  errorSchema = () => ({
1037
1046
  title: "error",
@@ -1056,14 +1065,14 @@ var init_dist2 = __esm({
1056
1065
  const out = path6.resolve(process.cwd(), opts.outDir);
1057
1066
  const files = [];
1058
1067
  await fs3.mkdir(out, { recursive: true });
1059
- const affix = (0, import_validation_core4.resolveAffix)(opts);
1068
+ const affix = (0, import_validation_core5.resolveAffix)(opts);
1060
1069
  const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX;
1061
1070
  const target = opts.target ?? "draft-2020-12";
1062
1071
  const document = resolveDocument(opts.document);
1063
1072
  for (const table of this.analysis.tables) {
1064
- const filePath = path6.join(out, (0, import_validation_core4.moduleFileName)(table.tsName, fileSuffix));
1073
+ const filePath = path6.join(out, (0, import_validation_core5.moduleFileName)(table.tsName, fileSuffix));
1065
1074
  const code = renderTableModule(table, affix, target, !!opts.applyDefaults);
1066
- const formatted = await (0, import_validation_core4.formatCode)(
1075
+ const formatted = await (0, import_validation_core5.formatCode)(
1067
1076
  buildHeader2(opts.outputHeader) + code,
1068
1077
  filePath,
1069
1078
  opts.format
@@ -1081,7 +1090,7 @@ var init_dist2 = __esm({
1081
1090
  `;
1082
1091
  await fs3.writeFile(
1083
1092
  componentsPath,
1084
- await (0, import_validation_core4.formatCode)(buildHeader2(opts.outputHeader) + code, componentsPath, opts.format),
1093
+ await (0, import_validation_core5.formatCode)(buildHeader2(opts.outputHeader) + code, componentsPath, opts.format),
1085
1094
  "utf8"
1086
1095
  );
1087
1096
  files.push(componentsPath);
@@ -1102,7 +1111,7 @@ var init_dist2 = __esm({
1102
1111
  `;
1103
1112
  await fs3.writeFile(
1104
1113
  tsPath,
1105
- await (0, import_validation_core4.formatCode)(buildHeader2(opts.outputHeader) + code, tsPath, opts.format),
1114
+ await (0, import_validation_core5.formatCode)(buildHeader2(opts.outputHeader) + code, tsPath, opts.format),
1106
1115
  "utf8"
1107
1116
  );
1108
1117
  files.push(tsPath);
@@ -1116,9 +1125,9 @@ var init_dist2 = __esm({
1116
1125
  const ext = opts.importExtension === "none" ? "" : ".js";
1117
1126
  const indexPath = path6.join(out, "index.ts");
1118
1127
  const index = this.analysis.tables.map(
1119
- (t) => `export * from '${(0, import_validation_core4.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
1128
+ (t) => `export * from '${(0, import_validation_core5.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
1120
1129
  ).concat(opts.components ? [`export * from './components${ext}';`] : []).concat(document && document.format !== "json" ? [`export * from './openapi${ext}';`] : []).join("\n") + "\n";
1121
- const indexFormatted = await (0, import_validation_core4.formatCode)(
1130
+ const indexFormatted = await (0, import_validation_core5.formatCode)(
1122
1131
  buildHeader2(opts.outputHeader) + index,
1123
1132
  indexPath,
1124
1133
  opts.format
@@ -1130,7 +1139,7 @@ var init_dist2 = __esm({
1130
1139
  renderTable(table, opts) {
1131
1140
  return renderTableModule(
1132
1141
  table,
1133
- (0, import_validation_core4.resolveAffix)(opts),
1142
+ (0, import_validation_core5.resolveAffix)(opts),
1134
1143
  opts?.target ?? "draft-2020-12",
1135
1144
  !!opts?.applyDefaults
1136
1145
  );
@@ -1179,7 +1188,7 @@ function foldedIntoBounds(c, checks) {
1179
1188
  );
1180
1189
  }
1181
1190
  function nonFiniteBranches(c) {
1182
- const { nan, infinity } = (0, import_validation_core6.nonFiniteAccepted)(c);
1191
+ const { nan, infinity } = (0, import_validation_core7.nonFiniteAccepted)(c);
1183
1192
  return [
1184
1193
  ...nan ? [`${NS}.Number.pipe(${filter("Number.isNaN(v)", "NaN, which this column stores")})`] : [],
1185
1194
  ...infinity ? [`${NS}.Literal(Infinity, -Infinity)`] : []
@@ -1193,11 +1202,11 @@ function dateExpr(mode, coerceDates) {
1193
1202
  const plain = `${NS}.ValidDateFromSelf`;
1194
1203
  if (coerceDates === "none") return plain;
1195
1204
  const fromString = piped(`${NS}.String`, [
1196
- `${NS}.pattern(new RegExp(${JSON.stringify(import_validation_core6.COERCIBLE_DATE_STRING)}))`,
1197
- filter((0, import_validation_core6.parsesToADate)("new Date(v)"), "a date the runtime can parse")
1205
+ `${NS}.pattern(new RegExp(${JSON.stringify(import_validation_core7.COERCIBLE_DATE_STRING)}))`,
1206
+ filter((0, import_validation_core7.parsesToADate)("new Date(v)"), "a date the runtime can parse")
1198
1207
  ]);
1199
1208
  const fromNumber = piped(`${NS}.Number`, [
1200
- filter((0, import_validation_core6.parsesToADate)("new Date(v)"), "a date the runtime can parse")
1209
+ filter((0, import_validation_core7.parsesToADate)("new Date(v)"), "a date the runtime can parse")
1201
1210
  ]);
1202
1211
  const union = `${NS}.Union(${plain}, ${fromString}, ${fromNumber})`;
1203
1212
  if (coerceDates === "all") return union;
@@ -1208,11 +1217,11 @@ function capSteps(c, mode) {
1208
1217
  if (c.shape?.kind === "byteString") {
1209
1218
  const n = c.shape.length;
1210
1219
  if (!n) return steps;
1211
- return mode === "select" ? [filter(`${import_validation_core6.CODEPOINT_LENGTH} <= ${n}`, `at most ${n} characters`)] : [filter(`new TextEncoder().encode(v).length <= ${n}`, `at most ${n} bytes`)];
1220
+ return mode === "select" ? [filter(`${import_validation_core7.CODEPOINT_LENGTH} <= ${n}`, `at most ${n} characters`)] : [filter(`new TextEncoder().encode(v).length <= ${n}`, `at most ${n} bytes`)];
1212
1221
  }
1213
1222
  if (c.maxLength) {
1214
1223
  steps.push(
1215
- filter(`${import_validation_core6.CODEPOINT_LENGTH} <= ${c.maxLength}`, `at most ${c.maxLength} characters`)
1224
+ filter(`${import_validation_core7.CODEPOINT_LENGTH} <= ${c.maxLength}`, `at most ${c.maxLength} characters`)
1216
1225
  );
1217
1226
  }
1218
1227
  if (c.maxBytes) {
@@ -1226,7 +1235,7 @@ function lengthSteps(c, lengths) {
1226
1235
  if (c.arrayDimensions || c.shape) return [];
1227
1236
  return lengths.filter((k) => k.column === c.name).map(
1228
1237
  (k) => filter(
1229
- `${import_validation_core6.CODEPOINT_LENGTH} ${OPS[k.operator]} ${k.value}`,
1238
+ `${import_validation_core7.CODEPOINT_LENGTH} ${OPS[k.operator]} ${k.value}`,
1230
1239
  `${k.name ? `${k.name}: ` : ""}length(${c.name}) ${k.operator} ${k.value}`
1231
1240
  )
1232
1241
  );
@@ -1304,11 +1313,11 @@ function exprForColumn(c, mode, coerceDates, checks, sets, lengths, replaced) {
1304
1313
  switch (c.tsType) {
1305
1314
  case "string": {
1306
1315
  const base = c.format === "uuid" ? `${NS}.UUID` : `${NS}.String`;
1307
- const pattern = c.format && c.format !== "uuid" && import_validation_core6.COLUMN_FORMATS[c.format] ? [`${NS}.pattern(new RegExp(${JSON.stringify(import_validation_core6.COLUMN_FORMATS[c.format])}))`] : [];
1316
+ const pattern = c.format && c.format !== "uuid" && import_validation_core7.COLUMN_FORMATS[c.format] ? [`${NS}.pattern(new RegExp(${JSON.stringify(import_validation_core7.COLUMN_FORMATS[c.format])}))`] : [];
1308
1317
  return piped(base, [...pattern, ...capSteps(c, mode), ...rest]);
1309
1318
  }
1310
1319
  case "number": {
1311
- const base = (0, import_validation_core6.isIntegerColumn)(c) ? `${NS}.Int` : `${NS}.Finite`;
1320
+ const base = (0, import_validation_core7.isIntegerColumn)(c) ? `${NS}.Int` : `${NS}.Finite`;
1312
1321
  return withNonFinite(c, piped(base, [...numericBounds(c, checks), ...rest]));
1313
1322
  }
1314
1323
  case "bigint":
@@ -1329,7 +1338,7 @@ function exprForColumn(c, mode, coerceDates, checks, sets, lengths, replaced) {
1329
1338
  return UNKNOWN_EXPR;
1330
1339
  }
1331
1340
  }
1332
- function renderField(c, mode, coerceDates, checks, sets, lengths, cardinalities, applyDefault, narrowRef) {
1341
+ function renderField(c, mode, coerceDates, checks, sets, lengths, cardinalities, applyDefault, narrowRef, brand) {
1333
1342
  let expr = exprForColumn(
1334
1343
  c,
1335
1344
  mode,
@@ -1344,8 +1353,9 @@ function renderField(c, mode, coerceDates, checks, sets, lengths, cardinalities,
1344
1353
  expr = `${NS}.Array(${expr})`;
1345
1354
  if (i === dims - 1) expr = piped(expr, cardinalitySteps(c, cardinalities));
1346
1355
  }
1356
+ if (brand) expr = piped(expr, [`${NS}.brand(${JSON.stringify(brand)})`]);
1347
1357
  if (c.nullable && !isUnknownExpr(expr)) expr = `${NS}.NullOr(${expr})`;
1348
- if (narrowRef) expr = withNarrowedType(expr, narrowRef);
1358
+ if (narrowRef && !brand) expr = withNarrowedType(expr, narrowRef);
1349
1359
  if (mode === "select") return expr;
1350
1360
  const wantsDefault = mode === "insert" && applyDefault && c.defaultValue !== void 0;
1351
1361
  if (wantsDefault) {
@@ -1357,7 +1367,7 @@ function renderField(c, mode, coerceDates, checks, sets, lengths, cardinalities,
1357
1367
  function wantsRef(c, allColumns) {
1358
1368
  return allColumns || hasNoRuntimeType(c);
1359
1369
  }
1360
- function renderObjectShape(cols, mode, coerceDates, checks, sets, lengths, cardinalities, typedJson, applyDefaults) {
1370
+ function renderObjectShape(cols, mode, coerceDates, checks, sets, lengths, cardinalities, typedJson, applyDefaults, brands) {
1361
1371
  return cols.map((c) => {
1362
1372
  const ref2 = typedJson && wantsRef(c, !!typedJson.allColumns) ? `(typeof ${typedJson.table}.$infer${typedJson.mode === "insert" ? "Insert" : "Select"})[${JSON.stringify(c.name)}]` : void 0;
1363
1373
  const field2 = renderField(
@@ -1369,7 +1379,8 @@ function renderObjectShape(cols, mode, coerceDates, checks, sets, lengths, cardi
1369
1379
  lengths,
1370
1380
  cardinalities,
1371
1381
  applyDefaults,
1372
- ref2
1382
+ ref2,
1383
+ brands?.plan.brandOf(brands.tsName, c.name)
1373
1384
  );
1374
1385
  return ` ${JSON.stringify(c.name)}: ${field2},`;
1375
1386
  }).join("\n");
@@ -1387,7 +1398,7 @@ function indentBlock(code, by = " ") {
1387
1398
  return code.split("\n").map((line) => line ? by + line : line).join("\n");
1388
1399
  }
1389
1400
  function parsedChecksFor(table) {
1390
- const parsed = (table.checks ?? []).map((k) => (0, import_validation_core6.parseCheck)(k.expression, k.name));
1401
+ const parsed = (table.checks ?? []).map((k) => (0, import_validation_core7.parseCheck)(k.expression, k.name));
1391
1402
  return {
1392
1403
  checks: parsed.flatMap((p) => p.ok ? p.checks : []),
1393
1404
  sets: parsed.flatMap((p) => p.ok ? p.sets ?? [] : []),
@@ -1397,15 +1408,15 @@ function parsedChecksFor(table) {
1397
1408
  };
1398
1409
  }
1399
1410
  function nestedNodeCols(node, mode) {
1400
- const all = mode === "insert" ? (0, import_validation_core6.insertColumns)(node.table) : (0, import_validation_core6.selectColumns)(node.table);
1401
- return (0, import_validation_core6.nestedNodeColumns)(all, node);
1411
+ const all = mode === "insert" ? (0, import_validation_core7.insertColumns)(node.table) : (0, import_validation_core7.selectColumns)(node.table);
1412
+ return (0, import_validation_core7.nestedNodeColumns)(all, node);
1402
1413
  }
1403
1414
  function nestedNodes(node, into = []) {
1404
1415
  into.push(node);
1405
1416
  for (const arm of node.arms) nestedNodes(arm.child, into);
1406
1417
  return into;
1407
1418
  }
1408
- function renderNestedObject(node, mode, coerceDates, typedJson, applyDefaults) {
1419
+ function renderNestedObject(node, mode, coerceDates, typedJson, applyDefaults, brands) {
1409
1420
  const cols = nestedNodeCols(node, mode);
1410
1421
  const { checks, sets, rows, lengths, cardinalities } = parsedChecksFor(node.table);
1411
1422
  const tj = typedJson ? { table: node.table.tsName, mode, allColumns: typedJson.allColumns } : void 0;
@@ -1418,12 +1429,20 @@ function renderNestedObject(node, mode, coerceDates, typedJson, applyDefaults) {
1418
1429
  lengths,
1419
1430
  cardinalities,
1420
1431
  tj,
1421
- applyDefaults
1432
+ applyDefaults,
1433
+ brands ? { plan: brands, tsName: node.table.tsName } : void 0
1422
1434
  );
1423
1435
  const arms = node.arms.map((arm) => {
1424
- const notes = (0, import_validation_core6.nestedArmNotes)(arm).map((n) => ` // ${n}
1436
+ const notes = (0, import_validation_core7.nestedArmNotes)(arm).map((n) => ` // ${n}
1425
1437
  `).join("");
1426
- const child = renderNestedObject(arm.child, mode, coerceDates, typedJson, applyDefaults);
1438
+ const child = renderNestedObject(
1439
+ arm.child,
1440
+ mode,
1441
+ coerceDates,
1442
+ typedJson,
1443
+ applyDefaults,
1444
+ brands
1445
+ );
1427
1446
  const inner = arm.single ? `${NS}.NullOr(
1428
1447
  ${indentBlock(indentBlock(child))}
1429
1448
  )` : `${NS}.Array(
@@ -1436,14 +1455,14 @@ ${indentBlock(indentBlock(child))}
1436
1455
  ${body}
1437
1456
  })`, rowSteps(rows, cols));
1438
1457
  }
1439
- function renderNestedSchemas(table, affix, coerceDates, typedJson, applyDefaults, plans) {
1458
+ function renderNestedSchemas(table, affix, coerceDates, typedJson, applyDefaults, plans, brands) {
1440
1459
  const out = [];
1441
1460
  for (const mode of ["insert", "select"]) {
1442
1461
  const plan = plans[mode];
1443
1462
  if (!plan) continue;
1444
- const name = (0, import_validation_core6.nestedSchemaName)(mode, table.tsName, affix);
1445
- const tname = (0, import_validation_core6.nestedTypeName)(mode, table.tsName, affix);
1446
- const expr = renderNestedObject(plan, mode, coerceDates, typedJson, applyDefaults);
1463
+ const name = (0, import_validation_core7.nestedSchemaName)(mode, table.tsName, affix);
1464
+ const tname = (0, import_validation_core7.nestedTypeName)(mode, table.tsName, affix);
1465
+ const expr = renderNestedObject(plan, mode, coerceDates, typedJson, applyDefaults, brands);
1447
1466
  out.push(
1448
1467
  `export const ${name} = ${expr};
1449
1468
 
@@ -1460,16 +1479,16 @@ function nestedPlansFor(table, analysis, depth) {
1460
1479
  const out = {};
1461
1480
  for (const mode of ["insert", "select"]) {
1462
1481
  if (mode === "insert" && table.readOnly) continue;
1463
- const plan = (0, import_validation_core6.buildNestedPlan)(table, analysis.tables, analysis.relations ?? [], mode, depth);
1482
+ const plan = (0, import_validation_core7.buildNestedPlan)(table, analysis.tables, analysis.relations ?? [], mode, depth);
1464
1483
  if (plan) out[mode] = plan;
1465
1484
  }
1466
1485
  return out;
1467
1486
  }
1468
- function renderTableSchemas(table, affix, coerceDates, typedJson, applyDefaults = false, wantsDuplicateFinder = false, nested = {}) {
1487
+ function renderTableSchemas(table, affix, coerceDates, typedJson, applyDefaults = false, wantsDuplicateFinder = false, nested = {}, brands) {
1469
1488
  const T = table.tsName;
1470
- const insertCols = (0, import_validation_core6.insertColumns)(table);
1471
- const updateCols = (0, import_validation_core6.updateColumns)(table);
1472
- const selectCols = (0, import_validation_core6.selectColumns)(table);
1489
+ const insertCols = (0, import_validation_core7.insertColumns)(table);
1490
+ const updateCols = (0, import_validation_core7.updateColumns)(table);
1491
+ const selectCols = (0, import_validation_core7.selectColumns)(table);
1473
1492
  const { checks, sets, rows, lengths, cardinalities } = parsedChecksFor(table);
1474
1493
  const tj = typedJson ? { table: T, allColumns: typedJson.allColumns } : void 0;
1475
1494
  const modes = [
@@ -1478,8 +1497,8 @@ function renderTableSchemas(table, affix, coerceDates, typedJson, applyDefaults
1478
1497
  ["select", selectCols]
1479
1498
  ];
1480
1499
  const blocks = modes.map(([mode, cols]) => {
1481
- const name = (0, import_validation_core6.schemaName)(mode, T, affix);
1482
- const tname = (0, import_validation_core6.typeName)(mode, T, affix);
1500
+ const name = (0, import_validation_core7.schemaName)(mode, T, affix);
1501
+ const tname = (0, import_validation_core7.typeName)(mode, T, affix);
1483
1502
  const body = renderObjectShape(
1484
1503
  cols,
1485
1504
  mode,
@@ -1491,7 +1510,8 @@ function renderTableSchemas(table, affix, coerceDates, typedJson, applyDefaults
1491
1510
  // The update schema references the insert-side inferred types: both describe a value going
1492
1511
  // in, and `$inferSelect` would name the post-default type for a column a write may omit.
1493
1512
  tj ? { ...tj, mode: mode === "select" ? "select" : "insert" } : void 0,
1494
- applyDefaults
1513
+ applyDefaults,
1514
+ brands ? { plan: brands, tsName: T } : void 0
1495
1515
  );
1496
1516
  const expr = piped(`${NS}.Struct({
1497
1517
  ${body}
@@ -1522,7 +1542,7 @@ export const ${STANDARD_PREFIX}${name} = ${NS}.standardSchemaV1(${name});`;
1522
1542
  const needsJson = !typedJson && [...insertCols, ...updateCols, ...selectCols, ...nestedCols].some(
1523
1543
  (c) => c.shape?.kind === "json"
1524
1544
  );
1525
- const finder = wantsDuplicateFinder ? (0, import_validation_core6.renderDuplicateFinder)(table, `findDuplicate${T}`, (0, import_validation_core6.typeName)("insert", T, affix)) : void 0;
1545
+ const finder = wantsDuplicateFinder ? (0, import_validation_core7.renderDuplicateFinder)(table, `findDuplicate${T}`, (0, import_validation_core7.typeName)("insert", T, affix)) : void 0;
1526
1546
  const duplicates = finder ? `
1527
1547
  ${finder}
1528
1548
  ` : "";
@@ -1532,13 +1552,22 @@ ${finder}
1532
1552
  coerceDates,
1533
1553
  typedJson,
1534
1554
  applyDefaults,
1535
- nested
1555
+ nested,
1556
+ brands
1536
1557
  );
1558
+ const selectName = (0, import_validation_core7.schemaName)("select", T, affix);
1559
+ const brandAliases = (brands?.aliasesFor(T) ?? []).map(
1560
+ (a) => `/** The nominal type of ${T}.${a.column}. */
1561
+ export type ${a.alias} = ${NS}.Schema.Type<typeof ${selectName}>[${JSON.stringify(a.column)}];`
1562
+ ).join("\n\n");
1563
+ const brandCode = brandAliases ? `
1564
+ ${brandAliases}
1565
+ ` : "";
1537
1566
  return `import * as ${NS} from 'effect/Schema';
1538
1567
  ${schemaImport}${needsJson ? `
1539
1568
  ${JSON_PREAMBLE}` : ""}
1540
1569
  ${blocks.join("\n\n")}
1541
- ${nestedCode}${duplicates}`;
1570
+ ${brandCode}${nestedCode}${duplicates}`;
1542
1571
  }
1543
1572
  function buildHeader3(h) {
1544
1573
  if (h && h.enabled === false) return "";
@@ -1550,11 +1579,11 @@ function buildHeader3(h) {
1550
1579
  ];
1551
1580
  return lines.join("\n") + "\n\n";
1552
1581
  }
1553
- var import_validation_core6, DEFAULT_FILE_SUFFIX2, STANDARD_PREFIX, NS, OPS, JSON_CONST, JSON_PREAMBLE, UNKNOWN_EXPR, EffectGenerator, index_default3;
1582
+ var import_validation_core7, DEFAULT_FILE_SUFFIX2, STANDARD_PREFIX, NS, OPS, JSON_CONST, JSON_PREAMBLE, UNKNOWN_EXPR, EffectGenerator, index_default3;
1554
1583
  var init_dist3 = __esm({
1555
1584
  "../generator-effect/dist/index.js"() {
1556
1585
  "use strict";
1557
- import_validation_core6 = require("@drzl/validation-core");
1586
+ import_validation_core7 = require("@drzl/validation-core");
1558
1587
  DEFAULT_FILE_SUFFIX2 = ".effect.ts";
1559
1588
  STANDARD_PREFIX = "Standard";
1560
1589
  NS = "Schema";
@@ -1612,12 +1641,12 @@ const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(()
1612
1641
  const out = path6.resolve(process.cwd(), opts.outDir);
1613
1642
  const files = [];
1614
1643
  await fs3.mkdir(out, { recursive: true });
1615
- const affix = (0, import_validation_core6.resolveAffix)(opts);
1644
+ const affix = (0, import_validation_core7.resolveAffix)(opts);
1616
1645
  const coerceDates = opts.coerceDates ?? "input";
1617
1646
  const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX2;
1618
1647
  const wantsTypes = opts.typedJson || opts.typedColumns;
1619
1648
  const typedJson = wantsTypes && opts.schemaPath ? {
1620
- schemaSpecifier: (0, import_validation_core6.resolveConfiguredImport)(
1649
+ schemaSpecifier: (0, import_validation_core7.resolveConfiguredImport)(
1621
1650
  opts.schemaPath,
1622
1651
  out,
1623
1652
  process.cwd(),
@@ -1630,9 +1659,11 @@ const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(()
1630
1659
  "[drzl] typedJson was requested but the schema path is unknown, so json columns keep their wide type."
1631
1660
  );
1632
1661
  }
1633
- const nestedDepth = opts.nestedSchemas ? (0, import_validation_core6.resolveNestedDepth)(opts.nestedDepth, (m) => console.warn(m)) : 0;
1662
+ const nestedDepth = opts.nestedSchemas ? (0, import_validation_core7.resolveNestedDepth)(opts.nestedDepth, (m) => console.warn(m)) : 0;
1663
+ const brands = (0, import_validation_core7.buildBrandPlan)(this.analysis.tables, opts.branded);
1664
+ for (const note of brands?.notes ?? []) console.warn(`[drzl] ${note}`);
1634
1665
  for (const table of this.analysis.tables) {
1635
- const filePath = path6.join(out, (0, import_validation_core6.moduleFileName)(table.tsName, fileSuffix));
1666
+ const filePath = path6.join(out, (0, import_validation_core7.moduleFileName)(table.tsName, fileSuffix));
1636
1667
  const code = renderTableSchemas(
1637
1668
  table,
1638
1669
  affix,
@@ -1640,9 +1671,10 @@ const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(()
1640
1671
  typedJson,
1641
1672
  !!opts.applyDefaults,
1642
1673
  !!opts?.duplicateFinder,
1643
- opts.nestedSchemas ? nestedPlansFor(table, this.analysis, nestedDepth) : {}
1674
+ opts.nestedSchemas ? nestedPlansFor(table, this.analysis, nestedDepth) : {},
1675
+ brands
1644
1676
  );
1645
- const formatted = await (0, import_validation_core6.formatCode)(
1677
+ const formatted = await (0, import_validation_core7.formatCode)(
1646
1678
  buildHeader3(opts.outputHeader) + code,
1647
1679
  filePath,
1648
1680
  opts.format
@@ -1651,7 +1683,7 @@ const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(()
1651
1683
  files.push(filePath);
1652
1684
  }
1653
1685
  const indexPath = path6.join(out, "index.ts");
1654
- const indexFormatted = await (0, import_validation_core6.formatCode)(
1686
+ const indexFormatted = await (0, import_validation_core7.formatCode)(
1655
1687
  buildHeader3(opts.outputHeader) + this.defaultIndex(this.analysis, opts),
1656
1688
  indexPath,
1657
1689
  opts.format
@@ -1663,17 +1695,19 @@ const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(()
1663
1695
  renderTable(table, opts) {
1664
1696
  return renderTableSchemas(
1665
1697
  table,
1666
- (0, import_validation_core6.resolveAffix)(opts),
1698
+ (0, import_validation_core7.resolveAffix)(opts),
1667
1699
  opts?.coerceDates ?? "input",
1668
1700
  void 0,
1669
1701
  !!opts?.applyDefaults,
1670
- !!opts?.duplicateFinder
1702
+ !!opts?.duplicateFinder,
1703
+ {},
1704
+ (0, import_validation_core7.buildBrandPlan)(this.analysis.tables, opts?.branded)
1671
1705
  );
1672
1706
  }
1673
1707
  defaultIndex(analysis, opts) {
1674
1708
  const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX2;
1675
1709
  return analysis.tables.map(
1676
- (t) => `export * from '${(0, import_validation_core6.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
1710
+ (t) => `export * from '${(0, import_validation_core7.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
1677
1711
  ).join("\n") + "\n";
1678
1712
  }
1679
1713
  };
@@ -1682,7 +1716,7 @@ const ${JSON_CONST}: ${NS}.Schema<${JSON_CONST}Type, unknown> = ${NS}.suspend(()
1682
1716
  });
1683
1717
 
1684
1718
  // src/cli.ts
1685
- var import_analyzer = require("@drzl/analyzer");
1719
+ var import_analyzer2 = require("@drzl/analyzer");
1686
1720
  var import_generator_orpc = require("@drzl/generator-orpc");
1687
1721
  var import_chalk3 = __toESM(require("chalk"), 1);
1688
1722
  var import_chokidar = __toESM(require("chokidar"), 1);
@@ -1706,6 +1740,11 @@ function validationOptions(g, cfg, outDir, caps = {}) {
1706
1740
  duplicateFinder: g.duplicateFinder,
1707
1741
  nestedSchemas: g.nestedSchemas,
1708
1742
  nestedDepth: g.nestedDepth,
1743
+ // Every validation generator can express a brand, including TypeBox, which has no brand
1744
+ // helper and gets one from `TUnsafe` instead. So this needs no capability flag: an option
1745
+ // that reached only four of the five would be the class of defect this file exists to
1746
+ // remove.
1747
+ branded: g.branded,
1709
1748
  // Only where the generator can act on them, so an unsupported option is absent rather than
1710
1749
  // present and ignored.
1711
1750
  ...caps.schemaTypes ? {
@@ -1713,7 +1752,9 @@ function validationOptions(g, cfg, outDir, caps = {}) {
1713
1752
  schemaPath: cfg.schema,
1714
1753
  typedJson: g.typedJson,
1715
1754
  typedColumns: g.typedColumns
1716
- } : {}
1755
+ } : {},
1756
+ ...caps.standardSchema ? { standardSchema: g.standardSchema } : {},
1757
+ ...caps.meta ? { meta: g.meta } : {}
1717
1758
  };
1718
1759
  }
1719
1760
 
@@ -1737,6 +1778,47 @@ var fs = __toESM(require("fs"), 1);
1737
1778
  var import_node_module = require("module");
1738
1779
  var path = __toESM(require("path"), 1);
1739
1780
  var import_zod = require("zod");
1781
+
1782
+ // src/patterns.ts
1783
+ function patternToRegExp(pattern) {
1784
+ return new RegExp(
1785
+ "^" + pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"
1786
+ );
1787
+ }
1788
+ function matchesAny(patterns, name) {
1789
+ return patterns.some((p) => patternToRegExp(p).test(name));
1790
+ }
1791
+ var DEFAULT_SCHEMA_ALIAS = "public";
1792
+ function tableAliases(table) {
1793
+ return [table.name, `${table.schema ?? DEFAULT_SCHEMA_ALIAS}.${table.name}`];
1794
+ }
1795
+ function matchesTable(patterns, table) {
1796
+ return tableAliases(table).some((alias) => matchesAny(patterns, alias));
1797
+ }
1798
+ function displayTableName(table) {
1799
+ return table.schema ? `${table.schema}.${table.name}` : table.name;
1800
+ }
1801
+ function addressableName(table) {
1802
+ return `${table.schema ?? DEFAULT_SCHEMA_ALIAS}.${table.name}`;
1803
+ }
1804
+ function hasNamedSchemas(tables) {
1805
+ return tables.some((t) => t.schema);
1806
+ }
1807
+ function ambiguousPatternWarnings(patterns, tables, option) {
1808
+ const out = [];
1809
+ for (const pattern of patterns) {
1810
+ if (pattern.includes(".")) continue;
1811
+ const matched = tables.filter((t) => matchesTable([pattern], t));
1812
+ const schemas = new Set(matched.map((t) => t.schema ?? DEFAULT_SCHEMA_ALIAS));
1813
+ if (schemas.size < 2) continue;
1814
+ out.push(
1815
+ `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]))}.`
1816
+ );
1817
+ }
1818
+ return out;
1819
+ }
1820
+
1821
+ // src/config.ts
1740
1822
  var NamingSchema = import_zod.z.object({
1741
1823
  routerSuffix: import_zod.z.string().default("Router"),
1742
1824
  procedureCase: import_zod.z.enum(["camel", "kebab", "snake"]).default("camel")
@@ -1816,6 +1898,36 @@ var GeneratorSchema = import_zod.z.object({
1816
1898
  * fact about the table rather than the row. This checks the half that needs no database.
1817
1899
  */
1818
1900
  duplicateFinder: import_zod.z.boolean().optional(),
1901
+ /**
1902
+ * zod only. Attach the facts the analyzer knows and a zod schema cannot state, as `.meta()` on
1903
+ * every field and every table schema: the declared SQL type, the primary key, the unique
1904
+ * constraints, whether the database generates or defaults the value, and the CHECK constraints,
1905
+ * including the ones DRZL declined to enforce.
1906
+ *
1907
+ * `z.toJSONSchema` copies these through, so they are also how an OpenAPI document built from the
1908
+ * emitted schemas gets the declared width back: DRZL enforces one as a `.refine()`, and
1909
+ * `toJSONSchema` drops every refinement in silence.
1910
+ *
1911
+ * `true` is the shorthand for `{ enabled: true }`. `{ description: true }` additionally writes a
1912
+ * `description`, which is what an OpenAPI viewer renders to a human.
1913
+ */
1914
+ meta: import_zod.z.union([
1915
+ import_zod.z.boolean(),
1916
+ import_zod.z.object({ enabled: import_zod.z.boolean().optional(), description: import_zod.z.boolean().optional() }).strict()
1917
+ ]).optional(),
1918
+ /**
1919
+ * TypeBox only. Give every emitted schema a `~standard` key, so it can be handed to a tRPC or
1920
+ * oRPC route.
1921
+ *
1922
+ * TypeBox is the one validator DRZL emits that carries none of its own: measured on 0.34.52, a
1923
+ * bare `Type.Object()` has no `~standard` and the package exports nothing matching
1924
+ * `/standard/i`. zod, valibot and arktype all put one on every schema they build, so the option
1925
+ * does nothing for them and is not passed through.
1926
+ *
1927
+ * The property is non-enumerable, so the schema stays a TypeBox schema in every respect that was
1928
+ * already observable, including the JSON Schema `JSON.stringify` produces.
1929
+ */
1930
+ standardSchema: import_zod.z.boolean().optional(),
1819
1931
  /**
1820
1932
  * Emit `NestedInsert<Table>` and `NestedSelect<Table>` beside the flat schemas: the table plus
1821
1933
  * one key per relation, so `{ ...user, posts: [...] }` can be validated whole.
@@ -1832,6 +1944,29 @@ var GeneratorSchema = import_zod.z.object({
1832
1944
  * it is also what terminates a cycle: `users -> posts -> users` stops here.
1833
1945
  */
1834
1946
  nestedDepth: import_zod.z.number().int().optional(),
1947
+ /**
1948
+ * Give every primary key, and every foreign key pointing at one, a nominal type, so a
1949
+ * `users.id` cannot be passed where a `posts.id` is wanted.
1950
+ *
1951
+ * Type level only, in all five validators. Measured on zod 4.4.3, `.brand()` returns the same
1952
+ * schema object it was called on and the parsed value of `1` is `1`, so nothing about what a
1953
+ * schema accepts changes and no bytes are added to the bundle. TypeBox has no brand of its own
1954
+ * and gets a `TUnsafe` cast, which leaves the schema object identical.
1955
+ *
1956
+ * Off by default: it changes the inferred type of every consumer of the select schemas, which
1957
+ * is the point, but it is a change to existing call sites rather than an addition.
1958
+ *
1959
+ * `true` is the shorthand for `{ enabled: true }`. `{ foreignKeys: false }` brands only the
1960
+ * keys themselves, and `{ aliases: false }` stops the `export type UsersId = ...` lines.
1961
+ */
1962
+ branded: import_zod.z.union([
1963
+ import_zod.z.boolean(),
1964
+ import_zod.z.object({
1965
+ enabled: import_zod.z.boolean().optional(),
1966
+ foreignKeys: import_zod.z.boolean().optional(),
1967
+ aliases: import_zod.z.boolean().optional()
1968
+ }).strict()
1969
+ ]).optional(),
1835
1970
  naming: NamingSchema.optional(),
1836
1971
  outputHeader: import_zod.z.object({
1837
1972
  enabled: import_zod.z.boolean().default(true).optional(),
@@ -1926,6 +2061,10 @@ var GeneratorSchema = import_zod.z.object({
1926
2061
  // template options
1927
2062
  templateOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).optional()
1928
2063
  });
2064
+ var ColumnRulesSchema = import_zod.z.object({
2065
+ omit: import_zod.z.array(import_zod.z.string()).optional(),
2066
+ pick: import_zod.z.array(import_zod.z.string()).optional()
2067
+ }).strict();
1929
2068
  var AnalyzerSchema = import_zod.z.object({
1930
2069
  includeRelations: import_zod.z.boolean().default(true),
1931
2070
  validateConstraints: import_zod.z.boolean().default(true),
@@ -1952,6 +2091,37 @@ var ConfigSchema = import_zod.z.object({
1952
2091
  */
1953
2092
  include: import_zod.z.array(import_zod.z.string()).optional(),
1954
2093
  exclude: import_zod.z.array(import_zod.z.string()).optional(),
2094
+ /**
2095
+ * Which columns of those tables to generate for, keyed by table.
2096
+ *
2097
+ * `include`/`exclude` is all or nothing per table, and the column that should not be in a
2098
+ * generated schema is usually sitting in a table you do want: `passwordHash` on `users`, an
2099
+ * internal note beside the public fields, a `tenantId` the server sets from the session and a
2100
+ * request body must not carry. Editing the emitted file is not an answer, because the next
2101
+ * `drzl generate` overwrites it.
2102
+ *
2103
+ * ```ts
2104
+ * columns: {
2105
+ * users: { omit: ['passwordHash'] },
2106
+ * 'app_*': { omit: ['deleted_at'] },
2107
+ * }
2108
+ * ```
2109
+ *
2110
+ * The key is a table pattern in the same language `include`/`exclude` uses: the database table
2111
+ * name, anchored, with `*` as the only metacharacter. Column patterns are the same language
2112
+ * again. Every matching entry applies, in the order written; within one entry `pick` narrows
2113
+ * first and `omit` then removes, so `omit` wins, exactly as `exclude` wins over `include`.
2114
+ *
2115
+ * Applies to every mode and every generator at once, because it narrows the analysis rather
2116
+ * than any one generator's output. A column cannot be kept in `select` and dropped from
2117
+ * `insert`: see the docs for why that form was not taken on.
2118
+ *
2119
+ * A pattern that matches nothing is an error rather than a no-op, because a typo in `omit`
2120
+ * that silently does nothing leaves the column exactly where it was while reading like a fix.
2121
+ * Dropping a primary key column is an error too; dropping a NOT NULL column with no default is
2122
+ * a warning.
2123
+ */
2124
+ columns: import_zod.z.record(import_zod.z.string(), ColumnRulesSchema).optional(),
1955
2125
  /**
1956
2126
  * How every relative specifier drzl invents spells its extension, for every generator.
1957
2127
  * A generator may override it. Defaults to `js`, which is the only form that resolves
@@ -2145,15 +2315,17 @@ function resolveTemplateDirsSync(cfg, cwd = process.cwd()) {
2145
2315
  return Array.from(new Set(results));
2146
2316
  }
2147
2317
  function filterTables(tables, opts) {
2148
- const toRegExp = (pattern) => new RegExp(
2149
- "^" + pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"
2150
- );
2151
- const matches = (patterns, name) => patterns.some((p) => toRegExp(p).test(name));
2152
2318
  let out = tables;
2153
- if (opts.include?.length) out = out.filter((t) => matches(opts.include, t.name));
2154
- if (opts.exclude?.length) out = out.filter((t) => !matches(opts.exclude, t.name));
2319
+ if (opts.include?.length) out = out.filter((t) => matchesTable(opts.include, t));
2320
+ if (opts.exclude?.length) out = out.filter((t) => !matchesTable(opts.exclude, t));
2155
2321
  return out;
2156
2322
  }
2323
+ function tableFilterWarnings(tables, opts) {
2324
+ return [
2325
+ ...ambiguousPatternWarnings(opts.include ?? [], tables, "include"),
2326
+ ...ambiguousPatternWarnings(opts.exclude ?? [], tables, "exclude")
2327
+ ];
2328
+ }
2157
2329
  function computeWatchTargets(cfg, cwd = process.cwd()) {
2158
2330
  const abs = (p) => path.resolve(cwd, p);
2159
2331
  const schemaAbs = abs(cfg.schema);
@@ -2187,6 +2359,9 @@ function trpcOptions(g, cfg, servicesDir) {
2187
2359
  };
2188
2360
  }
2189
2361
 
2362
+ // src/column-filter.ts
2363
+ var import_validation_core3 = require("@drzl/validation-core");
2364
+
2190
2365
  // src/doctor.ts
2191
2366
  var import_validation_core2 = require("@drzl/validation-core");
2192
2367
  var import_chalk = __toESM(require("chalk"), 1);
@@ -2445,6 +2620,99 @@ function renderDoctorReport(report) {
2445
2620
  return out.join("\n");
2446
2621
  }
2447
2622
 
2623
+ // src/column-filter.ts
2624
+ function checkedColumns(expression, name) {
2625
+ const parsed = (0, import_validation_core3.parseCheck)(expression, name);
2626
+ if (!parsed.ok) return [];
2627
+ return [...new Set(namedColumns(parsed).map((n) => n.column))];
2628
+ }
2629
+ function filterColumns(tables, spec) {
2630
+ const entries = Object.entries(spec ?? {});
2631
+ if (!entries.length) return { tables, warnings: [] };
2632
+ const errors = [];
2633
+ const warnings = [];
2634
+ const nameForConfig = hasNamedSchemas(tables) ? addressableName : displayTableName;
2635
+ for (const [tablePattern, rules] of entries) {
2636
+ const matched = tables.filter((t) => matchesTable([tablePattern], t));
2637
+ if (!matched.length) {
2638
+ errors.push(
2639
+ `columns[${JSON.stringify(tablePattern)}] matches no table. The schema declares: ${tables.map(nameForConfig).join(", ") || "(no tables)"}.`
2640
+ );
2641
+ continue;
2642
+ }
2643
+ const available = [...new Set(matched.flatMap((t) => t.columns.map((c) => c.name)))];
2644
+ for (const which of ["pick", "omit"]) {
2645
+ for (const pattern of rules[which] ?? []) {
2646
+ if (available.some((name) => matchesAny([pattern], name))) continue;
2647
+ errors.push(
2648
+ `columns[${JSON.stringify(tablePattern)}].${which} names ${JSON.stringify(pattern)}, which matches no column of ${matched.map(nameForConfig).join(", ")}. Available: ${available.join(", ")}.`
2649
+ );
2650
+ }
2651
+ }
2652
+ }
2653
+ warnings.push(
2654
+ ...ambiguousPatternWarnings(
2655
+ entries.map(([p]) => p),
2656
+ tables,
2657
+ "columns"
2658
+ )
2659
+ );
2660
+ const out = tables.map((table) => {
2661
+ const mine = entries.filter(([pattern]) => matchesTable([pattern], table));
2662
+ if (!mine.length) return table;
2663
+ let keep = table.columns;
2664
+ for (const [, rules] of mine) {
2665
+ if (rules.pick?.length) keep = keep.filter((c) => matchesAny(rules.pick, c.name));
2666
+ if (rules.omit?.length) keep = keep.filter((c) => !matchesAny(rules.omit, c.name));
2667
+ }
2668
+ if (keep.length === table.columns.length) return table;
2669
+ const kept = new Set(keep.map((c) => c.name));
2670
+ const dropped = table.columns.filter((c) => !kept.has(c.name));
2671
+ if (!keep.length) {
2672
+ errors.push(
2673
+ `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.`
2674
+ );
2675
+ return table;
2676
+ }
2677
+ const lostKey = (table.primaryKey?.columns ?? []).filter((n) => !kept.has(n));
2678
+ if (lostKey.length) {
2679
+ errors.push(
2680
+ `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.`
2681
+ );
2682
+ return table;
2683
+ }
2684
+ for (const c of dropped) {
2685
+ if (c.nullable || c.hasDefault || c.isGenerated || table.readOnly) continue;
2686
+ warnings.push(
2687
+ `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.`
2688
+ );
2689
+ }
2690
+ for (const k of table.checks ?? []) {
2691
+ const lost = checkedColumns(k.expression, k.name).filter(
2692
+ (n) => !kept.has(n) && table.columns.some((c) => c.name === n)
2693
+ );
2694
+ if (!lost.length) continue;
2695
+ warnings.push(
2696
+ `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.`
2697
+ );
2698
+ }
2699
+ return {
2700
+ ...table,
2701
+ columns: keep,
2702
+ unique: (table.unique ?? []).filter((k) => k.columns.every((n) => kept.has(n))),
2703
+ indexes: (table.indexes ?? []).filter((i) => i.columns.every((n) => kept.has(n))),
2704
+ ...table.foreignKeys ? { foreignKeys: table.foreignKeys.filter((f) => f.columns.every((n) => kept.has(n))) } : {}
2705
+ };
2706
+ });
2707
+ if (errors.length) {
2708
+ throw new Error(
2709
+ `drzl config: the "columns" option cannot be honoured.
2710
+ ` + errors.map((e) => ` - ${e}`).join("\n")
2711
+ );
2712
+ }
2713
+ return { tables: out, warnings };
2714
+ }
2715
+
2448
2716
  // src/drift.ts
2449
2717
  var import_node_fs = require("fs");
2450
2718
  var import_node_path = __toESM(require("path"), 1);
@@ -2645,7 +2913,7 @@ Need a template, adapter, or generator DRZL doesn't ship yet?
2645
2913
  );
2646
2914
  program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").option("--relations", "include relations", true).option("--validate", "validate constraints", true).option("--out <file>", "write analysis JSON to file").option("--json", "print JSON to stdout (overrides --out)", false).action(async (schema, opts) => {
2647
2915
  try {
2648
- const analyzer = new import_analyzer.SchemaAnalyzer(schema);
2916
+ const analyzer = new import_analyzer2.SchemaAnalyzer(schema);
2649
2917
  const spinner = !opts.json ? (0, import_ora.default)("Analyzing schema...").start() : null;
2650
2918
  const start = Date.now();
2651
2919
  const res = await analyzer.analyze({
@@ -2693,7 +2961,7 @@ program.command("doctor").description("Report what DRZL cannot type or enforce i
2693
2961
  process.exit(1);
2694
2962
  return;
2695
2963
  }
2696
- const analyzer = new import_analyzer.SchemaAnalyzer(target);
2964
+ const analyzer = new import_analyzer2.SchemaAnalyzer(target);
2697
2965
  const analysis = await analyzer.analyze({
2698
2966
  includeRelations: true,
2699
2967
  validateConstraints: true
@@ -2732,7 +3000,7 @@ program.command("generate").description("Run configured generators (drzl.config.
2732
3000
  process.exit(2);
2733
3001
  return;
2734
3002
  }
2735
- const analyzer = new import_analyzer.SchemaAnalyzer(cfg.schema);
3003
+ const analyzer = new import_analyzer2.SchemaAnalyzer(cfg.schema);
2736
3004
  const spinner = (0, import_ora.default)("Analyzing...").start();
2737
3005
  const t0 = Date.now();
2738
3006
  const analysis = await analyzer.analyze({
@@ -2740,8 +3008,11 @@ program.command("generate").description("Run configured generators (drzl.config.
2740
3008
  validateConstraints: cfg.analyzer.validateConstraints,
2741
3009
  includeHeuristicRelations: cfg.analyzer.includeHeuristicRelations
2742
3010
  });
2743
- analysis.tables = filterTables(analysis.tables, cfg);
2744
3011
  spinner.succeed(`Analysis complete in ${Date.now() - t0}ms`);
3012
+ const narrowed = filterColumns(analysis.tables, cfg.columns);
3013
+ const filterWarnings = tableFilterWarnings(narrowed.tables, cfg);
3014
+ analysis.tables = filterTables(narrowed.tables, cfg);
3015
+ for (const w of [...narrowed.warnings, ...filterWarnings]) console.warn(import_chalk3.default.yellow(w));
2745
3016
  reportWideColumns(analysis.issues);
2746
3017
  const driftDirs = computeGeneratorOutputDirs(cfg);
2747
3018
  const driftBefore = opts.check ? await snapshotAll(driftDirs) : null;
@@ -2832,7 +3103,7 @@ program.command("generate").description("Run configured generators (drzl.config.
2832
3103
  const gen = new ZodGenerator(analysis);
2833
3104
  const target = g.path ?? "src/validators/zod";
2834
3105
  const files = await gen.generate(
2835
- validationOptions(g, cfg, target, { schemaTypes: true })
3106
+ validationOptions(g, cfg, target, { schemaTypes: true, meta: true })
2836
3107
  );
2837
3108
  progress.stop();
2838
3109
  (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (zod): ${files.length} files`));
@@ -2906,7 +3177,10 @@ program.command("generate").description("Run configured generators (drzl.config.
2906
3177
  const gen = new TypeBoxGenerator(analysis);
2907
3178
  const target = g.path ?? "src/validators/typebox";
2908
3179
  const files = await gen.generate(
2909
- validationOptions(g, cfg, target, { schemaTypes: true })
3180
+ validationOptions(g, cfg, target, {
3181
+ schemaTypes: true,
3182
+ standardSchema: true
3183
+ })
2910
3184
  );
2911
3185
  progress.stop();
2912
3186
  (0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (typebox): ${files.length} files`));
@@ -2974,7 +3248,7 @@ Generated output is out of date (${drift.length} file(s)):`));
2974
3248
  });
2975
3249
  program.command("generate:orpc").argument("<schema>", "path to drizzle schema (TS)").option("-o, --outDir <dir>", "output directory", "src/api").option("--template <name>", "template name", "standard").option("--includeRelations", "include relation endpoints").action(async (schema, opts) => {
2976
3250
  try {
2977
- const analyzer = new import_analyzer.SchemaAnalyzer(schema);
3251
+ const analyzer = new import_analyzer2.SchemaAnalyzer(schema);
2978
3252
  const analysis = await analyzer.analyze({
2979
3253
  includeRelations: !!opts.includeRelations,
2980
3254
  validateConstraints: true
@@ -2994,7 +3268,7 @@ program.command("generate:orpc").argument("<schema>", "path to drizzle schema (T
2994
3268
  });
2995
3269
  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) => {
2996
3270
  try {
2997
- const analyzer = new import_analyzer.SchemaAnalyzer(schema);
3271
+ const analyzer = new import_analyzer2.SchemaAnalyzer(schema);
2998
3272
  const analysis = await analyzer.analyze({
2999
3273
  includeRelations: !!opts.includeRelations,
3000
3274
  validateConstraints: true
@@ -3096,13 +3370,17 @@ program.command("watch").description("Watch schema and regenerate on changes").o
3096
3370
  })
3097
3371
  );
3098
3372
  }
3099
- const analyzer = new import_analyzer.SchemaAnalyzer(cfg.schema);
3373
+ const analyzer = new import_analyzer2.SchemaAnalyzer(cfg.schema);
3100
3374
  const analysis = await analyzer.analyze({
3101
3375
  includeRelations: cfg.analyzer.includeRelations,
3102
3376
  validateConstraints: cfg.analyzer.validateConstraints,
3103
3377
  includeHeuristicRelations: cfg.analyzer.includeHeuristicRelations
3104
3378
  });
3105
- analysis.tables = filterTables(analysis.tables, cfg);
3379
+ const narrowed = filterColumns(analysis.tables, cfg.columns);
3380
+ const filterWarnings = tableFilterWarnings(narrowed.tables, cfg);
3381
+ analysis.tables = filterTables(narrowed.tables, cfg);
3382
+ if (!opts.json)
3383
+ for (const w of [...narrowed.warnings, ...filterWarnings]) console.warn(import_chalk3.default.yellow(w));
3106
3384
  if (!opts.json) reportWideColumns(analysis.issues);
3107
3385
  if (opts.pipeline === "analyze") {
3108
3386
  if (opts.json) {
@@ -3201,7 +3479,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
3201
3479
  const gen = new ZodGenerator(analysis);
3202
3480
  const target = g.path ?? "src/validators/zod";
3203
3481
  const files = await gen.generate(
3204
- validationOptions(g, cfg, target, { schemaTypes: true })
3482
+ validationOptions(g, cfg, target, { schemaTypes: true, meta: true })
3205
3483
  );
3206
3484
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
3207
3485
  import_chalk3.default.green(`Generated (zod): ${files.length} files`),
@@ -3261,7 +3539,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
3261
3539
  const gen = new TypeBoxGenerator(analysis);
3262
3540
  const target = g.path ?? "src/validators/typebox";
3263
3541
  const files = await gen.generate(
3264
- validationOptions(g, cfg, target, { schemaTypes: true })
3542
+ validationOptions(g, cfg, target, {
3543
+ schemaTypes: true,
3544
+ standardSchema: true
3545
+ })
3265
3546
  );
3266
3547
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
3267
3548
  import_chalk3.default.green(`Generated (typebox): ${files.length} files`),