@naturalcycles/js-lib 14.78.1 → 14.81.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.
@@ -56,7 +56,7 @@ function _LogMethod(opt = {}) {
56
56
  })
57
57
  .catch((err) => {
58
58
  logFinished(logger, callSignature, started, sma, logResultFn, undefined, err);
59
- return Promise.reject(err);
59
+ throw err;
60
60
  });
61
61
  }
62
62
  else {
@@ -80,7 +80,7 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
80
80
  // Wrap as Error if it's not Error
81
81
  cache.get(ctx).set(cacheKey, err instanceof Error ? err : new Error(err));
82
82
  }
83
- return Promise.reject(err);
83
+ throw err;
84
84
  });
85
85
  }
86
86
  else {
@@ -49,7 +49,7 @@ function _memoFn(fn, opt = {}) {
49
49
  // Wrap as Error if it's not Error
50
50
  cache.set(cacheKey, err instanceof Error ? err : new Error(err));
51
51
  }
52
- return Promise.reject(err);
52
+ throw err;
53
53
  });
54
54
  }
55
55
  else {
@@ -6,7 +6,7 @@ const __1 = require("..");
6
6
  function _Retry(opt = {}) {
7
7
  return (target, key, descriptor) => {
8
8
  const originalFn = descriptor.value;
9
- descriptor.value = (0, __1.pRetry)(originalFn, opt);
9
+ descriptor.value = (0, __1.pRetryFn)(originalFn, opt);
10
10
  return descriptor;
11
11
  };
12
12
  }
package/dist/error/try.js CHANGED
@@ -27,8 +27,6 @@ function _try(fn) {
27
27
  }
28
28
  }
29
29
  exports._try = _try;
30
- // todo: remove when eslint starts to know about Awaited
31
- /* eslint-disable no-undef */
32
30
  /**
33
31
  * Like _try, but for Promises.
34
32
  *
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export * from './promise/pFilter';
41
41
  export * from './promise/pHang';
42
42
  import { pMap, PMapOptions } from './promise/pMap';
43
43
  export * from './promise/pProps';
44
- import { pRetry, PRetryOptions } from './promise/pRetry';
44
+ import { pRetry, pRetryFn, PRetryOptions } from './promise/pRetry';
45
45
  export * from './promise/pState';
46
46
  import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout';
47
47
  export * from './promise/pTuple';
@@ -60,4 +60,4 @@ import { PQueue, PQueueCfg } from './promise/pQueue';
60
60
  export * from './seq/seq';
61
61
  export * from './math/stack.util';
62
62
  export type { AbortableMapper, AbortablePredicate, AbortableAsyncPredicate, AbortableAsyncMapper, PQueueCfg, MemoCache, 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 { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
63
+ 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
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SKIP = exports.END = exports.PQueue = exports.commonLoggerCreate = exports.commonLoggerPrefix = exports.commonLoggerPipe = exports.commonLogLevelNumber = exports.commonLoggerNoop = exports.commonLoggerMinLevel = exports.JsonSchemaAnyBuilder = exports.jsonSchema = exports._stringifyAny = exports._TryCatch = exports._tryCatch = exports.pTimeoutFn = exports.pTimeout = exports.pRetry = exports.AggregatedError = exports.pDefer = exports.ErrorMode = exports._noop = exports._passNothingPredicate = exports._passthroughPredicate = exports._passUndefinedMapper = exports._passthroughMapper = exports.pMap = exports._objectKeys = exports._stringMapEntries = exports._stringMapValues = exports._createPromiseDecorator = exports.is = void 0;
3
+ exports.SKIP = exports.END = exports.PQueue = exports.commonLoggerCreate = exports.commonLoggerPrefix = exports.commonLoggerPipe = exports.commonLogLevelNumber = exports.commonLoggerNoop = exports.commonLoggerMinLevel = exports.JsonSchemaAnyBuilder = exports.jsonSchema = exports._stringifyAny = exports._TryCatch = exports._tryCatch = exports.pTimeoutFn = exports.pTimeout = exports.pRetryFn = exports.pRetry = exports.AggregatedError = exports.pDefer = exports.ErrorMode = exports._noop = exports._passNothingPredicate = exports._passthroughPredicate = exports._passUndefinedMapper = exports._passthroughMapper = exports.pMap = exports._objectKeys = exports._stringMapEntries = exports._stringMapValues = exports._createPromiseDecorator = exports.is = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  (0, tslib_1.__exportStar)(require("./array/array.util"), exports);
6
6
  (0, tslib_1.__exportStar)(require("./lazy"), exports);
@@ -53,6 +53,7 @@ Object.defineProperty(exports, "pMap", { enumerable: true, get: function () { re
53
53
  (0, tslib_1.__exportStar)(require("./promise/pProps"), exports);
54
54
  const pRetry_1 = require("./promise/pRetry");
55
55
  Object.defineProperty(exports, "pRetry", { enumerable: true, get: function () { return pRetry_1.pRetry; } });
56
+ Object.defineProperty(exports, "pRetryFn", { enumerable: true, get: function () { return pRetry_1.pRetryFn; } });
56
57
  (0, tslib_1.__exportStar)(require("./promise/pState"), exports);
57
58
  const pTimeout_1 = require("./promise/pTimeout");
58
59
  Object.defineProperty(exports, "pTimeout", { enumerable: true, get: function () { return pTimeout_1.pTimeout; } });
@@ -113,8 +113,8 @@ export declare class JsonSchemaObjectBuilder<T extends AnyObject> extends JsonSc
113
113
  minProps(minProperties: number): this;
114
114
  maxProps(maxProperties: number): this;
115
115
  additionalProps(additionalProperties: boolean): this;
116
- baseDBEntity(): JsonSchemaObjectBuilder<T & BaseDBEntity>;
117
- savedDBEntity(): JsonSchemaObjectBuilder<T & SavedDBEntity>;
116
+ baseDBEntity<ID = string>(idType?: string): JsonSchemaObjectBuilder<T & BaseDBEntity<ID>>;
117
+ savedDBEntity<ID = string>(idType?: string): JsonSchemaObjectBuilder<T & SavedDBEntity<ID>>;
118
118
  extend<T2 extends AnyObject>(s2: JsonSchemaObjectBuilder<T2>): JsonSchemaObjectBuilder<T & T2>;
119
119
  }
120
120
  export declare class JsonSchemaArrayBuilder<ITEM> extends JsonSchemaAnyBuilder<ITEM[], JsonSchemaArray<ITEM>> {
@@ -306,16 +306,16 @@ class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
306
306
  Object.assign(this.schema, { additionalProperties });
307
307
  return this;
308
308
  }
309
- baseDBEntity() {
309
+ baseDBEntity(idType = 'string') {
310
310
  Object.assign(this.schema.properties, {
311
- id: { type: 'string' },
311
+ id: { type: idType },
312
312
  created: { type: 'number', format: 'unixTimestamp' },
313
313
  updated: { type: 'number', format: 'unixTimestamp' },
314
314
  });
315
315
  return this;
316
316
  }
317
- savedDBEntity() {
318
- return this.baseDBEntity().addRequired(['id', 'created', 'updated']);
317
+ savedDBEntity(idType = 'string') {
318
+ return this.baseDBEntity(idType).addRequired(['id', 'created', 'updated']);
319
319
  }
320
320
  extend(s2) {
321
321
  const builder = new JsonSchemaObjectBuilder();
@@ -1,3 +1,3 @@
1
1
  import { SavedDBEntity } from '../types';
2
- export declare const baseDBEntityJsonSchema: import("./jsonSchemaBuilder").JsonSchemaObjectBuilder<Partial<SavedDBEntity>>;
3
- export declare const savedDBEntityJsonSchema: import("./jsonSchemaBuilder").JsonSchemaObjectBuilder<SavedDBEntity>;
2
+ export declare const baseDBEntityJsonSchema: import("./jsonSchemaBuilder").JsonSchemaObjectBuilder<Partial<SavedDBEntity<string>>>;
3
+ export declare const savedDBEntityJsonSchema: import("./jsonSchemaBuilder").JsonSchemaObjectBuilder<SavedDBEntity<string>>;
@@ -1,10 +1,19 @@
1
- import { PMapOptions } from './pMap';
2
1
  /**
3
2
  * Promise.all for Object instead of Array.
4
- * Supports concurrency.
3
+ *
4
+ * Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
5
+ *
6
+ * Improvements:
7
+ *
8
+ * - Exported as { pProps }, so IDE auto-completion works
9
+ * - Simpler: no support for Map, Mapper, Options
10
+ * - Included Typescript typings (no need for @types/p-props)
11
+ *
12
+ * Concurrency implementation via pMap was removed in favor of preserving async
13
+ * stack traces (more important!).
5
14
  */
6
15
  export declare function pProps<T>(input: {
7
16
  [K in keyof T]: T[K] | Promise<T[K]>;
8
- }, opt?: PMapOptions): Promise<{
17
+ }): Promise<{
9
18
  [K in keyof T]: Awaited<T[K]>;
10
19
  }>;
@@ -1,26 +1,22 @@
1
1
  "use strict";
2
- /*
3
- Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
4
-
5
- Improvements:
6
-
7
- - Exported as { pProps }, so IDE auto-completion works
8
- - Simpler: no support for Map, Mapper, Options
9
- - Included Typescript typings (no need for @types/p-props)
10
- */
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.pProps = void 0;
13
- const pMap_1 = require("./pMap");
14
- // todo: remove when eslint starts to know about Awaited
15
- /* eslint-disable no-undef */
16
4
  /**
17
5
  * Promise.all for Object instead of Array.
18
- * Supports concurrency.
6
+ *
7
+ * Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
8
+ *
9
+ * Improvements:
10
+ *
11
+ * - Exported as { pProps }, so IDE auto-completion works
12
+ * - Simpler: no support for Map, Mapper, Options
13
+ * - Included Typescript typings (no need for @types/p-props)
14
+ *
15
+ * Concurrency implementation via pMap was removed in favor of preserving async
16
+ * stack traces (more important!).
19
17
  */
20
- async function pProps(input, opt) {
21
- const r = {};
18
+ async function pProps(input) {
22
19
  const keys = Object.keys(input);
23
- await (0, pMap_1.pMap)(Object.values(input), (v, i) => (r[keys[i]] = v), opt);
24
- return r;
20
+ return Object.fromEntries((await Promise.all(Object.values(input))).map((v, i) => [keys[i], v]));
25
21
  }
26
22
  exports.pProps = pProps;
@@ -5,6 +5,12 @@ export interface PRetryOptions {
5
5
  * Can be used to identify the place in the code that failed.
6
6
  */
7
7
  name?: string;
8
+ /**
9
+ * Timeout for each Try, in milliseconds.
10
+ *
11
+ * Defaults to 60_000
12
+ */
13
+ timeout?: number;
8
14
  /**
9
15
  * How many attempts to try.
10
16
  * First attempt is not a retry, but "initial try". It still counts.
@@ -29,7 +35,7 @@ export interface PRetryOptions {
29
35
  *
30
36
  * @default () => true
31
37
  */
32
- predicate?: (err: unknown, attempt: number, maxAttempts: number) => boolean;
38
+ predicate?: (err: Error, attempt: number, maxAttempts: number) => boolean;
33
39
  /**
34
40
  * Log the first attempt (which is not a "retry" yet).
35
41
  *
@@ -62,9 +68,18 @@ export interface PRetryOptions {
62
68
  * Default to `console`
63
69
  */
64
70
  logger?: CommonLogger;
71
+ /**
72
+ * Defaults to true.
73
+ * If true - preserves the stack trace in case of a Timeout (usually - very useful!).
74
+ * It has a certain perf cost.
75
+ *
76
+ * @experimental
77
+ */
78
+ keepStackTrace?: boolean;
65
79
  }
66
80
  /**
67
81
  * Returns a Function (!), enhanced with retry capabilities.
68
82
  * Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
69
83
  */
70
- export declare function pRetry<T extends AnyFunction>(fn: T, opt?: PRetryOptions): T;
84
+ export declare function pRetryFn<T extends AnyFunction>(fn: T, opt?: PRetryOptions): T;
85
+ export declare function pRetry<T>(fn: (attempt: number) => Promise<T>, opt?: PRetryOptions): Promise<T>;
@@ -1,58 +1,90 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pRetry = void 0;
3
+ exports.pRetry = exports.pRetryFn = void 0;
4
4
  const __1 = require("..");
5
+ const pTimeout_1 = require("./pTimeout");
5
6
  /**
6
7
  * Returns a Function (!), enhanced with retry capabilities.
7
8
  * Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
8
9
  */
9
- // eslint-disable-next-line @typescript-eslint/ban-types
10
- function pRetry(fn, opt = {}) {
11
- const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name = fn.name, } = opt;
10
+ function pRetryFn(fn, opt = {}) {
11
+ return async function pRetryFunction(...args) {
12
+ return await pRetry(() => fn.call(this, ...args), opt);
13
+ };
14
+ }
15
+ exports.pRetryFn = pRetryFn;
16
+ async function pRetry(fn, opt = {}) {
17
+ const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, keepStackTrace = true, timeout, } = opt;
18
+ const fakeError = keepStackTrace ? new Error('RetryError') : undefined;
12
19
  let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
13
20
  if (opt.logAll) {
14
- logFirstAttempt = logRetries = logFailures = true;
21
+ logSuccess = logFirstAttempt = logRetries = logFailures = true;
15
22
  }
16
23
  if (opt.logNone) {
17
24
  logSuccess = logFirstAttempt = logRetries = logFailures = false;
18
25
  }
19
- const fname = ['pRetry', name].filter(Boolean).join('.');
20
- return async function (...args) {
21
- let delay = initialDelay;
22
- let attempt = 0;
23
- return await new Promise((resolve, reject) => {
24
- const next = async () => {
25
- const started = Date.now();
26
- try {
27
- attempt++;
28
- if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
29
- logger.log(`${fname} attempt #${attempt}...`);
30
- }
31
- const r = await fn.apply(this, args);
32
- if (logSuccess) {
33
- logger.log(`${fname} attempt #${attempt} succeeded in ${(0, __1._since)(started)}`);
34
- }
35
- resolve(r);
26
+ const fname = name || fn.name || 'pRetry function';
27
+ let delay = initialDelay;
28
+ let attempt = 0;
29
+ let timer;
30
+ let timedOut = false;
31
+ return await new Promise((resolve, reject) => {
32
+ const rejectWithTimeout = () => {
33
+ timedOut = true; // to prevent more tries
34
+ const err = new pTimeout_1.TimeoutError(`"${fname}" timed out after ${timeout} ms`);
35
+ if (fakeError) {
36
+ // keep original stack
37
+ err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
38
+ }
39
+ reject(err);
40
+ };
41
+ const next = async () => {
42
+ if (timedOut)
43
+ return;
44
+ if (timeout) {
45
+ timer = setTimeout(rejectWithTimeout, timeout);
46
+ }
47
+ const started = Date.now();
48
+ try {
49
+ attempt++;
50
+ if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
51
+ logger.log(`${fname} attempt #${attempt}...`);
36
52
  }
37
- catch (err) {
38
- if (logFailures) {
39
- logger.warn(`${fname} attempt #${attempt} error in ${(0, __1._since)(started)}:`, (0, __1._stringifyAny)(err, {
40
- includeErrorData: true,
41
- }));
42
- }
43
- if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
44
- // Give up
45
- reject(err);
46
- }
47
- else {
48
- // Retry after delay
49
- delay *= delayMultiplier;
50
- setTimeout(next, delay);
53
+ const r = await fn(attempt);
54
+ clearTimeout(timer);
55
+ if (logSuccess) {
56
+ logger.log(`${fname} attempt #${attempt} succeeded in ${(0, __1._since)(started)}`);
57
+ }
58
+ resolve(r);
59
+ }
60
+ catch (err) {
61
+ clearTimeout(timer);
62
+ if (logFailures) {
63
+ logger.warn(`${fname} attempt #${attempt} error in ${(0, __1._since)(started)}:`, (0, __1._stringifyAny)(err, {
64
+ includeErrorData: true,
65
+ }));
66
+ }
67
+ if (attempt >= maxAttempts ||
68
+ (predicate && !predicate(err, attempt, maxAttempts))) {
69
+ // Give up
70
+ if (fakeError) {
71
+ // Preserve the original call stack
72
+ Object.defineProperty(err, 'stack', {
73
+ value: err.stack +
74
+ '\n --' +
75
+ fakeError.stack.replace('Error: RetryError', ''),
76
+ });
51
77
  }
78
+ reject(err);
52
79
  }
53
- };
54
- void next();
55
- });
56
- };
80
+ else {
81
+ // Retry after delay
82
+ delay *= delayMultiplier;
83
+ setTimeout(next, delay);
84
+ }
85
+ }
86
+ };
87
+ void next();
88
+ });
57
89
  }
58
90
  exports.pRetry = pRetry;
package/dist/types.d.ts CHANGED
@@ -24,13 +24,13 @@ export interface CreatedUpdated {
24
24
  created: number;
25
25
  updated: number;
26
26
  }
27
- export interface CreatedUpdatedId extends CreatedUpdated {
28
- id: string;
27
+ export interface CreatedUpdatedId<ID = string> extends CreatedUpdated {
28
+ id: ID;
29
29
  }
30
- export interface ObjectWithId {
31
- id: string;
30
+ export interface ObjectWithId<ID = string> {
31
+ id: ID;
32
32
  }
33
- export interface AnyObjectWithId extends AnyObject, ObjectWithId {
33
+ export interface AnyObjectWithId<ID = string> extends AnyObject, ObjectWithId<ID> {
34
34
  }
35
35
  /**
36
36
  * Convenience type shorthand.
@@ -127,8 +127,8 @@ export declare type UnixTimestamp = number;
127
127
  /**
128
128
  * Base interface for any Entity that was saved to DB.
129
129
  */
130
- export interface SavedDBEntity {
131
- id: string;
130
+ export interface SavedDBEntity<ID = string> {
131
+ id: ID;
132
132
  /**
133
133
  * unixTimestamp of when the entity was first created (in the DB).
134
134
  */
@@ -144,9 +144,9 @@ export interface SavedDBEntity {
144
144
  * hence `id`, `created` and `updated` fields CAN BE undefined (yet).
145
145
  * When it's known to be saved - `SavedDBEntity` interface can be used instead.
146
146
  */
147
- export declare type BaseDBEntity = Partial<SavedDBEntity>;
148
- export declare type Saved<E> = Merge<E, SavedDBEntity>;
149
- export declare type Unsaved<E> = Merge<E, BaseDBEntity>;
147
+ export declare type BaseDBEntity<ID = string> = Partial<SavedDBEntity<ID>>;
148
+ export declare type Saved<E, ID = string> = Merge<E, SavedDBEntity<ID>>;
149
+ export declare type Unsaved<E, ID = string> = Merge<E, BaseDBEntity<ID>>;
150
150
  /**
151
151
  * Named type for JSON.parse / JSON.stringify second argument
152
152
  */
@@ -53,7 +53,7 @@ export function _LogMethod(opt = {}) {
53
53
  })
54
54
  .catch((err) => {
55
55
  logFinished(logger, callSignature, started, sma, logResultFn, undefined, err);
56
- return Promise.reject(err);
56
+ throw err;
57
57
  });
58
58
  }
59
59
  else {
@@ -77,7 +77,7 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
77
77
  // Wrap as Error if it's not Error
78
78
  cache.get(ctx).set(cacheKey, err instanceof Error ? err : new Error(err));
79
79
  }
80
- return Promise.reject(err);
80
+ throw err;
81
81
  });
82
82
  }
83
83
  else {
@@ -46,7 +46,7 @@ export function _memoFn(fn, opt = {}) {
46
46
  // Wrap as Error if it's not Error
47
47
  cache.set(cacheKey, err instanceof Error ? err : new Error(err));
48
48
  }
49
- return Promise.reject(err);
49
+ throw err;
50
50
  });
51
51
  }
52
52
  else {
@@ -1,9 +1,9 @@
1
- import { pRetry } from '..';
1
+ import { pRetryFn } from '..';
2
2
  // eslint-disable-next-line @typescript-eslint/naming-convention
3
3
  export function _Retry(opt = {}) {
4
4
  return (target, key, descriptor) => {
5
5
  const originalFn = descriptor.value;
6
- descriptor.value = pRetry(originalFn, opt);
6
+ descriptor.value = pRetryFn(originalFn, opt);
7
7
  return descriptor;
8
8
  };
9
9
  }
@@ -23,8 +23,6 @@ export function _try(fn) {
23
23
  return [err, undefined];
24
24
  }
25
25
  }
26
- // todo: remove when eslint starts to know about Awaited
27
- /* eslint-disable no-undef */
28
26
  /**
29
27
  * Like _try, but for Promises.
30
28
  *
package/dist-esm/index.js CHANGED
@@ -38,7 +38,7 @@ export * from './promise/pFilter';
38
38
  export * from './promise/pHang';
39
39
  import { pMap } from './promise/pMap';
40
40
  export * from './promise/pProps';
41
- import { pRetry } from './promise/pRetry';
41
+ import { pRetry, pRetryFn } from './promise/pRetry';
42
42
  export * from './promise/pState';
43
43
  import { pTimeout, pTimeoutFn } from './promise/pTimeout';
44
44
  export * from './promise/pTuple';
@@ -55,4 +55,4 @@ export * from './string/safeJsonStringify';
55
55
  import { PQueue } from './promise/pQueue';
56
56
  export * from './seq/seq';
57
57
  export * from './math/stack.util';
58
- export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
58
+ 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, };
@@ -301,16 +301,16 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
301
301
  Object.assign(this.schema, { additionalProperties });
302
302
  return this;
303
303
  }
304
- baseDBEntity() {
304
+ baseDBEntity(idType = 'string') {
305
305
  Object.assign(this.schema.properties, {
306
- id: { type: 'string' },
306
+ id: { type: idType },
307
307
  created: { type: 'number', format: 'unixTimestamp' },
308
308
  updated: { type: 'number', format: 'unixTimestamp' },
309
309
  });
310
310
  return this;
311
311
  }
312
- savedDBEntity() {
313
- return this.baseDBEntity().addRequired(['id', 'created', 'updated']);
312
+ savedDBEntity(idType = 'string') {
313
+ return this.baseDBEntity(idType).addRequired(['id', 'created', 'updated']);
314
314
  }
315
315
  extend(s2) {
316
316
  const builder = new JsonSchemaObjectBuilder();
@@ -1,22 +1,18 @@
1
- /*
2
- Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
3
-
4
- Improvements:
5
-
6
- - Exported as { pProps }, so IDE auto-completion works
7
- - Simpler: no support for Map, Mapper, Options
8
- - Included Typescript typings (no need for @types/p-props)
9
- */
10
- import { pMap } from './pMap';
11
- // todo: remove when eslint starts to know about Awaited
12
- /* eslint-disable no-undef */
13
1
  /**
14
2
  * Promise.all for Object instead of Array.
15
- * Supports concurrency.
3
+ *
4
+ * Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
5
+ *
6
+ * Improvements:
7
+ *
8
+ * - Exported as { pProps }, so IDE auto-completion works
9
+ * - Simpler: no support for Map, Mapper, Options
10
+ * - Included Typescript typings (no need for @types/p-props)
11
+ *
12
+ * Concurrency implementation via pMap was removed in favor of preserving async
13
+ * stack traces (more important!).
16
14
  */
17
- export async function pProps(input, opt) {
18
- const r = {};
15
+ export async function pProps(input) {
19
16
  const keys = Object.keys(input);
20
- await pMap(Object.values(input), (v, i) => (r[keys[i]] = v), opt);
21
- return r;
17
+ return Object.fromEntries((await Promise.all(Object.values(input))).map((v, i) => [keys[i], v]));
22
18
  }
@@ -1,54 +1,85 @@
1
1
  import { _since, _stringifyAny } from '..';
2
+ import { TimeoutError } from './pTimeout';
2
3
  /**
3
4
  * Returns a Function (!), enhanced with retry capabilities.
4
5
  * Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
5
6
  */
6
- // eslint-disable-next-line @typescript-eslint/ban-types
7
- export function pRetry(fn, opt = {}) {
8
- const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name = fn.name, } = opt;
7
+ export function pRetryFn(fn, opt = {}) {
8
+ return async function pRetryFunction(...args) {
9
+ return await pRetry(() => fn.call(this, ...args), opt);
10
+ };
11
+ }
12
+ export async function pRetry(fn, opt = {}) {
13
+ const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, keepStackTrace = true, timeout, } = opt;
14
+ const fakeError = keepStackTrace ? new Error('RetryError') : undefined;
9
15
  let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
10
16
  if (opt.logAll) {
11
- logFirstAttempt = logRetries = logFailures = true;
17
+ logSuccess = logFirstAttempt = logRetries = logFailures = true;
12
18
  }
13
19
  if (opt.logNone) {
14
20
  logSuccess = logFirstAttempt = logRetries = logFailures = false;
15
21
  }
16
- const fname = ['pRetry', name].filter(Boolean).join('.');
17
- return async function (...args) {
18
- let delay = initialDelay;
19
- let attempt = 0;
20
- return await new Promise((resolve, reject) => {
21
- const next = async () => {
22
- const started = Date.now();
23
- try {
24
- attempt++;
25
- if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
26
- logger.log(`${fname} attempt #${attempt}...`);
27
- }
28
- const r = await fn.apply(this, args);
29
- if (logSuccess) {
30
- logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`);
31
- }
32
- resolve(r);
22
+ const fname = name || fn.name || 'pRetry function';
23
+ let delay = initialDelay;
24
+ let attempt = 0;
25
+ let timer;
26
+ let timedOut = false;
27
+ return await new Promise((resolve, reject) => {
28
+ const rejectWithTimeout = () => {
29
+ timedOut = true; // to prevent more tries
30
+ const err = new TimeoutError(`"${fname}" timed out after ${timeout} ms`);
31
+ if (fakeError) {
32
+ // keep original stack
33
+ err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
34
+ }
35
+ reject(err);
36
+ };
37
+ const next = async () => {
38
+ if (timedOut)
39
+ return;
40
+ if (timeout) {
41
+ timer = setTimeout(rejectWithTimeout, timeout);
42
+ }
43
+ const started = Date.now();
44
+ try {
45
+ attempt++;
46
+ if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
47
+ logger.log(`${fname} attempt #${attempt}...`);
33
48
  }
34
- catch (err) {
35
- if (logFailures) {
36
- logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, _stringifyAny(err, {
37
- includeErrorData: true,
38
- }));
39
- }
40
- if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
41
- // Give up
42
- reject(err);
43
- }
44
- else {
45
- // Retry after delay
46
- delay *= delayMultiplier;
47
- setTimeout(next, delay);
49
+ const r = await fn(attempt);
50
+ clearTimeout(timer);
51
+ if (logSuccess) {
52
+ logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`);
53
+ }
54
+ resolve(r);
55
+ }
56
+ catch (err) {
57
+ clearTimeout(timer);
58
+ if (logFailures) {
59
+ logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, _stringifyAny(err, {
60
+ includeErrorData: true,
61
+ }));
62
+ }
63
+ if (attempt >= maxAttempts ||
64
+ (predicate && !predicate(err, attempt, maxAttempts))) {
65
+ // Give up
66
+ if (fakeError) {
67
+ // Preserve the original call stack
68
+ Object.defineProperty(err, 'stack', {
69
+ value: err.stack +
70
+ '\n --' +
71
+ fakeError.stack.replace('Error: RetryError', ''),
72
+ });
48
73
  }
74
+ reject(err);
49
75
  }
50
- };
51
- void next();
52
- });
53
- };
76
+ else {
77
+ // Retry after delay
78
+ delay *= delayMultiplier;
79
+ setTimeout(next, delay);
80
+ }
81
+ }
82
+ };
83
+ void next();
84
+ });
54
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.78.1",
3
+ "version": "14.81.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -110,7 +110,7 @@ export function _LogMethod(opt: LogMethodOptions = {}): MethodDecorator {
110
110
  })
111
111
  .catch((err: any) => {
112
112
  logFinished(logger, callSignature, started, sma, logResultFn, undefined, err)
113
- return Promise.reject(err)
113
+ throw err
114
114
  })
115
115
  } else {
116
116
  // not a Promise
@@ -163,7 +163,7 @@ export const _Memo =
163
163
  cache.get(ctx)!.set(cacheKey, err instanceof Error ? err : new Error(err))
164
164
  }
165
165
 
166
- return Promise.reject(err)
166
+ throw err
167
167
  })
168
168
  } else {
169
169
  if (logMiss) {
@@ -83,7 +83,7 @@ export function _memoFn<T extends (...args: any[]) => any>(
83
83
  cache.set(cacheKey, err instanceof Error ? err : new Error(err))
84
84
  }
85
85
 
86
- return Promise.reject(err)
86
+ throw err
87
87
  }) as any
88
88
  } else {
89
89
  if (logMiss) {
@@ -1,10 +1,10 @@
1
- import { pRetry, PRetryOptions } from '..'
1
+ import { pRetryFn, PRetryOptions } from '..'
2
2
 
3
3
  // eslint-disable-next-line @typescript-eslint/naming-convention
4
4
  export function _Retry(opt: PRetryOptions = {}): MethodDecorator {
5
5
  return (target, key, descriptor) => {
6
6
  const originalFn = descriptor.value
7
- descriptor.value = pRetry(originalFn as any, opt)
7
+ descriptor.value = pRetryFn(originalFn as any, opt)
8
8
  return descriptor
9
9
  }
10
10
  }
package/src/error/try.ts CHANGED
@@ -27,9 +27,6 @@ export function _try<ERR = unknown, RETURN = void>(
27
27
  }
28
28
  }
29
29
 
30
- // todo: remove when eslint starts to know about Awaited
31
- /* eslint-disable no-undef */
32
-
33
30
  /**
34
31
  * Like _try, but for Promises.
35
32
  *
package/src/index.ts CHANGED
@@ -74,7 +74,7 @@ export * from './promise/pFilter'
74
74
  export * from './promise/pHang'
75
75
  import { pMap, PMapOptions } from './promise/pMap'
76
76
  export * from './promise/pProps'
77
- import { pRetry, PRetryOptions } from './promise/pRetry'
77
+ import { pRetry, pRetryFn, PRetryOptions } from './promise/pRetry'
78
78
  export * from './promise/pState'
79
79
  import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout'
80
80
  export * from './promise/pTuple'
@@ -250,6 +250,7 @@ export {
250
250
  pDefer,
251
251
  AggregatedError,
252
252
  pRetry,
253
+ pRetryFn,
253
254
  pTimeout,
254
255
  pTimeoutFn,
255
256
  _tryCatch,
@@ -365,9 +365,9 @@ export class JsonSchemaObjectBuilder<T extends AnyObject> extends JsonSchemaAnyB
365
365
  return this
366
366
  }
367
367
 
368
- baseDBEntity(): JsonSchemaObjectBuilder<T & BaseDBEntity> {
368
+ baseDBEntity<ID = string>(idType = 'string'): JsonSchemaObjectBuilder<T & BaseDBEntity<ID>> {
369
369
  Object.assign(this.schema.properties, {
370
- id: { type: 'string' },
370
+ id: { type: idType },
371
371
  created: { type: 'number', format: 'unixTimestamp' },
372
372
  updated: { type: 'number', format: 'unixTimestamp' },
373
373
  })
@@ -375,8 +375,8 @@ export class JsonSchemaObjectBuilder<T extends AnyObject> extends JsonSchemaAnyB
375
375
  return this
376
376
  }
377
377
 
378
- savedDBEntity(): JsonSchemaObjectBuilder<T & SavedDBEntity> {
379
- return this.baseDBEntity().addRequired(['id', 'created', 'updated']) as any
378
+ savedDBEntity<ID = string>(idType = 'string'): JsonSchemaObjectBuilder<T & SavedDBEntity<ID>> {
379
+ return this.baseDBEntity(idType).addRequired(['id', 'created', 'updated']) as any
380
380
  }
381
381
 
382
382
  extend<T2 extends AnyObject>(s2: JsonSchemaObjectBuilder<T2>): JsonSchemaObjectBuilder<T & T2> {
@@ -1,28 +1,20 @@
1
- /*
2
- Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
3
-
4
- Improvements:
5
-
6
- - Exported as { pProps }, so IDE auto-completion works
7
- - Simpler: no support for Map, Mapper, Options
8
- - Included Typescript typings (no need for @types/p-props)
9
- */
10
-
11
- import { pMap, PMapOptions } from './pMap'
12
-
13
- // todo: remove when eslint starts to know about Awaited
14
- /* eslint-disable no-undef */
15
-
16
1
  /**
17
2
  * Promise.all for Object instead of Array.
18
- * Supports concurrency.
3
+ *
4
+ * Inspired by Bluebird Promise.props() and https://github.com/sindresorhus/p-props
5
+ *
6
+ * Improvements:
7
+ *
8
+ * - Exported as { pProps }, so IDE auto-completion works
9
+ * - Simpler: no support for Map, Mapper, Options
10
+ * - Included Typescript typings (no need for @types/p-props)
11
+ *
12
+ * Concurrency implementation via pMap was removed in favor of preserving async
13
+ * stack traces (more important!).
19
14
  */
20
- export async function pProps<T>(
21
- input: { [K in keyof T]: T[K] | Promise<T[K]> },
22
- opt?: PMapOptions,
23
- ): Promise<{ [K in keyof T]: Awaited<T[K]> }> {
24
- const r = {} as { [K in keyof T]: Awaited<T[K]> }
25
- const keys = Object.keys(input) as (keyof T)[]
26
- await pMap(Object.values(input), (v, i) => (r[keys[i]!] = v), opt)
27
- return r
15
+ export async function pProps<T>(input: { [K in keyof T]: T[K] | Promise<T[K]> }): Promise<{
16
+ [K in keyof T]: Awaited<T[K]>
17
+ }> {
18
+ const keys = Object.keys(input)
19
+ return Object.fromEntries((await Promise.all(Object.values(input))).map((v, i) => [keys[i], v]))
28
20
  }
@@ -1,4 +1,5 @@
1
1
  import { _since, _stringifyAny, AnyFunction, CommonLogger } from '..'
2
+ import { TimeoutError } from './pTimeout'
2
3
 
3
4
  export interface PRetryOptions {
4
5
  /**
@@ -7,6 +8,13 @@ export interface PRetryOptions {
7
8
  */
8
9
  name?: string
9
10
 
11
+ /**
12
+ * Timeout for each Try, in milliseconds.
13
+ *
14
+ * Defaults to 60_000
15
+ */
16
+ timeout?: number
17
+
10
18
  /**
11
19
  * How many attempts to try.
12
20
  * First attempt is not a retry, but "initial try". It still counts.
@@ -34,7 +42,7 @@ export interface PRetryOptions {
34
42
  *
35
43
  * @default () => true
36
44
  */
37
- predicate?: (err: unknown, attempt: number, maxAttempts: number) => boolean
45
+ predicate?: (err: Error, attempt: number, maxAttempts: number) => boolean
38
46
 
39
47
  /**
40
48
  * Log the first attempt (which is not a "retry" yet).
@@ -74,76 +82,132 @@ export interface PRetryOptions {
74
82
  * Default to `console`
75
83
  */
76
84
  logger?: CommonLogger
85
+
86
+ /**
87
+ * Defaults to true.
88
+ * If true - preserves the stack trace in case of a Timeout (usually - very useful!).
89
+ * It has a certain perf cost.
90
+ *
91
+ * @experimental
92
+ */
93
+ keepStackTrace?: boolean
77
94
  }
78
95
 
79
96
  /**
80
97
  * Returns a Function (!), enhanced with retry capabilities.
81
98
  * Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
82
99
  */
83
- // eslint-disable-next-line @typescript-eslint/ban-types
84
- export function pRetry<T extends AnyFunction>(fn: T, opt: PRetryOptions = {}): T {
100
+ export function pRetryFn<T extends AnyFunction>(fn: T, opt: PRetryOptions = {}): T {
101
+ return async function pRetryFunction(this: any, ...args: any[]) {
102
+ return await pRetry(() => fn.call(this, ...args), opt)
103
+ } as any
104
+ }
105
+
106
+ export async function pRetry<T>(
107
+ fn: (attempt: number) => Promise<T>,
108
+ opt: PRetryOptions = {},
109
+ ): Promise<T> {
85
110
  const {
86
111
  maxAttempts = 4,
87
112
  delay: initialDelay = 1000,
88
113
  delayMultiplier = 2,
89
114
  predicate,
90
115
  logger = console,
91
- name = fn.name,
116
+ name,
117
+ keepStackTrace = true,
118
+ timeout,
92
119
  } = opt
93
120
 
121
+ const fakeError = keepStackTrace ? new Error('RetryError') : undefined
122
+
94
123
  let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt
95
124
 
96
125
  if (opt.logAll) {
97
- logFirstAttempt = logRetries = logFailures = true
126
+ logSuccess = logFirstAttempt = logRetries = logFailures = true
98
127
  }
99
128
  if (opt.logNone) {
100
129
  logSuccess = logFirstAttempt = logRetries = logFailures = false
101
130
  }
102
131
 
103
- const fname = ['pRetry', name].filter(Boolean).join('.')
132
+ const fname = name || fn.name || 'pRetry function'
104
133
 
105
- return async function (this: any, ...args: any[]) {
106
- let delay = initialDelay
107
- let attempt = 0
134
+ let delay = initialDelay
135
+ let attempt = 0
136
+ let timer: NodeJS.Timeout | undefined
137
+ let timedOut = false
108
138
 
109
- return await new Promise((resolve, reject) => {
110
- const next = async () => {
111
- const started = Date.now()
139
+ return await new Promise((resolve, reject) => {
140
+ const rejectWithTimeout = () => {
141
+ timedOut = true // to prevent more tries
142
+ const err = new TimeoutError(`"${fname}" timed out after ${timeout} ms`)
143
+ if (fakeError) {
144
+ // keep original stack
145
+ err.stack = fakeError.stack!.replace('Error: RetryError', 'TimeoutError')
146
+ }
147
+ reject(err)
148
+ }
112
149
 
113
- try {
114
- attempt++
115
- if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
116
- logger.log(`${fname} attempt #${attempt}...`)
117
- }
150
+ const next = async () => {
151
+ if (timedOut) return
118
152
 
119
- const r = await fn.apply(this, args)
153
+ if (timeout) {
154
+ timer = setTimeout(rejectWithTimeout, timeout)
155
+ }
120
156
 
121
- if (logSuccess) {
122
- logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
123
- }
124
- resolve(r)
125
- } catch (err) {
126
- if (logFailures) {
127
- logger.warn(
128
- `${fname} attempt #${attempt} error in ${_since(started)}:`,
129
- _stringifyAny(err, {
130
- includeErrorData: true,
131
- }),
132
- )
133
- }
157
+ const started = Date.now()
158
+
159
+ try {
160
+ attempt++
161
+ if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
162
+ logger.log(`${fname} attempt #${attempt}...`)
163
+ }
164
+
165
+ const r = await fn(attempt)
166
+
167
+ clearTimeout(timer!)
134
168
 
135
- if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
136
- // Give up
137
- reject(err)
138
- } else {
139
- // Retry after delay
140
- delay *= delayMultiplier
141
- setTimeout(next, delay)
169
+ if (logSuccess) {
170
+ logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
171
+ }
172
+
173
+ resolve(r)
174
+ } catch (err) {
175
+ clearTimeout(timer!)
176
+
177
+ if (logFailures) {
178
+ logger.warn(
179
+ `${fname} attempt #${attempt} error in ${_since(started)}:`,
180
+ _stringifyAny(err, {
181
+ includeErrorData: true,
182
+ }),
183
+ )
184
+ }
185
+
186
+ if (
187
+ attempt >= maxAttempts ||
188
+ (predicate && !predicate(err as Error, attempt, maxAttempts))
189
+ ) {
190
+ // Give up
191
+
192
+ if (fakeError) {
193
+ // Preserve the original call stack
194
+ Object.defineProperty(err, 'stack', {
195
+ value:
196
+ (err as Error).stack +
197
+ '\n --' +
198
+ fakeError.stack!.replace('Error: RetryError', ''),
199
+ })
142
200
  }
201
+
202
+ reject(err)
203
+ } else {
204
+ // Retry after delay
205
+ delay *= delayMultiplier
206
+ setTimeout(next, delay)
143
207
  }
144
208
  }
209
+ }
145
210
 
146
- void next()
147
- })
148
- } as any
211
+ void next()
212
+ })
149
213
  }
package/src/types.ts CHANGED
@@ -29,15 +29,15 @@ export interface CreatedUpdated {
29
29
  updated: number
30
30
  }
31
31
 
32
- export interface CreatedUpdatedId extends CreatedUpdated {
33
- id: string
32
+ export interface CreatedUpdatedId<ID = string> extends CreatedUpdated {
33
+ id: ID
34
34
  }
35
35
 
36
- export interface ObjectWithId {
37
- id: string
36
+ export interface ObjectWithId<ID = string> {
37
+ id: ID
38
38
  }
39
39
 
40
- export interface AnyObjectWithId extends AnyObject, ObjectWithId {}
40
+ export interface AnyObjectWithId<ID = string> extends AnyObject, ObjectWithId<ID> {}
41
41
 
42
42
  /**
43
43
  * Convenience type shorthand.
@@ -169,8 +169,8 @@ export type UnixTimestamp = number
169
169
  /**
170
170
  * Base interface for any Entity that was saved to DB.
171
171
  */
172
- export interface SavedDBEntity {
173
- id: string
172
+ export interface SavedDBEntity<ID = string> {
173
+ id: ID
174
174
 
175
175
  /**
176
176
  * unixTimestamp of when the entity was first created (in the DB).
@@ -189,10 +189,10 @@ export interface SavedDBEntity {
189
189
  * hence `id`, `created` and `updated` fields CAN BE undefined (yet).
190
190
  * When it's known to be saved - `SavedDBEntity` interface can be used instead.
191
191
  */
192
- export type BaseDBEntity = Partial<SavedDBEntity>
192
+ export type BaseDBEntity<ID = string> = Partial<SavedDBEntity<ID>>
193
193
 
194
- export type Saved<E> = Merge<E, SavedDBEntity>
195
- export type Unsaved<E> = Merge<E, BaseDBEntity>
194
+ export type Saved<E, ID = string> = Merge<E, SavedDBEntity<ID>>
195
+ export type Unsaved<E, ID = string> = Merge<E, BaseDBEntity<ID>>
196
196
 
197
197
  /**
198
198
  * Named type for JSON.parse / JSON.stringify second argument