UNPKG

oss-ratelimit

Version:

Flexible rate limiting library with Redis for TypeScript applications

360 lines (354 loc) 12.3 kB
import { EventEmitter } from 'events'; import { RedisClientType } from 'redis'; type RegisterConfigParam$1 = Omit<RatelimitConfig, 'redis'> & { redis?: RedisOptions; envRedisKey?: string; }; interface RateLimitBuilder<TNames extends string> { /** Registers or retrieves a rate limiter instance with the given name and config */ register: (name: TNames, config?: RegisterConfigParam$1) => Promise<Ratelimit>; /** Synchronously gets an initialized rate limiter instance by name */ get: (name: TNames) => Ratelimit; /** Checks if a limiter instance is registered and initialized */ isInitialized: (name: TNames | string) => boolean; /** Closes all Redis clients managed by this registry instance */ close: () => Promise<void>; /** Allows listening to registry events */ on: (eventName: 'redisConnect' | 'redisError' | 'limiterRegister' | 'limiterError' | 'close', listener: (...args: any[]) => void) => RateLimitBuilder<TNames>; /** Allows removing registry event listeners */ off: (eventName: 'redisConnect' | 'redisError' | 'limiterRegister' | 'limiterError' | 'close', listener: (...args: any[]) => void) => RateLimitBuilder<TNames>; /** Attempts to get the managed Redis client promise based on configuration key */ getClient: (config: { redis?: RedisOptions; envRedisKey?: string; }) => Promise<RedisClientType> | undefined; } /** * Initializes a rate limiter registry instance and returns a typed builder API. * @param options Configuration options for the registry instance. * @param options.defaultRedisOptions Default Redis connection options. * @typeparam TNames A string literal type union representing the valid names for limiters in this registry instance (e.g., 'api' | 'webhooks'). Defaults to `string`. */ declare function initRateLimit<TNames extends string = string>(options?: { defaultRedisOptions?: RedisOptions; }): RateLimitBuilder<TNames>; type RegisterConfigParam = Omit<RatelimitConfig, 'redis'> & { redis?: RedisOptions; envRedisKey?: string; }; /** * Options for the limiter initialization utility */ interface InitLimitersOptions<TNames extends string> { /** Registry instance to use for limiter registration */ registry: RateLimitBuilder<TNames>; /** Configuration for each limiter */ configs: Record<TNames, RegisterConfigParam>; /** Callback fired when each limiter is registered */ onRegister?: (name: TNames) => void; /** Callback fired when all limiters are initialized */ onComplete?: () => void; /** Whether to throw on initialization errors (default: true) */ throwOnError?: boolean; /** Whether to log progress (default: true) */ verbose?: boolean; } /** * Registers and initializes multiple rate limiters from a configuration object * * @param options Configuration options for initialization * @returns Promise that resolves to a record of initialized limiters * @throws If any limiter fails to initialize and throwOnError is true * * @example * ```typescript * // Initialize all limiters at once * const limiters = await initializeLimiters({ * registry: rl, * configs: limiterConfigs * }); * * // Use specific limiters * const apiLimiter = limiters.apiPublic; * ``` */ declare function initializeLimiters<TNames extends string>(options: InitLimitersOptions<TNames>): Promise<Record<TNames, Ratelimit>>; /** * Type-safe accessor for limiters that ensures they are initialized * * @param name Name of the limiter to retrieve * @param registry Registry instance containing the limiters * @returns The initialized limiter * @throws If the limiter is not found or not initialized * * @example * ```typescript * // Get a specific limiter * const apiLimiter = getInitializedLimiter('apiPublic', rl); * ``` */ declare function getInitializedLimiter<TNames extends string>(name: TNames, registry: RateLimitBuilder<TNames>): Ratelimit; /** * Create a type-safe accessor function for a specific registry * * @param registry Registry instance containing limiters * @returns A function that retrieves initialized limiters * * @example * ```typescript * // Create a bound getter for your registry * const getLimiter = createLimiterAccessor(rl); * * // Use it to get specific limiters * const apiLimiter = getLimiter('apiPublic'); * ``` */ declare function createLimiterAccessor<TNames extends string>(registry: RateLimitBuilder<TNames>): (name: TNames) => Ratelimit; /** * @package open-ratelimit * A production-ready, open-source rate limiter with multiple algorithms * Inspired by Upstash/ratelimit but with enhanced capabilities */ /** * Base error class for rate limiting operations */ declare class RatelimitError extends Error { constructor(message: string); } /** * Error thrown when Redis connection fails */ declare class RedisConnectionError extends RatelimitError { constructor(message: string); } /** * Error thrown when rate limit is exceeded */ declare class RateLimitExceededError extends RatelimitError { readonly retryAfter: number; readonly identifier: string; constructor(identifier: string, retryAfter: number); } /** * Time window definition using type literals */ type TimeWindow = `${number} ms` | `${number} s` | `${number} m` | `${number} h` | `${number} d`; /** * Parse time window string to milliseconds */ declare const parseTimeWindow: (window: TimeWindow) => number; /** * Base interface for all limiter options */ interface BaseLimiterOptions { limit: number; type: string; } /** * Fixed window algorithm options */ interface FixedWindowOptions extends BaseLimiterOptions { type: 'fixedWindow'; windowMs: number; } /** * Sliding window algorithm options */ interface SlidingWindowOptions extends BaseLimiterOptions { type: 'slidingWindow'; windowMs: number; } /** * Token bucket algorithm options */ interface TokenBucketOptions extends BaseLimiterOptions { type: 'tokenBucket'; refillRate: number; interval: number; } /** * Union type for all limiter algorithms */ type LimiterType = FixedWindowOptions | SlidingWindowOptions | TokenBucketOptions; /** * Create a fixed window limiter configuration */ declare const fixedWindow: (limit: number, window: TimeWindow) => FixedWindowOptions; /** * Create a sliding window limiter configuration */ declare const slidingWindow: (limit: number, window: TimeWindow) => SlidingWindowOptions; /** * Create a token bucket limiter configuration */ declare const tokenBucket: (refillRate: number, interval: TimeWindow, limit: number) => TokenBucketOptions; /** * Redis connection options */ interface RedisOptions { url?: string; host?: string; port?: number; username?: string; password?: string; database?: number; tls?: boolean; connectTimeout?: number; reconnectStrategy?: number | false | ((retries: number, cause: Error) => number | false | Error); } /** * Get or create a Redis client instance */ declare const getRedisClient: (options?: RedisOptions) => Promise<RedisClientType>; /** * Close Redis client if open */ declare const closeRedisClient: () => Promise<void>; /** * Rate limit check response */ interface RatelimitResponse { /** Whether the request is allowed */ success: boolean; /** Maximum number of requests allowed */ limit: number; /** Number of requests remaining in the current window */ remaining: number; /** Timestamp (ms) when the limit resets */ reset: number; /** Seconds until retry is possible (only when success=false) */ retryAfter?: number; /** Current pending requests count (only with analytics=true) */ pending?: number; /** Requests per second (only with analytics=true) */ throughput?: number; } /** * Rate limiter configuration options */ interface RatelimitConfig { /** Redis client instance or connection options */ redis: RedisClientType | RedisOptions; /** Rate limiting algorithm configuration */ limiter: LimiterType; /** Key prefix for Redis */ prefix?: string; /** Whether to collect analytics */ analytics?: boolean; /** Redis operation timeout in ms */ timeout?: number; /** Whether to use in-memory cache as fallback */ ephemeralCache?: boolean; /** TTL for ephemeral cache entries in ms */ ephemeralCacheTTL?: number; /** Whether to fail open (allow requests) when Redis is unavailable */ failOpen?: boolean; /** Whether to disable console logs */ silent?: boolean; } /** * Main rate limiter implementation */ declare class Ratelimit extends EventEmitter { private redis; private limiter; private prefix; private analytics; private timeout; private ephemeralCache?; private failOpen; private silent; private scripts; /** * Create a new rate limiter instance */ constructor(config: RatelimitConfig); /** * Initialize Lua scripts for each algorithm */ private initScripts; /** * Get the Redis client with timeout protection */ private getRedis; /** * Apply rate limit for an identifier */ limit(identifier: string): Promise<RatelimitResponse>; /** * Apply rate limit using appropriate algorithm */ private applyLimit; /** * Apply sliding window rate limit */ private applySlidingWindowLimit; /** * Apply fixed window rate limit */ private applyFixedWindowLimit; /** * Apply token bucket rate limit */ private applyTokenBucketLimit; /** * Apply rate limit using ephemeral cache (for fallback) */ private applyEphemeralLimit; /** * Block until rate limit allows or max wait time is reached */ block(identifier: string, options?: { maxWaitMs?: number; maxAttempts?: number; retryDelayMs?: number; }): Promise<RatelimitResponse>; /** * Reset rate limit for an identifier */ reset(identifier: string): Promise<boolean>; /** * Get current rate limit statistics */ getStats(identifier: string): Promise<{ used: number; remaining: number; limit: number; reset: number; }>; /** * Check if rate limit is exceeded without consuming a token */ check(identifier: string): Promise<boolean>; /** * Clean up resources */ close(): Promise<void>; } /** * Creates a single rate limiter instance with its own Redis client connection. * Does not use the shared client management features of the registry. * Consider using `createSingletonRateLimiter` with appropriate Redis options instead * if client reuse is desired. * * @deprecated Consider using createSingletonRateLimiter with the registry for better client management. */ declare const createRateLimiter: (config: Omit<RatelimitConfig, "redis"> & { redis?: RedisOptions; }) => Promise<Ratelimit>; declare const _default: { Ratelimit: typeof Ratelimit; createRateLimiter: (config: Omit<RatelimitConfig, "redis"> & { redis?: RedisOptions; }) => Promise<Ratelimit>; fixedWindow: (limit: number, window: TimeWindow) => FixedWindowOptions; slidingWindow: (limit: number, window: TimeWindow) => SlidingWindowOptions; tokenBucket: (refillRate: number, interval: TimeWindow, limit: number) => TokenBucketOptions; parseTimeWindow: (window: TimeWindow) => number; getRedisClient: (options?: RedisOptions) => Promise<RedisClientType>; closeRedisClient: () => Promise<void>; RatelimitError: typeof RatelimitError; RedisConnectionError: typeof RedisConnectionError; RateLimitExceededError: typeof RateLimitExceededError; }; export { type FixedWindowOptions, type InitLimitersOptions, type LimiterType, type RateLimitBuilder, RateLimitExceededError, Ratelimit, type RatelimitConfig, RatelimitError, type RatelimitResponse, RedisConnectionError, type RedisOptions, type RegisterConfigParam, type SlidingWindowOptions, type TimeWindow, type TokenBucketOptions, closeRedisClient, createLimiterAccessor, createRateLimiter, _default as default, fixedWindow, getInitializedLimiter, getRedisClient, initRateLimit, initializeLimiters, parseTimeWindow, slidingWindow, tokenBucket };