@drzl/validation-core 3.18.0 → 3.20.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/index.cjs CHANGED
@@ -44,7 +44,9 @@ __export(index_exports, {
44
44
  NAME_MODES: () => NAME_MODES,
45
45
  NESTED_PREFIX: () => NESTED_PREFIX,
46
46
  applyTableCase: () => applyTableCase,
47
+ buildBrandPlan: () => buildBrandPlan,
47
48
  buildNestedPlan: () => buildNestedPlan,
49
+ columnMetaFacts: () => columnMetaFacts,
48
50
  describeSet: () => describeSet,
49
51
  formatCode: () => formatCode,
50
52
  importSpecifier: () => importSpecifier,
@@ -63,10 +65,12 @@ __export(index_exports, {
63
65
  pascalCase: () => pascalCase,
64
66
  renderDuplicateFinder: () => renderDuplicateFinder,
65
67
  resolveAffix: () => resolveAffix,
68
+ resolveBranding: () => resolveBranding,
66
69
  resolveConfiguredImport: () => resolveConfiguredImport,
67
70
  resolveNestedDepth: () => resolveNestedDepth,
68
71
  schemaName: () => schemaName,
69
72
  selectColumns: () => selectColumns,
73
+ tableMetaFacts: () => tableMetaFacts,
70
74
  typeName: () => typeName,
71
75
  updateColumns: () => updateColumns,
72
76
  validateAffix: () => validateAffix
@@ -78,6 +82,259 @@ var import_node_module = require("module");
78
82
  var import_node_path2 = __toESM(require("path"), 1);
79
83
  var import_node_url = require("url");
80
84
 
85
+ // src/naming.ts
86
+ var NAME_MODES = ["insert", "update", "select"];
87
+ var DEFAULT_MODE_PREFIX = {
88
+ insert: "Insert",
89
+ update: "Update",
90
+ select: "Select"
91
+ };
92
+ var DEFAULT_TYPE_SUFFIX = {
93
+ insert: "Input",
94
+ update: "Input",
95
+ select: "Output"
96
+ };
97
+ var DEFAULT_SCHEMA_SUFFIX = "Schema";
98
+ var AFFIX_PROBE_TABLE = "users";
99
+ function spread(value, fallback) {
100
+ if (value === void 0) return { ...fallback };
101
+ if (typeof value === "string") return { insert: value, update: value, select: value };
102
+ return {
103
+ insert: value.insert ?? fallback.insert,
104
+ update: value.update ?? fallback.update,
105
+ select: value.select ?? fallback.select
106
+ };
107
+ }
108
+ function pascalCase(s) {
109
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
110
+ }
111
+ function applyTableCase(tsName, tableCase) {
112
+ return tableCase === "pascal" ? pascalCase(tsName) : tsName;
113
+ }
114
+ function resolveAffix(opts) {
115
+ const affix = opts?.affix;
116
+ const legacy = opts?.schemaSuffix ?? DEFAULT_SCHEMA_SUFFIX;
117
+ const legacyMap = {
118
+ insert: legacy,
119
+ update: legacy,
120
+ select: legacy
121
+ };
122
+ return {
123
+ tableCase: affix?.tableCase ?? "preserve",
124
+ schema: {
125
+ prefix: spread(affix?.schema?.prefix, DEFAULT_MODE_PREFIX),
126
+ suffix: spread(affix?.schema?.suffix, legacyMap)
127
+ },
128
+ type: {
129
+ prefix: spread(affix?.type?.prefix, DEFAULT_MODE_PREFIX),
130
+ suffix: spread(affix?.type?.suffix, DEFAULT_TYPE_SUFFIX)
131
+ }
132
+ };
133
+ }
134
+ function schemaName(mode, tsName, affix) {
135
+ return affix.schema.prefix[mode] + applyTableCase(tsName, affix.tableCase) + affix.schema.suffix[mode];
136
+ }
137
+ function typeName(mode, tsName, affix) {
138
+ return affix.type.prefix[mode] + applyTableCase(tsName, affix.tableCase) + affix.type.suffix[mode];
139
+ }
140
+ var PREFIX_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
141
+ var SUFFIX_RE = /^[A-Za-z0-9_$]+$/;
142
+ function validateAffix(affix, schemaSuffix) {
143
+ const issues = [];
144
+ if (!affix) return issues;
145
+ const checkOne = (value, path2, kind) => {
146
+ if (value === "") return;
147
+ const ok = kind === "prefix" ? PREFIX_RE.test(value) : SUFFIX_RE.test(value);
148
+ if (ok) return;
149
+ issues.push({
150
+ path: path2,
151
+ message: `${JSON.stringify(value)} cannot appear in a TypeScript identifier. Use only letters, digits, "_" and "$"` + (kind === "prefix" ? ", and do not start with a digit." : ".")
152
+ });
153
+ };
154
+ const checkValue = (value, base, kind) => {
155
+ if (value === void 0) return;
156
+ if (typeof value === "string") {
157
+ checkOne(value, base, kind);
158
+ return;
159
+ }
160
+ for (const mode of NAME_MODES) {
161
+ const v = value[mode];
162
+ if (v !== void 0) checkOne(v, [...base, mode], kind);
163
+ }
164
+ };
165
+ checkValue(affix.schema?.prefix, ["schema", "prefix"], "prefix");
166
+ checkValue(affix.schema?.suffix, ["schema", "suffix"], "suffix");
167
+ checkValue(affix.type?.prefix, ["type", "prefix"], "prefix");
168
+ checkValue(affix.type?.suffix, ["type", "suffix"], "suffix");
169
+ if (issues.length) return issues;
170
+ const resolved = resolveAffix({ affix, schemaSuffix });
171
+ const collisions = (space, build) => {
172
+ const seen = /* @__PURE__ */ new Map();
173
+ for (const mode of NAME_MODES) {
174
+ const name = build(mode);
175
+ const first = seen.get(name);
176
+ if (first) {
177
+ issues.push({
178
+ path: [space],
179
+ message: `The ${space} names for "${first}" and "${mode}" collide: both resolve to "${name}". All three are emitted into the same file, so at least one prefix or suffix has to differ.`
180
+ });
181
+ } else {
182
+ seen.set(name, mode);
183
+ }
184
+ }
185
+ };
186
+ collisions("schema", (mode) => schemaName(mode, AFFIX_PROBE_TABLE, resolved));
187
+ collisions("type", (mode) => typeName(mode, AFFIX_PROBE_TABLE, resolved));
188
+ return issues;
189
+ }
190
+
191
+ // src/branding.ts
192
+ function resolveBranding(opt) {
193
+ if (!opt) return void 0;
194
+ if (opt === true) return { foreignKeys: true, aliases: true };
195
+ if (opt.enabled === false) return void 0;
196
+ return { foreignKeys: opt.foreignKeys !== false, aliases: opt.aliases !== false };
197
+ }
198
+ var at = (tsName, column) => `${tsName}\0${column}`;
199
+ function referenceOf(table, columnName) {
200
+ const col = table.columns.find((c) => c.name === columnName);
201
+ if (col?.references) return { sqlTable: col.references.table, column: col.references.column };
202
+ for (const fk of table.foreignKeys ?? []) {
203
+ const i = fk.columns.indexOf(columnName);
204
+ if (i === -1) continue;
205
+ const target = fk.foreignColumns[i];
206
+ if (target === void 0) continue;
207
+ return { sqlTable: fk.foreignTable, column: target };
208
+ }
209
+ return void 0;
210
+ }
211
+ function isKeyColumn(table, columnName) {
212
+ return (table.primaryKey?.columns ?? []).includes(columnName);
213
+ }
214
+ function buildBrandPlan(tables, opt) {
215
+ const resolved = resolveBranding(opt);
216
+ if (!resolved) return void 0;
217
+ const notes = [];
218
+ const byTs = /* @__PURE__ */ new Map();
219
+ const dupTs = /* @__PURE__ */ new Set();
220
+ for (const t of tables) {
221
+ if (byTs.has(t.tsName)) dupTs.add(t.tsName);
222
+ byTs.set(t.tsName, t);
223
+ }
224
+ for (const name of dupTs) {
225
+ byTs.delete(name);
226
+ notes.push(
227
+ `two tables are exported as "${name}", so nothing on either is branded: a brand token is built from the export name and the two would be indistinguishable.`
228
+ );
229
+ }
230
+ const bySqlName = /* @__PURE__ */ new Map();
231
+ for (const t of byTs.values()) {
232
+ const list = bySqlName.get(t.name) ?? [];
233
+ list.push(t);
234
+ bySqlName.set(t.name, list);
235
+ }
236
+ const tokens = /* @__PURE__ */ new Map();
237
+ const cache = /* @__PURE__ */ new Map();
238
+ const resolveToken = (tsName, columnName) => {
239
+ const start = at(tsName, columnName);
240
+ if (cache.has(start)) return cache.get(start);
241
+ const origin = byTs.get(tsName)?.columns.find((c) => c.name === columnName);
242
+ if (!origin || origin.arrayDimensions) {
243
+ cache.set(start, void 0);
244
+ return void 0;
245
+ }
246
+ const seen = /* @__PURE__ */ new Set();
247
+ let curTable = tsName;
248
+ let curColumn = columnName;
249
+ let answer;
250
+ let terminal;
251
+ for (; ; ) {
252
+ const key = at(curTable, curColumn);
253
+ if (seen.has(key)) {
254
+ notes.push(
255
+ `${tsName}.${columnName} references a cycle of foreign keys, so it is not branded.`
256
+ );
257
+ break;
258
+ }
259
+ seen.add(key);
260
+ const table = byTs.get(curTable);
261
+ const col = table?.columns.find((c) => c.name === curColumn);
262
+ if (!table || !col) break;
263
+ const ref = resolved.foreignKeys ? referenceOf(table, curColumn) : void 0;
264
+ if (ref) {
265
+ const candidates = bySqlName.get(ref.sqlTable) ?? [];
266
+ if (candidates.length !== 1) {
267
+ notes.push(
268
+ candidates.length === 0 ? `${tsName}.${columnName} references a table "${ref.sqlTable}" that is not in this analysis, so it is not branded.` : `${tsName}.${columnName} references "${ref.sqlTable}", which names more than one table here, so it is not branded.`
269
+ );
270
+ break;
271
+ }
272
+ const next = candidates[0];
273
+ if (!next.columns.some((c) => c.name === ref.column)) {
274
+ notes.push(
275
+ `${tsName}.${columnName} references ${next.tsName}.${ref.column}, which is not a column of that table, so it is not branded.`
276
+ );
277
+ break;
278
+ }
279
+ curTable = next.tsName;
280
+ curColumn = ref.column;
281
+ continue;
282
+ }
283
+ if (isKeyColumn(table, curColumn)) {
284
+ answer = `${curTable}.${curColumn}`;
285
+ terminal = col;
286
+ }
287
+ break;
288
+ }
289
+ if (answer && terminal && terminal.tsType !== origin.tsType) {
290
+ notes.push(
291
+ `${tsName}.${columnName} is a ${origin.tsType} and ${answer} is a ${terminal.tsType}, so it is not branded.`
292
+ );
293
+ answer = void 0;
294
+ }
295
+ cache.set(start, answer);
296
+ return answer;
297
+ };
298
+ for (const t of byTs.values()) {
299
+ for (const c of t.columns) {
300
+ const token = resolveToken(t.tsName, c.name);
301
+ if (token) tokens.set(at(t.tsName, c.name), token);
302
+ }
303
+ }
304
+ const aliases = /* @__PURE__ */ new Map();
305
+ if (resolved.aliases) {
306
+ const claimed = /* @__PURE__ */ new Map();
307
+ const draft = [];
308
+ for (const t of byTs.values()) {
309
+ for (const c of t.columns) {
310
+ const token = tokens.get(at(t.tsName, c.name));
311
+ if (token !== `${t.tsName}.${c.name}`) continue;
312
+ const alias = pascalCase(t.tsName) + pascalCase(c.name);
313
+ draft.push({ tsName: t.tsName, entry: { alias, column: c.name, token } });
314
+ claimed.set(alias, [...claimed.get(alias) ?? [], `${t.tsName}.${c.name}`]);
315
+ }
316
+ }
317
+ for (const { tsName, entry } of draft) {
318
+ const owners = claimed.get(entry.alias) ?? [];
319
+ if (owners.length > 1) continue;
320
+ aliases.set(tsName, [...aliases.get(tsName) ?? [], entry]);
321
+ }
322
+ for (const [alias, owners] of claimed) {
323
+ if (owners.length > 1) {
324
+ notes.push(
325
+ `${owners.join(" and ")} both name their brand alias "${alias}", so neither is exported. The schemas are unaffected; refer to the type as the select type's property instead.`
326
+ );
327
+ }
328
+ }
329
+ }
330
+ return {
331
+ brandOf: (tsName, columnName) => tokens.get(at(tsName, columnName)),
332
+ aliasesFor: (tsName) => aliases.get(tsName) ?? [],
333
+ any: tokens.size > 0,
334
+ notes
335
+ };
336
+ }
337
+
81
338
  // src/checks.ts
82
339
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
83
340
  var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
@@ -351,113 +608,120 @@ function withTsExtension(p) {
351
608
  return `${p}.ts`;
352
609
  }
353
610
 
354
- // src/naming.ts
355
- var NAME_MODES = ["insert", "update", "select"];
356
- var DEFAULT_MODE_PREFIX = {
357
- insert: "Insert",
358
- update: "Update",
359
- select: "Select"
360
- };
361
- var DEFAULT_TYPE_SUFFIX = {
362
- insert: "Input",
363
- update: "Input",
364
- select: "Output"
365
- };
366
- var DEFAULT_SCHEMA_SUFFIX = "Schema";
367
- var AFFIX_PROBE_TABLE = "users";
368
- function spread(value, fallback) {
369
- if (value === void 0) return { ...fallback };
370
- if (typeof value === "string") return { insert: value, update: value, select: value };
371
- return {
372
- insert: value.insert ?? fallback.insert,
373
- update: value.update ?? fallback.update,
374
- select: value.select ?? fallback.select
375
- };
611
+ // src/meta.ts
612
+ function labelled(name, text) {
613
+ return name ? `${name}: ${text}` : text;
376
614
  }
377
- function pascalCase(s) {
378
- return s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
615
+ function literalText(value, kind) {
616
+ return kind === "string" ? `'${value}'` : value;
379
617
  }
380
- function applyTableCase(tsName, tableCase) {
381
- return tableCase === "pascal" ? pascalCase(tsName) : tsName;
618
+ function columnCheckText(k) {
619
+ return labelled(k.name, `${k.column} ${k.operator} ${literalText(k.value, k.kind)}`);
382
620
  }
383
- function resolveAffix(opts) {
384
- const affix = opts?.affix;
385
- const legacy = opts?.schemaSuffix ?? DEFAULT_SCHEMA_SUFFIX;
386
- const legacyMap = {
387
- insert: legacy,
388
- update: legacy,
389
- select: legacy
390
- };
391
- return {
392
- tableCase: affix?.tableCase ?? "preserve",
393
- schema: {
394
- prefix: spread(affix?.schema?.prefix, DEFAULT_MODE_PREFIX),
395
- suffix: spread(affix?.schema?.suffix, legacyMap)
396
- },
397
- type: {
398
- prefix: spread(affix?.type?.prefix, DEFAULT_MODE_PREFIX),
399
- suffix: spread(affix?.type?.suffix, DEFAULT_TYPE_SUFFIX)
400
- }
401
- };
621
+ function setText(k) {
622
+ return labelled(
623
+ k.name,
624
+ `${k.column} IN (${k.values.map((v) => literalText(v, k.kind)).join(", ")})`
625
+ );
402
626
  }
403
- function schemaName(mode, tsName, affix) {
404
- return affix.schema.prefix[mode] + applyTableCase(tsName, affix.tableCase) + affix.schema.suffix[mode];
627
+ function lengthText(k) {
628
+ return labelled(k.name, `length(${k.column}) ${k.operator} ${k.value}`);
405
629
  }
406
- function typeName(mode, tsName, affix) {
407
- return affix.type.prefix[mode] + applyTableCase(tsName, affix.tableCase) + affix.type.suffix[mode];
630
+ function cardinalityText(k) {
631
+ return labelled(k.name, `cardinality(${k.column}) ${k.operator} ${k.value}`);
408
632
  }
409
- var PREFIX_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
410
- var SUFFIX_RE = /^[A-Za-z0-9_$]+$/;
411
- function validateAffix(affix, schemaSuffix) {
412
- const issues = [];
413
- if (!affix) return issues;
414
- const checkOne = (value, path2, kind) => {
415
- if (value === "") return;
416
- const ok = kind === "prefix" ? PREFIX_RE.test(value) : SUFFIX_RE.test(value);
417
- if (ok) return;
418
- issues.push({
419
- path: path2,
420
- message: `${JSON.stringify(value)} cannot appear in a TypeScript identifier. Use only letters, digits, "_" and "$"` + (kind === "prefix" ? ", and do not start with a digit." : ".")
421
- });
422
- };
423
- const checkValue = (value, base, kind) => {
424
- if (value === void 0) return;
425
- if (typeof value === "string") {
426
- checkOne(value, base, kind);
633
+ function rowText(k) {
634
+ return labelled(k.name, `${k.left} ${k.operator} ${k.right}`);
635
+ }
636
+ function takesScalarChecks(c) {
637
+ return !c.arrayDimensions && !c.shape;
638
+ }
639
+ function classifyChecks(table) {
640
+ const perColumn = /* @__PURE__ */ new Map();
641
+ const rows = [];
642
+ const unenforced = [];
643
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
644
+ const add = (column, text, guard) => {
645
+ const c = byName.get(column);
646
+ if (!c || !guard(c)) {
647
+ unenforced.push(text);
427
648
  return;
428
649
  }
429
- for (const mode of NAME_MODES) {
430
- const v = value[mode];
431
- if (v !== void 0) checkOne(v, [...base, mode], kind);
432
- }
650
+ const list = perColumn.get(column) ?? [];
651
+ list.push(text);
652
+ perColumn.set(column, list);
433
653
  };
434
- checkValue(affix.schema?.prefix, ["schema", "prefix"], "prefix");
435
- checkValue(affix.schema?.suffix, ["schema", "suffix"], "suffix");
436
- checkValue(affix.type?.prefix, ["type", "prefix"], "prefix");
437
- checkValue(affix.type?.suffix, ["type", "suffix"], "suffix");
438
- if (issues.length) return issues;
439
- const resolved = resolveAffix({ affix, schemaSuffix });
440
- const collisions = (space, build) => {
441
- const seen = /* @__PURE__ */ new Map();
442
- for (const mode of NAME_MODES) {
443
- const name = build(mode);
444
- const first = seen.get(name);
445
- if (first) {
446
- issues.push({
447
- path: [space],
448
- message: `The ${space} names for "${first}" and "${mode}" collide: both resolve to "${name}". All three are emitted into the same file, so at least one prefix or suffix has to differ.`
449
- });
450
- } else {
451
- seen.set(name, mode);
452
- }
654
+ for (const k of table.checks ?? []) {
655
+ const parsed = parseCheck(k.expression, k.name);
656
+ if (!parsed.ok) {
657
+ unenforced.push(labelled(k.name, (k.expression ?? "").trim()));
658
+ continue;
659
+ }
660
+ for (const c of parsed.checks) add(c.column, columnCheckText(c), takesScalarChecks);
661
+ for (const s of parsed.sets ?? []) add(s.column, setText(s), takesScalarChecks);
662
+ for (const l of parsed.lengths ?? []) add(l.column, lengthText(l), takesScalarChecks);
663
+ for (const a of parsed.cardinalities ?? [])
664
+ add(a.column, cardinalityText(a), (c) => !!c.arrayDimensions);
665
+ for (const r of parsed.rows ?? []) {
666
+ if (byName.has(r.left) && byName.has(r.right)) rows.push(rowText(r));
667
+ else unenforced.push(rowText(r));
453
668
  }
669
+ }
670
+ return { perColumn, rows, unenforced };
671
+ }
672
+ function columnDescription(facts) {
673
+ const parts = [];
674
+ if (facts.maxLength !== void 0) parts.push(`at most ${facts.maxLength} characters`);
675
+ if (facts.maxBytes !== void 0) parts.push(`at most ${facts.maxBytes} bytes`);
676
+ for (const c of facts.checks ?? []) parts.push(`CHECK ${c}`);
677
+ return parts.length ? parts.join(". ") : void 0;
678
+ }
679
+ function tableDescription(facts) {
680
+ const parts = [];
681
+ for (const c of facts.checks ?? []) parts.push(`CHECK ${c}`);
682
+ if (facts.unenforcedChecks?.length) {
683
+ parts.push(
684
+ `not enforced by this schema, the database also checks: ${facts.unenforcedChecks.join("; ")}`
685
+ );
686
+ }
687
+ return parts.length ? parts.join(". ") : void 0;
688
+ }
689
+ function columnMetaFacts(column, table, opts = {}) {
690
+ const checks = classifyChecks(table).perColumn.get(column.name);
691
+ const facts = {
692
+ ...column.sqlType ? { sqlType: column.sqlType } : {},
693
+ ...column.maxLength !== void 0 ? { maxLength: column.maxLength } : {},
694
+ ...column.maxBytes !== void 0 ? { maxBytes: column.maxBytes } : {},
695
+ ...column.hasDefault ? { hasDefault: true } : {},
696
+ ...column.isGenerated ? { generated: true } : {},
697
+ ...checks?.length ? { checks } : {}
454
698
  };
455
- collisions("schema", (mode) => schemaName(mode, AFFIX_PROBE_TABLE, resolved));
456
- collisions("type", (mode) => typeName(mode, AFFIX_PROBE_TABLE, resolved));
457
- return issues;
699
+ if (!opts.description) return facts;
700
+ const description = columnDescription(facts);
701
+ return description ? { ...facts, description } : facts;
702
+ }
703
+ function tableMetaFacts(table, opts) {
704
+ const { rows, unenforced } = classifyChecks(table);
705
+ const pk = table.primaryKey?.columns ?? [];
706
+ const unique = (table.unique ?? []).map((k) => k.columns).filter((c) => c.length > 0);
707
+ const facts = {
708
+ table: table.name,
709
+ ...table.schema ? { schema: table.schema } : {},
710
+ ...opts.dialect ? { dialect: opts.dialect } : {},
711
+ mode: opts.mode,
712
+ ...pk.length ? { primaryKey: pk } : {},
713
+ ...unique.length ? { unique } : {},
714
+ ...table.readOnly ? { readOnly: true } : {},
715
+ ...rows.length ? { checks: rows } : {},
716
+ ...unenforced.length ? { unenforcedChecks: unenforced } : {}
717
+ };
718
+ if (!opts.description) return facts;
719
+ const description = tableDescription(facts);
720
+ return description ? { ...facts, description } : facts;
458
721
  }
459
722
 
460
723
  // src/nested.ts
724
+ var import_analyzer = require("@drzl/analyzer");
461
725
  var NESTED_PREFIX = "Nested";
462
726
  function nestedSchemaName(mode, tsName, affix) {
463
727
  return NESTED_PREFIX + schemaName(mode, tsName, affix);
@@ -485,13 +749,14 @@ var KINDS_BY_MODE = {
485
749
  };
486
750
  var KIND_ORDER = { many: 0, manyToMany: 1, one: 2 };
487
751
  function omittedColumnsFor(parent, child) {
488
- const back = (child.foreignKeys ?? []).filter((fk) => fk.foreignTable === parent.name);
752
+ const parentName = (0, import_analyzer.qualifiedTableName)(parent);
753
+ const back = (child.foreignKeys ?? []).filter((fk) => (0, import_analyzer.qualifiedForeignTable)(fk) === parentName);
489
754
  if (back.length === 1) return { omitted: [...back[0].columns] };
490
755
  if (back.length === 0) return { omitted: [] };
491
756
  const named = back.map((fk) => fk.columns.join("+")).join(", ");
492
757
  return {
493
758
  omitted: [],
494
- note: `${child.tsName} has ${back.length} foreign keys to ${parent.name} (${named}), so which one this relation uses is not stated. None were omitted: supply them yourself.`
759
+ note: `${child.tsName} has ${back.length} foreign keys to ${parentName} (${named}), so which one this relation uses is not stated. None were omitted: supply them yourself.`
495
760
  };
496
761
  }
497
762
  function buildNestedPlan(root, tables, relations, mode, depth) {
@@ -500,14 +765,15 @@ function buildNestedPlan(root, tables, relations, mode, depth) {
500
765
  }
501
766
  function buildNode(table, omitted, tables, relations, mode, depth) {
502
767
  if (depth <= 0) return { table, omitted, arms: [] };
503
- const byDbName = new Map(tables.map((t) => [t.name, t]));
768
+ const byName = new Map(tables.map((t) => [(0, import_analyzer.qualifiedTableName)(t), t]));
504
769
  const allowed = KINDS_BY_MODE[mode];
505
770
  const columnNames = new Set(table.columns.map((c) => c.name));
506
771
  const taken = /* @__PURE__ */ new Set();
507
772
  const arms = [];
508
- const candidates = relations.filter((r) => r.from === table.name && allowed.has(r.kind)).sort((a, b) => KIND_ORDER[a.kind] - KIND_ORDER[b.kind]);
773
+ const self = (0, import_analyzer.qualifiedTableName)(table);
774
+ const candidates = relations.filter((r) => r.from === self && allowed.has(r.kind)).sort((a, b) => KIND_ORDER[a.kind] - KIND_ORDER[b.kind]);
509
775
  for (const rel of candidates) {
510
- const child = byDbName.get(rel.to);
776
+ const child = byName.get(rel.to);
511
777
  if (!child) continue;
512
778
  const key = child.tsName;
513
779
  if (columnNames.has(key)) continue;
@@ -733,7 +999,9 @@ async function formatCode(code, filePath, fmt) {
733
999
  NAME_MODES,
734
1000
  NESTED_PREFIX,
735
1001
  applyTableCase,
1002
+ buildBrandPlan,
736
1003
  buildNestedPlan,
1004
+ columnMetaFacts,
737
1005
  describeSet,
738
1006
  formatCode,
739
1007
  importSpecifier,
@@ -752,10 +1020,12 @@ async function formatCode(code, filePath, fmt) {
752
1020
  pascalCase,
753
1021
  renderDuplicateFinder,
754
1022
  resolveAffix,
1023
+ resolveBranding,
755
1024
  resolveConfiguredImport,
756
1025
  resolveNestedDepth,
757
1026
  schemaName,
758
1027
  selectColumns,
1028
+ tableMetaFacts,
759
1029
  typeName,
760
1030
  updateColumns,
761
1031
  validateAffix