@vibeorm/dsl 4.1.0 → 4.3.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.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // @bun
2
2
  // packages/dsl/src/column.ts
3
- import { VibeError, assertJsonSchemaRef, encodePrismaString } from "@vibeorm/schema";
3
+ import { VibeError, assertJsonSchemaRef, assertValidatorRef, encodePrismaString, generatedColumnFromSql } from "@vibeorm/schema";
4
4
  function cloneColumnDef(params) {
5
5
  const { column } = params;
6
6
  return {
7
7
  __type: "column",
8
8
  __columnType: column.__columnType,
9
9
  __name: column.__name,
10
- __config: { ...column.__config, ...column.__config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: column.__config.jsonSchema }) } },
10
+ __config: { ...column.__config, ...column.__config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: column.__config.jsonSchema }) }, ...column.__config.validator === undefined ? {} : { validator: assertValidatorRef({ value: column.__config.validator }) }, ..."generated" in column.__config ? { generated: Object.freeze({ sql: generatedColumnFromSql({ value: column.__config.generated }).expression }) } : {} },
11
11
  __enumValues: column.__enumValues,
12
12
  __enumName: column.__enumName,
13
13
  __enumStorage: column.__enumStorage
@@ -24,7 +24,7 @@ class ChainableColumn {
24
24
  __enumStorage;
25
25
  constructor(columnType, config = {}) {
26
26
  this.__columnType = columnType;
27
- this.__config = { ...config };
27
+ this.__config = { ...config, ...config.validator === undefined ? {} : { validator: assertValidatorRef({ value: config.validator }) }, ..."generated" in config ? { generated: Object.freeze({ sql: generatedColumnFromSql({ value: config.generated }).expression }) } : {} };
28
28
  }
29
29
  optional() {
30
30
  this.__config.optional = true;
@@ -54,10 +54,62 @@ class ChainableColumn {
54
54
  this.__config.dbgenerated = expression;
55
55
  return this;
56
56
  }
57
+ generated(options) {
58
+ if ("generated" in this.__config)
59
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field may declare generation only once", meta: { issues: [{ rule: "generated-column-duplicate" }] } });
60
+ const generated = generatedColumnFromSql({ value: options });
61
+ this.__config.generated = Object.freeze({ sql: generated.expression });
62
+ return this;
63
+ }
64
+ validate(reference) {
65
+ if (this.__columnType === "json" || this.__columnType === "unsupported")
66
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Validators are only valid on queryable non-JSON scalar or enum columns; use .schema() for Json", meta: { issues: [{ rule: "validator-non-scalar" }] } });
67
+ if (this.__config.jsonSchema !== undefined)
68
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field cannot combine a scalar validator with a JSON schema", meta: { issues: [{ rule: "conflicting-validator-ref" }] } });
69
+ if (this.__config.validator !== undefined)
70
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field may declare only one validator reference", meta: { issues: [{ rule: "duplicate-validator-ref" }] } });
71
+ this.__config.validator = assertValidatorRef({ value: reference });
72
+ return this;
73
+ }
57
74
  comment(text) {
58
75
  this.__config.comment = text;
59
76
  return this;
60
77
  }
78
+ addCheckRule(params) {
79
+ const { options } = params;
80
+ const rule = Object.freeze({
81
+ rule: options?.rule ?? params.rule,
82
+ check: Object.freeze(params.check.kind === "oneOf" ? { ...params.check, values: Object.freeze([...params.check.values]) } : params.check),
83
+ ...options?.name === undefined ? {} : { name: options.name }
84
+ });
85
+ this.__config.checks = Object.freeze([...this.__config.checks ?? [], rule]);
86
+ return this;
87
+ }
88
+ }
89
+ function numericCarrier(value) {
90
+ return typeof value === "bigint" ? value.toString() : value;
91
+ }
92
+
93
+ class ChainableNumericColumn extends ChainableColumn {
94
+ zero() {
95
+ return this.__columnType === "decimal" ? "0" : 0;
96
+ }
97
+ nonNegative(options) {
98
+ return this.addCheckRule({ rule: "nonNegative", check: { kind: "compare", op: "gte", value: this.zero() }, options });
99
+ }
100
+ positive(options) {
101
+ return this.addCheckRule({ rule: "positive", check: { kind: "compare", op: "gt", value: this.zero() }, options });
102
+ }
103
+ atLeast(value, options) {
104
+ return this.addCheckRule({ rule: "atLeast", check: { kind: "compare", op: "gte", value: numericCarrier(value) }, options });
105
+ }
106
+ atMost(value, options) {
107
+ return this.addCheckRule({ rule: "atMost", check: { kind: "compare", op: "lte", value: numericCarrier(value) }, options });
108
+ }
109
+ range(range) {
110
+ const { min, max, ...options } = range;
111
+ return this.addCheckRule({ rule: "range", check: { kind: "range", min: numericCarrier(min), max: numericCarrier(max) }, options });
112
+ }
61
113
  }
62
114
 
63
115
  class ChainableStringColumn extends ChainableColumn {
@@ -72,13 +124,23 @@ class ChainableStringColumn extends ChainableColumn {
72
124
  this.__config.text = true;
73
125
  return this;
74
126
  }
127
+ uuid() {
128
+ this.__config.uuid = true;
129
+ return this;
130
+ }
75
131
  collation(name) {
76
132
  this.__config.collation = name;
77
133
  return this;
78
134
  }
135
+ oneOf(values, options) {
136
+ return this.addCheckRule({ rule: "allowed", check: { kind: "oneOf", values: [...values] }, options });
137
+ }
138
+ lengthAtMost(max, options) {
139
+ return this.addCheckRule({ rule: "length", check: { kind: "lengthAtMost", max }, options });
140
+ }
79
141
  }
80
142
 
81
- class ChainableDecimalColumn extends ChainableColumn {
143
+ class ChainableDecimalColumn extends ChainableNumericColumn {
82
144
  constructor(config = {}) {
83
145
  super("decimal", config);
84
146
  }
@@ -120,6 +182,10 @@ class ChainableIdColumn extends ChainableColumn {
120
182
  this.__config.type = idType;
121
183
  return this;
122
184
  }
185
+ uuid() {
186
+ this.__config.uuid = true;
187
+ return this;
188
+ }
123
189
  }
124
190
 
125
191
  class ChainableEnumColumn extends ChainableColumn {
@@ -137,22 +203,34 @@ class ChainableEnumColumn extends ChainableColumn {
137
203
  this.__config.default = value;
138
204
  return this;
139
205
  }
206
+ oneOf(values, options) {
207
+ return this.addCheckRule({ rule: "allowed", check: { kind: "oneOf", values: [...values] }, options });
208
+ }
140
209
  }
141
210
  function string(config = {}) {
142
211
  return new ChainableStringColumn(config);
143
212
  }
144
213
 
145
- class ChainableIntegerColumn extends ChainableColumn {
214
+ class ChainableIntegerColumn extends ChainableNumericColumn {
146
215
  autoincrement() {
147
216
  this.__config.autoincrement = true;
148
217
  return this;
149
218
  }
150
219
  }
220
+ function localDate({ ...config } = {}) {
221
+ return new ChainableColumn("localDate", config);
222
+ }
151
223
  function int(config = {}) {
152
224
  return new ChainableIntegerColumn("int", config);
153
225
  }
226
+
227
+ class ChainableFloatColumn extends ChainableNumericColumn {
228
+ constructor(config = {}) {
229
+ super("float", config);
230
+ }
231
+ }
154
232
  function float(config = {}) {
155
- return new ChainableColumn("float", config);
233
+ return new ChainableFloatColumn(config);
156
234
  }
157
235
  function decimal(config = {}) {
158
236
  return new ChainableDecimalColumn(config);
@@ -169,6 +247,8 @@ class ChainableJsonColumn extends ChainableColumn {
169
247
  super("json", { ...config, ...config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: config.jsonSchema }) } });
170
248
  }
171
249
  schema(reference) {
250
+ if (this.__config.validator !== undefined)
251
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field cannot combine a scalar validator with a JSON schema", meta: { issues: [{ rule: "conflicting-validator-ref" }] } });
172
252
  if (this.__config.jsonSchema !== undefined)
173
253
  throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field may declare only one JSON schema reference", meta: { issues: [{ rule: "duplicate-json-schema-ref" }] } });
174
254
  this.__config.jsonSchema = assertJsonSchemaRef({ value: reference });
@@ -235,11 +315,66 @@ function createEnum(config) {
235
315
  }
236
316
 
237
317
  // packages/dsl/src/generator/prisma.ts
238
- import { VibeError as VibeError6, encodePrismaString as encodePrismaString2, internalError, normalizeJsonSchemaRef as normalizeJsonSchemaRef2, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
318
+ import { VibeError as VibeError7, assertGeneratedColumns, assertRowRuleReferentialActions, encodePrismaString as encodePrismaString2, generatedColumnFromSql as generatedColumnFromSql3, internalError, normalizeJsonSchemaRef as normalizeJsonSchemaRef2, normalizeValidatorRef as normalizeValidatorRef2, providerToDialect as providerToDialect2, rowRulePrismaAttribute, indexKeyPrisma, indexKeyTerms } from "@vibeorm/schema";
239
319
  import { getSqlDialect as getSqlDialect2 } from "@vibeorm/sql";
240
320
 
321
+ // packages/dsl/src/row-rules.ts
322
+ import { VibeError as VibeError2, normalizeRowRules } from "@vibeorm/schema";
323
+ function assertOptions(params) {
324
+ const { value } = params;
325
+ if (value === null || typeof value !== "object" || Array.isArray(value) || Reflect.ownKeys(value).some((key) => typeof key !== "string" || !params.allowed.includes(key))) {
326
+ throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `Row-rule options accept only ${params.allowed.join(", ")}` });
327
+ }
328
+ }
329
+ function fieldArray(params) {
330
+ if (!Array.isArray(params.value) || params.value.some((entry) => typeof entry !== "string"))
331
+ throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "Row-rule fields must be an array of logical names" });
332
+ return Object.freeze([...params.value]);
333
+ }
334
+ function createRowRuleBuilder() {
335
+ return Object.freeze({
336
+ appendOnly(options = {}) {
337
+ assertOptions({ value: options, allowed: ["name"] });
338
+ return Object.freeze({ kind: "appendOnly", ...options });
339
+ },
340
+ fixed(options) {
341
+ assertOptions({ value: options, allowed: ["columns", "when", "name"] });
342
+ assertOptions({ value: options.when, allowed: ["fromCreation", "onceSet"] });
343
+ return Object.freeze({ kind: "fixed", ...options, columns: fieldArray({ value: options.columns }), when: Object.freeze({ ...options.when }) });
344
+ },
345
+ rowFixed(options) {
346
+ assertOptions({ value: options, allowed: ["except", "name"] });
347
+ return Object.freeze({ kind: "rowFixed", ...options, except: fieldArray({ value: options.except }) });
348
+ },
349
+ frozen(options) {
350
+ assertOptions({ value: options, allowed: ["from", "mutable", "name"] });
351
+ return Object.freeze({ kind: "frozen", ...options, mutable: fieldArray({ value: options.mutable }) });
352
+ }
353
+ });
354
+ }
355
+ function compileRowRules(params) {
356
+ if (params.model.__rowRules === undefined)
357
+ return [];
358
+ if (typeof params.model.__rowRules !== "function")
359
+ throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "rowRules must be a model-bound callback" });
360
+ const declarations = params.model.__rowRules(createRowRuleBuilder());
361
+ if (!Array.isArray(declarations))
362
+ throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "rowRules must return an array of declarations" });
363
+ const rules = declarations.map((declaration) => {
364
+ if (declaration === null || typeof declaration !== "object" || Array.isArray(declaration))
365
+ throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "Row-rule declarations must be data objects" });
366
+ const candidate = declaration;
367
+ if (candidate.kind !== "frozen")
368
+ return declaration;
369
+ if (typeof candidate.from !== "string" || candidate.from.length === 0)
370
+ throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "Frozen from must be create or a logical field name" });
371
+ return { ...candidate, kind: "frozen", from: candidate.from === "create" ? { create: true } : { field: candidate.from }, mutable: candidate.mutable };
372
+ });
373
+ return normalizeRowRules({ model: params.irModel, rules, dialect: params.dialect, schema: params.schema });
374
+ }
375
+
241
376
  // packages/dsl/src/predicate.ts
242
- import { VibeError as VibeError2 } from "@vibeorm/schema";
377
+ import { IDENTIFIER_LIMITS, VibeError as VibeError3, checkRulePredicate, identifierLength, isEnumType } from "@vibeorm/schema";
243
378
  import { compileExclusion, renderPredicate } from "@vibeorm/sql";
244
379
  function isFieldExpr(operand) {
245
380
  return typeof operand === "object" && operand !== null && "kind" in operand && operand.kind === "field";
@@ -254,7 +389,7 @@ function toValue(params) {
254
389
  if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
255
390
  return { kind: "value", value: input };
256
391
  }
257
- throw new VibeError2({
392
+ throw new VibeError3({
258
393
  code: "VIBE_SCHEMA_INVALID",
259
394
  message: `${at}: ${input === null ? "null" : typeof input} is not a comparable literal \u2014 use isNull()/isNotNull() for null, and a string, number, bigint or boolean otherwise`,
260
395
  meta: { rule: "predicate-literal-invalid", at }
@@ -272,17 +407,28 @@ function toExpression(params) {
272
407
  if (value.kind === "jsonText" && "field" in value && "path" in value && Array.isArray(value.path) && Array.from(value.path).every((segment) => typeof segment === "string" && !segment.includes("\x00"))) {
273
408
  return { kind: "jsonText", field: toRef({ expr: requireFieldExpr({ value: value.field, at }) }), path: [...value.path] };
274
409
  }
275
- if (value.kind === "length" && "expression" in value) {
276
- return { kind: "length", expression: toExpression({ value: value.expression, at }) };
410
+ if ((value.kind === "length" || value.kind === "lower" || value.kind === "upper") && "expression" in value) {
411
+ return { kind: value.kind, expression: toExpression({ value: value.expression, at }) };
412
+ }
413
+ if ((value.kind === "add" || value.kind === "sub" || value.kind === "mul") && "left" in value && "right" in value) {
414
+ const right = value.right;
415
+ return {
416
+ kind: value.kind,
417
+ left: toExpression({ value: value.left, at }),
418
+ right: typeof right === "object" && right !== null && "kind" in right && right.kind === "value" && "value" in right ? toValue({ input: right.value, at }) : toOperand({ operand: right, at })
419
+ };
277
420
  }
278
421
  }
279
- throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `${at}: expected a field, jsonText or length expression with a string-only JSON path`, meta: { rule: "predicate-operand-invalid", at } });
422
+ throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: `${at}: expected a field, jsonText, length, add, sub, mul, lower or upper expression; JSON paths must contain only strings`, meta: { rule: "predicate-operand-invalid", at } });
423
+ }
424
+ function toPredicateExpression(params) {
425
+ return toExpression(params);
280
426
  }
281
427
  function requireFieldExpr(params) {
282
428
  const { value, at } = params;
283
429
  if (isFieldExpr(value))
284
430
  return value;
285
- throw new VibeError2({
431
+ throw new VibeError3({
286
432
  code: "VIBE_SCHEMA_INVALID",
287
433
  message: `${at}: the left side of a comparison must be c.field("\u2026"), not a bare value`,
288
434
  meta: { rule: "predicate-operand-invalid", at }
@@ -291,7 +437,7 @@ function requireFieldExpr(params) {
291
437
  function requireOperands(params) {
292
438
  const { operands, at } = params;
293
439
  if (operands.length === 0) {
294
- throw new VibeError2({
440
+ throw new VibeError3({
295
441
  code: "VIBE_SCHEMA_INVALID",
296
442
  message: `${at}: an empty boolean group has no honest SQL spelling \u2014 pass at least one operand`,
297
443
  meta: { rule: "predicate-empty", at }
@@ -310,7 +456,7 @@ function createPredicateBuilder() {
310
456
  const at = negate ? "c.notInList()" : "c.inList()";
311
457
  const ref = toRef({ expr: requireFieldExpr({ value: field, at }) });
312
458
  if (values.length === 0) {
313
- throw new VibeError2({
459
+ throw new VibeError3({
314
460
  code: "VIBE_SCHEMA_INVALID",
315
461
  message: `${at}: an empty IN list is UNKNOWN on every row and is never what an author means`,
316
462
  meta: { rule: "predicate-empty", at, field: ref.name }
@@ -331,6 +477,11 @@ function createPredicateBuilder() {
331
477
  gte: compare("gte"),
332
478
  lt: compare("lt"),
333
479
  lte: compare("lte"),
480
+ add: ({ left, right }) => ({ ...toExpression({ value: { kind: "add", left, right }, at: "c.add()" }), __kind: "Int" }),
481
+ sub: ({ left, right }) => ({ ...toExpression({ value: { kind: "sub", left, right }, at: "c.sub()" }), __kind: "Int" }),
482
+ mul: ({ left, right }) => ({ ...toExpression({ value: { kind: "mul", left, right }, at: "c.mul()" }), __kind: "Int" }),
483
+ lower: (expression) => ({ kind: "lower", expression: toExpression({ value: expression, at: "c.lower()" }), __kind: "String" }),
484
+ upper: (expression) => ({ kind: "upper", expression: toExpression({ value: expression, at: "c.upper()" }), __kind: "String" }),
334
485
  jsonText: ({ field, path }) => ({ ...toExpression({ value: { kind: "jsonText", field: { kind: "field", name: field }, path }, at: "c.jsonText()" }), __kind: "String" }),
335
486
  length: (expression) => ({ kind: "length", expression: toExpression({ value: expression, at: "c.length()" }), __kind: "Int" }),
336
487
  isTrue: (operand) => ({ kind: "isTrue", operand }),
@@ -343,7 +494,7 @@ function createPredicateBuilder() {
343
494
  not: (operand) => ({ kind: "not", operand }),
344
495
  named: (name, predicate) => {
345
496
  if (name.trim() === "") {
346
- throw new VibeError2({
497
+ throw new VibeError3({
347
498
  code: "VIBE_SCHEMA_INVALID",
348
499
  message: "c.named(): a CHECK needs a non-empty name \u2014 the name is what keys the migration diff",
349
500
  meta: { rule: "predicate-unnamed-check" }
@@ -353,6 +504,262 @@ function createPredicateBuilder() {
353
504
  }
354
505
  };
355
506
  }
507
+ var MAX_LIST_PREVIEW = 6;
508
+ function preview(params) {
509
+ const { values } = params;
510
+ return values.length <= MAX_LIST_PREVIEW ? values.join(", ") : `${values.slice(0, MAX_LIST_PREVIEW).join(", ")}, \u2026`;
511
+ }
512
+ function ruleError(params) {
513
+ return new VibeError3({
514
+ code: "VIBE_SCHEMA_INVALID",
515
+ message: `${params.site}: ${params.message}`,
516
+ meta: { rule: params.rule, site: params.site, ...params.meta ?? {} }
517
+ });
518
+ }
519
+ function physicalColumn(params) {
520
+ return params.field.dbName ?? params.field.name;
521
+ }
522
+ function mintedName(params) {
523
+ return `${params.context.table}_${physicalColumn({ field: params.field })}_${params.rule}_check`;
524
+ }
525
+ function assertRuleWord(params) {
526
+ const { site, options } = params;
527
+ if (options?.name !== undefined && (typeof options.name !== "string" || options.name.trim() === "")) {
528
+ throw ruleError({ site, rule: "predicate-unnamed-check", message: "{ name } must be a non-empty CHECK name" });
529
+ }
530
+ if (options?.rule !== undefined && (typeof options.rule !== "string" || options.rule.trim() === "")) {
531
+ throw ruleError({ site, rule: "check-rule-invalid", message: "{ rule } must be a non-empty rule word" });
532
+ }
533
+ }
534
+ function ruleField(params) {
535
+ const { context, name, site } = params;
536
+ const found = typeof name === "string" ? context.fields.find((field) => field.name === name) : undefined;
537
+ if (found === undefined) {
538
+ throw ruleError({ site, rule: "check-rule-unknown-field", message: `names ${JSON.stringify(name)}, which model "${context.model}" does not declare`, meta: { model: context.model, field: name } });
539
+ }
540
+ if (found.isList) {
541
+ throw ruleError({ site, rule: "check-rule-list-field", message: `"${found.name}" is a list column \u2014 a check rule compares scalar values`, meta: { model: context.model, field: found.name } });
542
+ }
543
+ return found;
544
+ }
545
+ function ruleCheck(params) {
546
+ return { __type: "check", name: params.name, predicate: params.predicate, __origin: params.origin, __minted: params.minted };
547
+ }
548
+ function createChecksBuilder(params) {
549
+ const { context } = params;
550
+ const group = (kind) => (...args) => {
551
+ const arrayForm = Array.isArray(args[0]);
552
+ const names = arrayForm ? [...args[0]] : args;
553
+ const options = arrayForm ? args[1] : undefined;
554
+ const site = `c.${kind}(${names.map((name) => JSON.stringify(name)).join(", ")}) on "${context.model}"`;
555
+ assertRuleWord({ site, options });
556
+ const fields = names.map((name) => ruleField({ context, name, site }));
557
+ if (fields.length < 2 || fields.length > 8) {
558
+ throw ruleError({ site, rule: "check-rule-group-arity", message: `takes 2 to 8 columns, not ${fields.length}` });
559
+ }
560
+ if (new Set(fields.map((field) => field.name)).size !== fields.length) {
561
+ throw ruleError({ site, rule: "check-rule-duplicate-field", message: "names a column twice \u2014 the expansion would contradict itself" });
562
+ }
563
+ const predicate = checkRulePredicate({ rule: { kind, fields: fields.map((field) => field.name) } });
564
+ const name = options?.name ?? mintedName({ context, field: fields[0], rule: options?.rule ?? kind });
565
+ return ruleCheck({ name, predicate, origin: site, minted: options?.name === undefined });
566
+ };
567
+ const order = (earlier, later, options) => {
568
+ const site = `c.order(${JSON.stringify(earlier)}, ${JSON.stringify(later)}) on "${context.model}"`;
569
+ assertRuleWord({ site, options });
570
+ const first = ruleField({ context, name: earlier, site });
571
+ const second = ruleField({ context, name: later, site });
572
+ if (first.name === second.name) {
573
+ throw ruleError({ site, rule: "check-rule-duplicate-field", message: "orders a column against itself" });
574
+ }
575
+ const nameColumn = options?.nameColumn ?? later;
576
+ if (nameColumn !== earlier && nameColumn !== later) {
577
+ throw ruleError({ site, rule: "check-rule-invalid", message: `nameColumn ${JSON.stringify(nameColumn)} must be one of the two ordered columns` });
578
+ }
579
+ const strict = options?.strict !== false;
580
+ const rule = nameColumn === later ? { kind: "order", left: second.name, op: strict ? "gt" : "gte", right: first.name } : { kind: "order", left: first.name, op: strict ? "lt" : "lte", right: second.name };
581
+ const name = options?.name ?? mintedName({ context, field: nameColumn === later ? second : first, rule: options?.rule ?? "order" });
582
+ return ruleCheck({ name, predicate: checkRulePredicate({ rule }), origin: site, minted: options?.name === undefined });
583
+ };
584
+ const presence = (config) => {
585
+ const site = `c.presence({ by: ${JSON.stringify(config?.by)} }) on "${context.model}"`;
586
+ if (config === null || typeof config !== "object")
587
+ throw ruleError({ site, rule: "presence-invalid", message: "takes { by, states }" });
588
+ assertRuleWord({ site, options: { ...config.rule === undefined ? {} : { rule: config.rule } } });
589
+ const by = ruleField({ context, name: config.by, site });
590
+ const byType = by.type;
591
+ if (!isEnumType(byType) || by.isOptional) {
592
+ throw ruleError({ site, rule: "presence-by-invalid", message: `"${by.name}" must be a REQUIRED enum column \u2014 a NULL state makes every IN unknown, and a CHECK passes on unknown`, meta: { model: context.model, field: by.name } });
593
+ }
594
+ const members = context.enums.find((entry) => entry.name === byType.enum)?.values;
595
+ if (members === undefined) {
596
+ throw ruleError({ site, rule: "presence-by-invalid", message: `enum "${byType.enum}" of "${by.name}" is not declared in the schema` });
597
+ }
598
+ const states = config.states;
599
+ if (states === null || typeof states !== "object")
600
+ throw ruleError({ site, rule: "presence-invalid", message: "states must map every enum member to { set, unset }" });
601
+ const missing = members.filter((member) => !Object.hasOwn(states, member));
602
+ const extra = Object.keys(states).filter((key) => !members.includes(key));
603
+ if (missing.length > 0 || extra.length > 0) {
604
+ throw ruleError({
605
+ site,
606
+ rule: "presence-states-mismatch",
607
+ message: `states must list every member of enum "${byType.enum}" exactly once${missing.length === 0 ? "" : `; missing: ${preview({ values: missing })}`}${extra.length === 0 ? "" : `; not members: ${preview({ values: extra })}`}`,
608
+ meta: { missing, extra }
609
+ });
610
+ }
611
+ const setIn = new Map;
612
+ const unsetIn = new Map;
613
+ for (const member of members) {
614
+ const state = states[member];
615
+ if (state === null || typeof state !== "object")
616
+ throw ruleError({ site, rule: "presence-invalid", message: `state "${member}" must be { set?, unset? }` });
617
+ const lists = { set: state.set ?? [], unset: state.unset ?? [] };
618
+ for (const [kind, list] of Object.entries(lists)) {
619
+ if (!Array.isArray(list))
620
+ throw ruleError({ site, rule: "presence-invalid", message: `state "${member}".${kind} must be a list of columns` });
621
+ if (new Set(list).size !== list.length)
622
+ throw ruleError({ site, rule: "check-rule-duplicate-field", message: `state "${member}".${kind} names a column twice` });
623
+ for (const name of list) {
624
+ const field = ruleField({ context, name, site });
625
+ if (field.name === by.name)
626
+ throw ruleError({ site, rule: "presence-invalid", message: `state "${member}" lists the state column "${by.name}" itself` });
627
+ if (!field.isOptional) {
628
+ throw ruleError({ site, rule: "presence-required-column", message: `"${field.name}" is required, so it is set in every state \u2014 a presence table lists optional columns only`, meta: { model: context.model, field: field.name } });
629
+ }
630
+ const target = kind === "set" ? setIn : unsetIn;
631
+ target.set(field.name, [...target.get(field.name) ?? [], member]);
632
+ }
633
+ }
634
+ const both = lists.set.filter((name) => lists.unset.includes(name));
635
+ if (both.length > 0)
636
+ throw ruleError({ site, rule: "presence-conflict", message: `state "${member}" lists ${preview({ values: both })} as both set and unset` });
637
+ }
638
+ const columns = context.fields.filter((field) => setIn.has(field.name) || unsetIn.has(field.name));
639
+ if (columns.length === 0)
640
+ throw ruleError({ site, rule: "presence-empty", message: "names no column \u2014 it would hold nothing" });
641
+ const names = config.names ?? {};
642
+ for (const [column, name] of Object.entries(names)) {
643
+ if (!columns.some((field) => field.name === column))
644
+ throw ruleError({ site, rule: "presence-invalid", message: `names.${column} is not a column this presence table constrains` });
645
+ assertRuleWord({ site, options: { ...name === undefined ? {} : { name } } });
646
+ }
647
+ return columns.map((field) => {
648
+ const set = setIn.get(field.name) ?? [];
649
+ const unset = unsetIn.get(field.name) ?? [];
650
+ if (set.length === members.length) {
651
+ throw ruleError({ site, rule: "presence-constant-column", message: `"${field.name}" is set in every state \u2014 declare it required instead`, meta: { model: context.model, field: field.name } });
652
+ }
653
+ if (unset.length === members.length) {
654
+ throw ruleError({ site, rule: "presence-constant-column", message: `"${field.name}" is empty in every state \u2014 it can never hold a value`, meta: { model: context.model, field: field.name } });
655
+ }
656
+ const explicit = names[field.name];
657
+ return ruleCheck({
658
+ name: explicit ?? mintedName({ context, field, rule: config.rule ?? physicalColumn({ field: by }) }),
659
+ predicate: checkRulePredicate({ rule: { kind: "presence", by: by.name, field: field.name, set, unset } }),
660
+ origin: `presence table on "${context.model}.${by.name}" for column "${field.name}"`,
661
+ minted: explicit === undefined
662
+ });
663
+ });
664
+ };
665
+ return {
666
+ ...createPredicateBuilder(),
667
+ order,
668
+ allOrNone: group("allOrNone"),
669
+ exactlyOne: group("exactlyOne"),
670
+ atMostOne: group("atMostOne"),
671
+ anySet: group("anySet"),
672
+ presence
673
+ };
674
+ }
675
+ var EXACT_NUMBER = /^(-?)(\d+)(?:\.(\d+))?$/;
676
+ function compareBounds(params) {
677
+ const { left, right } = params;
678
+ if (typeof left === "number" && typeof right === "number")
679
+ return Number.isNaN(left - right) ? undefined : Math.sign(left - right);
680
+ const parse = (value) => {
681
+ const match = EXACT_NUMBER.exec(String(value));
682
+ if (match === null)
683
+ return;
684
+ const whole = match[2].replace(/^0+(?=\d)/, "");
685
+ const fraction = (match[3] ?? "").replace(/0+$/, "");
686
+ return { negative: match[1] === "-" && !(whole === "0" && fraction === ""), whole, fraction };
687
+ };
688
+ const a = parse(left);
689
+ const b = parse(right);
690
+ if (a === undefined || b === undefined)
691
+ return;
692
+ if (a.negative !== b.negative)
693
+ return a.negative ? -1 : 1;
694
+ let magnitude = a.whole.length - b.whole.length;
695
+ if (magnitude === 0 && a.whole !== b.whole)
696
+ magnitude = a.whole < b.whole ? -1 : 1;
697
+ if (magnitude === 0) {
698
+ const width = Math.max(a.fraction.length, b.fraction.length);
699
+ const x = a.fraction.padEnd(width, "0");
700
+ const y = b.fraction.padEnd(width, "0");
701
+ magnitude = x === y ? 0 : x < y ? -1 : 1;
702
+ }
703
+ return a.negative ? -Math.sign(magnitude) : Math.sign(magnitude);
704
+ }
705
+ function columnRuleChecks(params) {
706
+ const { model, context } = params;
707
+ const checks = [];
708
+ for (const [name, column] of model.__columns) {
709
+ const rules = column.__config.checks ?? [];
710
+ if (rules.length === 0)
711
+ continue;
712
+ for (const entry of rules) {
713
+ const site = `rule "${entry.rule}" on "${context.model}.${name}"`;
714
+ assertRuleWord({ site, options: { rule: entry.rule, ...entry.name === undefined ? {} : { name: entry.name } } });
715
+ const field = ruleField({ context, name, site });
716
+ const shape = entry.check;
717
+ let rule;
718
+ switch (shape.kind) {
719
+ case "compare":
720
+ rule = { kind: "compare", field: field.name, op: shape.op, value: shape.value };
721
+ break;
722
+ case "range":
723
+ if ((compareBounds({ left: shape.min, right: shape.max }) ?? 0) > 0) {
724
+ throw ruleError({ site, rule: "check-rule-range-bounds", message: `min ${String(shape.min)} is above max ${String(shape.max)} \u2014 no value could pass`, meta: { min: shape.min, max: shape.max } });
725
+ }
726
+ rule = { kind: "range", field: field.name, min: shape.min, max: shape.max };
727
+ break;
728
+ case "oneOf":
729
+ if (shape.values.length === 0)
730
+ throw ruleError({ site, rule: "check-rule-empty", message: "oneOf() has no values \u2014 an empty IN list is never what an author means" });
731
+ rule = { kind: "oneOf", field: field.name, values: shape.values };
732
+ break;
733
+ case "lengthAtMost":
734
+ if (!Number.isSafeInteger(shape.max) || shape.max < 0) {
735
+ throw ruleError({ site, rule: "check-rule-length-bound", message: `lengthAtMost(${String(shape.max)}) needs a non-negative whole number` });
736
+ }
737
+ rule = { kind: "lengthAtMost", field: field.name, max: shape.max };
738
+ break;
739
+ }
740
+ checks.push(ruleCheck({
741
+ name: entry.name ?? mintedName({ context, field, rule: entry.rule }),
742
+ predicate: checkRulePredicate({ rule }),
743
+ origin: site,
744
+ minted: entry.name === undefined
745
+ }));
746
+ }
747
+ }
748
+ return checks;
749
+ }
750
+ function isRuleCheck(entry) {
751
+ return entry !== null && typeof entry === "object" && "__type" in entry && entry.__type === "check";
752
+ }
753
+ function typedCheck(params) {
754
+ const { entry } = params;
755
+ return {
756
+ kind: "typed",
757
+ name: entry.name,
758
+ predicate: entry.predicate,
759
+ ...entry.__origin === undefined ? {} : { origin: entry.__origin },
760
+ ...entry.__minted === true ? { minted: true } : {}
761
+ };
762
+ }
356
763
  function evaluateChecks(params) {
357
764
  const { checks, model } = params;
358
765
  if (checks === undefined)
@@ -360,28 +767,61 @@ function evaluateChecks(params) {
360
767
  if (typeof checks !== "function") {
361
768
  return checks.map((check) => ({ kind: "raw", name: check.name, expression: check.expression }));
362
769
  }
363
- const declared = checks(createPredicateBuilder());
770
+ const builder = params.context === undefined ? contextlessChecksBuilder({ model }) : createChecksBuilder({ context: params.context });
771
+ const declared = checks(builder);
364
772
  return declared.map((entry, index) => {
365
- if (entry !== null && typeof entry === "object" && !("__type" in entry) && typeof entry.name === "string" && "expression" in entry && typeof entry.expression === "string") {
773
+ if (entry !== null && typeof entry === "object" && !("__type" in entry) && "name" in entry && typeof entry.name === "string" && "expression" in entry && typeof entry.expression === "string") {
366
774
  return { kind: "raw", name: entry.name, expression: entry.expression };
367
775
  }
368
- if (entry === null || typeof entry !== "object" || !("__type" in entry) || entry.__type !== "check") {
369
- throw new VibeError2({
776
+ if (Array.isArray(entry)) {
777
+ throw new VibeError3({
778
+ code: "VIBE_SCHEMA_INVALID",
779
+ message: `Model "${model}": checks[${index}] is a list \u2014 spread c.presence(...) into the checks: [...c.presence({ \u2026 })]`,
780
+ meta: { rule: "predicate-unnamed-check", model, index }
781
+ });
782
+ }
783
+ if (!isRuleCheck(entry)) {
784
+ throw new VibeError3({
370
785
  code: "VIBE_SCHEMA_INVALID",
371
- message: `Model "${model}": checks[${index}] must be c.named("name", predicate) or a raw { name, expression } CHECK`,
786
+ message: `Model "${model}": checks[${index}] must be c.named("name", predicate), a check rule (c.order, c.allOrNone, \u2026) or a raw { name, expression } CHECK`,
372
787
  meta: { rule: "predicate-unnamed-check", model, index }
373
788
  });
374
789
  }
375
- return { kind: "typed", name: entry.name, predicate: entry.predicate };
790
+ return typedCheck({ entry });
376
791
  });
377
792
  }
793
+ function contextlessChecksBuilder(params) {
794
+ const refuse = (name) => () => {
795
+ throw new VibeError3({
796
+ code: "VIBE_SCHEMA_INVALID",
797
+ message: `Model "${params.model}": c.${name}() needs the model's fields \u2014 compile the schema with toIR (or compileModelChecks)`,
798
+ meta: { rule: "check-rule-context", model: params.model }
799
+ });
800
+ };
801
+ return {
802
+ ...createPredicateBuilder(),
803
+ order: refuse("order"),
804
+ allOrNone: refuse("allOrNone"),
805
+ exactlyOne: refuse("exactlyOne"),
806
+ atMostOne: refuse("atMostOne"),
807
+ anySet: refuse("anySet"),
808
+ presence: refuse("presence")
809
+ };
810
+ }
378
811
  function compileExclusions(params) {
379
812
  const specs = typeof params.exclusions === "function" ? params.exclusions(createPredicateBuilder()) : params.exclusions ?? [];
380
813
  return specs.map((exclusion) => compileExclusion({ exclusion, model: params.model, dialect: params.dialect }));
381
814
  }
382
815
  function compileChecks(params) {
383
816
  const { checks, model, dialect, enums } = params;
384
- return evaluateChecks({ checks, model: model.name }).map((check) => check.kind === "raw" ? { name: check.name, expression: check.expression } : {
817
+ const context = { model: model.name, table: params.table ?? model.name, fields: model.fields, enums };
818
+ const evaluated = evaluateChecks({ checks, model: model.name, context });
819
+ assertRuleNames({ checks: evaluated, model: model.name, dialect });
820
+ return renderChecks({ checks: evaluated, model, dialect, enums });
821
+ }
822
+ function renderChecks(params) {
823
+ const { model, dialect, enums } = params;
824
+ return params.checks.map((check) => check.kind === "raw" ? { name: check.name, expression: check.expression } : {
385
825
  name: check.name,
386
826
  expression: renderPredicate({
387
827
  predicate: check.predicate,
@@ -389,15 +829,50 @@ function compileChecks(params) {
389
829
  dialect,
390
830
  enums,
391
831
  usage: "check",
392
- site: `CHECK "${check.name}"`
832
+ site: check.origin === undefined ? `CHECK "${check.name}"` : `CHECK "${check.name}" (${check.origin})`
393
833
  }),
394
834
  predicate: check.predicate
395
835
  });
396
836
  }
837
+ function assertRuleNames(params) {
838
+ const { checks, model, dialect } = params;
839
+ const seen = new Map;
840
+ const limit = IDENTIFIER_LIMITS[dialect.name];
841
+ for (const check of checks) {
842
+ const origin = check.kind === "typed" ? check.origin : undefined;
843
+ const previous = seen.get(check.name);
844
+ const previousOrigin = previous?.kind === "typed" ? previous.origin : undefined;
845
+ if (previous !== undefined && (origin !== undefined || previousOrigin !== undefined)) {
846
+ throw new VibeError3({
847
+ code: "VIBE_SCHEMA_INVALID",
848
+ message: `Model "${model}": CHECK name "${check.name}" is produced twice (${previousOrigin ?? "a hand-written CHECK"}; ${origin ?? "a hand-written CHECK"}) \u2014 names key the migration diff. Pass { name: "\u2026" } to one of the rules`,
849
+ meta: { rule: "duplicate-check", model, name: check.name }
850
+ });
851
+ }
852
+ seen.set(check.name, check);
853
+ if (check.kind === "typed" && check.minted === true && limit !== undefined && identifierLength({ name: check.name, dialect: dialect.name }) > limit.max) {
854
+ throw new VibeError3({
855
+ code: "VIBE_SCHEMA_INVALID",
856
+ message: `Model "${model}": generated CHECK name "${check.name}" (${origin ?? "check rule"}) is longer than ${dialect.name}'s ${limit.max}-${limit.unit} identifier limit \u2014 pass a shorter { name: "\u2026" } to the rule`,
857
+ meta: { rule: "identifier-too-long", model, name: check.name }
858
+ });
859
+ }
860
+ }
861
+ }
862
+ function compileModelChecks(params) {
863
+ const { model, predicateModel, dialect, enums } = params;
864
+ const context = { model: model.__name, table: model.__map ?? model.__name, fields: predicateModel.fields, enums };
865
+ const checks = [
866
+ ...evaluateChecks({ checks: model.__checks, model: model.__name, context }),
867
+ ...columnRuleChecks({ model, context }).map((entry) => typedCheck({ entry }))
868
+ ];
869
+ assertRuleNames({ checks, model: model.__name, dialect });
870
+ return renderChecks({ checks, model: predicateModel, dialect, enums });
871
+ }
397
872
  function compileIndexPredicate(params) {
398
873
  const { where, wherePredicate, columns, model, dialect, enums } = params;
399
874
  if (where !== undefined && wherePredicate !== undefined) {
400
- throw new VibeError2({
875
+ throw new VibeError3({
401
876
  code: "VIBE_SCHEMA_INVALID",
402
877
  message: `Index on "${model.name}" [${columns.join(", ")}] carries both a raw where: string and a typed .where() \u2014 an index has ONE predicate`,
403
878
  meta: { rule: "predicate-duplicate-where", model: model.name, columns: [...columns] }
@@ -423,17 +898,22 @@ function compileIndexPredicate(params) {
423
898
  // packages/dsl/src/to-ir.ts
424
899
  import {
425
900
  EMPTY_EXTENSION_REGISTRY,
426
- VibeError as VibeError5,
901
+ VibeError as VibeError6,
427
902
  assertValidSchemaIR,
903
+ assertIndexExpressions,
904
+ indexKeyFieldNames,
428
905
  exactIntegerLiteral,
906
+ generatedColumnFromSql as generatedColumnFromSql2,
907
+ isCanonicalLocalDate,
429
908
  providerToDialect,
430
909
  scalarListDefault,
431
910
  serializeJsonDefault,
432
911
  mergeExtensionDeclarations,
433
912
  normalizeJsonSchemaRef,
913
+ normalizeValidatorRef,
434
914
  unsupportedCapability
435
915
  } from "@vibeorm/schema";
436
- import { getSqlDialect } from "@vibeorm/sql";
916
+ import { getSqlDialect, renderSchemaExpression } from "@vibeorm/sql";
437
917
 
438
918
  // packages/dsl/src/field-type.ts
439
919
  import { fieldTypeIdentity } from "@vibeorm/schema";
@@ -441,6 +921,7 @@ function columnFieldType(params) {
441
921
  const { column } = params;
442
922
  switch (column.__columnType) {
443
923
  case "string":
924
+ case "localDate":
444
925
  return "String";
445
926
  case "int":
446
927
  return "Int";
@@ -467,14 +948,17 @@ function columnFieldType(params) {
467
948
  }
468
949
  }
469
950
  function fieldTypeKey(params) {
470
- return fieldTypeIdentity({ type: columnFieldType({ column: params.column }) });
951
+ return fieldTypeIdentity({
952
+ type: columnFieldType({ column: params.column }),
953
+ ...params.column.__columnType === "localDate" ? { nativeType: "Date" } : {}
954
+ });
471
955
  }
472
956
 
473
957
  // packages/dsl/src/relation-plan.ts
474
- import { VibeError as VibeError4, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
958
+ import { VibeError as VibeError5, indexKeyDefinitionKey, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
475
959
 
476
960
  // packages/dsl/src/model.ts
477
- import { VibeError as VibeError3 } from "@vibeorm/schema";
961
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
478
962
 
479
963
  // packages/dsl/src/naming.ts
480
964
  function capitalize(params) {
@@ -516,7 +1000,7 @@ function createIndexBuilder() {
516
1000
  const partial = (index) => {
517
1001
  const where = (predicate) => {
518
1002
  if (index.__where !== undefined) {
519
- throw new VibeError3({
1003
+ throw new VibeError4({
520
1004
  code: "VIBE_SCHEMA_INVALID",
521
1005
  message: `Index on [${index.__columns.join(", ")}] already carries a raw where: predicate \u2014 an index has ONE predicate, so use either the raw string or .where(), not both`,
522
1006
  meta: { rule: "predicate-duplicate-where", columns: [...index.__columns] }
@@ -531,53 +1015,92 @@ function createIndexBuilder() {
531
1015
  configurable: true
532
1016
  });
533
1017
  };
1018
+ const freeze = (value) => {
1019
+ if (value !== null && typeof value === "object") {
1020
+ for (const child of Object.values(value))
1021
+ freeze(child);
1022
+ Object.freeze(value);
1023
+ }
1024
+ return value;
1025
+ };
1026
+ const keyTerms = (params) => {
1027
+ if (params.keys.every((key) => typeof key === "string"))
1028
+ return { __columns: [...params.keys] };
1029
+ const terms = params.keys.map((entry) => {
1030
+ const sorted = typeof entry === "object" && entry !== null && "__type" in entry && entry.__type === "sortedIndexKey";
1031
+ const key = sorted ? entry.key : entry;
1032
+ const direction = sorted ? { sort: entry.sort } : {};
1033
+ if (typeof key === "string")
1034
+ return { kind: "field", field: { name: key, ...direction } };
1035
+ if (typeof key === "object" && key !== null && "__type" in key && key.__type === "rawIndexExpression") {
1036
+ if (typeof key.text !== "string" || key.text.trim() === "" || key.text.includes("\x00"))
1037
+ throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "i.sql requires nonempty NUL-free scalar SQL", meta: { rule: "expression-index-raw-sql" } });
1038
+ return { kind: "raw", sql: key.text, ...direction };
1039
+ }
1040
+ return { kind: "expression", expression: toPredicateExpression({ value: key, at: "index key" }), ...direction };
1041
+ });
1042
+ const computed = terms.some((term) => term.kind !== "field");
1043
+ return { __columns: computed ? [] : terms.flatMap((term) => term.kind === "field" ? [term.field.name] : []), __keys: freeze(terms) };
1044
+ };
1045
+ const scalar = createPredicateBuilder();
534
1046
  const createIndex = (params) => ({
535
1047
  __type: "index",
536
- __columns: [...params.columns],
1048
+ ...keyTerms({ keys: params.keys }),
537
1049
  __indexType: params.type,
538
1050
  ...params.map === undefined ? {} : { __map: params.map },
539
1051
  ...params.where === undefined ? {} : { __where: params.where },
540
1052
  ...params.ops === undefined ? {} : { __ops: params.ops }
541
1053
  });
542
1054
  return {
1055
+ field: scalar.field,
1056
+ add: scalar.add,
1057
+ sub: scalar.sub,
1058
+ mul: scalar.mul,
1059
+ lower: (value) => scalar.lower(typeof value === "string" ? scalar.field(value) : value),
1060
+ upper: (value) => scalar.upper(typeof value === "string" ? scalar.field(value) : value),
1061
+ asc: (key) => ({ __type: "sortedIndexKey", key, sort: "asc" }),
1062
+ desc: (key) => ({ __type: "sortedIndexKey", key, sort: "desc" }),
1063
+ sql: ({ text }) => ({ __type: "rawIndexExpression", text }),
543
1064
  on(...args) {
544
1065
  const [first, second] = args;
545
1066
  if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
546
- return partial(createIndex({ columns: first, type: second.type, where: second.where, map: second.map, ops: second.ops }));
1067
+ const options = second;
1068
+ return partial(createIndex({ keys: first, type: options.type, where: options.where, map: options.map, ops: options.ops }));
547
1069
  }
548
- return partial(createIndex({ columns: args }));
1070
+ return partial(createIndex({ keys: args }));
549
1071
  },
550
- index(...columns) {
551
- return partial(createIndex({ columns }));
1072
+ index(...keys) {
1073
+ return partial(createIndex({ keys }));
552
1074
  },
553
1075
  unique(...args) {
554
1076
  const [first, second] = args;
555
1077
  if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
556
1078
  if ("ops" in second && second.ops !== undefined) {
557
- throw new VibeError3({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses" } });
1079
+ throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses" } });
558
1080
  }
1081
+ const options = second;
559
1082
  return partial({
560
1083
  __type: "unique",
561
- __columns: [...first],
562
- ...second.map === undefined ? {} : { __map: second.map },
563
- ...second.where === undefined ? {} : { __where: second.where },
564
- ...second.nullsNotDistinct === true ? { __nullsNotDistinct: true } : {},
565
- ...second.name === undefined ? {} : { __clientName: second.name }
1084
+ ...keyTerms({ keys: first }),
1085
+ ...options.map === undefined ? {} : { __map: options.map },
1086
+ ...options.where === undefined ? {} : { __where: options.where },
1087
+ ...options.nullsNotDistinct === true ? { __nullsNotDistinct: true } : {},
1088
+ ...options.name === undefined ? {} : { __clientName: options.name }
566
1089
  });
567
1090
  }
568
- return partial({ __type: "unique", __columns: [...args] });
1091
+ return partial({ __type: "unique", ...keyTerms({ keys: args }) });
569
1092
  },
570
1093
  gin(...columns) {
571
- return partial(createIndex({ columns, type: "Gin" }));
1094
+ return partial(createIndex({ keys: columns, type: "Gin" }));
572
1095
  },
573
1096
  gist(...columns) {
574
- return partial(createIndex({ columns, type: "Gist" }));
1097
+ return partial(createIndex({ keys: columns, type: "Gist" }));
575
1098
  },
576
1099
  hash(...columns) {
577
- return partial(createIndex({ columns, type: "Hash" }));
1100
+ return partial(createIndex({ keys: columns, type: "Hash" }));
578
1101
  },
579
1102
  brin(...columns) {
580
- return partial(createIndex({ columns, type: "Brin" }));
1103
+ return partial(createIndex({ keys: columns, type: "Brin" }));
581
1104
  }
582
1105
  };
583
1106
  }
@@ -621,6 +1144,7 @@ function create(config) {
621
1144
  __primaryKey: config.primaryKey,
622
1145
  __exclusions: config.exclusions,
623
1146
  __checks: config.checks,
1147
+ __rowRules: config.rowRules,
624
1148
  __extensions: config.extensions,
625
1149
  __policy: config.policy,
626
1150
  __comment: config.comment
@@ -649,7 +1173,7 @@ function detectOneToOnePairs(params) {
649
1173
  for (const [modelName, modelRelations] of Object.entries(relations)) {
650
1174
  for (const [relName, rel] of Object.entries(modelRelations)) {
651
1175
  if (rel.__ownsForeignKey !== undefined && (typeof rel.__ownsForeignKey !== "boolean" || rel.__relationType !== "one" || rel.__ownsForeignKey && rel.__fromModel !== modelName)) {
652
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
1176
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
653
1177
  }
654
1178
  if (rel.__relationType === "one") {
655
1179
  allOnes.push({ modelName, relName, rel });
@@ -724,7 +1248,7 @@ function sameColumnTuple(params) {
724
1248
  function resolveFkOwner(params) {
725
1249
  const { a, b, models } = params;
726
1250
  const ownershipError = () => {
727
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Ambiguous foreign-key ownership between ${a.modelName}.${a.relName} and ${b.modelName}.${b.relName}; set ownsForeignKey: true on the foreign-key side`, meta: { rule: "ambiguous-foreign-key-owner" } });
1251
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Ambiguous foreign-key ownership between ${a.modelName}.${a.relName} and ${b.modelName}.${b.relName}; set ownsForeignKey: true on the foreign-key side`, meta: { rule: "ambiguous-foreign-key-owner" } });
728
1252
  };
729
1253
  const aOwns = a.rel.__ownsForeignKey;
730
1254
  const bOwns = b.rel.__ownsForeignKey;
@@ -893,28 +1417,31 @@ function planIndexes(params) {
893
1417
  }
894
1418
  }
895
1419
  for (const index of evaluateIndexes({ model })) {
1420
+ if (index.__keys?.some((term) => term.kind !== "field") && index.__ops !== undefined)
1421
+ throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom operator classes are not admitted on expression-bearing keys", meta: { feature: "expressionIndexOperatorClasses", rule: "expression-index-operator-classes", model: model.__name } });
896
1422
  if (index.__ops !== undefined) {
897
1423
  if (index.__ops === null || typeof index.__ops !== "object" || Array.isArray(index.__ops))
898
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Index ops must be a field-keyed qualified-name map", meta: { rule: "index-operator-class-map", model: model.__name } });
1424
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Index ops must be a field-keyed qualified-name map", meta: { rule: "index-operator-class-map", model: model.__name } });
899
1425
  if (index.__type === "unique")
900
- throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses", model: model.__name } });
1426
+ throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses", model: model.__name } });
901
1427
  for (const [field, operatorClass] of Object.entries(index.__ops)) {
902
1428
  if (!index.__columns.includes(field))
903
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Operator class names a field outside the index", meta: { rule: "index-operator-class-field", model: model.__name, field } });
1429
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Operator class names a field outside the index", meta: { rule: "index-operator-class-field", model: model.__name, field } });
904
1430
  if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass }))
905
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Index operator class must be a qualified SQL name { namespace, name }", meta: { rule: "index-operator-class-identity", model: model.__name, field } });
1431
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Index operator class must be a qualified SQL name { namespace, name }", meta: { rule: "index-operator-class-identity", model: model.__name, field } });
906
1432
  }
907
1433
  }
908
1434
  const operatorClasses = index.__columns.map((field) => index.__ops?.[field]);
909
1435
  const operatorSuffix = operatorClasses.some((operatorClass) => operatorClass !== undefined) ? `|ops:${JSON.stringify([index.__indexType ?? "BTree", operatorClasses.map((operatorClass) => operatorClass === undefined ? null : [operatorClass.namespace, operatorClass.name])])}` : "";
910
1436
  const predicateSuffix = index.__where !== undefined ? `|where:${index.__where}` : index.__wherePredicate === undefined ? "" : `|where:${predicateKey({ predicate: index.__wherePredicate })}`;
911
- const key = index.__columns.join(",") + predicateSuffix + operatorSuffix;
1437
+ const key = index.__keys === undefined ? index.__columns.join(",") + predicateSuffix + operatorSuffix : index.__keys.some((term) => term.kind !== "field") ? indexKeyDefinitionKey({ index: { kind: index.__type === "unique" ? "unique" : (index.__indexType ?? "BTree").toLowerCase(), fields: [], expressions: index.__keys, ...index.__map === undefined ? {} : { name: index.__map }, ...index.__where === undefined ? {} : { where: index.__where }, ...index.__wherePredicate === undefined ? {} : { wherePredicate: index.__wherePredicate }, ...index.__nullsNotDistinct === undefined ? {} : { nullsNotDistinct: index.__nullsNotDistinct } } }) : `${index.__columns.join(",")}${predicateSuffix}${operatorSuffix}|sort:${JSON.stringify(index.__keys)}`;
912
1438
  const existing = byKey.get(key);
913
1439
  const isUnique = index.__type === "unique";
914
1440
  if (existing === undefined || isUnique) {
915
1441
  byKey.set(key, {
916
1442
  isUnique,
917
1443
  columns: [...index.__columns],
1444
+ ...index.__keys === undefined ? {} : { keys: index.__keys },
918
1445
  indexType: index.__indexType,
919
1446
  ...index.__ops === undefined ? {} : { ops: index.__ops },
920
1447
  ...index.__map === undefined ? {} : { map: index.__map },
@@ -941,7 +1468,7 @@ function referenceableKeys(params) {
941
1468
  if (model.__primaryKey !== undefined && model.__primaryKey.length > 0)
942
1469
  keys.push([...model.__primaryKey]);
943
1470
  for (const index of evaluateIndexes({ model })) {
944
- if (index.__type === "unique" && index.__where === undefined && index.__wherePredicate === undefined) {
1471
+ if (index.__type === "unique" && !index.__keys?.some((term) => term.kind !== "field") && index.__where === undefined && index.__wherePredicate === undefined) {
945
1472
  keys.push([...index.__columns]);
946
1473
  }
947
1474
  }
@@ -953,7 +1480,7 @@ function assertForeignKeyIntegrity(params) {
953
1480
  const references = relation.references ?? [];
954
1481
  const at = `${relation.model}.${relation.name}`;
955
1482
  const refuse = (message, rule, meta = {}) => {
956
- throw new VibeError4({
1483
+ throw new VibeError5({
957
1484
  code: "VIBE_SCHEMA_INVALID",
958
1485
  message: `Relation ${at}: ${message}`,
959
1486
  meta: { rule, model: relation.model, relation: relation.name, ...meta }
@@ -1003,12 +1530,14 @@ function assertForeignKeyIntegrity(params) {
1003
1530
  }
1004
1531
  }
1005
1532
  function relationLookups(params) {
1533
+ const inputs = [];
1006
1534
  const incoming = new Map;
1007
1535
  for (const [modelName, relations] of Object.entries(params.schema.__relations)) {
1008
1536
  for (const [relName, rel] of Object.entries(relations)) {
1009
1537
  if (rel.__relationType !== "one")
1010
1538
  continue;
1011
1539
  const target = rel.__targetModel;
1540
+ inputs.push(modelName, relName, rel, target);
1012
1541
  const entries = incoming.get(target);
1013
1542
  const entry = { modelName, relName, rel };
1014
1543
  if (entries === undefined)
@@ -1020,6 +1549,7 @@ function relationLookups(params) {
1020
1549
  const parents = new Map;
1021
1550
  for (const [name, model] of params.schema.__models) {
1022
1551
  for (const child of model.__children.values()) {
1552
+ inputs.push(child.__name, name, model);
1023
1553
  const entries = parents.get(child.__name);
1024
1554
  if (entries === undefined)
1025
1555
  parents.set(child.__name, [[name, model]]);
@@ -1027,7 +1557,35 @@ function relationLookups(params) {
1027
1557
  entries.push([name, model]);
1028
1558
  }
1029
1559
  }
1030
- return { incoming, parents };
1560
+ return { incoming, parents, inputs };
1561
+ }
1562
+ function lookupsCurrent(params) {
1563
+ const { schema, inputs } = params;
1564
+ let at = 0;
1565
+ const byModel = schema.__relations;
1566
+ for (const modelName in byModel) {
1567
+ if (!Object.hasOwn(byModel, modelName))
1568
+ continue;
1569
+ const relations = byModel[modelName];
1570
+ for (const relName in relations) {
1571
+ if (!Object.hasOwn(relations, relName))
1572
+ continue;
1573
+ const rel = relations[relName];
1574
+ if (rel.__relationType !== "one")
1575
+ continue;
1576
+ if (inputs[at] !== modelName || inputs[at + 1] !== relName || inputs[at + 2] !== rel || inputs[at + 3] !== rel.__targetModel)
1577
+ return false;
1578
+ at += 4;
1579
+ }
1580
+ }
1581
+ for (const [name, model] of schema.__models) {
1582
+ for (const child of model.__children.values()) {
1583
+ if (inputs[at] !== child.__name || inputs[at + 1] !== name || inputs[at + 2] !== model)
1584
+ return false;
1585
+ at += 3;
1586
+ }
1587
+ }
1588
+ return at === inputs.length;
1031
1589
  }
1032
1590
  function planSchema(params) {
1033
1591
  const { schema } = params;
@@ -1041,7 +1599,8 @@ function planSchema(params) {
1041
1599
  let refreshLookups = false;
1042
1600
  for (const [modelName, model] of schema.__models) {
1043
1601
  if (refreshLookups) {
1044
- lookups = relationLookups({ schema });
1602
+ if (!lookupsCurrent({ schema, inputs: lookups.inputs }))
1603
+ lookups = relationLookups({ schema });
1045
1604
  refreshLookups = false;
1046
1605
  }
1047
1606
  const explicit = planExplicitRelations({
@@ -1113,18 +1672,41 @@ function idDefault(params) {
1113
1672
  }
1114
1673
  function fieldDefault(params) {
1115
1674
  const { column } = params;
1675
+ if (column.__columnType === "localDate") {
1676
+ const { default: value, dbgenerated, autoincrement, updatedAt, array, collation } = column.__config;
1677
+ if (array === true || updatedAt === true || autoincrement === true || dbgenerated !== undefined || collation !== undefined) {
1678
+ throw new VibeError6({
1679
+ code: "VIBE_SCHEMA_INVALID",
1680
+ message: `Local-date column "${column.__name ?? "localDate"}" requires a scalar calendar day and an optional canonical YYYY-MM-DD literal default; lists, collation, updatedAt and generated defaults are not supported`,
1681
+ meta: { issues: [{ rule: "local-date-declaration", field: column.__name }] }
1682
+ });
1683
+ }
1684
+ if (value === undefined)
1685
+ return;
1686
+ const candidate = { value };
1687
+ if (!isCanonicalLocalDate(candidate))
1688
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: `Local-date default on "${column.__name ?? "localDate"}" must be a canonical YYYY-MM-DD calendar-day literal in years 0001\u20139999` });
1689
+ return { kind: "literal", value: candidate.value };
1690
+ }
1116
1691
  if (column.__columnType === "id") {
1117
- if (column.__config.default !== undefined || column.__config.dbgenerated !== undefined || column.__config.autoincrement !== undefined)
1118
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
1692
+ const strategy = column.__config.type;
1693
+ const dbgenerated = column.__config.dbgenerated;
1694
+ if (column.__config.default !== undefined || column.__config.autoincrement !== undefined)
1695
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
1696
+ if (dbgenerated !== undefined) {
1697
+ if (strategy !== "none" || typeof dbgenerated !== "string")
1698
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: `An id with a database default needs dsl.id({ type: "none" }); the "${String(strategy)}" generator strategy would conflict with it`, meta: { issues: [{ rule: "id-dbgenerated-strategy", column: column.__name, strategy }] } });
1699
+ return { kind: "dbgenerated", sql: dbgenerated };
1700
+ }
1119
1701
  return idDefault({ column });
1120
1702
  }
1121
1703
  const { dbgenerated, default: value, autoincrement } = column.__config;
1122
1704
  if ([dbgenerated !== undefined, value !== undefined, autoincrement === true].filter(Boolean).length > 1) {
1123
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
1705
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
1124
1706
  }
1125
1707
  if (autoincrement === true) {
1126
1708
  if (column.__columnType !== "int" && column.__columnType !== "bigint" || column.__config.array === true || column.__config.optional === true) {
1127
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
1709
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
1128
1710
  }
1129
1711
  return { kind: "autoincrement" };
1130
1712
  }
@@ -1134,7 +1716,7 @@ function fieldDefault(params) {
1134
1716
  return;
1135
1717
  if (column.__config.array === true) {
1136
1718
  if (!Array.isArray(value))
1137
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
1719
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
1138
1720
  return { kind: "list", values: scalarListDefault({ type: fieldType({ column }), values: column.__columnType === "json" ? value.map((entry) => typeof entry === "string" ? entry : serializeJsonDefault({ value: entry })) : value }) };
1139
1721
  }
1140
1722
  if (column.__columnType === "json" && typeof value !== "string") {
@@ -1149,20 +1731,20 @@ function fieldDefault(params) {
1149
1731
  return { kind: "enumValue", value };
1150
1732
  }
1151
1733
  if (typeof value === "number" && (!Number.isFinite(value) || (column.__columnType === "int" || column.__columnType === "bigint") && !Number.isSafeInteger(value))) {
1152
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
1734
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
1153
1735
  }
1154
1736
  if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
1155
1737
  return { kind: "literal", value };
1156
1738
  }
1157
1739
  if (typeof value === "bigint")
1158
1740
  return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
1159
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
1741
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
1160
1742
  }
1161
1743
  function datetimeNativeType(params) {
1162
1744
  const { tz, precision } = params.config;
1163
1745
  if (precision !== undefined && (tz !== true || !Number.isInteger(precision) || precision < 0 || precision > 6)) {
1164
1746
  const at = params.column === undefined ? "A datetime column" : `Column "${params.column}"`;
1165
- throw new VibeError5({
1747
+ throw new VibeError6({
1166
1748
  code: "VIBE_SCHEMA_INVALID",
1167
1749
  message: `${at}: a datetime precision must be an integer from 0 to 6 on a tz() column, as in dsl.datetime().tz({ precision: 3 })`,
1168
1750
  meta: { issues: [{ rule: "datetime-precision", column: params.column, precision, tz: tz === true }] }
@@ -1172,14 +1754,51 @@ function datetimeNativeType(params) {
1172
1754
  return;
1173
1755
  return precision === undefined ? "Timestamptz" : `Timestamptz(${precision})`;
1174
1756
  }
1757
+ function stringNativeType(params) {
1758
+ const { maxLength, text, uuid } = params.config;
1759
+ if (uuid === true) {
1760
+ if (maxLength !== undefined || text === true) {
1761
+ const at = params.column === undefined ? "A string column" : `Column "${params.column}"`;
1762
+ throw new VibeError6({
1763
+ code: "VIBE_SCHEMA_INVALID",
1764
+ message: `${at}: uuid() is the column's type and cannot be combined with maxLength or text`,
1765
+ meta: { issues: [{ rule: "string-native-type", column: params.column }] }
1766
+ });
1767
+ }
1768
+ return "Uuid";
1769
+ }
1770
+ if (typeof maxLength === "number")
1771
+ return `VarChar(${maxLength})`;
1772
+ if (text === true)
1773
+ return "Text";
1774
+ return;
1775
+ }
1776
+ function idNativeType(params) {
1777
+ if (params.config.uuid !== true)
1778
+ return;
1779
+ if (params.config.type === "autoincrement") {
1780
+ throw new VibeError6({
1781
+ code: "VIBE_SCHEMA_INVALID",
1782
+ message: `${params.column === undefined ? "An id" : `Id "${params.column}"`}: uuid() needs a String id; an autoincrement id is an Int`,
1783
+ meta: { issues: [{ rule: "id-native-type", column: params.column }] }
1784
+ });
1785
+ }
1786
+ return "Uuid";
1787
+ }
1175
1788
  function nativeType(params) {
1176
1789
  const { column } = params;
1790
+ if (column.__columnType === "localDate")
1791
+ return "Date";
1792
+ if (column.__columnType === "string")
1793
+ return stringNativeType({ config: column.__config, column: column.__name });
1794
+ if (column.__columnType === "id")
1795
+ return idNativeType({ config: column.__config, column: column.__name });
1177
1796
  if (column.__columnType === "enum") {
1178
1797
  const { maxLength, text } = column.__config;
1179
1798
  if (maxLength !== undefined || text !== undefined) {
1180
1799
  const invalid = column.__enumStorage !== "string" || maxLength !== undefined && (typeof maxLength !== "number" || !Number.isSafeInteger(maxLength) || maxLength <= 0) || text !== undefined && typeof text !== "boolean" || text === true && maxLength !== undefined;
1181
1800
  if (invalid) {
1182
- throw new VibeError5({
1801
+ throw new VibeError6({
1183
1802
  code: "VIBE_SCHEMA_INVALID",
1184
1803
  message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
1185
1804
  meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
@@ -1187,7 +1806,7 @@ function nativeType(params) {
1187
1806
  }
1188
1807
  }
1189
1808
  }
1190
- if (column.__columnType === "string" || column.__columnType === "enum" && column.__enumStorage === "string") {
1809
+ if (column.__columnType === "enum" && column.__enumStorage === "string") {
1191
1810
  const { maxLength, text } = column.__config;
1192
1811
  if (typeof maxLength === "number")
1193
1812
  return `VarChar(${maxLength})`;
@@ -1216,7 +1835,8 @@ function buildField(params) {
1216
1835
  const dbName = typeof config.map === "string" ? config.map : undefined;
1217
1836
  const documentation = typeof config.comment === "string" ? config.comment : undefined;
1218
1837
  const collation = typeof config.collation === "string" ? config.collation : undefined;
1219
- const metadata = normalizeJsonSchemaRef({ documentation, reference: config.jsonSchema });
1838
+ const validatorMetadata = normalizeValidatorRef({ documentation, reference: config.validator });
1839
+ const metadata = normalizeJsonSchemaRef({ documentation: validatorMetadata.documentation, reference: config.jsonSchema });
1220
1840
  const defaultValue = fieldDefault({ column });
1221
1841
  const native = extensionNativeType ?? nativeType({ column });
1222
1842
  return {
@@ -1230,8 +1850,10 @@ function buildField(params) {
1230
1850
  ...collation === undefined ? {} : { collation },
1231
1851
  ...dbName === undefined ? {} : { dbName },
1232
1852
  ...defaultValue === undefined ? {} : { default: defaultValue },
1853
+ ..."generated" in config ? { generated: generatedColumnFromSql2({ value: config.generated }) } : {},
1233
1854
  ...native === undefined ? {} : { nativeType: native },
1234
1855
  ...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
1856
+ ...validatorMetadata.validator === undefined ? {} : { validator: validatorMetadata.validator },
1235
1857
  ...metadata
1236
1858
  };
1237
1859
  }
@@ -1265,17 +1887,26 @@ function buildIndex(params) {
1265
1887
  if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined) && !dialect.capabilities.indexOperatorClasses)
1266
1888
  throw unsupportedCapability({ dialect: dialect.name, feature: "indexOperatorClasses" });
1267
1889
  const kind = index.isUnique ? "unique" : index.indexType === undefined ? "btree" : INDEX_KINDS[index.indexType];
1890
+ const computed = index.keys?.some((term) => term.kind !== "field") === true;
1891
+ const keys = computed ? { fields: [], expressions: index.keys } : { fields: index.keys === undefined ? index.columns.map((name) => ({ name, ...index.ops?.[name] === undefined ? {} : { operatorClass: index.ops[name] } })) : index.keys.flatMap((term) => term.kind === "field" ? [{ ...term.field, ...index.ops?.[term.field.name] === undefined ? {} : { operatorClass: index.ops[term.field.name] } }] : []) };
1892
+ if (computed) {
1893
+ const declaration = { kind, ...keys, ...index.map === undefined ? {} : { name: index.map }, ...index.clientName === undefined ? {} : { clientName: index.clientName }, ...index.nullsNotDistinct === undefined ? {} : { nullsNotDistinct: index.nullsNotDistinct } };
1894
+ assertIndexExpressions({ schema: { datasource: { provider: dialect.name === "postgres" ? "postgresql" : dialect.name }, enums, extensions: [], models: [{ ...model, indexes: [declaration], relations: [], isView: false }] }, dialect: dialect.name });
1895
+ for (const term of index.keys)
1896
+ if (term.kind === "expression")
1897
+ renderSchemaExpression({ expression: term.expression, model, dialect, enums, usage: "indexExpression", site: `expression index on ${model.name}` });
1898
+ }
1268
1899
  const predicate = compileIndexPredicate({
1269
1900
  where: index.where,
1270
1901
  wherePredicate: index.wherePredicate,
1271
- columns: index.columns,
1902
+ columns: computed ? indexKeyFieldNames({ index: keys }) ?? [] : index.columns,
1272
1903
  model,
1273
1904
  dialect,
1274
1905
  enums
1275
1906
  });
1276
1907
  return {
1277
1908
  kind,
1278
- fields: index.columns.map((name) => ({ name, ...index.ops?.[name] === undefined ? {} : { operatorClass: index.ops[name] } })),
1909
+ ...keys,
1279
1910
  ...index.map === undefined ? {} : { name: index.map },
1280
1911
  ...predicate === undefined ? {} : { where: predicate.where },
1281
1912
  ...predicate?.wherePredicate === undefined ? {} : { wherePredicate: predicate.wherePredicate },
@@ -1362,8 +1993,8 @@ function buildModel(params) {
1362
1993
  }
1363
1994
  const predicateModel = { name: model.__name, fields };
1364
1995
  const exclusions = compileExclusions({ exclusions: model.__exclusions, model: predicateModel, dialect });
1365
- const checks = compileChecks({ checks: model.__checks, model: predicateModel, dialect, enums });
1366
- return {
1996
+ const checks = compileModelChecks({ model, predicateModel, dialect, enums });
1997
+ const irModel = {
1367
1998
  name: model.__name,
1368
1999
  ...model.__map === undefined ? {} : { dbName: model.__map },
1369
2000
  fields,
@@ -1376,6 +2007,8 @@ function buildModel(params) {
1376
2007
  isView: false,
1377
2008
  ...model.__comment === undefined ? {} : { documentation: model.__comment }
1378
2009
  };
2010
+ const rowRules = compileRowRules({ model, irModel, dialect: dialect.name });
2011
+ return rowRules.length === 0 ? irModel : { ...irModel, rowRules };
1379
2012
  }
1380
2013
 
1381
2014
  class ExtensionDeclarationCollector {
@@ -1420,7 +2053,7 @@ function toIR(params) {
1420
2053
  }
1421
2054
  }
1422
2055
  if (issues.length > 0) {
1423
- throw new VibeError5({
2056
+ throw new VibeError6({
1424
2057
  code: "VIBE_SCHEMA_INVALID",
1425
2058
  message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
1426
2059
  meta: { issues }
@@ -1444,11 +2077,36 @@ function predicateModel(params) {
1444
2077
  fields: Array.from(params.model.__columns, ([name, column]) => buildField({ name, column }))
1445
2078
  };
1446
2079
  }
2080
+ function plannedAdmissionModel(params) {
2081
+ const { model, plan } = params;
2082
+ return {
2083
+ name: model.__name,
2084
+ ...model.__map === undefined ? {} : { dbName: model.__map },
2085
+ fields: [...model.__columns].map(([name, column]) => buildField({ name, column })),
2086
+ relations: (plan.relations.get(model.__name) ?? []).map((relation) => ({
2087
+ name: relation.name,
2088
+ model: relation.model,
2089
+ targetModel: relation.targetModel,
2090
+ type: resolveRelationType({ relation, plan }),
2091
+ isList: relation.isList,
2092
+ isOptional: relation.isOptional,
2093
+ ...relation.fields === undefined ? {} : { fields: relation.fields },
2094
+ ...relation.references === undefined ? {} : { references: relation.references },
2095
+ ...relation.onDelete === undefined ? {} : { onDelete: relation.onDelete },
2096
+ ...relation.onUpdate === undefined ? {} : { onUpdate: relation.onUpdate }
2097
+ })),
2098
+ ...model.__primaryKey === undefined ? {} : { primaryKey: { fields: model.__primaryKey } },
2099
+ ...model.__policy === undefined ? {} : { policy: { field: model.__policy.field, context: model.__policy.context ?? model.__policy.field } },
2100
+ indexes: [],
2101
+ isView: false
2102
+ };
2103
+ }
1447
2104
  function prismaType(params) {
1448
2105
  const { column } = params;
1449
2106
  const list = column.__config.array === true ? "[]" : "";
1450
2107
  switch (column.__columnType) {
1451
2108
  case "string":
2109
+ case "localDate":
1452
2110
  return `String${list}`;
1453
2111
  case "int":
1454
2112
  return `Int${list}`;
@@ -1501,13 +2159,11 @@ function defaultAttribute(params) {
1501
2159
  }
1502
2160
  function nativeTypeAttribute(params) {
1503
2161
  const { column } = params;
1504
- if (column.__columnType === "string") {
1505
- const { maxLength, text } = column.__config;
1506
- if (typeof maxLength === "number")
1507
- return `@db.VarChar(${maxLength})`;
1508
- if (text === true)
1509
- return "@db.Text";
1510
- return;
2162
+ if (column.__columnType === "localDate")
2163
+ return "@db.Date";
2164
+ if (column.__columnType === "string" || column.__columnType === "id") {
2165
+ const native = column.__columnType === "string" ? stringNativeType({ config: column.__config, column: column.__name }) : idNativeType({ config: column.__config, column: column.__name });
2166
+ return native === undefined ? undefined : `@db.${native}`;
1511
2167
  }
1512
2168
  if (column.__columnType === "decimal") {
1513
2169
  const { precision, scale } = column.__config;
@@ -1534,6 +2190,10 @@ function formatColumn(params) {
1534
2190
  if (columnDefault !== undefined)
1535
2191
  parts.push(columnDefault);
1536
2192
  }
2193
+ if ("generated" in column.__config) {
2194
+ const generated = generatedColumnFromSql3({ value: column.__config.generated });
2195
+ parts.push(`@generated(sql: ${encodePrismaString2({ value: generated.expression })}, storage: "stored")`);
2196
+ }
1537
2197
  if (column.__columnType === "datetime" && column.__config.updatedAt === true) {
1538
2198
  parts.push("@updatedAt");
1539
2199
  }
@@ -1550,13 +2210,17 @@ function formatColumn(params) {
1550
2210
  parts.push(`@collation(${encodePrismaString2({ value: column.__config.collation })})`);
1551
2211
  }
1552
2212
  const columnLine = parts.join(" ");
1553
- const metadata = normalizeJsonSchemaRef2({ documentation: typeof column.__config.comment === "string" ? column.__config.comment : undefined, reference: column.__config.jsonSchema });
2213
+ const validatorMetadata = normalizeValidatorRef2({ documentation: typeof column.__config.comment === "string" ? column.__config.comment : undefined, reference: column.__config.validator });
2214
+ const metadata = normalizeJsonSchemaRef2({ documentation: validatorMetadata.documentation, reference: column.__config.jsonSchema });
2215
+ if (validatorMetadata.validator !== undefined && (column.__columnType === "json" || column.__columnType === "unsupported" || metadata.jsonSchema !== undefined))
2216
+ throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "Validators are only valid on queryable non-JSON scalar or enum columns; use .schema() for Json", meta: { issues: [{ rule: "validator-non-scalar", field: name }] } });
1554
2217
  if (metadata.jsonSchema !== undefined && column.__columnType !== "json")
1555
- throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "JSON schemas are only valid on Json fields", meta: { issues: [{ rule: "json-schema-non-json", field: name }] } });
2218
+ throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "JSON schemas are only valid on Json fields", meta: { issues: [{ rule: "json-schema-non-json", field: name }] } });
1556
2219
  const comments = [
1557
2220
  ...metadata.documentation === undefined ? [] : metadata.documentation.split(`
1558
2221
  `),
1559
- ...metadata.jsonSchema === undefined ? [] : [`@schema(${metadata.jsonSchema.name} from ${encodePrismaString2({ value: metadata.jsonSchema.from })})`]
2222
+ ...metadata.jsonSchema === undefined ? [] : [`@schema(${metadata.jsonSchema.name} from ${encodePrismaString2({ value: metadata.jsonSchema.from })})`],
2223
+ ...validatorMetadata.validator === undefined ? [] : [`@validate(${validatorMetadata.validator.name} from ${encodePrismaString2({ value: validatorMetadata.validator.from })})`]
1560
2224
  ];
1561
2225
  return [...comments.map((line) => ` /// ${line}`), columnLine].join(`
1562
2226
  `);
@@ -1596,8 +2260,8 @@ function formatRelation(params) {
1596
2260
  function formatIndex(params) {
1597
2261
  const { index } = params;
1598
2262
  if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined))
1599
- throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Prisma output cannot preserve qualified index operator classes; keep the DSL source", meta: { feature: "prismaIndexOperatorClasses", target: "prisma" } });
1600
- const columns = index.columns.join(", ");
2263
+ throw new VibeError7({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Prisma output cannot preserve qualified index operator classes; keep the DSL source", meta: { feature: "prismaIndexOperatorClasses", target: "prisma" } });
2264
+ const columns = index.keys === undefined ? index.columns.join(", ") : indexKeyTerms({ index: buildIndex({ index, model: predicateModel({ model: params.model }), dialect: params.dialect, enums: params.enums }) }).map((term) => indexKeyPrisma({ term })).join(", ");
1601
2265
  const predicate = compileIndexPredicate({
1602
2266
  where: index.where,
1603
2267
  wherePredicate: index.wherePredicate,
@@ -1647,14 +2311,24 @@ function formatModel(params) {
1647
2311
  for (const index of plan.indexes.get(model.__name) ?? []) {
1648
2312
  lines.push(formatIndex({ index, model: params.model, dialect: params.dialect, enums: params.enums }));
1649
2313
  }
1650
- for (const check of compileChecks({
1651
- checks: model.__checks,
1652
- model: predicateModel({ model: params.model }),
2314
+ for (const check of compileModelChecks({
2315
+ model: params.model,
2316
+ predicateModel: predicateModel({ model: params.model }),
1653
2317
  dialect: params.dialect,
1654
2318
  enums: params.enums
1655
2319
  })) {
1656
2320
  lines.push(` @@check(${encodePrismaString2({ value: check.expression })}, name: ${encodePrismaString2({ value: check.name })})`);
1657
2321
  }
2322
+ const irModel = plannedAdmissionModel({ model, plan });
2323
+ const rowRules = compileRowRules({ model, irModel, dialect: params.dialect.name });
2324
+ assertRowRuleReferentialActions({ schema: {
2325
+ datasource: { provider: params.dialect.name === "postgres" ? "postgresql" : params.dialect.name },
2326
+ enums: params.enums,
2327
+ extensions: [],
2328
+ models: [{ ...irModel, rowRules }]
2329
+ }, dialect: params.dialect.name });
2330
+ for (const rule of rowRules)
2331
+ lines.push(` ${rowRulePrismaAttribute({ rule })}`);
1658
2332
  for (const use of model.__extensions ?? []) {
1659
2333
  const args = [];
1660
2334
  if (use.fields !== undefined)
@@ -1688,24 +2362,31 @@ function generatePrisma(schema, config = {}) {
1688
2362
  for (const model of schema.__models.values()) {
1689
2363
  const exclusions = compileExclusions({ exclusions: model.__exclusions, model: { name: model.__name, fields: Array.from(model.__columns, ([name, column]) => buildField({ name, column })) }, dialect: getSqlDialect2({ dialect: providerToDialect2({ provider: config.provider ?? "postgresql" }) }) });
1690
2364
  if (exclusions.length > 0)
1691
- throw new VibeError6({ code: "VIBE_CONFIG", message: "Prisma output cannot preserve exclusion constraints; keep the DSL source", meta: { feature: "exclusionConstraints", target: "prisma" } });
2365
+ throw new VibeError7({ code: "VIBE_CONFIG", message: "Prisma output cannot preserve exclusion constraints; keep the DSL source", meta: { feature: "exclusionConstraints", target: "prisma" } });
1692
2366
  }
1693
2367
  const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
1694
2368
  if (stringEnums.length > 0) {
1695
- throw new VibeError6({
2369
+ throw new VibeError7({
1696
2370
  code: "VIBE_CONFIG",
1697
2371
  message: `Cannot print to Prisma: enum(s) ${stringEnums.join(", ")} use storage: "string", which .prisma cannot express losslessly. Keep the DSL as the source or switch the enum to database storage.`,
1698
2372
  meta: { enums: stringEnums, target: "prisma" }
1699
2373
  });
1700
2374
  }
1701
2375
  if (schema.__rls !== undefined) {
1702
- throw new VibeError6({
2376
+ throw new VibeError7({
1703
2377
  code: "VIBE_CONFIG",
1704
2378
  message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
1705
2379
  meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
1706
2380
  });
1707
2381
  }
1708
2382
  const plan = planSchema({ schema });
2383
+ if ([...schema.__models.values()].some((model) => [...model.__columns.values()].some((column) => ("generated" in column.__config))))
2384
+ assertGeneratedColumns({ schema: {
2385
+ datasource: { provider: config.provider ?? "postgresql" },
2386
+ enums: [],
2387
+ extensions: [],
2388
+ models: [...schema.__models.values()].map((model) => plannedAdmissionModel({ model, plan }))
2389
+ } });
1709
2390
  const sections = [];
1710
2391
  const dialect = getSqlDialect2({ dialect: providerToDialect2({ provider: config.provider ?? "postgresql" }) });
1711
2392
  const enums = Array.from(schema.__enums, ([name, values]) => ({ name, values: [...values] }));
@@ -1732,9 +2413,10 @@ function generatePrisma(schema, config = {}) {
1732
2413
  }
1733
2414
 
1734
2415
  // packages/dsl/src/relations.ts
1735
- import { VibeError as VibeError7 } from "@vibeorm/schema";
2416
+ import { VibeError as VibeError8 } from "@vibeorm/schema";
1736
2417
  var REF_KIND_BY_COLUMN_TYPE = {
1737
2418
  string: "String",
2419
+ localDate: "String",
1738
2420
  int: "Int",
1739
2421
  bigint: "BigInt",
1740
2422
  float: "Float",
@@ -1773,7 +2455,7 @@ function extractRefInfo(params) {
1773
2455
  }
1774
2456
  const members = ref;
1775
2457
  if (members.length === 0) {
1776
- throw new VibeError7({
2458
+ throw new VibeError8({
1777
2459
  code: "VIBE_SCHEMA_INVALID",
1778
2460
  message: `Relation to ${target}: "${side}" is an empty tuple \u2014 name at least one column`,
1779
2461
  meta: { rule: "relation-empty-tuple", side, targetModel: target }
@@ -1781,14 +2463,14 @@ function extractRefInfo(params) {
1781
2463
  }
1782
2464
  const models = [...new Set(members.map((member) => member.__model))].sort();
1783
2465
  if (models.length > 1) {
1784
- throw new VibeError7({
2466
+ throw new VibeError8({
1785
2467
  code: "VIBE_SCHEMA_INVALID",
1786
2468
  message: `Relation to ${target}: "${side}" mixes models ${models.join(", ")} \u2014 every member of a composite key must sit on one model`,
1787
2469
  meta: { rule: "relation-mixed-models", side, targetModel: target, models }
1788
2470
  });
1789
2471
  }
1790
2472
  if (members.some((member) => member.__type !== "columnRef")) {
1791
- throw new VibeError7({
2473
+ throw new VibeError8({
1792
2474
  code: "VIBE_SCHEMA_INVALID",
1793
2475
  message: `Relation to ${target}: "${side}" routes through a junction inside a tuple \u2014 implicit many-to-many is single-key; model the join with an explicit junction model`,
1794
2476
  meta: { rule: "relation-through-in-tuple", side, targetModel: target }
@@ -1802,7 +2484,7 @@ function createRelationFactory(params) {
1802
2484
  const from = extractRefInfo({ ref: config.from, side: "from", target: targetModelName });
1803
2485
  const to = extractRefInfo({ ref: config.to, side: "to", target: targetModelName });
1804
2486
  if (from.columns.length !== to.columns.length) {
1805
- throw new VibeError7({
2487
+ throw new VibeError8({
1806
2488
  code: "VIBE_SCHEMA_INVALID",
1807
2489
  message: `Relation to ${targetModelName}: "from" names ${from.columns.length} column(s) but "to" names ${to.columns.length} \u2014 a composite key pairs member by member`,
1808
2490
  meta: { rule: "relation-tuple-arity", targetModel: targetModelName, from: from.columns, to: to.columns }
@@ -1900,7 +2582,7 @@ function mergeRelations(...parts) {
1900
2582
  }
1901
2583
 
1902
2584
  // packages/dsl/src/schema.ts
1903
- import { VibeError as VibeError8, validateRlsContextIR } from "@vibeorm/schema";
2585
+ import { VibeError as VibeError9, validateRlsContextIR } from "@vibeorm/schema";
1904
2586
  function sameValues(left, right) {
1905
2587
  return left.length === right.length && left.every((value, index) => value === right[index]);
1906
2588
  }
@@ -1919,7 +2601,7 @@ function registerEnum(params) {
1919
2601
  ...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
1920
2602
  ...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
1921
2603
  ].join("; ");
1922
- throw new VibeError8({
2604
+ throw new VibeError9({
1923
2605
  code: "VIBE_SCHEMA_INVALID",
1924
2606
  message: `Enum "${name}" is defined more than once with conflicting definitions (${origin}): ${detail} \u2014 share one enum object across modules, or give the definitions different names`,
1925
2607
  meta: {
@@ -1945,7 +2627,7 @@ function addModelTree(params) {
1945
2627
  const { model, models, origin } = params;
1946
2628
  const existing = models.get(model.__name);
1947
2629
  if (existing !== undefined && existing !== model) {
1948
- throw new VibeError8({
2630
+ throw new VibeError9({
1949
2631
  code: "VIBE_SCHEMA_INVALID",
1950
2632
  message: `Model "${model.__name}" is defined more than once with different definitions (${origin}) \u2014 ` + `share one model object across modules, or give the definitions different names`,
1951
2633
  meta: {
@@ -2023,7 +2705,7 @@ var RLS_CONTEXT_TYPES = {
2023
2705
  };
2024
2706
  function defineRls(params) {
2025
2707
  if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
2026
- throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
2708
+ throw new VibeError9({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
2027
2709
  }
2028
2710
  const entries = [];
2029
2711
  const refs = [];
@@ -2031,7 +2713,7 @@ function defineRls(params) {
2031
2713
  const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
2032
2714
  const column = descriptor?.value;
2033
2715
  if (typeof key !== "string" || descriptor?.enumerable !== true || typeof column !== "object" || column === null || !("__columnType" in column) || !("__config" in column) || typeof column.__config !== "object" || column.__config === null || typeof column.__columnType !== "string" || !Object.hasOwn(RLS_CONTEXT_TYPES, column.__columnType) || "array" in column.__config && column.__config.array === true) {
2034
- throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
2716
+ throw new VibeError9({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
2035
2717
  }
2036
2718
  entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
2037
2719
  refs.push([key, Object.freeze({ context: key })]);
@@ -2039,22 +2721,22 @@ function defineRls(params) {
2039
2721
  const context = Object.freeze(Object.fromEntries(entries));
2040
2722
  const issues = validateRlsContextIR({ context });
2041
2723
  if (issues.length > 0) {
2042
- throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
2724
+ throw new VibeError9({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
2043
2725
  }
2044
2726
  const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
2045
2727
  if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
2046
- throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
2728
+ throw new VibeError9({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
2047
2729
  }
2048
2730
  const named = [];
2049
2731
  const classified = new Set;
2050
2732
  for (const key of Reflect.ownKeys(policies)) {
2051
2733
  const descriptor = Object.getOwnPropertyDescriptor(policies, key);
2052
2734
  if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
2053
- throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
2735
+ throw new VibeError9({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
2054
2736
  }
2055
2737
  const name = params.models[key]?.__name ?? key;
2056
2738
  if (classified.has(name)) {
2057
- throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
2739
+ throw new VibeError9({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
2058
2740
  }
2059
2741
  classified.add(name);
2060
2742
  named.push([name, descriptor.value]);
@@ -2297,6 +2979,7 @@ function enumFactory(config) {
2297
2979
  var generate = Object.assign((schemaDef) => ({ prisma: generatePrisma(schemaDef) }), { prisma: generatePrisma });
2298
2980
  var dsl = {
2299
2981
  string,
2982
+ localDate,
2300
2983
  int,
2301
2984
  bigint,
2302
2985
  float,
@@ -2326,9 +3009,11 @@ export {
2326
3009
  ChainableDatetimeColumn,
2327
3010
  ChainableDecimalColumn,
2328
3011
  ChainableEnumColumn,
3012
+ ChainableFloatColumn,
2329
3013
  ChainableIdColumn,
2330
3014
  ChainableIntegerColumn,
2331
3015
  ChainableJsonColumn,
3016
+ ChainableNumericColumn,
2332
3017
  ChainableStringColumn,
2333
3018
  base,
2334
3019
  baseWithWorkspace,
@@ -2336,11 +3021,15 @@ export {
2336
3021
  cloneColumnDef,
2337
3022
  compileChecks,
2338
3023
  compileIndexPredicate,
3024
+ compileModelChecks,
3025
+ compileRowRules,
2339
3026
  create,
3027
+ createChecksBuilder,
2340
3028
  createChildrenBuilder,
2341
3029
  createEnum,
2342
3030
  createIndexBuilder,
2343
3031
  createPredicateBuilder,
3032
+ createRowRuleBuilder,
2344
3033
  defineRelations,
2345
3034
  defineRelationsPart,
2346
3035
  defineRls,
@@ -2352,6 +3041,7 @@ export {
2352
3041
  fields,
2353
3042
  generatePrisma,
2354
3043
  getColumnNames,
3044
+ localDate,
2355
3045
  mergeRelations,
2356
3046
  planSchema,
2357
3047
  resolveRelationType,
@@ -2366,4 +3056,4 @@ export {
2366
3056
  withNameDescription
2367
3057
  };
2368
3058
 
2369
- //# debugId=502F7823C49420A564756E2164756E21
3059
+ //# debugId=7EE10300C00F60DE64756E2164756E21