@nimbus-cqrs/core 2.1.2 → 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.
- package/esm/_dnt.polyfills.d.ts +7 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +1 -0
- package/esm/index.d.ts +4 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +4 -0
- package/esm/lib/env/getEnv.d.ts +15 -0
- package/esm/lib/env/getEnv.d.ts.map +1 -0
- package/esm/lib/env/getEnv.js +35 -0
- package/esm/lib/eventBus/eventBus.d.ts +0 -1
- package/esm/lib/eventBus/eventBus.d.ts.map +1 -1
- package/esm/lib/eventBus/eventBus.js +31 -32
- package/esm/lib/exception/exception.d.ts +1 -1
- package/esm/lib/exception/exception.d.ts.map +1 -1
- package/esm/lib/exception/invalidInputException.d.ts +1 -1
- package/esm/lib/exception/invalidInputException.d.ts.map +1 -1
- package/esm/lib/log/logTruncator.d.ts +96 -0
- package/esm/lib/log/logTruncator.d.ts.map +1 -0
- package/esm/lib/log/logTruncator.js +254 -0
- package/esm/lib/log/logger.d.ts +20 -0
- package/esm/lib/log/logger.d.ts.map +1 -1
- package/esm/lib/log/logger.js +28 -1
- package/esm/lib/log/options.d.ts +13 -1
- package/esm/lib/log/options.d.ts.map +1 -1
- package/esm/lib/message/command.d.ts +11 -1
- package/esm/lib/message/command.d.ts.map +1 -1
- package/esm/lib/message/command.js +17 -10
- package/esm/lib/message/event.d.ts +11 -1
- package/esm/lib/message/event.d.ts.map +1 -1
- package/esm/lib/message/event.js +17 -10
- package/esm/lib/message/message.d.ts +14 -0
- package/esm/lib/message/message.d.ts.map +1 -1
- package/esm/lib/message/message.js +47 -1
- package/esm/lib/message/query.d.ts +11 -1
- package/esm/lib/message/query.d.ts.map +1 -1
- package/esm/lib/message/query.js +16 -9
- package/esm/lib/retry/withRetry.d.ts +175 -0
- package/esm/lib/retry/withRetry.d.ts.map +1 -0
- package/esm/lib/retry/withRetry.js +207 -0
- package/package.json +2 -2
|
@@ -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.
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@opentelemetry/api": "^1.9.1",
|
|
37
|
-
"zod": "^4.3
|
|
37
|
+
"zod": "^4.4.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.0.0"
|