@naturalcycles/js-lib 14.84.2 → 14.85.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/decorators/asyncMemo.decorator.d.ts +32 -9
- package/dist/decorators/asyncMemo.decorator.js +26 -21
- package/dist/decorators/decorator.util.d.ts +1 -1
- package/dist/decorators/decorator.util.js +2 -2
- package/dist/decorators/logMethod.decorator.d.ts +6 -4
- package/dist/decorators/logMethod.decorator.js +3 -3
- package/dist/decorators/memo.decorator.d.ts +27 -26
- package/dist/decorators/memo.decorator.js +25 -45
- package/dist/decorators/memo.util.d.ts +6 -6
- package/dist/decorators/memoFn.d.ts +5 -0
- package/dist/decorators/memoFn.js +21 -40
- package/dist/decorators/memoFnAsync.d.ts +10 -0
- package/dist/decorators/memoFnAsync.js +66 -0
- package/dist/decorators/memoSimple.decorator.d.ts +1 -1
- package/dist/decorators/memoSimple.decorator.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist-esm/decorators/asyncMemo.decorator.js +27 -34
- package/dist-esm/decorators/decorator.util.js +2 -2
- package/dist-esm/decorators/logMethod.decorator.js +3 -3
- package/dist-esm/decorators/memo.decorator.js +25 -45
- package/dist-esm/decorators/memoFn.js +21 -40
- package/dist-esm/decorators/memoFnAsync.js +62 -0
- package/dist-esm/decorators/memoSimple.decorator.js +3 -3
- package/dist-esm/index.js +1 -0
- package/package.json +1 -1
- package/src/decorators/asyncMemo.decorator.ts +72 -48
- package/src/decorators/decorator.util.ts +2 -2
- package/src/decorators/logMethod.decorator.ts +16 -7
- package/src/decorators/memo.decorator.ts +46 -91
- package/src/decorators/memo.util.ts +7 -7
- package/src/decorators/memoFn.ts +21 -52
- package/src/decorators/memoFnAsync.ts +87 -0
- package/src/decorators/memoSimple.decorator.ts +4 -4
- package/src/index.ts +1 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { __asyncValues } from "tslib";
|
|
2
1
|
import { _since } from '../time/time.util';
|
|
3
2
|
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util';
|
|
4
|
-
import { jsonMemoSerializer } from './memo.util';
|
|
3
|
+
import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
5
4
|
/**
|
|
6
5
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
7
6
|
*
|
|
@@ -9,18 +8,17 @@ import { jsonMemoSerializer } from './memo.util';
|
|
|
9
8
|
* Return `null` instead (it'll be cached).
|
|
10
9
|
*/
|
|
11
10
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
12
|
-
export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
11
|
+
export const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
13
12
|
if (typeof descriptor.value !== 'function') {
|
|
14
13
|
throw new TypeError('Memoization can be applied only to methods');
|
|
15
14
|
}
|
|
16
15
|
const originalFn = descriptor.value;
|
|
17
16
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
18
17
|
const cache = new Map();
|
|
19
|
-
const { logHit = false, logMiss = false,
|
|
18
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheRejections = false, } = opt;
|
|
20
19
|
const keyStr = String(key);
|
|
21
20
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
22
21
|
descriptor.value = async function (...args) {
|
|
23
|
-
var e_1, _a;
|
|
24
22
|
const ctx = this;
|
|
25
23
|
const cacheKey = cacheKeyFn(args);
|
|
26
24
|
if (!cache.has(ctx)) {
|
|
@@ -30,23 +28,7 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
30
28
|
}
|
|
31
29
|
let value;
|
|
32
30
|
try {
|
|
33
|
-
|
|
34
|
-
for (var _b = __asyncValues(cache.get(ctx)), _c; _c = await _b.next(), !_c.done;) {
|
|
35
|
-
const cacheLayer = _c.value;
|
|
36
|
-
value = await cacheLayer.get(cacheKey);
|
|
37
|
-
if (value !== undefined) {
|
|
38
|
-
// it's a hit!
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
44
|
-
finally {
|
|
45
|
-
try {
|
|
46
|
-
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
|
|
47
|
-
}
|
|
48
|
-
finally { if (e_1) throw e_1.error; }
|
|
49
|
-
}
|
|
31
|
+
value = await cache.get(ctx).get(cacheKey);
|
|
50
32
|
}
|
|
51
33
|
catch (err) {
|
|
52
34
|
// log error, but don't throw, treat it as a "miss"
|
|
@@ -55,7 +37,7 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
55
37
|
if (value !== undefined) {
|
|
56
38
|
// hit!
|
|
57
39
|
if (logHit) {
|
|
58
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
40
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_AsyncMemo hit`);
|
|
59
41
|
}
|
|
60
42
|
return value instanceof Error ? Promise.reject(value) : Promise.resolve(value);
|
|
61
43
|
}
|
|
@@ -63,34 +45,45 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
63
45
|
const started = Date.now();
|
|
64
46
|
try {
|
|
65
47
|
value = await originalFn.apply(ctx, args);
|
|
66
|
-
|
|
67
|
-
|
|
48
|
+
// Save the value in the Cache, without awaiting it
|
|
49
|
+
// This is to support both sync and async functions
|
|
50
|
+
void (async () => {
|
|
51
|
+
try {
|
|
52
|
+
await cache.get(ctx).set(cacheKey, value);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
68
55
|
// log and ignore the error
|
|
69
56
|
logger.error(err);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
72
59
|
return value;
|
|
73
60
|
}
|
|
74
61
|
catch (err) {
|
|
75
|
-
if (
|
|
62
|
+
if (cacheRejections) {
|
|
76
63
|
// We put it to cache as raw Error, not Promise.reject(err)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
64
|
+
// This is to support both sync and async functions
|
|
65
|
+
void (async () => {
|
|
66
|
+
try {
|
|
67
|
+
await cache.get(ctx).set(cacheKey, err);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// log and ignore the error
|
|
71
|
+
logger.error(err);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
81
74
|
}
|
|
82
75
|
throw err;
|
|
83
76
|
}
|
|
84
77
|
finally {
|
|
85
78
|
if (logMiss) {
|
|
86
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
79
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_AsyncMemo miss (${_since(started)})`);
|
|
87
80
|
}
|
|
88
81
|
}
|
|
89
82
|
};
|
|
90
83
|
descriptor.value.dropCache = async () => {
|
|
91
84
|
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
|
|
92
85
|
try {
|
|
93
|
-
await Promise.all([...cache.values()].
|
|
86
|
+
await Promise.all([...cache.values()].map(c => c.clear()));
|
|
94
87
|
cache.clear();
|
|
95
88
|
}
|
|
96
89
|
catch (err) {
|
|
@@ -19,8 +19,8 @@ export function _getTargetMethodSignature(target, keyStr) {
|
|
|
19
19
|
* returns:
|
|
20
20
|
* a, b, c
|
|
21
21
|
*/
|
|
22
|
-
export function _getArgsSignature(args = [],
|
|
23
|
-
if (
|
|
22
|
+
export function _getArgsSignature(args = [], logArgs = true) {
|
|
23
|
+
if (!logArgs)
|
|
24
24
|
return '';
|
|
25
25
|
return args
|
|
26
26
|
.map(arg => {
|
|
@@ -20,13 +20,13 @@ export function _LogMethod(opt = {}) {
|
|
|
20
20
|
_assert(typeof descriptor.value === 'function', '@_LogMethod can be applied only to methods');
|
|
21
21
|
const originalFn = descriptor.value;
|
|
22
22
|
const keyStr = String(key);
|
|
23
|
-
const { avg,
|
|
23
|
+
const { avg, logArgs = true, logStart, logResult, logResultLength = true, logger = console, } = opt;
|
|
24
24
|
let { logResultFn } = opt;
|
|
25
25
|
if (!logResultFn) {
|
|
26
26
|
if (logResult) {
|
|
27
27
|
logResultFn = r => ['result:', _stringifyAny(r)];
|
|
28
28
|
}
|
|
29
|
-
else if (
|
|
29
|
+
else if (logResultLength) {
|
|
30
30
|
logResultFn = r => (Array.isArray(r) ? [`result: ${r.length} items`] : []);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -38,7 +38,7 @@ export function _LogMethod(opt = {}) {
|
|
|
38
38
|
// e.g `NameOfYourClass.methodName`
|
|
39
39
|
// or `NameOfYourClass(instanceId).methodName`
|
|
40
40
|
const methodSignature = _getMethodSignature(ctx, keyStr);
|
|
41
|
-
const argsStr = _getArgsSignature(args,
|
|
41
|
+
const argsStr = _getArgsSignature(args, logArgs);
|
|
42
42
|
const callSignature = `${methodSignature}(${argsStr}) #${++count}`;
|
|
43
43
|
if (logStart)
|
|
44
44
|
logger.log(`>> ${callSignature}`);
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// Based on:
|
|
2
|
-
// https://github.com/mgechev/memo-decorator/blob/master/index.ts
|
|
3
|
-
// http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
|
|
4
|
-
// http://inlehmansterms.net/2015/03/01/javascript-memoization/
|
|
5
|
-
// https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
|
|
6
1
|
import { _since } from '../time/time.util';
|
|
7
2
|
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util';
|
|
8
3
|
import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
@@ -15,6 +10,15 @@ import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
|
15
10
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
16
11
|
*
|
|
17
12
|
* Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
|
|
13
|
+
*
|
|
14
|
+
* Doesn't support Async functions, use @_AsyncMemo instead!
|
|
15
|
+
* (or, it will simply return the [unresolved] Promise further, without awaiting it)
|
|
16
|
+
*
|
|
17
|
+
* Based on:
|
|
18
|
+
* https://github.com/mgechev/memo-decorator/blob/master/index.ts
|
|
19
|
+
* http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
|
|
20
|
+
* http://inlehmansterms.net/2015/03/01/javascript-memoization/
|
|
21
|
+
* https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
|
|
18
22
|
*/
|
|
19
23
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
20
24
|
export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
@@ -30,8 +34,7 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
30
34
|
// UPD: tests show that normal Map also doesn't leak (to be tested further)
|
|
31
35
|
// Normal Map is needed to allow .dropCache()
|
|
32
36
|
const cache = new Map();
|
|
33
|
-
const { logHit = false, logMiss = false,
|
|
34
|
-
const awaitPromise = Boolean(noCacheRejected || noCacheResolved);
|
|
37
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheErrors = false, } = opt;
|
|
35
38
|
const keyStr = String(key);
|
|
36
39
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
37
40
|
descriptor.value = function (...args) {
|
|
@@ -42,50 +45,27 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
42
45
|
}
|
|
43
46
|
else if (cache.get(ctx).has(cacheKey)) {
|
|
44
47
|
if (logHit) {
|
|
45
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
46
|
-
}
|
|
47
|
-
const res = cache.get(ctx).get(cacheKey);
|
|
48
|
-
if (awaitPromise) {
|
|
49
|
-
return res instanceof Error ? Promise.reject(res) : Promise.resolve(res);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
return res;
|
|
48
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`);
|
|
53
49
|
}
|
|
50
|
+
return cache.get(ctx).get(cacheKey);
|
|
54
51
|
}
|
|
55
52
|
const started = Date.now();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return res;
|
|
68
|
-
})
|
|
69
|
-
.catch(err => {
|
|
70
|
-
// console.log('REJECTED', err)
|
|
71
|
-
if (logMiss) {
|
|
72
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_Memo miss rejected (${_since(started)})`);
|
|
73
|
-
}
|
|
74
|
-
if (!noCacheRejected) {
|
|
75
|
-
// We put it to cache as raw Error, not Promise.reject(err)
|
|
76
|
-
// So, we'll need to check if it's instanceof Error to reject it or resolve
|
|
77
|
-
// Wrap as Error if it's not Error
|
|
78
|
-
cache.get(ctx).set(cacheKey, err instanceof Error ? err : new Error(err));
|
|
79
|
-
}
|
|
80
|
-
throw err;
|
|
81
|
-
});
|
|
53
|
+
let value;
|
|
54
|
+
try {
|
|
55
|
+
value = originalFn.apply(ctx, args);
|
|
56
|
+
cache.get(ctx).set(cacheKey, value);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (cacheErrors) {
|
|
61
|
+
cache.get(ctx).set(cacheKey, err);
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
82
64
|
}
|
|
83
|
-
|
|
65
|
+
finally {
|
|
84
66
|
if (logMiss) {
|
|
85
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
67
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo miss (${_since(started)})`);
|
|
86
68
|
}
|
|
87
|
-
cache.get(ctx).set(cacheKey, res);
|
|
88
|
-
return res;
|
|
89
69
|
}
|
|
90
70
|
};
|
|
91
71
|
descriptor.value.dropCache = () => {
|
|
@@ -1,60 +1,41 @@
|
|
|
1
1
|
import { _since } from '../time/time.util';
|
|
2
2
|
import { _getArgsSignature } from './decorator.util';
|
|
3
3
|
import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
4
|
+
/**
|
|
5
|
+
* Only supports Sync functions.
|
|
6
|
+
* To support Async functions - use _memoFnAsync.
|
|
7
|
+
* Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
|
|
8
|
+
*/
|
|
4
9
|
export function _memoFn(fn, opt = {}) {
|
|
5
|
-
const { logHit = false, logMiss = false,
|
|
10
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheErrors = false, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
|
|
6
11
|
const cache = cacheFactory();
|
|
7
|
-
const awaitPromise = Boolean(noCacheRejected || noCacheResolved);
|
|
8
12
|
const fnName = fn.name;
|
|
9
13
|
const memoizedFn = function (...args) {
|
|
10
14
|
const ctx = this;
|
|
11
15
|
const cacheKey = cacheKeyFn(args);
|
|
12
16
|
if (cache.has(cacheKey)) {
|
|
13
17
|
if (logHit) {
|
|
14
|
-
logger.log(`${fnName}(${_getArgsSignature(args,
|
|
15
|
-
}
|
|
16
|
-
const res = cache.get(cacheKey);
|
|
17
|
-
if (awaitPromise) {
|
|
18
|
-
return res instanceof Error ? Promise.reject(res) : Promise.resolve(res);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
return res;
|
|
18
|
+
logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn hit`);
|
|
22
19
|
}
|
|
20
|
+
return cache.get(cacheKey);
|
|
23
21
|
}
|
|
24
22
|
const started = Date.now();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return res;
|
|
37
|
-
})
|
|
38
|
-
.catch(err => {
|
|
39
|
-
// console.log('REJECTED', err)
|
|
40
|
-
if (logMiss) {
|
|
41
|
-
logger.log(`${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn miss rejected (${_since(started)})`);
|
|
42
|
-
}
|
|
43
|
-
if (!noCacheRejected) {
|
|
44
|
-
// We put it to cache as raw Error, not Promise.reject(err)
|
|
45
|
-
// So, we'll need to check if it's instanceof Error to reject it or resolve
|
|
46
|
-
// Wrap as Error if it's not Error
|
|
47
|
-
cache.set(cacheKey, err instanceof Error ? err : new Error(err));
|
|
48
|
-
}
|
|
49
|
-
throw err;
|
|
50
|
-
});
|
|
23
|
+
let value;
|
|
24
|
+
try {
|
|
25
|
+
value = fn.apply(ctx, args);
|
|
26
|
+
cache.set(cacheKey, value);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (cacheErrors) {
|
|
31
|
+
cache.set(cacheKey, err);
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
51
34
|
}
|
|
52
|
-
|
|
35
|
+
finally {
|
|
53
36
|
if (logMiss) {
|
|
54
|
-
logger.log(`${fnName}(${_getArgsSignature(args,
|
|
37
|
+
logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn miss (${_since(started)})`);
|
|
55
38
|
}
|
|
56
|
-
cache.set(cacheKey, res);
|
|
57
|
-
return res;
|
|
58
39
|
}
|
|
59
40
|
};
|
|
60
41
|
Object.assign(memoizedFn, { cache });
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { _since } from '../time/time.util';
|
|
2
|
+
import { _getArgsSignature } from './decorator.util';
|
|
3
|
+
import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
4
|
+
/**
|
|
5
|
+
* Only supports Sync functions.
|
|
6
|
+
* To support Async functions - use _memoFnAsync
|
|
7
|
+
*/
|
|
8
|
+
export function _memoFnAsync(fn, opt = {}) {
|
|
9
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheRejections = false, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
|
|
10
|
+
const cache = cacheFactory();
|
|
11
|
+
const fnName = fn.name;
|
|
12
|
+
const memoizedFn = async function (...args) {
|
|
13
|
+
const ctx = this;
|
|
14
|
+
const cacheKey = cacheKeyFn(args);
|
|
15
|
+
let value;
|
|
16
|
+
try {
|
|
17
|
+
value = await cache.get(cacheKey);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
logger.error(err);
|
|
21
|
+
}
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
if (logHit) {
|
|
24
|
+
logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync hit`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
const started = Date.now();
|
|
29
|
+
try {
|
|
30
|
+
value = await fn.apply(ctx, args);
|
|
31
|
+
void (async () => {
|
|
32
|
+
try {
|
|
33
|
+
await cache.set(cacheKey, value);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
logger.error(err);
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (cacheRejections) {
|
|
43
|
+
void (async () => {
|
|
44
|
+
try {
|
|
45
|
+
await cache.set(cacheKey, err);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
logger.error(err);
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
if (logMiss) {
|
|
56
|
+
logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync miss (${_since(started)})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
Object.assign(memoizedFn, { cache });
|
|
61
|
+
return memoizedFn;
|
|
62
|
+
}
|
|
@@ -34,7 +34,7 @@ export const memoSimple = (opt = {}) => (target, key, descriptor) => {
|
|
|
34
34
|
}
|
|
35
35
|
*/
|
|
36
36
|
const cache = new MapMemoCache();
|
|
37
|
-
const { logHit, logMiss,
|
|
37
|
+
const { logHit, logMiss, logArgs = true, logger = console } = opt;
|
|
38
38
|
const keyStr = String(key);
|
|
39
39
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
40
40
|
descriptor.value = function (...args) {
|
|
@@ -42,14 +42,14 @@ export const memoSimple = (opt = {}) => (target, key, descriptor) => {
|
|
|
42
42
|
const cacheKey = jsonMemoSerializer(args);
|
|
43
43
|
if (cache.has(cacheKey)) {
|
|
44
44
|
if (logHit) {
|
|
45
|
-
logger.log(`${methodSignature}(${_getArgsSignature(args,
|
|
45
|
+
logger.log(`${methodSignature}(${_getArgsSignature(args, logArgs)}) @memo hit`);
|
|
46
46
|
}
|
|
47
47
|
return cache.get(cacheKey);
|
|
48
48
|
}
|
|
49
49
|
const d = Date.now();
|
|
50
50
|
const res = originalFn.apply(ctx, args);
|
|
51
51
|
if (logMiss) {
|
|
52
|
-
logger.log(`${methodSignature}(${_getArgsSignature(args,
|
|
52
|
+
logger.log(`${methodSignature}(${_getArgsSignature(args, logArgs)}) @memo miss (${Date.now() - d} ms)`);
|
|
53
53
|
}
|
|
54
54
|
cache.set(cacheKey, res);
|
|
55
55
|
return res;
|
package/dist-esm/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from './decorators/logMethod.decorator';
|
|
|
10
10
|
export * from './decorators/memo.decorator';
|
|
11
11
|
export * from './decorators/asyncMemo.decorator';
|
|
12
12
|
export * from './decorators/memoFn';
|
|
13
|
+
export * from './decorators/memoFnAsync';
|
|
13
14
|
export * from './decorators/retry.decorator';
|
|
14
15
|
export * from './decorators/timeout.decorator';
|
|
15
16
|
export * from './error/app.error';
|
package/package.json
CHANGED
|
@@ -1,25 +1,51 @@
|
|
|
1
|
+
import { CommonLogger } from '../log/commonLogger'
|
|
1
2
|
import { _since } from '../time/time.util'
|
|
2
|
-
import { Merge } from '../typeFest'
|
|
3
3
|
import { AnyObject } from '../types'
|
|
4
4
|
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
import { AsyncMemoCache, jsonMemoSerializer, MapMemoCache } from './memo.util'
|
|
6
|
+
|
|
7
|
+
export interface AsyncMemoOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Provide a custom implementation of MemoCache.
|
|
10
|
+
* Function that creates an instance of `MemoCache`.
|
|
11
|
+
* e.g LRUMemoCache from `@naturalcycles/nodejs-lib`.
|
|
12
|
+
*/
|
|
13
|
+
cacheFactory?: () => AsyncMemoCache
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Provide a custom implementation of CacheKey function.
|
|
17
|
+
*/
|
|
18
|
+
cacheKeyFn?: (args: any[]) => any
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set to `true` to cache rejected promises (errors).
|
|
22
|
+
*
|
|
23
|
+
* Default false.
|
|
24
|
+
*/
|
|
25
|
+
cacheRejections?: boolean
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default to false
|
|
29
|
+
*/
|
|
30
|
+
logHit?: boolean
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Default to false
|
|
34
|
+
*/
|
|
35
|
+
logMiss?: boolean
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set to `false` to skip logging method arguments.
|
|
39
|
+
*
|
|
40
|
+
* Defaults to true.
|
|
41
|
+
*/
|
|
42
|
+
logArgs?: boolean
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default to `console`
|
|
46
|
+
*/
|
|
47
|
+
logger?: CommonLogger
|
|
48
|
+
}
|
|
23
49
|
|
|
24
50
|
/**
|
|
25
51
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
@@ -29,7 +55,7 @@ export type AsyncMemoOptions = Merge<
|
|
|
29
55
|
*/
|
|
30
56
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
31
57
|
export const _AsyncMemo =
|
|
32
|
-
(opt: AsyncMemoOptions): MethodDecorator =>
|
|
58
|
+
(opt: AsyncMemoOptions = {}): MethodDecorator =>
|
|
33
59
|
(target, key, descriptor) => {
|
|
34
60
|
if (typeof descriptor.value !== 'function') {
|
|
35
61
|
throw new TypeError('Memoization can be applied only to methods')
|
|
@@ -38,17 +64,16 @@ export const _AsyncMemo =
|
|
|
38
64
|
const originalFn = descriptor.value
|
|
39
65
|
|
|
40
66
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
41
|
-
const cache = new Map<AnyObject, AsyncMemoCache
|
|
67
|
+
const cache = new Map<AnyObject, AsyncMemoCache>()
|
|
42
68
|
|
|
43
69
|
const {
|
|
44
70
|
logHit = false,
|
|
45
71
|
logMiss = false,
|
|
46
|
-
|
|
72
|
+
logArgs = true,
|
|
47
73
|
logger = console,
|
|
48
|
-
cacheFactory,
|
|
74
|
+
cacheFactory = () => new MapMemoCache(),
|
|
49
75
|
cacheKeyFn = jsonMemoSerializer,
|
|
50
|
-
|
|
51
|
-
noCacheResolved = false,
|
|
76
|
+
cacheRejections = false,
|
|
52
77
|
} = opt
|
|
53
78
|
|
|
54
79
|
const keyStr = String(key)
|
|
@@ -68,13 +93,7 @@ export const _AsyncMemo =
|
|
|
68
93
|
let value: any
|
|
69
94
|
|
|
70
95
|
try {
|
|
71
|
-
|
|
72
|
-
value = await cacheLayer.get(cacheKey)
|
|
73
|
-
if (value !== undefined) {
|
|
74
|
-
// it's a hit!
|
|
75
|
-
break
|
|
76
|
-
}
|
|
77
|
-
}
|
|
96
|
+
value = await cache.get(ctx)!.get(cacheKey)
|
|
78
97
|
} catch (err) {
|
|
79
98
|
// log error, but don't throw, treat it as a "miss"
|
|
80
99
|
logger.error(err)
|
|
@@ -86,7 +105,7 @@ export const _AsyncMemo =
|
|
|
86
105
|
logger.log(
|
|
87
106
|
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
88
107
|
args,
|
|
89
|
-
|
|
108
|
+
logArgs,
|
|
90
109
|
)}) @_AsyncMemo hit`,
|
|
91
110
|
)
|
|
92
111
|
}
|
|
@@ -100,25 +119,30 @@ export const _AsyncMemo =
|
|
|
100
119
|
try {
|
|
101
120
|
value = await originalFn.apply(ctx, args)
|
|
102
121
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
// Save the value in the Cache, without awaiting it
|
|
123
|
+
// This is to support both sync and async functions
|
|
124
|
+
void (async () => {
|
|
125
|
+
try {
|
|
126
|
+
await cache.get(ctx)!.set(cacheKey, value)
|
|
127
|
+
} catch (err) {
|
|
128
|
+
// log and ignore the error
|
|
129
|
+
logger.error(err)
|
|
130
|
+
}
|
|
131
|
+
})()
|
|
111
132
|
|
|
112
133
|
return value
|
|
113
134
|
} catch (err) {
|
|
114
|
-
if (
|
|
135
|
+
if (cacheRejections) {
|
|
115
136
|
// We put it to cache as raw Error, not Promise.reject(err)
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
// This is to support both sync and async functions
|
|
138
|
+
void (async () => {
|
|
139
|
+
try {
|
|
140
|
+
await cache.get(ctx)!.set(cacheKey, err)
|
|
141
|
+
} catch (err) {
|
|
118
142
|
// log and ignore the error
|
|
119
143
|
logger.error(err)
|
|
120
|
-
}
|
|
121
|
-
)
|
|
144
|
+
}
|
|
145
|
+
})()
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
throw err
|
|
@@ -127,7 +151,7 @@ export const _AsyncMemo =
|
|
|
127
151
|
logger.log(
|
|
128
152
|
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
129
153
|
args,
|
|
130
|
-
|
|
154
|
+
logArgs,
|
|
131
155
|
)}) @_AsyncMemo miss (${_since(started)})`,
|
|
132
156
|
)
|
|
133
157
|
}
|
|
@@ -136,7 +160,7 @@ export const _AsyncMemo =
|
|
|
136
160
|
;(descriptor.value as any).dropCache = async () => {
|
|
137
161
|
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`)
|
|
138
162
|
try {
|
|
139
|
-
await Promise.all([...cache.values()].
|
|
163
|
+
await Promise.all([...cache.values()].map(c => c.clear()))
|
|
140
164
|
cache.clear()
|
|
141
165
|
} catch (err) {
|
|
142
166
|
logger.error(err)
|
|
@@ -23,8 +23,8 @@ export function _getTargetMethodSignature(target: AnyObject, keyStr: string): st
|
|
|
23
23
|
* returns:
|
|
24
24
|
* a, b, c
|
|
25
25
|
*/
|
|
26
|
-
export function _getArgsSignature(args: any[] = [],
|
|
27
|
-
if (
|
|
26
|
+
export function _getArgsSignature(args: any[] = [], logArgs = true): string {
|
|
27
|
+
if (!logArgs) return ''
|
|
28
28
|
|
|
29
29
|
return args
|
|
30
30
|
.map(arg => {
|