arkormx 1.0.0 → 1.2.0
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 +281 -7
- package/dist/index.cjs +439 -8
- package/dist/index.d.cts +181 -2
- package/dist/index.d.mts +181 -2
- package/dist/index.mjs +433 -9
- 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()').
|
|
@@ -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.
|
|
@@ -982,8 +1252,9 @@ const findModelBlock = (schema, table) => {
|
|
|
982
1252
|
*/
|
|
983
1253
|
const applyCreateTableOperation = (schema, operation) => {
|
|
984
1254
|
if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
|
|
1255
|
+
const schemaWithEnums = ensureEnumBlocks(schema, operation.columns);
|
|
985
1256
|
const block = buildModelBlock(operation);
|
|
986
|
-
return applyInverseRelations(`${
|
|
1257
|
+
return applyInverseRelations(`${schemaWithEnums.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? []);
|
|
987
1258
|
};
|
|
988
1259
|
/**
|
|
989
1260
|
* Apply an alter table operation to a Prisma schema string, modifying the model
|
|
@@ -996,7 +1267,10 @@ const applyCreateTableOperation = (schema, operation) => {
|
|
|
996
1267
|
const applyAlterTableOperation = (schema, operation) => {
|
|
997
1268
|
const model = findModelBlock(schema, operation.table);
|
|
998
1269
|
if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
|
|
999
|
-
|
|
1270
|
+
const schemaWithEnums = ensureEnumBlocks(schema, operation.addColumns);
|
|
1271
|
+
const refreshedModel = findModelBlock(schemaWithEnums, operation.table);
|
|
1272
|
+
if (!refreshedModel) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
|
|
1273
|
+
let block = refreshedModel.block;
|
|
1000
1274
|
const bodyLines = block.split("\n");
|
|
1001
1275
|
operation.dropColumns.forEach((column) => {
|
|
1002
1276
|
const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
|
|
@@ -1026,7 +1300,7 @@ const applyAlterTableOperation = (schema, operation) => {
|
|
|
1026
1300
|
injectLineIntoModelBody(bodyLines, relationLine, (line) => relationRegex.test(line));
|
|
1027
1301
|
}
|
|
1028
1302
|
block = bodyLines.join("\n");
|
|
1029
|
-
return applyInverseRelations(`${
|
|
1303
|
+
return applyInverseRelations(`${schemaWithEnums.slice(0, refreshedModel.start)}${block}${schemaWithEnums.slice(refreshedModel.end)}`, model.modelName, operation.addForeignKeys ?? []);
|
|
1030
1304
|
};
|
|
1031
1305
|
/**
|
|
1032
1306
|
* Apply a drop table operation to a Prisma schema string, removing the model block
|
|
@@ -5127,8 +5401,14 @@ var Model = class Model {
|
|
|
5127
5401
|
visible = [];
|
|
5128
5402
|
appends = [];
|
|
5129
5403
|
attributes;
|
|
5404
|
+
original;
|
|
5405
|
+
changes;
|
|
5406
|
+
touchedAttributes;
|
|
5130
5407
|
constructor(attributes = {}) {
|
|
5131
5408
|
this.attributes = {};
|
|
5409
|
+
this.original = {};
|
|
5410
|
+
this.changes = {};
|
|
5411
|
+
this.touchedAttributes = /* @__PURE__ */ new Set();
|
|
5132
5412
|
this.fill(attributes);
|
|
5133
5413
|
return new Proxy(this, {
|
|
5134
5414
|
get: (target, key, receiver) => {
|
|
@@ -5428,7 +5708,10 @@ var Model = class Model {
|
|
|
5428
5708
|
* @returns
|
|
5429
5709
|
*/
|
|
5430
5710
|
static hydrate(attributes) {
|
|
5431
|
-
|
|
5711
|
+
const model = new this(attributes);
|
|
5712
|
+
model.syncOriginal();
|
|
5713
|
+
model.syncChanges({});
|
|
5714
|
+
return model;
|
|
5432
5715
|
}
|
|
5433
5716
|
/**
|
|
5434
5717
|
* Hydrate multiple model instances from an array of plain objects of attributes.
|
|
@@ -5495,6 +5778,7 @@ var Model = class Model {
|
|
|
5495
5778
|
else if (mutator) resolved = mutator.call(this, resolved);
|
|
5496
5779
|
if (cast) resolved = resolveCast(cast).set(resolved);
|
|
5497
5780
|
this.attributes[key] = resolved;
|
|
5781
|
+
this.touchedAttributes.add(key);
|
|
5498
5782
|
return this;
|
|
5499
5783
|
}
|
|
5500
5784
|
/**
|
|
@@ -5507,12 +5791,15 @@ var Model = class Model {
|
|
|
5507
5791
|
async save() {
|
|
5508
5792
|
const identifier = this.getAttribute("id");
|
|
5509
5793
|
const payload = this.getRawAttributes();
|
|
5794
|
+
const previousOriginal = this.getOriginal();
|
|
5510
5795
|
const constructor = this.constructor;
|
|
5511
5796
|
if (identifier == null) {
|
|
5512
5797
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
5513
5798
|
await Model.dispatchEvent(constructor, "creating", this);
|
|
5514
5799
|
const model = await constructor.query().create(payload);
|
|
5515
5800
|
this.fill(model.getRawAttributes());
|
|
5801
|
+
this.syncChanges(previousOriginal);
|
|
5802
|
+
this.syncOriginal();
|
|
5516
5803
|
await Model.dispatchEvent(constructor, "created", this);
|
|
5517
5804
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5518
5805
|
return this;
|
|
@@ -5521,6 +5808,8 @@ var Model = class Model {
|
|
|
5521
5808
|
await Model.dispatchEvent(constructor, "updating", this);
|
|
5522
5809
|
const model = await constructor.query().where({ id: identifier }).update(payload);
|
|
5523
5810
|
this.fill(model.getRawAttributes());
|
|
5811
|
+
this.syncChanges(previousOriginal);
|
|
5812
|
+
this.syncOriginal();
|
|
5524
5813
|
await Model.dispatchEvent(constructor, "updated", this);
|
|
5525
5814
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5526
5815
|
return this;
|
|
@@ -5544,17 +5833,22 @@ var Model = class Model {
|
|
|
5544
5833
|
async delete() {
|
|
5545
5834
|
const identifier = this.getAttribute("id");
|
|
5546
5835
|
if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
|
|
5836
|
+
const previousOriginal = this.getOriginal();
|
|
5547
5837
|
const constructor = this.constructor;
|
|
5548
5838
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5549
5839
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5550
5840
|
if (softDeleteConfig.enabled) {
|
|
5551
5841
|
const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
|
|
5552
5842
|
this.fill(model.getRawAttributes());
|
|
5843
|
+
this.syncChanges(previousOriginal);
|
|
5844
|
+
this.syncOriginal();
|
|
5553
5845
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5554
5846
|
return this;
|
|
5555
5847
|
}
|
|
5556
5848
|
const deleted = await constructor.query().where({ id: identifier }).delete();
|
|
5557
5849
|
this.fill(deleted.getRawAttributes());
|
|
5850
|
+
this.syncChanges(previousOriginal);
|
|
5851
|
+
this.syncOriginal();
|
|
5558
5852
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5559
5853
|
return this;
|
|
5560
5854
|
}
|
|
@@ -5575,11 +5869,14 @@ var Model = class Model {
|
|
|
5575
5869
|
async forceDelete() {
|
|
5576
5870
|
const identifier = this.getAttribute("id");
|
|
5577
5871
|
if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
|
|
5872
|
+
const previousOriginal = this.getOriginal();
|
|
5578
5873
|
const constructor = this.constructor;
|
|
5579
5874
|
await Model.dispatchEvent(constructor, "forceDeleting", this);
|
|
5580
5875
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5581
5876
|
const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
|
|
5582
5877
|
this.fill(deleted.getRawAttributes());
|
|
5878
|
+
this.syncChanges(previousOriginal);
|
|
5879
|
+
this.syncOriginal();
|
|
5583
5880
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5584
5881
|
await Model.dispatchEvent(constructor, "forceDeleted", this);
|
|
5585
5882
|
return this;
|
|
@@ -5603,9 +5900,12 @@ var Model = class Model {
|
|
|
5603
5900
|
const constructor = this.constructor;
|
|
5604
5901
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5605
5902
|
if (!softDeleteConfig.enabled) return this;
|
|
5903
|
+
const previousOriginal = this.getOriginal();
|
|
5606
5904
|
await Model.dispatchEvent(constructor, "restoring", this);
|
|
5607
5905
|
const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
|
|
5608
5906
|
this.fill(model.getRawAttributes());
|
|
5907
|
+
this.syncChanges(previousOriginal);
|
|
5908
|
+
this.syncOriginal();
|
|
5609
5909
|
await Model.dispatchEvent(constructor, "restored", this);
|
|
5610
5910
|
return this;
|
|
5611
5911
|
}
|
|
@@ -5643,6 +5943,42 @@ var Model = class Model {
|
|
|
5643
5943
|
getRawAttributes() {
|
|
5644
5944
|
return { ...this.attributes };
|
|
5645
5945
|
}
|
|
5946
|
+
getOriginal(key) {
|
|
5947
|
+
if (typeof key === "string") return Model.cloneAttributeValue(this.original[key]);
|
|
5948
|
+
return Object.entries(this.original).reduce((all, [originalKey, value]) => {
|
|
5949
|
+
all[originalKey] = Model.cloneAttributeValue(value);
|
|
5950
|
+
return all;
|
|
5951
|
+
}, {});
|
|
5952
|
+
}
|
|
5953
|
+
/**
|
|
5954
|
+
* Determine whether the model has unsaved attribute changes.
|
|
5955
|
+
*
|
|
5956
|
+
* @param keys
|
|
5957
|
+
* @returns
|
|
5958
|
+
*/
|
|
5959
|
+
isDirty(keys) {
|
|
5960
|
+
return Object.keys(this.getDirtyAttributes(keys)).length > 0;
|
|
5961
|
+
}
|
|
5962
|
+
/**
|
|
5963
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
5964
|
+
*
|
|
5965
|
+
* @param keys
|
|
5966
|
+
* @returns
|
|
5967
|
+
*/
|
|
5968
|
+
isClean(keys) {
|
|
5969
|
+
return !this.isDirty(keys);
|
|
5970
|
+
}
|
|
5971
|
+
/**
|
|
5972
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
5973
|
+
*
|
|
5974
|
+
* @param keys
|
|
5975
|
+
* @returns
|
|
5976
|
+
*/
|
|
5977
|
+
wasChanged(keys) {
|
|
5978
|
+
const keyList = this.normalizeAttributeKeys(keys);
|
|
5979
|
+
if (keyList.length === 0) return Object.keys(this.changes).length > 0;
|
|
5980
|
+
return keyList.some((key) => Object.prototype.hasOwnProperty.call(this.changes, key));
|
|
5981
|
+
}
|
|
5646
5982
|
/**
|
|
5647
5983
|
* Convert the model instance to a plain object, applying visibility
|
|
5648
5984
|
* rules, appends, and mutators.
|
|
@@ -5833,6 +6169,34 @@ var Model = class Model {
|
|
|
5833
6169
|
return typeof method === "function" ? method : null;
|
|
5834
6170
|
}
|
|
5835
6171
|
/**
|
|
6172
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
6173
|
+
*
|
|
6174
|
+
* @param keys
|
|
6175
|
+
* @returns
|
|
6176
|
+
*/
|
|
6177
|
+
getDirtyAttributes(keys) {
|
|
6178
|
+
const requestedKeys = this.normalizeAttributeKeys(keys);
|
|
6179
|
+
return (requestedKeys.length > 0 ? requestedKeys : Array.from(new Set([...Object.keys(this.original), ...this.touchedAttributes]))).reduce((dirty, key) => {
|
|
6180
|
+
const currentValue = this.attributes[key];
|
|
6181
|
+
const originalValue = this.original[key];
|
|
6182
|
+
const hasCurrent = Object.prototype.hasOwnProperty.call(this.attributes, key);
|
|
6183
|
+
const hasOriginal = Object.prototype.hasOwnProperty.call(this.original, key);
|
|
6184
|
+
if (!hasCurrent && !hasOriginal) return dirty;
|
|
6185
|
+
if (hasCurrent !== hasOriginal || !Model.areAttributeValuesEqual(currentValue, originalValue)) dirty[key] = Model.cloneAttributeValue(currentValue);
|
|
6186
|
+
return dirty;
|
|
6187
|
+
}, {});
|
|
6188
|
+
}
|
|
6189
|
+
/**
|
|
6190
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
6191
|
+
*
|
|
6192
|
+
* @param keys
|
|
6193
|
+
* @returns
|
|
6194
|
+
*/
|
|
6195
|
+
normalizeAttributeKeys(keys) {
|
|
6196
|
+
if (typeof keys === "undefined") return [];
|
|
6197
|
+
return Array.isArray(keys) ? keys : [keys];
|
|
6198
|
+
}
|
|
6199
|
+
/**
|
|
5836
6200
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
5837
6201
|
*
|
|
5838
6202
|
* @param key
|
|
@@ -5874,6 +6238,66 @@ var Model = class Model {
|
|
|
5874
6238
|
if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
|
|
5875
6239
|
}
|
|
5876
6240
|
/**
|
|
6241
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
6242
|
+
*
|
|
6243
|
+
* @param value
|
|
6244
|
+
* @returns
|
|
6245
|
+
*/
|
|
6246
|
+
static cloneAttributeValue(value) {
|
|
6247
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
6248
|
+
if (Array.isArray(value)) return value.map((item) => Model.cloneAttributeValue(item));
|
|
6249
|
+
if (value && typeof value === "object") return Object.entries(value).reduce((all, [key, nestedValue]) => {
|
|
6250
|
+
all[key] = Model.cloneAttributeValue(nestedValue);
|
|
6251
|
+
return all;
|
|
6252
|
+
}, {});
|
|
6253
|
+
return value;
|
|
6254
|
+
}
|
|
6255
|
+
/**
|
|
6256
|
+
* Compare attribute values for dirty/change detection.
|
|
6257
|
+
*
|
|
6258
|
+
* @param left
|
|
6259
|
+
* @param right
|
|
6260
|
+
* @returns
|
|
6261
|
+
*/
|
|
6262
|
+
static areAttributeValuesEqual(left, right) {
|
|
6263
|
+
if (left === right) return true;
|
|
6264
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
6265
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
6266
|
+
if (left.length !== right.length) return false;
|
|
6267
|
+
return left.every((value, index) => Model.areAttributeValuesEqual(value, right[index]));
|
|
6268
|
+
}
|
|
6269
|
+
if (left && right && typeof left === "object" && typeof right === "object") {
|
|
6270
|
+
const leftEntries = Object.entries(left);
|
|
6271
|
+
const rightEntries = Object.entries(right);
|
|
6272
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
6273
|
+
return leftEntries.every(([key, value]) => {
|
|
6274
|
+
return Object.prototype.hasOwnProperty.call(right, key) && Model.areAttributeValuesEqual(value, right[key]);
|
|
6275
|
+
});
|
|
6276
|
+
}
|
|
6277
|
+
return false;
|
|
6278
|
+
}
|
|
6279
|
+
/**
|
|
6280
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
6281
|
+
*/
|
|
6282
|
+
syncOriginal() {
|
|
6283
|
+
this.original = Object.entries(this.attributes).reduce((all, [key, value]) => {
|
|
6284
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
6285
|
+
return all;
|
|
6286
|
+
}, {});
|
|
6287
|
+
this.touchedAttributes.clear();
|
|
6288
|
+
}
|
|
6289
|
+
/**
|
|
6290
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
6291
|
+
*
|
|
6292
|
+
* @param previousOriginal
|
|
6293
|
+
*/
|
|
6294
|
+
syncChanges(previousOriginal) {
|
|
6295
|
+
this.changes = Object.entries(this.getDirtyAttributes()).reduce((all, [key, value]) => {
|
|
6296
|
+
if (!Object.prototype.hasOwnProperty.call(previousOriginal, key) || !Model.areAttributeValuesEqual(value, previousOriginal[key])) all[key] = Model.cloneAttributeValue(value);
|
|
6297
|
+
return all;
|
|
6298
|
+
}, {});
|
|
6299
|
+
}
|
|
6300
|
+
/**
|
|
5877
6301
|
* Resolve lifecycle state for the provided model class.
|
|
5878
6302
|
*
|
|
5879
6303
|
* @param modelClass
|
|
@@ -5985,6 +6409,7 @@ exports.ArkormCollection = ArkormCollection;
|
|
|
5985
6409
|
exports.ArkormException = ArkormException;
|
|
5986
6410
|
exports.Attribute = Attribute;
|
|
5987
6411
|
exports.CliApp = CliApp;
|
|
6412
|
+
exports.EnumBuilder = EnumBuilder;
|
|
5988
6413
|
exports.ForeignKeyBuilder = ForeignKeyBuilder;
|
|
5989
6414
|
exports.InitCommand = InitCommand;
|
|
5990
6415
|
exports.InlineFactory = InlineFactory;
|
|
@@ -6003,6 +6428,8 @@ exports.Model = Model;
|
|
|
6003
6428
|
exports.ModelFactory = ModelFactory;
|
|
6004
6429
|
exports.ModelNotFoundException = ModelNotFoundException;
|
|
6005
6430
|
exports.ModelsSyncCommand = ModelsSyncCommand;
|
|
6431
|
+
exports.PRISMA_ENUM_MEMBER_REGEX = PRISMA_ENUM_MEMBER_REGEX;
|
|
6432
|
+
exports.PRISMA_ENUM_REGEX = PRISMA_ENUM_REGEX;
|
|
6006
6433
|
exports.PRISMA_MODEL_REGEX = PRISMA_MODEL_REGEX;
|
|
6007
6434
|
exports.Paginator = Paginator;
|
|
6008
6435
|
exports.QueryBuilder = QueryBuilder;
|
|
@@ -6023,6 +6450,7 @@ exports.applyDropTableOperation = applyDropTableOperation;
|
|
|
6023
6450
|
exports.applyMigrationRollbackToPrismaSchema = applyMigrationRollbackToPrismaSchema;
|
|
6024
6451
|
exports.applyMigrationToPrismaSchema = applyMigrationToPrismaSchema;
|
|
6025
6452
|
exports.applyOperationsToPrismaSchema = applyOperationsToPrismaSchema;
|
|
6453
|
+
exports.buildEnumBlock = buildEnumBlock;
|
|
6026
6454
|
exports.buildFieldLine = buildFieldLine;
|
|
6027
6455
|
exports.buildIndexLine = buildIndexLine;
|
|
6028
6456
|
exports.buildInverseRelationLine = buildInverseRelationLine;
|
|
@@ -6044,8 +6472,10 @@ exports.deriveRelationFieldName = deriveRelationFieldName;
|
|
|
6044
6472
|
exports.ensureArkormConfigLoading = ensureArkormConfigLoading;
|
|
6045
6473
|
exports.escapeRegex = escapeRegex;
|
|
6046
6474
|
exports.findAppliedMigration = findAppliedMigration;
|
|
6475
|
+
exports.findEnumBlock = findEnumBlock;
|
|
6047
6476
|
exports.findModelBlock = findModelBlock;
|
|
6048
6477
|
exports.formatDefaultValue = formatDefaultValue;
|
|
6478
|
+
exports.formatEnumDefaultValue = formatEnumDefaultValue;
|
|
6049
6479
|
exports.formatRelationAction = formatRelationAction;
|
|
6050
6480
|
exports.generateMigrationFile = generateMigrationFile;
|
|
6051
6481
|
exports.getActiveTransactionClient = getActiveTransactionClient;
|
|
@@ -6069,6 +6499,7 @@ exports.readAppliedMigrationsState = readAppliedMigrationsState;
|
|
|
6069
6499
|
exports.removeAppliedMigration = removeAppliedMigration;
|
|
6070
6500
|
exports.resetArkormRuntimeForTests = resetArkormRuntimeForTests;
|
|
6071
6501
|
exports.resolveCast = resolveCast;
|
|
6502
|
+
exports.resolveEnumName = resolveEnumName;
|
|
6072
6503
|
exports.resolveMigrationClassName = resolveMigrationClassName;
|
|
6073
6504
|
exports.resolveMigrationStateFilePath = resolveMigrationStateFilePath;
|
|
6074
6505
|
exports.resolvePrismaType = resolvePrismaType;
|