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

@codingaryan/smoothapi

v1.3.0

Published

API protection library — exponential backoff and circuit breaker for fetch

Readme

@codingaryan/smoothapi

API protection library for TypeScript/JavaScript. It wraps the native fetch API with state of the art protections like exponential backoff, full jitter, and a finite-state machine circuit breaker to protect against cascading failures.

Zero dependencies. Small bundle size. Built for modern ESM.

Install

npm install @codingaryan/smoothapi

Features

  • Exponential Backoff with Full Jitter: Prevents the "thundering herd" problem by randomizing retry delays.
  • Circuit Breaker (FSM): Isolated per-domain state machine (CLOSEDOPENHALF_OPEN).
  • Smart Retries: Automatically retries on specific HTTP status codes (e.g., 429, 500, 502, 503, 504) while throwing immediately on client errors (400, 401, 404).
  • Graceful Fallbacks: Optionally serve cached or default data instantly when the circuit is OPEN.
  • Request Deduplication: Automatically couples concurrent identical requests into a single network call.
  • Request Timeouts: Configurable timeouts to automatically abort requests that hang indefinitely.

Usage

Basic Usage (Defaults)

If you don't need custom configurations, you can use the smooth fetch with its defaults by simply passing an empty object.

import { createSmoothFetch } from '@codingaryan/smoothapi';

// Create it with default settings
const fetchWithRetry = createSmoothFetch({});

async function main() {
  try {
    // Drop-in replacement for native fetch
    const response = await fetchWithRetry('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error("Request failed completely:", err);
  }
}

Default Settings provided automatically:

  • Retries: 3 attempts
  • Backoff Base Delay: 100 milliseconds
  • Circuit Failure Threshold: Trips after 3 consecutive failures
  • Circuit Cooldown: Stays open for 10 seconds before probing
  • Status Codes to Retry: 429, 500, 502, 503, and 504

Advanced Usage (Custom Settings)

You can override any of the defaults to suit your application's needs, such as adding a fallback object.

import { createSmoothFetch } from '@codingaryan/smoothapi';

const fetchWithRetry = createSmoothFetch({
  backoff: {
    baseDelay: 100,      // ms to wait before first retry
    maxDelay: 30000,     // cap on exponential growth
    maxRetries: 3        // max number of retry attempts
  },
  circuitBreaker: {
    failureThreshold: 3, // trip OPEN after 3 consecutive failures
    cooldownMs: 10000    // stay OPEN for 10 seconds before probing
  },
  // Optional: Return this instead of throwing when the circuit is OPEN
  fallback: { error: "Service degraded, returning stale data." },
  // Optional: Custom status codes to retry on
  retryOn: [429, 500, 502, 503, 504],
  // Optional: Abort a request attempt if it takes longer than 5000ms
  timeoutMs: 5000
});

async function main() {
  try {
    const response = await fetchWithRetry('https://api.example.com/data');
    
    // If fallback triggered, it returns your fallback object directly
    if ('error' in response) {
        console.log("Fallback triggered:", response.error);
        return;
    }
    
    // Otherwise it's a standard Response object
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error("Request failed completely:", err);
  }
}

Client Error Handling & Alerts

By default, client errors (e.g. 400, 401, 403, 404, 405) resolve immediately and bypass the retry loop. If you want to handle these errors gracefully and alert users:

import { createSmoothFetch } from '@codingaryan/smoothapi';

const fetchWithRetry = createSmoothFetch({
  fallbackOnNonRetryable: true,
  // Optional: Trigger custom UI logic when a client error happens
  onNonRetryableError: (status, message) => {
    console.log(`Custom callback: Received status ${status}`);
  },
  // Optional: Fallback returned on non-retryable errors
  fallback: { error: "Page not found." }
});
  • Default Alerting: If fallbackOnNonRetryable is true and no custom onNonRetryableError is provided, it logs the warning to console.error.
  • Graceful Return: If no custom fallback is configured, it returns a mock Response wrapper with the status code and a JSON error body: { error: true, status: 404, message: "..." }. Callers can safely call .json(), .status, or .ok on it without crashing.

Request Deduplication

When multiple identical requests are made concurrently, SmoothAPI will execute only one network call and share the result with all callers. This reduces unnecessary load on downstream services and prevents exausting computing resources.

Enable with default key function (deduplicates by URL):

import { createSmoothFetch } from '@codingaryan/smoothapi';

const fetchWithRetry = createSmoothFetch({
  deduplication: {} // Empty object activates deduplication
});

// All three calls share a single network request
const [a, b, c] = await Promise.all([
  fetchWithRetry('http://api.example.com/users/1'),
  fetchWithRetry('http://api.example.com/users/1'),
  fetchWithRetry('http://api.example.com/users/1'),
]);

Custom key function for advanced coalescing:

const fetchWithRetry = createSmoothFetch({
  deduplication: {
    // Deduplicate by method + URL (ignores headers/body)
    keyFn: (url, options) => `${options?.method ?? 'GET'}:${url.toString()}`
  }
});

Opt out of deduplication for specific requests:

const fetchWithRetry = createSmoothFetch({
  deduplication: {
    keyFn: (url, options) => {
      // Skip dedup for POST requests
      if (options?.method === 'POST') return null;
      return url.toString();
    }
  }
});
  • Default Behavior: Deduplicates by URL only (method-agnostic). Concurrent GETs to the same URL are merged.
  • Error Propagation: If the network call fails, all waiting callers receive the same error.
  • Settlement: Once a request completes, the next call to the same URL triggers a fresh network request.

How It Works

  1. Host Extraction: The domain is automatically extracted from the URL. The circuit breaker state is isolated per host (e.g., api.github.com failing won't trip the circuit for api.stripe.com).
  2. Circuit Check: Before making a network request, the breaker checks the state. If it's OPEN, the request is blocked instantly (returning your fallback, or throwing a CircuitOpenError).
  3. Execution & Retries: If the response status is in your retryOn list, it's counted as a failure and retried with backoff.
  4. Recovery: After cooldownMs, the breaker enters HALF_OPEN state. The next request acts as a probe. If it succeeds, the circuit closes. If it fails, it snaps back to OPEN immediately.
  5. Memory Management: The circuit breaker cache is capped at a strict 1,000 domains limit. When exceeded, it automatically sweeps and removes CLOSED circuits with zero failures to prevent memory leaks in highly dynamic environments.

License

MIT