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

@simpill/resilience.utils

v1.0.0

Published

Circuit breaker, rate limiter, bulkhead, and jittered backoff for fault-tolerant APIs (Node and Edge).

Readme

Features: Type-safe · Node & Edge · Lightweight


Installation

From npm

npm install @simpill/resilience.utils

From GitHub

To use this package from the monorepo source:

git clone https://github.com/SkinnnyJay/simpill.git
cd simpill/utils/@simpill-resilience.utils
npm install && npm run build

In your project you can then install from the local path: npm install /path/to/simpill/utils/@simpill-resilience.utils or use npm link from the package directory.


Usage

import {
  CircuitBreaker,
  RateLimiter,
  createBulkhead,
  withJitter,
} from "@simpill/resilience.utils";

const cb = new CircuitBreaker({ failureThreshold: 5, openMs: 60000 });
const result = await cb.run(() => fetchSomething());

const limiter = new RateLimiter({ maxRequests: 10, windowMs: 1000 });
await limiter.run(() => callApi());

const bulkhead = createBulkhead(5);
await bulkhead.run(() => doWork());

const delayMs = withJitter(1000, { factor: 0.2, maxMs: 2000 });

API

  • CircuitBreaker(options) — run(fn), getState(): 'closed' | 'open' | 'half-open'; options: failureThreshold, successThreshold, openMs, halfOpenMaxCalls, onStateChange(state, previousState)
  • RateLimiter(options) — run(fn), fixed-window rate limit
  • createBulkhead(limit) — returns { run(fn) }, limits concurrent executions
  • withJitter(ms, options?) — returns jittered ms; options: factor, maxMs
  • retryResult(fn, options?) — retries fn via @simpill/async.utils retry; returns Result<T, AppError> (Ok/Err) with optional mapError

Circuit breaker: metrics and events

Use optional onStateChange(state, previousState) in the constructor to observe state transitions (e.g. for metrics or logging). getState() returns the current state ('closed' | 'open' | 'half-open'). For production metrics, call onStateChange from your monitoring integration.

Bulkhead: queueing

createBulkhead(limit) uses a Semaphore from @simpill/async.utils. There is no separate queue: callers that call run(fn) wait until a slot is free, then fn runs. Concurrency is limited to limit; extra calls block (await) until a slot opens. No configurable queue size or reject-overflow behavior.

Token bucket / leaky bucket

Only a fixed-window rate limiter is provided (RateLimiter). For token bucket or leaky bucket algorithms use another library or implement on top of withJitter and your own state.

Redis rate limiting

RateLimiter is in-memory only (single process). For distributed or Redis-backed rate limiting use a dedicated solution (e.g. Redis sliding window or token bucket).

Timeout wrapper

This package does not provide a timeout wrapper. Use @simpill/async.utils raceWithTimeout or @simpill/http.utils fetchWithTimeout to add timeouts. You can combine: e.g. circuitBreaker.run(() => fetchWithTimeout(...)).

Per-call overrides

CircuitBreaker, RateLimiter, and Bulkhead have no per-call overrides (e.g. different limits per request). Options are set at construction. Use separate instances (e.g. one breaker per backend, or one limiter per tenant) if you need different settings.

Error counting (circuit breaker)

CircuitBreaker treats any thrown error from run(fn) as a failure: it calls recordFailure() in the catch path. Success is when fn resolves without throwing. There is no option to classify errors (e.g. retryable vs non-retryable); wrap fn to throw only for failures you want to count if needed.

retryResult

retryResult(fn, options) runs fn with @simpill/async.utils retry and returns Result<T, AppError> from @simpill/patterns.utils: ok(value) on success, err(appError) on final failure. options extend RetryOptions (maxAttempts, delayMs, backoffMultiplier, onRetry, etc.) and add mapError?: (error: unknown) => AppError to turn the last thrown error into an AppError (default: wrap non-AppError in AppError with ERROR_CODES.INTERNAL).

Composing with http.utils

Use @simpill/http.utils for timeout and retry on fetch; use this package for circuit breaking or rate limiting around those calls. Example: wrap the HTTP call in the breaker so repeated failures open the circuit:

import { CircuitBreaker } from "@simpill/resilience.utils";
import { createHttpClient } from "@simpill/http.utils";

const cb = new CircuitBreaker({ failureThreshold: 5, openMs: 60_000 });
const client = createHttpClient({ baseUrl: "https://api.example.com" });

const res = await cb.run(() => client.get("/users"));

Or pass a custom fetch to createHttpClient that runs through the breaker. RateLimiter and createBulkhead can wrap the same way to limit how many requests run per window or concurrently.

Tuning guidance

| Component | Options | Guidance | |-----------|---------|----------| | CircuitBreaker | failureThreshold, successThreshold, openMs, halfOpenMaxCalls | Start with failureThreshold 5–10, openMs 30–60s; halfOpenMaxCalls 1–3 to test recovery. | | RateLimiter | maxRequests, windowMs | Match downstream limits (e.g. API 100/min → maxRequests 100, windowMs 60_000). Fixed window can allow bursts at boundary. | | createBulkhead | limit | Set to max concurrent calls the backend can handle; too low increases latency, too high can overload. | | withJitter | factor, maxMs | factor 0.2–0.3 and maxMs cap avoid thundering herd; use in retry delayMs. |

What we don't provide

  • Circuit breaker metrics / events — No callbacks or metrics; poll getState() or wrap run() to observe.
  • Bulkhead queue — Callers await until a slot is free; no separate queue or reject-overflow option.
  • Token bucket / leaky bucket — Only fixed-window RateLimiter; use another library for other algorithms.
  • Redis / distributed rate limiting — In-memory only; use a dedicated solution for multi-process or Redis.
  • Timeout wrapper — Use @simpill/async.utils raceWithTimeout or @simpill/http.utils fetchWithTimeout.
  • Per-call overrides — Options are per instance; use separate instances for different limits per backend/tenant.

When to use

| Use case | Recommendation | |----------|----------------| | Stop calling a failing dependency | CircuitBreaker with run() around the call. | | Cap requests per time window (single process) | RateLimiter with maxRequests/windowMs. | | Limit concurrent executions | createBulkhead(limit) and run(fn). | | Retry with Result instead of throw | retryResult(fn, options) with mapError if needed. | | Backoff jitter | withJitter(ms, { factor, maxMs }) and pass result to retry delayMs. | | Distributed / Redis rate limit | Use a dedicated library; RateLimiter is in-memory only. | | Timeout around a call | Use @simpill/async.utils raceWithTimeout or @simpill/http.utils fetchWithTimeout. |

Subpaths: @simpill/resilience.utils, ./client, ./server, ./shared.

Examples

npx ts-node examples/01-basic-usage.ts

| Example | Description | |---------|-------------| | 01-basic-usage.ts | CircuitBreaker, RateLimiter, createBulkhead, withJitter |

Development

npm install
npm test
npm run build
npm run verify

Documentation

License

ISC