@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
@@ -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, noLogArgs, logger = console } = opt;
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, noLogArgs)}) @memo hit`);
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, noLogArgs)}) @memo miss (${Date.now() - d} ms)`);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.84.2",
3
+ "version": "14.87.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -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 { MemoOptions } from './memo.decorator'
6
- import { AsyncMemoCache, jsonMemoSerializer } from './memo.util'
7
-
8
- export type AsyncMemoOptions = Merge<
9
- MemoOptions,
10
- {
11
- /**
12
- * Provide a custom implementation of MemoCache.
13
- * Function that creates an instance of `MemoCache`.
14
- * e.g LRUMemoCache from `@naturalcycles/nodejs-lib`.
15
- *
16
- * It's an ARRAY of Caches, to allow multiple layers of Cache.
17
- * It will check it one by one, starting from the first.
18
- * HIT will be returned immediately, MISS will go one level deeper, or returned (if the end of the Cache stack is reached).
19
- */
20
- cacheFactory: () => AsyncMemoCache[]
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
- noLogArgs = false,
75
+ logArgs = true,
47
76
  logger = console,
48
- cacheFactory,
77
+ cacheFactory = () => new MapMemoCache(),
49
78
  cacheKeyFn = jsonMemoSerializer,
50
- noCacheRejected = false,
51
- noCacheResolved = false,
79
+ cacheRejections = true,
52
80
  } = opt
53
81
 
54
82
  const keyStr = String(key)
@@ -68,13 +96,7 @@ export const _AsyncMemo =
68
96
  let value: any
69
97
 
70
98
  try {
71
- for await (const cacheLayer of cache.get(ctx)!) {
72
- value = await cacheLayer.get(cacheKey)
73
- if (value !== undefined) {
74
- // it's a hit!
75
- break
76
- }
77
- }
99
+ value = await cache.get(ctx)!.get(cacheKey)
78
100
  } catch (err) {
79
101
  // log error, but don't throw, treat it as a "miss"
80
102
  logger.error(err)
@@ -86,12 +108,16 @@ export const _AsyncMemo =
86
108
  logger.log(
87
109
  `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
88
110
  args,
89
- noLogArgs,
111
+ logArgs,
90
112
  )}) @_AsyncMemo hit`,
91
113
  )
92
114
  }
93
115
 
94
- return value instanceof Error ? Promise.reject(value) : Promise.resolve(value)
116
+ if (value instanceof Error) {
117
+ throw value
118
+ }
119
+
120
+ return value
95
121
  }
96
122
 
97
123
  // Here we know it's a MISS, let's execute the real method
@@ -100,25 +126,30 @@ export const _AsyncMemo =
100
126
  try {
101
127
  value = await originalFn.apply(ctx, args)
102
128
 
103
- if (!noCacheResolved) {
104
- Promise.all(cache.get(ctx)!.map(cacheLayer => cacheLayer.set(cacheKey, value))).catch(
105
- err => {
106
- // log and ignore the error
107
- logger.error(err)
108
- },
109
- )
110
- }
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
+ })()
111
139
 
112
140
  return value
113
141
  } catch (err) {
114
- if (!noCacheRejected) {
142
+ if (cacheRejections) {
115
143
  // We put it to cache as raw Error, not Promise.reject(err)
116
- Promise.all(cache.get(ctx)!.map(cacheLayer => cacheLayer.set(cacheKey, err))).catch(
117
- err => {
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) {
118
149
  // log and ignore the error
119
150
  logger.error(err)
120
- },
121
- )
151
+ }
152
+ })()
122
153
  }
123
154
 
124
155
  throw err
@@ -127,7 +158,7 @@ export const _AsyncMemo =
127
158
  logger.log(
128
159
  `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
129
160
  args,
130
- noLogArgs,
161
+ logArgs,
131
162
  )}) @_AsyncMemo miss (${_since(started)})`,
132
163
  )
133
164
  }
@@ -136,7 +167,7 @@ export const _AsyncMemo =
136
167
  ;(descriptor.value as any).dropCache = async () => {
137
168
  logger.log(`${methodSignature} @_AsyncMemo.dropCache()`)
138
169
  try {
139
- await Promise.all([...cache.values()].flatMap(c => c.map(c => c.clear())))
170
+ await Promise.all([...cache.values()].map(c => c.clear()))
140
171
  cache.clear()
141
172
  } catch (err) {
142
173
  logger.error(err)
@@ -5,9 +5,9 @@ export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
5
5
 
6
6
  /**
7
7
  * Called BEFORE the original function.
8
- * Returns void.
8
+ * If Promise is returned - it will be awaited.
9
9
  */
10
- beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void
10
+ beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void | Promise<void>
11
11
 
12
12
  /**
13
13
  * Called just AFTER the original function.
@@ -73,77 +73,71 @@ export function _createPromiseDecorator<RES = any, PARAMS = any>(
73
73
  // console.log(`@${cfg.decoratorName} called inside function`)
74
74
  const started = Date.now()
75
75
 
76
- return (
77
- Promise.resolve()
78
- // Before function
79
- .then(() => {
80
- // console.log(`@${cfg.decoratorName} Before`)
81
- if (cfg.beforeFn) {
82
- return cfg.beforeFn({
83
- decoratorParams,
84
- args,
85
- key,
86
- target,
87
- decoratorName,
88
- started,
89
- })
90
- }
76
+ try {
77
+ // Before function
78
+ // console.log(`@${cfg.decoratorName} Before`)
79
+ if (cfg.beforeFn) {
80
+ await cfg.beforeFn({
81
+ decoratorParams,
82
+ args,
83
+ key,
84
+ target,
85
+ decoratorName,
86
+ started,
91
87
  })
92
- // Original function
93
- .then(() => originalMethod.apply(this, args))
94
- .then(res => {
95
- // console.log(`${cfg.decoratorName} After`)
96
- const resp: PromiseDecoratorResp<PARAMS> = {
97
- decoratorParams,
98
- args,
99
- key,
100
- target,
101
- decoratorName,
102
- started,
103
- }
104
-
105
- if (cfg.thenFn) {
106
- res = cfg.thenFn({
107
- ...resp,
108
- res,
109
- })
110
- }
111
-
112
- cfg.finallyFn?.(resp)
113
-
114
- return res
88
+ }
89
+
90
+ // Original function
91
+ let res = await originalMethod.apply(this, args)
92
+
93
+ // console.log(`${cfg.decoratorName} After`)
94
+ const resp: PromiseDecoratorResp<PARAMS> = {
95
+ decoratorParams,
96
+ args,
97
+ key,
98
+ target,
99
+ decoratorName,
100
+ started,
101
+ }
102
+
103
+ if (cfg.thenFn) {
104
+ res = cfg.thenFn({
105
+ ...resp,
106
+ res,
115
107
  })
116
- .catch(err => {
117
- console.error(`@${decoratorName} ${methodSignature} catch:`, err)
118
-
119
- const resp: PromiseDecoratorResp<PARAMS> = {
120
- decoratorParams,
121
- args,
122
- key,
123
- target,
124
- decoratorName,
125
- started,
126
- }
127
-
128
- let handled = false
129
-
130
- if (cfg.catchFn) {
131
- cfg.catchFn({
132
- ...resp,
133
- err,
134
- })
135
- handled = true
136
- }
137
-
138
- cfg.finallyFn?.(resp)
139
-
140
- if (!handled) {
141
- throw err // rethrow
142
- }
108
+ }
109
+
110
+ cfg.finallyFn?.(resp)
111
+
112
+ return res
113
+ } catch (err) {
114
+ console.error(`@${decoratorName} ${methodSignature} catch:`, err)
115
+
116
+ const resp: PromiseDecoratorResp<PARAMS> = {
117
+ decoratorParams,
118
+ args,
119
+ key,
120
+ target,
121
+ decoratorName,
122
+ started,
123
+ }
124
+
125
+ let handled = false
126
+
127
+ if (cfg.catchFn) {
128
+ cfg.catchFn({
129
+ ...resp,
130
+ err,
143
131
  })
144
- // es2018 only
145
- // .finally(() => {})
146
- )
132
+ handled = true
133
+ }
134
+
135
+ cfg.finallyFn?.(resp)
136
+
137
+ if (!handled) {
138
+ throw err // rethrow
139
+ }
140
+ }
147
141
  }
148
142
 
149
143
  return pd
@@ -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[] = [], noLogArgs = false): string {
27
- if (noLogArgs) return ''
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
- * 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