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/cli.mjs +314 -30
- package/dist/index.cjs +324 -30
- package/dist/index.d.cts +116 -9
- package/dist/index.d.mts +116 -9
- package/dist/index.mjs +316 -31
- package/package.json +1 -1
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(
|
|
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) => ({
|
|
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) => ({
|
|
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) => ({
|
|
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
|
|
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
|
|
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
|
|
821
|
-
* @returns The derived or explicit
|
|
1086
|
+
* @param explicitAlias An optional explicit alias for the relation.
|
|
1087
|
+
* @returns The derived or explicit relation alias.
|
|
822
1088
|
*/
|
|
823
|
-
const
|
|
1089
|
+
const deriveRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
|
|
824
1090
|
if (explicitAlias && explicitAlias.trim().length > 0) return explicitAlias.trim();
|
|
825
|
-
|
|
826
|
-
|
|
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
|
|
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} ${
|
|
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
|
-
|
|
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
|
|
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(`${
|
|
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
|
-
|
|
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(`${
|
|
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;
|