oss-ratelimit
Version:
Flexible rate limiting library with Redis for TypeScript applications
360 lines (354 loc) • 12.3 kB
TypeScript
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 };