Files
skills/docs/foundation/flow/workflow/src/middleware/retry/retry.md
2026-05-29 08:20:56 +01:00

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 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

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) => 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.