first commit
This commit is contained in:
84
docs/foundation/flow/workflow/src/middleware/retry/retry.md
Normal file
84
docs/foundation/flow/workflow/src/middleware/retry/retry.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# retry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<RetryConfig>` —
|
||||
- `shouldRetry?: (error, attempt) => boolean`
|
||||
- `onRetry?: (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 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.
|
||||
Reference in New Issue
Block a user