UNPKG

zenin-limiter

Version:

Universal rate & throttle limiter middleware for Express, Fastify, and custom handlers

217 lines (203 loc) 7.64 kB
import { Request, Response, NextFunction } from 'express'; import { FastifyRequest, FastifyReply } from 'fastify'; import { CanActivate, ExecutionContext } from '@nestjs/common'; interface LimiterConfig { /** * Key generator system for rate limiting. * - keyType: 'ip' | 'user-agent' | 'header:X-API-KEY' | 'path' | 'custom' * - headerName: Used if keyType is a header * - customKeyGenerator: Custom function for key extraction */ keyType?: "ip" | "user-agent" | "header:X-API-KEY" | "path" | "custom"; headerName?: string; customKeyGenerator?: (req: any) => string; /** * Maximum number of allowed actions (requests) within the defined time window. * Can be a number or a function that returns a number based on request context. * Default: 100 */ limit?: number | ((req: any) => number); /** * Duration of the rate limit window in seconds. * Default: 60 * Example: `60` means "limit X requests per 60 seconds". */ windowInSeconds?: number; /** * Rate limiting strategy to use. * - 'fixed': Simple fixed window (default) * - 'sliding': Accurate sliding window * - 'tokenBucket': Smooth burst control */ strategy?: "fixed" | "sliding" | "tokenBucket"; /** * Event hooks for monitoring and debugging. */ onLimitReached?: (key: string, req?: any) => void; onReset?: (key: string) => void; onPass?: (key: string, req?: any) => void; onError?: (error: Error) => void; /** * Enable debug logging for all rate limiting decisions. */ debug?: boolean; /** * Dry run mode - simulate rate limiting without actually blocking requests. */ dryRun?: boolean; /** * Silent mode - log decisions but don't block requests. */ silent?: boolean; /** * Configuration for rate limiter. * This includes settings for memory store, cleanup intervals, and more. * If not provided, defaults will be used. * * Max number of keys (default: 1,000,000) * Calls between cleanups (default: 1000) * Enable per-key metrics (default: false) * Max keys to clean per batch (default: 1000) */ limiterConfig?: RateLimiterConfig; } type RateLimiterConfig = { maxStoreSize?: number; cleanupInterval?: number; enablePerKeyStats?: boolean; maxBatchCleanup?: number; }; interface RateLimitStrategy { isAllowed(key: string, req?: any): boolean | Promise<boolean>; getState?(key: string): RateLimitState | Promise<RateLimitState>; reset?(key: string): void | Promise<void>; } type RateLimitState = { remaining: number; resetAt: number; limit: number; }; /** * Express middleware for rate limiting. */ declare function expressLimiter(config: Partial<LimiterConfig>): (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>>>; declare function fastifyLimiter(config: Partial<LimiterConfig>): (req: FastifyRequest, reply: FastifyReply) => Promise<void>; declare function RateLimit(config: LimiterConfig): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor; declare class NestLimiterGuard implements CanActivate { private readonly config; private limiter; private keyFn; constructor(config: Partial<LimiterConfig>); canActivate(context: ExecutionContext): Promise<boolean>; } declare class RateLimitModule { } declare function universalLimiter(config: Partial<LimiterConfig>): (req: any, res: any, next: () => void) => Promise<any>; /** * Resets the rate limit for a specific key. * @param key The key to reset * @throws Error if key is invalid */ declare function resetKey(key: string): Promise<void>; /** * Resets all rate limit state. */ declare function resetAll(): Promise<void>; /** * Returns current rate limit metrics. * @param key Optional key for per-key stats * @returns Object containing hits and rejections (global or per-key) */ declare function getMetrics(key?: string): Promise<{ hits: number; rejections: number; }>; /** * Checks if a key is allowed based on rate limit. * Uses a min-heap for expirations, LRU for memory capping, and Promise-based locking. * Handles millions of users with hybrid cleanup (on-demand + periodic). * @param key Unique identifier for rate limiting * @param limit Maximum requests allowed in window * @param windowInSeconds Time window in seconds * @param config Optional configuration (maxStoreSize, cleanupInterval, enablePerKeyStats, maxBatchCleanup) * @param now Optional time source for testing (defaults to Date.now) * @returns True if allowed, false if rate limit exceeded * @throws Error if inputs are invalid */ declare function isAllowedMemory(key: string, limit: number, windowInSeconds: number, config?: RateLimiterConfig, now?: () => number): Promise<boolean>; declare class FixedWindowStrategy implements RateLimitStrategy { private config; private maxEntries; private gcStarted; private getLimitFn?; constructor(config: any); private getLimit; private startGC; stopGC(): void; isAllowed(key: string, req?: any): Promise<boolean>; reset(key: string): Promise<void>; getState(key: string): Promise<RateLimitState>; static resetAll(): Promise<void>; } interface RateLimiterStats { totalRequests: number; hits: number; rejections: number; activeKeys: number; memoryUsage?: number; } declare class RateLimiter { private strategy; private config; private stats; constructor(config: Partial<LimiterConfig>, strategy?: RateLimitStrategy); private createStrategy; private getLimit; private callHook; private logDebug; isAllowed(key: string, req?: any): Promise<boolean>; reset(key: string): Promise<void>; getState(key: string): Promise<any>; getStats(): RateLimiterStats; private getActiveKeysCount; } declare class SlidingWindowStrategy implements RateLimitStrategy { private limit; private windowMs; private config; private maxEntries; private gcStarted; private getLimitFn?; constructor(config: any); private startGC; stopGC(): void; private getLimit; isAllowed(key: string, req?: any): Promise<boolean>; getState(key: string, req?: any): Promise<RateLimitState>; reset(key: string): Promise<void>; static resetAll(): Promise<void>; } declare class TokenBucketStrategy implements RateLimitStrategy { private capacity; private refillRate; private config; private maxEntries; private gcStarted; private getLimitFn?; constructor(config: any); private startGC; stopGC(): void; private getLimit; isAllowed(key: string, req?: any): Promise<boolean>; getState(key: string, req?: any): Promise<RateLimitState>; reset(key: string): Promise<void>; static resetAll(): Promise<void>; } declare function applyDefaults(config: Partial<LimiterConfig>): LimiterConfig; interface ValidationError { field: string; message: string; } declare function validateConfig(config: LimiterConfig): ValidationError[]; declare function throwIfInvalid(config: LimiterConfig): void; export { FixedWindowStrategy, type LimiterConfig, NestLimiterGuard, RateLimit, RateLimitModule, type RateLimitState, type RateLimitStrategy, RateLimiter, type RateLimiterConfig, type RateLimiterStats, SlidingWindowStrategy, TokenBucketStrategy, applyDefaults, expressLimiter, fastifyLimiter, getMetrics, isAllowedMemory, resetAll, resetKey, throwIfInvalid, universalLimiter, validateConfig };