@fluixi/utils 1.0.0-alpha.53
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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/array/advanced.cjs +1 -0
- package/dist/lib/array/advanced.d.ts +1116 -0
- package/dist/lib/array/advanced.d.ts.map +1 -0
- package/dist/lib/array/advanced.js +2429 -0
- package/dist/lib/array/advanced.mjs +1 -0
- package/dist/lib/array/array.cjs +1 -0
- package/dist/lib/array/array.d.ts +6 -0
- package/dist/lib/array/array.d.ts.map +1 -0
- package/dist/lib/array/array.js +31 -0
- package/dist/lib/array/array.mjs +1 -0
- package/dist/lib/array/index.cjs +1 -0
- package/dist/lib/array/index.d.ts +4 -0
- package/dist/lib/array/index.d.ts.map +1 -0
- package/dist/lib/array/index.js +3 -0
- package/dist/lib/array/index.mjs +1 -0
- package/dist/lib/array/utils.cjs +1 -0
- package/dist/lib/array/utils.d.ts +100 -0
- package/dist/lib/array/utils.d.ts.map +1 -0
- package/dist/lib/array/utils.js +165 -0
- package/dist/lib/array/utils.mjs +1 -0
- package/dist/lib/color/color.cjs +1 -0
- package/dist/lib/color/color.d.ts +366 -0
- package/dist/lib/color/color.d.ts.map +1 -0
- package/dist/lib/color/color.js +722 -0
- package/dist/lib/color/color.mjs +1 -0
- package/dist/lib/color/constants.cjs +1 -0
- package/dist/lib/color/constants.d.ts +361 -0
- package/dist/lib/color/constants.d.ts.map +1 -0
- package/dist/lib/color/constants.js +358 -0
- package/dist/lib/color/constants.mjs +1 -0
- package/dist/lib/color/conversions.cjs +1 -0
- package/dist/lib/color/conversions.d.ts +166 -0
- package/dist/lib/color/conversions.d.ts.map +1 -0
- package/dist/lib/color/conversions.js +766 -0
- package/dist/lib/color/conversions.mjs +1 -0
- package/dist/lib/color/helpers.cjs +1 -0
- package/dist/lib/color/helpers.d.ts +75 -0
- package/dist/lib/color/helpers.d.ts.map +1 -0
- package/dist/lib/color/helpers.js +419 -0
- package/dist/lib/color/helpers.mjs +1 -0
- package/dist/lib/color/index.cjs +1 -0
- package/dist/lib/color/index.d.ts +12 -0
- package/dist/lib/color/index.d.ts.map +1 -0
- package/dist/lib/color/index.js +15 -0
- package/dist/lib/color/index.mjs +1 -0
- package/dist/lib/color/types.cjs +1 -0
- package/dist/lib/color/types.d.ts +179 -0
- package/dist/lib/color/types.d.ts.map +1 -0
- package/dist/lib/color/types.js +1 -0
- package/dist/lib/color/types.mjs +0 -0
- package/dist/lib/color/utils.cjs +1 -0
- package/dist/lib/color/utils.d.ts +199 -0
- package/dist/lib/color/utils.d.ts.map +1 -0
- package/dist/lib/color/utils.js +858 -0
- package/dist/lib/color/utils.mjs +1 -0
- package/dist/lib/compare/comparators.cjs +1 -0
- package/dist/lib/compare/comparators.d.ts +65 -0
- package/dist/lib/compare/comparators.d.ts.map +1 -0
- package/dist/lib/compare/comparators.js +210 -0
- package/dist/lib/compare/comparators.mjs +1 -0
- package/dist/lib/compare/index.cjs +1 -0
- package/dist/lib/compare/index.d.ts +2 -0
- package/dist/lib/compare/index.d.ts.map +1 -0
- package/dist/lib/compare/index.js +1 -0
- package/dist/lib/compare/index.mjs +1 -0
- package/dist/lib/crypto/hash.cjs +1 -0
- package/dist/lib/crypto/hash.d.ts +6 -0
- package/dist/lib/crypto/hash.d.ts.map +1 -0
- package/dist/lib/crypto/hash.js +53 -0
- package/dist/lib/crypto/hash.mjs +1 -0
- package/dist/lib/crypto/index.cjs +1 -0
- package/dist/lib/crypto/index.d.ts +2 -0
- package/dist/lib/crypto/index.d.ts.map +1 -0
- package/dist/lib/crypto/index.js +1 -0
- package/dist/lib/crypto/index.mjs +1 -0
- package/dist/lib/dom/events.cjs +1 -0
- package/dist/lib/dom/events.d.ts +159 -0
- package/dist/lib/dom/events.d.ts.map +1 -0
- package/dist/lib/dom/events.js +50 -0
- package/dist/lib/dom/events.mjs +1 -0
- package/dist/lib/dom/index.cjs +1 -0
- package/dist/lib/dom/index.d.ts +3 -0
- package/dist/lib/dom/index.d.ts.map +1 -0
- package/dist/lib/dom/index.js +2 -0
- package/dist/lib/dom/index.mjs +1 -0
- package/dist/lib/dom/utils.cjs +1 -0
- package/dist/lib/dom/utils.d.ts +6 -0
- package/dist/lib/dom/utils.d.ts.map +1 -0
- package/dist/lib/dom/utils.js +37 -0
- package/dist/lib/dom/utils.mjs +1 -0
- package/dist/lib/functions/functions.cjs +1 -0
- package/dist/lib/functions/functions.d.ts +329 -0
- package/dist/lib/functions/functions.d.ts.map +1 -0
- package/dist/lib/functions/functions.js +585 -0
- package/dist/lib/functions/functions.mjs +1 -0
- package/dist/lib/functions/index.cjs +1 -0
- package/dist/lib/functions/index.d.ts +3 -0
- package/dist/lib/functions/index.d.ts.map +1 -0
- package/dist/lib/functions/index.js +1 -0
- package/dist/lib/functions/index.mjs +1 -0
- package/dist/lib/functions/types.cjs +1 -0
- package/dist/lib/functions/types.d.ts +2 -0
- package/dist/lib/functions/types.d.ts.map +1 -0
- package/dist/lib/functions/types.js +1 -0
- package/dist/lib/functions/types.mjs +0 -0
- package/dist/lib/helpers.cjs +1 -0
- package/dist/lib/helpers.d.ts +13 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +66 -0
- package/dist/lib/helpers.mjs +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/maths/constants.cjs +1 -0
- package/dist/lib/maths/constants.d.ts +84 -0
- package/dist/lib/maths/constants.d.ts.map +1 -0
- package/dist/lib/maths/constants.js +79 -0
- package/dist/lib/maths/constants.mjs +1 -0
- package/dist/lib/maths/fn/advanced.cjs +1 -0
- package/dist/lib/maths/fn/advanced.d.ts +149 -0
- package/dist/lib/maths/fn/advanced.d.ts.map +1 -0
- package/dist/lib/maths/fn/advanced.js +264 -0
- package/dist/lib/maths/fn/advanced.mjs +1 -0
- package/dist/lib/maths/fn/basic.cjs +1 -0
- package/dist/lib/maths/fn/basic.d.ts +182 -0
- package/dist/lib/maths/fn/basic.d.ts.map +1 -0
- package/dist/lib/maths/fn/basic.js +343 -0
- package/dist/lib/maths/fn/basic.mjs +1 -0
- package/dist/lib/maths/fn/complex.cjs +1 -0
- package/dist/lib/maths/fn/complex.d.ts +153 -0
- package/dist/lib/maths/fn/complex.d.ts.map +1 -0
- package/dist/lib/maths/fn/complex.js +383 -0
- package/dist/lib/maths/fn/complex.mjs +1 -0
- package/dist/lib/maths/fn/index.cjs +1 -0
- package/dist/lib/maths/fn/index.d.ts +10 -0
- package/dist/lib/maths/fn/index.d.ts.map +1 -0
- package/dist/lib/maths/fn/index.js +9 -0
- package/dist/lib/maths/fn/index.mjs +1 -0
- package/dist/lib/maths/fn/trigo.cjs +1 -0
- package/dist/lib/maths/fn/trigo.d.ts +147 -0
- package/dist/lib/maths/fn/trigo.d.ts.map +1 -0
- package/dist/lib/maths/fn/trigo.js +227 -0
- package/dist/lib/maths/fn/trigo.mjs +1 -0
- package/dist/lib/maths/fn/vector.cjs +1 -0
- package/dist/lib/maths/fn/vector.d.ts +512 -0
- package/dist/lib/maths/fn/vector.d.ts.map +1 -0
- package/dist/lib/maths/fn/vector.js +1136 -0
- package/dist/lib/maths/fn/vector.mjs +1 -0
- package/dist/lib/maths/formatter.cjs +1 -0
- package/dist/lib/maths/formatter.d.ts +35 -0
- package/dist/lib/maths/formatter.d.ts.map +1 -0
- package/dist/lib/maths/formatter.js +56 -0
- package/dist/lib/maths/formatter.mjs +1 -0
- package/dist/lib/maths/guards.cjs +1 -0
- package/dist/lib/maths/guards.d.ts +166 -0
- package/dist/lib/maths/guards.d.ts.map +1 -0
- package/dist/lib/maths/guards.js +315 -0
- package/dist/lib/maths/guards.mjs +1 -0
- package/dist/lib/maths/index.cjs +1 -0
- package/dist/lib/maths/index.d.ts +24 -0
- package/dist/lib/maths/index.d.ts.map +1 -0
- package/dist/lib/maths/index.js +29 -0
- package/dist/lib/maths/index.mjs +1 -0
- package/dist/lib/maths/parser.cjs +1 -0
- package/dist/lib/maths/parser.d.ts +20 -0
- package/dist/lib/maths/parser.d.ts.map +1 -0
- package/dist/lib/maths/parser.js +78 -0
- package/dist/lib/maths/parser.mjs +1 -0
- package/dist/lib/maths/random.cjs +1 -0
- package/dist/lib/maths/random.d.ts +27 -0
- package/dist/lib/maths/random.d.ts.map +1 -0
- package/dist/lib/maths/random.js +49 -0
- package/dist/lib/maths/random.mjs +1 -0
- package/dist/lib/maths/types/advanced.cjs +1 -0
- package/dist/lib/maths/types/advanced.d.ts +357 -0
- package/dist/lib/maths/types/advanced.d.ts.map +1 -0
- package/dist/lib/maths/types/advanced.js +5 -0
- package/dist/lib/maths/types/advanced.mjs +0 -0
- package/dist/lib/maths/types/basic.cjs +1 -0
- package/dist/lib/maths/types/basic.d.ts +95 -0
- package/dist/lib/maths/types/basic.d.ts.map +1 -0
- package/dist/lib/maths/types/basic.js +5 -0
- package/dist/lib/maths/types/basic.mjs +0 -0
- package/dist/lib/maths/types/index.cjs +1 -0
- package/dist/lib/maths/types/index.d.ts +4 -0
- package/dist/lib/maths/types/index.d.ts.map +1 -0
- package/dist/lib/maths/types/index.js +3 -0
- package/dist/lib/maths/types/index.mjs +0 -0
- package/dist/lib/maths/types/negative.cjs +1 -0
- package/dist/lib/maths/types/negative.d.ts +51 -0
- package/dist/lib/maths/types/negative.d.ts.map +1 -0
- package/dist/lib/maths/types/negative.js +5 -0
- package/dist/lib/maths/types/negative.mjs +0 -0
- package/dist/lib/maths/types/operations.cjs +1 -0
- package/dist/lib/maths/types/operations.d.ts +188 -0
- package/dist/lib/maths/types/operations.d.ts.map +1 -0
- package/dist/lib/maths/types/operations.js +6 -0
- package/dist/lib/maths/types/operations.mjs +0 -0
- package/dist/lib/object/clone.cjs +1 -0
- package/dist/lib/object/clone.d.ts +46 -0
- package/dist/lib/object/clone.d.ts.map +1 -0
- package/dist/lib/object/clone.js +104 -0
- package/dist/lib/object/clone.mjs +1 -0
- package/dist/lib/object/diff.cjs +1 -0
- package/dist/lib/object/diff.d.ts +203 -0
- package/dist/lib/object/diff.d.ts.map +1 -0
- package/dist/lib/object/diff.js +805 -0
- package/dist/lib/object/diff.mjs +1 -0
- package/dist/lib/object/getter-setter.cjs +1 -0
- package/dist/lib/object/getter-setter.d.ts +361 -0
- package/dist/lib/object/getter-setter.d.ts.map +1 -0
- package/dist/lib/object/getter-setter.js +1771 -0
- package/dist/lib/object/getter-setter.mjs +1 -0
- package/dist/lib/object/index.cjs +6 -0
- package/dist/lib/object/index.d.ts +13 -0
- package/dist/lib/object/index.d.ts.map +1 -0
- package/dist/lib/object/index.js +12 -0
- package/dist/lib/object/index.mjs +6 -0
- package/dist/lib/object/merge.cjs +1 -0
- package/dist/lib/object/merge.d.ts +93 -0
- package/dist/lib/object/merge.d.ts.map +1 -0
- package/dist/lib/object/merge.js +474 -0
- package/dist/lib/object/merge.mjs +1 -0
- package/dist/lib/object/object.cjs +1 -0
- package/dist/lib/object/object.d.ts +128 -0
- package/dist/lib/object/object.d.ts.map +1 -0
- package/dist/lib/object/object.js +432 -0
- package/dist/lib/object/object.mjs +1 -0
- package/dist/lib/object/omit.cjs +1 -0
- package/dist/lib/object/omit.d.ts +351 -0
- package/dist/lib/object/omit.d.ts.map +1 -0
- package/dist/lib/object/omit.js +656 -0
- package/dist/lib/object/omit.mjs +1 -0
- package/dist/lib/object/reconcile.cjs +1 -0
- package/dist/lib/object/reconcile.d.ts +7 -0
- package/dist/lib/object/reconcile.d.ts.map +1 -0
- package/dist/lib/object/reconcile.js +122 -0
- package/dist/lib/object/reconcile.mjs +1 -0
- package/dist/lib/object/selector.cjs +1 -0
- package/dist/lib/object/selector.d.ts +50 -0
- package/dist/lib/object/selector.d.ts.map +1 -0
- package/dist/lib/object/selector.js +371 -0
- package/dist/lib/object/selector.mjs +1 -0
- package/dist/lib/object/sort.cjs +1 -0
- package/dist/lib/object/sort.d.ts +149 -0
- package/dist/lib/object/sort.d.ts.map +1 -0
- package/dist/lib/object/sort.js +298 -0
- package/dist/lib/object/sort.mjs +1 -0
- package/dist/lib/object/transform.cjs +1 -0
- package/dist/lib/object/transform.d.ts +620 -0
- package/dist/lib/object/transform.d.ts.map +1 -0
- package/dist/lib/object/transform.js +1058 -0
- package/dist/lib/object/transform.mjs +1 -0
- package/dist/lib/object/types.cjs +1 -0
- package/dist/lib/object/types.d.ts +250 -0
- package/dist/lib/object/types.d.ts.map +1 -0
- package/dist/lib/object/types.js +1 -0
- package/dist/lib/object/types.mjs +0 -0
- package/dist/lib/object/utils.cjs +6 -0
- package/dist/lib/object/utils.d.ts +165 -0
- package/dist/lib/object/utils.d.ts.map +1 -0
- package/dist/lib/object/utils.js +444 -0
- package/dist/lib/object/utils.mjs +6 -0
- package/dist/lib/primitive/boolean/boolean.cjs +1 -0
- package/dist/lib/primitive/boolean/boolean.d.ts +276 -0
- package/dist/lib/primitive/boolean/boolean.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/boolean.js +615 -0
- package/dist/lib/primitive/boolean/boolean.mjs +1 -0
- package/dist/lib/primitive/boolean/index.cjs +1 -0
- package/dist/lib/primitive/boolean/index.d.ts +3 -0
- package/dist/lib/primitive/boolean/index.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/index.js +2 -0
- package/dist/lib/primitive/boolean/index.mjs +1 -0
- package/dist/lib/primitive/boolean/types.cjs +1 -0
- package/dist/lib/primitive/boolean/types.d.ts +14 -0
- package/dist/lib/primitive/boolean/types.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/types.js +1 -0
- package/dist/lib/primitive/boolean/types.mjs +0 -0
- package/dist/lib/primitive/date/date.cjs +1 -0
- package/dist/lib/primitive/date/date.d.ts +641 -0
- package/dist/lib/primitive/date/date.d.ts.map +1 -0
- package/dist/lib/primitive/date/date.js +1998 -0
- package/dist/lib/primitive/date/date.mjs +1 -0
- package/dist/lib/primitive/date/index.cjs +1 -0
- package/dist/lib/primitive/date/index.d.ts +3 -0
- package/dist/lib/primitive/date/index.d.ts.map +1 -0
- package/dist/lib/primitive/date/index.js +2 -0
- package/dist/lib/primitive/date/index.mjs +1 -0
- package/dist/lib/primitive/date/types.cjs +1 -0
- package/dist/lib/primitive/date/types.d.ts +205 -0
- package/dist/lib/primitive/date/types.d.ts.map +1 -0
- package/dist/lib/primitive/date/types.js +7 -0
- package/dist/lib/primitive/date/types.mjs +0 -0
- package/dist/lib/primitive/index.cjs +5 -0
- package/dist/lib/primitive/index.d.ts +5 -0
- package/dist/lib/primitive/index.d.ts.map +1 -0
- package/dist/lib/primitive/index.js +4 -0
- package/dist/lib/primitive/index.mjs +5 -0
- package/dist/lib/primitive/string/index.cjs +5 -0
- package/dist/lib/primitive/string/index.d.ts +3 -0
- package/dist/lib/primitive/string/index.d.ts.map +1 -0
- package/dist/lib/primitive/string/index.js +2 -0
- package/dist/lib/primitive/string/index.mjs +5 -0
- package/dist/lib/primitive/string/parser-2.cjs +8 -0
- package/dist/lib/primitive/string/parser-2.d.ts +17 -0
- package/dist/lib/primitive/string/parser-2.d.ts.map +1 -0
- package/dist/lib/primitive/string/parser-2.js +656 -0
- package/dist/lib/primitive/string/parser-2.mjs +8 -0
- package/dist/lib/primitive/string/parser.cjs +3 -0
- package/dist/lib/primitive/string/parser.d.ts +9 -0
- package/dist/lib/primitive/string/parser.d.ts.map +1 -0
- package/dist/lib/primitive/string/parser.js +775 -0
- package/dist/lib/primitive/string/parser.mjs +3 -0
- package/dist/lib/primitive/string/string.cjs +5 -0
- package/dist/lib/primitive/string/string.d.ts +535 -0
- package/dist/lib/primitive/string/string.d.ts.map +1 -0
- package/dist/lib/primitive/string/string.js +1301 -0
- package/dist/lib/primitive/string/string.mjs +5 -0
- package/dist/lib/primitive/string/types.cjs +1 -0
- package/dist/lib/primitive/string/types.d.ts +318 -0
- package/dist/lib/primitive/string/types.d.ts.map +1 -0
- package/dist/lib/primitive/string/types.js +5 -0
- package/dist/lib/primitive/string/types.mjs +0 -0
- package/dist/lib/primitive/types.cjs +1 -0
- package/dist/lib/primitive/types.d.ts +27 -0
- package/dist/lib/primitive/types.d.ts.map +1 -0
- package/dist/lib/primitive/types.js +1 -0
- package/dist/lib/primitive/types.mjs +0 -0
- package/dist/lib/url/index.cjs +1 -0
- package/dist/lib/url/index.d.ts +3 -0
- package/dist/lib/url/index.d.ts.map +1 -0
- package/dist/lib/url/index.js +2 -0
- package/dist/lib/url/index.mjs +1 -0
- package/dist/lib/url/parser.cjs +1 -0
- package/dist/lib/url/parser.d.ts +216 -0
- package/dist/lib/url/parser.d.ts.map +1 -0
- package/dist/lib/url/parser.js +488 -0
- package/dist/lib/url/parser.mjs +1 -0
- package/dist/lib/url/types.cjs +1 -0
- package/dist/lib/url/types.d.ts +18 -0
- package/dist/lib/url/types.d.ts.map +1 -0
- package/dist/lib/url/types.js +1 -0
- package/dist/lib/url/types.mjs +0 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +150 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Object diff utilities for comparing and tracking changes
|
|
3
|
+
* @description Provides utilities to diff objects and return structured changes
|
|
4
|
+
*/
|
|
5
|
+
import { isObject } from './utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Default equality function using strict equality
|
|
8
|
+
*/
|
|
9
|
+
const defaultEqualityFn = (a, b) => a === b;
|
|
10
|
+
/**
|
|
11
|
+
* Deep equality check for arrays
|
|
12
|
+
*/
|
|
13
|
+
const deepArrayEqual = (arr1, arr2) => {
|
|
14
|
+
if (arr1.length !== arr2.length)
|
|
15
|
+
return false;
|
|
16
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
17
|
+
if (Array.isArray(arr1[i]) && Array.isArray(arr2[i])) {
|
|
18
|
+
if (!deepArrayEqual(arr1[i], arr2[i]))
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
else if (isObject(arr1[i]) && isObject(arr2[i])) {
|
|
22
|
+
if (!deepObjectEqual(arr1[i], arr2[i]))
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
else if (arr1[i] !== arr2[i]) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Deep equality check for objects
|
|
33
|
+
*/
|
|
34
|
+
const deepObjectEqual = (obj1, obj2) => {
|
|
35
|
+
const keys1 = Object.keys(obj1);
|
|
36
|
+
const keys2 = Object.keys(obj2);
|
|
37
|
+
if (keys1.length !== keys2.length)
|
|
38
|
+
return false;
|
|
39
|
+
for (const key of keys1) {
|
|
40
|
+
if (!(key in obj2))
|
|
41
|
+
return false;
|
|
42
|
+
const val1 = obj1[key];
|
|
43
|
+
const val2 = obj2[key];
|
|
44
|
+
if (Array.isArray(val1) && Array.isArray(val2)) {
|
|
45
|
+
if (!deepArrayEqual(val1, val2))
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
else if (isObject(val1) && isObject(val2)) {
|
|
49
|
+
if (!deepObjectEqual(val1, val2))
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
else if (val1 !== val2) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Compares two objects and returns only the changed values while preserving the object structure
|
|
60
|
+
*
|
|
61
|
+
* @param oldObj - The original object
|
|
62
|
+
* @param newObj - The new object to compare against
|
|
63
|
+
* @param options - Configuration options for diff behavior
|
|
64
|
+
* @returns Object with the same structure containing only changed values
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const old = { a: 1, b: { c: 2, d: 3 }, e: 5 };
|
|
69
|
+
* const new = { a: 1, b: { c: 4, d: 3 }, e: 5, f: 6 };
|
|
70
|
+
* const diff = diffObject(old, new);
|
|
71
|
+
* // Returns: { b: { c: 4 }, f: 6 }
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* // With arrays
|
|
77
|
+
* const old = { items: [1, 2, 3], meta: { count: 3 } };
|
|
78
|
+
* const new = { items: [1, 2, 4], meta: { count: 4 } };
|
|
79
|
+
* const diff = diffObject(old, new);
|
|
80
|
+
* // Returns: { items: [1, 2, 4], meta: { count: 4 } }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* // Include deleted properties
|
|
86
|
+
* const old = { a: 1, b: 2, c: 3 };
|
|
87
|
+
* const new = { a: 1, c: 4 };
|
|
88
|
+
* const diff = diffObject(old, new, { includeDeleted: true });
|
|
89
|
+
* // Returns: { b: undefined, c: 4 }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export const diffObject = (oldObj, newObj, options = {}) => {
|
|
93
|
+
const { includeDeleted = false, includeAdded = true, deepArrayComparison = true, equalityFn = defaultEqualityFn, preserveStructure = false, } = options;
|
|
94
|
+
// Handle null/undefined cases
|
|
95
|
+
if (oldObj == null && newObj == null) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
if (oldObj == null) {
|
|
99
|
+
return includeAdded ? { ...newObj } : {};
|
|
100
|
+
}
|
|
101
|
+
if (newObj == null) {
|
|
102
|
+
if (includeDeleted) {
|
|
103
|
+
const result = {};
|
|
104
|
+
for (const key in oldObj) {
|
|
105
|
+
result[key] = undefined;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
// Ensure both are objects
|
|
112
|
+
if (!isObject(oldObj) || !isObject(newObj)) {
|
|
113
|
+
return equalityFn(oldObj, newObj)
|
|
114
|
+
? {}
|
|
115
|
+
: newObj;
|
|
116
|
+
}
|
|
117
|
+
const result = {};
|
|
118
|
+
const processedKeys = new Set();
|
|
119
|
+
// Process all keys in newObj
|
|
120
|
+
for (const key in newObj) {
|
|
121
|
+
processedKeys.add(key);
|
|
122
|
+
const oldValue = oldObj[key];
|
|
123
|
+
const newValue = newObj[key];
|
|
124
|
+
// Key doesn't exist in old object - it's added
|
|
125
|
+
if (!(key in oldObj)) {
|
|
126
|
+
if (includeAdded) {
|
|
127
|
+
result[key] = newValue;
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Both values are arrays
|
|
132
|
+
if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
|
133
|
+
if (deepArrayComparison) {
|
|
134
|
+
if (!deepArrayEqual(oldValue, newValue)) {
|
|
135
|
+
result[key] = newValue;
|
|
136
|
+
}
|
|
137
|
+
else if (preserveStructure) {
|
|
138
|
+
result[key] = undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
if (oldValue !== newValue) {
|
|
143
|
+
result[key] = newValue;
|
|
144
|
+
}
|
|
145
|
+
else if (preserveStructure) {
|
|
146
|
+
result[key] = undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Both values are objects - recurse
|
|
152
|
+
if (isObject(oldValue) && isObject(newValue)) {
|
|
153
|
+
const nestedDiff = diffObject(oldValue, newValue, options);
|
|
154
|
+
// Only include if there are changes
|
|
155
|
+
if (Object.keys(nestedDiff).length > 0) {
|
|
156
|
+
result[key] = nestedDiff;
|
|
157
|
+
}
|
|
158
|
+
else if (preserveStructure) {
|
|
159
|
+
result[key] = undefined;
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Primitive comparison or type mismatch
|
|
164
|
+
if (!equalityFn(oldValue, newValue)) {
|
|
165
|
+
result[key] = newValue;
|
|
166
|
+
}
|
|
167
|
+
else if (preserveStructure) {
|
|
168
|
+
result[key] = undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Process deleted keys (exist in old but not in new)
|
|
172
|
+
if (includeDeleted) {
|
|
173
|
+
for (const key in oldObj) {
|
|
174
|
+
if (!processedKeys.has(key)) {
|
|
175
|
+
result[key] = undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Returns detailed diff with metadata about changes for objects and arrays
|
|
183
|
+
*
|
|
184
|
+
* @param oldObj - The original object or array
|
|
185
|
+
* @param newObj - The new object or array to compare against
|
|
186
|
+
* @param options - Configuration options for diff behavior
|
|
187
|
+
* @returns Detailed diff result with metadata including array indices in paths
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* // Object example
|
|
192
|
+
* const old = { a: 1, b: 2, c: 3 };
|
|
193
|
+
* const new = { a: 1, b: 4, d: 5 };
|
|
194
|
+
* const result = diffObjectDetailed(old, new);
|
|
195
|
+
* // {
|
|
196
|
+
* // changes: { b: 4, d: 5 },
|
|
197
|
+
* // added: ['d'],
|
|
198
|
+
* // deleted: ['c'],
|
|
199
|
+
* // modified: ['b'],
|
|
200
|
+
* // hasChanges: true
|
|
201
|
+
* // }
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* // Nested array example
|
|
207
|
+
* const old = { items: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }] };
|
|
208
|
+
* const new = { items: [{ id: 1, name: 'x' }, { id: 2, name: 'b' }] };
|
|
209
|
+
* const result = diffObjectDetailed(old, new);
|
|
210
|
+
* // modified: ['items[0].name']
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* // Top-level array example
|
|
216
|
+
* const old = [1, 2, 3, 4];
|
|
217
|
+
* const new = [1, 5, 3];
|
|
218
|
+
* const result = diffObjectDetailed(old, new);
|
|
219
|
+
* // {
|
|
220
|
+
* // changes: [1, 5, 3], // Returns the new array with changes
|
|
221
|
+
* // added: [],
|
|
222
|
+
* // deleted: ['[3]'], // Index 3 was removed
|
|
223
|
+
* // modified: ['[1]'], // Index 1 changed from 2 to 5
|
|
224
|
+
* // hasChanges: true
|
|
225
|
+
* // }
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* // Array of objects example
|
|
231
|
+
* const old = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
|
|
232
|
+
* const new = [{ id: 1, name: 'Alicia' }, { id: 2, name: 'Bob' }];
|
|
233
|
+
* const result = diffObjectDetailed(old, new);
|
|
234
|
+
* // {
|
|
235
|
+
* // changes: [{ id: 1, name: 'Alicia' }, { id: 2, name: 'Bob' }],
|
|
236
|
+
* // modified: ['[0]'], // First element modified
|
|
237
|
+
* // hasChanges: true
|
|
238
|
+
* // }
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
export const diffObjectDetailed = (oldObj, newObj, options = {}) => {
|
|
242
|
+
const added = [];
|
|
243
|
+
const deleted = [];
|
|
244
|
+
const modified = [];
|
|
245
|
+
const changes = {};
|
|
246
|
+
const { includeDeleted = false, includeAdded = true, deepArrayComparison = true, equalityFn = defaultEqualityFn, } = options;
|
|
247
|
+
// Handle arrays at the top level
|
|
248
|
+
if (Array.isArray(oldObj) && Array.isArray(newObj)) {
|
|
249
|
+
const maxLength = Math.max(oldObj.length, newObj.length);
|
|
250
|
+
const arrayChanges = [];
|
|
251
|
+
let hasArrayChanges = false;
|
|
252
|
+
for (let i = 0; i < maxLength; i++) {
|
|
253
|
+
const indexPath = `[${i}]`;
|
|
254
|
+
if (i >= oldObj.length) {
|
|
255
|
+
// Added element
|
|
256
|
+
added.push(indexPath);
|
|
257
|
+
arrayChanges[i] = newObj[i];
|
|
258
|
+
hasArrayChanges = true;
|
|
259
|
+
}
|
|
260
|
+
else if (i >= newObj.length) {
|
|
261
|
+
// Deleted element - always track deletions for arrays
|
|
262
|
+
deleted.push(indexPath);
|
|
263
|
+
hasArrayChanges = true;
|
|
264
|
+
// Don't add to arrayChanges - changes array should match newObj length
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// Compare elements
|
|
268
|
+
const oldElement = oldObj[i];
|
|
269
|
+
const newElement = newObj[i];
|
|
270
|
+
if (isObject(oldElement) && isObject(newElement)) {
|
|
271
|
+
const nestedDiff = diffObjectDetailed(oldElement, newElement, options);
|
|
272
|
+
if (nestedDiff.hasChanges) {
|
|
273
|
+
modified.push(indexPath);
|
|
274
|
+
arrayChanges[i] = newElement;
|
|
275
|
+
hasArrayChanges = true;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
arrayChanges[i] = oldElement;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else if (Array.isArray(oldElement) && Array.isArray(newElement)) {
|
|
282
|
+
const nestedDiff = diffObjectDetailed(oldElement, newElement, options);
|
|
283
|
+
if (nestedDiff.hasChanges) {
|
|
284
|
+
modified.push(indexPath);
|
|
285
|
+
arrayChanges[i] = newElement;
|
|
286
|
+
hasArrayChanges = true;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
arrayChanges[i] = oldElement;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (!equalityFn(oldElement, newElement)) {
|
|
293
|
+
modified.push(indexPath);
|
|
294
|
+
arrayChanges[i] = newElement;
|
|
295
|
+
hasArrayChanges = true;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
arrayChanges[i] = oldElement;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// For arrays, always include deletions in changes to trigger store updates
|
|
303
|
+
// The hasChanges flag includes deletions even if includeDeleted is false
|
|
304
|
+
return {
|
|
305
|
+
changes: (hasArrayChanges ? arrayChanges : {}),
|
|
306
|
+
added,
|
|
307
|
+
deleted,
|
|
308
|
+
modified,
|
|
309
|
+
hasChanges: hasArrayChanges || deleted.length > 0,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
// Helper to collect paths with array index support and categorize them
|
|
313
|
+
const collectDetailedPaths = (old, newVal, currentPath = '') => {
|
|
314
|
+
const result = {
|
|
315
|
+
added: [],
|
|
316
|
+
deleted: [],
|
|
317
|
+
modified: [],
|
|
318
|
+
};
|
|
319
|
+
// Handle arrays
|
|
320
|
+
if (Array.isArray(old) && Array.isArray(newVal)) {
|
|
321
|
+
const maxLength = Math.max(old.length, newVal.length);
|
|
322
|
+
for (let i = 0; i < maxLength; i++) {
|
|
323
|
+
const indexPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`;
|
|
324
|
+
if (i >= old.length) {
|
|
325
|
+
// Added element
|
|
326
|
+
result.added.push(indexPath);
|
|
327
|
+
}
|
|
328
|
+
else if (i >= newVal.length) {
|
|
329
|
+
// Deleted element
|
|
330
|
+
result.deleted.push(indexPath);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
// Compare elements
|
|
334
|
+
const oldElement = old[i];
|
|
335
|
+
const newElement = newVal[i];
|
|
336
|
+
if (isObject(oldElement) && isObject(newElement)) {
|
|
337
|
+
const nested = collectDetailedPaths(oldElement, newElement, indexPath);
|
|
338
|
+
result.added.push(...nested.added);
|
|
339
|
+
result.deleted.push(...nested.deleted);
|
|
340
|
+
result.modified.push(...nested.modified);
|
|
341
|
+
}
|
|
342
|
+
else if (Array.isArray(oldElement) && Array.isArray(newElement)) {
|
|
343
|
+
const nested = collectDetailedPaths(oldElement, newElement, indexPath);
|
|
344
|
+
result.added.push(...nested.added);
|
|
345
|
+
result.deleted.push(...nested.deleted);
|
|
346
|
+
result.modified.push(...nested.modified);
|
|
347
|
+
}
|
|
348
|
+
else if (oldElement !== newElement) {
|
|
349
|
+
result.modified.push(indexPath);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
// Handle objects
|
|
356
|
+
if (isObject(old) && isObject(newVal)) {
|
|
357
|
+
const allKeys = new Set([
|
|
358
|
+
...Object.keys(old || {}),
|
|
359
|
+
...Object.keys(newVal || {}),
|
|
360
|
+
]);
|
|
361
|
+
for (const key of allKeys) {
|
|
362
|
+
const oldValue = old?.[key];
|
|
363
|
+
const newValue = newVal?.[key];
|
|
364
|
+
const path = currentPath ? `${currentPath}.${key}` : key;
|
|
365
|
+
if (!(key in (old || {}))) {
|
|
366
|
+
// Added
|
|
367
|
+
result.added.push(path);
|
|
368
|
+
}
|
|
369
|
+
else if (!(key in (newVal || {}))) {
|
|
370
|
+
// Deleted
|
|
371
|
+
result.deleted.push(path);
|
|
372
|
+
}
|
|
373
|
+
else if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
|
374
|
+
// Compare arrays
|
|
375
|
+
if (!deepArrayEqual(oldValue, newValue)) {
|
|
376
|
+
const nested = collectDetailedPaths(oldValue, newValue, path);
|
|
377
|
+
result.added.push(...nested.added);
|
|
378
|
+
result.deleted.push(...nested.deleted);
|
|
379
|
+
result.modified.push(...nested.modified);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else if (isObject(oldValue) && isObject(newValue)) {
|
|
383
|
+
// Recurse into objects
|
|
384
|
+
const nested = collectDetailedPaths(oldValue, newValue, path);
|
|
385
|
+
result.added.push(...nested.added);
|
|
386
|
+
result.deleted.push(...nested.deleted);
|
|
387
|
+
result.modified.push(...nested.modified);
|
|
388
|
+
}
|
|
389
|
+
else if (oldValue !== newValue) {
|
|
390
|
+
// Modified
|
|
391
|
+
result.modified.push(path);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
396
|
+
};
|
|
397
|
+
// Handle null/undefined cases
|
|
398
|
+
if (oldObj == null && newObj == null) {
|
|
399
|
+
return { changes: {}, added, deleted, modified, hasChanges: false };
|
|
400
|
+
}
|
|
401
|
+
if (oldObj == null) {
|
|
402
|
+
if (includeAdded && newObj) {
|
|
403
|
+
for (const key in newObj) {
|
|
404
|
+
added.push(key);
|
|
405
|
+
changes[key] = newObj[key];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
changes,
|
|
410
|
+
added,
|
|
411
|
+
deleted,
|
|
412
|
+
modified,
|
|
413
|
+
hasChanges: added.length > 0,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if (newObj == null) {
|
|
417
|
+
if (includeDeleted) {
|
|
418
|
+
for (const key in oldObj) {
|
|
419
|
+
deleted.push(key);
|
|
420
|
+
changes[key] = undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
changes,
|
|
425
|
+
added,
|
|
426
|
+
deleted,
|
|
427
|
+
modified,
|
|
428
|
+
hasChanges: deleted.length > 0,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const processedKeys = new Set();
|
|
432
|
+
// Process all keys in newObj
|
|
433
|
+
for (const key in newObj) {
|
|
434
|
+
processedKeys.add(key);
|
|
435
|
+
const oldValue = oldObj[key];
|
|
436
|
+
const newValue = newObj[key];
|
|
437
|
+
// Key doesn't exist in old object - it's added
|
|
438
|
+
if (!(key in oldObj)) {
|
|
439
|
+
if (includeAdded) {
|
|
440
|
+
added.push(key);
|
|
441
|
+
changes[key] = newValue;
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
// Check if values are different
|
|
446
|
+
let isDifferent = false;
|
|
447
|
+
if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
|
448
|
+
isDifferent = deepArrayComparison
|
|
449
|
+
? !deepArrayEqual(oldValue, newValue)
|
|
450
|
+
: oldValue !== newValue;
|
|
451
|
+
if (isDifferent) {
|
|
452
|
+
const paths = collectDetailedPaths(oldValue, newValue, key);
|
|
453
|
+
added.push(...paths.added);
|
|
454
|
+
deleted.push(...paths.deleted);
|
|
455
|
+
modified.push(...paths.modified);
|
|
456
|
+
changes[key] = newValue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else if (isObject(oldValue) && isObject(newValue)) {
|
|
460
|
+
const nestedDiff = diffObject(oldValue, newValue, options);
|
|
461
|
+
isDifferent = Object.keys(nestedDiff).length > 0;
|
|
462
|
+
if (isDifferent) {
|
|
463
|
+
const paths = collectDetailedPaths(oldValue, newValue, key);
|
|
464
|
+
added.push(...paths.added);
|
|
465
|
+
deleted.push(...paths.deleted);
|
|
466
|
+
modified.push(...paths.modified);
|
|
467
|
+
changes[key] = nestedDiff;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
isDifferent = !equalityFn(oldValue, newValue);
|
|
472
|
+
if (isDifferent) {
|
|
473
|
+
modified.push(key);
|
|
474
|
+
changes[key] = newValue;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Process deleted keys - always track them for the store to work correctly
|
|
479
|
+
for (const key in oldObj) {
|
|
480
|
+
if (!processedKeys.has(key)) {
|
|
481
|
+
deleted.push(key);
|
|
482
|
+
// Always include deletions in changes so store signals are triggered
|
|
483
|
+
changes[key] = undefined;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
changes,
|
|
488
|
+
added,
|
|
489
|
+
deleted,
|
|
490
|
+
modified,
|
|
491
|
+
hasChanges: added.length > 0 || deleted.length > 0 || modified.length > 0,
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Checks if two objects have any differences
|
|
496
|
+
*
|
|
497
|
+
* @param oldObj - The original object
|
|
498
|
+
* @param newObj - The new object to compare against
|
|
499
|
+
* @param options - Configuration options for diff behavior
|
|
500
|
+
* @returns True if objects are different
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```ts
|
|
504
|
+
* hasObjectChanges({ a: 1 }, { a: 1 }) // false
|
|
505
|
+
* hasObjectChanges({ a: 1 }, { a: 2 }) // true
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
export const hasObjectChanges = (oldObj, newObj, options = {}) => {
|
|
509
|
+
const diff = diffObject(oldObj, newObj, options);
|
|
510
|
+
return Object.keys(diff).length > 0;
|
|
511
|
+
};
|
|
512
|
+
/**
|
|
513
|
+
* Applies a diff object to an original object or array to produce the new object/array
|
|
514
|
+
*
|
|
515
|
+
* @param baseObj - The base object or array to apply changes to
|
|
516
|
+
* @param diff - The diff object or array containing changes
|
|
517
|
+
* @param keepRef - If true, modifies the original object/array instead of creating a copy
|
|
518
|
+
* @returns New object/array with changes applied
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```ts
|
|
522
|
+
* // Object example
|
|
523
|
+
* const base = { a: 1, b: { c: 2, d: 3 } };
|
|
524
|
+
* const diff = { b: { c: 4 } };
|
|
525
|
+
* const result = applyDiff(base, diff);
|
|
526
|
+
* // Returns: { a: 1, b: { c: 4, d: 3 } }
|
|
527
|
+
* ```
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```ts
|
|
531
|
+
* // Array example
|
|
532
|
+
* const base = [1, 2, 3, 4];
|
|
533
|
+
* const diff = [10, undefined, 30]; // undefined means no change
|
|
534
|
+
* const result = applyDiff(base, diff);
|
|
535
|
+
* // Returns: [10, 2, 30, 4]
|
|
536
|
+
* ```
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```ts
|
|
540
|
+
* // Nested array/object example
|
|
541
|
+
* const base = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
|
|
542
|
+
* const diff = [{ name: 'Alicia' }, undefined]; // Change first item's name
|
|
543
|
+
* const result = applyDiff(base, diff);
|
|
544
|
+
* // Returns: [{ id: 1, name: 'Alicia' }, { id: 2, name: 'Bob' }]
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
function isApplyDiff(value) {
|
|
548
|
+
if (value &&
|
|
549
|
+
(value.diff || value.changes) &&
|
|
550
|
+
(value.modified ||
|
|
551
|
+
value.deleted ||
|
|
552
|
+
value.hasChanges)) {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
export const applyDiff = (baseObj, option, keepRefParam) => {
|
|
558
|
+
// If option is just a plain object (not ApplyDiff), apply it directly
|
|
559
|
+
if (!isApplyDiff(option)) {
|
|
560
|
+
const diff = option;
|
|
561
|
+
const shouldKeepRef = keepRefParam || false;
|
|
562
|
+
// Handle arrays
|
|
563
|
+
if (Array.isArray(baseObj) && Array.isArray(diff)) {
|
|
564
|
+
const result = shouldKeepRef ? baseObj : [...baseObj];
|
|
565
|
+
// Only adjust length if diff is shorter (deletions)
|
|
566
|
+
if (diff.length < result.length) {
|
|
567
|
+
result.length = diff.length;
|
|
568
|
+
}
|
|
569
|
+
for (let i = 0; i < diff.length; i++) {
|
|
570
|
+
const diffValue = diff[i];
|
|
571
|
+
// undefined means "no change" - keep original value
|
|
572
|
+
if (diffValue === undefined) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (isObject(result[i]) && isObject(diffValue)) {
|
|
576
|
+
result[i] = applyDiff(result[i], diffValue, shouldKeepRef);
|
|
577
|
+
}
|
|
578
|
+
else if (Array.isArray(result[i]) && Array.isArray(diffValue)) {
|
|
579
|
+
result[i] = applyDiff(result[i], diffValue, shouldKeepRef);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
result[i] = diffValue;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
// Handle objects
|
|
588
|
+
if (!isObject(baseObj))
|
|
589
|
+
return diff;
|
|
590
|
+
if (!isObject(diff))
|
|
591
|
+
return baseObj;
|
|
592
|
+
const result = shouldKeepRef ? baseObj : { ...baseObj };
|
|
593
|
+
for (const key in diff) {
|
|
594
|
+
const diffValue = diff[key];
|
|
595
|
+
if (diffValue === undefined) {
|
|
596
|
+
delete result[key];
|
|
597
|
+
}
|
|
598
|
+
else if (Array.isArray(result[key]) && Array.isArray(diffValue)) {
|
|
599
|
+
result[key] = applyDiff(result[key], diffValue, shouldKeepRef);
|
|
600
|
+
}
|
|
601
|
+
else if (isObject(result[key]) && isObject(diffValue)) {
|
|
602
|
+
result[key] = applyDiff(result[key], diffValue, shouldKeepRef);
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
result[key] = diffValue;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
// ApplyDiff mode - respect added/deleted/modified arrays
|
|
611
|
+
const { diff, added = [], deleted = [], modified = [], changes, keepRef: keepRefOption, } = option;
|
|
612
|
+
const shouldKeepRef = keepRefParam !== undefined ? keepRefParam : keepRefOption;
|
|
613
|
+
const diffToApply = changes || diff;
|
|
614
|
+
if (!diffToApply)
|
|
615
|
+
return baseObj;
|
|
616
|
+
// Create sets for quick lookup
|
|
617
|
+
const addedSet = new Set(added);
|
|
618
|
+
const deletedSet = new Set(deleted);
|
|
619
|
+
const modifiedSet = new Set(modified);
|
|
620
|
+
// Helper to check if a path should be applied
|
|
621
|
+
const shouldApplyPath = (path) => {
|
|
622
|
+
// If no specific paths provided, apply all
|
|
623
|
+
if (added.length === 0 && deleted.length === 0 && modified.length === 0) {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
// Check if path or any parent path is in the sets
|
|
627
|
+
const pathParts = path.split(/[.\[\]]+/).filter(Boolean);
|
|
628
|
+
for (let i = 0; i <= pathParts.length; i++) {
|
|
629
|
+
const partialPath = pathParts.slice(0, i).join('.');
|
|
630
|
+
const bracketPath = pathParts
|
|
631
|
+
.slice(0, i)
|
|
632
|
+
.map((p, idx) => (/^\d+$/.test(p) ? `[${p}]` : idx > 0 ? `.${p}` : p))
|
|
633
|
+
.join('');
|
|
634
|
+
if (addedSet.has(partialPath) ||
|
|
635
|
+
addedSet.has(bracketPath) ||
|
|
636
|
+
deletedSet.has(partialPath) ||
|
|
637
|
+
deletedSet.has(bracketPath) ||
|
|
638
|
+
modifiedSet.has(partialPath) ||
|
|
639
|
+
modifiedSet.has(bracketPath)) {
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return false;
|
|
644
|
+
};
|
|
645
|
+
// Handle arrays at the top level
|
|
646
|
+
if (Array.isArray(baseObj) && Array.isArray(diffToApply)) {
|
|
647
|
+
const result = shouldKeepRef ? baseObj : [...baseObj];
|
|
648
|
+
// Set the array to the correct length based on diffToApply
|
|
649
|
+
// Since diffObjectDetailed now excludes deleted elements from changes array,
|
|
650
|
+
// the changes array length matches the newObj length
|
|
651
|
+
result.length = diffToApply.length;
|
|
652
|
+
// Apply changes
|
|
653
|
+
for (let i = 0; i < diffToApply.length; i++) {
|
|
654
|
+
const indexPath = `[${i}]`;
|
|
655
|
+
const diffValue = diffToApply[i];
|
|
656
|
+
// undefined in the diff means "no change" - keep the original value
|
|
657
|
+
if (diffValue === undefined) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
// Check if this index should be applied
|
|
661
|
+
if (!shouldApplyPath(indexPath)) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (isObject(result[i]) && isObject(diffValue)) {
|
|
665
|
+
result[i] = applyDiff(result[i], diffValue, shouldKeepRef);
|
|
666
|
+
}
|
|
667
|
+
else if (Array.isArray(result[i]) && Array.isArray(diffValue)) {
|
|
668
|
+
result[i] = applyDiff(result[i], diffValue, shouldKeepRef);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
result[i] = diffValue;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return result;
|
|
675
|
+
}
|
|
676
|
+
// Handle objects
|
|
677
|
+
if (!isObject(baseObj))
|
|
678
|
+
return diffToApply;
|
|
679
|
+
if (!isObject(diffToApply))
|
|
680
|
+
return baseObj;
|
|
681
|
+
const result = shouldKeepRef ? baseObj : { ...baseObj };
|
|
682
|
+
// Apply deletions
|
|
683
|
+
for (const path of deleted) {
|
|
684
|
+
if (path.includes('.') || path.includes('['))
|
|
685
|
+
continue; // Only handle direct keys
|
|
686
|
+
if (shouldApplyPath(path)) {
|
|
687
|
+
delete result[path];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Apply additions and modifications
|
|
691
|
+
for (const key in diffToApply) {
|
|
692
|
+
const diffValue = diffToApply[key];
|
|
693
|
+
// Check if this key should be applied
|
|
694
|
+
if (!shouldApplyPath(key)) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
if (diffValue === undefined) {
|
|
698
|
+
delete result[key];
|
|
699
|
+
}
|
|
700
|
+
else if (Array.isArray(result[key]) && Array.isArray(diffValue)) {
|
|
701
|
+
result[key] = applyDiff(result[key], diffValue, shouldKeepRef);
|
|
702
|
+
}
|
|
703
|
+
else if (isObject(result[key]) && isObject(diffValue)) {
|
|
704
|
+
result[key] = applyDiff(result[key], diffValue, shouldKeepRef);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
result[key] = diffValue;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
};
|
|
712
|
+
/**
|
|
713
|
+
* Gets paths of all changed values with array index notation support
|
|
714
|
+
*
|
|
715
|
+
* @param oldObj - The original object
|
|
716
|
+
* @param newObj - The new object to compare against
|
|
717
|
+
* @param options - Configuration options for diff behavior
|
|
718
|
+
* @returns Array of paths to changed values (e.g., ['a.b[0].name', 'c[1].value'])
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```ts
|
|
722
|
+
* const old = { a: 1, b: { c: 2, d: 3 } };
|
|
723
|
+
* const new = { a: 1, b: { c: 4, d: 3 }, e: 5 };
|
|
724
|
+
* const paths = getChangedPaths(old, new);
|
|
725
|
+
* // Returns: ['b.c', 'e']
|
|
726
|
+
* ```
|
|
727
|
+
*
|
|
728
|
+
* @example
|
|
729
|
+
* ```ts
|
|
730
|
+
* const old = { items: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }] };
|
|
731
|
+
* const new = { items: [{ id: 1, name: 'x' }, { id: 2, name: 'b' }] };
|
|
732
|
+
* const paths = getChangedPaths(old, new);
|
|
733
|
+
* // Returns: ['items[0].name']
|
|
734
|
+
* ```
|
|
735
|
+
*/
|
|
736
|
+
export const getChangedPaths = (oldObj, newObj, options = {}) => {
|
|
737
|
+
const paths = [];
|
|
738
|
+
const collectPaths = (old, newVal, currentPath = '') => {
|
|
739
|
+
if (old === newVal)
|
|
740
|
+
return;
|
|
741
|
+
// Handle arrays with index notation
|
|
742
|
+
if (Array.isArray(old) && Array.isArray(newVal)) {
|
|
743
|
+
const maxLength = Math.max(old.length, newVal.length);
|
|
744
|
+
for (let i = 0; i < maxLength; i++) {
|
|
745
|
+
const indexPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`;
|
|
746
|
+
if (i >= old.length) {
|
|
747
|
+
// Element was added
|
|
748
|
+
paths.push(indexPath);
|
|
749
|
+
}
|
|
750
|
+
else if (i >= newVal.length) {
|
|
751
|
+
// Element was deleted
|
|
752
|
+
paths.push(indexPath);
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
const oldElement = old[i];
|
|
756
|
+
const newElement = newVal[i];
|
|
757
|
+
if (isObject(oldElement) && isObject(newElement)) {
|
|
758
|
+
// Recurse into object elements
|
|
759
|
+
collectPaths(oldElement, newElement, indexPath);
|
|
760
|
+
}
|
|
761
|
+
else if (Array.isArray(oldElement) && Array.isArray(newElement)) {
|
|
762
|
+
// Recurse into nested arrays
|
|
763
|
+
collectPaths(oldElement, newElement, indexPath);
|
|
764
|
+
}
|
|
765
|
+
else if (oldElement !== newElement) {
|
|
766
|
+
// Primitive value changed
|
|
767
|
+
paths.push(indexPath);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
// Handle non-object/non-array primitives
|
|
774
|
+
if (!isObject(old) || !isObject(newVal)) {
|
|
775
|
+
if (currentPath)
|
|
776
|
+
paths.push(currentPath);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
// Handle objects
|
|
780
|
+
const allKeys = new Set([
|
|
781
|
+
...Object.keys(old || {}),
|
|
782
|
+
...Object.keys(newVal || {}),
|
|
783
|
+
]);
|
|
784
|
+
for (const key of allKeys) {
|
|
785
|
+
const oldValue = old?.[key];
|
|
786
|
+
const newValue = newVal?.[key];
|
|
787
|
+
const path = currentPath ? `${currentPath}.${key}` : key;
|
|
788
|
+
if (!(key in (old || {})) || !(key in (newVal || {}))) {
|
|
789
|
+
paths.push(path);
|
|
790
|
+
}
|
|
791
|
+
else if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
|
792
|
+
// Compare arrays with index notation
|
|
793
|
+
collectPaths(oldValue, newValue, path);
|
|
794
|
+
}
|
|
795
|
+
else if (isObject(oldValue) && isObject(newValue)) {
|
|
796
|
+
collectPaths(oldValue, newValue, path);
|
|
797
|
+
}
|
|
798
|
+
else if (oldValue !== newValue) {
|
|
799
|
+
paths.push(path);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
collectPaths(oldObj, newObj);
|
|
804
|
+
return paths;
|
|
805
|
+
};
|