@drzl/validation-core 2.1.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;
@@ -223,10 +273,24 @@ interface ValidationRenderer<TOptions extends ValidationGenerateOptions = Valida
223
273
  renderIndex?(analysis: Analysis, opts?: TOptions): string;
224
274
  generate(opts: TOptions): Promise<string[]>;
225
275
  }
226
- declare function isGeneratedColumn(c: Column, primaryKeyColumns: string[]): boolean;
276
+ /**
277
+ * Whether the database generates this column's value, so it cannot be written.
278
+ *
279
+ * `primaryKeyColumns` is accepted for backwards compatibility and no longer consulted. It used
280
+ * to make every primary key count as generated, which dropped it from the insert schema whether
281
+ * or not the database supplied it. Right for a MySQL autoincrement column; wrong for a Postgres
282
+ * `integer('id').primaryKey()`, which Postgres does not generate, and for any natural key such
283
+ * as `text('slug').primaryKey()`. Those inserts became impossible to express: the required
284
+ * column was simply absent, with no way to provide it.
285
+ *
286
+ * Being a key says nothing about who supplies the value. `isGenerated` marks a column that
287
+ * cannot be written; `hasDefault` marks one that need not be, and those stay in the schema as
288
+ * optional.
289
+ */
290
+ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): boolean;
227
291
  declare function insertColumns(table: Table): Column[];
228
292
  declare function updateColumns(table: Table): Column[];
229
293
  declare function selectColumns(table: Table): Column[];
230
294
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
231
295
 
232
- 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;
@@ -223,10 +273,24 @@ interface ValidationRenderer<TOptions extends ValidationGenerateOptions = Valida
223
273
  renderIndex?(analysis: Analysis, opts?: TOptions): string;
224
274
  generate(opts: TOptions): Promise<string[]>;
225
275
  }
226
- declare function isGeneratedColumn(c: Column, primaryKeyColumns: string[]): boolean;
276
+ /**
277
+ * Whether the database generates this column's value, so it cannot be written.
278
+ *
279
+ * `primaryKeyColumns` is accepted for backwards compatibility and no longer consulted. It used
280
+ * to make every primary key count as generated, which dropped it from the insert schema whether
281
+ * or not the database supplied it. Right for a MySQL autoincrement column; wrong for a Postgres
282
+ * `integer('id').primaryKey()`, which Postgres does not generate, and for any natural key such
283
+ * as `text('slug').primaryKey()`. Those inserts became impossible to express: the required
284
+ * column was simply absent, with no way to provide it.
285
+ *
286
+ * Being a key says nothing about who supplies the value. `isGenerated` marks a column that
287
+ * cannot be written; `hasDefault` marks one that need not be, and those stay in the schema as
288
+ * optional.
289
+ */
290
+ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): boolean;
227
291
  declare function insertColumns(table: Table): Column[];
228
292
  declare function updateColumns(table: Table): Column[];
229
293
  declare function selectColumns(table: Table): Column[];
230
294
  declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
231
295
 
232
- 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";
@@ -166,12 +204,11 @@ function validateAffix(affix, schemaSuffix) {
166
204
  }
167
205
 
168
206
  // src/index.ts
169
- function isGeneratedColumn(c, primaryKeyColumns) {
170
- return c.isGenerated || primaryKeyColumns.includes(c.name);
207
+ function isGeneratedColumn(c, _primaryKeyColumns = []) {
208
+ return c.isGenerated;
171
209
  }
172
210
  function insertColumns(table) {
173
- const pkCols = table.primaryKey?.columns ?? [];
174
- return table.columns.filter((c) => !isGeneratedColumn(c, pkCols));
211
+ return table.columns.filter((c) => !isGeneratedColumn(c));
175
212
  }
176
213
  function updateColumns(table) {
177
214
  const pkCols = table.primaryKey?.columns ?? [];
@@ -185,7 +222,7 @@ async function formatCode(code, filePath, fmt) {
185
222
  const engine = fmt?.engine ?? "auto";
186
223
  try {
187
224
  if (engine === "prettier" || engine === "auto") {
188
- const prettier = await import("./prettier-Y62OQ7O7.js");
225
+ const prettier = await import("./prettier-LT6G5PW7.js");
189
226
  const cfgRef = fmt?.configPath ?? filePath;
190
227
  const cfg = await prettier.resolveConfig(cfgRef).catch(() => null);
191
228
  return prettier.format(code, { ...cfg ?? {}, parser: "typescript", filepath: filePath });
@@ -220,6 +257,7 @@ export {
220
257
  isGeneratedColumn,
221
258
  moduleFileName,
222
259
  moduleSpecifier,
260
+ parseCheck,
223
261
  pascalCase,
224
262
  resolveAffix,
225
263
  resolveConfiguredImport,