/** * Request Deduplication for LiveComponents * * Prevents duplicate requests for the same action with the same parameters. * Useful for preventing race conditions and reducing server load. */ export class RequestDeduplicator { constructor() { this.pendingRequests = new Map(); this.requestCache = new Map(); this.cacheTimeout = 1000; // 1 second cache } /** * Generate request key for deduplication * * @param {string} componentId - Component ID * @param {string} method - Action method name * @param {Object} params - Action parameters * @returns {string} Request key */ generateKey(componentId, method, params) { // Sort params for consistent key generation const sortedParams = Object.keys(params) .sort() .reduce((acc, key) => { acc[key] = params[key]; return acc; }, {}); const paramsString = JSON.stringify(sortedParams); return `${componentId}:${method}:${paramsString}`; } /** * Check if request is already pending * * @param {string} componentId - Component ID * @param {string} method - Action method name * @param {Object} params - Action parameters * @returns {Promise|null} Pending request promise or null */ getPendingRequest(componentId, method, params) { const key = this.generateKey(componentId, method, params); return this.pendingRequests.get(key) || null; } /** * Register pending request * * @param {string} componentId - Component ID * @param {string} method - Action method name * @param {Object} params - Action parameters * @param {Promise} promise - Request promise * @returns {Promise} Request promise */ registerPendingRequest(componentId, method, params, promise) { const key = this.generateKey(componentId, method, params); // Store pending request this.pendingRequests.set(key, promise); // Clean up when request completes promise .then(() => { this.pendingRequests.delete(key); }) .catch(() => { this.pendingRequests.delete(key); }); return promise; } /** * Check if request result is cached * * @param {string} componentId - Component ID * @param {string} method - Action method name * @param {Object} params - Action parameters * @returns {Object|null} Cached result or null */ getCachedResult(componentId, method, params) { const key = this.generateKey(componentId, method, params); const cached = this.requestCache.get(key); if (!cached) { return null; } // Check if cache is still valid const now = Date.now(); if (now - cached.timestamp > this.cacheTimeout) { this.requestCache.delete(key); return null; } return cached.result; } /** * Cache request result * * @param {string} componentId - Component ID * @param {string} method - Action method name * @param {Object} params - Action parameters * @param {Object} result - Request result */ cacheResult(componentId, method, params, result) { const key = this.generateKey(componentId, method, params); this.requestCache.set(key, { result, timestamp: Date.now() }); // Clean up old cache entries this.cleanupCache(); } /** * Clean up expired cache entries */ cleanupCache() { const now = Date.now(); for (const [key, cached] of this.requestCache.entries()) { if (now - cached.timestamp > this.cacheTimeout) { this.requestCache.delete(key); } } } /** * Clear all pending requests */ clearPendingRequests() { this.pendingRequests.clear(); } /** * Clear all cached results */ clearCache() { this.requestCache.clear(); } /** * Clear requests and cache for specific component * * @param {string} componentId - Component ID */ clearComponent(componentId) { // Clear pending requests for (const [key] of this.pendingRequests.entries()) { if (key.startsWith(`${componentId}:`)) { this.pendingRequests.delete(key); } } // Clear cache for (const [key] of this.requestCache.entries()) { if (key.startsWith(`${componentId}:`)) { this.requestCache.delete(key); } } } /** * Get statistics * * @returns {Object} Statistics */ getStats() { return { pendingRequests: this.pendingRequests.size, cachedResults: this.requestCache.size }; } }