@vibeorm/dsl 4.2.0 → 5.0.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 {
@@ -80,9 +132,15 @@ class ChainableStringColumn extends ChainableColumn {
80
132
  this.__config.collation = name;
81
133
  return this;
82
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
+ }
83
141
  }
84
142
 
85
- class ChainableDecimalColumn extends ChainableColumn {
143
+ class ChainableDecimalColumn extends ChainableNumericColumn {
86
144
  constructor(config = {}) {
87
145
  super("decimal", config);
88
146
  }
@@ -145,22 +203,34 @@ class ChainableEnumColumn extends ChainableColumn {
145
203
  this.__config.default = value;
146
204
  return this;
147
205
  }
206
+ oneOf(values, options) {
207
+ return this.addCheckRule({ rule: "allowed", check: { kind: "oneOf", values: [...values] }, options });
208
+ }
148
209
  }
149
210
  function string(config = {}) {
150
211
  return new ChainableStringColumn(config);
151
212
  }
152
213
 
153
- class ChainableIntegerColumn extends ChainableColumn {
214
+ class ChainableIntegerColumn extends ChainableNumericColumn {
154
215
  autoincrement() {
155
216
  this.__config.autoincrement = true;
156
217
  return this;
157
218
  }
158
219
  }
220
+ function localDate({ ...config } = {}) {
221
+ return new ChainableColumn("localDate", config);
222
+ }
159
223
  function int(config = {}) {
160
224
  return new ChainableIntegerColumn("int", config);
161
225
  }
226
+
227
+ class ChainableFloatColumn extends ChainableNumericColumn {
228
+ constructor(config = {}) {
229
+ super("float", config);
230
+ }
231
+ }
162
232
  function float(config = {}) {
163
- return new ChainableColumn("float", config);
233
+ return new ChainableFloatColumn(config);
164
234
  }
165
235
  function decimal(config = {}) {
166
236
  return new ChainableDecimalColumn(config);
@@ -177,6 +247,8 @@ class ChainableJsonColumn extends ChainableColumn {
177
247
  super("json", { ...config, ...config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: config.jsonSchema }) } });
178
248
  }
179
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" }] } });
180
252
  if (this.__config.jsonSchema !== undefined)
181
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" }] } });
182
254
  this.__config.jsonSchema = assertJsonSchemaRef({ value: reference });
@@ -243,11 +315,66 @@ function createEnum(config) {
243
315
  }
244
316
 
245
317
  // packages/dsl/src/generator/prisma.ts
246
- 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";
247
319
  import { getSqlDialect as getSqlDialect2 } from "@vibeorm/sql";
248
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
+
249
376
  // packages/dsl/src/predicate.ts
250
- import { VibeError as VibeError2 } from "@vibeorm/schema";
377
+ import { IDENTIFIER_LIMITS, VibeError as VibeError3, checkRulePredicate, identifierLength, isEnumType } from "@vibeorm/schema";
251
378
  import { compileExclusion, renderPredicate } from "@vibeorm/sql";
252
379
  function isFieldExpr(operand) {
253
380
  return typeof operand === "object" && operand !== null && "kind" in operand && operand.kind === "field";
@@ -262,7 +389,7 @@ function toValue(params) {
262
389
  if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
263
390
  return { kind: "value", value: input };
264
391
  }
265
- throw new VibeError2({
392
+ throw new VibeError3({
266
393
  code: "VIBE_SCHEMA_INVALID",
267
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`,
268
395
  meta: { rule: "predicate-literal-invalid", at }
@@ -280,17 +407,28 @@ function toExpression(params) {
280
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"))) {
281
408
  return { kind: "jsonText", field: toRef({ expr: requireFieldExpr({ value: value.field, at }) }), path: [...value.path] };
282
409
  }
283
- if (value.kind === "length" && "expression" in value) {
284
- 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
+ };
285
420
  }
286
421
  }
287
- 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);
288
426
  }
289
427
  function requireFieldExpr(params) {
290
428
  const { value, at } = params;
291
429
  if (isFieldExpr(value))
292
430
  return value;
293
- throw new VibeError2({
431
+ throw new VibeError3({
294
432
  code: "VIBE_SCHEMA_INVALID",
295
433
  message: `${at}: the left side of a comparison must be c.field("\u2026"), not a bare value`,
296
434
  meta: { rule: "predicate-operand-invalid", at }
@@ -299,7 +437,7 @@ function requireFieldExpr(params) {
299
437
  function requireOperands(params) {
300
438
  const { operands, at } = params;
301
439
  if (operands.length === 0) {
302
- throw new VibeError2({
440
+ throw new VibeError3({
303
441
  code: "VIBE_SCHEMA_INVALID",
304
442
  message: `${at}: an empty boolean group has no honest SQL spelling \u2014 pass at least one operand`,
305
443
  meta: { rule: "predicate-empty", at }
@@ -318,7 +456,7 @@ function createPredicateBuilder() {
318
456
  const at = negate ? "c.notInList()" : "c.inList()";
319
457
  const ref = toRef({ expr: requireFieldExpr({ value: field, at }) });
320
458
  if (values.length === 0) {
321
- throw new VibeError2({
459
+ throw new VibeError3({
322
460
  code: "VIBE_SCHEMA_INVALID",
323
461
  message: `${at}: an empty IN list is UNKNOWN on every row and is never what an author means`,
324
462
  meta: { rule: "predicate-empty", at, field: ref.name }
@@ -339,6 +477,11 @@ function createPredicateBuilder() {
339
477
  gte: compare("gte"),
340
478
  lt: compare("lt"),
341
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" }),
342
485
  jsonText: ({ field, path }) => ({ ...toExpression({ value: { kind: "jsonText", field: { kind: "field", name: field }, path }, at: "c.jsonText()" }), __kind: "String" }),
343
486
  length: (expression) => ({ kind: "length", expression: toExpression({ value: expression, at: "c.length()" }), __kind: "Int" }),
344
487
  isTrue: (operand) => ({ kind: "isTrue", operand }),
@@ -351,7 +494,7 @@ function createPredicateBuilder() {
351
494
  not: (operand) => ({ kind: "not", operand }),
352
495
  named: (name, predicate) => {
353
496
  if (name.trim() === "") {
354
- throw new VibeError2({
497
+ throw new VibeError3({
355
498
  code: "VIBE_SCHEMA_INVALID",
356
499
  message: "c.named(): a CHECK needs a non-empty name \u2014 the name is what keys the migration diff",
357
500
  meta: { rule: "predicate-unnamed-check" }
@@ -361,6 +504,262 @@ function createPredicateBuilder() {
361
504
  }
362
505
  };
363
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
+ }
364
763
  function evaluateChecks(params) {
365
764
  const { checks, model } = params;
366
765
  if (checks === undefined)
@@ -368,28 +767,61 @@ function evaluateChecks(params) {
368
767
  if (typeof checks !== "function") {
369
768
  return checks.map((check) => ({ kind: "raw", name: check.name, expression: check.expression }));
370
769
  }
371
- const declared = checks(createPredicateBuilder());
770
+ const builder = params.context === undefined ? contextlessChecksBuilder({ model }) : createChecksBuilder({ context: params.context });
771
+ const declared = checks(builder);
372
772
  return declared.map((entry, index) => {
373
- 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") {
374
774
  return { kind: "raw", name: entry.name, expression: entry.expression };
375
775
  }
376
- if (entry === null || typeof entry !== "object" || !("__type" in entry) || entry.__type !== "check") {
377
- throw new VibeError2({
776
+ if (Array.isArray(entry)) {
777
+ throw new VibeError3({
378
778
  code: "VIBE_SCHEMA_INVALID",
379
- message: `Model "${model}": checks[${index}] must be c.named("name", predicate) or a raw { name, expression } CHECK`,
779
+ message: `Model "${model}": checks[${index}] is a list \u2014 spread c.presence(...) into the checks: [...c.presence({ \u2026 })]`,
380
780
  meta: { rule: "predicate-unnamed-check", model, index }
381
781
  });
382
782
  }
383
- return { kind: "typed", name: entry.name, predicate: entry.predicate };
783
+ if (!isRuleCheck(entry)) {
784
+ throw new VibeError3({
785
+ code: "VIBE_SCHEMA_INVALID",
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`,
787
+ meta: { rule: "predicate-unnamed-check", model, index }
788
+ });
789
+ }
790
+ return typedCheck({ entry });
384
791
  });
385
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
+ }
386
811
  function compileExclusions(params) {
387
812
  const specs = typeof params.exclusions === "function" ? params.exclusions(createPredicateBuilder()) : params.exclusions ?? [];
388
813
  return specs.map((exclusion) => compileExclusion({ exclusion, model: params.model, dialect: params.dialect }));
389
814
  }
390
815
  function compileChecks(params) {
391
816
  const { checks, model, dialect, enums } = params;
392
- 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 } : {
393
825
  name: check.name,
394
826
  expression: renderPredicate({
395
827
  predicate: check.predicate,
@@ -397,15 +829,50 @@ function compileChecks(params) {
397
829
  dialect,
398
830
  enums,
399
831
  usage: "check",
400
- site: `CHECK "${check.name}"`
832
+ site: check.origin === undefined ? `CHECK "${check.name}"` : `CHECK "${check.name}" (${check.origin})`
401
833
  }),
402
834
  predicate: check.predicate
403
835
  });
404
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
+ }
405
872
  function compileIndexPredicate(params) {
406
873
  const { where, wherePredicate, columns, model, dialect, enums } = params;
407
874
  if (where !== undefined && wherePredicate !== undefined) {
408
- throw new VibeError2({
875
+ throw new VibeError3({
409
876
  code: "VIBE_SCHEMA_INVALID",
410
877
  message: `Index on "${model.name}" [${columns.join(", ")}] carries both a raw where: string and a typed .where() \u2014 an index has ONE predicate`,
411
878
  meta: { rule: "predicate-duplicate-where", model: model.name, columns: [...columns] }
@@ -431,17 +898,22 @@ function compileIndexPredicate(params) {
431
898
  // packages/dsl/src/to-ir.ts
432
899
  import {
433
900
  EMPTY_EXTENSION_REGISTRY,
434
- VibeError as VibeError5,
901
+ VibeError as VibeError6,
435
902
  assertValidSchemaIR,
903
+ assertIndexExpressions,
904
+ indexKeyFieldNames,
436
905
  exactIntegerLiteral,
906
+ generatedColumnFromSql as generatedColumnFromSql2,
907
+ isCanonicalLocalDate,
437
908
  providerToDialect,
438
909
  scalarListDefault,
439
910
  serializeJsonDefault,
440
911
  mergeExtensionDeclarations,
441
912
  normalizeJsonSchemaRef,
913
+ normalizeValidatorRef,
442
914
  unsupportedCapability
443
915
  } from "@vibeorm/schema";
444
- import { getSqlDialect } from "@vibeorm/sql";
916
+ import { getSqlDialect, renderSchemaExpression } from "@vibeorm/sql";
445
917
 
446
918
  // packages/dsl/src/field-type.ts
447
919
  import { fieldTypeIdentity } from "@vibeorm/schema";
@@ -449,6 +921,7 @@ function columnFieldType(params) {
449
921
  const { column } = params;
450
922
  switch (column.__columnType) {
451
923
  case "string":
924
+ case "localDate":
452
925
  return "String";
453
926
  case "int":
454
927
  return "Int";
@@ -475,14 +948,17 @@ function columnFieldType(params) {
475
948
  }
476
949
  }
477
950
  function fieldTypeKey(params) {
478
- return fieldTypeIdentity({ type: columnFieldType({ column: params.column }) });
951
+ return fieldTypeIdentity({
952
+ type: columnFieldType({ column: params.column }),
953
+ ...params.column.__columnType === "localDate" ? { nativeType: "Date" } : {}
954
+ });
479
955
  }
480
956
 
481
957
  // packages/dsl/src/relation-plan.ts
482
- import { VibeError as VibeError4, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
958
+ import { VibeError as VibeError5, indexKeyDefinitionKey, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
483
959
 
484
960
  // packages/dsl/src/model.ts
485
- import { VibeError as VibeError3 } from "@vibeorm/schema";
961
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
486
962
 
487
963
  // packages/dsl/src/naming.ts
488
964
  function capitalize(params) {
@@ -524,7 +1000,7 @@ function createIndexBuilder() {
524
1000
  const partial = (index) => {
525
1001
  const where = (predicate) => {
526
1002
  if (index.__where !== undefined) {
527
- throw new VibeError3({
1003
+ throw new VibeError4({
528
1004
  code: "VIBE_SCHEMA_INVALID",
529
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`,
530
1006
  meta: { rule: "predicate-duplicate-where", columns: [...index.__columns] }
@@ -539,53 +1015,92 @@ function createIndexBuilder() {
539
1015
  configurable: true
540
1016
  });
541
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();
542
1046
  const createIndex = (params) => ({
543
1047
  __type: "index",
544
- __columns: [...params.columns],
1048
+ ...keyTerms({ keys: params.keys }),
545
1049
  __indexType: params.type,
546
1050
  ...params.map === undefined ? {} : { __map: params.map },
547
1051
  ...params.where === undefined ? {} : { __where: params.where },
548
1052
  ...params.ops === undefined ? {} : { __ops: params.ops }
549
1053
  });
550
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 }),
551
1064
  on(...args) {
552
1065
  const [first, second] = args;
553
1066
  if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
554
- 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 }));
555
1069
  }
556
- return partial(createIndex({ columns: args }));
1070
+ return partial(createIndex({ keys: args }));
557
1071
  },
558
- index(...columns) {
559
- return partial(createIndex({ columns }));
1072
+ index(...keys) {
1073
+ return partial(createIndex({ keys }));
560
1074
  },
561
1075
  unique(...args) {
562
1076
  const [first, second] = args;
563
1077
  if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
564
1078
  if ("ops" in second && second.ops !== undefined) {
565
- 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" } });
566
1080
  }
1081
+ const options = second;
567
1082
  return partial({
568
1083
  __type: "unique",
569
- __columns: [...first],
570
- ...second.map === undefined ? {} : { __map: second.map },
571
- ...second.where === undefined ? {} : { __where: second.where },
572
- ...second.nullsNotDistinct === true ? { __nullsNotDistinct: true } : {},
573
- ...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 }
574
1089
  });
575
1090
  }
576
- return partial({ __type: "unique", __columns: [...args] });
1091
+ return partial({ __type: "unique", ...keyTerms({ keys: args }) });
577
1092
  },
578
1093
  gin(...columns) {
579
- return partial(createIndex({ columns, type: "Gin" }));
1094
+ return partial(createIndex({ keys: columns, type: "Gin" }));
580
1095
  },
581
1096
  gist(...columns) {
582
- return partial(createIndex({ columns, type: "Gist" }));
1097
+ return partial(createIndex({ keys: columns, type: "Gist" }));
583
1098
  },
584
1099
  hash(...columns) {
585
- return partial(createIndex({ columns, type: "Hash" }));
1100
+ return partial(createIndex({ keys: columns, type: "Hash" }));
586
1101
  },
587
1102
  brin(...columns) {
588
- return partial(createIndex({ columns, type: "Brin" }));
1103
+ return partial(createIndex({ keys: columns, type: "Brin" }));
589
1104
  }
590
1105
  };
591
1106
  }
@@ -629,6 +1144,7 @@ function create(config) {
629
1144
  __primaryKey: config.primaryKey,
630
1145
  __exclusions: config.exclusions,
631
1146
  __checks: config.checks,
1147
+ __rowRules: config.rowRules,
632
1148
  __extensions: config.extensions,
633
1149
  __policy: config.policy,
634
1150
  __comment: config.comment
@@ -657,7 +1173,7 @@ function detectOneToOnePairs(params) {
657
1173
  for (const [modelName, modelRelations] of Object.entries(relations)) {
658
1174
  for (const [relName, rel] of Object.entries(modelRelations)) {
659
1175
  if (rel.__ownsForeignKey !== undefined && (typeof rel.__ownsForeignKey !== "boolean" || rel.__relationType !== "one" || rel.__ownsForeignKey && rel.__fromModel !== modelName)) {
660
- 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}` });
661
1177
  }
662
1178
  if (rel.__relationType === "one") {
663
1179
  allOnes.push({ modelName, relName, rel });
@@ -732,7 +1248,7 @@ function sameColumnTuple(params) {
732
1248
  function resolveFkOwner(params) {
733
1249
  const { a, b, models } = params;
734
1250
  const ownershipError = () => {
735
- 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" } });
736
1252
  };
737
1253
  const aOwns = a.rel.__ownsForeignKey;
738
1254
  const bOwns = b.rel.__ownsForeignKey;
@@ -901,28 +1417,31 @@ function planIndexes(params) {
901
1417
  }
902
1418
  }
903
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 } });
904
1422
  if (index.__ops !== undefined) {
905
1423
  if (index.__ops === null || typeof index.__ops !== "object" || Array.isArray(index.__ops))
906
- 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 } });
907
1425
  if (index.__type === "unique")
908
- 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 } });
909
1427
  for (const [field, operatorClass] of Object.entries(index.__ops)) {
910
1428
  if (!index.__columns.includes(field))
911
- 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 } });
912
1430
  if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass }))
913
- 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 } });
914
1432
  }
915
1433
  }
916
1434
  const operatorClasses = index.__columns.map((field) => index.__ops?.[field]);
917
1435
  const operatorSuffix = operatorClasses.some((operatorClass) => operatorClass !== undefined) ? `|ops:${JSON.stringify([index.__indexType ?? "BTree", operatorClasses.map((operatorClass) => operatorClass === undefined ? null : [operatorClass.namespace, operatorClass.name])])}` : "";
918
1436
  const predicateSuffix = index.__where !== undefined ? `|where:${index.__where}` : index.__wherePredicate === undefined ? "" : `|where:${predicateKey({ predicate: index.__wherePredicate })}`;
919
- 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)}`;
920
1438
  const existing = byKey.get(key);
921
1439
  const isUnique = index.__type === "unique";
922
1440
  if (existing === undefined || isUnique) {
923
1441
  byKey.set(key, {
924
1442
  isUnique,
925
1443
  columns: [...index.__columns],
1444
+ ...index.__keys === undefined ? {} : { keys: index.__keys },
926
1445
  indexType: index.__indexType,
927
1446
  ...index.__ops === undefined ? {} : { ops: index.__ops },
928
1447
  ...index.__map === undefined ? {} : { map: index.__map },
@@ -949,7 +1468,7 @@ function referenceableKeys(params) {
949
1468
  if (model.__primaryKey !== undefined && model.__primaryKey.length > 0)
950
1469
  keys.push([...model.__primaryKey]);
951
1470
  for (const index of evaluateIndexes({ model })) {
952
- 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) {
953
1472
  keys.push([...index.__columns]);
954
1473
  }
955
1474
  }
@@ -961,7 +1480,7 @@ function assertForeignKeyIntegrity(params) {
961
1480
  const references = relation.references ?? [];
962
1481
  const at = `${relation.model}.${relation.name}`;
963
1482
  const refuse = (message, rule, meta = {}) => {
964
- throw new VibeError4({
1483
+ throw new VibeError5({
965
1484
  code: "VIBE_SCHEMA_INVALID",
966
1485
  message: `Relation ${at}: ${message}`,
967
1486
  meta: { rule, model: relation.model, relation: relation.name, ...meta }
@@ -1011,12 +1530,14 @@ function assertForeignKeyIntegrity(params) {
1011
1530
  }
1012
1531
  }
1013
1532
  function relationLookups(params) {
1533
+ const inputs = [];
1014
1534
  const incoming = new Map;
1015
1535
  for (const [modelName, relations] of Object.entries(params.schema.__relations)) {
1016
1536
  for (const [relName, rel] of Object.entries(relations)) {
1017
1537
  if (rel.__relationType !== "one")
1018
1538
  continue;
1019
1539
  const target = rel.__targetModel;
1540
+ inputs.push(modelName, relName, rel, target);
1020
1541
  const entries = incoming.get(target);
1021
1542
  const entry = { modelName, relName, rel };
1022
1543
  if (entries === undefined)
@@ -1028,6 +1549,7 @@ function relationLookups(params) {
1028
1549
  const parents = new Map;
1029
1550
  for (const [name, model] of params.schema.__models) {
1030
1551
  for (const child of model.__children.values()) {
1552
+ inputs.push(child.__name, name, model);
1031
1553
  const entries = parents.get(child.__name);
1032
1554
  if (entries === undefined)
1033
1555
  parents.set(child.__name, [[name, model]]);
@@ -1035,7 +1557,35 @@ function relationLookups(params) {
1035
1557
  entries.push([name, model]);
1036
1558
  }
1037
1559
  }
1038
- 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;
1039
1589
  }
1040
1590
  function planSchema(params) {
1041
1591
  const { schema } = params;
@@ -1049,7 +1599,8 @@ function planSchema(params) {
1049
1599
  let refreshLookups = false;
1050
1600
  for (const [modelName, model] of schema.__models) {
1051
1601
  if (refreshLookups) {
1052
- lookups = relationLookups({ schema });
1602
+ if (!lookupsCurrent({ schema, inputs: lookups.inputs }))
1603
+ lookups = relationLookups({ schema });
1053
1604
  refreshLookups = false;
1054
1605
  }
1055
1606
  const explicit = planExplicitRelations({
@@ -1121,25 +1672,41 @@ function idDefault(params) {
1121
1672
  }
1122
1673
  function fieldDefault(params) {
1123
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
+ }
1124
1691
  if (column.__columnType === "id") {
1125
1692
  const strategy = column.__config.type;
1126
1693
  const dbgenerated = column.__config.dbgenerated;
1127
1694
  if (column.__config.default !== undefined || column.__config.autoincrement !== undefined)
1128
- throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
1695
+ throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
1129
1696
  if (dbgenerated !== undefined) {
1130
1697
  if (strategy !== "none" || typeof dbgenerated !== "string")
1131
- throw new VibeError5({ 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 }] } });
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 }] } });
1132
1699
  return { kind: "dbgenerated", sql: dbgenerated };
1133
1700
  }
1134
1701
  return idDefault({ column });
1135
1702
  }
1136
1703
  const { dbgenerated, default: value, autoincrement } = column.__config;
1137
1704
  if ([dbgenerated !== undefined, value !== undefined, autoincrement === true].filter(Boolean).length > 1) {
1138
- 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` });
1139
1706
  }
1140
1707
  if (autoincrement === true) {
1141
1708
  if (column.__columnType !== "int" && column.__columnType !== "bigint" || column.__config.array === true || column.__config.optional === true) {
1142
- 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" });
1143
1710
  }
1144
1711
  return { kind: "autoincrement" };
1145
1712
  }
@@ -1149,7 +1716,7 @@ function fieldDefault(params) {
1149
1716
  return;
1150
1717
  if (column.__config.array === true) {
1151
1718
  if (!Array.isArray(value))
1152
- 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" });
1153
1720
  return { kind: "list", values: scalarListDefault({ type: fieldType({ column }), values: column.__columnType === "json" ? value.map((entry) => typeof entry === "string" ? entry : serializeJsonDefault({ value: entry })) : value }) };
1154
1721
  }
1155
1722
  if (column.__columnType === "json" && typeof value !== "string") {
@@ -1164,20 +1731,20 @@ function fieldDefault(params) {
1164
1731
  return { kind: "enumValue", value };
1165
1732
  }
1166
1733
  if (typeof value === "number" && (!Number.isFinite(value) || (column.__columnType === "int" || column.__columnType === "bigint") && !Number.isSafeInteger(value))) {
1167
- 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" });
1168
1735
  }
1169
1736
  if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
1170
1737
  return { kind: "literal", value };
1171
1738
  }
1172
1739
  if (typeof value === "bigint")
1173
1740
  return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
1174
- 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}` });
1175
1742
  }
1176
1743
  function datetimeNativeType(params) {
1177
1744
  const { tz, precision } = params.config;
1178
1745
  if (precision !== undefined && (tz !== true || !Number.isInteger(precision) || precision < 0 || precision > 6)) {
1179
1746
  const at = params.column === undefined ? "A datetime column" : `Column "${params.column}"`;
1180
- throw new VibeError5({
1747
+ throw new VibeError6({
1181
1748
  code: "VIBE_SCHEMA_INVALID",
1182
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 })`,
1183
1750
  meta: { issues: [{ rule: "datetime-precision", column: params.column, precision, tz: tz === true }] }
@@ -1192,7 +1759,7 @@ function stringNativeType(params) {
1192
1759
  if (uuid === true) {
1193
1760
  if (maxLength !== undefined || text === true) {
1194
1761
  const at = params.column === undefined ? "A string column" : `Column "${params.column}"`;
1195
- throw new VibeError5({
1762
+ throw new VibeError6({
1196
1763
  code: "VIBE_SCHEMA_INVALID",
1197
1764
  message: `${at}: uuid() is the column's type and cannot be combined with maxLength or text`,
1198
1765
  meta: { issues: [{ rule: "string-native-type", column: params.column }] }
@@ -1210,7 +1777,7 @@ function idNativeType(params) {
1210
1777
  if (params.config.uuid !== true)
1211
1778
  return;
1212
1779
  if (params.config.type === "autoincrement") {
1213
- throw new VibeError5({
1780
+ throw new VibeError6({
1214
1781
  code: "VIBE_SCHEMA_INVALID",
1215
1782
  message: `${params.column === undefined ? "An id" : `Id "${params.column}"`}: uuid() needs a String id; an autoincrement id is an Int`,
1216
1783
  meta: { issues: [{ rule: "id-native-type", column: params.column }] }
@@ -1220,6 +1787,8 @@ function idNativeType(params) {
1220
1787
  }
1221
1788
  function nativeType(params) {
1222
1789
  const { column } = params;
1790
+ if (column.__columnType === "localDate")
1791
+ return "Date";
1223
1792
  if (column.__columnType === "string")
1224
1793
  return stringNativeType({ config: column.__config, column: column.__name });
1225
1794
  if (column.__columnType === "id")
@@ -1229,7 +1798,7 @@ function nativeType(params) {
1229
1798
  if (maxLength !== undefined || text !== undefined) {
1230
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;
1231
1800
  if (invalid) {
1232
- throw new VibeError5({
1801
+ throw new VibeError6({
1233
1802
  code: "VIBE_SCHEMA_INVALID",
1234
1803
  message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
1235
1804
  meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
@@ -1266,7 +1835,8 @@ function buildField(params) {
1266
1835
  const dbName = typeof config.map === "string" ? config.map : undefined;
1267
1836
  const documentation = typeof config.comment === "string" ? config.comment : undefined;
1268
1837
  const collation = typeof config.collation === "string" ? config.collation : undefined;
1269
- 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 });
1270
1840
  const defaultValue = fieldDefault({ column });
1271
1841
  const native = extensionNativeType ?? nativeType({ column });
1272
1842
  return {
@@ -1280,8 +1850,10 @@ function buildField(params) {
1280
1850
  ...collation === undefined ? {} : { collation },
1281
1851
  ...dbName === undefined ? {} : { dbName },
1282
1852
  ...defaultValue === undefined ? {} : { default: defaultValue },
1853
+ ..."generated" in config ? { generated: generatedColumnFromSql2({ value: config.generated }) } : {},
1283
1854
  ...native === undefined ? {} : { nativeType: native },
1284
1855
  ...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
1856
+ ...validatorMetadata.validator === undefined ? {} : { validator: validatorMetadata.validator },
1285
1857
  ...metadata
1286
1858
  };
1287
1859
  }
@@ -1315,17 +1887,26 @@ function buildIndex(params) {
1315
1887
  if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined) && !dialect.capabilities.indexOperatorClasses)
1316
1888
  throw unsupportedCapability({ dialect: dialect.name, feature: "indexOperatorClasses" });
1317
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
+ }
1318
1899
  const predicate = compileIndexPredicate({
1319
1900
  where: index.where,
1320
1901
  wherePredicate: index.wherePredicate,
1321
- columns: index.columns,
1902
+ columns: computed ? indexKeyFieldNames({ index: keys }) ?? [] : index.columns,
1322
1903
  model,
1323
1904
  dialect,
1324
1905
  enums
1325
1906
  });
1326
1907
  return {
1327
1908
  kind,
1328
- fields: index.columns.map((name) => ({ name, ...index.ops?.[name] === undefined ? {} : { operatorClass: index.ops[name] } })),
1909
+ ...keys,
1329
1910
  ...index.map === undefined ? {} : { name: index.map },
1330
1911
  ...predicate === undefined ? {} : { where: predicate.where },
1331
1912
  ...predicate?.wherePredicate === undefined ? {} : { wherePredicate: predicate.wherePredicate },
@@ -1412,8 +1993,8 @@ function buildModel(params) {
1412
1993
  }
1413
1994
  const predicateModel = { name: model.__name, fields };
1414
1995
  const exclusions = compileExclusions({ exclusions: model.__exclusions, model: predicateModel, dialect });
1415
- const checks = compileChecks({ checks: model.__checks, model: predicateModel, dialect, enums });
1416
- return {
1996
+ const checks = compileModelChecks({ model, predicateModel, dialect, enums });
1997
+ const irModel = {
1417
1998
  name: model.__name,
1418
1999
  ...model.__map === undefined ? {} : { dbName: model.__map },
1419
2000
  fields,
@@ -1426,6 +2007,8 @@ function buildModel(params) {
1426
2007
  isView: false,
1427
2008
  ...model.__comment === undefined ? {} : { documentation: model.__comment }
1428
2009
  };
2010
+ const rowRules = compileRowRules({ model, irModel, dialect: dialect.name });
2011
+ return rowRules.length === 0 ? irModel : { ...irModel, rowRules };
1429
2012
  }
1430
2013
 
1431
2014
  class ExtensionDeclarationCollector {
@@ -1470,7 +2053,7 @@ function toIR(params) {
1470
2053
  }
1471
2054
  }
1472
2055
  if (issues.length > 0) {
1473
- throw new VibeError5({
2056
+ throw new VibeError6({
1474
2057
  code: "VIBE_SCHEMA_INVALID",
1475
2058
  message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
1476
2059
  meta: { issues }
@@ -1494,11 +2077,36 @@ function predicateModel(params) {
1494
2077
  fields: Array.from(params.model.__columns, ([name, column]) => buildField({ name, column }))
1495
2078
  };
1496
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
+ }
1497
2104
  function prismaType(params) {
1498
2105
  const { column } = params;
1499
2106
  const list = column.__config.array === true ? "[]" : "";
1500
2107
  switch (column.__columnType) {
1501
2108
  case "string":
2109
+ case "localDate":
1502
2110
  return `String${list}`;
1503
2111
  case "int":
1504
2112
  return `Int${list}`;
@@ -1551,6 +2159,8 @@ function defaultAttribute(params) {
1551
2159
  }
1552
2160
  function nativeTypeAttribute(params) {
1553
2161
  const { column } = params;
2162
+ if (column.__columnType === "localDate")
2163
+ return "@db.Date";
1554
2164
  if (column.__columnType === "string" || column.__columnType === "id") {
1555
2165
  const native = column.__columnType === "string" ? stringNativeType({ config: column.__config, column: column.__name }) : idNativeType({ config: column.__config, column: column.__name });
1556
2166
  return native === undefined ? undefined : `@db.${native}`;
@@ -1580,6 +2190,10 @@ function formatColumn(params) {
1580
2190
  if (columnDefault !== undefined)
1581
2191
  parts.push(columnDefault);
1582
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
+ }
1583
2197
  if (column.__columnType === "datetime" && column.__config.updatedAt === true) {
1584
2198
  parts.push("@updatedAt");
1585
2199
  }
@@ -1596,13 +2210,17 @@ function formatColumn(params) {
1596
2210
  parts.push(`@collation(${encodePrismaString2({ value: column.__config.collation })})`);
1597
2211
  }
1598
2212
  const columnLine = parts.join(" ");
1599
- 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 }] } });
1600
2217
  if (metadata.jsonSchema !== undefined && column.__columnType !== "json")
1601
- 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 }] } });
1602
2219
  const comments = [
1603
2220
  ...metadata.documentation === undefined ? [] : metadata.documentation.split(`
1604
2221
  `),
1605
- ...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 })})`]
1606
2224
  ];
1607
2225
  return [...comments.map((line) => ` /// ${line}`), columnLine].join(`
1608
2226
  `);
@@ -1642,8 +2260,8 @@ function formatRelation(params) {
1642
2260
  function formatIndex(params) {
1643
2261
  const { index } = params;
1644
2262
  if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined))
1645
- 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" } });
1646
- 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(", ");
1647
2265
  const predicate = compileIndexPredicate({
1648
2266
  where: index.where,
1649
2267
  wherePredicate: index.wherePredicate,
@@ -1693,14 +2311,24 @@ function formatModel(params) {
1693
2311
  for (const index of plan.indexes.get(model.__name) ?? []) {
1694
2312
  lines.push(formatIndex({ index, model: params.model, dialect: params.dialect, enums: params.enums }));
1695
2313
  }
1696
- for (const check of compileChecks({
1697
- checks: model.__checks,
1698
- model: predicateModel({ model: params.model }),
2314
+ for (const check of compileModelChecks({
2315
+ model: params.model,
2316
+ predicateModel: predicateModel({ model: params.model }),
1699
2317
  dialect: params.dialect,
1700
2318
  enums: params.enums
1701
2319
  })) {
1702
2320
  lines.push(` @@check(${encodePrismaString2({ value: check.expression })}, name: ${encodePrismaString2({ value: check.name })})`);
1703
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 })}`);
1704
2332
  for (const use of model.__extensions ?? []) {
1705
2333
  const args = [];
1706
2334
  if (use.fields !== undefined)
@@ -1734,24 +2362,31 @@ function generatePrisma(schema, config = {}) {
1734
2362
  for (const model of schema.__models.values()) {
1735
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" }) }) });
1736
2364
  if (exclusions.length > 0)
1737
- 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" } });
1738
2366
  }
1739
2367
  const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
1740
2368
  if (stringEnums.length > 0) {
1741
- throw new VibeError6({
2369
+ throw new VibeError7({
1742
2370
  code: "VIBE_CONFIG",
1743
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.`,
1744
2372
  meta: { enums: stringEnums, target: "prisma" }
1745
2373
  });
1746
2374
  }
1747
2375
  if (schema.__rls !== undefined) {
1748
- throw new VibeError6({
2376
+ throw new VibeError7({
1749
2377
  code: "VIBE_CONFIG",
1750
2378
  message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
1751
2379
  meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
1752
2380
  });
1753
2381
  }
1754
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
+ } });
1755
2390
  const sections = [];
1756
2391
  const dialect = getSqlDialect2({ dialect: providerToDialect2({ provider: config.provider ?? "postgresql" }) });
1757
2392
  const enums = Array.from(schema.__enums, ([name, values]) => ({ name, values: [...values] }));
@@ -1778,9 +2413,10 @@ function generatePrisma(schema, config = {}) {
1778
2413
  }
1779
2414
 
1780
2415
  // packages/dsl/src/relations.ts
1781
- import { VibeError as VibeError7 } from "@vibeorm/schema";
2416
+ import { VibeError as VibeError8 } from "@vibeorm/schema";
1782
2417
  var REF_KIND_BY_COLUMN_TYPE = {
1783
2418
  string: "String",
2419
+ localDate: "String",
1784
2420
  int: "Int",
1785
2421
  bigint: "BigInt",
1786
2422
  float: "Float",
@@ -1819,7 +2455,7 @@ function extractRefInfo(params) {
1819
2455
  }
1820
2456
  const members = ref;
1821
2457
  if (members.length === 0) {
1822
- throw new VibeError7({
2458
+ throw new VibeError8({
1823
2459
  code: "VIBE_SCHEMA_INVALID",
1824
2460
  message: `Relation to ${target}: "${side}" is an empty tuple \u2014 name at least one column`,
1825
2461
  meta: { rule: "relation-empty-tuple", side, targetModel: target }
@@ -1827,14 +2463,14 @@ function extractRefInfo(params) {
1827
2463
  }
1828
2464
  const models = [...new Set(members.map((member) => member.__model))].sort();
1829
2465
  if (models.length > 1) {
1830
- throw new VibeError7({
2466
+ throw new VibeError8({
1831
2467
  code: "VIBE_SCHEMA_INVALID",
1832
2468
  message: `Relation to ${target}: "${side}" mixes models ${models.join(", ")} \u2014 every member of a composite key must sit on one model`,
1833
2469
  meta: { rule: "relation-mixed-models", side, targetModel: target, models }
1834
2470
  });
1835
2471
  }
1836
2472
  if (members.some((member) => member.__type !== "columnRef")) {
1837
- throw new VibeError7({
2473
+ throw new VibeError8({
1838
2474
  code: "VIBE_SCHEMA_INVALID",
1839
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`,
1840
2476
  meta: { rule: "relation-through-in-tuple", side, targetModel: target }
@@ -1848,7 +2484,7 @@ function createRelationFactory(params) {
1848
2484
  const from = extractRefInfo({ ref: config.from, side: "from", target: targetModelName });
1849
2485
  const to = extractRefInfo({ ref: config.to, side: "to", target: targetModelName });
1850
2486
  if (from.columns.length !== to.columns.length) {
1851
- throw new VibeError7({
2487
+ throw new VibeError8({
1852
2488
  code: "VIBE_SCHEMA_INVALID",
1853
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`,
1854
2490
  meta: { rule: "relation-tuple-arity", targetModel: targetModelName, from: from.columns, to: to.columns }
@@ -1946,7 +2582,7 @@ function mergeRelations(...parts) {
1946
2582
  }
1947
2583
 
1948
2584
  // packages/dsl/src/schema.ts
1949
- import { VibeError as VibeError8, validateRlsContextIR } from "@vibeorm/schema";
2585
+ import { VibeError as VibeError9, validateRlsContextIR } from "@vibeorm/schema";
1950
2586
  function sameValues(left, right) {
1951
2587
  return left.length === right.length && left.every((value, index) => value === right[index]);
1952
2588
  }
@@ -1965,7 +2601,7 @@ function registerEnum(params) {
1965
2601
  ...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
1966
2602
  ...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
1967
2603
  ].join("; ");
1968
- throw new VibeError8({
2604
+ throw new VibeError9({
1969
2605
  code: "VIBE_SCHEMA_INVALID",
1970
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`,
1971
2607
  meta: {
@@ -1991,7 +2627,7 @@ function addModelTree(params) {
1991
2627
  const { model, models, origin } = params;
1992
2628
  const existing = models.get(model.__name);
1993
2629
  if (existing !== undefined && existing !== model) {
1994
- throw new VibeError8({
2630
+ throw new VibeError9({
1995
2631
  code: "VIBE_SCHEMA_INVALID",
1996
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`,
1997
2633
  meta: {
@@ -2069,7 +2705,7 @@ var RLS_CONTEXT_TYPES = {
2069
2705
  };
2070
2706
  function defineRls(params) {
2071
2707
  if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
2072
- 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" }] } });
2073
2709
  }
2074
2710
  const entries = [];
2075
2711
  const refs = [];
@@ -2077,7 +2713,7 @@ function defineRls(params) {
2077
2713
  const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
2078
2714
  const column = descriptor?.value;
2079
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) {
2080
- 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" }] } });
2081
2717
  }
2082
2718
  entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
2083
2719
  refs.push([key, Object.freeze({ context: key })]);
@@ -2085,22 +2721,22 @@ function defineRls(params) {
2085
2721
  const context = Object.freeze(Object.fromEntries(entries));
2086
2722
  const issues = validateRlsContextIR({ context });
2087
2723
  if (issues.length > 0) {
2088
- 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 } });
2089
2725
  }
2090
2726
  const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
2091
2727
  if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
2092
- 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" }] } });
2093
2729
  }
2094
2730
  const named = [];
2095
2731
  const classified = new Set;
2096
2732
  for (const key of Reflect.ownKeys(policies)) {
2097
2733
  const descriptor = Object.getOwnPropertyDescriptor(policies, key);
2098
2734
  if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
2099
- 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" }] } });
2100
2736
  }
2101
2737
  const name = params.models[key]?.__name ?? key;
2102
2738
  if (classified.has(name)) {
2103
- 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 }] } });
2104
2740
  }
2105
2741
  classified.add(name);
2106
2742
  named.push([name, descriptor.value]);
@@ -2343,6 +2979,7 @@ function enumFactory(config) {
2343
2979
  var generate = Object.assign((schemaDef) => ({ prisma: generatePrisma(schemaDef) }), { prisma: generatePrisma });
2344
2980
  var dsl = {
2345
2981
  string,
2982
+ localDate,
2346
2983
  int,
2347
2984
  bigint,
2348
2985
  float,
@@ -2372,9 +3009,11 @@ export {
2372
3009
  ChainableDatetimeColumn,
2373
3010
  ChainableDecimalColumn,
2374
3011
  ChainableEnumColumn,
3012
+ ChainableFloatColumn,
2375
3013
  ChainableIdColumn,
2376
3014
  ChainableIntegerColumn,
2377
3015
  ChainableJsonColumn,
3016
+ ChainableNumericColumn,
2378
3017
  ChainableStringColumn,
2379
3018
  base,
2380
3019
  baseWithWorkspace,
@@ -2382,11 +3021,15 @@ export {
2382
3021
  cloneColumnDef,
2383
3022
  compileChecks,
2384
3023
  compileIndexPredicate,
3024
+ compileModelChecks,
3025
+ compileRowRules,
2385
3026
  create,
3027
+ createChecksBuilder,
2386
3028
  createChildrenBuilder,
2387
3029
  createEnum,
2388
3030
  createIndexBuilder,
2389
3031
  createPredicateBuilder,
3032
+ createRowRuleBuilder,
2390
3033
  defineRelations,
2391
3034
  defineRelationsPart,
2392
3035
  defineRls,
@@ -2398,6 +3041,7 @@ export {
2398
3041
  fields,
2399
3042
  generatePrisma,
2400
3043
  getColumnNames,
3044
+ localDate,
2401
3045
  mergeRelations,
2402
3046
  planSchema,
2403
3047
  resolveRelationType,
@@ -2412,4 +3056,4 @@ export {
2412
3056
  withNameDescription
2413
3057
  };
2414
3058
 
2415
- //# debugId=24494B6B129BF22A64756E2164756E21
3059
+ //# debugId=7EE10300C00F60DE64756E2164756E21