@nimbus-cqrs/core 2.2.0 → 2.3.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.
@@ -0,0 +1,7 @@
1
+ declare global {
2
+ interface Error {
3
+ cause?: unknown;
4
+ }
5
+ }
6
+ export {};
7
+ //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CACF;AAED,OAAO,EAAE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
package/esm/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import "./_dnt.polyfills.js";
2
+ export * from './lib/env/getEnv.js';
1
3
  export * from './lib/eventBus/eventBus.js';
2
4
  export * from './lib/exception/exception.js';
3
5
  export * from './lib/exception/forbiddenException.js';
@@ -6,6 +8,7 @@ export * from './lib/exception/invalidInputException.js';
6
8
  export * from './lib/exception/notFoundException.js';
7
9
  export * from './lib/exception/unauthorizedException.js';
8
10
  export * from './lib/log/logFormatter.js';
11
+ export * from './lib/log/logTruncator.js';
9
12
  export * from './lib/log/logger.js';
10
13
  export * from './lib/log/logLevel.js';
11
14
  export * from './lib/log/options.js';
@@ -14,5 +17,6 @@ export * from './lib/message/event.js';
14
17
  export * from './lib/message/message.js';
15
18
  export * from './lib/message/query.js';
16
19
  export * from './lib/message/router.js';
20
+ export * from './lib/retry/withRetry.js';
17
21
  export * from './lib/tracing/withSpan.js';
18
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC"}
package/esm/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import "./_dnt.polyfills.js";
2
+ export * from './lib/env/getEnv.js';
1
3
  export * from './lib/eventBus/eventBus.js';
2
4
  export * from './lib/exception/exception.js';
3
5
  export * from './lib/exception/forbiddenException.js';
@@ -6,6 +8,7 @@ export * from './lib/exception/invalidInputException.js';
6
8
  export * from './lib/exception/notFoundException.js';
7
9
  export * from './lib/exception/unauthorizedException.js';
8
10
  export * from './lib/log/logFormatter.js';
11
+ export * from './lib/log/logTruncator.js';
9
12
  export * from './lib/log/logger.js';
10
13
  export * from './lib/log/logLevel.js';
11
14
  export * from './lib/log/options.js';
@@ -14,4 +17,5 @@ export * from './lib/message/event.js';
14
17
  export * from './lib/message/message.js';
15
18
  export * from './lib/message/query.js';
16
19
  export * from './lib/message/router.js';
20
+ export * from './lib/retry/withRetry.js';
17
21
  export * from './lib/tracing/withSpan.js';
@@ -0,0 +1,15 @@
1
+ type GetEnvInput = {
2
+ variables: string[];
3
+ };
4
+ /**
5
+ * Get environment variables from the process.env object.
6
+ * Throws an exception if any of the variables are not defined
7
+ * and logs the missing variable names.
8
+ *
9
+ * @param variables - The list of environment variables to get
10
+ * @returns {Record<string, string>} Object of the environment variables
11
+ * @throws {GenericException} Thrown if any of the requested variables are not defined
12
+ */
13
+ export declare const getEnv: ({ variables, }: GetEnvInput) => Record<string, string>;
14
+ export {};
15
+ //# sourceMappingURL=getEnv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getEnv.d.ts","sourceRoot":"","sources":["../../../src/lib/env/getEnv.ts"],"names":[],"mappings":"AAIA,KAAK,WAAW,GAAG;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,GAAI,gBAEpB,WAAW,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CA2BrC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import process from 'node:process';
2
+ import { GenericException } from '../exception/genericException.js';
3
+ import { getLogger } from '../log/logger.js';
4
+ /**
5
+ * Get environment variables from the process.env object.
6
+ * Throws an exception if any of the variables are not defined
7
+ * and logs the missing variable names.
8
+ *
9
+ * @param variables - The list of environment variables to get
10
+ * @returns {Record<string, string>} Object of the environment variables
11
+ * @throws {GenericException} Thrown if any of the requested variables are not defined
12
+ */
13
+ export const getEnv = ({ variables, }) => {
14
+ const envVars = {};
15
+ const missingEnvVars = [];
16
+ for (const variable of variables) {
17
+ if (!process.env[variable]) {
18
+ missingEnvVars.push(variable);
19
+ }
20
+ envVars[variable] = process.env[variable];
21
+ }
22
+ if (missingEnvVars.length > 0) {
23
+ getLogger().error({
24
+ category: 'Nimbus',
25
+ message: 'Undefined environment variables',
26
+ data: {
27
+ undefinedVariables: missingEnvVars,
28
+ },
29
+ });
30
+ throw new GenericException('Undefined environment variables', {
31
+ undefinedVariables: missingEnvVars,
32
+ });
33
+ }
34
+ return envVars;
35
+ };
@@ -240,7 +240,6 @@ export declare class NimbusEventBus {
240
240
  private _processEvent;
241
241
  private _recordDeliveryMetrics;
242
242
  private _handleFinalFailure;
243
- private _calculateRetryDelay;
244
243
  /**
245
244
  * Validate the size of the event and return the size in bytes.
246
245
  *
@@ -1 +1 @@
1
- {"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../../../src/lib/eventBus/eventBus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AA0CjD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,MAAM,SAAS,KAAK,IAAI;IACpD;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;OAEG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAyB;IAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;gBACS,OAAO,CAAC,EAAE,qBAAqB;IAU3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACI,QAAQ,CAAC,MAAM,SAAS,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAqD1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDG;IACI,cAAc,CAAC,MAAM,SAAS,KAAK,EAAE,EACxC,IAAI,EACJ,OAAO,EACP,OAAO,EACP,OAAO,GACV,EAAE,mBAAmB,CAAC,MAAM,CAAC,GAAG,IAAI;IAqCrC,OAAO,CAAC,aAAa;IAmFrB,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,mBAAmB;IAwC3B,OAAO,CAAC,oBAAoB;IAW5B;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;CAmB7B;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,aAAa,GACtB,MAAM,MAAM,EACZ,UAAU,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,KAC9C,IAEF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,WAAW,GAAI,OAAM,MAAkB,KAAG,cAStD,CAAC"}
1
+ {"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../../../src/lib/eventBus/eventBus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AA2CjD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,MAAM,SAAS,KAAK,IAAI;IACpD;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;OAEG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAyB;IAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;gBACS,OAAO,CAAC,EAAE,qBAAqB;IAU3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACI,QAAQ,CAAC,MAAM,SAAS,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAqD1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDG;IACI,cAAc,CAAC,MAAM,SAAS,KAAK,EAAE,EACxC,IAAI,EACJ,OAAO,EACP,OAAO,EACP,OAAO,GACV,EAAE,mBAAmB,CAAC,MAAM,CAAC,GAAG,IAAI;IAqCrC,OAAO,CAAC,aAAa;IA4ErB,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,mBAAmB;IAwC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;CAmB7B;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,aAAa,GACtB,MAAM,MAAM,EACZ,UAAU,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,KAC9C,IAEF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,WAAW,GAAI,OAAM,MAAkB,KAAG,cAStD,CAAC"}
@@ -2,6 +2,7 @@ import { metrics, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';
2
2
  import EventEmitter from 'node:events';
3
3
  import { GenericException } from '../exception/genericException.js';
4
4
  import { getLogger } from '../log/logger.js';
5
+ import { withRetry } from '../retry/withRetry.js';
5
6
  const tracer = trace.getTracer('nimbus');
6
7
  const meter = metrics.getMeter('nimbus');
7
8
  const eventsPublishedCounter = meter.createCounter('eventbus_events_published_total', {
@@ -301,35 +302,38 @@ export class NimbusEventBus {
301
302
  }),
302
303
  },
303
304
  }, async (span) => {
304
- let attempt = 0;
305
- while (attempt <= maxRetries) {
306
- try {
305
+ try {
306
+ await withRetry(async () => {
307
307
  await handler(event);
308
- this._recordDeliveryMetrics(metricLabels, 'success', startTime);
309
- span.end();
310
- return;
311
- }
312
- catch (error) {
313
- attempt++;
314
- if (attempt > maxRetries) {
315
- this._handleFinalFailure({
316
- error,
317
- event,
318
- span,
319
- metricLabels,
320
- startTime,
321
- retryConfig: {
322
- maxRetries,
323
- baseDelay,
324
- maxDelay,
325
- },
308
+ }, {
309
+ maxRetries,
310
+ initialDelayMs: baseDelay,
311
+ maxDelayMs: maxDelay,
312
+ jitterFactor: useJitter ? 0.1 : 0,
313
+ onRetry: ({ attempt, delayMs }) => {
314
+ retryAttemptsCounter.add(1, metricLabels);
315
+ span.addEvent('retry', {
316
+ attempt,
317
+ delay_ms: delayMs,
326
318
  });
327
- }
328
- retryAttemptsCounter.add(1, metricLabels);
329
- const delayMs = this._calculateRetryDelay(attempt, baseDelay, maxDelay, useJitter);
330
- span.addEvent('retry', { attempt, delay_ms: delayMs });
331
- await new Promise((resolve) => setTimeout(resolve, delayMs));
332
- }
319
+ },
320
+ });
321
+ this._recordDeliveryMetrics(metricLabels, 'success', startTime);
322
+ span.end();
323
+ }
324
+ catch (error) {
325
+ this._handleFinalFailure({
326
+ error,
327
+ event,
328
+ span,
329
+ metricLabels,
330
+ startTime,
331
+ retryConfig: {
332
+ maxRetries,
333
+ baseDelay,
334
+ maxDelay,
335
+ },
336
+ });
333
337
  }
334
338
  });
335
339
  }
@@ -355,11 +359,6 @@ export class NimbusEventBus {
355
359
  }
356
360
  throw exception;
357
361
  }
358
- _calculateRetryDelay(attempt, baseDelay, maxDelay, useJitter) {
359
- const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
360
- const jitter = useJitter ? Math.random() * delay * 0.1 : 0;
361
- return delay + jitter;
362
- }
363
362
  /**
364
363
  * Validate the size of the event and return the size in bytes.
365
364
  *
@@ -0,0 +1,96 @@
1
+ import type { LogInput } from './logger.js';
2
+ /**
3
+ * Truncates a {@link LogInput} before it is turned into a log record.
4
+ *
5
+ * Use with {@link setupLogger} via the `truncator` option, or provide a custom
6
+ * implementation. The built-in factory is {@link createLogTruncator}.
7
+ */
8
+ export type LogTruncator = (logInput: LogInput) => LogInput;
9
+ /**
10
+ * Options for {@link createLogTruncator}.
11
+ *
12
+ * All fields are optional; omitted fields use the built-in defaults.
13
+ */
14
+ export type LogTruncatorOptions = {
15
+ /**
16
+ * Maximum serialized byte size of `data` after structural truncation.
17
+ * If still over budget (or not JSON-serializable), `data` is replaced
18
+ * with a size marker. Defaults to 16_384 (16 KB).
19
+ */
20
+ maxBytes?: number;
21
+ /**
22
+ * Maximum number of array items kept inside `data` (also applies when
23
+ * converting `Map` / `Set` to arrays). Defaults to 50.
24
+ */
25
+ maxArrayItems?: number;
26
+ /**
27
+ * Maximum number of own enumerable keys kept on plain objects inside
28
+ * `data`. Defaults to 50.
29
+ */
30
+ maxObjectKeys?: number;
31
+ /**
32
+ * Maximum object nesting depth inside `data`, and maximum depth when
33
+ * walking `error.cause` / aggregate error chains.
34
+ * Defaults to 8. Values below 1 are clamped to 1 so root `data` stays
35
+ * an object.
36
+ */
37
+ maxDepth?: number;
38
+ /**
39
+ * Maximum length of `category`.
40
+ * Defaults to 50.
41
+ */
42
+ maxCategoryLength?: number;
43
+ /**
44
+ * Maximum length of `message` and `error.message`.
45
+ * Defaults to 200.
46
+ */
47
+ maxMessageLength?: number;
48
+ /**
49
+ * Maximum length of `error.stack`.
50
+ * Defaults to 200.
51
+ */
52
+ maxStackLength?: number;
53
+ /**
54
+ * Maximum length of string values inside `data`.
55
+ * Defaults to 200.
56
+ */
57
+ maxDataStringLength?: number;
58
+ };
59
+ /**
60
+ * Creates a {@link LogTruncator} that truncates each {@link LogInput} field
61
+ * separately (except `correlationId`, which is left untouched).
62
+ *
63
+ * - `message` / `category`: string length caps
64
+ * - `data`: structural truncation (arrays, objects, strings, depth, circular
65
+ * refs, common JS types) plus a byte-size cliff that replaces oversized or
66
+ * unserializable payloads with a size marker
67
+ * - `error`: stays `Error`-shaped; truncates `message` / `stack`, copies
68
+ * enumerable custom fields, and walks `cause` / aggregate errors up to
69
+ * `maxDepth`
70
+ *
71
+ * @param options - Optional limits; omitted fields use built-in defaults.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { createLogTruncator, setupLogger } from '@nimbus-cqrs/core';
76
+ *
77
+ * setupLogger({
78
+ * truncator: createLogTruncator(),
79
+ * });
80
+ *
81
+ * setupLogger({
82
+ * truncator: createLogTruncator({
83
+ * maxBytes: 8_192,
84
+ * maxArrayItems: 50,
85
+ * maxObjectKeys: 50,
86
+ * maxDepth: 8,
87
+ * maxCategoryLength: 50,
88
+ * maxMessageLength: 100,
89
+ * maxStackLength: 200,
90
+ * maxDataStringLength: 200,
91
+ * }),
92
+ * });
93
+ * ```
94
+ */
95
+ export declare const createLogTruncator: (options?: LogTruncatorOptions) => LogTruncator;
96
+ //# sourceMappingURL=logTruncator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logTruncator.d.ts","sourceRoot":"","sources":["../../../src/lib/log/logTruncator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAsDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,kBAAkB,GAC3B,UAAS,mBAAwB,KAClC,YA+TF,CAAC"}
@@ -0,0 +1,254 @@
1
+ const DEFAULT_MAX_BYTES = 16_384;
2
+ const DEFAULT_MAX_ARRAY_ITEMS = 50;
3
+ const DEFAULT_MAX_OBJECT_KEYS = 50;
4
+ const DEFAULT_MAX_DEPTH = 8;
5
+ const DEFAULT_MAX_CATEGORY_LENGTH = 50;
6
+ const DEFAULT_MAX_MESSAGE_LENGTH = 200;
7
+ const DEFAULT_MAX_STACK_LENGTH = 200;
8
+ const DEFAULT_MAX_DATA_STRING_LENGTH = 200;
9
+ const serializedByteSize = (value) => {
10
+ try {
11
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ };
17
+ const TRUNCATED_STRING_SUFFIX = ' [... __truncated]';
18
+ const truncateString = (value, maxLength) => {
19
+ if (value.length <= maxLength) {
20
+ return value;
21
+ }
22
+ return `${value.slice(0, maxLength)}${TRUNCATED_STRING_SUFFIX}`;
23
+ };
24
+ /**
25
+ * Creates a {@link LogTruncator} that truncates each {@link LogInput} field
26
+ * separately (except `correlationId`, which is left untouched).
27
+ *
28
+ * - `message` / `category`: string length caps
29
+ * - `data`: structural truncation (arrays, objects, strings, depth, circular
30
+ * refs, common JS types) plus a byte-size cliff that replaces oversized or
31
+ * unserializable payloads with a size marker
32
+ * - `error`: stays `Error`-shaped; truncates `message` / `stack`, copies
33
+ * enumerable custom fields, and walks `cause` / aggregate errors up to
34
+ * `maxDepth`
35
+ *
36
+ * @param options - Optional limits; omitted fields use built-in defaults.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { createLogTruncator, setupLogger } from '@nimbus-cqrs/core';
41
+ *
42
+ * setupLogger({
43
+ * truncator: createLogTruncator(),
44
+ * });
45
+ *
46
+ * setupLogger({
47
+ * truncator: createLogTruncator({
48
+ * maxBytes: 8_192,
49
+ * maxArrayItems: 50,
50
+ * maxObjectKeys: 50,
51
+ * maxDepth: 8,
52
+ * maxCategoryLength: 50,
53
+ * maxMessageLength: 100,
54
+ * maxStackLength: 200,
55
+ * maxDataStringLength: 200,
56
+ * }),
57
+ * });
58
+ * ```
59
+ */
60
+ export const createLogTruncator = (options = {}) => {
61
+ // Clamp so root `data` stays an object and string/array limits stay usable.
62
+ const maxBytes = Math.max(0, options.maxBytes ?? DEFAULT_MAX_BYTES);
63
+ const maxArrayItems = Math.max(0, options.maxArrayItems ?? DEFAULT_MAX_ARRAY_ITEMS);
64
+ const maxObjectKeys = Math.max(0, options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS);
65
+ const maxDepth = Math.max(1, options.maxDepth ?? DEFAULT_MAX_DEPTH);
66
+ const maxCategoryLength = Math.max(0, options.maxCategoryLength ?? DEFAULT_MAX_CATEGORY_LENGTH);
67
+ const maxMessageLength = Math.max(0, options.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH);
68
+ const maxStackLength = Math.max(0, options.maxStackLength ?? DEFAULT_MAX_STACK_LENGTH);
69
+ const maxDataStringLength = Math.max(0, options.maxDataStringLength ?? DEFAULT_MAX_DATA_STRING_LENGTH);
70
+ const truncateDataValue = (value, depth, seen) => {
71
+ if (typeof value === 'bigint') {
72
+ return truncateString(`${value}n`, maxDataStringLength);
73
+ }
74
+ if (value === null || typeof value !== 'object') {
75
+ if (typeof value === 'string' &&
76
+ value.length > maxDataStringLength) {
77
+ return truncateString(value, maxDataStringLength);
78
+ }
79
+ return value;
80
+ }
81
+ if (seen.has(value)) {
82
+ return '[Circular]';
83
+ }
84
+ if (depth >= maxDepth) {
85
+ return '[Max depth]';
86
+ }
87
+ seen.add(value);
88
+ try {
89
+ if (value instanceof Date) {
90
+ return truncateString(value.toISOString(), maxDataStringLength);
91
+ }
92
+ if (value instanceof RegExp) {
93
+ return truncateString(value.toString(), maxDataStringLength);
94
+ }
95
+ if (value instanceof ArrayBuffer) {
96
+ return {
97
+ __truncated: true,
98
+ reason: 'arrayBuffer',
99
+ byteLength: value.byteLength,
100
+ };
101
+ }
102
+ if (ArrayBuffer.isView(value)) {
103
+ return {
104
+ __truncated: true,
105
+ reason: 'typedArray',
106
+ type: value.constructor.name,
107
+ byteLength: value.byteLength,
108
+ };
109
+ }
110
+ if (value instanceof Error) {
111
+ return truncateErrorAsData(value, depth, seen);
112
+ }
113
+ if (value instanceof Map) {
114
+ return truncateDataValue(Array.from(value.entries()), depth + 1, seen);
115
+ }
116
+ if (value instanceof Set) {
117
+ return truncateDataValue(Array.from(value.values()), depth + 1, seen);
118
+ }
119
+ if (Array.isArray(value)) {
120
+ if (value.length > maxArrayItems) {
121
+ return [
122
+ ...value
123
+ .slice(0, maxArrayItems)
124
+ .map((item) => truncateDataValue(item, depth + 1, seen)),
125
+ {
126
+ __truncated: true,
127
+ omittedCount: value.length - maxArrayItems,
128
+ },
129
+ ];
130
+ }
131
+ return value.map((item) => truncateDataValue(item, depth + 1, seen));
132
+ }
133
+ const entries = Object.entries(value);
134
+ const limited = entries.length > maxObjectKeys
135
+ ? entries.slice(0, maxObjectKeys)
136
+ : entries;
137
+ const result = {};
138
+ for (const [key, entry] of limited) {
139
+ result[key] = truncateDataValue(entry, depth + 1, seen);
140
+ }
141
+ if (entries.length > maxObjectKeys) {
142
+ result.__truncated = true;
143
+ result.omittedKeyCount = entries.length - maxObjectKeys;
144
+ }
145
+ return result;
146
+ }
147
+ finally {
148
+ // Recursion stack: allow shared refs, still detect true cycles.
149
+ seen.delete(value);
150
+ }
151
+ };
152
+ const truncateErrorAsData = (error, depth, seen) => {
153
+ const result = {
154
+ name: error.name,
155
+ message: truncateString(error.message, maxMessageLength),
156
+ };
157
+ if (typeof error.stack === 'string') {
158
+ result.stack = truncateString(error.stack, maxStackLength);
159
+ }
160
+ for (const [key, entry] of Object.entries(error)) {
161
+ if (key === 'name' ||
162
+ key === 'message' ||
163
+ key === 'stack' ||
164
+ key === 'cause' ||
165
+ key === 'errors') {
166
+ continue;
167
+ }
168
+ result[key] = truncateDataValue(entry, depth + 1, seen);
169
+ }
170
+ if ('cause' in error && error.cause !== undefined) {
171
+ result.cause = truncateDataValue(error.cause, depth + 1, seen);
172
+ }
173
+ if (typeof AggregateError !== 'undefined' &&
174
+ error instanceof AggregateError) {
175
+ result.errors = truncateDataValue(error.errors, depth + 1, seen);
176
+ }
177
+ return result;
178
+ };
179
+ const truncateData = (data) => {
180
+ const truncated = truncateDataValue(data, 0, new WeakSet());
181
+ const byteSize = serializedByteSize(truncated);
182
+ if (byteSize !== undefined && byteSize <= maxBytes) {
183
+ return truncated;
184
+ }
185
+ return {
186
+ __truncated: true,
187
+ reason: byteSize === undefined ? 'unserializable' : 'size',
188
+ ...(byteSize !== undefined ? { serializedByteSize: byteSize } : {}),
189
+ maxBytes,
190
+ };
191
+ };
192
+ const copyCustomErrorFields = (source, target, depth) => {
193
+ for (const [key, entry] of Object.entries(source)) {
194
+ if (key === 'name' ||
195
+ key === 'message' ||
196
+ key === 'stack' ||
197
+ key === 'cause' ||
198
+ key === 'errors') {
199
+ continue;
200
+ }
201
+ target[key] =
202
+ entry instanceof Error
203
+ ? truncateError(entry, depth + 1)
204
+ : truncateDataValue(entry, depth + 1, new WeakSet());
205
+ }
206
+ };
207
+ const truncateError = (error, depth) => {
208
+ if (depth >= maxDepth) {
209
+ const maxDepthError = new Error('[Max depth]');
210
+ maxDepthError.name = error.name;
211
+ return maxDepthError;
212
+ }
213
+ const message = truncateString(error.message, maxMessageLength);
214
+ let truncated;
215
+ if (typeof AggregateError !== 'undefined' &&
216
+ error instanceof AggregateError) {
217
+ truncated = new AggregateError(error.errors.map((item) => item instanceof Error
218
+ ? truncateError(item, depth + 1)
219
+ : truncateDataValue(item, depth + 1, new WeakSet())), message);
220
+ }
221
+ else {
222
+ truncated = new Error(message);
223
+ }
224
+ truncated.name = error.name;
225
+ if (typeof error.stack === 'string') {
226
+ truncated.stack = truncateString(error.stack, maxStackLength);
227
+ }
228
+ if ('cause' in error && error.cause !== undefined) {
229
+ truncated.cause = error.cause instanceof Error
230
+ ? truncateError(error.cause, depth + 1)
231
+ : truncateDataValue(error.cause, depth + 1, new WeakSet());
232
+ }
233
+ copyCustomErrorFields(error, truncated, depth);
234
+ return truncated;
235
+ };
236
+ return (logInput) => {
237
+ const truncated = {
238
+ message: truncateString(logInput.message, maxMessageLength),
239
+ };
240
+ if (logInput.category !== undefined) {
241
+ truncated.category = truncateString(logInput.category, maxCategoryLength);
242
+ }
243
+ if (logInput.data !== undefined) {
244
+ truncated.data = truncateData(logInput.data);
245
+ }
246
+ if (logInput.error !== undefined) {
247
+ truncated.error = truncateError(logInput.error, 0);
248
+ }
249
+ if (logInput.correlationId !== undefined) {
250
+ truncated.correlationId = logInput.correlationId;
251
+ }
252
+ return truncated;
253
+ };
254
+ };
@@ -32,6 +32,12 @@ export type LogInput = {
32
32
  * Useful for tracking a request through multiple services or handlers.
33
33
  */
34
34
  correlationId?: string;
35
+ /**
36
+ * When `true`, skips the configured truncator for this log call.
37
+ * Useful for intentionally logging full payloads. Defaults to `false`.
38
+ * This flag is not included in the emitted log record.
39
+ */
40
+ skipTruncation?: boolean;
35
41
  };
36
42
  /**
37
43
  * A full log record with the log input and additional metadata attached.
@@ -80,6 +86,7 @@ export type LogRecord = {
80
86
  * @example
81
87
  * ```ts
82
88
  * import {
89
+ * createLogTruncator,
83
90
  * getLogger,
84
91
  * jsonLogFormatter,
85
92
  * parseLogLevel,
@@ -94,6 +101,7 @@ export type LogRecord = {
94
101
  * ? jsonLogFormatter
95
102
  * : prettyLogFormatter,
96
103
  * useConsoleColors: process.env.NODE_ENV !== 'production',
104
+ * truncator: createLogTruncator(),
97
105
  * });
98
106
  *
99
107
  * // Use the logger throughout your application
@@ -118,6 +126,7 @@ export declare class Logger {
118
126
  private readonly _logLevel;
119
127
  private readonly _formatter;
120
128
  private readonly _useConsoleColors;
129
+ private readonly _truncator?;
121
130
  constructor(options: LogOptions);
122
131
  /**
123
132
  * Configure the Logger.
@@ -125,6 +134,7 @@ export declare class Logger {
125
134
  * @param {LogOptions} options - The options for the Logger
126
135
  * @param {LogLevel} options.logLevel - The log level to use for the Logger
127
136
  * @param {LogFormatter} options.formatter - The formatter to use for the Logger
137
+ * @param {LogTruncator} options.truncator - Optional truncator for log inputs
128
138
  */
129
139
  static configure(options: LogOptions): void;
130
140
  /**
@@ -289,6 +299,14 @@ export declare class Logger {
289
299
  * @returns {string} The colorized string
290
300
  */
291
301
  private _colorizeString;
302
+ /**
303
+ * Apply the configured truncator, failing open if it throws.
304
+ *
305
+ * @param {LogInput} logInput - The original log input
306
+ *
307
+ * @returns {LogInput} The truncated log input, or the original on failure
308
+ */
309
+ private _applyTruncator;
292
310
  /**
293
311
  * Log a message.
294
312
  *
@@ -306,6 +324,7 @@ export declare class Logger {
306
324
  * @example
307
325
  * ```ts
308
326
  * import {
327
+ * createLogTruncator,
309
328
  * jsonLogFormatter,
310
329
  * parseLogLevel,
311
330
  * prettyLogFormatter,
@@ -319,6 +338,7 @@ export declare class Logger {
319
338
  * ? prettyLogFormatter
320
339
  * : jsonLogFormatter,
321
340
  * useConsoleColors: process.env.NODE_ENV === "development",
341
+ * truncator: createLogTruncator(),
322
342
  * });
323
343
  * ```
324
344
  */
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/lib/log/logger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,KAAK,QAAQ,EAAmB,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAElE;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IACnB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB;;OAEG;IACH,SAAS,EAAE,IAAI,CAAC;IAChB;;OAEG;IACH,KAAK,EAAE,QAAQ,CAAC;IAChB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,MAAM;IACf,OAAO,CAAC,MAAM,CAAC,SAAS,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;gBAEhC,OAAO,EAAE,UAAU;IAO/B;;;;;;OAMG;WACW,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAIlD;;;;OAIG;WACW,WAAW,IAAI,MAAM;IAQnC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMtC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMrC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMrC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMtC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMzC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAMxB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;CAwBf;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,UAAU,KAAG,IAEjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,eAAO,MAAM,SAAS,QAAO,MAE5B,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/lib/log/logger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EAAE,KAAK,QAAQ,EAAmB,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAGlE;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IACnB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB;;OAEG;IACH,SAAS,EAAE,IAAI,CAAC;IAChB;;OAEG;IACH,KAAK,EAAE,QAAQ,CAAC;IAChB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,MAAM;IACf,OAAO,CAAC,MAAM,CAAC,SAAS,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAe;gBAE/B,OAAO,EAAE,UAAU;IAQ/B;;;;;;;OAOG;WACW,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAIlD;;;;OAIG;WACW,WAAW,IAAI,MAAM;IAQnC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMtC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMrC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMrC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMtC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACI,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAMzC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAMxB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAgBvB;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;CAyBf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,UAAU,KAAG,IAEjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,eAAO,MAAM,SAAS,QAAO,MAE5B,CAAC"}
@@ -13,6 +13,7 @@ import { defaultLogOptions } from './options.js';
13
13
  * @example
14
14
  * ```ts
15
15
  * import {
16
+ * createLogTruncator,
16
17
  * getLogger,
17
18
  * jsonLogFormatter,
18
19
  * parseLogLevel,
@@ -27,6 +28,7 @@ import { defaultLogOptions } from './options.js';
27
28
  * ? jsonLogFormatter
28
29
  * : prettyLogFormatter,
29
30
  * useConsoleColors: process.env.NODE_ENV !== 'production',
31
+ * truncator: createLogTruncator(),
30
32
  * });
31
33
  *
32
34
  * // Use the logger throughout your application
@@ -51,11 +53,13 @@ export class Logger {
51
53
  _logLevel;
52
54
  _formatter;
53
55
  _useConsoleColors;
56
+ _truncator;
54
57
  constructor(options) {
55
58
  this._logLevel = options.logLevel ?? defaultLogOptions.logLevel;
56
59
  this._formatter = options.formatter ?? defaultLogOptions.formatter;
57
60
  this._useConsoleColors = options.useConsoleColors ??
58
61
  defaultLogOptions.useConsoleColors;
62
+ this._truncator = options.truncator ?? defaultLogOptions.truncator;
59
63
  }
60
64
  /**
61
65
  * Configure the Logger.
@@ -63,6 +67,7 @@ export class Logger {
63
67
  * @param {LogOptions} options - The options for the Logger
64
68
  * @param {LogLevel} options.logLevel - The log level to use for the Logger
65
69
  * @param {LogFormatter} options.formatter - The formatter to use for the Logger
70
+ * @param {LogTruncator} options.truncator - Optional truncator for log inputs
66
71
  */
67
72
  static configure(options) {
68
73
  Logger._instance = new Logger(options);
@@ -285,6 +290,25 @@ export class Logger {
285
290
  }
286
291
  return string;
287
292
  }
293
+ /**
294
+ * Apply the configured truncator, failing open if it throws.
295
+ *
296
+ * @param {LogInput} logInput - The original log input
297
+ *
298
+ * @returns {LogInput} The truncated log input, or the original on failure
299
+ */
300
+ _applyTruncator(logInput) {
301
+ if (!this._truncator || logInput.skipTruncation) {
302
+ return logInput;
303
+ }
304
+ try {
305
+ return this._truncator(logInput);
306
+ }
307
+ catch (error) {
308
+ console.warn('Log truncator failed; logging original input.', error);
309
+ return logInput;
310
+ }
311
+ }
288
312
  /**
289
313
  * Log a message.
290
314
  *
@@ -293,7 +317,8 @@ export class Logger {
293
317
  * @param {Function} logFunction - The log function
294
318
  */
295
319
  _log(logInput, logLevel, logFunction) {
296
- const logRecord = this._produceLogRecord(logInput, logLevel);
320
+ const truncatedInput = this._applyTruncator(logInput);
321
+ const logRecord = this._produceLogRecord(truncatedInput, logLevel);
297
322
  const formattedLogRecord = this._formatLogRecord(logRecord);
298
323
  if (Array.isArray(formattedLogRecord)) {
299
324
  if (this._useConsoleColors) {
@@ -320,6 +345,7 @@ export class Logger {
320
345
  * @example
321
346
  * ```ts
322
347
  * import {
348
+ * createLogTruncator,
323
349
  * jsonLogFormatter,
324
350
  * parseLogLevel,
325
351
  * prettyLogFormatter,
@@ -333,6 +359,7 @@ export class Logger {
333
359
  * ? prettyLogFormatter
334
360
  * : jsonLogFormatter,
335
361
  * useConsoleColors: process.env.NODE_ENV === "development",
362
+ * truncator: createLogTruncator(),
336
363
  * });
337
364
  * ```
338
365
  */
@@ -1,5 +1,6 @@
1
1
  import { type LogFormatter } from './logFormatter.js';
2
2
  import type { LogLevel } from './logLevel.js';
3
+ import type { LogTruncator } from './logTruncator.js';
3
4
  /**
4
5
  * Configuration options for the Logger.
5
6
  *
@@ -26,9 +27,20 @@ export type LogOptions = {
26
27
  * Defaults to false.
27
28
  */
28
29
  useConsoleColors?: boolean;
30
+ /**
31
+ * Optional truncator applied to each {@link LogInput} before formatting.
32
+ * Use {@link createLogTruncator} for the built-in implementation, or supply a custom one.
33
+ * Defaults to undefined (no truncation).
34
+ */
35
+ truncator?: LogTruncator;
29
36
  };
30
37
  /**
31
38
  * The default log options.
32
39
  */
33
- export declare const defaultLogOptions: Required<LogOptions>;
40
+ export declare const defaultLogOptions: {
41
+ logLevel: LogLevel;
42
+ formatter: LogFormatter;
43
+ useConsoleColors: boolean;
44
+ truncator?: LogTruncator;
45
+ };
34
46
  //# sourceMappingURL=options.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/log/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,UAAU,CAIlD,CAAC"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/log/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,YAAY,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,YAAY,CAAC;CAK5B,CAAC"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Context passed to {@link WithRetryOptions.shouldRetry} and
3
+ * {@link WithRetryOptions.onRetry} after a failed attempt.
4
+ */
5
+ export type RetryContext = {
6
+ /**
7
+ * The error thrown by the failed attempt.
8
+ */
9
+ error: Error;
10
+ /**
11
+ * The 1-based attempt number that failed.
12
+ */
13
+ attempt: number;
14
+ /**
15
+ * How many retries remain after this failure (not counting the
16
+ * attempt that just failed).
17
+ */
18
+ retriesLeft: number;
19
+ /**
20
+ * The delay in milliseconds before the next retry.
21
+ */
22
+ delayMs: number;
23
+ };
24
+ /**
25
+ * Options for {@link withRetry}.
26
+ */
27
+ export type WithRetryOptions = {
28
+ /**
29
+ * Maximum number of retries after the initial attempt.
30
+ * Total attempts = `maxRetries + 1`. Defaults to `2`.
31
+ */
32
+ maxRetries?: number;
33
+ /**
34
+ * Base delay in milliseconds before the first retry. Subsequent
35
+ * retries scale exponentially by {@link factor}. Defaults to `1000`.
36
+ */
37
+ initialDelayMs?: number;
38
+ /**
39
+ * Cap for the exponential backoff delay in milliseconds (applied
40
+ * before jitter). Defaults to `30000`.
41
+ */
42
+ maxDelayMs?: number;
43
+ /**
44
+ * Exponential growth factor. Defaults to `2`.
45
+ */
46
+ factor?: number;
47
+ /**
48
+ * Fraction of the capped base delay added as random jitter
49
+ * (`0` disables jitter). Defaults to `0.1` (up to 10%).
50
+ */
51
+ jitterFactor?: number;
52
+ /**
53
+ * Optional wall-clock budget for the entire retry sequence,
54
+ * measured with `performance.now()`. When exceeded, the last
55
+ * error is thrown without further attempts.
56
+ */
57
+ maxRetryTimeMs?: number;
58
+ /**
59
+ * Abort signal that cancels waiting between retries and prevents
60
+ * further attempts.
61
+ */
62
+ signal?: AbortSignal;
63
+ /**
64
+ * Decide whether a failed attempt should be retried. Returning
65
+ * `false` rejects with the failure error immediately.
66
+ */
67
+ shouldRetry?: (context: RetryContext) => boolean | Promise<boolean>;
68
+ /**
69
+ * Called after a failed attempt when another retry will occur,
70
+ * before waiting for {@link RetryContext.delayMs}.
71
+ */
72
+ onRetry?: (context: RetryContext) => void | Promise<void>;
73
+ };
74
+ /**
75
+ * Options for {@link calculateBackoffDelay}.
76
+ */
77
+ export type CalculateBackoffDelayOptions = {
78
+ /**
79
+ * Cap for the exponential base delay in milliseconds. Defaults to
80
+ * `Infinity` (no cap).
81
+ */
82
+ maxDelayMs?: number;
83
+ /**
84
+ * Exponential growth factor. Defaults to `2`.
85
+ */
86
+ factor?: number;
87
+ /**
88
+ * Fraction of the capped base delay added as random jitter
89
+ * (`0` disables jitter). Defaults to `0.1`.
90
+ */
91
+ jitterFactor?: number;
92
+ };
93
+ /**
94
+ * Error thrown from inside a {@link withRetry} operation to abort
95
+ * further retries immediately. The promise rejects with this error
96
+ * (or its cause message when constructed from another error).
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { RetryAbortedError, withRetry } from '@nimbus-cqrs/core';
101
+ *
102
+ * await withRetry(async () => {
103
+ * const response = await fetch(url);
104
+ * if (response.status === 404) {
105
+ * throw new RetryAbortedError('Resource not found');
106
+ * }
107
+ * return response.json();
108
+ * });
109
+ * ```
110
+ */
111
+ export declare class RetryAbortedError extends Error {
112
+ /**
113
+ * Optional underlying error when constructed from an `Error`.
114
+ */
115
+ readonly cause?: Error;
116
+ constructor(messageOrError?: string | Error);
117
+ }
118
+ /**
119
+ * Calculates an exponential backoff delay with optional jitter and
120
+ * max-delay cap for a given retry attempt.
121
+ *
122
+ * @param initialDelayMs - Base delay in milliseconds before exponential
123
+ * scaling.
124
+ * @param attempt - Zero-based retry attempt number (`0` for the first
125
+ * retry after the initial failure).
126
+ * @param options - Optional factor, max delay, and jitter settings.
127
+ * @returns The backoff delay in milliseconds (integer).
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { calculateBackoffDelay } from '@nimbus-cqrs/core';
132
+ *
133
+ * const delayMs = calculateBackoffDelay(1000, 0, {
134
+ * maxDelayMs: 30_000,
135
+ * jitterFactor: 0.1,
136
+ * });
137
+ * ```
138
+ */
139
+ export declare const calculateBackoffDelay: (initialDelayMs: number, attempt: number, options?: CalculateBackoffDelayOptions) => number;
140
+ /**
141
+ * Retries an async (or sync) function with exponential backoff until
142
+ * it succeeds, retries are exhausted, or retries are aborted.
143
+ *
144
+ * On exhaustion the last error is rethrown. Throw
145
+ * {@link RetryAbortedError} inside `fn` to stop retrying immediately.
146
+ * Pass an {@link AbortSignal} to cancel waiting between attempts.
147
+ *
148
+ * @param fn - Function to execute. Receives the 1-based attempt number.
149
+ * @param options - Retry, backoff, and callback options.
150
+ * @returns The fulfilled value of `fn`.
151
+ * @throws The last error after retries are exhausted, a
152
+ * {@link RetryAbortedError}, or the abort signal reason.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * import { withRetry } from '@nimbus-cqrs/core';
157
+ *
158
+ * const data = await withRetry(
159
+ * async (attempt) => {
160
+ * console.log(`Attempt ${attempt}`);
161
+ * return await fetch(url).then((r) => r.json());
162
+ * },
163
+ * {
164
+ * maxRetries: 3,
165
+ * initialDelayMs: 500,
166
+ * maxDelayMs: 10_000,
167
+ * onRetry: ({ attempt, delayMs, error }) => {
168
+ * console.warn(`Retry ${attempt} in ${delayMs}ms`, error);
169
+ * },
170
+ * },
171
+ * );
172
+ * ```
173
+ */
174
+ export declare const withRetry: <T>(fn: (attempt: number) => Promise<T> | T, options?: WithRetryOptions) => Promise<T>;
175
+ //# sourceMappingURL=withRetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withRetry.d.ts","sourceRoot":"","sources":["../../../src/lib/retry/withRetry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACvB;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;IACb;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,CACV,OAAO,EAAE,YAAY,KACpB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAQF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IACxC;;OAEG;IACH,SAAkB,KAAK,CAAC,EAAE,KAAK,CAAC;gBAEpB,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK;CAc9C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,qBAAqB,GAC9B,gBAAgB,MAAM,EACtB,SAAS,MAAM,EACf,UAAS,4BAAiC,KAC3C,MAcF,CAAC;AA+CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,SAAS,GAAU,CAAC,EAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACvC,UAAS,gBAAqB,KAC/B,OAAO,CAAC,CAAC,CAgFX,CAAC"}
@@ -0,0 +1,207 @@
1
+ const DEFAULT_MAX_RETRIES = 2;
2
+ const DEFAULT_INITIAL_DELAY_MS = 1000;
3
+ const DEFAULT_MAX_DELAY_MS = 30_000;
4
+ const DEFAULT_FACTOR = 2;
5
+ const DEFAULT_JITTER_FACTOR = 0.1;
6
+ /**
7
+ * Error thrown from inside a {@link withRetry} operation to abort
8
+ * further retries immediately. The promise rejects with this error
9
+ * (or its cause message when constructed from another error).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { RetryAbortedError, withRetry } from '@nimbus-cqrs/core';
14
+ *
15
+ * await withRetry(async () => {
16
+ * const response = await fetch(url);
17
+ * if (response.status === 404) {
18
+ * throw new RetryAbortedError('Resource not found');
19
+ * }
20
+ * return response.json();
21
+ * });
22
+ * ```
23
+ */
24
+ export class RetryAbortedError extends Error {
25
+ /**
26
+ * Optional underlying error when constructed from an `Error`.
27
+ */
28
+ cause;
29
+ constructor(messageOrError) {
30
+ const message = messageOrError instanceof Error
31
+ ? messageOrError.message
32
+ : (messageOrError ?? 'Retry aborted');
33
+ const cause = messageOrError instanceof Error
34
+ ? messageOrError
35
+ : undefined;
36
+ super(message, cause ? { cause } : undefined);
37
+ this.name = 'RetryAbortedError';
38
+ if (cause) {
39
+ this.cause = cause;
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * Calculates an exponential backoff delay with optional jitter and
45
+ * max-delay cap for a given retry attempt.
46
+ *
47
+ * @param initialDelayMs - Base delay in milliseconds before exponential
48
+ * scaling.
49
+ * @param attempt - Zero-based retry attempt number (`0` for the first
50
+ * retry after the initial failure).
51
+ * @param options - Optional factor, max delay, and jitter settings.
52
+ * @returns The backoff delay in milliseconds (integer).
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { calculateBackoffDelay } from '@nimbus-cqrs/core';
57
+ *
58
+ * const delayMs = calculateBackoffDelay(1000, 0, {
59
+ * maxDelayMs: 30_000,
60
+ * jitterFactor: 0.1,
61
+ * });
62
+ * ```
63
+ */
64
+ export const calculateBackoffDelay = (initialDelayMs, attempt, options = {}) => {
65
+ const factor = options.factor ?? DEFAULT_FACTOR;
66
+ const maxDelayMs = options.maxDelayMs ?? Infinity;
67
+ const jitterFactor = options.jitterFactor ?? DEFAULT_JITTER_FACTOR;
68
+ const baseDelay = Math.min(initialDelayMs * Math.pow(factor, attempt), maxDelayMs);
69
+ const jitter = jitterFactor > 0
70
+ ? Math.random() * baseDelay * jitterFactor
71
+ : 0;
72
+ return Math.floor(baseDelay + jitter);
73
+ };
74
+ const toError = (error) => error instanceof Error ? error : new Error(String(error));
75
+ const getAbortReason = (signal) => {
76
+ if (signal.reason instanceof Error) {
77
+ return signal.reason;
78
+ }
79
+ if (typeof signal.reason === 'string' && signal.reason.length > 0) {
80
+ return new Error(signal.reason);
81
+ }
82
+ return new DOMException('The operation was aborted.', 'AbortError');
83
+ };
84
+ /**
85
+ * Returns a promise that resolves after `ms` milliseconds, or rejects
86
+ * early when {@link signal} is aborted.
87
+ */
88
+ const delay = (ms, signal) => {
89
+ if (ms <= 0) {
90
+ if (signal?.aborted) {
91
+ return Promise.reject(getAbortReason(signal));
92
+ }
93
+ return Promise.resolve();
94
+ }
95
+ return new Promise((resolve, reject) => {
96
+ if (signal?.aborted) {
97
+ reject(getAbortReason(signal));
98
+ return;
99
+ }
100
+ const timer = setTimeout(() => {
101
+ signal?.removeEventListener('abort', onAbort);
102
+ resolve();
103
+ }, ms);
104
+ const onAbort = () => {
105
+ clearTimeout(timer);
106
+ reject(getAbortReason(signal));
107
+ };
108
+ signal?.addEventListener('abort', onAbort, { once: true });
109
+ });
110
+ };
111
+ /**
112
+ * Retries an async (or sync) function with exponential backoff until
113
+ * it succeeds, retries are exhausted, or retries are aborted.
114
+ *
115
+ * On exhaustion the last error is rethrown. Throw
116
+ * {@link RetryAbortedError} inside `fn` to stop retrying immediately.
117
+ * Pass an {@link AbortSignal} to cancel waiting between attempts.
118
+ *
119
+ * @param fn - Function to execute. Receives the 1-based attempt number.
120
+ * @param options - Retry, backoff, and callback options.
121
+ * @returns The fulfilled value of `fn`.
122
+ * @throws The last error after retries are exhausted, a
123
+ * {@link RetryAbortedError}, or the abort signal reason.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * import { withRetry } from '@nimbus-cqrs/core';
128
+ *
129
+ * const data = await withRetry(
130
+ * async (attempt) => {
131
+ * console.log(`Attempt ${attempt}`);
132
+ * return await fetch(url).then((r) => r.json());
133
+ * },
134
+ * {
135
+ * maxRetries: 3,
136
+ * initialDelayMs: 500,
137
+ * maxDelayMs: 10_000,
138
+ * onRetry: ({ attempt, delayMs, error }) => {
139
+ * console.warn(`Retry ${attempt} in ${delayMs}ms`, error);
140
+ * },
141
+ * },
142
+ * );
143
+ * ```
144
+ */
145
+ export const withRetry = async (fn, options = {}) => {
146
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
147
+ const initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
148
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
149
+ const factor = options.factor ?? DEFAULT_FACTOR;
150
+ const jitterFactor = options.jitterFactor ?? DEFAULT_JITTER_FACTOR;
151
+ const maxRetryTimeMs = options.maxRetryTimeMs;
152
+ const signal = options.signal;
153
+ const startedAt = performance.now();
154
+ let attempt = 0;
155
+ while (true) {
156
+ if (signal?.aborted) {
157
+ throw getAbortReason(signal);
158
+ }
159
+ attempt++;
160
+ try {
161
+ return await fn(attempt);
162
+ }
163
+ catch (error) {
164
+ if (error instanceof RetryAbortedError) {
165
+ throw error;
166
+ }
167
+ const err = toError(error);
168
+ const retriesLeft = maxRetries + 1 - attempt;
169
+ if (retriesLeft <= 0) {
170
+ throw err;
171
+ }
172
+ if (maxRetryTimeMs !== undefined &&
173
+ performance.now() - startedAt >= maxRetryTimeMs) {
174
+ throw err;
175
+ }
176
+ let delayMs = calculateBackoffDelay(initialDelayMs, attempt - 1, { maxDelayMs, factor, jitterFactor });
177
+ if (maxRetryTimeMs !== undefined) {
178
+ const remainingMs = maxRetryTimeMs -
179
+ (performance.now() - startedAt);
180
+ // floor() can turn a sub-millisecond remainder into 0.
181
+ // delay(0) is a no-op, so sync failures would otherwise
182
+ // burn through every remaining retry without ever
183
+ // advancing past maxRetryTimeMs.
184
+ if (remainingMs < 1) {
185
+ throw err;
186
+ }
187
+ delayMs = Math.min(delayMs, Math.floor(remainingMs));
188
+ }
189
+ const context = {
190
+ error: err,
191
+ attempt,
192
+ retriesLeft,
193
+ delayMs,
194
+ };
195
+ if (options.shouldRetry) {
196
+ const shouldRetry = await options.shouldRetry(context);
197
+ if (!shouldRetry) {
198
+ throw err;
199
+ }
200
+ }
201
+ if (options.onRetry) {
202
+ await options.onRetry(context);
203
+ }
204
+ await delay(delayMs, signal);
205
+ }
206
+ }
207
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-cqrs/core",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Simplify Event-Driven Applications - Core building blocks of the Nimbus framework.",
5
5
  "keywords": [
6
6
  "nimbus",