UNPKG

@tuanltntu/n8n-nodes-bitrix24

Version:

Comprehensive n8n community node for Bitrix24 API integration with CRM, Tasks, Chat, Telephony, and more

227 lines 8.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LoadOptionsOptimizer = void 0; /** * Utility class to optimize loadOptions performance with smart debouncing and caching * This helps prevent excessive API calls during form input validation * * Vấn đề chính: Mỗi keystroke đều load all fields từ Bitrix24 API * Giải pháp: Smart caching + debouncing + chỉ reload khi thực sự cần thiết */ class LoadOptionsOptimizer { /** * Get cached options if available and not expired */ static getCachedOptions(cacheKey) { const cached = this.cache.get(cacheKey); if (!cached) return null; const now = Date.now(); if (now - cached.timestamp > cached.ttl) { this.cache.delete(cacheKey); return null; } return cached.data; } /** * Set options in cache with TTL */ static setCachedOptions(cacheKey, data, ttl = this.DEFAULT_TTL) { this.cache.set(cacheKey, { data, timestamp: Date.now(), ttl, }); } /** * Generate cache key based on method name and parameters */ static generateCacheKey(methodName, params = {}) { const sortedParams = Object.keys(params) .sort() .map((key) => `${key}:${JSON.stringify(params[key])}`) .join("|"); return `${methodName}:${sortedParams}`; } /** * Clear expired cache entries */ static cleanupExpiredCache() { const now = Date.now(); for (const [key, cached] of this.cache.entries()) { if (now - cached.timestamp > cached.ttl) { this.cache.delete(key); } } } /** * Get last parameters for a method */ static getLastParams(methodName) { return this.lastParams.get(methodName); } /** * Set last parameters for a method */ static setLastParams(methodName, params) { this.lastParams.set(methodName, params); } /** * Check if parameters have changed significantly * Đây là key để fix vấn đề keystroke - chỉ reload khi thực sự cần thiết */ static hasSignificantParamsChange(methodName, newParams) { const lastParams = this.getLastParams(methodName); if (!lastParams) return true; // For field loading, only reload if entityType actually changed // Đây là fix chính: không reload fields nếu chỉ thay đổi text input if (methodName === "getCrmEntityFields") { return lastParams.entityType !== newParams.entityType; } // For other methods, check if any key parameter changed const keys = Object.keys(newParams); for (const key of keys) { if (lastParams[key] !== newParams[key]) { return true; } } return false; } /** * Smart loadOptions method with intelligent caching and debouncing * @param methodName - Name of the loadOptions method * @param params - Parameters for the method * @param loadFunction - The actual loading function * @param options - Configuration options */ static async loadOptionsWithOptimization(methodName, params, loadFunction, options = {}) { const { debounceDelay = this.DEFAULT_DEBOUNCE_DELAY, cacheTTL = this.DEFAULT_TTL, enableCache = true, enableDebounce = true, smartDebounce = true, minParamsChange = true, } = options; const cacheKey = this.generateCacheKey(methodName, params); // Cleanup expired cache periodically if (Math.random() < 0.1) { // 10% chance to cleanup this.cleanupExpiredCache(); } // Check cache first if enabled if (enableCache) { const cached = this.getCachedOptions(cacheKey); if (cached) { console.log(`[LoadOptionsOptimizer] Cache hit for ${methodName}`); return cached; } } // Check if there's already a pending request if (this.pendingRequests.has(cacheKey)) { console.log(`[LoadOptionsOptimizer] Returning pending request for ${methodName}`); return this.pendingRequests.get(cacheKey); } // SMART DEBOUNCING: Only reload if parameters actually changed significantly // Đây là fix chính cho vấn đề keystroke if (enableDebounce && smartDebounce && minParamsChange) { const lastParams = this.getLastParams(methodName); const paramsChanged = this.hasSignificantParamsChange(methodName, params); if (!paramsChanged && lastParams) { console.log(`[LoadOptionsOptimizer] Params unchanged for ${methodName}, returning cached result`); // Return cached result if available, otherwise empty array to avoid API call return this.getCachedOptions(cacheKey) || []; } // Update last params this.setLastParams(methodName, params); } // If debouncing is enabled, delay the request if (enableDebounce) { return new Promise((resolve, reject) => { // Clear existing timer if (this.debounceTimers.has(cacheKey)) { clearTimeout(this.debounceTimers.get(cacheKey)); } // Set new timer const timer = setTimeout(async () => { try { const result = await this.executeLoadFunction(cacheKey, params, loadFunction, enableCache, cacheTTL); resolve(result); } catch (error) { reject(error); } }, debounceDelay); this.debounceTimers.set(cacheKey, timer); }); } // Execute immediately if no debouncing return this.executeLoadFunction(cacheKey, params, loadFunction, enableCache, cacheTTL); } /** * Execute the actual loading function with caching */ static async executeLoadFunction(cacheKey, params, loadFunction, enableCache, cacheTTL) { try { // Create the promise and store it const loadPromise = loadFunction(params); this.pendingRequests.set(cacheKey, loadPromise); const result = await loadPromise; // Cache the result if enabled if (enableCache) { this.setCachedOptions(cacheKey, result, cacheTTL); console.log(`[LoadOptionsOptimizer] Cached result for ${cacheKey}`); } return result; } catch (error) { console.error(`[LoadOptionsOptimizer] Error loading options for ${cacheKey}:`, error); throw error; } finally { // Clean up pending request this.pendingRequests.delete(cacheKey); } } /** * Clear all cache and pending requests */ static clearAll() { this.cache.clear(); this.pendingRequests.clear(); this.lastParams.clear(); // Clear all debounce timers for (const timer of this.debounceTimers.values()) { clearTimeout(timer); } this.debounceTimers.clear(); console.log("[LoadOptionsOptimizer] All cache and timers cleared"); } /** * Clear cache for specific method */ static clearCacheForMethod(methodName) { for (const key of this.cache.keys()) { if (key.startsWith(methodName + ":")) { this.cache.delete(key); } } this.lastParams.delete(methodName); console.log(`[LoadOptionsOptimizer] Cache cleared for method: ${methodName}`); } /** * Get cache statistics */ static getCacheStats() { return { size: this.cache.size, pendingRequests: this.pendingRequests.size, activeTimers: this.debounceTimers.size, lastParams: this.lastParams.size, }; } } exports.LoadOptionsOptimizer = LoadOptionsOptimizer; LoadOptionsOptimizer.cache = new Map(); LoadOptionsOptimizer.pendingRequests = new Map(); LoadOptionsOptimizer.debounceTimers = new Map(); LoadOptionsOptimizer.lastParams = new Map(); // Track last parameters for each method // Default TTL for cache (5 minutes) LoadOptionsOptimizer.DEFAULT_TTL = 5 * 60 * 1000; // Default debounce delay (300ms) LoadOptionsOptimizer.DEFAULT_DEBOUNCE_DELAY = 300; //# sourceMappingURL=LoadOptionsOptimizer.js.map