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

exchequer

v0.1.0

Published

Minimal async token-bucket rate limiter — refill R/sec, burst B, await for capacity. Zero deps, isomorphic, injectable clock.

Readme

exchequer

npm MIT License

A minimal async token-bucket rate limiter for JavaScript and TypeScript.

Features

  • Simple API — Just take() and tryTake() methods
  • FIFO ordering — Waiters are served in first-in-first-out order
  • Fractional tokens — Precise rate limiting with fractional token precision
  • Zero dependencies — No runtime dependencies, minimal bundle size
  • Runtime agnostic — Works in Node.js, browsers, and edge runtimes
  • Clock injection — Deterministic testing with injectable time source
  • Type-safe — Full TypeScript support with strict mode

Installation

npm install exchequer
# or
pnpm add exchequer
# or
yarn add exchequer

Quick Start

import exchequer from "exchequer";

// Create a bucket: 10 tokens per second, burst capacity of 20
const bucket = exchequer({ rate: 10, burst: 20 });

// Try to take a token without blocking
if (bucket.tryTake(1)) {
  console.log("Token acquired!");
}

// Or wait asynchronously until a token is available
await bucket.take(1);
console.log("Token acquired!");

How It Works

Token buckets continuously refill at rate tokens per second, up to a maximum of burst tokens. When tokens are insufficient, take() waits asynchronously in FIFO order.

  • Burst capacity — Maximum tokens that can be accumulated
  • Rate — Tokens added per second (can be fractional)
  • FIFO queue — Waiters are served in order of arrival
  • Fractional precision — Token calculations maintain fractional precision

API

exchequer(options)

Creates a new token bucket rate limiter.

Options

interface ExchequerOptions {
  rate: number;           // Tokens per second (must be > 0)
  burst?: number;         // Maximum bucket capacity (defaults to rate)
  now?: () => number;     // Current time in ms (defaults to Date.now)
  setTimer?: (callback: () => void, ms: number) => { clear: () => void };
}
  • rate — Number of tokens added per second. Must be greater than 0.
  • burst — Maximum bucket capacity. Defaults to rate if not specified.
  • now — Function that returns current time in milliseconds since epoch. Used for deterministic testing.
  • setTimer — Function to schedule a callback after a delay. Used for testing and scheduler injection.

Returns

interface Exchequer {
  take(count?: number): Promise<void>;
  tryTake(count?: number): boolean;
  readonly tokens: number;
  reset(): void;
}

bucket.take(count)

Asynchronously wait until count tokens are available.

  • count — Number of tokens to wait for (defaults to 1)
  • Returns — Promise that resolves when tokens are available
  • ThrowsRangeError if count ≤ 0 or exceeds burst capacity
// Wait for 1 token (default)
await bucket.take();

// Wait for 5 tokens
await bucket.take(5);

bucket.tryTake(count)

Try to take tokens without blocking.

  • count — Number of tokens to take (defaults to 1)
  • Returnstrue if tokens were taken, false if insufficient tokens
// Try to take 1 token
if (bucket.tryTake()) {
  console.log("Token acquired!");
} else {
  console.log("Not enough tokens.");
}

// Try to take 5 tokens
if (bucket.tryTake(5)) {
  console.log("5 tokens acquired!");
} else {
  console.log("Not enough tokens for 5.");
}

bucket.tokens

Current number of available tokens (can be fractional). Lazily calculated based on elapsed time since last operation.

console.log(`Current tokens: ${bucket.tokens}`);

bucket.reset()

Reset the bucket to full burst capacity. Any queued waiters will be served immediately if enough tokens are available.

bucket.reset();
console.log(`Tokens after reset: ${bucket.tokens}`); // Will show burst capacity

Usage Examples

API Rate Limiting

import exchequer from "exchequer";

// Limit API calls to 10 per second with burst of 20
const apiLimiter = exchequer({ rate: 10, burst: 20 });

async function callAPI(url) {
  // Wait for token before making API call
  await apiLimiter.take();
  return fetch(url);
}

Processing Batches

import exchequer from "exchequer";

// Process items at a steady rate of 100 per second
const processor = exchequer({ rate: 100, burst: 200 });

async function processBatch(items) {
  for (const item of items) {
    await processor.take();
    await processItem(item);
  }
}

Non-blocking Token Check

import exchequer from "exchequer";

const bucket = exchequer({ rate: 5, burst: 10 });

function handleRequest() {
  if (bucket.tryTake()) {
    // Process request immediately
    processRequest();
  } else {
    // Rate limited - reject request
    sendError("Too many requests");
  }
}

Deterministic Testing

import exchequer from "exchequer";

// Inject clock for deterministic testing
let time = 0;
const bucket = exchequer({
  rate: 10,
  burst: 5,
  now: () => time,
  setTimer: (cb, ms) => {
    const timeout = setTimeout(() => {
      time += ms;
      cb();
    }, 10); // Short delay for testing
    return { clear: () => clearTimeout(timeout) };
  }
});

// Now you can control time in tests
time = 0;
console.log(bucket.tokens); // 5 (burst)

time = 500; // Advance 500ms
console.log(bucket.tokens); // 10 (5 burst + 5 from rate)

What exchequer does NOT do

This package has clear boundaries and intentionally does NOT provide:

  • Distributed coordination — Single-process, in-memory only
  • Priority queues — Strict FIFO ordering only, no priorities
  • Resource pooling — Not a reservoir scheduler for resource management
  • Cluster-aware limiting — No distributed rate limiting across processes
  • HTTP 429 handling — Use the forbear package for response-based rate limiting

Fractional Token Precision

The bucket maintains fractional token precision for accurate rate limiting:

const bucket = exchequer({ rate: 10, burst: 10 });

// Drain all tokens
bucket.tryTake(10);
console.log(bucket.tokens); // 0

// Advance 333ms (should get 3.33 tokens)
// In real usage, time advances naturally
// For testing, inject time control

Error Handling

The package validates inputs and throws RangeError for invalid parameters:

const bucket = exchequer({ rate: 10, burst: 5 });

// These will throw RangeError:
await bucket.take(0);        // count must be > 0
await bucket.take(-1);       // count must be > 0
await bucket.take(6);        // count (6) cannot exceed burst (5)

Type Safety

Full TypeScript support with comprehensive type definitions:

import exchequer from "exchequer";

const bucket: Exchequer = exchequer({ rate: 10, burst: 20 });

// Full type safety and autocomplete
await bucket.take(1);
const success = bucket.tryTake(1);
const currentTokens = bucket.tokens;
bucket.reset();

License

MIT

See Also