or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-limiter

A generic rate limiter for the web and node.js, useful for API clients, web crawling, or other tasks that need to be throttled

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/limiter@3.0.x

To install, run

npx @tessl/cli install tessl/npm-limiter@3.0.0

index.mddocs/

Limiter

Limiter provides a generic rate limiter for the web and Node.js. It's useful for API clients, web crawling, or other tasks that need to be throttled. The library implements two complementary classes: RateLimiter provides a high-level interface for enforcing rate limits with configurable tokens per interval, while TokenBucket offers a lower-level interface with configurable burst and drip rates.

Package Information

  • Package Name: limiter
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install limiter

Core Imports

import { RateLimiter, TokenBucket } from "limiter";

For CommonJS:

const { RateLimiter, TokenBucket } = require("limiter");

Basic Usage

import { RateLimiter } from "limiter";

// Allow 150 requests per hour (Twitter search limit example)
const limiter = new RateLimiter({ tokensPerInterval: 150, interval: "hour" });

async function sendRequest() {
  // Remove a token, waiting if necessary
  const remainingRequests = await limiter.removeTokens(1);
  console.log(`${remainingRequests} requests remaining`);
  // Make your API call here
}

// Synchronous token removal
if (limiter.tryRemoveTokens(1)) {
  console.log('Token removed successfully');
} else {
  console.log('No tokens available');
}

Architecture

Limiter is built around two core components:

  • RateLimiter: High-level rate limiter that combines a token bucket with interval-based restrictions to comply with common API limits like "150 requests per hour maximum"
  • TokenBucket: Lower-level hierarchical token bucket implementation providing configurable burst rate and drip rate with optional parent-child relationships
  • Clock Utilities: Internal high-resolution timing functions for precise rate limiting calculations

Capabilities

Rate Limiting

High-level rate limiting with configurable tokens per interval and optional immediate response mode.

class RateLimiter {
  /**
   * Create a new rate limiter
   * @param options Configuration options for the rate limiter
   */
  constructor(options: RateLimiterOpts);
  
  /**
   * Remove the requested number of tokens. Waits if necessary until tokens are available.
   * @param count The number of tokens to remove (must not exceed tokensPerInterval)
   * @returns Promise resolving to the number of tokens remaining after removal
   * @throws Error if count exceeds tokensPerInterval
   */
  removeTokens(count: number): Promise<number>;
  
  /**
   * Attempt to remove tokens immediately without waiting
   * @param count The number of tokens to remove
   * @returns true if tokens were successfully removed, false otherwise
   */
  tryRemoveTokens(count: number): boolean;
  
  /**
   * Get the current number of tokens remaining in the bucket
   * @returns The number of tokens currently available
   */
  getTokensRemaining(): number;
  
  /** The underlying token bucket used for rate limiting */
  tokenBucket: TokenBucket;
  /** Timestamp marking the start of the current interval */
  curIntervalStart: number;
  /** Number of tokens consumed in the current interval */
  tokensThisInterval: number;
  /** Whether to fire immediately when rate limiting is in effect */
  fireImmediately: boolean;
}

interface RateLimiterOpts {
  /** Maximum number of tokens that can be removed per interval */
  tokensPerInterval: number;
  /** The interval length in milliseconds or as a string (e.g., 'second', 'minute', 'hour', 'day') */
  interval: Interval;
  /** Whether to return immediately with -1 when rate limiting is active (default: false) */
  fireImmediately?: boolean;
}

Usage Examples:

import { RateLimiter } from "limiter";

// Basic rate limiting - 10 requests per second
const limiter = new RateLimiter({ tokensPerInterval: 10, interval: "second" });

// Async usage (waits for tokens to become available)
async function makeRequest() {
  const remaining = await limiter.removeTokens(1);
  console.log(`Request made, ${remaining} tokens remaining`);
}

// Immediate response mode for HTTP 429 handling
const apiLimiter = new RateLimiter({
  tokensPerInterval: 150,
  interval: "hour",
  fireImmediately: true
});

async function handleApiRequest(request, response) {
  const remaining = await apiLimiter.removeTokens(1);
  if (remaining < 0) {
    response.writeHead(429, {'Content-Type': 'text/plain'});
    response.end('429 Too Many Requests - rate limited');
  } else {
    // Process request normally
  }
}

// Check remaining tokens without removing any
console.log(`Current tokens: ${limiter.getTokensRemaining()}`);

Token Bucket

Lower-level hierarchical token bucket for precise rate control with burst capabilities and parent-child relationships.

class TokenBucket {
  /**
   * Create a new token bucket
   * @param options Configuration options for the token bucket
   */
  constructor(options: TokenBucketOpts);
  
  /**
   * Remove tokens from the bucket, waiting if necessary until enough tokens are available
   * @param count The number of tokens to remove (must not exceed bucketSize)
   * @returns Promise resolving to the number of tokens remaining after removal
   * @throws Error if count exceeds bucketSize
   */
  removeTokens(count: number): Promise<number>;
  
  /**
   * Attempt to remove tokens immediately without waiting
   * @param count The number of tokens to remove
   * @returns true if tokens were successfully removed, false otherwise
   */
  tryRemoveTokens(count: number): boolean;
  
  /**
   * Add any new tokens to the bucket based on time elapsed since last drip
   * @returns true if new tokens were added, false otherwise
   */
  drip(): boolean;
  
  /** Maximum number of tokens the bucket can hold (burst rate) */
  bucketSize: number;
  /** Number of tokens added to the bucket per interval */
  tokensPerInterval: number;
  /** The interval length in milliseconds */
  interval: number;
  /** Optional parent bucket for hierarchical rate limiting */
  parentBucket?: TokenBucket;
  /** Current number of tokens in the bucket */
  content: number;
  /** Timestamp of the last token drip operation */
  lastDrip: number;
}

interface TokenBucketOpts {
  /** Maximum number of tokens the bucket can hold (burst rate) */
  bucketSize: number;
  /** Number of tokens to add to the bucket per interval */
  tokensPerInterval: number;
  /** The interval length in milliseconds or as a string (e.g., 'second', 'minute', 'hour', 'day') */
  interval: Interval;
  /** Optional parent bucket for hierarchical rate limiting */
  parentBucket?: TokenBucket;
}

Usage Examples:

import { TokenBucket } from "limiter";

// Byte-level throttling at 50KB/sec sustained, 150KB/sec burst
const BURST_RATE = 1024 * 1024 * 150; // 150KB/sec burst
const FILL_RATE = 1024 * 1024 * 50;   // 50KB/sec sustained

const bucket = new TokenBucket({
  bucketSize: BURST_RATE,
  tokensPerInterval: FILL_RATE,
  interval: "second"
});

async function handleData(data) {
  await bucket.removeTokens(data.byteLength);
  sendData(data);
}

// Hierarchical token buckets
const parentBucket = new TokenBucket({
  bucketSize: 1000,
  tokensPerInterval: 100,
  interval: "second"
});

const childBucket = new TokenBucket({
  bucketSize: 100,
  tokensPerInterval: 50,
  interval: "second",
  parentBucket: parentBucket
});

// Removing tokens from child also removes from parent
await childBucket.removeTokens(10);

// Manual token drip
if (bucket.drip()) {
  console.log("New tokens were added to the bucket");
}

// Infinite bucket (bucketSize: 0)
const infiniteBucket = new TokenBucket({
  bucketSize: 0,
  tokensPerInterval: 10,
  interval: "second"
});

Types

type Interval = number | "second" | "sec" | "minute" | "min" | "hour" | "hr" | "day";

The Interval type supports both numeric values (milliseconds) and convenient string shortcuts:

  • "second" or "sec": 1000ms
  • "minute" or "min": 60,000ms
  • "hour" or "hr": 3,600,000ms
  • "day": 86,400,000ms

Error Handling

Both RateLimiter and TokenBucket throw errors in specific scenarios:

RateLimiter Errors

  • removeTokens(count): Throws Error if count exceeds tokensPerInterval
  • Constructor: Throws Error if interval is an invalid string

TokenBucket Errors

  • removeTokens(count): Throws Error if count exceeds bucketSize
  • Constructor: Throws Error if interval is an invalid string

Error Examples

try {
  // Invalid interval string
  const limiter = new RateLimiter({ 
    tokensPerInterval: 10, 
    interval: "invalid" as any 
  });
} catch (error) {
  console.error("Invalid interval:", error.message);
  // Output: "Invalid interval invalid"
}

try {
  // Requesting more tokens than allowed per interval
  const limiter = new RateLimiter({ tokensPerInterval: 10, interval: "second" });
  await limiter.removeTokens(15); // More than tokensPerInterval
} catch (error) {
  console.error("Too many tokens requested:", error.message);
  // Output: "Requested tokens 15 exceeds maximum tokens per interval 10"
}

try {
  // Requesting more tokens than bucket size
  const bucket = new TokenBucket({ 
    bucketSize: 5, 
    tokensPerInterval: 10, 
    interval: "second" 
  });
  await bucket.removeTokens(10); // More than bucketSize
} catch (error) {
  console.error("Bucket size exceeded:", error.message);
  // Output: "Requested tokens 10 exceeds bucket size 5"
}

Valid Interval Values

  • Numeric: Any positive number (milliseconds)
  • String shortcuts: "second", "sec", "minute", "min", "hour", "hr", "day"

Additional Notes

  • Both classes should be used with message queues or serialization to prevent race conditions with simultaneous removeTokens() calls
  • Race conditions can lead to out-of-order messages or apparent "lost" messages under heavy load
  • Use tryRemoveTokens() for non-blocking token removal when immediate response is needed
  • The RateLimiter automatically manages interval resets and token bucket refills
  • Token buckets support hierarchical relationships where child buckets also consume tokens from parent buckets
  • Infinite buckets (bucketSize: 0) always allow token removal and return Number.POSITIVE_INFINITY