@drzl/validation-core 3.22.6 → 3.23.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 +64 -0
- package/dist/index.d.cts +95 -1
- package/dist/index.d.ts +95 -1
- package/dist/index.js +62 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,7 @@ __export(index_exports, {
|
|
|
64
64
|
columnMetaFacts: () => columnMetaFacts,
|
|
65
65
|
comparisonWire: () => comparisonWire,
|
|
66
66
|
describeSet: () => describeSet,
|
|
67
|
+
fieldFacts: () => fieldFacts,
|
|
67
68
|
fileWriter: () => fileWriter,
|
|
68
69
|
formatCode: () => formatCode,
|
|
69
70
|
importSpecifier: () => importSpecifier,
|
|
@@ -75,6 +76,7 @@ __export(index_exports, {
|
|
|
75
76
|
lengthMeasure: () => lengthMeasure,
|
|
76
77
|
measureCompare: () => measureCompare,
|
|
77
78
|
measureExpression: () => measureExpression,
|
|
79
|
+
metaSchemaId: () => metaSchemaId,
|
|
78
80
|
moduleFileName: () => moduleFileName,
|
|
79
81
|
moduleSpecifier: () => moduleSpecifier,
|
|
80
82
|
needsNumericCanon: () => needsNumericCanon,
|
|
@@ -1461,6 +1463,60 @@ function fileWriter(sink) {
|
|
|
1461
1463
|
};
|
|
1462
1464
|
}
|
|
1463
1465
|
|
|
1466
|
+
// src/fields.ts
|
|
1467
|
+
var UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
|
|
1468
|
+
function controlFor(c) {
|
|
1469
|
+
if (c.enumValues?.length) return "select";
|
|
1470
|
+
if (c.tsType === "boolean") return "checkbox";
|
|
1471
|
+
if (c.tsType === "number" || c.tsType === "bigint") return "number";
|
|
1472
|
+
if (c.tsType === "Date") return "datetime-local";
|
|
1473
|
+
return "text";
|
|
1474
|
+
}
|
|
1475
|
+
function boundsFor(c, checks) {
|
|
1476
|
+
let lo = c.min !== void 0 ? { value: c.min, exclusive: false } : void 0;
|
|
1477
|
+
let hi = c.max !== void 0 ? { value: c.max, exclusive: false } : void 0;
|
|
1478
|
+
for (const k of checks) {
|
|
1479
|
+
if (k.column !== c.name || k.kind !== "number") continue;
|
|
1480
|
+
if (k.operator === ">=") lo = { value: k.value, exclusive: false };
|
|
1481
|
+
else if (k.operator === ">") lo = { value: k.value, exclusive: true };
|
|
1482
|
+
else if (k.operator === "<=") hi = { value: k.value, exclusive: false };
|
|
1483
|
+
else if (k.operator === "<") hi = { value: k.value, exclusive: true };
|
|
1484
|
+
}
|
|
1485
|
+
return {
|
|
1486
|
+
...lo ? { min: lo.value, ...lo.exclusive ? { exclusiveMin: true } : {} } : {},
|
|
1487
|
+
...hi ? { max: hi.value, ...hi.exclusive ? { exclusiveMax: true } : {} } : {}
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function fieldFacts(table) {
|
|
1491
|
+
const parsed = (table.checks ?? []).map(
|
|
1492
|
+
(k) => parseCheck(k.expression, k.name, table.dialect)
|
|
1493
|
+
);
|
|
1494
|
+
const ok = parsed.filter((p) => !!p && p.ok);
|
|
1495
|
+
const columnChecks = ok.flatMap((p) => p.checks ?? []);
|
|
1496
|
+
const lengthChecks = ok.flatMap((p) => p.lengths ?? []);
|
|
1497
|
+
return table.columns.filter((c) => !c.isGenerated).map((c) => ({
|
|
1498
|
+
name: c.name,
|
|
1499
|
+
control: controlFor(c),
|
|
1500
|
+
// A column the database fills is not one a form has to supply, which is the insert-side
|
|
1501
|
+
// question a form asks. `nullable` is reported beside it for a reader asking the other one.
|
|
1502
|
+
required: !c.nullable && !c.hasDefault,
|
|
1503
|
+
nullable: c.nullable,
|
|
1504
|
+
...(() => {
|
|
1505
|
+
const declared = c.maxLength;
|
|
1506
|
+
const fromCheck = lengthChecks.filter(
|
|
1507
|
+
(l) => l.column === c.name && (l.unit ?? "characters") === "characters" && (l.operator === "<=" || l.operator === "<")
|
|
1508
|
+
).map((l) => l.operator === "<" ? Number(l.value) - 1 : Number(l.value)).filter((n) => Number.isFinite(n));
|
|
1509
|
+
const all = [...declared !== void 0 ? [declared] : [], ...fromCheck];
|
|
1510
|
+
return all.length ? { maxLength: Math.min(...all) } : {};
|
|
1511
|
+
})(),
|
|
1512
|
+
...boundsFor(c, columnChecks),
|
|
1513
|
+
...c.integer ? { integer: true } : {},
|
|
1514
|
+
...c.enumValues?.length ? { options: [...c.enumValues] } : {},
|
|
1515
|
+
...c.format === "uuid" ? { pattern: UUID_PATTERN } : {},
|
|
1516
|
+
...c.defaultValue !== void 0 ? { defaultValue: c.defaultValue } : {}
|
|
1517
|
+
}));
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1464
1520
|
// src/files.ts
|
|
1465
1521
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
1466
1522
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -1521,6 +1577,11 @@ function withTsExtension(p) {
|
|
|
1521
1577
|
}
|
|
1522
1578
|
|
|
1523
1579
|
// src/meta.ts
|
|
1580
|
+
function metaSchemaId(table, mode) {
|
|
1581
|
+
const qualified = table.schema ? `${table.schema}_${table.name}` : table.name;
|
|
1582
|
+
const safe = qualified.replace(/[^A-Za-z0-9_]/g, "_");
|
|
1583
|
+
return `${safe}${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
|
|
1584
|
+
}
|
|
1524
1585
|
function classifyChecks(table) {
|
|
1525
1586
|
const perColumn = /* @__PURE__ */ new Map();
|
|
1526
1587
|
const rows = [];
|
|
@@ -1579,6 +1640,7 @@ function tableMetaFacts(table, opts) {
|
|
|
1579
1640
|
const pk = table.primaryKey?.columns ?? [];
|
|
1580
1641
|
const unique = (table.unique ?? []).map((k) => k.columns).filter((c) => c.length > 0);
|
|
1581
1642
|
const facts = {
|
|
1643
|
+
...opts.id ? { id: opts.id } : {},
|
|
1582
1644
|
table: table.name,
|
|
1583
1645
|
...table.schema ? { schema: table.schema } : {},
|
|
1584
1646
|
...opts.dialect ? { dialect: opts.dialect } : {},
|
|
@@ -2036,6 +2098,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
2036
2098
|
columnMetaFacts,
|
|
2037
2099
|
comparisonWire,
|
|
2038
2100
|
describeSet,
|
|
2101
|
+
fieldFacts,
|
|
2039
2102
|
fileWriter,
|
|
2040
2103
|
formatCode,
|
|
2041
2104
|
importSpecifier,
|
|
@@ -2047,6 +2110,7 @@ async function formatCode(code, filePath, fmt) {
|
|
|
2047
2110
|
lengthMeasure,
|
|
2048
2111
|
measureCompare,
|
|
2049
2112
|
measureExpression,
|
|
2113
|
+
metaSchemaId,
|
|
2050
2114
|
moduleFileName,
|
|
2051
2115
|
moduleSpecifier,
|
|
2052
2116
|
needsNumericCanon,
|
package/dist/index.d.cts
CHANGED
|
@@ -966,6 +966,65 @@ declare const CONSTRAINTS_MODULE = "constraints.ts";
|
|
|
966
966
|
*/
|
|
967
967
|
declare function renderConstraintsModule(tables: Table$1[], opts?: ConstraintsModuleOptions): string;
|
|
968
968
|
|
|
969
|
+
/**
|
|
970
|
+
* What one column is, in the terms a form control needs.
|
|
971
|
+
*
|
|
972
|
+
* Every validation generator already folds a table's `CHECK` constraints into the bounds it emits,
|
|
973
|
+
* and every one of them does it with a private copy of the same loop. A form generator reading
|
|
974
|
+
* `Column.min` and `Column.max` directly would be a fourth derivation and a wrong one: measured
|
|
975
|
+
* 2026-08-12, a column carrying `check('adult', age >= 18)` still reports
|
|
976
|
+
* `min: '-2147483648'`, the plain int32 range, because the analyzer leaves checks on the table and
|
|
977
|
+
* the generators fold them at emit time. An input rendered from that would carry
|
|
978
|
+
* `min="-2147483648"`, which looks like a bound and is not one.
|
|
979
|
+
*
|
|
980
|
+
* So the fold lives here, beside `classifyTableChecks` and `tableConstraints`, which are already
|
|
981
|
+
* the shared home for the same question. `fieldFacts` is what a form generator reads, and what the
|
|
982
|
+
* emitted schema's own bounds agree with by construction rather than by coincidence.
|
|
983
|
+
*/
|
|
984
|
+
interface FieldFacts {
|
|
985
|
+
/** The column, under the TypeScript name a generated schema spells. */
|
|
986
|
+
name: string;
|
|
987
|
+
/** The input type a control should use, derived from the column's TypeScript type. */
|
|
988
|
+
control: 'text' | 'number' | 'checkbox' | 'datetime-local' | 'select';
|
|
989
|
+
/**
|
|
990
|
+
* Whether a value has to be supplied on insert.
|
|
991
|
+
*
|
|
992
|
+
* False for a nullable column and for one carrying a default, since the database fills it. This
|
|
993
|
+
* is the insert-side answer; a select-side reader wants `nullable` instead.
|
|
994
|
+
*/
|
|
995
|
+
required: boolean;
|
|
996
|
+
nullable: boolean;
|
|
997
|
+
/** `maxlength`, from a declared width. Absent on an unbounded text column. */
|
|
998
|
+
maxLength?: number;
|
|
999
|
+
/**
|
|
1000
|
+
* `min` and `max`, as text.
|
|
1001
|
+
*
|
|
1002
|
+
* Text because a 64 bit bound is not representable as a JS number, which is the same reason the
|
|
1003
|
+
* analyzer reports them that way. Narrowed by any `CHECK` on the column: a check replaces the end
|
|
1004
|
+
* it constrains rather than sitting beside it, and it can never widen, because the declared range
|
|
1005
|
+
* is the column's type and no check makes an int32 hold more.
|
|
1006
|
+
*/
|
|
1007
|
+
min?: string;
|
|
1008
|
+
max?: string;
|
|
1009
|
+
/** Whether either bound is exclusive, which HTML cannot express and a validator can. */
|
|
1010
|
+
exclusiveMin?: boolean;
|
|
1011
|
+
exclusiveMax?: boolean;
|
|
1012
|
+
/** `step="1"` for an integer column. */
|
|
1013
|
+
integer?: boolean;
|
|
1014
|
+
/** The options a `<select>` should carry, from a native enum or a declared set. */
|
|
1015
|
+
options?: string[];
|
|
1016
|
+
/** A `pattern` where the column's format implies one and HTML can state it. */
|
|
1017
|
+
pattern?: string;
|
|
1018
|
+
/** The default the database applies, for a form's initial value. */
|
|
1019
|
+
defaultValue?: unknown;
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Every column of a table, as the facts a form control needs.
|
|
1023
|
+
*
|
|
1024
|
+
* Generated columns are left out: the database computes them and no form supplies one.
|
|
1025
|
+
*/
|
|
1026
|
+
declare function fieldFacts(table: Table$1): FieldFacts[];
|
|
1027
|
+
|
|
969
1028
|
/**
|
|
970
1029
|
* The facts a generated schema can carry beside itself.
|
|
971
1030
|
*
|
|
@@ -1037,6 +1096,15 @@ interface ColumnMetaFacts {
|
|
|
1037
1096
|
}
|
|
1038
1097
|
/** What a table's schema adds beside itself. */
|
|
1039
1098
|
interface TableMetaFacts {
|
|
1099
|
+
/**
|
|
1100
|
+
* The registry id, present only when asked for.
|
|
1101
|
+
*
|
|
1102
|
+
* zod reads this key out of the same object and registers the schema under it, which is what
|
|
1103
|
+
* makes `z.toJSONSchema` emit a `$ref` instead of inlining a copy, and what gives
|
|
1104
|
+
* `z.toJSONSchema(z.globalRegistry)` a name to file each schema under. See `metaSchemaId` for why
|
|
1105
|
+
* it is built from the qualified table name.
|
|
1106
|
+
*/
|
|
1107
|
+
id?: string;
|
|
1040
1108
|
/** The SQL table name, which is not the Drizzle export name the schema is named after. */
|
|
1041
1109
|
table: string;
|
|
1042
1110
|
/**
|
|
@@ -1085,7 +1153,33 @@ interface MetaFactOptions {
|
|
|
1085
1153
|
interface TableMetaOptions extends MetaFactOptions {
|
|
1086
1154
|
mode: string;
|
|
1087
1155
|
dialect?: string;
|
|
1156
|
+
/**
|
|
1157
|
+
* Also give the schema an `id`, which is what puts it in zod's registry under a name.
|
|
1158
|
+
*
|
|
1159
|
+
* Off by default, because it is the one metadata key with a failure mode. Measured against zod
|
|
1160
|
+
* 4.4.3: an `id` makes `z.toJSONSchema` emit `$ref: '#/$defs/<id>'` wherever one schema references
|
|
1161
|
+
* another, and `z.toJSONSchema(z.globalRegistry)` return a `{ schemas: { <id>: ... } }` bundle,
|
|
1162
|
+
* which is what makes a generated document self-describing. But two schemas sharing an id do not
|
|
1163
|
+
* report the collision: measured, `z.toJSONSchema(registry)` keeps the last one and **silently
|
|
1164
|
+
* drops the other**. Two tables, one entry, no warning, and nothing a consumer can check.
|
|
1165
|
+
*
|
|
1166
|
+
* So the id has to be unique across the whole analysis rather than within a file, which is why
|
|
1167
|
+
* `metaSchemaId` builds it from the qualified table name.
|
|
1168
|
+
*/
|
|
1169
|
+
id?: string;
|
|
1088
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* The registry id for one table's schema, unique across an analysis.
|
|
1173
|
+
*
|
|
1174
|
+
* Built from the qualified name, because two tables in two SQL schemas share a bare one and zod
|
|
1175
|
+
* does not report the collision: a registry dump silently keeps one of them. `reporting.users` becomes
|
|
1176
|
+
* `reporting_usersSelect`; a table in the default schema keeps the short `usersSelect`, so the
|
|
1177
|
+
* common case reads the way anybody would have written it by hand.
|
|
1178
|
+
*
|
|
1179
|
+
* Anything outside `[A-Za-z0-9_]` becomes `_`, because the id lands in a `$ref` as a JSON Pointer
|
|
1180
|
+
* segment and a `/` or a `~` there means something else entirely.
|
|
1181
|
+
*/
|
|
1182
|
+
declare function metaSchemaId(table: Table$1, mode: string): string;
|
|
1089
1183
|
/**
|
|
1090
1184
|
* The metadata for one column.
|
|
1091
1185
|
*
|
|
@@ -1736,4 +1830,4 @@ declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
|
1736
1830
|
*/
|
|
1737
1831
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1738
1832
|
|
|
1739
|
-
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, JSON_VALUE_TS_CONST, JSON_VALUE_TS_SOURCE, 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 };
|
|
1833
|
+
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 FieldFacts, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, JSON_VALUE_TS_CONST, JSON_VALUE_TS_SOURCE, 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, fieldFacts, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureCompare, measureExpression, metaSchemaId, 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
|
@@ -966,6 +966,65 @@ declare const CONSTRAINTS_MODULE = "constraints.ts";
|
|
|
966
966
|
*/
|
|
967
967
|
declare function renderConstraintsModule(tables: Table$1[], opts?: ConstraintsModuleOptions): string;
|
|
968
968
|
|
|
969
|
+
/**
|
|
970
|
+
* What one column is, in the terms a form control needs.
|
|
971
|
+
*
|
|
972
|
+
* Every validation generator already folds a table's `CHECK` constraints into the bounds it emits,
|
|
973
|
+
* and every one of them does it with a private copy of the same loop. A form generator reading
|
|
974
|
+
* `Column.min` and `Column.max` directly would be a fourth derivation and a wrong one: measured
|
|
975
|
+
* 2026-08-12, a column carrying `check('adult', age >= 18)` still reports
|
|
976
|
+
* `min: '-2147483648'`, the plain int32 range, because the analyzer leaves checks on the table and
|
|
977
|
+
* the generators fold them at emit time. An input rendered from that would carry
|
|
978
|
+
* `min="-2147483648"`, which looks like a bound and is not one.
|
|
979
|
+
*
|
|
980
|
+
* So the fold lives here, beside `classifyTableChecks` and `tableConstraints`, which are already
|
|
981
|
+
* the shared home for the same question. `fieldFacts` is what a form generator reads, and what the
|
|
982
|
+
* emitted schema's own bounds agree with by construction rather than by coincidence.
|
|
983
|
+
*/
|
|
984
|
+
interface FieldFacts {
|
|
985
|
+
/** The column, under the TypeScript name a generated schema spells. */
|
|
986
|
+
name: string;
|
|
987
|
+
/** The input type a control should use, derived from the column's TypeScript type. */
|
|
988
|
+
control: 'text' | 'number' | 'checkbox' | 'datetime-local' | 'select';
|
|
989
|
+
/**
|
|
990
|
+
* Whether a value has to be supplied on insert.
|
|
991
|
+
*
|
|
992
|
+
* False for a nullable column and for one carrying a default, since the database fills it. This
|
|
993
|
+
* is the insert-side answer; a select-side reader wants `nullable` instead.
|
|
994
|
+
*/
|
|
995
|
+
required: boolean;
|
|
996
|
+
nullable: boolean;
|
|
997
|
+
/** `maxlength`, from a declared width. Absent on an unbounded text column. */
|
|
998
|
+
maxLength?: number;
|
|
999
|
+
/**
|
|
1000
|
+
* `min` and `max`, as text.
|
|
1001
|
+
*
|
|
1002
|
+
* Text because a 64 bit bound is not representable as a JS number, which is the same reason the
|
|
1003
|
+
* analyzer reports them that way. Narrowed by any `CHECK` on the column: a check replaces the end
|
|
1004
|
+
* it constrains rather than sitting beside it, and it can never widen, because the declared range
|
|
1005
|
+
* is the column's type and no check makes an int32 hold more.
|
|
1006
|
+
*/
|
|
1007
|
+
min?: string;
|
|
1008
|
+
max?: string;
|
|
1009
|
+
/** Whether either bound is exclusive, which HTML cannot express and a validator can. */
|
|
1010
|
+
exclusiveMin?: boolean;
|
|
1011
|
+
exclusiveMax?: boolean;
|
|
1012
|
+
/** `step="1"` for an integer column. */
|
|
1013
|
+
integer?: boolean;
|
|
1014
|
+
/** The options a `<select>` should carry, from a native enum or a declared set. */
|
|
1015
|
+
options?: string[];
|
|
1016
|
+
/** A `pattern` where the column's format implies one and HTML can state it. */
|
|
1017
|
+
pattern?: string;
|
|
1018
|
+
/** The default the database applies, for a form's initial value. */
|
|
1019
|
+
defaultValue?: unknown;
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Every column of a table, as the facts a form control needs.
|
|
1023
|
+
*
|
|
1024
|
+
* Generated columns are left out: the database computes them and no form supplies one.
|
|
1025
|
+
*/
|
|
1026
|
+
declare function fieldFacts(table: Table$1): FieldFacts[];
|
|
1027
|
+
|
|
969
1028
|
/**
|
|
970
1029
|
* The facts a generated schema can carry beside itself.
|
|
971
1030
|
*
|
|
@@ -1037,6 +1096,15 @@ interface ColumnMetaFacts {
|
|
|
1037
1096
|
}
|
|
1038
1097
|
/** What a table's schema adds beside itself. */
|
|
1039
1098
|
interface TableMetaFacts {
|
|
1099
|
+
/**
|
|
1100
|
+
* The registry id, present only when asked for.
|
|
1101
|
+
*
|
|
1102
|
+
* zod reads this key out of the same object and registers the schema under it, which is what
|
|
1103
|
+
* makes `z.toJSONSchema` emit a `$ref` instead of inlining a copy, and what gives
|
|
1104
|
+
* `z.toJSONSchema(z.globalRegistry)` a name to file each schema under. See `metaSchemaId` for why
|
|
1105
|
+
* it is built from the qualified table name.
|
|
1106
|
+
*/
|
|
1107
|
+
id?: string;
|
|
1040
1108
|
/** The SQL table name, which is not the Drizzle export name the schema is named after. */
|
|
1041
1109
|
table: string;
|
|
1042
1110
|
/**
|
|
@@ -1085,7 +1153,33 @@ interface MetaFactOptions {
|
|
|
1085
1153
|
interface TableMetaOptions extends MetaFactOptions {
|
|
1086
1154
|
mode: string;
|
|
1087
1155
|
dialect?: string;
|
|
1156
|
+
/**
|
|
1157
|
+
* Also give the schema an `id`, which is what puts it in zod's registry under a name.
|
|
1158
|
+
*
|
|
1159
|
+
* Off by default, because it is the one metadata key with a failure mode. Measured against zod
|
|
1160
|
+
* 4.4.3: an `id` makes `z.toJSONSchema` emit `$ref: '#/$defs/<id>'` wherever one schema references
|
|
1161
|
+
* another, and `z.toJSONSchema(z.globalRegistry)` return a `{ schemas: { <id>: ... } }` bundle,
|
|
1162
|
+
* which is what makes a generated document self-describing. But two schemas sharing an id do not
|
|
1163
|
+
* report the collision: measured, `z.toJSONSchema(registry)` keeps the last one and **silently
|
|
1164
|
+
* drops the other**. Two tables, one entry, no warning, and nothing a consumer can check.
|
|
1165
|
+
*
|
|
1166
|
+
* So the id has to be unique across the whole analysis rather than within a file, which is why
|
|
1167
|
+
* `metaSchemaId` builds it from the qualified table name.
|
|
1168
|
+
*/
|
|
1169
|
+
id?: string;
|
|
1088
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* The registry id for one table's schema, unique across an analysis.
|
|
1173
|
+
*
|
|
1174
|
+
* Built from the qualified name, because two tables in two SQL schemas share a bare one and zod
|
|
1175
|
+
* does not report the collision: a registry dump silently keeps one of them. `reporting.users` becomes
|
|
1176
|
+
* `reporting_usersSelect`; a table in the default schema keeps the short `usersSelect`, so the
|
|
1177
|
+
* common case reads the way anybody would have written it by hand.
|
|
1178
|
+
*
|
|
1179
|
+
* Anything outside `[A-Za-z0-9_]` becomes `_`, because the id lands in a `$ref` as a JSON Pointer
|
|
1180
|
+
* segment and a `/` or a `~` there means something else entirely.
|
|
1181
|
+
*/
|
|
1182
|
+
declare function metaSchemaId(table: Table$1, mode: string): string;
|
|
1089
1183
|
/**
|
|
1090
1184
|
* The metadata for one column.
|
|
1091
1185
|
*
|
|
@@ -1736,4 +1830,4 @@ declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
|
1736
1830
|
*/
|
|
1737
1831
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1738
1832
|
|
|
1739
|
-
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, JSON_VALUE_TS_CONST, JSON_VALUE_TS_SOURCE, 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 };
|
|
1833
|
+
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 FieldFacts, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, JSON_VALUE_TS_CONST, JSON_VALUE_TS_SOURCE, 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, fieldFacts, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureCompare, measureExpression, metaSchemaId, 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
|
@@ -1355,6 +1355,60 @@ function fileWriter(sink) {
|
|
|
1355
1355
|
};
|
|
1356
1356
|
}
|
|
1357
1357
|
|
|
1358
|
+
// src/fields.ts
|
|
1359
|
+
var UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
|
|
1360
|
+
function controlFor(c) {
|
|
1361
|
+
if (c.enumValues?.length) return "select";
|
|
1362
|
+
if (c.tsType === "boolean") return "checkbox";
|
|
1363
|
+
if (c.tsType === "number" || c.tsType === "bigint") return "number";
|
|
1364
|
+
if (c.tsType === "Date") return "datetime-local";
|
|
1365
|
+
return "text";
|
|
1366
|
+
}
|
|
1367
|
+
function boundsFor(c, checks) {
|
|
1368
|
+
let lo = c.min !== void 0 ? { value: c.min, exclusive: false } : void 0;
|
|
1369
|
+
let hi = c.max !== void 0 ? { value: c.max, exclusive: false } : void 0;
|
|
1370
|
+
for (const k of checks) {
|
|
1371
|
+
if (k.column !== c.name || k.kind !== "number") continue;
|
|
1372
|
+
if (k.operator === ">=") lo = { value: k.value, exclusive: false };
|
|
1373
|
+
else if (k.operator === ">") lo = { value: k.value, exclusive: true };
|
|
1374
|
+
else if (k.operator === "<=") hi = { value: k.value, exclusive: false };
|
|
1375
|
+
else if (k.operator === "<") hi = { value: k.value, exclusive: true };
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
...lo ? { min: lo.value, ...lo.exclusive ? { exclusiveMin: true } : {} } : {},
|
|
1379
|
+
...hi ? { max: hi.value, ...hi.exclusive ? { exclusiveMax: true } : {} } : {}
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function fieldFacts(table) {
|
|
1383
|
+
const parsed = (table.checks ?? []).map(
|
|
1384
|
+
(k) => parseCheck(k.expression, k.name, table.dialect)
|
|
1385
|
+
);
|
|
1386
|
+
const ok = parsed.filter((p) => !!p && p.ok);
|
|
1387
|
+
const columnChecks = ok.flatMap((p) => p.checks ?? []);
|
|
1388
|
+
const lengthChecks = ok.flatMap((p) => p.lengths ?? []);
|
|
1389
|
+
return table.columns.filter((c) => !c.isGenerated).map((c) => ({
|
|
1390
|
+
name: c.name,
|
|
1391
|
+
control: controlFor(c),
|
|
1392
|
+
// A column the database fills is not one a form has to supply, which is the insert-side
|
|
1393
|
+
// question a form asks. `nullable` is reported beside it for a reader asking the other one.
|
|
1394
|
+
required: !c.nullable && !c.hasDefault,
|
|
1395
|
+
nullable: c.nullable,
|
|
1396
|
+
...(() => {
|
|
1397
|
+
const declared = c.maxLength;
|
|
1398
|
+
const fromCheck = lengthChecks.filter(
|
|
1399
|
+
(l) => l.column === c.name && (l.unit ?? "characters") === "characters" && (l.operator === "<=" || l.operator === "<")
|
|
1400
|
+
).map((l) => l.operator === "<" ? Number(l.value) - 1 : Number(l.value)).filter((n) => Number.isFinite(n));
|
|
1401
|
+
const all = [...declared !== void 0 ? [declared] : [], ...fromCheck];
|
|
1402
|
+
return all.length ? { maxLength: Math.min(...all) } : {};
|
|
1403
|
+
})(),
|
|
1404
|
+
...boundsFor(c, columnChecks),
|
|
1405
|
+
...c.integer ? { integer: true } : {},
|
|
1406
|
+
...c.enumValues?.length ? { options: [...c.enumValues] } : {},
|
|
1407
|
+
...c.format === "uuid" ? { pattern: UUID_PATTERN } : {},
|
|
1408
|
+
...c.defaultValue !== void 0 ? { defaultValue: c.defaultValue } : {}
|
|
1409
|
+
}));
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1358
1412
|
// src/files.ts
|
|
1359
1413
|
import nodeFs2 from "fs";
|
|
1360
1414
|
import nodePath from "path";
|
|
@@ -1415,6 +1469,11 @@ function withTsExtension(p) {
|
|
|
1415
1469
|
}
|
|
1416
1470
|
|
|
1417
1471
|
// src/meta.ts
|
|
1472
|
+
function metaSchemaId(table, mode) {
|
|
1473
|
+
const qualified = table.schema ? `${table.schema}_${table.name}` : table.name;
|
|
1474
|
+
const safe = qualified.replace(/[^A-Za-z0-9_]/g, "_");
|
|
1475
|
+
return `${safe}${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
|
|
1476
|
+
}
|
|
1418
1477
|
function classifyChecks(table) {
|
|
1419
1478
|
const perColumn = /* @__PURE__ */ new Map();
|
|
1420
1479
|
const rows = [];
|
|
@@ -1473,6 +1532,7 @@ function tableMetaFacts(table, opts) {
|
|
|
1473
1532
|
const pk = table.primaryKey?.columns ?? [];
|
|
1474
1533
|
const unique = (table.unique ?? []).map((k) => k.columns).filter((c) => c.length > 0);
|
|
1475
1534
|
const facts = {
|
|
1535
|
+
...opts.id ? { id: opts.id } : {},
|
|
1476
1536
|
table: table.name,
|
|
1477
1537
|
...table.schema ? { schema: table.schema } : {},
|
|
1478
1538
|
...opts.dialect ? { dialect: opts.dialect } : {},
|
|
@@ -1929,6 +1989,7 @@ export {
|
|
|
1929
1989
|
columnMetaFacts,
|
|
1930
1990
|
comparisonWire,
|
|
1931
1991
|
describeSet,
|
|
1992
|
+
fieldFacts,
|
|
1932
1993
|
fileWriter,
|
|
1933
1994
|
formatCode,
|
|
1934
1995
|
importSpecifier,
|
|
@@ -1940,6 +2001,7 @@ export {
|
|
|
1940
2001
|
lengthMeasure,
|
|
1941
2002
|
measureCompare,
|
|
1942
2003
|
measureExpression,
|
|
2004
|
+
metaSchemaId,
|
|
1943
2005
|
moduleFileName,
|
|
1944
2006
|
moduleSpecifier,
|
|
1945
2007
|
needsNumericCanon,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/validation-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.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.22.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"prettier": ">=3"
|