{"_id":"@bakidev/token-bucket","name":"@bakidev/token-bucket","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@bakidev/token-bucket","version":"1.0.0","description":"Async token-bucket rate limiter with FIFO waiting, 429/Retry-After backoff, and penalty-refill recovery.","keywords":["rate-limiting","rate-limiter","token-bucket","throttle","backoff","retry-after","429","async","typescript"],"license":"MIT","author":{"name":"Muhammed Nurbaki Kasikci","url":"https://github.com/mnkasikci"},"homepage":"https://github.com/mnkasikci/token-bucket#readme","repository":{"type":"git","url":"git+https://github.com/mnkasikci/token-bucket.git"},"bugs":{"url":"https://github.com/mnkasikci/token-bucket/issues"},"main":"lib/index.js","module":"lib/index.mjs","types":"lib/index.d.ts","engines":{"node":">=20"},"scripts":{"typecheck":"tsc --noEmit","build":"tsup","lint":"eslint .","lint:fix":"eslint . --fix","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"devDependencies":{"@types/node":"^20.4.2","eslint":"^8.50.0","eslint-config-standard-with-typescript":"^39.1.0","eslint-plugin-import":"^2.28.1","eslint-plugin-n":"^16.1.0","eslint-plugin-promise":"^6.1.1","tsup":"^8.0.2","typescript":"^5.2.2","vitest":"^4.1.10"},"publishConfig":{"access":"public"},"gitHead":"c3ad472b2c031270072b74f2f41713169df63ce9","_id":"@bakidev/token-bucket@1.0.0","_nodeVersion":"25.2.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-kiDwjOM3K+Ub7d7yBlmNJzTacMJB/o91QNBTh/W6eXV+cNqSAMVqnHWNdV8L+0uifCY6/3YGTD1qBKvjufwQKA==","shasum":"d0f83eaf158be7930c063b77fb392ee34711b43b","tarball":"https://registry.npmjs.org/@bakidev/token-bucket/-/token-bucket-1.0.0.tgz","fileCount":11,"unpackedSize":74158,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCHbBbduN49B0RyO62CmNhWFwLxkCVoKKoM4Arif0YB5QIgY4nGSleW7I6+4QmZdevaPyjWPpzlfOT0C97OHUxoa/0="}]},"_npmUser":{"name":"mnkasikci93","email":"mnkasikci@gmail.com"},"directories":{},"maintainers":[{"name":"mnkasikci93","email":"mnkasikci@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/token-bucket_1.0.0_1784587123698_0.38706958966237504"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-20T22:38:43.603Z","1.0.0":"2026-07-20T22:38:43.843Z","modified":"2026-07-20T22:38:44.015Z"},"maintainers":[{"name":"mnkasikci93","email":"mnkasikci@gmail.com"}],"description":"Async token-bucket rate limiter with FIFO waiting, 429/Retry-After backoff, and penalty-refill recovery.","homepage":"https://github.com/mnkasikci/token-bucket#readme","keywords":["rate-limiting","rate-limiter","token-bucket","throttle","backoff","retry-after","429","async","typescript"],"repository":{"type":"git","url":"git+https://github.com/mnkasikci/token-bucket.git"},"author":{"name":"Muhammed Nurbaki Kasikci","url":"https://github.com/mnkasikci"},"bugs":{"url":"https://github.com/mnkasikci/token-bucket/issues"},"license":"MIT","readme":"# Token Bucket\nThis package provides a simple and efficient implementation of the token bucket algorithm for rate limiting. It helps you control the frequency of actions (such as API requests) within a specified time frame.\n\nThe token bucket algorithm provides a simple mechanism to control how many actions (e.g., API requests) can be performed over a given time window. The bucket accumulates tokens at a steady rate which you can fine-tune and stop it for a duration, and each action consumes a token. If no tokens are available, the action can either fail or wait for tokens to refill.\n\n## Features\n- **Asynchronous Consumption:** Supports waiting for tokens asynchronously when they are unavailable.\n- **Customizable Refill Rates:** Allows fine-tuning of bucket capacity, refill rate, and time windows.\n- **Force Stop and Refill:** Can force a bucket to wait and stop refilling for a specified duration.\n- **Verbose Mode:** Logs the state of the token bucket for debugging purposes.\n\n## Getting Started\n\n### Simple Usage\n\n```ts\nimport { TokenBucket } from '@bakidev/token-bucket';\n\nconst tokenBucket = new TokenBucket({\n  capacity: 10,\n  fillPerWindow: 1,\n  windowInMs: 1000,\n});\n// Simple Usage: Returns true only when a token is available right away\nif (tokenBucket.consume()) {\n  // Your logic IF the required tokens are available right away.\n}\n\n// Async Usage: Wait until the tokens are available, then consume them\nawait tokenBucket.consumeAsync();\n// Your logic here, which runs AFTER the tokens are available and consumed.\n\n```\n\n### Usage with Verbose Logging\n```ts\nimport { TokenBucket } from '@bakidev/token-bucket';\n\nconst tokenBucket = new TokenBucket({\n  capacity: 20,\n  fillPerWindow: 5,\n  windowInMs: 2000,\n}, true);\n\nif (await tokenBucket.consumeAsync()) {\n  // Your async logic when a token is consumed successfully\n}\n// Logs the following\n// * When there are not enough tokens, it will log a message, which also tells when will the tokens be available\n// * Remaining tokens at each consumption\n// * Forced Stop started-ended\n```\n\n### Singleton Recommendation\n```ts\nimport { TokenBucket } from '@bakidev/token-bucket';\n\nexport const tokenBucket = new TokenBucket({\n  capacity: 20,\n  fillPerWindow: 5,\n  windowInMs: 2000,\n}, true);\n\n// For utilizing the same api / resource, using a single instance of tokenBucket throughout the application is recommended.\n```\n\n## Configuration Options\n\n### TokenBucketOptions\n\n- `capacity`: The maximum number of tokens in the bucket (the burst allowance).\n- `fillPerWindow`: The number of tokens added to the bucket per window.\n- `windowInMs`: The size of the window in milliseconds.\n- `initialTokens`: The initial number of tokens in the bucket. Defaults to the capacity if not provided.\n- `penaltyRefillFraction`: Fraction of capacity (0–1) to restore to when a forced stop ends. Defaults to `0`. See [Force Stop](#force-stop-and-backoff).\n\n```ts\nimport { TokenBucket } from '@bakidev/token-bucket';\n\nconst tokenBucket = new TokenBucket({\n  capacity: 20,\n  fillPerWindow: 2,\n  windowInMs: 1500,\n  initialTokens: 20,\n});\n```\n\n## Refill semantics and sizing\n\n### Refill is lumpy, not a trickle\n\n`fillPerWindow` tokens are added **as a single lump** at the end of each window,\nnot smoothly over it. `{ fillPerWindow: 100, windowInMs: 60000 }` means \"add 100\ntokens once every 60 seconds,\" not \"add ~1.6 tokens per second.\" Against an\nupstream that enforces a sliding window, a single lump can itself trip the limit,\nso size your window accordingly.\n\n### Worst-case throughput is `capacity + fillPerWindow`, not `fillPerWindow`\n\nA full bucket can be drained at once (a burst of `capacity`) and then refilled by\n`fillPerWindow` within the same rolling window. So in any single window the most\ncalls that can go through is:\n\n> **`capacity + fillPerWindow`**\n\nA bucket configured `{ capacity: 5, fillPerWindow: 10 }` can deliver **15** calls\nin the first rolling minute, not 10.\n\n**To stay under an upstream limit `L`, size such that `capacity + fillPerWindow <= L`.**\nFor a 60/min upstream, `{ capacity: 10, fillPerWindow: 50, windowInMs: 60000 }`\nsits exactly on the cap — leave margin below `L`.\n\n## Methods\n\n```ts\nconsume(amount: number = 1): boolean\n```\n\nConsumes the specified number of tokens from the bucket. Returns true if the tokens were successfully consumed, otherwise false. `amount` must be a finite number greater than `0` and no larger than `capacity`; anything else throws a `RangeError`.\n\n```ts\nconsumeAsync(amount: number = 1): Promise<boolean>\n```\n\nAsynchronously consumes the specified number of tokens. The returned promise\n**only resolves once the tokens have actually been consumed** — it never resolves\n`false`, so a `while (!await bucket.consumeAsync())` loop is unnecessary; a single\n`await bucket.consumeAsync()` is enough. If the bucket is destroyed while a call is\nwaiting, the promise rejects with a `BucketDestroyedError`. As with `consume`,\nan `amount` that is not a finite number greater than `0`, or that exceeds\n`capacity` (and so could never be satisfied), rejects with a `RangeError`.\n\nWaiters are released in **strict FIFO (head-of-line) order**: a large request at\nthe head of the queue holds back cheaper requests behind it until it can be\nsatisfied. This is deliberate — it prevents an expensive waiter from being starved\nindefinitely by a stream of cheap `amount: 1` callers under sustained load.\n\n```ts\nstart(): TokenBucket\n```\n\nStarts the internal timer for refilling tokens. Returns the TokenBucket instance to allow chaining.\n\n```ts\nstop(): void\n```\n\nStops the internal timer for refilling tokens.\n\n```ts\nforceWaitUntilMillisecondsPassed(delayInMs: number): void\n```\n\n<a name=\"force-stop-and-backoff\"></a>Forces the bucket to stop handing out tokens\nuntil `delayInMs` has passed before refilling. The intended use is aligning with\nan upstream rate limit — e.g. on a `429`, pass its `Retry-After` here.\n\n- **No full-burst on recovery.** When the penalty ends the bucket refills to\n  `penaltyRefillFraction * capacity` (default `0`), not a full burst, so it does\n  not immediately re-trip the same limit. Set `penaltyRefillFraction: 0.5` for a\n  gentler recovery that still avoids a full burst.\n- **Concurrent calls extend, not ignore.** With `concurrency > 1`, several\n  in-flight requests may each get a `429` with different `Retry-After` values. The\n  effective deadline is the furthest one requested (`max` of all deadlines), so\n  overlapping penalties back off for the longest of them rather than the first.\n\n```ts\ndestroy(): void\n```\n\nFully disposes the bucket: clears all timers and rejects every queued\n`consumeAsync` waiter with a `BucketDestroyedError`. **Idempotent** — safe to call\nmore than once (e.g. in a `finally`). Call this to let a short-lived process exit\nand to release a bucket you are done with.\n\n## Authors\nNurbaki Kasikci - [GitHub](https://github.com/mnkasikci)  - [Twitter](https://twitter.com/mnkasikci)\n\n## Credits\n\nThis package began as a fork of a rate limiter I built while working at\n[Grape Law Firm](https://github.com/grape-law-firm), which was also published as\n[@grapelaw/token-bucket](https://www.npmjs.com/package/@grapelaw/token-bucket)\n([source](https://github.com/grape-law-firm/token-bucket)). It is republished\nhere with their permission. This version adds bug fixes and full test coverage.\nThe original work is MIT-licensed (Copyright © 2024 Grape Law Firm); that notice\nis retained alongside mine in [LICENSE](LICENSE).\n\n## Contribution\nWe welcome contributions to improve this package and encourage users to submit bug reports, feature requests, or any other contributions that can enhance the project. Please follow the guidelines below to contribute:\n1. Report Issues: If you encounter any issues or have suggestions for improvements, please open an issue on [GitHub](https://github.com/mnkasikci/token-bucket/issues) \n2. Pull Requests: You are welcome to [submit Pull Requests](https://github.com/mnkasikci/token-bucket/pulls) (PRs) for bug fixes or new features. Make sure to follow the established coding conventions and explain the purpose of your changes. \n\n\n","readmeFilename":"README.md","_rev":"1-5980649075d5e5a1234134a8c4de0b18"}