# 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 from `constants/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 ```ts import { withRetry, RetryPresets } from "@suaveplan/workflow"; const result = await withRetry( () => fetch("/api/data").then((r) => r.json()), RetryPresets.standard(), ); ``` ## Usage: Advanced Example ```ts 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` — - `shouldRetry?: (error, attempt) => boolean` - `onRetry?: (error, attempt) => void` - `resolveRetryConfig(options?: Partial): RetryConfig`. - `withRetry(fn: () => T | Promise, options?: RetryOptions): Promise`. - `createRetryWrapper(options?: RetryOptions): (fn) => Promise`. - `RetryPresets` — `{ none, quick, standard, aggressive }`, each a function returning a fresh `RetryConfig`. ## 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.