@vibeorm/dsl 2.0.0-alpha.6 → 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/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({ options, config: restConfig });
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("${String(column.__config.dbType)}")${list}`;
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("${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("${value}")`;
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("${column.__config.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("${relationName}")`;
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("${relationName}")`;
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(`"${relationName}"`);
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: ${JSON.stringify(index.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: ${JSON.stringify(index.clientName)}`;
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 `"${value}"`;
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 = [`model ${model.__name} {`];
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(${JSON.stringify(check.expression)}, name: ${JSON.stringify(check.name)})`);
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("${model.__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, standaloneEnums } = params;
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
- standaloneEnums.set(value.__name, value.__options);
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, standaloneEnums });
1093
+ extractModelsAndEnums({ input: value, models, enums });
989
1094
  }
990
1095
  }
991
1096
  }
992
1097
  function resolveEnums(params) {
993
- const { models, standaloneEnums } = params;
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
- if (!enums.has(column.__enumName)) {
1001
- enums.set(column.__enumName, column.__enumValues);
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
- enums.set(name, column.__enumValues);
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
- return enums;
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 standaloneEnums = new Map;
1015
- extractModelsAndEnums({ input, models, standaloneEnums });
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: resolveEnums({ models, standaloneEnums }),
1020
- __relations: options?.relations ?? {}
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: Number(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 === "string") {
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) => buildModel({ model, plan, extensions, declarations, issues }));
1583
+ const models = Array.from(schema2.__models.values(), (model) => {
1584
+ const built = buildModel({ model, plan, extensions, declarations, issues });
1585
+ const rls = schema2.__rls?.policies[model.__name];
1586
+ return rls === undefined ? built : { ...built, rls };
1587
+ });
1588
+ if (schema2.__rls !== undefined) {
1589
+ for (const name of Object.keys(schema2.__rls.policies)) {
1590
+ if (!schema2.__models.has(name)) {
1591
+ issues.push({ rule: "rls-unknown-model", model: name, message: `RLS classification references model "${name}" absent from the composed schema` });
1592
+ }
1593
+ }
1594
+ }
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 VibeError({
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=EB3F790CB1F4BD1C64756E2164756E21
1783
+ //# debugId=EC151CEC887B44C364756E2164756E21