2.9 KiB
retry
(no submodules)
Purpose
withRetry is the standalone, reusable retry executor that
@suaveplan/workflow exposes alongside the Step.retry configuration.
Where the step's built-in retry is bound to the step's lifecycle (it only
fires inside Step.run), withRetry can wrap any async function and is
intended for the moments callers want retry semantics outside the
workflow engine — e.g. retrying a manual cleanup, retrying a request
inside a custom step that doesn't quite fit Step, or building higher-level
retry-with-circuit-breaker compositions.
The module also re-exports the RetryPresets object so callers can pick
sensible defaults (quick, standard, aggressive) without duplicating
the numbers in every consumer.
Features
withRetry(fn, options?)— retries with the resolved config.createRetryWrapper(options?)— reusable closure capturing options.resolveRetryConfig(partial?)— fills in defaults fromconstants/index.RetryPresets.{none, quick, standard, aggressive}— pre-baked configs.- Hooks:
onRetry(err, attempt)before each backoff,shouldRetry(err, attempt)to short-circuit early.
Usage: Basic Example
import { withRetry, RetryPresets } from "@suaveplan/workflow";
const result = await withRetry(
() => fetch("/api/data").then((r) => r.json()),
RetryPresets.standard(),
);
Usage: Advanced Example
import { createRetryWrapper } from "@suaveplan/workflow";
const wrap = createRetryWrapper({
maxAttempts: 4,
strategy: "exponential",
delayMs: 100,
maxDelayMs: 2_000,
shouldRetry: (err) => !(err instanceof TypeError),
onRetry: (err, attempt) => {
console.warn(`attempt ${attempt} failed:`, err.message);
},
});
const json = await wrap(() => fetch("/api/v1").then((r) => r.json()));
API Reference
RetryOptions extends Partial<RetryConfig>—shouldRetry?: (error, attempt) => booleanonRetry?: (error, attempt) => void
resolveRetryConfig(options?: Partial<RetryConfig>): RetryConfig.withRetry<T>(fn: () => T | Promise<T>, options?: RetryOptions): Promise<T>.createRetryWrapper(options?: RetryOptions): <T>(fn) => Promise<T>.RetryPresets—{ none, quick, standard, aggressive }, each a function returning a freshRetryConfig.
Implementation Notes
withRetry always rethrows the last error, not the first. Callers
that want the first-error semantics can capture it inside an onRetry
closure. The loop checks attempt >= maxAttempts before shouldRetry so
the final attempt always actually runs even if shouldRetry would have
returned false — the contract is "make all the attempts you promised."
RetryPresets are factory functions rather than constants so each call
returns a fresh object. That keeps callers from accidentally mutating a
shared preset (e.g. by spreading-and-overriding) and seeing the change
ripple through the rest of the application.