@naturalcycles/js-lib 14.213.0 → 14.215.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/array/array.util.d.ts +3 -0
- package/dist/array/array.util.js +3 -0
- package/dist/decorators/asyncMemo.decorator.d.ts +4 -14
- package/dist/decorators/asyncMemo.decorator.js +5 -11
- package/dist/decorators/memo.decorator.d.ts +0 -13
- package/dist/decorators/memo.decorator.js +1 -11
- package/dist/decorators/memoFn.js +1 -13
- package/dist/decorators/memoFnAsync.js +1 -13
- package/dist/decorators/memoSimple.decorator.d.ts +0 -3
- package/dist/decorators/memoSimple.decorator.js +1 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/object/deepEquals.d.ts +58 -6
- package/dist/object/deepEquals.js +146 -52
- package/dist/object/map2.d.ts +15 -0
- package/dist/object/map2.js +25 -0
- package/dist/object/object.util.d.ts +3 -1
- package/dist/object/set2.d.ts +11 -0
- package/dist/object/set2.js +19 -0
- package/dist/string/stringify.js +6 -0
- package/dist-esm/array/array.util.js +3 -0
- package/dist-esm/decorators/asyncMemo.decorator.js +6 -12
- package/dist-esm/decorators/memo.decorator.js +2 -12
- package/dist-esm/decorators/memoFn.js +1 -13
- package/dist-esm/decorators/memoFnAsync.js +1 -13
- package/dist-esm/decorators/memoSimple.decorator.js +2 -9
- package/dist-esm/index.js +2 -0
- package/dist-esm/object/deepEquals.js +142 -49
- package/dist-esm/object/map2.js +21 -0
- package/dist-esm/object/set2.js +15 -0
- package/dist-esm/string/stringify.js +6 -0
- package/package.json +1 -4
- package/src/array/array.util.ts +3 -0
- package/src/decorators/asyncMemo.decorator.ts +5 -42
- package/src/decorators/memo.decorator.ts +1 -37
- package/src/decorators/memoFn.ts +0 -18
- package/src/decorators/memoFnAsync.ts +0 -18
- package/src/decorators/memoSimple.decorator.ts +2 -20
- package/src/index.ts +2 -0
- package/src/object/deepEquals.ts +136 -45
- package/src/object/map2.ts +25 -0
- package/src/object/object.util.ts +3 -1
- package/src/object/set2.ts +18 -0
- package/src/string/stringify.ts +6 -0
- package/dist/lodash.types.d.ts +0 -4
- package/dist/lodash.types.js +0 -2
- package/dist-esm/lodash.types.js +0 -1
- package/src/lodash.types.ts +0 -6
|
@@ -104,6 +104,9 @@ export declare function _sortBy<T>(items: T[], mapper: Mapper<T, any>, mutate?:
|
|
|
104
104
|
export declare function _sortDescBy<T>(items: T[], mapper: Mapper<T, any>, mutate?: boolean): T[];
|
|
105
105
|
/**
|
|
106
106
|
* Like items.find(), but it tries to find from the END of the array.
|
|
107
|
+
*
|
|
108
|
+
* Node 18+ supports native array.findLast() - use that.
|
|
109
|
+
* iOS Safari only has it since 15.4
|
|
107
110
|
*/
|
|
108
111
|
export declare function _findLast<T>(items: T[], predicate: Predicate<T>): T | undefined;
|
|
109
112
|
export declare function _takeWhile<T>(items: T[], predicate: Predicate<T>): T[];
|
package/dist/array/array.util.js
CHANGED
|
@@ -173,6 +173,9 @@ function _sortDescBy(items, mapper, mutate = false) {
|
|
|
173
173
|
exports._sortDescBy = _sortDescBy;
|
|
174
174
|
/**
|
|
175
175
|
* Like items.find(), but it tries to find from the END of the array.
|
|
176
|
+
*
|
|
177
|
+
* Node 18+ supports native array.findLast() - use that.
|
|
178
|
+
* iOS Safari only has it since 15.4
|
|
176
179
|
*/
|
|
177
180
|
function _findLast(items, predicate) {
|
|
178
181
|
return [...items].reverse().find(predicate);
|
|
@@ -20,20 +20,6 @@ export interface AsyncMemoOptions {
|
|
|
20
20
|
* False will allow >1 execution in case of errors.
|
|
21
21
|
*/
|
|
22
22
|
cacheRejections?: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Default to false
|
|
25
|
-
*/
|
|
26
|
-
logHit?: boolean;
|
|
27
|
-
/**
|
|
28
|
-
* Default to false
|
|
29
|
-
*/
|
|
30
|
-
logMiss?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Set to `false` to skip logging method arguments.
|
|
33
|
-
*
|
|
34
|
-
* Defaults to true.
|
|
35
|
-
*/
|
|
36
|
-
logArgs?: boolean;
|
|
37
23
|
/**
|
|
38
24
|
* Default to `console`
|
|
39
25
|
*/
|
|
@@ -42,6 +28,10 @@ export interface AsyncMemoOptions {
|
|
|
42
28
|
/**
|
|
43
29
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
44
30
|
*
|
|
31
|
+
* Important: it awaits the method to return the result before caching it.
|
|
32
|
+
*
|
|
33
|
+
* todo: test for "swarm requests", it should return "the same promise" and not cause a swarm origin hit
|
|
34
|
+
*
|
|
45
35
|
* Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
|
|
46
36
|
* Return `null` instead (it'll be cached).
|
|
47
37
|
*/
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports._AsyncMemo = void 0;
|
|
4
|
-
const time_util_1 = require("../time/time.util");
|
|
5
4
|
const decorator_util_1 = require("./decorator.util");
|
|
6
5
|
const memo_util_1 = require("./memo.util");
|
|
7
6
|
/**
|
|
8
7
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
9
8
|
*
|
|
9
|
+
* Important: it awaits the method to return the result before caching it.
|
|
10
|
+
*
|
|
11
|
+
* todo: test for "swarm requests", it should return "the same promise" and not cause a swarm origin hit
|
|
12
|
+
*
|
|
10
13
|
* Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
|
|
11
14
|
* Return `null` instead (it'll be cached).
|
|
12
15
|
*/
|
|
@@ -18,7 +21,7 @@ const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
|
18
21
|
const originalFn = descriptor.value;
|
|
19
22
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
20
23
|
const cache = new Map();
|
|
21
|
-
const {
|
|
24
|
+
const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheRejections = true, } = opt;
|
|
22
25
|
const keyStr = String(key);
|
|
23
26
|
const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
|
|
24
27
|
descriptor.value = async function (...args) {
|
|
@@ -39,16 +42,12 @@ const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
|
39
42
|
}
|
|
40
43
|
if (value !== undefined) {
|
|
41
44
|
// hit!
|
|
42
|
-
if (logHit) {
|
|
43
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_AsyncMemo hit`);
|
|
44
|
-
}
|
|
45
45
|
if (value instanceof Error) {
|
|
46
46
|
throw value;
|
|
47
47
|
}
|
|
48
48
|
return value;
|
|
49
49
|
}
|
|
50
50
|
// Here we know it's a MISS, let's execute the real method
|
|
51
|
-
const started = Date.now();
|
|
52
51
|
try {
|
|
53
52
|
value = await originalFn.apply(ctx, args);
|
|
54
53
|
// Save the value in the Cache, without awaiting it
|
|
@@ -80,11 +79,6 @@ const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
|
80
79
|
}
|
|
81
80
|
throw err;
|
|
82
81
|
}
|
|
83
|
-
finally {
|
|
84
|
-
if (logMiss) {
|
|
85
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_AsyncMemo miss (${(0, time_util_1._since)(started)})`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
82
|
};
|
|
89
83
|
descriptor.value.dropCache = async () => {
|
|
90
84
|
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
|
|
@@ -19,19 +19,6 @@ export interface MemoOptions {
|
|
|
19
19
|
* False will allow >1 execution in case of errors.
|
|
20
20
|
*/
|
|
21
21
|
cacheErrors?: boolean;
|
|
22
|
-
/**
|
|
23
|
-
* Default to false
|
|
24
|
-
*/
|
|
25
|
-
logHit?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Default to false
|
|
28
|
-
*/
|
|
29
|
-
logMiss?: boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Defaults to true.
|
|
32
|
-
* Set to false to skip logging method arguments.
|
|
33
|
-
*/
|
|
34
|
-
logArgs?: boolean;
|
|
35
22
|
/**
|
|
36
23
|
* Default to `console`
|
|
37
24
|
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports._Memo = void 0;
|
|
4
|
-
const time_util_1 = require("../time/time.util");
|
|
5
4
|
const decorator_util_1 = require("./decorator.util");
|
|
6
5
|
const memo_util_1 = require("./memo.util");
|
|
7
6
|
/**
|
|
@@ -37,7 +36,7 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
37
36
|
// UPD: tests show that normal Map also doesn't leak (to be tested further)
|
|
38
37
|
// Normal Map is needed to allow .dropCache()
|
|
39
38
|
const cache = new Map();
|
|
40
|
-
const {
|
|
39
|
+
const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheErrors = true, } = opt;
|
|
41
40
|
const keyStr = String(key);
|
|
42
41
|
const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
|
|
43
42
|
descriptor.value = function (...args) {
|
|
@@ -48,16 +47,12 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
48
47
|
cache.set(ctx, cacheFactory());
|
|
49
48
|
}
|
|
50
49
|
else if (cache.get(ctx).has(cacheKey)) {
|
|
51
|
-
if (logHit) {
|
|
52
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_Memo hit`);
|
|
53
|
-
}
|
|
54
50
|
value = cache.get(ctx).get(cacheKey);
|
|
55
51
|
if (value instanceof Error) {
|
|
56
52
|
throw value;
|
|
57
53
|
}
|
|
58
54
|
return value;
|
|
59
55
|
}
|
|
60
|
-
const started = Date.now();
|
|
61
56
|
try {
|
|
62
57
|
value = originalFn.apply(ctx, args);
|
|
63
58
|
try {
|
|
@@ -79,11 +74,6 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
79
74
|
}
|
|
80
75
|
throw err;
|
|
81
76
|
}
|
|
82
|
-
finally {
|
|
83
|
-
if (logMiss) {
|
|
84
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_Memo miss (${(0, time_util_1._since)(started)})`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
77
|
};
|
|
88
78
|
descriptor.value.dropCache = () => {
|
|
89
79
|
logger.log(`${methodSignature} @_Memo.dropCache()`);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports._memoFn = void 0;
|
|
4
|
-
const time_util_1 = require("../time/time.util");
|
|
5
|
-
const decorator_util_1 = require("./decorator.util");
|
|
6
4
|
const memo_util_1 = require("./memo.util");
|
|
7
5
|
/**
|
|
8
6
|
* Only supports Sync functions.
|
|
@@ -10,24 +8,19 @@ const memo_util_1 = require("./memo.util");
|
|
|
10
8
|
* Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
|
|
11
9
|
*/
|
|
12
10
|
function _memoFn(fn, opt = {}) {
|
|
13
|
-
const {
|
|
11
|
+
const { logger = console, cacheErrors = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
|
|
14
12
|
const cache = cacheFactory();
|
|
15
|
-
const fnName = fn.name;
|
|
16
13
|
const memoizedFn = function (...args) {
|
|
17
14
|
const ctx = this;
|
|
18
15
|
const cacheKey = cacheKeyFn(args);
|
|
19
16
|
let value;
|
|
20
17
|
if (cache.has(cacheKey)) {
|
|
21
|
-
if (logHit) {
|
|
22
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFn hit`);
|
|
23
|
-
}
|
|
24
18
|
value = cache.get(cacheKey);
|
|
25
19
|
if (value instanceof Error) {
|
|
26
20
|
throw value;
|
|
27
21
|
}
|
|
28
22
|
return value;
|
|
29
23
|
}
|
|
30
|
-
const started = Date.now();
|
|
31
24
|
try {
|
|
32
25
|
value = fn.apply(ctx, args);
|
|
33
26
|
try {
|
|
@@ -49,11 +42,6 @@ function _memoFn(fn, opt = {}) {
|
|
|
49
42
|
}
|
|
50
43
|
throw err;
|
|
51
44
|
}
|
|
52
|
-
finally {
|
|
53
|
-
if (logMiss) {
|
|
54
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFn miss (${(0, time_util_1._since)(started)})`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
45
|
};
|
|
58
46
|
Object.assign(memoizedFn, { cache });
|
|
59
47
|
return memoizedFn;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports._memoFnAsync = void 0;
|
|
4
|
-
const time_util_1 = require("../time/time.util");
|
|
5
|
-
const decorator_util_1 = require("./decorator.util");
|
|
6
4
|
const memo_util_1 = require("./memo.util");
|
|
7
5
|
/**
|
|
8
6
|
* Only supports Sync functions.
|
|
9
7
|
* To support Async functions - use _memoFnAsync
|
|
10
8
|
*/
|
|
11
9
|
function _memoFnAsync(fn, opt = {}) {
|
|
12
|
-
const {
|
|
10
|
+
const { logger = console, cacheRejections = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
|
|
13
11
|
const cache = cacheFactory();
|
|
14
|
-
const fnName = fn.name;
|
|
15
12
|
const memoizedFn = async function (...args) {
|
|
16
13
|
const ctx = this;
|
|
17
14
|
const cacheKey = cacheKeyFn(args);
|
|
@@ -23,15 +20,11 @@ function _memoFnAsync(fn, opt = {}) {
|
|
|
23
20
|
logger.error(err);
|
|
24
21
|
}
|
|
25
22
|
if (value !== undefined) {
|
|
26
|
-
if (logHit) {
|
|
27
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFnAsync hit`);
|
|
28
|
-
}
|
|
29
23
|
if (value instanceof Error) {
|
|
30
24
|
throw value;
|
|
31
25
|
}
|
|
32
26
|
return value;
|
|
33
27
|
}
|
|
34
|
-
const started = Date.now();
|
|
35
28
|
try {
|
|
36
29
|
value = await fn.apply(ctx, args);
|
|
37
30
|
void (async () => {
|
|
@@ -57,11 +50,6 @@ function _memoFnAsync(fn, opt = {}) {
|
|
|
57
50
|
}
|
|
58
51
|
throw err;
|
|
59
52
|
}
|
|
60
|
-
finally {
|
|
61
|
-
if (logMiss) {
|
|
62
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFnAsync miss (${(0, time_util_1._since)(started)})`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
53
|
};
|
|
66
54
|
Object.assign(memoizedFn, { cache });
|
|
67
55
|
return memoizedFn;
|
|
@@ -37,23 +37,16 @@ const memoSimple = (opt = {}) => (target, key, descriptor) => {
|
|
|
37
37
|
}
|
|
38
38
|
*/
|
|
39
39
|
const cache = new memo_util_1.MapMemoCache();
|
|
40
|
-
const {
|
|
40
|
+
const { logger = console } = opt;
|
|
41
41
|
const keyStr = String(key);
|
|
42
42
|
const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
|
|
43
43
|
descriptor.value = function (...args) {
|
|
44
44
|
const ctx = this;
|
|
45
45
|
const cacheKey = (0, memo_util_1.jsonMemoSerializer)(args);
|
|
46
46
|
if (cache.has(cacheKey)) {
|
|
47
|
-
if (logHit) {
|
|
48
|
-
logger.log(`${methodSignature}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @memo hit`);
|
|
49
|
-
}
|
|
50
47
|
return cache.get(cacheKey);
|
|
51
48
|
}
|
|
52
|
-
const d = Date.now();
|
|
53
49
|
const res = originalFn.apply(ctx, args);
|
|
54
|
-
if (logMiss) {
|
|
55
|
-
logger.log(`${methodSignature}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @memo miss (${Date.now() - d} ms)`);
|
|
56
|
-
}
|
|
57
50
|
cache.set(cacheKey, res);
|
|
58
51
|
return res;
|
|
59
52
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export * from './object/deepEquals';
|
|
|
35
35
|
export * from './object/object.util';
|
|
36
36
|
export * from './object/sortObject';
|
|
37
37
|
export * from './object/sortObjectDeep';
|
|
38
|
+
export * from './object/map2';
|
|
39
|
+
export * from './object/set2';
|
|
38
40
|
export * from './promise/pDefer';
|
|
39
41
|
export * from './promise/pDelay';
|
|
40
42
|
export * from './promise/pFilter';
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,8 @@ tslib_1.__exportStar(require("./object/deepEquals"), exports);
|
|
|
39
39
|
tslib_1.__exportStar(require("./object/object.util"), exports);
|
|
40
40
|
tslib_1.__exportStar(require("./object/sortObject"), exports);
|
|
41
41
|
tslib_1.__exportStar(require("./object/sortObjectDeep"), exports);
|
|
42
|
+
tslib_1.__exportStar(require("./object/map2"), exports);
|
|
43
|
+
tslib_1.__exportStar(require("./object/set2"), exports);
|
|
42
44
|
tslib_1.__exportStar(require("./promise/pDefer"), exports);
|
|
43
45
|
tslib_1.__exportStar(require("./promise/pDelay"), exports);
|
|
44
46
|
tslib_1.__exportStar(require("./promise/pFilter"), exports);
|
|
@@ -1,11 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
Returns true if a and b are deeply equal.
|
|
3
|
+
|
|
4
|
+
Equality is checked recursively, with the following rules/caveats:
|
|
5
|
+
- Primitive values are checked with ===
|
|
6
|
+
- NaN === NaN
|
|
7
|
+
- Array length should be the same, and every value should be equal
|
|
8
|
+
- Sets are checked similarly to arrays (but order doesn't matter in Sets)
|
|
9
|
+
- Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
|
|
10
|
+
- Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
|
|
11
|
+
- Regex are compared by their source and flags
|
|
12
|
+
- Functions are compared by their `.toString`
|
|
13
|
+
- Any object that overrides `.toString()` is compared by that (e.g Function)
|
|
14
|
+
- Any object that overrides `.valueOf()` is compared by that (e.g Date)
|
|
15
|
+
|
|
16
|
+
What are the differences between various deep-equality functions?
|
|
17
|
+
There are:
|
|
18
|
+
- _deepEquals
|
|
19
|
+
- _deepJsonEquals
|
|
20
|
+
- _jsonEquals
|
|
21
|
+
|
|
22
|
+
_deepEquals uses "common sense" equality.
|
|
23
|
+
It tries to work "as you would expect it to".
|
|
24
|
+
With the important caveat that undefined values are treated the same as absent key.
|
|
25
|
+
So, _deepEquals should be the first choice.
|
|
26
|
+
It's also the most performant of 3.
|
|
27
|
+
|
|
28
|
+
_deepJsonEquals uses different logic, that's often not what you expect.
|
|
29
|
+
It should be used to compare objects of how they would look after "passing via JSON.stringify",
|
|
30
|
+
for example when you return it over the API to the Frontend,
|
|
31
|
+
or when you pass it to be saved to the Database.
|
|
32
|
+
If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
|
|
33
|
+
For these cases - it can be better than _deepEquals.
|
|
34
|
+
And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
|
|
35
|
+
|
|
36
|
+
_jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
|
|
37
|
+
It's the simplest implementation, but also the slowest of 3.
|
|
38
|
+
|
|
39
|
+
TLDR: _deepEquals should be useful in most of the cases, start there.
|
|
40
|
+
*/
|
|
41
|
+
export declare function _deepEquals(a: any, b: any): boolean;
|
|
42
|
+
/**
|
|
43
|
+
Returns true if a and b are deeply equal.
|
|
44
|
+
|
|
45
|
+
Equality is checked in the same way as if both arguments are processed via
|
|
46
|
+
JSON.stringify and JSON.parse:
|
|
47
|
+
- undefined values are removed, undefined values in an array are turned into `null`, etc.
|
|
48
|
+
- Any Regex, Map, Set, Function stringifies to {}.
|
|
49
|
+
- Date stringifies to its IsoDateTimeString representation.
|
|
50
|
+
- Any object that implements toJSON is compared by the output of its toJSON().
|
|
51
|
+
- NaN stringifies to null
|
|
52
|
+
- Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
|
|
53
|
+
|
|
54
|
+
See _deepEquals docs for more details and comparison.
|
|
6
55
|
*/
|
|
7
56
|
export declare function _deepJsonEquals(a: any, b: any): boolean;
|
|
8
57
|
/**
|
|
9
|
-
*
|
|
58
|
+
* Shortcut for JSON.stringify(a) === JSON.stringify(b)
|
|
59
|
+
*
|
|
60
|
+
* Simplest "deep equals" implementation, but also the slowest,
|
|
61
|
+
* and not robust, in the sense that it depends on the order of object keys.
|
|
10
62
|
*/
|
|
11
|
-
export declare function
|
|
63
|
+
export declare function _jsonEquals(a: any, b: any): boolean;
|
|
@@ -1,75 +1,169 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// Heavily inspired by https://github.com/epoberezkin/fast-deep-equal
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const isArray = Array.isArray;
|
|
5
|
-
const keyList = Object.keys;
|
|
6
|
-
const hasProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
exports._jsonEquals = exports._deepJsonEquals = exports._deepEquals = void 0;
|
|
7
5
|
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
Returns true if a and b are deeply equal.
|
|
7
|
+
|
|
8
|
+
Equality is checked recursively, with the following rules/caveats:
|
|
9
|
+
- Primitive values are checked with ===
|
|
10
|
+
- NaN === NaN
|
|
11
|
+
- Array length should be the same, and every value should be equal
|
|
12
|
+
- Sets are checked similarly to arrays (but order doesn't matter in Sets)
|
|
13
|
+
- Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
|
|
14
|
+
- Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
|
|
15
|
+
- Regex are compared by their source and flags
|
|
16
|
+
- Functions are compared by their `.toString`
|
|
17
|
+
- Any object that overrides `.toString()` is compared by that (e.g Function)
|
|
18
|
+
- Any object that overrides `.valueOf()` is compared by that (e.g Date)
|
|
19
|
+
|
|
20
|
+
What are the differences between various deep-equality functions?
|
|
21
|
+
There are:
|
|
22
|
+
- _deepEquals
|
|
23
|
+
- _deepJsonEquals
|
|
24
|
+
- _jsonEquals
|
|
25
|
+
|
|
26
|
+
_deepEquals uses "common sense" equality.
|
|
27
|
+
It tries to work "as you would expect it to".
|
|
28
|
+
With the important caveat that undefined values are treated the same as absent key.
|
|
29
|
+
So, _deepEquals should be the first choice.
|
|
30
|
+
It's also the most performant of 3.
|
|
31
|
+
|
|
32
|
+
_deepJsonEquals uses different logic, that's often not what you expect.
|
|
33
|
+
It should be used to compare objects of how they would look after "passing via JSON.stringify",
|
|
34
|
+
for example when you return it over the API to the Frontend,
|
|
35
|
+
or when you pass it to be saved to the Database.
|
|
36
|
+
If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
|
|
37
|
+
For these cases - it can be better than _deepEquals.
|
|
38
|
+
And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
|
|
39
|
+
|
|
40
|
+
_jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
|
|
41
|
+
It's the simplest implementation, but also the slowest of 3.
|
|
42
|
+
|
|
43
|
+
TLDR: _deepEquals should be useful in most of the cases, start there.
|
|
25
44
|
*/
|
|
26
45
|
function _deepEquals(a, b) {
|
|
27
46
|
if (a === b)
|
|
28
47
|
return true;
|
|
48
|
+
if (Number.isNaN(a)) {
|
|
49
|
+
return Number.isNaN(b);
|
|
50
|
+
}
|
|
29
51
|
if (a && b && typeof a === 'object' && typeof b === 'object') {
|
|
30
|
-
|
|
31
|
-
const arrB = isArray(b);
|
|
32
|
-
let i;
|
|
33
|
-
let length;
|
|
34
|
-
let key;
|
|
35
|
-
if (arrA !== arrB)
|
|
52
|
+
if (a.constructor !== b.constructor)
|
|
36
53
|
return false;
|
|
37
|
-
if (
|
|
38
|
-
length = a.length;
|
|
54
|
+
if (Array.isArray(a)) {
|
|
55
|
+
const length = a.length;
|
|
39
56
|
if (length !== b.length)
|
|
40
57
|
return false;
|
|
41
|
-
for (i = length; i-- !== 0;)
|
|
58
|
+
for (let i = length; i-- !== 0;) {
|
|
42
59
|
if (!_deepEquals(a[i], b[i]))
|
|
43
60
|
return false;
|
|
61
|
+
}
|
|
44
62
|
return true;
|
|
45
63
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
if (regexpA && regexpB)
|
|
57
|
-
return a.toString() === b.toString();
|
|
58
|
-
const keys = keyList(a);
|
|
59
|
-
length = keys.length;
|
|
60
|
-
if (length !== keyList(b).length)
|
|
61
|
-
return false;
|
|
62
|
-
for (i = length; i-- !== 0;)
|
|
63
|
-
if (!hasProp.call(b, keys[i]))
|
|
64
|
+
if (a instanceof Map && b instanceof Map) {
|
|
65
|
+
for (const key of new Set([...a.keys(), ...b.keys()])) {
|
|
66
|
+
if (!_deepEquals(a.get(key), b.get(key)))
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (a instanceof Set && b instanceof Set) {
|
|
72
|
+
if (a.size !== b.size)
|
|
64
73
|
return false;
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
for (const key of a) {
|
|
75
|
+
if (!b.has(key))
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (a.constructor === RegExp)
|
|
81
|
+
return a.source === b.source && a.flags === b.flags;
|
|
82
|
+
if (a.valueOf !== Object.prototype.valueOf)
|
|
83
|
+
return a.valueOf() === b.valueOf();
|
|
84
|
+
if (a.toString !== Object.prototype.toString)
|
|
85
|
+
return a.toString() === b.toString();
|
|
86
|
+
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
67
87
|
if (!_deepEquals(a[key], b[key]))
|
|
68
88
|
return false;
|
|
69
89
|
}
|
|
70
90
|
return true;
|
|
71
91
|
}
|
|
72
|
-
|
|
73
|
-
return a !== a && b !== b;
|
|
92
|
+
return a === b;
|
|
74
93
|
}
|
|
75
94
|
exports._deepEquals = _deepEquals;
|
|
95
|
+
/**
|
|
96
|
+
Returns true if a and b are deeply equal.
|
|
97
|
+
|
|
98
|
+
Equality is checked in the same way as if both arguments are processed via
|
|
99
|
+
JSON.stringify and JSON.parse:
|
|
100
|
+
- undefined values are removed, undefined values in an array are turned into `null`, etc.
|
|
101
|
+
- Any Regex, Map, Set, Function stringifies to {}.
|
|
102
|
+
- Date stringifies to its IsoDateTimeString representation.
|
|
103
|
+
- Any object that implements toJSON is compared by the output of its toJSON().
|
|
104
|
+
- NaN stringifies to null
|
|
105
|
+
- Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
|
|
106
|
+
|
|
107
|
+
See _deepEquals docs for more details and comparison.
|
|
108
|
+
*/
|
|
109
|
+
function _deepJsonEquals(a, b) {
|
|
110
|
+
if (a === b)
|
|
111
|
+
return true;
|
|
112
|
+
if (Number.isNaN(a)) {
|
|
113
|
+
a = null;
|
|
114
|
+
}
|
|
115
|
+
else if (typeof a === 'function') {
|
|
116
|
+
a = undefined;
|
|
117
|
+
}
|
|
118
|
+
else if (a && typeof a === 'object') {
|
|
119
|
+
if (a instanceof Date) {
|
|
120
|
+
a = a.valueOf();
|
|
121
|
+
}
|
|
122
|
+
else if ('toJSON' in a) {
|
|
123
|
+
a = a.toJSON();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (Number.isNaN(b)) {
|
|
127
|
+
b = null;
|
|
128
|
+
}
|
|
129
|
+
else if (typeof b === 'function') {
|
|
130
|
+
b = undefined;
|
|
131
|
+
}
|
|
132
|
+
else if (b && typeof b === 'object') {
|
|
133
|
+
if (b instanceof Date) {
|
|
134
|
+
b = b.valueOf();
|
|
135
|
+
}
|
|
136
|
+
else if ('toJSON' in b) {
|
|
137
|
+
b = b.toJSON();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (a && b && typeof a === 'object' && typeof b === 'object') {
|
|
141
|
+
if (Array.isArray(a)) {
|
|
142
|
+
const length = a.length;
|
|
143
|
+
if (length !== b.length)
|
|
144
|
+
return false;
|
|
145
|
+
for (let i = length; i-- !== 0;) {
|
|
146
|
+
if (!_deepJsonEquals(a[i], b[i]))
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
152
|
+
if (!_deepJsonEquals(a[key], b[key]))
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return a === b;
|
|
158
|
+
}
|
|
159
|
+
exports._deepJsonEquals = _deepJsonEquals;
|
|
160
|
+
/**
|
|
161
|
+
* Shortcut for JSON.stringify(a) === JSON.stringify(b)
|
|
162
|
+
*
|
|
163
|
+
* Simplest "deep equals" implementation, but also the slowest,
|
|
164
|
+
* and not robust, in the sense that it depends on the order of object keys.
|
|
165
|
+
*/
|
|
166
|
+
function _jsonEquals(a, b) {
|
|
167
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
168
|
+
}
|
|
169
|
+
exports._jsonEquals = _jsonEquals;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Like Map, but serializes to JSON as an object.
|
|
3
|
+
*
|
|
4
|
+
* Fixes the "issue" of stock Map being json-serialized as `{}`.
|
|
5
|
+
*
|
|
6
|
+
* @experimental
|
|
7
|
+
*/
|
|
8
|
+
export declare class Map2<K = any, V = any> extends Map<K, V> {
|
|
9
|
+
/**
|
|
10
|
+
* Convenience way to create Map2 from object.
|
|
11
|
+
*/
|
|
12
|
+
static of<V>(obj: Record<any, V>): Map2<string, V>;
|
|
13
|
+
toObject(): Record<string, V>;
|
|
14
|
+
toJSON(): Record<string, V>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Map2 = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Like Map, but serializes to JSON as an object.
|
|
6
|
+
*
|
|
7
|
+
* Fixes the "issue" of stock Map being json-serialized as `{}`.
|
|
8
|
+
*
|
|
9
|
+
* @experimental
|
|
10
|
+
*/
|
|
11
|
+
class Map2 extends Map {
|
|
12
|
+
/**
|
|
13
|
+
* Convenience way to create Map2 from object.
|
|
14
|
+
*/
|
|
15
|
+
static of(obj) {
|
|
16
|
+
return new Map2(Object.entries(obj));
|
|
17
|
+
}
|
|
18
|
+
toObject() {
|
|
19
|
+
return Object.fromEntries(this);
|
|
20
|
+
}
|
|
21
|
+
toJSON() {
|
|
22
|
+
return Object.fromEntries(this);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.Map2 = Map2;
|