@drzl/validation-core 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -84449,6 +84449,7 @@ __export(index_exports2, {
84449
84449
  IMPORT_EXTENSIONS: () => IMPORT_EXTENSIONS,
84450
84450
  NAME_MODES: () => NAME_MODES,
84451
84451
  applyTableCase: () => applyTableCase,
84452
+ describeSet: () => describeSet,
84452
84453
  formatCode: () => formatCode,
84453
84454
  importSpecifier: () => importSpecifier,
84454
84455
  insertColumns: () => insertColumns,
@@ -84470,6 +84471,94 @@ module.exports = __toCommonJS(index_exports2);
84470
84471
 
84471
84472
  // src/checks.ts
84472
84473
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
84474
+ var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
84475
+ function splitTopLevelAnd(expr) {
84476
+ const parts = [];
84477
+ let depth = 0;
84478
+ let inString = false;
84479
+ let start = 0;
84480
+ for (let i = 0; i < expr.length; i++) {
84481
+ const c5 = expr[i];
84482
+ if (inString) {
84483
+ if (c5 === "'") {
84484
+ if (expr[i + 1] === "'") i++;
84485
+ else inString = false;
84486
+ }
84487
+ continue;
84488
+ }
84489
+ if (c5 === "'") {
84490
+ inString = true;
84491
+ continue;
84492
+ }
84493
+ if (c5 === "(") depth++;
84494
+ else if (c5 === ")") depth--;
84495
+ else if (depth === 0 && /\s/.test(c5)) {
84496
+ const m7 = /^\s+AND\s+/i.exec(expr.slice(i));
84497
+ if (m7) {
84498
+ parts.push(expr.slice(start, i));
84499
+ i += m7[0].length - 1;
84500
+ start = i + 1;
84501
+ }
84502
+ }
84503
+ }
84504
+ parts.push(expr.slice(start));
84505
+ return parts;
84506
+ }
84507
+ function unwrap(expr) {
84508
+ let e7 = expr.trim();
84509
+ while (e7.startsWith("(") && e7.endsWith(")")) {
84510
+ let depth = 0;
84511
+ let inString = false;
84512
+ let wrapsWhole = true;
84513
+ for (let i = 0; i < e7.length; i++) {
84514
+ const c5 = e7[i];
84515
+ if (inString) {
84516
+ if (c5 === "'") {
84517
+ if (e7[i + 1] === "'") i++;
84518
+ else inString = false;
84519
+ }
84520
+ continue;
84521
+ }
84522
+ if (c5 === "'") inString = true;
84523
+ else if (c5 === "(") depth++;
84524
+ else if (c5 === ")") {
84525
+ depth--;
84526
+ if (depth === 0 && i < e7.length - 1) {
84527
+ wrapsWhole = false;
84528
+ break;
84529
+ }
84530
+ }
84531
+ }
84532
+ if (!wrapsWhole) break;
84533
+ e7 = e7.slice(1, -1).trim();
84534
+ }
84535
+ return e7;
84536
+ }
84537
+ function splitTopLevelCommas(list) {
84538
+ const parts = [];
84539
+ let depth = 0;
84540
+ let inString = false;
84541
+ let start = 0;
84542
+ for (let i = 0; i < list.length; i++) {
84543
+ const c5 = list[i];
84544
+ if (inString) {
84545
+ if (c5 === "'") {
84546
+ if (list[i + 1] === "'") i++;
84547
+ else inString = false;
84548
+ }
84549
+ continue;
84550
+ }
84551
+ if (c5 === "'") inString = true;
84552
+ else if (c5 === "(") depth++;
84553
+ else if (c5 === ")") depth--;
84554
+ else if (c5 === "," && depth === 0) {
84555
+ parts.push(list.slice(start, i));
84556
+ start = i + 1;
84557
+ }
84558
+ }
84559
+ parts.push(list.slice(start));
84560
+ return parts;
84561
+ }
84473
84562
  var BETWEEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+BETWEEN\s+(.+?)\s+AND\s+(.+?)\s*$/i;
84474
84563
  function literal(raw) {
84475
84564
  const t36 = raw.trim();
@@ -84479,9 +84568,11 @@ function literal(raw) {
84479
84568
  return void 0;
84480
84569
  }
84481
84570
  function parseCheck(expression, name) {
84482
- const expr = (expression ?? "").trim();
84571
+ const expr = unwrap((expression ?? "").trim());
84483
84572
  if (!expr) return { ok: false, reason: "empty expression" };
84484
84573
  if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
84574
+ if (/(^|[\s)])OR($|[\s(])/i.test(expr)) return { ok: false, reason: "contains OR" };
84575
+ if (/(^|[\s(])NOT($|[\s(])/i.test(expr)) return { ok: false, reason: "contains NOT" };
84485
84576
  const between = expr.match(BETWEEN);
84486
84577
  if (between) {
84487
84578
  const lo6 = literal(between[2]);
@@ -84496,6 +84587,40 @@ function parseCheck(expression, name) {
84496
84587
  ]
84497
84588
  };
84498
84589
  }
84590
+ const parts = splitTopLevelAnd(expr);
84591
+ if (parts.length > 1) {
84592
+ const checks = [];
84593
+ const sets = [];
84594
+ for (const part of parts) {
84595
+ const parsed = parseCheck(part, name);
84596
+ if (!parsed.ok)
84597
+ return { ok: false, reason: `part of an AND was not understood: ${parsed.reason}` };
84598
+ checks.push(...parsed.checks);
84599
+ if (parsed.sets) sets.push(...parsed.sets);
84600
+ }
84601
+ return sets.length ? { ok: true, checks, sets } : { ok: true, checks };
84602
+ }
84603
+ const inList = expr.match(IN_LIST);
84604
+ if (inList) {
84605
+ const raw = splitTopLevelCommas(inList[2]);
84606
+ const parsedValues = raw.map((r5) => literal(r5));
84607
+ if (parsedValues.some((v5) => !v5)) return { ok: false, reason: "IN list holds a non-literal" };
84608
+ const kinds = new Set(parsedValues.map((v5) => v5.kind));
84609
+ if (kinds.size > 1) return { ok: false, reason: "IN list mixes types" };
84610
+ if (!parsedValues.length) return { ok: false, reason: "IN list is empty" };
84611
+ return {
84612
+ ok: true,
84613
+ checks: [],
84614
+ sets: [
84615
+ {
84616
+ column: inList[1],
84617
+ values: parsedValues.map((v5) => v5.value),
84618
+ kind: parsedValues[0].kind,
84619
+ name
84620
+ }
84621
+ ]
84622
+ };
84623
+ }
84499
84624
  const cmp = expr.match(COMPARISON);
84500
84625
  if (!cmp) return { ok: false, reason: "not a single comparison this version understands" };
84501
84626
  const value = literal(cmp[3]);
@@ -84503,7 +84628,14 @@ function parseCheck(expression, name) {
84503
84628
  return { ok: false, reason: "right side is not a literal" };
84504
84629
  }
84505
84630
  const op3 = cmp[2] === "!=" ? "<>" : cmp[2];
84506
- return { ok: true, checks: [{ column: cmp[1], operator: op3, value: value.value, kind: value.kind, name }] };
84631
+ return {
84632
+ ok: true,
84633
+ checks: [{ column: cmp[1], operator: op3, value: value.value, kind: value.kind, name }]
84634
+ };
84635
+ }
84636
+ function describeSet(set) {
84637
+ const shown = set.values.map((v5) => set.kind === "string" ? `'${v5}'` : v5).join(", ");
84638
+ return `${set.name ? `${set.name}: ` : ""}${set.column} IN (${shown})`;
84507
84639
  }
84508
84640
 
84509
84641
  // src/files.ts
@@ -84731,6 +84863,7 @@ async function formatCode(code, filePath, fmt) {
84731
84863
  IMPORT_EXTENSIONS,
84732
84864
  NAME_MODES,
84733
84865
  applyTableCase,
84866
+ describeSet,
84734
84867
  formatCode,
84735
84868
  importSpecifier,
84736
84869
  insertColumns,
package/dist/index.d.cts CHANGED
@@ -206,10 +206,23 @@ interface ColumnCheck {
206
206
  /** Constraint name, used to say which one failed. */
207
207
  name?: string;
208
208
  }
209
+ /**
210
+ * A column constrained to a set of literals, from `col IN ('a', 'b')`.
211
+ *
212
+ * Kept separate from `ColumnCheck` rather than folded into it as another operator, so the
213
+ * existing shape is unchanged for anything already consuming it.
214
+ */
215
+ interface ColumnSet {
216
+ column: string;
217
+ values: string[];
218
+ kind: 'number' | 'string';
219
+ name?: string;
220
+ }
209
221
  /** A check that was understood, or the reason it was not. */
210
222
  type ParsedCheck = {
211
223
  ok: true;
212
224
  checks: ColumnCheck[];
225
+ sets?: ColumnSet[];
213
226
  } | {
214
227
  ok: false;
215
228
  reason: string;
@@ -222,6 +235,13 @@ type ParsedCheck = {
222
235
  * silently change what is enforced.
223
236
  */
224
237
  declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
238
+ /**
239
+ * A human-readable rendering of a set constraint, for an error message.
240
+ *
241
+ * Values are re-quoted the way SQL wrote them, so the message reads like the constraint in the
242
+ * schema rather than like its JavaScript translation.
243
+ */
244
+ declare function describeSet(set: ColumnSet): string;
225
245
 
226
246
  interface Table {
227
247
  name: string;
@@ -363,4 +383,4 @@ declare function updateColumns(table: Table): Column[];
363
383
  declare function selectColumns(table: Table): Column[];
364
384
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
365
385
 
366
- export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
386
+ export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
package/dist/index.d.ts CHANGED
@@ -206,10 +206,23 @@ interface ColumnCheck {
206
206
  /** Constraint name, used to say which one failed. */
207
207
  name?: string;
208
208
  }
209
+ /**
210
+ * A column constrained to a set of literals, from `col IN ('a', 'b')`.
211
+ *
212
+ * Kept separate from `ColumnCheck` rather than folded into it as another operator, so the
213
+ * existing shape is unchanged for anything already consuming it.
214
+ */
215
+ interface ColumnSet {
216
+ column: string;
217
+ values: string[];
218
+ kind: 'number' | 'string';
219
+ name?: string;
220
+ }
209
221
  /** A check that was understood, or the reason it was not. */
210
222
  type ParsedCheck = {
211
223
  ok: true;
212
224
  checks: ColumnCheck[];
225
+ sets?: ColumnSet[];
213
226
  } | {
214
227
  ok: false;
215
228
  reason: string;
@@ -222,6 +235,13 @@ type ParsedCheck = {
222
235
  * silently change what is enforced.
223
236
  */
224
237
  declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
238
+ /**
239
+ * A human-readable rendering of a set constraint, for an error message.
240
+ *
241
+ * Values are re-quoted the way SQL wrote them, so the message reads like the constraint in the
242
+ * schema rather than like its JavaScript translation.
243
+ */
244
+ declare function describeSet(set: ColumnSet): string;
225
245
 
226
246
  interface Table {
227
247
  name: string;
@@ -363,4 +383,4 @@ declare function updateColumns(table: Table): Column[];
363
383
  declare function selectColumns(table: Table): Column[];
364
384
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
365
385
 
366
- export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
386
+ export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, COLUMN_FORMATS, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ParsedCheck, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
package/dist/index.js CHANGED
@@ -2,6 +2,94 @@ import "./chunk-ZZGILH5A.js";
2
2
 
3
3
  // src/checks.ts
4
4
  var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
5
+ var IN_LIST = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.+)\)\s*$/i;
6
+ function splitTopLevelAnd(expr) {
7
+ const parts = [];
8
+ let depth = 0;
9
+ let inString = false;
10
+ let start = 0;
11
+ for (let i = 0; i < expr.length; i++) {
12
+ const c = expr[i];
13
+ if (inString) {
14
+ if (c === "'") {
15
+ if (expr[i + 1] === "'") i++;
16
+ else inString = false;
17
+ }
18
+ continue;
19
+ }
20
+ if (c === "'") {
21
+ inString = true;
22
+ continue;
23
+ }
24
+ if (c === "(") depth++;
25
+ else if (c === ")") depth--;
26
+ else if (depth === 0 && /\s/.test(c)) {
27
+ const m = /^\s+AND\s+/i.exec(expr.slice(i));
28
+ if (m) {
29
+ parts.push(expr.slice(start, i));
30
+ i += m[0].length - 1;
31
+ start = i + 1;
32
+ }
33
+ }
34
+ }
35
+ parts.push(expr.slice(start));
36
+ return parts;
37
+ }
38
+ function unwrap(expr) {
39
+ let e = expr.trim();
40
+ while (e.startsWith("(") && e.endsWith(")")) {
41
+ let depth = 0;
42
+ let inString = false;
43
+ let wrapsWhole = true;
44
+ for (let i = 0; i < e.length; i++) {
45
+ const c = e[i];
46
+ if (inString) {
47
+ if (c === "'") {
48
+ if (e[i + 1] === "'") i++;
49
+ else inString = false;
50
+ }
51
+ continue;
52
+ }
53
+ if (c === "'") inString = true;
54
+ else if (c === "(") depth++;
55
+ else if (c === ")") {
56
+ depth--;
57
+ if (depth === 0 && i < e.length - 1) {
58
+ wrapsWhole = false;
59
+ break;
60
+ }
61
+ }
62
+ }
63
+ if (!wrapsWhole) break;
64
+ e = e.slice(1, -1).trim();
65
+ }
66
+ return e;
67
+ }
68
+ function splitTopLevelCommas(list) {
69
+ const parts = [];
70
+ let depth = 0;
71
+ let inString = false;
72
+ let start = 0;
73
+ for (let i = 0; i < list.length; i++) {
74
+ const c = list[i];
75
+ if (inString) {
76
+ if (c === "'") {
77
+ if (list[i + 1] === "'") i++;
78
+ else inString = false;
79
+ }
80
+ continue;
81
+ }
82
+ if (c === "'") inString = true;
83
+ else if (c === "(") depth++;
84
+ else if (c === ")") depth--;
85
+ else if (c === "," && depth === 0) {
86
+ parts.push(list.slice(start, i));
87
+ start = i + 1;
88
+ }
89
+ }
90
+ parts.push(list.slice(start));
91
+ return parts;
92
+ }
5
93
  var BETWEEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+BETWEEN\s+(.+?)\s+AND\s+(.+?)\s*$/i;
6
94
  function literal(raw) {
7
95
  const t = raw.trim();
@@ -11,9 +99,11 @@ function literal(raw) {
11
99
  return void 0;
12
100
  }
13
101
  function parseCheck(expression, name) {
14
- const expr = (expression ?? "").trim();
102
+ const expr = unwrap((expression ?? "").trim());
15
103
  if (!expr) return { ok: false, reason: "empty expression" };
16
104
  if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
105
+ if (/(^|[\s)])OR($|[\s(])/i.test(expr)) return { ok: false, reason: "contains OR" };
106
+ if (/(^|[\s(])NOT($|[\s(])/i.test(expr)) return { ok: false, reason: "contains NOT" };
17
107
  const between = expr.match(BETWEEN);
18
108
  if (between) {
19
109
  const lo = literal(between[2]);
@@ -28,6 +118,40 @@ function parseCheck(expression, name) {
28
118
  ]
29
119
  };
30
120
  }
121
+ const parts = splitTopLevelAnd(expr);
122
+ if (parts.length > 1) {
123
+ const checks = [];
124
+ const sets = [];
125
+ for (const part of parts) {
126
+ const parsed = parseCheck(part, name);
127
+ if (!parsed.ok)
128
+ return { ok: false, reason: `part of an AND was not understood: ${parsed.reason}` };
129
+ checks.push(...parsed.checks);
130
+ if (parsed.sets) sets.push(...parsed.sets);
131
+ }
132
+ return sets.length ? { ok: true, checks, sets } : { ok: true, checks };
133
+ }
134
+ const inList = expr.match(IN_LIST);
135
+ if (inList) {
136
+ const raw = splitTopLevelCommas(inList[2]);
137
+ const parsedValues = raw.map((r) => literal(r));
138
+ if (parsedValues.some((v) => !v)) return { ok: false, reason: "IN list holds a non-literal" };
139
+ const kinds = new Set(parsedValues.map((v) => v.kind));
140
+ if (kinds.size > 1) return { ok: false, reason: "IN list mixes types" };
141
+ if (!parsedValues.length) return { ok: false, reason: "IN list is empty" };
142
+ return {
143
+ ok: true,
144
+ checks: [],
145
+ sets: [
146
+ {
147
+ column: inList[1],
148
+ values: parsedValues.map((v) => v.value),
149
+ kind: parsedValues[0].kind,
150
+ name
151
+ }
152
+ ]
153
+ };
154
+ }
31
155
  const cmp = expr.match(COMPARISON);
32
156
  if (!cmp) return { ok: false, reason: "not a single comparison this version understands" };
33
157
  const value = literal(cmp[3]);
@@ -35,7 +159,14 @@ function parseCheck(expression, name) {
35
159
  return { ok: false, reason: "right side is not a literal" };
36
160
  }
37
161
  const op = cmp[2] === "!=" ? "<>" : cmp[2];
38
- return { ok: true, checks: [{ column: cmp[1], operator: op, value: value.value, kind: value.kind, name }] };
162
+ return {
163
+ ok: true,
164
+ checks: [{ column: cmp[1], operator: op, value: value.value, kind: value.kind, name }]
165
+ };
166
+ }
167
+ function describeSet(set) {
168
+ const shown = set.values.map((v) => set.kind === "string" ? `'${v}'` : v).join(", ");
169
+ return `${set.name ? `${set.name}: ` : ""}${set.column} IN (${shown})`;
39
170
  }
40
171
 
41
172
  // src/files.ts
@@ -262,6 +393,7 @@ export {
262
393
  IMPORT_EXTENSIONS,
263
394
  NAME_MODES,
264
395
  applyTableCase,
396
+ describeSet,
265
397
  formatCode,
266
398
  importSpecifier,
267
399
  insertColumns,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drzl/validation-core",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",