@vibeorm/dsl 2.0.0-alpha.7 → 2.0.0-alpha.8
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 +1 -1
- package/dist/column.d.ts +5 -0
- 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 +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +247 -46
- package/dist/index.js.map +10 -10
- package/dist/model.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 +7 -2
- package/dist/to-ir.d.ts.map +1 -1
- package/dist/types.d.ts +119 -7
- 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;
|
|
@@ -180,25 +189,35 @@ function extensionType(config) {
|
|
|
180
189
|
});
|
|
181
190
|
}
|
|
182
191
|
function enumCol(config) {
|
|
183
|
-
const { options, ...restConfig } = config;
|
|
184
|
-
return new ChainableEnumColumn({
|
|
192
|
+
const { options, storage, ...restConfig } = config;
|
|
193
|
+
return new ChainableEnumColumn({
|
|
194
|
+
options,
|
|
195
|
+
config: restConfig,
|
|
196
|
+
...storage === undefined ? {} : { storage }
|
|
197
|
+
});
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
// packages/dsl/src/enum.ts
|
|
201
|
+
function enumValuesOf(params) {
|
|
202
|
+
const entries = Object.fromEntries(params.options.map((option) => [option, option]));
|
|
203
|
+
return Object.freeze(entries);
|
|
204
|
+
}
|
|
188
205
|
function createEnum(config) {
|
|
189
|
-
const { name, options } = config;
|
|
206
|
+
const { name, options, storage = "database" } = config;
|
|
190
207
|
return {
|
|
191
208
|
__type: "enum",
|
|
192
209
|
__name: name,
|
|
193
210
|
__options: options,
|
|
211
|
+
__storage: storage,
|
|
212
|
+
values: enumValuesOf({ options }),
|
|
194
213
|
field(fieldConfig = {}) {
|
|
195
|
-
return new ChainableEnumColumn({ options, config: fieldConfig, enumName: name });
|
|
214
|
+
return new ChainableEnumColumn({ options, config: fieldConfig, enumName: name, storage });
|
|
196
215
|
}
|
|
197
216
|
};
|
|
198
217
|
}
|
|
199
218
|
|
|
200
219
|
// packages/dsl/src/generator/prisma.ts
|
|
201
|
-
import { internalError } from "@vibeorm/schema";
|
|
220
|
+
import { VibeError, encodePrismaString, internalError } from "@vibeorm/schema";
|
|
202
221
|
|
|
203
222
|
// packages/dsl/src/naming.ts
|
|
204
223
|
function capitalize(params) {
|
|
@@ -320,7 +339,9 @@ function create(config) {
|
|
|
320
339
|
__map: config.map,
|
|
321
340
|
__primaryKey: config.primaryKey,
|
|
322
341
|
__checks: config.checks,
|
|
323
|
-
__extensions: config.extensions
|
|
342
|
+
__extensions: config.extensions,
|
|
343
|
+
__policy: config.policy,
|
|
344
|
+
__comment: config.comment
|
|
324
345
|
};
|
|
325
346
|
}
|
|
326
347
|
function getColumnNames(params) {
|
|
@@ -640,7 +661,7 @@ function prismaType(params) {
|
|
|
640
661
|
const args = Array.isArray(rawArgs) ? rawArgs.map(String).join(", ") : "";
|
|
641
662
|
return `${extensionTypeName}${args.length > 0 ? `(${args})` : ""}${list}`;
|
|
642
663
|
}
|
|
643
|
-
return `Unsupported(
|
|
664
|
+
return `Unsupported(${encodePrismaString({ value: String(column.__config.dbType) })})${list}`;
|
|
644
665
|
}
|
|
645
666
|
}
|
|
646
667
|
}
|
|
@@ -661,7 +682,7 @@ function defaultAttribute(params) {
|
|
|
661
682
|
const { column } = params;
|
|
662
683
|
const { dbgenerated, default: value } = column.__config;
|
|
663
684
|
if (typeof dbgenerated === "string") {
|
|
664
|
-
return `@default(dbgenerated(
|
|
685
|
+
return `@default(dbgenerated(${encodePrismaString({ value: dbgenerated })}))`;
|
|
665
686
|
}
|
|
666
687
|
if (value === undefined)
|
|
667
688
|
return;
|
|
@@ -672,7 +693,7 @@ function defaultAttribute(params) {
|
|
|
672
693
|
return `@default(${value})`;
|
|
673
694
|
}
|
|
674
695
|
if (typeof value === "string")
|
|
675
|
-
return `@default(
|
|
696
|
+
return `@default(${encodePrismaString({ value })})`;
|
|
676
697
|
if (typeof value === "boolean" || typeof value === "number")
|
|
677
698
|
return `@default(${value})`;
|
|
678
699
|
if (typeof value === "bigint")
|
|
@@ -720,11 +741,14 @@ function formatColumn(params) {
|
|
|
720
741
|
parts.push("@unique");
|
|
721
742
|
}
|
|
722
743
|
if (typeof column.__config.map === "string") {
|
|
723
|
-
parts.push(`@map(
|
|
744
|
+
parts.push(`@map(${encodePrismaString({ value: column.__config.map })})`);
|
|
724
745
|
}
|
|
725
746
|
const nativeType = nativeTypeAttribute({ column });
|
|
726
747
|
if (nativeType !== undefined)
|
|
727
748
|
parts.push(nativeType);
|
|
749
|
+
if (typeof column.__config.collation === "string") {
|
|
750
|
+
parts.push(`@collation(${encodePrismaString({ value: column.__config.collation })})`);
|
|
751
|
+
}
|
|
728
752
|
const columnLine = parts.join(" ");
|
|
729
753
|
const comment = column.__config.comment;
|
|
730
754
|
if (typeof comment === "string") {
|
|
@@ -739,17 +763,17 @@ function formatRelation(params) {
|
|
|
739
763
|
const { relation } = params;
|
|
740
764
|
const { name, targetModel, relationName } = relation;
|
|
741
765
|
if (relation.kind === "list") {
|
|
742
|
-
const attribute = relationName === undefined ? "" : ` @relation(
|
|
766
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString({ value: relationName })})`;
|
|
743
767
|
return ` ${name} ${targetModel}[]${attribute}`;
|
|
744
768
|
}
|
|
745
769
|
const optional = relation.isOptional ? "?" : "";
|
|
746
770
|
if (relation.kind === "backOne") {
|
|
747
|
-
const attribute = relationName === undefined ? "" : ` @relation(
|
|
771
|
+
const attribute = relationName === undefined ? "" : ` @relation(${encodePrismaString({ value: relationName })})`;
|
|
748
772
|
return ` ${name} ${targetModel}${optional}${attribute}`;
|
|
749
773
|
}
|
|
750
774
|
const args = [];
|
|
751
775
|
if (relationName !== undefined)
|
|
752
|
-
args.push(
|
|
776
|
+
args.push(encodePrismaString({ value: relationName }));
|
|
753
777
|
if (relation.fields === undefined || relation.references === undefined) {
|
|
754
778
|
throw internalError({
|
|
755
779
|
message: `planned FK relation "${relation.model}.${name}" has no fields/references`,
|
|
@@ -767,9 +791,9 @@ function formatRelation(params) {
|
|
|
767
791
|
function formatIndex(params) {
|
|
768
792
|
const { index } = params;
|
|
769
793
|
const columns = index.columns.join(", ");
|
|
770
|
-
const whereArg = index.where === undefined ? "" : `, where: ${
|
|
794
|
+
const whereArg = index.where === undefined ? "" : `, where: ${encodePrismaString({ value: index.where })}`;
|
|
771
795
|
const nndArg = index.nullsNotDistinct === true ? ", nullsNotDistinct: true" : "";
|
|
772
|
-
const nameArg = index.clientName === undefined ? "" : `, name: ${
|
|
796
|
+
const nameArg = index.clientName === undefined ? "" : `, name: ${encodePrismaString({ value: index.clientName })}`;
|
|
773
797
|
if (index.isUnique) {
|
|
774
798
|
return ` @@unique([${columns}]${nameArg}${nndArg}${whereArg})`;
|
|
775
799
|
}
|
|
@@ -779,7 +803,7 @@ function formatIndex(params) {
|
|
|
779
803
|
function formatExtensionValue(params) {
|
|
780
804
|
const { value } = params;
|
|
781
805
|
if (typeof value === "string")
|
|
782
|
-
return
|
|
806
|
+
return encodePrismaString({ value });
|
|
783
807
|
if (Array.isArray(value)) {
|
|
784
808
|
return `[${value.map((entry) => formatExtensionValue({ value: entry })).join(", ")}]`;
|
|
785
809
|
}
|
|
@@ -787,7 +811,11 @@ function formatExtensionValue(params) {
|
|
|
787
811
|
}
|
|
788
812
|
function formatModel(params) {
|
|
789
813
|
const { model, plan } = params;
|
|
790
|
-
const lines = [
|
|
814
|
+
const lines = [
|
|
815
|
+
...model.__comment === undefined ? [] : model.__comment.split(`
|
|
816
|
+
`).map((line) => `/// ${line}`),
|
|
817
|
+
`model ${model.__name} {`
|
|
818
|
+
];
|
|
791
819
|
lines.push(" //> Columns");
|
|
792
820
|
for (const [name, column] of model.__columns) {
|
|
793
821
|
lines.push(formatColumn({ name, column }));
|
|
@@ -804,7 +832,7 @@ function formatModel(params) {
|
|
|
804
832
|
lines.push(formatIndex({ index }));
|
|
805
833
|
}
|
|
806
834
|
for (const check of model.__checks ?? []) {
|
|
807
|
-
lines.push(` @@check(${
|
|
835
|
+
lines.push(` @@check(${encodePrismaString({ value: check.expression })}, name: ${encodePrismaString({ value: check.name })})`);
|
|
808
836
|
}
|
|
809
837
|
for (const use of model.__extensions ?? []) {
|
|
810
838
|
const args = [];
|
|
@@ -818,8 +846,13 @@ function formatModel(params) {
|
|
|
818
846
|
if (model.__primaryKey !== undefined && model.__primaryKey.length > 0) {
|
|
819
847
|
lines.push(` @@id([${model.__primaryKey.join(", ")}])`);
|
|
820
848
|
}
|
|
849
|
+
if (model.__policy !== undefined) {
|
|
850
|
+
const { field, context } = model.__policy;
|
|
851
|
+
const contextArg = context === undefined || context === field ? "" : `, context: ${encodePrismaString({ value: context })}`;
|
|
852
|
+
lines.push(` @@policy([${field}]${contextArg})`);
|
|
853
|
+
}
|
|
821
854
|
if (typeof model.__map === "string") {
|
|
822
|
-
lines.push(` @@map(
|
|
855
|
+
lines.push(` @@map(${encodePrismaString({ value: model.__map })})`);
|
|
823
856
|
}
|
|
824
857
|
lines.push("}");
|
|
825
858
|
return lines.join(`
|
|
@@ -831,6 +864,21 @@ function formatEnum(params) {
|
|
|
831
864
|
`);
|
|
832
865
|
}
|
|
833
866
|
function generatePrisma(schema, _config = {}) {
|
|
867
|
+
const stringEnums = [...schema.__enumStorage].filter(([, storage]) => storage === "string").map(([name]) => name);
|
|
868
|
+
if (stringEnums.length > 0) {
|
|
869
|
+
throw new VibeError({
|
|
870
|
+
code: "VIBE_CONFIG",
|
|
871
|
+
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.`,
|
|
872
|
+
meta: { enums: stringEnums, target: "prisma" }
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
if (schema.__rls !== undefined) {
|
|
876
|
+
throw new VibeError({
|
|
877
|
+
code: "VIBE_CONFIG",
|
|
878
|
+
message: "Cannot print to Prisma: this schema uses native RLS, which .prisma cannot express losslessly. Keep the DSL as the source.",
|
|
879
|
+
meta: { feature: "nativeRowLevelSecurity", target: "prisma" }
|
|
880
|
+
});
|
|
881
|
+
}
|
|
834
882
|
const plan = planSchema({ schema });
|
|
835
883
|
const sections = [];
|
|
836
884
|
if (schema.__enums.size > 0) {
|
|
@@ -964,6 +1012,41 @@ function mergeRelations(...parts) {
|
|
|
964
1012
|
}
|
|
965
1013
|
|
|
966
1014
|
// packages/dsl/src/schema.ts
|
|
1015
|
+
import { VibeError as VibeError2, validateRlsContextIR } from "@vibeorm/schema";
|
|
1016
|
+
function sameValues(left, right) {
|
|
1017
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1018
|
+
}
|
|
1019
|
+
function registerEnum(params) {
|
|
1020
|
+
const { registry, name, entry, origin } = params;
|
|
1021
|
+
const existing = registry.get(name);
|
|
1022
|
+
if (existing === undefined) {
|
|
1023
|
+
registry.set(name, entry);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const valuesDiffer = !sameValues(existing.values, entry.values);
|
|
1027
|
+
const storageDiffers = existing.storage !== entry.storage;
|
|
1028
|
+
if (!valuesDiffer && !storageDiffers)
|
|
1029
|
+
return;
|
|
1030
|
+
const detail = [
|
|
1031
|
+
...valuesDiffer ? [`values [${existing.values.join(", ")}] vs [${entry.values.join(", ")}]`] : [],
|
|
1032
|
+
...storageDiffers ? [`storage "${existing.storage}" vs "${entry.storage}"`] : []
|
|
1033
|
+
].join("; ");
|
|
1034
|
+
throw new VibeError2({
|
|
1035
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
1036
|
+
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
|
+
meta: {
|
|
1038
|
+
issues: [
|
|
1039
|
+
{
|
|
1040
|
+
rule: "enum-conflict",
|
|
1041
|
+
message: `Enum "${name}" reused with conflicting ${valuesDiffer ? "values" : "storage"} (${origin})`
|
|
1042
|
+
}
|
|
1043
|
+
],
|
|
1044
|
+
enum: name,
|
|
1045
|
+
existing,
|
|
1046
|
+
incoming: entry
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
967
1050
|
function isModelDef(value) {
|
|
968
1051
|
return typeof value === "object" && value !== null && "__type" in value && value.__type === "model";
|
|
969
1052
|
}
|
|
@@ -971,53 +1054,137 @@ function isEnumDef(value) {
|
|
|
971
1054
|
return typeof value === "object" && value !== null && "__type" in value && value.__type === "enum";
|
|
972
1055
|
}
|
|
973
1056
|
function addModelTree(params) {
|
|
974
|
-
const { model, models } = params;
|
|
1057
|
+
const { model, models, origin } = params;
|
|
1058
|
+
const existing = models.get(model.__name);
|
|
1059
|
+
if (existing !== undefined && existing !== model) {
|
|
1060
|
+
throw new VibeError2({
|
|
1061
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
1062
|
+
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
|
+
meta: {
|
|
1064
|
+
issues: [
|
|
1065
|
+
{
|
|
1066
|
+
rule: "model-conflict",
|
|
1067
|
+
message: `Model "${model.__name}" reused with a different definition (${origin})`,
|
|
1068
|
+
model: model.__name
|
|
1069
|
+
}
|
|
1070
|
+
],
|
|
1071
|
+
model: model.__name
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
975
1075
|
models.set(model.__name, model);
|
|
976
1076
|
for (const childModel of model.__children.values()) {
|
|
977
|
-
addModelTree({ model: childModel, models });
|
|
1077
|
+
addModelTree({ model: childModel, models, origin: `inline child of ${model.__name}` });
|
|
978
1078
|
}
|
|
979
1079
|
}
|
|
980
1080
|
function extractModelsAndEnums(params) {
|
|
981
|
-
const { input, models,
|
|
1081
|
+
const { input, models, enums } = params;
|
|
982
1082
|
for (const value of Object.values(input)) {
|
|
983
1083
|
if (isModelDef(value)) {
|
|
984
|
-
addModelTree({ model: value, models });
|
|
1084
|
+
addModelTree({ model: value, models, origin: "listed in dsl.schema()" });
|
|
985
1085
|
} else if (isEnumDef(value)) {
|
|
986
|
-
|
|
1086
|
+
registerEnum({
|
|
1087
|
+
registry: enums,
|
|
1088
|
+
name: value.__name,
|
|
1089
|
+
entry: { values: value.__options, storage: value.__storage },
|
|
1090
|
+
origin: "standalone enum listed in dsl.schema()"
|
|
1091
|
+
});
|
|
987
1092
|
} else if (typeof value === "object" && value !== null) {
|
|
988
|
-
extractModelsAndEnums({ input: value, models,
|
|
1093
|
+
extractModelsAndEnums({ input: value, models, enums });
|
|
989
1094
|
}
|
|
990
1095
|
}
|
|
991
1096
|
}
|
|
992
1097
|
function resolveEnums(params) {
|
|
993
|
-
const { models,
|
|
994
|
-
const enums = new Map(standaloneEnums);
|
|
1098
|
+
const { models, enums } = params;
|
|
995
1099
|
for (const [modelName, model] of models) {
|
|
996
1100
|
for (const [columnName, column] of model.__columns) {
|
|
997
1101
|
if (column.__columnType !== "enum" || column.__enumValues === undefined)
|
|
998
1102
|
continue;
|
|
1103
|
+
const entry = {
|
|
1104
|
+
values: column.__enumValues,
|
|
1105
|
+
storage: column.__enumStorage ?? "database"
|
|
1106
|
+
};
|
|
999
1107
|
if (column.__enumName !== undefined) {
|
|
1000
|
-
|
|
1001
|
-
enums
|
|
1002
|
-
|
|
1108
|
+
registerEnum({
|
|
1109
|
+
registry: enums,
|
|
1110
|
+
name: column.__enumName,
|
|
1111
|
+
entry,
|
|
1112
|
+
origin: `standalone enum used by ${modelName}.${columnName}`
|
|
1113
|
+
});
|
|
1003
1114
|
continue;
|
|
1004
1115
|
}
|
|
1005
1116
|
const name = inlineEnumName({ modelName, columnName });
|
|
1006
|
-
|
|
1117
|
+
registerEnum({
|
|
1118
|
+
registry: enums,
|
|
1119
|
+
name,
|
|
1120
|
+
entry,
|
|
1121
|
+
origin: `inline enum on ${modelName}.${columnName}, named ${name}`
|
|
1122
|
+
});
|
|
1007
1123
|
column.__enumName = name;
|
|
1008
1124
|
}
|
|
1009
1125
|
}
|
|
1010
|
-
|
|
1126
|
+
}
|
|
1127
|
+
var RLS_CONTEXT_TYPES = {
|
|
1128
|
+
string: "String",
|
|
1129
|
+
boolean: "Boolean",
|
|
1130
|
+
int: "Int",
|
|
1131
|
+
bigint: "BigInt",
|
|
1132
|
+
float: "Float",
|
|
1133
|
+
decimal: "Decimal",
|
|
1134
|
+
datetime: "DateTime"
|
|
1135
|
+
};
|
|
1136
|
+
function defineRls(params) {
|
|
1137
|
+
if (typeof params.context !== "object" || params.context === null || Array.isArray(params.context)) {
|
|
1138
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "RLS context must be a record of scalar declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1139
|
+
}
|
|
1140
|
+
const entries = [];
|
|
1141
|
+
const refs = [];
|
|
1142
|
+
for (const key of Reflect.ownKeys(params.context)) {
|
|
1143
|
+
const descriptor = Object.getOwnPropertyDescriptor(params.context, key);
|
|
1144
|
+
const column = descriptor?.value;
|
|
1145
|
+
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 VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "RLS context slots must be named scalar column declarations", meta: { issues: [{ rule: "rls-invalid-context" }] } });
|
|
1147
|
+
}
|
|
1148
|
+
entries.push([key, { type: RLS_CONTEXT_TYPES[column.__columnType], nullable: "optional" in column.__config && column.__config.optional === true }]);
|
|
1149
|
+
refs.push([key, Object.freeze({ context: key })]);
|
|
1150
|
+
}
|
|
1151
|
+
const context = Object.freeze(Object.fromEntries(entries));
|
|
1152
|
+
const issues = validateRlsContextIR({ context });
|
|
1153
|
+
if (issues.length > 0) {
|
|
1154
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "Invalid RLS context declaration", meta: { issues } });
|
|
1155
|
+
}
|
|
1156
|
+
const policies = params.policies({ context: Object.freeze(Object.fromEntries(refs)) });
|
|
1157
|
+
if (typeof policies !== "object" || policies === null || Array.isArray(policies)) {
|
|
1158
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be a record of model classifications", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1159
|
+
}
|
|
1160
|
+
const named = [];
|
|
1161
|
+
const classified = new Set;
|
|
1162
|
+
for (const key of Reflect.ownKeys(policies)) {
|
|
1163
|
+
const descriptor = Object.getOwnPropertyDescriptor(policies, key);
|
|
1164
|
+
if (typeof key !== "string" || descriptor?.enumerable !== true || !("value" in descriptor)) {
|
|
1165
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: "RLS policies must be named data properties", meta: { issues: [{ rule: "rls-invalid-rule" }] } });
|
|
1166
|
+
}
|
|
1167
|
+
const name = params.models[key]?.__name ?? key;
|
|
1168
|
+
if (classified.has(name)) {
|
|
1169
|
+
throw new VibeError2({ code: "VIBE_SCHEMA_INVALID", message: `RLS classifies model "${name}" more than once`, meta: { issues: [{ rule: "rls-duplicate-classification", model: name }] } });
|
|
1170
|
+
}
|
|
1171
|
+
classified.add(name);
|
|
1172
|
+
named.push([name, descriptor.value]);
|
|
1173
|
+
}
|
|
1174
|
+
return { context, policies: Object.fromEntries(named) };
|
|
1011
1175
|
}
|
|
1012
1176
|
function schema(input, options) {
|
|
1013
1177
|
const models = new Map;
|
|
1014
|
-
const
|
|
1015
|
-
extractModelsAndEnums({ input, models,
|
|
1178
|
+
const enums = new Map;
|
|
1179
|
+
extractModelsAndEnums({ input, models, enums });
|
|
1180
|
+
resolveEnums({ models, enums });
|
|
1016
1181
|
return {
|
|
1017
1182
|
__type: "schema",
|
|
1018
1183
|
__models: models,
|
|
1019
|
-
__enums:
|
|
1020
|
-
|
|
1184
|
+
__enums: new Map(Array.from(enums, ([name, entry]) => [name, entry.values])),
|
|
1185
|
+
__enumStorage: new Map(Array.from(enums, ([name, entry]) => [name, entry.storage])),
|
|
1186
|
+
__relations: options?.relations ?? {},
|
|
1187
|
+
...options?.rls === undefined ? {} : { __rls: options.rls }
|
|
1021
1188
|
};
|
|
1022
1189
|
}
|
|
1023
1190
|
function domain(params) {
|
|
@@ -1131,8 +1298,9 @@ var templates = {
|
|
|
1131
1298
|
// packages/dsl/src/to-ir.ts
|
|
1132
1299
|
import {
|
|
1133
1300
|
EMPTY_EXTENSION_REGISTRY,
|
|
1134
|
-
VibeError,
|
|
1301
|
+
VibeError as VibeError3,
|
|
1135
1302
|
assertValidSchemaIR,
|
|
1303
|
+
exactIntegerLiteral,
|
|
1136
1304
|
mergeExtensionDeclarations
|
|
1137
1305
|
} from "@vibeorm/schema";
|
|
1138
1306
|
function fieldType(params) {
|
|
@@ -1198,12 +1366,25 @@ function fieldDefault(params) {
|
|
|
1198
1366
|
return { kind: "literal", value };
|
|
1199
1367
|
}
|
|
1200
1368
|
if (typeof value === "bigint")
|
|
1201
|
-
return { kind: "literal", value:
|
|
1369
|
+
return { kind: "literal", value: exactIntegerLiteral({ text: value.toString() }) };
|
|
1202
1370
|
return;
|
|
1203
1371
|
}
|
|
1204
1372
|
function nativeType(params) {
|
|
1205
1373
|
const { column } = params;
|
|
1206
|
-
if (column.__columnType === "
|
|
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") {
|
|
1207
1388
|
const { maxLength, text } = column.__config;
|
|
1208
1389
|
if (typeof maxLength === "number")
|
|
1209
1390
|
return `VarChar(${maxLength})`;
|
|
@@ -1231,6 +1412,7 @@ function buildField(params) {
|
|
|
1231
1412
|
const isList = column.__columnType !== "id" && config.array === true;
|
|
1232
1413
|
const dbName = typeof config.map === "string" ? config.map : undefined;
|
|
1233
1414
|
const documentation = typeof config.comment === "string" ? config.comment : undefined;
|
|
1415
|
+
const collation = typeof config.collation === "string" ? config.collation : undefined;
|
|
1234
1416
|
const defaultValue = fieldDefault({ column });
|
|
1235
1417
|
const native = extensionNativeType ?? nativeType({ column });
|
|
1236
1418
|
return {
|
|
@@ -1241,6 +1423,7 @@ function buildField(params) {
|
|
|
1241
1423
|
isId: column.__columnType === "id",
|
|
1242
1424
|
isUnique: config.unique === true,
|
|
1243
1425
|
isUpdatedAt: column.__columnType === "datetime" && config.updatedAt === true,
|
|
1426
|
+
...collation === undefined ? {} : { collation },
|
|
1244
1427
|
...dbName === undefined ? {} : { dbName },
|
|
1245
1428
|
...defaultValue === undefined ? {} : { default: defaultValue },
|
|
1246
1429
|
...native === undefined ? {} : { nativeType: native },
|
|
@@ -1286,6 +1469,7 @@ function buildIndex(params) {
|
|
|
1286
1469
|
function buildModel(params) {
|
|
1287
1470
|
const { model, plan, extensions, declarations, issues } = params;
|
|
1288
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 };
|
|
1289
1473
|
const fields2 = [];
|
|
1290
1474
|
for (const [name, column] of model.__columns) {
|
|
1291
1475
|
const extensionTypeName = column.__config["extensionType"];
|
|
@@ -1366,8 +1550,10 @@ function buildModel(params) {
|
|
|
1366
1550
|
relations: (plan.relations.get(model.__name) ?? []).map((relation) => buildRelation({ relation, plan })),
|
|
1367
1551
|
indexes: (plan.indexes.get(model.__name) ?? []).map((index) => buildIndex({ index })),
|
|
1368
1552
|
...model.__checks === undefined || model.__checks.length === 0 ? {} : { checks: model.__checks.map((check) => ({ name: check.name, expression: check.expression })) },
|
|
1553
|
+
...policy === undefined ? {} : { policy },
|
|
1369
1554
|
...primaryKey === undefined ? {} : { primaryKey },
|
|
1370
|
-
isView: false
|
|
1555
|
+
isView: false,
|
|
1556
|
+
...model.__comment === undefined ? {} : { documentation: model.__comment }
|
|
1371
1557
|
};
|
|
1372
1558
|
}
|
|
1373
1559
|
|
|
@@ -1394,13 +1580,25 @@ function toIR(params) {
|
|
|
1394
1580
|
const plan = planSchema({ schema: schema2 });
|
|
1395
1581
|
const declarations = new ExtensionDeclarationCollector;
|
|
1396
1582
|
const issues = [];
|
|
1397
|
-
const models = Array.from(schema2.__models.values(), (model) =>
|
|
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
|
+
}
|
|
1398
1595
|
const enums = Array.from(schema2.__enums, ([name, values]) => ({
|
|
1399
1596
|
name,
|
|
1400
|
-
values: [...values]
|
|
1597
|
+
values: [...values],
|
|
1598
|
+
...schema2.__enumStorage.get(name) === "string" ? { storage: "string" } : {}
|
|
1401
1599
|
}));
|
|
1402
1600
|
if (issues.length > 0) {
|
|
1403
|
-
throw new
|
|
1601
|
+
throw new VibeError3({
|
|
1404
1602
|
code: "VIBE_SCHEMA_INVALID",
|
|
1405
1603
|
message: `Schema validation failed with ${issues.length} issue(s): ${issues.map((issue) => issue.message).join("; ")}`,
|
|
1406
1604
|
meta: { issues }
|
|
@@ -1410,7 +1608,8 @@ function toIR(params) {
|
|
|
1410
1608
|
datasource: { provider },
|
|
1411
1609
|
models,
|
|
1412
1610
|
enums,
|
|
1413
|
-
extensions: declarations.toIR()
|
|
1611
|
+
extensions: declarations.toIR(),
|
|
1612
|
+
...schema2.__rls === undefined ? {} : { rls: { context: schema2.__rls.context } }
|
|
1414
1613
|
};
|
|
1415
1614
|
assertValidSchemaIR({ schema: ir });
|
|
1416
1615
|
return ir;
|
|
@@ -1534,6 +1733,7 @@ var dsl = {
|
|
|
1534
1733
|
defineRelations,
|
|
1535
1734
|
defineRelationsPart,
|
|
1536
1735
|
mergeRelations,
|
|
1736
|
+
defineRls,
|
|
1537
1737
|
schema,
|
|
1538
1738
|
domain,
|
|
1539
1739
|
toIR,
|
|
@@ -1561,6 +1761,7 @@ export {
|
|
|
1561
1761
|
dsl,
|
|
1562
1762
|
domain,
|
|
1563
1763
|
detectOneToOnePairs,
|
|
1764
|
+
defineRls,
|
|
1564
1765
|
defineRelationsPart,
|
|
1565
1766
|
defineRelations,
|
|
1566
1767
|
createIndexBuilder,
|
|
@@ -1579,4 +1780,4 @@ export {
|
|
|
1579
1780
|
ChainableColumn
|
|
1580
1781
|
};
|
|
1581
1782
|
|
|
1582
|
-
//# debugId=
|
|
1783
|
+
//# debugId=EC151CEC887B44C364756E2164756E21
|