@zap-studio/retry 1.2.1 → 2.1.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.
@@ -1,4 +1,6 @@
1
- import { RetryPolicy, RetryRunOptions, RetryRunResult } from "./types.js";
1
+ import { r as RetryError, t as AbortError } from "./errors-CS5UPJWs.js";
2
+ import { RetryPolicy, RetryRunOptions, RetryRunResult, RetryRunResultOptions } from "./types.js";
3
+ import { ResultAsync } from "@zap-studio/monads";
2
4
  //#region src/base-policy.d.ts
3
5
  /**
4
6
  * Awaits a timer-based delay, unless `delayMs` is non-positive.
@@ -62,6 +64,43 @@ declare function runRetryPolicy<T, TError extends Error = Error, TData = unknown
62
64
  declare function runRetryPolicy<T, TError extends Error = Error, TData = unknown>(policy: RetryPolicy<TError, TData>, execute: (attempt: number) => Promise<T>, options?: RetryRunOptions & {
63
65
  throwOnExhausted?: true;
64
66
  }): Promise<T>;
67
+ /**
68
+ * Runs retry orchestration, returning a `ResultAsync` instead of throwing or
69
+ * returning the hand-rolled {@link RetryRunResult} union.
70
+ *
71
+ * Additive alternative to `runRetryPolicy` for consumers who prefer explicit
72
+ * `Result`/`ResultAsync` values (from `@zap-studio/monads`) over throw/catch.
73
+ * There's no `throwOnExhausted` option — this function always returns a
74
+ * `Result`, so the flag doesn't apply.
75
+ *
76
+ * @param policy - Retry policy: `next` is required, `onExhausted` and
77
+ * `isKnownError` fall back to their defaults when omitted.
78
+ * @param execute - Async function to execute per attempt.
79
+ * @param options - Runner settings, same as {@link RetryRunOptions} minus
80
+ * `throwOnExhausted`.
81
+ * @returns A `ResultAsync` resolving to `Ok` with the successful value, or `Err`
82
+ * with a `RetryError` (exhaustion) or `AbortError` (cancellation) — the same
83
+ * error object `runRetryPolicy`'s throw mode would throw. When
84
+ * `policy.isKnownError` rejects a caught value, it is wrapped in a new
85
+ * `RetryError` and returned on `Err` instead — in throw mode that same value
86
+ * is rethrown unchanged, not wrapped.
87
+ * @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep`
88
+ * function.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * import { runRetryPolicyResult } from "@zap-studio/retry";
93
+ *
94
+ * const result = await runRetryPolicyResult(policy, async () => fetchFlakyResource());
95
+ *
96
+ * if (isOk(result)) {
97
+ * console.log(result.value);
98
+ * } else {
99
+ * console.error(result.error);
100
+ * }
101
+ * ```
102
+ */
103
+ declare const runRetryPolicyResult: <T, TError extends Error = Error, TData = unknown>(policy: RetryPolicy<TError, TData>, execute: (attempt: number) => Promise<T>, options?: RetryRunResultOptions) => ResultAsync<T, RetryError | AbortError>;
65
104
  //#endregion
66
- export { defaultSleep, runRetryPolicy };
105
+ export { defaultSleep, runRetryPolicy, runRetryPolicyResult };
67
106
  //# sourceMappingURL=base-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-policy.d.ts","names":[],"sources":["../src/base-policy.ts"],"mappings":";;;;;;;;;;;;;;;cA+Ba,eAAsB,oBAAkB;;;;;;;;;;;;;iBAicrC,eACd,GACA,eAAe,QAAQ,OACvB,iBAEA,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,SAAS;EAAoB;IAC5B,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCV,eACd,GACA,eAAe,QAAQ,OACvB,iBAEA,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,UAAU;EAAoB;IAC7B,QAAQ"}
1
+ {"version":3,"file":"base-policy.d.ts","names":[],"sources":["../src/base-policy.ts"],"mappings":";;;;;;;;;;;;;;;;;cAsCa,eAAsB,oBAAkB;;;;;;;;;;;;;iBAwerC,eAAe,GAAG,eAAe,QAAQ,OAAO,iBAC9D,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,SAAS;EAAoB;IAC5B,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCV,eAAe,GAAG,eAAe,QAAQ,OAAO,iBAC9D,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,UAAU;EAAoB;IAC7B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4EE,uBAAwB,GAAG,eAAe,QAAQ,OAAO,iBACpE,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,UAAS,0BACR,YAAY,GAAG,aAAa"}
@@ -1,334 +1,3 @@
1
- import { AbortError, RetryError } from "./errors.js";
2
- //#region src/base-policy.ts
3
- /**
4
- * Awaits a timer-based delay, unless `delayMs` is non-positive.
5
- *
6
- * @param delayMs - Milliseconds to wait before resolving.
7
- * @returns Promise that resolves when the delay completes.
8
- *
9
- * @example
10
- * ```ts
11
- * import { defaultSleep } from "@zap-studio/retry";
12
- *
13
- * await defaultSleep(250); // waits 250ms
14
- * ```
15
- */
16
- const defaultSleep = async (delayMs) => {
17
- if (delayMs <= 0) return;
18
- await new Promise((resolve) => {
19
- setTimeout(resolve, delayMs);
20
- });
21
- };
22
- /**
23
- * Normalizes an abort `reason` into an `AbortError`.
24
- */
25
- const toAbortError = (reason) => {
26
- if (reason instanceof AbortError) return reason;
27
- if (reason instanceof Error) return new AbortError(reason.message, { cause: reason });
28
- if (typeof reason === "string" && reason.length > 0) return new AbortError(reason);
29
- if (reason === void 0) return new AbortError("Retry aborted.");
30
- try {
31
- return new AbortError(`Retry aborted: ${JSON.stringify(reason)}`);
32
- } catch {
33
- return new AbortError("Retry aborted.");
34
- }
35
- };
36
- /**
37
- * Throws when the provided abort signal is already aborted.
38
- *
39
- * @param signal - Optional abort signal to inspect.
40
- * @param logger - Optional logger; logs the abort at `debug` before throwing.
41
- * @throws {AbortError} When the signal is aborted.
42
- */
43
- const throwIfAborted = (signal, logger) => {
44
- if (signal?.aborted !== true) return;
45
- logger?.debug("retry aborted", { reason: signal.reason });
46
- throw toAbortError(signal.reason);
47
- };
48
- /**
49
- * Waits for delay sleep while observing cancellation through an abort signal.
50
- *
51
- * @param sleep - Sleep function used to await `delayMs`.
52
- * @param delayMs - Delay duration in milliseconds.
53
- * @param signal - Abort signal to observe while waiting.
54
- * @returns Promise that resolves when delay finishes.
55
- * @throws {AbortError} When the signal aborts before or during wait.
56
- */
57
- const sleepWithAbortSignal = async (sleep, delayMs, signal) => {
58
- if (signal.aborted) throw toAbortError(signal.reason);
59
- let onAbort;
60
- try {
61
- await Promise.race([sleep(delayMs), new Promise((_resolve, reject) => {
62
- onAbort = () => {
63
- reject(toAbortError(signal.reason));
64
- };
65
- signal.addEventListener("abort", onAbort, { once: true });
66
- })]);
67
- } finally {
68
- if (onAbort) signal.removeEventListener("abort", onAbort);
69
- }
70
- };
71
- /**
72
- * Logs a `next(...)` decision: `debug` when retrying, `warn` when exhausted.
73
- * Shared by both the throw-mode and non-throw retry loops.
74
- */
75
- const logRetryDecision = (logger, attempt, decision, error) => {
76
- if (decision.shouldRetry) {
77
- logger?.debug("retry scheduled", {
78
- attempt,
79
- delayMs: decision.delayMs,
80
- reason: decision.reason
81
- });
82
- return;
83
- }
84
- logger?.warn("retry policy exhausted", {
85
- attempts: attempt,
86
- error,
87
- reason: decision.reason
88
- });
89
- };
90
- /**
91
- * Runs the throw-mode retry loop: throws `RetryError` on exhaustion and
92
- * `AbortError` when `signal` aborts.
93
- *
94
- * @param policy - Resolved retry policy providing `next` and `onExhausted`.
95
- * @param execute - Async work callback per attempt.
96
- * @param sleep - Delay function between retries.
97
- * @param signal - Optional cancel signal.
98
- * @param logger - Optional logger; logs each retry decision at `debug` and
99
- * exhaustion at `warn`.
100
- * @returns Resolves to the first successful return value.
101
- * @throws {RetryError} When retries are exhausted and `onExhausted` returns
102
- * the terminal error.
103
- * @throws {AbortError} When `signal` is already aborted or aborts while waiting.
104
- * @throws {Error} Any error thrown by `next`, `onExhausted`, or `sleep`. Also
105
- * rethrows the original caught value immediately, bypassing retry, when
106
- * `policy.isKnownError` rejects it as outside this policy's error domain.
107
- */
108
- const runThrowMode = async (policy, execute, sleep, signal, logger) => {
109
- let attempt = 1;
110
- while (true) {
111
- throwIfAborted(signal, logger);
112
- try {
113
- return await execute(attempt);
114
- } catch (error) {
115
- throwIfAborted(signal, logger);
116
- if (!policy.isKnownError(error)) throw error;
117
- const decision = policy.next({
118
- attempt,
119
- error
120
- });
121
- logRetryDecision(logger, attempt, decision, error);
122
- if (!decision.shouldRetry) throw policy.onExhausted({
123
- attempts: attempt,
124
- error
125
- });
126
- if (decision.delayMs > 0) await (signal === void 0 ? sleep(decision.delayMs) : sleepWithAbortSignal(sleep, decision.delayMs, signal));
127
- attempt += 1;
128
- }
129
- }
130
- };
131
- /**
132
- * When `signal` is already aborted, builds the terminal `{ ok: false }` object
133
- * with a normalized `AbortError` on `error`.
134
- *
135
- * @param signal - Optional abort signal; only acts when `aborted` is set.
136
- * @param attempts - Number of finished attempts to report in the result.
137
- * @param logger - Optional logger; logs the abort at `debug`.
138
- * @returns Failure result or `undefined` if not aborted.
139
- */
140
- const buildAbortResult = (signal, attempts, logger) => {
141
- if (signal?.aborted !== true) return;
142
- logger?.debug("retry aborted", { reason: signal.reason });
143
- return {
144
- attempts,
145
- error: toAbortError(signal.reason),
146
- ok: false
147
- };
148
- };
149
- /**
150
- * Runs one `execute(attempt)` call and returns either a success value or a
151
- * captured error without rethrowing.
152
- *
153
- * @param execute - User work callback.
154
- * @param attempt - One-based attempt number passed to `execute`.
155
- * @returns A tagged success with `value` or a tagged failure with `error`.
156
- */
157
- const runAttempt = async (execute, attempt) => {
158
- try {
159
- return {
160
- ok: true,
161
- value: await execute(attempt)
162
- };
163
- } catch (error) {
164
- return {
165
- error,
166
- ok: false
167
- };
168
- }
169
- };
170
- /**
171
- * Awaits inter-attempt delay in result mode, mapping an abort during wait to
172
- * a terminal result instead of throwing when `throwOnExhausted` is false.
173
- *
174
- * @param sleep - Custom or default sleep implementation.
175
- * @param delayMs - Milliseconds to wait.
176
- * @param signal - If set, `sleep` is raced with the abort signal.
177
- * @param attempts - Attempt count to attach if the wait ends in abort.
178
- * @param logger - Optional logger; logs an abort ending the wait at `debug`.
179
- * @returns A terminal result when canceled during the wait, otherwise
180
- * `undefined`.
181
- * @throws {Error} The underlying `sleep` rejection when it is not an abort.
182
- */
183
- const waitForDelay = async (sleep, delayMs, signal, attempts, logger) => {
184
- if (signal === void 0) {
185
- await sleep(delayMs);
186
- return;
187
- }
188
- try {
189
- await sleepWithAbortSignal(sleep, delayMs, signal);
190
- return;
191
- } catch (error) {
192
- const aborted = buildAbortResult(signal, attempts, logger);
193
- if (aborted !== void 0) return aborted;
194
- throw error;
195
- }
196
- };
197
- /**
198
- * After a failed attempt, applies abort rules, `next`, optional delay, and
199
- * either returns a terminal `RetryRunResult` or `undefined` to continue.
200
- *
201
- * @param policy - Resolved retry policy hooks (`next`, `onExhausted`).
202
- * @param params - Failure context for the current attempt.
203
- * @param params.attempt - Current attempt number.
204
- * @param params.error - Error thrown by the attempt.
205
- * @param params.sleep - Delay function between retries.
206
- * @param params.signal - Optional abort signal.
207
- * @param params.logger - Optional logger; logs each retry decision at
208
- * `debug`, exhaustion at `warn`, and cancellation at `debug`.
209
- * @returns Terminal non-throw result if the loop should stop, otherwise
210
- * `undefined` to schedule another attempt.
211
- * @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep` when
212
- * the error is not an abort.
213
- */
214
- const handleFailure = async (policy, params) => {
215
- const { attempt, error, sleep, signal, logger } = params;
216
- const abortResult = buildAbortResult(signal, attempt, logger);
217
- if (abortResult !== void 0) return abortResult;
218
- const decision = policy.next({
219
- attempt,
220
- error
221
- });
222
- logRetryDecision(logger, attempt, decision, error);
223
- if (!decision.shouldRetry) return {
224
- attempts: attempt,
225
- error: policy.onExhausted({
226
- attempts: attempt,
227
- error
228
- }),
229
- ok: false
230
- };
231
- if (decision.delayMs > 0) {
232
- const delayAbortResult = await waitForDelay(sleep, decision.delayMs, signal, attempt, logger);
233
- if (delayAbortResult !== void 0) return delayAbortResult;
234
- }
235
- };
236
- /**
237
- * Runs the non-throw retry loop, returning
238
- * `RetryRunResult`.
239
- *
240
- * @param policy - Resolved retry policy providing `next` and `onExhausted`.
241
- * @param execute - Async work callback per attempt.
242
- * @param sleep - Delay function between retries.
243
- * @param signal - Optional cancel signal.
244
- * @param logger - Optional logger; logs each retry decision at `debug`,
245
- * exhaustion at `warn`, and cancellation at `debug`.
246
- * @returns Terminal success or failure object. When `policy.isKnownError`
247
- * rejects a caught value as outside this policy's error domain, the
248
- * original value is wrapped in a `RetryError` and returned on
249
- * `result.error` immediately, bypassing retry — never thrown.
250
- * @throws {Error} Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`
251
- * failure.
252
- */
253
- const runResultMode = async (policy, execute, sleep, signal, logger) => {
254
- let attempt = 1;
255
- while (true) {
256
- const abortResult = buildAbortResult(signal, Math.max(0, attempt - 1), logger);
257
- if (abortResult !== void 0) return abortResult;
258
- const execution = await runAttempt(execute, attempt);
259
- if (execution.ok) return {
260
- ok: true,
261
- value: execution.value
262
- };
263
- const attemptAbortResult = buildAbortResult(signal, attempt, logger);
264
- if (attemptAbortResult !== void 0) return attemptAbortResult;
265
- if (!policy.isKnownError(execution.error)) return {
266
- attempts: attempt,
267
- error: new RetryError("Retry policy encountered an unknown error.", {
268
- attempts: attempt,
269
- lastError: execution.error
270
- }),
271
- ok: false
272
- };
273
- const failure = await handleFailure(policy, {
274
- attempt,
275
- error: execution.error,
276
- logger,
277
- signal,
278
- sleep
279
- });
280
- if (failure !== void 0) return failure;
281
- attempt += 1;
282
- }
283
- };
284
- /**
285
- * Default `onExhausted` used when a policy omits it: wraps the exhaustion
286
- * context in a generic `RetryError`.
287
- */
288
- const defaultOnExhausted = (input) => new RetryError("Retry policy exhausted all attempts.", {
289
- attempts: input.attempts,
290
- lastData: input.data,
291
- lastError: input.error
292
- });
293
- /**
294
- * Default `isKnownError` used when a policy omits it: accepts any `Error`
295
- * instance and rejects everything else.
296
- */
297
- const defaultIsKnownError = (error) => error instanceof Error;
298
- /**
299
- * Runs retry orchestration in non-throw mode.
300
- *
301
- * When `throwOnExhausted` is `false`, returns a discriminated result union.
302
- *
303
- * @param policy - Retry policy: `next` is required, `onExhausted` and
304
- * `isKnownError` fall back to their defaults when omitted.
305
- * @param execute - Async function to execute per attempt.
306
- * @param options - Runner settings.
307
- * @returns Success value or terminal result object based on option mode.
308
- * @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`
309
- * function. When `throwOnExhausted` is `false`, exhaustion itself is returned
310
- * as `{ ok: false }` instead of thrown.
311
- * Cancellation is returned as `{ ok: false, error: AbortError }` in non-throw
312
- * mode. A value rejected by `policy.isKnownError` is wrapped in a
313
- * `RetryError` and returned the same way in non-throw mode; in throw mode
314
- * it is rethrown as-is.
315
- *
316
- * @example
317
- * const result = await runRetryPolicy(policy, doWork, { throwOnExhausted: false });
318
- * if (!result.ok) console.error(result.error);
319
- */
320
- async function runRetryPolicy(policy, execute, options = {}) {
321
- const sleep = options.sleep ?? defaultSleep;
322
- const { signal, logger } = options;
323
- const resolvedPolicy = {
324
- isKnownError: (error) => policy.isKnownError ? policy.isKnownError(error) : defaultIsKnownError(error),
325
- next: (input) => policy.next(input),
326
- onExhausted: (input) => policy.onExhausted ? policy.onExhausted(input) : defaultOnExhausted(input)
327
- };
328
- if (options.throwOnExhausted === false) return await runResultMode(resolvedPolicy, execute, sleep, signal, logger);
329
- return await runThrowMode(resolvedPolicy, execute, sleep, signal, logger);
330
- }
331
- //#endregion
332
- export { defaultSleep, runRetryPolicy };
333
-
334
- //# sourceMappingURL=base-policy.js.map
1
+ import { n as runRetryPolicy, r as runRetryPolicyResult, t as defaultSleep } from "./base-policy-DkxBg9dh.js";
2
+ import "./errors.js";
3
+ export { defaultSleep, runRetryPolicy, runRetryPolicyResult };
@@ -1 +1 @@
1
- {"version":3,"file":"errors-CS5UPJWs.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;UAciB;;;;WAIN;;;;WAIA;;;;WAIA;;;;;;;;UASM;;;;WAIN;;;;;;;;;;;cAYE,mBAAmB;;;;WAId;;;;WAIA;;;;WAIA;;;;EAKhB,YAAY,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;cA4B3B,mBAAmB;;;;oBAIL;;;;;;;EAQzB,YAAY,iBAAiB,UAAS"}
1
+ {"version":3,"file":"errors-CS5UPJWs.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;UAYiB;;;;WAIN;;;;WAIA;;;;WAIA;;;;;;;;UASM;;;;WAIN;;;;;;;;;;;cAYE,mBAAmB;;;;WAId;;;;WAIA;;;;WAIA;;;;EAKhB,YAAY,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;cA4B3B,mBAAmB;;;;oBAIL;;;;;;;EAQzB,YAAY,iBAAiB,UAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["// oxlint-disable max-classes-per-file -- Public retry error types are intentionally colocated.\n\n/**\n * Terminal error types used by retry policies and runners.\n *\n * @module @zap-studio/retry/errors\n */\n\n/**\n * Context payload attached to `RetryError`.\n *\n * @example\n * const context: RetryErrorContext = { attempts: 3, lastError: new Error(\"network\") };\n */\nexport interface RetryErrorContext {\n /**\n * Count of completed attempts at exhaustion.\n */\n readonly attempts: number;\n /**\n * The last error object raised by a failed `execute` attempt.\n */\n readonly lastError?: unknown;\n /**\n * Optional data captured from the last attempt when provided by a policy.\n */\n readonly lastData?: unknown;\n}\n\n/**\n * Context payload attached to `AbortError`.\n *\n * @example\n * const context: AbortErrorContext = { cause: new Error(\"shutting down\") };\n */\nexport interface AbortErrorContext {\n /**\n * When the abort `reason` was an `Error`, the optional wrapped cause.\n */\n readonly cause?: unknown;\n}\n\n/**\n * Error thrown when retries are exhausted.\n *\n * @example\n * throw new RetryError(\"Retry exhausted\", {\n * attempts: 3,\n * lastError: new Error(\"network\"),\n * });\n */\nexport class RetryError extends Error {\n /**\n * Total attempts performed before exhaustion.\n */\n public readonly attempts: number;\n /**\n * Last captured error from execution.\n */\n public readonly lastError?: unknown;\n /**\n * Last captured data value, when available.\n */\n public readonly lastData?: unknown;\n\n /**\n * Creates a RetryError with structured terminal context.\n */\n constructor(message: string, context: RetryErrorContext) {\n super(message);\n this.name = \"RetryError\";\n this.attempts = context.attempts;\n this.lastError = context.lastError;\n this.lastData = context.lastData;\n }\n}\n\n/**\n * Error thrown when retry orchestration is canceled through `AbortSignal`.\n *\n * @example\n * ```ts\n * import { AbortError, runRetryPolicy } from \"@zap-studio/retry\";\n *\n * const controller = new AbortController();\n * controller.abort(\"shutting down\");\n *\n * try {\n * await runRetryPolicy(policy, doWork, { signal: controller.signal });\n * } catch (error) {\n * if (error instanceof AbortError) {\n * console.error(\"Retry canceled:\", error.message);\n * }\n * }\n * ```\n */\nexport class AbortError extends Error {\n /**\n * Optional wrapped cause when the native abort `reason` was an `Error`.\n */\n public override readonly cause?: unknown;\n\n /**\n * Creates an AbortError with an optional diagnostic cause.\n *\n * @param message - Human-readable abort description.\n * @param context - Optional `cause` link for diagnostic chaining.\n */\n constructor(message: string, context: AbortErrorContext = {}) {\n super(message);\n this.name = \"AbortError\";\n this.cause = context.cause;\n }\n}\n"],"mappings":";;;;;;;;;;AAmDA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;CAIA;;;;CAIA;;;;CAKA,YAAY,SAAiB,SAA4B;EACvD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,WAAW,QAAQ;EACxB,KAAK,YAAY,QAAQ;EACzB,KAAK,WAAW,QAAQ;CAC1B;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;;;;CAQA,YAAY,SAAiB,UAA6B,CAAC,GAAG;EAC5D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,QAAQ,QAAQ;CACvB;AACF"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Terminal error types used by retry policies and runners.\n *\n * @module @zap-studio/retry/errors\n */\n\n/**\n * Context payload attached to `RetryError`.\n *\n * @example\n * const context: RetryErrorContext = { attempts: 3, lastError: new Error(\"network\") };\n */\nexport interface RetryErrorContext {\n /**\n * Count of completed attempts at exhaustion.\n */\n readonly attempts: number;\n /**\n * The last error object raised by a failed `execute` attempt.\n */\n readonly lastError?: unknown;\n /**\n * Optional data captured from the last attempt when provided by a policy.\n */\n readonly lastData?: unknown;\n}\n\n/**\n * Context payload attached to `AbortError`.\n *\n * @example\n * const context: AbortErrorContext = { cause: new Error(\"shutting down\") };\n */\nexport interface AbortErrorContext {\n /**\n * When the abort `reason` was an `Error`, the optional wrapped cause.\n */\n readonly cause?: unknown;\n}\n\n/**\n * Error thrown when retries are exhausted.\n *\n * @example\n * throw new RetryError(\"Retry exhausted\", {\n * attempts: 3,\n * lastError: new Error(\"network\"),\n * });\n */\nexport class RetryError extends Error {\n /**\n * Total attempts performed before exhaustion.\n */\n public readonly attempts: number;\n /**\n * Last captured error from execution.\n */\n public readonly lastError?: unknown;\n /**\n * Last captured data value, when available.\n */\n public readonly lastData?: unknown;\n\n /**\n * Creates a RetryError with structured terminal context.\n */\n constructor(message: string, context: RetryErrorContext) {\n super(message);\n this.name = \"RetryError\";\n this.attempts = context.attempts;\n this.lastError = context.lastError;\n this.lastData = context.lastData;\n }\n}\n\n/**\n * Error thrown when retry orchestration is canceled through `AbortSignal`.\n *\n * @example\n * ```ts\n * import { AbortError, runRetryPolicy } from \"@zap-studio/retry\";\n *\n * const controller = new AbortController();\n * controller.abort(\"shutting down\");\n *\n * try {\n * await runRetryPolicy(policy, doWork, { signal: controller.signal });\n * } catch (error) {\n * if (error instanceof AbortError) {\n * console.error(\"Retry canceled:\", error.message);\n * }\n * }\n * ```\n */\nexport class AbortError extends Error {\n /**\n * Optional wrapped cause when the native abort `reason` was an `Error`.\n */\n public override readonly cause?: unknown;\n\n /**\n * Creates an AbortError with an optional diagnostic cause.\n *\n * @param message - Human-readable abort description.\n * @param context - Optional `cause` link for diagnostic chaining.\n */\n constructor(message: string, context: AbortErrorContext = {}) {\n super(message);\n this.name = \"AbortError\";\n this.cause = context.cause;\n }\n}\n"],"mappings":";;;;;;;;;;AAiDA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;CAIA;;;;CAIA;;;;CAKA,YAAY,SAAiB,SAA4B;EACvD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,WAAW,QAAQ;EACxB,KAAK,YAAY,QAAQ;EACzB,KAAK,WAAW,QAAQ;CAC1B;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;;;;CAQA,YAAY,SAAiB,UAA6B,CAAC,GAAG;EAC5D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,QAAQ,QAAQ;CACvB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"exponential-backoff.d.ts","names":[],"sources":["../src/exponential-backoff.ts"],"mappings":";;;;;;;;;;;;;;UAyBiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;;EAKA,SAAS,aAAa;;;;;;;;;;;;cAaX,qBACX,SAAS,8BACR"}
1
+ {"version":3,"file":"exponential-backoff.d.ts","names":[],"sources":["../src/exponential-backoff.ts"],"mappings":";;;;;;;;;;;;;;UAsBiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;;EAKA,SAAS,aAAa;;;;;;;;;;;;cAaX,qBAAsB,SAAS,8BAA4B"}
@@ -1,11 +1,6 @@
1
1
  import { applyJitter } from "./jitter.js";
2
2
  //#region src/exponential-backoff.ts
3
3
  /**
4
- * Exponential backoff retry strategy.
5
- *
6
- * @module @zap-studio/retry/exponential-backoff
7
- */
8
- /**
9
4
  * Creates a retry policy with exponential delay growth up to a max cap.
10
5
  *
11
6
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"exponential-backoff.js","names":[],"sources":["../src/exponential-backoff.ts"],"sourcesContent":["/**\n * Exponential backoff retry strategy.\n *\n * @module @zap-studio/retry/exponential-backoff\n */\n\nimport { applyJitter } from \"./jitter.js\";\nimport type { JitterMode, JitterOptions } from \"./jitter.js\";\nimport type {\n RetryDecision,\n RetryDecisionInput,\n RetryPolicy,\n} from \"./types.js\";\n\n/**\n * Configuration for `exponentialBackoff(...)`.\n *\n * @example\n * const options: ExponentialBackoffOptions = {\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * maxDelayMs: 2_000,\n * jitter: \"full\",\n * };\n */\nexport interface ExponentialBackoffOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Initial delay in milliseconds, doubled each retry until capped.\n */\n baseDelayMs: number;\n /**\n * Hard upper bound in milliseconds for computed exponential delay.\n */\n maxDelayMs: number;\n /**\n * Optional jitter applied to the computed delay, after capping at\n * `maxDelayMs`.\n */\n jitter?: JitterMode | JitterOptions;\n}\n\n/**\n * Creates a retry policy with exponential delay growth up to a max cap.\n *\n * @example\n * const policy = exponentialBackoff({\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * maxDelayMs: 2_000,\n * });\n */\nexport const exponentialBackoff = (\n options: ExponentialBackoffOptions\n): RetryPolicy => {\n const { maxAttempts, baseDelayMs, maxDelayMs, jitter } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n const exponent = Math.max(0, input.attempt - 1);\n const cappedDelayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** exponent);\n const delayMs = applyJitter(cappedDelayMs, jitter);\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,MAAa,sBACX,YACgB;CAChB,MAAM,EAAE,aAAa,aAAa,YAAY,WAAW;CAEzD,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,MAAM,WAAW,KAAK,IAAI,GAAG,MAAM,UAAU,CAAC;EAC9C,MAAM,gBAAgB,KAAK,IAAI,YAAY,cAAc,KAAK,QAAQ;EAGtE,OAAO;GAAE,SAFO,YAAY,eAAe,MAE5B;GAAG,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
1
+ {"version":3,"file":"exponential-backoff.js","names":[],"sources":["../src/exponential-backoff.ts"],"sourcesContent":["/**\n * Exponential backoff retry strategy.\n *\n * @module @zap-studio/retry/exponential-backoff\n */\n\nimport type { JitterMode, JitterOptions } from \"./jitter.ts\";\nimport type { RetryDecision, RetryDecisionInput, RetryPolicy } from \"./types.ts\";\n\nimport { applyJitter } from \"./jitter.ts\";\n\n/**\n * Configuration for `exponentialBackoff(...)`.\n *\n * @example\n * const options: ExponentialBackoffOptions = {\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * maxDelayMs: 2_000,\n * jitter: \"full\",\n * };\n */\nexport interface ExponentialBackoffOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Initial delay in milliseconds, doubled each retry until capped.\n */\n baseDelayMs: number;\n /**\n * Hard upper bound in milliseconds for computed exponential delay.\n */\n maxDelayMs: number;\n /**\n * Optional jitter applied to the computed delay, after capping at\n * `maxDelayMs`.\n */\n jitter?: JitterMode | JitterOptions;\n}\n\n/**\n * Creates a retry policy with exponential delay growth up to a max cap.\n *\n * @example\n * const policy = exponentialBackoff({\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * maxDelayMs: 2_000,\n * });\n */\nexport const exponentialBackoff = (options: ExponentialBackoffOptions): RetryPolicy => {\n const { maxAttempts, baseDelayMs, maxDelayMs, jitter } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n const exponent = Math.max(0, input.attempt - 1);\n const cappedDelayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** exponent);\n const delayMs = applyJitter(cappedDelayMs, jitter);\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;AAoDA,MAAa,sBAAsB,YAAoD;CACrF,MAAM,EAAE,aAAa,aAAa,YAAY,WAAW;CAEzD,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,MAAM,WAAW,KAAK,IAAI,GAAG,MAAM,UAAU,CAAC;EAC9C,MAAM,gBAAgB,KAAK,IAAI,YAAY,cAAc,KAAK,QAAQ;EAGtE,OAAO;GAAE,SAFO,YAAY,eAAe,MAE5B;GAAG,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"fixed-delay.d.ts","names":[],"sources":["../src/fixed-delay.ts"],"mappings":";;;;;;;;UAkBiB;;;;EAIf;;;;EAIA;;;;;;;;;;;cAYW,aAAc,SAAS,sBAAoB"}
1
+ {"version":3,"file":"fixed-delay.d.ts","names":[],"sources":["../src/fixed-delay.ts"],"mappings":";;;;;;;;UAciB;;;;EAIf;;;;EAIA;;;;;;;;;;;cAYW,aAAc,SAAS,sBAAoB"}
@@ -1 +1 @@
1
- {"version":3,"file":"fixed-delay.js","names":[],"sources":["../src/fixed-delay.ts"],"sourcesContent":["/**\n * Fixed-delay retry strategy.\n *\n * @module @zap-studio/retry/fixed-delay\n */\n\nimport type {\n RetryDecision,\n RetryDecisionInput,\n RetryPolicy,\n} from \"./types.js\";\n\n/**\n * Configuration for `fixedDelay(...)`.\n *\n * @example\n * const options: FixedDelayOptions = { maxAttempts: 3, delayMs: 250 };\n */\nexport interface FixedDelayOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Constant delay in milliseconds before each retry after a failure.\n */\n delayMs: number;\n}\n\n/**\n * Creates a retry policy with a constant delay between attempts.\n *\n * @example\n * const policy = fixedDelay({\n * maxAttempts: 3,\n * delayMs: 250,\n * });\n */\nexport const fixedDelay = (options: FixedDelayOptions): RetryPolicy => {\n const { maxAttempts, delayMs } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;AAsCA,MAAa,cAAc,YAA4C;CACrE,MAAM,EAAE,aAAa,YAAY;CAEjC,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,OAAO;GAAE;GAAS,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
1
+ {"version":3,"file":"fixed-delay.js","names":[],"sources":["../src/fixed-delay.ts"],"sourcesContent":["/**\n * Fixed-delay retry strategy.\n *\n * @module @zap-studio/retry/fixed-delay\n */\n\nimport type { RetryDecision, RetryDecisionInput, RetryPolicy } from \"./types.ts\";\n\n/**\n * Configuration for `fixedDelay(...)`.\n *\n * @example\n * const options: FixedDelayOptions = { maxAttempts: 3, delayMs: 250 };\n */\nexport interface FixedDelayOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Constant delay in milliseconds before each retry after a failure.\n */\n delayMs: number;\n}\n\n/**\n * Creates a retry policy with a constant delay between attempts.\n *\n * @example\n * const policy = fixedDelay({\n * maxAttempts: 3,\n * delayMs: 250,\n * });\n */\nexport const fixedDelay = (options: FixedDelayOptions): RetryPolicy => {\n const { maxAttempts, delayMs } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;AAkCA,MAAa,cAAc,YAA4C;CACrE,MAAM,EAAE,aAAa,YAAY;CAEjC,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,OAAO;GAAE;GAAS,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { i as RetryErrorContext, n as AbortErrorContext, r as RetryError, t as AbortError } from "./errors-CS5UPJWs.js";
2
- import { RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult } from "./types.js";
3
- import { defaultSleep, runRetryPolicy } from "./base-policy.js";
2
+ import { RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult, RetryRunResultOptions } from "./types.js";
3
+ import { defaultSleep, runRetryPolicy, runRetryPolicyResult } from "./base-policy.js";
4
4
  import { JitterMode, JitterOptions, applyJitter } from "./jitter.js";
5
5
  import { ExponentialBackoffOptions, exponentialBackoff } from "./exponential-backoff.js";
6
6
  import { FixedDelayOptions, fixedDelay } from "./fixed-delay.js";
7
7
  import { LinearBackoffOptions, linearBackoff } from "./linear-backoff.js";
8
- export { AbortError, type AbortErrorContext, type ExponentialBackoffOptions, type FixedDelayOptions, type JitterMode, type JitterOptions, type LinearBackoffOptions, type RetryDecision, type RetryDecisionInput, RetryError, type RetryErrorContext, type RetryExhaustedInput, type RetryPolicy, type RetryRunOptions, type RetryRunResult, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy };
8
+ export { AbortError, type AbortErrorContext, type ExponentialBackoffOptions, type FixedDelayOptions, type JitterMode, type JitterOptions, type LinearBackoffOptions, type RetryDecision, type RetryDecisionInput, RetryError, type RetryErrorContext, type RetryExhaustedInput, type RetryPolicy, type RetryRunOptions, type RetryRunResult, type RetryRunResultOptions, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy, runRetryPolicyResult };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
+ import { n as runRetryPolicy, r as runRetryPolicyResult, t as defaultSleep } from "./base-policy-DkxBg9dh.js";
1
2
  import { AbortError, RetryError } from "./errors.js";
2
- import { defaultSleep, runRetryPolicy } from "./base-policy.js";
3
3
  import { applyJitter } from "./jitter.js";
4
4
  import { exponentialBackoff } from "./exponential-backoff.js";
5
5
  import { fixedDelay } from "./fixed-delay.js";
6
6
  import { linearBackoff } from "./linear-backoff.js";
7
- export { AbortError, RetryError, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy };
7
+ export { AbortError, RetryError, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy, runRetryPolicyResult };
@@ -1 +1 @@
1
- {"version":3,"file":"jitter.d.ts","names":[],"sources":["../src/jitter.ts"],"mappings":";;;;;;;;;;;;;;KAcY;;;;;;;UAQK;;;;EAIf,MAAM;;;;;;EAMN;;;;;;;;;;;;;cAcW,cACX,iBACA,SAAS,aAAa"}
1
+ {"version":3,"file":"jitter.d.ts","names":[],"sources":["../src/jitter.ts"],"mappings":";;;;;;;;;;;;;;KAcY;;;;;;;UAQK;;;;EAIf,MAAM;;;;;;EAMN;;;;;;;;;;;;;cAcW,cAAe,iBAAiB,SAAS,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"jitter.js","names":[],"sources":["../src/jitter.ts"],"sourcesContent":["/**\n * Jitter strategies applied to a computed backoff delay.\n *\n * @module @zap-studio/retry/jitter\n */\n\n/**\n * Supported jitter strategies.\n *\n * - `\"full\"`: `random(0, delayMs)` — max spread, best thundering-herd\n * protection.\n * - `\"equal\"`: `delayMs/2 + random(0, delayMs/2)` — keeps a floor at half\n * the computed delay, less spread than full jitter.\n */\nexport type JitterMode = \"equal\" | \"full\";\n\n/**\n * Configuration for jitter application.\n *\n * @example\n * const jitter: JitterOptions = { mode: \"full\" };\n */\nexport interface JitterOptions {\n /**\n * Jitter strategy to apply.\n */\n mode: JitterMode;\n /**\n * Random source in `[0, 1)`, overridable for deterministic tests.\n *\n * @default Math.random\n */\n random?: () => number;\n}\n\n/**\n * Applies a jitter strategy to a computed delay.\n *\n * @param delayMs - Delay in milliseconds before jitter.\n * @param jitter - Jitter mode shorthand, full `JitterOptions`, or `undefined`\n * to leave `delayMs` untouched.\n * @returns Jittered delay in milliseconds, rounded to the nearest integer.\n *\n * @example\n * const delayMs = applyJitter(1000, \"full\"); // 0-1000\n */\nexport const applyJitter = (\n delayMs: number,\n jitter?: JitterMode | JitterOptions\n): number => {\n if (jitter === undefined) {\n return delayMs;\n }\n\n const mode = typeof jitter === \"string\" ? jitter : jitter.mode;\n const random =\n (typeof jitter === \"string\" ? undefined : jitter.random) ?? Math.random;\n\n if (mode === \"full\") {\n return Math.round(random() * delayMs);\n }\n\n const half = delayMs / 2;\n return Math.round(half + random() * half);\n};\n"],"mappings":";;;;;;;;;;;;AA8CA,MAAa,eACX,SACA,WACW;CACX,IAAI,WAAW,KAAA,GACb,OAAO;CAGT,MAAM,OAAO,OAAO,WAAW,WAAW,SAAS,OAAO;CAC1D,MAAM,UACH,OAAO,WAAW,WAAW,KAAA,IAAY,OAAO,WAAW,KAAK;CAEnE,IAAI,SAAS,QACX,OAAO,KAAK,MAAM,OAAO,IAAI,OAAO;CAGtC,MAAM,OAAO,UAAU;CACvB,OAAO,KAAK,MAAM,OAAO,OAAO,IAAI,IAAI;AAC1C"}
1
+ {"version":3,"file":"jitter.js","names":[],"sources":["../src/jitter.ts"],"sourcesContent":["/**\n * Jitter strategies applied to a computed backoff delay.\n *\n * @module @zap-studio/retry/jitter\n */\n\n/**\n * Supported jitter strategies.\n *\n * - `\"full\"`: `random(0, delayMs)` — max spread, best thundering-herd\n * protection.\n * - `\"equal\"`: `delayMs/2 + random(0, delayMs/2)` — keeps a floor at half\n * the computed delay, less spread than full jitter.\n */\nexport type JitterMode = \"equal\" | \"full\";\n\n/**\n * Configuration for jitter application.\n *\n * @example\n * const jitter: JitterOptions = { mode: \"full\" };\n */\nexport interface JitterOptions {\n /**\n * Jitter strategy to apply.\n */\n mode: JitterMode;\n /**\n * Random source in `[0, 1)`, overridable for deterministic tests.\n *\n * @default Math.random\n */\n random?: () => number;\n}\n\n/**\n * Applies a jitter strategy to a computed delay.\n *\n * @param delayMs - Delay in milliseconds before jitter.\n * @param jitter - Jitter mode shorthand, full `JitterOptions`, or `undefined`\n * to leave `delayMs` untouched.\n * @returns Jittered delay in milliseconds, rounded to the nearest integer.\n *\n * @example\n * const delayMs = applyJitter(1000, \"full\"); // 0-1000\n */\nexport const applyJitter = (delayMs: number, jitter?: JitterMode | JitterOptions): number => {\n if (jitter === undefined) {\n return delayMs;\n }\n\n const mode = typeof jitter === \"string\" ? jitter : jitter.mode;\n const random = (typeof jitter === \"string\" ? undefined : jitter.random) ?? Math.random;\n\n if (mode === \"full\") {\n return Math.round(random() * delayMs);\n }\n\n const half = delayMs / 2;\n return Math.round(half + random() * half);\n};\n"],"mappings":";;;;;;;;;;;;AA8CA,MAAa,eAAe,SAAiB,WAAgD;CAC3F,IAAI,WAAW,KAAA,GACb,OAAO;CAGT,MAAM,OAAO,OAAO,WAAW,WAAW,SAAS,OAAO;CAC1D,MAAM,UAAU,OAAO,WAAW,WAAW,KAAA,IAAY,OAAO,WAAW,KAAK;CAEhF,IAAI,SAAS,QACX,OAAO,KAAK,MAAM,OAAO,IAAI,OAAO;CAGtC,MAAM,OAAO,UAAU;CACvB,OAAO,KAAK,MAAM,OAAO,OAAO,IAAI,IAAI;AAC1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"linear-backoff.d.ts","names":[],"sources":["../src/linear-backoff.ts"],"mappings":";;;;;;;;;;;;;;;UA0BiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;;EAKA,SAAS,aAAa;;;;;;;;;;;;;cAcX,gBAAiB,SAAS,yBAAuB"}
1
+ {"version":3,"file":"linear-backoff.d.ts","names":[],"sources":["../src/linear-backoff.ts"],"mappings":";;;;;;;;;;;;;;;UAuBiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;;EAKA,SAAS,aAAa;;;;;;;;;;;;;cAcX,gBAAiB,SAAS,yBAAuB"}
@@ -1,11 +1,6 @@
1
1
  import { applyJitter } from "./jitter.js";
2
2
  //#region src/linear-backoff.ts
3
3
  /**
4
- * Linear backoff retry strategy.
5
- *
6
- * @module @zap-studio/retry/linear-backoff
7
- */
8
- /**
9
4
  * Creates a retry policy with linear delay growth up to a max cap.
10
5
  *
11
6
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"linear-backoff.js","names":[],"sources":["../src/linear-backoff.ts"],"sourcesContent":["/**\n * Linear backoff retry strategy.\n *\n * @module @zap-studio/retry/linear-backoff\n */\n\nimport { applyJitter } from \"./jitter.js\";\nimport type { JitterMode, JitterOptions } from \"./jitter.js\";\nimport type {\n RetryDecision,\n RetryDecisionInput,\n RetryPolicy,\n} from \"./types.js\";\n\n/**\n * Configuration for `linearBackoff(...)`.\n *\n * @example\n * const options: LinearBackoffOptions = {\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * incrementMs: 100,\n * maxDelayMs: 2_000,\n * jitter: \"equal\",\n * };\n */\nexport interface LinearBackoffOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Delay in milliseconds after the first failed attempt.\n */\n baseDelayMs: number;\n /**\n * Amount added to the delay for each subsequent retry.\n */\n incrementMs: number;\n /**\n * Hard upper bound in milliseconds for computed linear delay.\n */\n maxDelayMs: number;\n /**\n * Optional jitter applied to the computed delay, after capping at\n * `maxDelayMs`.\n */\n jitter?: JitterMode | JitterOptions;\n}\n\n/**\n * Creates a retry policy with linear delay growth up to a max cap.\n *\n * @example\n * const policy = linearBackoff({\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * incrementMs: 100,\n * maxDelayMs: 2_000,\n * });\n */\nexport const linearBackoff = (options: LinearBackoffOptions): RetryPolicy => {\n const { maxAttempts, baseDelayMs, incrementMs, maxDelayMs, jitter } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n const cappedDelayMs = Math.min(\n maxDelayMs,\n baseDelayMs + incrementMs * (input.attempt - 1)\n );\n const delayMs = applyJitter(cappedDelayMs, jitter);\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6DA,MAAa,iBAAiB,YAA+C;CAC3E,MAAM,EAAE,aAAa,aAAa,aAAa,YAAY,WAAW;CAEtE,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,MAAM,gBAAgB,KAAK,IACzB,YACA,cAAc,eAAe,MAAM,UAAU,EAC/C;EAGA,OAAO;GAAE,SAFO,YAAY,eAAe,MAE5B;GAAG,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
1
+ {"version":3,"file":"linear-backoff.js","names":[],"sources":["../src/linear-backoff.ts"],"sourcesContent":["/**\n * Linear backoff retry strategy.\n *\n * @module @zap-studio/retry/linear-backoff\n */\n\nimport type { JitterMode, JitterOptions } from \"./jitter.ts\";\nimport type { RetryDecision, RetryDecisionInput, RetryPolicy } from \"./types.ts\";\n\nimport { applyJitter } from \"./jitter.ts\";\n\n/**\n * Configuration for `linearBackoff(...)`.\n *\n * @example\n * const options: LinearBackoffOptions = {\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * incrementMs: 100,\n * maxDelayMs: 2_000,\n * jitter: \"equal\",\n * };\n */\nexport interface LinearBackoffOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Delay in milliseconds after the first failed attempt.\n */\n baseDelayMs: number;\n /**\n * Amount added to the delay for each subsequent retry.\n */\n incrementMs: number;\n /**\n * Hard upper bound in milliseconds for computed linear delay.\n */\n maxDelayMs: number;\n /**\n * Optional jitter applied to the computed delay, after capping at\n * `maxDelayMs`.\n */\n jitter?: JitterMode | JitterOptions;\n}\n\n/**\n * Creates a retry policy with linear delay growth up to a max cap.\n *\n * @example\n * const policy = linearBackoff({\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * incrementMs: 100,\n * maxDelayMs: 2_000,\n * });\n */\nexport const linearBackoff = (options: LinearBackoffOptions): RetryPolicy => {\n const { maxAttempts, baseDelayMs, incrementMs, maxDelayMs, jitter } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n const cappedDelayMs = Math.min(maxDelayMs, baseDelayMs + incrementMs * (input.attempt - 1));\n const delayMs = applyJitter(cappedDelayMs, jitter);\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;AA0DA,MAAa,iBAAiB,YAA+C;CAC3E,MAAM,EAAE,aAAa,aAAa,aAAa,YAAY,WAAW;CAEtE,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,MAAM,gBAAgB,KAAK,IAAI,YAAY,cAAc,eAAe,MAAM,UAAU,EAAE;EAG1F,OAAO;GAAE,SAFO,YAAY,eAAe,MAE5B;GAAG,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
package/dist/types.d.ts CHANGED
@@ -166,6 +166,17 @@ interface RetryRunOptions {
166
166
  */
167
167
  readonly logger?: Logger;
168
168
  }
169
+ /**
170
+ * Options for `runRetryPolicyResult(...)`.
171
+ *
172
+ * Same as {@link RetryRunOptions} minus `throwOnExhausted`, which doesn't
173
+ * apply — `runRetryPolicyResult` always returns a `Result`, never throws for
174
+ * exhaustion or abort.
175
+ *
176
+ * @example
177
+ * const options: RetryRunResultOptions = { signal: controller.signal };
178
+ */
179
+ type RetryRunResultOptions = Omit<RetryRunOptions, "throwOnExhausted">;
169
180
  /**
170
181
  * Result union returned by non-throw runner mode.
171
182
  *
@@ -202,5 +213,5 @@ type RetryRunResult<T> = {
202
213
  attempts: number;
203
214
  };
204
215
  //#endregion
205
- export { ResolvedRetryPolicy, RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult };
216
+ export { ResolvedRetryPolicy, RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult, RetryRunResultOptions };
206
217
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;UAuBiB,YAAY,eAAe,QAAQ,OAAO;;;;;;EAMzD,OAAO,OAAO,mBAAmB,QAAQ,WAAW;;;;;;;;EAQpD,eAAe,OAAO,oBAAoB,QAAQ,WAAW;;;;;;;;;;;;;EAa7D,gBAAgB,mBAAmB,SAAS;;;;;;UAO7B,oBAAoB,eAAe,OAAO;;;;EAIzD,MAAM,YAAY,QAAQ;;;;;EAK1B,cAAc,OAAO,oBAAoB,QAAQ,WAAW;;;;;EAK5D,eAAe,mBAAmB,SAAS;;;;;;;;UAS5B;;;;;WAKN;;;;WAIA;;;;WAIA;;;;;;;;UASM,mBACf,eAAe,QAAQ,OACvB;;;;WAKS;;;;;WAKA;;;;;WAKA,QAAQ;;;;;WAKR,OAAO;;;;;;;;UASD,oBACf,eAAe,QAAQ,OACvB;;;;WAKS;;;;WAIA,QAAQ;;;;WAIR,OAAO;;;;;;;;UASD;;;;;;WAMN,SAAS,oBAAoB;;;;;;WAM7B,SAAS;;;;;;;;WAQT;;;;;;;WAOA,SAAS;;;;;;;;;;;;;KAcR,eAAe;;;;EAKrB;;;;EAIA,OAAO;;;;;EAMP;;;;;EAKA,OAAO,aAAa;;;;EAIpB"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;UAuBiB,YAAY,eAAe,QAAQ,OAAO;;;;;;EAMzD,OAAO,OAAO,mBAAmB,QAAQ,WAAW;;;;;;;;EAQpD,eAAe,OAAO,oBAAoB,QAAQ,WAAW;;;;;;;;;;;;;EAa7D,gBAAgB,mBAAmB,SAAS;;;;;;UAO7B,oBAAoB,eAAe,OAAO;;;;EAIzD,MAAM,YAAY,QAAQ;;;;;EAK1B,cAAc,OAAO,oBAAoB,QAAQ,WAAW;;;;;EAK5D,eAAe,mBAAmB,SAAS;;;;;;;;UAS5B;;;;;WAKN;;;;WAIA;;;;WAIA;;;;;;;;UASM,mBAAmB,eAAe,QAAQ,OAAO;;;;WAIvD;;;;;WAKA;;;;;WAKA,QAAQ;;;;;WAKR,OAAO;;;;;;;;UASD,oBAAoB,eAAe,QAAQ,OAAO;;;;WAIxD;;;;WAIA,QAAQ;;;;WAIR,OAAO;;;;;;;;UASD;;;;;;WAMN,SAAS,oBAAoB;;;;;;WAM7B,SAAS;;;;;;;;WAQT;;;;;;;WAOA,SAAS;;;;;;;;;;;;KAaR,wBAAwB,KAAK;;;;;;;;;;;;KAa7B,eAAe;;;;EAKrB;;;;EAIA,OAAO;;;;;EAMP;;;;;EAKA,OAAO,aAAa;;;;EAIpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zap-studio/retry",
3
- "version": "1.2.1",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "Composable, tree-shakeable retry policies for resilient async operations.",
6
6
  "keywords": [
@@ -43,15 +43,23 @@
43
43
  "publishConfig": {
44
44
  "access": "public"
45
45
  },
46
+ "dependencies": {
47
+ "@zap-studio/monads": "1.0.0"
48
+ },
46
49
  "devDependencies": {
50
+ "@opentelemetry/api": "^1.9.0",
51
+ "@opentelemetry/context-async-hooks": "^2.10.0",
52
+ "@opentelemetry/sdk-metrics": "^2.10.0",
53
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
47
54
  "tsdown": "^0.22.14",
48
55
  "typescript": "^7.0.2",
49
56
  "vitest": "^4.1.10",
50
- "@zap-studio/logger": "1.0.0",
51
- "@zap-studio/typescript": "0.0.0"
57
+ "@zap-studio/typescript": "0.0.0",
58
+ "@zap-studio/logger": "2.0.0"
52
59
  },
53
60
  "peerDependencies": {
54
- "@zap-studio/logger": "1.0.0"
61
+ "@opentelemetry/api": "^1.9.0",
62
+ "@zap-studio/logger": "2.0.0"
55
63
  },
56
64
  "peerDependenciesMeta": {
57
65
  "@zap-studio/logger": {