import { Func } from '../types.ts'; /** * Token bucket implementation of rate limiting with optional persistence support. * * @example * // Basic usage * const bucket = new RateLimitTokenBucket({ * capacity: 10, * refillIntervalMs: 1000 * }); * * bucket.consume(); * bucket.consume(); * * const rateLimited = async () => { * // 0 ms if tokens are available * // 1000 / 10 = 100 ms if tokens are not available * await bucket.waitAndConsume(); * return 'success'; * } * * @example * // With persistence (e.g., Redis backend) * const bucket = new RateLimitTokenBucket({ * capacity: 100, * refillIntervalMs: 60000, * save: async (state) => { * await redis.set('rate-limit:user:123', JSON.stringify(state)); * }, * load: async () => { * const data = await redis.get('rate-limit:user:123'); * return data ? JSON.parse(data) : undefined; * } * }); * * // Load state from backend before using * await bucket.load(); * * // Check and consume * if (bucket.hasTokens()) { * bucket.consume(); * await bucket.save(); * } */ export declare class RateLimitTokenBucket { readonly capacity: number; readonly refillIntervalMs: number; constructor(config: RateLimitTokenBucket.Config); /** * Whether save and load functions are configured for persistence. */ get isSaveable(): boolean; /** * Get the current state for persistence. * This is the minimal data structure needed to restore the bucket. */ get state(): RateLimitTokenBucket.State; /** * Save the current state using the configured save function. * @throws If no save function is configured */ save(): Promise; /** * Load state from the configured load function. * If the load function returns undefined/null, the bucket state is not modified. * @throws If no load function is configured */ load(): Promise; /** * Check if there are enough tokens available without consuming them. * * @param count - Number of tokens to check for (default: 1) * @returns true if tokens are available, false otherwise * * @example * if (bucket.hasTokens()) { * // We're within rate limit, proceed * bucket.consume(); * } */ hasTokens(count?: number): boolean; reset(): void; get tokens(): number; consume(count?: number): boolean; getWaitTimeMs(count?: number): number; getNextAvailable(count?: number): Date; /** * Get a snapshot of current statistics and state. * * @returns Object containing usage statistics and current state */ get snapshot(): { currentTokens: number; capacity: number; refillIntervalMs: number; totalRequests: number; rejectedRequests: number; successfulRequests: number; rejectionRate: number; totalWaitTime: number; waitCount: number; averageWaitTime: number; uptime: number; createdAt: number; }; /** * Waits for the next token to be available before * allowing the caller to proceed. * * @param onRateLimit - Callback to invoke when the rate limit is exceeded * * @example * const bucket = new RateLimitTokenBucket({ * capacity: 10, * refillIntervalMs: 1000 * }); * * await bucket.waitForToken(() => { * console.log('Rate limit exceeded'); * }); * console.log('Token available'); * * bucket.consume(); * console.log('Token consumed'); */ waitForToken(count?: number, opts?: { onRateLimit?: ((error: RateLimitError, nextAvailable: Date) => void) | undefined; abortController?: AbortController | undefined; jitterFactor?: number | undefined; }): Promise; /** * Waits for tokens to be available and consumes them atomically. * * @param onRateLimit - Callback to invoke when the rate limit is exceeded * * @example * const bucket = new RateLimitTokenBucket({ * capacity: 10, * refillIntervalMs: 1000 * }); * * await bucket.waitAndConsume(() => { * console.log('Rate limit exceeded'); * }); * console.log('Token acquired and consumed'); */ waitAndConsume(count?: number, opts?: { onRateLimit?: ((error: RateLimitError, nextAvailable: Date) => void) | undefined; abortController?: AbortController | undefined; jitterFactor?: number | undefined; }): Promise; } export declare namespace RateLimitTokenBucket { /** * Configuration options for creating a rate limit token bucket. */ interface Config { /** Maximum number of tokens in the bucket */ capacity: number; /** Time in milliseconds required to generate one token */ refillIntervalMs: number; /** Initial state to restore from (for persistence) */ initialState?: State; /** Function to save the current state (for persistence) */ save?: SaveFn; /** Function to load the state (for persistence) */ load?: LoadFn; } /** * The minimal state structure for persistence. */ interface State { /** Current number of tokens */ tokens: number; /** Timestamp of the last refill */ lastRefill: number; /** Usage statistics */ stats?: Stats; } /** * Usage statistics for the token bucket. */ interface Stats { totalRequests: number; rejectedRequests: number; totalWaitTime: number; waitCount: number; createdAt: number; } type SaveFn = (state: State) => void | Promise; type LoadFn = () => State | undefined | null | Promise; } /** * Error thrown when rate limit is exceeded. */ export declare class RateLimitError extends Error { maxCalls: number; constructor(message: string, maxCalls: number); } export declare const isRateLimitError: (error: unknown) => error is RateLimitError; /** * Configuration options for rate limiting behavior when creating a new bucket. * * @template T - The function type being rate limited */ export type RateLimitOptions = { /** Maximum number of calls allowed within the time window */ maxCalls: number; /** Time window in milliseconds for rate limiting (default: 1000) */ windowMs?: number; /** Whether to throw an error when limit is exceeded (default: true) */ throws?: boolean; /** Callback invoked when rate limit is exceeded */ onLimitReached?: (error: RateLimitError, nextAvailable: Date, args: Parameters) => void | Promise; }; /** * Configuration options for rate limiting behavior when using an existing bucket. * * @template T - The function type being rate limited */ export type RateLimitBucketOptions = { /** An existing RateLimitTokenBucket instance to use */ bucket: RateLimitTokenBucket; /** Whether to throw an error when limit is exceeded (default: true) */ throws?: boolean; /** Callback invoked when rate limit is exceeded */ onLimitReached?: (error: RateLimitError, nextAvailable: Date, args: Parameters) => void | Promise; }; /** * Rate limiter that restricts function calls to a specified number per time window. * * Implements a token bucket rate limiting algorithm that enforces limits by either throwing errors or waiting for a token before proceeding. * * @template T - The function type being rate limited * @param fn - Function to apply rate limiting to * @param opts - Rate limiting configuration options or bucket options * @returns Rate-limited version of the original function (async) * * @example * // Throwing rate limiter with options * const rateLimitedFn = rateLimit(fn, { * maxCalls: 10, * windowMs: 1000, * throws: true, * onLimitReached: (error) => { * console.error('Rate limit exceeded:', error.message); * } * }); * * for (let i = 0; i < 10; i++) { * await rateLimitedFn(); * } * * // will throw an error * await rateLimitedFn(); * * @example * // With an existing bucket (useful for persistence) * const bucket = new RateLimitTokenBucket({ * capacity: 10, * refillIntervalMs: 100, * save: async (state) => redis.set('key', JSON.stringify(state)), * load: async () => JSON.parse(await redis.get('key')) * }); * * const rateLimitedFn = rateLimit(fn, { * bucket, * throws: true * }); * * // When bucket.isSaveable is true, load() is called before each check * // and save() is called after each successful consume * await rateLimitedFn(); * * @example * // Waits for token if throws: false * const rateLimitedWithoutThrowing = rateLimit(fn, { * maxCalls: 10, * windowMs: 1000, * throws: false * }); * * for (let i = 0; i < 10; i++) { * await rateLimitedWithoutThrowing(); * } * * // will wait for token, then call fn * await rateLimitedWithoutThrowing(); * */ export declare function rateLimit(fn: T, opts: RateLimitOptions & { throws: false; }): (...args: Parameters) => Promise>; export declare function rateLimit(fn: T, opts: RateLimitOptions & { throws?: true; }): (...args: Parameters) => Promise>; export declare function rateLimit(fn: T, opts: RateLimitBucketOptions & { throws: false; }): (...args: Parameters) => Promise>; export declare function rateLimit(fn: T, opts: RateLimitBucketOptions & { throws?: true; }): (...args: Parameters) => Promise>;