/** * Request Deduplication Utility * * Prevents duplicate API calls for the same resource within a time window. * This significantly reduces server load and improves performance. */ interface PendingRequest { promise: Promise; timestamp: number; } class RequestDeduplicator { private pendingRequests = new Map>(); private readonly DEFAULT_TTL = 5000; // 5 seconds default TTL /** * Execute a request with deduplication * * If a request with the same key is already pending and within TTL, * the existing promise is returned instead of making a new request. * * @param key - Unique identifier for the request * @param requestFn - Function that returns a promise for the request * @param ttl - Time-to-live in milliseconds (default: 5000ms) * @returns Promise that resolves with the request result */ async execute( key: string, requestFn: () => Promise, ttl: number = this.DEFAULT_TTL ): Promise { // Check if there's a pending request const pending = this.pendingRequests.get(key); if (pending) { const age = Date.now() - pending.timestamp; // If request is still fresh, reuse it if (age < ttl) { console.log(`[RequestDeduplicator] Reusing pending request: ${key} (age: ${age}ms)`); return pending.promise; } else { // Request is stale, remove it console.log(`[RequestDeduplicator] Stale request removed: ${key} (age: ${age}ms)`); this.pendingRequests.delete(key); } } // Create new request console.log(`[RequestDeduplicator] Creating new request: ${key}`); const promise = requestFn() .finally(() => { // Clean up after request completes this.pendingRequests.delete(key); console.log(`[RequestDeduplicator] Request completed and cleaned up: ${key}`); }); this.pendingRequests.set(key, { promise, timestamp: Date.now(), }); return promise; } /** * Cancel a pending request * * @param key - The request key to cancel */ cancel(key: string): void { if (this.pendingRequests.has(key)) { this.pendingRequests.delete(key); console.log(`[RequestDeduplicator] Request cancelled: ${key}`); } } /** * Clear all pending requests */ clear(): void { const count = this.pendingRequests.size; this.pendingRequests.clear(); console.log(`[RequestDeduplicator] Cleared ${count} pending requests`); } /** * Get count of pending requests */ getPendingCount(): number { return this.pendingRequests.size; } /** * Get all pending request keys (for debugging) */ getPendingKeys(): string[] { return Array.from(this.pendingRequests.keys()); } } // Singleton instance export const requestDeduplicator = new RequestDeduplicator();