@drzl/validation-core 3.6.0 → 3.8.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 +143 -2
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +141 -2
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -84441,6 +84441,7 @@ ${codeblock}`, options7);
|
|
|
84441
84441
|
var index_exports2 = {};
|
|
84442
84442
|
__export(index_exports2, {
|
|
84443
84443
|
AFFIX_PROBE_TABLE: () => AFFIX_PROBE_TABLE,
|
|
84444
|
+
COLUMN_FORMATS: () => COLUMN_FORMATS,
|
|
84444
84445
|
DEFAULT_IMPORT_EXTENSION: () => DEFAULT_IMPORT_EXTENSION,
|
|
84445
84446
|
DEFAULT_MODE_PREFIX: () => DEFAULT_MODE_PREFIX,
|
|
84446
84447
|
DEFAULT_SCHEMA_SUFFIX: () => DEFAULT_SCHEMA_SUFFIX,
|
|
@@ -84448,6 +84449,7 @@ __export(index_exports2, {
|
|
|
84448
84449
|
IMPORT_EXTENSIONS: () => IMPORT_EXTENSIONS,
|
|
84449
84450
|
NAME_MODES: () => NAME_MODES,
|
|
84450
84451
|
applyTableCase: () => applyTableCase,
|
|
84452
|
+
describeSet: () => describeSet,
|
|
84451
84453
|
formatCode: () => formatCode,
|
|
84452
84454
|
importSpecifier: () => importSpecifier,
|
|
84453
84455
|
insertColumns: () => insertColumns,
|
|
@@ -84469,6 +84471,94 @@ module.exports = __toCommonJS(index_exports2);
|
|
|
84469
84471
|
|
|
84470
84472
|
// src/checks.ts
|
|
84471
84473
|
var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
|
|
84474
|
+
var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
|
|
84475
|
+
function splitTopLevelAnd(expr) {
|
|
84476
|
+
const parts = [];
|
|
84477
|
+
let depth = 0;
|
|
84478
|
+
let inString = false;
|
|
84479
|
+
let start = 0;
|
|
84480
|
+
for (let i = 0; i < expr.length; i++) {
|
|
84481
|
+
const c5 = expr[i];
|
|
84482
|
+
if (inString) {
|
|
84483
|
+
if (c5 === "'") {
|
|
84484
|
+
if (expr[i + 1] === "'") i++;
|
|
84485
|
+
else inString = false;
|
|
84486
|
+
}
|
|
84487
|
+
continue;
|
|
84488
|
+
}
|
|
84489
|
+
if (c5 === "'") {
|
|
84490
|
+
inString = true;
|
|
84491
|
+
continue;
|
|
84492
|
+
}
|
|
84493
|
+
if (c5 === "(") depth++;
|
|
84494
|
+
else if (c5 === ")") depth--;
|
|
84495
|
+
else if (depth === 0 && /\s/.test(c5)) {
|
|
84496
|
+
const m7 = /^\s+AND\s+/i.exec(expr.slice(i));
|
|
84497
|
+
if (m7) {
|
|
84498
|
+
parts.push(expr.slice(start, i));
|
|
84499
|
+
i += m7[0].length - 1;
|
|
84500
|
+
start = i + 1;
|
|
84501
|
+
}
|
|
84502
|
+
}
|
|
84503
|
+
}
|
|
84504
|
+
parts.push(expr.slice(start));
|
|
84505
|
+
return parts;
|
|
84506
|
+
}
|
|
84507
|
+
function unwrap(expr) {
|
|
84508
|
+
let e7 = expr.trim();
|
|
84509
|
+
while (e7.startsWith("(") && e7.endsWith(")")) {
|
|
84510
|
+
let depth = 0;
|
|
84511
|
+
let inString = false;
|
|
84512
|
+
let wrapsWhole = true;
|
|
84513
|
+
for (let i = 0; i < e7.length; i++) {
|
|
84514
|
+
const c5 = e7[i];
|
|
84515
|
+
if (inString) {
|
|
84516
|
+
if (c5 === "'") {
|
|
84517
|
+
if (e7[i + 1] === "'") i++;
|
|
84518
|
+
else inString = false;
|
|
84519
|
+
}
|
|
84520
|
+
continue;
|
|
84521
|
+
}
|
|
84522
|
+
if (c5 === "'") inString = true;
|
|
84523
|
+
else if (c5 === "(") depth++;
|
|
84524
|
+
else if (c5 === ")") {
|
|
84525
|
+
depth--;
|
|
84526
|
+
if (depth === 0 && i < e7.length - 1) {
|
|
84527
|
+
wrapsWhole = false;
|
|
84528
|
+
break;
|
|
84529
|
+
}
|
|
84530
|
+
}
|
|
84531
|
+
}
|
|
84532
|
+
if (!wrapsWhole) break;
|
|
84533
|
+
e7 = e7.slice(1, -1).trim();
|
|
84534
|
+
}
|
|
84535
|
+
return e7;
|
|
84536
|
+
}
|
|
84537
|
+
function splitTopLevelCommas(list) {
|
|
84538
|
+
const parts = [];
|
|
84539
|
+
let depth = 0;
|
|
84540
|
+
let inString = false;
|
|
84541
|
+
let start = 0;
|
|
84542
|
+
for (let i = 0; i < list.length; i++) {
|
|
84543
|
+
const c5 = list[i];
|
|
84544
|
+
if (inString) {
|
|
84545
|
+
if (c5 === "'") {
|
|
84546
|
+
if (list[i + 1] === "'") i++;
|
|
84547
|
+
else inString = false;
|
|
84548
|
+
}
|
|
84549
|
+
continue;
|
|
84550
|
+
}
|
|
84551
|
+
if (c5 === "'") inString = true;
|
|
84552
|
+
else if (c5 === "(") depth++;
|
|
84553
|
+
else if (c5 === ")") depth--;
|
|
84554
|
+
else if (c5 === "," && depth === 0) {
|
|
84555
|
+
parts.push(list.slice(start, i));
|
|
84556
|
+
start = i + 1;
|
|
84557
|
+
}
|
|
84558
|
+
}
|
|
84559
|
+
parts.push(list.slice(start));
|
|
84560
|
+
return parts;
|
|
84561
|
+
}
|
|
84472
84562
|
var BETWEEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+BETWEEN\s+(.+?)\s+AND\s+(.+?)\s*$/i;
|
|
84473
84563
|
function literal(raw) {
|
|
84474
84564
|
const t36 = raw.trim();
|
|
@@ -84478,9 +84568,11 @@ function literal(raw) {
|
|
|
84478
84568
|
return void 0;
|
|
84479
84569
|
}
|
|
84480
84570
|
function parseCheck(expression, name) {
|
|
84481
|
-
const expr = (expression ?? "").trim();
|
|
84571
|
+
const expr = unwrap((expression ?? "").trim());
|
|
84482
84572
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
84483
84573
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
84574
|
+
if (/(^|[\s)])OR($|[\s(])/i.test(expr)) return { ok: false, reason: "contains OR" };
|
|
84575
|
+
if (/(^|[\s(])NOT($|[\s(])/i.test(expr)) return { ok: false, reason: "contains NOT" };
|
|
84484
84576
|
const between = expr.match(BETWEEN);
|
|
84485
84577
|
if (between) {
|
|
84486
84578
|
const lo6 = literal(between[2]);
|
|
@@ -84495,6 +84587,40 @@ function parseCheck(expression, name) {
|
|
|
84495
84587
|
]
|
|
84496
84588
|
};
|
|
84497
84589
|
}
|
|
84590
|
+
const parts = splitTopLevelAnd(expr);
|
|
84591
|
+
if (parts.length > 1) {
|
|
84592
|
+
const checks = [];
|
|
84593
|
+
const sets = [];
|
|
84594
|
+
for (const part of parts) {
|
|
84595
|
+
const parsed = parseCheck(part, name);
|
|
84596
|
+
if (!parsed.ok)
|
|
84597
|
+
return { ok: false, reason: `part of an AND was not understood: ${parsed.reason}` };
|
|
84598
|
+
checks.push(...parsed.checks);
|
|
84599
|
+
if (parsed.sets) sets.push(...parsed.sets);
|
|
84600
|
+
}
|
|
84601
|
+
return sets.length ? { ok: true, checks, sets } : { ok: true, checks };
|
|
84602
|
+
}
|
|
84603
|
+
const inList = expr.match(IN_LIST);
|
|
84604
|
+
if (inList) {
|
|
84605
|
+
const raw = splitTopLevelCommas(inList[2]);
|
|
84606
|
+
const parsedValues = raw.map((r5) => literal(r5));
|
|
84607
|
+
if (parsedValues.some((v5) => !v5)) return { ok: false, reason: "IN list holds a non-literal" };
|
|
84608
|
+
const kinds = new Set(parsedValues.map((v5) => v5.kind));
|
|
84609
|
+
if (kinds.size > 1) return { ok: false, reason: "IN list mixes types" };
|
|
84610
|
+
if (!parsedValues.length) return { ok: false, reason: "IN list is empty" };
|
|
84611
|
+
return {
|
|
84612
|
+
ok: true,
|
|
84613
|
+
checks: [],
|
|
84614
|
+
sets: [
|
|
84615
|
+
{
|
|
84616
|
+
column: inList[1],
|
|
84617
|
+
values: parsedValues.map((v5) => v5.value),
|
|
84618
|
+
kind: parsedValues[0].kind,
|
|
84619
|
+
name
|
|
84620
|
+
}
|
|
84621
|
+
]
|
|
84622
|
+
};
|
|
84623
|
+
}
|
|
84498
84624
|
const cmp = expr.match(COMPARISON);
|
|
84499
84625
|
if (!cmp) return { ok: false, reason: "not a single comparison this version understands" };
|
|
84500
84626
|
const value = literal(cmp[3]);
|
|
@@ -84502,7 +84628,14 @@ function parseCheck(expression, name) {
|
|
|
84502
84628
|
return { ok: false, reason: "right side is not a literal" };
|
|
84503
84629
|
}
|
|
84504
84630
|
const op3 = cmp[2] === "!=" ? "<>" : cmp[2];
|
|
84505
|
-
return {
|
|
84631
|
+
return {
|
|
84632
|
+
ok: true,
|
|
84633
|
+
checks: [{ column: cmp[1], operator: op3, value: value.value, kind: value.kind, name }]
|
|
84634
|
+
};
|
|
84635
|
+
}
|
|
84636
|
+
function describeSet(set) {
|
|
84637
|
+
const shown = set.values.map((v5) => set.kind === "string" ? `'${v5}'` : v5).join(", ");
|
|
84638
|
+
return `${set.name ? `${set.name}: ` : ""}${set.column} IN (${shown})`;
|
|
84506
84639
|
}
|
|
84507
84640
|
|
|
84508
84641
|
// src/files.ts
|
|
@@ -84674,6 +84807,12 @@ function validateAffix(affix, schemaSuffix) {
|
|
|
84674
84807
|
function isGeneratedColumn(c5, _primaryKeyColumns = []) {
|
|
84675
84808
|
return c5.isGenerated;
|
|
84676
84809
|
}
|
|
84810
|
+
var COLUMN_FORMATS = {
|
|
84811
|
+
// Sign, decimals, exponents, NaN/Infinity, surrounding whitespace, and the underscore digit
|
|
84812
|
+
// separators and 0x/0o/0b integer literals Postgres 16 added. Agrees with Postgres on all 43
|
|
84813
|
+
// probes, `1_000` and `0xDEAD_beef` through to `1__0`, `_1`, `0x` and `1e+`.
|
|
84814
|
+
numeric: "^\\s*([+-]?(0[xX][0-9a-fA-F](_?[0-9a-fA-F])*|0[oO][0-7](_?[0-7])*|0[bB][01](_?[01])*)|[+-]?(\\d(_?\\d)*(\\.(\\d(_?\\d)*)?)?|\\.\\d(_?\\d)*)([eE][+-]?\\d(_?\\d)*)?|[+-]?(NaN|Infinity))\\s*$"
|
|
84815
|
+
};
|
|
84677
84816
|
function isIntegerColumn(c5) {
|
|
84678
84817
|
if (typeof c5.integer === "boolean") return c5.integer;
|
|
84679
84818
|
return c5.dbType === "INTEGER" || c5.min !== void 0 && c5.max !== void 0;
|
|
@@ -84716,6 +84855,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
84716
84855
|
// Annotate the CommonJS export names for ESM import in node:
|
|
84717
84856
|
0 && (module.exports = {
|
|
84718
84857
|
AFFIX_PROBE_TABLE,
|
|
84858
|
+
COLUMN_FORMATS,
|
|
84719
84859
|
DEFAULT_IMPORT_EXTENSION,
|
|
84720
84860
|
DEFAULT_MODE_PREFIX,
|
|
84721
84861
|
DEFAULT_SCHEMA_SUFFIX,
|
|
@@ -84723,6 +84863,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
84723
84863
|
IMPORT_EXTENSIONS,
|
|
84724
84864
|
NAME_MODES,
|
|
84725
84865
|
applyTableCase,
|
|
84866
|
+
describeSet,
|
|
84726
84867
|
formatCode,
|
|
84727
84868
|
importSpecifier,
|
|
84728
84869
|
insertColumns,
|
package/dist/index.d.cts
CHANGED
|
@@ -206,10 +206,23 @@ interface ColumnCheck {
|
|
|
206
206
|
/** Constraint name, used to say which one failed. */
|
|
207
207
|
name?: string;
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* A column constrained to a set of literals, from `col IN ('a', 'b')`.
|
|
211
|
+
*
|
|
212
|
+
* Kept separate from `ColumnCheck` rather than folded into it as another operator, so the
|
|
213
|
+
* existing shape is unchanged for anything already consuming it.
|
|
214
|
+
*/
|
|
215
|
+
interface ColumnSet {
|
|
216
|
+
column: string;
|
|
217
|
+
values: string[];
|
|
218
|
+
kind: 'number' | 'string';
|
|
219
|
+
name?: string;
|
|
220
|
+
}
|
|
209
221
|
/** A check that was understood, or the reason it was not. */
|
|
210
222
|
type ParsedCheck = {
|
|
211
223
|
ok: true;
|
|
212
224
|
checks: ColumnCheck[];
|
|
225
|
+
sets?: ColumnSet[];
|
|
213
226
|
} | {
|
|
214
227
|
ok: false;
|
|
215
228
|
reason: string;
|
|
@@ -222,6 +235,13 @@ type ParsedCheck = {
|
|
|
222
235
|
* silently change what is enforced.
|
|
223
236
|
*/
|
|
224
237
|
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
238
|
+
/**
|
|
239
|
+
* A human-readable rendering of a set constraint, for an error message.
|
|
240
|
+
*
|
|
241
|
+
* Values are re-quoted the way SQL wrote them, so the message reads like the constraint in the
|
|
242
|
+
* schema rather than like its JavaScript translation.
|
|
243
|
+
*/
|
|
244
|
+
declare function describeSet(set: ColumnSet): string;
|
|
225
245
|
|
|
226
246
|
interface Table {
|
|
227
247
|
name: string;
|
|
@@ -338,10 +358,29 @@ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): bo
|
|
|
338
358
|
* was true only while integers were the sole bounded type, so it is kept strictly for an
|
|
339
359
|
* analysis produced before `Column.integer` existed, and never consulted when the flag is set.
|
|
340
360
|
*/
|
|
361
|
+
/**
|
|
362
|
+
* Patterns for string columns whose contents the database constrains.
|
|
363
|
+
*
|
|
364
|
+
* Every entry was validated against Postgres through PGlite rather than reasoned about: the
|
|
365
|
+
* pattern and the database agree on a pool built to include the awkward *valid* forms, because
|
|
366
|
+
* the real hazard is over-rejection. A check that turns away something Postgres accepts breaks
|
|
367
|
+
* working code, which is worse than the bare `string` it replaces.
|
|
368
|
+
*
|
|
369
|
+
* That is why there is exactly one entry. Candidates for `date`, `timestamp`, `time`, `interval`,
|
|
370
|
+
* `inet`, `cidr` and `macaddr` were all built and all rejected, each by a value Postgres accepts
|
|
371
|
+
* and the pattern did not:
|
|
372
|
+
*
|
|
373
|
+
* date `today`, `January 8, 1999`, `20200101`, `01/02/2020`, `infinity`
|
|
374
|
+
* time `allballs`, `12:00:00+02`
|
|
375
|
+
* macaddr `2020-01-01`, which Postgres pads into `20:20:00:01:00:01`
|
|
376
|
+
* inet `10.1/16`, `::ffff:1.2.3.4`
|
|
377
|
+
* cidr parses as `inet` and then demands zero host bits, which no regex can state
|
|
378
|
+
*/
|
|
379
|
+
declare const COLUMN_FORMATS: Record<string, string>;
|
|
341
380
|
declare function isIntegerColumn(c: Column): boolean;
|
|
342
381
|
declare function insertColumns(table: Table): Column[];
|
|
343
382
|
declare function updateColumns(table: Table): Column[];
|
|
344
383
|
declare function selectColumns(table: Table): Column[];
|
|
345
384
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
346
385
|
|
|
347
|
-
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, type ColumnCheck, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
|
|
386
|
+
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
|
package/dist/index.d.ts
CHANGED
|
@@ -206,10 +206,23 @@ interface ColumnCheck {
|
|
|
206
206
|
/** Constraint name, used to say which one failed. */
|
|
207
207
|
name?: string;
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* A column constrained to a set of literals, from `col IN ('a', 'b')`.
|
|
211
|
+
*
|
|
212
|
+
* Kept separate from `ColumnCheck` rather than folded into it as another operator, so the
|
|
213
|
+
* existing shape is unchanged for anything already consuming it.
|
|
214
|
+
*/
|
|
215
|
+
interface ColumnSet {
|
|
216
|
+
column: string;
|
|
217
|
+
values: string[];
|
|
218
|
+
kind: 'number' | 'string';
|
|
219
|
+
name?: string;
|
|
220
|
+
}
|
|
209
221
|
/** A check that was understood, or the reason it was not. */
|
|
210
222
|
type ParsedCheck = {
|
|
211
223
|
ok: true;
|
|
212
224
|
checks: ColumnCheck[];
|
|
225
|
+
sets?: ColumnSet[];
|
|
213
226
|
} | {
|
|
214
227
|
ok: false;
|
|
215
228
|
reason: string;
|
|
@@ -222,6 +235,13 @@ type ParsedCheck = {
|
|
|
222
235
|
* silently change what is enforced.
|
|
223
236
|
*/
|
|
224
237
|
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
238
|
+
/**
|
|
239
|
+
* A human-readable rendering of a set constraint, for an error message.
|
|
240
|
+
*
|
|
241
|
+
* Values are re-quoted the way SQL wrote them, so the message reads like the constraint in the
|
|
242
|
+
* schema rather than like its JavaScript translation.
|
|
243
|
+
*/
|
|
244
|
+
declare function describeSet(set: ColumnSet): string;
|
|
225
245
|
|
|
226
246
|
interface Table {
|
|
227
247
|
name: string;
|
|
@@ -338,10 +358,29 @@ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): bo
|
|
|
338
358
|
* was true only while integers were the sole bounded type, so it is kept strictly for an
|
|
339
359
|
* analysis produced before `Column.integer` existed, and never consulted when the flag is set.
|
|
340
360
|
*/
|
|
361
|
+
/**
|
|
362
|
+
* Patterns for string columns whose contents the database constrains.
|
|
363
|
+
*
|
|
364
|
+
* Every entry was validated against Postgres through PGlite rather than reasoned about: the
|
|
365
|
+
* pattern and the database agree on a pool built to include the awkward *valid* forms, because
|
|
366
|
+
* the real hazard is over-rejection. A check that turns away something Postgres accepts breaks
|
|
367
|
+
* working code, which is worse than the bare `string` it replaces.
|
|
368
|
+
*
|
|
369
|
+
* That is why there is exactly one entry. Candidates for `date`, `timestamp`, `time`, `interval`,
|
|
370
|
+
* `inet`, `cidr` and `macaddr` were all built and all rejected, each by a value Postgres accepts
|
|
371
|
+
* and the pattern did not:
|
|
372
|
+
*
|
|
373
|
+
* date `today`, `January 8, 1999`, `20200101`, `01/02/2020`, `infinity`
|
|
374
|
+
* time `allballs`, `12:00:00+02`
|
|
375
|
+
* macaddr `2020-01-01`, which Postgres pads into `20:20:00:01:00:01`
|
|
376
|
+
* inet `10.1/16`, `::ffff:1.2.3.4`
|
|
377
|
+
* cidr parses as `inet` and then demands zero host bits, which no regex can state
|
|
378
|
+
*/
|
|
379
|
+
declare const COLUMN_FORMATS: Record<string, string>;
|
|
341
380
|
declare function isIntegerColumn(c: Column): boolean;
|
|
342
381
|
declare function insertColumns(table: Table): Column[];
|
|
343
382
|
declare function updateColumns(table: Table): Column[];
|
|
344
383
|
declare function selectColumns(table: Table): Column[];
|
|
345
384
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
346
385
|
|
|
347
|
-
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, type ColumnCheck, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
|
|
386
|
+
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,94 @@ import "./chunk-ZZGILH5A.js";
|
|
|
2
2
|
|
|
3
3
|
// src/checks.ts
|
|
4
4
|
var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
|
|
5
|
+
var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
|
|
6
|
+
function splitTopLevelAnd(expr) {
|
|
7
|
+
const parts = [];
|
|
8
|
+
let depth = 0;
|
|
9
|
+
let inString = false;
|
|
10
|
+
let start = 0;
|
|
11
|
+
for (let i = 0; i < expr.length; i++) {
|
|
12
|
+
const c = expr[i];
|
|
13
|
+
if (inString) {
|
|
14
|
+
if (c === "'") {
|
|
15
|
+
if (expr[i + 1] === "'") i++;
|
|
16
|
+
else inString = false;
|
|
17
|
+
}
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (c === "'") {
|
|
21
|
+
inString = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (c === "(") depth++;
|
|
25
|
+
else if (c === ")") depth--;
|
|
26
|
+
else if (depth === 0 && /\s/.test(c)) {
|
|
27
|
+
const m = /^\s+AND\s+/i.exec(expr.slice(i));
|
|
28
|
+
if (m) {
|
|
29
|
+
parts.push(expr.slice(start, i));
|
|
30
|
+
i += m[0].length - 1;
|
|
31
|
+
start = i + 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
parts.push(expr.slice(start));
|
|
36
|
+
return parts;
|
|
37
|
+
}
|
|
38
|
+
function unwrap(expr) {
|
|
39
|
+
let e = expr.trim();
|
|
40
|
+
while (e.startsWith("(") && e.endsWith(")")) {
|
|
41
|
+
let depth = 0;
|
|
42
|
+
let inString = false;
|
|
43
|
+
let wrapsWhole = true;
|
|
44
|
+
for (let i = 0; i < e.length; i++) {
|
|
45
|
+
const c = e[i];
|
|
46
|
+
if (inString) {
|
|
47
|
+
if (c === "'") {
|
|
48
|
+
if (e[i + 1] === "'") i++;
|
|
49
|
+
else inString = false;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (c === "'") inString = true;
|
|
54
|
+
else if (c === "(") depth++;
|
|
55
|
+
else if (c === ")") {
|
|
56
|
+
depth--;
|
|
57
|
+
if (depth === 0 && i < e.length - 1) {
|
|
58
|
+
wrapsWhole = false;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!wrapsWhole) break;
|
|
64
|
+
e = e.slice(1, -1).trim();
|
|
65
|
+
}
|
|
66
|
+
return e;
|
|
67
|
+
}
|
|
68
|
+
function splitTopLevelCommas(list) {
|
|
69
|
+
const parts = [];
|
|
70
|
+
let depth = 0;
|
|
71
|
+
let inString = false;
|
|
72
|
+
let start = 0;
|
|
73
|
+
for (let i = 0; i < list.length; i++) {
|
|
74
|
+
const c = list[i];
|
|
75
|
+
if (inString) {
|
|
76
|
+
if (c === "'") {
|
|
77
|
+
if (list[i + 1] === "'") i++;
|
|
78
|
+
else inString = false;
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (c === "'") inString = true;
|
|
83
|
+
else if (c === "(") depth++;
|
|
84
|
+
else if (c === ")") depth--;
|
|
85
|
+
else if (c === "," && depth === 0) {
|
|
86
|
+
parts.push(list.slice(start, i));
|
|
87
|
+
start = i + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
parts.push(list.slice(start));
|
|
91
|
+
return parts;
|
|
92
|
+
}
|
|
5
93
|
var BETWEEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+BETWEEN\s+(.+?)\s+AND\s+(.+?)\s*$/i;
|
|
6
94
|
function literal(raw) {
|
|
7
95
|
const t = raw.trim();
|
|
@@ -11,9 +99,11 @@ function literal(raw) {
|
|
|
11
99
|
return void 0;
|
|
12
100
|
}
|
|
13
101
|
function parseCheck(expression, name) {
|
|
14
|
-
const expr = (expression ?? "").trim();
|
|
102
|
+
const expr = unwrap((expression ?? "").trim());
|
|
15
103
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
16
104
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
105
|
+
if (/(^|[\s)])OR($|[\s(])/i.test(expr)) return { ok: false, reason: "contains OR" };
|
|
106
|
+
if (/(^|[\s(])NOT($|[\s(])/i.test(expr)) return { ok: false, reason: "contains NOT" };
|
|
17
107
|
const between = expr.match(BETWEEN);
|
|
18
108
|
if (between) {
|
|
19
109
|
const lo = literal(between[2]);
|
|
@@ -28,6 +118,40 @@ function parseCheck(expression, name) {
|
|
|
28
118
|
]
|
|
29
119
|
};
|
|
30
120
|
}
|
|
121
|
+
const parts = splitTopLevelAnd(expr);
|
|
122
|
+
if (parts.length > 1) {
|
|
123
|
+
const checks = [];
|
|
124
|
+
const sets = [];
|
|
125
|
+
for (const part of parts) {
|
|
126
|
+
const parsed = parseCheck(part, name);
|
|
127
|
+
if (!parsed.ok)
|
|
128
|
+
return { ok: false, reason: `part of an AND was not understood: ${parsed.reason}` };
|
|
129
|
+
checks.push(...parsed.checks);
|
|
130
|
+
if (parsed.sets) sets.push(...parsed.sets);
|
|
131
|
+
}
|
|
132
|
+
return sets.length ? { ok: true, checks, sets } : { ok: true, checks };
|
|
133
|
+
}
|
|
134
|
+
const inList = expr.match(IN_LIST);
|
|
135
|
+
if (inList) {
|
|
136
|
+
const raw = splitTopLevelCommas(inList[2]);
|
|
137
|
+
const parsedValues = raw.map((r) => literal(r));
|
|
138
|
+
if (parsedValues.some((v) => !v)) return { ok: false, reason: "IN list holds a non-literal" };
|
|
139
|
+
const kinds = new Set(parsedValues.map((v) => v.kind));
|
|
140
|
+
if (kinds.size > 1) return { ok: false, reason: "IN list mixes types" };
|
|
141
|
+
if (!parsedValues.length) return { ok: false, reason: "IN list is empty" };
|
|
142
|
+
return {
|
|
143
|
+
ok: true,
|
|
144
|
+
checks: [],
|
|
145
|
+
sets: [
|
|
146
|
+
{
|
|
147
|
+
column: inList[1],
|
|
148
|
+
values: parsedValues.map((v) => v.value),
|
|
149
|
+
kind: parsedValues[0].kind,
|
|
150
|
+
name
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
31
155
|
const cmp = expr.match(COMPARISON);
|
|
32
156
|
if (!cmp) return { ok: false, reason: "not a single comparison this version understands" };
|
|
33
157
|
const value = literal(cmp[3]);
|
|
@@ -35,7 +159,14 @@ function parseCheck(expression, name) {
|
|
|
35
159
|
return { ok: false, reason: "right side is not a literal" };
|
|
36
160
|
}
|
|
37
161
|
const op = cmp[2] === "!=" ? "<>" : cmp[2];
|
|
38
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
checks: [{ column: cmp[1], operator: op, value: value.value, kind: value.kind, name }]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function describeSet(set) {
|
|
168
|
+
const shown = set.values.map((v) => set.kind === "string" ? `'${v}'` : v).join(", ");
|
|
169
|
+
return `${set.name ? `${set.name}: ` : ""}${set.column} IN (${shown})`;
|
|
39
170
|
}
|
|
40
171
|
|
|
41
172
|
// src/files.ts
|
|
@@ -207,6 +338,12 @@ function validateAffix(affix, schemaSuffix) {
|
|
|
207
338
|
function isGeneratedColumn(c, _primaryKeyColumns = []) {
|
|
208
339
|
return c.isGenerated;
|
|
209
340
|
}
|
|
341
|
+
var COLUMN_FORMATS = {
|
|
342
|
+
// Sign, decimals, exponents, NaN/Infinity, surrounding whitespace, and the underscore digit
|
|
343
|
+
// separators and 0x/0o/0b integer literals Postgres 16 added. Agrees with Postgres on all 43
|
|
344
|
+
// probes, `1_000` and `0xDEAD_beef` through to `1__0`, `_1`, `0x` and `1e+`.
|
|
345
|
+
numeric: "^\\s*([+-]?(0[xX][0-9a-fA-F](_?[0-9a-fA-F])*|0[oO][0-7](_?[0-7])*|0[bB][01](_?[01])*)|[+-]?(\\d(_?\\d)*(\\.(\\d(_?\\d)*)?)?|\\.\\d(_?\\d)*)([eE][+-]?\\d(_?\\d)*)?|[+-]?(NaN|Infinity))\\s*$"
|
|
346
|
+
};
|
|
210
347
|
function isIntegerColumn(c) {
|
|
211
348
|
if (typeof c.integer === "boolean") return c.integer;
|
|
212
349
|
return c.dbType === "INTEGER" || c.min !== void 0 && c.max !== void 0;
|
|
@@ -248,6 +385,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
248
385
|
}
|
|
249
386
|
export {
|
|
250
387
|
AFFIX_PROBE_TABLE,
|
|
388
|
+
COLUMN_FORMATS,
|
|
251
389
|
DEFAULT_IMPORT_EXTENSION,
|
|
252
390
|
DEFAULT_MODE_PREFIX,
|
|
253
391
|
DEFAULT_SCHEMA_SUFFIX,
|
|
@@ -255,6 +393,7 @@ export {
|
|
|
255
393
|
IMPORT_EXTENSIONS,
|
|
256
394
|
NAME_MODES,
|
|
257
395
|
applyTableCase,
|
|
396
|
+
describeSet,
|
|
258
397
|
formatCode,
|
|
259
398
|
importSpecifier,
|
|
260
399
|
insertColumns,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/validation-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"sideEffects": false,
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@drzl/analyzer": "^1.
|
|
14
|
+
"@drzl/analyzer": "^1.9.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"tsup": "^8.5.1",
|