@forcecalendar/core 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/calendar/Calendar.js +715 -0
- package/core/calendar/DateUtils.js +553 -0
- package/core/conflicts/ConflictDetector.js +517 -0
- package/core/events/Event.js +914 -0
- package/core/events/EventStore.js +1198 -0
- package/core/events/RRuleParser.js +420 -0
- package/core/events/RecurrenceEngine.js +382 -0
- package/core/ics/ICSHandler.js +389 -0
- package/core/ics/ICSParser.js +475 -0
- package/core/performance/AdaptiveMemoryManager.js +333 -0
- package/core/performance/LRUCache.js +118 -0
- package/core/performance/PerformanceOptimizer.js +523 -0
- package/core/search/EventSearch.js +476 -0
- package/core/state/StateManager.js +546 -0
- package/core/timezone/TimezoneDatabase.js +294 -0
- package/core/timezone/TimezoneManager.js +419 -0
- package/core/types.js +366 -0
- package/package.json +11 -9
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PerformanceOptimizer - Optimizes calendar operations for large datasets
|
|
3
|
+
* Includes caching, lazy loading, and batch processing with adaptive memory management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { LRUCache } from './LRUCache.js';
|
|
7
|
+
import { AdaptiveMemoryManager } from './AdaptiveMemoryManager.js';
|
|
8
|
+
|
|
9
|
+
export class PerformanceOptimizer {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
// Configuration
|
|
12
|
+
this.config = {
|
|
13
|
+
enableCache: true,
|
|
14
|
+
cacheCapacity: 500,
|
|
15
|
+
maxIndexDays: 365,
|
|
16
|
+
batchSize: 100,
|
|
17
|
+
enableMetrics: true,
|
|
18
|
+
cleanupInterval: 3600000, // 1 hour in ms
|
|
19
|
+
maxIndexAge: 30 * 24 * 60 * 60 * 1000, // 30 days in ms
|
|
20
|
+
enableAdaptiveMemory: true, // Enable adaptive memory management
|
|
21
|
+
...config
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Caches with initial capacities
|
|
25
|
+
this.eventCache = new LRUCache(this.config.cacheCapacity);
|
|
26
|
+
this.queryCache = new LRUCache(Math.floor(this.config.cacheCapacity / 2));
|
|
27
|
+
this.dateRangeCache = new LRUCache(Math.floor(this.config.cacheCapacity / 4));
|
|
28
|
+
|
|
29
|
+
// Adaptive memory manager
|
|
30
|
+
if (this.config.enableAdaptiveMemory) {
|
|
31
|
+
this.memoryManager = new AdaptiveMemoryManager({
|
|
32
|
+
checkInterval: 30000,
|
|
33
|
+
memoryThreshold: 0.75,
|
|
34
|
+
criticalThreshold: 0.90
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Register caches with memory manager
|
|
38
|
+
this.memoryManager.registerCache('events', this.eventCache, {
|
|
39
|
+
priority: 3, // Highest priority
|
|
40
|
+
initialCapacity: this.config.cacheCapacity,
|
|
41
|
+
minCapacity: 50,
|
|
42
|
+
maxCapacity: 2000
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.memoryManager.registerCache('queries', this.queryCache, {
|
|
46
|
+
priority: 2,
|
|
47
|
+
initialCapacity: Math.floor(this.config.cacheCapacity / 2),
|
|
48
|
+
minCapacity: 25,
|
|
49
|
+
maxCapacity: 1000
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
this.memoryManager.registerCache('dateRanges', this.dateRangeCache, {
|
|
53
|
+
priority: 1,
|
|
54
|
+
initialCapacity: Math.floor(this.config.cacheCapacity / 4),
|
|
55
|
+
minCapacity: 10,
|
|
56
|
+
maxCapacity: 500
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Lazy loading tracking
|
|
61
|
+
this.lazyIndexes = new Map(); // eventId -> Set of date strings
|
|
62
|
+
this.pendingIndexes = new Map(); // eventId -> Promise
|
|
63
|
+
|
|
64
|
+
// Batch processing
|
|
65
|
+
this.batchQueue = [];
|
|
66
|
+
this.batchTimer = null;
|
|
67
|
+
this.batchCallbacks = [];
|
|
68
|
+
|
|
69
|
+
// Performance metrics
|
|
70
|
+
this.metrics = {
|
|
71
|
+
operations: {},
|
|
72
|
+
averageTimes: {},
|
|
73
|
+
slowQueries: []
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Cleanup timer
|
|
77
|
+
this.cleanupTimer = null;
|
|
78
|
+
if (this.config.cleanupInterval > 0) {
|
|
79
|
+
this.startCleanupTimer();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Measure operation performance
|
|
85
|
+
* @param {string} operation - Operation name
|
|
86
|
+
* @param {Function} fn - Function to measure
|
|
87
|
+
* @returns {*} Function result
|
|
88
|
+
*/
|
|
89
|
+
measure(operation, fn) {
|
|
90
|
+
if (!this.config.enableMetrics) {
|
|
91
|
+
return fn();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const start = performance.now();
|
|
95
|
+
try {
|
|
96
|
+
const result = fn();
|
|
97
|
+
const duration = performance.now() - start;
|
|
98
|
+
this.recordMetric(operation, duration);
|
|
99
|
+
return result;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const duration = performance.now() - start;
|
|
102
|
+
this.recordMetric(operation, duration, true);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Measure async operation performance
|
|
109
|
+
* @param {string} operation - Operation name
|
|
110
|
+
* @param {Function} fn - Async function to measure
|
|
111
|
+
* @returns {Promise<*>} Function result
|
|
112
|
+
*/
|
|
113
|
+
async measureAsync(operation, fn) {
|
|
114
|
+
if (!this.config.enableMetrics) {
|
|
115
|
+
return await fn();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const start = performance.now();
|
|
119
|
+
try {
|
|
120
|
+
const result = await fn();
|
|
121
|
+
const duration = performance.now() - start;
|
|
122
|
+
this.recordMetric(operation, duration);
|
|
123
|
+
return result;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const duration = performance.now() - start;
|
|
126
|
+
this.recordMetric(operation, duration, true);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Record performance metric
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
recordMetric(operation, duration, isError = false) {
|
|
136
|
+
if (!this.metrics.operations[operation]) {
|
|
137
|
+
this.metrics.operations[operation] = {
|
|
138
|
+
count: 0,
|
|
139
|
+
totalTime: 0,
|
|
140
|
+
errors: 0,
|
|
141
|
+
min: Infinity,
|
|
142
|
+
max: 0
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const metric = this.metrics.operations[operation];
|
|
147
|
+
metric.count++;
|
|
148
|
+
metric.totalTime += duration;
|
|
149
|
+
metric.min = Math.min(metric.min, duration);
|
|
150
|
+
metric.max = Math.max(metric.max, duration);
|
|
151
|
+
|
|
152
|
+
if (isError) {
|
|
153
|
+
metric.errors++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Update average
|
|
157
|
+
this.metrics.averageTimes[operation] = metric.totalTime / metric.count;
|
|
158
|
+
|
|
159
|
+
// Track slow queries
|
|
160
|
+
if (duration > 100) {
|
|
161
|
+
this.metrics.slowQueries.push({
|
|
162
|
+
operation,
|
|
163
|
+
duration,
|
|
164
|
+
timestamp: new Date(),
|
|
165
|
+
isError
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Keep only last 100 slow queries
|
|
169
|
+
if (this.metrics.slowQueries.length > 100) {
|
|
170
|
+
this.metrics.slowQueries.shift();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get performance metrics
|
|
177
|
+
* @returns {Object} Performance metrics
|
|
178
|
+
*/
|
|
179
|
+
getMetrics() {
|
|
180
|
+
const summary = {
|
|
181
|
+
cacheStats: {
|
|
182
|
+
event: this.eventCache.getStats(),
|
|
183
|
+
query: this.queryCache.getStats(),
|
|
184
|
+
dateRange: this.dateRangeCache.getStats()
|
|
185
|
+
},
|
|
186
|
+
operations: {},
|
|
187
|
+
slowestOperations: [],
|
|
188
|
+
recentSlowQueries: this.metrics.slowQueries.slice(-10),
|
|
189
|
+
memoryManagement: this.memoryManager ? this.memoryManager.getStats() : null
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Process operations
|
|
193
|
+
for (const [op, data] of Object.entries(this.metrics.operations)) {
|
|
194
|
+
summary.operations[op] = {
|
|
195
|
+
count: data.count,
|
|
196
|
+
avgTime: `${(data.totalTime / data.count).toFixed(2)}ms`,
|
|
197
|
+
minTime: `${data.min.toFixed(2)}ms`,
|
|
198
|
+
maxTime: `${data.max.toFixed(2)}ms`,
|
|
199
|
+
totalTime: `${data.totalTime.toFixed(2)}ms`,
|
|
200
|
+
errors: data.errors,
|
|
201
|
+
errorRate: `${((data.errors / data.count) * 100).toFixed(2)}%`
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Find slowest operations
|
|
206
|
+
summary.slowestOperations = Object.entries(this.metrics.averageTimes)
|
|
207
|
+
.sort((a, b) => b[1] - a[1])
|
|
208
|
+
.slice(0, 5)
|
|
209
|
+
.map(([op, time]) => ({
|
|
210
|
+
operation: op,
|
|
211
|
+
avgTime: `${time.toFixed(2)}ms`
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
return summary;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if event should use lazy indexing
|
|
219
|
+
* @param {import('../events/Event.js').Event} event - Event to check
|
|
220
|
+
* @returns {boolean} True if should use lazy indexing
|
|
221
|
+
*/
|
|
222
|
+
shouldUseLazyIndexing(event) {
|
|
223
|
+
const daySpan = Math.ceil(
|
|
224
|
+
(event.end - event.start) / (24 * 60 * 60 * 1000)
|
|
225
|
+
);
|
|
226
|
+
return daySpan > this.config.maxIndexDays;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create lazy index markers for large events
|
|
231
|
+
* @param {import('../events/Event.js').Event} event - Event to index
|
|
232
|
+
* @returns {Object} Index boundaries
|
|
233
|
+
*/
|
|
234
|
+
createLazyIndexMarkers(event) {
|
|
235
|
+
const markers = {
|
|
236
|
+
eventId: event.id,
|
|
237
|
+
start: event.start,
|
|
238
|
+
end: event.end,
|
|
239
|
+
indexed: new Set(),
|
|
240
|
+
pending: false
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Index first and last month only initially
|
|
244
|
+
const startMonth = new Date(event.start.getFullYear(), event.start.getMonth(), 1);
|
|
245
|
+
const endMonth = new Date(event.end.getFullYear(), event.end.getMonth(), 1);
|
|
246
|
+
|
|
247
|
+
markers.indexed.add(this.getMonthKey(startMonth));
|
|
248
|
+
if (this.getMonthKey(startMonth) !== this.getMonthKey(endMonth)) {
|
|
249
|
+
markers.indexed.add(this.getMonthKey(endMonth));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
this.lazyIndexes.set(event.id, markers);
|
|
253
|
+
return markers;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Expand lazy index for a specific date range
|
|
258
|
+
* @param {string} eventId - Event ID
|
|
259
|
+
* @param {Date} rangeStart - Start of range to index
|
|
260
|
+
* @param {Date} rangeEnd - End of range to index
|
|
261
|
+
* @returns {Promise<Set<string>>} Indexed date strings
|
|
262
|
+
*/
|
|
263
|
+
async expandLazyIndex(eventId, rangeStart, rangeEnd) {
|
|
264
|
+
const markers = this.lazyIndexes.get(eventId);
|
|
265
|
+
if (!markers) {
|
|
266
|
+
return new Set();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check if already pending
|
|
270
|
+
if (markers.pending) {
|
|
271
|
+
return this.pendingIndexes.get(eventId);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
markers.pending = true;
|
|
275
|
+
|
|
276
|
+
const promise = new Promise((resolve) => {
|
|
277
|
+
// Simulate async indexing (in real app, could be in worker)
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
const indexed = new Set();
|
|
280
|
+
const current = new Date(rangeStart);
|
|
281
|
+
|
|
282
|
+
while (current <= rangeEnd) {
|
|
283
|
+
const dateStr = current.toDateString();
|
|
284
|
+
if (!markers.indexed.has(dateStr)) {
|
|
285
|
+
indexed.add(dateStr);
|
|
286
|
+
markers.indexed.add(dateStr);
|
|
287
|
+
}
|
|
288
|
+
current.setDate(current.getDate() + 1);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
markers.pending = false;
|
|
292
|
+
this.pendingIndexes.delete(eventId);
|
|
293
|
+
resolve(indexed);
|
|
294
|
+
}, 0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
this.pendingIndexes.set(eventId, promise);
|
|
298
|
+
return promise;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get month key for date
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
getMonthKey(date) {
|
|
306
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Cache event with TTL
|
|
311
|
+
* @param {string} key - Cache key
|
|
312
|
+
* @param {*} value - Value to cache
|
|
313
|
+
* @param {string} cacheType - Type of cache to use
|
|
314
|
+
*/
|
|
315
|
+
cache(key, value, cacheType = 'event') {
|
|
316
|
+
if (!this.config.enableCache) return;
|
|
317
|
+
|
|
318
|
+
let cache;
|
|
319
|
+
let cacheManagerName;
|
|
320
|
+
|
|
321
|
+
switch (cacheType) {
|
|
322
|
+
case 'event':
|
|
323
|
+
cache = this.eventCache;
|
|
324
|
+
cacheManagerName = 'events';
|
|
325
|
+
break;
|
|
326
|
+
case 'query':
|
|
327
|
+
cache = this.queryCache;
|
|
328
|
+
cacheManagerName = 'queries';
|
|
329
|
+
break;
|
|
330
|
+
case 'dateRange':
|
|
331
|
+
cache = this.dateRangeCache;
|
|
332
|
+
cacheManagerName = 'dateRanges';
|
|
333
|
+
break;
|
|
334
|
+
default:
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
cache.put(key, value);
|
|
339
|
+
|
|
340
|
+
// Update access time in memory manager
|
|
341
|
+
if (this.memoryManager) {
|
|
342
|
+
this.memoryManager.touchCache(cacheManagerName);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get from cache
|
|
348
|
+
* @param {string} key - Cache key
|
|
349
|
+
* @param {string} cacheType - Type of cache
|
|
350
|
+
* @returns {*} Cached value or undefined
|
|
351
|
+
*/
|
|
352
|
+
getFromCache(key, cacheType = 'event') {
|
|
353
|
+
if (!this.config.enableCache) return undefined;
|
|
354
|
+
|
|
355
|
+
let result;
|
|
356
|
+
let cacheManagerName;
|
|
357
|
+
|
|
358
|
+
switch (cacheType) {
|
|
359
|
+
case 'event':
|
|
360
|
+
result = this.eventCache.get(key);
|
|
361
|
+
cacheManagerName = 'events';
|
|
362
|
+
break;
|
|
363
|
+
case 'query':
|
|
364
|
+
result = this.queryCache.get(key);
|
|
365
|
+
cacheManagerName = 'queries';
|
|
366
|
+
break;
|
|
367
|
+
case 'dateRange':
|
|
368
|
+
result = this.dateRangeCache.get(key);
|
|
369
|
+
cacheManagerName = 'dateRanges';
|
|
370
|
+
break;
|
|
371
|
+
default:
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Update access time on cache hit
|
|
376
|
+
if (result !== undefined && this.memoryManager) {
|
|
377
|
+
this.memoryManager.touchCache(cacheManagerName);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Invalidate caches for an event
|
|
385
|
+
* @param {string} eventId - Event ID
|
|
386
|
+
*/
|
|
387
|
+
invalidateEventCaches(eventId) {
|
|
388
|
+
// Remove from event cache
|
|
389
|
+
this.eventCache.delete(eventId);
|
|
390
|
+
|
|
391
|
+
// Clear query cache (conservative approach)
|
|
392
|
+
// In production, track which queries include this event
|
|
393
|
+
this.queryCache.clear();
|
|
394
|
+
this.dateRangeCache.clear();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Batch operation for efficiency
|
|
399
|
+
* @param {Function} operation - Operation to batch
|
|
400
|
+
* @returns {Promise} Batch result
|
|
401
|
+
*/
|
|
402
|
+
batch(operation) {
|
|
403
|
+
return new Promise((resolve, reject) => {
|
|
404
|
+
this.batchQueue.push(operation);
|
|
405
|
+
this.batchCallbacks.push({ resolve, reject });
|
|
406
|
+
|
|
407
|
+
if (this.batchQueue.length >= this.config.batchSize) {
|
|
408
|
+
this.processBatch();
|
|
409
|
+
} else if (!this.batchTimer) {
|
|
410
|
+
// Process batch after 10ms if not full
|
|
411
|
+
this.batchTimer = setTimeout(() => this.processBatch(), 10);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Process batched operations
|
|
418
|
+
* @private
|
|
419
|
+
*/
|
|
420
|
+
processBatch() {
|
|
421
|
+
if (this.batchTimer) {
|
|
422
|
+
clearTimeout(this.batchTimer);
|
|
423
|
+
this.batchTimer = null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (this.batchQueue.length === 0) return;
|
|
427
|
+
|
|
428
|
+
const operations = this.batchQueue.splice(0);
|
|
429
|
+
const callbacks = this.batchCallbacks.splice(0);
|
|
430
|
+
|
|
431
|
+
// Process all operations
|
|
432
|
+
const results = [];
|
|
433
|
+
const errors = [];
|
|
434
|
+
|
|
435
|
+
operations.forEach((op, index) => {
|
|
436
|
+
try {
|
|
437
|
+
results[index] = op();
|
|
438
|
+
} catch (error) {
|
|
439
|
+
errors[index] = error;
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Resolve callbacks
|
|
444
|
+
callbacks.forEach((callback, index) => {
|
|
445
|
+
if (errors[index]) {
|
|
446
|
+
callback.reject(errors[index]);
|
|
447
|
+
} else {
|
|
448
|
+
callback.resolve(results[index]);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Start cleanup timer for old indexes
|
|
455
|
+
* @private
|
|
456
|
+
*/
|
|
457
|
+
startCleanupTimer() {
|
|
458
|
+
this.cleanupTimer = setInterval(() => {
|
|
459
|
+
this.cleanupOldIndexes();
|
|
460
|
+
}, this.config.cleanupInterval);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Clean up old indexes
|
|
465
|
+
* @private
|
|
466
|
+
*/
|
|
467
|
+
cleanupOldIndexes() {
|
|
468
|
+
const now = Date.now();
|
|
469
|
+
const maxAge = this.config.maxIndexAge;
|
|
470
|
+
|
|
471
|
+
// Clean up lazy indexes for events that are too old
|
|
472
|
+
for (const [eventId, markers] of this.lazyIndexes) {
|
|
473
|
+
if (markers.end.getTime() < now - maxAge) {
|
|
474
|
+
this.lazyIndexes.delete(eventId);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Clean up slow query log
|
|
479
|
+
if (this.metrics.slowQueries.length > 100) {
|
|
480
|
+
this.metrics.slowQueries = this.metrics.slowQueries.slice(-100);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Optimize query by checking cache first
|
|
486
|
+
* @param {string} queryKey - Unique query identifier
|
|
487
|
+
* @param {Function} queryFn - Function to execute if not cached
|
|
488
|
+
* @returns {*} Query result
|
|
489
|
+
*/
|
|
490
|
+
optimizeQuery(queryKey, queryFn) {
|
|
491
|
+
// Check cache first
|
|
492
|
+
const cached = this.getFromCache(queryKey, 'query');
|
|
493
|
+
if (cached !== undefined) {
|
|
494
|
+
return cached;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Execute query and cache result
|
|
498
|
+
const result = this.measure(`query:${queryKey}`, queryFn);
|
|
499
|
+
this.cache(queryKey, result, 'query');
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Destroy optimizer and clean up resources
|
|
505
|
+
*/
|
|
506
|
+
destroy() {
|
|
507
|
+
if (this.cleanupTimer) {
|
|
508
|
+
clearInterval(this.cleanupTimer);
|
|
509
|
+
this.cleanupTimer = null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (this.batchTimer) {
|
|
513
|
+
clearTimeout(this.batchTimer);
|
|
514
|
+
this.batchTimer = null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
this.eventCache.clear();
|
|
518
|
+
this.queryCache.clear();
|
|
519
|
+
this.dateRangeCache.clear();
|
|
520
|
+
this.lazyIndexes.clear();
|
|
521
|
+
this.pendingIndexes.clear();
|
|
522
|
+
}
|
|
523
|
+
}
|