{"_id":"bun-rate-limiter","_rev":"4-6623c1ab8f9e6af02d492c3bb7f3d35a","name":"bun-rate-limiter","dist-tags":{"latest":"0.2.2"},"versions":{"0.1.1":{"name":"bun-rate-limiter","version":"0.1.1","keywords":["queue","concurrency","rate-limit","throttle","async","promise","bun"],"author":{"name":"Don Lair","email":"don.lair@gmail.com"},"license":"MIT","_id":"bun-rate-limiter@0.1.1","maintainers":[{"name":"donlair","email":"don.lair@gmail.com"}],"homepage":"https://github.com/donlair/bun-rate-limiter#readme","bugs":{"url":"https://github.com/donlair/bun-rate-limiter/issues"},"dist":{"shasum":"f0bfdd0788db485c8c489daa29c7b6fb37680cf7","tarball":"https://registry.npmjs.org/bun-rate-limiter/-/bun-rate-limiter-0.1.1.tgz","fileCount":56,"integrity":"sha512-jvOlxqXBR2Gmv8boAqv+eOJ2XZDNqZhM1b9svWOmrFaN11Xzf5ZfZ0/Z1tY9b8vFlry5y1jK0vqWCN5cYJV9rw==","signatures":[{"sig":"MEUCIQCjsfUsy7X6Huz8lz6cQlfWE+jPAo3d+3f5lOioc1RnEgIgWkYogNxnapR93fQaqndzlAIWFnV0UGJfRPTM3RPETuE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":145101},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.3.0"},"exports":{".":{"bun":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"e2c090e838f58abed048f80d21147c563992b5f5","scripts":{"ci":"biome ci src tests","lint":"biome lint --write src tests","test":"bun test","build":"bun run build:types && bun run build:types:rewrite && bun run build:esm && bun run build:cjs","check":"biome check src tests","format":"biome format --write src tests","prepack":"bun run build","build:cjs":"bun build src/index.ts --outfile dist/index.cjs --target bun --format cjs","build:esm":"bun build src/index.ts --outdir dist --target bun --format esm","test:watch":"bun test --watch","build:types":"tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist","test:coverage":"bun test --coverage","build:types:rewrite":"bun run scripts/rewrite-dts-imports.ts"},"_npmUser":{"name":"donlair","email":"don.lair@gmail.com"},"repository":{"url":"git+https://github.com/donlair/bun-rate-limiter.git","type":"git"},"_npmVersion":"10.9.2","description":"A modern, modular rate limiter and concurrency queue built for Bun","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"devDependencies":{"@types/bun":"latest","typescript":"^5","@biomejs/biome":"^2.3.8"},"_npmOperationalInternal":{"tmp":"tmp/bun-rate-limiter_0.1.1_1765759332734_0.7618195088852546","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"bun-rate-limiter","version":"0.2.0","keywords":["queue","concurrency","rate-limit","throttle","async","promise","bun","node","nodejs","vercel","serverless","lambda","redis","distributed"],"author":{"name":"Don Lair","email":"don.lair@gmail.com"},"license":"MIT","_id":"bun-rate-limiter@0.2.0","maintainers":[{"name":"donlair","email":"don.lair@gmail.com"}],"homepage":"https://github.com/donlair/bun-rate-limiter#readme","bugs":{"url":"https://github.com/donlair/bun-rate-limiter/issues"},"dist":{"shasum":"9455d69a0b950d713d0e6411d79a7e115adb595e","tarball":"https://registry.npmjs.org/bun-rate-limiter/-/bun-rate-limiter-0.2.0.tgz","fileCount":62,"integrity":"sha512-UrlcjRbMMxdW3i3qizgwCVwZwPx/60sGCX/5sHyRXq8QbFqyN3uu4W8n4DOOCmkMTwK5dtC7OA34wSdSH4BrRA==","signatures":[{"sig":"MEQCIADsn3nYLacjgkO1ZkpCMhTqHGSkGy94krWzCwYZYR6CAiBD+n9MIL6LoBinZkM9XyV/uiyYcZjN05LDBSpgO5Kmpg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/bun-rate-limiter@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":202186},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.3.0","node":">=18.0.0"},"exports":{".":{"bun":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"1efb4aa2a8e7fa5041289f3c990f833fda4bb8fa","scripts":{"ci":"biome ci src tests","lint":"biome lint --write src tests","test":"bun test ./tests","build":"bun run build:types && bun run build:types:rewrite && bun run build:esm && bun run build:cjs","check":"biome check src tests","format":"biome format --write src tests","prepack":"bun run build","release":"bun test ./tests && bun run build && npm publish --access public","build:cjs":"bun build src/index.ts --outfile dist/index.cjs --target node --format cjs","build:esm":"bun build src/index.ts --outdir dist --target node --format esm","changeset":"changeset","test:watch":"bun test --watch ./tests","build:types":"tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist","test:coverage":"bun test --coverage ./tests","version-packages":"changeset version","build:types:rewrite":"bun run scripts/rewrite-dts-imports.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cb7125c3-e3e0-445b-942f-b63b42828f90"}},"repository":{"url":"git+https://github.com/donlair/bun-rate-limiter.git","type":"git"},"_npmVersion":"11.6.2","description":"A modern, modular rate limiter and concurrency queue. Built with Bun, runs anywhere—Node.js 18+, Vercel, AWS Lambda, and more.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","_hasShrinkwrap":false,"packageManager":"bun@1.3.4","devDependencies":{"@types/bun":"latest","typescript":"^5","@biomejs/biome":"^2.3.8","@changesets/cli":"^2.29.8"},"_npmOperationalInternal":{"tmp":"tmp/bun-rate-limiter_0.2.0_1765986636203_0.3793775922347806","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"bun-rate-limiter","version":"0.2.1","keywords":["queue","concurrency","rate-limit","throttle","async","promise","bun","node","nodejs","vercel","serverless","lambda","redis","distributed"],"author":{"name":"Don Lair","email":"don.lair@gmail.com"},"license":"MIT","_id":"bun-rate-limiter@0.2.1","maintainers":[{"name":"donlair","email":"don.lair@gmail.com"}],"homepage":"https://github.com/donlair/bun-rate-limiter#readme","bugs":{"url":"https://github.com/donlair/bun-rate-limiter/issues"},"dist":{"shasum":"f3689101b001d0ac2adb266586143a1bc916a8b4","tarball":"https://registry.npmjs.org/bun-rate-limiter/-/bun-rate-limiter-0.2.1.tgz","fileCount":43,"integrity":"sha512-NpIRfQHDhxZzSmDsXGaAAseM3bPZ+1DIn8H4O4o+FSZnsmjx6tikcpWG9dfFQnLeysQ8jI7jQ1qMpH9WrbdiMg==","signatures":[{"sig":"MEUCIDAh7VABeENPL0O1nLN7O1ohtIalQcoQt0JSp8oF78gLAiEApuY+78YwnT115Rr3L9zgxe42ACY0jTZJwJVhpdhD0Dg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/bun-rate-limiter@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":140401},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"bun":">=1.3.0","node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"a763b249fea087878ce5a95e670cdf1fc720e3e2","scripts":{"ci":"biome ci src tests","lint":"biome lint --write src tests","test":"bun test ./tests","build":"bun run build:types && bun run build:types:rewrite && bun run build:esm && bun run build:cjs","check":"biome check src tests","format":"biome format --write src tests","prepack":"bun run build","release":"bun test ./tests && bun run build && npm publish --access public","build:cjs":"bun build src/index.ts --outfile dist/index.cjs --target node --format cjs","build:esm":"bun build src/index.ts --outdir dist --target node --format esm","changeset":"changeset","test:watch":"bun test --watch ./tests","build:types":"tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist","test:coverage":"bun test --coverage ./tests","version-packages":"changeset version","build:types:rewrite":"bun run scripts/rewrite-dts-imports.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cb7125c3-e3e0-445b-942f-b63b42828f90"}},"repository":{"url":"git+https://github.com/donlair/bun-rate-limiter.git","type":"git"},"_npmVersion":"11.6.2","description":"A modern, modular rate limiter and concurrency queue. Built with Bun, runs anywhere—Node.js 18+, Vercel, AWS Lambda, and more.","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"packageManager":"bun@1.3.4","devDependencies":{"@types/bun":"latest","typescript":"^5","@biomejs/biome":"^2.3.8","@changesets/cli":"^2.29.8"},"_npmOperationalInternal":{"tmp":"tmp/bun-rate-limiter_0.2.1_1768514089125_0.8286663865746109","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"_id":"bun-rate-limiter@0.2.2","bugs":{"url":"https://github.com/donlair/bun-rate-limiter/issues"},"dist":{"shasum":"0b3c8514dbbd4f1f9670c4e0b101bd024da34ef2","tarball":"https://registry.npmjs.org/bun-rate-limiter/-/bun-rate-limiter-0.2.2.tgz","fileCount":43,"integrity":"sha512-FpfcJKgpwjFz4Zey33KmycY8QBklHXEszwC/EkOU63yNZ2kYVacIHQJytgM8aw/sQ9VhchrD4jZ6sWnD3FMM0A==","signatures":[{"sig":"MEQCIHy43gMIt8dgvPxF8LQYbSW/Wd7o1yFHdxz7oozw+0+VAiBJtDF4btDr2JLAhFCdiPx8vneE9BQU8rpCVFvbsDPLoQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHS07Q2yguiQEtOB97r3Hf9BE7g0aCiq3AQNB5CE8p/HAiEAsw6F0EpoYXT2qzcg0MSfI5vNyCtvLVTFtXZ5nAEHFSM="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/bun-rate-limiter@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":141683},"main":"./dist/index.js","name":"bun-rate-limiter","type":"module","types":"./dist/index.d.ts","author":{"name":"Don Lair","email":"don.lair@gmail.com"},"module":"./dist/index.js","engines":{"bun":">=1.3.0","node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"be3b2f822e590efcadbc2d18b26e425e397c754a","license":"MIT","scripts":{"ci":"biome ci src tests","lint":"biome lint --write src tests","test":"bun test ./tests","build":"bun run build:types && bun run build:types:rewrite && bun run build:esm && bun run build:cjs","check":"biome check src tests","format":"biome format --write src tests","prepack":"bun run build","release":"bun test ./tests && bun run build && npm publish --access public","build:cjs":"bun build src/index.ts --outfile dist/index.cjs --target node --format cjs","build:esm":"bun build src/index.ts --outdir dist --target node --format esm","changeset":"changeset","test:watch":"bun test --watch ./tests","build:types":"tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist","test:coverage":"bun test --coverage ./tests","version-packages":"changeset version","build:types:rewrite":"bun run scripts/rewrite-dts-imports.ts"},"version":"0.2.2","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cb7125c3-e3e0-445b-942f-b63b42828f90"}},"homepage":"https://github.com/donlair/bun-rate-limiter#readme","keywords":["queue","concurrency","rate-limit","throttle","async","promise","bun","node","nodejs","vercel","serverless","lambda","redis","distributed"],"repository":{"url":"git+https://github.com/donlair/bun-rate-limiter.git","type":"git"},"_npmVersion":"11.19.0","description":"A modern, modular rate limiter and concurrency queue. Built with Bun, runs anywhere—Node.js 18+, Vercel, AWS Lambda, and more.","directories":{},"maintainers":[{"name":"donlair","email":"don.lair@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"packageManager":"bun@1.3.4","devDependencies":{"@types/bun":"latest","typescript":"^5","@biomejs/biome":"^2.3.8","@changesets/cli":"^2.29.8"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bun-rate-limiter_0.2.2_1790011514031_0.472080752121534"}}},"time":{"created":"2025-12-15T00:42:12.733Z","modified":"2026-09-21T17:25:14.467Z","0.1.1":"2025-12-15T00:42:12.888Z","0.2.0":"2025-12-17T15:50:36.350Z","0.2.1":"2026-01-15T21:54:49.263Z","0.2.2":"2026-09-21T17:25:14.128Z"},"bugs":{"url":"https://github.com/donlair/bun-rate-limiter/issues"},"author":{"name":"Don Lair","email":"don.lair@gmail.com"},"license":"MIT","homepage":"https://github.com/donlair/bun-rate-limiter#readme","keywords":["queue","concurrency","rate-limit","throttle","async","promise","bun","node","nodejs","vercel","serverless","lambda","redis","distributed"],"repository":{"url":"git+https://github.com/donlair/bun-rate-limiter.git","type":"git"},"description":"A modern, modular rate limiter and concurrency queue. Built with Bun, runs anywhere—Node.js 18+, Vercel, AWS Lambda, and more.","maintainers":[{"name":"donlair","email":"don.lair@gmail.com"}],"readme":"# bun-rate-limiter\n\n[![npm version](https://img.shields.io/npm/v/bun-rate-limiter.svg)](https://www.npmjs.com/package/bun-rate-limiter)\n[![npm downloads](https://img.shields.io/npm/dm/bun-rate-limiter.svg)](https://www.npmjs.com/package/bun-rate-limiter)\n[![GitHub](https://img.shields.io/github/stars/donlair/bun-rate-limiter?style=social)](https://github.com/donlair/bun-rate-limiter)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA lightweight, zero-runtime-dependency task scheduler and rate limiter. Built with Bun, runs anywhere—Node.js 18+, Vercel, AWS Lambda, and more.\n\n**What makes this different?** Composable throttlers. Instead of a single rate limiting strategy, you combine multiple throttlers that work together (anti-burst spacing, token buckets, interval caps) and the scheduler enforces the most restrictive constraint. Need \"2000 requests/minute with 10ms minimum between requests\"? That's two throttlers composed together, and it works the same locally or distributed via Redis.\n\nUse this if you need:\n- **API rate limiting** with precise control (burst limits + smoothing)\n- **Distributed rate limiting** via Redis that works across serverless instances\n- **Composable strategies** that combine spacing, intervals, and token buckets\n- **Similar API to p-queue** with more flexible throttling options\n\n## Quick Decision\n\n**Use this library if:**\n- You need distributed rate limiting across serverless instances (Vercel, Lambda)\n- You want composable strategies (burst limits + smoothing + per-key limits)\n- You're okay with Redis as a dependency for distributed limiting\n\n**Consider alternatives if:**\n- You only need single-process rate limiting (simpler options exist)\n- You can't tolerate any Redis downtime (no automatic fallback)\n- You need sub-5ms latency (Redis adds network overhead)\n\n## TL;DR: API Rate Limiting Recipe\n\nMost users want this: **2000 requests/minute with anti-burst smoothing**. Copy-paste and adjust the numbers:\n\n```typescript\nimport { RateLimiter } from 'bun-rate-limiter';\n\nconst limiter = new RateLimiter({\n  concurrency: 50,\n  limits: {\n    minDelayMs: 30,  // Anti-burst: ~33 req/sec max burst rate\n    tokenBucket: {\n      capacity: 2000,       // Allow burst up to 2000\n      refillAmount: 2000,   // Refill 2000 tokens\n      refillInterval: 60_000, // Every 60 seconds\n    },\n  },\n});\n\n// All API calls go through the limiter\nconst data = await limiter.add(() => fetch('https://api.example.com/data'));\n```\n\n**Need distributed rate limiting?** Add Redis (works on Vercel, AWS Lambda, etc.):\n\n```typescript\nimport { RateLimiter } from 'bun-rate-limiter';\n\n// For Node.js, see \"Using with Node.js\" section for adapters\nconst limiter = new RateLimiter({\n  concurrency: 50,\n  backend: { type: 'redis', redis: redisClient, keyPrefix: 'myapp:rl' },\n  limits: {\n    minDelayMs: 30,\n    tokenBucket: { capacity: 2000, refillAmount: 2000, refillInterval: 60_000 },\n  },\n  defaultRateLimitKey: 'api', // All requests share this limit\n});\n```\n\n## Compatibility\n\n**Runs on:** Node.js 18+, Bun, Deno (untested), Vercel, AWS Lambda, Cloudflare Workers\n**Package managers:** npm, pnpm, yarn, bun\n\n*Why \"bun-rate-limiter\"?* The project uses Bun's tooling for development (tests, builds), but publishes standard JavaScript that runs anywhere—like a TypeScript library.\n\n## Installation\n\n```bash\nbun add bun-rate-limiter\n```\n\n```bash\npnpm add bun-rate-limiter\n```\n\n```bash\nyarn add bun-rate-limiter\n```\n\n```bash\nnpm i bun-rate-limiter\n```\n\n## Quick Start\n\n```typescript\nimport { RateLimiter } from 'bun-rate-limiter';\n\n// Create a queue with concurrency limit and rate limiting\nconst queue = new RateLimiter({\n  concurrency: 5,    // Max 5 concurrent tasks\n  limits: {\n    minDelayMs: 100, // 100ms minimum between task starts\n  },\n});\n\n// Add tasks\nconst result = await queue.add(async () => {\n  const response = await fetch('https://api.example.com/data');\n  return response.json();\n});\n\n// Add with priority (higher = runs first)\nawait queue.add(async () => 'important!', { priority: 10 });\n\n// Add multiple tasks\nconst results = await queue.addAll([\n  async () => fetchUser(1),\n  async () => fetchUser(2),\n  async () => fetchUser(3),\n]);\n```\n\n## Common Recipes\n\n### Anti-burst only (smooth out traffic)\n```typescript\nconst limiter = new RateLimiter({\n  concurrency: 10,\n  limits: { minDelayMs: 100 }, // Max 10 req/sec, evenly spaced\n});\n```\n\n### Hard cap per interval\n```typescript\nconst limiter = new RateLimiter({\n  concurrency: 10,\n  limits: { interval: { limit: 100, interval: 1000 } }, // Max 100/sec, can burst\n});\n```\n\n### Burst + steady state (token bucket)\n```typescript\nconst limiter = new RateLimiter({\n  concurrency: 20,\n  limits: {\n    tokenBucket: { capacity: 50, refillAmount: 10, refillInterval: 1000 },\n  },\n}); // Burst 50, then 10/sec steady\n```\n\n### Per-user rate limits (distributed)\n```typescript\nconst limiter = new RateLimiter({\n  concurrency: 50,\n  backend: { type: 'redis', redis: redisClient, keyPrefix: 'myapp:rl' },\n  limits: { tokenBucket: { capacity: 100, refillAmount: 100, refillInterval: 60_000 } },\n});\n\n// Each user gets their own bucket\nawait limiter.add(() => fetchUserData(userId), { rateLimitKey: `user:${userId}` });\n```\n\n## Core Concepts\n\n### Throttlers\n\nA **throttler** answers: \"How long should we wait before starting the next task?\" This library provides three types:\n\n| Throttler | Use Case | Example |\n|-----------|----------|---------|\n| **Spacing** | Anti-burst pacing | \"At least 100ms between requests\" |\n| **Interval** | Hard cap per window | \"Max 100 requests per second\" |\n| **Token Bucket** | Burst + steady state | \"Allow 50 burst, then 10/sec refill\" |\n\nEach has a local (in-memory) and distributed (Redis) variant.\n\n### Composition\n\nThrottlers **compose** by taking the maximum delay. If you combine a spacing throttler (100ms) with an interval throttler (10/sec), both constraints apply—you get at most 10/sec AND at least 100ms apart.\n\n```typescript\n// This enforces BOTH: max 10/sec AND 100ms minimum spacing\nconst limiter = new RateLimiter({\n  concurrency: 10,\n  throttlers: [\n    new IntervalThrottler({ limit: 10, interval: 1000 }),\n    new SpacingThrottler(100),\n  ],\n});\n```\n\n### The `limits` Shorthand\n\nThe `limits` option is syntactic sugar that creates throttlers for you:\n\n| `limits` config | Creates |\n|-----------------|---------|\n| `{ minDelayMs: 100 }` | `SpacingThrottler(100)` |\n| `{ interval: { limit: 10, interval: 1000 } }` | `IntervalThrottler(...)` |\n| `{ tokenBucket: { capacity: 50, ... } }` | `TokenBucketThrottler(...)` |\n\nWhen you add `backend: { type: 'redis' }`, the same `limits` config creates Redis-backed throttlers instead (for distributed rate limiting).\n\n### Choosing Between Token Bucket and Interval\n\n| Strategy | Best For | Behavior |\n|----------|----------|----------|\n| **Token Bucket** | API rate limits with burst allowance | Allows burst up to capacity, then steady refill |\n| **Interval** | Hard caps per time window | Strict \"N requests per X seconds\" |\n\n**For distributed (Redis) limiting:** Use token bucket. Interval is local-only because distributed interval tracking requires complex coordination that adds latency without significant benefit over token bucket.\n\n## API\n\n### `new RateLimiter(options?)`\n\nCreate a new queue instance.\n\n```typescript\ninterface RateLimiterOptions {\n  concurrency?: number;   // Max concurrent tasks (default: 1)\n  limits?: RateLimiterLimits; // Happy-path rate limiting config\n  backend?: BackendOptions; // Optional distributed backend (e.g. Redis)\n  throttlers?: IThrottler[]; // Advanced: manual sync throttlers\n  asyncThrottlers?: IAsyncThrottler[]; // Advanced: manual async throttlers (e.g. Redis)\n  compose?: boolean;      // Combine limits with manual throttlers (default: false)\n  defaultRateLimitKey?: string; // Default key for tasks without rateLimitKey\n  autoStart?: boolean;    // Start processing immediately (default: true)\n  timeout?: number;       // Default timeout in ms for all tasks (default: none)\n}\n\n// Rate limiting configuration (tokenBucket and interval are mutually exclusive)\ntype RateLimiterLimits =\n  | { minDelayMs?: number; tokenBucket?: TokenBucketLimits; interval?: never }\n  | { minDelayMs?: number; interval?: IntervalLimits; tokenBucket?: never };\n\ninterface TokenBucketLimits {\n  capacity: number;       // Max tokens (burst size)\n  refillAmount: number;   // Tokens added per interval\n  refillInterval: number; // Refill period in ms\n}\n\ninterface IntervalLimits {\n  limit: number;          // Max operations per interval\n  interval: number;       // Time window in ms\n}\n\n// Backend configuration for distributed rate limiting\ninterface BackendOptions {\n  type: 'redis';\n  redis: IRedisClient;    // Your Redis client instance\n  keyPrefix?: string;     // Key prefix (default: 'bun-rate-limiter')\n  defaultKey?: string;    // Default rate limit key\n}\n```\n\n### `queue.add<T>(fn, options?): Promise<T>`\n\nAdd a task to the queue.\n\n```typescript\ninterface TaskOptions {\n  priority?: number;      // Higher = runs first (default: 0)\n  rateLimitKey?: string;  // Per-key identifier for distributed rate limiting\n  signal?: AbortSignal;   // Cancel the task\n  timeout?: number;       // Task timeout in ms (overrides default)\n}\n\n// Example with timeout\nimport { TimeoutError } from 'bun-rate-limiter';\n\ntry {\n  const result = await queue.add(\n    async () => {\n      const response = await fetch('https://api.example.com/slow');\n      return response.json();\n    },\n    { timeout: 5000 } // 5 second timeout\n  );\n} catch (error) {\n  if (error instanceof TimeoutError) {\n    console.log('Task timed out!');\n  }\n}\n\n// Example with AbortSignal\nconst controller = new AbortController();\nconst promise = queue.add(\n  async ({ signal }) => {\n    // signal is provided to your function\n    const response = await fetch(url, { signal });\n    return response.json();\n  },\n  { signal: controller.signal }\n);\n\n// Cancel the task\ncontroller.abort();\n```\n\n### `queue.addAll<T>(fns, options?): Promise<T[]>`\n\nAdd multiple tasks with the same options.\n\n```typescript\nconst results = await queue.addAll([\n  async () => 1,\n  async () => 2,\n  async () => 3,\n], { priority: 5 });\n```\n\n### `queue.pause()` / `queue.start()`\n\nPause and resume the queue. Running tasks will complete.\n\n```typescript\nqueue.pause();\n// ... add tasks while paused ...\nqueue.start(); // Resume processing\n```\n\n### `queue.clear()`\n\nRemove all pending tasks from the queue.\n\n### `queue.resetAsyncThrottlers()`\n\nReset the state of all async throttlers (e.g., Redis-backed distributed rate limiters). Useful for testing or recovering from stuck state. Does not remove pending tasks.\n\n```typescript\nawait queue.resetAsyncThrottlers();\n```\n\n### Properties\n\n- `queue.size` - Number of tasks waiting in the queue\n- `queue.pending` - Number of tasks currently running (matches p-queue convention)\n- `queue.runningCount` - Alias for pending\n- `queue.isPaused` - Whether the queue is paused\n- `queue.isRateLimited` - Whether the queue is currently rate limited\n- `queue.isSaturated` - Whether the queue is at capacity (concurrency OR rate limited)\n\n### Events\n\nSubscribe to queue lifecycle events with `on()`, `once()`, or unsubscribe with `off()`.\n\n```typescript\n// Subscribe to events (returns unsubscribe function)\nconst unsubscribe = queue.on('idle', () => console.log('All done!'));\n\n// Queue starts processing\nqueue.on('active', () => console.log('Processing started'));\n\n// Task added\nqueue.on('add', () => console.log('Task added'));\n\n// Task completed\nqueue.on('completed', (result) => console.log('Completed:', result));\n\n// Task failed\nqueue.on('error', (error) => console.error('Error:', error));\n\n// Subscribe once (auto-unsubscribes after first event)\nqueue.once('idle', () => console.log('First idle!'));\n\n// Unsubscribe manually\nconst handler = () => console.log('active');\nqueue.on('active', handler);\nqueue.off('active', handler); // Remove specific handler\n\n// Or use the returned unsubscribe function\nunsubscribe();\n```\n\n## Advanced Usage\n\n### Custom Throttlers\n\n#### Built-in throttlers (what they do)\n\nThis library ships with a few built-in throttling strategies. Each one answers the question: “How long should we wait before starting the next job?”\n\n- **`SpacingThrottler(minDelayMs)`**: enforces a minimum delay between job starts (anti-burst pacing).\n  - Good for APIs that require \"at least X ms between requests\".\n  - Note: `RateLimiter({ limits: { minDelayMs } })` internally creates a `SpacingThrottler` when no backend is specified, or a `RedisSpacingThrottler` when using the Redis backend.\n\n- **`IntervalThrottler({ limit, interval })`**: caps the number of job starts in a moving time window (e.g. “no more than 10 per second”).\n  - Good for hard “N requests per interval” limits.\n\n- **`TokenBucketThrottler({ capacity, refillAmount, refillInterval, initialTokens? })`**: allows bursts up to `capacity`, then refills over time. Optional `initialTokens` sets starting token count (defaults to `capacity`).\n  - Good when you want “burst + steady state” behavior (smoother than a hard window).\n\n- **`RedisSpacingThrottler({ redis, minDelayMs, keyPrefix?, defaultKey?, keyFn? })`** (async throttler): like `SpacingThrottler`, but coordinated via Redis for distributed rate limiting. Optional params: `keyPrefix` (default: `'bun-rate-limiter'`), `defaultKey`, `keyFn` (custom key derivation function).\n  - Good when you need true global minimum delay across multiple processes/servers.\n\n- **`RedisTokenBucketThrottler({ redis, capacity, refillAmount, refillInterval, keyPrefix?, defaultKey?, keyFn? })`** (async throttler): like `TokenBucketThrottler`, but coordinated via Redis for distributed rate limiting. Same optional params as `RedisSpacingThrottler`.\n  - Good when you have multiple Bun processes/servers that must share a global/per-key limit.\n\nUse the built-in `IntervalThrottler` for rate limiting (e.g., 10 requests per second):\n\n```typescript\nimport {\n  RateLimiter,\n  StandardScheduler,\n  PriorityQueue,\n  IntervalThrottler,\n  Job\n} from 'bun-rate-limiter';\n\n// Create custom throttler: max 10 jobs per 1000ms\nconst throttler = new IntervalThrottler({ limit: 10, interval: 1000 });\n\n// Create queue with custom scheduler\nconst queue = new PriorityQueue<Job<unknown>>((a, b) => b.priority - a.priority);\nconst scheduler = new StandardScheduler(queue, [throttler], {\n  concurrency: 10,\n  autoStart: true\n});\n```\n\nYou can also plug throttlers directly into `RateLimiter`:\n\n```typescript\nimport { RateLimiter, TokenBucketThrottler } from 'bun-rate-limiter';\n\n// Allow bursts of up to 10, refilling at 10 tokens per second\nconst queue = new RateLimiter({\n  concurrency: 10,\n  throttlers: [\n    new TokenBucketThrottler({\n      capacity: 10,\n      refillAmount: 10,\n      refillInterval: 1000,\n    }),\n  ],\n});\n```\n\n#### Composing Throttlers\n\n`throttlers` are composable. The scheduler consults all throttlers and enforces the most restrictive delay:\n\n- Before starting a job, it calls `getNextRunDelay()` on every throttler and waits the **maximum** delay returned.\n- When a job starts, it calls `notifyJobStarted()` on every throttler so each strategy can update its own state.\n\nIf multiple throttlers “conflict”, the result is simply stricter rate limiting (the intersection of policies). For example:\n\n```typescript\nimport { RateLimiter, IntervalThrottler, SpacingThrottler } from 'bun-rate-limiter';\n\nconst queue = new RateLimiter({\n  concurrency: 10,\n  throttlers: [\n    new IntervalThrottler({ limit: 10, interval: 1000 }), // <= 10/sec\n    new SpacingThrottler(100), // >= 100ms between starts\n  ],\n});\n```\n\n#### Composition patterns (why you might do this)\n\n- **Anti-burst pacing only** (simple “don’t spike”): use `limits.minDelayMs` or `SpacingThrottler`.\n- **Hard cap only** (“N per interval”): use `IntervalThrottler`.\n- **Burst + steady state** (“allow bursts, then smooth out”): use `TokenBucketThrottler`.\n- **Hard cap + pacing** (common for flaky APIs): combine `IntervalThrottler` + `SpacingThrottler` so you avoid bursts *and* respect a strict maximum.\n- **Distributed limits**: add one or more `asyncThrottlers` (e.g. Redis token bucket) so multiple processes share the same limit; you can still combine with local sync throttlers for extra smoothing.\n\nNote: a throttler that always returns a positive delay (or a very large delay) can effectively stall the queue.\n\n#### How `limits` and `backend` interact\n\nThe `limits` option provides a simple way to configure rate limiting without manually instantiating throttlers:\n\n| Configuration | What happens |\n|--------------|--------------|\n| `limits` only | Creates in-memory (sync) throttlers |\n| `limits` + `backend: { type: 'redis' }` | Creates Redis-backed (async) throttlers only* |\n| `throttlers`/`asyncThrottlers` only | Uses your manual throttlers directly |\n| `limits` + manual throttlers + `compose: true` | Combines both (advanced) |\n\nWhen you specify both `limits` and manual `throttlers`/`asyncThrottlers` without `compose: true`, the library throws an error to prevent accidental double-throttling. Set `compose: true` to explicitly opt-in to combining them.\n\n*\\*Note: `limits.interval` is not supported with the Redis backend (throws error). Use `limits.tokenBucket` for distributed rate limiting.*\n\n### Distributed (Redis) Throttlers\n\nFor distributed rate limiting across multiple processes/servers, you have two options:\n- **Simple:** Use `limits` + `backend: { type: 'redis' }` (recommended for most cases)\n- **Advanced:** Manually configure `asyncThrottlers` for full control\n\nPrerequisites:\n- A running Redis instance\n- Bun `>= 1.3.0` (for Bun’s `RedisClient`)\n\n#### Global minimum spacing (Redis)\n\nLocal `SpacingThrottler` (and `limits.minDelayMs` without a backend) is per-process. If you need a true global minimum delay between starts across multiple processes, use the Redis backend:\n\n```typescript\nimport { RedisClient } from 'bun';\nimport { RateLimiter } from 'bun-rate-limiter';\n\nconst redis = new RedisClient(process.env.REDIS_URL);\nawait redis.connect();\n\nconst queue = new RateLimiter({\n  concurrency: 50,\n  backend: { type: 'redis', redis, keyPrefix: 'myapp:rl' },\n  limits: { minDelayMs: 3 },\n});\n\nawait queue.add(async () => fetch(url), { rateLimitKey: 'global' });\n```\n\n#### Combining global spacing + global limit\n\nTo enforce both “at least 3ms between starts” and “<= 2000 per minute” globally, compose two async throttlers:\n\n```typescript\nimport { RedisClient } from 'bun';\nimport { RateLimiter } from 'bun-rate-limiter';\n\nconst redis = new RedisClient(process.env.REDIS_URL);\nawait redis.connect();\n\nconst queue = new RateLimiter({\n  concurrency: 50,\n  backend: { type: 'redis', redis, keyPrefix: 'myapp:rl' },\n  limits: {\n    minDelayMs: 3,\n    tokenBucket: { capacity: 2000, refillAmount: 2000, refillInterval: 60_000 },\n  },\n});\n```\n\nExample: Redis token bucket with per-user keys:\n\n```typescript\nimport { RedisClient } from 'bun';\nimport { RateLimiter, RedisTokenBucketThrottler } from 'bun-rate-limiter';\n\nconst redis = new RedisClient(process.env.REDIS_URL);\nawait redis.connect();\n\nconst queue = new RateLimiter({\n  concurrency: 5,\n  asyncThrottlers: [\n    new RedisTokenBucketThrottler({\n      redis,\n      keyPrefix: 'myapp:rl:',\n      capacity: 10,\n      refillAmount: 10,\n      refillInterval: 1000,\n    }),\n  ],\n});\n\nawait queue.add(async () => fetchUser(123), { rateLimitKey: 'user:123' });\n```\n\nNotes:\n- `rateLimitKey` controls the \"bucket\" a task consumes from (per-user, per-org, global, etc).\n- Use `defaultRateLimitKey` in `RateLimiterOptions` to set a default key for all tasks (e.g., `'global'`).\n- `asyncThrottlers` are composable; delays are combined by taking the maximum, same as sync throttlers.\n- To explicitly reset distributed throttler state (without clearing pending tasks): `await queue.resetAsyncThrottlers()`.\n- Example code you can run locally: `examples/simple-redis-token-bucket.ts` or `examples/monorepo/README.md`.\n\n### Using with Node.js\n\nThe library works out of the box with Node.js 18+. For Redis-backed rate limiting, you'll need to adapt your Redis client to the `IRedisClient` interface:\n\n#### ioredis adapter\n\n```typescript\nimport Redis from 'ioredis';\nimport { RateLimiter, type IRedisClient } from 'bun-rate-limiter';\n\nconst redis = new Redis(process.env.REDIS_URL);\n\n// Adapter: ioredis -> IRedisClient\nconst redisClient: IRedisClient = {\n  send: (command, args) => redis.call(command, ...args) as Promise<unknown>,\n};\n\nconst limiter = new RateLimiter({\n  backend: { type: 'redis', redis: redisClient, keyPrefix: 'myapp:rl' },\n  limits: { minDelayMs: 30, tokenBucket: { capacity: 2000, refillAmount: 2000, refillInterval: 60_000 } },\n});\n```\n\n#### node-redis adapter\n\n```typescript\nimport { createClient } from 'redis';\nimport { RateLimiter, type IRedisClient } from 'bun-rate-limiter';\n\nconst redis = createClient({ url: process.env.REDIS_URL });\nawait redis.connect();\n\n// Adapter: node-redis -> IRedisClient\nconst redisClient: IRedisClient = {\n  send: (command, args) => redis.sendCommand([command, ...args.map(String)]),\n};\n```\n\n#### Serverless considerations\n\nIn serverless environments (Vercel, AWS Lambda), each invocation may create a new instance. Use a singleton pattern to reuse connections within a container's lifetime:\n\n```typescript\nlet limiter: RateLimiter | null = null;\n\nexport async function getRateLimiter(): Promise<RateLimiter> {\n  if (limiter) return limiter;\n  // Create once per container lifetime\n  const redis = createClient({ url: process.env.REDIS_URL });\n  await redis.connect();\n  limiter = new RateLimiter({ backend: { type: 'redis', redis: adapter, ... }, ... });\n  return limiter;\n}\n```\n\nThe rate limit state lives in Redis, so it's automatically shared across all serverless instances.\n\nSee full examples:\n- `examples/node-ioredis/` - Simple Node.js + ioredis example\n- `examples/nextjs-server-action/` - Next.js 16 server action with Vercel deployment\n\n### Using ArrayQueue (FIFO)\n\nFor simple FIFO ordering without priorities:\n\n```typescript\nimport { ArrayQueue, StandardScheduler, Job } from 'bun-rate-limiter';\n\nconst queue = new ArrayQueue<Job<unknown>>();\nconst scheduler = new StandardScheduler(queue, [], { concurrency: 3 });\n```\n\n## Architecture\n\nbun-rate-limiter uses a modular, component-based architecture:\n\n```\nRateLimiter (Facade)\n    |\n    +-- StandardScheduler (Coordinator)\n    |       |\n    |       +-- IQueue (Storage Strategy)\n    |       |     +-- ArrayQueue (FIFO)\n    |       |     +-- PriorityQueue (Priority-based)\n    |       |\n    |       +-- IThrottler[] (Rate Limiting)\n    |       |     +-- SpacingThrottler (Min delay between tasks)\n    |       |     +-- IntervalThrottler (N tasks per interval)\n    |       |     +-- TokenBucketThrottler (Burst + refill)\n    |       |\n    |       +-- IAsyncThrottler[] (Distributed Rate Limiting)\n    |             +-- RedisSpacingThrottler (Min delay via Redis)\n    |             +-- RedisTokenBucketThrottler (Burst + refill via Redis)\n    |\n    +-- Job (Task Wrapper)\n    |\n    +-- EventBus (Event Handling)\n```\n\n## Limitations\n\n### Error handling\n\n- **Redis failures**: If Redis becomes unavailable during operation, tasks will fail with connection errors. The library does **not** automatically fall back to local rate limiting—you must handle Redis errors in your application.\n- **No automatic retries**: Failed tasks are not automatically retried. Use the `error` event to implement your own retry logic.\n\n**Mitigation example** (fail-open pattern):\n\n```typescript\nasync function rateLimitedApiCall<T>(fn: () => Promise<T>): Promise<T> {\n  try {\n    return await limiter.add(fn);\n  } catch (error) {\n    if (error instanceof Error && error.message.includes('ECONNREFUSED')) {\n      // Redis unavailable: fail open (allow request without rate limiting)\n      console.warn('Rate limiter unavailable, proceeding without limit');\n      return await fn();\n    }\n    throw error;\n  }\n}\n```\n\n### Distributed rate limiting caveats\n\n- **Clock skew**: Redis throttlers assume reasonably synchronized clocks. Significant clock drift between servers may cause rate limit inaccuracies.\n- **Network latency**: Each rate-limited task requires a Redis round-trip. For very high-frequency operations (>1000/sec per process), local throttlers may be more appropriate.\n- **No persistence across Redis restarts**: Token bucket state is stored in Redis keys with TTLs. If Redis restarts, rate limit state resets.\n\n### Testing\n\n- For unit tests, you can use local (non-Redis) throttlers\n- For integration tests with Redis, use `queue.resetAsyncThrottlers()` between tests to clear state\n- The library doesn't provide built-in time mocking; use your test framework's timer mocks\n\n### Not included\n\n- **Sliding window log**: Only fixed window (interval) and token bucket algorithms are included\n- **Rate limit headers**: No built-in HTTP header generation (X-RateLimit-Remaining, etc.)—implement in your middleware\n- **Metrics/observability**: No built-in Prometheus/StatsD integration; use events for custom metrics\n\n## License\n\nMIT\n","readmeFilename":"README.md"}