UNPKG

ts-rate-limiter

Version:

High-performance, flexible rate limiting for TypeScript and Bun

107 lines (85 loc) 3.05 kB
import type { MemoryStorageOptions, StorageProvider } from '../types'; export declare class MemoryStorage implements StorageProvider { private records: Map<string, { count: number, resetTime: number }> private timestamps: Map<string, number[]> private cleanupTimer: NodeJS.Timeout | null private enableAutoCleanup: boolean private cleanupIntervalMs: number constructor(options?: MemoryStorageOptions) { this.records = new Map() this.timestamps = new Map() const defaultConfig = config.memoryStorage || {} this.enableAutoCleanup = options?.enableAutoCleanup ?? defaultConfig.enableAutoCleanup ?? false this.cleanupIntervalMs = options?.cleanupIntervalMs ?? defaultConfig.cleanupIntervalMs ?? 60 * 1000 this.cleanupTimer = null if (this.enableAutoCleanup) { this.startCleanupTimer() } } async increment(key: string, windowMs: number): Promise<{ count: number, resetTime: number }> { const now = Date.now() const record = this.records.get(key) if (!record || now > record.resetTime) { const newRecord = { count: 1, resetTime: now + windowMs, } this.records.set(key, newRecord) this.timestamps.set(key, [now]) return newRecord } record.count += 1 const timestamps = this.timestamps.get(key) || [] timestamps.push(now) this.timestamps.set(key, timestamps) return record } async reset(key: string): Promise<void> { this.records.delete(key) this.timestamps.delete(key) } async getCount(key: string): Promise<number> { const record = this.records.get(key) return record?.count || 0 } async getSlidingWindowCount(key: string, windowMs: number): Promise<number> { const timestamps = this.timestamps.get(key) || [] const now = Date.now() const windowStart = now - windowMs return timestamps.filter(time => time > windowStart).length } async batchIncrement(keys: string[], windowMs: number): Promise<Map<string, { count: number, resetTime: number }>> { const results = new Map<string, { count: number, resetTime: number }>() for (const key of keys) { const result = await this.increment(key, windowMs) results.set(key, result) } return results } cleanExpired(): void { const now = Date.now() for (const [key, record] of this.records.entries()) { if (now > record.resetTime) { this.records.delete(key) } } const maxAge = now - 3600000 for (const [key, timestamps] of this.timestamps.entries()) { const filtered = timestamps.filter(time => time > maxAge) if (filtered.length === 0) { this.timestamps.delete(key) } else if (filtered.length !== timestamps.length) { this.timestamps.set(key, filtered) } } } dispose(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer) } } private startCleanupTimer(): void { this.cleanupTimer = setInterval(() => this.cleanExpired(), this.cleanupIntervalMs) } }