Token Bucket Throttle With Burst Capacity

A token bucket limiter that refills at a steady rate, allows bursts up to capacity, and reports allow and deny decisions with a deterministic clock.

JavascriptData Fetching

Code

export class TokenBucket {
  constructor(options) {
    const { capacity, refillPerSecond, now = () => Date.now() } = options;
    if (!Number.isInteger(capacity) || capacity < 1) {
      throw new RangeError("capacity must be a positive integer");
    }
    if (!Number.isFinite(refillPerSecond) || refillPerSecond <= 0) {
      throw new RangeError("refillPerSecond must be a positive number");
    }
    this.capacity = capacity;
    this.refillPerSecond = refillPerSecond;
    this.now = now;
    this.tokens = capacity;
    this.lastRefill = now();
  }

  get available() {
    this.refill();
    return this.tokens;
  }

  tryConsume(cost = 1) {
    if (!Number.isFinite(cost) || cost <= 0) {
      throw new RangeError("cost must be a positive number");
    }
    if (cost > this.capacity) {
      throw new RangeError(`cost ${cost} exceeds bucket capacity ${this.capacity}`);
    }
    this.refill();
    if (this.tokens < cost) {
      return { allowed: false, remaining: this.tokens };
    }
    this.tokens -= cost;
    return { allowed: true, remaining: this.tokens };
  }

  refill() {
    const t = this.now();
    const elapsedMs = Math.max(0, t - this.lastRefill);
    const gained = (elapsedMs / 1000) * this.refillPerSecond;
    this.tokens = Math.min(this.capacity, this.tokens + gained);
    this.lastRefill = t;
  }
}