@drzl/validation-core 3.0.0 → 3.1.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.d.cts CHANGED
@@ -173,6 +173,56 @@ declare function typeName(mode: NameMode, tsName: string, affix: ResolvedAffix):
173
173
  */
174
174
  declare function validateAffix(affix?: AffixOptions, schemaSuffix?: string): AffixIssue[];
175
175
 
176
+ /**
177
+ * Turning a SQL CHECK constraint into something a validator can enforce.
178
+ *
179
+ * No official Drizzle validator module does this. Verified against `drizzle-orm/zod` at
180
+ * 1.0.0-rc.4: a table with `check('age_adult', sql`${t.age} >= 18`)` produces an insert schema
181
+ * that accepts `{ age: 5 }`. The constraint is in the schema, the database will reject the row,
182
+ * and the validator says nothing.
183
+ *
184
+ * Only expressions whose meaning is unambiguous are translated. Everything else is reported and
185
+ * skipped, because a validator that quietly enforces a *guess* at your constraint is worse than
186
+ * one that enforces nothing: it rejects rows the database would have accepted.
187
+ *
188
+ * Two pieces of SQL semantics that a naive translation gets wrong:
189
+ *
190
+ * 1. **A CHECK passes when it evaluates to TRUE *or NULL*.** So `CHECK (age >= 18)` on a
191
+ * nullable column accepts NULL. Emitting `.gte(18)` on the inner type and applying
192
+ * `.nullable()` around it reproduces that exactly, which is why the constraint belongs on the
193
+ * base expression rather than on the whole field.
194
+ * 2. **A multi-column check cannot live on a field.** `start_date < end_date` is a statement
195
+ * about the row, so it is not returned here at all.
196
+ */
197
+ /** A comparison of one column against one literal, which is the case worth translating. */
198
+ interface ColumnCheck {
199
+ /** Column the constraint is about, as it appears in the expression. */
200
+ column: string;
201
+ operator: '>=' | '>' | '<=' | '<' | '=' | '<>';
202
+ /** The literal, still as text: a 64 bit bound must not pass through a JS number. */
203
+ value: string;
204
+ /** Whether the literal was quoted, which distinguishes `'5'` from `5`. */
205
+ kind: 'number' | 'string';
206
+ /** Constraint name, used to say which one failed. */
207
+ name?: string;
208
+ }
209
+ /** A check that was understood, or the reason it was not. */
210
+ type ParsedCheck = {
211
+ ok: true;
212
+ checks: ColumnCheck[];
213
+ } | {
214
+ ok: false;
215
+ reason: string;
216
+ };
217
+ /**
218
+ * Parse one check expression.
219
+ *
220
+ * Deliberately narrow. `BETWEEN` is included because it is common and means exactly two
221
+ * inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
222
+ * silently change what is enforced.
223
+ */
224
+ declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
225
+
176
226
  interface Table {
177
227
  name: string;
178
228
  tsName: string;
@@ -243,4 +293,4 @@ declare function updateColumns(table: Table): Column[];
243
293
  declare function selectColumns(table: Table): Column[];
244
294
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
245
295
 
246
- export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, formatCode, importSpecifier, insertColumns, isGeneratedColumn, moduleFileName, moduleSpecifier, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
296
+ export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, 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, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
package/dist/index.d.ts CHANGED
@@ -173,6 +173,56 @@ declare function typeName(mode: NameMode, tsName: string, affix: ResolvedAffix):
173
173
  */
174
174
  declare function validateAffix(affix?: AffixOptions, schemaSuffix?: string): AffixIssue[];
175
175
 
176
+ /**
177
+ * Turning a SQL CHECK constraint into something a validator can enforce.
178
+ *
179
+ * No official Drizzle validator module does this. Verified against `drizzle-orm/zod` at
180
+ * 1.0.0-rc.4: a table with `check('age_adult', sql`${t.age} >= 18`)` produces an insert schema
181
+ * that accepts `{ age: 5 }`. The constraint is in the schema, the database will reject the row,
182
+ * and the validator says nothing.
183
+ *
184
+ * Only expressions whose meaning is unambiguous are translated. Everything else is reported and
185
+ * skipped, because a validator that quietly enforces a *guess* at your constraint is worse than
186
+ * one that enforces nothing: it rejects rows the database would have accepted.
187
+ *
188
+ * Two pieces of SQL semantics that a naive translation gets wrong:
189
+ *
190
+ * 1. **A CHECK passes when it evaluates to TRUE *or NULL*.** So `CHECK (age >= 18)` on a
191
+ * nullable column accepts NULL. Emitting `.gte(18)` on the inner type and applying
192
+ * `.nullable()` around it reproduces that exactly, which is why the constraint belongs on the
193
+ * base expression rather than on the whole field.
194
+ * 2. **A multi-column check cannot live on a field.** `start_date < end_date` is a statement
195
+ * about the row, so it is not returned here at all.
196
+ */
197
+ /** A comparison of one column against one literal, which is the case worth translating. */
198
+ interface ColumnCheck {
199
+ /** Column the constraint is about, as it appears in the expression. */
200
+ column: string;
201
+ operator: '>=' | '>' | '<=' | '<' | '=' | '<>';
202
+ /** The literal, still as text: a 64 bit bound must not pass through a JS number. */
203
+ value: string;
204
+ /** Whether the literal was quoted, which distinguishes `'5'` from `5`. */
205
+ kind: 'number' | 'string';
206
+ /** Constraint name, used to say which one failed. */
207
+ name?: string;
208
+ }
209
+ /** A check that was understood, or the reason it was not. */
210
+ type ParsedCheck = {
211
+ ok: true;
212
+ checks: ColumnCheck[];
213
+ } | {
214
+ ok: false;
215
+ reason: string;
216
+ };
217
+ /**
218
+ * Parse one check expression.
219
+ *
220
+ * Deliberately narrow. `BETWEEN` is included because it is common and means exactly two
221
+ * inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
222
+ * silently change what is enforced.
223
+ */
224
+ declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
225
+
176
226
  interface Table {
177
227
  name: string;
178
228
  tsName: string;
@@ -243,4 +293,4 @@ declare function updateColumns(table: Table): Column[];
243
293
  declare function selectColumns(table: Table): Column[];
244
294
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
245
295
 
246
- export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, NAME_MODES, type NameMode, type ResolvedAffix, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, formatCode, importSpecifier, insertColumns, isGeneratedColumn, moduleFileName, moduleSpecifier, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
296
+ export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, 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, moduleFileName, moduleSpecifier, parseCheck, pascalCase, resolveAffix, resolveConfiguredImport, schemaName, selectColumns, typeName, updateColumns, validateAffix };
package/dist/index.js CHANGED
@@ -1,4 +1,42 @@
1
- import "./chunk-MKBO26DX.js";
1
+ import "./chunk-ZZGILH5A.js";
2
+
3
+ // src/checks.ts
4
+ var COMPARISON = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(>=|<=|<>|!=|>|<|=)\s*(.+?)\s*$/;
5
+ var BETWEEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+BETWEEN\s+(.+?)\s+AND\s+(.+?)\s*$/i;
6
+ function literal(raw) {
7
+ const t = raw.trim();
8
+ if (/^-?\d+(\.\d+)?$/.test(t)) return { value: t, kind: "number" };
9
+ const m = t.match(/^'((?:[^']|'')*)'$/);
10
+ if (m) return { value: m[1].replace(/''/g, "'"), kind: "string" };
11
+ return void 0;
12
+ }
13
+ function parseCheck(expression, name) {
14
+ const expr = (expression ?? "").trim();
15
+ if (!expr) return { ok: false, reason: "empty expression" };
16
+ if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
17
+ const between = expr.match(BETWEEN);
18
+ if (between) {
19
+ const lo = literal(between[2]);
20
+ const hi = literal(between[3]);
21
+ if (!lo || !hi) return { ok: false, reason: "BETWEEN bounds are not literals" };
22
+ if (lo.kind !== hi.kind) return { ok: false, reason: "BETWEEN bounds are of mixed types" };
23
+ return {
24
+ ok: true,
25
+ checks: [
26
+ { column: between[1], operator: ">=", value: lo.value, kind: lo.kind, name },
27
+ { column: between[1], operator: "<=", value: hi.value, kind: hi.kind, name }
28
+ ]
29
+ };
30
+ }
31
+ const cmp = expr.match(COMPARISON);
32
+ if (!cmp) return { ok: false, reason: "not a single comparison this version understands" };
33
+ const value = literal(cmp[3]);
34
+ if (!value) {
35
+ return { ok: false, reason: "right side is not a literal" };
36
+ }
37
+ const op = cmp[2] === "!=" ? "<>" : cmp[2];
38
+ return { ok: true, checks: [{ column: cmp[1], operator: op, value: value.value, kind: value.kind, name }] };
39
+ }
2
40
 
3
41
  // src/files.ts
4
42
  import nodeFs from "fs";
@@ -184,7 +222,7 @@ async function formatCode(code, filePath, fmt) {
184
222
  const engine = fmt?.engine ?? "auto";
185
223
  try {
186
224
  if (engine === "prettier" || engine === "auto") {
187
- const prettier = await import("./prettier-Y62OQ7O7.js");
225
+ const prettier = await import("./prettier-LT6G5PW7.js");
188
226
  const cfgRef = fmt?.configPath ?? filePath;
189
227
  const cfg = await prettier.resolveConfig(cfgRef).catch(() => null);
190
228
  return prettier.format(code, { ...cfg ?? {}, parser: "typescript", filepath: filePath });
@@ -219,6 +257,7 @@ export {
219
257
  isGeneratedColumn,
220
258
  moduleFileName,
221
259
  moduleSpecifier,
260
+ parseCheck,
222
261
  pascalCase,
223
262
  resolveAffix,
224
263
  resolveConfiguredImport,