@vibeorm/dsl 2.4.0 → 4.0.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/LICENSE +21 -0
- package/dist/column.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +216 -135
- package/dist/index.js.map +8 -8
- package/dist/types.d.ts +28 -10
- package/package.json +6 -4
- package/dist/column.d.ts.map +0 -1
- package/dist/enum.d.ts.map +0 -1
- package/dist/field-type.d.ts.map +0 -1
- package/dist/generator/index.d.ts.map +0 -1
- package/dist/generator/prisma.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/model.d.ts.map +0 -1
- package/dist/naming.d.ts.map +0 -1
- package/dist/predicate.d.ts.map +0 -1
- package/dist/relation-plan.d.ts.map +0 -1
- package/dist/relation.d.ts.map +0 -1
- package/dist/relations.d.ts.map +0 -1
- package/dist/schema.d.ts.map +0 -1
- package/dist/templates.d.ts.map +0 -1
- package/dist/to-ir.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/validate.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/dsl/src/column.ts
|
|
3
|
+
import { VibeError, assertJsonSchemaRef, encodePrismaString } from "@vibeorm/schema";
|
|
3
4
|
function cloneColumnDef(params) {
|
|
4
5
|
const { column } = params;
|
|
5
6
|
return {
|
|
6
7
|
__type: "column",
|
|
7
8
|
__columnType: column.__columnType,
|
|
8
9
|
__name: column.__name,
|
|
9
|
-
__config: { ...column.__config },
|
|
10
|
+
__config: { ...column.__config, ...column.__config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: column.__config.jsonSchema }) } },
|
|
10
11
|
__enumValues: column.__enumValues,
|
|
11
12
|
__enumName: column.__enumName,
|
|
12
13
|
__enumStorage: column.__enumStorage
|
|
@@ -165,10 +166,17 @@ function datetime(config = {}) {
|
|
|
165
166
|
|
|
166
167
|
class ChainableJsonColumn extends ChainableColumn {
|
|
167
168
|
constructor(config = {}) {
|
|
168
|
-
super("json", config);
|
|
169
|
+
super("json", { ...config, ...config.jsonSchema === undefined ? {} : { jsonSchema: assertJsonSchemaRef({ value: config.jsonSchema }) } });
|
|
170
|
+
}
|
|
171
|
+
schema(reference) {
|
|
172
|
+
if (this.__config.jsonSchema !== undefined)
|
|
173
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "A field may declare only one JSON schema reference", meta: { issues: [{ rule: "duplicate-json-schema-ref" }] } });
|
|
174
|
+
this.__config.jsonSchema = assertJsonSchemaRef({ value: reference });
|
|
175
|
+
return this;
|
|
169
176
|
}
|
|
170
177
|
typed(config) {
|
|
171
|
-
const
|
|
178
|
+
const ref = assertJsonSchemaRef({ value: config });
|
|
179
|
+
const annotation = `@type(${ref.name} from ${encodePrismaString({ value: ref.from })})`;
|
|
172
180
|
this.__config.comment = this.__config.comment === undefined ? annotation : `${this.__config.comment} ${annotation}`;
|
|
173
181
|
return this;
|
|
174
182
|
}
|
|
@@ -227,11 +235,11 @@ function createEnum(config) {
|
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
// packages/dsl/src/generator/prisma.ts
|
|
230
|
-
import { VibeError as
|
|
238
|
+
import { VibeError as VibeError6, encodePrismaString as encodePrismaString2, internalError, normalizeJsonSchemaRef as normalizeJsonSchemaRef2, providerToDialect as providerToDialect2 } from "@vibeorm/schema";
|
|
231
239
|
import { getSqlDialect as getSqlDialect2 } from "@vibeorm/sql";
|
|
232
240
|
|
|
233
241
|
// packages/dsl/src/predicate.ts
|
|
234
|
-
import { VibeError } from "@vibeorm/schema";
|
|
242
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
235
243
|
import { compileExclusion, renderPredicate } from "@vibeorm/sql";
|
|
236
244
|
function isFieldExpr(operand) {
|
|
237
245
|
return typeof operand === "object" && operand !== null && "kind" in operand && operand.kind === "field";
|
|
@@ -246,7 +254,7 @@ function toValue(params) {
|
|
|
246
254
|
if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
|
|
247
255
|
return { kind: "value", value: input };
|
|
248
256
|
}
|
|
249
|
-
throw new
|
|
257
|
+
throw new VibeError2({
|
|
250
258
|
code: "VIBE_SCHEMA_INVALID",
|
|
251
259
|
message: `${at}: ${input === null ? "null" : typeof input} is not a comparable literal \u2014 use isNull()/isNotNull() for null, and a string, number, bigint or boolean otherwise`,
|
|
252
260
|
meta: { rule: "predicate-literal-invalid", at }
|
|
@@ -254,13 +262,27 @@ function toValue(params) {
|
|
|
254
262
|
}
|
|
255
263
|
function toOperand(params) {
|
|
256
264
|
const { operand, at } = params;
|
|
257
|
-
return
|
|
265
|
+
return typeof operand === "object" && operand !== null ? toExpression({ value: operand, at }) : toValue({ input: operand, at });
|
|
266
|
+
}
|
|
267
|
+
function toExpression(params) {
|
|
268
|
+
const { value, at } = params;
|
|
269
|
+
if (isFieldExpr(value))
|
|
270
|
+
return toRef({ expr: value });
|
|
271
|
+
if (typeof value === "object" && value !== null && "kind" in value) {
|
|
272
|
+
if (value.kind === "jsonText" && "field" in value && "path" in value && Array.isArray(value.path) && Array.from(value.path).every((segment) => typeof segment === "string" && !segment.includes("\x00"))) {
|
|
273
|
+
return { kind: "jsonText", field: toRef({ expr: requireFieldExpr({ value: value.field, at }) }), path: [...value.path] };
|
|
274
|
+
}
|
|
275
|
+
if (value.kind === "length" && "expression" in value) {
|
|
276
|
+
return { kind: "length", expression: toExpression({ value: value.expression, at }) };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `${at}: expected a field, jsonText or length expression with a string-only JSON path`, meta: { rule: "predicate-operand-invalid", at } });
|
|
258
280
|
}
|
|
259
281
|
function requireFieldExpr(params) {
|
|
260
282
|
const { value, at } = params;
|
|
261
283
|
if (isFieldExpr(value))
|
|
262
284
|
return value;
|
|
263
|
-
throw new
|
|
285
|
+
throw new VibeError2({
|
|
264
286
|
code: "VIBE_SCHEMA_INVALID",
|
|
265
287
|
message: `${at}: the left side of a comparison must be c.field("\u2026"), not a bare value`,
|
|
266
288
|
meta: { rule: "predicate-operand-invalid", at }
|
|
@@ -269,7 +291,7 @@ function requireFieldExpr(params) {
|
|
|
269
291
|
function requireOperands(params) {
|
|
270
292
|
const { operands, at } = params;
|
|
271
293
|
if (operands.length === 0) {
|
|
272
|
-
throw new
|
|
294
|
+
throw new VibeError2({
|
|
273
295
|
code: "VIBE_SCHEMA_INVALID",
|
|
274
296
|
message: `${at}: an empty boolean group has no honest SQL spelling \u2014 pass at least one operand`,
|
|
275
297
|
meta: { rule: "predicate-empty", at }
|
|
@@ -281,14 +303,14 @@ function createPredicateBuilder() {
|
|
|
281
303
|
const compare = (op) => (left, right) => ({
|
|
282
304
|
kind: "compare",
|
|
283
305
|
op,
|
|
284
|
-
left:
|
|
306
|
+
left: toExpression({ value: left, at: `c.${op}()` }),
|
|
285
307
|
right: toOperand({ operand: right, at: `c.${op}()` })
|
|
286
308
|
});
|
|
287
309
|
const membership = (negate) => (field, values) => {
|
|
288
310
|
const at = negate ? "c.notInList()" : "c.inList()";
|
|
289
311
|
const ref = toRef({ expr: requireFieldExpr({ value: field, at }) });
|
|
290
312
|
if (values.length === 0) {
|
|
291
|
-
throw new
|
|
313
|
+
throw new VibeError2({
|
|
292
314
|
code: "VIBE_SCHEMA_INVALID",
|
|
293
315
|
message: `${at}: an empty IN list is UNKNOWN on every row and is never what an author means`,
|
|
294
316
|
meta: { rule: "predicate-empty", at, field: ref.name }
|
|
@@ -309,6 +331,9 @@ function createPredicateBuilder() {
|
|
|
309
331
|
gte: compare("gte"),
|
|
310
332
|
lt: compare("lt"),
|
|
311
333
|
lte: compare("lte"),
|
|
334
|
+
jsonText: ({ field, path }) => ({ ...toExpression({ value: { kind: "jsonText", field: { kind: "field", name: field }, path }, at: "c.jsonText()" }), __kind: "String" }),
|
|
335
|
+
length: (expression) => ({ kind: "length", expression: toExpression({ value: expression, at: "c.length()" }), __kind: "Int" }),
|
|
336
|
+
isTrue: (operand) => ({ kind: "isTrue", operand }),
|
|
312
337
|
inList: membership(false),
|
|
313
338
|
notInList: membership(true),
|
|
314
339
|
isNull: (name) => ({ kind: "isNull", field: { kind: "field", name } }),
|
|
@@ -318,7 +343,7 @@ function createPredicateBuilder() {
|
|
|
318
343
|
not: (operand) => ({ kind: "not", operand }),
|
|
319
344
|
named: (name, predicate) => {
|
|
320
345
|
if (name.trim() === "") {
|
|
321
|
-
throw new
|
|
346
|
+
throw new VibeError2({
|
|
322
347
|
code: "VIBE_SCHEMA_INVALID",
|
|
323
348
|
message: "c.named(): a CHECK needs a non-empty name \u2014 the name is what keys the migration diff",
|
|
324
349
|
meta: { rule: "predicate-unnamed-check" }
|
|
@@ -337,10 +362,13 @@ function evaluateChecks(params) {
|
|
|
337
362
|
}
|
|
338
363
|
const declared = checks(createPredicateBuilder());
|
|
339
364
|
return declared.map((entry, index) => {
|
|
340
|
-
if (entry
|
|
341
|
-
|
|
365
|
+
if (entry !== null && typeof entry === "object" && !("__type" in entry) && typeof entry.name === "string" && "expression" in entry && typeof entry.expression === "string") {
|
|
366
|
+
return { kind: "raw", name: entry.name, expression: entry.expression };
|
|
367
|
+
}
|
|
368
|
+
if (entry === null || typeof entry !== "object" || !("__type" in entry) || entry.__type !== "check") {
|
|
369
|
+
throw new VibeError2({
|
|
342
370
|
code: "VIBE_SCHEMA_INVALID",
|
|
343
|
-
message: `Model "${model}": checks[${index}]
|
|
371
|
+
message: `Model "${model}": checks[${index}] must be c.named("name", predicate) or a raw { name, expression } CHECK`,
|
|
344
372
|
meta: { rule: "predicate-unnamed-check", model, index }
|
|
345
373
|
});
|
|
346
374
|
}
|
|
@@ -369,7 +397,7 @@ function compileChecks(params) {
|
|
|
369
397
|
function compileIndexPredicate(params) {
|
|
370
398
|
const { where, wherePredicate, columns, model, dialect, enums } = params;
|
|
371
399
|
if (where !== undefined && wherePredicate !== undefined) {
|
|
372
|
-
throw new
|
|
400
|
+
throw new VibeError2({
|
|
373
401
|
code: "VIBE_SCHEMA_INVALID",
|
|
374
402
|
message: `Index on "${model.name}" [${columns.join(", ")}] carries both a raw where: string and a typed .where() \u2014 an index has ONE predicate`,
|
|
375
403
|
meta: { rule: "predicate-duplicate-where", model: model.name, columns: [...columns] }
|
|
@@ -395,13 +423,14 @@ function compileIndexPredicate(params) {
|
|
|
395
423
|
// packages/dsl/src/to-ir.ts
|
|
396
424
|
import {
|
|
397
425
|
EMPTY_EXTENSION_REGISTRY,
|
|
398
|
-
VibeError as
|
|
426
|
+
VibeError as VibeError5,
|
|
399
427
|
assertValidSchemaIR,
|
|
400
428
|
exactIntegerLiteral,
|
|
401
429
|
providerToDialect,
|
|
402
430
|
scalarListDefault,
|
|
403
431
|
serializeJsonDefault,
|
|
404
432
|
mergeExtensionDeclarations,
|
|
433
|
+
normalizeJsonSchemaRef,
|
|
405
434
|
unsupportedCapability
|
|
406
435
|
} from "@vibeorm/schema";
|
|
407
436
|
import { getSqlDialect } from "@vibeorm/sql";
|
|
@@ -442,10 +471,10 @@ function fieldTypeKey(params) {
|
|
|
442
471
|
}
|
|
443
472
|
|
|
444
473
|
// packages/dsl/src/relation-plan.ts
|
|
445
|
-
import { VibeError as
|
|
474
|
+
import { VibeError as VibeError4, isSqlObjectIdentity, predicateKey } from "@vibeorm/schema";
|
|
446
475
|
|
|
447
476
|
// packages/dsl/src/model.ts
|
|
448
|
-
import { VibeError as
|
|
477
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
449
478
|
|
|
450
479
|
// packages/dsl/src/naming.ts
|
|
451
480
|
function capitalize(params) {
|
|
@@ -487,7 +516,7 @@ function createIndexBuilder() {
|
|
|
487
516
|
const partial = (index) => {
|
|
488
517
|
const where = (predicate) => {
|
|
489
518
|
if (index.__where !== undefined) {
|
|
490
|
-
throw new
|
|
519
|
+
throw new VibeError3({
|
|
491
520
|
code: "VIBE_SCHEMA_INVALID",
|
|
492
521
|
message: `Index on [${index.__columns.join(", ")}] already carries a raw where: predicate \u2014 an index has ONE predicate, so use either the raw string or .where(), not both`,
|
|
493
522
|
meta: { rule: "predicate-duplicate-where", columns: [...index.__columns] }
|
|
@@ -525,7 +554,7 @@ function createIndexBuilder() {
|
|
|
525
554
|
const [first, second] = args;
|
|
526
555
|
if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
|
|
527
556
|
if ("ops" in second && second.ops !== undefined) {
|
|
528
|
-
throw new
|
|
557
|
+
throw new VibeError3({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses" } });
|
|
529
558
|
}
|
|
530
559
|
return partial({
|
|
531
560
|
__type: "unique",
|
|
@@ -609,6 +638,10 @@ function evaluateIndexes(params) {
|
|
|
609
638
|
}
|
|
610
639
|
|
|
611
640
|
// packages/dsl/src/relation-plan.ts
|
|
641
|
+
function relationPairKey(params) {
|
|
642
|
+
const { relation, reverse } = params;
|
|
643
|
+
return JSON.stringify(reverse ? [relation.__toModel, relation.__toColumns, relation.__fromModel, relation.__fromColumns, relation.__alias ?? ""] : [relation.__fromModel, relation.__fromColumns, relation.__toModel, relation.__toColumns, relation.__alias ?? ""]);
|
|
644
|
+
}
|
|
612
645
|
function detectOneToOnePairs(params) {
|
|
613
646
|
const { relations, models } = params;
|
|
614
647
|
const mergeMap = new Map;
|
|
@@ -616,13 +649,22 @@ function detectOneToOnePairs(params) {
|
|
|
616
649
|
for (const [modelName, modelRelations] of Object.entries(relations)) {
|
|
617
650
|
for (const [relName, rel] of Object.entries(modelRelations)) {
|
|
618
651
|
if (rel.__ownsForeignKey !== undefined && (typeof rel.__ownsForeignKey !== "boolean" || rel.__relationType !== "one" || rel.__ownsForeignKey && rel.__fromModel !== modelName)) {
|
|
619
|
-
throw new
|
|
652
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
|
|
620
653
|
}
|
|
621
654
|
if (rel.__relationType === "one") {
|
|
622
655
|
allOnes.push({ modelName, relName, rel });
|
|
623
656
|
}
|
|
624
657
|
}
|
|
625
658
|
}
|
|
659
|
+
const candidatesByKey = new Map;
|
|
660
|
+
for (const [position, entry] of allOnes.entries()) {
|
|
661
|
+
const key = relationPairKey({ relation: entry.rel });
|
|
662
|
+
const bucket = candidatesByKey.get(key);
|
|
663
|
+
if (bucket === undefined)
|
|
664
|
+
candidatesByKey.set(key, { positions: [position], start: 0 });
|
|
665
|
+
else
|
|
666
|
+
bucket.positions.push(position);
|
|
667
|
+
}
|
|
626
668
|
const paired = new Set;
|
|
627
669
|
for (let i = 0;i < allOnes.length; i++) {
|
|
628
670
|
const a = allOnes[i];
|
|
@@ -631,13 +673,17 @@ function detectOneToOnePairs(params) {
|
|
|
631
673
|
const keyA = `${a.modelName}:${a.relName}`;
|
|
632
674
|
if (paired.has(keyA))
|
|
633
675
|
continue;
|
|
634
|
-
|
|
676
|
+
const bucket = candidatesByKey.get(relationPairKey({ relation: a.rel, reverse: true }));
|
|
677
|
+
if (bucket === undefined)
|
|
678
|
+
continue;
|
|
679
|
+
for (let candidate = bucket.start;candidate < bucket.positions.length; candidate++) {
|
|
680
|
+
const j = bucket.positions[candidate];
|
|
635
681
|
const b = allOnes[j];
|
|
636
|
-
if (b === undefined)
|
|
637
|
-
continue;
|
|
638
682
|
const keyB = `${b.modelName}:${b.relName}`;
|
|
639
|
-
if (paired.has(keyB))
|
|
683
|
+
if (j <= i || paired.has(keyB)) {
|
|
684
|
+
bucket.start = candidate + 1;
|
|
640
685
|
continue;
|
|
686
|
+
}
|
|
641
687
|
const sameColumnPair = a.rel.__fromModel === b.rel.__toModel && sameColumnTuple({ left: a.rel.__fromColumns, right: b.rel.__toColumns }) && a.rel.__toModel === b.rel.__fromModel && sameColumnTuple({ left: a.rel.__toColumns, right: b.rel.__fromColumns });
|
|
642
688
|
if (!sameColumnPair || (a.rel.__alias ?? "") !== (b.rel.__alias ?? ""))
|
|
643
689
|
continue;
|
|
@@ -665,6 +711,7 @@ function detectOneToOnePairs(params) {
|
|
|
665
711
|
});
|
|
666
712
|
paired.add(fkKey);
|
|
667
713
|
paired.add(backKey);
|
|
714
|
+
bucket.start = candidate + 1;
|
|
668
715
|
break;
|
|
669
716
|
}
|
|
670
717
|
}
|
|
@@ -677,7 +724,7 @@ function sameColumnTuple(params) {
|
|
|
677
724
|
function resolveFkOwner(params) {
|
|
678
725
|
const { a, b, models } = params;
|
|
679
726
|
const ownershipError = () => {
|
|
680
|
-
throw new
|
|
727
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `Ambiguous foreign-key ownership between ${a.modelName}.${a.relName} and ${b.modelName}.${b.relName}; set ownsForeignKey: true on the foreign-key side`, meta: { rule: "ambiguous-foreign-key-owner" } });
|
|
681
728
|
};
|
|
682
729
|
const aOwns = a.rel.__ownsForeignKey;
|
|
683
730
|
const bOwns = b.rel.__ownsForeignKey;
|
|
@@ -765,7 +812,7 @@ function planExplicitRelations(params) {
|
|
|
765
812
|
return planned;
|
|
766
813
|
}
|
|
767
814
|
function planMissingReverseRelations(params) {
|
|
768
|
-
const { modelName, relations, mergeMap, existing } = params;
|
|
815
|
+
const { modelName, relations, incoming, mergeMap, existing } = params;
|
|
769
816
|
const planned = [];
|
|
770
817
|
const existingLists = new Set;
|
|
771
818
|
const existingNames = new Set;
|
|
@@ -775,44 +822,40 @@ function planMissingReverseRelations(params) {
|
|
|
775
822
|
existingLists.add(`${relation.targetModel}:${relation.relationName ?? ""}`);
|
|
776
823
|
}
|
|
777
824
|
}
|
|
778
|
-
for (const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
825
|
+
for (const { modelName: sourceModelName, relName, rel } of incoming) {
|
|
826
|
+
if (mergeMap.has(`${sourceModelName}:${relName}`))
|
|
827
|
+
continue;
|
|
828
|
+
const reverseKey = `${sourceModelName}:${rel.__alias ?? ""}`;
|
|
829
|
+
if (existingLists.has(reverseKey))
|
|
830
|
+
continue;
|
|
831
|
+
if (sourceModelName === modelName) {
|
|
832
|
+
const hasExplicitReverse = Object.values(relations[sourceModelName] ?? {}).some((other) => other.__relationType === "many" && other.__targetModel === modelName && other.__alias === rel.__alias);
|
|
833
|
+
if (hasExplicitReverse)
|
|
786
834
|
continue;
|
|
787
|
-
if (sourceModelName === modelName) {
|
|
788
|
-
const hasExplicitReverse = Object.values(sourceRelations).some((other) => other.__relationType === "many" && other.__targetModel === modelName && other.__alias === rel.__alias);
|
|
789
|
-
if (hasExplicitReverse)
|
|
790
|
-
continue;
|
|
791
|
-
}
|
|
792
|
-
const baseName = rel.__alias ?? sourceModelName;
|
|
793
|
-
let name = baseName;
|
|
794
|
-
let counter = 1;
|
|
795
|
-
while (existingNames.has(name)) {
|
|
796
|
-
counter++;
|
|
797
|
-
name = `${baseName}${counter}`;
|
|
798
|
-
}
|
|
799
|
-
planned.push({
|
|
800
|
-
model: modelName,
|
|
801
|
-
name,
|
|
802
|
-
targetModel: sourceModelName,
|
|
803
|
-
kind: "list",
|
|
804
|
-
isList: true,
|
|
805
|
-
isOptional: false,
|
|
806
|
-
relationName: rel.__alias
|
|
807
|
-
});
|
|
808
|
-
existingLists.add(reverseKey);
|
|
809
|
-
existingNames.add(name);
|
|
810
835
|
}
|
|
836
|
+
const baseName = rel.__alias ?? sourceModelName;
|
|
837
|
+
let name = baseName;
|
|
838
|
+
let counter = 1;
|
|
839
|
+
while (existingNames.has(name)) {
|
|
840
|
+
counter++;
|
|
841
|
+
name = `${baseName}${counter}`;
|
|
842
|
+
}
|
|
843
|
+
planned.push({
|
|
844
|
+
model: modelName,
|
|
845
|
+
name,
|
|
846
|
+
targetModel: sourceModelName,
|
|
847
|
+
kind: "list",
|
|
848
|
+
isList: true,
|
|
849
|
+
isOptional: false,
|
|
850
|
+
relationName: rel.__alias
|
|
851
|
+
});
|
|
852
|
+
existingLists.add(reverseKey);
|
|
853
|
+
existingNames.add(name);
|
|
811
854
|
}
|
|
812
855
|
return planned;
|
|
813
856
|
}
|
|
814
857
|
function planChildRelations(params) {
|
|
815
|
-
const { modelName, model,
|
|
858
|
+
const { modelName, model, parents } = params;
|
|
816
859
|
const planned = [];
|
|
817
860
|
for (const [childKey, childModel] of model.__children) {
|
|
818
861
|
planned.push({
|
|
@@ -824,22 +867,18 @@ function planChildRelations(params) {
|
|
|
824
867
|
isOptional: false
|
|
825
868
|
});
|
|
826
869
|
}
|
|
827
|
-
for (const [parentModelName, parentModel] of
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
fields: [toFkColumnName({ modelName: parentModelName })],
|
|
840
|
-
references: [parentPk]
|
|
841
|
-
});
|
|
842
|
-
}
|
|
870
|
+
for (const [parentModelName, parentModel] of parents) {
|
|
871
|
+
const parentPk = parentModel.__primaryKey?.[0] ?? Array.from(parentModel.__columns.entries()).find(([, column]) => column.__columnType === "id")?.[0] ?? "id";
|
|
872
|
+
planned.push({
|
|
873
|
+
model: modelName,
|
|
874
|
+
name: parentModelName,
|
|
875
|
+
targetModel: parentModelName,
|
|
876
|
+
kind: "fkOwner",
|
|
877
|
+
isList: false,
|
|
878
|
+
isOptional: false,
|
|
879
|
+
fields: [toFkColumnName({ modelName: parentModelName })],
|
|
880
|
+
references: [parentPk]
|
|
881
|
+
});
|
|
843
882
|
}
|
|
844
883
|
return planned;
|
|
845
884
|
}
|
|
@@ -856,14 +895,14 @@ function planIndexes(params) {
|
|
|
856
895
|
for (const index of evaluateIndexes({ model })) {
|
|
857
896
|
if (index.__ops !== undefined) {
|
|
858
897
|
if (index.__ops === null || typeof index.__ops !== "object" || Array.isArray(index.__ops))
|
|
859
|
-
throw new
|
|
898
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Index ops must be a field-keyed qualified-name map", meta: { rule: "index-operator-class-map", model: model.__name } });
|
|
860
899
|
if (index.__type === "unique")
|
|
861
|
-
throw new
|
|
900
|
+
throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Custom unique operator classes can change client selector equality", meta: { feature: "uniqueIndexOperatorClasses", model: model.__name } });
|
|
862
901
|
for (const [field, operatorClass] of Object.entries(index.__ops)) {
|
|
863
902
|
if (!index.__columns.includes(field))
|
|
864
|
-
throw new
|
|
903
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Operator class names a field outside the index", meta: { rule: "index-operator-class-field", model: model.__name, field } });
|
|
865
904
|
if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass }))
|
|
866
|
-
throw new
|
|
905
|
+
throw new VibeError4({ 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 } });
|
|
867
906
|
}
|
|
868
907
|
}
|
|
869
908
|
const operatorClasses = index.__columns.map((field) => index.__ops?.[field]);
|
|
@@ -914,7 +953,7 @@ function assertForeignKeyIntegrity(params) {
|
|
|
914
953
|
const references = relation.references ?? [];
|
|
915
954
|
const at = `${relation.model}.${relation.name}`;
|
|
916
955
|
const refuse = (message, rule, meta = {}) => {
|
|
917
|
-
throw new
|
|
956
|
+
throw new VibeError4({
|
|
918
957
|
code: "VIBE_SCHEMA_INVALID",
|
|
919
958
|
message: `Relation ${at}: ${message}`,
|
|
920
959
|
meta: { rule, model: relation.model, relation: relation.name, ...meta }
|
|
@@ -963,6 +1002,33 @@ function assertForeignKeyIntegrity(params) {
|
|
|
963
1002
|
refuse(`references ${relation.targetModel}.(${references.join(", ")}), which is not a unique key there \u2014 declare those columns as the primary key, a unique column, or a total @@unique index`, "relation-reference-not-unique", { references, candidates: keys.map((key) => [...key]) });
|
|
964
1003
|
}
|
|
965
1004
|
}
|
|
1005
|
+
function relationLookups(params) {
|
|
1006
|
+
const incoming = new Map;
|
|
1007
|
+
for (const [modelName, relations] of Object.entries(params.schema.__relations)) {
|
|
1008
|
+
for (const [relName, rel] of Object.entries(relations)) {
|
|
1009
|
+
if (rel.__relationType !== "one")
|
|
1010
|
+
continue;
|
|
1011
|
+
const target = rel.__targetModel;
|
|
1012
|
+
const entries = incoming.get(target);
|
|
1013
|
+
const entry = { modelName, relName, rel };
|
|
1014
|
+
if (entries === undefined)
|
|
1015
|
+
incoming.set(target, [entry]);
|
|
1016
|
+
else
|
|
1017
|
+
entries.push(entry);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const parents = new Map;
|
|
1021
|
+
for (const [name, model] of params.schema.__models) {
|
|
1022
|
+
for (const child of model.__children.values()) {
|
|
1023
|
+
const entries = parents.get(child.__name);
|
|
1024
|
+
if (entries === undefined)
|
|
1025
|
+
parents.set(child.__name, [[name, model]]);
|
|
1026
|
+
else
|
|
1027
|
+
entries.push([name, model]);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return { incoming, parents };
|
|
1031
|
+
}
|
|
966
1032
|
function planSchema(params) {
|
|
967
1033
|
const { schema } = params;
|
|
968
1034
|
const mergeMap = detectOneToOnePairs({
|
|
@@ -971,7 +1037,13 @@ function planSchema(params) {
|
|
|
971
1037
|
});
|
|
972
1038
|
const relations = new Map;
|
|
973
1039
|
const indexes = new Map;
|
|
1040
|
+
let lookups = relationLookups({ schema });
|
|
1041
|
+
let refreshLookups = false;
|
|
974
1042
|
for (const [modelName, model] of schema.__models) {
|
|
1043
|
+
if (refreshLookups) {
|
|
1044
|
+
lookups = relationLookups({ schema });
|
|
1045
|
+
refreshLookups = false;
|
|
1046
|
+
}
|
|
975
1047
|
const explicit = planExplicitRelations({
|
|
976
1048
|
modelName,
|
|
977
1049
|
model,
|
|
@@ -981,16 +1053,22 @@ function planSchema(params) {
|
|
|
981
1053
|
const reverses = planMissingReverseRelations({
|
|
982
1054
|
modelName,
|
|
983
1055
|
relations: schema.__relations,
|
|
1056
|
+
incoming: lookups.incoming.get(modelName) ?? [],
|
|
984
1057
|
mergeMap,
|
|
985
1058
|
existing: explicit
|
|
986
1059
|
});
|
|
987
|
-
const children = planChildRelations({ modelName, model,
|
|
1060
|
+
const children = planChildRelations({ modelName, model, parents: lookups.parents.get(modelName) ?? [] });
|
|
988
1061
|
const planned = [...explicit, ...reverses, ...children];
|
|
989
1062
|
for (const relation of planned) {
|
|
990
|
-
if (relation.kind === "fkOwner")
|
|
1063
|
+
if (relation.kind === "fkOwner") {
|
|
1064
|
+
if (schema.__models.get(relation.targetModel)?.__indexes !== undefined)
|
|
1065
|
+
refreshLookups = true;
|
|
991
1066
|
assertForeignKeyIntegrity({ relation, models: schema.__models });
|
|
1067
|
+
}
|
|
992
1068
|
}
|
|
993
1069
|
relations.set(modelName, planned);
|
|
1070
|
+
if (model.__indexes !== undefined)
|
|
1071
|
+
refreshLookups = true;
|
|
994
1072
|
indexes.set(modelName, planIndexes({ model }));
|
|
995
1073
|
}
|
|
996
1074
|
return { relations, indexes };
|
|
@@ -1037,16 +1115,16 @@ function fieldDefault(params) {
|
|
|
1037
1115
|
const { column } = params;
|
|
1038
1116
|
if (column.__columnType === "id") {
|
|
1039
1117
|
if (column.__config.default !== undefined || column.__config.dbgenerated !== undefined || column.__config.autoincrement !== undefined)
|
|
1040
|
-
throw new
|
|
1118
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
|
|
1041
1119
|
return idDefault({ column });
|
|
1042
1120
|
}
|
|
1043
1121
|
const { dbgenerated, default: value, autoincrement } = column.__config;
|
|
1044
1122
|
if ([dbgenerated !== undefined, value !== undefined, autoincrement === true].filter(Boolean).length > 1) {
|
|
1045
|
-
throw new
|
|
1123
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
|
|
1046
1124
|
}
|
|
1047
1125
|
if (autoincrement === true) {
|
|
1048
1126
|
if (column.__columnType !== "int" && column.__columnType !== "bigint" || column.__config.array === true || column.__config.optional === true) {
|
|
1049
|
-
throw new
|
|
1127
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
|
|
1050
1128
|
}
|
|
1051
1129
|
return { kind: "autoincrement" };
|
|
1052
1130
|
}
|
|
@@ -1056,7 +1134,7 @@ function fieldDefault(params) {
|
|
|
1056
1134
|
return;
|
|
1057
1135
|
if (column.__config.array === true) {
|
|
1058
1136
|
if (!Array.isArray(value))
|
|
1059
|
-
throw new
|
|
1137
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
|
|
1060
1138
|
return { kind: "list", values: scalarListDefault({ type: fieldType({ column }), values: column.__columnType === "json" ? value.map((entry) => typeof entry === "string" ? entry : serializeJsonDefault({ value: entry })) : value }) };
|
|
1061
1139
|
}
|
|
1062
1140
|
if (column.__columnType === "json" && typeof value !== "string") {
|
|
@@ -1071,20 +1149,20 @@ function fieldDefault(params) {
|
|
|
1071
1149
|
return { kind: "enumValue", value };
|
|
1072
1150
|
}
|
|
1073
1151
|
if (typeof value === "number" && (!Number.isFinite(value) || (column.__columnType === "int" || column.__columnType === "bigint") && !Number.isSafeInteger(value))) {
|
|
1074
|
-
throw new
|
|
1152
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
|
|
1075
1153
|
}
|
|
1076
1154
|
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
1077
1155
|
return { kind: "literal", value };
|
|
1078
1156
|
}
|
|
1079
1157
|
if (typeof value === "bigint")
|
|
1080
1158
|
return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
|
|
1081
|
-
throw new
|
|
1159
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
|
|
1082
1160
|
}
|
|
1083
1161
|
function datetimeNativeType(params) {
|
|
1084
1162
|
const { tz, precision } = params.config;
|
|
1085
1163
|
if (precision !== undefined && (tz !== true || !Number.isInteger(precision) || precision < 0 || precision > 6)) {
|
|
1086
1164
|
const at = params.column === undefined ? "A datetime column" : `Column "${params.column}"`;
|
|
1087
|
-
throw new
|
|
1165
|
+
throw new VibeError5({
|
|
1088
1166
|
code: "VIBE_SCHEMA_INVALID",
|
|
1089
1167
|
message: `${at}: a datetime precision must be an integer from 0 to 6 on a tz() column, as in dsl.datetime().tz({ precision: 3 })`,
|
|
1090
1168
|
meta: { issues: [{ rule: "datetime-precision", column: params.column, precision, tz: tz === true }] }
|
|
@@ -1101,7 +1179,7 @@ function nativeType(params) {
|
|
|
1101
1179
|
if (maxLength !== undefined || text !== undefined) {
|
|
1102
1180
|
const invalid = column.__enumStorage !== "string" || maxLength !== undefined && (typeof maxLength !== "number" || !Number.isSafeInteger(maxLength) || maxLength <= 0) || text !== undefined && typeof text !== "boolean" || text === true && maxLength !== undefined;
|
|
1103
1181
|
if (invalid) {
|
|
1104
|
-
throw new
|
|
1182
|
+
throw new VibeError5({
|
|
1105
1183
|
code: "VIBE_SCHEMA_INVALID",
|
|
1106
1184
|
message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
|
|
1107
1185
|
meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
|
|
@@ -1138,6 +1216,7 @@ function buildField(params) {
|
|
|
1138
1216
|
const dbName = typeof config.map === "string" ? config.map : undefined;
|
|
1139
1217
|
const documentation = typeof config.comment === "string" ? config.comment : undefined;
|
|
1140
1218
|
const collation = typeof config.collation === "string" ? config.collation : undefined;
|
|
1219
|
+
const metadata = normalizeJsonSchemaRef({ documentation, reference: config.jsonSchema });
|
|
1141
1220
|
const defaultValue = fieldDefault({ column });
|
|
1142
1221
|
const native = extensionNativeType ?? nativeType({ column });
|
|
1143
1222
|
return {
|
|
@@ -1153,7 +1232,7 @@ function buildField(params) {
|
|
|
1153
1232
|
...defaultValue === undefined ? {} : { default: defaultValue },
|
|
1154
1233
|
...native === undefined ? {} : { nativeType: native },
|
|
1155
1234
|
...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
|
|
1156
|
-
...
|
|
1235
|
+
...metadata
|
|
1157
1236
|
};
|
|
1158
1237
|
}
|
|
1159
1238
|
function buildRelation(params) {
|
|
@@ -1341,7 +1420,7 @@ function toIR(params) {
|
|
|
1341
1420
|
}
|
|
1342
1421
|
}
|
|
1343
1422
|
if (issues.length > 0) {
|
|
1344
|
-
throw new
|
|
1423
|
+
throw new VibeError5({
|
|
1345
1424
|
code: "VIBE_SCHEMA_INVALID",
|
|
1346
1425
|
message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
|
|
1347
1426
|
meta: { issues }
|
|
@@ -1398,7 +1477,7 @@ function prismaType(params) {
|
|
|
1398
1477
|
const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
|
|
1399
1478
|
return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
|
|
1400
1479
|
}
|
|
1401
|
-
return `Unsupported(${
|
|
1480
|
+
return `Unsupported(${encodePrismaString2({ value: String(column.__config.dbType) })})${list}`;
|
|
1402
1481
|
}
|
|
1403
1482
|
}
|
|
1404
1483
|
}
|
|
@@ -1413,11 +1492,11 @@ function defaultAttribute(params) {
|
|
|
1413
1492
|
}
|
|
1414
1493
|
const numericColumn = column.__columnType === "int" || column.__columnType === "bigint" || column.__columnType === "float" || column.__columnType === "decimal";
|
|
1415
1494
|
if (value.kind === "literal")
|
|
1416
|
-
return `@default(${typeof value.value === "string" && !(numericColumn && /^-?\d+(\.\d+)?$/.test(value.value)) ?
|
|
1495
|
+
return `@default(${typeof value.value === "string" && !(numericColumn && /^-?\d+(\.\d+)?$/.test(value.value)) ? encodePrismaString2({ value: value.value }) : String(value.value)})`;
|
|
1417
1496
|
if (value.kind === "enumValue")
|
|
1418
1497
|
return `@default(${value.value})`;
|
|
1419
1498
|
if (value.kind === "dbgenerated")
|
|
1420
|
-
return `@default(dbgenerated(${
|
|
1499
|
+
return `@default(dbgenerated(${encodePrismaString2({ value: value.sql })}))`;
|
|
1421
1500
|
return `@default(${value.kind}())`;
|
|
1422
1501
|
}
|
|
1423
1502
|
function nativeTypeAttribute(params) {
|
|
@@ -1462,39 +1541,41 @@ function formatColumn(params) {
|
|
|
1462
1541
|
parts.push("@unique");
|
|
1463
1542
|
}
|
|
1464
1543
|
if (typeof column.__config.map === "string") {
|
|
1465
|
-
parts.push(`@map(${
|
|
1544
|
+
parts.push(`@map(${encodePrismaString2({ value: column.__config.map })})`);
|
|
1466
1545
|
}
|
|
1467
1546
|
const nativeType = nativeTypeAttribute({ column });
|
|
1468
1547
|
if (nativeType !== undefined)
|
|
1469
1548
|
parts.push(nativeType);
|
|
1470
1549
|
if (typeof column.__config.collation === "string") {
|
|
1471
|
-
parts.push(`@collation(${
|
|
1550
|
+
parts.push(`@collation(${encodePrismaString2({ value: column.__config.collation })})`);
|
|
1472
1551
|
}
|
|
1473
1552
|
const columnLine = parts.join(" ");
|
|
1474
|
-
const
|
|
1475
|
-
if (
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1553
|
+
const metadata = normalizeJsonSchemaRef2({ documentation: typeof column.__config.comment === "string" ? column.__config.comment : undefined, reference: column.__config.jsonSchema });
|
|
1554
|
+
if (metadata.jsonSchema !== undefined && column.__columnType !== "json")
|
|
1555
|
+
throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "JSON schemas are only valid on Json fields", meta: { issues: [{ rule: "json-schema-non-json", field: name }] } });
|
|
1556
|
+
const comments = [
|
|
1557
|
+
...metadata.documentation === undefined ? [] : metadata.documentation.split(`
|
|
1558
|
+
`),
|
|
1559
|
+
...metadata.jsonSchema === undefined ? [] : [`@schema(${metadata.jsonSchema.name} from ${encodePrismaString2({ value: metadata.jsonSchema.from })})`]
|
|
1560
|
+
];
|
|
1561
|
+
return [...comments.map((line) => ` /// ${line}`), columnLine].join(`
|
|
1479
1562
|
`);
|
|
1480
|
-
}
|
|
1481
|
-
return columnLine;
|
|
1482
1563
|
}
|
|
1483
1564
|
function formatRelation(params) {
|
|
1484
1565
|
const { relation } = params;
|
|
1485
1566
|
const { name, targetModel, relationName } = relation;
|
|
1486
1567
|
if (relation.kind === "list") {
|
|
1487
|
-
const attribute = relationName === undefined ? "" : ` @relation(${
|
|
1568
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString2({ value: relationName })})`;
|
|
1488
1569
|
return ` ${name} ${targetModel}[]${attribute}`;
|
|
1489
1570
|
}
|
|
1490
1571
|
const optional = relation.isOptional ? "?" : "";
|
|
1491
1572
|
if (relation.kind === "backOne") {
|
|
1492
|
-
const attribute = relationName === undefined ? "" : ` @relation(${
|
|
1573
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString2({ value: relationName })})`;
|
|
1493
1574
|
return ` ${name} ${targetModel}${optional}${attribute}`;
|
|
1494
1575
|
}
|
|
1495
1576
|
const args = [];
|
|
1496
1577
|
if (relationName !== undefined)
|
|
1497
|
-
args.push(
|
|
1578
|
+
args.push(encodePrismaString2({ value: relationName }));
|
|
1498
1579
|
if (relation.fields === undefined || relation.references === undefined) {
|
|
1499
1580
|
throw internalError({
|
|
1500
1581
|
message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
|
|
@@ -1508,14 +1589,14 @@ function formatRelation(params) {
|
|
|
1508
1589
|
if (relation.onUpdate !== undefined)
|
|
1509
1590
|
args.push(`onUpdate: ${relation.onUpdate}`);
|
|
1510
1591
|
if (relation.constraintName !== undefined) {
|
|
1511
|
-
args.push(`map: ${
|
|
1592
|
+
args.push(`map: ${encodePrismaString2({ value: relation.constraintName })}`);
|
|
1512
1593
|
}
|
|
1513
1594
|
return ` ${name} ${targetModel}${optional} @relation(${args.join(", ")})`;
|
|
1514
1595
|
}
|
|
1515
1596
|
function formatIndex(params) {
|
|
1516
1597
|
const { index } = params;
|
|
1517
1598
|
if (index.ops !== undefined && Object.values(index.ops).some((operatorClass) => operatorClass !== undefined))
|
|
1518
|
-
throw new
|
|
1599
|
+
throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Prisma output cannot preserve qualified index operator classes; keep the DSL source", meta: { feature: "prismaIndexOperatorClasses", target: "prisma" } });
|
|
1519
1600
|
const columns = index.columns.join(", ");
|
|
1520
1601
|
const predicate = compileIndexPredicate({
|
|
1521
1602
|
where: index.where,
|
|
@@ -1525,10 +1606,10 @@ function formatIndex(params) {
|
|
|
1525
1606
|
dialect: params.dialect,
|
|
1526
1607
|
enums: params.enums
|
|
1527
1608
|
});
|
|
1528
|
-
const whereArg = predicate === undefined ? "" : `, where: ${
|
|
1529
|
-
const mapArg = index.map === undefined ? "" : `, map: ${
|
|
1609
|
+
const whereArg = predicate === undefined ? "" : `, where: ${encodePrismaString2({ value: predicate.where })}`;
|
|
1610
|
+
const mapArg = index.map === undefined ? "" : `, map: ${encodePrismaString2({ value: index.map })}`;
|
|
1530
1611
|
const nndArg = index.nullsNotDistinct === true ? ", nullsNotDistinct: true" : "";
|
|
1531
|
-
const nameArg = index.clientName === undefined ? "" : `, name: ${
|
|
1612
|
+
const nameArg = index.clientName === undefined ? "" : `, name: ${encodePrismaString2({ value: index.clientName })}`;
|
|
1532
1613
|
if (index.isUnique) {
|
|
1533
1614
|
return ` @@unique([${columns}]${nameArg}${mapArg}${nndArg}${whereArg})`;
|
|
1534
1615
|
}
|
|
@@ -1538,7 +1619,7 @@ function formatIndex(params) {
|
|
|
1538
1619
|
function formatExtensionValue(params) {
|
|
1539
1620
|
const { value } = params;
|
|
1540
1621
|
if (typeof value === "string")
|
|
1541
|
-
return
|
|
1622
|
+
return encodePrismaString2({ value });
|
|
1542
1623
|
if (Array.isArray(value)) {
|
|
1543
1624
|
return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
|
|
1544
1625
|
}
|
|
@@ -1572,7 +1653,7 @@ function formatModel(params) {
|
|
|
1572
1653
|
dialect: params.dialect,
|
|
1573
1654
|
enums: params.enums
|
|
1574
1655
|
})) {
|
|
1575
|
-
lines.push(` @@check(${
|
|
1656
|
+
lines.push(` @@check(${encodePrismaString2({ value: check.expression })}, name: ${encodePrismaString2({ value: check.name })})`);
|
|
1576
1657
|
}
|
|
1577
1658
|
for (const use of model.__extensions ?? []) {
|
|
1578
1659
|
const args = [];
|
|
@@ -1588,11 +1669,11 @@ function formatModel(params) {
|
|
|
1588
1669
|
}
|
|
1589
1670
|
if (model.__policy !== undefined) {
|
|
1590
1671
|
const { field, context } = model.__policy;
|
|
1591
|
-
const contextArg = context === undefined || context === field ? "" : `, context: ${
|
|
1672
|
+
const contextArg = context === undefined || context === field ? "" : `, context: ${encodePrismaString2({ value: context })}`;
|
|
1592
1673
|
lines.push(` @@policy([${field}]${contextArg})`);
|
|
1593
1674
|
}
|
|
1594
1675
|
if (typeof model.__map === "string") {
|
|
1595
|
-
lines.push(` @@map(${
|
|
1676
|
+
lines.push(` @@map(${encodePrismaString2({ value: model.__map })})`);
|
|
1596
1677
|
}
|
|
1597
1678
|
lines.push("}");
|
|
1598
1679
|
return lines.join(`
|
|
@@ -1607,18 +1688,18 @@ function generatePrisma(schema, config = {}) {
|
|
|
1607
1688
|
for (const model of schema.__models.values()) {
|
|
1608
1689
|
const exclusions = compileExclusions({ exclusions: model.__exclusions, model: { name: model.__name, fields: Array.from(model.__columns, ([name, column]) => buildField({ name, column })) }, dialect: getSqlDialect2({ dialect: providerToDialect2({ provider: config.provider ?? "postgresql" }) }) });
|
|
1609
1690
|
if (exclusions.length > 0)
|
|
1610
|
-
throw new
|
|
1691
|
+
throw new VibeError6({ code: "VIBE_CONFIG", message: "Prisma output cannot preserve exclusion constraints; keep the DSL source", meta: { feature: "exclusionConstraints", target: "prisma" } });
|
|
1611
1692
|
}
|
|
1612
1693
|
const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
|
|
1613
1694
|
if (stringEnums.length > 0) {
|
|
1614
|
-
throw new
|
|
1695
|
+
throw new VibeError6({
|
|
1615
1696
|
code: "VIBE_CONFIG",
|
|
1616
1697
|
message: `Cannot print to Prisma: enum(s) ${stringEnums.join(", ")} use storage: "string", which .prisma cannot express losslessly. Keep the DSL as the source or switch the enum to database storage.`,
|
|
1617
1698
|
meta: { enums: stringEnums, target: "prisma" }
|
|
1618
1699
|
});
|
|
1619
1700
|
}
|
|
1620
1701
|
if (schema.__rls !== undefined) {
|
|
1621
|
-
throw new
|
|
1702
|
+
throw new VibeError6({
|
|
1622
1703
|
code: "VIBE_CONFIG",
|
|
1623
1704
|
message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
|
|
1624
1705
|
meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
|
|
@@ -1651,7 +1732,7 @@ function generatePrisma(schema, config = {}) {
|
|
|
1651
1732
|
}
|
|
1652
1733
|
|
|
1653
1734
|
// packages/dsl/src/relations.ts
|
|
1654
|
-
import { VibeError as
|
|
1735
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
1655
1736
|
var REF_KIND_BY_COLUMN_TYPE = {
|
|
1656
1737
|
string: "String",
|
|
1657
1738
|
int: "Int",
|
|
@@ -1692,7 +1773,7 @@ function extractRefInfo(params) {
|
|
|
1692
1773
|
}
|
|
1693
1774
|
const members = ref;
|
|
1694
1775
|
if (members.length === 0) {
|
|
1695
|
-
throw new
|
|
1776
|
+
throw new VibeError7({
|
|
1696
1777
|
code: "VIBE_SCHEMA_INVALID",
|
|
1697
1778
|
message: `Relation to ${target}: "${side}" is an empty tuple \u2014 name at least one column`,
|
|
1698
1779
|
meta: { rule: "relation-empty-tuple", side, targetModel: target }
|
|
@@ -1700,14 +1781,14 @@ function extractRefInfo(params) {
|
|
|
1700
1781
|
}
|
|
1701
1782
|
const models = [...new Set(members.map((member) => member.__model))].sort();
|
|
1702
1783
|
if (models.length > 1) {
|
|
1703
|
-
throw new
|
|
1784
|
+
throw new VibeError7({
|
|
1704
1785
|
code: "VIBE_SCHEMA_INVALID",
|
|
1705
1786
|
message: `Relation to ${target}: "${side}" mixes models ${models.join(", ")} \u2014 every member of a composite key must sit on one model`,
|
|
1706
1787
|
meta: { rule: "relation-mixed-models", side, targetModel: target, models }
|
|
1707
1788
|
});
|
|
1708
1789
|
}
|
|
1709
1790
|
if (members.some((member) => member.__type !== "columnRef")) {
|
|
1710
|
-
throw new
|
|
1791
|
+
throw new VibeError7({
|
|
1711
1792
|
code: "VIBE_SCHEMA_INVALID",
|
|
1712
1793
|
message: `Relation to ${target}: "${side}" routes through a junction inside a tuple \u2014 implicit many-to-many is single-key; model the join with an explicit junction model`,
|
|
1713
1794
|
meta: { rule: "relation-through-in-tuple", side, targetModel: target }
|
|
@@ -1721,7 +1802,7 @@ function createRelationFactory(params) {
|
|
|
1721
1802
|
const from = extractRefInfo({ ref: config.from, side: "from", target: targetModelName });
|
|
1722
1803
|
const to = extractRefInfo({ ref: config.to, side: "to", target: targetModelName });
|
|
1723
1804
|
if (from.columns.length !== to.columns.length) {
|
|
1724
|
-
throw new
|
|
1805
|
+
throw new VibeError7({
|
|
1725
1806
|
code: "VIBE_SCHEMA_INVALID",
|
|
1726
1807
|
message: `Relation to ${targetModelName}: "from" names ${from.columns.length} column(s) but "to" names ${to.columns.length} \u2014 a composite key pairs member by member`,
|
|
1727
1808
|
meta: { rule: "relation-tuple-arity", targetModel: targetModelName, from: from.columns, to: to.columns }
|
|
@@ -1819,7 +1900,7 @@ function mergeRelations(...parts) {
|
|
|
1819
1900
|
}
|
|
1820
1901
|
|
|
1821
1902
|
// packages/dsl/src/schema.ts
|
|
1822
|
-
import { VibeError as
|
|
1903
|
+
import { VibeError as VibeError8, validateRlsContextIR } from "@vibeorm/schema";
|
|
1823
1904
|
function sameValues(left, right) {
|
|
1824
1905
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1825
1906
|
}
|
|
@@ -1838,7 +1919,7 @@ function registerEnum(params) {
|
|
|
1838
1919
|
...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
|
|
1839
1920
|
...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
|
|
1840
1921
|
].join("; ");
|
|
1841
|
-
throw new
|
|
1922
|
+
throw new VibeError8({
|
|
1842
1923
|
code: "VIBE_SCHEMA_INVALID",
|
|
1843
1924
|
message: `Enum "${name}" is defined more than once with conflicting definitions (${origin}): ${detail} \u2014 share one enum object across modules, or give the definitions different names`,
|
|
1844
1925
|
meta: {
|
|
@@ -1864,7 +1945,7 @@ function addModelTree(params) {
|
|
|
1864
1945
|
const { model, models, origin } = params;
|
|
1865
1946
|
const existing = models.get(model.__name);
|
|
1866
1947
|
if (existing !== undefined && existing !== model) {
|
|
1867
|
-
throw new
|
|
1948
|
+
throw new VibeError8({
|
|
1868
1949
|
code: "VIBE_SCHEMA_INVALID",
|
|
1869
1950
|
message: `Model "${model.__name}" is defined more than once with different definitions (${origin}) \u2014 ` + `share one model object across modules, or give the definitions different names`,
|
|
1870
1951
|
meta: {
|
|
@@ -1942,7 +2023,7 @@ var RLS_CONTEXT_TYPES = {
|
|
|
1942
2023
|
};
|
|
1943
2024
|
function defineRls(params) {
|
|
1944
2025
|
if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
|
|
1945
|
-
throw new
|
|
2026
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1946
2027
|
}
|
|
1947
2028
|
const entries = [];
|
|
1948
2029
|
const refs = [];
|
|
@@ -1950,7 +2031,7 @@ function defineRls(params) {
|
|
|
1950
2031
|
const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
|
|
1951
2032
|
const column = descriptor?.value;
|
|
1952
2033
|
if (typeof key !== "string" || descriptor?.enumerable !== true || typeof column !== "object" || column === null || !("__columnType" in column) || !("__config" in column) || typeof column.__config !== "object" || column.__config === null || typeof column.__columnType !== "string" || !Object.hasOwn(RLS_CONTEXT_TYPES, column.__columnType) || "array" in column.__config && column.__config.array === true) {
|
|
1953
|
-
throw new
|
|
2034
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1954
2035
|
}
|
|
1955
2036
|
entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
|
|
1956
2037
|
refs.push([key, Object.freeze({ context: key })]);
|
|
@@ -1958,22 +2039,22 @@ function defineRls(params) {
|
|
|
1958
2039
|
const context = Object.freeze(Object.fromEntries(entries));
|
|
1959
2040
|
const issues = validateRlsContextIR({ context });
|
|
1960
2041
|
if (issues.length > 0) {
|
|
1961
|
-
throw new
|
|
2042
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
|
|
1962
2043
|
}
|
|
1963
2044
|
const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
|
|
1964
2045
|
if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
|
|
1965
|
-
throw new
|
|
2046
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1966
2047
|
}
|
|
1967
2048
|
const named = [];
|
|
1968
2049
|
const classified = new Set;
|
|
1969
2050
|
for (const key of Reflect.ownKeys(policies)) {
|
|
1970
2051
|
const descriptor = Object.getOwnPropertyDescriptor(policies, key);
|
|
1971
2052
|
if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
|
|
1972
|
-
throw new
|
|
2053
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1973
2054
|
}
|
|
1974
2055
|
const name = params.models[key]?.__name ?? key;
|
|
1975
2056
|
if (classified.has(name)) {
|
|
1976
|
-
throw new
|
|
2057
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
|
|
1977
2058
|
}
|
|
1978
2059
|
classified.add(name);
|
|
1979
2060
|
named.push([name, descriptor.value]);
|
|
@@ -2285,4 +2366,4 @@ export {
|
|
|
2285
2366
|
withNameDescription
|
|
2286
2367
|
};
|
|
2287
2368
|
|
|
2288
|
-
//# debugId=
|
|
2369
|
+
//# debugId=21B90DDA6F5E192964756E2164756E21
|