@vibeorm/dsl 2.0.0-alpha.8 → 2.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/README.md +23 -3
- package/dist/column.d.ts +8 -3
- package/dist/column.d.ts.map +1 -1
- package/dist/generator/prisma.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +630 -585
- package/dist/index.js.map +10 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/relation-plan.d.ts +1 -0
- package/dist/relation-plan.d.ts.map +1 -1
- package/dist/relations.d.ts.map +1 -1
- package/dist/to-ir.d.ts +6 -2
- package/dist/to-ir.d.ts.map +1 -1
- package/dist/types.d.ts +18 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -137,8 +137,15 @@ class ChainableEnumColumn extends ChainableColumn {
|
|
|
137
137
|
function string(config = {}) {
|
|
138
138
|
return new ChainableStringColumn(config);
|
|
139
139
|
}
|
|
140
|
+
|
|
141
|
+
class ChainableIntegerColumn extends ChainableColumn {
|
|
142
|
+
autoincrement() {
|
|
143
|
+
this.__config.autoincrement = true;
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
140
147
|
function int(config = {}) {
|
|
141
|
-
return new
|
|
148
|
+
return new ChainableIntegerColumn("int", config);
|
|
142
149
|
}
|
|
143
150
|
function float(config = {}) {
|
|
144
151
|
return new ChainableColumn("float", config);
|
|
@@ -173,7 +180,7 @@ function bytes(config = {}) {
|
|
|
173
180
|
return new ChainableColumn("bytes", config);
|
|
174
181
|
}
|
|
175
182
|
function bigint(config = {}) {
|
|
176
|
-
return new
|
|
183
|
+
return new ChainableIntegerColumn("bigint", config);
|
|
177
184
|
}
|
|
178
185
|
function unsupported(dbType) {
|
|
179
186
|
const config = { dbType };
|
|
@@ -217,7 +224,21 @@ function createEnum(config) {
|
|
|
217
224
|
}
|
|
218
225
|
|
|
219
226
|
// packages/dsl/src/generator/prisma.ts
|
|
220
|
-
import { VibeError, encodePrismaString, internalError } from "@vibeorm/schema";
|
|
227
|
+
import { VibeError as VibeError3, encodePrismaString, internalError } from "@vibeorm/schema";
|
|
228
|
+
|
|
229
|
+
// packages/dsl/src/to-ir.ts
|
|
230
|
+
import {
|
|
231
|
+
EMPTY_EXTENSION_REGISTRY,
|
|
232
|
+
VibeError as VibeError2,
|
|
233
|
+
assertValidSchemaIR,
|
|
234
|
+
exactIntegerLiteral,
|
|
235
|
+
scalarListDefault,
|
|
236
|
+
serializeJsonDefault,
|
|
237
|
+
mergeExtensionDeclarations
|
|
238
|
+
} from "@vibeorm/schema";
|
|
239
|
+
|
|
240
|
+
// packages/dsl/src/relation-plan.ts
|
|
241
|
+
import { VibeError } from "@vibeorm/schema";
|
|
221
242
|
|
|
222
243
|
// packages/dsl/src/naming.ts
|
|
223
244
|
function capitalize(params) {
|
|
@@ -256,17 +277,18 @@ function isChildrenDef(value) {
|
|
|
256
277
|
return typeof value === "object" && value !== null && "__type" in value && value.__type === "children";
|
|
257
278
|
}
|
|
258
279
|
function createIndexBuilder() {
|
|
259
|
-
const createIndex = (columns, type, where) => ({
|
|
280
|
+
const createIndex = (columns, type, where, map) => ({
|
|
260
281
|
__type: "index",
|
|
261
282
|
__columns: [...columns],
|
|
262
283
|
__indexType: type,
|
|
284
|
+
...map === undefined ? {} : { __map: map },
|
|
263
285
|
...where === undefined ? {} : { __where: where }
|
|
264
286
|
});
|
|
265
287
|
return {
|
|
266
288
|
on(...args) {
|
|
267
289
|
const [first, second] = args;
|
|
268
290
|
if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
|
|
269
|
-
return createIndex(first, second.type, second.where);
|
|
291
|
+
return createIndex(first, second.type, second.where, second.map);
|
|
270
292
|
}
|
|
271
293
|
return createIndex(args);
|
|
272
294
|
},
|
|
@@ -279,6 +301,7 @@ function createIndexBuilder() {
|
|
|
279
301
|
return {
|
|
280
302
|
__type: "unique",
|
|
281
303
|
__columns: [...first],
|
|
304
|
+
...second.map === undefined ? {} : { __map: second.map },
|
|
282
305
|
...second.where === undefined ? {} : { __where: second.where },
|
|
283
306
|
...second.nullsNotDistinct === true ? { __nullsNotDistinct: true } : {},
|
|
284
307
|
...second.name === undefined ? {} : { __clientName: second.name }
|
|
@@ -362,6 +385,9 @@ function detectOneToOnePairs(params) {
|
|
|
362
385
|
const allOnes = [];
|
|
363
386
|
for (const [modelName, modelRelations] of Object.entries(relations)) {
|
|
364
387
|
for (const [relName, rel] of Object.entries(modelRelations)) {
|
|
388
|
+
if (rel.__ownsForeignKey !== undefined && (typeof rel.__ownsForeignKey !== "boolean" || rel.__relationType !== "one" || rel.__ownsForeignKey && rel.__fromModel !== modelName)) {
|
|
389
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: `Invalid foreign-key ownership declaration on ${modelName}.${relName}` });
|
|
390
|
+
}
|
|
365
391
|
if (rel.__relationType === "one") {
|
|
366
392
|
allOnes.push({ modelName, relName, rel });
|
|
367
393
|
}
|
|
@@ -383,7 +409,7 @@ function detectOneToOnePairs(params) {
|
|
|
383
409
|
if (paired.has(keyB))
|
|
384
410
|
continue;
|
|
385
411
|
const sameColumnPair = a.rel.__fromModel === b.rel.__toModel && a.rel.__fromColumn === b.rel.__toColumn && a.rel.__toModel === b.rel.__fromModel && a.rel.__toColumn === b.rel.__fromColumn;
|
|
386
|
-
if (!sameColumnPair)
|
|
412
|
+
if (!sameColumnPair || (a.rel.__alias ?? "") !== (b.rel.__alias ?? ""))
|
|
387
413
|
continue;
|
|
388
414
|
const { fkOwner, backRelation } = resolveFkOwner({ a, b, models });
|
|
389
415
|
const onDelete = fkOwner.rel.__onDelete ?? backRelation.rel.__onDelete;
|
|
@@ -413,21 +439,35 @@ function detectOneToOnePairs(params) {
|
|
|
413
439
|
}
|
|
414
440
|
function resolveFkOwner(params) {
|
|
415
441
|
const { a, b, models } = params;
|
|
416
|
-
const
|
|
417
|
-
|
|
442
|
+
const ownershipError = () => {
|
|
443
|
+
throw new VibeError({ 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" } });
|
|
444
|
+
};
|
|
445
|
+
const aOwns = a.rel.__ownsForeignKey;
|
|
446
|
+
const bOwns = b.rel.__ownsForeignKey;
|
|
447
|
+
if (aOwns !== undefined || bOwns !== undefined) {
|
|
448
|
+
if (aOwns === bOwns)
|
|
449
|
+
return ownershipError();
|
|
450
|
+
return aOwns === true || bOwns === false ? { fkOwner: a, backRelation: b } : { fkOwner: b, backRelation: a };
|
|
451
|
+
}
|
|
452
|
+
const aFromIsId = models.get(a.modelName)?.__columns.get(a.rel.__fromColumn)?.__columnType === "id" || models.get(a.modelName)?.__primaryKey?.includes(a.rel.__fromColumn) === true;
|
|
453
|
+
const bFromIsId = models.get(b.modelName)?.__columns.get(b.rel.__fromColumn)?.__columnType === "id" || models.get(b.modelName)?.__primaryKey?.includes(b.rel.__fromColumn) === true;
|
|
418
454
|
if (aFromIsId && !bFromIsId)
|
|
419
455
|
return { fkOwner: b, backRelation: a };
|
|
420
456
|
if (!aFromIsId && bFromIsId)
|
|
421
457
|
return { fkOwner: a, backRelation: b };
|
|
422
|
-
|
|
458
|
+
const aActions = a.rel.__onDelete !== undefined || a.rel.__onUpdate !== undefined;
|
|
459
|
+
const bActions = b.rel.__onDelete !== undefined || b.rel.__onUpdate !== undefined;
|
|
460
|
+
if (aActions && !bActions) {
|
|
423
461
|
return { fkOwner: a, backRelation: b };
|
|
424
462
|
}
|
|
425
|
-
if (
|
|
463
|
+
if (bActions && !aActions) {
|
|
426
464
|
return { fkOwner: b, backRelation: a };
|
|
427
465
|
}
|
|
428
466
|
const aLooksFk = looksLikeForeignKey({ column: a.rel.__fromColumn });
|
|
429
467
|
const bLooksFk = looksLikeForeignKey({ column: b.rel.__fromColumn });
|
|
430
|
-
|
|
468
|
+
if (aLooksFk === bLooksFk)
|
|
469
|
+
return ownershipError();
|
|
470
|
+
return aLooksFk ? { fkOwner: a, backRelation: b } : { fkOwner: b, backRelation: a };
|
|
431
471
|
}
|
|
432
472
|
function looksLikeForeignKey(params) {
|
|
433
473
|
const { column } = params;
|
|
@@ -453,7 +493,7 @@ function planExplicitRelations(params) {
|
|
|
453
493
|
planned.push({ ...base, kind: "backOne", isList: false, isOptional: true });
|
|
454
494
|
continue;
|
|
455
495
|
}
|
|
456
|
-
const ownsFk = mergeInfo?.role === "fkOwner" || rel.__fromModel === modelName;
|
|
496
|
+
const ownsFk = mergeInfo?.role === "fkOwner" || rel.__ownsForeignKey !== false && rel.__fromModel === modelName;
|
|
457
497
|
if (!ownsFk) {
|
|
458
498
|
planned.push({ ...base, kind: "backOne", isList: false, isOptional: rel.__optional });
|
|
459
499
|
continue;
|
|
@@ -570,10 +610,13 @@ function planIndexes(params) {
|
|
|
570
610
|
isUnique,
|
|
571
611
|
columns: [...index.__columns],
|
|
572
612
|
indexType: index.__indexType,
|
|
613
|
+
...index.__map === undefined ? {} : { map: index.__map },
|
|
573
614
|
...index.__where === undefined ? {} : { where: index.__where },
|
|
574
615
|
...index.__nullsNotDistinct === true ? { nullsNotDistinct: true } : {},
|
|
575
616
|
...index.__clientName === undefined ? {} : { clientName: index.__clientName }
|
|
576
617
|
});
|
|
618
|
+
} else if (index.__map !== undefined) {
|
|
619
|
+
byKey.set(key, { ...existing, map: index.__map, indexType: index.__indexType ?? existing.indexType });
|
|
577
620
|
} else if (index.__indexType !== undefined && existing.indexType === undefined) {
|
|
578
621
|
byKey.set(key, { ...existing, indexType: index.__indexType });
|
|
579
622
|
}
|
|
@@ -627,319 +670,639 @@ function findCounterparts(params) {
|
|
|
627
670
|
return (plan.relations.get(relation.targetModel) ?? []).filter((candidate) => candidate !== relation && candidate.targetModel === relation.model && (candidate.relationName ?? "") === nameKey);
|
|
628
671
|
}
|
|
629
672
|
|
|
630
|
-
// packages/dsl/src/
|
|
631
|
-
function
|
|
673
|
+
// packages/dsl/src/to-ir.ts
|
|
674
|
+
function fieldType(params) {
|
|
632
675
|
const { column } = params;
|
|
633
|
-
const list = column.__config.array === true ? "[]" : "";
|
|
634
676
|
switch (column.__columnType) {
|
|
635
677
|
case "string":
|
|
636
|
-
return
|
|
678
|
+
return "String";
|
|
637
679
|
case "int":
|
|
638
|
-
return
|
|
680
|
+
return "Int";
|
|
639
681
|
case "bigint":
|
|
640
|
-
return
|
|
682
|
+
return "BigInt";
|
|
641
683
|
case "float":
|
|
642
|
-
return
|
|
684
|
+
return "Float";
|
|
643
685
|
case "decimal":
|
|
644
|
-
return
|
|
686
|
+
return "Decimal";
|
|
645
687
|
case "boolean":
|
|
646
|
-
return
|
|
688
|
+
return "Boolean";
|
|
647
689
|
case "datetime":
|
|
648
|
-
return
|
|
690
|
+
return "DateTime";
|
|
649
691
|
case "json":
|
|
650
|
-
return
|
|
692
|
+
return "Json";
|
|
651
693
|
case "bytes":
|
|
652
|
-
return
|
|
694
|
+
return "Bytes";
|
|
653
695
|
case "id":
|
|
654
696
|
return column.__config.type === "autoincrement" ? "Int" : "String";
|
|
655
697
|
case "enum":
|
|
656
|
-
return column.__enumName === undefined ?
|
|
657
|
-
case "unsupported":
|
|
658
|
-
|
|
659
|
-
if (typeof extensionTypeName === "string") {
|
|
660
|
-
const rawArgs = column.__config["extensionArgs"];
|
|
661
|
-
const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
|
|
662
|
-
return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
|
|
663
|
-
}
|
|
664
|
-
return `Unsupported(${encodePrismaString({ value: String(column.__config.dbType) })})${list}`;
|
|
665
|
-
}
|
|
698
|
+
return column.__enumName === undefined ? "String" : { enum: column.__enumName };
|
|
699
|
+
case "unsupported":
|
|
700
|
+
return "String";
|
|
666
701
|
}
|
|
667
702
|
}
|
|
668
|
-
function
|
|
669
|
-
|
|
670
|
-
switch (idType) {
|
|
703
|
+
function idDefault(params) {
|
|
704
|
+
switch (params.column.__config.type) {
|
|
671
705
|
case "ulid":
|
|
706
|
+
return { kind: "ulid" };
|
|
672
707
|
case "cuid":
|
|
708
|
+
return { kind: "cuid" };
|
|
673
709
|
case "uuid":
|
|
710
|
+
return { kind: "uuid" };
|
|
674
711
|
case "nanoid":
|
|
712
|
+
return { kind: "nanoid" };
|
|
675
713
|
case "autoincrement":
|
|
676
|
-
return
|
|
714
|
+
return { kind: "autoincrement" };
|
|
677
715
|
default:
|
|
678
716
|
return;
|
|
679
717
|
}
|
|
680
718
|
}
|
|
681
|
-
function
|
|
719
|
+
function fieldDefault(params) {
|
|
682
720
|
const { column } = params;
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
721
|
+
if (column.__columnType === "id") {
|
|
722
|
+
if (column.__config.default !== undefined || column.__config.dbgenerated !== undefined || column.__config.autoincrement !== undefined)
|
|
723
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "Use the id generator strategy instead of a conflicting id default" });
|
|
724
|
+
return idDefault({ column });
|
|
725
|
+
}
|
|
726
|
+
const { dbgenerated, default: value, autoincrement } = column.__config;
|
|
727
|
+
if ([dbgenerated !== undefined, value !== undefined, autoincrement === true].filter(Boolean).length > 1) {
|
|
728
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `Column "${column.__name ?? column.__columnType}" has conflicting defaults` });
|
|
686
729
|
}
|
|
730
|
+
if (autoincrement === true) {
|
|
731
|
+
if (column.__columnType !== "int" && column.__columnType !== "bigint" || column.__config.array === true || column.__config.optional === true) {
|
|
732
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "autoincrement requires a required scalar Int or BigInt column" });
|
|
733
|
+
}
|
|
734
|
+
return { kind: "autoincrement" };
|
|
735
|
+
}
|
|
736
|
+
if (typeof dbgenerated === "string")
|
|
737
|
+
return { kind: "dbgenerated", sql: dbgenerated };
|
|
687
738
|
if (value === undefined)
|
|
688
739
|
return;
|
|
689
|
-
if (column.
|
|
690
|
-
|
|
740
|
+
if (column.__config.array === true) {
|
|
741
|
+
if (!Array.isArray(value))
|
|
742
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "A scalar-list default must be an array" });
|
|
743
|
+
return { kind: "list", values: scalarListDefault({ type: fieldType({ column }), values: column.__columnType === "json" ? value.map((entry) => typeof entry === "string" ? entry : serializeJsonDefault({ value: entry })) : value }) };
|
|
691
744
|
}
|
|
745
|
+
if (column.__columnType === "json" && typeof value !== "string") {
|
|
746
|
+
return { kind: "literal", value: serializeJsonDefault({ value }) };
|
|
747
|
+
}
|
|
748
|
+
if (column.__columnType === "datetime" && value === "now")
|
|
749
|
+
return { kind: "now" };
|
|
692
750
|
if (column.__columnType === "enum" && typeof value === "string") {
|
|
693
|
-
return
|
|
751
|
+
return { kind: "enumValue", value };
|
|
752
|
+
}
|
|
753
|
+
if (typeof value === "number" && (!Number.isFinite(value) || (column.__columnType === "int" || column.__columnType === "bigint") && !Number.isSafeInteger(value))) {
|
|
754
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "Numeric defaults must be finite; integer defaults must be exact" });
|
|
755
|
+
}
|
|
756
|
+
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
757
|
+
return { kind: "literal", value };
|
|
694
758
|
}
|
|
695
|
-
if (typeof value === "string")
|
|
696
|
-
return `@default(${encodePrismaString({ value })})`;
|
|
697
|
-
if (typeof value === "boolean" || typeof value === "number")
|
|
698
|
-
return `@default(${value})`;
|
|
699
759
|
if (typeof value === "bigint")
|
|
700
|
-
return
|
|
701
|
-
|
|
760
|
+
return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
|
|
761
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
|
|
702
762
|
}
|
|
703
|
-
function
|
|
763
|
+
function nativeType(params) {
|
|
704
764
|
const { column } = params;
|
|
705
|
-
if (column.__columnType === "
|
|
765
|
+
if (column.__columnType === "enum") {
|
|
766
|
+
const { maxLength, text } = column.__config;
|
|
767
|
+
if (maxLength !== undefined || text !== undefined) {
|
|
768
|
+
const invalid = column.__enumStorage !== "string" || maxLength !== undefined && (typeof maxLength !== "number" || !Number.isSafeInteger(maxLength) || maxLength <= 0) || text !== undefined && typeof text !== "boolean" || text === true && maxLength !== undefined;
|
|
769
|
+
if (invalid) {
|
|
770
|
+
throw new VibeError2({
|
|
771
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
772
|
+
message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
|
|
773
|
+
meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (column.__columnType === "string" || column.__columnType === "enum" && column.__enumStorage === "string") {
|
|
706
779
|
const { maxLength, text } = column.__config;
|
|
707
780
|
if (typeof maxLength === "number")
|
|
708
|
-
return
|
|
781
|
+
return `VarChar(${maxLength})`;
|
|
709
782
|
if (text === true)
|
|
710
|
-
return "
|
|
783
|
+
return "Text";
|
|
711
784
|
return;
|
|
712
785
|
}
|
|
713
786
|
if (column.__columnType === "decimal") {
|
|
714
787
|
const { precision, scale } = column.__config;
|
|
715
788
|
if (typeof precision === "number")
|
|
716
|
-
return
|
|
789
|
+
return `Decimal(${precision}, ${scale ?? 0})`;
|
|
717
790
|
return;
|
|
718
791
|
}
|
|
719
792
|
if (column.__columnType === "datetime" && column.__config.tz === true) {
|
|
720
|
-
return "
|
|
793
|
+
return "Timestamptz";
|
|
794
|
+
}
|
|
795
|
+
if (column.__columnType === "unsupported") {
|
|
796
|
+
return String(column.__config.dbType);
|
|
721
797
|
}
|
|
722
798
|
return;
|
|
723
799
|
}
|
|
724
|
-
function
|
|
725
|
-
const { name, column } = params;
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
parts.push(nativeType);
|
|
749
|
-
if (typeof column.__config.collation === "string") {
|
|
750
|
-
parts.push(`@collation(${encodePrismaString({ value: column.__config.collation })})`);
|
|
751
|
-
}
|
|
752
|
-
const columnLine = parts.join(" ");
|
|
753
|
-
const comment = column.__config.comment;
|
|
754
|
-
if (typeof comment === "string") {
|
|
755
|
-
const commentLines = comment.split(`
|
|
756
|
-
`).map((line) => ` /// ${line}`);
|
|
757
|
-
return [...commentLines, columnLine].join(`
|
|
758
|
-
`);
|
|
759
|
-
}
|
|
760
|
-
return columnLine;
|
|
800
|
+
function buildField(params) {
|
|
801
|
+
const { name, column, extensionNativeType } = params;
|
|
802
|
+
const config = column.__config;
|
|
803
|
+
const isList = column.__columnType !== "id" && config.array === true;
|
|
804
|
+
const dbName = typeof config.map === "string" ? config.map : undefined;
|
|
805
|
+
const documentation = typeof config.comment === "string" ? config.comment : undefined;
|
|
806
|
+
const collation = typeof config.collation === "string" ? config.collation : undefined;
|
|
807
|
+
const defaultValue = fieldDefault({ column });
|
|
808
|
+
const native = extensionNativeType ?? nativeType({ column });
|
|
809
|
+
return {
|
|
810
|
+
name,
|
|
811
|
+
type: fieldType({ column }),
|
|
812
|
+
isList,
|
|
813
|
+
isOptional: config.optional === true,
|
|
814
|
+
isId: column.__columnType === "id",
|
|
815
|
+
isUnique: config.unique === true,
|
|
816
|
+
isUpdatedAt: column.__columnType === "datetime" && config.updatedAt === true,
|
|
817
|
+
...collation === undefined ? {} : { collation },
|
|
818
|
+
...dbName === undefined ? {} : { dbName },
|
|
819
|
+
...defaultValue === undefined ? {} : { default: defaultValue },
|
|
820
|
+
...native === undefined ? {} : { nativeType: native },
|
|
821
|
+
...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
|
|
822
|
+
...documentation === undefined ? {} : { documentation }
|
|
823
|
+
};
|
|
761
824
|
}
|
|
762
|
-
function
|
|
763
|
-
const { relation } = params;
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (relation.fields === undefined || relation.references === undefined) {
|
|
778
|
-
throw internalError({
|
|
779
|
-
message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
|
|
780
|
-
meta: { model: relation.model, relation: name }
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
args.push(`fields: [${relation.fields.join(", ")}]`);
|
|
784
|
-
args.push(`references: [${relation.references.join(", ")}]`);
|
|
785
|
-
if (relation.onDelete !== undefined)
|
|
786
|
-
args.push(`onDelete: ${relation.onDelete}`);
|
|
787
|
-
if (relation.onUpdate !== undefined)
|
|
788
|
-
args.push(`onUpdate: ${relation.onUpdate}`);
|
|
789
|
-
return ` ${name} ${targetModel}${optional} @relation(${args.join(", ")})`;
|
|
825
|
+
function buildRelation(params) {
|
|
826
|
+
const { relation, plan } = params;
|
|
827
|
+
return {
|
|
828
|
+
name: relation.name,
|
|
829
|
+
model: relation.model,
|
|
830
|
+
targetModel: relation.targetModel,
|
|
831
|
+
type: resolveRelationType({ relation, plan }),
|
|
832
|
+
isList: relation.isList,
|
|
833
|
+
isOptional: relation.isOptional,
|
|
834
|
+
...relation.relationName === undefined ? {} : { relationName: relation.relationName },
|
|
835
|
+
...relation.fields === undefined ? {} : { fields: [...relation.fields] },
|
|
836
|
+
...relation.references === undefined ? {} : { references: [...relation.references] },
|
|
837
|
+
...relation.onDelete === undefined ? {} : { onDelete: relation.onDelete },
|
|
838
|
+
...relation.onUpdate === undefined ? {} : { onUpdate: relation.onUpdate }
|
|
839
|
+
};
|
|
790
840
|
}
|
|
791
|
-
|
|
841
|
+
var INDEX_KINDS = {
|
|
842
|
+
BTree: "btree",
|
|
843
|
+
Hash: "hash",
|
|
844
|
+
Gin: "gin",
|
|
845
|
+
Gist: "gist",
|
|
846
|
+
SpGist: "spgist",
|
|
847
|
+
Brin: "brin"
|
|
848
|
+
};
|
|
849
|
+
function buildIndex(params) {
|
|
792
850
|
const { index } = params;
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
}
|
|
803
|
-
function formatExtensionValue(params) {
|
|
804
|
-
const { value } = params;
|
|
805
|
-
if (typeof value === "string")
|
|
806
|
-
return encodePrismaString({ value });
|
|
807
|
-
if (Array.isArray(value)) {
|
|
808
|
-
return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
|
|
809
|
-
}
|
|
810
|
-
return String(value);
|
|
851
|
+
const kind = index.isUnique ? "unique" : index.indexType === undefined ? "btree" : INDEX_KINDS[index.indexType];
|
|
852
|
+
return {
|
|
853
|
+
kind,
|
|
854
|
+
fields: index.columns.map((name) => ({ name })),
|
|
855
|
+
...index.map === undefined ? {} : { name: index.map },
|
|
856
|
+
...index.where === undefined ? {} : { where: index.where },
|
|
857
|
+
...index.nullsNotDistinct === true ? { nullsNotDistinct: true } : {},
|
|
858
|
+
...index.clientName === undefined ? {} : { clientName: index.clientName }
|
|
859
|
+
};
|
|
811
860
|
}
|
|
812
|
-
function
|
|
813
|
-
const { model, plan } = params;
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
`model ${model.__name} {`
|
|
818
|
-
];
|
|
819
|
-
lines.push(" //> Columns");
|
|
861
|
+
function buildModel(params) {
|
|
862
|
+
const { model, plan, extensions, declarations, issues } = params;
|
|
863
|
+
const primaryKey = model.__primaryKey !== undefined && model.__primaryKey.length > 0 ? { fields: [...model.__primaryKey] } : undefined;
|
|
864
|
+
const policy = model.__policy === undefined ? undefined : { field: model.__policy.field, context: model.__policy.context ?? model.__policy.field };
|
|
865
|
+
const fields = [];
|
|
820
866
|
for (const [name, column] of model.__columns) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
867
|
+
const extensionTypeName = column.__config["extensionType"];
|
|
868
|
+
if (typeof extensionTypeName !== "string") {
|
|
869
|
+
fields.push(buildField({ name, column }));
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const registered = extensions.fieldTypes.get(extensionTypeName);
|
|
873
|
+
if (registered === undefined) {
|
|
874
|
+
issues.push({
|
|
875
|
+
rule: "extension-attribute",
|
|
876
|
+
message: `Field "${model.__name}.${name}" uses extension type "${extensionTypeName}" but no configured extension provides it`,
|
|
877
|
+
model: model.__name,
|
|
878
|
+
field: name
|
|
879
|
+
});
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const rawArgs = column.__config["extensionArgs"];
|
|
883
|
+
const lowered = registered.fieldType.lower({
|
|
884
|
+
model: model.__name,
|
|
885
|
+
field: name,
|
|
886
|
+
args: Array.isArray(rawArgs) ? rawArgs.filter((entry) => typeof entry === "string" || typeof entry === "number") : []
|
|
887
|
+
});
|
|
888
|
+
if ("error" in lowered) {
|
|
889
|
+
issues.push({
|
|
890
|
+
rule: "extension-attribute",
|
|
891
|
+
message: `Field "${model.__name}.${name}": ${lowered.error}`,
|
|
892
|
+
model: model.__name,
|
|
893
|
+
field: name
|
|
894
|
+
});
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
fields.push(buildField({ name, column, extensionNativeType: lowered.nativeType }));
|
|
898
|
+
if (lowered.declaration !== undefined) {
|
|
899
|
+
declarations.add({
|
|
900
|
+
extension: registered.extension.name,
|
|
901
|
+
model: model.__name,
|
|
902
|
+
declaration: lowered.declaration
|
|
903
|
+
});
|
|
904
|
+
}
|
|
836
905
|
}
|
|
837
906
|
for (const use of model.__extensions ?? []) {
|
|
838
|
-
const
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
907
|
+
const registered = extensions.modelAttributes.get(use.attribute);
|
|
908
|
+
if (registered === undefined) {
|
|
909
|
+
issues.push({
|
|
910
|
+
rule: "unknown-attribute",
|
|
911
|
+
message: `Unknown block attribute "@@${use.attribute}" on model "${model.__name}" \u2014 no extension provides it`,
|
|
912
|
+
model: model.__name
|
|
913
|
+
});
|
|
914
|
+
continue;
|
|
843
915
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
916
|
+
const parsed = registered.attribute.parse({
|
|
917
|
+
model: model.__name,
|
|
918
|
+
args: {
|
|
919
|
+
named: { ...use.args ?? {} },
|
|
920
|
+
...use.fields === undefined ? {} : { fields: use.fields }
|
|
921
|
+
},
|
|
922
|
+
fieldNames: fields.map((field) => field.name)
|
|
923
|
+
});
|
|
924
|
+
if ("error" in parsed) {
|
|
925
|
+
issues.push({
|
|
926
|
+
rule: "extension-attribute",
|
|
927
|
+
message: `@@${use.attribute} on "${model.__name}": ${parsed.error}`,
|
|
928
|
+
model: model.__name
|
|
929
|
+
});
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
declarations.add({
|
|
933
|
+
extension: registered.extension.name,
|
|
934
|
+
model: model.__name,
|
|
935
|
+
declaration: parsed.declaration
|
|
936
|
+
});
|
|
856
937
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
938
|
+
return {
|
|
939
|
+
name: model.__name,
|
|
940
|
+
...model.__map === undefined ? {} : { dbName: model.__map },
|
|
941
|
+
fields,
|
|
942
|
+
relations: (plan.relations.get(model.__name) ?? []).map((relation) => buildRelation({ relation, plan })),
|
|
943
|
+
indexes: (plan.indexes.get(model.__name) ?? []).map((index) => buildIndex({ index })),
|
|
944
|
+
...model.__checks === undefined || model.__checks.length === 0 ? {} : { checks: model.__checks.map((check) => ({ name: check.name, expression: check.expression })) },
|
|
945
|
+
...policy === undefined ? {} : { policy },
|
|
946
|
+
...primaryKey === undefined ? {} : { primaryKey },
|
|
947
|
+
isView: false,
|
|
948
|
+
...model.__comment === undefined ? {} : { documentation: model.__comment }
|
|
949
|
+
};
|
|
865
950
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
});
|
|
951
|
+
|
|
952
|
+
class ExtensionDeclarationCollector {
|
|
953
|
+
byExtension = new Map;
|
|
954
|
+
add(params) {
|
|
955
|
+
const perModel = this.byExtension.get(params.extension) ?? new Map;
|
|
956
|
+
this.byExtension.set(params.extension, perModel);
|
|
957
|
+
const existing = perModel.get(params.model) ?? {};
|
|
958
|
+
perModel.set(params.model, mergeExtensionDeclarations({ base: existing, next: params.declaration }));
|
|
874
959
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
960
|
+
toIR() {
|
|
961
|
+
return [...this.byExtension.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([name, perModel]) => ({
|
|
962
|
+
name,
|
|
963
|
+
options: {
|
|
964
|
+
models: Object.fromEntries([...perModel.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
|
|
965
|
+
}
|
|
966
|
+
}));
|
|
881
967
|
}
|
|
968
|
+
}
|
|
969
|
+
function toIR(params) {
|
|
970
|
+
const { schema, provider = "postgresql" } = params;
|
|
971
|
+
const extensions = params.extensions ?? EMPTY_EXTENSION_REGISTRY;
|
|
882
972
|
const plan = planSchema({ schema });
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
973
|
+
const declarations = new ExtensionDeclarationCollector;
|
|
974
|
+
const issues = [];
|
|
975
|
+
const models = Array.from(schema.__models.values(), (model) => {
|
|
976
|
+
const built = buildModel({ model, plan, extensions, declarations, issues });
|
|
977
|
+
const rls = schema.__rls?.policies[model.__name];
|
|
978
|
+
return rls === undefined ? built : { ...built, rls };
|
|
979
|
+
});
|
|
980
|
+
if (schema.__rls !== undefined) {
|
|
981
|
+
for (const name of Object.keys(schema.__rls.policies)) {
|
|
982
|
+
if (!schema.__models.has(name)) {
|
|
983
|
+
issues.push({ rule: "rls-unknown-model", model: name, message: `RLS classification references model "${name}" absent from the composed schema` });
|
|
984
|
+
}
|
|
892
985
|
}
|
|
893
986
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
987
|
+
const enums = Array.from(schema.__enums, ([name, values]) => ({
|
|
988
|
+
name,
|
|
989
|
+
values: [...values],
|
|
990
|
+
...schema.__enumStorage.get(name) === "string" ? { storage: "string" } : {}
|
|
991
|
+
}));
|
|
992
|
+
if (issues.length > 0) {
|
|
993
|
+
throw new VibeError2({
|
|
994
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
995
|
+
message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
|
|
996
|
+
meta: { issues }
|
|
997
|
+
});
|
|
901
998
|
}
|
|
902
|
-
|
|
903
|
-
|
|
999
|
+
const ir = {
|
|
1000
|
+
datasource: { provider },
|
|
1001
|
+
models,
|
|
1002
|
+
enums,
|
|
1003
|
+
extensions: declarations.toIR(),
|
|
1004
|
+
...schema.__rls === undefined ? {} : { rls: { context: schema.__rls.context } }
|
|
1005
|
+
};
|
|
1006
|
+
assertValidSchemaIR({ schema: ir });
|
|
1007
|
+
return ir;
|
|
904
1008
|
}
|
|
905
1009
|
|
|
906
|
-
// packages/dsl/src/
|
|
907
|
-
function
|
|
908
|
-
const {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
return {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
1010
|
+
// packages/dsl/src/generator/prisma.ts
|
|
1011
|
+
function prismaType(params) {
|
|
1012
|
+
const { column } = params;
|
|
1013
|
+
const list = column.__config.array === true ? "[]" : "";
|
|
1014
|
+
switch (column.__columnType) {
|
|
1015
|
+
case "string":
|
|
1016
|
+
return `String${list}`;
|
|
1017
|
+
case "int":
|
|
1018
|
+
return `Int${list}`;
|
|
1019
|
+
case "bigint":
|
|
1020
|
+
return `BigInt${list}`;
|
|
1021
|
+
case "float":
|
|
1022
|
+
return `Float${list}`;
|
|
1023
|
+
case "decimal":
|
|
1024
|
+
return `Decimal${list}`;
|
|
1025
|
+
case "boolean":
|
|
1026
|
+
return `Boolean${list}`;
|
|
1027
|
+
case "datetime":
|
|
1028
|
+
return `DateTime${list}`;
|
|
1029
|
+
case "json":
|
|
1030
|
+
return `Json${list}`;
|
|
1031
|
+
case "bytes":
|
|
1032
|
+
return `Bytes${list}`;
|
|
1033
|
+
case "id":
|
|
1034
|
+
return column.__config.type === "autoincrement" ? "Int" : "String";
|
|
1035
|
+
case "enum":
|
|
1036
|
+
return column.__enumName === undefined ? `String${list}` : `${column.__enumName}${list}`;
|
|
1037
|
+
case "unsupported": {
|
|
1038
|
+
const extensionTypeName = column.__config["extensionType"];
|
|
1039
|
+
if (typeof extensionTypeName === "string") {
|
|
1040
|
+
const rawArgs = column.__config["extensionArgs"];
|
|
1041
|
+
const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
|
|
1042
|
+
return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
|
|
1043
|
+
}
|
|
1044
|
+
return `Unsupported(${encodePrismaString({ value: String(column.__config.dbType) })})${list}`;
|
|
921
1045
|
}
|
|
922
|
-
}
|
|
1046
|
+
}
|
|
923
1047
|
}
|
|
924
|
-
function
|
|
925
|
-
const {
|
|
926
|
-
|
|
1048
|
+
function defaultAttribute(params) {
|
|
1049
|
+
const { column } = params;
|
|
1050
|
+
const value = fieldDefault({ column });
|
|
1051
|
+
if (value === undefined)
|
|
1052
|
+
return;
|
|
1053
|
+
if (value.kind === "list") {
|
|
1054
|
+
const values = value.values.map((entry) => column.__columnType === "enum" ? String(entry) : column.__columnType === "bigint" ? String(entry) : JSON.stringify(entry));
|
|
1055
|
+
return `@default([${values.join(", ")}])`;
|
|
1056
|
+
}
|
|
1057
|
+
if (value.kind === "literal")
|
|
1058
|
+
return `@default(${typeof value.value === "string" ? encodePrismaString({ value: value.value }) : String(value.value)})`;
|
|
1059
|
+
if (value.kind === "enumValue")
|
|
1060
|
+
return `@default(${value.value})`;
|
|
1061
|
+
if (value.kind === "dbgenerated")
|
|
1062
|
+
return `@default(dbgenerated(${encodePrismaString({ value: value.sql })}))`;
|
|
1063
|
+
return `@default(${value.kind}())`;
|
|
927
1064
|
}
|
|
928
|
-
function
|
|
929
|
-
const {
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
}
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
|
|
1065
|
+
function nativeTypeAttribute(params) {
|
|
1066
|
+
const { column } = params;
|
|
1067
|
+
if (column.__columnType === "string") {
|
|
1068
|
+
const { maxLength, text } = column.__config;
|
|
1069
|
+
if (typeof maxLength === "number")
|
|
1070
|
+
return `@db.VarChar(${maxLength})`;
|
|
1071
|
+
if (text === true)
|
|
1072
|
+
return "@db.Text";
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (column.__columnType === "decimal") {
|
|
1076
|
+
const { precision, scale } = column.__config;
|
|
1077
|
+
if (typeof precision === "number")
|
|
1078
|
+
return `@db.Decimal(${precision}, ${scale ?? 0})`;
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (column.__columnType === "datetime" && column.__config.tz === true) {
|
|
1082
|
+
return "@db.Timestamptz";
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
function formatColumn(params) {
|
|
1087
|
+
const { name, column } = params;
|
|
1088
|
+
const parts = [` ${name} ${prismaType({ column })}${column.__config.optional === true ? "?" : ""}`];
|
|
1089
|
+
if (column.__columnType === "id") {
|
|
1090
|
+
parts.push("@id");
|
|
1091
|
+
const idDefault2 = defaultAttribute({ column });
|
|
1092
|
+
if (idDefault2 !== undefined)
|
|
1093
|
+
parts.push(idDefault2);
|
|
1094
|
+
} else {
|
|
1095
|
+
const columnDefault = defaultAttribute({ column });
|
|
1096
|
+
if (columnDefault !== undefined)
|
|
1097
|
+
parts.push(columnDefault);
|
|
1098
|
+
}
|
|
1099
|
+
if (column.__columnType === "datetime" && column.__config.updatedAt === true) {
|
|
1100
|
+
parts.push("@updatedAt");
|
|
1101
|
+
}
|
|
1102
|
+
if (column.__config.unique === true) {
|
|
1103
|
+
parts.push("@unique");
|
|
1104
|
+
}
|
|
1105
|
+
if (typeof column.__config.map === "string") {
|
|
1106
|
+
parts.push(`@map(${encodePrismaString({ value: column.__config.map })})`);
|
|
1107
|
+
}
|
|
1108
|
+
const nativeType2 = nativeTypeAttribute({ column });
|
|
1109
|
+
if (nativeType2 !== undefined)
|
|
1110
|
+
parts.push(nativeType2);
|
|
1111
|
+
if (typeof column.__config.collation === "string") {
|
|
1112
|
+
parts.push(`@collation(${encodePrismaString({ value: column.__config.collation })})`);
|
|
1113
|
+
}
|
|
1114
|
+
const columnLine = parts.join(" ");
|
|
1115
|
+
const comment = column.__config.comment;
|
|
1116
|
+
if (typeof comment === "string") {
|
|
1117
|
+
const commentLines = comment.split(`
|
|
1118
|
+
`).map((line) => ` /// ${line}`);
|
|
1119
|
+
return [...commentLines, columnLine].join(`
|
|
1120
|
+
`);
|
|
1121
|
+
}
|
|
1122
|
+
return columnLine;
|
|
1123
|
+
}
|
|
1124
|
+
function formatRelation(params) {
|
|
1125
|
+
const { relation } = params;
|
|
1126
|
+
const { name, targetModel, relationName } = relation;
|
|
1127
|
+
if (relation.kind === "list") {
|
|
1128
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString({ value: relationName })})`;
|
|
1129
|
+
return ` ${name} ${targetModel}[]${attribute}`;
|
|
1130
|
+
}
|
|
1131
|
+
const optional = relation.isOptional ? "?" : "";
|
|
1132
|
+
if (relation.kind === "backOne") {
|
|
1133
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString({ value: relationName })})`;
|
|
1134
|
+
return ` ${name} ${targetModel}${optional}${attribute}`;
|
|
1135
|
+
}
|
|
1136
|
+
const args = [];
|
|
1137
|
+
if (relationName !== undefined)
|
|
1138
|
+
args.push(encodePrismaString({ value: relationName }));
|
|
1139
|
+
if (relation.fields === undefined || relation.references === undefined) {
|
|
1140
|
+
throw internalError({
|
|
1141
|
+
message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
|
|
1142
|
+
meta: { model: relation.model, relation: name }
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
args.push(`fields: [${relation.fields.join(", ")}]`);
|
|
1146
|
+
args.push(`references: [${relation.references.join(", ")}]`);
|
|
1147
|
+
if (relation.onDelete !== undefined)
|
|
1148
|
+
args.push(`onDelete: ${relation.onDelete}`);
|
|
1149
|
+
if (relation.onUpdate !== undefined)
|
|
1150
|
+
args.push(`onUpdate: ${relation.onUpdate}`);
|
|
1151
|
+
return ` ${name} ${targetModel}${optional} @relation(${args.join(", ")})`;
|
|
1152
|
+
}
|
|
1153
|
+
function formatIndex(params) {
|
|
1154
|
+
const { index } = params;
|
|
1155
|
+
const columns = index.columns.join(", ");
|
|
1156
|
+
const whereArg = index.where === undefined ? "" : `, where: ${encodePrismaString({ value: index.where })}`;
|
|
1157
|
+
const mapArg = index.map === undefined ? "" : `, map: ${encodePrismaString({ value: index.map })}`;
|
|
1158
|
+
const nndArg = index.nullsNotDistinct === true ? ", nullsNotDistinct: true" : "";
|
|
1159
|
+
const nameArg = index.clientName === undefined ? "" : `, name: ${encodePrismaString({ value: index.clientName })}`;
|
|
1160
|
+
if (index.isUnique) {
|
|
1161
|
+
return ` @@unique([${columns}]${nameArg}${mapArg}${nndArg}${whereArg})`;
|
|
1162
|
+
}
|
|
1163
|
+
const typeArg = index.indexType === undefined ? "" : `, type: ${index.indexType}`;
|
|
1164
|
+
return ` @@index([${columns}]${typeArg}${mapArg}${whereArg})`;
|
|
1165
|
+
}
|
|
1166
|
+
function formatExtensionValue(params) {
|
|
1167
|
+
const { value } = params;
|
|
1168
|
+
if (typeof value === "string")
|
|
1169
|
+
return encodePrismaString({ value });
|
|
1170
|
+
if (Array.isArray(value)) {
|
|
1171
|
+
return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
|
|
1172
|
+
}
|
|
1173
|
+
return String(value);
|
|
1174
|
+
}
|
|
1175
|
+
function formatModel(params) {
|
|
1176
|
+
const { model, plan } = params;
|
|
1177
|
+
const lines = [
|
|
1178
|
+
...model.__comment === undefined ? [] : model.__comment.split(`
|
|
1179
|
+
`).map((line) => `/// ${line}`),
|
|
1180
|
+
`model ${model.__name} {`
|
|
1181
|
+
];
|
|
1182
|
+
lines.push(" //> Columns");
|
|
1183
|
+
for (const [name, column] of model.__columns) {
|
|
1184
|
+
lines.push(formatColumn({ name, column }));
|
|
1185
|
+
}
|
|
1186
|
+
lines.push("");
|
|
1187
|
+
lines.push(" //> Relations");
|
|
1188
|
+
for (const relation of plan.relations.get(model.__name) ?? []) {
|
|
1189
|
+
lines.push(formatRelation({ relation }));
|
|
1190
|
+
}
|
|
1191
|
+
lines.push("");
|
|
1192
|
+
lines.push(" //> Indexes");
|
|
1193
|
+
lines.push("");
|
|
1194
|
+
for (const index of plan.indexes.get(model.__name) ?? []) {
|
|
1195
|
+
lines.push(formatIndex({ index }));
|
|
1196
|
+
}
|
|
1197
|
+
for (const check of model.__checks ?? []) {
|
|
1198
|
+
lines.push(` @@check(${encodePrismaString({ value: check.expression })}, name: ${encodePrismaString({ value: check.name })})`);
|
|
1199
|
+
}
|
|
1200
|
+
for (const use of model.__extensions ?? []) {
|
|
1201
|
+
const args = [];
|
|
1202
|
+
if (use.fields !== undefined)
|
|
1203
|
+
args.push(`[${use.fields.join(", ")}]`);
|
|
1204
|
+
for (const [key, value] of Object.entries(use.args ?? {})) {
|
|
1205
|
+
args.push(`${key}: ${formatExtensionValue({ value })}`);
|
|
1206
|
+
}
|
|
1207
|
+
lines.push(` @@${use.attribute}(${args.join(", ")})`);
|
|
1208
|
+
}
|
|
1209
|
+
if (model.__primaryKey !== undefined && model.__primaryKey.length > 0) {
|
|
1210
|
+
lines.push(` @@id([${model.__primaryKey.join(", ")}])`);
|
|
1211
|
+
}
|
|
1212
|
+
if (model.__policy !== undefined) {
|
|
1213
|
+
const { field, context } = model.__policy;
|
|
1214
|
+
const contextArg = context === undefined || context === field ? "" : `, context: ${encodePrismaString({ value: context })}`;
|
|
1215
|
+
lines.push(` @@policy([${field}]${contextArg})`);
|
|
1216
|
+
}
|
|
1217
|
+
if (typeof model.__map === "string") {
|
|
1218
|
+
lines.push(` @@map(${encodePrismaString({ value: model.__map })})`);
|
|
1219
|
+
}
|
|
1220
|
+
lines.push("}");
|
|
1221
|
+
return lines.join(`
|
|
1222
|
+
`);
|
|
1223
|
+
}
|
|
1224
|
+
function formatEnum(params) {
|
|
1225
|
+
const { name, values } = params;
|
|
1226
|
+
return [`enum ${name} {`, ...values.map((value) => ` ${value}`), "}"].join(`
|
|
1227
|
+
`);
|
|
1228
|
+
}
|
|
1229
|
+
function generatePrisma(schema, _config = {}) {
|
|
1230
|
+
const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
|
|
1231
|
+
if (stringEnums.length > 0) {
|
|
1232
|
+
throw new VibeError3({
|
|
1233
|
+
code: "VIBE_CONFIG",
|
|
1234
|
+
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.`,
|
|
1235
|
+
meta: { enums: stringEnums, target: "prisma" }
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
if (schema.__rls !== undefined) {
|
|
1239
|
+
throw new VibeError3({
|
|
1240
|
+
code: "VIBE_CONFIG",
|
|
1241
|
+
message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
|
|
1242
|
+
meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
const plan = planSchema({ schema });
|
|
1246
|
+
const sections = [];
|
|
1247
|
+
if (schema.__enums.size > 0) {
|
|
1248
|
+
sections.push("// ============================================");
|
|
1249
|
+
sections.push("// Enums");
|
|
1250
|
+
sections.push("// ============================================");
|
|
1251
|
+
sections.push("");
|
|
1252
|
+
for (const [name, values] of schema.__enums) {
|
|
1253
|
+
sections.push(formatEnum({ name, values }));
|
|
1254
|
+
sections.push("");
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
sections.push("// ============================================");
|
|
1258
|
+
sections.push("// Models");
|
|
1259
|
+
sections.push("// ============================================");
|
|
1260
|
+
sections.push("");
|
|
1261
|
+
for (const model of schema.__models.values()) {
|
|
1262
|
+
sections.push(formatModel({ model, plan }));
|
|
1263
|
+
sections.push("");
|
|
1264
|
+
}
|
|
1265
|
+
return sections.join(`
|
|
1266
|
+
`).trim();
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// packages/dsl/src/relations.ts
|
|
1270
|
+
function createColumnRef(params) {
|
|
1271
|
+
const { model, column } = params;
|
|
1272
|
+
return {
|
|
1273
|
+
__type: "columnRef",
|
|
1274
|
+
__model: model,
|
|
1275
|
+
__column: column,
|
|
1276
|
+
through(junction) {
|
|
1277
|
+
return {
|
|
1278
|
+
__type: "throughRef",
|
|
1279
|
+
__model: model,
|
|
1280
|
+
__column: column,
|
|
1281
|
+
__junction: junction.__model,
|
|
1282
|
+
__junctionColumn: junction.__column
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function extractRefInfo(params) {
|
|
1288
|
+
const { ref } = params;
|
|
1289
|
+
return { model: ref.__model, column: ref.__column };
|
|
1290
|
+
}
|
|
1291
|
+
function createRelationFactory(params) {
|
|
1292
|
+
const { relationType, targetModelName } = params;
|
|
1293
|
+
return (config) => {
|
|
1294
|
+
const from = extractRefInfo({ ref: config.from });
|
|
1295
|
+
const to = extractRefInfo({ ref: config.to });
|
|
1296
|
+
let throughJunction;
|
|
1297
|
+
let throughFromCol;
|
|
1298
|
+
let throughToCol;
|
|
1299
|
+
if (config.from.__type === "throughRef") {
|
|
1300
|
+
throughJunction = config.from.__junction;
|
|
1301
|
+
throughFromCol = config.from.__junctionColumn;
|
|
1302
|
+
}
|
|
1303
|
+
if (config.to.__type === "throughRef") {
|
|
1304
|
+
throughJunction = throughJunction ?? config.to.__junction;
|
|
1305
|
+
throughToCol = config.to.__junctionColumn;
|
|
943
1306
|
}
|
|
944
1307
|
return {
|
|
945
1308
|
__type: "storedRelation",
|
|
@@ -954,6 +1317,7 @@ function createRelationFactory(params) {
|
|
|
954
1317
|
__throughToCol: throughToCol,
|
|
955
1318
|
__optional: config.optional ?? relationType === "many",
|
|
956
1319
|
__alias: config.alias,
|
|
1320
|
+
..."ownsForeignKey" in config ? { __ownsForeignKey: config.ownsForeignKey } : {},
|
|
957
1321
|
__onDelete: config.onDelete,
|
|
958
1322
|
__onUpdate: config.onUpdate
|
|
959
1323
|
};
|
|
@@ -1012,7 +1376,7 @@ function mergeRelations(...parts) {
|
|
|
1012
1376
|
}
|
|
1013
1377
|
|
|
1014
1378
|
// packages/dsl/src/schema.ts
|
|
1015
|
-
import { VibeError as
|
|
1379
|
+
import { VibeError as VibeError4, validateRlsContextIR } from "@vibeorm/schema";
|
|
1016
1380
|
function sameValues(left, right) {
|
|
1017
1381
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1018
1382
|
}
|
|
@@ -1031,7 +1395,7 @@ function registerEnum(params) {
|
|
|
1031
1395
|
...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
|
|
1032
1396
|
...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
|
|
1033
1397
|
].join("; ");
|
|
1034
|
-
throw new
|
|
1398
|
+
throw new VibeError4({
|
|
1035
1399
|
code: "VIBE_SCHEMA_INVALID",
|
|
1036
1400
|
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`,
|
|
1037
1401
|
meta: {
|
|
@@ -1057,7 +1421,7 @@ function addModelTree(params) {
|
|
|
1057
1421
|
const { model, models, origin } = params;
|
|
1058
1422
|
const existing = models.get(model.__name);
|
|
1059
1423
|
if (existing !== undefined && existing !== model) {
|
|
1060
|
-
throw new
|
|
1424
|
+
throw new VibeError4({
|
|
1061
1425
|
code: "VIBE_SCHEMA_INVALID",
|
|
1062
1426
|
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`,
|
|
1063
1427
|
meta: {
|
|
@@ -1135,7 +1499,7 @@ var RLS_CONTEXT_TYPES = {
|
|
|
1135
1499
|
};
|
|
1136
1500
|
function defineRls(params) {
|
|
1137
1501
|
if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
|
|
1138
|
-
throw new
|
|
1502
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1139
1503
|
}
|
|
1140
1504
|
const entries = [];
|
|
1141
1505
|
const refs = [];
|
|
@@ -1143,7 +1507,7 @@ function defineRls(params) {
|
|
|
1143
1507
|
const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
|
|
1144
1508
|
const column = descriptor?.value;
|
|
1145
1509
|
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) {
|
|
1146
|
-
throw new
|
|
1510
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1147
1511
|
}
|
|
1148
1512
|
entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
|
|
1149
1513
|
refs.push([key, Object.freeze({ context: key })]);
|
|
@@ -1151,22 +1515,22 @@ function defineRls(params) {
|
|
|
1151
1515
|
const context = Object.freeze(Object.fromEntries(entries));
|
|
1152
1516
|
const issues = validateRlsContextIR({ context });
|
|
1153
1517
|
if (issues.length > 0) {
|
|
1154
|
-
throw new
|
|
1518
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
|
|
1155
1519
|
}
|
|
1156
1520
|
const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
|
|
1157
1521
|
if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
|
|
1158
|
-
throw new
|
|
1522
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1159
1523
|
}
|
|
1160
1524
|
const named = [];
|
|
1161
1525
|
const classified = new Set;
|
|
1162
1526
|
for (const key of Reflect.ownKeys(policies)) {
|
|
1163
1527
|
const descriptor = Object.getOwnPropertyDescriptor(policies, key);
|
|
1164
1528
|
if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
|
|
1165
|
-
throw new
|
|
1529
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1166
1530
|
}
|
|
1167
1531
|
const name = params.models[key]?.__name ?? key;
|
|
1168
1532
|
if (classified.has(name)) {
|
|
1169
|
-
throw new
|
|
1533
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
|
|
1170
1534
|
}
|
|
1171
1535
|
classified.add(name);
|
|
1172
1536
|
named.push([name, descriptor.value]);
|
|
@@ -1295,326 +1659,6 @@ var templates = {
|
|
|
1295
1659
|
fields
|
|
1296
1660
|
};
|
|
1297
1661
|
|
|
1298
|
-
// packages/dsl/src/to-ir.ts
|
|
1299
|
-
import {
|
|
1300
|
-
EMPTY_EXTENSION_REGISTRY,
|
|
1301
|
-
VibeError as VibeError3,
|
|
1302
|
-
assertValidSchemaIR,
|
|
1303
|
-
exactIntegerLiteral,
|
|
1304
|
-
mergeExtensionDeclarations
|
|
1305
|
-
} from "@vibeorm/schema";
|
|
1306
|
-
function fieldType(params) {
|
|
1307
|
-
const { column } = params;
|
|
1308
|
-
switch (column.__columnType) {
|
|
1309
|
-
case "string":
|
|
1310
|
-
return "String";
|
|
1311
|
-
case "int":
|
|
1312
|
-
return "Int";
|
|
1313
|
-
case "bigint":
|
|
1314
|
-
return "BigInt";
|
|
1315
|
-
case "float":
|
|
1316
|
-
return "Float";
|
|
1317
|
-
case "decimal":
|
|
1318
|
-
return "Decimal";
|
|
1319
|
-
case "boolean":
|
|
1320
|
-
return "Boolean";
|
|
1321
|
-
case "datetime":
|
|
1322
|
-
return "DateTime";
|
|
1323
|
-
case "json":
|
|
1324
|
-
return "Json";
|
|
1325
|
-
case "bytes":
|
|
1326
|
-
return "Bytes";
|
|
1327
|
-
case "id":
|
|
1328
|
-
return column.__config.type === "autoincrement" ? "Int" : "String";
|
|
1329
|
-
case "enum":
|
|
1330
|
-
return column.__enumName === undefined ? "String" : { enum: column.__enumName };
|
|
1331
|
-
case "unsupported":
|
|
1332
|
-
return "String";
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
function idDefault(params) {
|
|
1336
|
-
switch (params.column.__config.type) {
|
|
1337
|
-
case "ulid":
|
|
1338
|
-
return { kind: "ulid" };
|
|
1339
|
-
case "cuid":
|
|
1340
|
-
return { kind: "cuid" };
|
|
1341
|
-
case "uuid":
|
|
1342
|
-
return { kind: "uuid" };
|
|
1343
|
-
case "nanoid":
|
|
1344
|
-
return { kind: "nanoid" };
|
|
1345
|
-
case "autoincrement":
|
|
1346
|
-
return { kind: "autoincrement" };
|
|
1347
|
-
default:
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
function fieldDefault(params) {
|
|
1352
|
-
const { column } = params;
|
|
1353
|
-
if (column.__columnType === "id")
|
|
1354
|
-
return idDefault({ column });
|
|
1355
|
-
const { dbgenerated, default: value } = column.__config;
|
|
1356
|
-
if (typeof dbgenerated === "string")
|
|
1357
|
-
return { kind: "dbgenerated", sql: dbgenerated };
|
|
1358
|
-
if (value === undefined)
|
|
1359
|
-
return;
|
|
1360
|
-
if (column.__columnType === "datetime" && value === "now")
|
|
1361
|
-
return { kind: "now" };
|
|
1362
|
-
if (column.__columnType === "enum" && typeof value === "string") {
|
|
1363
|
-
return { kind: "enumValue", value };
|
|
1364
|
-
}
|
|
1365
|
-
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
1366
|
-
return { kind: "literal", value };
|
|
1367
|
-
}
|
|
1368
|
-
if (typeof value === "bigint")
|
|
1369
|
-
return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
|
|
1370
|
-
return;
|
|
1371
|
-
}
|
|
1372
|
-
function nativeType(params) {
|
|
1373
|
-
const { column } = params;
|
|
1374
|
-
if (column.__columnType === "enum") {
|
|
1375
|
-
const { maxLength, text } = column.__config;
|
|
1376
|
-
if (maxLength !== undefined || text !== undefined) {
|
|
1377
|
-
const invalid = column.__enumStorage !== "string" || maxLength !== undefined && (typeof maxLength !== "number" || !Number.isSafeInteger(maxLength) || maxLength <= 0) || text !== undefined && typeof text !== "boolean" || text === true && maxLength !== undefined;
|
|
1378
|
-
if (invalid) {
|
|
1379
|
-
throw new VibeError3({
|
|
1380
|
-
code: "VIBE_SCHEMA_INVALID",
|
|
1381
|
-
message: "Enum text/maxLength options require string storage and either a positive integer maxLength or text: true, not both",
|
|
1382
|
-
meta: { issues: [{ rule: "enum-string-storage-options", enum: column.__enumName }] }
|
|
1383
|
-
});
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
if (column.__columnType === "string" || column.__columnType === "enum" && column.__enumStorage === "string") {
|
|
1388
|
-
const { maxLength, text } = column.__config;
|
|
1389
|
-
if (typeof maxLength === "number")
|
|
1390
|
-
return `VarChar(${maxLength})`;
|
|
1391
|
-
if (text === true)
|
|
1392
|
-
return "Text";
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
if (column.__columnType === "decimal") {
|
|
1396
|
-
const { precision, scale } = column.__config;
|
|
1397
|
-
if (typeof precision === "number")
|
|
1398
|
-
return `Decimal(${precision}, ${scale ?? 0})`;
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
if (column.__columnType === "datetime" && column.__config.tz === true) {
|
|
1402
|
-
return "Timestamptz";
|
|
1403
|
-
}
|
|
1404
|
-
if (column.__columnType === "unsupported") {
|
|
1405
|
-
return String(column.__config.dbType);
|
|
1406
|
-
}
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
function buildField(params) {
|
|
1410
|
-
const { name, column, extensionNativeType } = params;
|
|
1411
|
-
const config = column.__config;
|
|
1412
|
-
const isList = column.__columnType !== "id" && config.array === true;
|
|
1413
|
-
const dbName = typeof config.map === "string" ? config.map : undefined;
|
|
1414
|
-
const documentation = typeof config.comment === "string" ? config.comment : undefined;
|
|
1415
|
-
const collation = typeof config.collation === "string" ? config.collation : undefined;
|
|
1416
|
-
const defaultValue = fieldDefault({ column });
|
|
1417
|
-
const native = extensionNativeType ?? nativeType({ column });
|
|
1418
|
-
return {
|
|
1419
|
-
name,
|
|
1420
|
-
type: fieldType({ column }),
|
|
1421
|
-
isList,
|
|
1422
|
-
isOptional: config.optional === true,
|
|
1423
|
-
isId: column.__columnType === "id",
|
|
1424
|
-
isUnique: config.unique === true,
|
|
1425
|
-
isUpdatedAt: column.__columnType === "datetime" && config.updatedAt === true,
|
|
1426
|
-
...collation === undefined ? {} : { collation },
|
|
1427
|
-
...dbName === undefined ? {} : { dbName },
|
|
1428
|
-
...defaultValue === undefined ? {} : { default: defaultValue },
|
|
1429
|
-
...native === undefined ? {} : { nativeType: native },
|
|
1430
|
-
...column.__columnType === "unsupported" ? { isUnsupported: true } : {},
|
|
1431
|
-
...documentation === undefined ? {} : { documentation }
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
function buildRelation(params) {
|
|
1435
|
-
const { relation, plan } = params;
|
|
1436
|
-
return {
|
|
1437
|
-
name: relation.name,
|
|
1438
|
-
model: relation.model,
|
|
1439
|
-
targetModel: relation.targetModel,
|
|
1440
|
-
type: resolveRelationType({ relation, plan }),
|
|
1441
|
-
isList: relation.isList,
|
|
1442
|
-
isOptional: relation.isOptional,
|
|
1443
|
-
...relation.relationName === undefined ? {} : { relationName: relation.relationName },
|
|
1444
|
-
...relation.fields === undefined ? {} : { fields: [...relation.fields] },
|
|
1445
|
-
...relation.references === undefined ? {} : { references: [...relation.references] },
|
|
1446
|
-
...relation.onDelete === undefined ? {} : { onDelete: relation.onDelete },
|
|
1447
|
-
...relation.onUpdate === undefined ? {} : { onUpdate: relation.onUpdate }
|
|
1448
|
-
};
|
|
1449
|
-
}
|
|
1450
|
-
var INDEX_KINDS = {
|
|
1451
|
-
BTree: "btree",
|
|
1452
|
-
Hash: "hash",
|
|
1453
|
-
Gin: "gin",
|
|
1454
|
-
Gist: "gist",
|
|
1455
|
-
SpGist: "spgist",
|
|
1456
|
-
Brin: "brin"
|
|
1457
|
-
};
|
|
1458
|
-
function buildIndex(params) {
|
|
1459
|
-
const { index } = params;
|
|
1460
|
-
const kind = index.isUnique ? "unique" : index.indexType === undefined ? "btree" : INDEX_KINDS[index.indexType];
|
|
1461
|
-
return {
|
|
1462
|
-
kind,
|
|
1463
|
-
fields: index.columns.map((name) => ({ name })),
|
|
1464
|
-
...index.where === undefined ? {} : { where: index.where },
|
|
1465
|
-
...index.nullsNotDistinct === true ? { nullsNotDistinct: true } : {},
|
|
1466
|
-
...index.clientName === undefined ? {} : { clientName: index.clientName }
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
|
-
function buildModel(params) {
|
|
1470
|
-
const { model, plan, extensions, declarations, issues } = params;
|
|
1471
|
-
const primaryKey = model.__primaryKey !== undefined && model.__primaryKey.length > 0 ? { fields: [...model.__primaryKey] } : undefined;
|
|
1472
|
-
const policy = model.__policy === undefined ? undefined : { field: model.__policy.field, context: model.__policy.context ?? model.__policy.field };
|
|
1473
|
-
const fields2 = [];
|
|
1474
|
-
for (const [name, column] of model.__columns) {
|
|
1475
|
-
const extensionTypeName = column.__config["extensionType"];
|
|
1476
|
-
if (typeof extensionTypeName !== "string") {
|
|
1477
|
-
fields2.push(buildField({ name, column }));
|
|
1478
|
-
continue;
|
|
1479
|
-
}
|
|
1480
|
-
const registered = extensions.fieldTypes.get(extensionTypeName);
|
|
1481
|
-
if (registered === undefined) {
|
|
1482
|
-
issues.push({
|
|
1483
|
-
rule: "extension-attribute",
|
|
1484
|
-
message: `Field "${model.__name}.${name}" uses extension type "${extensionTypeName}" but no configured extension provides it`,
|
|
1485
|
-
model: model.__name,
|
|
1486
|
-
field: name
|
|
1487
|
-
});
|
|
1488
|
-
continue;
|
|
1489
|
-
}
|
|
1490
|
-
const rawArgs = column.__config["extensionArgs"];
|
|
1491
|
-
const lowered = registered.fieldType.lower({
|
|
1492
|
-
model: model.__name,
|
|
1493
|
-
field: name,
|
|
1494
|
-
args: Array.isArray(rawArgs) ? rawArgs.filter((entry) => typeof entry === "string" || typeof entry === "number") : []
|
|
1495
|
-
});
|
|
1496
|
-
if ("error" in lowered) {
|
|
1497
|
-
issues.push({
|
|
1498
|
-
rule: "extension-attribute",
|
|
1499
|
-
message: `Field "${model.__name}.${name}": ${lowered.error}`,
|
|
1500
|
-
model: model.__name,
|
|
1501
|
-
field: name
|
|
1502
|
-
});
|
|
1503
|
-
continue;
|
|
1504
|
-
}
|
|
1505
|
-
fields2.push(buildField({ name, column, extensionNativeType: lowered.nativeType }));
|
|
1506
|
-
if (lowered.declaration !== undefined) {
|
|
1507
|
-
declarations.add({
|
|
1508
|
-
extension: registered.extension.name,
|
|
1509
|
-
model: model.__name,
|
|
1510
|
-
declaration: lowered.declaration
|
|
1511
|
-
});
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
for (const use of model.__extensions ?? []) {
|
|
1515
|
-
const registered = extensions.modelAttributes.get(use.attribute);
|
|
1516
|
-
if (registered === undefined) {
|
|
1517
|
-
issues.push({
|
|
1518
|
-
rule: "unknown-attribute",
|
|
1519
|
-
message: `Unknown block attribute "@@${use.attribute}" on model "${model.__name}" \u2014 no extension provides it`,
|
|
1520
|
-
model: model.__name
|
|
1521
|
-
});
|
|
1522
|
-
continue;
|
|
1523
|
-
}
|
|
1524
|
-
const parsed = registered.attribute.parse({
|
|
1525
|
-
model: model.__name,
|
|
1526
|
-
args: {
|
|
1527
|
-
named: { ...use.args ?? {} },
|
|
1528
|
-
...use.fields === undefined ? {} : { fields: use.fields }
|
|
1529
|
-
},
|
|
1530
|
-
fieldNames: fields2.map((field) => field.name)
|
|
1531
|
-
});
|
|
1532
|
-
if ("error" in parsed) {
|
|
1533
|
-
issues.push({
|
|
1534
|
-
rule: "extension-attribute",
|
|
1535
|
-
message: `@@${use.attribute} on "${model.__name}": ${parsed.error}`,
|
|
1536
|
-
model: model.__name
|
|
1537
|
-
});
|
|
1538
|
-
continue;
|
|
1539
|
-
}
|
|
1540
|
-
declarations.add({
|
|
1541
|
-
extension: registered.extension.name,
|
|
1542
|
-
model: model.__name,
|
|
1543
|
-
declaration: parsed.declaration
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
return {
|
|
1547
|
-
name: model.__name,
|
|
1548
|
-
...model.__map === undefined ? {} : { dbName: model.__map },
|
|
1549
|
-
fields: fields2,
|
|
1550
|
-
relations: (plan.relations.get(model.__name) ?? []).map((relation) => buildRelation({ relation, plan })),
|
|
1551
|
-
indexes: (plan.indexes.get(model.__name) ?? []).map((index) => buildIndex({ index })),
|
|
1552
|
-
...model.__checks === undefined || model.__checks.length === 0 ? {} : { checks: model.__checks.map((check) => ({ name: check.name, expression: check.expression })) },
|
|
1553
|
-
...policy === undefined ? {} : { policy },
|
|
1554
|
-
...primaryKey === undefined ? {} : { primaryKey },
|
|
1555
|
-
isView: false,
|
|
1556
|
-
...model.__comment === undefined ? {} : { documentation: model.__comment }
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
class ExtensionDeclarationCollector {
|
|
1561
|
-
byExtension = new Map;
|
|
1562
|
-
add(params) {
|
|
1563
|
-
const perModel = this.byExtension.get(params.extension) ?? new Map;
|
|
1564
|
-
this.byExtension.set(params.extension, perModel);
|
|
1565
|
-
const existing = perModel.get(params.model) ?? {};
|
|
1566
|
-
perModel.set(params.model, mergeExtensionDeclarations({ base: existing, next: params.declaration }));
|
|
1567
|
-
}
|
|
1568
|
-
toIR() {
|
|
1569
|
-
return [...this.byExtension.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([name, perModel]) => ({
|
|
1570
|
-
name,
|
|
1571
|
-
options: {
|
|
1572
|
-
models: Object.fromEntries([...perModel.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
|
|
1573
|
-
}
|
|
1574
|
-
}));
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
function toIR(params) {
|
|
1578
|
-
const { schema: schema2, provider = "postgresql" } = params;
|
|
1579
|
-
const extensions = params.extensions ?? EMPTY_EXTENSION_REGISTRY;
|
|
1580
|
-
const plan = planSchema({ schema: schema2 });
|
|
1581
|
-
const declarations = new ExtensionDeclarationCollector;
|
|
1582
|
-
const issues = [];
|
|
1583
|
-
const models = Array.from(schema2.__models.values(), (model) => {
|
|
1584
|
-
const built = buildModel({ model, plan, extensions, declarations, issues });
|
|
1585
|
-
const rls = schema2.__rls?.policies[model.__name];
|
|
1586
|
-
return rls === undefined ? built : { ...built, rls };
|
|
1587
|
-
});
|
|
1588
|
-
if (schema2.__rls !== undefined) {
|
|
1589
|
-
for (const name of Object.keys(schema2.__rls.policies)) {
|
|
1590
|
-
if (!schema2.__models.has(name)) {
|
|
1591
|
-
issues.push({ rule: "rls-unknown-model", model: name, message: `RLS classification references model "${name}" absent from the composed schema` });
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
const enums = Array.from(schema2.__enums, ([name, values]) => ({
|
|
1596
|
-
name,
|
|
1597
|
-
values: [...values],
|
|
1598
|
-
...schema2.__enumStorage.get(name) === "string" ? { storage: "string" } : {}
|
|
1599
|
-
}));
|
|
1600
|
-
if (issues.length > 0) {
|
|
1601
|
-
throw new VibeError3({
|
|
1602
|
-
code: "VIBE_SCHEMA_INVALID",
|
|
1603
|
-
message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
|
|
1604
|
-
meta: { issues }
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
const ir = {
|
|
1608
|
-
datasource: { provider },
|
|
1609
|
-
models,
|
|
1610
|
-
enums,
|
|
1611
|
-
extensions: declarations.toIR(),
|
|
1612
|
-
...schema2.__rls === undefined ? {} : { rls: { context: schema2.__rls.context } }
|
|
1613
|
-
};
|
|
1614
|
-
assertValidSchemaIR({ schema: ir });
|
|
1615
|
-
return ir;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
1662
|
// packages/dsl/src/validate.ts
|
|
1619
1663
|
function validateSchema(schema2) {
|
|
1620
1664
|
const errors = [];
|
|
@@ -1773,6 +1817,7 @@ export {
|
|
|
1773
1817
|
baseWithWorkspace,
|
|
1774
1818
|
base,
|
|
1775
1819
|
ChainableStringColumn,
|
|
1820
|
+
ChainableIntegerColumn,
|
|
1776
1821
|
ChainableIdColumn,
|
|
1777
1822
|
ChainableEnumColumn,
|
|
1778
1823
|
ChainableDecimalColumn,
|
|
@@ -1780,4 +1825,4 @@ export {
|
|
|
1780
1825
|
ChainableColumn
|
|
1781
1826
|
};
|
|
1782
1827
|
|
|
1783
|
-
//# debugId=
|
|
1828
|
+
//# debugId=88D4E8D0B695042E64756E2164756E21
|