@vibeorm/dsl 2.0.0-alpha.7 → 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 +24 -4
- package/dist/column.d.ts +13 -3
- package/dist/column.d.ts.map +1 -1
- package/dist/enum.d.ts +7 -0
- package/dist/enum.d.ts.map +1 -1
- package/dist/generator/prisma.d.ts.map +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +955 -709
- package/dist/index.js.map +12 -12
- 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 +4 -4
- package/dist/relations.d.ts.map +1 -1
- package/dist/schema.d.ts +5 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/to-ir.d.ts +13 -4
- package/dist/to-ir.d.ts.map +1 -1
- package/dist/types.d.ts +137 -15
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8,7 +8,8 @@ function cloneColumnDef(params) {
|
|
|
8
8
|
__name: column.__name,
|
|
9
9
|
__config: { ...column.__config },
|
|
10
10
|
__enumValues: column.__enumValues,
|
|
11
|
-
__enumName: column.__enumName
|
|
11
|
+
__enumName: column.__enumName,
|
|
12
|
+
__enumStorage: column.__enumStorage
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -19,6 +20,7 @@ class ChainableColumn {
|
|
|
19
20
|
__name;
|
|
20
21
|
__enumValues;
|
|
21
22
|
__enumName;
|
|
23
|
+
__enumStorage;
|
|
22
24
|
constructor(columnType, config = {}) {
|
|
23
25
|
this.__columnType = columnType;
|
|
24
26
|
this.__config = { ...config };
|
|
@@ -69,6 +71,10 @@ class ChainableStringColumn extends ChainableColumn {
|
|
|
69
71
|
this.__config.text = true;
|
|
70
72
|
return this;
|
|
71
73
|
}
|
|
74
|
+
collation(name) {
|
|
75
|
+
this.__config.collation = name;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
class ChainableDecimalColumn extends ChainableColumn {
|
|
@@ -119,6 +125,9 @@ class ChainableEnumColumn extends ChainableColumn {
|
|
|
119
125
|
if (params.enumName !== undefined) {
|
|
120
126
|
this.__enumName = params.enumName;
|
|
121
127
|
}
|
|
128
|
+
if (params.storage !== undefined) {
|
|
129
|
+
this.__enumStorage = params.storage;
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
default(value) {
|
|
124
133
|
this.__config.default = value;
|
|
@@ -128,8 +137,15 @@ class ChainableEnumColumn extends ChainableColumn {
|
|
|
128
137
|
function string(config = {}) {
|
|
129
138
|
return new ChainableStringColumn(config);
|
|
130
139
|
}
|
|
140
|
+
|
|
141
|
+
class ChainableIntegerColumn extends ChainableColumn {
|
|
142
|
+
autoincrement() {
|
|
143
|
+
this.__config.autoincrement = true;
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
131
147
|
function int(config = {}) {
|
|
132
|
-
return new
|
|
148
|
+
return new ChainableIntegerColumn("int", config);
|
|
133
149
|
}
|
|
134
150
|
function float(config = {}) {
|
|
135
151
|
return new ChainableColumn("float", config);
|
|
@@ -164,7 +180,7 @@ function bytes(config = {}) {
|
|
|
164
180
|
return new ChainableColumn("bytes", config);
|
|
165
181
|
}
|
|
166
182
|
function bigint(config = {}) {
|
|
167
|
-
return new
|
|
183
|
+
return new ChainableIntegerColumn("bigint", config);
|
|
168
184
|
}
|
|
169
185
|
function unsupported(dbType) {
|
|
170
186
|
const config = { dbType };
|
|
@@ -180,25 +196,49 @@ function extensionType(config) {
|
|
|
180
196
|
});
|
|
181
197
|
}
|
|
182
198
|
function enumCol(config) {
|
|
183
|
-
const { options, ...restConfig } = config;
|
|
184
|
-
return new ChainableEnumColumn({
|
|
199
|
+
const { options, storage, ...restConfig } = config;
|
|
200
|
+
return new ChainableEnumColumn({
|
|
201
|
+
options,
|
|
202
|
+
config: restConfig,
|
|
203
|
+
...storage === undefined ? {} : { storage }
|
|
204
|
+
});
|
|
185
205
|
}
|
|
186
206
|
|
|
187
207
|
// packages/dsl/src/enum.ts
|
|
208
|
+
function enumValuesOf(params) {
|
|
209
|
+
const entries = Object.fromEntries(params.options.map((option) => [option, option]));
|
|
210
|
+
return Object.freeze(entries);
|
|
211
|
+
}
|
|
188
212
|
function createEnum(config) {
|
|
189
|
-
const { name, options } = config;
|
|
213
|
+
const { name, options, storage = "database" } = config;
|
|
190
214
|
return {
|
|
191
215
|
__type: "enum",
|
|
192
216
|
__name: name,
|
|
193
217
|
__options: options,
|
|
218
|
+
__storage: storage,
|
|
219
|
+
values: enumValuesOf({ options }),
|
|
194
220
|
field(fieldConfig = {}) {
|
|
195
|
-
return new ChainableEnumColumn({ options, config: fieldConfig, enumName: name });
|
|
221
|
+
return new ChainableEnumColumn({ options, config: fieldConfig, enumName: name, storage });
|
|
196
222
|
}
|
|
197
223
|
};
|
|
198
224
|
}
|
|
199
225
|
|
|
200
226
|
// packages/dsl/src/generator/prisma.ts
|
|
201
|
-
import { 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";
|
|
202
242
|
|
|
203
243
|
// packages/dsl/src/naming.ts
|
|
204
244
|
function capitalize(params) {
|
|
@@ -237,17 +277,18 @@ function isChildrenDef(value) {
|
|
|
237
277
|
return typeof value === "object" && value !== null && "__type" in value && value.__type === "children";
|
|
238
278
|
}
|
|
239
279
|
function createIndexBuilder() {
|
|
240
|
-
const createIndex = (columns, type, where) => ({
|
|
280
|
+
const createIndex = (columns, type, where, map) => ({
|
|
241
281
|
__type: "index",
|
|
242
282
|
__columns: [...columns],
|
|
243
283
|
__indexType: type,
|
|
284
|
+
...map === undefined ? {} : { __map: map },
|
|
244
285
|
...where === undefined ? {} : { __where: where }
|
|
245
286
|
});
|
|
246
287
|
return {
|
|
247
288
|
on(...args) {
|
|
248
289
|
const [first, second] = args;
|
|
249
290
|
if (args.length === 2 && Array.isArray(first) && typeof second === "object" && !Array.isArray(second)) {
|
|
250
|
-
return createIndex(first, second.type, second.where);
|
|
291
|
+
return createIndex(first, second.type, second.where, second.map);
|
|
251
292
|
}
|
|
252
293
|
return createIndex(args);
|
|
253
294
|
},
|
|
@@ -260,6 +301,7 @@ function createIndexBuilder() {
|
|
|
260
301
|
return {
|
|
261
302
|
__type: "unique",
|
|
262
303
|
__columns: [...first],
|
|
304
|
+
...second.map === undefined ? {} : { __map: second.map },
|
|
263
305
|
...second.where === undefined ? {} : { __where: second.where },
|
|
264
306
|
...second.nullsNotDistinct === true ? { __nullsNotDistinct: true } : {},
|
|
265
307
|
...second.name === undefined ? {} : { __clientName: second.name }
|
|
@@ -320,7 +362,9 @@ function create(config) {
|
|
|
320
362
|
__map: config.map,
|
|
321
363
|
__primaryKey: config.primaryKey,
|
|
322
364
|
__checks: config.checks,
|
|
323
|
-
__extensions: config.extensions
|
|
365
|
+
__extensions: config.extensions,
|
|
366
|
+
__policy: config.policy,
|
|
367
|
+
__comment: config.comment
|
|
324
368
|
};
|
|
325
369
|
}
|
|
326
370
|
function getColumnNames(params) {
|
|
@@ -341,6 +385,9 @@ function detectOneToOnePairs(params) {
|
|
|
341
385
|
const allOnes = [];
|
|
342
386
|
for (const [modelName, modelRelations] of Object.entries(relations)) {
|
|
343
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
|
+
}
|
|
344
391
|
if (rel.__relationType === "one") {
|
|
345
392
|
allOnes.push({ modelName, relName, rel });
|
|
346
393
|
}
|
|
@@ -362,7 +409,7 @@ function detectOneToOnePairs(params) {
|
|
|
362
409
|
if (paired.has(keyB))
|
|
363
410
|
continue;
|
|
364
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;
|
|
365
|
-
if (!sameColumnPair)
|
|
412
|
+
if (!sameColumnPair || (a.rel.__alias ?? "") !== (b.rel.__alias ?? ""))
|
|
366
413
|
continue;
|
|
367
414
|
const { fkOwner, backRelation } = resolveFkOwner({ a, b, models });
|
|
368
415
|
const onDelete = fkOwner.rel.__onDelete ?? backRelation.rel.__onDelete;
|
|
@@ -392,21 +439,35 @@ function detectOneToOnePairs(params) {
|
|
|
392
439
|
}
|
|
393
440
|
function resolveFkOwner(params) {
|
|
394
441
|
const { a, b, models } = params;
|
|
395
|
-
const
|
|
396
|
-
|
|
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;
|
|
397
454
|
if (aFromIsId && !bFromIsId)
|
|
398
455
|
return { fkOwner: b, backRelation: a };
|
|
399
456
|
if (!aFromIsId && bFromIsId)
|
|
400
457
|
return { fkOwner: a, backRelation: b };
|
|
401
|
-
|
|
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) {
|
|
402
461
|
return { fkOwner: a, backRelation: b };
|
|
403
462
|
}
|
|
404
|
-
if (
|
|
463
|
+
if (bActions && !aActions) {
|
|
405
464
|
return { fkOwner: b, backRelation: a };
|
|
406
465
|
}
|
|
407
466
|
const aLooksFk = looksLikeForeignKey({ column: a.rel.__fromColumn });
|
|
408
467
|
const bLooksFk = looksLikeForeignKey({ column: b.rel.__fromColumn });
|
|
409
|
-
|
|
468
|
+
if (aLooksFk === bLooksFk)
|
|
469
|
+
return ownershipError();
|
|
470
|
+
return aLooksFk ? { fkOwner: a, backRelation: b } : { fkOwner: b, backRelation: a };
|
|
410
471
|
}
|
|
411
472
|
function looksLikeForeignKey(params) {
|
|
412
473
|
const { column } = params;
|
|
@@ -432,7 +493,7 @@ function planExplicitRelations(params) {
|
|
|
432
493
|
planned.push({ ...base, kind: "backOne", isList: false, isOptional: true });
|
|
433
494
|
continue;
|
|
434
495
|
}
|
|
435
|
-
const ownsFk = mergeInfo?.role === "fkOwner" || rel.__fromModel === modelName;
|
|
496
|
+
const ownsFk = mergeInfo?.role === "fkOwner" || rel.__ownsForeignKey !== false && rel.__fromModel === modelName;
|
|
436
497
|
if (!ownsFk) {
|
|
437
498
|
planned.push({ ...base, kind: "backOne", isList: false, isOptional: rel.__optional });
|
|
438
499
|
continue;
|
|
@@ -549,10 +610,13 @@ function planIndexes(params) {
|
|
|
549
610
|
isUnique,
|
|
550
611
|
columns: [...index.__columns],
|
|
551
612
|
indexType: index.__indexType,
|
|
613
|
+
...index.__map === undefined ? {} : { map: index.__map },
|
|
552
614
|
...index.__where === undefined ? {} : { where: index.__where },
|
|
553
615
|
...index.__nullsNotDistinct === true ? { nullsNotDistinct: true } : {},
|
|
554
616
|
...index.__clientName === undefined ? {} : { clientName: index.__clientName }
|
|
555
617
|
});
|
|
618
|
+
} else if (index.__map !== undefined) {
|
|
619
|
+
byKey.set(key, { ...existing, map: index.__map, indexType: index.__indexType ?? existing.indexType });
|
|
556
620
|
} else if (index.__indexType !== undefined && existing.indexType === undefined) {
|
|
557
621
|
byKey.set(key, { ...existing, indexType: index.__indexType });
|
|
558
622
|
}
|
|
@@ -606,815 +670,994 @@ function findCounterparts(params) {
|
|
|
606
670
|
return (plan.relations.get(relation.targetModel) ?? []).filter((candidate) => candidate !== relation && candidate.targetModel === relation.model && (candidate.relationName ?? "") === nameKey);
|
|
607
671
|
}
|
|
608
672
|
|
|
609
|
-
// packages/dsl/src/
|
|
610
|
-
function
|
|
673
|
+
// packages/dsl/src/to-ir.ts
|
|
674
|
+
function fieldType(params) {
|
|
611
675
|
const { column } = params;
|
|
612
|
-
const list = column.__config.array === true ? "[]" : "";
|
|
613
676
|
switch (column.__columnType) {
|
|
614
677
|
case "string":
|
|
615
|
-
return
|
|
678
|
+
return "String";
|
|
616
679
|
case "int":
|
|
617
|
-
return
|
|
680
|
+
return "Int";
|
|
618
681
|
case "bigint":
|
|
619
|
-
return
|
|
682
|
+
return "BigInt";
|
|
620
683
|
case "float":
|
|
621
|
-
return
|
|
684
|
+
return "Float";
|
|
622
685
|
case "decimal":
|
|
623
|
-
return
|
|
686
|
+
return "Decimal";
|
|
624
687
|
case "boolean":
|
|
625
|
-
return
|
|
688
|
+
return "Boolean";
|
|
626
689
|
case "datetime":
|
|
627
|
-
return
|
|
690
|
+
return "DateTime";
|
|
628
691
|
case "json":
|
|
629
|
-
return
|
|
692
|
+
return "Json";
|
|
630
693
|
case "bytes":
|
|
631
|
-
return
|
|
694
|
+
return "Bytes";
|
|
632
695
|
case "id":
|
|
633
696
|
return column.__config.type === "autoincrement" ? "Int" : "String";
|
|
634
697
|
case "enum":
|
|
635
|
-
return column.__enumName === undefined ?
|
|
636
|
-
case "unsupported":
|
|
637
|
-
|
|
638
|
-
if (typeof extensionTypeName === "string") {
|
|
639
|
-
const rawArgs = column.__config["extensionArgs"];
|
|
640
|
-
const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
|
|
641
|
-
return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
|
|
642
|
-
}
|
|
643
|
-
return `Unsupported("${String(column.__config.dbType)}")${list}`;
|
|
644
|
-
}
|
|
698
|
+
return column.__enumName === undefined ? "String" : { enum: column.__enumName };
|
|
699
|
+
case "unsupported":
|
|
700
|
+
return "String";
|
|
645
701
|
}
|
|
646
702
|
}
|
|
647
|
-
function
|
|
648
|
-
|
|
649
|
-
switch (idType) {
|
|
703
|
+
function idDefault(params) {
|
|
704
|
+
switch (params.column.__config.type) {
|
|
650
705
|
case "ulid":
|
|
706
|
+
return { kind: "ulid" };
|
|
651
707
|
case "cuid":
|
|
708
|
+
return { kind: "cuid" };
|
|
652
709
|
case "uuid":
|
|
710
|
+
return { kind: "uuid" };
|
|
653
711
|
case "nanoid":
|
|
712
|
+
return { kind: "nanoid" };
|
|
654
713
|
case "autoincrement":
|
|
655
|
-
return
|
|
714
|
+
return { kind: "autoincrement" };
|
|
656
715
|
default:
|
|
657
716
|
return;
|
|
658
717
|
}
|
|
659
718
|
}
|
|
660
|
-
function
|
|
719
|
+
function fieldDefault(params) {
|
|
661
720
|
const { column } = params;
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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` });
|
|
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" };
|
|
665
735
|
}
|
|
736
|
+
if (typeof dbgenerated === "string")
|
|
737
|
+
return { kind: "dbgenerated", sql: dbgenerated };
|
|
666
738
|
if (value === undefined)
|
|
667
739
|
return;
|
|
668
|
-
if (column.
|
|
669
|
-
|
|
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 }) };
|
|
744
|
+
}
|
|
745
|
+
if (column.__columnType === "json" && typeof value !== "string") {
|
|
746
|
+
return { kind: "literal", value: serializeJsonDefault({ value }) };
|
|
670
747
|
}
|
|
748
|
+
if (column.__columnType === "datetime" && value === "now")
|
|
749
|
+
return { kind: "now" };
|
|
671
750
|
if (column.__columnType === "enum" && typeof value === "string") {
|
|
672
|
-
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 };
|
|
673
758
|
}
|
|
674
|
-
if (typeof value === "string")
|
|
675
|
-
return `@default("${value}")`;
|
|
676
|
-
if (typeof value === "boolean" || typeof value === "number")
|
|
677
|
-
return `@default(${value})`;
|
|
678
759
|
if (typeof value === "bigint")
|
|
679
|
-
return
|
|
680
|
-
|
|
760
|
+
return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
|
|
761
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `Unsupported default for ${column.__columnType}` });
|
|
681
762
|
}
|
|
682
|
-
function
|
|
763
|
+
function nativeType(params) {
|
|
683
764
|
const { column } = params;
|
|
684
|
-
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") {
|
|
685
779
|
const { maxLength, text } = column.__config;
|
|
686
780
|
if (typeof maxLength === "number")
|
|
687
|
-
return
|
|
781
|
+
return `VarChar(${maxLength})`;
|
|
688
782
|
if (text === true)
|
|
689
|
-
return "
|
|
783
|
+
return "Text";
|
|
690
784
|
return;
|
|
691
785
|
}
|
|
692
786
|
if (column.__columnType === "decimal") {
|
|
693
787
|
const { precision, scale } = column.__config;
|
|
694
788
|
if (typeof precision === "number")
|
|
695
|
-
return
|
|
789
|
+
return `Decimal(${precision}, ${scale ?? 0})`;
|
|
696
790
|
return;
|
|
697
791
|
}
|
|
698
792
|
if (column.__columnType === "datetime" && column.__config.tz === true) {
|
|
699
|
-
return "
|
|
793
|
+
return "Timestamptz";
|
|
794
|
+
}
|
|
795
|
+
if (column.__columnType === "unsupported") {
|
|
796
|
+
return String(column.__config.dbType);
|
|
700
797
|
}
|
|
701
798
|
return;
|
|
702
799
|
}
|
|
703
|
-
function
|
|
704
|
-
const { name, column } = params;
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
parts.push(nativeType);
|
|
728
|
-
const columnLine = parts.join(" ");
|
|
729
|
-
const comment = column.__config.comment;
|
|
730
|
-
if (typeof comment === "string") {
|
|
731
|
-
const commentLines = comment.split(`
|
|
732
|
-
`).map((line) => ` /// ${line}`);
|
|
733
|
-
return [...commentLines, columnLine].join(`
|
|
734
|
-
`);
|
|
735
|
-
}
|
|
736
|
-
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
|
+
};
|
|
737
824
|
}
|
|
738
|
-
function
|
|
739
|
-
const { relation } = params;
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (relation.fields === undefined || relation.references === undefined) {
|
|
754
|
-
throw internalError({
|
|
755
|
-
message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
|
|
756
|
-
meta: { model: relation.model, relation: name }
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
args.push(`fields: [${relation.fields.join(", ")}]`);
|
|
760
|
-
args.push(`references: [${relation.references.join(", ")}]`);
|
|
761
|
-
if (relation.onDelete !== undefined)
|
|
762
|
-
args.push(`onDelete: ${relation.onDelete}`);
|
|
763
|
-
if (relation.onUpdate !== undefined)
|
|
764
|
-
args.push(`onUpdate: ${relation.onUpdate}`);
|
|
765
|
-
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
|
+
};
|
|
766
840
|
}
|
|
767
|
-
|
|
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) {
|
|
768
850
|
const { index } = params;
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
function formatExtensionValue(params) {
|
|
780
|
-
const { value } = params;
|
|
781
|
-
if (typeof value === "string")
|
|
782
|
-
return `"${value}"`;
|
|
783
|
-
if (Array.isArray(value)) {
|
|
784
|
-
return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
|
|
785
|
-
}
|
|
786
|
-
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
|
+
};
|
|
787
860
|
}
|
|
788
|
-
function
|
|
789
|
-
const { model, plan } = params;
|
|
790
|
-
const
|
|
791
|
-
|
|
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 = [];
|
|
792
866
|
for (const [name, column] of model.__columns) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
for (const relation of plan.relations.get(model.__name) ?? []) {
|
|
798
|
-
lines.push(formatRelation({ relation }));
|
|
799
|
-
}
|
|
800
|
-
lines.push("");
|
|
801
|
-
lines.push(" //> Indexes");
|
|
802
|
-
lines.push("");
|
|
803
|
-
for (const index of plan.indexes.get(model.__name) ?? []) {
|
|
804
|
-
lines.push(formatIndex({ index }));
|
|
805
|
-
}
|
|
806
|
-
for (const check of model.__checks ?? []) {
|
|
807
|
-
lines.push(` @@check(${JSON.stringify(check.expression)}, name: ${JSON.stringify(check.name)})`);
|
|
808
|
-
}
|
|
809
|
-
for (const use of model.__extensions ?? []) {
|
|
810
|
-
const args = [];
|
|
811
|
-
if (use.fields !== undefined)
|
|
812
|
-
args.push(`[${use.fields.join(", ")}]`);
|
|
813
|
-
for (const [key, value] of Object.entries(use.args ?? {})) {
|
|
814
|
-
args.push(`${key}: ${formatExtensionValue({ value })}`);
|
|
867
|
+
const extensionTypeName = column.__config["extensionType"];
|
|
868
|
+
if (typeof extensionTypeName !== "string") {
|
|
869
|
+
fields.push(buildField({ name, column }));
|
|
870
|
+
continue;
|
|
815
871
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return lines.join(`
|
|
826
|
-
`);
|
|
827
|
-
}
|
|
828
|
-
function formatEnum(params) {
|
|
829
|
-
const { name, values } = params;
|
|
830
|
-
return [`enum ${name} {`, ...values.map((value) => ` ${value}`), "}"].join(`
|
|
831
|
-
`);
|
|
832
|
-
}
|
|
833
|
-
function generatePrisma(schema, _config = {}) {
|
|
834
|
-
const plan = planSchema({ schema });
|
|
835
|
-
const sections = [];
|
|
836
|
-
if (schema.__enums.size > 0) {
|
|
837
|
-
sections.push("// ============================================");
|
|
838
|
-
sections.push("// Enums");
|
|
839
|
-
sections.push("// ============================================");
|
|
840
|
-
sections.push("");
|
|
841
|
-
for (const [name, values] of schema.__enums) {
|
|
842
|
-
sections.push(formatEnum({ name, values }));
|
|
843
|
-
sections.push("");
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
sections.push("// ============================================");
|
|
847
|
-
sections.push("// Models");
|
|
848
|
-
sections.push("// ============================================");
|
|
849
|
-
sections.push("");
|
|
850
|
-
for (const model of schema.__models.values()) {
|
|
851
|
-
sections.push(formatModel({ model, plan }));
|
|
852
|
-
sections.push("");
|
|
853
|
-
}
|
|
854
|
-
return sections.join(`
|
|
855
|
-
`).trim();
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// packages/dsl/src/relations.ts
|
|
859
|
-
function createColumnRef(params) {
|
|
860
|
-
const { model, column } = params;
|
|
861
|
-
return {
|
|
862
|
-
__type: "columnRef",
|
|
863
|
-
__model: model,
|
|
864
|
-
__column: column,
|
|
865
|
-
through(junction) {
|
|
866
|
-
return {
|
|
867
|
-
__type: "throughRef",
|
|
868
|
-
__model: model,
|
|
869
|
-
__column: column,
|
|
870
|
-
__junction: junction.__model,
|
|
871
|
-
__junctionColumn: junction.__column
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
function extractRefInfo(params) {
|
|
877
|
-
const { ref } = params;
|
|
878
|
-
return { model: ref.__model, column: ref.__column };
|
|
879
|
-
}
|
|
880
|
-
function createRelationFactory(params) {
|
|
881
|
-
const { relationType, targetModelName } = params;
|
|
882
|
-
return (config) => {
|
|
883
|
-
const from = extractRefInfo({ ref: config.from });
|
|
884
|
-
const to = extractRefInfo({ ref: config.to });
|
|
885
|
-
let throughJunction;
|
|
886
|
-
let throughFromCol;
|
|
887
|
-
let throughToCol;
|
|
888
|
-
if (config.from.__type === "throughRef") {
|
|
889
|
-
throughJunction = config.from.__junction;
|
|
890
|
-
throughFromCol = config.from.__junctionColumn;
|
|
891
|
-
}
|
|
892
|
-
if (config.to.__type === "throughRef") {
|
|
893
|
-
throughJunction = throughJunction ?? config.to.__junction;
|
|
894
|
-
throughToCol = config.to.__junctionColumn;
|
|
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;
|
|
895
881
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
__fromColumn: from.column,
|
|
902
|
-
__toModel: to.model,
|
|
903
|
-
__toColumn: to.column,
|
|
904
|
-
__throughJunction: throughJunction,
|
|
905
|
-
__throughFromCol: throughFromCol,
|
|
906
|
-
__throughToCol: throughToCol,
|
|
907
|
-
__optional: config.optional ?? relationType === "many",
|
|
908
|
-
__alias: config.alias,
|
|
909
|
-
__onDelete: config.onDelete,
|
|
910
|
-
__onUpdate: config.onUpdate
|
|
911
|
-
};
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
function createRelationsBuilder(params) {
|
|
915
|
-
const { models } = params;
|
|
916
|
-
const builder = {};
|
|
917
|
-
const oneObj = {};
|
|
918
|
-
const manyObj = {};
|
|
919
|
-
for (const modelName of Object.keys(models)) {
|
|
920
|
-
oneObj[modelName] = createRelationFactory({ relationType: "one", targetModelName: modelName });
|
|
921
|
-
manyObj[modelName] = createRelationFactory({
|
|
922
|
-
relationType: "many",
|
|
923
|
-
targetModelName: modelName
|
|
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") : []
|
|
924
887
|
});
|
|
925
|
-
|
|
926
|
-
|
|
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
|
+
});
|
|
927
895
|
continue;
|
|
928
|
-
const columnRefs = {};
|
|
929
|
-
for (const columnName of model.__columns.keys()) {
|
|
930
|
-
columnRefs[columnName] = createColumnRef({ model: modelName, column: columnName });
|
|
931
896
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
const result = {};
|
|
940
|
-
for (const [modelName, relations] of Object.entries(params.output)) {
|
|
941
|
-
if (relations) {
|
|
942
|
-
result[modelName] = relations;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
return result;
|
|
946
|
-
}
|
|
947
|
-
function defineRelations(models, callback) {
|
|
948
|
-
const builder = createRelationsBuilder({ models });
|
|
949
|
-
return processRelationsOutput({ output: callback(builder) });
|
|
950
|
-
}
|
|
951
|
-
function defineRelationsPart(models, callback) {
|
|
952
|
-
const builder = createRelationsBuilder({ models });
|
|
953
|
-
return processRelationsOutput({ output: callback(builder) });
|
|
954
|
-
}
|
|
955
|
-
function mergeRelations(...parts) {
|
|
956
|
-
const result = {};
|
|
957
|
-
for (const part of parts) {
|
|
958
|
-
for (const [modelName, relations] of Object.entries(part)) {
|
|
959
|
-
const existing = result[modelName] ?? {};
|
|
960
|
-
result[modelName] = { ...existing, ...relations };
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
return result;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// packages/dsl/src/schema.ts
|
|
967
|
-
function isModelDef(value) {
|
|
968
|
-
return typeof value === "object" && value !== null && "__type" in value && value.__type === "model";
|
|
969
|
-
}
|
|
970
|
-
function isEnumDef(value) {
|
|
971
|
-
return typeof value === "object" && value !== null && "__type" in value && value.__type === "enum";
|
|
972
|
-
}
|
|
973
|
-
function addModelTree(params) {
|
|
974
|
-
const { model, models } = params;
|
|
975
|
-
models.set(model.__name, model);
|
|
976
|
-
for (const childModel of model.__children.values()) {
|
|
977
|
-
addModelTree({ model: childModel, models });
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
function extractModelsAndEnums(params) {
|
|
981
|
-
const { input, models, standaloneEnums } = params;
|
|
982
|
-
for (const value of Object.values(input)) {
|
|
983
|
-
if (isModelDef(value)) {
|
|
984
|
-
addModelTree({ model: value, models });
|
|
985
|
-
} else if (isEnumDef(value)) {
|
|
986
|
-
standaloneEnums.set(value.__name, value.__options);
|
|
987
|
-
} else if (typeof value === "object" && value !== null) {
|
|
988
|
-
extractModelsAndEnums({ input: value, models, standaloneEnums });
|
|
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
|
+
});
|
|
989
904
|
}
|
|
990
905
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
if (!enums.has(column.__enumName)) {
|
|
1001
|
-
enums.set(column.__enumName, column.__enumValues);
|
|
1002
|
-
}
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
const name = inlineEnumName({ modelName, columnName });
|
|
1006
|
-
enums.set(name, column.__enumValues);
|
|
1007
|
-
column.__enumName = name;
|
|
906
|
+
for (const use of model.__extensions ?? []) {
|
|
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;
|
|
1008
915
|
}
|
|
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
|
+
});
|
|
1009
937
|
}
|
|
1010
|
-
return
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
return id({ type: "ulid" });
|
|
1032
|
-
},
|
|
1033
|
-
get createdAt() {
|
|
1034
|
-
return datetime({ default: "now", tz: true, index: true });
|
|
1035
|
-
},
|
|
1036
|
-
get updatedAt() {
|
|
1037
|
-
return datetime({ default: "now", updatedAt: true, tz: true, index: true });
|
|
1038
|
-
},
|
|
1039
|
-
get deletedAt() {
|
|
1040
|
-
return datetime({ optional: true, index: true });
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
var baseWithWorkspace = {
|
|
1044
|
-
get id() {
|
|
1045
|
-
return id({ type: "ulid" });
|
|
1046
|
-
},
|
|
1047
|
-
get createdAt() {
|
|
1048
|
-
return datetime({ default: "now", tz: true, index: true });
|
|
1049
|
-
},
|
|
1050
|
-
get updatedAt() {
|
|
1051
|
-
return datetime({ default: "now", updatedAt: true, tz: true, index: true });
|
|
1052
|
-
},
|
|
1053
|
-
get deletedAt() {
|
|
1054
|
-
return datetime({ optional: true, index: true });
|
|
1055
|
-
},
|
|
1056
|
-
get workspaceId() {
|
|
1057
|
-
return string({ index: true });
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
1060
|
-
var withMetadata = {
|
|
1061
|
-
get metadata() {
|
|
1062
|
-
return json({ optional: true });
|
|
1063
|
-
},
|
|
1064
|
-
get settings() {
|
|
1065
|
-
return json({ optional: true });
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
var withActive = {
|
|
1069
|
-
get isActive() {
|
|
1070
|
-
return boolean({ default: true });
|
|
1071
|
-
}
|
|
1072
|
-
};
|
|
1073
|
-
var withAudit = {
|
|
1074
|
-
get createdById() {
|
|
1075
|
-
return string({ optional: true, index: true });
|
|
1076
|
-
},
|
|
1077
|
-
get updatedById() {
|
|
1078
|
-
return string({ optional: true, index: true });
|
|
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
|
+
};
|
|
950
|
+
}
|
|
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 }));
|
|
1079
959
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
+
}));
|
|
1084
967
|
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
|
|
968
|
+
}
|
|
969
|
+
function toIR(params) {
|
|
970
|
+
const { schema, provider = "postgresql" } = params;
|
|
971
|
+
const extensions = params.extensions ?? EMPTY_EXTENSION_REGISTRY;
|
|
972
|
+
const plan = planSchema({ schema });
|
|
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
|
+
}
|
|
985
|
+
}
|
|
1092
986
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
get description() {
|
|
1105
|
-
return string({ optional: true });
|
|
1106
|
-
},
|
|
1107
|
-
get externalId() {
|
|
1108
|
-
return string({ index: true });
|
|
1109
|
-
},
|
|
1110
|
-
get metadata() {
|
|
1111
|
-
return json({ optional: true });
|
|
1112
|
-
},
|
|
1113
|
-
get settings() {
|
|
1114
|
-
return json({ optional: true });
|
|
1115
|
-
},
|
|
1116
|
-
get isActive() {
|
|
1117
|
-
return boolean({ default: true });
|
|
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
|
+
});
|
|
1118
998
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
};
|
|
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;
|
|
1008
|
+
}
|
|
1130
1009
|
|
|
1131
|
-
// packages/dsl/src/
|
|
1132
|
-
|
|
1133
|
-
EMPTY_EXTENSION_REGISTRY,
|
|
1134
|
-
VibeError,
|
|
1135
|
-
assertValidSchemaIR,
|
|
1136
|
-
mergeExtensionDeclarations
|
|
1137
|
-
} from "@vibeorm/schema";
|
|
1138
|
-
function fieldType(params) {
|
|
1010
|
+
// packages/dsl/src/generator/prisma.ts
|
|
1011
|
+
function prismaType(params) {
|
|
1139
1012
|
const { column } = params;
|
|
1013
|
+
const list = column.__config.array === true ? "[]" : "";
|
|
1140
1014
|
switch (column.__columnType) {
|
|
1141
1015
|
case "string":
|
|
1142
|
-
return
|
|
1016
|
+
return `String${list}`;
|
|
1143
1017
|
case "int":
|
|
1144
|
-
return
|
|
1018
|
+
return `Int${list}`;
|
|
1145
1019
|
case "bigint":
|
|
1146
|
-
return
|
|
1020
|
+
return `BigInt${list}`;
|
|
1147
1021
|
case "float":
|
|
1148
|
-
return
|
|
1022
|
+
return `Float${list}`;
|
|
1149
1023
|
case "decimal":
|
|
1150
|
-
return
|
|
1024
|
+
return `Decimal${list}`;
|
|
1151
1025
|
case "boolean":
|
|
1152
|
-
return
|
|
1026
|
+
return `Boolean${list}`;
|
|
1153
1027
|
case "datetime":
|
|
1154
|
-
return
|
|
1028
|
+
return `DateTime${list}`;
|
|
1155
1029
|
case "json":
|
|
1156
|
-
return
|
|
1030
|
+
return `Json${list}`;
|
|
1157
1031
|
case "bytes":
|
|
1158
|
-
return
|
|
1032
|
+
return `Bytes${list}`;
|
|
1159
1033
|
case "id":
|
|
1160
1034
|
return column.__config.type === "autoincrement" ? "Int" : "String";
|
|
1161
1035
|
case "enum":
|
|
1162
|
-
return column.__enumName === undefined ?
|
|
1163
|
-
case "unsupported":
|
|
1164
|
-
|
|
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}`;
|
|
1045
|
+
}
|
|
1165
1046
|
}
|
|
1166
1047
|
}
|
|
1167
|
-
function
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
default
|
|
1180
|
-
|
|
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}())`;
|
|
1064
|
+
}
|
|
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(", ")}]`;
|
|
1181
1172
|
}
|
|
1173
|
+
return String(value);
|
|
1182
1174
|
}
|
|
1183
|
-
function
|
|
1184
|
-
const {
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
return { kind: "now" };
|
|
1194
|
-
if (column.__columnType === "enum" && typeof value === "string") {
|
|
1195
|
-
return { kind: "enumValue", value };
|
|
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 }));
|
|
1196
1185
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1186
|
+
lines.push("");
|
|
1187
|
+
lines.push(" //> Relations");
|
|
1188
|
+
for (const relation of plan.relations.get(model.__name) ?? []) {
|
|
1189
|
+
lines.push(formatRelation({ relation }));
|
|
1199
1190
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
+
`);
|
|
1203
1223
|
}
|
|
1204
|
-
function
|
|
1205
|
-
const {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
+
});
|
|
1213
1237
|
}
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
+
});
|
|
1219
1244
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
+
}
|
|
1222
1256
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
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("");
|
|
1225
1264
|
}
|
|
1226
|
-
return
|
|
1265
|
+
return sections.join(`
|
|
1266
|
+
`).trim();
|
|
1227
1267
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
const dbName = typeof config.map === "string" ? config.map : undefined;
|
|
1233
|
-
const documentation = typeof config.comment === "string" ? config.comment : undefined;
|
|
1234
|
-
const defaultValue = fieldDefault({ column });
|
|
1235
|
-
const native = extensionNativeType ?? nativeType({ column });
|
|
1268
|
+
|
|
1269
|
+
// packages/dsl/src/relations.ts
|
|
1270
|
+
function createColumnRef(params) {
|
|
1271
|
+
const { model, column } = params;
|
|
1236
1272
|
return {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
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
|
+
}
|
|
1249
1285
|
};
|
|
1250
1286
|
}
|
|
1251
|
-
function
|
|
1252
|
-
const {
|
|
1253
|
-
return {
|
|
1254
|
-
name: relation.name,
|
|
1255
|
-
model: relation.model,
|
|
1256
|
-
targetModel: relation.targetModel,
|
|
1257
|
-
type: resolveRelationType({ relation, plan }),
|
|
1258
|
-
isList: relation.isList,
|
|
1259
|
-
isOptional: relation.isOptional,
|
|
1260
|
-
...relation.relationName === undefined ? {} : { relationName: relation.relationName },
|
|
1261
|
-
...relation.fields === undefined ? {} : { fields: [...relation.fields] },
|
|
1262
|
-
...relation.references === undefined ? {} : { references: [...relation.references] },
|
|
1263
|
-
...relation.onDelete === undefined ? {} : { onDelete: relation.onDelete },
|
|
1264
|
-
...relation.onUpdate === undefined ? {} : { onUpdate: relation.onUpdate }
|
|
1265
|
-
};
|
|
1287
|
+
function extractRefInfo(params) {
|
|
1288
|
+
const { ref } = params;
|
|
1289
|
+
return { model: ref.__model, column: ref.__column };
|
|
1266
1290
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
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;
|
|
1306
|
+
}
|
|
1307
|
+
return {
|
|
1308
|
+
__type: "storedRelation",
|
|
1309
|
+
__relationType: relationType,
|
|
1310
|
+
__targetModel: targetModelName,
|
|
1311
|
+
__fromModel: from.model,
|
|
1312
|
+
__fromColumn: from.column,
|
|
1313
|
+
__toModel: to.model,
|
|
1314
|
+
__toColumn: to.column,
|
|
1315
|
+
__throughJunction: throughJunction,
|
|
1316
|
+
__throughFromCol: throughFromCol,
|
|
1317
|
+
__throughToCol: throughToCol,
|
|
1318
|
+
__optional: config.optional ?? relationType === "many",
|
|
1319
|
+
__alias: config.alias,
|
|
1320
|
+
..."ownsForeignKey" in config ? { __ownsForeignKey: config.ownsForeignKey } : {},
|
|
1321
|
+
__onDelete: config.onDelete,
|
|
1322
|
+
__onUpdate: config.onUpdate
|
|
1323
|
+
};
|
|
1284
1324
|
};
|
|
1285
1325
|
}
|
|
1286
|
-
function
|
|
1287
|
-
const {
|
|
1288
|
-
const
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1326
|
+
function createRelationsBuilder(params) {
|
|
1327
|
+
const { models } = params;
|
|
1328
|
+
const builder = {};
|
|
1329
|
+
const oneObj = {};
|
|
1330
|
+
const manyObj = {};
|
|
1331
|
+
for (const modelName of Object.keys(models)) {
|
|
1332
|
+
oneObj[modelName] = createRelationFactory({ relationType: "one", targetModelName: modelName });
|
|
1333
|
+
manyObj[modelName] = createRelationFactory({
|
|
1334
|
+
relationType: "many",
|
|
1335
|
+
targetModelName: modelName
|
|
1336
|
+
});
|
|
1337
|
+
const model = models[modelName];
|
|
1338
|
+
if (model === undefined)
|
|
1294
1339
|
continue;
|
|
1340
|
+
const columnRefs = {};
|
|
1341
|
+
for (const columnName of model.__columns.keys()) {
|
|
1342
|
+
columnRefs[columnName] = createColumnRef({ model: modelName, column: columnName });
|
|
1343
|
+
}
|
|
1344
|
+
builder[modelName] = columnRefs;
|
|
1345
|
+
}
|
|
1346
|
+
builder.one = oneObj;
|
|
1347
|
+
builder.many = manyObj;
|
|
1348
|
+
return builder;
|
|
1349
|
+
}
|
|
1350
|
+
function processRelationsOutput(params) {
|
|
1351
|
+
const result = {};
|
|
1352
|
+
for (const [modelName, relations] of Object.entries(params.output)) {
|
|
1353
|
+
if (relations) {
|
|
1354
|
+
result[modelName] = relations;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return result;
|
|
1358
|
+
}
|
|
1359
|
+
function defineRelations(models, callback) {
|
|
1360
|
+
const builder = createRelationsBuilder({ models });
|
|
1361
|
+
return processRelationsOutput({ output: callback(builder) });
|
|
1362
|
+
}
|
|
1363
|
+
function defineRelationsPart(models, callback) {
|
|
1364
|
+
const builder = createRelationsBuilder({ models });
|
|
1365
|
+
return processRelationsOutput({ output: callback(builder) });
|
|
1366
|
+
}
|
|
1367
|
+
function mergeRelations(...parts) {
|
|
1368
|
+
const result = {};
|
|
1369
|
+
for (const part of parts) {
|
|
1370
|
+
for (const [modelName, relations] of Object.entries(part)) {
|
|
1371
|
+
const existing = result[modelName] ?? {};
|
|
1372
|
+
result[modelName] = { ...existing, ...relations };
|
|
1295
1373
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1374
|
+
}
|
|
1375
|
+
return result;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// packages/dsl/src/schema.ts
|
|
1379
|
+
import { VibeError as VibeError4, validateRlsContextIR } from "@vibeorm/schema";
|
|
1380
|
+
function sameValues(left, right) {
|
|
1381
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1382
|
+
}
|
|
1383
|
+
function registerEnum(params) {
|
|
1384
|
+
const { registry, name, entry, origin } = params;
|
|
1385
|
+
const existing = registry.get(name);
|
|
1386
|
+
if (existing === undefined) {
|
|
1387
|
+
registry.set(name, entry);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const valuesDiffer = !sameValues(existing.values, entry.values);
|
|
1391
|
+
const storageDiffers = existing.storage !== entry.storage;
|
|
1392
|
+
if (!valuesDiffer && !storageDiffers)
|
|
1393
|
+
return;
|
|
1394
|
+
const detail = [
|
|
1395
|
+
...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
|
|
1396
|
+
...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
|
|
1397
|
+
].join("; ");
|
|
1398
|
+
throw new VibeError4({
|
|
1399
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
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`,
|
|
1401
|
+
meta: {
|
|
1402
|
+
issues: [
|
|
1403
|
+
{
|
|
1404
|
+
rule: "enum-conflict",
|
|
1405
|
+
message: `Enum "${name}" reused with conflicting ${valuesDiffer ? "values" : "storage"} (${origin})`
|
|
1406
|
+
}
|
|
1407
|
+
],
|
|
1408
|
+
enum: name,
|
|
1409
|
+
existing,
|
|
1410
|
+
incoming: entry
|
|
1305
1411
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
function isModelDef(value) {
|
|
1415
|
+
return typeof value === "object" && value !== null && "__type" in value && value.__type === "model";
|
|
1416
|
+
}
|
|
1417
|
+
function isEnumDef(value) {
|
|
1418
|
+
return typeof value === "object" && value !== null && "__type" in value && value.__type === "enum";
|
|
1419
|
+
}
|
|
1420
|
+
function addModelTree(params) {
|
|
1421
|
+
const { model, models, origin } = params;
|
|
1422
|
+
const existing = models.get(model.__name);
|
|
1423
|
+
if (existing !== undefined && existing !== model) {
|
|
1424
|
+
throw new VibeError4({
|
|
1425
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
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`,
|
|
1427
|
+
meta: {
|
|
1428
|
+
issues: [
|
|
1429
|
+
{
|
|
1430
|
+
rule: "model-conflict",
|
|
1431
|
+
message: `Model "${model.__name}" reused with a different definition (${origin})`,
|
|
1432
|
+
model: model.__name
|
|
1433
|
+
}
|
|
1434
|
+
],
|
|
1435
|
+
model: model.__name
|
|
1436
|
+
}
|
|
1311
1437
|
});
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1438
|
+
}
|
|
1439
|
+
models.set(model.__name, model);
|
|
1440
|
+
for (const childModel of model.__children.values()) {
|
|
1441
|
+
addModelTree({ model: childModel, models, origin: `inline child of ${model.__name}` });
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
function extractModelsAndEnums(params) {
|
|
1445
|
+
const { input, models, enums } = params;
|
|
1446
|
+
for (const value of Object.values(input)) {
|
|
1447
|
+
if (isModelDef(value)) {
|
|
1448
|
+
addModelTree({ model: value, models, origin: "listed in dsl.schema()" });
|
|
1449
|
+
} else if (isEnumDef(value)) {
|
|
1450
|
+
registerEnum({
|
|
1451
|
+
registry: enums,
|
|
1452
|
+
name: value.__name,
|
|
1453
|
+
entry: { values: value.__options, storage: value.__storage },
|
|
1454
|
+
origin: "standalone enum listed in dsl.schema()"
|
|
1318
1455
|
});
|
|
1319
|
-
|
|
1456
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1457
|
+
extractModelsAndEnums({ input: value, models, enums });
|
|
1320
1458
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function resolveEnums(params) {
|
|
1462
|
+
const { models, enums } = params;
|
|
1463
|
+
for (const [modelName, model] of models) {
|
|
1464
|
+
for (const [columnName, column] of model.__columns) {
|
|
1465
|
+
if (column.__columnType !== "enum" || column.__enumValues === undefined)
|
|
1466
|
+
continue;
|
|
1467
|
+
const entry = {
|
|
1468
|
+
values: column.__enumValues,
|
|
1469
|
+
storage: column.__enumStorage ?? "database"
|
|
1470
|
+
};
|
|
1471
|
+
if (column.__enumName !== undefined) {
|
|
1472
|
+
registerEnum({
|
|
1473
|
+
registry: enums,
|
|
1474
|
+
name: column.__enumName,
|
|
1475
|
+
entry,
|
|
1476
|
+
origin: `standalone enum used by ${modelName}.${columnName}`
|
|
1477
|
+
});
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
const name = inlineEnumName({ modelName, columnName });
|
|
1481
|
+
registerEnum({
|
|
1482
|
+
registry: enums,
|
|
1483
|
+
name,
|
|
1484
|
+
entry,
|
|
1485
|
+
origin: `inline enum on ${modelName}.${columnName}, named ${name}`
|
|
1327
1486
|
});
|
|
1487
|
+
column.__enumName = name;
|
|
1328
1488
|
}
|
|
1329
1489
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1490
|
+
}
|
|
1491
|
+
var RLS_CONTEXT_TYPES = {
|
|
1492
|
+
string: "String",
|
|
1493
|
+
boolean: "Boolean",
|
|
1494
|
+
int: "Int",
|
|
1495
|
+
bigint: "BigInt",
|
|
1496
|
+
float: "Float",
|
|
1497
|
+
decimal: "Decimal",
|
|
1498
|
+
datetime: "DateTime"
|
|
1499
|
+
};
|
|
1500
|
+
function defineRls(params) {
|
|
1501
|
+
if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
|
|
1502
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1503
|
+
}
|
|
1504
|
+
const entries = [];
|
|
1505
|
+
const refs = [];
|
|
1506
|
+
for (const key of Reflect.ownKeys(params.context)) {
|
|
1507
|
+
const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
|
|
1508
|
+
const column = descriptor?.value;
|
|
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) {
|
|
1510
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1339
1511
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1512
|
+
entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
|
|
1513
|
+
refs.push([key, Object.freeze({ context: key })]);
|
|
1514
|
+
}
|
|
1515
|
+
const context = Object.freeze(Object.fromEntries(entries));
|
|
1516
|
+
const issues = validateRlsContextIR({ context });
|
|
1517
|
+
if (issues.length > 0) {
|
|
1518
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
|
|
1519
|
+
}
|
|
1520
|
+
const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
|
|
1521
|
+
if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
|
|
1522
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1523
|
+
}
|
|
1524
|
+
const named = [];
|
|
1525
|
+
const classified = new Set;
|
|
1526
|
+
for (const key of Reflect.ownKeys(policies)) {
|
|
1527
|
+
const descriptor = Object.getOwnPropertyDescriptor(policies, key);
|
|
1528
|
+
if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
|
|
1529
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1355
1530
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
model: model
|
|
1359
|
-
|
|
1360
|
-
|
|
1531
|
+
const name = params.models[key]?.__name ?? key;
|
|
1532
|
+
if (classified.has(name)) {
|
|
1533
|
+
throw new VibeError4({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
|
|
1534
|
+
}
|
|
1535
|
+
classified.add(name);
|
|
1536
|
+
named.push([name, descriptor.value]);
|
|
1361
1537
|
}
|
|
1538
|
+
return { context, policies: Object.fromEntries(named) };
|
|
1539
|
+
}
|
|
1540
|
+
function schema(input, options) {
|
|
1541
|
+
const models = new Map;
|
|
1542
|
+
const enums = new Map;
|
|
1543
|
+
extractModelsAndEnums({ input, models, enums });
|
|
1544
|
+
resolveEnums({ models, enums });
|
|
1362
1545
|
return {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
...
|
|
1369
|
-
...primaryKey === undefined ? {} : { primaryKey },
|
|
1370
|
-
isView: false
|
|
1546
|
+
__type: "schema",
|
|
1547
|
+
__models: models,
|
|
1548
|
+
__enums: new Map(Array.from(enums, ([name, entry]) => [name, entry.values])),
|
|
1549
|
+
__enumStorage: new Map(Array.from(enums, ([name, entry]) => [name, entry.storage])),
|
|
1550
|
+
__relations: options?.relations ?? {},
|
|
1551
|
+
...options?.rls === undefined ? {} : { __rls: options.rls }
|
|
1371
1552
|
};
|
|
1372
1553
|
}
|
|
1554
|
+
function domain(params) {
|
|
1555
|
+
const { name, models } = params;
|
|
1556
|
+
return { name, models };
|
|
1557
|
+
}
|
|
1373
1558
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1559
|
+
// packages/dsl/src/templates.ts
|
|
1560
|
+
var base = {
|
|
1561
|
+
get id() {
|
|
1562
|
+
return id({ type: "ulid" });
|
|
1563
|
+
},
|
|
1564
|
+
get createdAt() {
|
|
1565
|
+
return datetime({ default: "now", tz: true, index: true });
|
|
1566
|
+
},
|
|
1567
|
+
get updatedAt() {
|
|
1568
|
+
return datetime({ default: "now", updatedAt: true, tz: true, index: true });
|
|
1569
|
+
},
|
|
1570
|
+
get deletedAt() {
|
|
1571
|
+
return datetime({ optional: true, index: true });
|
|
1381
1572
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
})
|
|
1573
|
+
};
|
|
1574
|
+
var baseWithWorkspace = {
|
|
1575
|
+
get id() {
|
|
1576
|
+
return id({ type: "ulid" });
|
|
1577
|
+
},
|
|
1578
|
+
get createdAt() {
|
|
1579
|
+
return datetime({ default: "now", tz: true, index: true });
|
|
1580
|
+
},
|
|
1581
|
+
get updatedAt() {
|
|
1582
|
+
return datetime({ default: "now", updatedAt: true, tz: true, index: true });
|
|
1583
|
+
},
|
|
1584
|
+
get deletedAt() {
|
|
1585
|
+
return datetime({ optional: true, index: true });
|
|
1586
|
+
},
|
|
1587
|
+
get workspaceId() {
|
|
1588
|
+
return string({ index: true });
|
|
1389
1589
|
}
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
const models = Array.from(schema2.__models.values(), (model) => buildModel({ model, plan, extensions, declarations, issues }));
|
|
1398
|
-
const enums = Array.from(schema2.__enums, ([name, values]) => ({
|
|
1399
|
-
name,
|
|
1400
|
-
values: [...values]
|
|
1401
|
-
}));
|
|
1402
|
-
if (issues.length > 0) {
|
|
1403
|
-
throw new VibeError({
|
|
1404
|
-
code: "VIBE_SCHEMA_INVALID",
|
|
1405
|
-
message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
|
|
1406
|
-
meta: { issues }
|
|
1407
|
-
});
|
|
1590
|
+
};
|
|
1591
|
+
var withMetadata = {
|
|
1592
|
+
get metadata() {
|
|
1593
|
+
return json({ optional: true });
|
|
1594
|
+
},
|
|
1595
|
+
get settings() {
|
|
1596
|
+
return json({ optional: true });
|
|
1408
1597
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1598
|
+
};
|
|
1599
|
+
var withActive = {
|
|
1600
|
+
get isActive() {
|
|
1601
|
+
return boolean({ default: true });
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
var withAudit = {
|
|
1605
|
+
get createdById() {
|
|
1606
|
+
return string({ optional: true, index: true });
|
|
1607
|
+
},
|
|
1608
|
+
get updatedById() {
|
|
1609
|
+
return string({ optional: true, index: true });
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
var withExternalId = {
|
|
1613
|
+
get externalId() {
|
|
1614
|
+
return string({ index: true });
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
var withNameDescription = {
|
|
1618
|
+
get name() {
|
|
1619
|
+
return string();
|
|
1620
|
+
},
|
|
1621
|
+
get description() {
|
|
1622
|
+
return string({ optional: true });
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
var fields = {
|
|
1626
|
+
get datetime() {
|
|
1627
|
+
return datetime({ default: "now", updatedAt: true, tz: true, index: true });
|
|
1628
|
+
},
|
|
1629
|
+
get id() {
|
|
1630
|
+
return id({ type: "ulid" });
|
|
1631
|
+
},
|
|
1632
|
+
get title() {
|
|
1633
|
+
return string();
|
|
1634
|
+
},
|
|
1635
|
+
get description() {
|
|
1636
|
+
return string({ optional: true });
|
|
1637
|
+
},
|
|
1638
|
+
get externalId() {
|
|
1639
|
+
return string({ index: true });
|
|
1640
|
+
},
|
|
1641
|
+
get metadata() {
|
|
1642
|
+
return json({ optional: true });
|
|
1643
|
+
},
|
|
1644
|
+
get settings() {
|
|
1645
|
+
return json({ optional: true });
|
|
1646
|
+
},
|
|
1647
|
+
get isActive() {
|
|
1648
|
+
return boolean({ default: true });
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
var templates = {
|
|
1652
|
+
base,
|
|
1653
|
+
baseWithWorkspace,
|
|
1654
|
+
withMetadata,
|
|
1655
|
+
withActive,
|
|
1656
|
+
withAudit,
|
|
1657
|
+
withExternalId,
|
|
1658
|
+
withNameDescription,
|
|
1659
|
+
fields
|
|
1660
|
+
};
|
|
1418
1661
|
|
|
1419
1662
|
// packages/dsl/src/validate.ts
|
|
1420
1663
|
function validateSchema(schema2) {
|
|
@@ -1534,6 +1777,7 @@ var dsl = {
|
|
|
1534
1777
|
defineRelations,
|
|
1535
1778
|
defineRelationsPart,
|
|
1536
1779
|
mergeRelations,
|
|
1780
|
+
defineRls,
|
|
1537
1781
|
schema,
|
|
1538
1782
|
domain,
|
|
1539
1783
|
toIR,
|
|
@@ -1561,6 +1805,7 @@ export {
|
|
|
1561
1805
|
dsl,
|
|
1562
1806
|
domain,
|
|
1563
1807
|
detectOneToOnePairs,
|
|
1808
|
+
defineRls,
|
|
1564
1809
|
defineRelationsPart,
|
|
1565
1810
|
defineRelations,
|
|
1566
1811
|
createIndexBuilder,
|
|
@@ -1572,6 +1817,7 @@ export {
|
|
|
1572
1817
|
baseWithWorkspace,
|
|
1573
1818
|
base,
|
|
1574
1819
|
ChainableStringColumn,
|
|
1820
|
+
ChainableIntegerColumn,
|
|
1575
1821
|
ChainableIdColumn,
|
|
1576
1822
|
ChainableEnumColumn,
|
|
1577
1823
|
ChainableDecimalColumn,
|
|
@@ -1579,4 +1825,4 @@ export {
|
|
|
1579
1825
|
ChainableColumn
|
|
1580
1826
|
};
|
|
1581
1827
|
|
|
1582
|
-
//# debugId=
|
|
1828
|
+
//# debugId=88D4E8D0B695042E64756E2164756E21
|