limiter.ts
Language TypeScript1.6 KB55 lines
import { sleep as realSleep } from './retry.js'/*** Token bucket rate limiter.** Dependency-free and clock-injectable so it can be tested without real time* passing, and so it runs unchanged in a Cloudflare Worker.*/export class TokenBucket {private tokens: numberprivate lastRefill: numberconstructor(private readonly capacity: number,private readonly refillPerSecond: number,private readonly now: () => number = () => Date.now(),private readonly sleep: (ms: number) => Promise<void> = realSleep,) {if (capacity <= 0 || refillPerSecond <= 0) {throw new Error('TokenBucket requires positive capacity and refill rate')}this.tokens = capacitythis.lastRefill = now()}private refill(): void {const t = this.now()const elapsedSec = (t - this.lastRefill) / 1000if (elapsedSec <= 0) returnthis.tokens = Math.min(this.capacity, this.tokens + elapsedSec * this.refillPerSecond)this.lastRefill = t}/** Resolves when a token is available. Never rejects. */async acquire(): Promise<void> {// Loop rather than compute-once: other callers may drain the bucket while// this one is sleeping.for (;;) {this.refill()if (this.tokens >= 1) {this.tokens -= 1return}const deficit = 1 - this.tokensconst waitMs = Math.ceil((deficit / this.refillPerSecond) * 1000)await this.sleep(Math.max(waitMs, 1))}}/** Test/debug visibility. Not part of the public contract. */get available(): number {this.refill()return this.tokens}}