@naturalcycles/js-lib 14.83.1 → 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 +35 -33
- 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 -30
- package/dist/decorators/memo.decorator.js +31 -56
- 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/error/assert.d.ts +4 -4
- package/dist/error/error.model.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/promise/pRetry.d.ts +5 -1
- package/dist/promise/pRetry.js +6 -1
- package/dist/promise/pTimeout.d.ts +5 -0
- package/dist/promise/pTimeout.js +5 -1
- package/dist-esm/decorators/asyncMemo.decorator.js +36 -46
- 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 +30 -56
- 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/dist-esm/promise/pRetry.js +3 -1
- package/dist-esm/promise/pTimeout.js +2 -1
- package/package.json +1 -1
- package/src/decorators/asyncMemo.decorator.ts +80 -60
- package/src/decorators/decorator.util.ts +2 -2
- package/src/decorators/logMethod.decorator.ts +16 -7
- package/src/decorators/memo.decorator.ts +51 -103
- 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/error/assert.ts +4 -4
- package/src/error/error.model.ts +2 -0
- package/src/index.ts +1 -0
- package/src/promise/pRetry.ts +12 -2
- package/src/promise/pTimeout.ts +14 -1
|
@@ -37,7 +37,7 @@ const memoSimple = (opt = {}) => (target, key, descriptor) => {
|
|
|
37
37
|
}
|
|
38
38
|
*/
|
|
39
39
|
const cache = new memo_util_1.MapMemoCache();
|
|
40
|
-
const { logHit, logMiss,
|
|
40
|
+
const { logHit, logMiss, logArgs = true, 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) {
|
|
@@ -45,14 +45,14 @@ const memoSimple = (opt = {}) => (target, key, descriptor) => {
|
|
|
45
45
|
const cacheKey = (0, memo_util_1.jsonMemoSerializer)(args);
|
|
46
46
|
if (cache.has(cacheKey)) {
|
|
47
47
|
if (logHit) {
|
|
48
|
-
logger.log(`${methodSignature}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
48
|
+
logger.log(`${methodSignature}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @memo hit`);
|
|
49
49
|
}
|
|
50
50
|
return cache.get(cacheKey);
|
|
51
51
|
}
|
|
52
52
|
const d = Date.now();
|
|
53
53
|
const res = originalFn.apply(ctx, args);
|
|
54
54
|
if (logMiss) {
|
|
55
|
-
logger.log(`${methodSignature}(${(0, decorator_util_1._getArgsSignature)(args,
|
|
55
|
+
logger.log(`${methodSignature}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @memo miss (${Date.now() - d} ms)`);
|
|
56
56
|
}
|
|
57
57
|
cache.set(cacheKey, res);
|
|
58
58
|
return res;
|
package/dist/error/assert.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ErrorData
|
|
1
|
+
import { ErrorData } from '..';
|
|
2
2
|
import { AppError } from './app.error';
|
|
3
3
|
/**
|
|
4
4
|
* Evaluates the `condition` (casts it to Boolean).
|
|
@@ -16,21 +16,21 @@ import { AppError } from './app.error';
|
|
|
16
16
|
* 3. Sets `userFriendly` flag to true, cause it's always better to have at least SOME clue, rather than fully generic "Oops" error.
|
|
17
17
|
*/
|
|
18
18
|
export declare function _assert(condition: any, // will be evaluated as Boolean
|
|
19
|
-
message?: string, errorData?:
|
|
19
|
+
message?: string, errorData?: ErrorData): asserts condition;
|
|
20
20
|
/**
|
|
21
21
|
* Like _assert(), but prints more helpful error message.
|
|
22
22
|
* API is similar to Node's assert.equals().
|
|
23
23
|
*
|
|
24
24
|
* Does SHALLOW, but strict equality (===), use _assertDeepEquals() for deep equality.
|
|
25
25
|
*/
|
|
26
|
-
export declare function _assertEquals<T>(actual: any, expected: T, message?: string, errorData?:
|
|
26
|
+
export declare function _assertEquals<T>(actual: any, expected: T, message?: string, errorData?: ErrorData): asserts actual is T;
|
|
27
27
|
/**
|
|
28
28
|
* Like _assert(), but prints more helpful error message.
|
|
29
29
|
* API is similar to Node's assert.deepEquals().
|
|
30
30
|
*
|
|
31
31
|
* Does DEEP equality via _deepEquals()
|
|
32
32
|
*/
|
|
33
|
-
export declare function _assertDeepEquals<T>(actual: any, expected: T, message?: string, errorData?:
|
|
33
|
+
export declare function _assertDeepEquals<T>(actual: any, expected: T, message?: string, errorData?: ErrorData): asserts actual is T;
|
|
34
34
|
export declare function _assertIsError<ERR extends Error = Error>(err: any, message?: string): asserts err is ERR;
|
|
35
35
|
export declare function _assertIsString(v: any, message?: string): asserts v is string;
|
|
36
36
|
export declare function _assertIsNumber(v: any, message?: string): asserts v is number;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './decorators/memo.decorator';
|
|
|
11
11
|
export * from './decorators/asyncMemo.decorator';
|
|
12
12
|
import { MemoCache, AsyncMemoCache } from './decorators/memo.util';
|
|
13
13
|
export * from './decorators/memoFn';
|
|
14
|
+
export * from './decorators/memoFnAsync';
|
|
14
15
|
export * from './decorators/retry.decorator';
|
|
15
16
|
export * from './decorators/timeout.decorator';
|
|
16
17
|
export * from './error/app.error';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "_createPromiseDecorator", { enumerable: true, ge
|
|
|
15
15
|
(0, tslib_1.__exportStar)(require("./decorators/memo.decorator"), exports);
|
|
16
16
|
(0, tslib_1.__exportStar)(require("./decorators/asyncMemo.decorator"), exports);
|
|
17
17
|
(0, tslib_1.__exportStar)(require("./decorators/memoFn"), exports);
|
|
18
|
+
(0, tslib_1.__exportStar)(require("./decorators/memoFnAsync"), exports);
|
|
18
19
|
(0, tslib_1.__exportStar)(require("./decorators/retry.decorator"), exports);
|
|
19
20
|
(0, tslib_1.__exportStar)(require("./decorators/timeout.decorator"), exports);
|
|
20
21
|
(0, tslib_1.__exportStar)(require("./error/app.error"), exports);
|
package/dist/promise/pRetry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyFunction, CommonLogger } from '..';
|
|
1
|
+
import { AnyFunction, CommonLogger, ErrorData } from '..';
|
|
2
2
|
export interface PRetryOptions {
|
|
3
3
|
/**
|
|
4
4
|
* If set - will be included in the error message.
|
|
@@ -76,6 +76,10 @@ export interface PRetryOptions {
|
|
|
76
76
|
* @experimental
|
|
77
77
|
*/
|
|
78
78
|
keepStackTrace?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Will be merged with `err.data` object.
|
|
81
|
+
*/
|
|
82
|
+
errorData?: ErrorData;
|
|
79
83
|
}
|
|
80
84
|
/**
|
|
81
85
|
* Returns a Function (!), enhanced with retry capabilities.
|
package/dist/promise/pRetry.js
CHANGED
|
@@ -31,7 +31,7 @@ async function pRetry(fn, opt = {}) {
|
|
|
31
31
|
return await new Promise((resolve, reject) => {
|
|
32
32
|
const rejectWithTimeout = () => {
|
|
33
33
|
timedOut = true; // to prevent more tries
|
|
34
|
-
const err = new pTimeout_1.TimeoutError(`"${fname}" timed out after ${timeout} ms
|
|
34
|
+
const err = new pTimeout_1.TimeoutError(`"${fname}" timed out after ${timeout} ms`, opt.errorData);
|
|
35
35
|
if (fakeError) {
|
|
36
36
|
// keep original stack
|
|
37
37
|
err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
|
|
@@ -75,6 +75,11 @@ async function pRetry(fn, opt = {}) {
|
|
|
75
75
|
fakeError.stack.replace('Error: RetryError', ''),
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
+
;
|
|
79
|
+
err.data = {
|
|
80
|
+
...err.data,
|
|
81
|
+
...opt.errorData,
|
|
82
|
+
};
|
|
78
83
|
reject(err);
|
|
79
84
|
}
|
|
80
85
|
else {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AppError } from '../error/app.error';
|
|
2
|
+
import { ErrorData } from '../error/error.model';
|
|
2
3
|
import { AnyFunction } from '../types';
|
|
3
4
|
export declare class TimeoutError extends AppError {
|
|
4
5
|
}
|
|
@@ -25,6 +26,10 @@ export interface PTimeoutOptions {
|
|
|
25
26
|
* @experimental
|
|
26
27
|
*/
|
|
27
28
|
keepStackTrace?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Will be merged with `err.data` object.
|
|
31
|
+
*/
|
|
32
|
+
errorData?: ErrorData;
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
30
35
|
* Decorates a Function with a timeout.
|
package/dist/promise/pTimeout.js
CHANGED
|
@@ -37,11 +37,15 @@ async function pTimeout(promise, opt) {
|
|
|
37
37
|
catch (err) {
|
|
38
38
|
if (fakeError)
|
|
39
39
|
err.stack = fakeError.stack; // keep original stack
|
|
40
|
+
err.data = {
|
|
41
|
+
...err.data,
|
|
42
|
+
...opt.errorData,
|
|
43
|
+
};
|
|
40
44
|
reject(err);
|
|
41
45
|
}
|
|
42
46
|
return;
|
|
43
47
|
}
|
|
44
|
-
const err = new TimeoutError(`"${name || 'pTimeout function'}" timed out after ${timeout} ms
|
|
48
|
+
const err = new TimeoutError(`"${name || 'pTimeout function'}" timed out after ${timeout} ms`, opt.errorData);
|
|
45
49
|
if (fakeError)
|
|
46
50
|
err.stack = fakeError.stack; // keep original stack
|
|
47
51
|
reject(err);
|
|
@@ -1,8 +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 {
|
|
5
|
-
import { jsonMemoSerializer } from './memo.util';
|
|
3
|
+
import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
6
4
|
/**
|
|
7
5
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
8
6
|
*
|
|
@@ -10,18 +8,17 @@ import { jsonMemoSerializer } from './memo.util';
|
|
|
10
8
|
* Return `null` instead (it'll be cached).
|
|
11
9
|
*/
|
|
12
10
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
13
|
-
export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
11
|
+
export const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
14
12
|
if (typeof descriptor.value !== 'function') {
|
|
15
13
|
throw new TypeError('Memoization can be applied only to methods');
|
|
16
14
|
}
|
|
17
15
|
const originalFn = descriptor.value;
|
|
18
16
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
19
17
|
const cache = new Map();
|
|
20
|
-
const { logHit = false, logMiss = false,
|
|
18
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheRejections = false, } = opt;
|
|
21
19
|
const keyStr = String(key);
|
|
22
20
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
23
21
|
descriptor.value = async function (...args) {
|
|
24
|
-
var e_1, _a;
|
|
25
22
|
const ctx = this;
|
|
26
23
|
const cacheKey = cacheKeyFn(args);
|
|
27
24
|
if (!cache.has(ctx)) {
|
|
@@ -29,37 +26,9 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
29
26
|
// here, no need to check the cache. It's definitely a miss, because the cacheLayers is just created
|
|
30
27
|
// UPD: no! AsyncMemo supports "persistent caches" (e.g Database-backed cache)
|
|
31
28
|
}
|
|
32
|
-
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
33
|
-
// Special event - CACHE_DROP
|
|
34
|
-
// Function will return undefined
|
|
35
|
-
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
|
|
36
|
-
try {
|
|
37
|
-
await Promise.all(cache.get(ctx).map(c => c.clear()));
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
logger.error(err);
|
|
41
|
-
}
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
29
|
let value;
|
|
45
30
|
try {
|
|
46
|
-
|
|
47
|
-
for (var _b = __asyncValues(cache.get(ctx)), _c; _c = await _b.next(), !_c.done;) {
|
|
48
|
-
const cacheLayer = _c.value;
|
|
49
|
-
value = await cacheLayer.get(cacheKey);
|
|
50
|
-
if (value !== undefined) {
|
|
51
|
-
// it's a hit!
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
57
|
-
finally {
|
|
58
|
-
try {
|
|
59
|
-
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
|
|
60
|
-
}
|
|
61
|
-
finally { if (e_1) throw e_1.error; }
|
|
62
|
-
}
|
|
31
|
+
value = await cache.get(ctx).get(cacheKey);
|
|
63
32
|
}
|
|
64
33
|
catch (err) {
|
|
65
34
|
// log error, but don't throw, treat it as a "miss"
|
|
@@ -68,7 +37,7 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
68
37
|
if (value !== undefined) {
|
|
69
38
|
// hit!
|
|
70
39
|
if (logHit) {
|
|
71
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
40
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_AsyncMemo hit`);
|
|
72
41
|
}
|
|
73
42
|
return value instanceof Error ? Promise.reject(value) : Promise.resolve(value);
|
|
74
43
|
}
|
|
@@ -76,29 +45,50 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
76
45
|
const started = Date.now();
|
|
77
46
|
try {
|
|
78
47
|
value = await originalFn.apply(ctx, args);
|
|
79
|
-
|
|
80
|
-
|
|
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) {
|
|
81
55
|
// log and ignore the error
|
|
82
56
|
logger.error(err);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
85
59
|
return value;
|
|
86
60
|
}
|
|
87
61
|
catch (err) {
|
|
88
|
-
if (
|
|
62
|
+
if (cacheRejections) {
|
|
89
63
|
// We put it to cache as raw Error, not Promise.reject(err)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
})();
|
|
94
74
|
}
|
|
95
75
|
throw err;
|
|
96
76
|
}
|
|
97
77
|
finally {
|
|
98
78
|
if (logMiss) {
|
|
99
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
79
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_AsyncMemo miss (${_since(started)})`);
|
|
100
80
|
}
|
|
101
81
|
}
|
|
102
82
|
};
|
|
83
|
+
descriptor.value.dropCache = async () => {
|
|
84
|
+
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
|
|
85
|
+
try {
|
|
86
|
+
await Promise.all([...cache.values()].map(c => c.clear()));
|
|
87
|
+
cache.clear();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
logger.error(err);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
103
93
|
return descriptor;
|
|
104
94
|
};
|
|
@@ -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,15 +1,6 @@
|
|
|
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';
|
|
9
|
-
/**
|
|
10
|
-
* Symbol to indicate that the Cache should be dropped.
|
|
11
|
-
*/
|
|
12
|
-
export const CACHE_DROP = Symbol('CACHE_DROP');
|
|
13
4
|
/**
|
|
14
5
|
* Memoizes the method of the class, so it caches the output and returns the cached version if the "key"
|
|
15
6
|
* of the cache is the same. Key, by defaul, is calculated as `JSON.stringify(...args)`.
|
|
@@ -19,6 +10,15 @@ export const CACHE_DROP = Symbol('CACHE_DROP');
|
|
|
19
10
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
20
11
|
*
|
|
21
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/
|
|
22
22
|
*/
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
24
24
|
export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
@@ -34,70 +34,44 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
34
34
|
// UPD: tests show that normal Map also doesn't leak (to be tested further)
|
|
35
35
|
// Normal Map is needed to allow .dropCache()
|
|
36
36
|
const cache = new Map();
|
|
37
|
-
const { logHit = false, logMiss = false,
|
|
38
|
-
const awaitPromise = Boolean(noCacheRejected || noCacheResolved);
|
|
37
|
+
const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheErrors = false, } = opt;
|
|
39
38
|
const keyStr = String(key);
|
|
40
39
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
41
40
|
descriptor.value = function (...args) {
|
|
42
|
-
var _a;
|
|
43
41
|
const ctx = this;
|
|
44
|
-
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
45
|
-
// Special event - CACHE_DROP
|
|
46
|
-
// Function will return undefined
|
|
47
|
-
logger.log(`${methodSignature} @_Memo.CACHE_DROP`);
|
|
48
|
-
return (_a = cache.get(ctx)) === null || _a === void 0 ? void 0 : _a.clear();
|
|
49
|
-
}
|
|
50
42
|
const cacheKey = cacheKeyFn(args);
|
|
51
43
|
if (!cache.has(ctx)) {
|
|
52
44
|
cache.set(ctx, cacheFactory());
|
|
53
45
|
}
|
|
54
46
|
else if (cache.get(ctx).has(cacheKey)) {
|
|
55
47
|
if (logHit) {
|
|
56
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
57
|
-
}
|
|
58
|
-
const res = cache.get(ctx).get(cacheKey);
|
|
59
|
-
if (awaitPromise) {
|
|
60
|
-
return res instanceof Error ? Promise.reject(res) : Promise.resolve(res);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
return res;
|
|
48
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`);
|
|
64
49
|
}
|
|
50
|
+
return cache.get(ctx).get(cacheKey);
|
|
65
51
|
}
|
|
66
52
|
const started = Date.now();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (logMiss) {
|
|
73
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_Memo miss resolved (${_since(started)})`);
|
|
74
|
-
}
|
|
75
|
-
if (!noCacheResolved) {
|
|
76
|
-
cache.get(ctx).set(cacheKey, res);
|
|
77
|
-
}
|
|
78
|
-
return res;
|
|
79
|
-
})
|
|
80
|
-
.catch(err => {
|
|
81
|
-
// console.log('REJECTED', err)
|
|
82
|
-
if (logMiss) {
|
|
83
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_Memo miss rejected (${_since(started)})`);
|
|
84
|
-
}
|
|
85
|
-
if (!noCacheRejected) {
|
|
86
|
-
// We put it to cache as raw Error, not Promise.reject(err)
|
|
87
|
-
// So, we'll need to check if it's instanceof Error to reject it or resolve
|
|
88
|
-
// Wrap as Error if it's not Error
|
|
89
|
-
cache.get(ctx).set(cacheKey, err instanceof Error ? err : new Error(err));
|
|
90
|
-
}
|
|
91
|
-
throw err;
|
|
92
|
-
});
|
|
53
|
+
let value;
|
|
54
|
+
try {
|
|
55
|
+
value = originalFn.apply(ctx, args);
|
|
56
|
+
cache.get(ctx).set(cacheKey, value);
|
|
57
|
+
return value;
|
|
93
58
|
}
|
|
94
|
-
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (cacheErrors) {
|
|
61
|
+
cache.get(ctx).set(cacheKey, err);
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
95
66
|
if (logMiss) {
|
|
96
|
-
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
67
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo miss (${_since(started)})`);
|
|
97
68
|
}
|
|
98
|
-
cache.get(ctx).set(cacheKey, res);
|
|
99
|
-
return res;
|
|
100
69
|
}
|
|
101
70
|
};
|
|
71
|
+
descriptor.value.dropCache = () => {
|
|
72
|
+
logger.log(`${methodSignature} @_Memo.dropCache()`);
|
|
73
|
+
cache.forEach(memoCache => memoCache.clear());
|
|
74
|
+
cache.clear();
|
|
75
|
+
};
|
|
102
76
|
return descriptor;
|
|
103
77
|
};
|
|
@@ -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';
|