@drzl/validation-core 3.22.3 → 3.22.5
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 +50 -5
- package/dist/index.d.cts +38 -15
- package/dist/index.d.ts +38 -15
- package/dist/index.js +47 -5
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
AFFIX_PREFIX_PATTERN: () => AFFIX_PREFIX_PATTERN,
|
|
34
34
|
AFFIX_PROBE_TABLE: () => AFFIX_PROBE_TABLE,
|
|
35
35
|
AFFIX_SUFFIX_PATTERN: () => AFFIX_SUFFIX_PATTERN,
|
|
36
|
+
BIGINT_DIGITS_PATTERN: () => BIGINT_DIGITS_PATTERN,
|
|
36
37
|
CODEPOINT_LENGTH: () => CODEPOINT_LENGTH,
|
|
37
38
|
COERCIBLE_DATE_STRING: () => COERCIBLE_DATE_STRING,
|
|
38
39
|
COLUMN_FORMATS: () => COLUMN_FORMATS,
|
|
@@ -48,6 +49,8 @@ __export(index_exports, {
|
|
|
48
49
|
NESTED_PREFIX: () => NESTED_PREFIX,
|
|
49
50
|
NUMERIC_CANON_NAME: () => NUMERIC_CANON_NAME,
|
|
50
51
|
NUMERIC_CANON_SOURCE: () => NUMERIC_CANON_SOURCE,
|
|
52
|
+
VALIBOT_JSON_CONST: () => VALIBOT_JSON_CONST,
|
|
53
|
+
VALIBOT_JSON_SOURCE: () => VALIBOT_JSON_SOURCE,
|
|
51
54
|
applyTableCase: () => applyTableCase,
|
|
52
55
|
applyWirePolicy: () => applyWirePolicy,
|
|
53
56
|
buildBrandPlan: () => buildBrandPlan,
|
|
@@ -153,7 +156,7 @@ function measureExpression(measure, variable) {
|
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
function lengthCheckLabel(check) {
|
|
156
|
-
const fn = check.unit === "bytes" ? "octet_length" : "length";
|
|
159
|
+
const fn = check.fn ? check.fn === "char_length" ? "length" : check.fn : check.unit === "bytes" ? "octet_length" : "length";
|
|
157
160
|
const rule = `${fn}(${check.column}) ${check.operator} ${check.value}`;
|
|
158
161
|
return check.name ? `${check.name}: ${rule}` : rule;
|
|
159
162
|
}
|
|
@@ -404,7 +407,13 @@ function foldDisjunctionToSet(branches, name) {
|
|
|
404
407
|
sets: [{ column, values, kind, name }]
|
|
405
408
|
};
|
|
406
409
|
}
|
|
407
|
-
function
|
|
410
|
+
function lengthUnit(fn, dialect) {
|
|
411
|
+
const name = fn.toLowerCase();
|
|
412
|
+
if (name === "octet_length") return "bytes";
|
|
413
|
+
if (name === "char_length") return "characters";
|
|
414
|
+
return dialect === "mysql" ? "bytes" : "characters";
|
|
415
|
+
}
|
|
416
|
+
function parseCheck(expression, name, dialect) {
|
|
408
417
|
const expr = unwrap((expression ?? "").trim());
|
|
409
418
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
410
419
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
@@ -481,11 +490,12 @@ function parseCheck(expression, name) {
|
|
|
481
490
|
const lengthOf = expr.match(LENGTH_OF);
|
|
482
491
|
if (lengthOf) {
|
|
483
492
|
const op2 = lengthOf[3] === "!=" ? "<>" : lengthOf[3];
|
|
484
|
-
const
|
|
493
|
+
const fn = lengthOf[1].toLowerCase();
|
|
494
|
+
const unit = lengthUnit(fn, dialect);
|
|
485
495
|
return {
|
|
486
496
|
ok: true,
|
|
487
497
|
checks: [],
|
|
488
|
-
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, name }]
|
|
498
|
+
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, fn, name }]
|
|
489
499
|
};
|
|
490
500
|
}
|
|
491
501
|
const cardinalityOf = expr.match(CARDINALITY_OF);
|
|
@@ -992,7 +1002,7 @@ function classifyTableChecks(table) {
|
|
|
992
1002
|
const out = [];
|
|
993
1003
|
for (const k of table.checks ?? []) {
|
|
994
1004
|
const expression = (k.expression ?? "").trim();
|
|
995
|
-
const parsed = parseCheck(k.expression, k.name);
|
|
1005
|
+
const parsed = parseCheck(k.expression, k.name, table.dialect);
|
|
996
1006
|
if (!parsed.ok) {
|
|
997
1007
|
out.push({
|
|
998
1008
|
...k.name ? { name: k.name } : {},
|
|
@@ -1791,6 +1801,38 @@ function parsesToADate(expr) {
|
|
|
1791
1801
|
return `!Number.isNaN(${expr}.getTime())`;
|
|
1792
1802
|
}
|
|
1793
1803
|
var CODEPOINT_LENGTH = "[...v].length";
|
|
1804
|
+
var BIGINT_DIGITS_PATTERN = String.raw`/^-?\d+$/`;
|
|
1805
|
+
var VALIBOT_JSON_CONST = "DrzlJsonValue";
|
|
1806
|
+
var VALIBOT_JSON_SOURCE = `type ${VALIBOT_JSON_CONST}Type =
|
|
1807
|
+
| string
|
|
1808
|
+
| number
|
|
1809
|
+
| boolean
|
|
1810
|
+
| null
|
|
1811
|
+
| ${VALIBOT_JSON_CONST}Type[]
|
|
1812
|
+
| { [key: string]: ${VALIBOT_JSON_CONST}Type };
|
|
1813
|
+
|
|
1814
|
+
const ${VALIBOT_JSON_CONST}: v.GenericSchema<${VALIBOT_JSON_CONST}Type> = v.lazy(() =>
|
|
1815
|
+
v.union([
|
|
1816
|
+
v.string(),
|
|
1817
|
+
v.pipe(v.number(), v.finite()),
|
|
1818
|
+
v.boolean(),
|
|
1819
|
+
v.null(),
|
|
1820
|
+
v.array(${VALIBOT_JSON_CONST}),
|
|
1821
|
+
v.pipe(
|
|
1822
|
+
// The plain-object test comes before the record, not after it. A valibot pipe passes the
|
|
1823
|
+
// *output* of each step onward, and \`v.record\` outputs a freshly built object, so a check
|
|
1824
|
+
// placed after it inspects that new object and reports every input as plain. A Date sailed
|
|
1825
|
+
// through: it has no own enumerable keys, so the record accepted it and rebuilt it as \`{}\`.
|
|
1826
|
+
v.custom<Record<string, ${VALIBOT_JSON_CONST}Type>>((o) => {
|
|
1827
|
+
if (typeof o !== 'object' || o === null || Array.isArray(o)) return false;
|
|
1828
|
+
const p = Object.getPrototypeOf(o);
|
|
1829
|
+
return p === Object.prototype || p === null;
|
|
1830
|
+
}, 'not a plain object'),
|
|
1831
|
+
v.record(v.string(), ${VALIBOT_JSON_CONST})
|
|
1832
|
+
),
|
|
1833
|
+
])
|
|
1834
|
+
);
|
|
1835
|
+
`;
|
|
1794
1836
|
function isIntegerColumn(c) {
|
|
1795
1837
|
if (typeof c.integer === "boolean") return c.integer;
|
|
1796
1838
|
return c.dbType === "INTEGER" || c.min !== void 0 && c.max !== void 0;
|
|
@@ -1952,6 +1994,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
1952
1994
|
AFFIX_PREFIX_PATTERN,
|
|
1953
1995
|
AFFIX_PROBE_TABLE,
|
|
1954
1996
|
AFFIX_SUFFIX_PATTERN,
|
|
1997
|
+
BIGINT_DIGITS_PATTERN,
|
|
1955
1998
|
CODEPOINT_LENGTH,
|
|
1956
1999
|
COERCIBLE_DATE_STRING,
|
|
1957
2000
|
COLUMN_FORMATS,
|
|
@@ -1967,6 +2010,8 @@ async function formatCode(code, filePath, fmt) {
|
|
|
1967
2010
|
NESTED_PREFIX,
|
|
1968
2011
|
NUMERIC_CANON_NAME,
|
|
1969
2012
|
NUMERIC_CANON_SOURCE,
|
|
2013
|
+
VALIBOT_JSON_CONST,
|
|
2014
|
+
VALIBOT_JSON_SOURCE,
|
|
1970
2015
|
applyTableCase,
|
|
1971
2016
|
applyWirePolicy,
|
|
1972
2017
|
buildBrandPlan,
|
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
|
/**
|
|
@@ -587,21 +596,11 @@ type ParsedCheck = {
|
|
|
587
596
|
reason: string;
|
|
588
597
|
};
|
|
589
598
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
594
|
-
* silently change what is enforced.
|
|
595
|
-
*
|
|
596
|
-
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
597
|
-
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
598
|
-
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
599
|
-
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
600
|
-
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
601
|
-
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
602
|
-
* 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.
|
|
603
602
|
*/
|
|
604
|
-
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
603
|
+
declare function parseCheck(expression: string | undefined, name?: string, dialect?: string): ParsedCheck;
|
|
605
604
|
/**
|
|
606
605
|
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
607
606
|
*
|
|
@@ -1568,6 +1567,30 @@ declare function parsesToADate(expr: string): string;
|
|
|
1568
1567
|
* MySQL 8 on utf8mb4: `varchar(10)` takes ten emoji, `tinytext` takes 63 of them and refuses 64.
|
|
1569
1568
|
*/
|
|
1570
1569
|
declare const CODEPOINT_LENGTH = "[...v].length";
|
|
1570
|
+
/**
|
|
1571
|
+
* The digits of an integer, as a regex literal for an emitted module.
|
|
1572
|
+
*
|
|
1573
|
+
* A bigint cannot cross JSON as a number: `JSON.stringify(1n)` throws on the way out, and a
|
|
1574
|
+
* `number` loses precision past 2^53. So every generator that puts a bigint column on a wire spells
|
|
1575
|
+
* it as its decimal digits, and they all spell it the same way from here.
|
|
1576
|
+
*/
|
|
1577
|
+
declare const BIGINT_DIGITS_PATTERN: string;
|
|
1578
|
+
/** The name of the recursive JSON schema a valibot module defines when it has a json column. */
|
|
1579
|
+
declare const VALIBOT_JSON_CONST = "DrzlJsonValue";
|
|
1580
|
+
/**
|
|
1581
|
+
* The JSON value space for valibot, emitted once into any module that needs it.
|
|
1582
|
+
*
|
|
1583
|
+
* Valibot has no `json()` built-in, unlike zod 4, and `v.any()` accepts `undefined`, `NaN`, bigints
|
|
1584
|
+
* and every class instance, none of which survive the round trip through a json column. So the
|
|
1585
|
+
* space is spelled out, recursively, with `v.finite()` so `Infinity` is rejected rather than
|
|
1586
|
+
* written out as `null`.
|
|
1587
|
+
*
|
|
1588
|
+
* Shared rather than copied because two kinds of generator need the same text: the standalone
|
|
1589
|
+
* valibot generator, whose json column is a value the driver hands back, and the route generators,
|
|
1590
|
+
* whose json column arrives from `JSON.parse` on a request body. The value space is the same in
|
|
1591
|
+
* both directions, and two copies of it would be two things to keep in step.
|
|
1592
|
+
*/
|
|
1593
|
+
declare const VALIBOT_JSON_SOURCE = "type DrzlJsonValueType =\n | string\n | number\n | boolean\n | null\n | DrzlJsonValueType[]\n | { [key: string]: DrzlJsonValueType };\n\nconst DrzlJsonValue: v.GenericSchema<DrzlJsonValueType> = v.lazy(() =>\n v.union([\n v.string(),\n v.pipe(v.number(), v.finite()),\n v.boolean(),\n v.null(),\n v.array(DrzlJsonValue),\n v.pipe(\n // The plain-object test comes before the record, not after it. A valibot pipe passes the\n // *output* of each step onward, and `v.record` outputs a freshly built object, so a check\n // placed after it inspects that new object and reports every input as plain. A Date sailed\n // through: it has no own enumerable keys, so the record accepted it and rebuilt it as `{}`.\n v.custom<Record<string, DrzlJsonValueType>>((o) => {\n if (typeof o !== 'object' || o === null || Array.isArray(o)) return false;\n const p = Object.getPrototypeOf(o);\n return p === Object.prototype || p === null;\n }, 'not a plain object'),\n v.record(v.string(), DrzlJsonValue)\n ),\n ])\n);\n";
|
|
1571
1594
|
declare function isIntegerColumn(c: Column): boolean;
|
|
1572
1595
|
/**
|
|
1573
1596
|
* The non-finite doubles the emitted schema must admit beside the column's range, as the analyzer
|
|
@@ -1697,4 +1720,4 @@ declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
|
1697
1720
|
*/
|
|
1698
1721
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1699
1722
|
|
|
1700
|
-
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 };
|
|
1723
|
+
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, BIGINT_DIGITS_PATTERN, 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, VALIBOT_JSON_CONST, VALIBOT_JSON_SOURCE, 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
|
/**
|
|
@@ -587,21 +596,11 @@ type ParsedCheck = {
|
|
|
587
596
|
reason: string;
|
|
588
597
|
};
|
|
589
598
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
594
|
-
* silently change what is enforced.
|
|
595
|
-
*
|
|
596
|
-
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
597
|
-
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
598
|
-
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
599
|
-
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
600
|
-
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
601
|
-
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
602
|
-
* 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.
|
|
603
602
|
*/
|
|
604
|
-
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
603
|
+
declare function parseCheck(expression: string | undefined, name?: string, dialect?: string): ParsedCheck;
|
|
605
604
|
/**
|
|
606
605
|
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
607
606
|
*
|
|
@@ -1568,6 +1567,30 @@ declare function parsesToADate(expr: string): string;
|
|
|
1568
1567
|
* MySQL 8 on utf8mb4: `varchar(10)` takes ten emoji, `tinytext` takes 63 of them and refuses 64.
|
|
1569
1568
|
*/
|
|
1570
1569
|
declare const CODEPOINT_LENGTH = "[...v].length";
|
|
1570
|
+
/**
|
|
1571
|
+
* The digits of an integer, as a regex literal for an emitted module.
|
|
1572
|
+
*
|
|
1573
|
+
* A bigint cannot cross JSON as a number: `JSON.stringify(1n)` throws on the way out, and a
|
|
1574
|
+
* `number` loses precision past 2^53. So every generator that puts a bigint column on a wire spells
|
|
1575
|
+
* it as its decimal digits, and they all spell it the same way from here.
|
|
1576
|
+
*/
|
|
1577
|
+
declare const BIGINT_DIGITS_PATTERN: string;
|
|
1578
|
+
/** The name of the recursive JSON schema a valibot module defines when it has a json column. */
|
|
1579
|
+
declare const VALIBOT_JSON_CONST = "DrzlJsonValue";
|
|
1580
|
+
/**
|
|
1581
|
+
* The JSON value space for valibot, emitted once into any module that needs it.
|
|
1582
|
+
*
|
|
1583
|
+
* Valibot has no `json()` built-in, unlike zod 4, and `v.any()` accepts `undefined`, `NaN`, bigints
|
|
1584
|
+
* and every class instance, none of which survive the round trip through a json column. So the
|
|
1585
|
+
* space is spelled out, recursively, with `v.finite()` so `Infinity` is rejected rather than
|
|
1586
|
+
* written out as `null`.
|
|
1587
|
+
*
|
|
1588
|
+
* Shared rather than copied because two kinds of generator need the same text: the standalone
|
|
1589
|
+
* valibot generator, whose json column is a value the driver hands back, and the route generators,
|
|
1590
|
+
* whose json column arrives from `JSON.parse` on a request body. The value space is the same in
|
|
1591
|
+
* both directions, and two copies of it would be two things to keep in step.
|
|
1592
|
+
*/
|
|
1593
|
+
declare const VALIBOT_JSON_SOURCE = "type DrzlJsonValueType =\n | string\n | number\n | boolean\n | null\n | DrzlJsonValueType[]\n | { [key: string]: DrzlJsonValueType };\n\nconst DrzlJsonValue: v.GenericSchema<DrzlJsonValueType> = v.lazy(() =>\n v.union([\n v.string(),\n v.pipe(v.number(), v.finite()),\n v.boolean(),\n v.null(),\n v.array(DrzlJsonValue),\n v.pipe(\n // The plain-object test comes before the record, not after it. A valibot pipe passes the\n // *output* of each step onward, and `v.record` outputs a freshly built object, so a check\n // placed after it inspects that new object and reports every input as plain. A Date sailed\n // through: it has no own enumerable keys, so the record accepted it and rebuilt it as `{}`.\n v.custom<Record<string, DrzlJsonValueType>>((o) => {\n if (typeof o !== 'object' || o === null || Array.isArray(o)) return false;\n const p = Object.getPrototypeOf(o);\n return p === Object.prototype || p === null;\n }, 'not a plain object'),\n v.record(v.string(), DrzlJsonValue)\n ),\n ])\n);\n";
|
|
1571
1594
|
declare function isIntegerColumn(c: Column): boolean;
|
|
1572
1595
|
/**
|
|
1573
1596
|
* The non-finite doubles the emitted schema must admit beside the column's range, as the analyzer
|
|
@@ -1697,4 +1720,4 @@ declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
|
1697
1720
|
*/
|
|
1698
1721
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1699
1722
|
|
|
1700
|
-
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 };
|
|
1723
|
+
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, BIGINT_DIGITS_PATTERN, 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, VALIBOT_JSON_CONST, VALIBOT_JSON_SOURCE, 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
|
@@ -52,7 +52,7 @@ function measureExpression(measure, variable) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
function lengthCheckLabel(check) {
|
|
55
|
-
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";
|
|
56
56
|
const rule = `${fn}(${check.column}) ${check.operator} ${check.value}`;
|
|
57
57
|
return check.name ? `${check.name}: ${rule}` : rule;
|
|
58
58
|
}
|
|
@@ -303,7 +303,13 @@ function foldDisjunctionToSet(branches, name) {
|
|
|
303
303
|
sets: [{ column, values, kind, name }]
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
|
-
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) {
|
|
307
313
|
const expr = unwrap((expression ?? "").trim());
|
|
308
314
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
309
315
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
@@ -380,11 +386,12 @@ function parseCheck(expression, name) {
|
|
|
380
386
|
const lengthOf = expr.match(LENGTH_OF);
|
|
381
387
|
if (lengthOf) {
|
|
382
388
|
const op2 = lengthOf[3] === "!=" ? "<>" : lengthOf[3];
|
|
383
|
-
const
|
|
389
|
+
const fn = lengthOf[1].toLowerCase();
|
|
390
|
+
const unit = lengthUnit(fn, dialect);
|
|
384
391
|
return {
|
|
385
392
|
ok: true,
|
|
386
393
|
checks: [],
|
|
387
|
-
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, name }]
|
|
394
|
+
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, fn, name }]
|
|
388
395
|
};
|
|
389
396
|
}
|
|
390
397
|
const cardinalityOf = expr.match(CARDINALITY_OF);
|
|
@@ -891,7 +898,7 @@ function classifyTableChecks(table) {
|
|
|
891
898
|
const out = [];
|
|
892
899
|
for (const k of table.checks ?? []) {
|
|
893
900
|
const expression = (k.expression ?? "").trim();
|
|
894
|
-
const parsed = parseCheck(k.expression, k.name);
|
|
901
|
+
const parsed = parseCheck(k.expression, k.name, table.dialect);
|
|
895
902
|
if (!parsed.ok) {
|
|
896
903
|
out.push({
|
|
897
904
|
...k.name ? { name: k.name } : {},
|
|
@@ -1690,6 +1697,38 @@ function parsesToADate(expr) {
|
|
|
1690
1697
|
return `!Number.isNaN(${expr}.getTime())`;
|
|
1691
1698
|
}
|
|
1692
1699
|
var CODEPOINT_LENGTH = "[...v].length";
|
|
1700
|
+
var BIGINT_DIGITS_PATTERN = String.raw`/^-?\d+$/`;
|
|
1701
|
+
var VALIBOT_JSON_CONST = "DrzlJsonValue";
|
|
1702
|
+
var VALIBOT_JSON_SOURCE = `type ${VALIBOT_JSON_CONST}Type =
|
|
1703
|
+
| string
|
|
1704
|
+
| number
|
|
1705
|
+
| boolean
|
|
1706
|
+
| null
|
|
1707
|
+
| ${VALIBOT_JSON_CONST}Type[]
|
|
1708
|
+
| { [key: string]: ${VALIBOT_JSON_CONST}Type };
|
|
1709
|
+
|
|
1710
|
+
const ${VALIBOT_JSON_CONST}: v.GenericSchema<${VALIBOT_JSON_CONST}Type> = v.lazy(() =>
|
|
1711
|
+
v.union([
|
|
1712
|
+
v.string(),
|
|
1713
|
+
v.pipe(v.number(), v.finite()),
|
|
1714
|
+
v.boolean(),
|
|
1715
|
+
v.null(),
|
|
1716
|
+
v.array(${VALIBOT_JSON_CONST}),
|
|
1717
|
+
v.pipe(
|
|
1718
|
+
// The plain-object test comes before the record, not after it. A valibot pipe passes the
|
|
1719
|
+
// *output* of each step onward, and \`v.record\` outputs a freshly built object, so a check
|
|
1720
|
+
// placed after it inspects that new object and reports every input as plain. A Date sailed
|
|
1721
|
+
// through: it has no own enumerable keys, so the record accepted it and rebuilt it as \`{}\`.
|
|
1722
|
+
v.custom<Record<string, ${VALIBOT_JSON_CONST}Type>>((o) => {
|
|
1723
|
+
if (typeof o !== 'object' || o === null || Array.isArray(o)) return false;
|
|
1724
|
+
const p = Object.getPrototypeOf(o);
|
|
1725
|
+
return p === Object.prototype || p === null;
|
|
1726
|
+
}, 'not a plain object'),
|
|
1727
|
+
v.record(v.string(), ${VALIBOT_JSON_CONST})
|
|
1728
|
+
),
|
|
1729
|
+
])
|
|
1730
|
+
);
|
|
1731
|
+
`;
|
|
1693
1732
|
function isIntegerColumn(c) {
|
|
1694
1733
|
if (typeof c.integer === "boolean") return c.integer;
|
|
1695
1734
|
return c.dbType === "INTEGER" || c.min !== void 0 && c.max !== void 0;
|
|
@@ -1850,6 +1889,7 @@ export {
|
|
|
1850
1889
|
AFFIX_PREFIX_PATTERN,
|
|
1851
1890
|
AFFIX_PROBE_TABLE,
|
|
1852
1891
|
AFFIX_SUFFIX_PATTERN,
|
|
1892
|
+
BIGINT_DIGITS_PATTERN,
|
|
1853
1893
|
CODEPOINT_LENGTH,
|
|
1854
1894
|
COERCIBLE_DATE_STRING,
|
|
1855
1895
|
COLUMN_FORMATS,
|
|
@@ -1865,6 +1905,8 @@ export {
|
|
|
1865
1905
|
NESTED_PREFIX,
|
|
1866
1906
|
NUMERIC_CANON_NAME,
|
|
1867
1907
|
NUMERIC_CANON_SOURCE,
|
|
1908
|
+
VALIBOT_JSON_CONST,
|
|
1909
|
+
VALIBOT_JSON_SOURCE,
|
|
1868
1910
|
applyTableCase,
|
|
1869
1911
|
applyWirePolicy,
|
|
1870
1912
|
buildBrandPlan,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/validation-core",
|
|
3
|
-
"version": "3.22.
|
|
3
|
+
"version": "3.22.5",
|
|
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.5"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"prettier": ">=3"
|