{"id":18374676,"url":"https://github.com/jupiterone/hierarchical-token-bucket","last_synced_at":"2025-08-07T21:19:22.424Z","repository":{"id":40478505,"uuid":"488391030","full_name":"JupiterOne/hierarchical-token-bucket","owner":"JupiterOne","description":null,"archived":false,"fork":false,"pushed_at":"2024-01-02T19:16:23.000Z","size":84,"stargazers_count":0,"open_issues_count":3,"forks_count":3,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-02-15T22:13:24.885Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JupiterOne.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-05-03T23:21:30.000Z","updated_at":"2022-05-05T14:54:53.000Z","dependencies_parsed_at":"2023-02-16T09:30:24.090Z","dependency_job_id":"abb0ca7a-4d16-443c-b511-9e5ab8197472","html_url":"https://github.com/JupiterOne/hierarchical-token-bucket","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JupiterOne%2Fhierarchical-token-bucket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JupiterOne%2Fhierarchical-token-bucket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JupiterOne%2Fhierarchical-token-bucket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JupiterOne%2Fhierarchical-token-bucket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JupiterOne","download_url":"https://codeload.github.com/JupiterOne/hierarchical-token-bucket/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248332170,"owners_count":21086041,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T00:15:37.122Z","updated_at":"2025-04-11T02:46:51.167Z","avatar_url":"https://github.com/JupiterOne.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @jupiterone/hierarchical-token-bucket\n\nThis project exports a `HierarchicalTokenBucket` class that can support nested \nrate limits. This should be used in client-side rate limiting strategies in \norder to honor rate limits that are composed in a nested structure. One such \nexample is AWS API rate limits, which can be limited by an account-level, \nservice-level, region-level, or API-level bucket.\n\nThe token bucket returns a numeric `timeToWaitInMs` from its primary interface, \n`.take()`. This allows the token bucket to remain synchronous, so it does not \nblock other requests. Each caller is expected to honor the `timeToWaitInMs` \nreturned from `.take()`.\n\nReturning a `timeToWaitInMs` when the bucket is already exhausted, rather than \nsimply preventing the caller from `take()`ing a token and forcing it to re-call, \nessentially creates a lightweight FIFO queue where each caller invokes the \ninterface just one time.\n\nUsage:\n\n```ts\nimport { HierarchicalTokenBucket } from '@jupiterone/hierarchical-token-bucket';\n\nasync function sleep(ms: number) {\n  return new Promise(r =\u003e setTimeout(r, ms));\n}\n\nconst parentBucket = new HierarchicalTokenBucket({\n  maximumCapacity: 100,\n  refillRate: 10\n});\n\nconst childBucket = parentBucket.child({\n  maximumCapacity: 10,\n  refillRate: 1,\n});\n\nconst timeToWaitInMs = childBucket.take();\n\nawait sleep(timeToWaitInMs);\nawait fetch('https://my.rate-limited.resource');\n```\n\nAlternately, this can be simplified by invoking `withTokenBucket`.\n\n```ts\nimport { \n  HierarchicalTokenBucket,\n  withTokenBucket\n} from '@jupiterone/hierarchical-token-bucket';\n\nconst tokenBucket = new HierarchicalTokenBucket({\n  maximumCapacity: 100,\n  refillRate: 10\n});\n\nconst cb = () =\u003e fetch('https://my.rate-limited.resource');\nawait withTokenBucket(tokenBucket, cb);\n```\nOne can also specify a child without passing options, in which case `maximumCapacity`\nand `refillRate` are inherited from the parent bucket. This means that the child\nbucket will not limit usage any more than the parent bucket would, but it might be\nuseful when instrumenting code for optional limiting. \n\n## Class: `HierarchicalTokenBucket`\n\n### `new HierarchicalTokenBucket(params)`\n\n- `params.maximumCapacity` {number} The total number of requests allowed when \n  the bucket is full.\n- `params.refillRate` {number} The number of requests to add to the bucket per \n  second. The bucket will never exceed `maximumCapacity` requests.\n\n### `tokenBucket.take()`\n\nTakes a token from this and all parent token buckets. Returns the number of\nmilliseconds that must elapse before attempting to redeem the token.\nReturns 0 if the token can be redeemed immediately.\n\nConsumers need only call this function once, but may need to wait before\nredeeming their token.\n\n```ts\nconst timeToWaitInMs = hierarchicalTokenBucket.take();\n\nif (timeToWaitInMs \u003e 0) {\n  await new Promise(r =\u003e setTimeout(r, timeToWaitInMs));\n}\n\nawait fetch('https://my.target.host/that/supports/throttling')\n```\n\n### `tokenBucket.metadata`\n\nReturns the token bucket metadata, including\n  - options.maximumCapacity\n  - options.refillRate\n  - metrics.firstTakeTimestamp\n  - metrics.takeCount\n\nThis metadata can be used to adjust the token bucket `options` in the event\nthat a rate-limited request is encountered. For example:\n\n```ts\ntry {\n  const timeToWaitInMs = tokenBucket.take();\n  await sleep(timeToWaitInMs);\n  await client.request();\n} catch (err) {\n  if (isRateLimitError(err)) {\n    const { options, metrics } = tokenBucket.metadata;\n    logger.warn({\n      maximumCapacity: options.maximumCapacity,\n      refillRate: options.refillRate,\n      firstTakeTimestamp: options.firstTakeTimestamp,\n      takeCount: metrics.takeCount,\n    }, 'Encountered rate limited request. Operator should adjust token bucket maximumCapacity or refillRate.');\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjupiterone%2Fhierarchical-token-bucket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjupiterone%2Fhierarchical-token-bucket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjupiterone%2Fhierarchical-token-bucket/lists"}