@vibeorm/dsl 2.4.0 → 4.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,12 +1,13 @@
1
1
  // @bun
2
2
  // packages/dsl/src/column.ts
3
+ import { VibeError, assertJsonSchemaRef, encodePrismaString } from "@vibeorm/schema";
3
4
  function cloneColumnDef(params) {
4
5
  const { column } = params;
5
6
  return {
6
7
  __type: "column",
7
8
  __columnType: column.__columnType,
8
9
  __name: column.__name,
9
- __config: { ...column.__config },
10
+ __config: { ...column.__config, ...column.__config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: column.__config.jsonSchema }) } },
10
11
  __enumValues: column.__enumValues,
11
12
  __enumName: column.__enumName,
12
13
  __enumStorage: column.__enumStorage
@@ -165,10 +166,17 @@ function datetime(config = {}) {
165
166
 
166
167
  class ChainableJsonColumn extends ChainableColumn {
167
168
  constructor(config = {}) {
168
- super("json", config);
169
+ super("json", { ...config, ...config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: config.jsonSchema }) } });
170
+ }
171
+ schema(reference) {
172
+ if (this.__config.jsonSchema !== undefined)
173
+ 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
+ this.__config.jsonSchema = assertJsonSchemaRef({ value: reference });
175
+ return this;
169
176
  }
170
177
  typed(config) {
171
- const annotation = `@type(${config.name} from "${config.from}")`;
178
+ const ref = assertJsonSchemaRef({ value: config });
179
+ const annotation = `@type(${ref.name} from ${encodePrismaString({ value: ref.from })})`;
172
180
  this.__config.comment = this.__config.comment === undefined ? annotation : `${this.__config.comment} ${annotation}`;
173
181
  return this;
174
182
  }
@@ -227,11 +235,11 @@ function createEnum(config) {
227
235
  }
228
236
 
229
237
  // packages/dsl/src/generator/prisma.ts
230
- import { VibeError as VibeError5, encodePrismaString, internalError, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
238
+ import { VibeError as VibeError6, encodePrismaString as encodePrismaString2, internalError, normalizeJsonSchemaRef as normalizeJsonSchemaRef2, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
231
239
  import { getSqlDialect as getSqlDialect2 } from "@vibeorm/sql";
232
240
 
233
241
  // packages/dsl/src/predicate.ts
234
- import { VibeError } from "@vibeorm/schema";
242
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
235
243
  import { compileExclusion, renderPredicate } from "@vibeorm/sql";
236
244
  function isFieldExpr(operand) {
237
245
  return typeof operand === "object" && operand !== null && "kind" in operand && operand.kind === "field";
@@ -246,7 +254,7 @@ function toValue(params) {
246
254
  if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
247
255
  return { kind: "value", value: input };
248
256
  }
249
- throw new VibeError({
257
+ throw new VibeError2({
250
258
  code: "VIBE_SCHEMA_INVALID",
251
259
  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`,
252
260
  meta: { rule: "predicate-literal-invalid", at }
@@ -254,13 +262,27 @@ function toValue(params) {
254
262
  }
255
263
  function toOperand(params) {
256
264
  const { operand, at } = params;
257
- return isFieldExpr(operand) ? toRef({ expr: operand }) : toValue({ input: operand, at });
265
+ return typeof operand === "object" && operand !== null ? toExpression({ value: operand, at }) : toValue({ input: operand, at });
266
+ }
267
+ function toExpression(params) {
268
+ const { value, at } = params;
269
+ if (isFieldExpr(value))
270
+ return toRef({ expr: value });
271
+ if (typeof value === "object" && value !== null && "kind" in value) {
272
+ 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
+ return { kind: "jsonText", field: toRef({ expr: requireFieldExpr({ value: value.field, at }) }), path: [...value.path] };
274
+ }
275
+ if (value.kind === "length" && "expression" in value) {
276
+ return { kind: "length", expression: toExpression({ value: value.expression, at }) };
277
+ }
278
+ }
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 } });
258
280
  }
259
281
  function requireFieldExpr(params) {
260
282
  const { value, at } = params;
261
283
  if (isFieldExpr(value))
262
284
  return value;
263
- throw new VibeError({
285
+ throw new VibeError2({
264
286
  code: "VIBE_SCHEMA_INVALID",
265
287
  message: `${at}: the left side of a comparison must be c.field("\u2026"), not a bare value`,
266
288
  meta: { rule: "predicate-operand-invalid", at }
@@ -269,7 +291,7 @@ function requireFieldExpr(params) {
269
291
  function requireOperands(params) {
270
292
  const { operands, at } = params;
271
293
  if (operands.length === 0) {
272
- throw new VibeError({
294
+ throw new VibeError2({
273
295
  code: "VIBE_SCHEMA_INVALID",
274
296
  message: `${at}: an empty boolean group has no honest SQL spelling \u2014 pass at least one operand`,
275
297
  meta: { rule: "predicate-empty", at }
@@ -281,14 +303,14 @@ function createPredicateBuilder() {
281
303
  const compare = (op) => (left, right) => ({
282
304
  kind: "compare",
283
305
  op,
284
- left: toRef({ expr: requireFieldExpr({ value: left, at: `c.${op}()` }) }),
306
+ left: toExpression({ value: left, at: `c.${op}()` }),
285
307
  right: toOperand({ operand: right, at: `c.${op}()` })
286
308
  });
287
309
  const membership = (negate) => (field, values) => {
288
310
  const at = negate ? "c.notInList()" : "c.inList()";
289
311
  const ref = toRef({ expr: requireFieldExpr({ value: field, at }) });
290
312
  if (values.length === 0) {
291
- throw new VibeError({
313
+ throw new VibeError2({
292
314
  code: "VIBE_SCHEMA_INVALID",
293
315
  message: `${at}: an empty IN list is UNKNOWN on every row and is never what an author means`,
294
316
  meta: { rule: "predicate-empty", at, field: ref.name }
@@ -309,6 +331,9 @@ function createPredicateBuilder() {
309
331
  gte: compare("gte"),
310
332
  lt: compare("lt"),
311
333
  lte: compare("lte"),
334
+ jsonText: ({ field, path }) => ({ ...toExpression({ value: { kind: "jsonText", field: { kind: "field", name: field }, path }, at: "c.jsonText()" }), __kind: "String" }),
335
+ length: (expression) => ({ kind: "length", expression: toExpression({ value: expression, at: "c.length()" }), __kind: "Int" }),
336
+ isTrue: (operand) => ({ kind: "isTrue", operand }),
312
337
  inList: membership(false),
313
338
  notInList: membership(true),
314
339
  isNull: (name) => ({ kind: "isNull", field: { kind: "field", name } }),
@@ -318,7 +343,7 @@ function createPredicateBuilder() {
318
343
  not: (operand) => ({ kind: "not", operand }),
319
344
  named: (name, predicate) => {
320
345
  if (name.trim() === "") {
321
- throw new VibeError({
346
+ throw new VibeError2({
322
347
  code: "VIBE_SCHEMA_INVALID",
323
348
  message: "c.named(): a CHECK needs a non-empty name \u2014 the name is what keys the migration diff",
324
349
  meta: { rule: "predicate-unnamed-check" }
@@ -337,10 +362,13 @@ function evaluateChecks(params) {
337
362
  }
338
363
  const declared = checks(createPredicateBuilder());
339
364
  return declared.map((entry, index) => {
340
- if (entry === null || typeof entry !== "object" || entry.__type !== "check") {
341
- throw new VibeError({
365
+ if (entry !== null && typeof entry === "object" && !("__type" in entry) && typeof entry.name === "string" && "expression" in entry && typeof entry.expression === "string") {
366
+ return { kind: "raw", name: entry.name, expression: entry.expression };
367
+ }
368
+ if (entry === null || typeof entry !== "object" || !("__type" in entry) || entry.__type !== "check") {
369
+ throw new VibeError2({
342
370
  code: "VIBE_SCHEMA_INVALID",
343
- message: `Model "${model}": checks[${index}] is not a named CHECK \u2014 wrap the expression in c.named("name", \u2026)`,
371
+ message: `Model "${model}": checks[${index}] must be c.named("name", predicate) or a raw { name, expression } CHECK`,
344
372
  meta: { rule: "predicate-unnamed-check", model, index }
345
373
  });
346
374
  }
@@ -369,7 +397,7 @@ function compileChecks(params) {
369
397
  function compileIndexPredicate(params) {
370
398
  const { where, wherePredicate, columns, model, dialect, enums } = params;
371
399
  if (where !== undefined && wherePredicate !== undefined) {
372
- throw new VibeError({
400
+ throw new VibeError2({
373
401
  code: "VIBE_SCHEMA_INVALID",
374
402
  message: `Index on "${model.name}" [${columns.join(", ")}] carries both a raw where: string and a typed .where() \u2014 an index has ONE predicate`,
375
403
  meta: { rule: "predicate-duplicate-where", model: model.name, columns: [...columns] }
@@ -395,13 +423,14 @@ function compileIndexPredicate(params) {
395
423
  // packages/dsl/src/to-ir.ts
396
424
  import {
397
425
  EMPTY_EXTENSION_REGISTRY,
398
- VibeError as VibeError4,
426
+ VibeError as VibeError5,
399
427
  assertValidSchemaIR,
400
428
  exactIntegerLiteral,
401
429
  providerToDialect,
402
430
  scalarListDefault,
403
431
  serializeJsonDefault,
404
432
  mergeExtensionDeclarations,
433
+ normalizeJsonSchemaRef,
405
434
  unsupportedCapability
406
435
  } from "@vibeorm/schema";
407
436
  import { getSqlDialect } from "@vibeorm/sql";
@@ -442,10 +471,10 @@ function fieldTypeKey(params) {
442
471
  }
443
472
 
444
473
  // packages/dsl/src/relation-plan.ts
445
- import { VibeError as VibeError3, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
474
+ import { VibeError as VibeError4, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
446
475
 
447
476
  // packages/dsl/src/model.ts
448
- import { VibeError as VibeError2 } from "@vibeorm/schema";
477
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
449
478
 
450
479
  // packages/dsl/src/naming.ts
451
480
  function capitalize(params) {
@@ -487,7 +516,7 @@ function createIndexBuilder() {
487
516
  const partial = (index) => {
488
517
  const where = (predicate) => {
489
518
  if (index.__where !== undefined) {
490
- throw new VibeError2({
519
+ throw new VibeError3({
491
520
  code: "VIBE_SCHEMA_INVALID",
492
521
  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`,
493
522
  meta: { rule: "predicate-duplicate-where", columns: [...index.__columns] }
@@ -525,7 +554,7 @@ function createIndexBuilder() {
525
554
  const [first, second] = args;
526
555
  if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
527
556
  if ("ops" in second && second.ops !== undefined) {
528
- throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses" } });
557
+ throw new VibeError3({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses" } });
529
558
  }
530
559
  return partial({
531
560
  __type: "unique",
@@ -609,6 +638,10 @@ function evaluateIndexes(params) {
609
638
  }
610
639
 
611
640
  // packages/dsl/src/relation-plan.ts
641
+ function relationPairKey(params) {
642
+ const { relation, reverse } = params;
643
+ return JSON.stringify(reverse ? [relation.__toModel, relation.__toColumns, relation.__fromModel, relation.__fromColumns, relation.__alias ?? ""] : [relation.__fromModel, relation.__fromColumns, relation.__toModel, relation.__toColumns, relation.__alias ?? ""]);
644
+ }
612
645
  function detectOneToOnePairs(params) {
613
646
  const { relations, models } = params;
614
647
  const mergeMap = new Map;
@@ -616,13 +649,22 @@ function detectOneToOnePairs(params) {
616
649
  for (const [modelName, modelRelations] of Object.entries(relations)) {
617
650
  for (const [relName, rel] of Object.entries(modelRelations)) {
618
651
  if (rel.__ownsForeignKey !== undefined && (typeof rel.__ownsForeignKey !== "boolean" || rel.__relationType !== "one" || rel.__ownsForeignKey && rel.__fromModel !== modelName)) {
619
- throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
652
+ throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
620
653
  }
621
654
  if (rel.__relationType === "one") {
622
655
  allOnes.push({ modelName, relName, rel });
623
656
  }
624
657
  }
625
658
  }
659
+ const candidatesByKey = new Map;
660
+ for (const [position, entry] of allOnes.entries()) {
661
+ const key = relationPairKey({ relation: entry.rel });
662
+ const bucket = candidatesByKey.get(key);
663
+ if (bucket === undefined)
664
+ candidatesByKey.set(key, { positions: [position], start: 0 });
665
+ else
666
+ bucket.positions.push(position);
667
+ }
626
668
  const paired = new Set;
627
669
  for (let i = 0;i < allOnes.length; i++) {
628
670
  const a = allOnes[i];
@@ -631,13 +673,17 @@ function detectOneToOnePairs(params) {
631
673
  const keyA = `${a.modelName}:${a.relName}`;
632
674
  if (paired.has(keyA))
633
675
  continue;
634
- for (let j = i + 1;j < allOnes.length; j++) {
676
+ const bucket = candidatesByKey.get(relationPairKey({ relation: a.rel, reverse: true }));
677
+ if (bucket === undefined)
678
+ continue;
679
+ for (let candidate = bucket.start;candidate < bucket.positions.length; candidate++) {
680
+ const j = bucket.positions[candidate];
635
681
  const b = allOnes[j];
636
- if (b === undefined)
637
- continue;
638
682
  const keyB = `${b.modelName}:${b.relName}`;
639
- if (paired.has(keyB))
683
+ if (j <= i || paired.has(keyB)) {
684
+ bucket.start = candidate + 1;
640
685
  continue;
686
+ }
641
687
  const sameColumnPair = a.rel.__fromModel === b.rel.__toModel && sameColumnTuple({ left: a.rel.__fromColumns, right: b.rel.__toColumns }) && a.rel.__toModel === b.rel.__fromModel && sameColumnTuple({ left: a.rel.__toColumns, right: b.rel.__fromColumns });
642
688
  if (!sameColumnPair || (a.rel.__alias ?? "") !== (b.rel.__alias ?? ""))
643
689
  continue;
@@ -665,6 +711,7 @@ function detectOneToOnePairs(params) {
665
711
  });
666
712
  paired.add(fkKey);
667
713
  paired.add(backKey);
714
+ bucket.start = candidate + 1;
668
715
  break;
669
716
  }
670
717
  }
@@ -677,7 +724,7 @@ function sameColumnTuple(params) {
677
724
  function resolveFkOwner(params) {
678
725
  const { a, b, models } = params;
679
726
  const ownershipError = () => {
680
- throw new VibeError3({ 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" } });
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" } });
681
728
  };
682
729
  const aOwns = a.rel.__ownsForeignKey;
683
730
  const bOwns = b.rel.__ownsForeignKey;
@@ -765,7 +812,7 @@ function planExplicitRelations(params) {
765
812
  return planned;
766
813
  }
767
814
  function planMissingReverseRelations(params) {
768
- const { modelName, relations, mergeMap, existing } = params;
815
+ const { modelName, relations, incoming, mergeMap, existing } = params;
769
816
  const planned = [];
770
817
  const existingLists = new Set;
771
818
  const existingNames = new Set;
@@ -775,44 +822,40 @@ function planMissingReverseRelations(params) {
775
822
  existingLists.add(`${relation.targetModel}:${relation.relationName ?? ""}`);
776
823
  }
777
824
  }
778
- for (const [sourceModelName, sourceRelations] of Object.entries(relations)) {
779
- for (const [relName, rel] of Object.entries(sourceRelations)) {
780
- if (rel.__targetModel !== modelName || rel.__relationType !== "one")
781
- continue;
782
- if (mergeMap.has(`${sourceModelName}:${relName}`))
783
- continue;
784
- const reverseKey = `${sourceModelName}:${rel.__alias ?? ""}`;
785
- if (existingLists.has(reverseKey))
825
+ for (const { modelName: sourceModelName, relName, rel } of incoming) {
826
+ if (mergeMap.has(`${sourceModelName}:${relName}`))
827
+ continue;
828
+ const reverseKey = `${sourceModelName}:${rel.__alias ?? ""}`;
829
+ if (existingLists.has(reverseKey))
830
+ continue;
831
+ if (sourceModelName === modelName) {
832
+ const hasExplicitReverse = Object.values(relations[sourceModelName] ?? {}).some((other) => other.__relationType === "many" && other.__targetModel === modelName && other.__alias === rel.__alias);
833
+ if (hasExplicitReverse)
786
834
  continue;
787
- if (sourceModelName === modelName) {
788
- const hasExplicitReverse = Object.values(sourceRelations).some((other) => other.__relationType === "many" && other.__targetModel === modelName && other.__alias === rel.__alias);
789
- if (hasExplicitReverse)
790
- continue;
791
- }
792
- const baseName = rel.__alias ?? sourceModelName;
793
- let name = baseName;
794
- let counter = 1;
795
- while (existingNames.has(name)) {
796
- counter++;
797
- name = `${baseName}${counter}`;
798
- }
799
- planned.push({
800
- model: modelName,
801
- name,
802
- targetModel: sourceModelName,
803
- kind: "list",
804
- isList: true,
805
- isOptional: false,
806
- relationName: rel.__alias
807
- });
808
- existingLists.add(reverseKey);
809
- existingNames.add(name);
810
835
  }
836
+ const baseName = rel.__alias ?? sourceModelName;
837
+ let name = baseName;
838
+ let counter = 1;
839
+ while (existingNames.has(name)) {
840
+ counter++;
841
+ name = `${baseName}${counter}`;
842
+ }
843
+ planned.push({
844
+ model: modelName,
845
+ name,
846
+ targetModel: sourceModelName,
847
+ kind: "list",
848
+ isList: true,
849
+ isOptional: false,
850
+ relationName: rel.__alias
851
+ });
852
+ existingLists.add(reverseKey);
853
+ existingNames.add(name);
811
854
  }
812
855
  return planned;
813
856
  }
814
857
  function planChildRelations(params) {
815
- const { modelName, model, models } = params;
858
+ const { modelName, model, parents } = params;
816
859
  const planned = [];
817
860
  for (const [childKey, childModel] of model.__children) {
818
861
  planned.push({
@@ -824,22 +867,18 @@ function planChildRelations(params) {
824
867
  isOptional: false
825
868
  });
826
869
  }
827
- for (const [parentModelName, parentModel] of models) {
828
- for (const childModel of parentModel.__children.values()) {
829
- if (childModel.__name !== modelName)
830
- continue;
831
- const parentPk = parentModel.__primaryKey?.[0] ?? Array.from(parentModel.__columns.entries()).find(([, column]) => column.__columnType === "id")?.[0] ?? "id";
832
- planned.push({
833
- model: modelName,
834
- name: parentModelName,
835
- targetModel: parentModelName,
836
- kind: "fkOwner",
837
- isList: false,
838
- isOptional: false,
839
- fields: [toFkColumnName({ modelName: parentModelName })],
840
- references: [parentPk]
841
- });
842
- }
870
+ for (const [parentModelName, parentModel] of parents) {
871
+ const parentPk = parentModel.__primaryKey?.[0] ?? Array.from(parentModel.__columns.entries()).find(([, column]) => column.__columnType === "id")?.[0] ?? "id";
872
+ planned.push({
873
+ model: modelName,
874
+ name: parentModelName,
875
+ targetModel: parentModelName,
876
+ kind: "fkOwner",
877
+ isList: false,
878
+ isOptional: false,
879
+ fields: [toFkColumnName({ modelName: parentModelName })],
880
+ references: [parentPk]
881
+ });
843
882
  }
844
883
  return planned;
845
884
  }
@@ -856,14 +895,14 @@ function planIndexes(params) {
856
895
  for (const index of evaluateIndexes({ model })) {
857
896
  if (index.__ops !== undefined) {
858
897
  if (index.__ops === null || typeof index.__ops !== "object" || Array.isArray(index.__ops))
859
- throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: "Index ops must be a field-keyed qualified-name map", meta: { rule: "index-operator-class-map", model: model.__name } });
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 } });
860
899
  if (index.__type === "unique")
861
- throw new VibeError3({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses", model: model.__name } });
900
+ throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses", model: model.__name } });
862
901
  for (const [field, operatorClass] of Object.entries(index.__ops)) {
863
902
  if (!index.__columns.includes(field))
864
- throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: "Operator class names a field outside the index", meta: { rule: "index-operator-class-field", model: model.__name, 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 } });
865
904
  if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass }))
866
- throw new VibeError3({ 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 } });
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 } });
867
906
  }
868
907
  }
869
908
  const operatorClasses = index.__columns.map((field) => index.__ops?.[field]);
@@ -914,7 +953,7 @@ function assertForeignKeyIntegrity(params) {
914
953
  const references = relation.references ?? [];
915
954
  const at = `${relation.model}.${relation.name}`;
916
955
  const refuse = (message, rule, meta = {}) => {
917
- throw new VibeError3({
956
+ throw new VibeError4({
918
957
  code: "VIBE_SCHEMA_INVALID",
919
958
  message: `Relation ${at}: ${message}`,
920
959
  meta: { rule, model: relation.model, relation: relation.name, ...meta }
@@ -963,6 +1002,33 @@ function assertForeignKeyIntegrity(params) {
963
1002
  refuse(`references ${relation.targetModel}.(${references.join(", ")}), which is not a unique key there \u2014 declare those columns as the primary key, a unique column, or a total @@unique index`, "relation-reference-not-unique", { references, candidates: keys.map((key) => [...key]) });
964
1003
  }
965
1004
  }
1005
+ function relationLookups(params) {
1006
+ const incoming = new Map;
1007
+ for (const [modelName, relations] of Object.entries(params.schema.__relations)) {
1008
+ for (const [relName, rel] of Object.entries(relations)) {
1009
+ if (rel.__relationType !== "one")
1010
+ continue;
1011
+ const target = rel.__targetModel;
1012
+ const entries = incoming.get(target);
1013
+ const entry = { modelName, relName, rel };
1014
+ if (entries === undefined)
1015
+ incoming.set(target, [entry]);
1016
+ else
1017
+ entries.push(entry);
1018
+ }
1019
+ }
1020
+ const parents = new Map;
1021
+ for (const [name, model] of params.schema.__models) {
1022
+ for (const child of model.__children.values()) {
1023
+ const entries = parents.get(child.__name);
1024
+ if (entries === undefined)
1025
+ parents.set(child.__name, [[name, model]]);
1026
+ else
1027
+ entries.push([name, model]);
1028
+ }
1029
+ }
1030
+ return { incoming, parents };
1031
+ }
966
1032
  function planSchema(params) {
967
1033
  const { schema } = params;
968
1034
  const mergeMap = detectOneToOnePairs({
@@ -971,7 +1037,13 @@ function planSchema(params) {
971
1037
  });
972
1038
  const relations = new Map;
973
1039
  const indexes = new Map;
1040
+ let lookups = relationLookups({ schema });
1041
+ let refreshLookups = false;
974
1042
  for (const [modelName, model] of schema.__models) {
1043
+ if (refreshLookups) {
1044
+ lookups = relationLookups({ schema });
1045
+ refreshLookups = false;
1046
+ }
975
1047
  const explicit = planExplicitRelations({
976
1048
  modelName,
977
1049
  model,
@@ -981,16 +1053,22 @@ function planSchema(params) {
981
1053
  const reverses = planMissingReverseRelations({
982
1054
  modelName,
983
1055
  relations: schema.__relations,
1056
+ incoming: lookups.incoming.get(modelName) ?? [],
984
1057
  mergeMap,
985
1058
  existing: explicit
986
1059
  });
987
- const children = planChildRelations({ modelName, model, models: schema.__models });
1060
+ const children = planChildRelations({ modelName, model, parents: lookups.parents.get(modelName) ?? [] });
988
1061
  const planned = [...explicit, ...reverses, ...children];
989
1062
  for (const relation of planned) {
990
- if (relation.kind === "fkOwner")
1063
+ if (relation.kind === "fkOwner") {
1064
+ if (schema.__models.get(relation.targetModel)?.__indexes !== undefined)
1065
+ refreshLookups = true;
991
1066
  assertForeignKeyIntegrity({ relation, models: schema.__models });
1067
+ }
992
1068
  }
993
1069
  relations.set(modelName, planned);
1070
+ if (model.__indexes !== undefined)
1071
+ refreshLookups = true;
994
1072
  indexes.set(modelName, planIndexes({ model }));
995
1073
  }
996
1074
  return { relations, indexes };
@@ -1037,16 +1115,16 @@ function fieldDefault(params) {
1037
1115
  const { column } = params;
1038
1116
  if (column.__columnType === "id") {
1039
1117
  if (column.__config.default !== undefined || column.__config.dbgenerated !== undefined || column.__config.autoincrement !== undefined)
1040
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
1118
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
1041
1119
  return idDefault({ column });
1042
1120
  }
1043
1121
  const { dbgenerated, default: value, autoincrement } = column.__config;
1044
1122
  if ([dbgenerated !== undefined, value !== undefined, autoincrement === true].filter(Boolean).length > 1) {
1045
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
1123
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
1046
1124
  }
1047
1125
  if (autoincrement === true) {
1048
1126
  if (column.__columnType !== "int" && column.__columnType !== "bigint" || column.__config.array === true || column.__config.optional === true) {
1049
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
1127
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
1050
1128
  }
1051
1129
  return { kind: "autoincrement" };
1052
1130
  }
@@ -1056,7 +1134,7 @@ function fieldDefault(params) {
1056
1134
  return;
1057
1135
  if (column.__config.array === true) {
1058
1136
  if (!Array.isArray(value))
1059
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
1137
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
1060
1138
  return { kind: "list", values: scalarListDefault({ type: fieldType({ column }), values: column.__columnType === "json" ? value.map((entry) => typeof entry === "string" ? entry : serializeJsonDefault({ value: entry })) : value }) };
1061
1139
  }
1062
1140
  if (column.__columnType === "json" && typeof value !== "string") {
@@ -1071,20 +1149,20 @@ function fieldDefault(params) {
1071
1149
  return { kind: "enumValue", value };
1072
1150
  }
1073
1151
  if (typeof value === "number" && (!Number.isFinite(value) || (column.__columnType === "int" || column.__columnType === "bigint") && !Number.isSafeInteger(value))) {
1074
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
1152
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
1075
1153
  }
1076
1154
  if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
1077
1155
  return { kind: "literal", value };
1078
1156
  }
1079
1157
  if (typeof value === "bigint")
1080
1158
  return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
1081
- throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
1159
+ throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
1082
1160
  }
1083
1161
  function datetimeNativeType(params) {
1084
1162
  const { tz, precision } = params.config;
1085
1163
  if (precision !== undefined && (tz !== true || !Number.isInteger(precision) || precision < 0 || precision > 6)) {
1086
1164
  const at = params.column === undefined ? "A datetime column" : `Column "${params.column}"`;
1087
- throw new VibeError4({
1165
+ throw new VibeError5({
1088
1166
  code: "VIBE_SCHEMA_INVALID",
1089
1167
  message: `${at}: a datetime precision must be an integer from 0 to 6 on a tz() column, as in dsl.datetime().tz({ precision: 3 })`,
1090
1168
  meta: { issues: [{ rule: "datetime-precision", column: params.column, precision, tz: tz === true }] }
@@ -1101,7 +1179,7 @@ function nativeType(params) {
1101
1179
  if (maxLength !== undefined || text !== undefined) {
1102
1180
  const invalid = column.__enumStorage !== "string" || maxLength !== undefined && (typeof maxLength !== "number" || !Number.isSafeInteger(maxLength) || maxLength <= 0) || text !== undefined && typeof text !== "boolean" || text === true && maxLength !== undefined;
1103
1181
  if (invalid) {
1104
- throw new VibeError4({
1182
+ throw new VibeError5({
1105
1183
  code: "VIBE_SCHEMA_INVALID",
1106
1184
  message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
1107
1185
  meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
@@ -1138,6 +1216,7 @@ function buildField(params) {
1138
1216
  const dbName = typeof config.map === "string" ? config.map : undefined;
1139
1217
  const documentation = typeof config.comment === "string" ? config.comment : undefined;
1140
1218
  const collation = typeof config.collation === "string" ? config.collation : undefined;
1219
+ const metadata = normalizeJsonSchemaRef({ documentation, reference: config.jsonSchema });
1141
1220
  const defaultValue = fieldDefault({ column });
1142
1221
  const native = extensionNativeType ?? nativeType({ column });
1143
1222
  return {
@@ -1153,7 +1232,7 @@ function buildField(params) {
1153
1232
  ...defaultValue === undefined ? {} : { default: defaultValue },
1154
1233
  ...native === undefined ? {} : { nativeType: native },
1155
1234
  ...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
1156
- ...documentation === undefined ? {} : { documentation }
1235
+ ...metadata
1157
1236
  };
1158
1237
  }
1159
1238
  function buildRelation(params) {
@@ -1341,7 +1420,7 @@ function toIR(params) {
1341
1420
  }
1342
1421
  }
1343
1422
  if (issues.length > 0) {
1344
- throw new VibeError4({
1423
+ throw new VibeError5({
1345
1424
  code: "VIBE_SCHEMA_INVALID",
1346
1425
  message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
1347
1426
  meta: { issues }
@@ -1398,7 +1477,7 @@ function prismaType(params) {
1398
1477
  const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
1399
1478
  return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
1400
1479
  }
1401
- return `Unsupported(${encodePrismaString({ value: String(column.__config.dbType) })})${list}`;
1480
+ return `Unsupported(${encodePrismaString2({ value: String(column.__config.dbType) })})${list}`;
1402
1481
  }
1403
1482
  }
1404
1483
  }
@@ -1413,11 +1492,11 @@ function defaultAttribute(params) {
1413
1492
  }
1414
1493
  const numericColumn = column.__columnType === "int" || column.__columnType === "bigint" || column.__columnType === "float" || column.__columnType === "decimal";
1415
1494
  if (value.kind === "literal")
1416
- return `@default(${typeof value.value === "string" && !(numericColumn && /^-?\d+(\.\d+)?$/.test(value.value)) ? encodePrismaString({ value: value.value }) : String(value.value)})`;
1495
+ return `@default(${typeof value.value === "string" && !(numericColumn && /^-?\d+(\.\d+)?$/.test(value.value)) ? encodePrismaString2({ value: value.value }) : String(value.value)})`;
1417
1496
  if (value.kind === "enumValue")
1418
1497
  return `@default(${value.value})`;
1419
1498
  if (value.kind === "dbgenerated")
1420
- return `@default(dbgenerated(${encodePrismaString({ value: value.sql })}))`;
1499
+ return `@default(dbgenerated(${encodePrismaString2({ value: value.sql })}))`;
1421
1500
  return `@default(${value.kind}())`;
1422
1501
  }
1423
1502
  function nativeTypeAttribute(params) {
@@ -1462,39 +1541,41 @@ function formatColumn(params) {
1462
1541
  parts.push("@unique");
1463
1542
  }
1464
1543
  if (typeof column.__config.map === "string") {
1465
- parts.push(`@map(${encodePrismaString({ value: column.__config.map })})`);
1544
+ parts.push(`@map(${encodePrismaString2({ value: column.__config.map })})`);
1466
1545
  }
1467
1546
  const nativeType = nativeTypeAttribute({ column });
1468
1547
  if (nativeType !== undefined)
1469
1548
  parts.push(nativeType);
1470
1549
  if (typeof column.__config.collation === "string") {
1471
- parts.push(`@collation(${encodePrismaString({ value: column.__config.collation })})`);
1550
+ parts.push(`@collation(${encodePrismaString2({ value: column.__config.collation })})`);
1472
1551
  }
1473
1552
  const columnLine = parts.join(" ");
1474
- const comment = column.__config.comment;
1475
- if (typeof comment === "string") {
1476
- const commentLines = comment.split(`
1477
- `).map((line) => ` /// ${line}`);
1478
- return [...commentLines, columnLine].join(`
1553
+ const metadata = normalizeJsonSchemaRef2({ documentation: typeof column.__config.comment === "string" ? column.__config.comment : undefined, reference: column.__config.jsonSchema });
1554
+ 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 }] } });
1556
+ const comments = [
1557
+ ...metadata.documentation === undefined ? [] : metadata.documentation.split(`
1558
+ `),
1559
+ ...metadata.jsonSchema === undefined ? [] : [`@schema(${metadata.jsonSchema.name} from ${encodePrismaString2({ value: metadata.jsonSchema.from })})`]
1560
+ ];
1561
+ return [...comments.map((line) => ` /// ${line}`), columnLine].join(`
1479
1562
  `);
1480
- }
1481
- return columnLine;
1482
1563
  }
1483
1564
  function formatRelation(params) {
1484
1565
  const { relation } = params;
1485
1566
  const { name, targetModel, relationName } = relation;
1486
1567
  if (relation.kind === "list") {
1487
- const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString({ value: relationName })})`;
1568
+ const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString2({ value: relationName })})`;
1488
1569
  return ` ${name} ${targetModel}[]${attribute}`;
1489
1570
  }
1490
1571
  const optional = relation.isOptional ? "?" : "";
1491
1572
  if (relation.kind === "backOne") {
1492
- const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString({ value: relationName })})`;
1573
+ const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString2({ value: relationName })})`;
1493
1574
  return ` ${name} ${targetModel}${optional}${attribute}`;
1494
1575
  }
1495
1576
  const args = [];
1496
1577
  if (relationName !== undefined)
1497
- args.push(encodePrismaString({ value: relationName }));
1578
+ args.push(encodePrismaString2({ value: relationName }));
1498
1579
  if (relation.fields === undefined || relation.references === undefined) {
1499
1580
  throw internalError({
1500
1581
  message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
@@ -1508,14 +1589,14 @@ function formatRelation(params) {
1508
1589
  if (relation.onUpdate !== undefined)
1509
1590
  args.push(`onUpdate: ${relation.onUpdate}`);
1510
1591
  if (relation.constraintName !== undefined) {
1511
- args.push(`map: ${encodePrismaString({ value: relation.constraintName })}`);
1592
+ args.push(`map: ${encodePrismaString2({ value: relation.constraintName })}`);
1512
1593
  }
1513
1594
  return ` ${name} ${targetModel}${optional} @relation(${args.join(", ")})`;
1514
1595
  }
1515
1596
  function formatIndex(params) {
1516
1597
  const { index } = params;
1517
1598
  if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined))
1518
- throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Prisma output cannot preserve qualified index operator classes; keep the DSL source", meta: { feature: "prismaIndexOperatorClasses", target: "prisma" } });
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" } });
1519
1600
  const columns = index.columns.join(", ");
1520
1601
  const predicate = compileIndexPredicate({
1521
1602
  where: index.where,
@@ -1525,10 +1606,10 @@ function formatIndex(params) {
1525
1606
  dialect: params.dialect,
1526
1607
  enums: params.enums
1527
1608
  });
1528
- const whereArg = predicate === undefined ? "" : `, where: ${encodePrismaString({ value: predicate.where })}`;
1529
- const mapArg = index.map === undefined ? "" : `, map: ${encodePrismaString({ value: index.map })}`;
1609
+ const whereArg = predicate === undefined ? "" : `, where: ${encodePrismaString2({ value: predicate.where })}`;
1610
+ const mapArg = index.map === undefined ? "" : `, map: ${encodePrismaString2({ value: index.map })}`;
1530
1611
  const nndArg = index.nullsNotDistinct === true ? ", nullsNotDistinct: true" : "";
1531
- const nameArg = index.clientName === undefined ? "" : `, name: ${encodePrismaString({ value: index.clientName })}`;
1612
+ const nameArg = index.clientName === undefined ? "" : `, name: ${encodePrismaString2({ value: index.clientName })}`;
1532
1613
  if (index.isUnique) {
1533
1614
  return ` @@unique([${columns}]${nameArg}${mapArg}${nndArg}${whereArg})`;
1534
1615
  }
@@ -1538,7 +1619,7 @@ function formatIndex(params) {
1538
1619
  function formatExtensionValue(params) {
1539
1620
  const { value } = params;
1540
1621
  if (typeof value === "string")
1541
- return encodePrismaString({ value });
1622
+ return encodePrismaString2({ value });
1542
1623
  if (Array.isArray(value)) {
1543
1624
  return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
1544
1625
  }
@@ -1572,7 +1653,7 @@ function formatModel(params) {
1572
1653
  dialect: params.dialect,
1573
1654
  enums: params.enums
1574
1655
  })) {
1575
- lines.push(` @@check(${encodePrismaString({ value: check.expression })}, name: ${encodePrismaString({ value: check.name })})`);
1656
+ lines.push(` @@check(${encodePrismaString2({ value: check.expression })}, name: ${encodePrismaString2({ value: check.name })})`);
1576
1657
  }
1577
1658
  for (const use of model.__extensions ?? []) {
1578
1659
  const args = [];
@@ -1588,11 +1669,11 @@ function formatModel(params) {
1588
1669
  }
1589
1670
  if (model.__policy !== undefined) {
1590
1671
  const { field, context } = model.__policy;
1591
- const contextArg = context === undefined || context === field ? "" : `, context: ${encodePrismaString({ value: context })}`;
1672
+ const contextArg = context === undefined || context === field ? "" : `, context: ${encodePrismaString2({ value: context })}`;
1592
1673
  lines.push(` @@policy([${field}]${contextArg})`);
1593
1674
  }
1594
1675
  if (typeof model.__map === "string") {
1595
- lines.push(` @@map(${encodePrismaString({ value: model.__map })})`);
1676
+ lines.push(` @@map(${encodePrismaString2({ value: model.__map })})`);
1596
1677
  }
1597
1678
  lines.push("}");
1598
1679
  return lines.join(`
@@ -1607,18 +1688,18 @@ function generatePrisma(schema, config = {}) {
1607
1688
  for (const model of schema.__models.values()) {
1608
1689
  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" }) }) });
1609
1690
  if (exclusions.length > 0)
1610
- throw new VibeError5({ code: "VIBE_CONFIG", message: "Prisma output cannot preserve exclusion constraints; keep the DSL source", meta: { feature: "exclusionConstraints", target: "prisma" } });
1691
+ throw new VibeError6({ code: "VIBE_CONFIG", message: "Prisma output cannot preserve exclusion constraints; keep the DSL source", meta: { feature: "exclusionConstraints", target: "prisma" } });
1611
1692
  }
1612
1693
  const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
1613
1694
  if (stringEnums.length > 0) {
1614
- throw new VibeError5({
1695
+ throw new VibeError6({
1615
1696
  code: "VIBE_CONFIG",
1616
1697
  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.`,
1617
1698
  meta: { enums: stringEnums, target: "prisma" }
1618
1699
  });
1619
1700
  }
1620
1701
  if (schema.__rls !== undefined) {
1621
- throw new VibeError5({
1702
+ throw new VibeError6({
1622
1703
  code: "VIBE_CONFIG",
1623
1704
  message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
1624
1705
  meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
@@ -1651,7 +1732,7 @@ function generatePrisma(schema, config = {}) {
1651
1732
  }
1652
1733
 
1653
1734
  // packages/dsl/src/relations.ts
1654
- import { VibeError as VibeError6 } from "@vibeorm/schema";
1735
+ import { VibeError as VibeError7 } from "@vibeorm/schema";
1655
1736
  var REF_KIND_BY_COLUMN_TYPE = {
1656
1737
  string: "String",
1657
1738
  int: "Int",
@@ -1692,7 +1773,7 @@ function extractRefInfo(params) {
1692
1773
  }
1693
1774
  const members = ref;
1694
1775
  if (members.length === 0) {
1695
- throw new VibeError6({
1776
+ throw new VibeError7({
1696
1777
  code: "VIBE_SCHEMA_INVALID",
1697
1778
  message: `Relation to ${target}: "${side}" is an empty tuple \u2014 name at least one column`,
1698
1779
  meta: { rule: "relation-empty-tuple", side, targetModel: target }
@@ -1700,14 +1781,14 @@ function extractRefInfo(params) {
1700
1781
  }
1701
1782
  const models = [...new Set(members.map((member) => member.__model))].sort();
1702
1783
  if (models.length > 1) {
1703
- throw new VibeError6({
1784
+ throw new VibeError7({
1704
1785
  code: "VIBE_SCHEMA_INVALID",
1705
1786
  message: `Relation to ${target}: "${side}" mixes models ${models.join(", ")} \u2014 every member of a composite key must sit on one model`,
1706
1787
  meta: { rule: "relation-mixed-models", side, targetModel: target, models }
1707
1788
  });
1708
1789
  }
1709
1790
  if (members.some((member) => member.__type !== "columnRef")) {
1710
- throw new VibeError6({
1791
+ throw new VibeError7({
1711
1792
  code: "VIBE_SCHEMA_INVALID",
1712
1793
  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`,
1713
1794
  meta: { rule: "relation-through-in-tuple", side, targetModel: target }
@@ -1721,7 +1802,7 @@ function createRelationFactory(params) {
1721
1802
  const from = extractRefInfo({ ref: config.from, side: "from", target: targetModelName });
1722
1803
  const to = extractRefInfo({ ref: config.to, side: "to", target: targetModelName });
1723
1804
  if (from.columns.length !== to.columns.length) {
1724
- throw new VibeError6({
1805
+ throw new VibeError7({
1725
1806
  code: "VIBE_SCHEMA_INVALID",
1726
1807
  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`,
1727
1808
  meta: { rule: "relation-tuple-arity", targetModel: targetModelName, from: from.columns, to: to.columns }
@@ -1819,7 +1900,7 @@ function mergeRelations(...parts) {
1819
1900
  }
1820
1901
 
1821
1902
  // packages/dsl/src/schema.ts
1822
- import { VibeError as VibeError7, validateRlsContextIR } from "@vibeorm/schema";
1903
+ import { VibeError as VibeError8, validateRlsContextIR } from "@vibeorm/schema";
1823
1904
  function sameValues(left, right) {
1824
1905
  return left.length === right.length && left.every((value, index) => value === right[index]);
1825
1906
  }
@@ -1838,7 +1919,7 @@ function registerEnum(params) {
1838
1919
  ...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
1839
1920
  ...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
1840
1921
  ].join("; ");
1841
- throw new VibeError7({
1922
+ throw new VibeError8({
1842
1923
  code: "VIBE_SCHEMA_INVALID",
1843
1924
  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`,
1844
1925
  meta: {
@@ -1864,7 +1945,7 @@ function addModelTree(params) {
1864
1945
  const { model, models, origin } = params;
1865
1946
  const existing = models.get(model.__name);
1866
1947
  if (existing !== undefined && existing !== model) {
1867
- throw new VibeError7({
1948
+ throw new VibeError8({
1868
1949
  code: "VIBE_SCHEMA_INVALID",
1869
1950
  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`,
1870
1951
  meta: {
@@ -1942,7 +2023,7 @@ var RLS_CONTEXT_TYPES = {
1942
2023
  };
1943
2024
  function defineRls(params) {
1944
2025
  if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
1945
- throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-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" }] } });
1946
2027
  }
1947
2028
  const entries = [];
1948
2029
  const refs = [];
@@ -1950,7 +2031,7 @@ function defineRls(params) {
1950
2031
  const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
1951
2032
  const column = descriptor?.value;
1952
2033
  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) {
1953
- throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
2034
+ throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
1954
2035
  }
1955
2036
  entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
1956
2037
  refs.push([key, Object.freeze({ context: key })]);
@@ -1958,22 +2039,22 @@ function defineRls(params) {
1958
2039
  const context = Object.freeze(Object.fromEntries(entries));
1959
2040
  const issues = validateRlsContextIR({ context });
1960
2041
  if (issues.length > 0) {
1961
- throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
2042
+ throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
1962
2043
  }
1963
2044
  const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
1964
2045
  if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
1965
- throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
2046
+ throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
1966
2047
  }
1967
2048
  const named = [];
1968
2049
  const classified = new Set;
1969
2050
  for (const key of Reflect.ownKeys(policies)) {
1970
2051
  const descriptor = Object.getOwnPropertyDescriptor(policies, key);
1971
2052
  if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
1972
- throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
2053
+ throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
1973
2054
  }
1974
2055
  const name = params.models[key]?.__name ?? key;
1975
2056
  if (classified.has(name)) {
1976
- throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: 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 }] } });
1977
2058
  }
1978
2059
  classified.add(name);
1979
2060
  named.push([name, descriptor.value]);
@@ -2285,4 +2366,4 @@ export {
2285
2366
  withNameDescription
2286
2367
  };
2287
2368
 
2288
- //# debugId=0EC0F0E6154BF1C064756E2164756E21
2369
+ //# debugId=21B90DDA6F5E192964756E2164756E21