@drzl/validation-core 3.22.2 → 3.22.4
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 +44 -5
- package/dist/index.d.cts +43 -15
- package/dist/index.d.ts +43 -15
- package/dist/index.js +42 -5
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
canonicalMembers: () => canonicalMembers,
|
|
56
56
|
canonicalNumericText: () => canonicalNumericText,
|
|
57
57
|
classifyTableChecks: () => classifyTableChecks,
|
|
58
|
+
codePointCompare: () => codePointCompare,
|
|
58
59
|
columnMetaFacts: () => columnMetaFacts,
|
|
59
60
|
comparisonWire: () => comparisonWire,
|
|
60
61
|
describeSet: () => describeSet,
|
|
@@ -67,6 +68,7 @@ __export(index_exports, {
|
|
|
67
68
|
isProjectInstallPath: () => isProjectInstallPath,
|
|
68
69
|
lengthCheckLabel: () => lengthCheckLabel,
|
|
69
70
|
lengthMeasure: () => lengthMeasure,
|
|
71
|
+
measureCompare: () => measureCompare,
|
|
70
72
|
measureExpression: () => measureExpression,
|
|
71
73
|
moduleFileName: () => moduleFileName,
|
|
72
74
|
moduleSpecifier: () => moduleSpecifier,
|
|
@@ -112,6 +114,34 @@ function lengthMeasure(column, check) {
|
|
|
112
114
|
if (column.tsType !== "string") return void 0;
|
|
113
115
|
return check.unit === "bytes" ? "utf8Bytes" : "codePoints";
|
|
114
116
|
}
|
|
117
|
+
function codePointCompare(variable, operator, bound) {
|
|
118
|
+
const units = `${variable}.length`;
|
|
119
|
+
const spread2 = `[...${variable}].length`;
|
|
120
|
+
switch (operator) {
|
|
121
|
+
case "<=":
|
|
122
|
+
case "<":
|
|
123
|
+
return `(${units} ${operator} ${bound} || ${spread2} ${operator} ${bound})`;
|
|
124
|
+
case ">=":
|
|
125
|
+
case ">":
|
|
126
|
+
return `(${units} ${operator} ${bound} && ${spread2} ${operator} ${bound})`;
|
|
127
|
+
case "===":
|
|
128
|
+
return `(${units} >= ${bound} && ${spread2} === ${bound})`;
|
|
129
|
+
case "!==":
|
|
130
|
+
return `(${units} < ${bound} || ${spread2} !== ${bound})`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function measureCompare(measure, variable, operator, bound) {
|
|
134
|
+
const js = {
|
|
135
|
+
">=": ">=",
|
|
136
|
+
">": ">",
|
|
137
|
+
"<=": "<=",
|
|
138
|
+
"<": "<",
|
|
139
|
+
"=": "===",
|
|
140
|
+
"<>": "!=="
|
|
141
|
+
}[operator];
|
|
142
|
+
if (measure === "codePoints") return codePointCompare(variable, js, bound);
|
|
143
|
+
return `${measureExpression(measure, variable)} ${js} ${bound}`;
|
|
144
|
+
}
|
|
115
145
|
function measureExpression(measure, variable) {
|
|
116
146
|
switch (measure) {
|
|
117
147
|
case "codePoints":
|
|
@@ -123,7 +153,7 @@ function measureExpression(measure, variable) {
|
|
|
123
153
|
}
|
|
124
154
|
}
|
|
125
155
|
function lengthCheckLabel(check) {
|
|
126
|
-
const fn = check.unit === "bytes" ? "octet_length" : "length";
|
|
156
|
+
const fn = check.fn ? check.fn === "char_length" ? "length" : check.fn : check.unit === "bytes" ? "octet_length" : "length";
|
|
127
157
|
const rule = `${fn}(${check.column}) ${check.operator} ${check.value}`;
|
|
128
158
|
return check.name ? `${check.name}: ${rule}` : rule;
|
|
129
159
|
}
|
|
@@ -374,7 +404,13 @@ function foldDisjunctionToSet(branches, name) {
|
|
|
374
404
|
sets: [{ column, values, kind, name }]
|
|
375
405
|
};
|
|
376
406
|
}
|
|
377
|
-
function
|
|
407
|
+
function lengthUnit(fn, dialect) {
|
|
408
|
+
const name = fn.toLowerCase();
|
|
409
|
+
if (name === "octet_length") return "bytes";
|
|
410
|
+
if (name === "char_length") return "characters";
|
|
411
|
+
return dialect === "mysql" ? "bytes" : "characters";
|
|
412
|
+
}
|
|
413
|
+
function parseCheck(expression, name, dialect) {
|
|
378
414
|
const expr = unwrap((expression ?? "").trim());
|
|
379
415
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
380
416
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
@@ -451,11 +487,12 @@ function parseCheck(expression, name) {
|
|
|
451
487
|
const lengthOf = expr.match(LENGTH_OF);
|
|
452
488
|
if (lengthOf) {
|
|
453
489
|
const op2 = lengthOf[3] === "!=" ? "<>" : lengthOf[3];
|
|
454
|
-
const
|
|
490
|
+
const fn = lengthOf[1].toLowerCase();
|
|
491
|
+
const unit = lengthUnit(fn, dialect);
|
|
455
492
|
return {
|
|
456
493
|
ok: true,
|
|
457
494
|
checks: [],
|
|
458
|
-
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, name }]
|
|
495
|
+
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, fn, name }]
|
|
459
496
|
};
|
|
460
497
|
}
|
|
461
498
|
const cardinalityOf = expr.match(CARDINALITY_OF);
|
|
@@ -962,7 +999,7 @@ function classifyTableChecks(table) {
|
|
|
962
999
|
const out = [];
|
|
963
1000
|
for (const k of table.checks ?? []) {
|
|
964
1001
|
const expression = (k.expression ?? "").trim();
|
|
965
|
-
const parsed = parseCheck(k.expression, k.name);
|
|
1002
|
+
const parsed = parseCheck(k.expression, k.name, table.dialect);
|
|
966
1003
|
if (!parsed.ok) {
|
|
967
1004
|
out.push({
|
|
968
1005
|
...k.name ? { name: k.name } : {},
|
|
@@ -1944,6 +1981,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
1944
1981
|
canonicalMembers,
|
|
1945
1982
|
canonicalNumericText,
|
|
1946
1983
|
classifyTableChecks,
|
|
1984
|
+
codePointCompare,
|
|
1947
1985
|
columnMetaFacts,
|
|
1948
1986
|
comparisonWire,
|
|
1949
1987
|
describeSet,
|
|
@@ -1956,6 +1994,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
1956
1994
|
isProjectInstallPath,
|
|
1957
1995
|
lengthCheckLabel,
|
|
1958
1996
|
lengthMeasure,
|
|
1997
|
+
measureCompare,
|
|
1959
1998
|
measureExpression,
|
|
1960
1999
|
moduleFileName,
|
|
1961
2000
|
moduleSpecifier,
|
package/dist/index.d.cts
CHANGED
|
@@ -452,6 +452,15 @@ interface LengthCheck {
|
|
|
452
452
|
* a given column.
|
|
453
453
|
*/
|
|
454
454
|
unit?: 'characters' | 'bytes';
|
|
455
|
+
/**
|
|
456
|
+
* The function as written, before the engine decided what it counts.
|
|
457
|
+
*
|
|
458
|
+
* `unit` used to imply this, because the two were the same question: `octet_length` meant bytes
|
|
459
|
+
* and everything else meant characters. On MySQL `length()` also means bytes, so deriving the
|
|
460
|
+
* name back from the unit relabelled a user's `length(name) <= 5` as `octet_length(name) <= 5`,
|
|
461
|
+
* which is a constraint they did not write. The label the ledger matches on has to be theirs.
|
|
462
|
+
*/
|
|
463
|
+
fn?: 'length' | 'char_length' | 'octet_length';
|
|
455
464
|
name?: string;
|
|
456
465
|
}
|
|
457
466
|
/**
|
|
@@ -504,6 +513,35 @@ declare function lengthMeasure(column: {
|
|
|
504
513
|
* TypeBox reach it off the row object as `o["blob"]`. `CODEPOINT_LENGTH` is the fixed-variable
|
|
505
514
|
* spelling of the first arm and stays for the call sites that already use it.
|
|
506
515
|
*/
|
|
516
|
+
/**
|
|
517
|
+
* A code-point count compared against a bound, without spreading the string when it cannot matter.
|
|
518
|
+
*
|
|
519
|
+
* `[...v].length` allocates an array of code points, and it sits on the path every ordinary row
|
|
520
|
+
* takes. A UTF-16 unit count is free and is never *smaller* than a code-point count, since only a
|
|
521
|
+
* surrogate pair costs two units for one code point. That one fact settles every operator here:
|
|
522
|
+
*
|
|
523
|
+
* `<=`, `<` units within the bound means code points are too short-circuit to ACCEPT
|
|
524
|
+
* `>=`, `>` units under the bound means code points are too short-circuit to REJECT
|
|
525
|
+
* `===` units under the bound means code points cannot equal it short-circuit to REJECT
|
|
526
|
+
* `!==` units under the bound means code points cannot equal it short-circuit to ACCEPT
|
|
527
|
+
*
|
|
528
|
+
* So the spread runs only for a string long enough in units to be in doubt, which for a cap is the
|
|
529
|
+
* only string that was ever in doubt. Measured over a 20000-string random pool plus the awkward
|
|
530
|
+
* cases (emoji, combining marks, a lone surrogate, empty, CJK at the bound): the two forms answer
|
|
531
|
+
* identically on every one. Throughput on the check alone, cap 64: ordinary ASCII rows 23171k/s
|
|
532
|
+
* against 215029k/s, rows at the cap 4250k/s against 235657k/s.
|
|
533
|
+
*
|
|
534
|
+
* Only the code-point measurement is rewritten. A `byteLength` reads `v.length` already and has
|
|
535
|
+
* nothing to avoid, and the UTF-8 arm has its own sound short-circuits in both directions but a
|
|
536
|
+
* different allocation to weigh, so it is left for its own measurement rather than folded in here.
|
|
537
|
+
*/
|
|
538
|
+
declare function codePointCompare(variable: string, operator: '>=' | '>' | '<=' | '<' | '===' | '!==', bound: string | number): string;
|
|
539
|
+
/**
|
|
540
|
+
* A length comparison over whichever measurement the column answers, short-circuited where that is
|
|
541
|
+
* sound. The one place an operator and a count meet, so no generator has to re-derive which
|
|
542
|
+
* rewrites are safe; see `codePointCompare`.
|
|
543
|
+
*/
|
|
544
|
+
declare function measureCompare(measure: LengthMeasure, variable: string, operator: LengthCheck['operator'], bound: string | number): string;
|
|
507
545
|
declare function measureExpression(measure: LengthMeasure, variable: string): string;
|
|
508
546
|
/**
|
|
509
547
|
* A count constraint as the sentence every emitted schema attaches and the ledger keys on.
|
|
@@ -558,21 +596,11 @@ type ParsedCheck = {
|
|
|
558
596
|
reason: string;
|
|
559
597
|
};
|
|
560
598
|
/**
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
565
|
-
* silently change what is enforced.
|
|
566
|
-
*
|
|
567
|
-
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
568
|
-
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
569
|
-
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
570
|
-
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
571
|
-
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
572
|
-
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
573
|
-
* no SQL operator spells an `OR` inside itself the way `BETWEEN` spells an `AND`.
|
|
599
|
+
* `dialect` decides what `length()` counts, and nothing else in this parser. Optional, and absent
|
|
600
|
+
* means the Postgres reading, which is what every caller got before it existed: a caller that does
|
|
601
|
+
* not know its dialect keeps the answer it already had rather than being given a new one.
|
|
574
602
|
*/
|
|
575
|
-
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
603
|
+
declare function parseCheck(expression: string | undefined, name?: string, dialect?: string): ParsedCheck;
|
|
576
604
|
/**
|
|
577
605
|
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
578
606
|
*
|
|
@@ -1668,4 +1696,4 @@ declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
|
1668
1696
|
*/
|
|
1669
1697
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1670
1698
|
|
|
1671
|
-
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, CONSTRAINTS_MODULE, type CardinalityCheck, type CheckPart, type CheckPartPlace, type ClassifiedCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, type ComparisonWire, type ConstraintFacts, type ConstraintKind, type ConstraintsModuleOptions, type ConstraintsOption, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, type LengthMeasure, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, NUMERIC_CANON_NAME, NUMERIC_CANON_SOURCE, type NameMode, type NestedArm, type NestedMode, type NestedNode, type NullCheck, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableConstraints, type TableMetaFacts, type TableMetaOptions, type UnenforcedLiteral, type UnenforcedPart, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, type WireLiteralFit, type WireLiteralQuestion, applyTableCase, applyWirePolicy, buildBrandPlan, buildNestedPlan, canonicalMembers, canonicalNumericText, classifyTableChecks, columnMetaFacts, comparisonWire, describeSet, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureExpression, moduleFileName, moduleSpecifier, needsNumericCanon, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, nonFiniteRefused, parseCheck, parsesToADate, pascalCase, renderConstraintsModule, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveConstraints, resolveNestedDepth, schemaName, selectColumns, tableConstraints, tableMetaFacts, typeName, updateColumns, validateAffix, wireLiteralFit, wireNumberLiteral };
|
|
1699
|
+
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, CONSTRAINTS_MODULE, type CardinalityCheck, type CheckPart, type CheckPartPlace, type ClassifiedCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, type ComparisonWire, type ConstraintFacts, type ConstraintKind, type ConstraintsModuleOptions, type ConstraintsOption, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, type LengthMeasure, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, NUMERIC_CANON_NAME, NUMERIC_CANON_SOURCE, type NameMode, type NestedArm, type NestedMode, type NestedNode, type NullCheck, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableConstraints, type TableMetaFacts, type TableMetaOptions, type UnenforcedLiteral, type UnenforcedPart, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, type WireLiteralFit, type WireLiteralQuestion, applyTableCase, applyWirePolicy, buildBrandPlan, buildNestedPlan, canonicalMembers, canonicalNumericText, classifyTableChecks, codePointCompare, columnMetaFacts, comparisonWire, describeSet, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureCompare, measureExpression, moduleFileName, moduleSpecifier, needsNumericCanon, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, nonFiniteRefused, parseCheck, parsesToADate, pascalCase, renderConstraintsModule, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveConstraints, resolveNestedDepth, schemaName, selectColumns, tableConstraints, tableMetaFacts, typeName, updateColumns, validateAffix, wireLiteralFit, wireNumberLiteral };
|
package/dist/index.d.ts
CHANGED
|
@@ -452,6 +452,15 @@ interface LengthCheck {
|
|
|
452
452
|
* a given column.
|
|
453
453
|
*/
|
|
454
454
|
unit?: 'characters' | 'bytes';
|
|
455
|
+
/**
|
|
456
|
+
* The function as written, before the engine decided what it counts.
|
|
457
|
+
*
|
|
458
|
+
* `unit` used to imply this, because the two were the same question: `octet_length` meant bytes
|
|
459
|
+
* and everything else meant characters. On MySQL `length()` also means bytes, so deriving the
|
|
460
|
+
* name back from the unit relabelled a user's `length(name) <= 5` as `octet_length(name) <= 5`,
|
|
461
|
+
* which is a constraint they did not write. The label the ledger matches on has to be theirs.
|
|
462
|
+
*/
|
|
463
|
+
fn?: 'length' | 'char_length' | 'octet_length';
|
|
455
464
|
name?: string;
|
|
456
465
|
}
|
|
457
466
|
/**
|
|
@@ -504,6 +513,35 @@ declare function lengthMeasure(column: {
|
|
|
504
513
|
* TypeBox reach it off the row object as `o["blob"]`. `CODEPOINT_LENGTH` is the fixed-variable
|
|
505
514
|
* spelling of the first arm and stays for the call sites that already use it.
|
|
506
515
|
*/
|
|
516
|
+
/**
|
|
517
|
+
* A code-point count compared against a bound, without spreading the string when it cannot matter.
|
|
518
|
+
*
|
|
519
|
+
* `[...v].length` allocates an array of code points, and it sits on the path every ordinary row
|
|
520
|
+
* takes. A UTF-16 unit count is free and is never *smaller* than a code-point count, since only a
|
|
521
|
+
* surrogate pair costs two units for one code point. That one fact settles every operator here:
|
|
522
|
+
*
|
|
523
|
+
* `<=`, `<` units within the bound means code points are too short-circuit to ACCEPT
|
|
524
|
+
* `>=`, `>` units under the bound means code points are too short-circuit to REJECT
|
|
525
|
+
* `===` units under the bound means code points cannot equal it short-circuit to REJECT
|
|
526
|
+
* `!==` units under the bound means code points cannot equal it short-circuit to ACCEPT
|
|
527
|
+
*
|
|
528
|
+
* So the spread runs only for a string long enough in units to be in doubt, which for a cap is the
|
|
529
|
+
* only string that was ever in doubt. Measured over a 20000-string random pool plus the awkward
|
|
530
|
+
* cases (emoji, combining marks, a lone surrogate, empty, CJK at the bound): the two forms answer
|
|
531
|
+
* identically on every one. Throughput on the check alone, cap 64: ordinary ASCII rows 23171k/s
|
|
532
|
+
* against 215029k/s, rows at the cap 4250k/s against 235657k/s.
|
|
533
|
+
*
|
|
534
|
+
* Only the code-point measurement is rewritten. A `byteLength` reads `v.length` already and has
|
|
535
|
+
* nothing to avoid, and the UTF-8 arm has its own sound short-circuits in both directions but a
|
|
536
|
+
* different allocation to weigh, so it is left for its own measurement rather than folded in here.
|
|
537
|
+
*/
|
|
538
|
+
declare function codePointCompare(variable: string, operator: '>=' | '>' | '<=' | '<' | '===' | '!==', bound: string | number): string;
|
|
539
|
+
/**
|
|
540
|
+
* A length comparison over whichever measurement the column answers, short-circuited where that is
|
|
541
|
+
* sound. The one place an operator and a count meet, so no generator has to re-derive which
|
|
542
|
+
* rewrites are safe; see `codePointCompare`.
|
|
543
|
+
*/
|
|
544
|
+
declare function measureCompare(measure: LengthMeasure, variable: string, operator: LengthCheck['operator'], bound: string | number): string;
|
|
507
545
|
declare function measureExpression(measure: LengthMeasure, variable: string): string;
|
|
508
546
|
/**
|
|
509
547
|
* A count constraint as the sentence every emitted schema attaches and the ledger keys on.
|
|
@@ -558,21 +596,11 @@ type ParsedCheck = {
|
|
|
558
596
|
reason: string;
|
|
559
597
|
};
|
|
560
598
|
/**
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
565
|
-
* silently change what is enforced.
|
|
566
|
-
*
|
|
567
|
-
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
568
|
-
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
569
|
-
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
570
|
-
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
571
|
-
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
572
|
-
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
573
|
-
* no SQL operator spells an `OR` inside itself the way `BETWEEN` spells an `AND`.
|
|
599
|
+
* `dialect` decides what `length()` counts, and nothing else in this parser. Optional, and absent
|
|
600
|
+
* means the Postgres reading, which is what every caller got before it existed: a caller that does
|
|
601
|
+
* not know its dialect keeps the answer it already had rather than being given a new one.
|
|
574
602
|
*/
|
|
575
|
-
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
603
|
+
declare function parseCheck(expression: string | undefined, name?: string, dialect?: string): ParsedCheck;
|
|
576
604
|
/**
|
|
577
605
|
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
578
606
|
*
|
|
@@ -1668,4 +1696,4 @@ declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
|
1668
1696
|
*/
|
|
1669
1697
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1670
1698
|
|
|
1671
|
-
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, CONSTRAINTS_MODULE, type CardinalityCheck, type CheckPart, type CheckPartPlace, type ClassifiedCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, type ComparisonWire, type ConstraintFacts, type ConstraintKind, type ConstraintsModuleOptions, type ConstraintsOption, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, type LengthMeasure, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, NUMERIC_CANON_NAME, NUMERIC_CANON_SOURCE, type NameMode, type NestedArm, type NestedMode, type NestedNode, type NullCheck, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableConstraints, type TableMetaFacts, type TableMetaOptions, type UnenforcedLiteral, type UnenforcedPart, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, type WireLiteralFit, type WireLiteralQuestion, applyTableCase, applyWirePolicy, buildBrandPlan, buildNestedPlan, canonicalMembers, canonicalNumericText, classifyTableChecks, columnMetaFacts, comparisonWire, describeSet, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureExpression, moduleFileName, moduleSpecifier, needsNumericCanon, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, nonFiniteRefused, parseCheck, parsesToADate, pascalCase, renderConstraintsModule, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveConstraints, resolveNestedDepth, schemaName, selectColumns, tableConstraints, tableMetaFacts, typeName, updateColumns, validateAffix, wireLiteralFit, wireNumberLiteral };
|
|
1699
|
+
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, CONSTRAINTS_MODULE, type CardinalityCheck, type CheckPart, type CheckPartPlace, type ClassifiedCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, type ComparisonWire, type ConstraintFacts, type ConstraintKind, type ConstraintsModuleOptions, type ConstraintsOption, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, type LengthMeasure, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, NUMERIC_CANON_NAME, NUMERIC_CANON_SOURCE, type NameMode, type NestedArm, type NestedMode, type NestedNode, type NullCheck, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableConstraints, type TableMetaFacts, type TableMetaOptions, type UnenforcedLiteral, type UnenforcedPart, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, type WireLiteralFit, type WireLiteralQuestion, applyTableCase, applyWirePolicy, buildBrandPlan, buildNestedPlan, canonicalMembers, canonicalNumericText, classifyTableChecks, codePointCompare, columnMetaFacts, comparisonWire, describeSet, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureCompare, measureExpression, moduleFileName, moduleSpecifier, needsNumericCanon, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, nonFiniteRefused, parseCheck, parsesToADate, pascalCase, renderConstraintsModule, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveConstraints, resolveNestedDepth, schemaName, selectColumns, tableConstraints, tableMetaFacts, typeName, updateColumns, validateAffix, wireLiteralFit, wireNumberLiteral };
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,34 @@ function lengthMeasure(column, check) {
|
|
|
13
13
|
if (column.tsType !== "string") return void 0;
|
|
14
14
|
return check.unit === "bytes" ? "utf8Bytes" : "codePoints";
|
|
15
15
|
}
|
|
16
|
+
function codePointCompare(variable, operator, bound) {
|
|
17
|
+
const units = `${variable}.length`;
|
|
18
|
+
const spread2 = `[...${variable}].length`;
|
|
19
|
+
switch (operator) {
|
|
20
|
+
case "<=":
|
|
21
|
+
case "<":
|
|
22
|
+
return `(${units} ${operator} ${bound} || ${spread2} ${operator} ${bound})`;
|
|
23
|
+
case ">=":
|
|
24
|
+
case ">":
|
|
25
|
+
return `(${units} ${operator} ${bound} && ${spread2} ${operator} ${bound})`;
|
|
26
|
+
case "===":
|
|
27
|
+
return `(${units} >= ${bound} && ${spread2} === ${bound})`;
|
|
28
|
+
case "!==":
|
|
29
|
+
return `(${units} < ${bound} || ${spread2} !== ${bound})`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function measureCompare(measure, variable, operator, bound) {
|
|
33
|
+
const js = {
|
|
34
|
+
">=": ">=",
|
|
35
|
+
">": ">",
|
|
36
|
+
"<=": "<=",
|
|
37
|
+
"<": "<",
|
|
38
|
+
"=": "===",
|
|
39
|
+
"<>": "!=="
|
|
40
|
+
}[operator];
|
|
41
|
+
if (measure === "codePoints") return codePointCompare(variable, js, bound);
|
|
42
|
+
return `${measureExpression(measure, variable)} ${js} ${bound}`;
|
|
43
|
+
}
|
|
16
44
|
function measureExpression(measure, variable) {
|
|
17
45
|
switch (measure) {
|
|
18
46
|
case "codePoints":
|
|
@@ -24,7 +52,7 @@ function measureExpression(measure, variable) {
|
|
|
24
52
|
}
|
|
25
53
|
}
|
|
26
54
|
function lengthCheckLabel(check) {
|
|
27
|
-
const fn = check.unit === "bytes" ? "octet_length" : "length";
|
|
55
|
+
const fn = check.fn ? check.fn === "char_length" ? "length" : check.fn : check.unit === "bytes" ? "octet_length" : "length";
|
|
28
56
|
const rule = `${fn}(${check.column}) ${check.operator} ${check.value}`;
|
|
29
57
|
return check.name ? `${check.name}: ${rule}` : rule;
|
|
30
58
|
}
|
|
@@ -275,7 +303,13 @@ function foldDisjunctionToSet(branches, name) {
|
|
|
275
303
|
sets: [{ column, values, kind, name }]
|
|
276
304
|
};
|
|
277
305
|
}
|
|
278
|
-
function
|
|
306
|
+
function lengthUnit(fn, dialect) {
|
|
307
|
+
const name = fn.toLowerCase();
|
|
308
|
+
if (name === "octet_length") return "bytes";
|
|
309
|
+
if (name === "char_length") return "characters";
|
|
310
|
+
return dialect === "mysql" ? "bytes" : "characters";
|
|
311
|
+
}
|
|
312
|
+
function parseCheck(expression, name, dialect) {
|
|
279
313
|
const expr = unwrap((expression ?? "").trim());
|
|
280
314
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
281
315
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
@@ -352,11 +386,12 @@ function parseCheck(expression, name) {
|
|
|
352
386
|
const lengthOf = expr.match(LENGTH_OF);
|
|
353
387
|
if (lengthOf) {
|
|
354
388
|
const op2 = lengthOf[3] === "!=" ? "<>" : lengthOf[3];
|
|
355
|
-
const
|
|
389
|
+
const fn = lengthOf[1].toLowerCase();
|
|
390
|
+
const unit = lengthUnit(fn, dialect);
|
|
356
391
|
return {
|
|
357
392
|
ok: true,
|
|
358
393
|
checks: [],
|
|
359
|
-
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, name }]
|
|
394
|
+
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, fn, name }]
|
|
360
395
|
};
|
|
361
396
|
}
|
|
362
397
|
const cardinalityOf = expr.match(CARDINALITY_OF);
|
|
@@ -863,7 +898,7 @@ function classifyTableChecks(table) {
|
|
|
863
898
|
const out = [];
|
|
864
899
|
for (const k of table.checks ?? []) {
|
|
865
900
|
const expression = (k.expression ?? "").trim();
|
|
866
|
-
const parsed = parseCheck(k.expression, k.name);
|
|
901
|
+
const parsed = parseCheck(k.expression, k.name, table.dialect);
|
|
867
902
|
if (!parsed.ok) {
|
|
868
903
|
out.push({
|
|
869
904
|
...k.name ? { name: k.name } : {},
|
|
@@ -1844,6 +1879,7 @@ export {
|
|
|
1844
1879
|
canonicalMembers,
|
|
1845
1880
|
canonicalNumericText,
|
|
1846
1881
|
classifyTableChecks,
|
|
1882
|
+
codePointCompare,
|
|
1847
1883
|
columnMetaFacts,
|
|
1848
1884
|
comparisonWire,
|
|
1849
1885
|
describeSet,
|
|
@@ -1856,6 +1892,7 @@ export {
|
|
|
1856
1892
|
isProjectInstallPath,
|
|
1857
1893
|
lengthCheckLabel,
|
|
1858
1894
|
lengthMeasure,
|
|
1895
|
+
measureCompare,
|
|
1859
1896
|
measureExpression,
|
|
1860
1897
|
moduleFileName,
|
|
1861
1898
|
moduleSpecifier,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/validation-core",
|
|
3
|
-
"version": "3.22.
|
|
3
|
+
"version": "3.22.4",
|
|
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.21.
|
|
27
|
+
"@drzl/analyzer": "^1.21.4"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"prettier": ">=3"
|