/** * Transient HTTP retry policy. * * Centralizes the "retry on transient failure" behavior so it can be applied at * a single choke point per call site rather than scattered across every request * method. It is deliberately PURE: it knows nothing about the Telegram bot token * or any other caller secret, so it never risks leaking one. Token redaction and * response-body interpretation stay the caller's responsibility. * * WHY this exists: operators may point TELEGRAM_API_BASE_URL at an nginx reverse * proxy or Azure gateway. Those proxies answer with 504 Gateway Timeout (or * 502/503) instead of a normal Telegram JSON payload. Without retries a single * proxy hiccup drops an operator notification or spins the long-poll loop; a * bounded, jittered retry smooths over the transient blips. */ /** * HTTP status codes that indicate a TRANSIENT failure — the request may well * succeed if retried after a short pause: * 408 Request Timeout, 429 Too Many Requests, * 500 Internal Server Error, 502 Bad Gateway, * 503 Service Unavailable, 504 Gateway Timeout. * * Every other 4xx is PERMANENT (bad request, unauthorized, not found, …) and is * never retried — retrying it only wastes time and risks duplicate side effects. */ export declare const TRANSIENT_STATUS: ReadonlySet; /** True when `status` is in the transient (retryable) set. */ export declare function isTransientStatus(status: number): boolean; /** * Parse the `Retry-After` response header into milliseconds. * * Per RFC 9110 the value is either a number of seconds (`Retry-After: 120`) or * an HTTP-date (`Retry-After: Wed, 21 Oct 2026 07:28:00 GMT`). Returns * `undefined` when the header is absent or unparseable, and clamps any positive * result to {@link RETRY_AFTER_MAX_MS}. A date in the past yields `0`. */ export declare function parseRetryAfterMs(response: Response): number | undefined; /** Tunables for {@link computeBackoffMs} / {@link fetchWithRetry}. */ export interface RetryPolicy { /** Total number of attempts (1 = no retry). */ maxAttempts: number; /** Base delay for the first backoff step, in ms. */ baseMs: number; /** Hard ceiling for any single backoff wait, in ms. */ capMs: number; } /** Sensible default: 3 attempts, 500ms base, 8s cap. */ export declare const DEFAULT_RETRY_POLICY: RetryPolicy; /** * Exponential backoff with FULL jitter. * * The deterministic component is `baseMs * 2^attempt`, capped at `capMs`. We * then draw a uniformly random value in `[0, capped]` (the "full jitter" * strategy from the AWS Architecture Blog). Full jitter — rather than * equal-jitter or none — is chosen because it maximizes spread across many * concurrent clients hammering the same recovering proxy, which minimizes * retry-storm synchronization. * * @param attempt Zero-based retry index (0 for the first backoff). */ export declare function computeBackoffMs(attempt: number, policy?: Pick): number; /** * Sleep for `ms`, rejecting immediately if `signal` aborts mid-wait. * * Retries must not swallow an intentional shutdown: if the caller's signal * fires while we are backing off, propagate the abort at once rather than * finishing the sleep. */ export declare function abortableDelay(ms: number, signal?: AbortSignal): Promise; /** * `fetch` with bounded, jittered retry on transient failures. * * Behavior: * - Performs the fetch. If the response status is transient AND attempts * remain, waits (honoring `Retry-After` when present, otherwise * {@link computeBackoffMs}) and retries. Otherwise returns the response — the * caller still inspects `.ok` to handle permanent (4xx) errors itself. * - Only a thrown `TypeError` — how native `fetch` reports a genuine transport * failure (DNS, ECONNREFUSED, socket reset) — is retried (if attempts * remain, else the last error is rethrown). Every other throw propagates * IMMEDIATELY with no wasted backoff: an AbortError / TimeoutError from * `init.signal` (or `AbortSignal.timeout`) is intentional and must not be * retried, and a programmer error (e.g. a bad argument) is not transient. * - The backoff sleep is abort-aware: aborting `init.signal` mid-wait rejects * at once. * * Kept PURE — no knowledge of any caller secret. Token redaction is layered on * top by the caller's own try/catch. */ export declare function fetchWithRetry(input: string | URL | Request, init?: RequestInit, policy?: RetryPolicy): Promise; //# sourceMappingURL=http-retry.d.ts.map