@zap-studio/retry 0.2.0 → 0.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/CHANGELOG.md +18 -0
- package/README.md +16 -4
- package/dist/abort.d.mts +30 -0
- package/dist/abort.d.mts.map +1 -0
- package/dist/abort.mjs +61 -0
- package/dist/abort.mjs.map +1 -0
- package/dist/errors-fWo_KyVO.d.mts +76 -0
- package/dist/errors-fWo_KyVO.d.mts.map +1 -0
- package/dist/errors.d.mts +2 -0
- package/dist/{error.mjs → errors.mjs} +21 -3
- package/dist/errors.mjs.map +1 -0
- package/dist/exponential-backoff.d.mts +18 -0
- package/dist/exponential-backoff.d.mts.map +1 -1
- package/dist/exponential-backoff.mjs +9 -0
- package/dist/exponential-backoff.mjs.map +1 -1
- package/dist/fixed-delay.d.mts +12 -0
- package/dist/fixed-delay.d.mts.map +1 -1
- package/dist/fixed-delay.mjs +6 -0
- package/dist/fixed-delay.mjs.map +1 -1
- package/dist/index.d.mts +3 -32
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +9 -172
- package/dist/index.mjs.map +1 -1
- package/dist/result-mode.d.mts +24 -0
- package/dist/result-mode.d.mts.map +1 -0
- package/dist/result-mode.mjs +145 -0
- package/dist/result-mode.mjs.map +1 -0
- package/dist/sleep.d.mts +17 -0
- package/dist/sleep.d.mts.map +1 -0
- package/dist/sleep.mjs +21 -0
- package/dist/sleep.mjs.map +1 -0
- package/dist/throw-mode.d.mts +26 -0
- package/dist/throw-mode.d.mts.map +1 -0
- package/dist/throw-mode.mjs +50 -0
- package/dist/throw-mode.mjs.map +1 -0
- package/dist/types.d.mts +58 -3
- package/dist/types.d.mts.map +1 -1
- package/package.json +6 -2
- package/dist/error-CVW4I654.d.mts +0 -44
- package/dist/error-CVW4I654.d.mts.map +0 -1
- package/dist/error.d.mts +0 -2
- package/dist/error.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { RetryError } from "./
|
|
1
|
+
import { RetryError } from "./errors.mjs";
|
|
2
|
+
import { runResultMode } from "./result-mode.mjs";
|
|
3
|
+
import { defaultSleep } from "./sleep.mjs";
|
|
4
|
+
import { runThrowMode } from "./throw-mode.mjs";
|
|
2
5
|
//#region src/index.ts
|
|
3
6
|
/**
|
|
4
7
|
* Retry runner base class and shared orchestration implementation.
|
|
@@ -40,7 +43,8 @@ var BaseRetryPolicy = class {
|
|
|
40
43
|
* @throws Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`
|
|
41
44
|
* function. When `throwOnExhausted` is `false`, exhaustion itself is returned
|
|
42
45
|
* as `{ ok: false }` instead of thrown.
|
|
43
|
-
*
|
|
46
|
+
* Cancellation is returned as `{ ok: false, error: AbortError }` in non-throw
|
|
47
|
+
* mode (not wrapped in `RetryError`).
|
|
44
48
|
*
|
|
45
49
|
* @example
|
|
46
50
|
* const result = await policy.run(doWork, { throwOnExhausted: false });
|
|
@@ -49,178 +53,11 @@ var BaseRetryPolicy = class {
|
|
|
49
53
|
async run(execute, options = {}) {
|
|
50
54
|
const sleep = options.sleep ?? defaultSleep;
|
|
51
55
|
const signal = options.signal;
|
|
52
|
-
if (options.throwOnExhausted === false) return
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Runs retry orchestration in throwing mode.
|
|
57
|
-
*
|
|
58
|
-
* This path is selected when `throwOnExhausted` is not `false`.
|
|
59
|
-
*
|
|
60
|
-
* @param execute - Async function to execute per attempt.
|
|
61
|
-
* @param sleep - Delay function used between retry attempts.
|
|
62
|
-
* @returns The successful execution value.
|
|
63
|
-
* @throws {RetryError} Terminal error returned by `onExhausted(...)`.
|
|
64
|
-
* @throws Any error thrown by `next`, by `onExhausted`, or by `sleep`.
|
|
65
|
-
*/
|
|
66
|
-
async runThrowMode(execute, sleep, signal) {
|
|
67
|
-
let attempt = 1;
|
|
68
|
-
while (true) {
|
|
69
|
-
throwIfAborted(signal);
|
|
70
|
-
try {
|
|
71
|
-
return await execute(attempt);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
throwIfAborted(signal);
|
|
74
|
-
const typedError = error;
|
|
75
|
-
const decision = this.next({
|
|
76
|
-
attempt,
|
|
77
|
-
error: typedError
|
|
78
|
-
});
|
|
79
|
-
if (!decision.shouldRetry) throw this.onExhausted({
|
|
80
|
-
attempts: attempt,
|
|
81
|
-
error: typedError
|
|
82
|
-
});
|
|
83
|
-
if (decision.delayMs > 0) if (signal) await sleepWithAbortSignal(sleep, decision.delayMs, signal);
|
|
84
|
-
else await sleep(decision.delayMs);
|
|
85
|
-
attempt += 1;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Runs retry orchestration in non-throw mode.
|
|
91
|
-
*
|
|
92
|
-
* This path is selected when `throwOnExhausted` is `false`.
|
|
93
|
-
*
|
|
94
|
-
* @param execute - Async function to execute per attempt.
|
|
95
|
-
* @param sleep - Delay function used between retry attempts.
|
|
96
|
-
* @returns A discriminated result union containing success value or terminal error.
|
|
97
|
-
* @throws Any error thrown by `next`, by `onExhausted`, or by `sleep`.
|
|
98
|
-
*/
|
|
99
|
-
async runResultMode(execute, sleep, signal) {
|
|
100
|
-
let attempt = 1;
|
|
101
|
-
while (true) {
|
|
102
|
-
if (signal?.aborted) {
|
|
103
|
-
const attempts = Math.max(0, attempt - 1);
|
|
104
|
-
return {
|
|
105
|
-
ok: false,
|
|
106
|
-
error: toRetryError(signal.reason, attempts),
|
|
107
|
-
attempts
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
return {
|
|
112
|
-
ok: true,
|
|
113
|
-
value: await execute(attempt)
|
|
114
|
-
};
|
|
115
|
-
} catch (error) {
|
|
116
|
-
if (signal?.aborted) {
|
|
117
|
-
const attempts = Math.max(0, attempt - 1);
|
|
118
|
-
return {
|
|
119
|
-
ok: false,
|
|
120
|
-
error: toRetryError(signal.reason, attempts),
|
|
121
|
-
attempts
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const typedError = error;
|
|
125
|
-
const decision = this.next({
|
|
126
|
-
attempt,
|
|
127
|
-
error: typedError
|
|
128
|
-
});
|
|
129
|
-
if (!decision.shouldRetry) return {
|
|
130
|
-
ok: false,
|
|
131
|
-
error: this.onExhausted({
|
|
132
|
-
attempts: attempt,
|
|
133
|
-
error: typedError
|
|
134
|
-
}),
|
|
135
|
-
attempts: attempt
|
|
136
|
-
};
|
|
137
|
-
if (decision.delayMs > 0) if (signal) try {
|
|
138
|
-
await sleepWithAbortSignal(sleep, decision.delayMs, signal);
|
|
139
|
-
} catch (error) {
|
|
140
|
-
if (signal.aborted) return {
|
|
141
|
-
ok: false,
|
|
142
|
-
error: toRetryError(signal.reason, attempt),
|
|
143
|
-
attempts: attempt
|
|
144
|
-
};
|
|
145
|
-
throw error;
|
|
146
|
-
}
|
|
147
|
-
else await sleep(decision.delayMs);
|
|
148
|
-
attempt += 1;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
56
|
+
if (options.throwOnExhausted === false) return runResultMode(this, execute, sleep, signal);
|
|
57
|
+
return runThrowMode(this, execute, sleep, signal);
|
|
151
58
|
}
|
|
152
59
|
};
|
|
153
|
-
/**
|
|
154
|
-
* Default delay implementation used by `run(...)` when no custom sleep function is provided.
|
|
155
|
-
*
|
|
156
|
-
* Returns immediately when `delayMs` is non-positive.
|
|
157
|
-
*/
|
|
158
|
-
async function defaultSleep(delayMs) {
|
|
159
|
-
if (delayMs <= 0) return;
|
|
160
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Throws an abort error when the provided signal is already aborted.
|
|
164
|
-
*
|
|
165
|
-
* @param signal - Optional cancellation signal.
|
|
166
|
-
* @throws {Error} Abort reason converted to an `Error`.
|
|
167
|
-
*/
|
|
168
|
-
function throwIfAborted(signal) {
|
|
169
|
-
if (!signal?.aborted) return;
|
|
170
|
-
throw toAbortError(signal.reason);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Normalizes an abort reason value into an `Error` instance.
|
|
174
|
-
*
|
|
175
|
-
* @param reason - Abort reason from `AbortSignal.reason`.
|
|
176
|
-
* @returns Normalized error instance.
|
|
177
|
-
*/
|
|
178
|
-
function toAbortError(reason) {
|
|
179
|
-
if (reason instanceof Error) return reason;
|
|
180
|
-
if (typeof reason === "string" && reason.length > 0) return new Error(reason);
|
|
181
|
-
if (reason === void 0) return /* @__PURE__ */ new Error("Retry aborted.");
|
|
182
|
-
try {
|
|
183
|
-
return /* @__PURE__ */ new Error(`Retry aborted: ${JSON.stringify(reason)}`);
|
|
184
|
-
} catch {
|
|
185
|
-
return /* @__PURE__ */ new Error("Retry aborted.");
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Converts an abort reason into a `RetryError` for non-throw runner mode.
|
|
190
|
-
*
|
|
191
|
-
* @param reason - Abort reason from `AbortSignal.reason`.
|
|
192
|
-
* @returns Retry terminal error value.
|
|
193
|
-
*/
|
|
194
|
-
function toRetryError(reason, attempts) {
|
|
195
|
-
const abortError = toAbortError(reason);
|
|
196
|
-
return new RetryError(abortError.message, {
|
|
197
|
-
attempts,
|
|
198
|
-
lastError: abortError
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Awaits delay sleep while also observing cancellation via `AbortSignal`.
|
|
203
|
-
*
|
|
204
|
-
* @param sleep - Delay function.
|
|
205
|
-
* @param delayMs - Delay duration in milliseconds.
|
|
206
|
-
* @param signal - Cancellation signal.
|
|
207
|
-
* @throws {Error} Abort reason converted to an `Error` when canceled.
|
|
208
|
-
*/
|
|
209
|
-
async function sleepWithAbortSignal(sleep, delayMs, signal) {
|
|
210
|
-
if (signal.aborted) throw toAbortError(signal.reason);
|
|
211
|
-
let onAbort;
|
|
212
|
-
try {
|
|
213
|
-
await Promise.race([sleep(delayMs), new Promise((_, reject) => {
|
|
214
|
-
onAbort = () => {
|
|
215
|
-
reject(toAbortError(signal.reason));
|
|
216
|
-
};
|
|
217
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
218
|
-
})]);
|
|
219
|
-
} finally {
|
|
220
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
60
|
//#endregion
|
|
224
|
-
export { BaseRetryPolicy
|
|
61
|
+
export { BaseRetryPolicy };
|
|
225
62
|
|
|
226
63
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Retry runner base class and shared orchestration implementation.\n *\n * @module @zap-studio/retry\n */\n\nimport { RetryError } from \"./error.js\";\nimport type {\n RetryDecision,\n RetryDecisionInput,\n RetryExhaustedInput,\n RetryPolicy,\n RetryRunOptions,\n RetryRunResult,\n} from \"./types.js\";\n\n/**\n * Base class for implementing retry policies and running retry orchestration.\n *\n * Extend this class and implement {@link BaseRetryPolicy.next} to define retry\n * behavior, then call {@link BaseRetryPolicy.run} to execute operations with that\n * policy.\n */\nexport abstract class BaseRetryPolicy<TError = unknown, TData = unknown> implements RetryPolicy<\n TError,\n TData\n> {\n /**\n * Returns the retry decision for a failed attempt.\n *\n * @param input - Attempt context used to compute retry behavior.\n * @throws Any error thrown by a concrete retry policy implementation.\n */\n public abstract next(input: RetryDecisionInput<TError, TData>): RetryDecision;\n\n /**\n * Builds the terminal error thrown or returned when retries are exhausted.\n *\n * Override this when you need custom terminal error types.\n *\n * @param input - Exhaustion context.\n * @returns `RetryError` by default.\n * @throws Any error thrown by an overriding policy implementation.\n */\n public onExhausted(input: RetryExhaustedInput<TError, TData>): RetryError {\n return new RetryError(\"Retry policy exhausted all attempts.\", {\n attempts: input.attempts,\n lastError: input.error,\n lastData: input.data,\n });\n }\n\n /**\n * Runs retry orchestration in non-throw mode.\n *\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings with `throwOnExhausted: false`.\n * @returns A discriminated result union containing success value or terminal error.\n * @throws Any error thrown by `next`, `onExhausted`, or a custom `sleep`.\n */\n public async run<T>(\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions & { throwOnExhausted: false },\n ): Promise<RetryRunResult<T>>;\n\n /**\n * Runs retry orchestration and throws terminal error on exhaustion.\n *\n * @param execute - Async function to execute per attempt.\n * @param options - Optional runner settings.\n * @returns The successful execution value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns the\n * terminal retry error. The default implementation returns `RetryError` with the last\n * execution failure available on `RetryError.lastError`.\n * @throws {Error} When `options.signal` is already aborted or aborts while retrying.\n * @throws Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function.\n */\n public async run<T>(\n execute: (attempt: number) => Promise<T>,\n options?: RetryRunOptions & { throwOnExhausted?: true },\n ): Promise<T>;\n\n /**\n * Runs retry orchestration in non-throw mode.\n *\n * When `throwOnExhausted` is `false`, returns a discriminated result union.\n *\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings.\n * @returns Success value or terminal result object based on option mode.\n * @throws Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function. When `throwOnExhausted` is `false`, exhaustion itself is returned\n * as `{ ok: false }` instead of thrown.\n * Abort errors are also returned as `{ ok: false }` in non-throw mode.\n *\n * @example\n * const result = await policy.run(doWork, { throwOnExhausted: false });\n * if (!result.ok) console.error(result.error);\n */\n public async run<T>(\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions = {},\n ): Promise<T | RetryRunResult<T>> {\n const sleep = options.sleep ?? defaultSleep;\n const signal = options.signal;\n if (options.throwOnExhausted === false) {\n return this.runResultMode(execute, sleep, signal);\n }\n\n return this.runThrowMode(execute, sleep, signal);\n }\n\n /**\n * Runs retry orchestration in throwing mode.\n *\n * This path is selected when `throwOnExhausted` is not `false`.\n *\n * @param execute - Async function to execute per attempt.\n * @param sleep - Delay function used between retry attempts.\n * @returns The successful execution value.\n * @throws {RetryError} Terminal error returned by `onExhausted(...)`.\n * @throws Any error thrown by `next`, by `onExhausted`, or by `sleep`.\n */\n private async runThrowMode<T>(\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal,\n ): Promise<T> {\n let attempt = 1;\n\n while (true) {\n throwIfAborted(signal);\n\n try {\n return await execute(attempt);\n } catch (error) {\n throwIfAborted(signal);\n\n const typedError = error as TError;\n const decision = this.next({\n attempt,\n error: typedError,\n });\n\n if (!decision.shouldRetry) {\n throw this.onExhausted({\n attempts: attempt,\n error: typedError,\n });\n }\n\n if (decision.delayMs > 0) {\n if (signal) {\n await sleepWithAbortSignal(sleep, decision.delayMs, signal);\n } else {\n await sleep(decision.delayMs);\n }\n }\n\n attempt += 1;\n }\n }\n }\n\n /**\n * Runs retry orchestration in non-throw mode.\n *\n * This path is selected when `throwOnExhausted` is `false`.\n *\n * @param execute - Async function to execute per attempt.\n * @param sleep - Delay function used between retry attempts.\n * @returns A discriminated result union containing success value or terminal error.\n * @throws Any error thrown by `next`, by `onExhausted`, or by `sleep`.\n */\n private async runResultMode<T>(\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal,\n ): Promise<RetryRunResult<T>> {\n let attempt = 1;\n\n while (true) {\n if (signal?.aborted) {\n const attempts = Math.max(0, attempt - 1);\n return {\n ok: false,\n error: toRetryError(signal.reason, attempts),\n attempts,\n };\n }\n\n try {\n const value = await execute(attempt);\n return { ok: true, value };\n } catch (error) {\n if (signal?.aborted) {\n const attempts = Math.max(0, attempt - 1);\n return {\n ok: false,\n error: toRetryError(signal.reason, attempts),\n attempts,\n };\n }\n\n const typedError = error as TError;\n const decision = this.next({\n attempt,\n error: typedError,\n });\n\n if (!decision.shouldRetry) {\n const terminalError = this.onExhausted({\n attempts: attempt,\n error: typedError,\n });\n\n return {\n ok: false,\n error: terminalError,\n attempts: attempt,\n };\n }\n\n if (decision.delayMs > 0) {\n if (signal) {\n try {\n await sleepWithAbortSignal(sleep, decision.delayMs, signal);\n } catch (error) {\n if (signal.aborted) {\n return {\n ok: false,\n error: toRetryError(signal.reason, attempt),\n attempts: attempt,\n };\n }\n throw error;\n }\n } else {\n await sleep(decision.delayMs);\n }\n }\n\n attempt += 1;\n }\n }\n }\n}\n\n/**\n * Default delay implementation used by `run(...)` when no custom sleep function is provided.\n *\n * Returns immediately when `delayMs` is non-positive.\n */\nexport async function defaultSleep(delayMs: number): Promise<void> {\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n}\n\n/**\n * Throws an abort error when the provided signal is already aborted.\n *\n * @param signal - Optional cancellation signal.\n * @throws {Error} Abort reason converted to an `Error`.\n */\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) {\n return;\n }\n\n throw toAbortError(signal.reason);\n}\n\n/**\n * Normalizes an abort reason value into an `Error` instance.\n *\n * @param reason - Abort reason from `AbortSignal.reason`.\n * @returns Normalized error instance.\n */\nfunction toAbortError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n\n if (typeof reason === \"string\" && reason.length > 0) {\n return new Error(reason);\n }\n\n if (reason === undefined) {\n return new Error(\"Retry aborted.\");\n }\n\n try {\n return new Error(`Retry aborted: ${JSON.stringify(reason)}`);\n } catch {\n return new Error(\"Retry aborted.\");\n }\n}\n\n/**\n * Converts an abort reason into a `RetryError` for non-throw runner mode.\n *\n * @param reason - Abort reason from `AbortSignal.reason`.\n * @returns Retry terminal error value.\n */\nfunction toRetryError(reason: unknown, attempts: number): RetryError {\n const abortError = toAbortError(reason);\n return new RetryError(abortError.message, {\n attempts,\n lastError: abortError,\n });\n}\n\n/**\n * Awaits delay sleep while also observing cancellation via `AbortSignal`.\n *\n * @param sleep - Delay function.\n * @param delayMs - Delay duration in milliseconds.\n * @param signal - Cancellation signal.\n * @throws {Error} Abort reason converted to an `Error` when canceled.\n */\nasync function sleepWithAbortSignal(\n sleep: (delayMs: number) => Promise<void>,\n delayMs: number,\n signal: AbortSignal,\n): Promise<void> {\n if (signal.aborted) {\n throw toAbortError(signal.reason);\n }\n\n let onAbort: (() => void) | undefined;\n\n try {\n await Promise.race([\n sleep(delayMs),\n new Promise<never>((_, reject) => {\n onAbort = (): void => {\n reject(toAbortError(signal.reason));\n };\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }),\n ]);\n } finally {\n if (onAbort) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Retry runner base class and shared orchestration implementation.\n *\n * @module @zap-studio/retry\n */\n\nimport { RetryError } from \"./errors.js\";\nimport { runResultMode } from \"./result-mode.js\";\nimport { defaultSleep } from \"./sleep.js\";\nimport { runThrowMode } from \"./throw-mode.js\";\nimport type {\n RetryDecision,\n RetryDecisionInput,\n RetryExhaustedInput,\n RetryPolicy,\n RetryRunOptions,\n RetryRunResult,\n} from \"./types.js\";\n\n/**\n * Base class for implementing retry policies and running retry orchestration.\n *\n * Extend this class and implement {@link BaseRetryPolicy.next} to define retry\n * behavior, then call {@link BaseRetryPolicy.run} to execute operations with that\n * policy.\n */\nexport abstract class BaseRetryPolicy<TError = unknown, TData = unknown> implements RetryPolicy<\n TError,\n TData\n> {\n /**\n * Returns the retry decision for a failed attempt.\n *\n * @param input - Attempt context used to compute retry behavior.\n * @throws Any error thrown by a concrete retry policy implementation.\n */\n public abstract next(input: RetryDecisionInput<TError, TData>): RetryDecision;\n\n /**\n * Builds the terminal error thrown or returned when retries are exhausted.\n *\n * Override this when you need custom terminal error types.\n *\n * @param input - Exhaustion context.\n * @returns `RetryError` by default.\n * @throws Any error thrown by an overriding policy implementation.\n */\n public onExhausted(input: RetryExhaustedInput<TError, TData>): RetryError {\n return new RetryError(\"Retry policy exhausted all attempts.\", {\n attempts: input.attempts,\n lastError: input.error,\n lastData: input.data,\n });\n }\n\n /**\n * Runs retry orchestration in non-throw mode.\n *\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings with `throwOnExhausted: false`.\n * @returns A discriminated result union containing success value or terminal error.\n * @throws Any error thrown by `next`, `onExhausted`, or a custom `sleep`.\n */\n public async run<T>(\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions & { throwOnExhausted: false },\n ): Promise<RetryRunResult<T>>;\n\n /**\n * Runs retry orchestration and throws terminal error on exhaustion.\n *\n * @param execute - Async function to execute per attempt.\n * @param options - Optional runner settings.\n * @returns The successful execution value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns the\n * terminal retry error. The default implementation returns `RetryError` with the last\n * execution failure available on `RetryError.lastError`.\n * @throws {AbortError} When `options.signal` is already aborted or aborts while retrying.\n * @throws Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function.\n */\n public async run<T>(\n execute: (attempt: number) => Promise<T>,\n options?: RetryRunOptions & { throwOnExhausted?: true },\n ): Promise<T>;\n\n /**\n * Runs retry orchestration in non-throw mode.\n *\n * When `throwOnExhausted` is `false`, returns a discriminated result union.\n *\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings.\n * @returns Success value or terminal result object based on option mode.\n * @throws Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function. When `throwOnExhausted` is `false`, exhaustion itself is returned\n * as `{ ok: false }` instead of thrown.\n * Cancellation is returned as `{ ok: false, error: AbortError }` in non-throw\n * mode (not wrapped in `RetryError`).\n *\n * @example\n * const result = await policy.run(doWork, { throwOnExhausted: false });\n * if (!result.ok) console.error(result.error);\n */\n public async run<T>(\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions = {},\n ): Promise<T | RetryRunResult<T>> {\n const sleep = options.sleep ?? defaultSleep;\n const signal = options.signal;\n if (options.throwOnExhausted === false) {\n return runResultMode(this, execute, sleep, signal);\n }\n\n return runThrowMode(this, execute, sleep, signal);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,IAAsB,kBAAtB,MAGE;;;;;;;;;;CAkBA,YAAmB,OAAuD;AACxE,SAAO,IAAI,WAAW,wCAAwC;GAC5D,UAAU,MAAM;GAChB,WAAW,MAAM;GACjB,UAAU,MAAM;GACjB,CAAC;;;;;;;;;;;;;;;;;;;;CAoDJ,MAAa,IACX,SACA,UAA2B,EAAE,EACG;EAChC,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,SAAS,QAAQ;AACvB,MAAI,QAAQ,qBAAqB,MAC/B,QAAO,cAAc,MAAM,SAAS,OAAO,OAAO;AAGpD,SAAO,aAAa,MAAM,SAAS,OAAO,OAAO"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { r as RetryError } from "./errors-fWo_KyVO.mjs";
|
|
2
|
+
import { RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryRunResult } from "./types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/result-mode.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Runs the non-throw retry loop, returning
|
|
7
|
+
* `RetryRunResult`.
|
|
8
|
+
*
|
|
9
|
+
* @param policy - Object providing `next` and `onExhausted` (same contract as
|
|
10
|
+
* `BaseRetryPolicy`).
|
|
11
|
+
* @param execute - Async work callback per attempt.
|
|
12
|
+
* @param sleep - Delay function between retries.
|
|
13
|
+
* @param signal - Optional cancel signal.
|
|
14
|
+
* @returns Terminal success or failure object.
|
|
15
|
+
* @throws Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`
|
|
16
|
+
* failure.
|
|
17
|
+
*/
|
|
18
|
+
declare function runResultMode<T, TError, TData>(policy: {
|
|
19
|
+
next: (input: RetryDecisionInput<TError, TData>) => RetryDecision;
|
|
20
|
+
onExhausted: (input: RetryExhaustedInput<TError, TData>) => RetryError;
|
|
21
|
+
}, execute: (attempt: number) => Promise<T>, sleep: (delayMs: number) => Promise<void>, signal?: AbortSignal): Promise<RetryRunResult<T>>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { runResultMode };
|
|
24
|
+
//# sourceMappingURL=result-mode.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result-mode.d.mts","names":[],"sources":["../src/result-mode.ts"],"mappings":";;;;;;AA6BA;;;;;;;;;;;iBAAsB,aAAA,kBAAA,CACpB,MAAA;EACE,IAAA,GAAO,KAAA,EAAO,kBAAA,CAAmB,MAAA,EAAQ,KAAA,MAAW,aAAA;EACpD,WAAA,GAAc,KAAA,EAAO,mBAAA,CAAoB,MAAA,EAAQ,KAAA,MAAW,UAAA;AAAA,GAE9D,OAAA,GAAU,OAAA,aAAoB,OAAA,CAAQ,CAAA,GACtC,KAAA,GAAQ,OAAA,aAAoB,OAAA,QAC5B,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,cAAA,CAAe,CAAA"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { sleepWithAbortSignal, toAbortError } from "./abort.mjs";
|
|
2
|
+
//#region src/result-mode.ts
|
|
3
|
+
/**
|
|
4
|
+
* Result-mode execution path for `BaseRetryPolicy.run` when
|
|
5
|
+
* `throwOnExhausted: false` is set.
|
|
6
|
+
*
|
|
7
|
+
* @module @zap-studio/retry/result-mode
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Runs the non-throw retry loop, returning
|
|
11
|
+
* `RetryRunResult`.
|
|
12
|
+
*
|
|
13
|
+
* @param policy - Object providing `next` and `onExhausted` (same contract as
|
|
14
|
+
* `BaseRetryPolicy`).
|
|
15
|
+
* @param execute - Async work callback per attempt.
|
|
16
|
+
* @param sleep - Delay function between retries.
|
|
17
|
+
* @param signal - Optional cancel signal.
|
|
18
|
+
* @returns Terminal success or failure object.
|
|
19
|
+
* @throws Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`
|
|
20
|
+
* failure.
|
|
21
|
+
*/
|
|
22
|
+
async function runResultMode(policy, execute, sleep, signal) {
|
|
23
|
+
let attempt = 1;
|
|
24
|
+
while (true) {
|
|
25
|
+
const earlyAbortResult = abortResult(signal, Math.max(0, attempt - 1));
|
|
26
|
+
if (earlyAbortResult) return earlyAbortResult;
|
|
27
|
+
const execution = await runAttempt(execute, attempt);
|
|
28
|
+
if (execution.ok) return {
|
|
29
|
+
ok: true,
|
|
30
|
+
value: execution.value
|
|
31
|
+
};
|
|
32
|
+
const failure = await handleFailure(policy, {
|
|
33
|
+
attempt,
|
|
34
|
+
error: execution.error,
|
|
35
|
+
sleep,
|
|
36
|
+
signal
|
|
37
|
+
});
|
|
38
|
+
if (failure) return failure;
|
|
39
|
+
attempt += 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runs one `execute(attempt)` call and returns either a success value or a
|
|
44
|
+
* captured error without rethrowing.
|
|
45
|
+
*
|
|
46
|
+
* @param execute - User work callback.
|
|
47
|
+
* @param attempt - One-based attempt number passed to `execute`.
|
|
48
|
+
* @returns A tagged success with `value` or a tagged failure with `error`.
|
|
49
|
+
*/
|
|
50
|
+
async function runAttempt(execute, attempt) {
|
|
51
|
+
try {
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
value: await execute(attempt)
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* After a failed attempt, applies abort rules, `next`, optional delay, and
|
|
65
|
+
* either returns a terminal `RetryRunResult` or `undefined` to continue.
|
|
66
|
+
*
|
|
67
|
+
* @param policy - Retry policy hooks (`next`, `onExhausted`) matching
|
|
68
|
+
* `BaseRetryPolicy`.
|
|
69
|
+
* @param params - Failure context for the current attempt.
|
|
70
|
+
* @param params.attempt - Current attempt number.
|
|
71
|
+
* @param params.error - Error thrown by the attempt.
|
|
72
|
+
* @param params.sleep - Delay function between retries.
|
|
73
|
+
* @param params.signal - Optional abort signal.
|
|
74
|
+
* @returns Terminal non-throw result if the loop should stop, otherwise
|
|
75
|
+
* `undefined` to schedule another attempt.
|
|
76
|
+
* @throws Any error thrown by `next`, `onExhausted`, or a custom `sleep` when
|
|
77
|
+
* the error is not an abort.
|
|
78
|
+
*/
|
|
79
|
+
async function handleFailure(policy, params) {
|
|
80
|
+
const { attempt, error, sleep, signal } = params;
|
|
81
|
+
const postExecuteAbortResult = abortResult(signal, attempt);
|
|
82
|
+
if (postExecuteAbortResult) return postExecuteAbortResult;
|
|
83
|
+
const decision = policy.next({
|
|
84
|
+
attempt,
|
|
85
|
+
error
|
|
86
|
+
});
|
|
87
|
+
if (!decision.shouldRetry) return {
|
|
88
|
+
ok: false,
|
|
89
|
+
error: policy.onExhausted({
|
|
90
|
+
attempts: attempt,
|
|
91
|
+
error
|
|
92
|
+
}),
|
|
93
|
+
attempts: attempt
|
|
94
|
+
};
|
|
95
|
+
if (decision.delayMs > 0) {
|
|
96
|
+
const sleepAbortResult = await waitForDelay(sleep, decision.delayMs, signal, attempt);
|
|
97
|
+
if (sleepAbortResult) return sleepAbortResult;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* When `signal` is already aborted, builds the terminal `{ ok: false }` object
|
|
102
|
+
* with a normalized `AbortError` on `error` (not wrapped in `RetryError`).
|
|
103
|
+
*
|
|
104
|
+
* @param signal - Optional abort signal; only acts when `aborted` is set.
|
|
105
|
+
* @param attempts - Number of finished attempts to report in the result.
|
|
106
|
+
* @returns Failure result or `undefined` if not aborted.
|
|
107
|
+
*/
|
|
108
|
+
function abortResult(signal, attempts) {
|
|
109
|
+
if (!signal?.aborted) return;
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: toAbortError(signal.reason),
|
|
113
|
+
attempts
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Awaits inter-attempt delay in result mode, mapping an abort during wait to
|
|
118
|
+
* a terminal result instead of throwing when `throwOnExhausted` is false.
|
|
119
|
+
*
|
|
120
|
+
* @param sleep - Custom or default sleep implementation.
|
|
121
|
+
* @param delayMs - Milliseconds to wait.
|
|
122
|
+
* @param signal - If set, `sleep` is raced with the abort signal.
|
|
123
|
+
* @param attempts - Attempt count to attach if the wait ends in abort.
|
|
124
|
+
* @returns A terminal result when canceled during the wait, otherwise
|
|
125
|
+
* `undefined`.
|
|
126
|
+
* @throws The underlying `sleep` rejection when it is not an abort.
|
|
127
|
+
*/
|
|
128
|
+
async function waitForDelay(sleep, delayMs, signal, attempts) {
|
|
129
|
+
if (!signal) {
|
|
130
|
+
await sleep(delayMs);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await sleepWithAbortSignal(sleep, delayMs, signal);
|
|
135
|
+
return;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const aborted = abortResult(signal, attempts);
|
|
138
|
+
if (aborted) return aborted;
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { runResultMode };
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=result-mode.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result-mode.mjs","names":[],"sources":["../src/result-mode.ts"],"sourcesContent":["/**\n * Result-mode execution path for `BaseRetryPolicy.run` when\n * `throwOnExhausted: false` is set.\n *\n * @module @zap-studio/retry/result-mode\n */\n\nimport { sleepWithAbortSignal, toAbortError } from \"./abort.js\";\nimport type { RetryError } from \"./errors.js\";\nimport type {\n RetryDecision,\n RetryDecisionInput,\n RetryExhaustedInput,\n RetryRunResult,\n} from \"./types.js\";\n\n/**\n * Runs the non-throw retry loop, returning\n * `RetryRunResult`.\n *\n * @param policy - Object providing `next` and `onExhausted` (same contract as\n * `BaseRetryPolicy`).\n * @param execute - Async work callback per attempt.\n * @param sleep - Delay function between retries.\n * @param signal - Optional cancel signal.\n * @returns Terminal success or failure object.\n * @throws Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`\n * failure.\n */\nexport async function runResultMode<T, TError, TData>(\n policy: {\n next: (input: RetryDecisionInput<TError, TData>) => RetryDecision;\n onExhausted: (input: RetryExhaustedInput<TError, TData>) => RetryError;\n },\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal,\n): Promise<RetryRunResult<T>> {\n let attempt = 1;\n\n while (true) {\n const earlyAbortResult = abortResult(signal, Math.max(0, attempt - 1));\n if (earlyAbortResult) return earlyAbortResult;\n\n const execution = await runAttempt(execute, attempt);\n if (execution.ok) {\n return { ok: true, value: execution.value };\n }\n\n const failure = await handleFailure(policy, {\n attempt,\n error: execution.error as TError,\n sleep,\n signal,\n });\n if (failure) return failure;\n\n attempt += 1;\n }\n}\n\n/**\n * Runs one `execute(attempt)` call and returns either a success value or a\n * captured error without rethrowing.\n *\n * @param execute - User work callback.\n * @param attempt - One-based attempt number passed to `execute`.\n * @returns A tagged success with `value` or a tagged failure with `error`.\n */\nasync function runAttempt<T>(\n execute: (attempt: number) => Promise<T>,\n attempt: number,\n): Promise<{ ok: true; value: T } | { ok: false; error: unknown }> {\n try {\n return {\n ok: true,\n value: await execute(attempt),\n };\n } catch (error) {\n return {\n ok: false,\n error,\n };\n }\n}\n\n/**\n * After a failed attempt, applies abort rules, `next`, optional delay, and\n * either returns a terminal `RetryRunResult` or `undefined` to continue.\n *\n * @param policy - Retry policy hooks (`next`, `onExhausted`) matching\n * `BaseRetryPolicy`.\n * @param params - Failure context for the current attempt.\n * @param params.attempt - Current attempt number.\n * @param params.error - Error thrown by the attempt.\n * @param params.sleep - Delay function between retries.\n * @param params.signal - Optional abort signal.\n * @returns Terminal non-throw result if the loop should stop, otherwise\n * `undefined` to schedule another attempt.\n * @throws Any error thrown by `next`, `onExhausted`, or a custom `sleep` when\n * the error is not an abort.\n */\nasync function handleFailure<TError, TData>(\n policy: {\n next: (input: RetryDecisionInput<TError, TData>) => RetryDecision;\n onExhausted: (input: RetryExhaustedInput<TError, TData>) => RetryError;\n },\n params: {\n attempt: number;\n error: TError;\n sleep: (delayMs: number) => Promise<void>;\n signal: AbortSignal | undefined;\n },\n): Promise<RetryRunResult<never> | undefined> {\n const { attempt, error, sleep, signal } = params;\n const postExecuteAbortResult = abortResult(signal, attempt);\n if (postExecuteAbortResult) return postExecuteAbortResult;\n\n const decision = policy.next({\n attempt,\n error,\n });\n\n if (!decision.shouldRetry) {\n const terminalError = policy.onExhausted({\n attempts: attempt,\n error,\n });\n\n return {\n ok: false,\n error: terminalError,\n attempts: attempt,\n };\n }\n\n if (decision.delayMs > 0) {\n const sleepAbortResult = await waitForDelay(sleep, decision.delayMs, signal, attempt);\n if (sleepAbortResult) return sleepAbortResult;\n }\n\n return;\n}\n\n/**\n * When `signal` is already aborted, builds the terminal `{ ok: false }` object\n * with a normalized `AbortError` on `error` (not wrapped in `RetryError`).\n *\n * @param signal - Optional abort signal; only acts when `aborted` is set.\n * @param attempts - Number of finished attempts to report in the result.\n * @returns Failure result or `undefined` if not aborted.\n */\nfunction abortResult(\n signal: AbortSignal | undefined,\n attempts: number,\n): RetryRunResult<never> | undefined {\n if (!signal?.aborted) {\n return;\n }\n\n return {\n ok: false,\n error: toAbortError(signal.reason),\n attempts,\n };\n}\n\n/**\n * Awaits inter-attempt delay in result mode, mapping an abort during wait to\n * a terminal result instead of throwing when `throwOnExhausted` is false.\n *\n * @param sleep - Custom or default sleep implementation.\n * @param delayMs - Milliseconds to wait.\n * @param signal - If set, `sleep` is raced with the abort signal.\n * @param attempts - Attempt count to attach if the wait ends in abort.\n * @returns A terminal result when canceled during the wait, otherwise\n * `undefined`.\n * @throws The underlying `sleep` rejection when it is not an abort.\n */\nasync function waitForDelay(\n sleep: (delayMs: number) => Promise<void>,\n delayMs: number,\n signal: AbortSignal | undefined,\n attempts: number,\n): Promise<RetryRunResult<never> | undefined> {\n if (!signal) {\n await sleep(delayMs);\n return;\n }\n\n try {\n await sleepWithAbortSignal(sleep, delayMs, signal);\n return;\n } catch (error) {\n const aborted = abortResult(signal, attempts);\n if (aborted) {\n return aborted;\n }\n throw error;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,eAAsB,cACpB,QAIA,SACA,OACA,QAC4B;CAC5B,IAAI,UAAU;AAEd,QAAO,MAAM;EACX,MAAM,mBAAmB,YAAY,QAAQ,KAAK,IAAI,GAAG,UAAU,EAAE,CAAC;AACtE,MAAI,iBAAkB,QAAO;EAE7B,MAAM,YAAY,MAAM,WAAW,SAAS,QAAQ;AACpD,MAAI,UAAU,GACZ,QAAO;GAAE,IAAI;GAAM,OAAO,UAAU;GAAO;EAG7C,MAAM,UAAU,MAAM,cAAc,QAAQ;GAC1C;GACA,OAAO,UAAU;GACjB;GACA;GACD,CAAC;AACF,MAAI,QAAS,QAAO;AAEpB,aAAW;;;;;;;;;;;AAYf,eAAe,WACb,SACA,SACiE;AACjE,KAAI;AACF,SAAO;GACL,IAAI;GACJ,OAAO,MAAM,QAAQ,QAAQ;GAC9B;UACM,OAAO;AACd,SAAO;GACL,IAAI;GACJ;GACD;;;;;;;;;;;;;;;;;;;AAoBL,eAAe,cACb,QAIA,QAM4C;CAC5C,MAAM,EAAE,SAAS,OAAO,OAAO,WAAW;CAC1C,MAAM,yBAAyB,YAAY,QAAQ,QAAQ;AAC3D,KAAI,uBAAwB,QAAO;CAEnC,MAAM,WAAW,OAAO,KAAK;EAC3B;EACA;EACD,CAAC;AAEF,KAAI,CAAC,SAAS,YAMZ,QAAO;EACL,IAAI;EACJ,OAPoB,OAAO,YAAY;GACvC,UAAU;GACV;GACD,CAAC;EAKA,UAAU;EACX;AAGH,KAAI,SAAS,UAAU,GAAG;EACxB,MAAM,mBAAmB,MAAM,aAAa,OAAO,SAAS,SAAS,QAAQ,QAAQ;AACrF,MAAI,iBAAkB,QAAO;;;;;;;;;;;AAcjC,SAAS,YACP,QACA,UACmC;AACnC,KAAI,CAAC,QAAQ,QACX;AAGF,QAAO;EACL,IAAI;EACJ,OAAO,aAAa,OAAO,OAAO;EAClC;EACD;;;;;;;;;;;;;;AAeH,eAAe,aACb,OACA,SACA,QACA,UAC4C;AAC5C,KAAI,CAAC,QAAQ;AACX,QAAM,MAAM,QAAQ;AACpB;;AAGF,KAAI;AACF,QAAM,qBAAqB,OAAO,SAAS,OAAO;AAClD;UACO,OAAO;EACd,MAAM,UAAU,YAAY,QAAQ,SAAS;AAC7C,MAAI,QACF,QAAO;AAET,QAAM"}
|
package/dist/sleep.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/sleep.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default delay implementation used by `BaseRetryPolicy.run` when no custom
|
|
4
|
+
* `sleep` is provided.
|
|
5
|
+
*
|
|
6
|
+
* @module @zap-studio/retry/sleep
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Awaits a timer-based delay, unless `delayMs` is non-positive.
|
|
10
|
+
*
|
|
11
|
+
* @param delayMs - Milliseconds to wait before resolving.
|
|
12
|
+
* @returns Promise that resolves when the delay completes.
|
|
13
|
+
*/
|
|
14
|
+
declare function defaultSleep(delayMs: number): Promise<void>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { defaultSleep };
|
|
17
|
+
//# sourceMappingURL=sleep.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.d.mts","names":[],"sources":["../src/sleep.ts"],"mappings":";;AAaA;;;;;;;;;;;iBAAsB,YAAA,CAAa,OAAA,WAAkB,OAAA"}
|
package/dist/sleep.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/sleep.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default delay implementation used by `BaseRetryPolicy.run` when no custom
|
|
4
|
+
* `sleep` is provided.
|
|
5
|
+
*
|
|
6
|
+
* @module @zap-studio/retry/sleep
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Awaits a timer-based delay, unless `delayMs` is non-positive.
|
|
10
|
+
*
|
|
11
|
+
* @param delayMs - Milliseconds to wait before resolving.
|
|
12
|
+
* @returns Promise that resolves when the delay completes.
|
|
13
|
+
*/
|
|
14
|
+
async function defaultSleep(delayMs) {
|
|
15
|
+
if (delayMs <= 0) return;
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { defaultSleep };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=sleep.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.mjs","names":[],"sources":["../src/sleep.ts"],"sourcesContent":["/**\n * Default delay implementation used by `BaseRetryPolicy.run` when no custom\n * `sleep` is provided.\n *\n * @module @zap-studio/retry/sleep\n */\n\n/**\n * Awaits a timer-based delay, unless `delayMs` is non-positive.\n *\n * @param delayMs - Milliseconds to wait before resolving.\n * @returns Promise that resolves when the delay completes.\n */\nexport async function defaultSleep(delayMs: number): Promise<void> {\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,eAAsB,aAAa,SAAgC;AACjE,KAAI,WAAW,EACb;AAGF,OAAM,IAAI,SAAS,YAAY,WAAW,SAAS,QAAQ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { r as RetryError } from "./errors-fWo_KyVO.mjs";
|
|
2
|
+
import { RetryDecision, RetryDecisionInput, RetryExhaustedInput } from "./types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/throw-mode.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Runs the throw-mode retry loop: throws `RetryError` on exhaustion and
|
|
7
|
+
* `AbortError` when `signal` aborts.
|
|
8
|
+
*
|
|
9
|
+
* @param policy - Object providing `next` and `onExhausted` (same contract as
|
|
10
|
+
* `BaseRetryPolicy`).
|
|
11
|
+
* @param execute - Async work callback per attempt.
|
|
12
|
+
* @param sleep - Delay function between retries.
|
|
13
|
+
* @param signal - Optional cancel signal.
|
|
14
|
+
* @returns Resolves to the first successful return value.
|
|
15
|
+
* @throws {RetryError} When retries are exhausted and `onExhausted` returns
|
|
16
|
+
* the terminal error.
|
|
17
|
+
* @throws {AbortError} When `signal` is already aborted or aborts while waiting.
|
|
18
|
+
* @throws Any error thrown by `next`, `onExhausted`, or `sleep`.
|
|
19
|
+
*/
|
|
20
|
+
declare function runThrowMode<T, TError, TData>(policy: {
|
|
21
|
+
next: (input: RetryDecisionInput<TError, TData>) => RetryDecision;
|
|
22
|
+
onExhausted: (input: RetryExhaustedInput<TError, TData>) => RetryError;
|
|
23
|
+
}, execute: (attempt: number) => Promise<T>, sleep: (delayMs: number) => Promise<void>, signal?: AbortSignal): Promise<T>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { runThrowMode };
|
|
26
|
+
//# sourceMappingURL=throw-mode.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throw-mode.d.mts","names":[],"sources":["../src/throw-mode.ts"],"mappings":";;;;;;AA0BA;;;;;;;;;;;;;iBAAsB,YAAA,kBAAA,CACpB,MAAA;EACE,IAAA,GAAO,KAAA,EAAO,kBAAA,CAAmB,MAAA,EAAQ,KAAA,MAAW,aAAA;EACpD,WAAA,GAAc,KAAA,EAAO,mBAAA,CAAoB,MAAA,EAAQ,KAAA,MAAW,UAAA;AAAA,GAE9D,OAAA,GAAU,OAAA,aAAoB,OAAA,CAAQ,CAAA,GACtC,KAAA,GAAQ,OAAA,aAAoB,OAAA,QAC5B,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { sleepWithAbortSignal, throwIfAborted } from "./abort.mjs";
|
|
2
|
+
//#region src/throw-mode.ts
|
|
3
|
+
/**
|
|
4
|
+
* Throw-mode execution path for `BaseRetryPolicy.run` (default when
|
|
5
|
+
* `throwOnExhausted` is not `false`).
|
|
6
|
+
*
|
|
7
|
+
* @module @zap-studio/retry/throw-mode
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Runs the throw-mode retry loop: throws `RetryError` on exhaustion and
|
|
11
|
+
* `AbortError` when `signal` aborts.
|
|
12
|
+
*
|
|
13
|
+
* @param policy - Object providing `next` and `onExhausted` (same contract as
|
|
14
|
+
* `BaseRetryPolicy`).
|
|
15
|
+
* @param execute - Async work callback per attempt.
|
|
16
|
+
* @param sleep - Delay function between retries.
|
|
17
|
+
* @param signal - Optional cancel signal.
|
|
18
|
+
* @returns Resolves to the first successful return value.
|
|
19
|
+
* @throws {RetryError} When retries are exhausted and `onExhausted` returns
|
|
20
|
+
* the terminal error.
|
|
21
|
+
* @throws {AbortError} When `signal` is already aborted or aborts while waiting.
|
|
22
|
+
* @throws Any error thrown by `next`, `onExhausted`, or `sleep`.
|
|
23
|
+
*/
|
|
24
|
+
async function runThrowMode(policy, execute, sleep, signal) {
|
|
25
|
+
let attempt = 1;
|
|
26
|
+
while (true) {
|
|
27
|
+
throwIfAborted(signal);
|
|
28
|
+
try {
|
|
29
|
+
return await execute(attempt);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throwIfAborted(signal);
|
|
32
|
+
const typedError = error;
|
|
33
|
+
const decision = policy.next({
|
|
34
|
+
attempt,
|
|
35
|
+
error: typedError
|
|
36
|
+
});
|
|
37
|
+
if (!decision.shouldRetry) throw policy.onExhausted({
|
|
38
|
+
attempts: attempt,
|
|
39
|
+
error: typedError
|
|
40
|
+
});
|
|
41
|
+
if (decision.delayMs > 0) if (signal) await sleepWithAbortSignal(sleep, decision.delayMs, signal);
|
|
42
|
+
else await sleep(decision.delayMs);
|
|
43
|
+
attempt += 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { runThrowMode };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=throw-mode.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throw-mode.mjs","names":[],"sources":["../src/throw-mode.ts"],"sourcesContent":["/**\n * Throw-mode execution path for `BaseRetryPolicy.run` (default when\n * `throwOnExhausted` is not `false`).\n *\n * @module @zap-studio/retry/throw-mode\n */\n\nimport { sleepWithAbortSignal, throwIfAborted } from \"./abort.js\";\nimport { RetryError } from \"./errors.js\";\nimport type { RetryDecision, RetryDecisionInput, RetryExhaustedInput } from \"./types.js\";\n\n/**\n * Runs the throw-mode retry loop: throws `RetryError` on exhaustion and\n * `AbortError` when `signal` aborts.\n *\n * @param policy - Object providing `next` and `onExhausted` (same contract as\n * `BaseRetryPolicy`).\n * @param execute - Async work callback per attempt.\n * @param sleep - Delay function between retries.\n * @param signal - Optional cancel signal.\n * @returns Resolves to the first successful return value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns\n * the terminal error.\n * @throws {AbortError} When `signal` is already aborted or aborts while waiting.\n * @throws Any error thrown by `next`, `onExhausted`, or `sleep`.\n */\nexport async function runThrowMode<T, TError, TData>(\n policy: {\n next: (input: RetryDecisionInput<TError, TData>) => RetryDecision;\n onExhausted: (input: RetryExhaustedInput<TError, TData>) => RetryError;\n },\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal,\n): Promise<T> {\n let attempt = 1;\n\n while (true) {\n throwIfAborted(signal);\n\n try {\n return await execute(attempt);\n } catch (error) {\n throwIfAborted(signal);\n\n const typedError = error as TError;\n const decision = policy.next({\n attempt,\n error: typedError,\n });\n\n if (!decision.shouldRetry) {\n throw policy.onExhausted({\n attempts: attempt,\n error: typedError,\n });\n }\n\n if (decision.delayMs > 0) {\n if (signal) {\n await sleepWithAbortSignal(sleep, decision.delayMs, signal);\n } else {\n await sleep(decision.delayMs);\n }\n }\n\n attempt += 1;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,eAAsB,aACpB,QAIA,SACA,OACA,QACY;CACZ,IAAI,UAAU;AAEd,QAAO,MAAM;AACX,iBAAe,OAAO;AAEtB,MAAI;AACF,UAAO,MAAM,QAAQ,QAAQ;WACtB,OAAO;AACd,kBAAe,OAAO;GAEtB,MAAM,aAAa;GACnB,MAAM,WAAW,OAAO,KAAK;IAC3B;IACA,OAAO;IACR,CAAC;AAEF,OAAI,CAAC,SAAS,YACZ,OAAM,OAAO,YAAY;IACvB,UAAU;IACV,OAAO;IACR,CAAC;AAGJ,OAAI,SAAS,UAAU,EACrB,KAAI,OACF,OAAM,qBAAqB,OAAO,SAAS,SAAS,OAAO;OAE3D,OAAM,MAAM,SAAS,QAAQ;AAIjC,cAAW"}
|