/** * Token-bucket rate limiter with hierarchical key support. * * Keys are structured as `method` (global) or `method:channel` (per-channel). * Each bucket tracks tokens, last refill timestamp, and optional backoff window * set after a 429 response. * * The limiter is single-threaded (JS event loop), no locks needed. * * Usage: * ``` * const rl = new TokenBucketRateLimiter({ globalCapacity: 20, globalRefillPerSec: 20/60 }); * await rl.acquire('chat.postMessage', 'C123'); * ``` * * Default config values are conservative (40-60% of Slack Tier 3 limits). * Override via constructor options or CORTEX_SLACK_RL_* env vars. */ export declare class TokenBucketRateLimiter { private readonly globalCapacity; private readonly globalRefillPerSec; private readonly perChannelCapacity; private readonly perChannelRefillPerSec; private readonly cleanupIntervalMs; /** Per-key bucket state */ private buckets; /** Timer handle for periodic cleanup */ private cleanupTimer; constructor(opts?: { globalCapacity?: number; globalRefillPerSec?: number; perChannelCapacity?: number; perChannelRefillPerSec?: number; cleanupIntervalMs?: number; }); /** * Non-blocking token acquisition. * Returns `{ ok: true }` on success, or `{ ok: false, retryAfterMs }` if no * token is available (caller should wait `retryAfterMs` before retrying). */ tryAcquire(method: string, channel?: string): { ok: boolean; retryAfterMs?: number; }; /** * Blocking token acquisition. Resolves when a token is available. * Uses polling with short intervals so it plays well with the event loop. */ acquire(method: string, channel?: string): Promise; /** * Report that a 429 was received from the API. * Sets a backoff window for the given method (and optionally channel), * during which no tokens will be granted. */ reportThrottled(method: string, channel?: string, retryAfterSec?: number): void; /** * Dispose the rate limiter: clear all state and stop the cleanup timer. */ dispose(): void; /** Exposed for testing: get internal bucket count */ _bucketCount(): number; /** Exposed for testing: return true if backoff is active for a key */ _isBackedOff(key: string): boolean; /** Exposed for testing: simulate time passing by running cleanup + advancing state */ _advanceTime(ms: number): void; private _keys; /** * Try to consume one token from the bucket identified by `key`. * Performs refill before checking. */ private _tryConsume; /** Refund one token (used when channel bucket rejects but global was consumed). */ private _refund; private _getOrCreate; private _refill; /** * Determine capacity for a key. * Keys starting with `__global__` use global capacity; others use per-channel. */ private _capacityForKey; /** * Determine refill rate for a key. * Keys starting with `__global__` use global refill rate; others use per-channel. */ private _refillPerSecForKey; /** * Remove buckets that haven't been touched in >5 minutes. * Called periodically by the cleanup timer. */ private _cleanup; private _sleep; }