@vibeorm/schema 2.3.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 +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +703 -177
- package/dist/index.js.map +15 -11
- 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/physical-names.d.ts +119 -0
- package/dist/physical-names.d.ts.map +1 -0
- package/dist/prisma-literal.d.ts +13 -0
- package/dist/prisma-literal.d.ts.map +1 -1
- 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":
|
|
@@ -280,22 +400,255 @@ function isSqlObjectIdentity(value) {
|
|
|
280
400
|
function isSqlObjectDefinition(value) {
|
|
281
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);
|
|
282
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
|
+
}
|
|
283
475
|
function assertMigrationObjects(params) {
|
|
284
476
|
if (!Array.isArray(params.objects))
|
|
285
|
-
|
|
477
|
+
invalidDeclaration({ path: "migrationObjects", expected: "array" });
|
|
286
478
|
const ids = new Set;
|
|
287
479
|
const owners = new Set;
|
|
288
|
-
for (const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
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);
|
|
296
516
|
owners.add(key);
|
|
297
517
|
}
|
|
298
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;
|
|
551
|
+
}
|
|
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 } });
|
|
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);
|
|
651
|
+
}
|
|
299
652
|
// packages/schema/src/prisma-literal.ts
|
|
300
653
|
var STRING_ESCAPES = {
|
|
301
654
|
'"': '"',
|
|
@@ -389,9 +742,25 @@ function exactIntegerLiteral(params) {
|
|
|
389
742
|
return asNumber;
|
|
390
743
|
return BigInt(text).toString();
|
|
391
744
|
}
|
|
745
|
+
var DECIMAL_LITERAL = /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;
|
|
746
|
+
function exactDecimalLiteral(params) {
|
|
747
|
+
const { text } = params;
|
|
748
|
+
if (!DECIMAL_LITERAL.test(text)) {
|
|
749
|
+
throw new VibeError({
|
|
750
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
751
|
+
message: `Not a numeric literal: ${JSON.stringify(text)}`,
|
|
752
|
+
meta: { issues: [{ rule: "malformed-numeric-literal", message: text }], literal: text }
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (INTEGER_LITERAL.test(text))
|
|
756
|
+
return exactIntegerLiteral({ text });
|
|
757
|
+
const mantissa = text.replace(/[eE].*$/, "").replace("-", "").replace(".", "").replace(/^0+/, "");
|
|
758
|
+
return mantissa.length <= 15 ? Number(text) : text;
|
|
759
|
+
}
|
|
392
760
|
// packages/schema/src/reserved.ts
|
|
393
761
|
var RESERVED_DELEGATE_NAMES = [
|
|
394
762
|
"findMany",
|
|
763
|
+
"findPage",
|
|
395
764
|
"findFirst",
|
|
396
765
|
"findUnique",
|
|
397
766
|
"findFirstOrThrow",
|
|
@@ -710,78 +1079,20 @@ function scalarListDefault(params) {
|
|
|
710
1079
|
});
|
|
711
1080
|
}
|
|
712
1081
|
|
|
713
|
-
// packages/schema/src/
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
transactionalDdl: true,
|
|
728
|
-
createManyAndReturn: true,
|
|
729
|
-
jsonColumnType: true,
|
|
730
|
-
orderByNulls: true,
|
|
731
|
-
fullTextEngines: Object.freeze(["bm25", "tsvector"]),
|
|
732
|
-
rowLockStrengths: Object.freeze(["update", "noKeyUpdate", "share", "keyShare"]),
|
|
733
|
-
rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
|
|
734
|
-
advisoryLocks: true
|
|
735
|
-
});
|
|
736
|
-
var SQLITE_CAPABILITIES = Object.freeze({
|
|
737
|
-
exclusionConstraints: false,
|
|
738
|
-
exclusionExtensions: Object.freeze([]),
|
|
739
|
-
checkNormalization: "sourceText",
|
|
740
|
-
nativeRowLevelSecurity: false,
|
|
741
|
-
returning: true,
|
|
742
|
-
scalarArrays: false,
|
|
743
|
-
nativeEnums: false,
|
|
744
|
-
ilike: false,
|
|
745
|
-
lateralJoin: false,
|
|
746
|
-
distinctOn: false,
|
|
747
|
-
partialIndexes: true,
|
|
748
|
-
conflictTargetConstraint: false,
|
|
749
|
-
transactionalDdl: true,
|
|
750
|
-
createManyAndReturn: true,
|
|
751
|
-
jsonColumnType: false,
|
|
752
|
-
orderByNulls: true,
|
|
753
|
-
fullTextEngines: Object.freeze(["fts5"]),
|
|
754
|
-
rowLockStrengths: Object.freeze([]),
|
|
755
|
-
rowLockWaitPolicies: Object.freeze([]),
|
|
756
|
-
advisoryLocks: false
|
|
757
|
-
});
|
|
758
|
-
var MYSQL_CAPABILITIES = Object.freeze({
|
|
759
|
-
exclusionConstraints: false,
|
|
760
|
-
exclusionExtensions: Object.freeze([]),
|
|
761
|
-
checkNormalization: "temporary",
|
|
762
|
-
nativeRowLevelSecurity: false,
|
|
763
|
-
returning: false,
|
|
764
|
-
scalarArrays: false,
|
|
765
|
-
nativeEnums: true,
|
|
766
|
-
ilike: false,
|
|
767
|
-
lateralJoin: false,
|
|
768
|
-
distinctOn: false,
|
|
769
|
-
partialIndexes: false,
|
|
770
|
-
conflictTargetConstraint: false,
|
|
771
|
-
transactionalDdl: false,
|
|
772
|
-
createManyAndReturn: false,
|
|
773
|
-
jsonColumnType: true,
|
|
774
|
-
orderByNulls: false,
|
|
775
|
-
fullTextEngines: Object.freeze(["fulltext"]),
|
|
776
|
-
rowLockStrengths: Object.freeze(["update", "share"]),
|
|
777
|
-
rowLockWaitPolicies: Object.freeze(["wait", "nowait", "skipLocked"]),
|
|
778
|
-
advisoryLocks: false
|
|
779
|
-
});
|
|
780
|
-
var DIALECT_CAPABILITIES = Object.freeze({
|
|
781
|
-
postgres: POSTGRES_CAPABILITIES,
|
|
782
|
-
sqlite: SQLITE_CAPABILITIES,
|
|
783
|
-
mysql: MYSQL_CAPABILITIES
|
|
784
|
-
});
|
|
1082
|
+
// packages/schema/src/m2m.ts
|
|
1083
|
+
function implicitJoinTable(params) {
|
|
1084
|
+
const sorted = [params.modelA, params.modelB].sort();
|
|
1085
|
+
const first = sorted[0];
|
|
1086
|
+
const second = sorted[1];
|
|
1087
|
+
const key = params.relationName ?? `${first}To${second}`;
|
|
1088
|
+
return {
|
|
1089
|
+
tableName: `_${key}`,
|
|
1090
|
+
modelA: first,
|
|
1091
|
+
modelB: second,
|
|
1092
|
+
columnA: "A",
|
|
1093
|
+
columnB: "B"
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
785
1096
|
|
|
786
1097
|
// packages/schema/src/validate.ts
|
|
787
1098
|
var RESERVED_ENUM_NAMES = [
|
|
@@ -837,6 +1148,7 @@ var RESERVED_ENUM_NAMES = [
|
|
|
837
1148
|
"SortOrder",
|
|
838
1149
|
"SortOrderWithNulls",
|
|
839
1150
|
"KeysetToken",
|
|
1151
|
+
"KeysetPage",
|
|
840
1152
|
"KeysetOptions",
|
|
841
1153
|
"RowLockRequest",
|
|
842
1154
|
"JsonValue",
|
|
@@ -915,6 +1227,7 @@ var GENERATED_TYPE_NAMES = {
|
|
|
915
1227
|
"MinAggregateInput",
|
|
916
1228
|
"MaxAggregateInput",
|
|
917
1229
|
"FindManyArgs",
|
|
1230
|
+
"FindPageArgs",
|
|
918
1231
|
"FindFirstArgs",
|
|
919
1232
|
"FindUniqueArgs",
|
|
920
1233
|
"CreateArgs",
|
|
@@ -1085,6 +1398,15 @@ function validateGeneratedNames(params) {
|
|
|
1085
1398
|
function validateSchemaIR(params) {
|
|
1086
1399
|
const { schema } = params;
|
|
1087
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
|
+
}
|
|
1088
1410
|
const modelByName = new Map;
|
|
1089
1411
|
for (const model of schema.models) {
|
|
1090
1412
|
if (modelByName.has(model.name)) {
|
|
@@ -1256,10 +1578,197 @@ function validateSchemaIR(params) {
|
|
|
1256
1578
|
});
|
|
1257
1579
|
}
|
|
1258
1580
|
}
|
|
1581
|
+
validateIdentifierLengths({ schema, issues });
|
|
1582
|
+
validatePhysicalNameUniqueness({ schema, issues });
|
|
1259
1583
|
validateGeneratedNames({ schema, issues });
|
|
1260
1584
|
validateNativeRls({ schema, issues });
|
|
1261
1585
|
return issues;
|
|
1262
1586
|
}
|
|
1587
|
+
function validatePhysicalNameUniqueness(params) {
|
|
1588
|
+
const { schema, issues } = params;
|
|
1589
|
+
const dialect = providerToDialect({ provider: schema.datasource.provider });
|
|
1590
|
+
const owners = new Map;
|
|
1591
|
+
const claim = (entry) => {
|
|
1592
|
+
const key = JSON.stringify([entry.physicalNamespace ?? "public", entry.namespace, entry.name]);
|
|
1593
|
+
const previous = owners.get(key);
|
|
1594
|
+
if (previous === undefined) {
|
|
1595
|
+
owners.set(key, `${entry.what} of ${entry.owner}`);
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
issues.push({
|
|
1599
|
+
rule: "duplicate-physical-name",
|
|
1600
|
+
message: `${entry.what} "${entry.name}" of ${entry.owner} collides with the ${previous} \u2014 both resolve to one physical ${entry.namespace === "relation" ? "object" : "name"}, and only one can exist`,
|
|
1601
|
+
...entry.model === undefined ? {} : { model: entry.model },
|
|
1602
|
+
...entry.field === undefined ? {} : { field: entry.field }
|
|
1603
|
+
});
|
|
1604
|
+
return false;
|
|
1605
|
+
};
|
|
1606
|
+
const indexNamespace = (table) => dialect === "mysql" ? `index:${table}` : "relation";
|
|
1607
|
+
const constraintNamespace = (table) => dialect === "mysql" ? "constraint" : `constraint:${table}`;
|
|
1608
|
+
for (const model of schema.models) {
|
|
1609
|
+
const table = model.dbName ?? model.name;
|
|
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 }))
|
|
1612
|
+
continue;
|
|
1613
|
+
for (const fieldIR of model.fields) {
|
|
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 });
|
|
1615
|
+
}
|
|
1616
|
+
if (model.isView)
|
|
1617
|
+
continue;
|
|
1618
|
+
const columnOf = (name) => model.fields.find((candidate) => candidate.name === name)?.dbName ?? name;
|
|
1619
|
+
const seenIndexKeys = new Set;
|
|
1620
|
+
const declarations = [
|
|
1621
|
+
...model.indexes.map((index) => ({ index, site: index.kind === "unique" ? "@@unique" : "@@index" })),
|
|
1622
|
+
...model.fields.filter((fieldIR) => fieldIR.isUnique).map((fieldIR) => ({ index: { kind: "unique", fields: [{ name: fieldIR.name }] }, site: `@unique on "${fieldIR.name}"` }))
|
|
1623
|
+
];
|
|
1624
|
+
for (const { index, site } of declarations) {
|
|
1625
|
+
const unique = index.kind === "unique";
|
|
1626
|
+
const columns = index.fields.map((entry) => `${columnOf(entry.name)}${entry.sort === "desc" ? " desc" : ""}`);
|
|
1627
|
+
const key = `${unique ? "u" : "i"}${index.nullsNotDistinct === true ? "n" : ""}:${unique ? "btree" : index.kind}:${columns.join(",")}:${index.where?.trim() ?? ""}`;
|
|
1628
|
+
if (seenIndexKeys.has(key))
|
|
1629
|
+
continue;
|
|
1630
|
+
seenIndexKeys.add(key);
|
|
1631
|
+
const name = index.name ?? defaultIndexName({ table, columns: index.fields.map((entry) => columnOf(entry.name)), unique, dialect });
|
|
1632
|
+
claim({ namespace: indexNamespace(table), physicalNamespace, name, what: unique ? "unique index" : "index", owner: `${site} on "${model.name}"`, model: model.name });
|
|
1633
|
+
if (index.constraintName !== undefined) {
|
|
1634
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name: index.constraintName, what: "unique constraint", owner: `${site} on "${model.name}"`, model: model.name });
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (dialect === "postgres" && (model.primaryKey !== undefined || model.fields.some((fieldIR) => fieldIR.isId))) {
|
|
1638
|
+
const name = model.primaryKeyConstraintName ?? defaultPrimaryKeyName({ table, dialect });
|
|
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 });
|
|
1641
|
+
}
|
|
1642
|
+
for (const relation of model.relations) {
|
|
1643
|
+
if (relation.fields === undefined || relation.fields.length === 0)
|
|
1644
|
+
continue;
|
|
1645
|
+
const target = schema.models.find((candidate) => candidate.name === relation.targetModel);
|
|
1646
|
+
if (target !== undefined && target.isView)
|
|
1647
|
+
continue;
|
|
1648
|
+
const name = relation.constraintName ?? defaultForeignKeyName({ table, columns: relation.fields.map((entry) => columnOf(entry)), dialect });
|
|
1649
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name, what: "foreign key", owner: `relation "${model.name}.${relation.name}"`, model: model.name });
|
|
1650
|
+
}
|
|
1651
|
+
for (const checkIR of model.checks ?? []) {
|
|
1652
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name: checkIR.name, what: "CHECK constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1653
|
+
}
|
|
1654
|
+
for (const exclusion of model.exclusions ?? []) {
|
|
1655
|
+
claim({ namespace: constraintNamespace(table), physicalNamespace, name: exclusion.name, what: "exclusion constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1656
|
+
if (dialect === "postgres")
|
|
1657
|
+
claim({ namespace: "relation", physicalNamespace, name: exclusion.name, what: "exclusion constraint", owner: `model "${model.name}"`, model: model.name });
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (dialect === "postgres") {
|
|
1661
|
+
for (const enumDef of schema.enums) {
|
|
1662
|
+
if (enumStorageOf({ enumDef }) !== "database")
|
|
1663
|
+
continue;
|
|
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}"` });
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
const seenJoinTables = new Set;
|
|
1668
|
+
for (const model of schema.models) {
|
|
1669
|
+
for (const relation of model.relations) {
|
|
1670
|
+
if (relation.targetModel === model.name)
|
|
1671
|
+
continue;
|
|
1672
|
+
if (!isImplicitManyToManyRelation({ model, relation, models: schema.models }))
|
|
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
|
+
}
|
|
1680
|
+
const join = implicitJoinTable({ modelA: model.name, modelB: relation.targetModel, ...relation.relationName === undefined ? {} : { relationName: relation.relationName } });
|
|
1681
|
+
const joinKey = JSON.stringify([physicalNamespace, join.tableName]);
|
|
1682
|
+
if (seenJoinTables.has(joinKey))
|
|
1683
|
+
continue;
|
|
1684
|
+
seenJoinTables.add(joinKey);
|
|
1685
|
+
const owner = `implicit relation "${model.name}.${relation.name}"`;
|
|
1686
|
+
claim({ namespace: "relation", physicalNamespace, name: join.tableName, what: "join table", owner, model: model.name });
|
|
1687
|
+
const derived = implicitJoinTableObjectNames({ table: join.tableName });
|
|
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 });
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
function validateIdentifierLengths(params) {
|
|
1696
|
+
const { schema, issues } = params;
|
|
1697
|
+
const dialect = providerToDialect({ provider: schema.datasource.provider });
|
|
1698
|
+
const limit = IDENTIFIER_LIMITS[dialect];
|
|
1699
|
+
if (limit === undefined)
|
|
1700
|
+
return;
|
|
1701
|
+
const { max, unit } = limit;
|
|
1702
|
+
const consequence = dialect === "postgres" ? `PostgreSQL silently truncates identifiers to ${max} bytes (NAMEDATALEN - 1), so the created object never carries this name and migrations cannot converge` : `MySQL refuses identifiers longer than ${max} characters (ER_TOO_LONG_IDENT)`;
|
|
1703
|
+
const check = (entry) => {
|
|
1704
|
+
const length = identifierLength({ name: entry.name, dialect });
|
|
1705
|
+
if (length <= max)
|
|
1706
|
+
return;
|
|
1707
|
+
issues.push({
|
|
1708
|
+
rule: "identifier-too-long",
|
|
1709
|
+
message: `${entry.what} "${entry.name}" is ${length} ${unit} \u2014 ${consequence}. ${entry.remedy}`,
|
|
1710
|
+
...entry.model === undefined ? {} : { model: entry.model }
|
|
1711
|
+
});
|
|
1712
|
+
};
|
|
1713
|
+
for (const model of schema.models) {
|
|
1714
|
+
const table = model.dbName ?? model.name;
|
|
1715
|
+
check({ name: table, what: model.isView ? "View name" : "Table name", model: model.name, remedy: `Map "${model.name}" to a shorter physical name (@@map).` });
|
|
1716
|
+
for (const fieldIR of model.fields) {
|
|
1717
|
+
check({ name: fieldIR.dbName ?? fieldIR.name, what: "Column name", model: model.name, remedy: `Map "${model.name}.${fieldIR.name}" to a shorter column name (@map).` });
|
|
1718
|
+
}
|
|
1719
|
+
if (model.isView)
|
|
1720
|
+
continue;
|
|
1721
|
+
for (const index of model.indexes) {
|
|
1722
|
+
if (index.name !== undefined) {
|
|
1723
|
+
check({ name: index.name, what: index.kind === "unique" ? "Unique index name" : "Index name", model: model.name, remedy: "Shorten the index name (map:)." });
|
|
1724
|
+
}
|
|
1725
|
+
if (index.constraintName !== undefined) {
|
|
1726
|
+
check({ name: index.constraintName, what: "Unique constraint name", model: model.name, remedy: "Shorten the constraint name." });
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
if (model.primaryKeyConstraintName !== undefined) {
|
|
1730
|
+
check({ name: model.primaryKeyConstraintName, what: "Primary key constraint name", model: model.name, remedy: "Shorten the constraint name (map:)." });
|
|
1731
|
+
}
|
|
1732
|
+
for (const relation of model.relations) {
|
|
1733
|
+
if (relation.constraintName === undefined)
|
|
1734
|
+
continue;
|
|
1735
|
+
check({ name: relation.constraintName, what: "Foreign key constraint name", model: model.name, remedy: "Shorten the constraint name (map:)." });
|
|
1736
|
+
}
|
|
1737
|
+
for (const checkIR of model.checks ?? []) {
|
|
1738
|
+
check({ name: checkIR.name, what: "CHECK constraint name", model: model.name, remedy: "Shorten the CHECK name." });
|
|
1739
|
+
}
|
|
1740
|
+
for (const exclusion of model.exclusions ?? []) {
|
|
1741
|
+
check({ name: exclusion.name, what: "Exclusion constraint name", model: model.name, remedy: "Shorten the exclusion name." });
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
if (dialect === "postgres") {
|
|
1745
|
+
for (const enumDef of schema.enums) {
|
|
1746
|
+
if (enumStorageOf({ enumDef }) !== "database")
|
|
1747
|
+
continue;
|
|
1748
|
+
check({ name: enumDef.dbName ?? enumDef.name, what: "Enum type name", remedy: `Map enum "${enumDef.name}" to a shorter type name (@@map).` });
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const seenJoinTables = new Set;
|
|
1752
|
+
for (const model of schema.models) {
|
|
1753
|
+
for (const relation of model.relations) {
|
|
1754
|
+
if (relation.targetModel === model.name)
|
|
1755
|
+
continue;
|
|
1756
|
+
if (!isImplicitManyToManyRelation({ model, relation, models: schema.models }))
|
|
1757
|
+
continue;
|
|
1758
|
+
const join = implicitJoinTable({ modelA: model.name, modelB: relation.targetModel, ...relation.relationName === undefined ? {} : { relationName: relation.relationName } });
|
|
1759
|
+
if (seenJoinTables.has(join.tableName))
|
|
1760
|
+
continue;
|
|
1761
|
+
seenJoinTables.add(join.tableName);
|
|
1762
|
+
const remedy = `Give the relation an explicit shorter name (@relation("...")).`;
|
|
1763
|
+
check({ name: join.tableName, what: "Implicit join table name", model: model.name, remedy });
|
|
1764
|
+
const derived = implicitJoinTableObjectNames({ table: join.tableName });
|
|
1765
|
+
check({ name: derived.uniqueIndex, what: "Implicit join table index name", model: model.name, remedy });
|
|
1766
|
+
check({ name: derived.indexB, what: "Implicit join table index name", model: model.name, remedy });
|
|
1767
|
+
check({ name: derived.foreignKeyA, what: "Implicit join table foreign key name", model: model.name, remedy });
|
|
1768
|
+
check({ name: derived.foreignKeyB, what: "Implicit join table foreign key name", model: model.name, remedy });
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1263
1772
|
function validateModel(params) {
|
|
1264
1773
|
const { model, modelByName, enumByName, issues } = params;
|
|
1265
1774
|
const memberNames = new Set;
|
|
@@ -1545,6 +2054,20 @@ function validatePredicateFields(params) {
|
|
|
1545
2054
|
}
|
|
1546
2055
|
function assertValidSchemaIR(params) {
|
|
1547
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
|
+
}
|
|
1548
2071
|
for (const model of params.schema.models) {
|
|
1549
2072
|
if (model.exclusions !== undefined && !Array.isArray(model.exclusions))
|
|
1550
2073
|
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Exclusions must be an array", meta: { rule: "exclusion-definition" } });
|
|
@@ -1786,25 +2309,12 @@ function validRlsForeignKey(params) {
|
|
|
1786
2309
|
];
|
|
1787
2310
|
return validFields && uniqueSets.some((set) => set.length === refs.length && set.every((name) => refs.includes(name)));
|
|
1788
2311
|
}
|
|
1789
|
-
// packages/schema/src/m2m.ts
|
|
1790
|
-
function implicitJoinTable(params) {
|
|
1791
|
-
const sorted = [params.modelA, params.modelB].sort();
|
|
1792
|
-
const first = sorted[0];
|
|
1793
|
-
const second = sorted[1];
|
|
1794
|
-
const key = params.relationName ?? `${first}To${second}`;
|
|
1795
|
-
return {
|
|
1796
|
-
tableName: `_${key}`,
|
|
1797
|
-
modelA: first,
|
|
1798
|
-
modelB: second,
|
|
1799
|
-
columnA: "A",
|
|
1800
|
-
columnB: "B"
|
|
1801
|
-
};
|
|
1802
|
-
}
|
|
1803
2312
|
export {
|
|
1804
2313
|
DIALECTS,
|
|
1805
2314
|
DIALECT_CAPABILITIES,
|
|
1806
2315
|
EMPTY_EXTENSION_REGISTRY,
|
|
1807
2316
|
ENUM_STORAGES,
|
|
2317
|
+
IDENTIFIER_LIMITS,
|
|
1808
2318
|
MYSQL_CAPABILITIES,
|
|
1809
2319
|
POSTGRES_CAPABILITIES,
|
|
1810
2320
|
PREDICATE_COMPARISON_OPS,
|
|
@@ -1817,23 +2327,34 @@ export {
|
|
|
1817
2327
|
RlsContextError,
|
|
1818
2328
|
SCOPED_REFUSAL_REASONS,
|
|
1819
2329
|
SQLITE_CAPABILITIES,
|
|
2330
|
+
SQL_NAMESPACES,
|
|
1820
2331
|
ScopedQueryError,
|
|
1821
2332
|
VIBE_ERROR_CODES,
|
|
1822
2333
|
VibeError,
|
|
2334
|
+
assertIdentifierFits,
|
|
2335
|
+
assertIndexOperatorClasses,
|
|
1823
2336
|
assertMigrationObjects,
|
|
2337
|
+
assertSqlNamespace,
|
|
1824
2338
|
assertValidSchemaIR,
|
|
1825
2339
|
buildComputedManifest,
|
|
1826
2340
|
buildExtensionRegistry,
|
|
1827
2341
|
decodePrismaString,
|
|
2342
|
+
defaultForeignKeyName,
|
|
2343
|
+
defaultIndexName,
|
|
2344
|
+
defaultPrimaryKeyName,
|
|
1828
2345
|
defineExtension,
|
|
1829
2346
|
encodePrismaString,
|
|
1830
2347
|
enumStorageOf,
|
|
2348
|
+
exactDecimalLiteral,
|
|
1831
2349
|
exactIntegerLiteral,
|
|
1832
2350
|
extensionDeclarations,
|
|
1833
2351
|
extensionManifestEntry,
|
|
1834
2352
|
extensionOptionsHash,
|
|
1835
2353
|
fieldTypeIdentity,
|
|
2354
|
+
fitDefaultName,
|
|
2355
|
+
identifierLength,
|
|
1836
2356
|
implicitJoinTable,
|
|
2357
|
+
implicitJoinTableObjectNames,
|
|
1837
2358
|
internalError,
|
|
1838
2359
|
invalidStoredData,
|
|
1839
2360
|
isEnumType,
|
|
@@ -1844,16 +2365,21 @@ export {
|
|
|
1844
2365
|
isSqlObjectDefinition,
|
|
1845
2366
|
isSqlObjectIdentity,
|
|
1846
2367
|
mergeExtensionDeclarations,
|
|
2368
|
+
postgresGeneratedObjectName,
|
|
1847
2369
|
predicateFieldNames,
|
|
1848
2370
|
predicateKey,
|
|
2371
|
+
prepareDatabaseModuleSchema,
|
|
1849
2372
|
providerToDialect,
|
|
2373
|
+
qualifiedSqlName,
|
|
1850
2374
|
scalarListDefault,
|
|
1851
2375
|
serializeJsonDefault,
|
|
2376
|
+
sqlNamespaceIssue,
|
|
1852
2377
|
sqlObjectKey,
|
|
2378
|
+
sqlObjectNamespace,
|
|
1853
2379
|
unsupportedCapability,
|
|
1854
2380
|
validateComputedConfig,
|
|
1855
2381
|
validateRlsContextIR,
|
|
1856
2382
|
validateSchemaIR
|
|
1857
2383
|
};
|
|
1858
2384
|
|
|
1859
|
-
//# debugId=
|
|
2385
|
+
//# debugId=20984641ACA6D36E64756E2164756E21
|