arkormx 1.1.0 → 1.2.1

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.cjs CHANGED
@@ -238,6 +238,95 @@ var ForeignKeyBuilder = class {
238
238
 
239
239
  //#endregion
240
240
  //#region src/database/TableBuilder.ts
241
+ const PRISMA_ENUM_MEMBER_REGEX$1 = /^[A-Za-z][A-Za-z0-9_]*$/;
242
+ const normalizeEnumMember = (columnName, value) => {
243
+ const normalized = value.trim();
244
+ if (!normalized) throw new Error(`Enum column [${columnName}] must define only non-empty values.`);
245
+ if (!PRISMA_ENUM_MEMBER_REGEX$1.test(normalized)) throw new Error(`Enum column [${columnName}] contains invalid Prisma enum value [${normalized}].`);
246
+ return normalized;
247
+ };
248
+ const normalizeEnumMembers = (columnName, values) => {
249
+ const normalizedValues = values.map((value) => normalizeEnumMember(columnName, value));
250
+ const seen = /* @__PURE__ */ new Set();
251
+ for (const value of normalizedValues) {
252
+ if (seen.has(value)) throw new Error(`Enum column [${columnName}] contains duplicate enum value [${value}].`);
253
+ seen.add(value);
254
+ }
255
+ return normalizedValues;
256
+ };
257
+ /**
258
+ * The EnumBuilder class provides a fluent interface for configuring enum columns
259
+ * after they are defined on a table.
260
+ *
261
+ * @author Legacy (3m1n3nc3)
262
+ * @since 0.2.3
263
+ */
264
+ var EnumBuilder = class {
265
+ tableBuilder;
266
+ columnName;
267
+ constructor(tableBuilder, columnName) {
268
+ this.tableBuilder = tableBuilder;
269
+ this.columnName = columnName;
270
+ }
271
+ /**
272
+ * Defines the Prisma enum name for this column.
273
+ *
274
+ * @param name
275
+ * @returns
276
+ */
277
+ enumName(name) {
278
+ this.tableBuilder.enumName(name, this.columnName);
279
+ return this;
280
+ }
281
+ /**
282
+ * Marks the enum column as nullable.
283
+ *
284
+ * @returns
285
+ */
286
+ nullable() {
287
+ this.tableBuilder.nullable(this.columnName);
288
+ return this;
289
+ }
290
+ /**
291
+ * Marks the enum column as unique.
292
+ *
293
+ * @returns
294
+ */
295
+ unique() {
296
+ this.tableBuilder.unique(this.columnName);
297
+ return this;
298
+ }
299
+ /**
300
+ * Sets a default value for the enum column.
301
+ *
302
+ * @param value
303
+ * @returns
304
+ */
305
+ default(value) {
306
+ this.tableBuilder.default(value, this.columnName);
307
+ return this;
308
+ }
309
+ /**
310
+ * Positions the enum column after another column when supported.
311
+ *
312
+ * @param referenceColumn
313
+ * @returns
314
+ */
315
+ after(referenceColumn) {
316
+ this.tableBuilder.after(referenceColumn, this.columnName);
317
+ return this;
318
+ }
319
+ /**
320
+ * Maps the enum column to a custom database column name.
321
+ *
322
+ * @param name
323
+ * @returns
324
+ */
325
+ map(name) {
326
+ this.tableBuilder.map(name, this.columnName);
327
+ return this;
328
+ }
329
+ };
241
330
  /**
242
331
  * The TableBuilder class provides a fluent interface for defining
243
332
  * the structure of a database table in a migration, including columns to add or drop.
@@ -290,6 +379,27 @@ var TableBuilder = class {
290
379
  return this.column(name, "uuid", options);
291
380
  }
292
381
  /**
382
+ * Defines an enum column in the table.
383
+ *
384
+ * @param name The name of the enum column.
385
+ * @param values Either an array of string values for the enum or the name of an existing enum to reuse.
386
+ * @param options Additional options for the enum column.
387
+ * @returns
388
+ */
389
+ enum(name, valuesOrEnumName, options = {}) {
390
+ const isEnumReuse = typeof valuesOrEnumName === "string";
391
+ if (!isEnumReuse && valuesOrEnumName.length === 0) throw new Error(`Enum column [${name}] must define at least one value.`);
392
+ const normalizedEnumValues = isEnumReuse ? void 0 : normalizeEnumMembers(name, valuesOrEnumName);
393
+ const enumName = isEnumReuse ? valuesOrEnumName.trim() : options.enumName?.trim();
394
+ if (isEnumReuse && !enumName) throw new Error(`Enum column [${name}] must define an enum name.`);
395
+ this.column(name, "enum", {
396
+ ...options,
397
+ enumName,
398
+ enumValues: normalizedEnumValues
399
+ });
400
+ return new EnumBuilder(this, name);
401
+ }
402
+ /**
293
403
  * Defines a string column in the table.
294
404
  *
295
405
  * @param name The name of the string column.
@@ -459,6 +569,21 @@ var TableBuilder = class {
459
569
  return this;
460
570
  }
461
571
  /**
572
+ * Sets the Prisma enum name for an enum column.
573
+ *
574
+ * @param name The enum name to assign.
575
+ * @param columnName Optional explicit target column name. When omitted, applies to the latest defined column.
576
+ * @returns The current TableBuilder instance for chaining.
577
+ */
578
+ enumName(name, columnName) {
579
+ const column = this.resolveColumn(columnName);
580
+ if (column.type !== "enum") throw new Error(`Column [${column.name}] is not an enum column.`);
581
+ const enumName = name.trim();
582
+ if (!enumName) throw new Error(`Enum column [${column.name}] must define an enum name.`);
583
+ column.enumName = enumName;
584
+ return this;
585
+ }
586
+ /**
462
587
  * Sets a default value for a column.
463
588
  *
464
589
  * @param value The default scalar value or Prisma expression (e.g. 'now()').
@@ -533,7 +658,7 @@ var TableBuilder = class {
533
658
  */
534
659
  foreign(column) {
535
660
  const columnName = this.resolveColumn(column).name;
536
- return this.foreignKey(columnName + (column ? "" : "Id"));
661
+ return this.foreignKey(column ?? columnName);
537
662
  }
538
663
  /**
539
664
  * Returns a deep copy of the defined columns for the table.
@@ -541,7 +666,10 @@ var TableBuilder = class {
541
666
  * @returns
542
667
  */
543
668
  getColumns() {
544
- return this.columns.map((column) => ({ ...column }));
669
+ return this.columns.map((column) => ({
670
+ ...column,
671
+ enumValues: column.enumValues ? [...column.enumValues] : void 0
672
+ }));
545
673
  }
546
674
  /**
547
675
  * Returns a copy of the defined column names to be dropped from the table.
@@ -582,6 +710,8 @@ var TableBuilder = class {
582
710
  this.columns.push({
583
711
  name,
584
712
  type,
713
+ enumName: options.enumName,
714
+ enumValues: options.enumValues ? [...options.enumValues] : void 0,
585
715
  map: options.map,
586
716
  nullable: options.nullable,
587
717
  unique: options.unique,
@@ -681,7 +811,10 @@ var SchemaBuilder = class {
681
811
  return this.operations.map((operation) => {
682
812
  if (operation.type === "createTable") return {
683
813
  ...operation,
684
- columns: operation.columns.map((column) => ({ ...column })),
814
+ columns: operation.columns.map((column) => ({
815
+ ...column,
816
+ enumValues: column.enumValues ? [...column.enumValues] : void 0
817
+ })),
685
818
  indexes: operation.indexes.map((index) => ({
686
819
  ...index,
687
820
  columns: [...index.columns]
@@ -690,7 +823,10 @@ var SchemaBuilder = class {
690
823
  };
691
824
  if (operation.type === "alterTable") return {
692
825
  ...operation,
693
- addColumns: operation.addColumns.map((column) => ({ ...column })),
826
+ addColumns: operation.addColumns.map((column) => ({
827
+ ...column,
828
+ enumValues: column.enumValues ? [...column.enumValues] : void 0
829
+ })),
694
830
  dropColumns: [...operation.dropColumns],
695
831
  addIndexes: operation.addIndexes.map((index) => ({
696
832
  ...index,
@@ -706,6 +842,8 @@ var SchemaBuilder = class {
706
842
  //#endregion
707
843
  //#region src/helpers/migrations.ts
708
844
  const PRISMA_MODEL_REGEX = /model\s+(\w+)\s*\{[\s\S]*?\n\}/g;
845
+ const PRISMA_ENUM_REGEX = /enum\s+(\w+)\s*\{[\s\S]*?\n\}/g;
846
+ const PRISMA_ENUM_MEMBER_REGEX = /^[A-Za-z][A-Za-z0-9_]*$/;
709
847
  /**
710
848
  * Convert a table name to a PascalCase model name, with basic singularization.
711
849
  *
@@ -733,6 +871,7 @@ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
733
871
  */
734
872
  const resolvePrismaType = (column) => {
735
873
  if (column.type === "id") return "Int";
874
+ if (column.type === "enum") return resolveEnumName(column);
736
875
  if (column.type === "uuid") return "String";
737
876
  if (column.type === "string" || column.type === "text") return "String";
738
877
  if (column.type === "integer") return "Int";
@@ -742,6 +881,11 @@ const resolvePrismaType = (column) => {
742
881
  if (column.type === "json") return "Json";
743
882
  return "DateTime";
744
883
  };
884
+ const resolveEnumName = (column) => {
885
+ if (column.type !== "enum") throw new ArkormException(`Column [${column.name}] is not an enum column.`);
886
+ if (column.enumName && column.enumName.trim().length > 0) return column.enumName.trim();
887
+ throw new ArkormException(`Enum column [${column.name}] must define an enum name.`);
888
+ };
745
889
  /**
746
890
  * Format a default value for inclusion in a Prisma schema field definition, based on its type.
747
891
  *
@@ -756,6 +900,61 @@ const formatDefaultValue = (value) => {
756
900
  if (typeof value === "boolean") return `@default(${value ? "true" : "false"})`;
757
901
  };
758
902
  /**
903
+ * Format a default value for an enum column as a Prisma @default attribute, validating that it is a non-empty string.
904
+ *
905
+ * @param value
906
+ * @returns
907
+ */
908
+ const formatEnumDefaultValue = (value) => {
909
+ if (value == null) return void 0;
910
+ if (typeof value !== "string" || value.trim().length === 0) throw new ArkormException("Enum default values must be provided as non-empty strings.");
911
+ return `@default(${value.trim()})`;
912
+ };
913
+ /**
914
+ * Normalize an enum value by ensuring it is a non-empty string and trimming whitespace.
915
+ *
916
+ * @param value
917
+ * @returns
918
+ */
919
+ const normalizeEnumValue = (value) => {
920
+ if (typeof value !== "string" || value.trim().length === 0) throw new ArkormException("Enum values must be provided as non-empty strings.");
921
+ const normalized = value.trim();
922
+ if (!PRISMA_ENUM_MEMBER_REGEX.test(normalized)) throw new ArkormException(`Enum value [${normalized}] is not a valid Prisma enum member name.`);
923
+ return normalized;
924
+ };
925
+ /**
926
+ * Extract the enum values from a Prisma enum block string.
927
+ *
928
+ * @param block
929
+ * @returns
930
+ */
931
+ const extractEnumBlockValues = (block) => {
932
+ return block.split("\n").slice(1, -1).map((line) => line.trim()).filter(Boolean);
933
+ };
934
+ const validateEnumValues = (column, enumName, enumValues) => {
935
+ const normalizedValues = enumValues.map(normalizeEnumValue);
936
+ const seen = /* @__PURE__ */ new Set();
937
+ for (const value of normalizedValues) {
938
+ if (seen.has(value)) throw new ArkormException(`Prisma enum [${enumName}] for column [${column.name}] contains duplicate value [${value}].`);
939
+ seen.add(value);
940
+ }
941
+ return normalizedValues;
942
+ };
943
+ /**
944
+ * Validate that a default value for an enum column is included in the defined enum values.
945
+ *
946
+ * @param column
947
+ * @param enumName
948
+ * @param enumValues
949
+ * @returns
950
+ */
951
+ const validateEnumDefaultValue = (column, enumName, enumValues) => {
952
+ if (column.default == null) return;
953
+ const normalizedDefault = normalizeEnumValue(column.default);
954
+ if (enumValues.includes(normalizedDefault)) return;
955
+ throw new ArkormException(`Enum default value [${normalizedDefault}] is not defined in Prisma enum [${enumName}] for column [${column.name}].`);
956
+ };
957
+ /**
759
958
  * Build a single line of a Prisma model field definition based on a SchemaColumn, including type and modifiers.
760
959
  *
761
960
  * @param column
@@ -776,11 +975,82 @@ const buildFieldLine = (column) => {
776
975
  const primary = column.primary ? " @id" : "";
777
976
  const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
778
977
  const updatedAt = column.updatedAt ? " @updatedAt" : "";
779
- const defaultValue = formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
978
+ const defaultValue = column.type === "enum" ? formatEnumDefaultValue(column.default) : formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
780
979
  const defaultSuffix = defaultValue ? ` ${defaultValue}` : "";
781
980
  return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${updatedAt}${mapped}`;
782
981
  };
783
982
  /**
983
+ * Build a Prisma enum block string based on an enum name and its values, validating that
984
+ * at least one value is provided.
985
+ *
986
+ * @param enumName The name of the enum to create.
987
+ * @param values The array of values for the enum.
988
+ * @returns The Prisma enum block string.
989
+ */
990
+ const buildEnumBlock = (enumName, values) => {
991
+ if (values.length === 0) throw new ArkormException(`Enum [${enumName}] must define at least one value.`);
992
+ return `enum ${enumName} {\n${values.map((value) => ` ${value}`).join("\n")}\n}`;
993
+ };
994
+ /**
995
+ * Find the Prisma enum block in a schema string that corresponds to a given enum
996
+ * name, returning its details if found.
997
+ *
998
+ * @param schema The Prisma schema string to search for the enum block.
999
+ * @param enumName The name of the enum to find in the schema.
1000
+ * @returns
1001
+ */
1002
+ const findEnumBlock = (schema, enumName) => {
1003
+ const candidates = [...schema.matchAll(PRISMA_ENUM_REGEX)];
1004
+ for (const match of candidates) {
1005
+ const block = match[0];
1006
+ const matchedEnumName = match[1];
1007
+ const start = match.index ?? 0;
1008
+ const end = start + block.length;
1009
+ if (matchedEnumName === enumName) return {
1010
+ enumName: matchedEnumName,
1011
+ block,
1012
+ start,
1013
+ end
1014
+ };
1015
+ }
1016
+ return null;
1017
+ };
1018
+ /**
1019
+ * Ensure that Prisma enum blocks exist in the schema for any enum columns defined in a
1020
+ * create or alter table operation, adding them if necessary and validating against
1021
+ * existing blocks.
1022
+ *
1023
+ * @param schema The current Prisma schema string to check and modify.
1024
+ * @param columns The array of schema column definitions to check for enum types and ensure corresponding blocks exist for.
1025
+ * @returns
1026
+ */
1027
+ const ensureEnumBlocks = (schema, columns) => {
1028
+ let nextSchema = schema;
1029
+ for (const column of columns) {
1030
+ if (column.type !== "enum") continue;
1031
+ const enumName = resolveEnumName(column);
1032
+ const enumValues = column.enumValues ?? [];
1033
+ const existing = findEnumBlock(nextSchema, enumName);
1034
+ if (existing) {
1035
+ const existingValues = validateEnumValues(column, enumName, extractEnumBlockValues(existing.block));
1036
+ if (enumValues.length === 0) {
1037
+ validateEnumDefaultValue(column, enumName, existingValues);
1038
+ continue;
1039
+ }
1040
+ const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
1041
+ if (existingValues.join("|") !== normalizedEnumValues.join("|")) throw new ArkormException(`Prisma enum [${enumName}] already exists with different values.`);
1042
+ validateEnumDefaultValue(column, enumName, existingValues);
1043
+ continue;
1044
+ }
1045
+ if (enumValues.length === 0) throw new ArkormException(`Prisma enum [${enumName}] was not found for column [${column.name}].`);
1046
+ const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
1047
+ validateEnumDefaultValue(column, enumName, normalizedEnumValues);
1048
+ const block = buildEnumBlock(enumName, normalizedEnumValues);
1049
+ nextSchema = `${nextSchema.trimEnd()}\n\n${block}\n`;
1050
+ }
1051
+ return nextSchema;
1052
+ };
1053
+ /**
784
1054
  * Build a Prisma model-level @@index definition line.
785
1055
  *
786
1056
  * @param index The schema index definition to convert to a Prisma \@\@index line.
@@ -806,24 +1076,24 @@ const deriveRelationFieldName = (columnName) => {
806
1076
  if (trimmed.endsWith("_id") && trimmed.length > 3) return trimmed.slice(0, -3).replace(/_([a-zA-Z0-9])/g, (_, letter) => letter.toUpperCase());
807
1077
  return `${trimmed.charAt(0).toLowerCase()}${trimmed.slice(1)}`;
808
1078
  };
809
- const pascalWords = (value) => {
810
- return value.match(/[A-Z][a-z0-9]*/g) ?? [value];
811
- };
812
1079
  /**
813
- * Derive a relation name for the inverse side of a relation based on the
1080
+ * Derive a relation name for both sides of a relation based on the
814
1081
  * source and target model names, using an explicit alias if provided or a
815
- * convention of combining the target model name with the last segment of
816
- * the source model name.
1082
+ * convention of combining the full source model name with the target model name.
817
1083
  *
818
1084
  * @param sourceModelName The name of the source model in the relation.
819
1085
  * @param targetModelName The name of the target model in the relation.
820
- * @param explicitAlias An optional explicit alias for the inverse relation.
821
- * @returns The derived or explicit inverse relation alias.
1086
+ * @param explicitAlias An optional explicit alias for the relation.
1087
+ * @returns The derived or explicit relation alias.
822
1088
  */
823
- const deriveInverseRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
1089
+ const deriveRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
824
1090
  if (explicitAlias && explicitAlias.trim().length > 0) return explicitAlias.trim();
825
- const sourceWords = pascalWords(sourceModelName);
826
- return `${sourceWords[sourceWords.length - 1] ?? sourceModelName}${targetModelName}`;
1091
+ return [sourceModelName, targetModelName].sort((left, right) => left.localeCompare(right)).join("");
1092
+ };
1093
+ const deriveInverseRelationAlias = deriveRelationAlias;
1094
+ const deriveSingularFieldName = (modelName) => {
1095
+ if (!modelName) return "item";
1096
+ return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
827
1097
  };
828
1098
  const deriveCollectionFieldName = (modelName) => {
829
1099
  if (!modelName) return "items";
@@ -831,6 +1101,12 @@ const deriveCollectionFieldName = (modelName) => {
831
1101
  if (camel.endsWith("s")) return `${camel}es`;
832
1102
  return `${camel}s`;
833
1103
  };
1104
+ const resolveForeignKeyColumn = (columns, foreignKey) => {
1105
+ return columns.find((column) => column.name === foreignKey.column);
1106
+ };
1107
+ const isOneToOneForeignKey = (column) => {
1108
+ return Boolean(column?.unique || column?.primary);
1109
+ };
834
1110
  /**
835
1111
  * Format a SchemaForeignKeyAction value as a Prisma onDelete action string.
836
1112
  *
@@ -851,15 +1127,16 @@ const formatRelationAction = (action) => {
851
1127
  * @param foreignKey The foreign key definition to convert to a relation line.
852
1128
  * @returns The corresponding Prisma schema line for the relation field.
853
1129
  */
854
- const buildRelationLine = (foreignKey) => {
1130
+ const buildRelationLine = (sourceModelName, foreignKey, columns = []) => {
855
1131
  if (!foreignKey.referencesTable.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced table.`);
856
1132
  if (!foreignKey.referencesColumn.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced column.`);
1133
+ const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
857
1134
  const fieldName = foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column);
858
1135
  const targetModel = toModelName(foreignKey.referencesTable);
859
- const relationName = foreignKey.relationAlias?.trim();
860
- const relationPrefix = relationName ? `@relation("${relationName.replace(/"/g, "\\\"")}", ` : "@relation(";
1136
+ const relationName = deriveRelationAlias(sourceModelName, targetModel, foreignKey.relationAlias?.trim());
1137
+ const optional = sourceColumn?.nullable ? "?" : "";
861
1138
  const onDelete = foreignKey.onDelete ? `, onDelete: ${formatRelationAction(foreignKey.onDelete)}` : "";
862
- return ` ${fieldName} ${targetModel} ${relationPrefix}fields: [${foreignKey.column}], references: [${foreignKey.referencesColumn}]${onDelete})`;
1139
+ return ` ${fieldName} ${targetModel}${optional} @relation("${relationName.replace(/"/g, "\\\"")}", fields: [${foreignKey.column}], references: [${foreignKey.referencesColumn}]${onDelete})`;
863
1140
  };
864
1141
  /**
865
1142
  * Build a Prisma relation field line for the inverse side of a relation, based
@@ -871,8 +1148,11 @@ const buildRelationLine = (foreignKey) => {
871
1148
  * @param foreignKey The foreign key definition for the relation.
872
1149
  * @returns The Prisma schema line for the inverse relation field.
873
1150
  */
874
- const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey) => {
875
- return ` ${deriveCollectionFieldName(sourceModelName)} ${sourceModelName}[] @relation("${deriveInverseRelationAlias(sourceModelName, targetModelName, foreignKey.inverseRelationAlias).replace(/"/g, "\\\"")}")`;
1151
+ const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey, columns = []) => {
1152
+ const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1153
+ const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
1154
+ const relationName = deriveRelationAlias(sourceModelName, targetModelName, foreignKey.relationAlias?.trim());
1155
+ return ` ${fieldName} ${isOneToOneForeignKey(sourceColumn) ? `${sourceModelName}?` : `${sourceModelName}[]`} @relation("${relationName.replace(/"/g, "\\\"")}")`;
876
1156
  };
877
1157
  /**
878
1158
  * Inject a line into the body of a Prisma model block if it does not already
@@ -900,14 +1180,15 @@ const injectLineIntoModelBody = (bodyLines, line, exists) => {
900
1180
  * @param foreignKeys An array of foreign key definitions to process.
901
1181
  * @returns The updated Prisma schema string with inverse relations applied.
902
1182
  */
903
- const applyInverseRelations = (schema, sourceModelName, foreignKeys) => {
1183
+ const applyInverseRelations = (schema, sourceModelName, foreignKeys, columns = []) => {
904
1184
  let nextSchema = schema;
905
1185
  for (const foreignKey of foreignKeys) {
906
1186
  const targetModel = findModelBlock(nextSchema, foreignKey.referencesTable);
907
1187
  if (!targetModel) continue;
908
- const inverseLine = buildInverseRelationLine(sourceModelName, targetModel.modelName, foreignKey);
1188
+ const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
1189
+ const inverseLine = buildInverseRelationLine(sourceModelName, targetModel.modelName, foreignKey, columns);
909
1190
  const targetBodyLines = targetModel.block.split("\n");
910
- const fieldName = deriveCollectionFieldName(sourceModelName);
1191
+ const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
911
1192
  const fieldRegex = new RegExp(`^\\s*${escapeRegex(fieldName)}\\s+`);
912
1193
  injectLineIntoModelBody(targetBodyLines, inverseLine, (line) => fieldRegex.test(line));
913
1194
  const updatedTarget = targetBodyLines.join("\n");
@@ -926,7 +1207,7 @@ const buildModelBlock = (operation) => {
926
1207
  const modelName = toModelName(operation.table);
927
1208
  const mapped = operation.table !== modelName.toLowerCase();
928
1209
  const fields = operation.columns.map(buildFieldLine);
929
- const relations = (operation.foreignKeys ?? []).map(buildRelationLine);
1210
+ const relations = (operation.foreignKeys ?? []).map((foreignKey) => buildRelationLine(modelName, foreignKey, operation.columns));
930
1211
  const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${(0, _h3ravel_support.str)(operation.table).snake()}")`] : []];
931
1212
  return `model ${modelName} {\n${(metadata.length > 0 ? [
932
1213
  ...fields,
@@ -982,8 +1263,9 @@ const findModelBlock = (schema, table) => {
982
1263
  */
983
1264
  const applyCreateTableOperation = (schema, operation) => {
984
1265
  if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
1266
+ const schemaWithEnums = ensureEnumBlocks(schema, operation.columns);
985
1267
  const block = buildModelBlock(operation);
986
- return applyInverseRelations(`${schema.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? []);
1268
+ return applyInverseRelations(`${schemaWithEnums.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? [], operation.columns);
987
1269
  };
988
1270
  /**
989
1271
  * Apply an alter table operation to a Prisma schema string, modifying the model
@@ -996,7 +1278,10 @@ const applyCreateTableOperation = (schema, operation) => {
996
1278
  const applyAlterTableOperation = (schema, operation) => {
997
1279
  const model = findModelBlock(schema, operation.table);
998
1280
  if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
999
- let block = model.block;
1281
+ const schemaWithEnums = ensureEnumBlocks(schema, operation.addColumns);
1282
+ const refreshedModel = findModelBlock(schemaWithEnums, operation.table);
1283
+ if (!refreshedModel) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
1284
+ let block = refreshedModel.block;
1000
1285
  const bodyLines = block.split("\n");
1001
1286
  operation.dropColumns.forEach((column) => {
1002
1287
  const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
@@ -1021,12 +1306,12 @@ const applyAlterTableOperation = (schema, operation) => {
1021
1306
  bodyLines.splice(insertIndex, 0, indexLine);
1022
1307
  });
1023
1308
  for (const foreignKey of operation.addForeignKeys ?? []) {
1024
- const relationLine = buildRelationLine(foreignKey);
1309
+ const relationLine = buildRelationLine(model.modelName, foreignKey, operation.addColumns);
1025
1310
  const relationRegex = new RegExp(`^\\s*${escapeRegex(foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column))}\\s+`);
1026
1311
  injectLineIntoModelBody(bodyLines, relationLine, (line) => relationRegex.test(line));
1027
1312
  }
1028
1313
  block = bodyLines.join("\n");
1029
- return applyInverseRelations(`${schema.slice(0, model.start)}${block}${schema.slice(model.end)}`, model.modelName, operation.addForeignKeys ?? []);
1314
+ return applyInverseRelations(`${schemaWithEnums.slice(0, refreshedModel.start)}${block}${schemaWithEnums.slice(refreshedModel.end)}`, model.modelName, operation.addForeignKeys ?? [], operation.addColumns);
1030
1315
  };
1031
1316
  /**
1032
1317
  * Apply a drop table operation to a Prisma schema string, removing the model block
@@ -6135,6 +6420,7 @@ exports.ArkormCollection = ArkormCollection;
6135
6420
  exports.ArkormException = ArkormException;
6136
6421
  exports.Attribute = Attribute;
6137
6422
  exports.CliApp = CliApp;
6423
+ exports.EnumBuilder = EnumBuilder;
6138
6424
  exports.ForeignKeyBuilder = ForeignKeyBuilder;
6139
6425
  exports.InitCommand = InitCommand;
6140
6426
  exports.InlineFactory = InlineFactory;
@@ -6153,6 +6439,8 @@ exports.Model = Model;
6153
6439
  exports.ModelFactory = ModelFactory;
6154
6440
  exports.ModelNotFoundException = ModelNotFoundException;
6155
6441
  exports.ModelsSyncCommand = ModelsSyncCommand;
6442
+ exports.PRISMA_ENUM_MEMBER_REGEX = PRISMA_ENUM_MEMBER_REGEX;
6443
+ exports.PRISMA_ENUM_REGEX = PRISMA_ENUM_REGEX;
6156
6444
  exports.PRISMA_MODEL_REGEX = PRISMA_MODEL_REGEX;
6157
6445
  exports.Paginator = Paginator;
6158
6446
  exports.QueryBuilder = QueryBuilder;
@@ -6173,6 +6461,7 @@ exports.applyDropTableOperation = applyDropTableOperation;
6173
6461
  exports.applyMigrationRollbackToPrismaSchema = applyMigrationRollbackToPrismaSchema;
6174
6462
  exports.applyMigrationToPrismaSchema = applyMigrationToPrismaSchema;
6175
6463
  exports.applyOperationsToPrismaSchema = applyOperationsToPrismaSchema;
6464
+ exports.buildEnumBlock = buildEnumBlock;
6176
6465
  exports.buildFieldLine = buildFieldLine;
6177
6466
  exports.buildIndexLine = buildIndexLine;
6178
6467
  exports.buildInverseRelationLine = buildInverseRelationLine;
@@ -6190,12 +6479,16 @@ exports.defineConfig = defineConfig;
6190
6479
  exports.defineFactory = defineFactory;
6191
6480
  exports.deriveCollectionFieldName = deriveCollectionFieldName;
6192
6481
  exports.deriveInverseRelationAlias = deriveInverseRelationAlias;
6482
+ exports.deriveRelationAlias = deriveRelationAlias;
6193
6483
  exports.deriveRelationFieldName = deriveRelationFieldName;
6484
+ exports.deriveSingularFieldName = deriveSingularFieldName;
6194
6485
  exports.ensureArkormConfigLoading = ensureArkormConfigLoading;
6195
6486
  exports.escapeRegex = escapeRegex;
6196
6487
  exports.findAppliedMigration = findAppliedMigration;
6488
+ exports.findEnumBlock = findEnumBlock;
6197
6489
  exports.findModelBlock = findModelBlock;
6198
6490
  exports.formatDefaultValue = formatDefaultValue;
6491
+ exports.formatEnumDefaultValue = formatEnumDefaultValue;
6199
6492
  exports.formatRelationAction = formatRelationAction;
6200
6493
  exports.generateMigrationFile = generateMigrationFile;
6201
6494
  exports.getActiveTransactionClient = getActiveTransactionClient;
@@ -6219,6 +6512,7 @@ exports.readAppliedMigrationsState = readAppliedMigrationsState;
6219
6512
  exports.removeAppliedMigration = removeAppliedMigration;
6220
6513
  exports.resetArkormRuntimeForTests = resetArkormRuntimeForTests;
6221
6514
  exports.resolveCast = resolveCast;
6515
+ exports.resolveEnumName = resolveEnumName;
6222
6516
  exports.resolveMigrationClassName = resolveMigrationClassName;
6223
6517
  exports.resolveMigrationStateFilePath = resolveMigrationStateFilePath;
6224
6518
  exports.resolvePrismaType = resolvePrismaType;