@vibeorm/schema 2.4.0 → 2.5.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 +6 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/database-module.d.ts +2 -0
- package/dist/database-module.d.ts.map +1 -1
- package/dist/index-operator-classes.d.ts +8 -0
- package/dist/index-operator-classes.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +511 -271
- package/dist/index.js.map +13 -10
- package/dist/ir.d.ts +9 -1
- package/dist/ir.d.ts.map +1 -1
- package/dist/migration-objects.d.ts +7 -0
- package/dist/migration-objects.d.ts.map +1 -1
- package/dist/module-schema.d.ts +8 -0
- package/dist/module-schema.d.ts.map +1 -0
- package/dist/namespaces.d.ts +30 -0
- package/dist/namespaces.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
|
@@ -1,85 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// packages/schema/src/ir.ts
|
|
3
|
-
var DIALECTS = ["postgres", "sqlite", "mysql"];
|
|
4
|
-
function providerToDialect(params) {
|
|
5
|
-
const { provider } = params;
|
|
6
|
-
switch (provider) {
|
|
7
|
-
case "postgresql":
|
|
8
|
-
case "pglite":
|
|
9
|
-
return "postgres";
|
|
10
|
-
case "sqlite":
|
|
11
|
-
return "sqlite";
|
|
12
|
-
case "mysql":
|
|
13
|
-
return "mysql";
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function isEnumType(fieldType) {
|
|
17
|
-
return typeof fieldType === "object" && "enum" in fieldType;
|
|
18
|
-
}
|
|
19
|
-
function fieldTypeIdentity(params) {
|
|
20
|
-
const { type } = params;
|
|
21
|
-
return isEnumType(type) ? `enum:${type.enum}` : type;
|
|
22
|
-
}
|
|
23
|
-
var ENUM_STORAGES = ["database", "string"];
|
|
24
|
-
function enumStorageOf(params) {
|
|
25
|
-
return params.enumDef.storage ?? "database";
|
|
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
|
-
}
|
|
83
2
|
// packages/schema/src/errors.ts
|
|
84
3
|
var VIBE_ERROR_CODES = [
|
|
85
4
|
"VIBE_NOT_FOUND",
|
|
@@ -223,10 +142,203 @@ class ReadOnlyClientError extends VibeError {
|
|
|
223
142
|
}
|
|
224
143
|
}
|
|
225
144
|
|
|
145
|
+
// packages/schema/src/physical-names.ts
|
|
146
|
+
var IDENTIFIER_LIMITS = Object.freeze({
|
|
147
|
+
postgres: { max: 63, unit: "bytes" },
|
|
148
|
+
mysql: { max: 64, unit: "characters" },
|
|
149
|
+
sqlite: undefined
|
|
150
|
+
});
|
|
151
|
+
var ENCODER = new TextEncoder;
|
|
152
|
+
function identifierLength(params) {
|
|
153
|
+
const limit = IDENTIFIER_LIMITS[params.dialect];
|
|
154
|
+
return limit?.unit === "bytes" ? ENCODER.encode(params.name).length : [...params.name].length;
|
|
155
|
+
}
|
|
156
|
+
function cutIdentifier(params) {
|
|
157
|
+
let out = "";
|
|
158
|
+
let used = 0;
|
|
159
|
+
for (const char of params.name) {
|
|
160
|
+
const size = params.unit === "bytes" ? ENCODER.encode(char).length : 1;
|
|
161
|
+
if (used + size > params.length)
|
|
162
|
+
break;
|
|
163
|
+
out += char;
|
|
164
|
+
used += size;
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
function fitDefaultName(params) {
|
|
169
|
+
const limit = IDENTIFIER_LIMITS[params.dialect];
|
|
170
|
+
if (limit === undefined)
|
|
171
|
+
return `${params.prefix}${params.suffix}`;
|
|
172
|
+
const room = limit.max - identifierLength({ name: params.suffix, dialect: params.dialect });
|
|
173
|
+
if (identifierLength({ name: params.prefix, dialect: params.dialect }) <= room)
|
|
174
|
+
return `${params.prefix}${params.suffix}`;
|
|
175
|
+
return `${cutIdentifier({ name: params.prefix, length: room, unit: limit.unit })}${params.suffix}`;
|
|
176
|
+
}
|
|
177
|
+
function assertIdentifierFits(params) {
|
|
178
|
+
const limit = IDENTIFIER_LIMITS[params.dialect];
|
|
179
|
+
if (limit === undefined)
|
|
180
|
+
return;
|
|
181
|
+
const length = identifierLength({ name: params.name, dialect: params.dialect });
|
|
182
|
+
if (length <= limit.max)
|
|
183
|
+
return;
|
|
184
|
+
throw new VibeError({
|
|
185
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
186
|
+
message: `${params.what} "${params.name}" is ${length} ${limit.unit} \u2014 ${params.dialect} keeps at most ${limit.max}; the created object would never carry this name and migrations could not converge. ${params.remedy}`,
|
|
187
|
+
meta: { name: params.name, dialect: params.dialect, length, max: limit.max }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function defaultIndexName(params) {
|
|
191
|
+
return fitDefaultName({
|
|
192
|
+
prefix: `${params.table}_${params.columns.join("_")}`,
|
|
193
|
+
suffix: params.unique ? "_key" : "_idx",
|
|
194
|
+
dialect: params.dialect
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function defaultForeignKeyName(params) {
|
|
198
|
+
return fitDefaultName({ prefix: `${params.table}_${params.columns.join("_")}`, suffix: "_fkey", dialect: params.dialect });
|
|
199
|
+
}
|
|
200
|
+
function defaultPrimaryKeyName(params) {
|
|
201
|
+
return fitDefaultName({ prefix: params.table, suffix: "_pkey", dialect: params.dialect });
|
|
202
|
+
}
|
|
203
|
+
function implicitJoinTableObjectNames(params) {
|
|
204
|
+
return {
|
|
205
|
+
uniqueIndex: `${params.table}_AB_unique`,
|
|
206
|
+
indexB: `${params.table}_B_index`,
|
|
207
|
+
foreignKeyA: `${params.table}_A_fkey`,
|
|
208
|
+
foreignKeyB: `${params.table}_B_fkey`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function postgresGeneratedObjectName(params) {
|
|
212
|
+
const limit = IDENTIFIER_LIMITS.postgres;
|
|
213
|
+
const overhead = params.label.length + 2;
|
|
214
|
+
let chars1 = [...params.name1];
|
|
215
|
+
let chars2 = [...params.name2];
|
|
216
|
+
const bytes = (chars) => ENCODER.encode(chars.join("")).length;
|
|
217
|
+
while (bytes(chars1) + bytes(chars2) + overhead > limit.max) {
|
|
218
|
+
if (bytes(chars1) > bytes(chars2))
|
|
219
|
+
chars1 = chars1.slice(0, -1);
|
|
220
|
+
else
|
|
221
|
+
chars2 = chars2.slice(0, -1);
|
|
222
|
+
}
|
|
223
|
+
return `${chars1.join("")}_${chars2.join("")}_${params.label}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// packages/schema/src/namespaces.ts
|
|
227
|
+
var SQL_NAMESPACES = Object.freeze({
|
|
228
|
+
postgres: "public",
|
|
229
|
+
sqlite: "main",
|
|
230
|
+
private: Object.freeze(["_vibeorm_shadow", "_vibe_shadow"])
|
|
231
|
+
});
|
|
232
|
+
function sqlNamespaceIssue(params) {
|
|
233
|
+
const { namespace } = params;
|
|
234
|
+
if (typeof namespace !== "string" || namespace.trim().length === 0 || namespace.includes("\x00"))
|
|
235
|
+
return "Namespace must be a non-empty identifier without NUL bytes";
|
|
236
|
+
if (identifierLength({ name: namespace, dialect: "postgres" }) > IDENTIFIER_LIMITS.postgres.max)
|
|
237
|
+
return "PostgreSQL namespace exceeds its identifier byte limit";
|
|
238
|
+
if (namespace === "information_schema" || namespace.startsWith("pg_") || SQL_NAMESPACES.private.includes(namespace))
|
|
239
|
+
return "System and private VibeORM namespaces cannot be declared";
|
|
240
|
+
if (params.allowPublic === false && namespace === SQL_NAMESPACES.postgres)
|
|
241
|
+
return "A fixed package namespace must be non-public; omit namespace for a legacy public artifact";
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
function assertSqlNamespace(params) {
|
|
245
|
+
if (params.dialect !== "postgres")
|
|
246
|
+
throw unsupportedCapability({ dialect: params.dialect, feature: "fixedNamespaces" });
|
|
247
|
+
const issue = sqlNamespaceIssue(params);
|
|
248
|
+
if (issue !== undefined)
|
|
249
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { rule: "namespace", namespace: params.namespace } });
|
|
250
|
+
}
|
|
251
|
+
function qualifiedSqlName(params) {
|
|
252
|
+
return { namespace: params.namespace ?? params.defaultNamespace ?? SQL_NAMESPACES.postgres, name: params.name };
|
|
253
|
+
}
|
|
254
|
+
// packages/schema/src/capabilities.ts
|
|
255
|
+
var POSTGRES_CAPABILITIES = Object.freeze({
|
|
256
|
+
fixedNamespaces: true,
|
|
257
|
+
externalObjects: true,
|
|
258
|
+
indexOperatorClasses: true,
|
|
259
|
+
exclusionConstraints: true,
|
|
260
|
+
exclusionExtensions: Object.freeze(["btree_gist"]),
|
|
261
|
+
checkNormalization: "rollback",
|
|
262
|
+
nativeRowLevelSecurity: true,
|
|
263
|
+
returning: true,
|
|
264
|
+
scalarArrays: true,
|
|
265
|
+
nativeEnums: true,
|
|
266
|
+
ilike: true,
|
|
267
|
+
lateralJoin: true,
|
|
268
|
+
distinctOn: true,
|
|
269
|
+
partialIndexes: true,
|
|
270
|
+
conflictTargetConstraint: true,
|
|
271
|
+
transactionalDdl: true,
|
|
272
|
+
createManyAndReturn: true,
|
|
273
|
+
jsonColumnType: true,
|
|
274
|
+
orderByNulls: true,
|
|
275
|
+
fullTextEngines: Object.freeze(["bm25", "tsvector"]),
|
|
276
|
+
rowLockStrengths: Object.freeze(["update", "noKeyUpdate", "share", "keyShare"]),
|
|
277
|
+
rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
|
|
278
|
+
advisoryLocks: true
|
|
279
|
+
});
|
|
280
|
+
var SQLITE_CAPABILITIES = Object.freeze({
|
|
281
|
+
fixedNamespaces: false,
|
|
282
|
+
externalObjects: false,
|
|
283
|
+
indexOperatorClasses: false,
|
|
284
|
+
exclusionConstraints: false,
|
|
285
|
+
exclusionExtensions: Object.freeze([]),
|
|
286
|
+
checkNormalization: "sourceText",
|
|
287
|
+
nativeRowLevelSecurity: false,
|
|
288
|
+
returning: true,
|
|
289
|
+
scalarArrays: false,
|
|
290
|
+
nativeEnums: false,
|
|
291
|
+
ilike: false,
|
|
292
|
+
lateralJoin: false,
|
|
293
|
+
distinctOn: false,
|
|
294
|
+
partialIndexes: true,
|
|
295
|
+
conflictTargetConstraint: false,
|
|
296
|
+
transactionalDdl: true,
|
|
297
|
+
createManyAndReturn: true,
|
|
298
|
+
jsonColumnType: false,
|
|
299
|
+
orderByNulls: true,
|
|
300
|
+
fullTextEngines: Object.freeze(["fts5"]),
|
|
301
|
+
rowLockStrengths: Object.freeze([]),
|
|
302
|
+
rowLockWaitPolicies: Object.freeze([]),
|
|
303
|
+
advisoryLocks: false
|
|
304
|
+
});
|
|
305
|
+
var MYSQL_CAPABILITIES = Object.freeze({
|
|
306
|
+
fixedNamespaces: false,
|
|
307
|
+
externalObjects: false,
|
|
308
|
+
indexOperatorClasses: false,
|
|
309
|
+
exclusionConstraints: false,
|
|
310
|
+
exclusionExtensions: Object.freeze([]),
|
|
311
|
+
checkNormalization: "temporary",
|
|
312
|
+
nativeRowLevelSecurity: false,
|
|
313
|
+
returning: false,
|
|
314
|
+
scalarArrays: false,
|
|
315
|
+
nativeEnums: true,
|
|
316
|
+
ilike: false,
|
|
317
|
+
lateralJoin: false,
|
|
318
|
+
distinctOn: false,
|
|
319
|
+
partialIndexes: false,
|
|
320
|
+
conflictTargetConstraint: false,
|
|
321
|
+
transactionalDdl: false,
|
|
322
|
+
createManyAndReturn: false,
|
|
323
|
+
jsonColumnType: true,
|
|
324
|
+
orderByNulls: false,
|
|
325
|
+
fullTextEngines: Object.freeze(["fulltext"]),
|
|
326
|
+
rowLockStrengths: Object.freeze(["update", "share"]),
|
|
327
|
+
rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
|
|
328
|
+
advisoryLocks: false
|
|
329
|
+
});
|
|
330
|
+
var DIALECT_CAPABILITIES = Object.freeze({
|
|
331
|
+
postgres: POSTGRES_CAPABILITIES,
|
|
332
|
+
sqlite: SQLITE_CAPABILITIES,
|
|
333
|
+
mysql: MYSQL_CAPABILITIES
|
|
334
|
+
});
|
|
335
|
+
|
|
226
336
|
// packages/schema/src/migration-objects.ts
|
|
227
337
|
function sqlObjectKey(params) {
|
|
228
338
|
const object = params.identity;
|
|
229
339
|
switch (object.kind) {
|
|
340
|
+
case "namespace":
|
|
341
|
+
return JSON.stringify([object.kind, object.name]);
|
|
230
342
|
case "table":
|
|
231
343
|
case "view":
|
|
232
344
|
case "sequence":
|
|
@@ -246,6 +358,12 @@ function sqlObjectKey(params) {
|
|
|
246
358
|
return JSON.stringify([object.kind, object.table.namespace, object.table.name, object.column]);
|
|
247
359
|
}
|
|
248
360
|
}
|
|
361
|
+
function sqlObjectNamespace(params) {
|
|
362
|
+
const identity = params.identity;
|
|
363
|
+
if (identity.kind === "namespace")
|
|
364
|
+
return identity.name;
|
|
365
|
+
return "table" in identity ? identity.table.namespace : identity.name.namespace;
|
|
366
|
+
}
|
|
249
367
|
function record(value) {
|
|
250
368
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
251
369
|
}
|
|
@@ -256,6 +374,8 @@ function isSqlObjectIdentity(value) {
|
|
|
256
374
|
if (!record(value))
|
|
257
375
|
return false;
|
|
258
376
|
switch (value.kind) {
|
|
377
|
+
case "namespace":
|
|
378
|
+
return typeof value.name === "string" && value.name.length > 0;
|
|
259
379
|
case "table":
|
|
260
380
|
case "view":
|
|
261
381
|
case "sequence":
|
|
@@ -277,24 +397,257 @@ function isSqlObjectIdentity(value) {
|
|
|
277
397
|
return false;
|
|
278
398
|
}
|
|
279
399
|
}
|
|
280
|
-
function isSqlObjectDefinition(value) {
|
|
281
|
-
return record(value) && (value.format === "catalog-v1" || value.format === "catalog-v2") && 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);
|
|
400
|
+
function isSqlObjectDefinition(value) {
|
|
401
|
+
return record(value) && (value.format === "catalog-v1" || value.format === "catalog-v2") && 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);
|
|
402
|
+
}
|
|
403
|
+
function qualifiedNameIssue(params) {
|
|
404
|
+
if (!record(params.value))
|
|
405
|
+
return { path: params.path, expected: "qualified SQL name { namespace, name }" };
|
|
406
|
+
const member = typeof params.value.namespace !== "string" || params.value.namespace.length === 0 ? "namespace" : "name";
|
|
407
|
+
return { path: `${params.path}.${member}`, expected: "non-empty string" };
|
|
408
|
+
}
|
|
409
|
+
function identityIssue(params) {
|
|
410
|
+
const { value, path } = params;
|
|
411
|
+
if (!record(value))
|
|
412
|
+
return { path, expected: "SQL object identity" };
|
|
413
|
+
switch (value.kind) {
|
|
414
|
+
case "namespace":
|
|
415
|
+
return { path: `${path}.name`, expected: "non-empty namespace string" };
|
|
416
|
+
case "table":
|
|
417
|
+
case "view":
|
|
418
|
+
case "sequence":
|
|
419
|
+
case "postgresExtension":
|
|
420
|
+
case "enum":
|
|
421
|
+
return qualifiedNameIssue({ value: value.name, path: `${path}.name` });
|
|
422
|
+
case "function": {
|
|
423
|
+
if (!name(value.name))
|
|
424
|
+
return qualifiedNameIssue({ value: value.name, path: `${path}.name` });
|
|
425
|
+
if (!Array.isArray(value.argumentTypes))
|
|
426
|
+
return { path: `${path}.argumentTypes`, expected: "array of qualified SQL type names" };
|
|
427
|
+
const index = value.argumentTypes.findIndex((entry) => !name(entry));
|
|
428
|
+
return qualifiedNameIssue({ value: value.argumentTypes[index], path: `${path}.argumentTypes[${index}]` });
|
|
429
|
+
}
|
|
430
|
+
case "index":
|
|
431
|
+
case "trigger":
|
|
432
|
+
case "constraint":
|
|
433
|
+
case "column":
|
|
434
|
+
case "columnDefault":
|
|
435
|
+
case "generatedColumn": {
|
|
436
|
+
if (!name(value.table))
|
|
437
|
+
return qualifiedNameIssue({ value: value.table, path: `${path}.table` });
|
|
438
|
+
if (value.kind === "column" || value.kind === "columnDefault" || value.kind === "generatedColumn")
|
|
439
|
+
return { path: `${path}.column`, expected: "non-empty column name" };
|
|
440
|
+
if (typeof value.name !== "string" || value.name.length === 0)
|
|
441
|
+
return { path: `${path}.name`, expected: "non-empty object name" };
|
|
442
|
+
return { path: `${path}.constraintType`, expected: ["check", "foreignKey", "exclusion", "unique", "primaryKey"] };
|
|
443
|
+
}
|
|
444
|
+
default:
|
|
445
|
+
return { path: `${path}.kind`, expected: ["namespace", "table", "view", "sequence", "postgresExtension", "enum", "function", "index", "trigger", "constraint", "column", "columnDefault", "generatedColumn"] };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function definitionIssue(params) {
|
|
449
|
+
const { value, path } = params;
|
|
450
|
+
if (!record(value))
|
|
451
|
+
return { path, expected: "independent SQL object definition" };
|
|
452
|
+
if (value.format !== "catalog-v1" && value.format !== "catalog-v2")
|
|
453
|
+
return { path: `${path}.format`, expected: ["catalog-v1", "catalog-v2"] };
|
|
454
|
+
if (!isSqlObjectIdentity(value.identity))
|
|
455
|
+
return identityIssue({ value: value.identity, path: `${path}.identity` });
|
|
456
|
+
if (!record(value.facts) || Object.keys(value.facts).length === 0)
|
|
457
|
+
return { path: `${path}.facts`, expected: "non-empty serializable catalog facts" };
|
|
458
|
+
for (const [key, fact] of Object.entries(value.facts)) {
|
|
459
|
+
if (!(fact === null || typeof fact === "string" || typeof fact === "boolean" || typeof fact === "number" && Number.isFinite(fact) || Array.isArray(fact) && fact.every((entry) => typeof entry === "string")))
|
|
460
|
+
return { path: `${path}.facts.${key}`, expected: "string, finite number, boolean, null or string array" };
|
|
461
|
+
}
|
|
462
|
+
if (!Array.isArray(value.dependencies))
|
|
463
|
+
return { path: `${path}.dependencies`, expected: "array of SQL object identities" };
|
|
464
|
+
const index = value.dependencies.findIndex((entry) => !isSqlObjectIdentity(entry));
|
|
465
|
+
return identityIssue({ value: value.dependencies[index], path: `${path}.dependencies[${index}]` });
|
|
466
|
+
}
|
|
467
|
+
function invalidDeclaration(params) {
|
|
468
|
+
const expected = typeof params.expected === "string" ? params.expected : params.expected.join(", ");
|
|
469
|
+
throw new VibeError({
|
|
470
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
471
|
+
message: `Invalid migration-only object declaration at ${params.path}; expected ${expected}`,
|
|
472
|
+
meta: { path: params.path, expected: params.expected, ...params.id === undefined ? {} : { id: params.id } }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function assertMigrationObjects(params) {
|
|
476
|
+
if (!Array.isArray(params.objects))
|
|
477
|
+
invalidDeclaration({ path: "migrationObjects", expected: "array" });
|
|
478
|
+
const ids = new Set;
|
|
479
|
+
const owners = new Set;
|
|
480
|
+
for (const [index, value] of params.objects.entries()) {
|
|
481
|
+
const object = value;
|
|
482
|
+
const path = `migrationObjects[${index}]`;
|
|
483
|
+
if (!record(object))
|
|
484
|
+
invalidDeclaration({ path, expected: "object" });
|
|
485
|
+
if (typeof object.id !== "string" || object.id.length === 0)
|
|
486
|
+
invalidDeclaration({ path: `${path}.id`, expected: "non-empty string" });
|
|
487
|
+
const id = object.id;
|
|
488
|
+
if (!["postgres", "sqlite", "mysql"].includes(String(object.dialect)))
|
|
489
|
+
invalidDeclaration({ path: `${path}.dialect`, expected: ["postgres", "sqlite", "mysql"], id });
|
|
490
|
+
const identity = object.identity;
|
|
491
|
+
if (!isSqlObjectIdentity(identity))
|
|
492
|
+
invalidDeclaration({ ...identityIssue({ value: identity, path: `${path}.identity` }), id });
|
|
493
|
+
for (const member of ["create", "drop"]) {
|
|
494
|
+
const statements = object[member];
|
|
495
|
+
if (!Array.isArray(statements))
|
|
496
|
+
invalidDeclaration({ path: `${path}.${member}`, expected: "array of SQL strings", id });
|
|
497
|
+
const invalid = statements.findIndex((entry) => typeof entry !== "string");
|
|
498
|
+
if (invalid !== -1)
|
|
499
|
+
invalidDeclaration({ path: `${path}.${member}[${invalid}]`, expected: "SQL string", id });
|
|
500
|
+
}
|
|
501
|
+
if (!Array.isArray(object.dependsOn))
|
|
502
|
+
invalidDeclaration({ path: `${path}.dependsOn`, expected: "array of SQL object identities", id });
|
|
503
|
+
for (const [dependencyIndex, dependency] of object.dependsOn.entries())
|
|
504
|
+
if (!isSqlObjectIdentity(dependency))
|
|
505
|
+
invalidDeclaration({ ...identityIssue({ value: dependency, path: `${path}.dependsOn[${dependencyIndex}]` }), id });
|
|
506
|
+
if (object.expected !== undefined) {
|
|
507
|
+
if (!isSqlObjectDefinition(object.expected))
|
|
508
|
+
invalidDeclaration({ ...definitionIssue({ value: object.expected, path: `${path}.expected` }), id });
|
|
509
|
+
if (sqlObjectKey({ identity: object.expected.identity }) !== sqlObjectKey({ identity }))
|
|
510
|
+
invalidDeclaration({ path: `${path}.expected.identity`, expected: "the declaration's SQL object identity", id });
|
|
511
|
+
}
|
|
512
|
+
const key = sqlObjectKey({ identity });
|
|
513
|
+
if (ids.has(id) || owners.has(key))
|
|
514
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Duplicate migration object identity or id", meta: { id, key, path: `${path}.${ids.has(id) ? "id" : "identity"}` } });
|
|
515
|
+
ids.add(id);
|
|
516
|
+
owners.add(key);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// packages/schema/src/index-operator-classes.ts
|
|
521
|
+
function assertIndexOperatorClasses(params) {
|
|
522
|
+
for (const model of params.schema.models)
|
|
523
|
+
for (const index of model.indexes)
|
|
524
|
+
for (const field of index.fields) {
|
|
525
|
+
if (field.operatorClass === undefined)
|
|
526
|
+
continue;
|
|
527
|
+
if (!DIALECT_CAPABILITIES[params.dialect].indexOperatorClasses)
|
|
528
|
+
throw unsupportedCapability({ dialect: params.dialect, feature: "indexOperatorClasses" });
|
|
529
|
+
if (index.kind === "unique" && params.physical !== true)
|
|
530
|
+
throw unsupportedCapability({ dialect: params.dialect, feature: "uniqueIndexOperatorClasses", hint: "custom unique operator classes can change client selector equality" });
|
|
531
|
+
if (!isSqlObjectIdentity({ kind: "table", name: field.operatorClass }))
|
|
532
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Index operator class must be a qualified SQL name { namespace, name }", meta: { rule: "index-operator-class-identity", model: model.name, field: field.name } });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// packages/schema/src/ir.ts
|
|
536
|
+
var DIALECTS = ["postgres", "sqlite", "mysql"];
|
|
537
|
+
function providerToDialect(params) {
|
|
538
|
+
const { provider } = params;
|
|
539
|
+
switch (provider) {
|
|
540
|
+
case "postgresql":
|
|
541
|
+
case "pglite":
|
|
542
|
+
return "postgres";
|
|
543
|
+
case "sqlite":
|
|
544
|
+
return "sqlite";
|
|
545
|
+
case "mysql":
|
|
546
|
+
return "mysql";
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function isEnumType(fieldType) {
|
|
550
|
+
return typeof fieldType === "object" && "enum" in fieldType;
|
|
282
551
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
552
|
+
function fieldTypeIdentity(params) {
|
|
553
|
+
const { type } = params;
|
|
554
|
+
return isEnumType(type) ? type.namespace === undefined || type.namespace === "public" ? `enum:${type.enum}` : JSON.stringify(["enum", type.namespace, type.enum]) : type;
|
|
555
|
+
}
|
|
556
|
+
var ENUM_STORAGES = ["database", "string"];
|
|
557
|
+
function enumStorageOf(params) {
|
|
558
|
+
return params.enumDef.storage ?? "database";
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// packages/schema/src/module-schema.ts
|
|
562
|
+
function prepareDatabaseModuleSchema(params) {
|
|
563
|
+
const { schema, module } = params;
|
|
564
|
+
const namespace = module.namespace;
|
|
565
|
+
const dialect = providerToDialect({ provider: schema.datasource.provider });
|
|
566
|
+
if (namespace !== undefined)
|
|
567
|
+
assertSqlNamespace({ namespace, dialect, allowPublic: false });
|
|
568
|
+
const expected = namespace ?? SQL_NAMESPACES.postgres;
|
|
569
|
+
for (const entry of [...schema.models, ...schema.enums]) {
|
|
570
|
+
if (entry.namespace === undefined)
|
|
571
|
+
continue;
|
|
572
|
+
assertSqlNamespace({ namespace: entry.namespace, dialect });
|
|
573
|
+
if (qualifiedSqlName({ name: entry.dbName ?? entry.name, namespace: entry.namespace }).namespace !== expected)
|
|
574
|
+
throw new VibeError({ code: "VIBE_CONFIG", message: "Module schema placement differs from its immutable namespace; host configuration cannot remap it", meta: { reason: "module-placement-mismatch", moduleId: module.id, name: entry.name } });
|
|
297
575
|
}
|
|
576
|
+
for (const model of schema.models)
|
|
577
|
+
for (const field of model.fields) {
|
|
578
|
+
if (!isEnumType(field.type) || field.type.namespace === undefined)
|
|
579
|
+
continue;
|
|
580
|
+
assertSqlNamespace({ namespace: field.type.namespace, dialect });
|
|
581
|
+
if (field.type.namespace !== expected)
|
|
582
|
+
throw new VibeError({ code: "VIBE_CONFIG", message: "Module enum reference placement differs from its immutable namespace", meta: { reason: "module-placement-mismatch", moduleId: module.id, model: model.name, field: field.name } });
|
|
583
|
+
}
|
|
584
|
+
if (namespace === undefined)
|
|
585
|
+
return schema;
|
|
586
|
+
const nativeEnums = new Set(schema.enums.filter((entry) => enumStorageOf({ enumDef: entry }) === "database").map((entry) => entry.name));
|
|
587
|
+
return {
|
|
588
|
+
...schema,
|
|
589
|
+
models: schema.models.map((model) => ({ ...model, namespace, fields: model.fields.map((field) => {
|
|
590
|
+
const type = field.type;
|
|
591
|
+
return isEnumType(type) && nativeEnums.has(type.enum) ? { ...field, type: { ...type, namespace } } : field;
|
|
592
|
+
}) })),
|
|
593
|
+
enums: schema.enums.map((entry) => enumStorageOf({ enumDef: entry }) === "database" ? { ...entry, namespace } : entry)
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
// packages/schema/src/predicate.ts
|
|
597
|
+
var PREDICATE_COMPARISON_OPS = [
|
|
598
|
+
"eq",
|
|
599
|
+
"ne",
|
|
600
|
+
"gt",
|
|
601
|
+
"gte",
|
|
602
|
+
"lt",
|
|
603
|
+
"lte"
|
|
604
|
+
];
|
|
605
|
+
function isPredicateFieldRef(operand) {
|
|
606
|
+
return operand.kind === "field";
|
|
607
|
+
}
|
|
608
|
+
function predicateFieldNames(params) {
|
|
609
|
+
const seen = new Set;
|
|
610
|
+
const walk = (node) => {
|
|
611
|
+
switch (node.kind) {
|
|
612
|
+
case "compare":
|
|
613
|
+
seen.add(node.left.name);
|
|
614
|
+
if (isPredicateFieldRef(node.right))
|
|
615
|
+
seen.add(node.right.name);
|
|
616
|
+
return;
|
|
617
|
+
case "inList":
|
|
618
|
+
case "isNull":
|
|
619
|
+
seen.add(node.field.name);
|
|
620
|
+
return;
|
|
621
|
+
case "and":
|
|
622
|
+
case "or":
|
|
623
|
+
for (const operand of node.operands)
|
|
624
|
+
walk(operand);
|
|
625
|
+
return;
|
|
626
|
+
case "not":
|
|
627
|
+
walk(node.operand);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
walk(params.predicate);
|
|
632
|
+
return [...seen];
|
|
633
|
+
}
|
|
634
|
+
function predicateKey(params) {
|
|
635
|
+
const encode = (node) => {
|
|
636
|
+
switch (node.kind) {
|
|
637
|
+
case "compare":
|
|
638
|
+
return `cmp(${node.op},${node.left.name},${isPredicateFieldRef(node.right) ? `f:${node.right.name}` : `v:${typeof node.right.value}:${String(node.right.value)}`})`;
|
|
639
|
+
case "inList":
|
|
640
|
+
return `in${node.negate === true ? "!" : ""}(${node.field.name},[${node.values.map((entry) => `${typeof entry.value}:${String(entry.value)}`).join("|")}])`;
|
|
641
|
+
case "isNull":
|
|
642
|
+
return `null${node.negate === true ? "!" : ""}(${node.field.name})`;
|
|
643
|
+
case "and":
|
|
644
|
+
case "or":
|
|
645
|
+
return `${node.kind}(${node.operands.map(encode).join(",")})`;
|
|
646
|
+
case "not":
|
|
647
|
+
return `not(${encode(node.operand)})`;
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
return encode(params.predicate);
|
|
298
651
|
}
|
|
299
652
|
// packages/schema/src/prisma-literal.ts
|
|
300
653
|
var STRING_ESCAPES = {
|
|
@@ -407,6 +760,7 @@ function exactDecimalLiteral(params) {
|
|
|
407
760
|
// packages/schema/src/reserved.ts
|
|
408
761
|
var RESERVED_DELEGATE_NAMES = [
|
|
409
762
|
"findMany",
|
|
763
|
+
"findPage",
|
|
410
764
|
"findFirst",
|
|
411
765
|
"findUnique",
|
|
412
766
|
"findFirstOrThrow",
|
|
@@ -740,160 +1094,6 @@ function implicitJoinTable(params) {
|
|
|
740
1094
|
};
|
|
741
1095
|
}
|
|
742
1096
|
|
|
743
|
-
// packages/schema/src/physical-names.ts
|
|
744
|
-
var IDENTIFIER_LIMITS = Object.freeze({
|
|
745
|
-
postgres: { max: 63, unit: "bytes" },
|
|
746
|
-
mysql: { max: 64, unit: "characters" },
|
|
747
|
-
sqlite: undefined
|
|
748
|
-
});
|
|
749
|
-
var ENCODER = new TextEncoder;
|
|
750
|
-
function identifierLength(params) {
|
|
751
|
-
const limit = IDENTIFIER_LIMITS[params.dialect];
|
|
752
|
-
return limit?.unit === "bytes" ? ENCODER.encode(params.name).length : [...params.name].length;
|
|
753
|
-
}
|
|
754
|
-
function cutIdentifier(params) {
|
|
755
|
-
let out = "";
|
|
756
|
-
let used = 0;
|
|
757
|
-
for (const char of params.name) {
|
|
758
|
-
const size = params.unit === "bytes" ? ENCODER.encode(char).length : 1;
|
|
759
|
-
if (used + size > params.length)
|
|
760
|
-
break;
|
|
761
|
-
out += char;
|
|
762
|
-
used += size;
|
|
763
|
-
}
|
|
764
|
-
return out;
|
|
765
|
-
}
|
|
766
|
-
function fitDefaultName(params) {
|
|
767
|
-
const limit = IDENTIFIER_LIMITS[params.dialect];
|
|
768
|
-
if (limit === undefined)
|
|
769
|
-
return `${params.prefix}${params.suffix}`;
|
|
770
|
-
const room = limit.max - identifierLength({ name: params.suffix, dialect: params.dialect });
|
|
771
|
-
if (identifierLength({ name: params.prefix, dialect: params.dialect }) <= room)
|
|
772
|
-
return `${params.prefix}${params.suffix}`;
|
|
773
|
-
return `${cutIdentifier({ name: params.prefix, length: room, unit: limit.unit })}${params.suffix}`;
|
|
774
|
-
}
|
|
775
|
-
function assertIdentifierFits(params) {
|
|
776
|
-
const limit = IDENTIFIER_LIMITS[params.dialect];
|
|
777
|
-
if (limit === undefined)
|
|
778
|
-
return;
|
|
779
|
-
const length = identifierLength({ name: params.name, dialect: params.dialect });
|
|
780
|
-
if (length <= limit.max)
|
|
781
|
-
return;
|
|
782
|
-
throw new VibeError({
|
|
783
|
-
code: "VIBE_SCHEMA_INVALID",
|
|
784
|
-
message: `${params.what} "${params.name}" is ${length} ${limit.unit} \u2014 ${params.dialect} keeps at most ${limit.max}; the created object would never carry this name and migrations could not converge. ${params.remedy}`,
|
|
785
|
-
meta: { name: params.name, dialect: params.dialect, length, max: limit.max }
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
function defaultIndexName(params) {
|
|
789
|
-
return fitDefaultName({
|
|
790
|
-
prefix: `${params.table}_${params.columns.join("_")}`,
|
|
791
|
-
suffix: params.unique ? "_key" : "_idx",
|
|
792
|
-
dialect: params.dialect
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
function defaultForeignKeyName(params) {
|
|
796
|
-
return fitDefaultName({ prefix: `${params.table}_${params.columns.join("_")}`, suffix: "_fkey", dialect: params.dialect });
|
|
797
|
-
}
|
|
798
|
-
function defaultPrimaryKeyName(params) {
|
|
799
|
-
return fitDefaultName({ prefix: params.table, suffix: "_pkey", dialect: params.dialect });
|
|
800
|
-
}
|
|
801
|
-
function implicitJoinTableObjectNames(params) {
|
|
802
|
-
return {
|
|
803
|
-
uniqueIndex: `${params.table}_AB_unique`,
|
|
804
|
-
indexB: `${params.table}_B_index`,
|
|
805
|
-
foreignKeyA: `${params.table}_A_fkey`,
|
|
806
|
-
foreignKeyB: `${params.table}_B_fkey`
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
function postgresGeneratedObjectName(params) {
|
|
810
|
-
const limit = IDENTIFIER_LIMITS.postgres;
|
|
811
|
-
const overhead = params.label.length + 2;
|
|
812
|
-
let chars1 = [...params.name1];
|
|
813
|
-
let chars2 = [...params.name2];
|
|
814
|
-
const bytes = (chars) => ENCODER.encode(chars.join("")).length;
|
|
815
|
-
while (bytes(chars1) + bytes(chars2) + overhead > limit.max) {
|
|
816
|
-
if (bytes(chars1) > bytes(chars2))
|
|
817
|
-
chars1 = chars1.slice(0, -1);
|
|
818
|
-
else
|
|
819
|
-
chars2 = chars2.slice(0, -1);
|
|
820
|
-
}
|
|
821
|
-
return `${chars1.join("")}_${chars2.join("")}_${params.label}`;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// packages/schema/src/capabilities.ts
|
|
825
|
-
var POSTGRES_CAPABILITIES = Object.freeze({
|
|
826
|
-
exclusionConstraints: true,
|
|
827
|
-
exclusionExtensions: Object.freeze(["btree_gist"]),
|
|
828
|
-
checkNormalization: "rollback",
|
|
829
|
-
nativeRowLevelSecurity: true,
|
|
830
|
-
returning: true,
|
|
831
|
-
scalarArrays: true,
|
|
832
|
-
nativeEnums: true,
|
|
833
|
-
ilike: true,
|
|
834
|
-
lateralJoin: true,
|
|
835
|
-
distinctOn: true,
|
|
836
|
-
partialIndexes: true,
|
|
837
|
-
conflictTargetConstraint: true,
|
|
838
|
-
transactionalDdl: true,
|
|
839
|
-
createManyAndReturn: true,
|
|
840
|
-
jsonColumnType: true,
|
|
841
|
-
orderByNulls: true,
|
|
842
|
-
fullTextEngines: Object.freeze(["bm25", "tsvector"]),
|
|
843
|
-
rowLockStrengths: Object.freeze(["update", "noKeyUpdate", "share", "keyShare"]),
|
|
844
|
-
rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
|
|
845
|
-
advisoryLocks: true
|
|
846
|
-
});
|
|
847
|
-
var SQLITE_CAPABILITIES = Object.freeze({
|
|
848
|
-
exclusionConstraints: false,
|
|
849
|
-
exclusionExtensions: Object.freeze([]),
|
|
850
|
-
checkNormalization: "sourceText",
|
|
851
|
-
nativeRowLevelSecurity: false,
|
|
852
|
-
returning: true,
|
|
853
|
-
scalarArrays: false,
|
|
854
|
-
nativeEnums: false,
|
|
855
|
-
ilike: false,
|
|
856
|
-
lateralJoin: false,
|
|
857
|
-
distinctOn: false,
|
|
858
|
-
partialIndexes: true,
|
|
859
|
-
conflictTargetConstraint: false,
|
|
860
|
-
transactionalDdl: true,
|
|
861
|
-
createManyAndReturn: true,
|
|
862
|
-
jsonColumnType: false,
|
|
863
|
-
orderByNulls: true,
|
|
864
|
-
fullTextEngines: Object.freeze(["fts5"]),
|
|
865
|
-
rowLockStrengths: Object.freeze([]),
|
|
866
|
-
rowLockWaitPolicies: Object.freeze([]),
|
|
867
|
-
advisoryLocks: false
|
|
868
|
-
});
|
|
869
|
-
var MYSQL_CAPABILITIES = Object.freeze({
|
|
870
|
-
exclusionConstraints: false,
|
|
871
|
-
exclusionExtensions: Object.freeze([]),
|
|
872
|
-
checkNormalization: "temporary",
|
|
873
|
-
nativeRowLevelSecurity: false,
|
|
874
|
-
returning: false,
|
|
875
|
-
scalarArrays: false,
|
|
876
|
-
nativeEnums: true,
|
|
877
|
-
ilike: false,
|
|
878
|
-
lateralJoin: false,
|
|
879
|
-
distinctOn: false,
|
|
880
|
-
partialIndexes: false,
|
|
881
|
-
conflictTargetConstraint: false,
|
|
882
|
-
transactionalDdl: false,
|
|
883
|
-
createManyAndReturn: false,
|
|
884
|
-
jsonColumnType: true,
|
|
885
|
-
orderByNulls: false,
|
|
886
|
-
fullTextEngines: Object.freeze(["fulltext"]),
|
|
887
|
-
rowLockStrengths: Object.freeze(["update", "share"]),
|
|
888
|
-
rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
|
|
889
|
-
advisoryLocks: false
|
|
890
|
-
});
|
|
891
|
-
var DIALECT_CAPABILITIES = Object.freeze({
|
|
892
|
-
postgres: POSTGRES_CAPABILITIES,
|
|
893
|
-
sqlite: SQLITE_CAPABILITIES,
|
|
894
|
-
mysql: MYSQL_CAPABILITIES
|
|
895
|
-
});
|
|
896
|
-
|
|
897
1097
|
// packages/schema/src/validate.ts
|
|
898
1098
|
var RESERVED_ENUM_NAMES = [
|
|
899
1099
|
"sql",
|
|
@@ -948,6 +1148,7 @@ var RESERVED_ENUM_NAMES = [
|
|
|
948
1148
|
"SortOrder",
|
|
949
1149
|
"SortOrderWithNulls",
|
|
950
1150
|
"KeysetToken",
|
|
1151
|
+
"KeysetPage",
|
|
951
1152
|
"KeysetOptions",
|
|
952
1153
|
"RowLockRequest",
|
|
953
1154
|
"JsonValue",
|
|
@@ -1026,6 +1227,7 @@ var GENERATED_TYPE_NAMES = {
|
|
|
1026
1227
|
"MinAggregateInput",
|
|
1027
1228
|
"MaxAggregateInput",
|
|
1028
1229
|
"FindManyArgs",
|
|
1230
|
+
"FindPageArgs",
|
|
1029
1231
|
"FindFirstArgs",
|
|
1030
1232
|
"FindUniqueArgs",
|
|
1031
1233
|
"CreateArgs",
|
|
@@ -1196,6 +1398,15 @@ function validateGeneratedNames(params) {
|
|
|
1196
1398
|
function validateSchemaIR(params) {
|
|
1197
1399
|
const { schema } = params;
|
|
1198
1400
|
const issues = [];
|
|
1401
|
+
for (const entry of [...schema.models, ...schema.enums]) {
|
|
1402
|
+
if (entry.namespace === undefined)
|
|
1403
|
+
continue;
|
|
1404
|
+
const issue = sqlNamespaceIssue({ namespace: entry.namespace });
|
|
1405
|
+
if (issue !== undefined)
|
|
1406
|
+
issues.push({ rule: "namespace", message: issue });
|
|
1407
|
+
if (providerToDialect({ provider: schema.datasource.provider }) !== "postgres")
|
|
1408
|
+
issues.push({ rule: "namespace-dialect", message: "Explicit fixed namespaces require PostgreSQL or PGlite" });
|
|
1409
|
+
}
|
|
1199
1410
|
const modelByName = new Map;
|
|
1200
1411
|
for (const model of schema.models) {
|
|
1201
1412
|
if (modelByName.has(model.name)) {
|
|
@@ -1378,7 +1589,7 @@ function validatePhysicalNameUniqueness(params) {
|
|
|
1378
1589
|
const dialect = providerToDialect({ provider: schema.datasource.provider });
|
|
1379
1590
|
const owners = new Map;
|
|
1380
1591
|
const claim = (entry) => {
|
|
1381
|
-
const key =
|
|
1592
|
+
const key = JSON.stringify([entry.physicalNamespace ?? "public", entry.namespace, entry.name]);
|
|
1382
1593
|
const previous = owners.get(key);
|
|
1383
1594
|
if (previous === undefined) {
|
|
1384
1595
|
owners.set(key, `${entry.what} of ${entry.owner}`);
|
|
@@ -1396,10 +1607,11 @@ function validatePhysicalNameUniqueness(params) {
|
|
|
1396
1607
|
const constraintNamespace = (table) => dialect === "mysql" ? "constraint" : `constraint:${table}`;
|
|
1397
1608
|
for (const model of schema.models) {
|
|
1398
1609
|
const table = model.dbName ?? model.name;
|
|
1399
|
-
|
|
1610
|
+
const physicalNamespace = qualifiedSqlName({ name: table, namespace: model.namespace }).namespace;
|
|
1611
|
+
if (!claim({ namespace: "relation", physicalNamespace, name: table, what: model.isView ? "view" : "table", owner: `model "${model.name}"`, model: model.name }))
|
|
1400
1612
|
continue;
|
|
1401
1613
|
for (const fieldIR of model.fields) {
|
|
1402
|
-
claim({ namespace: `column:${table}`, name: fieldIR.dbName ?? fieldIR.name, what: "column", owner: `field "${model.name}.${fieldIR.name}"`, model: model.name, field: fieldIR.name });
|
|
1614
|
+
claim({ namespace: `column:${table}`, physicalNamespace, name: fieldIR.dbName ?? fieldIR.name, what: "column", owner: `field "${model.name}.${fieldIR.name}"`, model: model.name, field: fieldIR.name });
|
|
1403
1615
|
}
|
|
1404
1616
|
if (model.isView)
|
|
1405
1617
|
continue;
|
|
@@ -1417,15 +1629,15 @@ function validatePhysicalNameUniqueness(params) {
|
|
|
1417
1629
|
continue;
|
|
1418
1630
|
seenIndexKeys.add(key);
|
|
1419
1631
|
const name = index.name ?? defaultIndexName({ table, columns: index.fields.map((entry) => columnOf(entry.name)), unique, dialect });
|
|
1420
|
-
claim({ namespace: indexNamespace(table), name, what: unique ? "unique index" : "index", owner: `${site} on "${model.name}"`, model: model.name });
|
|
1632
|
+
claim({ namespace: indexNamespace(table), physicalNamespace, name, what: unique ? "unique index" : "index", owner: `${site} on "${model.name}"`, model: model.name });
|
|
1421
1633
|
if (index.constraintName !== undefined) {
|
|
1422
|
-
claim({ namespace: constraintNamespace(table), name: index.constraintName, what: "unique constraint", owner: `${site} on "${model.name}"`, model: model.name });
|
|
1634
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name: index.constraintName, what: "unique constraint", owner: `${site} on "${model.name}"`, model: model.name });
|
|
1423
1635
|
}
|
|
1424
1636
|
}
|
|
1425
1637
|
if (dialect === "postgres" && (model.primaryKey !== undefined || model.fields.some((fieldIR) => fieldIR.isId))) {
|
|
1426
1638
|
const name = model.primaryKeyConstraintName ?? defaultPrimaryKeyName({ table, dialect });
|
|
1427
|
-
claim({ namespace: "relation", name, what: "primary key", owner: `model "${model.name}"`, model: model.name });
|
|
1428
|
-
claim({ namespace: constraintNamespace(table), name, what: "primary key", owner: `model "${model.name}"`, model: model.name });
|
|
1639
|
+
claim({ namespace: "relation", physicalNamespace, name, what: "primary key", owner: `model "${model.name}"`, model: model.name });
|
|
1640
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name, what: "primary key", owner: `model "${model.name}"`, model: model.name });
|
|
1429
1641
|
}
|
|
1430
1642
|
for (const relation of model.relations) {
|
|
1431
1643
|
if (relation.fields === undefined || relation.fields.length === 0)
|
|
@@ -1434,22 +1646,22 @@ function validatePhysicalNameUniqueness(params) {
|
|
|
1434
1646
|
if (target !== undefined && target.isView)
|
|
1435
1647
|
continue;
|
|
1436
1648
|
const name = relation.constraintName ?? defaultForeignKeyName({ table, columns: relation.fields.map((entry) => columnOf(entry)), dialect });
|
|
1437
|
-
claim({ namespace: constraintNamespace(table), name, what: "foreign key", owner: `relation "${model.name}.${relation.name}"`, model: model.name });
|
|
1649
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name, what: "foreign key", owner: `relation "${model.name}.${relation.name}"`, model: model.name });
|
|
1438
1650
|
}
|
|
1439
1651
|
for (const checkIR of model.checks ?? []) {
|
|
1440
|
-
claim({ namespace: constraintNamespace(table), name: checkIR.name, what: "CHECK constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1652
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name: checkIR.name, what: "CHECK constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1441
1653
|
}
|
|
1442
1654
|
for (const exclusion of model.exclusions ?? []) {
|
|
1443
|
-
claim({ namespace: constraintNamespace(table), name: exclusion.name, what: "exclusion constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1655
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name: exclusion.name, what: "exclusion constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1444
1656
|
if (dialect === "postgres")
|
|
1445
|
-
claim({ namespace: "relation", name: exclusion.name, what: "exclusion constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1657
|
+
claim({ namespace: "relation", physicalNamespace, name: exclusion.name, what: "exclusion constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1446
1658
|
}
|
|
1447
1659
|
}
|
|
1448
1660
|
if (dialect === "postgres") {
|
|
1449
1661
|
for (const enumDef of schema.enums) {
|
|
1450
1662
|
if (enumStorageOf({ enumDef }) !== "database")
|
|
1451
1663
|
continue;
|
|
1452
|
-
claim({ namespace: "relation", name: enumDef.dbName ?? enumDef.name, what: "enum type", owner: `enum "${enumDef.name}"` });
|
|
1664
|
+
claim({ namespace: "relation", physicalNamespace: qualifiedSqlName({ name: enumDef.dbName ?? enumDef.name, namespace: enumDef.namespace }).namespace, name: enumDef.dbName ?? enumDef.name, what: "enum type", owner: `enum "${enumDef.name}"` });
|
|
1453
1665
|
}
|
|
1454
1666
|
}
|
|
1455
1667
|
const seenJoinTables = new Set;
|
|
@@ -1459,17 +1671,24 @@ function validatePhysicalNameUniqueness(params) {
|
|
|
1459
1671
|
continue;
|
|
1460
1672
|
if (!isImplicitManyToManyRelation({ model, relation, models: schema.models }))
|
|
1461
1673
|
continue;
|
|
1674
|
+
const target = schema.models.find((candidate) => candidate.name === relation.targetModel);
|
|
1675
|
+
const physicalNamespace = model.namespace ?? "public";
|
|
1676
|
+
if ((target?.namespace ?? "public") !== physicalNamespace) {
|
|
1677
|
+
issues.push({ rule: "implicit-join-namespace", message: "Implicit many-to-many endpoints must share one fixed namespace", model: model.name, field: relation.name });
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1462
1680
|
const join = implicitJoinTable({ modelA: model.name, modelB: relation.targetModel, ...relation.relationName === undefined ? {} : { relationName: relation.relationName } });
|
|
1463
|
-
|
|
1681
|
+
const joinKey = JSON.stringify([physicalNamespace, join.tableName]);
|
|
1682
|
+
if (seenJoinTables.has(joinKey))
|
|
1464
1683
|
continue;
|
|
1465
|
-
seenJoinTables.add(
|
|
1684
|
+
seenJoinTables.add(joinKey);
|
|
1466
1685
|
const owner = `implicit relation "${model.name}.${relation.name}"`;
|
|
1467
|
-
claim({ namespace: "relation", name: join.tableName, what: "join table", owner, model: model.name });
|
|
1686
|
+
claim({ namespace: "relation", physicalNamespace, name: join.tableName, what: "join table", owner, model: model.name });
|
|
1468
1687
|
const derived = implicitJoinTableObjectNames({ table: join.tableName });
|
|
1469
|
-
claim({ namespace: indexNamespace(join.tableName), name: derived.uniqueIndex, what: "unique index", owner, model: model.name });
|
|
1470
|
-
claim({ namespace: indexNamespace(join.tableName), name: derived.indexB, what: "index", owner, model: model.name });
|
|
1471
|
-
claim({ namespace: constraintNamespace(join.tableName), name: derived.foreignKeyA, what: "foreign key", owner, model: model.name });
|
|
1472
|
-
claim({ namespace: constraintNamespace(join.tableName), name: derived.foreignKeyB, what: "foreign key", owner, model: model.name });
|
|
1688
|
+
claim({ namespace: indexNamespace(join.tableName), physicalNamespace, name: derived.uniqueIndex, what: "unique index", owner, model: model.name });
|
|
1689
|
+
claim({ namespace: indexNamespace(join.tableName), physicalNamespace, name: derived.indexB, what: "index", owner, model: model.name });
|
|
1690
|
+
claim({ namespace: constraintNamespace(join.tableName), physicalNamespace, name: derived.foreignKeyA, what: "foreign key", owner, model: model.name });
|
|
1691
|
+
claim({ namespace: constraintNamespace(join.tableName), physicalNamespace, name: derived.foreignKeyB, what: "foreign key", owner, model: model.name });
|
|
1473
1692
|
}
|
|
1474
1693
|
}
|
|
1475
1694
|
}
|
|
@@ -1835,6 +2054,20 @@ function validatePredicateFields(params) {
|
|
|
1835
2054
|
}
|
|
1836
2055
|
function assertValidSchemaIR(params) {
|
|
1837
2056
|
const dialect = providerToDialect({ provider: params.schema.datasource.provider });
|
|
2057
|
+
assertIndexOperatorClasses({ schema: params.schema, dialect });
|
|
2058
|
+
for (const entry of [...params.schema.models, ...params.schema.enums])
|
|
2059
|
+
if (entry.namespace !== undefined)
|
|
2060
|
+
assertSqlNamespace({ namespace: entry.namespace, dialect });
|
|
2061
|
+
for (const model of params.schema.models)
|
|
2062
|
+
for (const field of model.fields) {
|
|
2063
|
+
if (!isEnumType(field.type) || field.type.namespace === undefined)
|
|
2064
|
+
continue;
|
|
2065
|
+
assertSqlNamespace({ namespace: field.type.namespace, dialect });
|
|
2066
|
+
const enumType = field.type;
|
|
2067
|
+
const declared = params.schema.enums.find((entry) => entry.name === enumType.enum || entry.dbName === enumType.enum);
|
|
2068
|
+
if (declared !== undefined && (declared.namespace ?? "public") !== enumType.namespace)
|
|
2069
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Enum reference namespace differs from its declared native type", meta: { rule: "enum-namespace", model: model.name, field: field.name } });
|
|
2070
|
+
}
|
|
1838
2071
|
for (const model of params.schema.models) {
|
|
1839
2072
|
if (model.exclusions !== undefined && !Array.isArray(model.exclusions))
|
|
1840
2073
|
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusions must be an array", meta: { rule: "exclusion-definition" } });
|
|
@@ -2094,11 +2327,14 @@ export {
|
|
|
2094
2327
|
RlsContextError,
|
|
2095
2328
|
SCOPED_REFUSAL_REASONS,
|
|
2096
2329
|
SQLITE_CAPABILITIES,
|
|
2330
|
+
SQL_NAMESPACES,
|
|
2097
2331
|
ScopedQueryError,
|
|
2098
2332
|
VIBE_ERROR_CODES,
|
|
2099
2333
|
VibeError,
|
|
2100
2334
|
assertIdentifierFits,
|
|
2335
|
+
assertIndexOperatorClasses,
|
|
2101
2336
|
assertMigrationObjects,
|
|
2337
|
+
assertSqlNamespace,
|
|
2102
2338
|
assertValidSchemaIR,
|
|
2103
2339
|
buildComputedManifest,
|
|
2104
2340
|
buildExtensionRegistry,
|
|
@@ -2132,14 +2368,18 @@ export {
|
|
|
2132
2368
|
postgresGeneratedObjectName,
|
|
2133
2369
|
predicateFieldNames,
|
|
2134
2370
|
predicateKey,
|
|
2371
|
+
prepareDatabaseModuleSchema,
|
|
2135
2372
|
providerToDialect,
|
|
2373
|
+
qualifiedSqlName,
|
|
2136
2374
|
scalarListDefault,
|
|
2137
2375
|
serializeJsonDefault,
|
|
2376
|
+
sqlNamespaceIssue,
|
|
2138
2377
|
sqlObjectKey,
|
|
2378
|
+
sqlObjectNamespace,
|
|
2139
2379
|
unsupportedCapability,
|
|
2140
2380
|
validateComputedConfig,
|
|
2141
2381
|
validateRlsContextIR,
|
|
2142
2382
|
validateSchemaIR
|
|
2143
2383
|
};
|
|
2144
2384
|
|
|
2145
|
-
//# debugId=
|
|
2385
|
+
//# debugId=20984641ACA6D36E64756E2164756E21
|