@vibeorm/schema 2.0.0 → 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/capabilities.d.ts +48 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/errors.d.ts +59 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/exclusion.d.ts +40 -0
- package/dist/exclusion.d.ts.map +1 -0
- package/dist/extension.d.ts +12 -0
- package/dist/extension.d.ts.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -27
- package/dist/index.js.map +11 -9
- package/dist/ir.d.ts +46 -4
- package/dist/ir.d.ts.map +1 -1
- package/dist/migration-objects.d.ts +63 -0
- package/dist/migration-objects.d.ts.map +1 -0
- package/dist/predicate.d.ts +105 -0
- package/dist/predicate.d.ts.map +1 -0
- package/dist/reserved.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/package.json +1 -1
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(
|
|
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 "${
|
|
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(
|
|
473
|
+
if (RESERVED_DELEGATE_NAMES.includes(name2)) {
|
|
314
474
|
throw new VibeError({
|
|
315
475
|
code: "VIBE_CONFIG",
|
|
316
|
-
message: `extension name "${
|
|
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(`$${
|
|
480
|
+
if (RESERVED_CLIENT_NAMES.includes(`$${name2}`)) {
|
|
321
481
|
throw new VibeError({
|
|
322
482
|
code: "VIBE_CONFIG",
|
|
323
|
-
message: `extension 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);
|
|
@@ -545,6 +705,9 @@ function scalarListDefault(params) {
|
|
|
545
705
|
|
|
546
706
|
// packages/schema/src/capabilities.ts
|
|
547
707
|
var POSTGRES_CAPABILITIES = Object.freeze({
|
|
708
|
+
exclusionConstraints: true,
|
|
709
|
+
exclusionExtensions: Object.freeze(["btree_gist"]),
|
|
710
|
+
checkNormalization: "rollback",
|
|
548
711
|
nativeRowLevelSecurity: true,
|
|
549
712
|
returning: true,
|
|
550
713
|
scalarArrays: true,
|
|
@@ -558,9 +721,15 @@ var POSTGRES_CAPABILITIES = Object.freeze({
|
|
|
558
721
|
createManyAndReturn: true,
|
|
559
722
|
jsonColumnType: true,
|
|
560
723
|
orderByNulls: true,
|
|
561
|
-
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
|
|
562
728
|
});
|
|
563
729
|
var SQLITE_CAPABILITIES = Object.freeze({
|
|
730
|
+
exclusionConstraints: false,
|
|
731
|
+
exclusionExtensions: Object.freeze([]),
|
|
732
|
+
checkNormalization: "sourceText",
|
|
564
733
|
nativeRowLevelSecurity: false,
|
|
565
734
|
returning: true,
|
|
566
735
|
scalarArrays: false,
|
|
@@ -574,9 +743,15 @@ var SQLITE_CAPABILITIES = Object.freeze({
|
|
|
574
743
|
createManyAndReturn: true,
|
|
575
744
|
jsonColumnType: false,
|
|
576
745
|
orderByNulls: true,
|
|
577
|
-
fullTextEngines: Object.freeze(["fts5"])
|
|
746
|
+
fullTextEngines: Object.freeze(["fts5"]),
|
|
747
|
+
rowLockStrengths: Object.freeze([]),
|
|
748
|
+
rowLockWaitPolicies: Object.freeze([]),
|
|
749
|
+
advisoryLocks: false
|
|
578
750
|
});
|
|
579
751
|
var MYSQL_CAPABILITIES = Object.freeze({
|
|
752
|
+
exclusionConstraints: false,
|
|
753
|
+
exclusionExtensions: Object.freeze([]),
|
|
754
|
+
checkNormalization: "temporary",
|
|
580
755
|
nativeRowLevelSecurity: false,
|
|
581
756
|
returning: false,
|
|
582
757
|
scalarArrays: false,
|
|
@@ -590,7 +765,10 @@ var MYSQL_CAPABILITIES = Object.freeze({
|
|
|
590
765
|
createManyAndReturn: false,
|
|
591
766
|
jsonColumnType: true,
|
|
592
767
|
orderByNulls: false,
|
|
593
|
-
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
|
|
594
772
|
});
|
|
595
773
|
var DIALECT_CAPABILITIES = Object.freeze({
|
|
596
774
|
postgres: POSTGRES_CAPABILITIES,
|
|
@@ -600,6 +778,15 @@ var DIALECT_CAPABILITIES = Object.freeze({
|
|
|
600
778
|
|
|
601
779
|
// packages/schema/src/validate.ts
|
|
602
780
|
var RESERVED_ENUM_NAMES = [
|
|
781
|
+
"sql",
|
|
782
|
+
"decode",
|
|
783
|
+
"sqlProjection",
|
|
784
|
+
"sqlModels",
|
|
785
|
+
"createSqlModel",
|
|
786
|
+
"SqlColumn",
|
|
787
|
+
"SqlFragment",
|
|
788
|
+
"ResultDecoder",
|
|
789
|
+
"ExtendedDateTime",
|
|
603
790
|
"enumValidators",
|
|
604
791
|
"enumValidator",
|
|
605
792
|
"OmittedKeys",
|
|
@@ -641,6 +828,7 @@ var RESERVED_ENUM_NAMES = [
|
|
|
641
828
|
"SortOrderWithNulls",
|
|
642
829
|
"KeysetToken",
|
|
643
830
|
"KeysetOptions",
|
|
831
|
+
"RowLockRequest",
|
|
644
832
|
"JsonValue",
|
|
645
833
|
"InputJsonValue",
|
|
646
834
|
"InputJsonArray",
|
|
@@ -653,6 +841,9 @@ var RESERVED_ENUM_NAMES = [
|
|
|
653
841
|
"ScopedClientInstance",
|
|
654
842
|
"ScopedModelsConfig",
|
|
655
843
|
"ScopedNestedMode",
|
|
844
|
+
"VibeTransactionClientInstance",
|
|
845
|
+
"ScopedTransactionClientInstance",
|
|
846
|
+
"ReadOnlyClientInstance",
|
|
656
847
|
"ViewClient",
|
|
657
848
|
"ViewDefinitionInput",
|
|
658
849
|
"ViewRow",
|
|
@@ -662,6 +853,7 @@ var RESERVED_ENUM_NAMES = [
|
|
|
662
853
|
"QueryEvent",
|
|
663
854
|
"Skip",
|
|
664
855
|
"TransactionOptions",
|
|
856
|
+
"AdvisoryLockOptions",
|
|
665
857
|
"SchemaIR",
|
|
666
858
|
"VibeExtension",
|
|
667
859
|
"ComputedConfig",
|
|
@@ -695,6 +887,9 @@ var GENERATED_TYPE_NAMES = {
|
|
|
695
887
|
"WhereInput",
|
|
696
888
|
"WhereUniqueInput",
|
|
697
889
|
"OrderByInput",
|
|
890
|
+
"ScalarOrderByInput",
|
|
891
|
+
"RelationOrderByInput",
|
|
892
|
+
"RelationFindManyArgs",
|
|
698
893
|
"CreateInput",
|
|
699
894
|
"UncheckedCreateInput",
|
|
700
895
|
"CreateManyInput",
|
|
@@ -750,7 +945,8 @@ var GENERATED_TYPE_NAMES = {
|
|
|
750
945
|
"ViewRelationResult",
|
|
751
946
|
"ViewIncludeResult",
|
|
752
947
|
"ViewResult",
|
|
753
|
-
"ViewDelegate"
|
|
948
|
+
"ViewDelegate",
|
|
949
|
+
"ReadOnlyDelegate"
|
|
754
950
|
],
|
|
755
951
|
nestedPrefixes: [
|
|
756
952
|
"Create",
|
|
@@ -769,10 +965,11 @@ var GENERATED_TYPE_NAMES = {
|
|
|
769
965
|
};
|
|
770
966
|
function validateGeneratedNames(params) {
|
|
771
967
|
const { schema, issues } = params;
|
|
772
|
-
const owners = new Map(RESERVED_ENUM_NAMES.map((
|
|
968
|
+
const owners = new Map(RESERVED_ENUM_NAMES.map((name2) => [name2, "generated client"]));
|
|
773
969
|
if (schema.rls !== undefined) {
|
|
774
970
|
owners.set("RlsContext", "native RLS client");
|
|
775
971
|
owners.set("BoundVibeClientInstance", "native RLS client");
|
|
972
|
+
owners.set("BoundVibeTransactionClientInstance", "native RLS client");
|
|
776
973
|
}
|
|
777
974
|
const register = (params2) => {
|
|
778
975
|
const previous = owners.get(params2.name);
|
|
@@ -1157,6 +1354,15 @@ function validateModel(params) {
|
|
|
1157
1354
|
});
|
|
1158
1355
|
}
|
|
1159
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
|
+
}
|
|
1160
1366
|
}
|
|
1161
1367
|
for (const index of model.indexes) {
|
|
1162
1368
|
for (const indexField of index.fields) {
|
|
@@ -1169,10 +1375,66 @@ function validateModel(params) {
|
|
|
1169
1375
|
});
|
|
1170
1376
|
}
|
|
1171
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
|
+
}
|
|
1172
1409
|
}
|
|
1173
1410
|
}
|
|
1174
1411
|
function assertValidSchemaIR(params) {
|
|
1175
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 });
|
|
1176
1438
|
if (params.schema.rls !== undefined && !DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
|
|
1177
1439
|
throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
|
|
1178
1440
|
}
|
|
@@ -1188,8 +1450,8 @@ function assertValidSchemaIR(params) {
|
|
|
1188
1450
|
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `List default on non-list field ${model.name}.${field.name}` });
|
|
1189
1451
|
scalarListDefault({ type: field.type, values: field.default.values });
|
|
1190
1452
|
if (isEnumType(field.type)) {
|
|
1191
|
-
const
|
|
1192
|
-
const members = params.schema.enums.find((entry) => entry.name ===
|
|
1453
|
+
const name2 = field.type.enum;
|
|
1454
|
+
const members = params.schema.enums.find((entry) => entry.name === name2)?.values ?? [];
|
|
1193
1455
|
if (field.default.values.some((value) => typeof value !== "string" || !members.includes(value)))
|
|
1194
1456
|
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `Invalid enum-list default on ${model.name}.${field.name}` });
|
|
1195
1457
|
}
|
|
@@ -1289,10 +1551,10 @@ function validateNativeRls(params) {
|
|
|
1289
1551
|
tables.add(table);
|
|
1290
1552
|
const columns = new Set;
|
|
1291
1553
|
for (const field of model.fields) {
|
|
1292
|
-
const
|
|
1293
|
-
if (columns.has(
|
|
1294
|
-
add({ rule: "rls-duplicate-column", message: `RLS model ${model.name} repeats physical column ${
|
|
1295
|
-
columns.add(
|
|
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);
|
|
1296
1558
|
}
|
|
1297
1559
|
if (model.policy !== undefined)
|
|
1298
1560
|
add({ rule: "rls-policy-conflict", message: "Native RLS cannot combine with application-level ModelIR.policy" });
|
|
@@ -1345,7 +1607,7 @@ function validateNativeRls(params) {
|
|
|
1345
1607
|
add({ rule: "rls-invalid-through", message: "Native RLS through requires a to-one relation with an actual, complete foreign key on this model" });
|
|
1346
1608
|
continue;
|
|
1347
1609
|
}
|
|
1348
|
-
if (relation.isOptional || relation.fields?.some((
|
|
1610
|
+
if (relation.isOptional || relation.fields?.some((name2) => model.fields.find((field) => field.name === name2)?.isOptional)) {
|
|
1349
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 });
|
|
1350
1612
|
continue;
|
|
1351
1613
|
}
|
|
@@ -1377,8 +1639,8 @@ function validRlsForeignKey(params) {
|
|
|
1377
1639
|
const refs = relation.references;
|
|
1378
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)
|
|
1379
1641
|
return false;
|
|
1380
|
-
const validFields = fields.every((
|
|
1381
|
-
const field = model.fields.find((f) => f.name ===
|
|
1642
|
+
const validFields = fields.every((name2, index) => {
|
|
1643
|
+
const field = model.fields.find((f) => f.name === name2);
|
|
1382
1644
|
const ref = target.fields.find((f) => f.name === refs[index]);
|
|
1383
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);
|
|
1384
1646
|
});
|
|
@@ -1387,7 +1649,7 @@ function validRlsForeignKey(params) {
|
|
|
1387
1649
|
...target.primaryKey === undefined ? [] : [target.primaryKey.fields],
|
|
1388
1650
|
...target.indexes.filter((index) => index.kind === "unique" && index.where === undefined).map((index) => index.fields.map((f) => f.name))
|
|
1389
1651
|
];
|
|
1390
|
-
return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((
|
|
1652
|
+
return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((name2) => refs.includes(name2)));
|
|
1391
1653
|
}
|
|
1392
1654
|
// packages/schema/src/m2m.ts
|
|
1393
1655
|
function implicitJoinTable(params) {
|
|
@@ -1408,17 +1670,24 @@ export {
|
|
|
1408
1670
|
validateRlsContextIR,
|
|
1409
1671
|
validateComputedConfig,
|
|
1410
1672
|
unsupportedCapability,
|
|
1673
|
+
sqlObjectKey,
|
|
1411
1674
|
serializeJsonDefault,
|
|
1412
1675
|
scalarListDefault,
|
|
1413
1676
|
providerToDialect,
|
|
1677
|
+
predicateKey,
|
|
1678
|
+
predicateFieldNames,
|
|
1414
1679
|
mergeExtensionDeclarations,
|
|
1680
|
+
isSqlObjectIdentity,
|
|
1681
|
+
isSqlObjectDefinition,
|
|
1415
1682
|
isReservedDelegateName,
|
|
1416
1683
|
isReservedClientName,
|
|
1684
|
+
isPredicateFieldRef,
|
|
1417
1685
|
isImplicitManyToManyRelation,
|
|
1418
1686
|
isEnumType,
|
|
1419
1687
|
invalidStoredData,
|
|
1420
1688
|
internalError,
|
|
1421
1689
|
implicitJoinTable,
|
|
1690
|
+
fieldTypeIdentity,
|
|
1422
1691
|
extensionOptionsHash,
|
|
1423
1692
|
extensionManifestEntry,
|
|
1424
1693
|
extensionDeclarations,
|
|
@@ -1430,16 +1699,20 @@ export {
|
|
|
1430
1699
|
buildExtensionRegistry,
|
|
1431
1700
|
buildComputedManifest,
|
|
1432
1701
|
assertValidSchemaIR,
|
|
1702
|
+
assertMigrationObjects,
|
|
1433
1703
|
VibeError,
|
|
1434
1704
|
VIBE_ERROR_CODES,
|
|
1435
1705
|
ScopedQueryError,
|
|
1436
1706
|
SQLITE_CAPABILITIES,
|
|
1437
1707
|
SCOPED_REFUSAL_REASONS,
|
|
1438
1708
|
RlsContextError,
|
|
1709
|
+
ReadOnlyClientError,
|
|
1439
1710
|
RLS_CONTEXT_REASONS,
|
|
1440
1711
|
RESERVED_ENUM_NAMES,
|
|
1441
1712
|
RESERVED_DELEGATE_NAMES,
|
|
1442
1713
|
RESERVED_CLIENT_NAMES,
|
|
1714
|
+
READ_ONLY_REFUSAL_REASONS,
|
|
1715
|
+
PREDICATE_COMPARISON_OPS,
|
|
1443
1716
|
POSTGRES_CAPABILITIES,
|
|
1444
1717
|
MYSQL_CAPABILITIES,
|
|
1445
1718
|
ENUM_STORAGES,
|
|
@@ -1448,4 +1721,4 @@ export {
|
|
|
1448
1721
|
DIALECTS
|
|
1449
1722
|
};
|
|
1450
1723
|
|
|
1451
|
-
//# debugId=
|
|
1724
|
+
//# debugId=D5526EF61FB41A9D64756E2164756E21
|