@naturalcycles/js-lib 14.85.0 → 14.88.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.
@@ -12,9 +12,12 @@ export interface AsyncMemoOptions {
12
12
  */
13
13
  cacheKeyFn?: (args: any[]) => any;
14
14
  /**
15
- * Set to `true` to cache rejected promises (errors).
15
+ * Default true.
16
16
  *
17
- * Default false.
17
+ * Set to `false` to skip caching rejected promises (errors).
18
+ *
19
+ * True will ensure "max 1 execution", but will "remember" rejection.
20
+ * False will allow >1 execution in case of errors.
18
21
  */
19
22
  cacheRejections?: boolean;
20
23
  /**
@@ -18,7 +18,7 @@ const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
18
18
  const originalFn = descriptor.value;
19
19
  // Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
20
20
  const cache = new Map();
21
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheRejections = false, } = opt;
21
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheRejections = true, } = opt;
22
22
  const keyStr = String(key);
23
23
  const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
24
24
  descriptor.value = async function (...args) {
@@ -42,7 +42,10 @@ const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
42
42
  if (logHit) {
43
43
  logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_AsyncMemo hit`);
44
44
  }
45
- return value instanceof Error ? Promise.reject(value) : Promise.resolve(value);
45
+ if (value instanceof Error) {
46
+ throw value;
47
+ }
48
+ return value;
46
49
  }
47
50
  // Here we know it's a MISS, let's execute the real method
48
51
  const started = Date.now();
@@ -2,9 +2,9 @@ export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
2
2
  decoratorName: string;
3
3
  /**
4
4
  * Called BEFORE the original function.
5
- * Returns void.
5
+ * If Promise is returned - it will be awaited.
6
6
  */
7
- beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void;
7
+ beforeFn?: (r: PromiseDecoratorResp<PARAMS>) => void | Promise<void>;
8
8
  /**
9
9
  * Called just AFTER the original function.
10
10
  * The output of this hook will be passed further,
@@ -24,12 +24,11 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
24
24
  pd.value = async function (...args) {
25
25
  // console.log(`@${cfg.decoratorName} called inside function`)
26
26
  const started = Date.now();
27
- return (Promise.resolve()
27
+ try {
28
28
  // Before function
29
- .then(() => {
30
29
  // console.log(`@${cfg.decoratorName} Before`)
31
30
  if (cfg.beforeFn) {
32
- return cfg.beforeFn({
31
+ await cfg.beforeFn({
33
32
  decoratorParams,
34
33
  args,
35
34
  key,
@@ -38,10 +37,8 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
38
37
  started,
39
38
  });
40
39
  }
41
- })
42
40
  // Original function
43
- .then(() => originalMethod.apply(this, args))
44
- .then(res => {
41
+ let res = await originalMethod.apply(this, args);
45
42
  // console.log(`${cfg.decoratorName} After`)
46
43
  const resp = {
47
44
  decoratorParams,
@@ -59,8 +56,8 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
59
56
  }
60
57
  cfg.finallyFn?.(resp);
61
58
  return res;
62
- })
63
- .catch(err => {
59
+ }
60
+ catch (err) {
64
61
  console.error(`@${decoratorName} ${methodSignature} catch:`, err);
65
62
  const resp = {
66
63
  decoratorParams,
@@ -82,10 +79,7 @@ function _createPromiseDecorator(cfg, decoratorParams = {}) {
82
79
  if (!handled) {
83
80
  throw err; // rethrow
84
81
  }
85
- })
86
- // es2018 only
87
- // .finally(() => {})
88
- );
82
+ }
89
83
  };
90
84
  return pd;
91
85
  };
@@ -12,8 +12,11 @@ export interface MemoOptions {
12
12
  */
13
13
  cacheKeyFn?: (args: any[]) => any;
14
14
  /**
15
- * Defaults to false.
16
- * Set to true to cache thrown errors.
15
+ * Defaults to true.
16
+ * Set to false to skip caching errors.
17
+ *
18
+ * True will ensure "max 1 execution", but will "remember" errors.
19
+ * False will allow >1 execution in case of errors.
17
20
  */
18
21
  cacheErrors?: boolean;
19
22
  /**
@@ -37,12 +37,13 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
37
37
  // UPD: tests show that normal Map also doesn't leak (to be tested further)
38
38
  // Normal Map is needed to allow .dropCache()
39
39
  const cache = new Map();
40
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheErrors = false, } = opt;
40
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheErrors = true, } = opt;
41
41
  const keyStr = String(key);
42
42
  const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
43
43
  descriptor.value = function (...args) {
44
44
  const ctx = this;
45
45
  const cacheKey = cacheKeyFn(args);
46
+ let value;
46
47
  if (!cache.has(ctx)) {
47
48
  cache.set(ctx, cacheFactory());
48
49
  }
@@ -50,18 +51,31 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
50
51
  if (logHit) {
51
52
  logger.log(`${(0, decorator_util_1._getMethodSignature)(ctx, keyStr)}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) @_Memo hit`);
52
53
  }
53
- return cache.get(ctx).get(cacheKey);
54
+ value = cache.get(ctx).get(cacheKey);
55
+ if (value instanceof Error) {
56
+ throw value;
57
+ }
58
+ return value;
54
59
  }
55
60
  const started = Date.now();
56
- let value;
57
61
  try {
58
62
  value = originalFn.apply(ctx, args);
59
- cache.get(ctx).set(cacheKey, value);
63
+ try {
64
+ cache.get(ctx).set(cacheKey, value);
65
+ }
66
+ catch (err) {
67
+ logger.error(err);
68
+ }
60
69
  return value;
61
70
  }
62
71
  catch (err) {
63
72
  if (cacheErrors) {
64
- cache.get(ctx).set(cacheKey, err);
73
+ try {
74
+ cache.get(ctx).set(cacheKey, err);
75
+ }
76
+ catch (err) {
77
+ logger.error(err);
78
+ }
65
79
  }
66
80
  throw err;
67
81
  }
@@ -10,28 +10,42 @@ const memo_util_1 = require("./memo.util");
10
10
  * Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
11
11
  */
12
12
  function _memoFn(fn, opt = {}) {
13
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheErrors = false, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
13
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheErrors = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
14
14
  const cache = cacheFactory();
15
15
  const fnName = fn.name;
16
16
  const memoizedFn = function (...args) {
17
17
  const ctx = this;
18
18
  const cacheKey = cacheKeyFn(args);
19
+ let value;
19
20
  if (cache.has(cacheKey)) {
20
21
  if (logHit) {
21
22
  logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFn hit`);
22
23
  }
23
- return cache.get(cacheKey);
24
+ value = cache.get(cacheKey);
25
+ if (value instanceof Error) {
26
+ throw value;
27
+ }
28
+ return value;
24
29
  }
25
30
  const started = Date.now();
26
- let value;
27
31
  try {
28
32
  value = fn.apply(ctx, args);
29
- cache.set(cacheKey, value);
33
+ try {
34
+ cache.set(cacheKey, value);
35
+ }
36
+ catch (err) {
37
+ logger.error(err);
38
+ }
30
39
  return value;
31
40
  }
32
41
  catch (err) {
33
42
  if (cacheErrors) {
34
- cache.set(cacheKey, err);
43
+ try {
44
+ cache.set(cacheKey, err);
45
+ }
46
+ catch (err) {
47
+ logger.error(err);
48
+ }
35
49
  }
36
50
  throw err;
37
51
  }
@@ -9,7 +9,7 @@ const memo_util_1 = require("./memo.util");
9
9
  * To support Async functions - use _memoFnAsync
10
10
  */
11
11
  function _memoFnAsync(fn, opt = {}) {
12
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheRejections = false, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
12
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheRejections = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
13
13
  const cache = cacheFactory();
14
14
  const fnName = fn.name;
15
15
  const memoizedFn = async function (...args) {
@@ -26,6 +26,9 @@ function _memoFnAsync(fn, opt = {}) {
26
26
  if (logHit) {
27
27
  logger.log(`${fnName}(${(0, decorator_util_1._getArgsSignature)(args, logArgs)}) memoFnAsync hit`);
28
28
  }
29
+ if (value instanceof Error) {
30
+ throw value;
31
+ }
29
32
  return value;
30
33
  }
31
34
  const started = Date.now();
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ export * from './string/string.util';
52
52
  import { JsonStringifyFunction, StringifyAnyOptions, _stringifyAny } from './string/stringifyAny';
53
53
  export * from './time/time.util';
54
54
  import { Class, ConditionalExcept, ConditionalPick, Merge, Promisable, ReadonlyDeep, Simplify } from './typeFest';
55
- import { AsyncMapper, AsyncPredicate, BaseDBEntity, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, Saved, Unsaved, BatchResult, InstanceId, IsoDate, IsoDateTime, KeyValueTuple, Mapper, ObjectMapper, ObjectPredicate, Predicate, PromiseMap, AnyObject, AnyFunction, Reviver, SavedDBEntity, StringMap, UnixTimestamp, ValueOf, ValuesOf, AbortableMapper, AbortableAsyncPredicate, AbortableAsyncMapper, AbortablePredicate, END, SKIP, _noop, _objectKeys, _passNothingPredicate, _passthroughMapper, _passthroughPredicate, _passUndefinedMapper, _stringMapEntries, _stringMapValues } from './types';
55
+ import { AsyncMapper, AsyncPredicate, BaseDBEntity, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, Saved, Unsaved, BatchResult, InstanceId, IsoDate, IsoDateTime, KeyValueTuple, Mapper, ObjectMapper, ObjectPredicate, Predicate, PromiseMap, AnyObject, AnyFunction, Reviver, SavedDBEntity, StringMap, UnixTimestamp, Integer, ValueOf, ValuesOf, AbortableMapper, AbortableAsyncPredicate, AbortableAsyncMapper, AbortablePredicate, END, SKIP, _noop, _objectKeys, _passNothingPredicate, _passthroughMapper, _passthroughPredicate, _passUndefinedMapper, _stringMapEntries, _stringMapValues } from './types';
56
56
  export * from './unit/size.util';
57
57
  import { is } from './vendor/is';
58
58
  import { CommonLogLevel, CommonLogFunction, CommonLogger, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, CommonLogWithLevelFunction, commonLoggerCreate } from './log/commonLogger';
@@ -60,5 +60,6 @@ export * from './string/safeJsonStringify';
60
60
  import { PQueue, PQueueCfg } from './promise/pQueue';
61
61
  export * from './seq/seq';
62
62
  export * from './math/stack.util';
63
- export type { AbortableMapper, AbortablePredicate, AbortableAsyncPredicate, AbortableAsyncMapper, PQueueCfg, MemoCache, AsyncMemoCache, PromiseDecoratorCfg, PromiseDecoratorResp, ErrorData, ErrorObject, HttpErrorData, HttpErrorResponse, Admin401ErrorData, Admin403ErrorData, StringMap, PromiseMap, AnyObject, AnyFunction, ValuesOf, ValueOf, KeyValueTuple, ObjectMapper, ObjectPredicate, InstanceId, IsoDate, IsoDateTime, Reviver, PMapOptions, Mapper, AsyncMapper, Predicate, AsyncPredicate, BatchResult, DeferredPromise, PRetryOptions, PTimeoutOptions, TryCatchOptions, StringifyAnyOptions, JsonStringifyFunction, Merge, ReadonlyDeep, Promisable, Simplify, ConditionalPick, ConditionalExcept, Class, UnixTimestamp, BaseDBEntity, SavedDBEntity, Saved, Unsaved, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, JsonSchema, JsonSchemaAny, JsonSchemaOneOf, JsonSchemaAllOf, JsonSchemaAnyOf, JsonSchemaNot, JsonSchemaRef, JsonSchemaConst, JsonSchemaEnum, JsonSchemaString, JsonSchemaNumber, JsonSchemaBoolean, JsonSchemaNull, JsonSchemaRootObject, JsonSchemaObject, JsonSchemaArray, JsonSchemaTuple, JsonSchemaBuilder, CommonLogLevel, CommonLogWithLevelFunction, CommonLogFunction, CommonLogger, };
63
+ export * from './string/leven';
64
+ export type { AbortableMapper, AbortablePredicate, AbortableAsyncPredicate, AbortableAsyncMapper, PQueueCfg, MemoCache, AsyncMemoCache, PromiseDecoratorCfg, PromiseDecoratorResp, ErrorData, ErrorObject, HttpErrorData, HttpErrorResponse, Admin401ErrorData, Admin403ErrorData, StringMap, PromiseMap, AnyObject, AnyFunction, ValuesOf, ValueOf, KeyValueTuple, ObjectMapper, ObjectPredicate, InstanceId, IsoDate, IsoDateTime, Reviver, PMapOptions, Mapper, AsyncMapper, Predicate, AsyncPredicate, BatchResult, DeferredPromise, PRetryOptions, PTimeoutOptions, TryCatchOptions, StringifyAnyOptions, JsonStringifyFunction, Merge, ReadonlyDeep, Promisable, Simplify, ConditionalPick, ConditionalExcept, Class, UnixTimestamp, Integer, BaseDBEntity, SavedDBEntity, Saved, Unsaved, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, JsonSchema, JsonSchemaAny, JsonSchemaOneOf, JsonSchemaAllOf, JsonSchemaAnyOf, JsonSchemaNot, JsonSchemaRef, JsonSchemaConst, JsonSchemaEnum, JsonSchemaString, JsonSchemaNumber, JsonSchemaBoolean, JsonSchemaNull, JsonSchemaRootObject, JsonSchemaObject, JsonSchemaArray, JsonSchemaTuple, JsonSchemaBuilder, CommonLogLevel, CommonLogWithLevelFunction, CommonLogFunction, CommonLogger, };
64
65
  export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
package/dist/index.js CHANGED
@@ -92,3 +92,4 @@ const pQueue_1 = require("./promise/pQueue");
92
92
  Object.defineProperty(exports, "PQueue", { enumerable: true, get: function () { return pQueue_1.PQueue; } });
93
93
  (0, tslib_1.__exportStar)(require("./seq/seq"), exports);
94
94
  (0, tslib_1.__exportStar)(require("./math/stack.util"), exports);
95
+ (0, tslib_1.__exportStar)(require("./string/leven"), exports);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Modified version of: https://github.com/sindresorhus/leven/
3
+ *
4
+ * Returns a Levenshtein distance between first and second word.
5
+ */
6
+ export declare function _leven(first: string, second: string): number;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._leven = void 0;
4
+ const array = [];
5
+ const characterCodeCache = [];
6
+ /* eslint-disable unicorn/prefer-code-point, no-bitwise */
7
+ /**
8
+ * Modified version of: https://github.com/sindresorhus/leven/
9
+ *
10
+ * Returns a Levenshtein distance between first and second word.
11
+ */
12
+ function _leven(first, second) {
13
+ if (first === second) {
14
+ return 0;
15
+ }
16
+ const swap = first;
17
+ // Swapping the strings if `a` is longer than `b` so we know which one is the
18
+ // shortest & which one is the longest
19
+ if (first.length > second.length) {
20
+ first = second;
21
+ second = swap;
22
+ }
23
+ let firstLength = first.length;
24
+ let secondLength = second.length;
25
+ // Performing suffix trimming:
26
+ // We can linearly drop suffix common to both strings since they
27
+ // don't increase distance at all
28
+ // Note: `~-` is the bitwise way to perform a `- 1` operation
29
+ while (firstLength > 0 && first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength)) {
30
+ firstLength--;
31
+ secondLength--;
32
+ }
33
+ // Performing prefix trimming
34
+ // We can linearly drop prefix common to both strings since they
35
+ // don't increase distance at all
36
+ let start = 0;
37
+ while (start < firstLength && first.charCodeAt(start) === second.charCodeAt(start)) {
38
+ start++;
39
+ }
40
+ firstLength -= start;
41
+ secondLength -= start;
42
+ if (firstLength === 0) {
43
+ return secondLength;
44
+ }
45
+ let bCharacterCode;
46
+ let result;
47
+ let temporary;
48
+ let temporary2;
49
+ let index = 0;
50
+ let index2 = 0;
51
+ while (index < firstLength) {
52
+ characterCodeCache[index] = first.charCodeAt(start + index);
53
+ array[index] = ++index;
54
+ }
55
+ while (index2 < secondLength) {
56
+ bCharacterCode = second.charCodeAt(start + index2);
57
+ temporary = index2++;
58
+ result = index2;
59
+ for (index = 0; index < firstLength; index++) {
60
+ temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
61
+ temporary = array[index];
62
+ // eslint-disable-next-line no-multi-assign
63
+ result = array[index] =
64
+ temporary > result
65
+ ? temporary2 > result
66
+ ? result + 1
67
+ : temporary2
68
+ : temporary2 > temporary
69
+ ? temporary + 1
70
+ : temporary2;
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ exports._leven = _leven;
package/dist/types.d.ts CHANGED
@@ -124,6 +124,10 @@ export declare type IsoDateTime = string;
124
124
  * @example 1628945450
125
125
  */
126
126
  export declare type UnixTimestamp = number;
127
+ /**
128
+ * Same as `number`, but with semantic meaning that it's an Integer.
129
+ */
130
+ export declare type Integer = number;
127
131
  /**
128
132
  * Base interface for any Entity that was saved to DB.
129
133
  */
@@ -15,7 +15,7 @@ export const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
15
15
  const originalFn = descriptor.value;
16
16
  // Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
17
17
  const cache = new Map();
18
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheRejections = false, } = opt;
18
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheRejections = true, } = opt;
19
19
  const keyStr = String(key);
20
20
  const methodSignature = _getTargetMethodSignature(target, keyStr);
21
21
  descriptor.value = async function (...args) {
@@ -39,7 +39,10 @@ export const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
39
39
  if (logHit) {
40
40
  logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_AsyncMemo hit`);
41
41
  }
42
- return value instanceof Error ? Promise.reject(value) : Promise.resolve(value);
42
+ if (value instanceof Error) {
43
+ throw value;
44
+ }
45
+ return value;
43
46
  }
44
47
  // Here we know it's a MISS, let's execute the real method
45
48
  const started = Date.now();
@@ -19,14 +19,14 @@ export function _createPromiseDecorator(cfg, decoratorParams = {}) {
19
19
  const key = String(propertyKey);
20
20
  const methodSignature = _getTargetMethodSignature(target, key);
21
21
  pd.value = async function (...args) {
22
+ var _a, _b;
22
23
  // console.log(`@${cfg.decoratorName} called inside function`)
23
24
  const started = Date.now();
24
- return (Promise.resolve()
25
+ try {
25
26
  // Before function
26
- .then(() => {
27
27
  // console.log(`@${cfg.decoratorName} Before`)
28
28
  if (cfg.beforeFn) {
29
- return cfg.beforeFn({
29
+ await cfg.beforeFn({
30
30
  decoratorParams,
31
31
  args,
32
32
  key,
@@ -35,11 +35,8 @@ export function _createPromiseDecorator(cfg, decoratorParams = {}) {
35
35
  started,
36
36
  });
37
37
  }
38
- })
39
38
  // Original function
40
- .then(() => originalMethod.apply(this, args))
41
- .then(res => {
42
- var _a;
39
+ let res = await originalMethod.apply(this, args);
43
40
  // console.log(`${cfg.decoratorName} After`)
44
41
  const resp = {
45
42
  decoratorParams,
@@ -54,9 +51,8 @@ export function _createPromiseDecorator(cfg, decoratorParams = {}) {
54
51
  }
55
52
  (_a = cfg.finallyFn) === null || _a === void 0 ? void 0 : _a.call(cfg, resp);
56
53
  return res;
57
- })
58
- .catch(err => {
59
- var _a;
54
+ }
55
+ catch (err) {
60
56
  console.error(`@${decoratorName} ${methodSignature} catch:`, err);
61
57
  const resp = {
62
58
  decoratorParams,
@@ -71,14 +67,11 @@ export function _createPromiseDecorator(cfg, decoratorParams = {}) {
71
67
  cfg.catchFn(Object.assign(Object.assign({}, resp), { err }));
72
68
  handled = true;
73
69
  }
74
- (_a = cfg.finallyFn) === null || _a === void 0 ? void 0 : _a.call(cfg, resp);
70
+ (_b = cfg.finallyFn) === null || _b === void 0 ? void 0 : _b.call(cfg, resp);
75
71
  if (!handled) {
76
72
  throw err; // rethrow
77
73
  }
78
- })
79
- // es2018 only
80
- // .finally(() => {})
81
- );
74
+ }
82
75
  };
83
76
  return pd;
84
77
  };
@@ -34,12 +34,13 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
34
34
  // UPD: tests show that normal Map also doesn't leak (to be tested further)
35
35
  // Normal Map is needed to allow .dropCache()
36
36
  const cache = new Map();
37
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheErrors = false, } = opt;
37
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, cacheErrors = true, } = opt;
38
38
  const keyStr = String(key);
39
39
  const methodSignature = _getTargetMethodSignature(target, keyStr);
40
40
  descriptor.value = function (...args) {
41
41
  const ctx = this;
42
42
  const cacheKey = cacheKeyFn(args);
43
+ let value;
43
44
  if (!cache.has(ctx)) {
44
45
  cache.set(ctx, cacheFactory());
45
46
  }
@@ -47,18 +48,31 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
47
48
  if (logHit) {
48
49
  logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`);
49
50
  }
50
- return cache.get(ctx).get(cacheKey);
51
+ value = cache.get(ctx).get(cacheKey);
52
+ if (value instanceof Error) {
53
+ throw value;
54
+ }
55
+ return value;
51
56
  }
52
57
  const started = Date.now();
53
- let value;
54
58
  try {
55
59
  value = originalFn.apply(ctx, args);
56
- cache.get(ctx).set(cacheKey, value);
60
+ try {
61
+ cache.get(ctx).set(cacheKey, value);
62
+ }
63
+ catch (err) {
64
+ logger.error(err);
65
+ }
57
66
  return value;
58
67
  }
59
68
  catch (err) {
60
69
  if (cacheErrors) {
61
- cache.get(ctx).set(cacheKey, err);
70
+ try {
71
+ cache.get(ctx).set(cacheKey, err);
72
+ }
73
+ catch (err) {
74
+ logger.error(err);
75
+ }
62
76
  }
63
77
  throw err;
64
78
  }
@@ -7,28 +7,42 @@ import { jsonMemoSerializer, MapMemoCache } from './memo.util';
7
7
  * Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
8
8
  */
9
9
  export function _memoFn(fn, opt = {}) {
10
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheErrors = false, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
10
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheErrors = true, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
11
11
  const cache = cacheFactory();
12
12
  const fnName = fn.name;
13
13
  const memoizedFn = function (...args) {
14
14
  const ctx = this;
15
15
  const cacheKey = cacheKeyFn(args);
16
+ let value;
16
17
  if (cache.has(cacheKey)) {
17
18
  if (logHit) {
18
19
  logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn hit`);
19
20
  }
20
- return cache.get(cacheKey);
21
+ value = cache.get(cacheKey);
22
+ if (value instanceof Error) {
23
+ throw value;
24
+ }
25
+ return value;
21
26
  }
22
27
  const started = Date.now();
23
- let value;
24
28
  try {
25
29
  value = fn.apply(ctx, args);
26
- cache.set(cacheKey, value);
30
+ try {
31
+ cache.set(cacheKey, value);
32
+ }
33
+ catch (err) {
34
+ logger.error(err);
35
+ }
27
36
  return value;
28
37
  }
29
38
  catch (err) {
30
39
  if (cacheErrors) {
31
- cache.set(cacheKey, err);
40
+ try {
41
+ cache.set(cacheKey, err);
42
+ }
43
+ catch (err) {
44
+ logger.error(err);
45
+ }
32
46
  }
33
47
  throw err;
34
48
  }
@@ -6,7 +6,7 @@ import { jsonMemoSerializer, MapMemoCache } from './memo.util';
6
6
  * To support Async functions - use _memoFnAsync
7
7
  */
8
8
  export function _memoFnAsync(fn, opt = {}) {
9
- const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheRejections = false, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
9
+ const { logHit = false, logMiss = false, logArgs = true, logger = console, cacheRejections = true, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
10
10
  const cache = cacheFactory();
11
11
  const fnName = fn.name;
12
12
  const memoizedFn = async function (...args) {
@@ -23,6 +23,9 @@ export function _memoFnAsync(fn, opt = {}) {
23
23
  if (logHit) {
24
24
  logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync hit`);
25
25
  }
26
+ if (value instanceof Error) {
27
+ throw value;
28
+ }
26
29
  return value;
27
30
  }
28
31
  const started = Date.now();
package/dist-esm/index.js CHANGED
@@ -56,4 +56,5 @@ export * from './string/safeJsonStringify';
56
56
  import { PQueue } from './promise/pQueue';
57
57
  export * from './seq/seq';
58
58
  export * from './math/stack.util';
59
+ export * from './string/leven';
59
60
  export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
@@ -0,0 +1,71 @@
1
+ const array = [];
2
+ const characterCodeCache = [];
3
+ /* eslint-disable unicorn/prefer-code-point, no-bitwise */
4
+ /**
5
+ * Modified version of: https://github.com/sindresorhus/leven/
6
+ *
7
+ * Returns a Levenshtein distance between first and second word.
8
+ */
9
+ export function _leven(first, second) {
10
+ if (first === second) {
11
+ return 0;
12
+ }
13
+ const swap = first;
14
+ // Swapping the strings if `a` is longer than `b` so we know which one is the
15
+ // shortest & which one is the longest
16
+ if (first.length > second.length) {
17
+ first = second;
18
+ second = swap;
19
+ }
20
+ let firstLength = first.length;
21
+ let secondLength = second.length;
22
+ // Performing suffix trimming:
23
+ // We can linearly drop suffix common to both strings since they
24
+ // don't increase distance at all
25
+ // Note: `~-` is the bitwise way to perform a `- 1` operation
26
+ while (firstLength > 0 && first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength)) {
27
+ firstLength--;
28
+ secondLength--;
29
+ }
30
+ // Performing prefix trimming
31
+ // We can linearly drop prefix common to both strings since they
32
+ // don't increase distance at all
33
+ let start = 0;
34
+ while (start < firstLength && first.charCodeAt(start) === second.charCodeAt(start)) {
35
+ start++;
36
+ }
37
+ firstLength -= start;
38
+ secondLength -= start;
39
+ if (firstLength === 0) {
40
+ return secondLength;
41
+ }
42
+ let bCharacterCode;
43
+ let result;
44
+ let temporary;
45
+ let temporary2;
46
+ let index = 0;
47
+ let index2 = 0;
48
+ while (index < firstLength) {
49
+ characterCodeCache[index] = first.charCodeAt(start + index);
50
+ array[index] = ++index;
51
+ }
52
+ while (index2 < secondLength) {
53
+ bCharacterCode = second.charCodeAt(start + index2);
54
+ temporary = index2++;
55
+ result = index2;
56
+ for (index = 0; index < firstLength; index++) {
57
+ temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
58
+ temporary = array[index];
59
+ // eslint-disable-next-line no-multi-assign
60
+ result = array[index] =
61
+ temporary > result
62
+ ? temporary2 > result
63
+ ? result + 1
64
+ : temporary2
65
+ : temporary2 > temporary
66
+ ? temporary + 1
67
+ : temporary2;
68
+ }
69
+ }
70
+ return result;
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.85.0",
3
+ "version": "14.88.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -18,9 +18,12 @@ export interface AsyncMemoOptions {
18
18
  cacheKeyFn?: (args: any[]) => any
19
19
 
20
20
  /**
21
- * Set to `true` to cache rejected promises (errors).
21
+ * Default true.
22
22
  *
23
- * Default false.
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.
24
27
  */
25
28
  cacheRejections?: boolean
26
29
 
@@ -73,7 +76,7 @@ export const _AsyncMemo =
73
76
  logger = console,
74
77
  cacheFactory = () => new MapMemoCache(),
75
78
  cacheKeyFn = jsonMemoSerializer,
76
- cacheRejections = false,
79
+ cacheRejections = true,
77
80
  } = opt
78
81
 
79
82
  const keyStr = String(key)
@@ -110,7 +113,11 @@ export const _AsyncMemo =
110
113
  )
111
114
  }
112
115
 
113
- return value instanceof Error ? Promise.reject(value) : Promise.resolve(value)
116
+ if (value instanceof Error) {
117
+ throw value
118
+ }
119
+
120
+ return value
114
121
  }
115
122
 
116
123
  // Here we know it's a MISS, let's execute the real method
@@ -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
@@ -18,8 +18,11 @@ export interface MemoOptions {
18
18
  cacheKeyFn?: (args: any[]) => any
19
19
 
20
20
  /**
21
- * Defaults to false.
22
- * Set to true to cache thrown errors.
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.
23
26
  */
24
27
  cacheErrors?: boolean
25
28
 
@@ -89,7 +92,7 @@ export const _Memo =
89
92
  logger = console,
90
93
  cacheFactory = () => new MapMemoCache(),
91
94
  cacheKeyFn = jsonMemoSerializer,
92
- cacheErrors = false,
95
+ cacheErrors = true,
93
96
  } = opt
94
97
 
95
98
  const keyStr = String(key)
@@ -97,8 +100,8 @@ export const _Memo =
97
100
 
98
101
  descriptor.value = function (this: typeof target, ...args: any[]): any {
99
102
  const ctx = this
100
-
101
103
  const cacheKey = cacheKeyFn(args)
104
+ let value: any
102
105
 
103
106
  if (!cache.has(ctx)) {
104
107
  cache.set(ctx, cacheFactory())
@@ -109,21 +112,34 @@ export const _Memo =
109
112
  )
110
113
  }
111
114
 
112
- return cache.get(ctx)!.get(cacheKey)
115
+ value = cache.get(ctx)!.get(cacheKey)
116
+
117
+ if (value instanceof Error) {
118
+ throw value
119
+ }
120
+
121
+ return value
113
122
  }
114
123
 
115
124
  const started = Date.now()
116
- let value: any
117
125
 
118
126
  try {
119
127
  value = originalFn.apply(ctx, args)
120
128
 
121
- cache.get(ctx)!.set(cacheKey, value)
129
+ try {
130
+ cache.get(ctx)!.set(cacheKey, value)
131
+ } catch (err) {
132
+ logger.error(err)
133
+ }
122
134
 
123
135
  return value
124
136
  } catch (err) {
125
137
  if (cacheErrors) {
126
- cache.get(ctx)!.set(cacheKey, err)
138
+ try {
139
+ cache.get(ctx)!.set(cacheKey, err)
140
+ } catch (err) {
141
+ logger.error(err)
142
+ }
127
143
  }
128
144
 
129
145
  throw err
@@ -21,7 +21,7 @@ export function _memoFn<T extends (...args: any[]) => any>(
21
21
  logMiss = false,
22
22
  logArgs = true,
23
23
  logger = console,
24
- cacheErrors = false,
24
+ cacheErrors = true,
25
25
  cacheFactory = () => new MapMemoCache(),
26
26
  cacheKeyFn = jsonMemoSerializer,
27
27
  } = opt
@@ -32,28 +32,41 @@ export function _memoFn<T extends (...args: any[]) => any>(
32
32
  const memoizedFn = function (this: any, ...args: any[]): T {
33
33
  const ctx = this
34
34
  const cacheKey = cacheKeyFn(args)
35
+ let value: any
35
36
 
36
37
  if (cache.has(cacheKey)) {
37
38
  if (logHit) {
38
39
  logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn hit`)
39
40
  }
40
41
 
41
- return cache.get(cacheKey)
42
+ value = cache.get(cacheKey)
43
+
44
+ if (value instanceof Error) {
45
+ throw value
46
+ }
47
+
48
+ return value
42
49
  }
43
50
 
44
51
  const started = Date.now()
45
52
 
46
- let value: any
47
-
48
53
  try {
49
54
  value = fn.apply(ctx, args)
50
55
 
51
- cache.set(cacheKey, value)
56
+ try {
57
+ cache.set(cacheKey, value)
58
+ } catch (err) {
59
+ logger.error(err)
60
+ }
52
61
 
53
62
  return value
54
63
  } catch (err) {
55
64
  if (cacheErrors) {
56
- cache.set(cacheKey, err)
65
+ try {
66
+ cache.set(cacheKey, err)
67
+ } catch (err) {
68
+ logger.error(err)
69
+ }
57
70
  }
58
71
 
59
72
  throw err
@@ -20,7 +20,7 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
20
20
  logMiss = false,
21
21
  logArgs = true,
22
22
  logger = console,
23
- cacheRejections = false,
23
+ cacheRejections = true,
24
24
  cacheFactory = () => new MapMemoCache(),
25
25
  cacheKeyFn = jsonMemoSerializer,
26
26
  } = opt
@@ -44,6 +44,10 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
44
44
  logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync hit`)
45
45
  }
46
46
 
47
+ if (value instanceof Error) {
48
+ throw value
49
+ }
50
+
47
51
  return value
48
52
  }
49
53
 
package/src/index.ts CHANGED
@@ -119,6 +119,7 @@ import {
119
119
  SavedDBEntity,
120
120
  StringMap,
121
121
  UnixTimestamp,
122
+ Integer,
122
123
  ValueOf,
123
124
  ValuesOf,
124
125
  AbortableMapper,
@@ -154,6 +155,7 @@ export * from './string/safeJsonStringify'
154
155
  import { PQueue, PQueueCfg } from './promise/pQueue'
155
156
  export * from './seq/seq'
156
157
  export * from './math/stack.util'
158
+ export * from './string/leven'
157
159
 
158
160
  export type {
159
161
  AbortableMapper,
@@ -204,6 +206,7 @@ export type {
204
206
  ConditionalExcept,
205
207
  Class,
206
208
  UnixTimestamp,
209
+ Integer,
207
210
  BaseDBEntity,
208
211
  SavedDBEntity,
209
212
  Saved,
@@ -0,0 +1,86 @@
1
+ const array: number[] = []
2
+ const characterCodeCache: number[] = []
3
+
4
+ /* eslint-disable unicorn/prefer-code-point, no-bitwise */
5
+
6
+ /**
7
+ * Modified version of: https://github.com/sindresorhus/leven/
8
+ *
9
+ * Returns a Levenshtein distance between first and second word.
10
+ */
11
+ export function _leven(first: string, second: string): number {
12
+ if (first === second) {
13
+ return 0
14
+ }
15
+
16
+ const swap = first
17
+
18
+ // Swapping the strings if `a` is longer than `b` so we know which one is the
19
+ // shortest & which one is the longest
20
+ if (first.length > second.length) {
21
+ first = second
22
+ second = swap
23
+ }
24
+
25
+ let firstLength = first.length
26
+ let secondLength = second.length
27
+
28
+ // Performing suffix trimming:
29
+ // We can linearly drop suffix common to both strings since they
30
+ // don't increase distance at all
31
+ // Note: `~-` is the bitwise way to perform a `- 1` operation
32
+ while (firstLength > 0 && first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength)) {
33
+ firstLength--
34
+ secondLength--
35
+ }
36
+
37
+ // Performing prefix trimming
38
+ // We can linearly drop prefix common to both strings since they
39
+ // don't increase distance at all
40
+ let start = 0
41
+
42
+ while (start < firstLength && first.charCodeAt(start) === second.charCodeAt(start)) {
43
+ start++
44
+ }
45
+
46
+ firstLength -= start
47
+ secondLength -= start
48
+
49
+ if (firstLength === 0) {
50
+ return secondLength
51
+ }
52
+
53
+ let bCharacterCode
54
+ let result: number
55
+ let temporary: number
56
+ let temporary2: number
57
+ let index = 0
58
+ let index2 = 0
59
+
60
+ while (index < firstLength) {
61
+ characterCodeCache[index] = first.charCodeAt(start + index)
62
+ array[index] = ++index
63
+ }
64
+
65
+ while (index2 < secondLength) {
66
+ bCharacterCode = second.charCodeAt(start + index2)
67
+ temporary = index2++
68
+ result = index2
69
+
70
+ for (index = 0; index < firstLength; index++) {
71
+ temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1
72
+ temporary = array[index]!
73
+ // eslint-disable-next-line no-multi-assign
74
+ result = array[index] =
75
+ temporary > result
76
+ ? temporary2 > result
77
+ ? result + 1
78
+ : temporary2
79
+ : temporary2 > temporary
80
+ ? temporary + 1
81
+ : temporary2
82
+ }
83
+ }
84
+
85
+ return result!
86
+ }
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
  */