@drzl/validation-core 3.10.0 → 3.12.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 CHANGED
@@ -84441,6 +84441,7 @@ ${codeblock}`, options7);
84441
84441
  var index_exports2 = {};
84442
84442
  __export(index_exports2, {
84443
84443
  AFFIX_PROBE_TABLE: () => AFFIX_PROBE_TABLE,
84444
+ CODEPOINT_LENGTH: () => CODEPOINT_LENGTH,
84444
84445
  COLUMN_FORMATS: () => COLUMN_FORMATS,
84445
84446
  DEFAULT_IMPORT_EXTENSION: () => DEFAULT_IMPORT_EXTENSION,
84446
84447
  DEFAULT_MODE_PREFIX: () => DEFAULT_MODE_PREFIX,
@@ -84472,6 +84473,7 @@ module.exports = __toCommonJS(index_exports2);
84472
84473
  // src/checks.ts
84473
84474
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
84474
84475
  var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
84476
+ var LENGTH_OF = /^\s*(?:length|char_length)\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s*(>=|<=|<>|!=|>|<|=)\s*(\d+)\s*$/i;
84475
84477
  function splitTopLevelAnd(expr) {
84476
84478
  const parts = [];
84477
84479
  let depth = 0;
@@ -84592,6 +84594,7 @@ function parseCheck(expression, name) {
84592
84594
  const checks = [];
84593
84595
  const sets = [];
84594
84596
  const rows = [];
84597
+ const lengths = [];
84595
84598
  for (const part of parts) {
84596
84599
  const parsed = parseCheck(part, name);
84597
84600
  if (!parsed.ok)
@@ -84599,12 +84602,23 @@ function parseCheck(expression, name) {
84599
84602
  checks.push(...parsed.checks);
84600
84603
  if (parsed.sets) sets.push(...parsed.sets);
84601
84604
  if (parsed.rows) rows.push(...parsed.rows);
84605
+ if (parsed.lengths) lengths.push(...parsed.lengths);
84602
84606
  }
84603
84607
  return {
84604
84608
  ok: true,
84605
84609
  checks,
84606
84610
  ...sets.length ? { sets } : {},
84607
- ...rows.length ? { rows } : {}
84611
+ ...rows.length ? { rows } : {},
84612
+ ...lengths.length ? { lengths } : {}
84613
+ };
84614
+ }
84615
+ const lengthOf = expr.match(LENGTH_OF);
84616
+ if (lengthOf) {
84617
+ const op4 = lengthOf[2] === "!=" ? "<>" : lengthOf[2];
84618
+ return {
84619
+ ok: true,
84620
+ checks: [],
84621
+ lengths: [{ column: lengthOf[1], operator: op4, value: lengthOf[3], name }]
84608
84622
  };
84609
84623
  }
84610
84624
  const inList = expr.match(IN_LIST);
@@ -84825,6 +84839,7 @@ var COLUMN_FORMATS = {
84825
84839
  // probes, `1_000` and `0xDEAD_beef` through to `1__0`, `_1`, `0x` and `1e+`.
84826
84840
  numeric: "^\\s*([+-]?(0[xX][0-9a-fA-F](_?[0-9a-fA-F])*|0[oO][0-7](_?[0-7])*|0[bB][01](_?[01])*)|[+-]?(\\d(_?\\d)*(\\.(\\d(_?\\d)*)?)?|\\.\\d(_?\\d)*)([eE][+-]?\\d(_?\\d)*)?|[+-]?(NaN|Infinity))\\s*$"
84827
84841
  };
84842
+ var CODEPOINT_LENGTH = "[...v].length";
84828
84843
  function isIntegerColumn(c5) {
84829
84844
  if (typeof c5.integer === "boolean") return c5.integer;
84830
84845
  return c5.dbType === "INTEGER" || c5.min !== void 0 && c5.max !== void 0;
@@ -84867,6 +84882,7 @@ async function formatCode(code, filePath, fmt) {
84867
84882
  // Annotate the CommonJS export names for ESM import in node:
84868
84883
  0 && (module.exports = {
84869
84884
  AFFIX_PROBE_TABLE,
84885
+ CODEPOINT_LENGTH,
84870
84886
  COLUMN_FORMATS,
84871
84887
  DEFAULT_IMPORT_EXTENSION,
84872
84888
  DEFAULT_MODE_PREFIX,
package/dist/index.d.cts CHANGED
@@ -230,12 +230,27 @@ interface RowCheck {
230
230
  operator: ColumnCheck['operator'];
231
231
  name?: string;
232
232
  }
233
+ /**
234
+ * A constraint on a column's *character* count, from `CHECK (length(name) > 3)`.
235
+ *
236
+ * Kept apart from `ColumnCheck` because it is not a comparison of the value: it compares a count
237
+ * derived from it, and the count Postgres takes is code points rather than UTF-16 units. See
238
+ * `CODEPOINT_LENGTH` for why that distinction is load bearing.
239
+ */
240
+ interface LengthCheck {
241
+ column: string;
242
+ operator: ColumnCheck['operator'];
243
+ /** Decimal, as text, matching how the other bounds are carried. */
244
+ value: string;
245
+ name?: string;
246
+ }
233
247
  /** A check that was understood, or the reason it was not. */
234
248
  type ParsedCheck = {
235
249
  ok: true;
236
250
  checks: ColumnCheck[];
237
251
  sets?: ColumnSet[];
238
252
  rows?: RowCheck[];
253
+ lengths?: LengthCheck[];
239
254
  } | {
240
255
  ok: false;
241
256
  reason: string;
@@ -405,10 +420,32 @@ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): bo
405
420
  * cidr parses as `inet` and then demands zero host bits, which no regex can state
406
421
  */
407
422
  declare const COLUMN_FORMATS: Record<string, string>;
423
+ /**
424
+ * Why a character limit is not `.max(n)`.
425
+ *
426
+ * Postgres and MySQL count `varchar(n)` in **characters**; every JavaScript validator counts
427
+ * `.length`, which is UTF-16 code units. They agree until the text leaves the basic plane, and
428
+ * then they do not: `varchar(10)` accepts ten emoji, and `.max(10)` refuses eight of them.
429
+ *
430
+ * Verified against Postgres through PGlite, for `varchar(10)`:
431
+ *
432
+ * 3 emoji db accepts .max(10) accepts
433
+ * 8 emoji db accepts .max(10) REFUSES
434
+ * 10 emoji db accepts .max(10) REFUSES
435
+ * 11 emoji db refuses .max(10) refuses
436
+ *
437
+ * `[...v].length` counts code points, which is what the database counts, and matches on all four.
438
+ * Refusing a user's emoji is the failure mode this avoids, and it is the same rule applied
439
+ * everywhere else here: never reject what the database accepts.
440
+ *
441
+ * `@sinclair/typebox` and ArkType cannot express it. Both state a length declaratively with no
442
+ * predicate to hook, so they keep the UTF-16 form and are documented as approximate.
443
+ */
444
+ declare const CODEPOINT_LENGTH = "[...v].length";
408
445
  declare function isIntegerColumn(c: Column): boolean;
409
446
  declare function insertColumns(table: Table): Column[];
410
447
  declare function updateColumns(table: Table): Column[];
411
448
  declare function selectColumns(table: Table): Column[];
412
449
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
413
450
 
414
- export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type RowCheck, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
451
+ export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, CODEPOINT_LENGTH, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type RowCheck, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
package/dist/index.d.ts CHANGED
@@ -230,12 +230,27 @@ interface RowCheck {
230
230
  operator: ColumnCheck['operator'];
231
231
  name?: string;
232
232
  }
233
+ /**
234
+ * A constraint on a column's *character* count, from `CHECK (length(name) > 3)`.
235
+ *
236
+ * Kept apart from `ColumnCheck` because it is not a comparison of the value: it compares a count
237
+ * derived from it, and the count Postgres takes is code points rather than UTF-16 units. See
238
+ * `CODEPOINT_LENGTH` for why that distinction is load bearing.
239
+ */
240
+ interface LengthCheck {
241
+ column: string;
242
+ operator: ColumnCheck['operator'];
243
+ /** Decimal, as text, matching how the other bounds are carried. */
244
+ value: string;
245
+ name?: string;
246
+ }
233
247
  /** A check that was understood, or the reason it was not. */
234
248
  type ParsedCheck = {
235
249
  ok: true;
236
250
  checks: ColumnCheck[];
237
251
  sets?: ColumnSet[];
238
252
  rows?: RowCheck[];
253
+ lengths?: LengthCheck[];
239
254
  } | {
240
255
  ok: false;
241
256
  reason: string;
@@ -405,10 +420,32 @@ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): bo
405
420
  * cidr parses as `inet` and then demands zero host bits, which no regex can state
406
421
  */
407
422
  declare const COLUMN_FORMATS: Record<string, string>;
423
+ /**
424
+ * Why a character limit is not `.max(n)`.
425
+ *
426
+ * Postgres and MySQL count `varchar(n)` in **characters**; every JavaScript validator counts
427
+ * `.length`, which is UTF-16 code units. They agree until the text leaves the basic plane, and
428
+ * then they do not: `varchar(10)` accepts ten emoji, and `.max(10)` refuses eight of them.
429
+ *
430
+ * Verified against Postgres through PGlite, for `varchar(10)`:
431
+ *
432
+ * 3 emoji db accepts .max(10) accepts
433
+ * 8 emoji db accepts .max(10) REFUSES
434
+ * 10 emoji db accepts .max(10) REFUSES
435
+ * 11 emoji db refuses .max(10) refuses
436
+ *
437
+ * `[...v].length` counts code points, which is what the database counts, and matches on all four.
438
+ * Refusing a user's emoji is the failure mode this avoids, and it is the same rule applied
439
+ * everywhere else here: never reject what the database accepts.
440
+ *
441
+ * `@sinclair/typebox` and ArkType cannot express it. Both state a length declaratively with no
442
+ * predicate to hook, so they keep the UTF-16 form and are documented as approximate.
443
+ */
444
+ declare const CODEPOINT_LENGTH = "[...v].length";
408
445
  declare function isIntegerColumn(c: Column): boolean;
409
446
  declare function insertColumns(table: Table): Column[];
410
447
  declare function updateColumns(table: Table): Column[];
411
448
  declare function selectColumns(table: Table): Column[];
412
449
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
413
450
 
414
- export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type RowCheck, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
451
+ export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, CODEPOINT_LENGTH, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type RowCheck, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import "./chunk-ZZGILH5A.js";
3
3
  // src/checks.ts
4
4
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
5
5
  var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
6
+ var LENGTH_OF = /^\s*(?:length|char_length)\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s*(>=|<=|<>|!=|>|<|=)\s*(\d+)\s*$/i;
6
7
  function splitTopLevelAnd(expr) {
7
8
  const parts = [];
8
9
  let depth = 0;
@@ -123,6 +124,7 @@ function parseCheck(expression, name) {
123
124
  const checks = [];
124
125
  const sets = [];
125
126
  const rows = [];
127
+ const lengths = [];
126
128
  for (const part of parts) {
127
129
  const parsed = parseCheck(part, name);
128
130
  if (!parsed.ok)
@@ -130,12 +132,23 @@ function parseCheck(expression, name) {
130
132
  checks.push(...parsed.checks);
131
133
  if (parsed.sets) sets.push(...parsed.sets);
132
134
  if (parsed.rows) rows.push(...parsed.rows);
135
+ if (parsed.lengths) lengths.push(...parsed.lengths);
133
136
  }
134
137
  return {
135
138
  ok: true,
136
139
  checks,
137
140
  ...sets.length ? { sets } : {},
138
- ...rows.length ? { rows } : {}
141
+ ...rows.length ? { rows } : {},
142
+ ...lengths.length ? { lengths } : {}
143
+ };
144
+ }
145
+ const lengthOf = expr.match(LENGTH_OF);
146
+ if (lengthOf) {
147
+ const op2 = lengthOf[2] === "!=" ? "<>" : lengthOf[2];
148
+ return {
149
+ ok: true,
150
+ checks: [],
151
+ lengths: [{ column: lengthOf[1], operator: op2, value: lengthOf[3], name }]
139
152
  };
140
153
  }
141
154
  const inList = expr.match(IN_LIST);
@@ -356,6 +369,7 @@ var COLUMN_FORMATS = {
356
369
  // probes, `1_000` and `0xDEAD_beef` through to `1__0`, `_1`, `0x` and `1e+`.
357
370
  numeric: "^\\s*([+-]?(0[xX][0-9a-fA-F](_?[0-9a-fA-F])*|0[oO][0-7](_?[0-7])*|0[bB][01](_?[01])*)|[+-]?(\\d(_?\\d)*(\\.(\\d(_?\\d)*)?)?|\\.\\d(_?\\d)*)([eE][+-]?\\d(_?\\d)*)?|[+-]?(NaN|Infinity))\\s*$"
358
371
  };
372
+ var CODEPOINT_LENGTH = "[...v].length";
359
373
  function isIntegerColumn(c) {
360
374
  if (typeof c.integer === "boolean") return c.integer;
361
375
  return c.dbType === "INTEGER" || c.min !== void 0 && c.max !== void 0;
@@ -397,6 +411,7 @@ async function formatCode(code, filePath, fmt) {
397
411
  }
398
412
  export {
399
413
  AFFIX_PROBE_TABLE,
414
+ CODEPOINT_LENGTH,
400
415
  COLUMN_FORMATS,
401
416
  DEFAULT_IMPORT_EXTENSION,
402
417
  DEFAULT_MODE_PREFIX,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/validation-core",
3
- "version": "3.10.0",
3
+ "version": "3.12.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",