@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.
Files changed (41) hide show
  1. package/dist/decorators/asyncMemo.decorator.d.ts +35 -9
  2. package/dist/decorators/asyncMemo.decorator.js +30 -22
  3. package/dist/decorators/createPromiseDecorator.d.ts +2 -2
  4. package/dist/decorators/createPromiseDecorator.js +6 -12
  5. package/dist/decorators/decorator.util.d.ts +1 -1
  6. package/dist/decorators/decorator.util.js +2 -2
  7. package/dist/decorators/logMethod.decorator.d.ts +6 -4
  8. package/dist/decorators/logMethod.decorator.js +3 -3
  9. package/dist/decorators/memo.decorator.d.ts +29 -25
  10. package/dist/decorators/memo.decorator.js +36 -42
  11. package/dist/decorators/memo.util.d.ts +6 -6
  12. package/dist/decorators/memoFn.d.ts +5 -0
  13. package/dist/decorators/memoFn.js +32 -37
  14. package/dist/decorators/memoFnAsync.d.ts +10 -0
  15. package/dist/decorators/memoFnAsync.js +69 -0
  16. package/dist/decorators/memoSimple.decorator.d.ts +1 -1
  17. package/dist/decorators/memoSimple.decorator.js +3 -3
  18. package/dist/index.d.ts +3 -2
  19. package/dist/index.js +1 -0
  20. package/dist/types.d.ts +4 -0
  21. package/dist-esm/decorators/asyncMemo.decorator.js +31 -35
  22. package/dist-esm/decorators/createPromiseDecorator.js +8 -15
  23. package/dist-esm/decorators/decorator.util.js +2 -2
  24. package/dist-esm/decorators/logMethod.decorator.js +3 -3
  25. package/dist-esm/decorators/memo.decorator.js +36 -42
  26. package/dist-esm/decorators/memoFn.js +32 -37
  27. package/dist-esm/decorators/memoFnAsync.js +65 -0
  28. package/dist-esm/decorators/memoSimple.decorator.js +3 -3
  29. package/dist-esm/index.js +1 -0
  30. package/package.json +1 -1
  31. package/src/decorators/asyncMemo.decorator.ts +80 -49
  32. package/src/decorators/createPromiseDecorator.ts +64 -70
  33. package/src/decorators/decorator.util.ts +2 -2
  34. package/src/decorators/logMethod.decorator.ts +16 -7
  35. package/src/decorators/memo.decorator.ts +61 -90
  36. package/src/decorators/memo.util.ts +7 -7
  37. package/src/decorators/memoFn.ts +33 -51
  38. package/src/decorators/memoFnAsync.ts +91 -0
  39. package/src/decorators/memoSimple.decorator.ts +4 -4
  40. package/src/index.ts +3 -0
  41. package/src/types.ts +5 -0
@@ -1,9 +1,3 @@
1
- // Based on:
2
- // https://github.com/mgechev/memo-decorator/blob/master/index.ts
3
- // http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
4
- // http://inlehmansterms.net/2015/03/01/javascript-memoization/
5
- // https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
6
-
7
1
  import { CommonLogger } from '../log/commonLogger'
8
2
  import { _since } from '../time/time.util'
9
3
  import { AnyObject } from '../types'
@@ -12,51 +6,45 @@ import { jsonMemoSerializer, MapMemoCache, MemoCache } from './memo.util'
12
6
 
13
7
  export interface MemoOptions {
14
8
  /**
15
- * Default to false
16
- */
17
- logHit?: boolean
18
- /**
19
- * 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`
20
12
  */
21
- logMiss?: boolean
13
+ cacheFactory?: () => MemoCache
22
14
 
23
15
  /**
24
- * Skip logging method arguments.
16
+ * Provide a custom implementation of CacheKey function.
25
17
  */
26
- noLogArgs?: boolean
18
+ cacheKeyFn?: (args: any[]) => any
27
19
 
28
20
  /**
29
- * Default to `console`
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.
30
26
  */
31
- logger?: CommonLogger
27
+ cacheErrors?: boolean
32
28
 
33
29
  /**
34
- * Provide a custom implementation of MemoCache.
35
- * Function that creates an instance of `MemoCache`.
36
- * e.g LRUMemoCache from `@naturalcycles/nodejs-lib`
30
+ * Default to false
37
31
  */
38
- cacheFactory?: () => MemoCache
39
-
32
+ logHit?: boolean
40
33
  /**
41
- * Provide a custom implementation of CacheKey function.
34
+ * Default to false
42
35
  */
43
- cacheKeyFn?: (args: any[]) => any
36
+ logMiss?: boolean
44
37
 
45
38
  /**
46
- * Don't cache resolved promises.
47
- * Setting this to `true` will make the decorator to await the result.
48
- *
49
- * Default false.
39
+ * Defaults to true.
40
+ * Set to false to skip logging method arguments.
50
41
  */
51
- noCacheResolved?: boolean
42
+ logArgs?: boolean
52
43
 
53
44
  /**
54
- * Don't cache rejected promises.
55
- * Setting this to `true` will make the decorator to await the result.
56
- *
57
- * Default false.
45
+ * Default to `console`
58
46
  */
59
- noCacheRejected?: boolean
47
+ logger?: CommonLogger
60
48
  }
61
49
 
62
50
  /**
@@ -68,6 +56,15 @@ export interface MemoOptions {
68
56
  * If you don't want it that way - you can use a static method, then there will be only one "instance".
69
57
  *
70
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/
71
68
  */
72
69
  // eslint-disable-next-line @typescript-eslint/naming-convention
73
70
  export const _Memo =
@@ -91,96 +88,70 @@ export const _Memo =
91
88
  const {
92
89
  logHit = false,
93
90
  logMiss = false,
94
- noLogArgs = false,
91
+ logArgs = true,
95
92
  logger = console,
96
93
  cacheFactory = () => new MapMemoCache(),
97
94
  cacheKeyFn = jsonMemoSerializer,
98
- noCacheRejected = false,
99
- noCacheResolved = false,
95
+ cacheErrors = true,
100
96
  } = opt
101
97
 
102
- const awaitPromise = Boolean(noCacheRejected || noCacheResolved)
103
98
  const keyStr = String(key)
104
99
  const methodSignature = _getTargetMethodSignature(target, keyStr)
105
100
 
106
101
  descriptor.value = function (this: typeof target, ...args: any[]): any {
107
102
  const ctx = this
108
-
109
103
  const cacheKey = cacheKeyFn(args)
104
+ let value: any
110
105
 
111
106
  if (!cache.has(ctx)) {
112
107
  cache.set(ctx, cacheFactory())
113
108
  } else if (cache.get(ctx)!.has(cacheKey)) {
114
109
  if (logHit) {
115
110
  logger.log(
116
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_Memo hit`,
111
+ `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`,
117
112
  )
118
113
  }
119
114
 
120
- const res = cache.get(ctx)!.get(cacheKey)
115
+ value = cache.get(ctx)!.get(cacheKey)
121
116
 
122
- if (awaitPromise) {
123
- return res instanceof Error ? Promise.reject(res) : Promise.resolve(res)
124
- } else {
125
- return res
117
+ if (value instanceof Error) {
118
+ throw value
126
119
  }
120
+
121
+ return value
127
122
  }
128
123
 
129
124
  const started = Date.now()
130
125
 
131
- const res: any = originalFn.apply(ctx, args)
132
-
133
- if (awaitPromise) {
134
- return (res as Promise<any>)
135
- .then(res => {
136
- // console.log('RESOLVED', res)
137
- if (logMiss) {
138
- logger.log(
139
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
140
- args,
141
- noLogArgs,
142
- )}) @_Memo miss resolved (${_since(started)})`,
143
- )
144
- }
145
-
146
- if (!noCacheResolved) {
147
- cache.get(ctx)!.set(cacheKey, res)
148
- }
149
-
150
- return res
151
- })
152
- .catch(err => {
153
- // console.log('REJECTED', err)
154
- if (logMiss) {
155
- logger.log(
156
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
157
- args,
158
- noLogArgs,
159
- )}) @_Memo miss rejected (${_since(started)})`,
160
- )
161
- }
162
-
163
- if (!noCacheRejected) {
164
- // We put it to cache as raw Error, not Promise.reject(err)
165
- // So, we'll need to check if it's instanceof Error to reject it or resolve
166
- // Wrap as Error if it's not Error
167
- cache.get(ctx)!.set(cacheKey, err instanceof Error ? err : new Error(err))
168
- }
169
-
170
- throw err
171
- })
172
- } 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 {
173
147
  if (logMiss) {
174
148
  logger.log(
175
149
  `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
176
150
  args,
177
- noLogArgs,
151
+ logArgs,
178
152
  )}) @_Memo miss (${_since(started)})`,
179
153
  )
180
154
  }
181
-
182
- cache.get(ctx)!.set(cacheKey, res)
183
- return res
184
155
  }
185
156
  } as any
186
157
  ;(descriptor.value as any).dropCache = () => {
@@ -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
 
@@ -7,6 +7,11 @@ export interface MemoizedFunction {
7
7
  cache: MemoCache
8
8
  }
9
9
 
10
+ /**
11
+ * Only supports Sync functions.
12
+ * To support Async functions - use _memoFnAsync.
13
+ * Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
14
+ */
10
15
  export function _memoFn<T extends (...args: any[]) => any>(
11
16
  fn: T,
12
17
  opt: MemoOptions = {},
@@ -14,86 +19,63 @@ export function _memoFn<T extends (...args: any[]) => any>(
14
19
  const {
15
20
  logHit = false,
16
21
  logMiss = false,
17
- noLogArgs = false,
22
+ logArgs = true,
18
23
  logger = console,
19
- noCacheRejected = false,
20
- noCacheResolved = false,
24
+ cacheErrors = true,
21
25
  cacheFactory = () => new MapMemoCache(),
22
26
  cacheKeyFn = jsonMemoSerializer,
23
27
  } = opt
24
28
 
25
29
  const cache = cacheFactory()
26
- const awaitPromise = Boolean(noCacheRejected || noCacheResolved)
27
30
  const fnName = fn.name
28
31
 
29
32
  const memoizedFn = function (this: any, ...args: any[]): T {
30
33
  const ctx = this
31
34
  const cacheKey = cacheKeyFn(args)
35
+ let value: any
32
36
 
33
37
  if (cache.has(cacheKey)) {
34
38
  if (logHit) {
35
- logger.log(`${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn hit`)
39
+ logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn hit`)
36
40
  }
37
41
 
38
- const res = cache.get(cacheKey)
42
+ value = cache.get(cacheKey)
39
43
 
40
- if (awaitPromise) {
41
- return res instanceof Error ? (Promise.reject(res) as any) : Promise.resolve(res)
42
- } else {
43
- return res
44
+ if (value instanceof Error) {
45
+ throw value
44
46
  }
47
+
48
+ return value
45
49
  }
46
50
 
47
51
  const started = Date.now()
48
52
 
49
- const res: any = fn.apply(ctx, args)
50
-
51
- if (awaitPromise) {
52
- return (res as Promise<any>)
53
- .then(res => {
54
- // console.log('RESOLVED', res)
55
- if (logMiss) {
56
- logger.log(
57
- `${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn miss resolved (${_since(
58
- started,
59
- )})`,
60
- )
61
- }
62
-
63
- if (!noCacheResolved) {
64
- cache.set(cacheKey, res)
65
- }
53
+ try {
54
+ value = fn.apply(ctx, args)
66
55
 
67
- return res
68
- })
69
- .catch(err => {
70
- // console.log('REJECTED', err)
71
- if (logMiss) {
72
- logger.log(
73
- `${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn miss rejected (${_since(
74
- started,
75
- )})`,
76
- )
77
- }
56
+ try {
57
+ cache.set(cacheKey, value)
58
+ } catch (err) {
59
+ logger.error(err)
60
+ }
78
61
 
79
- if (!noCacheRejected) {
80
- // We put it to cache as raw Error, not Promise.reject(err)
81
- // So, we'll need to check if it's instanceof Error to reject it or resolve
82
- // Wrap as Error if it's not Error
83
- cache.set(cacheKey, err instanceof Error ? err : new Error(err))
84
- }
62
+ return value
63
+ } catch (err) {
64
+ if (cacheErrors) {
65
+ try {
66
+ cache.set(cacheKey, err)
67
+ } catch (err) {
68
+ logger.error(err)
69
+ }
70
+ }
85
71
 
86
- throw err
87
- }) as any
88
- } else {
72
+ throw err
73
+ } finally {
89
74
  if (logMiss) {
90
75
  logger.log(
91
- `${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn miss (${_since(started)})`,
76
+ `${fnName}(${_getArgsSignature(args, logArgs)}) memoFn miss (${_since(started)})`,
92
77
  )
93
78
  }
94
-
95
- cache.set(cacheKey, res)
96
- return res
97
79
  }
98
80
  }
99
81
 
@@ -0,0 +1,91 @@
1
+ import { _since } from '../time/time.util'
2
+ import { AsyncMemoOptions } from './asyncMemo.decorator'
3
+ import { _getArgsSignature } from './decorator.util'
4
+ import { AsyncMemoCache, jsonMemoSerializer, MapMemoCache } from './memo.util'
5
+
6
+ export interface MemoizedAsyncFunction {
7
+ cache: AsyncMemoCache
8
+ }
9
+
10
+ /**
11
+ * Only supports Sync functions.
12
+ * To support Async functions - use _memoFnAsync
13
+ */
14
+ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
15
+ fn: T,
16
+ opt: AsyncMemoOptions = {},
17
+ ): T & MemoizedAsyncFunction {
18
+ const {
19
+ logHit = false,
20
+ logMiss = false,
21
+ logArgs = true,
22
+ logger = console,
23
+ cacheRejections = true,
24
+ cacheFactory = () => new MapMemoCache(),
25
+ cacheKeyFn = jsonMemoSerializer,
26
+ } = opt
27
+
28
+ const cache = cacheFactory()
29
+ const fnName = fn.name
30
+
31
+ const memoizedFn = async function (this: any, ...args: any[]): Promise<any> {
32
+ const ctx = this
33
+ const cacheKey = cacheKeyFn(args)
34
+ let value: any
35
+
36
+ try {
37
+ value = await cache.get(cacheKey)
38
+ } catch (err) {
39
+ logger.error(err)
40
+ }
41
+
42
+ if (value !== undefined) {
43
+ if (logHit) {
44
+ logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync hit`)
45
+ }
46
+
47
+ if (value instanceof Error) {
48
+ throw value
49
+ }
50
+
51
+ return value
52
+ }
53
+
54
+ const started = Date.now()
55
+
56
+ try {
57
+ value = await fn.apply(ctx, args)
58
+
59
+ void (async () => {
60
+ try {
61
+ await cache.set(cacheKey, value)
62
+ } catch (err) {
63
+ logger.error(err)
64
+ }
65
+ })()
66
+
67
+ return value
68
+ } catch (err) {
69
+ if (cacheRejections) {
70
+ void (async () => {
71
+ try {
72
+ await cache.set(cacheKey, err)
73
+ } catch (err) {
74
+ logger.error(err)
75
+ }
76
+ })()
77
+ }
78
+
79
+ throw err
80
+ } finally {
81
+ if (logMiss) {
82
+ logger.log(
83
+ `${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync miss (${_since(started)})`,
84
+ )
85
+ }
86
+ }
87
+ }
88
+
89
+ Object.assign(memoizedFn, { cache })
90
+ return memoizedFn as T & MemoizedAsyncFunction
91
+ }
@@ -18,7 +18,7 @@ import { jsonMemoSerializer, MapMemoCache, MemoCache } from './memo.util'
18
18
  export interface MemoOpts {
19
19
  logHit?: boolean
20
20
  logMiss?: boolean
21
- noLogArgs?: boolean
21
+ logArgs?: boolean
22
22
  logger?: CommonLogger
23
23
  }
24
24
 
@@ -57,7 +57,7 @@ export const memoSimple =
57
57
  */
58
58
  const cache: MemoCache = new MapMemoCache()
59
59
 
60
- const { logHit, logMiss, noLogArgs, logger = console } = opt
60
+ const { logHit, logMiss, logArgs = true, logger = console } = opt
61
61
  const keyStr = String(key)
62
62
  const methodSignature = _getTargetMethodSignature(target, keyStr)
63
63
 
@@ -67,7 +67,7 @@ export const memoSimple =
67
67
 
68
68
  if (cache.has(cacheKey)) {
69
69
  if (logHit) {
70
- logger.log(`${methodSignature}(${_getArgsSignature(args, noLogArgs)}) @memo hit`)
70
+ logger.log(`${methodSignature}(${_getArgsSignature(args, logArgs)}) @memo hit`)
71
71
  }
72
72
  return cache.get(cacheKey)
73
73
  }
@@ -78,7 +78,7 @@ export const memoSimple =
78
78
 
79
79
  if (logMiss) {
80
80
  logger.log(
81
- `${methodSignature}(${_getArgsSignature(args, noLogArgs)}) @memo miss (${
81
+ `${methodSignature}(${_getArgsSignature(args, logArgs)}) @memo miss (${
82
82
  Date.now() - d
83
83
  } ms)`,
84
84
  )
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export * from './decorators/memo.decorator'
15
15
  export * from './decorators/asyncMemo.decorator'
16
16
  import { MemoCache, AsyncMemoCache } from './decorators/memo.util'
17
17
  export * from './decorators/memoFn'
18
+ export * from './decorators/memoFnAsync'
18
19
  export * from './decorators/retry.decorator'
19
20
  export * from './decorators/timeout.decorator'
20
21
  export * from './error/app.error'
@@ -118,6 +119,7 @@ import {
118
119
  SavedDBEntity,
119
120
  StringMap,
120
121
  UnixTimestamp,
122
+ Integer,
121
123
  ValueOf,
122
124
  ValuesOf,
123
125
  AbortableMapper,
@@ -203,6 +205,7 @@ export type {
203
205
  ConditionalExcept,
204
206
  Class,
205
207
  UnixTimestamp,
208
+ Integer,
206
209
  BaseDBEntity,
207
210
  SavedDBEntity,
208
211
  Saved,
package/src/types.ts CHANGED
@@ -166,6 +166,11 @@ export type IsoDateTime = string
166
166
  */
167
167
  export type UnixTimestamp = number
168
168
 
169
+ /**
170
+ * Same as `number`, but with semantic meaning that it's an Integer.
171
+ */
172
+ export type Integer = number
173
+
169
174
  /**
170
175
  * Base interface for any Entity that was saved to DB.
171
176
  */