@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
JavaScript
"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