@naturalcycles/js-lib 15.98.1 → 15.99.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 +2 -3
- package/dist/decorators/asyncMemo.decorator.js +2 -3
- package/dist/decorators/memo.decorator.d.ts +4 -3
- package/dist/decorators/memo.decorator.js +18 -3
- package/dist/decorators/memo.util.d.ts +5 -2
- package/dist/decorators/memo.util.js +7 -2
- package/dist/decorators/memoized.js +14 -0
- package/package.json +1 -1
- package/src/decorators/asyncMemo.decorator.ts +2 -3
- package/src/decorators/memo.decorator.ts +17 -3
- package/src/decorators/memo.util.ts +11 -3
- package/src/decorators/memoized.ts +13 -0
|
@@ -31,10 +31,9 @@ export interface AsyncMemoInstance {
|
|
|
31
31
|
/**
|
|
32
32
|
* Like `@_Memo`, but awaits the method and caches the resolved **value**, not the Promise:
|
|
33
33
|
*
|
|
34
|
-
* - A rejection is NOT cached, so the next call retries the method.
|
|
35
|
-
* `@_Memo` caches the Promise as-is, so it serves a rejection to every caller until the key is evicted.
|
|
36
34
|
* - The cache is async (`AsyncMemoCache`), so it can be backed by e.g Redis or Datastore
|
|
37
|
-
* and survive a process restart.
|
|
35
|
+
* and survive a process restart. That's the reason to use it over `@_Memo`.
|
|
36
|
+
* - A rejection is NOT cached, so the next call retries the method (same as `@_Memo`).
|
|
38
37
|
* - Parallel calls with the same key share one in-flight Promise, also while the cache lookup is pending.
|
|
39
38
|
*
|
|
40
39
|
* Cache errors (`get`/`set`) are logged and treated as a cache miss, they never fail the method.
|
|
@@ -6,10 +6,9 @@ import { jsonMemoSerializer, MapAsyncMemoCache } from './memo.util.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Like `@_Memo`, but awaits the method and caches the resolved **value**, not the Promise:
|
|
8
8
|
*
|
|
9
|
-
* - A rejection is NOT cached, so the next call retries the method.
|
|
10
|
-
* `@_Memo` caches the Promise as-is, so it serves a rejection to every caller until the key is evicted.
|
|
11
9
|
* - The cache is async (`AsyncMemoCache`), so it can be backed by e.g Redis or Datastore
|
|
12
|
-
* and survive a process restart.
|
|
10
|
+
* and survive a process restart. That's the reason to use it over `@_Memo`.
|
|
11
|
+
* - A rejection is NOT cached, so the next call retries the method (same as `@_Memo`).
|
|
13
12
|
* - Parallel calls with the same key share one in-flight Promise, also while the cache lookup is pending.
|
|
14
13
|
*
|
|
15
14
|
* Cache errors (`get`/`set`) are logged and treated as a cache miss, they never fail the method.
|
|
@@ -35,9 +35,10 @@ export interface MemoInstance {
|
|
|
35
35
|
* So, error-throwing functions will be called multiple times.
|
|
36
36
|
* Therefor, if the origin function can possibly throw - it should try to be idempotent.
|
|
37
37
|
*
|
|
38
|
-
* For an async method it's the returned **Promise** that is cached, so
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* For an async method it's the returned **Promise** that is cached, so parallel calls with the same key
|
|
39
|
+
* share one in-flight Promise. A rejected Promise is evicted from the cache, so the next call retries
|
|
40
|
+
* the method, same as for a thrown Error.
|
|
41
|
+
* Use `@_AsyncMemo` for an async-backed cache (e.g Redis), which stores the resolved value instead.
|
|
41
42
|
*
|
|
42
43
|
* Cache is stored **per instance** - separate cache for separate instances of the class.
|
|
43
44
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
@@ -12,9 +12,10 @@ import { jsonMemoSerializer, MapMemoCache } from './memo.util.js';
|
|
|
12
12
|
* So, error-throwing functions will be called multiple times.
|
|
13
13
|
* Therefor, if the origin function can possibly throw - it should try to be idempotent.
|
|
14
14
|
*
|
|
15
|
-
* For an async method it's the returned **Promise** that is cached, so
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* For an async method it's the returned **Promise** that is cached, so parallel calls with the same key
|
|
16
|
+
* share one in-flight Promise. A rejected Promise is evicted from the cache, so the next call retries
|
|
17
|
+
* the method, same as for a thrown Error.
|
|
18
|
+
* Use `@_AsyncMemo` for an async-backed cache (e.g Redis), which stores the resolved value instead.
|
|
18
19
|
*
|
|
19
20
|
* Cache is stored **per instance** - separate cache for separate instances of the class.
|
|
20
21
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
@@ -61,6 +62,20 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
61
62
|
catch (err) {
|
|
62
63
|
logger.error({ err });
|
|
63
64
|
}
|
|
65
|
+
if (value instanceof Promise) {
|
|
66
|
+
// Don't cache a rejection: evict it, so the next call retries the method.
|
|
67
|
+
// The derived Promise is handled, so it adds no unhandled rejection.
|
|
68
|
+
value.catch(() => {
|
|
69
|
+
try {
|
|
70
|
+
// Only if the key still holds this very Promise (and not a newer one, cached after an eviction)
|
|
71
|
+
if (cache.get(cacheKey) === value)
|
|
72
|
+
cache.delete(cacheKey);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger.error({ err });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
64
79
|
return value;
|
|
65
80
|
};
|
|
66
81
|
_objectAssign(descriptor.value, {
|
|
@@ -19,6 +19,7 @@ export interface MemoCache<KEY = any, VALUE = any> {
|
|
|
19
19
|
*/
|
|
20
20
|
get: (k: KEY) => VALUE;
|
|
21
21
|
set: (k: KEY, v: VALUE, opt?: MemoCacheOptions) => void;
|
|
22
|
+
delete: (k: KEY) => void;
|
|
22
23
|
/**
|
|
23
24
|
* Clear is only called when `_getMemoCache().clear()` is called.
|
|
24
25
|
* Otherwise the Cache is "persistent" (never cleared).
|
|
@@ -33,6 +34,7 @@ export interface AsyncMemoCache<KEY = any, VALUE = any> {
|
|
|
33
34
|
*/
|
|
34
35
|
get: (k: KEY) => Promise<VALUE | typeof MISS>;
|
|
35
36
|
set: (k: KEY, v: VALUE, opt?: MemoCacheOptions) => Promise<void>;
|
|
37
|
+
delete: (k: KEY) => Promise<void>;
|
|
36
38
|
/**
|
|
37
39
|
* Clear is only called when `_getAsyncMemo().clear()` is called.
|
|
38
40
|
* Otherwise the Cache is "persistent" (never cleared).
|
|
@@ -44,17 +46,18 @@ export declare class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<K
|
|
|
44
46
|
has(k: KEY): boolean;
|
|
45
47
|
get(k: KEY): VALUE;
|
|
46
48
|
set(k: KEY, v: VALUE): void;
|
|
49
|
+
delete(k: KEY): void;
|
|
47
50
|
clear(): void;
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
53
|
* Implementation of AsyncMemoCache backed by a synchronous Map.
|
|
51
54
|
* The default cache of `@_AsyncMemo`: in-memory, never expires.
|
|
52
|
-
*
|
|
53
|
-
* should be retried). For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
55
|
+
* For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
54
56
|
*/
|
|
55
57
|
export declare class MapAsyncMemoCache<KEY = any, VALUE = any> implements AsyncMemoCache<KEY, VALUE> {
|
|
56
58
|
private m;
|
|
57
59
|
get(k: KEY): Promise<VALUE | typeof MISS>;
|
|
58
60
|
set(k: KEY, v: VALUE): Promise<void>;
|
|
61
|
+
delete(k: KEY): Promise<void>;
|
|
59
62
|
clear(): Promise<void>;
|
|
60
63
|
}
|
|
@@ -63,6 +63,9 @@ export class MapMemoCache {
|
|
|
63
63
|
set(k, v) {
|
|
64
64
|
this.m.set(k, v);
|
|
65
65
|
}
|
|
66
|
+
delete(k) {
|
|
67
|
+
this.m.delete(k);
|
|
68
|
+
}
|
|
66
69
|
clear() {
|
|
67
70
|
this.m.clear();
|
|
68
71
|
}
|
|
@@ -70,8 +73,7 @@ export class MapMemoCache {
|
|
|
70
73
|
/**
|
|
71
74
|
* Implementation of AsyncMemoCache backed by a synchronous Map.
|
|
72
75
|
* The default cache of `@_AsyncMemo`: in-memory, never expires.
|
|
73
|
-
*
|
|
74
|
-
* should be retried). For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
76
|
+
* For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
75
77
|
*/
|
|
76
78
|
export class MapAsyncMemoCache {
|
|
77
79
|
m = new Map();
|
|
@@ -85,6 +87,9 @@ export class MapAsyncMemoCache {
|
|
|
85
87
|
async set(k, v) {
|
|
86
88
|
this.m.set(k, v);
|
|
87
89
|
}
|
|
90
|
+
async delete(k) {
|
|
91
|
+
this.m.delete(k);
|
|
92
|
+
}
|
|
88
93
|
async clear() {
|
|
89
94
|
this.m.clear();
|
|
90
95
|
}
|
|
@@ -21,6 +21,20 @@ export function _memoized(fn, opt = {}) {
|
|
|
21
21
|
catch (err) {
|
|
22
22
|
logger.error({ err });
|
|
23
23
|
}
|
|
24
|
+
if (value instanceof Promise) {
|
|
25
|
+
// Don't cache a rejection: evict it, so the next call retries the function.
|
|
26
|
+
// The derived Promise is handled, so it adds no unhandled rejection.
|
|
27
|
+
value.catch(() => {
|
|
28
|
+
try {
|
|
29
|
+
// Only if the key still holds this very Promise (and not a newer one, cached after an eviction)
|
|
30
|
+
if (cache.get(cacheKey) === value)
|
|
31
|
+
cache.delete(cacheKey);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
logger.error({ err });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
24
38
|
return value;
|
|
25
39
|
};
|
|
26
40
|
Object.assign(memoizedFn, { cache });
|
package/package.json
CHANGED
|
@@ -43,10 +43,9 @@ export interface AsyncMemoInstance {
|
|
|
43
43
|
/**
|
|
44
44
|
* Like `@_Memo`, but awaits the method and caches the resolved **value**, not the Promise:
|
|
45
45
|
*
|
|
46
|
-
* - A rejection is NOT cached, so the next call retries the method.
|
|
47
|
-
* `@_Memo` caches the Promise as-is, so it serves a rejection to every caller until the key is evicted.
|
|
48
46
|
* - The cache is async (`AsyncMemoCache`), so it can be backed by e.g Redis or Datastore
|
|
49
|
-
* and survive a process restart.
|
|
47
|
+
* and survive a process restart. That's the reason to use it over `@_Memo`.
|
|
48
|
+
* - A rejection is NOT cached, so the next call retries the method (same as `@_Memo`).
|
|
50
49
|
* - Parallel calls with the same key share one in-flight Promise, also while the cache lookup is pending.
|
|
51
50
|
*
|
|
52
51
|
* Cache errors (`get`/`set`) are logged and treated as a cache miss, they never fail the method.
|
|
@@ -47,9 +47,10 @@ export interface MemoInstance {
|
|
|
47
47
|
* So, error-throwing functions will be called multiple times.
|
|
48
48
|
* Therefor, if the origin function can possibly throw - it should try to be idempotent.
|
|
49
49
|
*
|
|
50
|
-
* For an async method it's the returned **Promise** that is cached, so
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* For an async method it's the returned **Promise** that is cached, so parallel calls with the same key
|
|
51
|
+
* share one in-flight Promise. A rejected Promise is evicted from the cache, so the next call retries
|
|
52
|
+
* the method, same as for a thrown Error.
|
|
53
|
+
* Use `@_AsyncMemo` for an async-backed cache (e.g Redis), which stores the resolved value instead.
|
|
53
54
|
*
|
|
54
55
|
* Cache is stored **per instance** - separate cache for separate instances of the class.
|
|
55
56
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
@@ -115,6 +116,19 @@ export const _Memo =
|
|
|
115
116
|
logger.error({ err })
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
if (value instanceof Promise) {
|
|
120
|
+
// Don't cache a rejection: evict it, so the next call retries the method.
|
|
121
|
+
// The derived Promise is handled, so it adds no unhandled rejection.
|
|
122
|
+
value.catch(() => {
|
|
123
|
+
try {
|
|
124
|
+
// Only if the key still holds this very Promise (and not a newer one, cached after an eviction)
|
|
125
|
+
if (cache.get(cacheKey) === value) cache.delete(cacheKey)
|
|
126
|
+
} catch (err) {
|
|
127
|
+
logger.error({ err })
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
118
132
|
return value
|
|
119
133
|
} as any
|
|
120
134
|
|
|
@@ -28,7 +28,7 @@ export interface MemoCache<KEY = any, VALUE = any> {
|
|
|
28
28
|
*/
|
|
29
29
|
get: (k: KEY) => VALUE
|
|
30
30
|
set: (k: KEY, v: VALUE, opt?: MemoCacheOptions) => void
|
|
31
|
-
|
|
31
|
+
delete: (k: KEY) => void
|
|
32
32
|
/**
|
|
33
33
|
* Clear is only called when `_getMemoCache().clear()` is called.
|
|
34
34
|
* Otherwise the Cache is "persistent" (never cleared).
|
|
@@ -46,6 +46,7 @@ export interface AsyncMemoCache<KEY = any, VALUE = any> {
|
|
|
46
46
|
*/
|
|
47
47
|
get: (k: KEY) => Promise<VALUE | typeof MISS>
|
|
48
48
|
set: (k: KEY, v: VALUE, opt?: MemoCacheOptions) => Promise<void>
|
|
49
|
+
delete: (k: KEY) => Promise<void>
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Clear is only called when `_getAsyncMemo().clear()` is called.
|
|
@@ -115,6 +116,10 @@ export class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<KEY, VALU
|
|
|
115
116
|
this.m.set(k, v)
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
delete(k: KEY): void {
|
|
120
|
+
this.m.delete(k)
|
|
121
|
+
}
|
|
122
|
+
|
|
118
123
|
clear(): void {
|
|
119
124
|
this.m.clear()
|
|
120
125
|
}
|
|
@@ -123,8 +128,7 @@ export class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<KEY, VALU
|
|
|
123
128
|
/**
|
|
124
129
|
* Implementation of AsyncMemoCache backed by a synchronous Map.
|
|
125
130
|
* The default cache of `@_AsyncMemo`: in-memory, never expires.
|
|
126
|
-
*
|
|
127
|
-
* should be retried). For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
131
|
+
* For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
128
132
|
*/
|
|
129
133
|
export class MapAsyncMemoCache<KEY = any, VALUE = any> implements AsyncMemoCache<KEY, VALUE> {
|
|
130
134
|
private m = new Map<KEY, VALUE>()
|
|
@@ -140,6 +144,10 @@ export class MapAsyncMemoCache<KEY = any, VALUE = any> implements AsyncMemoCache
|
|
|
140
144
|
this.m.set(k, v)
|
|
141
145
|
}
|
|
142
146
|
|
|
147
|
+
async delete(k: KEY): Promise<void> {
|
|
148
|
+
this.m.delete(k)
|
|
149
|
+
}
|
|
150
|
+
|
|
143
151
|
async clear(): Promise<void> {
|
|
144
152
|
this.m.clear()
|
|
145
153
|
}
|
|
@@ -41,6 +41,19 @@ export function _memoized<FN extends AnyFunction>(
|
|
|
41
41
|
logger.error({ err })
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
if (value instanceof Promise) {
|
|
45
|
+
// Don't cache a rejection: evict it, so the next call retries the function.
|
|
46
|
+
// The derived Promise is handled, so it adds no unhandled rejection.
|
|
47
|
+
value.catch(() => {
|
|
48
|
+
try {
|
|
49
|
+
// Only if the key still holds this very Promise (and not a newer one, cached after an eviction)
|
|
50
|
+
if (cache.get(cacheKey) === value) cache.delete(cacheKey)
|
|
51
|
+
} catch (err) {
|
|
52
|
+
logger.error({ err })
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
return value
|
|
45
58
|
}
|
|
46
59
|
|