@vibeorm/schema 2.0.0-alpha.8 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,14 +16,75 @@ function providerToDialect(params) {
16
16
  function isEnumType(fieldType) {
17
17
  return typeof fieldType === "object" && "enum" in fieldType;
18
18
  }
19
+ function fieldTypeIdentity(params) {
20
+ const { type } = params;
21
+ return isEnumType(type) ? `enum:${type.enum}` : type;
22
+ }
19
23
  var ENUM_STORAGES = ["database", "string"];
20
24
  function enumStorageOf(params) {
21
25
  return params.enumDef.storage ?? "database";
22
26
  }
27
+ // packages/schema/src/predicate.ts
28
+ var PREDICATE_COMPARISON_OPS = [
29
+ "eq",
30
+ "ne",
31
+ "gt",
32
+ "gte",
33
+ "lt",
34
+ "lte"
35
+ ];
36
+ function isPredicateFieldRef(operand) {
37
+ return operand.kind === "field";
38
+ }
39
+ function predicateFieldNames(params) {
40
+ const seen = new Set;
41
+ const walk = (node) => {
42
+ switch (node.kind) {
43
+ case "compare":
44
+ seen.add(node.left.name);
45
+ if (isPredicateFieldRef(node.right))
46
+ seen.add(node.right.name);
47
+ return;
48
+ case "inList":
49
+ case "isNull":
50
+ seen.add(node.field.name);
51
+ return;
52
+ case "and":
53
+ case "or":
54
+ for (const operand of node.operands)
55
+ walk(operand);
56
+ return;
57
+ case "not":
58
+ walk(node.operand);
59
+ return;
60
+ }
61
+ };
62
+ walk(params.predicate);
63
+ return [...seen];
64
+ }
65
+ function predicateKey(params) {
66
+ const encode = (node) => {
67
+ switch (node.kind) {
68
+ case "compare":
69
+ return `cmp(${node.op},${node.left.name},${isPredicateFieldRef(node.right) ? `f:${node.right.name}` : `v:${typeof node.right.value}:${String(node.right.value)}`})`;
70
+ case "inList":
71
+ return `in${node.negate === true ? "!" : ""}(${node.field.name},[${node.values.map((entry) => `${typeof entry.value}:${String(entry.value)}`).join("|")}])`;
72
+ case "isNull":
73
+ return `null${node.negate === true ? "!" : ""}(${node.field.name})`;
74
+ case "and":
75
+ case "or":
76
+ return `${node.kind}(${node.operands.map(encode).join(",")})`;
77
+ case "not":
78
+ return `not(${encode(node.operand)})`;
79
+ }
80
+ };
81
+ return encode(params.predicate);
82
+ }
23
83
  // packages/schema/src/errors.ts
24
84
  var VIBE_ERROR_CODES = [
25
85
  "VIBE_NOT_FOUND",
26
86
  "VIBE_UNIQUE_VIOLATION",
87
+ "VIBE_EXCLUSION_VIOLATION",
27
88
  "VIBE_FK_VIOLATION",
28
89
  "VIBE_VALIDATION",
29
90
  "VIBE_DATA_INVALID",
@@ -33,7 +94,9 @@ var VIBE_ERROR_CODES = [
33
94
  "VIBE_CONFIG",
34
95
  "VIBE_MIGRATION",
35
96
  "VIBE_TRANSACTION",
97
+ "VIBE_LOCK_NOT_AVAILABLE",
36
98
  "VIBE_SCOPED",
99
+ "VIBE_READ_ONLY",
37
100
  "VIBE_RLS_CONTEXT",
38
101
  "VIBE_PERMISSION_DENIED",
39
102
  "VIBE_ADAPTER",
@@ -137,6 +200,100 @@ class ScopedQueryError extends VibeError {
137
200
  this.reason = params.reason;
138
201
  }
139
202
  }
203
+ var READ_ONLY_REFUSAL_REASONS = [
204
+ "write",
205
+ "raw",
206
+ "control",
207
+ "unclassified",
208
+ "foreign_operation"
209
+ ];
210
+
211
+ class ReadOnlyClientError extends VibeError {
212
+ path;
213
+ reason;
214
+ constructor(params) {
215
+ super({
216
+ code: "VIBE_READ_ONLY",
217
+ message: params.message,
218
+ meta: { ...params.meta, path: params.path, reason: params.reason }
219
+ });
220
+ this.name = "ReadOnlyClientError";
221
+ this.path = params.path;
222
+ this.reason = params.reason;
223
+ }
224
+ }
225
+
226
+ // packages/schema/src/migration-objects.ts
227
+ function sqlObjectKey(params) {
228
+ const object = params.identity;
229
+ switch (object.kind) {
230
+ case "table":
231
+ case "view":
232
+ case "sequence":
233
+ case "postgresExtension":
234
+ return JSON.stringify([object.kind, object.name.namespace, object.name.name]);
235
+ case "function":
236
+ return JSON.stringify([object.kind, object.name.namespace, object.name.name, object.argumentTypes.map((type) => [type.namespace, type.name])]);
237
+ case "index":
238
+ case "trigger":
239
+ return JSON.stringify([object.kind, object.table.namespace, object.table.name, object.name]);
240
+ case "constraint":
241
+ return JSON.stringify([object.kind, object.table.namespace, object.table.name, object.name, object.constraintType]);
242
+ case "column":
243
+ case "columnDefault":
244
+ case "generatedColumn":
245
+ return JSON.stringify([object.kind, object.table.namespace, object.table.name, object.column]);
246
+ }
247
+ }
248
+ function record(value) {
249
+ return value !== null && typeof value === "object" && !Array.isArray(value);
250
+ }
251
+ function name(value) {
252
+ return record(value) && typeof value.namespace === "string" && value.namespace.length > 0 && typeof value.name === "string" && value.name.length > 0;
253
+ }
254
+ function isSqlObjectIdentity(value) {
255
+ if (!record(value))
256
+ return false;
257
+ switch (value.kind) {
258
+ case "table":
259
+ case "view":
260
+ case "sequence":
261
+ case "postgresExtension":
262
+ return name(value.name);
263
+ case "function":
264
+ return name(value.name) && Array.isArray(value.argumentTypes) && value.argumentTypes.every(name);
265
+ case "index":
266
+ case "trigger":
267
+ return name(value.table) && typeof value.name === "string" && value.name.length > 0;
268
+ case "constraint":
269
+ return name(value.table) && typeof value.name === "string" && value.name.length > 0 && ["check", "foreignKey", "exclusion", "unique", "primaryKey"].includes(String(value.constraintType));
270
+ case "column":
271
+ case "columnDefault":
272
+ case "generatedColumn":
273
+ return name(value.table) && typeof value.column === "string" && value.column.length > 0;
274
+ default:
275
+ return false;
276
+ }
277
+ }
278
+ function isSqlObjectDefinition(value) {
279
+ return record(value) && value.format === "catalog-v1" && isSqlObjectIdentity(value.identity) && record(value.facts) && Object.keys(value.facts).length > 0 && Object.values(value.facts).every((fact) => fact === null || typeof fact === "string" || typeof fact === "boolean" || typeof fact === "number" && Number.isFinite(fact) || Array.isArray(fact) && fact.every((entry) => typeof entry === "string")) && Array.isArray(value.dependencies) && value.dependencies.every(isSqlObjectIdentity);
280
+ }
281
+ function assertMigrationObjects(params) {
282
+ if (!Array.isArray(params.objects))
283
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "migrationObjects must be an array" });
284
+ const ids = new Set;
285
+ const owners = new Set;
286
+ for (const object of params.objects) {
287
+ if (!record(object) || typeof object.id !== "string" || object.id.length === 0 || !["postgres", "sqlite", "mysql"].includes(String(object.dialect)) || !isSqlObjectIdentity(object.identity) || !Array.isArray(object.create) || !object.create.every((sql) => typeof sql === "string") || !Array.isArray(object.drop) || !object.drop.every((sql) => typeof sql === "string") || !Array.isArray(object.dependsOn) || !object.dependsOn.every(isSqlObjectIdentity) || object.expected !== undefined && (!isSqlObjectDefinition(object.expected) || sqlObjectKey({ identity: object.expected.identity }) !== sqlObjectKey({ identity: object.identity }))) {
288
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Invalid migration-only object declaration" });
289
+ }
290
+ const key = sqlObjectKey({ identity: object.identity });
291
+ if (ids.has(object.id) || owners.has(key))
292
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Duplicate migration object identity or id", meta: { id: object.id, key } });
293
+ ids.add(object.id);
294
+ owners.add(key);
295
+ }
296
+ }
140
297
  // packages/schema/src/prisma-literal.ts
141
298
  var STRING_ESCAPES = {
142
299
  '"': '"',
@@ -255,6 +412,8 @@ var RESERVED_CLIENT_NAMES = [
255
412
  "$connect",
256
413
  "$disconnect",
257
414
  "$transaction",
415
+ "$querySql",
416
+ "$executeSql",
258
417
  "$queryRaw",
259
418
  "$queryRawUnsafe",
260
419
  "$executeRaw",
@@ -264,7 +423,8 @@ var RESERVED_CLIENT_NAMES = [
264
423
  "$defineView",
265
424
  "$withPolicy",
266
425
  "$bypassPolicy",
267
- "$scoped"
426
+ "$scoped",
427
+ "$readOnly"
268
428
  ];
269
429
  var RESERVED_COLUMN_PREFIX = "__vibe_";
270
430
  function isReservedDelegateName(params) {
@@ -302,26 +462,26 @@ function extensionManifestEntry(params) {
302
462
  }
303
463
  var EXTENSION_NAME_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
304
464
  function defineExtension(extension) {
305
- const { name } = extension;
306
- if (!EXTENSION_NAME_PATTERN.test(name)) {
465
+ const { name: name2 } = extension;
466
+ if (!EXTENSION_NAME_PATTERN.test(name2)) {
307
467
  throw new VibeError({
308
468
  code: "VIBE_CONFIG",
309
- message: `extension name "${name}" is invalid \u2014 must match ${EXTENSION_NAME_PATTERN} (it becomes a client identifier)`,
310
- meta: { name }
469
+ message: `extension name "${name2}" is invalid \u2014 must match ${EXTENSION_NAME_PATTERN} (it becomes a client identifier)`,
470
+ meta: { name: name2 }
311
471
  });
312
472
  }
313
- if (RESERVED_DELEGATE_NAMES.includes(name)) {
473
+ if (RESERVED_DELEGATE_NAMES.includes(name2)) {
314
474
  throw new VibeError({
315
475
  code: "VIBE_CONFIG",
316
- message: `extension name "${name}" collides with a core delegate method`,
317
- meta: { name }
476
+ message: `extension name "${name2}" collides with a core delegate method`,
477
+ meta: { name: name2 }
318
478
  });
319
479
  }
320
- if (RESERVED_CLIENT_NAMES.includes(`$${name}`)) {
480
+ if (RESERVED_CLIENT_NAMES.includes(`$${name2}`)) {
321
481
  throw new VibeError({
322
482
  code: "VIBE_CONFIG",
323
- message: `extension name "${name}" collides with the core client method "$${name}"`,
324
- meta: { name }
483
+ message: `extension name "${name2}" collides with the core client method "$${name2}"`,
484
+ meta: { name: name2 }
325
485
  });
326
486
  }
327
487
  return Object.freeze(extension);
@@ -467,8 +627,87 @@ function validateComputedConfig(params) {
467
627
  });
468
628
  }
469
629
  }
630
+ // packages/schema/src/default-values.ts
631
+ function serializeJsonDefault(params) {
632
+ const ancestors = new Set;
633
+ const visit = (value) => {
634
+ if (value === null)
635
+ return "null";
636
+ if (typeof value === "string" || typeof value === "boolean")
637
+ return JSON.stringify(value);
638
+ if (typeof value === "number" && Number.isFinite(value))
639
+ return JSON.stringify(value);
640
+ if (typeof value !== "object" || value === null || ancestors.has(value)) {
641
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "JSON defaults require finite, acyclic JSON values" });
642
+ }
643
+ ancestors.add(value);
644
+ let text;
645
+ if (Array.isArray(value)) {
646
+ text = `[${Array.from(value, visit).join(",")}]`;
647
+ } else {
648
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
649
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "JSON defaults require plain objects, not class instances" });
650
+ }
651
+ if (Reflect.ownKeys(value).some((key) => {
652
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
653
+ return typeof key !== "string" || !descriptor.enumerable || !("value" in descriptor);
654
+ })) {
655
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "JSON defaults require enumerable data properties, without symbols or accessors" });
656
+ }
657
+ text = `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${visit(Object.getOwnPropertyDescriptor(value, key).value)}`).join(",")}}`;
658
+ }
659
+ ancestors.delete(value);
660
+ return text;
661
+ };
662
+ return visit(params.value);
663
+ }
664
+ function scalarListDefault(params) {
665
+ const { type } = params;
666
+ return Array.from(params.values, (value) => {
667
+ if (type === "BigInt" && typeof value === "bigint")
668
+ return exactIntegerLiteral({ text: value.toString() });
669
+ if (type === "BigInt" && typeof value === "string" && /^-?\d+$/.test(value))
670
+ return exactIntegerLiteral({ text: value });
671
+ if ((type === "Int" || type === "BigInt") && typeof value === "number" && Number.isSafeInteger(value) && (type !== "Int" || value >= -2147483648 && value <= 2147483647))
672
+ return value;
673
+ if (type === "Float" && typeof value === "number" && Number.isFinite(value))
674
+ return value;
675
+ if (type === "Boolean" && typeof value === "boolean")
676
+ return value;
677
+ if ((type === "String" || isEnumType(type)) && typeof value === "string")
678
+ return value;
679
+ if (type === "Decimal" && (typeof value === "string" || typeof value === "number") && /^-?\d+(\.\d+)?$/.test(String(value))) {
680
+ const text = String(value);
681
+ const [integer, fraction = ""] = text.replace(/^-/, "").split(".");
682
+ const whole = BigInt(integer).toString();
683
+ const tail = fraction.replace(/0+$/, "");
684
+ const sign = text.startsWith("-") && (whole !== "0" || tail !== "") ? "-" : "";
685
+ return `${sign}${whole}${tail === "" ? "" : `.${tail}`}`;
686
+ }
687
+ if (type === "Json" && typeof value === "string")
688
+ return value;
689
+ if (type === "DateTime") {
690
+ const text = value instanceof Date && Number.isFinite(value.getTime()) ? value.toISOString() : value;
691
+ if (typeof text === "string") {
692
+ const match = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.(\d{1,6}))?(Z|[+-]\d{2}(?::?\d{2})?)?$/.exec(text);
693
+ const timestamp = match === null ? NaN : Date.parse(match[2] === undefined ? `${text}Z` : text);
694
+ if (Number.isFinite(timestamp))
695
+ return new Date(timestamp).toISOString().replace(/\.\d{3}Z$/, `.${(match?.[1] ?? "").padEnd(6, "0")}Z`);
696
+ }
697
+ }
698
+ if (type === "Bytes" && value instanceof Uint8Array)
699
+ return `\\x${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
700
+ if (type === "Bytes" && typeof value === "string" && /^\\x(?:[0-9a-f]{2})*$/i.test(value))
701
+ return value.toLowerCase();
702
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `Invalid scalar-list default element for ${isEnumType(type) ? type.enum : type}` });
703
+ });
704
+ }
705
+
470
706
  // packages/schema/src/capabilities.ts
471
707
  var POSTGRES_CAPABILITIES = Object.freeze({
708
+ exclusionConstraints: true,
709
+ exclusionExtensions: Object.freeze(["btree_gist"]),
710
+ checkNormalization: "rollback",
472
711
  nativeRowLevelSecurity: true,
473
712
  returning: true,
474
713
  scalarArrays: true,
@@ -482,9 +721,15 @@ var POSTGRES_CAPABILITIES = Object.freeze({
482
721
  createManyAndReturn: true,
483
722
  jsonColumnType: true,
484
723
  orderByNulls: true,
485
- fullTextEngines: Object.freeze(["bm25", "tsvector"])
724
+ fullTextEngines: Object.freeze(["bm25", "tsvector"]),
725
+ rowLockStrengths: Object.freeze(["update", "noKeyUpdate", "share", "keyShare"]),
726
+ rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
727
+ advisoryLocks: true
486
728
  });
487
729
  var SQLITE_CAPABILITIES = Object.freeze({
730
+ exclusionConstraints: false,
731
+ exclusionExtensions: Object.freeze([]),
732
+ checkNormalization: "sourceText",
488
733
  nativeRowLevelSecurity: false,
489
734
  returning: true,
490
735
  scalarArrays: false,
@@ -498,9 +743,15 @@ var SQLITE_CAPABILITIES = Object.freeze({
498
743
  createManyAndReturn: true,
499
744
  jsonColumnType: false,
500
745
  orderByNulls: true,
501
- fullTextEngines: Object.freeze(["fts5"])
746
+ fullTextEngines: Object.freeze(["fts5"]),
747
+ rowLockStrengths: Object.freeze([]),
748
+ rowLockWaitPolicies: Object.freeze([]),
749
+ advisoryLocks: false
502
750
  });
503
751
  var MYSQL_CAPABILITIES = Object.freeze({
752
+ exclusionConstraints: false,
753
+ exclusionExtensions: Object.freeze([]),
754
+ checkNormalization: "temporary",
504
755
  nativeRowLevelSecurity: false,
505
756
  returning: false,
506
757
  scalarArrays: false,
@@ -514,7 +765,10 @@ var MYSQL_CAPABILITIES = Object.freeze({
514
765
  createManyAndReturn: false,
515
766
  jsonColumnType: true,
516
767
  orderByNulls: false,
517
- fullTextEngines: Object.freeze(["fulltext"])
768
+ fullTextEngines: Object.freeze(["fulltext"]),
769
+ rowLockStrengths: Object.freeze(["update", "share"]),
770
+ rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
771
+ advisoryLocks: false
518
772
  });
519
773
  var DIALECT_CAPABILITIES = Object.freeze({
520
774
  postgres: POSTGRES_CAPABILITIES,
@@ -524,6 +778,15 @@ var DIALECT_CAPABILITIES = Object.freeze({
524
778
 
525
779
  // packages/schema/src/validate.ts
526
780
  var RESERVED_ENUM_NAMES = [
781
+ "sql",
782
+ "decode",
783
+ "sqlProjection",
784
+ "sqlModels",
785
+ "createSqlModel",
786
+ "SqlColumn",
787
+ "SqlFragment",
788
+ "ResultDecoder",
789
+ "ExtendedDateTime",
527
790
  "enumValidators",
528
791
  "enumValidator",
529
792
  "OmittedKeys",
@@ -565,6 +828,7 @@ var RESERVED_ENUM_NAMES = [
565
828
  "SortOrderWithNulls",
566
829
  "KeysetToken",
567
830
  "KeysetOptions",
831
+ "RowLockRequest",
568
832
  "JsonValue",
569
833
  "InputJsonValue",
570
834
  "InputJsonArray",
@@ -577,6 +841,9 @@ var RESERVED_ENUM_NAMES = [
577
841
  "ScopedClientInstance",
578
842
  "ScopedModelsConfig",
579
843
  "ScopedNestedMode",
844
+ "VibeTransactionClientInstance",
845
+ "ScopedTransactionClientInstance",
846
+ "ReadOnlyClientInstance",
580
847
  "ViewClient",
581
848
  "ViewDefinitionInput",
582
849
  "ViewRow",
@@ -586,6 +853,7 @@ var RESERVED_ENUM_NAMES = [
586
853
  "QueryEvent",
587
854
  "Skip",
588
855
  "TransactionOptions",
856
+ "AdvisoryLockOptions",
589
857
  "SchemaIR",
590
858
  "VibeExtension",
591
859
  "ComputedConfig",
@@ -619,6 +887,9 @@ var GENERATED_TYPE_NAMES = {
619
887
  "WhereInput",
620
888
  "WhereUniqueInput",
621
889
  "OrderByInput",
890
+ "ScalarOrderByInput",
891
+ "RelationOrderByInput",
892
+ "RelationFindManyArgs",
622
893
  "CreateInput",
623
894
  "UncheckedCreateInput",
624
895
  "CreateManyInput",
@@ -674,7 +945,8 @@ var GENERATED_TYPE_NAMES = {
674
945
  "ViewRelationResult",
675
946
  "ViewIncludeResult",
676
947
  "ViewResult",
677
- "ViewDelegate"
948
+ "ViewDelegate",
949
+ "ReadOnlyDelegate"
678
950
  ],
679
951
  nestedPrefixes: [
680
952
  "Create",
@@ -693,10 +965,11 @@ var GENERATED_TYPE_NAMES = {
693
965
  };
694
966
  function validateGeneratedNames(params) {
695
967
  const { schema, issues } = params;
696
- const owners = new Map(RESERVED_ENUM_NAMES.map((name) => [name, "generated client"]));
968
+ const owners = new Map(RESERVED_ENUM_NAMES.map((name2) => [name2, "generated client"]));
697
969
  if (schema.rls !== undefined) {
698
970
  owners.set("RlsContext", "native RLS client");
699
971
  owners.set("BoundVibeClientInstance", "native RLS client");
972
+ owners.set("BoundVibeTransactionClientInstance", "native RLS client");
700
973
  }
701
974
  const register = (params2) => {
702
975
  const previous = owners.get(params2.name);
@@ -1081,6 +1354,15 @@ function validateModel(params) {
1081
1354
  });
1082
1355
  }
1083
1356
  checkNames.add(check.name);
1357
+ if (check.predicate !== undefined) {
1358
+ validatePredicateFields({
1359
+ predicate: check.predicate,
1360
+ model,
1361
+ fieldNames,
1362
+ site: `CHECK "${check.name}"`,
1363
+ issues
1364
+ });
1365
+ }
1084
1366
  }
1085
1367
  for (const index of model.indexes) {
1086
1368
  for (const indexField of index.fields) {
@@ -1093,13 +1375,98 @@ function validateModel(params) {
1093
1375
  });
1094
1376
  }
1095
1377
  }
1378
+ if (index.wherePredicate !== undefined) {
1379
+ validatePredicateFields({
1380
+ predicate: index.wherePredicate,
1381
+ model,
1382
+ fieldNames,
1383
+ site: `partial index on [${index.fields.map((entry) => entry.name).join(", ")}]`,
1384
+ issues
1385
+ });
1386
+ }
1387
+ }
1388
+ }
1389
+ function validatePredicateFields(params) {
1390
+ const { predicate, model, fieldNames, site, issues } = params;
1391
+ for (const name2 of predicateFieldNames({ predicate })) {
1392
+ if (!fieldNames.has(name2)) {
1393
+ issues.push({
1394
+ rule: "predicate-unknown-field",
1395
+ message: `${site} on "${model.name}" references unknown field "${name2}"`,
1396
+ model: model.name,
1397
+ field: name2
1398
+ });
1399
+ continue;
1400
+ }
1401
+ if (model.fields.find((field) => field.name === name2)?.isList === true) {
1402
+ issues.push({
1403
+ rule: "predicate-list-field",
1404
+ message: `${site} on "${model.name}" compares scalar list "${name2}" \u2014 use a raw expression for array operators`,
1405
+ model: model.name,
1406
+ field: name2
1407
+ });
1408
+ }
1096
1409
  }
1097
1410
  }
1098
1411
  function assertValidSchemaIR(params) {
1099
1412
  const dialect = providerToDialect({ provider: params.schema.datasource.provider });
1413
+ for (const model of params.schema.models) {
1414
+ if (model.exclusions !== undefined && !Array.isArray(model.exclusions))
1415
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusions must be an array", meta: { rule: "exclusion-definition" } });
1416
+ if ((model.exclusions?.length ?? 0) === 0)
1417
+ continue;
1418
+ if (!DIALECT_CAPABILITIES[dialect].exclusionConstraints)
1419
+ throw unsupportedCapability({ dialect, feature: "exclusionConstraints" });
1420
+ const names = new Set;
1421
+ const exclusions = model.exclusions ?? [];
1422
+ for (const exclusion of exclusions) {
1423
+ if (exclusion === null || typeof exclusion !== "object")
1424
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Invalid exclusion declaration", meta: { rule: "exclusion-definition" } });
1425
+ const source = exclusion.source;
1426
+ if (source !== undefined) {
1427
+ const references = [...source.keys.flatMap((key) => ("field" in key) ? [key.field] : key.fields), ...[source.range.lower, source.range.upper].flatMap((endpoint) => ("field" in endpoint) ? [endpoint.field] : []), ...source.where === undefined ? [] : predicateFieldNames({ predicate: source.where })];
1428
+ if (references.some((name2) => !model.fields.some((field) => field.name === name2 && !field.isList)))
1429
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusion references an unknown or list field", meta: { rule: "exclusion-field", model: model.name } });
1430
+ }
1431
+ if (!exclusion.name || names.has(exclusion.name) || typeof exclusion.definition !== "string" || !exclusion.definition.startsWith("EXCLUDE USING ") || !Array.isArray(exclusion.requiredExtensions) || exclusion.requiredExtensions.some((name2) => name2 !== "btree_gist"))
1432
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Invalid exclusion declaration", meta: { rule: "exclusion-definition", model: model.name } });
1433
+ names.add(exclusion.name);
1434
+ }
1435
+ }
1436
+ if (params.schema.migrationObjects !== undefined)
1437
+ assertMigrationObjects({ objects: params.schema.migrationObjects });
1100
1438
  if (params.schema.rls !== undefined && !DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
1101
1439
  throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
1102
1440
  }
1441
+ for (const model of params.schema.models) {
1442
+ const counters = model.fields.filter((field) => field.default?.kind === "autoincrement");
1443
+ if (dialect === "mysql" && counters.length > 1)
1444
+ throw unsupportedCapability({ dialect, feature: "multiple auto-increment columns" });
1445
+ for (const field of model.fields) {
1446
+ if (field.default?.kind === "list") {
1447
+ if (dialect !== "postgres")
1448
+ throw unsupportedCapability({ dialect, feature: "scalar-list defaults" });
1449
+ if (!field.isList)
1450
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `List default on non-list field ${model.name}.${field.name}` });
1451
+ scalarListDefault({ type: field.type, values: field.default.values });
1452
+ if (isEnumType(field.type)) {
1453
+ const name2 = field.type.enum;
1454
+ const members = params.schema.enums.find((entry) => entry.name === name2)?.values ?? [];
1455
+ if (field.default.values.some((value) => typeof value !== "string" || !members.includes(value)))
1456
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `Invalid enum-list default on ${model.name}.${field.name}` });
1457
+ }
1458
+ }
1459
+ if (field.default?.kind !== "autoincrement")
1460
+ continue;
1461
+ if (field.isList || field.isOptional || field.type !== "Int" && field.type !== "BigInt")
1462
+ throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `autoincrement requires a required scalar Int or BigInt: ${model.name}.${field.name}` });
1463
+ const pk = model.primaryKey?.fields ?? model.fields.filter((entry) => entry.isId).map((entry) => entry.name);
1464
+ if (dialect === "sqlite" && (pk.length !== 1 || pk[0] !== field.name))
1465
+ throw unsupportedCapability({ dialect, feature: `non-primary auto-increment column ${model.name}.${field.name}` });
1466
+ if (dialect === "mysql" && pk[0] !== field.name && !field.isUnique && !model.indexes.some((index) => index.fields[0]?.name === field.name))
1467
+ throw unsupportedCapability({ dialect, feature: `unindexed auto-increment column ${model.name}.${field.name}` });
1468
+ }
1469
+ }
1103
1470
  const issues = validateSchemaIR(params);
1104
1471
  if (issues.length > 0) {
1105
1472
  throw new VibeError({
@@ -1184,10 +1551,10 @@ function validateNativeRls(params) {
1184
1551
  tables.add(table);
1185
1552
  const columns = new Set;
1186
1553
  for (const field of model.fields) {
1187
- const name = field.dbName ?? field.name;
1188
- if (columns.has(name))
1189
- add({ rule: "rls-duplicate-column", message: `RLS model ${model.name} repeats physical column ${name}`, field: field.name });
1190
- columns.add(name);
1554
+ const name2 = field.dbName ?? field.name;
1555
+ if (columns.has(name2))
1556
+ add({ rule: "rls-duplicate-column", message: `RLS model ${model.name} repeats physical column ${name2}`, field: field.name });
1557
+ columns.add(name2);
1191
1558
  }
1192
1559
  if (model.policy !== undefined)
1193
1560
  add({ rule: "rls-policy-conflict", message: "Native RLS cannot combine with application-level ModelIR.policy" });
@@ -1240,7 +1607,7 @@ function validateNativeRls(params) {
1240
1607
  add({ rule: "rls-invalid-through", message: "Native RLS through requires a to-one relation with an actual, complete foreign key on this model" });
1241
1608
  continue;
1242
1609
  }
1243
- if (relation.isOptional || relation.fields?.some((name) => model.fields.find((field) => field.name === name)?.isOptional)) {
1610
+ if (relation.isOptional || relation.fields?.some((name2) => model.fields.find((field) => field.name === name2)?.isOptional)) {
1244
1611
  add({ rule: "rls-nullable-through", message: "Native RLS through requires a non-nullable parent relation and foreign key; NULL parent links cannot satisfy WITH CHECK. Make the link required or use a direct ownership rule", field: relation.name });
1245
1612
  continue;
1246
1613
  }
@@ -1272,8 +1639,8 @@ function validRlsForeignKey(params) {
1272
1639
  const refs = relation.references;
1273
1640
  if (fields === undefined || refs === undefined || fields.length === 0 || fields.length !== refs.length || new Set(fields).size !== fields.length || new Set(refs).size !== refs.length)
1274
1641
  return false;
1275
- const validFields = fields.every((name, index) => {
1276
- const field = model.fields.find((f) => f.name === name);
1642
+ const validFields = fields.every((name2, index) => {
1643
+ const field = model.fields.find((f) => f.name === name2);
1277
1644
  const ref = target.fields.find((f) => f.name === refs[index]);
1278
1645
  return field !== undefined && ref !== undefined && !field.isList && !ref.isList && !field.isUnsupported && !ref.isUnsupported && (field.type === ref.type || isEnumType(field.type) && isEnumType(ref.type) && field.type.enum === ref.type.enum);
1279
1646
  });
@@ -1282,7 +1649,7 @@ function validRlsForeignKey(params) {
1282
1649
  ...target.primaryKey === undefined ? [] : [target.primaryKey.fields],
1283
1650
  ...target.indexes.filter((index) => index.kind === "unique" && index.where === undefined).map((index) => index.fields.map((f) => f.name))
1284
1651
  ];
1285
- return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((name) => refs.includes(name)));
1652
+ return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((name2) => refs.includes(name2)));
1286
1653
  }
1287
1654
  // packages/schema/src/m2m.ts
1288
1655
  function implicitJoinTable(params) {
@@ -1303,15 +1670,24 @@ export {
1303
1670
  validateRlsContextIR,
1304
1671
  validateComputedConfig,
1305
1672
  unsupportedCapability,
1673
+ sqlObjectKey,
1674
+ serializeJsonDefault,
1675
+ scalarListDefault,
1306
1676
  providerToDialect,
1677
+ predicateKey,
1678
+ predicateFieldNames,
1307
1679
  mergeExtensionDeclarations,
1680
+ isSqlObjectIdentity,
1681
+ isSqlObjectDefinition,
1308
1682
  isReservedDelegateName,
1309
1683
  isReservedClientName,
1684
+ isPredicateFieldRef,
1310
1685
  isImplicitManyToManyRelation,
1311
1686
  isEnumType,
1312
1687
  invalidStoredData,
1313
1688
  internalError,
1314
1689
  implicitJoinTable,
1690
+ fieldTypeIdentity,
1315
1691
  extensionOptionsHash,
1316
1692
  extensionManifestEntry,
1317
1693
  extensionDeclarations,
@@ -1323,16 +1699,20 @@ export {
1323
1699
  buildExtensionRegistry,
1324
1700
  buildComputedManifest,
1325
1701
  assertValidSchemaIR,
1702
+ assertMigrationObjects,
1326
1703
  VibeError,
1327
1704
  VIBE_ERROR_CODES,
1328
1705
  ScopedQueryError,
1329
1706
  SQLITE_CAPABILITIES,
1330
1707
  SCOPED_REFUSAL_REASONS,
1331
1708
  RlsContextError,
1709
+ ReadOnlyClientError,
1332
1710
  RLS_CONTEXT_REASONS,
1333
1711
  RESERVED_ENUM_NAMES,
1334
1712
  RESERVED_DELEGATE_NAMES,
1335
1713
  RESERVED_CLIENT_NAMES,
1714
+ READ_ONLY_REFUSAL_REASONS,
1715
+ PREDICATE_COMPARISON_OPS,
1336
1716
  POSTGRES_CAPABILITIES,
1337
1717
  MYSQL_CAPABILITIES,
1338
1718
  ENUM_STORAGES,
@@ -1341,4 +1721,4 @@ export {
1341
1721
  DIALECTS
1342
1722
  };
1343
1723
 
1344
- //# debugId=917A26DA81AB84E364756E2164756E21
1724
+ //# debugId=D5526EF61FB41A9D64756E2164756E21