@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/README.md +2 -2
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/default-values.d.ts +11 -0
- package/dist/default-values.d.ts.map +1 -0
- 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 +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -25
- package/dist/index.js.map +12 -9
- package/dist/ir.d.ts +49 -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);
|
|
@@ -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((
|
|
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
|
|
1188
|
-
if (columns.has(
|
|
1189
|
-
add({ rule: "rls-duplicate-column", message: `RLS model ${model.name} repeats physical column ${
|
|
1190
|
-
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);
|
|
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((
|
|
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((
|
|
1276
|
-
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);
|
|
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((
|
|
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=
|
|
1724
|
+
//# debugId=D5526EF61FB41A9D64756E2164756E21
|