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.mjs
CHANGED
|
@@ -209,6 +209,95 @@ var ForeignKeyBuilder = class {
|
|
|
209
209
|
|
|
210
210
|
//#endregion
|
|
211
211
|
//#region src/database/TableBuilder.ts
|
|
212
|
+
const PRISMA_ENUM_MEMBER_REGEX$1 = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
213
|
+
const normalizeEnumMember = (columnName, value) => {
|
|
214
|
+
const normalized = value.trim();
|
|
215
|
+
if (!normalized) throw new Error(`Enum column [${columnName}] must define only non-empty values.`);
|
|
216
|
+
if (!PRISMA_ENUM_MEMBER_REGEX$1.test(normalized)) throw new Error(`Enum column [${columnName}] contains invalid Prisma enum value [${normalized}].`);
|
|
217
|
+
return normalized;
|
|
218
|
+
};
|
|
219
|
+
const normalizeEnumMembers = (columnName, values) => {
|
|
220
|
+
const normalizedValues = values.map((value) => normalizeEnumMember(columnName, value));
|
|
221
|
+
const seen = /* @__PURE__ */ new Set();
|
|
222
|
+
for (const value of normalizedValues) {
|
|
223
|
+
if (seen.has(value)) throw new Error(`Enum column [${columnName}] contains duplicate enum value [${value}].`);
|
|
224
|
+
seen.add(value);
|
|
225
|
+
}
|
|
226
|
+
return normalizedValues;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* The EnumBuilder class provides a fluent interface for configuring enum columns
|
|
230
|
+
* after they are defined on a table.
|
|
231
|
+
*
|
|
232
|
+
* @author Legacy (3m1n3nc3)
|
|
233
|
+
* @since 0.2.3
|
|
234
|
+
*/
|
|
235
|
+
var EnumBuilder = class {
|
|
236
|
+
tableBuilder;
|
|
237
|
+
columnName;
|
|
238
|
+
constructor(tableBuilder, columnName) {
|
|
239
|
+
this.tableBuilder = tableBuilder;
|
|
240
|
+
this.columnName = columnName;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Defines the Prisma enum name for this column.
|
|
244
|
+
*
|
|
245
|
+
* @param name
|
|
246
|
+
* @returns
|
|
247
|
+
*/
|
|
248
|
+
enumName(name) {
|
|
249
|
+
this.tableBuilder.enumName(name, this.columnName);
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Marks the enum column as nullable.
|
|
254
|
+
*
|
|
255
|
+
* @returns
|
|
256
|
+
*/
|
|
257
|
+
nullable() {
|
|
258
|
+
this.tableBuilder.nullable(this.columnName);
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Marks the enum column as unique.
|
|
263
|
+
*
|
|
264
|
+
* @returns
|
|
265
|
+
*/
|
|
266
|
+
unique() {
|
|
267
|
+
this.tableBuilder.unique(this.columnName);
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Sets a default value for the enum column.
|
|
272
|
+
*
|
|
273
|
+
* @param value
|
|
274
|
+
* @returns
|
|
275
|
+
*/
|
|
276
|
+
default(value) {
|
|
277
|
+
this.tableBuilder.default(value, this.columnName);
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Positions the enum column after another column when supported.
|
|
282
|
+
*
|
|
283
|
+
* @param referenceColumn
|
|
284
|
+
* @returns
|
|
285
|
+
*/
|
|
286
|
+
after(referenceColumn) {
|
|
287
|
+
this.tableBuilder.after(referenceColumn, this.columnName);
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Maps the enum column to a custom database column name.
|
|
292
|
+
*
|
|
293
|
+
* @param name
|
|
294
|
+
* @returns
|
|
295
|
+
*/
|
|
296
|
+
map(name) {
|
|
297
|
+
this.tableBuilder.map(name, this.columnName);
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
212
301
|
/**
|
|
213
302
|
* The TableBuilder class provides a fluent interface for defining
|
|
214
303
|
* the structure of a database table in a migration, including columns to add or drop.
|
|
@@ -261,6 +350,27 @@ var TableBuilder = class {
|
|
|
261
350
|
return this.column(name, "uuid", options);
|
|
262
351
|
}
|
|
263
352
|
/**
|
|
353
|
+
* Defines an enum column in the table.
|
|
354
|
+
*
|
|
355
|
+
* @param name The name of the enum column.
|
|
356
|
+
* @param values Either an array of string values for the enum or the name of an existing enum to reuse.
|
|
357
|
+
* @param options Additional options for the enum column.
|
|
358
|
+
* @returns
|
|
359
|
+
*/
|
|
360
|
+
enum(name, valuesOrEnumName, options = {}) {
|
|
361
|
+
const isEnumReuse = typeof valuesOrEnumName === "string";
|
|
362
|
+
if (!isEnumReuse && valuesOrEnumName.length === 0) throw new Error(`Enum column [${name}] must define at least one value.`);
|
|
363
|
+
const normalizedEnumValues = isEnumReuse ? void 0 : normalizeEnumMembers(name, valuesOrEnumName);
|
|
364
|
+
const enumName = isEnumReuse ? valuesOrEnumName.trim() : options.enumName?.trim();
|
|
365
|
+
if (isEnumReuse && !enumName) throw new Error(`Enum column [${name}] must define an enum name.`);
|
|
366
|
+
this.column(name, "enum", {
|
|
367
|
+
...options,
|
|
368
|
+
enumName,
|
|
369
|
+
enumValues: normalizedEnumValues
|
|
370
|
+
});
|
|
371
|
+
return new EnumBuilder(this, name);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
264
374
|
* Defines a string column in the table.
|
|
265
375
|
*
|
|
266
376
|
* @param name The name of the string column.
|
|
@@ -430,6 +540,21 @@ var TableBuilder = class {
|
|
|
430
540
|
return this;
|
|
431
541
|
}
|
|
432
542
|
/**
|
|
543
|
+
* Sets the Prisma enum name for an enum column.
|
|
544
|
+
*
|
|
545
|
+
* @param name The enum name to assign.
|
|
546
|
+
* @param columnName Optional explicit target column name. When omitted, applies to the latest defined column.
|
|
547
|
+
* @returns The current TableBuilder instance for chaining.
|
|
548
|
+
*/
|
|
549
|
+
enumName(name, columnName) {
|
|
550
|
+
const column = this.resolveColumn(columnName);
|
|
551
|
+
if (column.type !== "enum") throw new Error(`Column [${column.name}] is not an enum column.`);
|
|
552
|
+
const enumName = name.trim();
|
|
553
|
+
if (!enumName) throw new Error(`Enum column [${column.name}] must define an enum name.`);
|
|
554
|
+
column.enumName = enumName;
|
|
555
|
+
return this;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
433
558
|
* Sets a default value for a column.
|
|
434
559
|
*
|
|
435
560
|
* @param value The default scalar value or Prisma expression (e.g. 'now()').
|
|
@@ -504,7 +629,7 @@ var TableBuilder = class {
|
|
|
504
629
|
*/
|
|
505
630
|
foreign(column) {
|
|
506
631
|
const columnName = this.resolveColumn(column).name;
|
|
507
|
-
return this.foreignKey(
|
|
632
|
+
return this.foreignKey(column ?? columnName);
|
|
508
633
|
}
|
|
509
634
|
/**
|
|
510
635
|
* Returns a deep copy of the defined columns for the table.
|
|
@@ -512,7 +637,10 @@ var TableBuilder = class {
|
|
|
512
637
|
* @returns
|
|
513
638
|
*/
|
|
514
639
|
getColumns() {
|
|
515
|
-
return this.columns.map((column) => ({
|
|
640
|
+
return this.columns.map((column) => ({
|
|
641
|
+
...column,
|
|
642
|
+
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
643
|
+
}));
|
|
516
644
|
}
|
|
517
645
|
/**
|
|
518
646
|
* Returns a copy of the defined column names to be dropped from the table.
|
|
@@ -553,6 +681,8 @@ var TableBuilder = class {
|
|
|
553
681
|
this.columns.push({
|
|
554
682
|
name,
|
|
555
683
|
type,
|
|
684
|
+
enumName: options.enumName,
|
|
685
|
+
enumValues: options.enumValues ? [...options.enumValues] : void 0,
|
|
556
686
|
map: options.map,
|
|
557
687
|
nullable: options.nullable,
|
|
558
688
|
unique: options.unique,
|
|
@@ -652,7 +782,10 @@ var SchemaBuilder = class {
|
|
|
652
782
|
return this.operations.map((operation) => {
|
|
653
783
|
if (operation.type === "createTable") return {
|
|
654
784
|
...operation,
|
|
655
|
-
columns: operation.columns.map((column) => ({
|
|
785
|
+
columns: operation.columns.map((column) => ({
|
|
786
|
+
...column,
|
|
787
|
+
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
788
|
+
})),
|
|
656
789
|
indexes: operation.indexes.map((index) => ({
|
|
657
790
|
...index,
|
|
658
791
|
columns: [...index.columns]
|
|
@@ -661,7 +794,10 @@ var SchemaBuilder = class {
|
|
|
661
794
|
};
|
|
662
795
|
if (operation.type === "alterTable") return {
|
|
663
796
|
...operation,
|
|
664
|
-
addColumns: operation.addColumns.map((column) => ({
|
|
797
|
+
addColumns: operation.addColumns.map((column) => ({
|
|
798
|
+
...column,
|
|
799
|
+
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
800
|
+
})),
|
|
665
801
|
dropColumns: [...operation.dropColumns],
|
|
666
802
|
addIndexes: operation.addIndexes.map((index) => ({
|
|
667
803
|
...index,
|
|
@@ -677,6 +813,8 @@ var SchemaBuilder = class {
|
|
|
677
813
|
//#endregion
|
|
678
814
|
//#region src/helpers/migrations.ts
|
|
679
815
|
const PRISMA_MODEL_REGEX = /model\s+(\w+)\s*\{[\s\S]*?\n\}/g;
|
|
816
|
+
const PRISMA_ENUM_REGEX = /enum\s+(\w+)\s*\{[\s\S]*?\n\}/g;
|
|
817
|
+
const PRISMA_ENUM_MEMBER_REGEX = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
680
818
|
/**
|
|
681
819
|
* Convert a table name to a PascalCase model name, with basic singularization.
|
|
682
820
|
*
|
|
@@ -704,6 +842,7 @@ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
|
704
842
|
*/
|
|
705
843
|
const resolvePrismaType = (column) => {
|
|
706
844
|
if (column.type === "id") return "Int";
|
|
845
|
+
if (column.type === "enum") return resolveEnumName(column);
|
|
707
846
|
if (column.type === "uuid") return "String";
|
|
708
847
|
if (column.type === "string" || column.type === "text") return "String";
|
|
709
848
|
if (column.type === "integer") return "Int";
|
|
@@ -713,6 +852,11 @@ const resolvePrismaType = (column) => {
|
|
|
713
852
|
if (column.type === "json") return "Json";
|
|
714
853
|
return "DateTime";
|
|
715
854
|
};
|
|
855
|
+
const resolveEnumName = (column) => {
|
|
856
|
+
if (column.type !== "enum") throw new ArkormException(`Column [${column.name}] is not an enum column.`);
|
|
857
|
+
if (column.enumName && column.enumName.trim().length > 0) return column.enumName.trim();
|
|
858
|
+
throw new ArkormException(`Enum column [${column.name}] must define an enum name.`);
|
|
859
|
+
};
|
|
716
860
|
/**
|
|
717
861
|
* Format a default value for inclusion in a Prisma schema field definition, based on its type.
|
|
718
862
|
*
|
|
@@ -727,6 +871,61 @@ const formatDefaultValue = (value) => {
|
|
|
727
871
|
if (typeof value === "boolean") return `@default(${value ? "true" : "false"})`;
|
|
728
872
|
};
|
|
729
873
|
/**
|
|
874
|
+
* Format a default value for an enum column as a Prisma @default attribute, validating that it is a non-empty string.
|
|
875
|
+
*
|
|
876
|
+
* @param value
|
|
877
|
+
* @returns
|
|
878
|
+
*/
|
|
879
|
+
const formatEnumDefaultValue = (value) => {
|
|
880
|
+
if (value == null) return void 0;
|
|
881
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new ArkormException("Enum default values must be provided as non-empty strings.");
|
|
882
|
+
return `@default(${value.trim()})`;
|
|
883
|
+
};
|
|
884
|
+
/**
|
|
885
|
+
* Normalize an enum value by ensuring it is a non-empty string and trimming whitespace.
|
|
886
|
+
*
|
|
887
|
+
* @param value
|
|
888
|
+
* @returns
|
|
889
|
+
*/
|
|
890
|
+
const normalizeEnumValue = (value) => {
|
|
891
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new ArkormException("Enum values must be provided as non-empty strings.");
|
|
892
|
+
const normalized = value.trim();
|
|
893
|
+
if (!PRISMA_ENUM_MEMBER_REGEX.test(normalized)) throw new ArkormException(`Enum value [${normalized}] is not a valid Prisma enum member name.`);
|
|
894
|
+
return normalized;
|
|
895
|
+
};
|
|
896
|
+
/**
|
|
897
|
+
* Extract the enum values from a Prisma enum block string.
|
|
898
|
+
*
|
|
899
|
+
* @param block
|
|
900
|
+
* @returns
|
|
901
|
+
*/
|
|
902
|
+
const extractEnumBlockValues = (block) => {
|
|
903
|
+
return block.split("\n").slice(1, -1).map((line) => line.trim()).filter(Boolean);
|
|
904
|
+
};
|
|
905
|
+
const validateEnumValues = (column, enumName, enumValues) => {
|
|
906
|
+
const normalizedValues = enumValues.map(normalizeEnumValue);
|
|
907
|
+
const seen = /* @__PURE__ */ new Set();
|
|
908
|
+
for (const value of normalizedValues) {
|
|
909
|
+
if (seen.has(value)) throw new ArkormException(`Prisma enum [${enumName}] for column [${column.name}] contains duplicate value [${value}].`);
|
|
910
|
+
seen.add(value);
|
|
911
|
+
}
|
|
912
|
+
return normalizedValues;
|
|
913
|
+
};
|
|
914
|
+
/**
|
|
915
|
+
* Validate that a default value for an enum column is included in the defined enum values.
|
|
916
|
+
*
|
|
917
|
+
* @param column
|
|
918
|
+
* @param enumName
|
|
919
|
+
* @param enumValues
|
|
920
|
+
* @returns
|
|
921
|
+
*/
|
|
922
|
+
const validateEnumDefaultValue = (column, enumName, enumValues) => {
|
|
923
|
+
if (column.default == null) return;
|
|
924
|
+
const normalizedDefault = normalizeEnumValue(column.default);
|
|
925
|
+
if (enumValues.includes(normalizedDefault)) return;
|
|
926
|
+
throw new ArkormException(`Enum default value [${normalizedDefault}] is not defined in Prisma enum [${enumName}] for column [${column.name}].`);
|
|
927
|
+
};
|
|
928
|
+
/**
|
|
730
929
|
* Build a single line of a Prisma model field definition based on a SchemaColumn, including type and modifiers.
|
|
731
930
|
*
|
|
732
931
|
* @param column
|
|
@@ -747,11 +946,82 @@ const buildFieldLine = (column) => {
|
|
|
747
946
|
const primary = column.primary ? " @id" : "";
|
|
748
947
|
const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
|
|
749
948
|
const updatedAt = column.updatedAt ? " @updatedAt" : "";
|
|
750
|
-
const defaultValue = formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
|
|
949
|
+
const defaultValue = column.type === "enum" ? formatEnumDefaultValue(column.default) : formatDefaultValue(column.default) ?? (column.type === "uuid" && column.primary ? "@default(uuid())" : void 0);
|
|
751
950
|
const defaultSuffix = defaultValue ? ` ${defaultValue}` : "";
|
|
752
951
|
return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${updatedAt}${mapped}`;
|
|
753
952
|
};
|
|
754
953
|
/**
|
|
954
|
+
* Build a Prisma enum block string based on an enum name and its values, validating that
|
|
955
|
+
* at least one value is provided.
|
|
956
|
+
*
|
|
957
|
+
* @param enumName The name of the enum to create.
|
|
958
|
+
* @param values The array of values for the enum.
|
|
959
|
+
* @returns The Prisma enum block string.
|
|
960
|
+
*/
|
|
961
|
+
const buildEnumBlock = (enumName, values) => {
|
|
962
|
+
if (values.length === 0) throw new ArkormException(`Enum [${enumName}] must define at least one value.`);
|
|
963
|
+
return `enum ${enumName} {\n${values.map((value) => ` ${value}`).join("\n")}\n}`;
|
|
964
|
+
};
|
|
965
|
+
/**
|
|
966
|
+
* Find the Prisma enum block in a schema string that corresponds to a given enum
|
|
967
|
+
* name, returning its details if found.
|
|
968
|
+
*
|
|
969
|
+
* @param schema The Prisma schema string to search for the enum block.
|
|
970
|
+
* @param enumName The name of the enum to find in the schema.
|
|
971
|
+
* @returns
|
|
972
|
+
*/
|
|
973
|
+
const findEnumBlock = (schema, enumName) => {
|
|
974
|
+
const candidates = [...schema.matchAll(PRISMA_ENUM_REGEX)];
|
|
975
|
+
for (const match of candidates) {
|
|
976
|
+
const block = match[0];
|
|
977
|
+
const matchedEnumName = match[1];
|
|
978
|
+
const start = match.index ?? 0;
|
|
979
|
+
const end = start + block.length;
|
|
980
|
+
if (matchedEnumName === enumName) return {
|
|
981
|
+
enumName: matchedEnumName,
|
|
982
|
+
block,
|
|
983
|
+
start,
|
|
984
|
+
end
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
return null;
|
|
988
|
+
};
|
|
989
|
+
/**
|
|
990
|
+
* Ensure that Prisma enum blocks exist in the schema for any enum columns defined in a
|
|
991
|
+
* create or alter table operation, adding them if necessary and validating against
|
|
992
|
+
* existing blocks.
|
|
993
|
+
*
|
|
994
|
+
* @param schema The current Prisma schema string to check and modify.
|
|
995
|
+
* @param columns The array of schema column definitions to check for enum types and ensure corresponding blocks exist for.
|
|
996
|
+
* @returns
|
|
997
|
+
*/
|
|
998
|
+
const ensureEnumBlocks = (schema, columns) => {
|
|
999
|
+
let nextSchema = schema;
|
|
1000
|
+
for (const column of columns) {
|
|
1001
|
+
if (column.type !== "enum") continue;
|
|
1002
|
+
const enumName = resolveEnumName(column);
|
|
1003
|
+
const enumValues = column.enumValues ?? [];
|
|
1004
|
+
const existing = findEnumBlock(nextSchema, enumName);
|
|
1005
|
+
if (existing) {
|
|
1006
|
+
const existingValues = validateEnumValues(column, enumName, extractEnumBlockValues(existing.block));
|
|
1007
|
+
if (enumValues.length === 0) {
|
|
1008
|
+
validateEnumDefaultValue(column, enumName, existingValues);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
|
|
1012
|
+
if (existingValues.join("|") !== normalizedEnumValues.join("|")) throw new ArkormException(`Prisma enum [${enumName}] already exists with different values.`);
|
|
1013
|
+
validateEnumDefaultValue(column, enumName, existingValues);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (enumValues.length === 0) throw new ArkormException(`Prisma enum [${enumName}] was not found for column [${column.name}].`);
|
|
1017
|
+
const normalizedEnumValues = validateEnumValues(column, enumName, enumValues);
|
|
1018
|
+
validateEnumDefaultValue(column, enumName, normalizedEnumValues);
|
|
1019
|
+
const block = buildEnumBlock(enumName, normalizedEnumValues);
|
|
1020
|
+
nextSchema = `${nextSchema.trimEnd()}\n\n${block}\n`;
|
|
1021
|
+
}
|
|
1022
|
+
return nextSchema;
|
|
1023
|
+
};
|
|
1024
|
+
/**
|
|
755
1025
|
* Build a Prisma model-level @@index definition line.
|
|
756
1026
|
*
|
|
757
1027
|
* @param index The schema index definition to convert to a Prisma \@\@index line.
|
|
@@ -777,24 +1047,24 @@ const deriveRelationFieldName = (columnName) => {
|
|
|
777
1047
|
if (trimmed.endsWith("_id") && trimmed.length > 3) return trimmed.slice(0, -3).replace(/_([a-zA-Z0-9])/g, (_, letter) => letter.toUpperCase());
|
|
778
1048
|
return `${trimmed.charAt(0).toLowerCase()}${trimmed.slice(1)}`;
|
|
779
1049
|
};
|
|
780
|
-
const pascalWords = (value) => {
|
|
781
|
-
return value.match(/[A-Z][a-z0-9]*/g) ?? [value];
|
|
782
|
-
};
|
|
783
1050
|
/**
|
|
784
|
-
* Derive a relation name for
|
|
1051
|
+
* Derive a relation name for both sides of a relation based on the
|
|
785
1052
|
* source and target model names, using an explicit alias if provided or a
|
|
786
|
-
* convention of combining the
|
|
787
|
-
* the source model name.
|
|
1053
|
+
* convention of combining the full source model name with the target model name.
|
|
788
1054
|
*
|
|
789
1055
|
* @param sourceModelName The name of the source model in the relation.
|
|
790
1056
|
* @param targetModelName The name of the target model in the relation.
|
|
791
|
-
* @param explicitAlias An optional explicit alias for the
|
|
792
|
-
* @returns The derived or explicit
|
|
1057
|
+
* @param explicitAlias An optional explicit alias for the relation.
|
|
1058
|
+
* @returns The derived or explicit relation alias.
|
|
793
1059
|
*/
|
|
794
|
-
const
|
|
1060
|
+
const deriveRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
|
|
795
1061
|
if (explicitAlias && explicitAlias.trim().length > 0) return explicitAlias.trim();
|
|
796
|
-
|
|
797
|
-
|
|
1062
|
+
return [sourceModelName, targetModelName].sort((left, right) => left.localeCompare(right)).join("");
|
|
1063
|
+
};
|
|
1064
|
+
const deriveInverseRelationAlias = deriveRelationAlias;
|
|
1065
|
+
const deriveSingularFieldName = (modelName) => {
|
|
1066
|
+
if (!modelName) return "item";
|
|
1067
|
+
return `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
|
|
798
1068
|
};
|
|
799
1069
|
const deriveCollectionFieldName = (modelName) => {
|
|
800
1070
|
if (!modelName) return "items";
|
|
@@ -802,6 +1072,12 @@ const deriveCollectionFieldName = (modelName) => {
|
|
|
802
1072
|
if (camel.endsWith("s")) return `${camel}es`;
|
|
803
1073
|
return `${camel}s`;
|
|
804
1074
|
};
|
|
1075
|
+
const resolveForeignKeyColumn = (columns, foreignKey) => {
|
|
1076
|
+
return columns.find((column) => column.name === foreignKey.column);
|
|
1077
|
+
};
|
|
1078
|
+
const isOneToOneForeignKey = (column) => {
|
|
1079
|
+
return Boolean(column?.unique || column?.primary);
|
|
1080
|
+
};
|
|
805
1081
|
/**
|
|
806
1082
|
* Format a SchemaForeignKeyAction value as a Prisma onDelete action string.
|
|
807
1083
|
*
|
|
@@ -822,15 +1098,16 @@ const formatRelationAction = (action) => {
|
|
|
822
1098
|
* @param foreignKey The foreign key definition to convert to a relation line.
|
|
823
1099
|
* @returns The corresponding Prisma schema line for the relation field.
|
|
824
1100
|
*/
|
|
825
|
-
const buildRelationLine = (foreignKey) => {
|
|
1101
|
+
const buildRelationLine = (sourceModelName, foreignKey, columns = []) => {
|
|
826
1102
|
if (!foreignKey.referencesTable.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced table.`);
|
|
827
1103
|
if (!foreignKey.referencesColumn.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced column.`);
|
|
1104
|
+
const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
|
|
828
1105
|
const fieldName = foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column);
|
|
829
1106
|
const targetModel = toModelName(foreignKey.referencesTable);
|
|
830
|
-
const relationName = foreignKey.relationAlias?.trim();
|
|
831
|
-
const
|
|
1107
|
+
const relationName = deriveRelationAlias(sourceModelName, targetModel, foreignKey.relationAlias?.trim());
|
|
1108
|
+
const optional = sourceColumn?.nullable ? "?" : "";
|
|
832
1109
|
const onDelete = foreignKey.onDelete ? `, onDelete: ${formatRelationAction(foreignKey.onDelete)}` : "";
|
|
833
|
-
return ` ${fieldName} ${targetModel} ${
|
|
1110
|
+
return ` ${fieldName} ${targetModel}${optional} @relation("${relationName.replace(/"/g, "\\\"")}", fields: [${foreignKey.column}], references: [${foreignKey.referencesColumn}]${onDelete})`;
|
|
834
1111
|
};
|
|
835
1112
|
/**
|
|
836
1113
|
* Build a Prisma relation field line for the inverse side of a relation, based
|
|
@@ -842,8 +1119,11 @@ const buildRelationLine = (foreignKey) => {
|
|
|
842
1119
|
* @param foreignKey The foreign key definition for the relation.
|
|
843
1120
|
* @returns The Prisma schema line for the inverse relation field.
|
|
844
1121
|
*/
|
|
845
|
-
const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey) => {
|
|
846
|
-
|
|
1122
|
+
const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey, columns = []) => {
|
|
1123
|
+
const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
|
|
1124
|
+
const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
|
|
1125
|
+
const relationName = deriveRelationAlias(sourceModelName, targetModelName, foreignKey.relationAlias?.trim());
|
|
1126
|
+
return ` ${fieldName} ${isOneToOneForeignKey(sourceColumn) ? `${sourceModelName}?` : `${sourceModelName}[]`} @relation("${relationName.replace(/"/g, "\\\"")}")`;
|
|
847
1127
|
};
|
|
848
1128
|
/**
|
|
849
1129
|
* Inject a line into the body of a Prisma model block if it does not already
|
|
@@ -871,14 +1151,15 @@ const injectLineIntoModelBody = (bodyLines, line, exists) => {
|
|
|
871
1151
|
* @param foreignKeys An array of foreign key definitions to process.
|
|
872
1152
|
* @returns The updated Prisma schema string with inverse relations applied.
|
|
873
1153
|
*/
|
|
874
|
-
const applyInverseRelations = (schema, sourceModelName, foreignKeys) => {
|
|
1154
|
+
const applyInverseRelations = (schema, sourceModelName, foreignKeys, columns = []) => {
|
|
875
1155
|
let nextSchema = schema;
|
|
876
1156
|
for (const foreignKey of foreignKeys) {
|
|
877
1157
|
const targetModel = findModelBlock(nextSchema, foreignKey.referencesTable);
|
|
878
1158
|
if (!targetModel) continue;
|
|
879
|
-
const
|
|
1159
|
+
const sourceColumn = resolveForeignKeyColumn(columns, foreignKey);
|
|
1160
|
+
const inverseLine = buildInverseRelationLine(sourceModelName, targetModel.modelName, foreignKey, columns);
|
|
880
1161
|
const targetBodyLines = targetModel.block.split("\n");
|
|
881
|
-
const fieldName = deriveCollectionFieldName(sourceModelName);
|
|
1162
|
+
const fieldName = isOneToOneForeignKey(sourceColumn) ? deriveSingularFieldName(sourceModelName) : deriveCollectionFieldName(sourceModelName);
|
|
882
1163
|
const fieldRegex = new RegExp(`^\\s*${escapeRegex(fieldName)}\\s+`);
|
|
883
1164
|
injectLineIntoModelBody(targetBodyLines, inverseLine, (line) => fieldRegex.test(line));
|
|
884
1165
|
const updatedTarget = targetBodyLines.join("\n");
|
|
@@ -897,7 +1178,7 @@ const buildModelBlock = (operation) => {
|
|
|
897
1178
|
const modelName = toModelName(operation.table);
|
|
898
1179
|
const mapped = operation.table !== modelName.toLowerCase();
|
|
899
1180
|
const fields = operation.columns.map(buildFieldLine);
|
|
900
|
-
const relations = (operation.foreignKeys ?? []).map(buildRelationLine);
|
|
1181
|
+
const relations = (operation.foreignKeys ?? []).map((foreignKey) => buildRelationLine(modelName, foreignKey, operation.columns));
|
|
901
1182
|
const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${str(operation.table).snake()}")`] : []];
|
|
902
1183
|
return `model ${modelName} {\n${(metadata.length > 0 ? [
|
|
903
1184
|
...fields,
|
|
@@ -953,8 +1234,9 @@ const findModelBlock = (schema, table) => {
|
|
|
953
1234
|
*/
|
|
954
1235
|
const applyCreateTableOperation = (schema, operation) => {
|
|
955
1236
|
if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
|
|
1237
|
+
const schemaWithEnums = ensureEnumBlocks(schema, operation.columns);
|
|
956
1238
|
const block = buildModelBlock(operation);
|
|
957
|
-
return applyInverseRelations(`${
|
|
1239
|
+
return applyInverseRelations(`${schemaWithEnums.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? [], operation.columns);
|
|
958
1240
|
};
|
|
959
1241
|
/**
|
|
960
1242
|
* Apply an alter table operation to a Prisma schema string, modifying the model
|
|
@@ -967,7 +1249,10 @@ const applyCreateTableOperation = (schema, operation) => {
|
|
|
967
1249
|
const applyAlterTableOperation = (schema, operation) => {
|
|
968
1250
|
const model = findModelBlock(schema, operation.table);
|
|
969
1251
|
if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
|
|
970
|
-
|
|
1252
|
+
const schemaWithEnums = ensureEnumBlocks(schema, operation.addColumns);
|
|
1253
|
+
const refreshedModel = findModelBlock(schemaWithEnums, operation.table);
|
|
1254
|
+
if (!refreshedModel) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
|
|
1255
|
+
let block = refreshedModel.block;
|
|
971
1256
|
const bodyLines = block.split("\n");
|
|
972
1257
|
operation.dropColumns.forEach((column) => {
|
|
973
1258
|
const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
|
|
@@ -992,12 +1277,12 @@ const applyAlterTableOperation = (schema, operation) => {
|
|
|
992
1277
|
bodyLines.splice(insertIndex, 0, indexLine);
|
|
993
1278
|
});
|
|
994
1279
|
for (const foreignKey of operation.addForeignKeys ?? []) {
|
|
995
|
-
const relationLine = buildRelationLine(foreignKey);
|
|
1280
|
+
const relationLine = buildRelationLine(model.modelName, foreignKey, operation.addColumns);
|
|
996
1281
|
const relationRegex = new RegExp(`^\\s*${escapeRegex(foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column))}\\s+`);
|
|
997
1282
|
injectLineIntoModelBody(bodyLines, relationLine, (line) => relationRegex.test(line));
|
|
998
1283
|
}
|
|
999
1284
|
block = bodyLines.join("\n");
|
|
1000
|
-
return applyInverseRelations(`${
|
|
1285
|
+
return applyInverseRelations(`${schemaWithEnums.slice(0, refreshedModel.start)}${block}${schemaWithEnums.slice(refreshedModel.end)}`, model.modelName, operation.addForeignKeys ?? [], operation.addColumns);
|
|
1001
1286
|
};
|
|
1002
1287
|
/**
|
|
1003
1288
|
* Apply a drop table operation to a Prisma schema string, removing the model block
|
|
@@ -6102,4 +6387,4 @@ var Model = class Model {
|
|
|
6102
6387
|
};
|
|
6103
6388
|
|
|
6104
6389
|
//#endregion
|
|
6105
|
-
export { ArkormCollection, ArkormException, Attribute, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, QueryConstraintException, RelationResolutionException, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
|
|
6390
|
+
export { ArkormCollection, ArkormException, Attribute, CliApp, EnumBuilder, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, MigrateRollbackCommand, Migration, MigrationHistoryCommand, MissingDelegateException, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_ENUM_MEMBER_REGEX, PRISMA_ENUM_REGEX, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, QueryConstraintException, RelationResolutionException, SEEDER_BRAND, SchemaBuilder, ScopeNotDefinedException, SeedCommand, Seeder, TableBuilder, URLDriver, UniqueConstraintResolutionException, UnsupportedAdapterFeatureException, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationRollbackToPrismaSchema, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildEnumBlock, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationRunId, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationAlias, deriveRelationFieldName, deriveSingularFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findEnumBlock, findModelBlock, formatDefaultValue, formatEnumDefaultValue, formatRelationAction, generateMigrationFile, getActiveTransactionClient, getDefaultStubsPath, getLastMigrationRun, getLatestAppliedMigrations, getMigrationPlan, getRuntimePaginationCurrentPageResolver, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, isTransactionCapableClient, loadArkormConfig, markMigrationApplied, markMigrationRun, pad, readAppliedMigrationsState, removeAppliedMigration, resetArkormRuntimeForTests, resolveCast, resolveEnumName, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runArkormTransaction, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
|