@naturalcycles/js-lib 14.84.2 → 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.
Files changed (35) hide show
  1. package/dist/decorators/asyncMemo.decorator.d.ts +32 -9
  2. package/dist/decorators/asyncMemo.decorator.js +26 -21
  3. package/dist/decorators/decorator.util.d.ts +1 -1
  4. package/dist/decorators/decorator.util.js +2 -2
  5. package/dist/decorators/logMethod.decorator.d.ts +6 -4
  6. package/dist/decorators/logMethod.decorator.js +3 -3
  7. package/dist/decorators/memo.decorator.d.ts +27 -26
  8. package/dist/decorators/memo.decorator.js +25 -45
  9. package/dist/decorators/memo.util.d.ts +6 -6
  10. package/dist/decorators/memoFn.d.ts +5 -0
  11. package/dist/decorators/memoFn.js +21 -40
  12. package/dist/decorators/memoFnAsync.d.ts +10 -0
  13. package/dist/decorators/memoFnAsync.js +66 -0
  14. package/dist/decorators/memoSimple.decorator.d.ts +1 -1
  15. package/dist/decorators/memoSimple.decorator.js +3 -3
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist-esm/decorators/asyncMemo.decorator.js +27 -34
  19. package/dist-esm/decorators/decorator.util.js +2 -2
  20. package/dist-esm/decorators/logMethod.decorator.js +3 -3
  21. package/dist-esm/decorators/memo.decorator.js +25 -45
  22. package/dist-esm/decorators/memoFn.js +21 -40
  23. package/dist-esm/decorators/memoFnAsync.js +62 -0
  24. package/dist-esm/decorators/memoSimple.decorator.js +3 -3
  25. package/dist-esm/index.js +1 -0
  26. package/package.json +1 -1
  27. package/src/decorators/asyncMemo.decorator.ts +72 -48
  28. package/src/decorators/decorator.util.ts +2 -2
  29. package/src/decorators/logMethod.decorator.ts +16 -7
  30. package/src/decorators/memo.decorator.ts +46 -91
  31. package/src/decorators/memo.util.ts +7 -7
  32. package/src/decorators/memoFn.ts +21 -52
  33. package/src/decorators/memoFnAsync.ts +87 -0
  34. package/src/decorators/memoSimple.decorator.ts +4 -4
  35. package/src/index.ts +1 -0
@@ -16,14 +16,16 @@ export interface LogMethodOptions {
16
16
  avg?: number
17
17
 
18
18
  /**
19
- * Skip logging method arguments
19
+ * Defaults to true.
20
+ * Set to false to skip logging method arguments
20
21
  */
21
- noLogArgs?: boolean
22
+ logArgs?: boolean
22
23
 
23
24
  /**
24
- * Skip logging result length when result is an array.
25
+ * Defaults to true.
26
+ * Set to false to skip logging result length when result is an array.
25
27
  */
26
- noLogResultLength?: boolean
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 { avg, noLogArgs, logStart, logResult, noLogResultLength, logger = console } = opt
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 (!noLogResultLength) {
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, noLogArgs)
106
+ const argsStr = _getArgsSignature(args, logArgs)
98
107
  const callSignature = `${methodSignature}(${argsStr}) #${++count}`
99
108
  if (logStart) logger.log(`>> ${callSignature}`)
100
109
 
@@ -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,42 @@ 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 false.
22
+ * Set to true to cache thrown errors.
30
23
  */
31
- logger?: CommonLogger
24
+ cacheErrors?: boolean
32
25
 
33
26
  /**
34
- * Provide a custom implementation of MemoCache.
35
- * Function that creates an instance of `MemoCache`.
36
- * e.g LRUMemoCache from `@naturalcycles/nodejs-lib`
27
+ * Default to false
37
28
  */
38
- cacheFactory?: () => MemoCache
39
-
29
+ logHit?: boolean
40
30
  /**
41
- * Provide a custom implementation of CacheKey function.
31
+ * Default to false
42
32
  */
43
- cacheKeyFn?: (args: any[]) => any
33
+ logMiss?: boolean
44
34
 
45
35
  /**
46
- * Don't cache resolved promises.
47
- * Setting this to `true` will make the decorator to await the result.
48
- *
49
- * Default false.
36
+ * Defaults to true.
37
+ * Set to false to skip logging method arguments.
50
38
  */
51
- noCacheResolved?: boolean
39
+ logArgs?: boolean
52
40
 
53
41
  /**
54
- * Don't cache rejected promises.
55
- * Setting this to `true` will make the decorator to await the result.
56
- *
57
- * Default false.
42
+ * Default to `console`
58
43
  */
59
- noCacheRejected?: boolean
44
+ logger?: CommonLogger
60
45
  }
61
46
 
62
47
  /**
@@ -68,6 +53,15 @@ export interface MemoOptions {
68
53
  * If you don't want it that way - you can use a static method, then there will be only one "instance".
69
54
  *
70
55
  * Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
56
+ *
57
+ * Doesn't support Async functions, use @_AsyncMemo instead!
58
+ * (or, it will simply return the [unresolved] Promise further, without awaiting it)
59
+ *
60
+ * Based on:
61
+ * https://github.com/mgechev/memo-decorator/blob/master/index.ts
62
+ * http://decodize.com/blog/2012/08/27/javascript-memoization-caching-results-for-better-performance/
63
+ * http://inlehmansterms.net/2015/03/01/javascript-memoization/
64
+ * https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
71
65
  */
72
66
  // eslint-disable-next-line @typescript-eslint/naming-convention
73
67
  export const _Memo =
@@ -91,15 +85,13 @@ export const _Memo =
91
85
  const {
92
86
  logHit = false,
93
87
  logMiss = false,
94
- noLogArgs = false,
88
+ logArgs = true,
95
89
  logger = console,
96
90
  cacheFactory = () => new MapMemoCache(),
97
91
  cacheKeyFn = jsonMemoSerializer,
98
- noCacheRejected = false,
99
- noCacheResolved = false,
92
+ cacheErrors = false,
100
93
  } = opt
101
94
 
102
- const awaitPromise = Boolean(noCacheRejected || noCacheResolved)
103
95
  const keyStr = String(key)
104
96
  const methodSignature = _getTargetMethodSignature(target, keyStr)
105
97
 
@@ -113,74 +105,37 @@ export const _Memo =
113
105
  } else if (cache.get(ctx)!.has(cacheKey)) {
114
106
  if (logHit) {
115
107
  logger.log(
116
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_Memo hit`,
108
+ `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`,
117
109
  )
118
110
  }
119
111
 
120
- const res = cache.get(ctx)!.get(cacheKey)
121
-
122
- if (awaitPromise) {
123
- return res instanceof Error ? Promise.reject(res) : Promise.resolve(res)
124
- } else {
125
- return res
126
- }
112
+ return cache.get(ctx)!.get(cacheKey)
127
113
  }
128
114
 
129
115
  const started = Date.now()
116
+ let value: any
117
+
118
+ try {
119
+ value = originalFn.apply(ctx, args)
130
120
 
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 {
121
+ cache.get(ctx)!.set(cacheKey, value)
122
+
123
+ return value
124
+ } catch (err) {
125
+ if (cacheErrors) {
126
+ cache.get(ctx)!.set(cacheKey, err)
127
+ }
128
+
129
+ throw err
130
+ } finally {
173
131
  if (logMiss) {
174
132
  logger.log(
175
133
  `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
176
134
  args,
177
- noLogArgs,
135
+ logArgs,
178
136
  )}) @_Memo miss (${_since(started)})`,
179
137
  )
180
138
  }
181
-
182
- cache.get(ctx)!.set(cacheKey, res)
183
- return res
184
139
  }
185
140
  } as any
186
141
  ;(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,16 +19,14 @@ 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 = false,
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 {
@@ -32,68 +35,34 @@ export function _memoFn<T extends (...args: any[]) => any>(
32
35
 
33
36
  if (cache.has(cacheKey)) {
34
37
  if (logHit) {
35
- logger.log(`${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn hit`)
38
+ logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn hit`)
36
39
  }
37
40
 
38
- const res = cache.get(cacheKey)
39
-
40
- if (awaitPromise) {
41
- return res instanceof Error ? (Promise.reject(res) as any) : Promise.resolve(res)
42
- } else {
43
- return res
44
- }
41
+ return cache.get(cacheKey)
45
42
  }
46
43
 
47
44
  const started = Date.now()
48
45
 
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
- }
46
+ let value: any
62
47
 
63
- if (!noCacheResolved) {
64
- cache.set(cacheKey, res)
65
- }
48
+ try {
49
+ value = fn.apply(ctx, args)
66
50
 
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
- }
51
+ cache.set(cacheKey, value)
78
52
 
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
- }
53
+ return value
54
+ } catch (err) {
55
+ if (cacheErrors) {
56
+ cache.set(cacheKey, err)
57
+ }
85
58
 
86
- throw err
87
- }) as any
88
- } else {
59
+ throw err
60
+ } finally {
89
61
  if (logMiss) {
90
62
  logger.log(
91
- `${fnName}(${_getArgsSignature(args, noLogArgs)}) memoFn miss (${_since(started)})`,
63
+ `${fnName}(${_getArgsSignature(args, logArgs)}) memoFn miss (${_since(started)})`,
92
64
  )
93
65
  }
94
-
95
- cache.set(cacheKey, res)
96
- return res
97
66
  }
98
67
  }
99
68
 
@@ -0,0 +1,87 @@
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 = false,
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
+ return value
48
+ }
49
+
50
+ const started = Date.now()
51
+
52
+ try {
53
+ value = await fn.apply(ctx, args)
54
+
55
+ void (async () => {
56
+ try {
57
+ await cache.set(cacheKey, value)
58
+ } catch (err) {
59
+ logger.error(err)
60
+ }
61
+ })()
62
+
63
+ return value
64
+ } catch (err) {
65
+ if (cacheRejections) {
66
+ void (async () => {
67
+ try {
68
+ await cache.set(cacheKey, err)
69
+ } catch (err) {
70
+ logger.error(err)
71
+ }
72
+ })()
73
+ }
74
+
75
+ throw err
76
+ } finally {
77
+ if (logMiss) {
78
+ logger.log(
79
+ `${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync miss (${_since(started)})`,
80
+ )
81
+ }
82
+ }
83
+ }
84
+
85
+ Object.assign(memoizedFn, { cache })
86
+ return memoizedFn as T & MemoizedAsyncFunction
87
+ }
@@ -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'