import { Duration, type DurationLike } from "luxon"; interface CalculateExponentialBackoffOptions { attempt: number; baseMin?: DurationLike; absCap?: DurationLike; fuzzFactor?: number; } /** * Generates a backoff time where both the Minimum floor and Maximum ceiling * are "fuzzed" with jitter to prevent clustering at the edges. * * @param attempt - The current retry attempt (0-indexed). * @param baseMin - The nominal minimum wait time (default: 1s). * @param absCap - The absolute maximum wait time (default: 60s). * @param fuzzFactor - How much to wobble the edges (0.1 = +/- 10%). * * @returns A random duration between the nominal minimum and maximum, in seconds. */ export function calculateExponentialBackoff({ attempt, baseMin: baseMinDuration = Duration.fromObject({ minutes: 1 }), absCap: absCapDuration = Duration.fromObject({ hours: 1 }), fuzzFactor = 0.2, }: CalculateExponentialBackoffOptions): number { const baseMin = Duration.fromDurationLike(baseMinDuration).as("seconds"); const absCap = Duration.fromDurationLike(absCapDuration).as("seconds"); // 1. Calculate nominal boundaries // Example: If baseMin is 1s, the nominal boundaries are 1s, 2s, 4s, 8s... (The 'ceiling' grows exponentially) const nominalMin = baseMin; const nominalCeiling = Math.min(baseMin * Math.pow(2, attempt), absCap); // 2. Fuzz the Min (The Floor) // Example: If min is 1s and fuzz is 0.2, the floor becomes random between 0.8s and 1.2s const minFuzz = nominalMin * fuzzFactor; const fuzzedMin = nominalMin + (Math.random() * 2 * minFuzz - minFuzz); // 3. Fuzz the Max (The Ceiling) // Example: If ceiling is 4s (and fuzz is 0.2), it becomes random between 3.2s and 4.8s const maxFuzz = nominalCeiling * fuzzFactor; const fuzzedCeiling = nominalCeiling + (Math.random() * 2 * maxFuzz - maxFuzz); // Safety: Ensure we don't return a negative number or cross boundaries weirdly // (e.g. if fuzz makes min > max, we swap or clamp) const safeMin = Math.max(0, fuzzedMin); const safeMax = Math.max(safeMin, fuzzedCeiling); // 4. Return random value in the new fuzzy range return safeMin + Math.random() * (safeMax - safeMin); }