footprintjs - v11.2.0
    Preparing search index...

    Interface RetryPolicy

    Declarative per-stage retry policy — the policy the attempt runner runs (AttemptPolicy in the design is this one type, under this one name).

    WHY this exists: a retry hand-rolled inside a stage function is invisible to the trace. The stage shows one execution, the commit log shows one bundle, and the two failed calls that preceded the successful one left no mark anywhere — an unexplained behaviour in a library whose whole thesis is that nothing invisible happens. Declared here instead, every attempt is part of the record: each attempt fires FlowRecorder.onAttempt with its AttemptRecord, each failed attempt that is retried also fires FlowRecorder.onStageRetry, and the stage's execution-tree node carries attempts and attemptSummary.

    A POLICY, not a stage kind — retry sits on a normal stage the way isPausable / isStreaming do. The serialized node type is unchanged and every existing consumer keeps rendering the stage exactly as before.

    flowChart<State>('Fetch user', fetchFn, 'fetch-user')
    .retry({ attempts: 3, backoffMs: (n) => 100 * 2 ** (n - 1) })
    interface RetryPolicy {
        attempts: number;
        backoffMs?: number | ((attempt: number, error: unknown) => number);
        maxBackoffMs?: number;
        retryOn?: (error: unknown) => boolean;
        timeoutMs?: number;
    }
    Index
    attempts: number

    How many times the stage function may run in total, INCLUDING the first run. Must be an integer >= 1; 1 means "no retry" (a declared no-op, accepted so a policy can be switched off without deleting it — such a stage runs once and records no attempts, unless it declares timeoutMs or the run has a deadline: then it records its one attempt).

    backoffMs?: number | ((attempt: number, error: unknown) => number)

    How long to wait before the NEXT attempt. Either a fixed number of milliseconds, or a function of the attempt that just FAILED (1-based) and its error, returning milliseconds — (n) => 100 * 2 ** (n - 1) is exponential backoff; (n, error) => statedWaitMs(error) ?? 100 * n honours a wait the outside system stated (a Retry-After) without the engine parsing anything. A one-argument function is unchanged. Omitted (or a non-finite / negative result, or a throw) means no wait.

    The wait is a plain awaited timer that is CANCELLED by the run's AbortSignal. It is not a pause: a chart cannot be checkpointed in the middle of a backoff. Under a run deadline, a wait that would outlast it is never started: the series stops no-time-left.

    maxBackoffMs?: number

    The most one wait may last, in milliseconds — a finite number >= 0. The runner ENFORCES it: every wait is min(backoffMs's answer, maxBackoffMs), whatever the error stated, so the declaration cannot lie.

    It is what makes a FUNCTION backoff countable before the run. A function may read the error (a stated Retry-After), and no error exists at build, so the static budget check never asks it: with maxBackoffMs it counts each wait at the ceiling; without, the stage is unbounded ('backoff').

    .retry({ attempts: 4, timeoutMs: 10_000, backoffMs: (n, error) => statedWaitMs(error) ?? 500 * n, maxBackoffMs: 5_000 })
    
    retryOn?: (error: unknown) => boolean

    Is THIS error worth another attempt? Defaults to retrying every error. Returning false ends the stage immediately on the normal error path, with no retry event; the attempt's record says why it stopped (reason: 'declined').

    A throw from this predicate is treated as false — a broken predicate must not turn a failing stage into an infinite retry loop. The record tells the two apart: a throw stops the series declined with retryOnThrew: true.

    timeoutMs?: number

    The cap on ONE attempt, in milliseconds — a finite number > 0. The attempt runs under min(timeoutMs, time left before the run's deadline); when the cap runs out, the attempt's signal (scope.$attempt().signal) aborts with an AttemptTimeoutError, the engine stops waiting for it, and the attempt is recorded timeout (then retried like any failure, if the policy allows).

    What the engine stops is the WAITING. The function's own work stops only if it passes the signal on (fetch(url, { signal: scope.$attempt().signal })); whatever it writes after its attempt ended is dropped and recorded on the stage (StageSnapshot.lateWrites).

    Declared with attempts: 1, it is a timeout with no retry — the stage still records its one attempt.