@naturalcycles/js-lib 14.84.2 → 14.87.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 +35 -9
- package/dist/decorators/asyncMemo.decorator.js +30 -22
- package/dist/decorators/createPromiseDecorator.d.ts +2 -2
- package/dist/decorators/createPromiseDecorator.js +6 -12
- 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 +29 -25
- package/dist/decorators/memo.decorator.js +36 -42
- package/dist/decorators/memo.util.d.ts +6 -6
- package/dist/decorators/memoFn.d.ts +5 -0
- package/dist/decorators/memoFn.js +32 -37
- package/dist/decorators/memoFnAsync.d.ts +10 -0
- package/dist/decorators/memoFnAsync.js +69 -0
- package/dist/decorators/memoSimple.decorator.d.ts +1 -1
- package/dist/decorators/memoSimple.decorator.js +3 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/types.d.ts +4 -0
- package/dist-esm/decorators/asyncMemo.decorator.js +31 -35
- package/dist-esm/decorators/createPromiseDecorator.js +8 -15
- 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 +36 -42
- package/dist-esm/decorators/memoFn.js +32 -37
- package/dist-esm/decorators/memoFnAsync.js +65 -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 +80 -49
- package/src/decorators/createPromiseDecorator.ts +64 -70
- package/src/decorators/decorator.util.ts +2 -2
- package/src/decorators/logMethod.decorator.ts +16 -7
- package/src/decorators/memo.decorator.ts +61 -90
- package/src/decorators/memo.util.ts +7 -7
- package/src/decorators/memoFn.ts +33 -51
- package/src/decorators/memoFnAsync.ts +91 -0
- package/src/decorators/memoSimple.decorator.ts +4 -4
- package/src/index.ts +3 -0
- package/src/types.ts +5 -0
|
@@ -1,22 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { MemoOptions } from './memo.decorator';
|
|
1
|
+
import { CommonLogger } from '../log/commonLogger';
|
|
3
2
|
import { AsyncMemoCache } from './memo.util';
|
|
4
|
-
export
|
|
3
|
+
export interface AsyncMemoOptions {
|
|
5
4
|
/**
|
|
6
5
|
* Provide a custom implementation of MemoCache.
|
|
7
6
|
* Function that creates an instance of `MemoCache`.
|
|
8
7
|
* e.g LRUMemoCache from `@naturalcycles/nodejs-lib`.
|
|
8
|
+
*/
|
|
9
|
+
cacheFactory?: () => AsyncMemoCache;
|
|
10
|
+
/**
|
|
11
|
+
* Provide a custom implementation of CacheKey function.
|
|
12
|
+
*/
|
|
13
|
+
cacheKeyFn?: (args: any[]) => any;
|
|
14
|
+
/**
|
|
15
|
+
* Default true.
|
|
16
|
+
*
|
|
17
|
+
* Set to `false` to skip caching rejected promises (errors).
|
|
18
|
+
*
|
|
19
|
+
* True will ensure "max 1 execution", but will "remember" rejection.
|
|
20
|
+
* False will allow >1 execution in case of errors.
|
|
21
|
+
*/
|
|
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.
|
|
9
33
|
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
34
|
+
* Defaults to true.
|
|
35
|
+
*/
|
|
36
|
+
logArgs?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Default to `console`
|
|
13
39
|
*/
|
|
14
|
-
|
|
15
|
-
}
|
|
40
|
+
logger?: CommonLogger;
|
|
41
|
+
}
|
|
16
42
|
/**
|
|
17
43
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
18
44
|
*
|
|
19
45
|
* Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
|
|
20
46
|
* Return `null` instead (it'll be cached).
|
|
21
47
|
*/
|
|
22
|
-
export declare const _AsyncMemo: (opt
|
|
48
|
+
export declare const _AsyncMemo: (opt?: AsyncMemoOptions) => MethodDecorator;
|
|
@@ -11,14 +11,14 @@ const memo_util_1 = require("./memo.util");
|
|
|
11
11
|
* Return `null` instead (it'll be cached).
|
|
12
12
|
*/
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
14
|
-
const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
14
|
+
const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
15
15
|
if (typeof descriptor.value !== 'function') {
|
|
16
16
|
throw new TypeError('Memoization can be applied only to methods');
|
|
17
17
|
}
|
|
18
18
|
const originalFn = descriptor.value;
|
|
19
19
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
20
20
|
const cache = new Map();
|
|
21
|
-
const { logHit = false, logMiss = false,
|
|
21
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheRejections = true, } = opt;
|
|
22
22
|
const keyStr = String(key);
|
|
23
23
|
const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
|
|
24
24
|
descriptor.value = async function (...args) {
|
|
@@ -31,13 +31,7 @@ const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
31
31
|
}
|
|
32
32
|
let value;
|
|
33
33
|
try {
|
|
34
|
-
|
|
35
|
-
value = await cacheLayer.get(cacheKey);
|
|
36
|
-
if (value !== undefined) {
|
|
37
|
-
// it's a hit!
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
34
|
+
value = await cache.get(ctx).get(cacheKey);
|
|
41
35
|
}
|
|
42
36
|
catch (err) {
|
|
43
37
|
// log error, but don't throw, treat it as a "miss"
|
|
@@ -46,42 +40,56 @@ const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
46
40
|
if (value !== undefined) {
|
|
47
41
|
// hit!
|
|
48
42
|
if (logHit) {
|
|
49
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
43
|
+
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_AsyncMemo hit`);
|
|
44
|
+
}
|
|
45
|
+
if (value instanceof Error) {
|
|
46
|
+
throw value;
|
|
50
47
|
}
|
|
51
|
-
return value
|
|
48
|
+
return value;
|
|
52
49
|
}
|
|
53
50
|
// Here we know it's a MISS, let's execute the real method
|
|
54
51
|
const started = Date.now();
|
|
55
52
|
try {
|
|
56
53
|
value = await originalFn.apply(ctx, args);
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
// Save the value in the Cache, without awaiting it
|
|
55
|
+
// This is to support both sync and async functions
|
|
56
|
+
void (async () => {
|
|
57
|
+
try {
|
|
58
|
+
await cache.get(ctx).set(cacheKey, value);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
59
61
|
// log and ignore the error
|
|
60
62
|
logger.error(err);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
63
65
|
return value;
|
|
64
66
|
}
|
|
65
67
|
catch (err) {
|
|
66
|
-
if (
|
|
68
|
+
if (cacheRejections) {
|
|
67
69
|
// We put it to cache as raw Error, not Promise.reject(err)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// This is to support both sync and async functions
|
|
71
|
+
void (async () => {
|
|
72
|
+
try {
|
|
73
|
+
await cache.get(ctx).set(cacheKey, err);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
// log and ignore the error
|
|
77
|
+
logger.error(err);
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
72
80
|
}
|
|
73
81
|
throw err;
|
|
74
82
|
}
|
|
75
83
|
finally {
|
|
76
84
|
if (logMiss) {
|
|
77
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
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)})`);
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
};
|
|
81
89
|
descriptor.value.dropCache = async () => {
|
|
82
90
|
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
|
|
83
91
|
try {
|
|
84
|
-
await Promise.all([...cache.values()].
|
|
92
|
+
await Promise.all([...cache.values()].map(c => c.clear()));
|
|
85
93
|
cache.clear();
|
|
86
94
|
}
|
|
87
95
|
catch (err) {
|
|
@@ -2,9 +2,9 @@ export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
|
|
|
2
2
|
decoratorName: string;
|
|
3
3
|
/**
|
|
4
4
|
* Called BEFORE the original function.
|
|
5
|
-
*
|
|
5
|
+
* If Promise is returned - it will be awaited.
|
|
6
6
|
*/
|
|
7
|
-
beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void
|
|
7
|
+
beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void | Promise<void>;
|
|
8
8
|
/**
|
|
9
9
|
* Called just AFTER the original function.
|
|
10
10
|
* The output of this hook will be passed further,
|
|
@@ -24,12 +24,11 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
|
|
|
24
24
|
pd.value = async function (...args) {
|
|
25
25
|
// console.log(`@${cfg.decoratorName} called inside function`)
|
|
26
26
|
const started = Date.now();
|
|
27
|
-
|
|
27
|
+
try {
|
|
28
28
|
// Before function
|
|
29
|
-
.then(() => {
|
|
30
29
|
// console.log(`@${cfg.decoratorName} Before`)
|
|
31
30
|
if (cfg.beforeFn) {
|
|
32
|
-
|
|
31
|
+
await cfg.beforeFn({
|
|
33
32
|
decoratorParams,
|
|
34
33
|
args,
|
|
35
34
|
key,
|
|
@@ -38,10 +37,8 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
|
|
|
38
37
|
started,
|
|
39
38
|
});
|
|
40
39
|
}
|
|
41
|
-
})
|
|
42
40
|
// Original function
|
|
43
|
-
|
|
44
|
-
.then(res => {
|
|
41
|
+
let res = await originalMethod.apply(this, args);
|
|
45
42
|
// console.log(`${cfg.decoratorName} After`)
|
|
46
43
|
const resp = {
|
|
47
44
|
decoratorParams,
|
|
@@ -59,8 +56,8 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
|
|
|
59
56
|
}
|
|
60
57
|
cfg.finallyFn?.(resp);
|
|
61
58
|
return res;
|
|
62
|
-
}
|
|
63
|
-
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
64
61
|
console.error(`@${decoratorName} ${methodSignature} catch:`, err);
|
|
65
62
|
const resp = {
|
|
66
63
|
decoratorParams,
|
|
@@ -82,10 +79,7 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
|
|
|
82
79
|
if (!handled) {
|
|
83
80
|
throw err; // rethrow
|
|
84
81
|
}
|
|
85
|
-
}
|
|
86
|
-
// es2018 only
|
|
87
|
-
// .finally(() => {})
|
|
88
|
-
);
|
|
82
|
+
}
|
|
89
83
|
};
|
|
90
84
|
return pd;
|
|
91
85
|
};
|
|
@@ -15,4 +15,4 @@ export declare function _getTargetMethodSignature(target: AnyObject, keyStr: str
|
|
|
15
15
|
* returns:
|
|
16
16
|
* a, b, c
|
|
17
17
|
*/
|
|
18
|
-
export declare function _getArgsSignature(args?: any[],
|
|
18
|
+
export declare function _getArgsSignature(args?: any[], logArgs?: boolean): string;
|
|
@@ -24,8 +24,8 @@ exports._getTargetMethodSignature = _getTargetMethodSignature;
|
|
|
24
24
|
* returns:
|
|
25
25
|
* a, b, c
|
|
26
26
|
*/
|
|
27
|
-
function _getArgsSignature(args = [],
|
|
28
|
-
if (
|
|
27
|
+
function _getArgsSignature(args = [], logArgs = true) {
|
|
28
|
+
if (!logArgs)
|
|
29
29
|
return '';
|
|
30
30
|
return args
|
|
31
31
|
.map(arg => {
|
|
@@ -11,13 +11,15 @@ export interface LogMethodOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
avg?: number;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Defaults to true.
|
|
15
|
+
* Set to false to skip logging method arguments
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
+
logArgs?: boolean;
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* Defaults to true.
|
|
20
|
+
* Set to false to skip logging result length when result is an array.
|
|
19
21
|
*/
|
|
20
|
-
|
|
22
|
+
logResultLength?: boolean;
|
|
21
23
|
/**
|
|
22
24
|
* Also log on method start.
|
|
23
25
|
* Example:
|
|
@@ -23,13 +23,13 @@ function _LogMethod(opt = {}) {
|
|
|
23
23
|
(0, __1._assert)(typeof descriptor.value === 'function', '@_LogMethod can be applied only to methods');
|
|
24
24
|
const originalFn = descriptor.value;
|
|
25
25
|
const keyStr = String(key);
|
|
26
|
-
const { avg,
|
|
26
|
+
const { avg, logArgs = true, logStart, logResult, logResultLength = true, logger = console, } = opt;
|
|
27
27
|
let { logResultFn } = opt;
|
|
28
28
|
if (!logResultFn) {
|
|
29
29
|
if (logResult) {
|
|
30
30
|
logResultFn = r => ['result:', (0, __1._stringifyAny)(r)];
|
|
31
31
|
}
|
|
32
|
-
else if (
|
|
32
|
+
else if (logResultLength) {
|
|
33
33
|
logResultFn = r => (Array.isArray(r) ? [`result: ${r.length} items`] : []);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -41,7 +41,7 @@ function _LogMethod(opt = {}) {
|
|
|
41
41
|
// e.g `NameOfYourClass.methodName`
|
|
42
42
|
// or `NameOfYourClass(instanceId).methodName`
|
|
43
43
|
const methodSignature = (0, decorator_util_1._getMethodSignature)(ctx, keyStr);
|
|
44
|
-
const argsStr = (0, decorator_util_1._getArgsSignature)(args,
|
|
44
|
+
const argsStr = (0, decorator_util_1._getArgsSignature)(args, logArgs);
|
|
45
45
|
const callSignature = `${methodSignature}(${argsStr}) #${++count}`;
|
|
46
46
|
if (logStart)
|
|
47
47
|
logger.log(`>> ${callSignature}`);
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
import { CommonLogger } from '../log/commonLogger';
|
|
2
2
|
import { MemoCache } from './memo.util';
|
|
3
3
|
export interface MemoOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Default to false
|
|
6
|
-
*/
|
|
7
|
-
logHit?: boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Default to false
|
|
10
|
-
*/
|
|
11
|
-
logMiss?: boolean;
|
|
12
|
-
/**
|
|
13
|
-
* Skip logging method arguments.
|
|
14
|
-
*/
|
|
15
|
-
noLogArgs?: boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Default to `console`
|
|
18
|
-
*/
|
|
19
|
-
logger?: CommonLogger;
|
|
20
4
|
/**
|
|
21
5
|
* Provide a custom implementation of MemoCache.
|
|
22
6
|
* Function that creates an instance of `MemoCache`.
|
|
@@ -28,19 +12,30 @@ export interface MemoOptions {
|
|
|
28
12
|
*/
|
|
29
13
|
cacheKeyFn?: (args: any[]) => any;
|
|
30
14
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
15
|
+
* Defaults to true.
|
|
16
|
+
* Set to false to skip caching errors.
|
|
33
17
|
*
|
|
34
|
-
*
|
|
18
|
+
* True will ensure "max 1 execution", but will "remember" errors.
|
|
19
|
+
* False will allow >1 execution in case of errors.
|
|
35
20
|
*/
|
|
36
|
-
|
|
21
|
+
cacheErrors?: boolean;
|
|
37
22
|
/**
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
/**
|
|
36
|
+
* Default to `console`
|
|
42
37
|
*/
|
|
43
|
-
|
|
38
|
+
logger?: CommonLogger;
|
|
44
39
|
}
|
|
45
40
|
/**
|
|
46
41
|
* Memoizes the method of the class, so it caches the output and returns the cached version if the "key"
|
|
@@ -51,5 +46,14 @@ export interface MemoOptions {
|
|
|
51
46
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
52
47
|
*
|
|
53
48
|
* Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
|
|
49
|
+
*
|
|
50
|
+
* Doesn't support Async functions, use @_AsyncMemo instead!
|
|
51
|
+
* (or, it will simply return the [unresolved] Promise further, without awaiting it)
|
|
52
|
+
*
|
|
53
|
+
* Based on:
|
|
54
|
+
* https://github.com/mgechev/memo-decorator/blob/master/index.ts
|
|
55
|
+
* http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
|
|
56
|
+
* http://inlehmansterms.net/2015/03/01/javascript-memoization/
|
|
57
|
+
* https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
|
|
54
58
|
*/
|
|
55
59
|
export declare const _Memo: (opt?: MemoOptions) => MethodDecorator;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Based on:
|
|
3
|
-
// https://github.com/mgechev/memo-decorator/blob/master/index.ts
|
|
4
|
-
// http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
|
|
5
|
-
// http://inlehmansterms.net/2015/03/01/javascript-memoization/
|
|
6
|
-
// https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports._Memo = void 0;
|
|
9
4
|
const time_util_1 = require("../time/time.util");
|
|
@@ -18,6 +13,15 @@ const memo_util_1 = require("./memo.util");
|
|
|
18
13
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
19
14
|
*
|
|
20
15
|
* Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
|
|
16
|
+
*
|
|
17
|
+
* Doesn't support Async functions, use @_AsyncMemo instead!
|
|
18
|
+
* (or, it will simply return the [unresolved] Promise further, without awaiting it)
|
|
19
|
+
*
|
|
20
|
+
* Based on:
|
|
21
|
+
* https://github.com/mgechev/memo-decorator/blob/master/index.ts
|
|
22
|
+
* http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
|
|
23
|
+
* http://inlehmansterms.net/2015/03/01/javascript-memoization/
|
|
24
|
+
* https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
|
|
21
25
|
*/
|
|
22
26
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
23
27
|
const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
@@ -33,62 +37,52 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
33
37
|
// UPD: tests show that normal Map also doesn't leak (to be tested further)
|
|
34
38
|
// Normal Map is needed to allow .dropCache()
|
|
35
39
|
const cache = new Map();
|
|
36
|
-
const { logHit = false, logMiss = false,
|
|
37
|
-
const awaitPromise = Boolean(noCacheRejected || noCacheResolved);
|
|
40
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheErrors = true, } = opt;
|
|
38
41
|
const keyStr = String(key);
|
|
39
42
|
const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
|
|
40
43
|
descriptor.value = function (...args) {
|
|
41
44
|
const ctx = this;
|
|
42
45
|
const cacheKey = cacheKeyFn(args);
|
|
46
|
+
let value;
|
|
43
47
|
if (!cache.has(ctx)) {
|
|
44
48
|
cache.set(ctx, cacheFactory());
|
|
45
49
|
}
|
|
46
50
|
else if (cache.get(ctx).has(cacheKey)) {
|
|
47
51
|
if (logHit) {
|
|
48
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
49
|
-
}
|
|
50
|
-
const res = cache.get(ctx).get(cacheKey);
|
|
51
|
-
if (awaitPromise) {
|
|
52
|
-
return res instanceof Error ? Promise.reject(res) : Promise.resolve(res);
|
|
52
|
+
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_Memo hit`);
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
value = cache.get(ctx).get(cacheKey);
|
|
55
|
+
if (value instanceof Error) {
|
|
56
|
+
throw value;
|
|
56
57
|
}
|
|
58
|
+
return value;
|
|
57
59
|
}
|
|
58
60
|
const started = Date.now();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// console.log('REJECTED', err)
|
|
74
|
-
if (logMiss) {
|
|
75
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, noLogArgs)}) @_Memo miss rejected (${(0, time_util_1._since)(started)})`);
|
|
61
|
+
try {
|
|
62
|
+
value = originalFn.apply(ctx, args);
|
|
63
|
+
try {
|
|
64
|
+
cache.get(ctx).set(cacheKey, value);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
logger.error(err);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (cacheErrors) {
|
|
73
|
+
try {
|
|
74
|
+
cache.get(ctx).set(cacheKey, err);
|
|
76
75
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// So, we'll need to check if it's instanceof Error to reject it or resolve
|
|
80
|
-
// Wrap as Error if it's not Error
|
|
81
|
-
cache.get(ctx).set(cacheKey, err instanceof Error ? err : new Error(err));
|
|
76
|
+
catch (err) {
|
|
77
|
+
logger.error(err);
|
|
82
78
|
}
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
85
81
|
}
|
|
86
|
-
|
|
82
|
+
finally {
|
|
87
83
|
if (logMiss) {
|
|
88
|
-
logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
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)})`);
|
|
89
85
|
}
|
|
90
|
-
cache.get(ctx).set(cacheKey, res);
|
|
91
|
-
return res;
|
|
92
86
|
}
|
|
93
87
|
};
|
|
94
88
|
descriptor.value.dropCache = () => {
|
|
@@ -3,8 +3,8 @@ export declare type MemoSerializer = (args: any[]) => any;
|
|
|
3
3
|
export declare const jsonMemoSerializer: MemoSerializer;
|
|
4
4
|
export interface MemoCache<KEY = any, VALUE = any> {
|
|
5
5
|
has(k: KEY): boolean;
|
|
6
|
-
get(k: KEY): VALUE | undefined;
|
|
7
|
-
set(k: KEY, v: VALUE): void;
|
|
6
|
+
get(k: KEY): VALUE | Error | undefined;
|
|
7
|
+
set(k: KEY, v: VALUE | Error): void;
|
|
8
8
|
/**
|
|
9
9
|
* Clear is only called when `.dropCache()` is called.
|
|
10
10
|
* Otherwise the Cache is "persistent" (never cleared).
|
|
@@ -17,8 +17,8 @@ export interface AsyncMemoCache<KEY = any, VALUE = any> {
|
|
|
17
17
|
* This also means that you CANNOT store `undefined` value in the Cache, as it'll be treated as a MISS.
|
|
18
18
|
* You CAN store `null` value instead, it will be treated as a HIT.
|
|
19
19
|
*/
|
|
20
|
-
get(k: KEY): Promisable<VALUE | undefined>;
|
|
21
|
-
set(k: KEY, v: VALUE): Promisable<void>;
|
|
20
|
+
get(k: KEY): Promisable<VALUE | Error | undefined>;
|
|
21
|
+
set(k: KEY, v: VALUE | Error): Promisable<void>;
|
|
22
22
|
/**
|
|
23
23
|
* Clear is only called when `.dropCache()` is called.
|
|
24
24
|
* Otherwise the Cache is "persistent" (never cleared).
|
|
@@ -28,7 +28,7 @@ export interface AsyncMemoCache<KEY = any, VALUE = any> {
|
|
|
28
28
|
export declare class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<KEY, VALUE>, AsyncMemoCache<KEY, VALUE> {
|
|
29
29
|
private m;
|
|
30
30
|
has(k: KEY): boolean;
|
|
31
|
-
get(k: KEY): VALUE | undefined;
|
|
32
|
-
set(k: KEY, v: VALUE): void;
|
|
31
|
+
get(k: KEY): VALUE | Error | undefined;
|
|
32
|
+
set(k: KEY, v: VALUE | Error): void;
|
|
33
33
|
clear(): void;
|
|
34
34
|
}
|
|
@@ -3,4 +3,9 @@ import { MemoCache } from './memo.util';
|
|
|
3
3
|
export interface MemoizedFunction {
|
|
4
4
|
cache: MemoCache;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Only supports Sync functions.
|
|
8
|
+
* To support Async functions - use _memoFnAsync.
|
|
9
|
+
* Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
|
|
10
|
+
*/
|
|
6
11
|
export declare function _memoFn<T extends (...args: any[]) => any>(fn: T, opt?: MemoOptions): T & MemoizedFunction;
|
|
@@ -4,60 +4,55 @@ exports._memoFn = void 0;
|
|
|
4
4
|
const time_util_1 = require("../time/time.util");
|
|
5
5
|
const decorator_util_1 = require("./decorator.util");
|
|
6
6
|
const memo_util_1 = require("./memo.util");
|
|
7
|
+
/**
|
|
8
|
+
* Only supports Sync functions.
|
|
9
|
+
* To support Async functions - use _memoFnAsync.
|
|
10
|
+
* Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
|
|
11
|
+
*/
|
|
7
12
|
function _memoFn(fn, opt = {}) {
|
|
8
|
-
const { logHit = false, logMiss = false,
|
|
13
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheErrors = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
|
|
9
14
|
const cache = cacheFactory();
|
|
10
|
-
const awaitPromise = Boolean(noCacheRejected || noCacheResolved);
|
|
11
15
|
const fnName = fn.name;
|
|
12
16
|
const memoizedFn = function (...args) {
|
|
13
17
|
const ctx = this;
|
|
14
18
|
const cacheKey = cacheKeyFn(args);
|
|
19
|
+
let value;
|
|
15
20
|
if (cache.has(cacheKey)) {
|
|
16
21
|
if (logHit) {
|
|
17
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
22
|
+
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFn hit`);
|
|
18
23
|
}
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
return res;
|
|
24
|
+
value = cache.get(cacheKey);
|
|
25
|
+
if (value instanceof Error) {
|
|
26
|
+
throw value;
|
|
25
27
|
}
|
|
28
|
+
return value;
|
|
26
29
|
}
|
|
27
30
|
const started = Date.now();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// console.log('REJECTED', err)
|
|
43
|
-
if (logMiss) {
|
|
44
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, noLogArgs)}) memoFn miss rejected (${(0, time_util_1._since)(started)})`);
|
|
31
|
+
try {
|
|
32
|
+
value = fn.apply(ctx, args);
|
|
33
|
+
try {
|
|
34
|
+
cache.set(cacheKey, value);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
logger.error(err);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (cacheErrors) {
|
|
43
|
+
try {
|
|
44
|
+
cache.set(cacheKey, err);
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// So, we'll need to check if it's instanceof Error to reject it or resolve
|
|
49
|
-
// Wrap as Error if it's not Error
|
|
50
|
-
cache.set(cacheKey, err instanceof Error ? err : new Error(err));
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.error(err);
|
|
51
48
|
}
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
54
51
|
}
|
|
55
|
-
|
|
52
|
+
finally {
|
|
56
53
|
if (logMiss) {
|
|
57
|
-
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
54
|
+
logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFn miss (${(0, time_util_1._since)(started)})`);
|
|
58
55
|
}
|
|
59
|
-
cache.set(cacheKey, res);
|
|
60
|
-
return res;
|
|
61
56
|
}
|
|
62
57
|
};
|
|
63
58
|
Object.assign(memoizedFn, { cache });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AsyncMemoOptions } from './asyncMemo.decorator';
|
|
2
|
+
import { AsyncMemoCache } from './memo.util';
|
|
3
|
+
export interface MemoizedAsyncFunction {
|
|
4
|
+
cache: AsyncMemoCache;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Only supports Sync functions.
|
|
8
|
+
* To support Async functions - use _memoFnAsync
|
|
9
|
+
*/
|
|
10
|
+
export declare function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(fn: T, opt?: AsyncMemoOptions): T & MemoizedAsyncFunction;
|