zenin-limiter
Version:
Universal rate & throttle limiter middleware for Express, Fastify, and custom handlers
217 lines (203 loc) • 7.64 kB
TypeScript
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 };