@naturalcycles/js-lib 14.84.0 → 14.85.1
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 +39 -34
- 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 -29
- package/dist/decorators/memo.decorator.js +42 -53
- 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/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 +2 -2
- package/dist/promise/pTimeout.d.ts +3 -2
- package/dist-esm/decorators/asyncMemo.decorator.js +40 -47
- 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 +41 -53
- 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 +88 -61
- package/src/decorators/decorator.util.ts +2 -2
- package/src/decorators/logMethod.decorator.ts +16 -7
- package/src/decorators/memo.decorator.ts +66 -102
- 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/error/assert.ts +4 -4
- package/src/error/error.model.ts +2 -0
- package/src/index.ts +1 -0
- package/src/promise/pRetry.ts +2 -2
- package/src/promise/pTimeout.ts +3 -2
|
@@ -0,0 +1,65 @@
|
|
|
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 = true, 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
|
+
if (value instanceof Error) {
|
|
27
|
+
throw value;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
const started = Date.now();
|
|
32
|
+
try {
|
|
33
|
+
value = await fn.apply(ctx, args);
|
|
34
|
+
void (async () => {
|
|
35
|
+
try {
|
|
36
|
+
await cache.set(cacheKey, value);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
logger.error(err);
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (cacheRejections) {
|
|
46
|
+
void (async () => {
|
|
47
|
+
try {
|
|
48
|
+
await cache.set(cacheKey, err);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logger.error(err);
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (logMiss) {
|
|
59
|
+
logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync miss (${_since(started)})`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
Object.assign(memoizedFn, { cache });
|
|
64
|
+
return memoizedFn;
|
|
65
|
+
}
|
|
@@ -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,54 @@
|
|
|
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
|
+
* Default true.
|
|
22
|
+
*
|
|
23
|
+
* Set to `false` to skip caching rejected promises (errors).
|
|
24
|
+
*
|
|
25
|
+
* True will ensure "max 1 execution", but will "remember" rejection.
|
|
26
|
+
* False will allow >1 execution in case of errors.
|
|
27
|
+
*/
|
|
28
|
+
cacheRejections?: boolean
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default to false
|
|
32
|
+
*/
|
|
33
|
+
logHit?: boolean
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default to false
|
|
37
|
+
*/
|
|
38
|
+
logMiss?: boolean
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Set to `false` to skip logging method arguments.
|
|
42
|
+
*
|
|
43
|
+
* Defaults to true.
|
|
44
|
+
*/
|
|
45
|
+
logArgs?: boolean
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default to `console`
|
|
49
|
+
*/
|
|
50
|
+
logger?: CommonLogger
|
|
51
|
+
}
|
|
23
52
|
|
|
24
53
|
/**
|
|
25
54
|
* Like @_Memo, but allowing async MemoCache implementation.
|
|
@@ -29,7 +58,7 @@ export type AsyncMemoOptions = Merge<
|
|
|
29
58
|
*/
|
|
30
59
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
31
60
|
export const _AsyncMemo =
|
|
32
|
-
(opt: AsyncMemoOptions): MethodDecorator =>
|
|
61
|
+
(opt: AsyncMemoOptions = {}): MethodDecorator =>
|
|
33
62
|
(target, key, descriptor) => {
|
|
34
63
|
if (typeof descriptor.value !== 'function') {
|
|
35
64
|
throw new TypeError('Memoization can be applied only to methods')
|
|
@@ -38,17 +67,16 @@ export const _AsyncMemo =
|
|
|
38
67
|
const originalFn = descriptor.value
|
|
39
68
|
|
|
40
69
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
41
|
-
const cache = new Map<AnyObject, AsyncMemoCache
|
|
70
|
+
const cache = new Map<AnyObject, AsyncMemoCache>()
|
|
42
71
|
|
|
43
72
|
const {
|
|
44
73
|
logHit = false,
|
|
45
74
|
logMiss = false,
|
|
46
|
-
|
|
75
|
+
logArgs = true,
|
|
47
76
|
logger = console,
|
|
48
|
-
cacheFactory,
|
|
77
|
+
cacheFactory = () => new MapMemoCache(),
|
|
49
78
|
cacheKeyFn = jsonMemoSerializer,
|
|
50
|
-
|
|
51
|
-
noCacheResolved = false,
|
|
79
|
+
cacheRejections = true,
|
|
52
80
|
} = opt
|
|
53
81
|
|
|
54
82
|
const keyStr = String(key)
|
|
@@ -65,29 +93,10 @@ export const _AsyncMemo =
|
|
|
65
93
|
// UPD: no! AsyncMemo supports "persistent caches" (e.g Database-backed cache)
|
|
66
94
|
}
|
|
67
95
|
|
|
68
|
-
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
69
|
-
// Special event - CACHE_DROP
|
|
70
|
-
// Function will return undefined
|
|
71
|
-
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`)
|
|
72
|
-
try {
|
|
73
|
-
await Promise.all(cache.get(ctx)!.map(c => c.clear()))
|
|
74
|
-
} catch (err) {
|
|
75
|
-
logger.error(err)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
|
|
81
96
|
let value: any
|
|
82
97
|
|
|
83
98
|
try {
|
|
84
|
-
|
|
85
|
-
value = await cacheLayer.get(cacheKey)
|
|
86
|
-
if (value !== undefined) {
|
|
87
|
-
// it's a hit!
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
}
|
|
99
|
+
value = await cache.get(ctx)!.get(cacheKey)
|
|
91
100
|
} catch (err) {
|
|
92
101
|
// log error, but don't throw, treat it as a "miss"
|
|
93
102
|
logger.error(err)
|
|
@@ -99,12 +108,16 @@ export const _AsyncMemo =
|
|
|
99
108
|
logger.log(
|
|
100
109
|
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
101
110
|
args,
|
|
102
|
-
|
|
111
|
+
logArgs,
|
|
103
112
|
)}) @_AsyncMemo hit`,
|
|
104
113
|
)
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
if (value instanceof Error) {
|
|
117
|
+
throw value
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return value
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
// Here we know it's a MISS, let's execute the real method
|
|
@@ -113,25 +126,30 @@ export const _AsyncMemo =
|
|
|
113
126
|
try {
|
|
114
127
|
value = await originalFn.apply(ctx, args)
|
|
115
128
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
// Save the value in the Cache, without awaiting it
|
|
130
|
+
// This is to support both sync and async functions
|
|
131
|
+
void (async () => {
|
|
132
|
+
try {
|
|
133
|
+
await cache.get(ctx)!.set(cacheKey, value)
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// log and ignore the error
|
|
136
|
+
logger.error(err)
|
|
137
|
+
}
|
|
138
|
+
})()
|
|
124
139
|
|
|
125
140
|
return value
|
|
126
141
|
} catch (err) {
|
|
127
|
-
if (
|
|
142
|
+
if (cacheRejections) {
|
|
128
143
|
// We put it to cache as raw Error, not Promise.reject(err)
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
// This is to support both sync and async functions
|
|
145
|
+
void (async () => {
|
|
146
|
+
try {
|
|
147
|
+
await cache.get(ctx)!.set(cacheKey, err)
|
|
148
|
+
} catch (err) {
|
|
131
149
|
// log and ignore the error
|
|
132
150
|
logger.error(err)
|
|
133
|
-
}
|
|
134
|
-
)
|
|
151
|
+
}
|
|
152
|
+
})()
|
|
135
153
|
}
|
|
136
154
|
|
|
137
155
|
throw err
|
|
@@ -140,12 +158,21 @@ export const _AsyncMemo =
|
|
|
140
158
|
logger.log(
|
|
141
159
|
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
142
160
|
args,
|
|
143
|
-
|
|
161
|
+
logArgs,
|
|
144
162
|
)}) @_AsyncMemo miss (${_since(started)})`,
|
|
145
163
|
)
|
|
146
164
|
}
|
|
147
165
|
}
|
|
148
166
|
} as any
|
|
167
|
+
;(descriptor.value as any).dropCache = async () => {
|
|
168
|
+
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`)
|
|
169
|
+
try {
|
|
170
|
+
await Promise.all([...cache.values()].map(c => c.clear()))
|
|
171
|
+
cache.clear()
|
|
172
|
+
} catch (err) {
|
|
173
|
+
logger.error(err)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
149
176
|
|
|
150
177
|
return descriptor
|
|
151
178
|
}
|
|
@@ -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 => {
|
|
@@ -16,14 +16,16 @@ export interface LogMethodOptions {
|
|
|
16
16
|
avg?: number
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Defaults to true.
|
|
20
|
+
* Set to false to skip logging method arguments
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
+
logArgs?: boolean
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
+
* Defaults to true.
|
|
26
|
+
* Set to false to skip logging result length when result is an array.
|
|
25
27
|
*/
|
|
26
|
-
|
|
28
|
+
logResultLength?: boolean
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Also log on method start.
|
|
@@ -74,12 +76,19 @@ export function _LogMethod(opt: LogMethodOptions = {}): MethodDecorator {
|
|
|
74
76
|
const originalFn = descriptor.value
|
|
75
77
|
const keyStr = String(key)
|
|
76
78
|
|
|
77
|
-
const {
|
|
79
|
+
const {
|
|
80
|
+
avg,
|
|
81
|
+
logArgs = true,
|
|
82
|
+
logStart,
|
|
83
|
+
logResult,
|
|
84
|
+
logResultLength = true,
|
|
85
|
+
logger = console,
|
|
86
|
+
} = opt
|
|
78
87
|
let { logResultFn } = opt
|
|
79
88
|
if (!logResultFn) {
|
|
80
89
|
if (logResult) {
|
|
81
90
|
logResultFn = r => ['result:', _stringifyAny(r)]
|
|
82
|
-
} else if (
|
|
91
|
+
} else if (logResultLength) {
|
|
83
92
|
logResultFn = r => (Array.isArray(r) ? [`result: ${r.length} items`] : [])
|
|
84
93
|
}
|
|
85
94
|
}
|
|
@@ -94,7 +103,7 @@ export function _LogMethod(opt: LogMethodOptions = {}): MethodDecorator {
|
|
|
94
103
|
// e.g `NameOfYourClass.methodName`
|
|
95
104
|
// or `NameOfYourClass(instanceId).methodName`
|
|
96
105
|
const methodSignature = _getMethodSignature(ctx, keyStr)
|
|
97
|
-
const argsStr = _getArgsSignature(args,
|
|
106
|
+
const argsStr = _getArgsSignature(args, logArgs)
|
|
98
107
|
const callSignature = `${methodSignature}(${argsStr}) #${++count}`
|
|
99
108
|
if (logStart) logger.log(`>> ${callSignature}`)
|
|
100
109
|
|
|
@@ -1,67 +1,50 @@
|
|
|
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
|
-
|
|
7
1
|
import { CommonLogger } from '../log/commonLogger'
|
|
8
2
|
import { _since } from '../time/time.util'
|
|
9
3
|
import { AnyObject } from '../types'
|
|
10
4
|
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util'
|
|
11
5
|
import { jsonMemoSerializer, MapMemoCache, MemoCache } from './memo.util'
|
|
12
6
|
|
|
13
|
-
/**
|
|
14
|
-
* Symbol to indicate that the Cache should be dropped.
|
|
15
|
-
*/
|
|
16
|
-
export const CACHE_DROP = Symbol('CACHE_DROP')
|
|
17
|
-
|
|
18
7
|
export interface MemoOptions {
|
|
19
8
|
/**
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Default to false
|
|
9
|
+
* Provide a custom implementation of MemoCache.
|
|
10
|
+
* Function that creates an instance of `MemoCache`.
|
|
11
|
+
* e.g LRUMemoCache from `@naturalcycles/nodejs-lib`
|
|
25
12
|
*/
|
|
26
|
-
|
|
13
|
+
cacheFactory?: () => MemoCache
|
|
27
14
|
|
|
28
15
|
/**
|
|
29
|
-
*
|
|
16
|
+
* Provide a custom implementation of CacheKey function.
|
|
30
17
|
*/
|
|
31
|
-
|
|
18
|
+
cacheKeyFn?: (args: any[]) => any
|
|
32
19
|
|
|
33
20
|
/**
|
|
34
|
-
*
|
|
21
|
+
* Defaults to true.
|
|
22
|
+
* Set to false to skip caching errors.
|
|
23
|
+
*
|
|
24
|
+
* True will ensure "max 1 execution", but will "remember" errors.
|
|
25
|
+
* False will allow >1 execution in case of errors.
|
|
35
26
|
*/
|
|
36
|
-
|
|
27
|
+
cacheErrors?: boolean
|
|
37
28
|
|
|
38
29
|
/**
|
|
39
|
-
*
|
|
40
|
-
* Function that creates an instance of `MemoCache`.
|
|
41
|
-
* e.g LRUMemoCache from `@naturalcycles/nodejs-lib`
|
|
30
|
+
* Default to false
|
|
42
31
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
logHit?: boolean
|
|
45
33
|
/**
|
|
46
|
-
*
|
|
34
|
+
* Default to false
|
|
47
35
|
*/
|
|
48
|
-
|
|
36
|
+
logMiss?: boolean
|
|
49
37
|
|
|
50
38
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* Default false.
|
|
39
|
+
* Defaults to true.
|
|
40
|
+
* Set to false to skip logging method arguments.
|
|
55
41
|
*/
|
|
56
|
-
|
|
42
|
+
logArgs?: boolean
|
|
57
43
|
|
|
58
44
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Setting this to `true` will make the decorator to await the result.
|
|
61
|
-
*
|
|
62
|
-
* Default false.
|
|
45
|
+
* Default to `console`
|
|
63
46
|
*/
|
|
64
|
-
|
|
47
|
+
logger?: CommonLogger
|
|
65
48
|
}
|
|
66
49
|
|
|
67
50
|
/**
|
|
@@ -73,6 +56,15 @@ export interface MemoOptions {
|
|
|
73
56
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
74
57
|
*
|
|
75
58
|
* Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
|
|
59
|
+
*
|
|
60
|
+
* Doesn't support Async functions, use @_AsyncMemo instead!
|
|
61
|
+
* (or, it will simply return the [unresolved] Promise further, without awaiting it)
|
|
62
|
+
*
|
|
63
|
+
* Based on:
|
|
64
|
+
* https://github.com/mgechev/memo-decorator/blob/master/index.ts
|
|
65
|
+
* http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
|
|
66
|
+
* http://inlehmansterms.net/2015/03/01/javascript-memoization/
|
|
67
|
+
* https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
|
|
76
68
|
*/
|
|
77
69
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
78
70
|
export const _Memo =
|
|
@@ -96,105 +88,77 @@ export const _Memo =
|
|
|
96
88
|
const {
|
|
97
89
|
logHit = false,
|
|
98
90
|
logMiss = false,
|
|
99
|
-
|
|
91
|
+
logArgs = true,
|
|
100
92
|
logger = console,
|
|
101
93
|
cacheFactory = () => new MapMemoCache(),
|
|
102
94
|
cacheKeyFn = jsonMemoSerializer,
|
|
103
|
-
|
|
104
|
-
noCacheResolved = false,
|
|
95
|
+
cacheErrors = true,
|
|
105
96
|
} = opt
|
|
106
97
|
|
|
107
|
-
const awaitPromise = Boolean(noCacheRejected || noCacheResolved)
|
|
108
98
|
const keyStr = String(key)
|
|
109
99
|
const methodSignature = _getTargetMethodSignature(target, keyStr)
|
|
110
100
|
|
|
111
101
|
descriptor.value = function (this: typeof target, ...args: any[]): any {
|
|
112
102
|
const ctx = this
|
|
113
|
-
|
|
114
|
-
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
115
|
-
// Special event - CACHE_DROP
|
|
116
|
-
// Function will return undefined
|
|
117
|
-
logger.log(`${methodSignature} @_Memo.CACHE_DROP`)
|
|
118
|
-
return cache.get(ctx)?.clear()
|
|
119
|
-
}
|
|
120
|
-
|
|
121
103
|
const cacheKey = cacheKeyFn(args)
|
|
104
|
+
let value: any
|
|
122
105
|
|
|
123
106
|
if (!cache.has(ctx)) {
|
|
124
107
|
cache.set(ctx, cacheFactory())
|
|
125
108
|
} else if (cache.get(ctx)!.has(cacheKey)) {
|
|
126
109
|
if (logHit) {
|
|
127
110
|
logger.log(
|
|
128
|
-
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args,
|
|
111
|
+
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`,
|
|
129
112
|
)
|
|
130
113
|
}
|
|
131
114
|
|
|
132
|
-
|
|
115
|
+
value = cache.get(ctx)!.get(cacheKey)
|
|
133
116
|
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
} else {
|
|
137
|
-
return res
|
|
117
|
+
if (value instanceof Error) {
|
|
118
|
+
throw value
|
|
138
119
|
}
|
|
120
|
+
|
|
121
|
+
return value
|
|
139
122
|
}
|
|
140
123
|
|
|
141
124
|
const started = Date.now()
|
|
142
125
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.catch(err => {
|
|
165
|
-
// console.log('REJECTED', err)
|
|
166
|
-
if (logMiss) {
|
|
167
|
-
logger.log(
|
|
168
|
-
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
169
|
-
args,
|
|
170
|
-
noLogArgs,
|
|
171
|
-
)}) @_Memo miss rejected (${_since(started)})`,
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!noCacheRejected) {
|
|
176
|
-
// We put it to cache as raw Error, not Promise.reject(err)
|
|
177
|
-
// So, we'll need to check if it's instanceof Error to reject it or resolve
|
|
178
|
-
// Wrap as Error if it's not Error
|
|
179
|
-
cache.get(ctx)!.set(cacheKey, err instanceof Error ? err : new Error(err))
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
throw err
|
|
183
|
-
})
|
|
184
|
-
} else {
|
|
126
|
+
try {
|
|
127
|
+
value = originalFn.apply(ctx, args)
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
cache.get(ctx)!.set(cacheKey, value)
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logger.error(err)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return value
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (cacheErrors) {
|
|
138
|
+
try {
|
|
139
|
+
cache.get(ctx)!.set(cacheKey, err)
|
|
140
|
+
} catch (err) {
|
|
141
|
+
logger.error(err)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw err
|
|
146
|
+
} finally {
|
|
185
147
|
if (logMiss) {
|
|
186
148
|
logger.log(
|
|
187
149
|
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
188
150
|
args,
|
|
189
|
-
|
|
151
|
+
logArgs,
|
|
190
152
|
)}) @_Memo miss (${_since(started)})`,
|
|
191
153
|
)
|
|
192
154
|
}
|
|
193
|
-
|
|
194
|
-
cache.get(ctx)!.set(cacheKey, res)
|
|
195
|
-
return res
|
|
196
155
|
}
|
|
197
156
|
} as any
|
|
157
|
+
;(descriptor.value as any).dropCache = () => {
|
|
158
|
+
logger.log(`${methodSignature} @_Memo.dropCache()`)
|
|
159
|
+
cache.forEach(memoCache => memoCache.clear())
|
|
160
|
+
cache.clear()
|
|
161
|
+
}
|
|
198
162
|
|
|
199
163
|
return descriptor
|
|
200
164
|
}
|
|
@@ -11,8 +11,8 @@ export const jsonMemoSerializer: MemoSerializer = args => {
|
|
|
11
11
|
|
|
12
12
|
export interface MemoCache<KEY = any, VALUE = any> {
|
|
13
13
|
has(k: KEY): boolean
|
|
14
|
-
get(k: KEY): VALUE | undefined
|
|
15
|
-
set(k: KEY, v: VALUE): void
|
|
14
|
+
get(k: KEY): VALUE | Error | undefined
|
|
15
|
+
set(k: KEY, v: VALUE | Error): void
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Clear is only called when `.dropCache()` is called.
|
|
@@ -29,8 +29,8 @@ export interface AsyncMemoCache<KEY = any, VALUE = any> {
|
|
|
29
29
|
* This also means that you CANNOT store `undefined` value in the Cache, as it'll be treated as a MISS.
|
|
30
30
|
* You CAN store `null` value instead, it will be treated as a HIT.
|
|
31
31
|
*/
|
|
32
|
-
get(k: KEY): Promisable<VALUE | undefined>
|
|
33
|
-
set(k: KEY, v: VALUE): Promisable<void>
|
|
32
|
+
get(k: KEY): Promisable<VALUE | Error | undefined>
|
|
33
|
+
set(k: KEY, v: VALUE | Error): Promisable<void>
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Clear is only called when `.dropCache()` is called.
|
|
@@ -88,17 +88,17 @@ export class ObjectMemoCache implements MemoCache {
|
|
|
88
88
|
export class MapMemoCache<KEY = any, VALUE = any>
|
|
89
89
|
implements MemoCache<KEY, VALUE>, AsyncMemoCache<KEY, VALUE>
|
|
90
90
|
{
|
|
91
|
-
private m = new Map<KEY, VALUE>()
|
|
91
|
+
private m = new Map<KEY, VALUE | Error>()
|
|
92
92
|
|
|
93
93
|
has(k: KEY): boolean {
|
|
94
94
|
return this.m.has(k)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
get(k: KEY): VALUE | undefined {
|
|
97
|
+
get(k: KEY): VALUE | Error | undefined {
|
|
98
98
|
return this.m.get(k)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
set(k: KEY, v: VALUE): void {
|
|
101
|
+
set(k: KEY, v: VALUE | Error): void {
|
|
102
102
|
this.m.set(k, v)
|
|
103
103
|
}
|
|
104
104
|
|