@drzl/validation-core 3.17.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 +368 -98
- package/dist/index.d.cts +266 -16
- package/dist/index.d.ts +266 -16
- package/dist/index.js +364 -98
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,6 +5,259 @@ import { createRequire } from "module";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { pathToFileURL } from "url";
|
|
7
7
|
|
|
8
|
+
// src/naming.ts
|
|
9
|
+
var NAME_MODES = ["insert", "update", "select"];
|
|
10
|
+
var DEFAULT_MODE_PREFIX = {
|
|
11
|
+
insert: "Insert",
|
|
12
|
+
update: "Update",
|
|
13
|
+
select: "Select"
|
|
14
|
+
};
|
|
15
|
+
var DEFAULT_TYPE_SUFFIX = {
|
|
16
|
+
insert: "Input",
|
|
17
|
+
update: "Input",
|
|
18
|
+
select: "Output"
|
|
19
|
+
};
|
|
20
|
+
var DEFAULT_SCHEMA_SUFFIX = "Schema";
|
|
21
|
+
var AFFIX_PROBE_TABLE = "users";
|
|
22
|
+
function spread(value, fallback) {
|
|
23
|
+
if (value === void 0) return { ...fallback };
|
|
24
|
+
if (typeof value === "string") return { insert: value, update: value, select: value };
|
|
25
|
+
return {
|
|
26
|
+
insert: value.insert ?? fallback.insert,
|
|
27
|
+
update: value.update ?? fallback.update,
|
|
28
|
+
select: value.select ?? fallback.select
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function pascalCase(s) {
|
|
32
|
+
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("");
|
|
33
|
+
}
|
|
34
|
+
function applyTableCase(tsName, tableCase) {
|
|
35
|
+
return tableCase === "pascal" ? pascalCase(tsName) : tsName;
|
|
36
|
+
}
|
|
37
|
+
function resolveAffix(opts) {
|
|
38
|
+
const affix = opts?.affix;
|
|
39
|
+
const legacy = opts?.schemaSuffix ?? DEFAULT_SCHEMA_SUFFIX;
|
|
40
|
+
const legacyMap = {
|
|
41
|
+
insert: legacy,
|
|
42
|
+
update: legacy,
|
|
43
|
+
select: legacy
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
tableCase: affix?.tableCase ?? "preserve",
|
|
47
|
+
schema: {
|
|
48
|
+
prefix: spread(affix?.schema?.prefix, DEFAULT_MODE_PREFIX),
|
|
49
|
+
suffix: spread(affix?.schema?.suffix, legacyMap)
|
|
50
|
+
},
|
|
51
|
+
type: {
|
|
52
|
+
prefix: spread(affix?.type?.prefix, DEFAULT_MODE_PREFIX),
|
|
53
|
+
suffix: spread(affix?.type?.suffix, DEFAULT_TYPE_SUFFIX)
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function schemaName(mode, tsName, affix) {
|
|
58
|
+
return affix.schema.prefix[mode] + applyTableCase(tsName, affix.tableCase) + affix.schema.suffix[mode];
|
|
59
|
+
}
|
|
60
|
+
function typeName(mode, tsName, affix) {
|
|
61
|
+
return affix.type.prefix[mode] + applyTableCase(tsName, affix.tableCase) + affix.type.suffix[mode];
|
|
62
|
+
}
|
|
63
|
+
var PREFIX_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
64
|
+
var SUFFIX_RE = /^[A-Za-z0-9_$]+$/;
|
|
65
|
+
function validateAffix(affix, schemaSuffix) {
|
|
66
|
+
const issues = [];
|
|
67
|
+
if (!affix) return issues;
|
|
68
|
+
const checkOne = (value, path2, kind) => {
|
|
69
|
+
if (value === "") return;
|
|
70
|
+
const ok = kind === "prefix" ? PREFIX_RE.test(value) : SUFFIX_RE.test(value);
|
|
71
|
+
if (ok) return;
|
|
72
|
+
issues.push({
|
|
73
|
+
path: path2,
|
|
74
|
+
message: `${JSON.stringify(value)} cannot appear in a TypeScript identifier. Use only letters, digits, "_" and "$"` + (kind === "prefix" ? ", and do not start with a digit." : ".")
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const checkValue = (value, base, kind) => {
|
|
78
|
+
if (value === void 0) return;
|
|
79
|
+
if (typeof value === "string") {
|
|
80
|
+
checkOne(value, base, kind);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const mode of NAME_MODES) {
|
|
84
|
+
const v = value[mode];
|
|
85
|
+
if (v !== void 0) checkOne(v, [...base, mode], kind);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
checkValue(affix.schema?.prefix, ["schema", "prefix"], "prefix");
|
|
89
|
+
checkValue(affix.schema?.suffix, ["schema", "suffix"], "suffix");
|
|
90
|
+
checkValue(affix.type?.prefix, ["type", "prefix"], "prefix");
|
|
91
|
+
checkValue(affix.type?.suffix, ["type", "suffix"], "suffix");
|
|
92
|
+
if (issues.length) return issues;
|
|
93
|
+
const resolved = resolveAffix({ affix, schemaSuffix });
|
|
94
|
+
const collisions = (space, build) => {
|
|
95
|
+
const seen = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const mode of NAME_MODES) {
|
|
97
|
+
const name = build(mode);
|
|
98
|
+
const first = seen.get(name);
|
|
99
|
+
if (first) {
|
|
100
|
+
issues.push({
|
|
101
|
+
path: [space],
|
|
102
|
+
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.`
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
seen.set(name, mode);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
collisions("schema", (mode) => schemaName(mode, AFFIX_PROBE_TABLE, resolved));
|
|
110
|
+
collisions("type", (mode) => typeName(mode, AFFIX_PROBE_TABLE, resolved));
|
|
111
|
+
return issues;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/branding.ts
|
|
115
|
+
function resolveBranding(opt) {
|
|
116
|
+
if (!opt) return void 0;
|
|
117
|
+
if (opt === true) return { foreignKeys: true, aliases: true };
|
|
118
|
+
if (opt.enabled === false) return void 0;
|
|
119
|
+
return { foreignKeys: opt.foreignKeys !== false, aliases: opt.aliases !== false };
|
|
120
|
+
}
|
|
121
|
+
var at = (tsName, column) => `${tsName}\0${column}`;
|
|
122
|
+
function referenceOf(table, columnName) {
|
|
123
|
+
const col = table.columns.find((c) => c.name === columnName);
|
|
124
|
+
if (col?.references) return { sqlTable: col.references.table, column: col.references.column };
|
|
125
|
+
for (const fk of table.foreignKeys ?? []) {
|
|
126
|
+
const i = fk.columns.indexOf(columnName);
|
|
127
|
+
if (i === -1) continue;
|
|
128
|
+
const target = fk.foreignColumns[i];
|
|
129
|
+
if (target === void 0) continue;
|
|
130
|
+
return { sqlTable: fk.foreignTable, column: target };
|
|
131
|
+
}
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
function isKeyColumn(table, columnName) {
|
|
135
|
+
return (table.primaryKey?.columns ?? []).includes(columnName);
|
|
136
|
+
}
|
|
137
|
+
function buildBrandPlan(tables, opt) {
|
|
138
|
+
const resolved = resolveBranding(opt);
|
|
139
|
+
if (!resolved) return void 0;
|
|
140
|
+
const notes = [];
|
|
141
|
+
const byTs = /* @__PURE__ */ new Map();
|
|
142
|
+
const dupTs = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const t of tables) {
|
|
144
|
+
if (byTs.has(t.tsName)) dupTs.add(t.tsName);
|
|
145
|
+
byTs.set(t.tsName, t);
|
|
146
|
+
}
|
|
147
|
+
for (const name of dupTs) {
|
|
148
|
+
byTs.delete(name);
|
|
149
|
+
notes.push(
|
|
150
|
+
`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.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const bySqlName = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const t of byTs.values()) {
|
|
155
|
+
const list = bySqlName.get(t.name) ?? [];
|
|
156
|
+
list.push(t);
|
|
157
|
+
bySqlName.set(t.name, list);
|
|
158
|
+
}
|
|
159
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
160
|
+
const cache = /* @__PURE__ */ new Map();
|
|
161
|
+
const resolveToken = (tsName, columnName) => {
|
|
162
|
+
const start = at(tsName, columnName);
|
|
163
|
+
if (cache.has(start)) return cache.get(start);
|
|
164
|
+
const origin = byTs.get(tsName)?.columns.find((c) => c.name === columnName);
|
|
165
|
+
if (!origin || origin.arrayDimensions) {
|
|
166
|
+
cache.set(start, void 0);
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
170
|
+
let curTable = tsName;
|
|
171
|
+
let curColumn = columnName;
|
|
172
|
+
let answer;
|
|
173
|
+
let terminal;
|
|
174
|
+
for (; ; ) {
|
|
175
|
+
const key = at(curTable, curColumn);
|
|
176
|
+
if (seen.has(key)) {
|
|
177
|
+
notes.push(
|
|
178
|
+
`${tsName}.${columnName} references a cycle of foreign keys, so it is not branded.`
|
|
179
|
+
);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
seen.add(key);
|
|
183
|
+
const table = byTs.get(curTable);
|
|
184
|
+
const col = table?.columns.find((c) => c.name === curColumn);
|
|
185
|
+
if (!table || !col) break;
|
|
186
|
+
const ref = resolved.foreignKeys ? referenceOf(table, curColumn) : void 0;
|
|
187
|
+
if (ref) {
|
|
188
|
+
const candidates = bySqlName.get(ref.sqlTable) ?? [];
|
|
189
|
+
if (candidates.length !== 1) {
|
|
190
|
+
notes.push(
|
|
191
|
+
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.`
|
|
192
|
+
);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
const next = candidates[0];
|
|
196
|
+
if (!next.columns.some((c) => c.name === ref.column)) {
|
|
197
|
+
notes.push(
|
|
198
|
+
`${tsName}.${columnName} references ${next.tsName}.${ref.column}, which is not a column of that table, so it is not branded.`
|
|
199
|
+
);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
curTable = next.tsName;
|
|
203
|
+
curColumn = ref.column;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (isKeyColumn(table, curColumn)) {
|
|
207
|
+
answer = `${curTable}.${curColumn}`;
|
|
208
|
+
terminal = col;
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
if (answer && terminal && terminal.tsType !== origin.tsType) {
|
|
213
|
+
notes.push(
|
|
214
|
+
`${tsName}.${columnName} is a ${origin.tsType} and ${answer} is a ${terminal.tsType}, so it is not branded.`
|
|
215
|
+
);
|
|
216
|
+
answer = void 0;
|
|
217
|
+
}
|
|
218
|
+
cache.set(start, answer);
|
|
219
|
+
return answer;
|
|
220
|
+
};
|
|
221
|
+
for (const t of byTs.values()) {
|
|
222
|
+
for (const c of t.columns) {
|
|
223
|
+
const token = resolveToken(t.tsName, c.name);
|
|
224
|
+
if (token) tokens.set(at(t.tsName, c.name), token);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
228
|
+
if (resolved.aliases) {
|
|
229
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
230
|
+
const draft = [];
|
|
231
|
+
for (const t of byTs.values()) {
|
|
232
|
+
for (const c of t.columns) {
|
|
233
|
+
const token = tokens.get(at(t.tsName, c.name));
|
|
234
|
+
if (token !== `${t.tsName}.${c.name}`) continue;
|
|
235
|
+
const alias = pascalCase(t.tsName) + pascalCase(c.name);
|
|
236
|
+
draft.push({ tsName: t.tsName, entry: { alias, column: c.name, token } });
|
|
237
|
+
claimed.set(alias, [...claimed.get(alias) ?? [], `${t.tsName}.${c.name}`]);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const { tsName, entry } of draft) {
|
|
241
|
+
const owners = claimed.get(entry.alias) ?? [];
|
|
242
|
+
if (owners.length > 1) continue;
|
|
243
|
+
aliases.set(tsName, [...aliases.get(tsName) ?? [], entry]);
|
|
244
|
+
}
|
|
245
|
+
for (const [alias, owners] of claimed) {
|
|
246
|
+
if (owners.length > 1) {
|
|
247
|
+
notes.push(
|
|
248
|
+
`${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.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
brandOf: (tsName, columnName) => tokens.get(at(tsName, columnName)),
|
|
255
|
+
aliasesFor: (tsName) => aliases.get(tsName) ?? [],
|
|
256
|
+
any: tokens.size > 0,
|
|
257
|
+
notes
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
8
261
|
// src/checks.ts
|
|
9
262
|
var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
|
|
10
263
|
var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
|
|
@@ -278,113 +531,120 @@ function withTsExtension(p) {
|
|
|
278
531
|
return `${p}.ts`;
|
|
279
532
|
}
|
|
280
533
|
|
|
281
|
-
// src/
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
insert: "Insert",
|
|
285
|
-
update: "Update",
|
|
286
|
-
select: "Select"
|
|
287
|
-
};
|
|
288
|
-
var DEFAULT_TYPE_SUFFIX = {
|
|
289
|
-
insert: "Input",
|
|
290
|
-
update: "Input",
|
|
291
|
-
select: "Output"
|
|
292
|
-
};
|
|
293
|
-
var DEFAULT_SCHEMA_SUFFIX = "Schema";
|
|
294
|
-
var AFFIX_PROBE_TABLE = "users";
|
|
295
|
-
function spread(value, fallback) {
|
|
296
|
-
if (value === void 0) return { ...fallback };
|
|
297
|
-
if (typeof value === "string") return { insert: value, update: value, select: value };
|
|
298
|
-
return {
|
|
299
|
-
insert: value.insert ?? fallback.insert,
|
|
300
|
-
update: value.update ?? fallback.update,
|
|
301
|
-
select: value.select ?? fallback.select
|
|
302
|
-
};
|
|
534
|
+
// src/meta.ts
|
|
535
|
+
function labelled(name, text) {
|
|
536
|
+
return name ? `${name}: ${text}` : text;
|
|
303
537
|
}
|
|
304
|
-
function
|
|
305
|
-
return
|
|
538
|
+
function literalText(value, kind) {
|
|
539
|
+
return kind === "string" ? `'${value}'` : value;
|
|
306
540
|
}
|
|
307
|
-
function
|
|
308
|
-
return
|
|
541
|
+
function columnCheckText(k) {
|
|
542
|
+
return labelled(k.name, `${k.column} ${k.operator} ${literalText(k.value, k.kind)}`);
|
|
309
543
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
update: legacy,
|
|
316
|
-
select: legacy
|
|
317
|
-
};
|
|
318
|
-
return {
|
|
319
|
-
tableCase: affix?.tableCase ?? "preserve",
|
|
320
|
-
schema: {
|
|
321
|
-
prefix: spread(affix?.schema?.prefix, DEFAULT_MODE_PREFIX),
|
|
322
|
-
suffix: spread(affix?.schema?.suffix, legacyMap)
|
|
323
|
-
},
|
|
324
|
-
type: {
|
|
325
|
-
prefix: spread(affix?.type?.prefix, DEFAULT_MODE_PREFIX),
|
|
326
|
-
suffix: spread(affix?.type?.suffix, DEFAULT_TYPE_SUFFIX)
|
|
327
|
-
}
|
|
328
|
-
};
|
|
544
|
+
function setText(k) {
|
|
545
|
+
return labelled(
|
|
546
|
+
k.name,
|
|
547
|
+
`${k.column} IN (${k.values.map((v) => literalText(v, k.kind)).join(", ")})`
|
|
548
|
+
);
|
|
329
549
|
}
|
|
330
|
-
function
|
|
331
|
-
return
|
|
550
|
+
function lengthText(k) {
|
|
551
|
+
return labelled(k.name, `length(${k.column}) ${k.operator} ${k.value}`);
|
|
332
552
|
}
|
|
333
|
-
function
|
|
334
|
-
return
|
|
553
|
+
function cardinalityText(k) {
|
|
554
|
+
return labelled(k.name, `cardinality(${k.column}) ${k.operator} ${k.value}`);
|
|
335
555
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (value === void 0) return;
|
|
352
|
-
if (typeof value === "string") {
|
|
353
|
-
checkOne(value, base, kind);
|
|
556
|
+
function rowText(k) {
|
|
557
|
+
return labelled(k.name, `${k.left} ${k.operator} ${k.right}`);
|
|
558
|
+
}
|
|
559
|
+
function takesScalarChecks(c) {
|
|
560
|
+
return !c.arrayDimensions && !c.shape;
|
|
561
|
+
}
|
|
562
|
+
function classifyChecks(table) {
|
|
563
|
+
const perColumn = /* @__PURE__ */ new Map();
|
|
564
|
+
const rows = [];
|
|
565
|
+
const unenforced = [];
|
|
566
|
+
const byName = new Map(table.columns.map((c) => [c.name, c]));
|
|
567
|
+
const add = (column, text, guard) => {
|
|
568
|
+
const c = byName.get(column);
|
|
569
|
+
if (!c || !guard(c)) {
|
|
570
|
+
unenforced.push(text);
|
|
354
571
|
return;
|
|
355
572
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
573
|
+
const list = perColumn.get(column) ?? [];
|
|
574
|
+
list.push(text);
|
|
575
|
+
perColumn.set(column, list);
|
|
360
576
|
};
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
for (const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
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.`
|
|
376
|
-
});
|
|
377
|
-
} else {
|
|
378
|
-
seen.set(name, mode);
|
|
379
|
-
}
|
|
577
|
+
for (const k of table.checks ?? []) {
|
|
578
|
+
const parsed = parseCheck(k.expression, k.name);
|
|
579
|
+
if (!parsed.ok) {
|
|
580
|
+
unenforced.push(labelled(k.name, (k.expression ?? "").trim()));
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
for (const c of parsed.checks) add(c.column, columnCheckText(c), takesScalarChecks);
|
|
584
|
+
for (const s of parsed.sets ?? []) add(s.column, setText(s), takesScalarChecks);
|
|
585
|
+
for (const l of parsed.lengths ?? []) add(l.column, lengthText(l), takesScalarChecks);
|
|
586
|
+
for (const a of parsed.cardinalities ?? [])
|
|
587
|
+
add(a.column, cardinalityText(a), (c) => !!c.arrayDimensions);
|
|
588
|
+
for (const r of parsed.rows ?? []) {
|
|
589
|
+
if (byName.has(r.left) && byName.has(r.right)) rows.push(rowText(r));
|
|
590
|
+
else unenforced.push(rowText(r));
|
|
380
591
|
}
|
|
592
|
+
}
|
|
593
|
+
return { perColumn, rows, unenforced };
|
|
594
|
+
}
|
|
595
|
+
function columnDescription(facts) {
|
|
596
|
+
const parts = [];
|
|
597
|
+
if (facts.maxLength !== void 0) parts.push(`at most ${facts.maxLength} characters`);
|
|
598
|
+
if (facts.maxBytes !== void 0) parts.push(`at most ${facts.maxBytes} bytes`);
|
|
599
|
+
for (const c of facts.checks ?? []) parts.push(`CHECK ${c}`);
|
|
600
|
+
return parts.length ? parts.join(". ") : void 0;
|
|
601
|
+
}
|
|
602
|
+
function tableDescription(facts) {
|
|
603
|
+
const parts = [];
|
|
604
|
+
for (const c of facts.checks ?? []) parts.push(`CHECK ${c}`);
|
|
605
|
+
if (facts.unenforcedChecks?.length) {
|
|
606
|
+
parts.push(
|
|
607
|
+
`not enforced by this schema, the database also checks: ${facts.unenforcedChecks.join("; ")}`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return parts.length ? parts.join(". ") : void 0;
|
|
611
|
+
}
|
|
612
|
+
function columnMetaFacts(column, table, opts = {}) {
|
|
613
|
+
const checks = classifyChecks(table).perColumn.get(column.name);
|
|
614
|
+
const facts = {
|
|
615
|
+
...column.sqlType ? { sqlType: column.sqlType } : {},
|
|
616
|
+
...column.maxLength !== void 0 ? { maxLength: column.maxLength } : {},
|
|
617
|
+
...column.maxBytes !== void 0 ? { maxBytes: column.maxBytes } : {},
|
|
618
|
+
...column.hasDefault ? { hasDefault: true } : {},
|
|
619
|
+
...column.isGenerated ? { generated: true } : {},
|
|
620
|
+
...checks?.length ? { checks } : {}
|
|
381
621
|
};
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return
|
|
622
|
+
if (!opts.description) return facts;
|
|
623
|
+
const description = columnDescription(facts);
|
|
624
|
+
return description ? { ...facts, description } : facts;
|
|
625
|
+
}
|
|
626
|
+
function tableMetaFacts(table, opts) {
|
|
627
|
+
const { rows, unenforced } = classifyChecks(table);
|
|
628
|
+
const pk = table.primaryKey?.columns ?? [];
|
|
629
|
+
const unique = (table.unique ?? []).map((k) => k.columns).filter((c) => c.length > 0);
|
|
630
|
+
const facts = {
|
|
631
|
+
table: table.name,
|
|
632
|
+
...table.schema ? { schema: table.schema } : {},
|
|
633
|
+
...opts.dialect ? { dialect: opts.dialect } : {},
|
|
634
|
+
mode: opts.mode,
|
|
635
|
+
...pk.length ? { primaryKey: pk } : {},
|
|
636
|
+
...unique.length ? { unique } : {},
|
|
637
|
+
...table.readOnly ? { readOnly: true } : {},
|
|
638
|
+
...rows.length ? { checks: rows } : {},
|
|
639
|
+
...unenforced.length ? { unenforcedChecks: unenforced } : {}
|
|
640
|
+
};
|
|
641
|
+
if (!opts.description) return facts;
|
|
642
|
+
const description = tableDescription(facts);
|
|
643
|
+
return description ? { ...facts, description } : facts;
|
|
385
644
|
}
|
|
386
645
|
|
|
387
646
|
// src/nested.ts
|
|
647
|
+
import { qualifiedForeignTable, qualifiedTableName } from "@drzl/analyzer";
|
|
388
648
|
var NESTED_PREFIX = "Nested";
|
|
389
649
|
function nestedSchemaName(mode, tsName, affix) {
|
|
390
650
|
return NESTED_PREFIX + schemaName(mode, tsName, affix);
|
|
@@ -412,13 +672,14 @@ var KINDS_BY_MODE = {
|
|
|
412
672
|
};
|
|
413
673
|
var KIND_ORDER = { many: 0, manyToMany: 1, one: 2 };
|
|
414
674
|
function omittedColumnsFor(parent, child) {
|
|
415
|
-
const
|
|
675
|
+
const parentName = qualifiedTableName(parent);
|
|
676
|
+
const back = (child.foreignKeys ?? []).filter((fk) => qualifiedForeignTable(fk) === parentName);
|
|
416
677
|
if (back.length === 1) return { omitted: [...back[0].columns] };
|
|
417
678
|
if (back.length === 0) return { omitted: [] };
|
|
418
679
|
const named = back.map((fk) => fk.columns.join("+")).join(", ");
|
|
419
680
|
return {
|
|
420
681
|
omitted: [],
|
|
421
|
-
note: `${child.tsName} has ${back.length} foreign keys to ${
|
|
682
|
+
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.`
|
|
422
683
|
};
|
|
423
684
|
}
|
|
424
685
|
function buildNestedPlan(root, tables, relations, mode, depth) {
|
|
@@ -427,14 +688,15 @@ function buildNestedPlan(root, tables, relations, mode, depth) {
|
|
|
427
688
|
}
|
|
428
689
|
function buildNode(table, omitted, tables, relations, mode, depth) {
|
|
429
690
|
if (depth <= 0) return { table, omitted, arms: [] };
|
|
430
|
-
const
|
|
691
|
+
const byName = new Map(tables.map((t) => [qualifiedTableName(t), t]));
|
|
431
692
|
const allowed = KINDS_BY_MODE[mode];
|
|
432
693
|
const columnNames = new Set(table.columns.map((c) => c.name));
|
|
433
694
|
const taken = /* @__PURE__ */ new Set();
|
|
434
695
|
const arms = [];
|
|
435
|
-
const
|
|
696
|
+
const self = qualifiedTableName(table);
|
|
697
|
+
const candidates = relations.filter((r) => r.from === self && allowed.has(r.kind)).sort((a, b) => KIND_ORDER[a.kind] - KIND_ORDER[b.kind]);
|
|
436
698
|
for (const rel of candidates) {
|
|
437
|
-
const child =
|
|
699
|
+
const child = byName.get(rel.to);
|
|
438
700
|
if (!child) continue;
|
|
439
701
|
const key = child.tsName;
|
|
440
702
|
if (columnNames.has(key)) continue;
|
|
@@ -659,7 +921,9 @@ export {
|
|
|
659
921
|
NAME_MODES,
|
|
660
922
|
NESTED_PREFIX,
|
|
661
923
|
applyTableCase,
|
|
924
|
+
buildBrandPlan,
|
|
662
925
|
buildNestedPlan,
|
|
926
|
+
columnMetaFacts,
|
|
663
927
|
describeSet,
|
|
664
928
|
formatCode,
|
|
665
929
|
importSpecifier,
|
|
@@ -678,10 +942,12 @@ export {
|
|
|
678
942
|
pascalCase,
|
|
679
943
|
renderDuplicateFinder,
|
|
680
944
|
resolveAffix,
|
|
945
|
+
resolveBranding,
|
|
681
946
|
resolveConfiguredImport,
|
|
682
947
|
resolveNestedDepth,
|
|
683
948
|
schemaName,
|
|
684
949
|
selectColumns,
|
|
950
|
+
tableMetaFacts,
|
|
685
951
|
typeName,
|
|
686
952
|
updateColumns,
|
|
687
953
|
validateAffix
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/validation-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"sideEffects": false,
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drzl/analyzer": "^1.
|
|
27
|
+
"@drzl/analyzer": "^1.20.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"prettier": ">=3"
|