retry-timeout-pagination
Small, dependency-free TypeScript utilities for reliable asynchronous work — retry with exponential backoff, timeout with AbortSignal, and offset/cursor pagination.
This package is intentionally small. It gives you primitives — not a particular HTTP client, framework, or API shape — so you can drop it into whatever you're already building.
Three primitives. One job each.
Compose them together for production API clients, or use each one on its own wherever async work needs to be more resilient.
retry()
Retries a failing operation with exponential backoff and optional jitter. Decide
exactly which errors are worth retrying with retryOn, and cancel the
whole sequence with an AbortSignal.
timeout()
Races an operation against a timer and propagates an AbortSignal into
it, so the underlying fetch or task is actually cancelled — not just abandoned.
paginateOffset() / paginateCursor()
Walks paginated APIs of either shape — offset/limit or cursor-based — and stops automatically on empty pages, exhausted totals, or a repeated cursor.
Import what you need.
Ships as ESM and CommonJS with generated TypeScript declarations, so it works the same way whichever module system your project uses.
Retry only what's worth retrying.
A good production rule: retry network errors, HTTP 429 and 5xx — not most 4xx validation
or auth failures. retryOn gives you that control per-call.
retryOn predicate per errorsignalRetryExhaustedError with attempts +
last errorActually cancel the work, not just the wait.
timeout() races your operation against a timer and passes an
AbortSignal into it — so a slow fetch gets aborted, not left
running in the background.
AbortSignal into your
operationTimeoutError with the
durationWorks with either API shape.
paginateOffset() handles offset/limit APIs and
expects { items, total? } back. paginateCursor() handles
cursor-based APIs and expects { items, nextCursor? }.
maxPages guard for both strategies
PaginationError instead of looping foreverBuilt to stack.
A typical production client wraps each page fetch in a timeout, and each timeout attempt in a retry.
One AbortSignal flows through all three layers, so
cancelling the outer pagination call cancels everything underneath it.
POST, check idempotency keys before
enabling retriesretry() options
Full option list for retry() — see the README for
timeout(), paginateOffset() and paginateCursor().
| Option | Default | Description |
|---|---|---|
maxAttempts |
3 | Total attempts, including the first |
delayMs |
250 | Base exponential backoff |
maxDelayMs |
30000 | Maximum calculated delay |
jitter |
true | Randomizes delay from 0 to the calculated maximum |
retryOn |
all errors | Decide whether a given error is retryable |
onRetry |
— | Observe retry attempts as they happen |
signal |
— | Cancel the retry sequence |
delay |
— | Fully customize the retry delay function |
Want the full docs?
The README covers error types, cancellation, and every option in detail — or open an issue on GitLab if something's missing.