arkormx 1.0.0 → 1.1.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/index.cjs +151 -1
- package/dist/index.d.cts +73 -0
- package/dist/index.d.mts +73 -0
- package/dist/index.mjs +151 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5127,8 +5127,14 @@ var Model = class Model {
|
|
|
5127
5127
|
visible = [];
|
|
5128
5128
|
appends = [];
|
|
5129
5129
|
attributes;
|
|
5130
|
+
original;
|
|
5131
|
+
changes;
|
|
5132
|
+
touchedAttributes;
|
|
5130
5133
|
constructor(attributes = {}) {
|
|
5131
5134
|
this.attributes = {};
|
|
5135
|
+
this.original = {};
|
|
5136
|
+
this.changes = {};
|
|
5137
|
+
this.touchedAttributes = /* @__PURE__ */ new Set();
|
|
5132
5138
|
this.fill(attributes);
|
|
5133
5139
|
return new Proxy(this, {
|
|
5134
5140
|
get: (target, key, receiver) => {
|
|
@@ -5428,7 +5434,10 @@ var Model = class Model {
|
|
|
5428
5434
|
* @returns
|
|
5429
5435
|
*/
|
|
5430
5436
|
static hydrate(attributes) {
|
|
5431
|
-
|
|
5437
|
+
const model = new this(attributes);
|
|
5438
|
+
model.syncOriginal();
|
|
5439
|
+
model.syncChanges({});
|
|
5440
|
+
return model;
|
|
5432
5441
|
}
|
|
5433
5442
|
/**
|
|
5434
5443
|
* Hydrate multiple model instances from an array of plain objects of attributes.
|
|
@@ -5495,6 +5504,7 @@ var Model = class Model {
|
|
|
5495
5504
|
else if (mutator) resolved = mutator.call(this, resolved);
|
|
5496
5505
|
if (cast) resolved = resolveCast(cast).set(resolved);
|
|
5497
5506
|
this.attributes[key] = resolved;
|
|
5507
|
+
this.touchedAttributes.add(key);
|
|
5498
5508
|
return this;
|
|
5499
5509
|
}
|
|
5500
5510
|
/**
|
|
@@ -5507,12 +5517,15 @@ var Model = class Model {
|
|
|
5507
5517
|
async save() {
|
|
5508
5518
|
const identifier = this.getAttribute("id");
|
|
5509
5519
|
const payload = this.getRawAttributes();
|
|
5520
|
+
const previousOriginal = this.getOriginal();
|
|
5510
5521
|
const constructor = this.constructor;
|
|
5511
5522
|
if (identifier == null) {
|
|
5512
5523
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
5513
5524
|
await Model.dispatchEvent(constructor, "creating", this);
|
|
5514
5525
|
const model = await constructor.query().create(payload);
|
|
5515
5526
|
this.fill(model.getRawAttributes());
|
|
5527
|
+
this.syncChanges(previousOriginal);
|
|
5528
|
+
this.syncOriginal();
|
|
5516
5529
|
await Model.dispatchEvent(constructor, "created", this);
|
|
5517
5530
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5518
5531
|
return this;
|
|
@@ -5521,6 +5534,8 @@ var Model = class Model {
|
|
|
5521
5534
|
await Model.dispatchEvent(constructor, "updating", this);
|
|
5522
5535
|
const model = await constructor.query().where({ id: identifier }).update(payload);
|
|
5523
5536
|
this.fill(model.getRawAttributes());
|
|
5537
|
+
this.syncChanges(previousOriginal);
|
|
5538
|
+
this.syncOriginal();
|
|
5524
5539
|
await Model.dispatchEvent(constructor, "updated", this);
|
|
5525
5540
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5526
5541
|
return this;
|
|
@@ -5544,17 +5559,22 @@ var Model = class Model {
|
|
|
5544
5559
|
async delete() {
|
|
5545
5560
|
const identifier = this.getAttribute("id");
|
|
5546
5561
|
if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
|
|
5562
|
+
const previousOriginal = this.getOriginal();
|
|
5547
5563
|
const constructor = this.constructor;
|
|
5548
5564
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5549
5565
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5550
5566
|
if (softDeleteConfig.enabled) {
|
|
5551
5567
|
const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
|
|
5552
5568
|
this.fill(model.getRawAttributes());
|
|
5569
|
+
this.syncChanges(previousOriginal);
|
|
5570
|
+
this.syncOriginal();
|
|
5553
5571
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5554
5572
|
return this;
|
|
5555
5573
|
}
|
|
5556
5574
|
const deleted = await constructor.query().where({ id: identifier }).delete();
|
|
5557
5575
|
this.fill(deleted.getRawAttributes());
|
|
5576
|
+
this.syncChanges(previousOriginal);
|
|
5577
|
+
this.syncOriginal();
|
|
5558
5578
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5559
5579
|
return this;
|
|
5560
5580
|
}
|
|
@@ -5575,11 +5595,14 @@ var Model = class Model {
|
|
|
5575
5595
|
async forceDelete() {
|
|
5576
5596
|
const identifier = this.getAttribute("id");
|
|
5577
5597
|
if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
|
|
5598
|
+
const previousOriginal = this.getOriginal();
|
|
5578
5599
|
const constructor = this.constructor;
|
|
5579
5600
|
await Model.dispatchEvent(constructor, "forceDeleting", this);
|
|
5580
5601
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5581
5602
|
const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
|
|
5582
5603
|
this.fill(deleted.getRawAttributes());
|
|
5604
|
+
this.syncChanges(previousOriginal);
|
|
5605
|
+
this.syncOriginal();
|
|
5583
5606
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5584
5607
|
await Model.dispatchEvent(constructor, "forceDeleted", this);
|
|
5585
5608
|
return this;
|
|
@@ -5603,9 +5626,12 @@ var Model = class Model {
|
|
|
5603
5626
|
const constructor = this.constructor;
|
|
5604
5627
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5605
5628
|
if (!softDeleteConfig.enabled) return this;
|
|
5629
|
+
const previousOriginal = this.getOriginal();
|
|
5606
5630
|
await Model.dispatchEvent(constructor, "restoring", this);
|
|
5607
5631
|
const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
|
|
5608
5632
|
this.fill(model.getRawAttributes());
|
|
5633
|
+
this.syncChanges(previousOriginal);
|
|
5634
|
+
this.syncOriginal();
|
|
5609
5635
|
await Model.dispatchEvent(constructor, "restored", this);
|
|
5610
5636
|
return this;
|
|
5611
5637
|
}
|
|
@@ -5643,6 +5669,42 @@ var Model = class Model {
|
|
|
5643
5669
|
getRawAttributes() {
|
|
5644
5670
|
return { ...this.attributes };
|
|
5645
5671
|
}
|
|
5672
|
+
getOriginal(key) {
|
|
5673
|
+
if (typeof key === "string") return Model.cloneAttributeValue(this.original[key]);
|
|
5674
|
+
return Object.entries(this.original).reduce((all, [originalKey, value]) => {
|
|
5675
|
+
all[originalKey] = Model.cloneAttributeValue(value);
|
|
5676
|
+
return all;
|
|
5677
|
+
}, {});
|
|
5678
|
+
}
|
|
5679
|
+
/**
|
|
5680
|
+
* Determine whether the model has unsaved attribute changes.
|
|
5681
|
+
*
|
|
5682
|
+
* @param keys
|
|
5683
|
+
* @returns
|
|
5684
|
+
*/
|
|
5685
|
+
isDirty(keys) {
|
|
5686
|
+
return Object.keys(this.getDirtyAttributes(keys)).length > 0;
|
|
5687
|
+
}
|
|
5688
|
+
/**
|
|
5689
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
5690
|
+
*
|
|
5691
|
+
* @param keys
|
|
5692
|
+
* @returns
|
|
5693
|
+
*/
|
|
5694
|
+
isClean(keys) {
|
|
5695
|
+
return !this.isDirty(keys);
|
|
5696
|
+
}
|
|
5697
|
+
/**
|
|
5698
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
5699
|
+
*
|
|
5700
|
+
* @param keys
|
|
5701
|
+
* @returns
|
|
5702
|
+
*/
|
|
5703
|
+
wasChanged(keys) {
|
|
5704
|
+
const keyList = this.normalizeAttributeKeys(keys);
|
|
5705
|
+
if (keyList.length === 0) return Object.keys(this.changes).length > 0;
|
|
5706
|
+
return keyList.some((key) => Object.prototype.hasOwnProperty.call(this.changes, key));
|
|
5707
|
+
}
|
|
5646
5708
|
/**
|
|
5647
5709
|
* Convert the model instance to a plain object, applying visibility
|
|
5648
5710
|
* rules, appends, and mutators.
|
|
@@ -5833,6 +5895,34 @@ var Model = class Model {
|
|
|
5833
5895
|
return typeof method === "function" ? method : null;
|
|
5834
5896
|
}
|
|
5835
5897
|
/**
|
|
5898
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
5899
|
+
*
|
|
5900
|
+
* @param keys
|
|
5901
|
+
* @returns
|
|
5902
|
+
*/
|
|
5903
|
+
getDirtyAttributes(keys) {
|
|
5904
|
+
const requestedKeys = this.normalizeAttributeKeys(keys);
|
|
5905
|
+
return (requestedKeys.length > 0 ? requestedKeys : Array.from(new Set([...Object.keys(this.original), ...this.touchedAttributes]))).reduce((dirty, key) => {
|
|
5906
|
+
const currentValue = this.attributes[key];
|
|
5907
|
+
const originalValue = this.original[key];
|
|
5908
|
+
const hasCurrent = Object.prototype.hasOwnProperty.call(this.attributes, key);
|
|
5909
|
+
const hasOriginal = Object.prototype.hasOwnProperty.call(this.original, key);
|
|
5910
|
+
if (!hasCurrent && !hasOriginal) return dirty;
|
|
5911
|
+
if (hasCurrent !== hasOriginal || !Model.areAttributeValuesEqual(currentValue, originalValue)) dirty[key] = Model.cloneAttributeValue(currentValue);
|
|
5912
|
+
return dirty;
|
|
5913
|
+
}, {});
|
|
5914
|
+
}
|
|
5915
|
+
/**
|
|
5916
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
5917
|
+
*
|
|
5918
|
+
* @param keys
|
|
5919
|
+
* @returns
|
|
5920
|
+
*/
|
|
5921
|
+
normalizeAttributeKeys(keys) {
|
|
5922
|
+
if (typeof keys === "undefined") return [];
|
|
5923
|
+
return Array.isArray(keys) ? keys : [keys];
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5836
5926
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
5837
5927
|
*
|
|
5838
5928
|
* @param key
|
|
@@ -5874,6 +5964,66 @@ var Model = class Model {
|
|
|
5874
5964
|
if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
|
|
5875
5965
|
}
|
|
5876
5966
|
/**
|
|
5967
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
5968
|
+
*
|
|
5969
|
+
* @param value
|
|
5970
|
+
* @returns
|
|
5971
|
+
*/
|
|
5972
|
+
static cloneAttributeValue(value) {
|
|
5973
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
5974
|
+
if (Array.isArray(value)) return value.map((item) => Model.cloneAttributeValue(item));
|
|
5975
|
+
if (value && typeof value === "object") return Object.entries(value).reduce((all, [key, nestedValue]) => {
|
|
5976
|
+
all[key] = Model.cloneAttributeValue(nestedValue);
|
|
5977
|
+
return all;
|
|
5978
|
+
}, {});
|
|
5979
|
+
return value;
|
|
5980
|
+
}
|
|
5981
|
+
/**
|
|
5982
|
+
* Compare attribute values for dirty/change detection.
|
|
5983
|
+
*
|
|
5984
|
+
* @param left
|
|
5985
|
+
* @param right
|
|
5986
|
+
* @returns
|
|
5987
|
+
*/
|
|
5988
|
+
static areAttributeValuesEqual(left, right) {
|
|
5989
|
+
if (left === right) return true;
|
|
5990
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
5991
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
5992
|
+
if (left.length !== right.length) return false;
|
|
5993
|
+
return left.every((value, index) => Model.areAttributeValuesEqual(value, right[index]));
|
|
5994
|
+
}
|
|
5995
|
+
if (left && right && typeof left === "object" && typeof right === "object") {
|
|
5996
|
+
const leftEntries = Object.entries(left);
|
|
5997
|
+
const rightEntries = Object.entries(right);
|
|
5998
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
5999
|
+
return leftEntries.every(([key, value]) => {
|
|
6000
|
+
return Object.prototype.hasOwnProperty.call(right, key) && Model.areAttributeValuesEqual(value, right[key]);
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
6003
|
+
return false;
|
|
6004
|
+
}
|
|
6005
|
+
/**
|
|
6006
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
6007
|
+
*/
|
|
6008
|
+
syncOriginal() {
|
|
6009
|
+
this.original = Object.entries(this.attributes).reduce((all, [key, value]) => {
|
|
6010
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
6011
|
+
return all;
|
|
6012
|
+
}, {});
|
|
6013
|
+
this.touchedAttributes.clear();
|
|
6014
|
+
}
|
|
6015
|
+
/**
|
|
6016
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
6017
|
+
*
|
|
6018
|
+
* @param previousOriginal
|
|
6019
|
+
*/
|
|
6020
|
+
syncChanges(previousOriginal) {
|
|
6021
|
+
this.changes = Object.entries(this.getDirtyAttributes()).reduce((all, [key, value]) => {
|
|
6022
|
+
if (!Object.prototype.hasOwnProperty.call(previousOriginal, key) || !Model.areAttributeValuesEqual(value, previousOriginal[key])) all[key] = Model.cloneAttributeValue(value);
|
|
6023
|
+
return all;
|
|
6024
|
+
}, {});
|
|
6025
|
+
}
|
|
6026
|
+
/**
|
|
5877
6027
|
* Resolve lifecycle state for the provided model class.
|
|
5878
6028
|
*
|
|
5879
6029
|
* @param modelClass
|
package/dist/index.d.cts
CHANGED
|
@@ -594,6 +594,9 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
594
594
|
protected visible: string[];
|
|
595
595
|
protected appends: string[];
|
|
596
596
|
protected readonly attributes: Record<string, unknown>;
|
|
597
|
+
protected original: Record<string, unknown>;
|
|
598
|
+
protected changes: Record<string, unknown>;
|
|
599
|
+
protected readonly touchedAttributes: Set<string>;
|
|
597
600
|
constructor(attributes?: Record<string, unknown>);
|
|
598
601
|
/**
|
|
599
602
|
* Set the Prisma client delegates for all models.
|
|
@@ -863,6 +866,37 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
863
866
|
* @returns
|
|
864
867
|
*/
|
|
865
868
|
getRawAttributes(): Partial<TAttributes>;
|
|
869
|
+
/**
|
|
870
|
+
* Get the model's original persisted attributes.
|
|
871
|
+
*
|
|
872
|
+
* @returns
|
|
873
|
+
*/
|
|
874
|
+
getOriginal(): Partial<TAttributes>;
|
|
875
|
+
/**
|
|
876
|
+
* @param key The attribute key to retrieve the original value for.
|
|
877
|
+
*/
|
|
878
|
+
getOriginal<TKey extends keyof TAttributes & string>(key: TKey): TAttributes[TKey] | undefined;
|
|
879
|
+
/**
|
|
880
|
+
* Determine whether the model has unsaved attribute changes.
|
|
881
|
+
*
|
|
882
|
+
* @param keys
|
|
883
|
+
* @returns
|
|
884
|
+
*/
|
|
885
|
+
isDirty(keys?: string | string[]): boolean;
|
|
886
|
+
/**
|
|
887
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
888
|
+
*
|
|
889
|
+
* @param keys
|
|
890
|
+
* @returns
|
|
891
|
+
*/
|
|
892
|
+
isClean(keys?: string | string[]): boolean;
|
|
893
|
+
/**
|
|
894
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
895
|
+
*
|
|
896
|
+
* @param keys
|
|
897
|
+
* @returns
|
|
898
|
+
*/
|
|
899
|
+
wasChanged(keys?: string | string[]): boolean;
|
|
866
900
|
/**
|
|
867
901
|
* Convert the model instance to a plain object, applying visibility
|
|
868
902
|
* rules, appends, and mutators.
|
|
@@ -1004,6 +1038,20 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
1004
1038
|
* @returns
|
|
1005
1039
|
*/
|
|
1006
1040
|
private resolveGetMutator;
|
|
1041
|
+
/**
|
|
1042
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
1043
|
+
*
|
|
1044
|
+
* @param keys
|
|
1045
|
+
* @returns
|
|
1046
|
+
*/
|
|
1047
|
+
private getDirtyAttributes;
|
|
1048
|
+
/**
|
|
1049
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
1050
|
+
*
|
|
1051
|
+
* @param keys
|
|
1052
|
+
* @returns
|
|
1053
|
+
*/
|
|
1054
|
+
private normalizeAttributeKeys;
|
|
1007
1055
|
/**
|
|
1008
1056
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
1009
1057
|
*
|
|
@@ -1026,6 +1074,31 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
1026
1074
|
* Ensures event listeners are own properties on subclass constructors.
|
|
1027
1075
|
*/
|
|
1028
1076
|
private static ensureOwnEventListeners;
|
|
1077
|
+
/**
|
|
1078
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
1079
|
+
*
|
|
1080
|
+
* @param value
|
|
1081
|
+
* @returns
|
|
1082
|
+
*/
|
|
1083
|
+
private static cloneAttributeValue;
|
|
1084
|
+
/**
|
|
1085
|
+
* Compare attribute values for dirty/change detection.
|
|
1086
|
+
*
|
|
1087
|
+
* @param left
|
|
1088
|
+
* @param right
|
|
1089
|
+
* @returns
|
|
1090
|
+
*/
|
|
1091
|
+
private static areAttributeValuesEqual;
|
|
1092
|
+
/**
|
|
1093
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
1094
|
+
*/
|
|
1095
|
+
private syncOriginal;
|
|
1096
|
+
/**
|
|
1097
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
1098
|
+
*
|
|
1099
|
+
* @param previousOriginal
|
|
1100
|
+
*/
|
|
1101
|
+
private syncChanges;
|
|
1029
1102
|
/**
|
|
1030
1103
|
* Resolve lifecycle state for the provided model class.
|
|
1031
1104
|
*
|
package/dist/index.d.mts
CHANGED
|
@@ -594,6 +594,9 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
594
594
|
protected visible: string[];
|
|
595
595
|
protected appends: string[];
|
|
596
596
|
protected readonly attributes: Record<string, unknown>;
|
|
597
|
+
protected original: Record<string, unknown>;
|
|
598
|
+
protected changes: Record<string, unknown>;
|
|
599
|
+
protected readonly touchedAttributes: Set<string>;
|
|
597
600
|
constructor(attributes?: Record<string, unknown>);
|
|
598
601
|
/**
|
|
599
602
|
* Set the Prisma client delegates for all models.
|
|
@@ -863,6 +866,37 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
863
866
|
* @returns
|
|
864
867
|
*/
|
|
865
868
|
getRawAttributes(): Partial<TAttributes>;
|
|
869
|
+
/**
|
|
870
|
+
* Get the model's original persisted attributes.
|
|
871
|
+
*
|
|
872
|
+
* @returns
|
|
873
|
+
*/
|
|
874
|
+
getOriginal(): Partial<TAttributes>;
|
|
875
|
+
/**
|
|
876
|
+
* @param key The attribute key to retrieve the original value for.
|
|
877
|
+
*/
|
|
878
|
+
getOriginal<TKey extends keyof TAttributes & string>(key: TKey): TAttributes[TKey] | undefined;
|
|
879
|
+
/**
|
|
880
|
+
* Determine whether the model has unsaved attribute changes.
|
|
881
|
+
*
|
|
882
|
+
* @param keys
|
|
883
|
+
* @returns
|
|
884
|
+
*/
|
|
885
|
+
isDirty(keys?: string | string[]): boolean;
|
|
886
|
+
/**
|
|
887
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
888
|
+
*
|
|
889
|
+
* @param keys
|
|
890
|
+
* @returns
|
|
891
|
+
*/
|
|
892
|
+
isClean(keys?: string | string[]): boolean;
|
|
893
|
+
/**
|
|
894
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
895
|
+
*
|
|
896
|
+
* @param keys
|
|
897
|
+
* @returns
|
|
898
|
+
*/
|
|
899
|
+
wasChanged(keys?: string | string[]): boolean;
|
|
866
900
|
/**
|
|
867
901
|
* Convert the model instance to a plain object, applying visibility
|
|
868
902
|
* rules, appends, and mutators.
|
|
@@ -1004,6 +1038,20 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
1004
1038
|
* @returns
|
|
1005
1039
|
*/
|
|
1006
1040
|
private resolveGetMutator;
|
|
1041
|
+
/**
|
|
1042
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
1043
|
+
*
|
|
1044
|
+
* @param keys
|
|
1045
|
+
* @returns
|
|
1046
|
+
*/
|
|
1047
|
+
private getDirtyAttributes;
|
|
1048
|
+
/**
|
|
1049
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
1050
|
+
*
|
|
1051
|
+
* @param keys
|
|
1052
|
+
* @returns
|
|
1053
|
+
*/
|
|
1054
|
+
private normalizeAttributeKeys;
|
|
1007
1055
|
/**
|
|
1008
1056
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
1009
1057
|
*
|
|
@@ -1026,6 +1074,31 @@ declare abstract class Model<TSchema extends PrismaDelegateLike | Record<string,
|
|
|
1026
1074
|
* Ensures event listeners are own properties on subclass constructors.
|
|
1027
1075
|
*/
|
|
1028
1076
|
private static ensureOwnEventListeners;
|
|
1077
|
+
/**
|
|
1078
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
1079
|
+
*
|
|
1080
|
+
* @param value
|
|
1081
|
+
* @returns
|
|
1082
|
+
*/
|
|
1083
|
+
private static cloneAttributeValue;
|
|
1084
|
+
/**
|
|
1085
|
+
* Compare attribute values for dirty/change detection.
|
|
1086
|
+
*
|
|
1087
|
+
* @param left
|
|
1088
|
+
* @param right
|
|
1089
|
+
* @returns
|
|
1090
|
+
*/
|
|
1091
|
+
private static areAttributeValuesEqual;
|
|
1092
|
+
/**
|
|
1093
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
1094
|
+
*/
|
|
1095
|
+
private syncOriginal;
|
|
1096
|
+
/**
|
|
1097
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
1098
|
+
*
|
|
1099
|
+
* @param previousOriginal
|
|
1100
|
+
*/
|
|
1101
|
+
private syncChanges;
|
|
1029
1102
|
/**
|
|
1030
1103
|
* Resolve lifecycle state for the provided model class.
|
|
1031
1104
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -5098,8 +5098,14 @@ var Model = class Model {
|
|
|
5098
5098
|
visible = [];
|
|
5099
5099
|
appends = [];
|
|
5100
5100
|
attributes;
|
|
5101
|
+
original;
|
|
5102
|
+
changes;
|
|
5103
|
+
touchedAttributes;
|
|
5101
5104
|
constructor(attributes = {}) {
|
|
5102
5105
|
this.attributes = {};
|
|
5106
|
+
this.original = {};
|
|
5107
|
+
this.changes = {};
|
|
5108
|
+
this.touchedAttributes = /* @__PURE__ */ new Set();
|
|
5103
5109
|
this.fill(attributes);
|
|
5104
5110
|
return new Proxy(this, {
|
|
5105
5111
|
get: (target, key, receiver) => {
|
|
@@ -5399,7 +5405,10 @@ var Model = class Model {
|
|
|
5399
5405
|
* @returns
|
|
5400
5406
|
*/
|
|
5401
5407
|
static hydrate(attributes) {
|
|
5402
|
-
|
|
5408
|
+
const model = new this(attributes);
|
|
5409
|
+
model.syncOriginal();
|
|
5410
|
+
model.syncChanges({});
|
|
5411
|
+
return model;
|
|
5403
5412
|
}
|
|
5404
5413
|
/**
|
|
5405
5414
|
* Hydrate multiple model instances from an array of plain objects of attributes.
|
|
@@ -5466,6 +5475,7 @@ var Model = class Model {
|
|
|
5466
5475
|
else if (mutator) resolved = mutator.call(this, resolved);
|
|
5467
5476
|
if (cast) resolved = resolveCast(cast).set(resolved);
|
|
5468
5477
|
this.attributes[key] = resolved;
|
|
5478
|
+
this.touchedAttributes.add(key);
|
|
5469
5479
|
return this;
|
|
5470
5480
|
}
|
|
5471
5481
|
/**
|
|
@@ -5478,12 +5488,15 @@ var Model = class Model {
|
|
|
5478
5488
|
async save() {
|
|
5479
5489
|
const identifier = this.getAttribute("id");
|
|
5480
5490
|
const payload = this.getRawAttributes();
|
|
5491
|
+
const previousOriginal = this.getOriginal();
|
|
5481
5492
|
const constructor = this.constructor;
|
|
5482
5493
|
if (identifier == null) {
|
|
5483
5494
|
await Model.dispatchEvent(constructor, "saving", this);
|
|
5484
5495
|
await Model.dispatchEvent(constructor, "creating", this);
|
|
5485
5496
|
const model = await constructor.query().create(payload);
|
|
5486
5497
|
this.fill(model.getRawAttributes());
|
|
5498
|
+
this.syncChanges(previousOriginal);
|
|
5499
|
+
this.syncOriginal();
|
|
5487
5500
|
await Model.dispatchEvent(constructor, "created", this);
|
|
5488
5501
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5489
5502
|
return this;
|
|
@@ -5492,6 +5505,8 @@ var Model = class Model {
|
|
|
5492
5505
|
await Model.dispatchEvent(constructor, "updating", this);
|
|
5493
5506
|
const model = await constructor.query().where({ id: identifier }).update(payload);
|
|
5494
5507
|
this.fill(model.getRawAttributes());
|
|
5508
|
+
this.syncChanges(previousOriginal);
|
|
5509
|
+
this.syncOriginal();
|
|
5495
5510
|
await Model.dispatchEvent(constructor, "updated", this);
|
|
5496
5511
|
await Model.dispatchEvent(constructor, "saved", this);
|
|
5497
5512
|
return this;
|
|
@@ -5515,17 +5530,22 @@ var Model = class Model {
|
|
|
5515
5530
|
async delete() {
|
|
5516
5531
|
const identifier = this.getAttribute("id");
|
|
5517
5532
|
if (identifier == null) throw new ArkormException("Cannot delete a model without an id.");
|
|
5533
|
+
const previousOriginal = this.getOriginal();
|
|
5518
5534
|
const constructor = this.constructor;
|
|
5519
5535
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5520
5536
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5521
5537
|
if (softDeleteConfig.enabled) {
|
|
5522
5538
|
const model = await constructor.query().where({ id: identifier }).update({ [softDeleteConfig.column]: /* @__PURE__ */ new Date() });
|
|
5523
5539
|
this.fill(model.getRawAttributes());
|
|
5540
|
+
this.syncChanges(previousOriginal);
|
|
5541
|
+
this.syncOriginal();
|
|
5524
5542
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5525
5543
|
return this;
|
|
5526
5544
|
}
|
|
5527
5545
|
const deleted = await constructor.query().where({ id: identifier }).delete();
|
|
5528
5546
|
this.fill(deleted.getRawAttributes());
|
|
5547
|
+
this.syncChanges(previousOriginal);
|
|
5548
|
+
this.syncOriginal();
|
|
5529
5549
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5530
5550
|
return this;
|
|
5531
5551
|
}
|
|
@@ -5546,11 +5566,14 @@ var Model = class Model {
|
|
|
5546
5566
|
async forceDelete() {
|
|
5547
5567
|
const identifier = this.getAttribute("id");
|
|
5548
5568
|
if (identifier == null) throw new ArkormException("Cannot force delete a model without an id.");
|
|
5569
|
+
const previousOriginal = this.getOriginal();
|
|
5549
5570
|
const constructor = this.constructor;
|
|
5550
5571
|
await Model.dispatchEvent(constructor, "forceDeleting", this);
|
|
5551
5572
|
await Model.dispatchEvent(constructor, "deleting", this);
|
|
5552
5573
|
const deleted = await constructor.query().withTrashed().where({ id: identifier }).delete();
|
|
5553
5574
|
this.fill(deleted.getRawAttributes());
|
|
5575
|
+
this.syncChanges(previousOriginal);
|
|
5576
|
+
this.syncOriginal();
|
|
5554
5577
|
await Model.dispatchEvent(constructor, "deleted", this);
|
|
5555
5578
|
await Model.dispatchEvent(constructor, "forceDeleted", this);
|
|
5556
5579
|
return this;
|
|
@@ -5574,9 +5597,12 @@ var Model = class Model {
|
|
|
5574
5597
|
const constructor = this.constructor;
|
|
5575
5598
|
const softDeleteConfig = constructor.getSoftDeleteConfig();
|
|
5576
5599
|
if (!softDeleteConfig.enabled) return this;
|
|
5600
|
+
const previousOriginal = this.getOriginal();
|
|
5577
5601
|
await Model.dispatchEvent(constructor, "restoring", this);
|
|
5578
5602
|
const model = await constructor.query().withTrashed().where({ id: identifier }).update({ [softDeleteConfig.column]: null });
|
|
5579
5603
|
this.fill(model.getRawAttributes());
|
|
5604
|
+
this.syncChanges(previousOriginal);
|
|
5605
|
+
this.syncOriginal();
|
|
5580
5606
|
await Model.dispatchEvent(constructor, "restored", this);
|
|
5581
5607
|
return this;
|
|
5582
5608
|
}
|
|
@@ -5614,6 +5640,42 @@ var Model = class Model {
|
|
|
5614
5640
|
getRawAttributes() {
|
|
5615
5641
|
return { ...this.attributes };
|
|
5616
5642
|
}
|
|
5643
|
+
getOriginal(key) {
|
|
5644
|
+
if (typeof key === "string") return Model.cloneAttributeValue(this.original[key]);
|
|
5645
|
+
return Object.entries(this.original).reduce((all, [originalKey, value]) => {
|
|
5646
|
+
all[originalKey] = Model.cloneAttributeValue(value);
|
|
5647
|
+
return all;
|
|
5648
|
+
}, {});
|
|
5649
|
+
}
|
|
5650
|
+
/**
|
|
5651
|
+
* Determine whether the model has unsaved attribute changes.
|
|
5652
|
+
*
|
|
5653
|
+
* @param keys
|
|
5654
|
+
* @returns
|
|
5655
|
+
*/
|
|
5656
|
+
isDirty(keys) {
|
|
5657
|
+
return Object.keys(this.getDirtyAttributes(keys)).length > 0;
|
|
5658
|
+
}
|
|
5659
|
+
/**
|
|
5660
|
+
* Determine whether the model has no unsaved attribute changes.
|
|
5661
|
+
*
|
|
5662
|
+
* @param keys
|
|
5663
|
+
* @returns
|
|
5664
|
+
*/
|
|
5665
|
+
isClean(keys) {
|
|
5666
|
+
return !this.isDirty(keys);
|
|
5667
|
+
}
|
|
5668
|
+
/**
|
|
5669
|
+
* Determine whether the model changed during the last successful persistence operation.
|
|
5670
|
+
*
|
|
5671
|
+
* @param keys
|
|
5672
|
+
* @returns
|
|
5673
|
+
*/
|
|
5674
|
+
wasChanged(keys) {
|
|
5675
|
+
const keyList = this.normalizeAttributeKeys(keys);
|
|
5676
|
+
if (keyList.length === 0) return Object.keys(this.changes).length > 0;
|
|
5677
|
+
return keyList.some((key) => Object.prototype.hasOwnProperty.call(this.changes, key));
|
|
5678
|
+
}
|
|
5617
5679
|
/**
|
|
5618
5680
|
* Convert the model instance to a plain object, applying visibility
|
|
5619
5681
|
* rules, appends, and mutators.
|
|
@@ -5804,6 +5866,34 @@ var Model = class Model {
|
|
|
5804
5866
|
return typeof method === "function" ? method : null;
|
|
5805
5867
|
}
|
|
5806
5868
|
/**
|
|
5869
|
+
* Build a map of dirty attributes, optionally limited to specific keys.
|
|
5870
|
+
*
|
|
5871
|
+
* @param keys
|
|
5872
|
+
* @returns
|
|
5873
|
+
*/
|
|
5874
|
+
getDirtyAttributes(keys) {
|
|
5875
|
+
const requestedKeys = this.normalizeAttributeKeys(keys);
|
|
5876
|
+
return (requestedKeys.length > 0 ? requestedKeys : Array.from(new Set([...Object.keys(this.original), ...this.touchedAttributes]))).reduce((dirty, key) => {
|
|
5877
|
+
const currentValue = this.attributes[key];
|
|
5878
|
+
const originalValue = this.original[key];
|
|
5879
|
+
const hasCurrent = Object.prototype.hasOwnProperty.call(this.attributes, key);
|
|
5880
|
+
const hasOriginal = Object.prototype.hasOwnProperty.call(this.original, key);
|
|
5881
|
+
if (!hasCurrent && !hasOriginal) return dirty;
|
|
5882
|
+
if (hasCurrent !== hasOriginal || !Model.areAttributeValuesEqual(currentValue, originalValue)) dirty[key] = Model.cloneAttributeValue(currentValue);
|
|
5883
|
+
return dirty;
|
|
5884
|
+
}, {});
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* Normalize a key or key list for dirty/change lookups.
|
|
5888
|
+
*
|
|
5889
|
+
* @param keys
|
|
5890
|
+
* @returns
|
|
5891
|
+
*/
|
|
5892
|
+
normalizeAttributeKeys(keys) {
|
|
5893
|
+
if (typeof keys === "undefined") return [];
|
|
5894
|
+
return Array.isArray(keys) ? keys : [keys];
|
|
5895
|
+
}
|
|
5896
|
+
/**
|
|
5807
5897
|
* Resolve an Attribute object mutator method for a given key, if it exists.
|
|
5808
5898
|
*
|
|
5809
5899
|
* @param key
|
|
@@ -5845,6 +5935,66 @@ var Model = class Model {
|
|
|
5845
5935
|
if (!Object.prototype.hasOwnProperty.call(this, "eventListeners")) this.eventListeners = { ...this.eventListeners || {} };
|
|
5846
5936
|
}
|
|
5847
5937
|
/**
|
|
5938
|
+
* Clone an attribute value to keep snapshot state isolated from live mutations.
|
|
5939
|
+
*
|
|
5940
|
+
* @param value
|
|
5941
|
+
* @returns
|
|
5942
|
+
*/
|
|
5943
|
+
static cloneAttributeValue(value) {
|
|
5944
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
5945
|
+
if (Array.isArray(value)) return value.map((item) => Model.cloneAttributeValue(item));
|
|
5946
|
+
if (value && typeof value === "object") return Object.entries(value).reduce((all, [key, nestedValue]) => {
|
|
5947
|
+
all[key] = Model.cloneAttributeValue(nestedValue);
|
|
5948
|
+
return all;
|
|
5949
|
+
}, {});
|
|
5950
|
+
return value;
|
|
5951
|
+
}
|
|
5952
|
+
/**
|
|
5953
|
+
* Compare attribute values for dirty/change detection.
|
|
5954
|
+
*
|
|
5955
|
+
* @param left
|
|
5956
|
+
* @param right
|
|
5957
|
+
* @returns
|
|
5958
|
+
*/
|
|
5959
|
+
static areAttributeValuesEqual(left, right) {
|
|
5960
|
+
if (left === right) return true;
|
|
5961
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
5962
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
5963
|
+
if (left.length !== right.length) return false;
|
|
5964
|
+
return left.every((value, index) => Model.areAttributeValuesEqual(value, right[index]));
|
|
5965
|
+
}
|
|
5966
|
+
if (left && right && typeof left === "object" && typeof right === "object") {
|
|
5967
|
+
const leftEntries = Object.entries(left);
|
|
5968
|
+
const rightEntries = Object.entries(right);
|
|
5969
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
5970
|
+
return leftEntries.every(([key, value]) => {
|
|
5971
|
+
return Object.prototype.hasOwnProperty.call(right, key) && Model.areAttributeValuesEqual(value, right[key]);
|
|
5972
|
+
});
|
|
5973
|
+
}
|
|
5974
|
+
return false;
|
|
5975
|
+
}
|
|
5976
|
+
/**
|
|
5977
|
+
* Sync the original snapshot to the model's current raw attributes.
|
|
5978
|
+
*/
|
|
5979
|
+
syncOriginal() {
|
|
5980
|
+
this.original = Object.entries(this.attributes).reduce((all, [key, value]) => {
|
|
5981
|
+
all[key] = Model.cloneAttributeValue(value);
|
|
5982
|
+
return all;
|
|
5983
|
+
}, {});
|
|
5984
|
+
this.touchedAttributes.clear();
|
|
5985
|
+
}
|
|
5986
|
+
/**
|
|
5987
|
+
* Sync the last-changed snapshot from a previous original state.
|
|
5988
|
+
*
|
|
5989
|
+
* @param previousOriginal
|
|
5990
|
+
*/
|
|
5991
|
+
syncChanges(previousOriginal) {
|
|
5992
|
+
this.changes = Object.entries(this.getDirtyAttributes()).reduce((all, [key, value]) => {
|
|
5993
|
+
if (!Object.prototype.hasOwnProperty.call(previousOriginal, key) || !Model.areAttributeValuesEqual(value, previousOriginal[key])) all[key] = Model.cloneAttributeValue(value);
|
|
5994
|
+
return all;
|
|
5995
|
+
}, {});
|
|
5996
|
+
}
|
|
5997
|
+
/**
|
|
5848
5998
|
* Resolve lifecycle state for the provided model class.
|
|
5849
5999
|
*
|
|
5850
6000
|
* @param modelClass
|