@vibeorm/schema 2.0.0 → 2.1.1
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 +301 -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,8 @@ var RESERVED_ENUM_NAMES = [
|
|
|
662
853
|
"QueryEvent",
|
|
663
854
|
"Skip",
|
|
664
855
|
"TransactionOptions",
|
|
856
|
+
"AdvisoryLockOptions",
|
|
857
|
+
"StatementDiagnosticsOptions",
|
|
665
858
|
"SchemaIR",
|
|
666
859
|
"VibeExtension",
|
|
667
860
|
"ComputedConfig",
|
|
@@ -695,6 +888,9 @@ var GENERATED_TYPE_NAMES = {
|
|
|
695
888
|
"WhereInput",
|
|
696
889
|
"WhereUniqueInput",
|
|
697
890
|
"OrderByInput",
|
|
891
|
+
"ScalarOrderByInput",
|
|
892
|
+
"RelationOrderByInput",
|
|
893
|
+
"RelationFindManyArgs",
|
|
698
894
|
"CreateInput",
|
|
699
895
|
"UncheckedCreateInput",
|
|
700
896
|
"CreateManyInput",
|
|
@@ -750,7 +946,8 @@ var GENERATED_TYPE_NAMES = {
|
|
|
750
946
|
"ViewRelationResult",
|
|
751
947
|
"ViewIncludeResult",
|
|
752
948
|
"ViewResult",
|
|
753
|
-
"ViewDelegate"
|
|
949
|
+
"ViewDelegate",
|
|
950
|
+
"ReadOnlyDelegate"
|
|
754
951
|
],
|
|
755
952
|
nestedPrefixes: [
|
|
756
953
|
"Create",
|
|
@@ -769,10 +966,11 @@ var GENERATED_TYPE_NAMES = {
|
|
|
769
966
|
};
|
|
770
967
|
function validateGeneratedNames(params) {
|
|
771
968
|
const { schema, issues } = params;
|
|
772
|
-
const owners = new Map(RESERVED_ENUM_NAMES.map((
|
|
969
|
+
const owners = new Map(RESERVED_ENUM_NAMES.map((name2) => [name2, "generated client"]));
|
|
773
970
|
if (schema.rls !== undefined) {
|
|
774
971
|
owners.set("RlsContext", "native RLS client");
|
|
775
972
|
owners.set("BoundVibeClientInstance", "native RLS client");
|
|
973
|
+
owners.set("BoundVibeTransactionClientInstance", "native RLS client");
|
|
776
974
|
}
|
|
777
975
|
const register = (params2) => {
|
|
778
976
|
const previous = owners.get(params2.name);
|
|
@@ -1157,6 +1355,15 @@ function validateModel(params) {
|
|
|
1157
1355
|
});
|
|
1158
1356
|
}
|
|
1159
1357
|
checkNames.add(check.name);
|
|
1358
|
+
if (check.predicate !== undefined) {
|
|
1359
|
+
validatePredicateFields({
|
|
1360
|
+
predicate: check.predicate,
|
|
1361
|
+
model,
|
|
1362
|
+
fieldNames,
|
|
1363
|
+
site: `CHECK "${check.name}"`,
|
|
1364
|
+
issues
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1160
1367
|
}
|
|
1161
1368
|
for (const index of model.indexes) {
|
|
1162
1369
|
for (const indexField of index.fields) {
|
|
@@ -1169,10 +1376,66 @@ function validateModel(params) {
|
|
|
1169
1376
|
});
|
|
1170
1377
|
}
|
|
1171
1378
|
}
|
|
1379
|
+
if (index.wherePredicate !== undefined) {
|
|
1380
|
+
validatePredicateFields({
|
|
1381
|
+
predicate: index.wherePredicate,
|
|
1382
|
+
model,
|
|
1383
|
+
fieldNames,
|
|
1384
|
+
site: `partial index on [${index.fields.map((entry) => entry.name).join(", ")}]`,
|
|
1385
|
+
issues
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
function validatePredicateFields(params) {
|
|
1391
|
+
const { predicate, model, fieldNames, site, issues } = params;
|
|
1392
|
+
for (const name2 of predicateFieldNames({ predicate })) {
|
|
1393
|
+
if (!fieldNames.has(name2)) {
|
|
1394
|
+
issues.push({
|
|
1395
|
+
rule: "predicate-unknown-field",
|
|
1396
|
+
message: `${site} on "${model.name}" references unknown field "${name2}"`,
|
|
1397
|
+
model: model.name,
|
|
1398
|
+
field: name2
|
|
1399
|
+
});
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
if (model.fields.find((field) => field.name === name2)?.isList === true) {
|
|
1403
|
+
issues.push({
|
|
1404
|
+
rule: "predicate-list-field",
|
|
1405
|
+
message: `${site} on "${model.name}" compares scalar list "${name2}" \u2014 use a raw expression for array operators`,
|
|
1406
|
+
model: model.name,
|
|
1407
|
+
field: name2
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1172
1410
|
}
|
|
1173
1411
|
}
|
|
1174
1412
|
function assertValidSchemaIR(params) {
|
|
1175
1413
|
const dialect = providerToDialect({ provider: params.schema.datasource.provider });
|
|
1414
|
+
for (const model of params.schema.models) {
|
|
1415
|
+
if (model.exclusions !== undefined && !Array.isArray(model.exclusions))
|
|
1416
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusions must be an array", meta: { rule: "exclusion-definition" } });
|
|
1417
|
+
if ((model.exclusions?.length ?? 0) === 0)
|
|
1418
|
+
continue;
|
|
1419
|
+
if (!DIALECT_CAPABILITIES[dialect].exclusionConstraints)
|
|
1420
|
+
throw unsupportedCapability({ dialect, feature: "exclusionConstraints" });
|
|
1421
|
+
const names = new Set;
|
|
1422
|
+
const exclusions = model.exclusions ?? [];
|
|
1423
|
+
for (const exclusion of exclusions) {
|
|
1424
|
+
if (exclusion === null || typeof exclusion !== "object")
|
|
1425
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Invalid exclusion declaration", meta: { rule: "exclusion-definition" } });
|
|
1426
|
+
const source = exclusion.source;
|
|
1427
|
+
if (source !== undefined) {
|
|
1428
|
+
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 })];
|
|
1429
|
+
if (references.some((name2) => !model.fields.some((field) => field.name === name2 && !field.isList)))
|
|
1430
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusion references an unknown or list field", meta: { rule: "exclusion-field", model: model.name } });
|
|
1431
|
+
}
|
|
1432
|
+
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"))
|
|
1433
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Invalid exclusion declaration", meta: { rule: "exclusion-definition", model: model.name } });
|
|
1434
|
+
names.add(exclusion.name);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
if (params.schema.migrationObjects !== undefined)
|
|
1438
|
+
assertMigrationObjects({ objects: params.schema.migrationObjects });
|
|
1176
1439
|
if (params.schema.rls !== undefined && !DIALECT_CAPABILITIES[dialect].nativeRowLevelSecurity) {
|
|
1177
1440
|
throw unsupportedCapability({ dialect, feature: "nativeRowLevelSecurity" });
|
|
1178
1441
|
}
|
|
@@ -1188,8 +1451,8 @@ function assertValidSchemaIR(params) {
|
|
|
1188
1451
|
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `List default on non-list field ${model.name}.${field.name}` });
|
|
1189
1452
|
scalarListDefault({ type: field.type, values: field.default.values });
|
|
1190
1453
|
if (isEnumType(field.type)) {
|
|
1191
|
-
const
|
|
1192
|
-
const members = params.schema.enums.find((entry) => entry.name ===
|
|
1454
|
+
const name2 = field.type.enum;
|
|
1455
|
+
const members = params.schema.enums.find((entry) => entry.name === name2)?.values ?? [];
|
|
1193
1456
|
if (field.default.values.some((value) => typeof value !== "string" || !members.includes(value)))
|
|
1194
1457
|
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `Invalid enum-list default on ${model.name}.${field.name}` });
|
|
1195
1458
|
}
|
|
@@ -1289,10 +1552,10 @@ function validateNativeRls(params) {
|
|
|
1289
1552
|
tables.add(table);
|
|
1290
1553
|
const columns = new Set;
|
|
1291
1554
|
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(
|
|
1555
|
+
const name2 = field.dbName ?? field.name;
|
|
1556
|
+
if (columns.has(name2))
|
|
1557
|
+
add({ rule: "rls-duplicate-column", message: `RLS model ${model.name} repeats physical column ${name2}`, field: field.name });
|
|
1558
|
+
columns.add(name2);
|
|
1296
1559
|
}
|
|
1297
1560
|
if (model.policy !== undefined)
|
|
1298
1561
|
add({ rule: "rls-policy-conflict", message: "Native RLS cannot combine with application-level ModelIR.policy" });
|
|
@@ -1345,7 +1608,7 @@ function validateNativeRls(params) {
|
|
|
1345
1608
|
add({ rule: "rls-invalid-through", message: "Native RLS through requires a to-one relation with an actual, complete foreign key on this model" });
|
|
1346
1609
|
continue;
|
|
1347
1610
|
}
|
|
1348
|
-
if (relation.isOptional || relation.fields?.some((
|
|
1611
|
+
if (relation.isOptional || relation.fields?.some((name2) => model.fields.find((field) => field.name === name2)?.isOptional)) {
|
|
1349
1612
|
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
1613
|
continue;
|
|
1351
1614
|
}
|
|
@@ -1377,8 +1640,8 @@ function validRlsForeignKey(params) {
|
|
|
1377
1640
|
const refs = relation.references;
|
|
1378
1641
|
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
1642
|
return false;
|
|
1380
|
-
const validFields = fields.every((
|
|
1381
|
-
const field = model.fields.find((f) => f.name ===
|
|
1643
|
+
const validFields = fields.every((name2, index) => {
|
|
1644
|
+
const field = model.fields.find((f) => f.name === name2);
|
|
1382
1645
|
const ref = target.fields.find((f) => f.name === refs[index]);
|
|
1383
1646
|
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
1647
|
});
|
|
@@ -1387,7 +1650,7 @@ function validRlsForeignKey(params) {
|
|
|
1387
1650
|
...target.primaryKey === undefined ? [] : [target.primaryKey.fields],
|
|
1388
1651
|
...target.indexes.filter((index) => index.kind === "unique" && index.where === undefined).map((index) => index.fields.map((f) => f.name))
|
|
1389
1652
|
];
|
|
1390
|
-
return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((
|
|
1653
|
+
return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((name2) => refs.includes(name2)));
|
|
1391
1654
|
}
|
|
1392
1655
|
// packages/schema/src/m2m.ts
|
|
1393
1656
|
function implicitJoinTable(params) {
|
|
@@ -1408,17 +1671,24 @@ export {
|
|
|
1408
1671
|
validateRlsContextIR,
|
|
1409
1672
|
validateComputedConfig,
|
|
1410
1673
|
unsupportedCapability,
|
|
1674
|
+
sqlObjectKey,
|
|
1411
1675
|
serializeJsonDefault,
|
|
1412
1676
|
scalarListDefault,
|
|
1413
1677
|
providerToDialect,
|
|
1678
|
+
predicateKey,
|
|
1679
|
+
predicateFieldNames,
|
|
1414
1680
|
mergeExtensionDeclarations,
|
|
1681
|
+
isSqlObjectIdentity,
|
|
1682
|
+
isSqlObjectDefinition,
|
|
1415
1683
|
isReservedDelegateName,
|
|
1416
1684
|
isReservedClientName,
|
|
1685
|
+
isPredicateFieldRef,
|
|
1417
1686
|
isImplicitManyToManyRelation,
|
|
1418
1687
|
isEnumType,
|
|
1419
1688
|
invalidStoredData,
|
|
1420
1689
|
internalError,
|
|
1421
1690
|
implicitJoinTable,
|
|
1691
|
+
fieldTypeIdentity,
|
|
1422
1692
|
extensionOptionsHash,
|
|
1423
1693
|
extensionManifestEntry,
|
|
1424
1694
|
extensionDeclarations,
|
|
@@ -1430,16 +1700,20 @@ export {
|
|
|
1430
1700
|
buildExtensionRegistry,
|
|
1431
1701
|
buildComputedManifest,
|
|
1432
1702
|
assertValidSchemaIR,
|
|
1703
|
+
assertMigrationObjects,
|
|
1433
1704
|
VibeError,
|
|
1434
1705
|
VIBE_ERROR_CODES,
|
|
1435
1706
|
ScopedQueryError,
|
|
1436
1707
|
SQLITE_CAPABILITIES,
|
|
1437
1708
|
SCOPED_REFUSAL_REASONS,
|
|
1438
1709
|
RlsContextError,
|
|
1710
|
+
ReadOnlyClientError,
|
|
1439
1711
|
RLS_CONTEXT_REASONS,
|
|
1440
1712
|
RESERVED_ENUM_NAMES,
|
|
1441
1713
|
RESERVED_DELEGATE_NAMES,
|
|
1442
1714
|
RESERVED_CLIENT_NAMES,
|
|
1715
|
+
READ_ONLY_REFUSAL_REASONS,
|
|
1716
|
+
PREDICATE_COMPARISON_OPS,
|
|
1443
1717
|
POSTGRES_CAPABILITIES,
|
|
1444
1718
|
MYSQL_CAPABILITIES,
|
|
1445
1719
|
ENUM_STORAGES,
|
|
@@ -1448,4 +1722,4 @@ export {
|
|
|
1448
1722
|
DIALECTS
|
|
1449
1723
|
};
|
|
1450
1724
|
|
|
1451
|
-
//# debugId=
|
|
1725
|
+
//# debugId=A977A79FC940B2A264756E2164756E21
|