npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

smart-retry-js

v1.0.0

Published

Production-ready retry utility with exponential back-off, jitter, per-attempt timeouts, and a circuit breaker. Zero runtime dependencies.

Readme

smart-retry-js

Production-ready retry utility for TypeScript & Node.js — exponential back-off, jitter, per-attempt timeouts, and a circuit breaker. Zero runtime dependencies.

npm version license types


Features

| Feature | Description | |---|---| | Exponential Back-off | Delay doubles on each retry attempt | | Jitter | full, equal, or none — prevents thundering-herd problems | | Circuit Breaker | Stops calling a broken service, auto-recovers after a cooldown | | Per-attempt Timeout | Rejects with TimeoutError if a single attempt is too slow | | onRetry hook | Called after each failed attempt with the error and attempt index | | shouldRetry predicate | Veto retries based on the error type (e.g. never retry 4xx) | | Dual ESM + CJS | Works in both import and require environments | | Full type exports | All types exported; works with strict TypeScript |


Installation

npm install smart-retry-js

Quick Start

import { retry } from 'smart-retry-js';

const data = await retry(
  () => fetch('https://api.example.com/data').then(r => r.json()),
  { retries: 5, delay: 200 },
);

API

retry(asyncFn, options?)

| Parameter | Type | Description | |---|---|---| | asyncFn | () => Promise<T> | The async operation to execute | | options | RetryOptions | Configuration (all fields optional) |

Returns Promise<T> — resolves with the function's return value, or rejects with the last error after all retries are exhausted.


Options Reference

interface RetryOptions {
  retries?: number;          // max attempts            (default: 3)
  delay?: number;            // base delay in ms        (default: 100)
  backoff?: number;          // delay multiplier        (default: 2)
  jitter?: 'full' | 'equal' | 'none';   // (default: 'none')
  timeout?: number;          // per-attempt timeout ms  (no timeout by default)
  circuitBreaker?: {
    threshold: number;       // failures before opening circuit
    cooldown: number;        // ms before half-open retry
  };
  onRetry?: (error: unknown, attempt: number) => void;
  shouldRetry?: (error: unknown) => boolean;
}

Examples

1. Basic Retry

import { retry } from 'smart-retry-js';

const result = await retry(
  () => unstableApiCall(),
  { retries: 3 },
);

2. Exponential Back-off

Each retry waits delay × backoff^attempt milliseconds.

const result = await retry(
  () => fetchData(),
  {
    retries: 5,
    delay: 100,    // 100ms → 200ms → 400ms → 800ms → 1600ms
    backoff: 2,
    jitter: 'none',
  },
);

3. Jitter

Full Jitter

Delay is a random value in [0, exponentialDelay] — maximum variance, best for high-throughput scenarios.

await retry(() => callApi(), {
  retries: 5,
  delay: 100,
  backoff: 2,
  jitter: 'full',
});

Equal Jitter

Delay is exponentialDelay/2 + random(0, exponentialDelay/2) — balanced variance, guaranteed minimum wait.

await retry(() => callApi(), {
  retries: 5,
  delay: 100,
  backoff: 2,
  jitter: 'equal',
});

4. Per-attempt Timeout

Each attempt is individually time-boxed. Slow attempts throw TimeoutError.

import { retry, TimeoutError } from 'smart-retry-js';

try {
  await retry(() => slowDatabaseQuery(), {
    retries: 3,
    timeout: 2_000,   // each attempt must finish in 2s
  });
} catch (err) {
  if (err instanceof TimeoutError) {
    console.error(`Attempt ${err.attempt} timed out`);
  }
}

5. Circuit Breaker

The circuit breaker tracks failures across calls. Once the threshold is reached the circuit opens and further calls are rejected immediately with CircuitOpenError — without even calling your function. After the cooldown the circuit enters HALF_OPEN and allows one trial call through.

import { retry, CircuitOpenError } from 'smart-retry-js';

try {
  await retry(() => callDownstreamService(), {
    retries: 10,
    delay: 50,
    circuitBreaker: {
      threshold: 5,      // open after 5 consecutive failures
      cooldown: 10_000,  // try again after 10 seconds
    },
  });
} catch (err) {
  if (err instanceof CircuitOpenError) {
    console.warn('Circuit is open — service is unavailable');
  }
}

6. onRetry Hook

Useful for logging, metrics, or alerting on each failed attempt.

await retry(() => fetchData(), {
  retries: 5,
  onRetry(error, attempt) {
    console.warn(`Attempt ${attempt} failed:`, error);
    metrics.increment('api.retry', { attempt });
  },
});

7. shouldRetry Predicate

Control which errors are worth retrying. Return false to abort immediately.

class NotFoundError extends Error {}
class RateLimitError extends Error {}

await retry(() => callApi(), {
  retries: 5,
  shouldRetry(error) {
    // Never retry client errors
    if (error instanceof NotFoundError) return false;
    // Always retry transient errors
    if (error instanceof RateLimitError) return true;
    return true;
  },
});

8. Using the Circuit Breaker Standalone

You can use CircuitBreaker independently of retry():

import { CircuitBreaker } from 'smart-retry-js';

const cb = new CircuitBreaker({ threshold: 3, cooldown: 5_000 });

// Execute through the circuit breaker
const result = await cb.execute(() => callExternalApi());

// Inspect current state
console.log(cb.getSnapshot());
// { state: 'CLOSED', failures: 0, lastFailureTime: null }

Error Types

import { TimeoutError, CircuitOpenError } from 'smart-retry-js';

| Class | When thrown | |---|---| | TimeoutError | Per-attempt timeout expired. Has .attempt (1-based number) | | CircuitOpenError | Circuit breaker is in OPEN state — call blocked entirely |


State Machine

            failure × threshold
  CLOSED ─────────────────────► OPEN
    ▲                             │
    │ success                     │ cooldown elapsed
    │                             ▼
    └───────────────────── HALF_OPEN
         success / failure

TypeScript

All types are exported. No any usage, strict mode throughout.

import type {
  RetryOptions,
  CircuitBreakerOptions,
  CircuitBreakerSnapshot,
  CircuitState,
  JitterStrategy,
} from 'smart-retry-js';

Development

# Install dependencies
npm install

# Build (ESM + CJS + .d.ts)
npm run build

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Coverage report
npm run test:coverage

# Type-check without emitting
npm run typecheck

License

MIT © Gaurav Kathiriya