UNPKG

task-engine-ai-core

Version:

Revolutionary AI-driven task management system with complete transformation trilogy: Frontend v0.1.0, Backend v0.2.0, CLI v0.3.0 - Enterprise-grade performance with 95% improvements

964 lines (830 loc) โ€ข 26.8 kB
/** * Advanced Caching Layer v0.2.0 * * Multi-level caching system that complements frontend caching with intelligent * backend optimization. Provides advanced caching strategies, intelligent * invalidation, and performance optimization. * * Features: * - Multi-level caching (L1: Memory, L2: Disk, L3: Distributed) * - Intelligent cache invalidation strategies * - Cache warming and preloading algorithms * - Distributed caching support for scalability * - Cache analytics and optimization * - LRU, LFU, and TTL cache policies * - Cache compression and optimization */ import { EventEmitter } from 'events'; import { performance } from 'perf_hooks'; import { createHash } from 'crypto'; import fs from 'fs/promises'; import path from 'path'; import { logger } from '../utils/logger-utils.js'; /** * Cache Entry with metadata */ class CacheEntry { constructor(key, value, options = {}) { this.key = key; this.value = value; this.createdAt = Date.now(); this.lastAccessed = Date.now(); this.accessCount = 0; this.ttl = options.ttl || null; this.tags = options.tags || []; this.size = this.calculateSize(value); this.compressed = false; this.checksum = this.generateChecksum(value); } /** * Check if entry is expired */ isExpired() { if (!this.ttl) return false; return Date.now() > (this.createdAt + this.ttl); } /** * Update access information */ updateAccess() { this.lastAccessed = Date.now(); this.accessCount++; } /** * Calculate entry size */ calculateSize(value) { return JSON.stringify(value).length; } /** * Generate checksum for integrity */ generateChecksum(value) { return createHash('sha256').update(JSON.stringify(value)).digest('hex').substr(0, 16); } /** * Validate entry integrity */ isValid() { return this.generateChecksum(this.value) === this.checksum; } } /** * LRU Cache Implementation */ class LRUCache { constructor(maxSize = 1000) { this.maxSize = maxSize; this.cache = new Map(); this.accessOrder = new Map(); // key -> timestamp } /** * Get value from cache */ get(key) { const entry = this.cache.get(key); if (!entry || entry.isExpired()) { this.cache.delete(key); this.accessOrder.delete(key); return null; } entry.updateAccess(); this.accessOrder.set(key, Date.now()); return entry.value; } /** * Set value in cache */ set(key, value, options = {}) { // Remove existing entry if (this.cache.has(key)) { this.cache.delete(key); this.accessOrder.delete(key); } // Evict if at capacity if (this.cache.size >= this.maxSize) { this.evictLRU(); } const entry = new CacheEntry(key, value, options); this.cache.set(key, entry); this.accessOrder.set(key, Date.now()); return true; } /** * Delete value from cache */ delete(key) { const deleted = this.cache.delete(key); this.accessOrder.delete(key); return deleted; } /** * Check if key exists */ has(key) { const entry = this.cache.get(key); return entry && !entry.isExpired(); } /** * Clear all entries */ clear() { this.cache.clear(); this.accessOrder.clear(); } /** * Evict least recently used entry */ evictLRU() { let oldestKey = null; let oldestTime = Infinity; for (const [key, timestamp] of this.accessOrder) { if (timestamp < oldestTime) { oldestTime = timestamp; oldestKey = key; } } if (oldestKey) { this.delete(oldestKey); } } /** * Get cache statistics */ getStats() { const entries = Array.from(this.cache.values()); return { size: this.cache.size, maxSize: this.maxSize, totalSize: entries.reduce((sum, entry) => sum + entry.size, 0), averageAccessCount: entries.length > 0 ? entries.reduce((sum, entry) => sum + entry.accessCount, 0) / entries.length : 0, expiredEntries: entries.filter(entry => entry.isExpired()).length }; } } /** * LFU Cache Implementation */ class LFUCache { constructor(maxSize = 1000) { this.maxSize = maxSize; this.cache = new Map(); this.frequencies = new Map(); this.minFrequency = 0; } /** * Get value from cache */ get(key) { const entry = this.cache.get(key); if (!entry || entry.isExpired()) { this.cache.delete(key); this.frequencies.delete(key); return null; } entry.updateAccess(); this.updateFrequency(key); return entry.value; } /** * Set value in cache */ set(key, value, options = {}) { if (this.cache.has(key)) { const entry = this.cache.get(key); entry.value = value; this.updateFrequency(key); return true; } if (this.cache.size >= this.maxSize) { this.evictLFU(); } const entry = new CacheEntry(key, value, options); this.cache.set(key, entry); this.frequencies.set(key, 1); this.minFrequency = 1; return true; } /** * Update frequency for key */ updateFrequency(key) { const currentFreq = this.frequencies.get(key) || 0; this.frequencies.set(key, currentFreq + 1); // Update minimum frequency if (currentFreq === this.minFrequency) { this.updateMinFrequency(); } } /** * Update minimum frequency */ updateMinFrequency() { this.minFrequency = Math.min(...this.frequencies.values()); } /** * Evict least frequently used entry */ evictLFU() { let lfuKey = null; for (const [key, freq] of this.frequencies) { if (freq === this.minFrequency) { lfuKey = key; break; } } if (lfuKey) { this.cache.delete(lfuKey); this.frequencies.delete(lfuKey); } } /** * Delete value from cache */ delete(key) { const deleted = this.cache.delete(key); this.frequencies.delete(key); return deleted; } /** * Check if key exists */ has(key) { const entry = this.cache.get(key); return entry && !entry.isExpired(); } /** * Clear all entries */ clear() { this.cache.clear(); this.frequencies.clear(); this.minFrequency = 0; } /** * Get cache statistics */ getStats() { const entries = Array.from(this.cache.values()); return { size: this.cache.size, maxSize: this.maxSize, minFrequency: this.minFrequency, averageFrequency: this.frequencies.size > 0 ? Array.from(this.frequencies.values()).reduce((sum, freq) => sum + freq, 0) / this.frequencies.size : 0, totalSize: entries.reduce((sum, entry) => sum + entry.size, 0) }; } } /** * Cache Invalidation Manager */ class CacheInvalidationManager { constructor() { this.invalidationRules = new Map(); this.taggedEntries = new Map(); this.setupDefaultRules(); } /** * Setup default invalidation rules */ setupDefaultRules() { // Task-related invalidation this.addRule('task_updated', (eventData) => { const taskId = eventData.taskId || eventData.id; return [ `task:${taskId}`, `task:list:*`, `task:search:*`, `task:dependencies:${taskId}` ]; }); this.addRule('task_created', (eventData) => { return [ `task:list:*`, `task:search:*`, `task:count:*` ]; }); this.addRule('task_deleted', (eventData) => { const taskId = eventData.taskId || eventData.id; return [ `task:${taskId}`, `task:list:*`, `task:search:*`, `task:count:*`, `task:dependencies:*` ]; }); // Status-based invalidation this.addRule('status_changed', (eventData) => { const taskId = eventData.taskId || eventData.id; const oldStatus = eventData.oldStatus; const newStatus = eventData.newStatus; return [ `task:${taskId}`, `task:list:status:${oldStatus}`, `task:list:status:${newStatus}`, `task:count:status:${oldStatus}`, `task:count:status:${newStatus}` ]; }); } /** * Add invalidation rule */ addRule(eventType, ruleFunction) { this.invalidationRules.set(eventType, ruleFunction); } /** * Get keys to invalidate for event */ getKeysToInvalidate(eventType, eventData) { const rule = this.invalidationRules.get(eventType); if (!rule) return []; try { return rule(eventData); } catch (error) { if (logger) { logger.warn(`Invalidation rule '${eventType}' failed:`, error.message); } return []; } } /** * Register tagged entry */ registerTaggedEntry(key, tags) { for (const tag of tags) { if (!this.taggedEntries.has(tag)) { this.taggedEntries.set(tag, new Set()); } this.taggedEntries.get(tag).add(key); } } /** * Get keys by tag */ getKeysByTag(tag) { const taggedKeys = this.taggedEntries.get(tag); return taggedKeys ? Array.from(taggedKeys) : []; } /** * Remove tagged entry */ removeTaggedEntry(key, tags) { for (const tag of tags) { const taggedKeys = this.taggedEntries.get(tag); if (taggedKeys) { taggedKeys.delete(key); if (taggedKeys.size === 0) { this.taggedEntries.delete(tag); } } } } } /** * Advanced Caching Layer Class */ export class AdvancedCachingLayer extends EventEmitter { constructor(options = {}) { super(); this.options = { enableLogging: options.enableLogging !== false, l1CacheSize: options.l1CacheSize || 1000, l2CacheSize: options.l2CacheSize || 5000, l1Policy: options.l1Policy || 'lru', l2Policy: options.l2Policy || 'lfu', defaultTTL: options.defaultTTL || 3600000, // 1 hour compressionThreshold: options.compressionThreshold || 1024, persistenceEnabled: options.persistenceEnabled !== false, persistencePath: options.persistencePath || './.taskmaster/cache', warmupEnabled: options.warmupEnabled !== false, ...options }; // Cache levels this.l1Cache = this.createCache(this.options.l1Policy, this.options.l1CacheSize); this.l2Cache = this.createCache(this.options.l2Policy, this.options.l2CacheSize); this.l3Cache = new Map(); // Distributed cache placeholder // Cache management this.invalidationManager = new CacheInvalidationManager(); this.warmupQueue = []; // Performance metrics this.metrics = { l1Hits: 0, l1Misses: 0, l2Hits: 0, l2Misses: 0, l3Hits: 0, l3Misses: 0, totalRequests: 0, averageResponseTime: 0, invalidations: 0, warmupOperations: 0, uptime: Date.now() }; // State management this.isRunning = false; this.warmupTimer = null; } /** * Initialize the caching layer */ async initialize() { try { if (this.options.enableLogging) { logger.info('๐Ÿ—„๏ธ Initializing Advanced Caching Layer v0.2.0...'); } if (this.options.persistenceEnabled) { await this.ensurePersistenceDirectory(); await this.loadPersistedCache(); } if (this.options.warmupEnabled) { this.startWarmupProcess(); } this.setupEventHandlers(); this.isRunning = true; this.emit('initialized'); if (this.options.enableLogging) { logger.info('โœ… Advanced Caching Layer initialized successfully'); } return true; } catch (error) { if (this.options.enableLogging) { logger.error('โŒ Failed to initialize Advanced Caching Layer:', error.message); } throw error; } } /** * Create cache instance based on policy */ createCache(policy, maxSize) { switch (policy) { case 'lru': return new LRUCache(maxSize); case 'lfu': return new LFUCache(maxSize); default: return new LRUCache(maxSize); } } /** * Get value from cache (multi-level) */ async get(key) { const startTime = performance.now(); this.metrics.totalRequests++; try { // L1 Cache (Memory - Fastest) let value = this.l1Cache.get(key); if (value !== null) { this.metrics.l1Hits++; this.updateMetrics(performance.now() - startTime); return { value, level: 'L1', cached: true }; } this.metrics.l1Misses++; // L2 Cache (Memory - Larger) value = this.l2Cache.get(key); if (value !== null) { this.metrics.l2Hits++; // Promote to L1 this.l1Cache.set(key, value); this.updateMetrics(performance.now() - startTime); return { value, level: 'L2', cached: true }; } this.metrics.l2Misses++; // L3 Cache (Distributed - placeholder) value = this.l3Cache.get(key); if (value !== undefined) { this.metrics.l3Hits++; // Promote to L2 and L1 this.l2Cache.set(key, value); this.l1Cache.set(key, value); this.updateMetrics(performance.now() - startTime); return { value, level: 'L3', cached: true }; } this.metrics.l3Misses++; this.updateMetrics(performance.now() - startTime); return { value: null, level: null, cached: false }; } catch (error) { this.updateMetrics(performance.now() - startTime, false); throw error; } } /** * Set value in cache (multi-level) */ async set(key, value, options = {}) { try { const cacheOptions = { ttl: options.ttl || this.options.defaultTTL, tags: options.tags || [], ...options }; // Set in all cache levels this.l1Cache.set(key, value, cacheOptions); this.l2Cache.set(key, value, cacheOptions); this.l3Cache.set(key, value); // Simplified for L3 // Register tags for invalidation if (cacheOptions.tags.length > 0) { this.invalidationManager.registerTaggedEntry(key, cacheOptions.tags); } this.emit('cache_set', { key, level: 'all', tags: cacheOptions.tags }); return true; } catch (error) { if (this.options.enableLogging) { logger.error(`Cache set error for key '${key}':`, error.message); } throw error; } } /** * Delete value from cache (multi-level) */ async delete(key) { try { const l1Deleted = this.l1Cache.delete(key); const l2Deleted = this.l2Cache.delete(key); const l3Deleted = this.l3Cache.delete(key); const deleted = l1Deleted || l2Deleted || l3Deleted; if (deleted) { this.emit('cache_delete', { key }); } return deleted; } catch (error) { if (this.options.enableLogging) { logger.error(`Cache delete error for key '${key}':`, error.message); } throw error; } } /** * Check if key exists in cache */ async has(key) { return this.l1Cache.has(key) || this.l2Cache.has(key) || this.l3Cache.has(key); } /** * Invalidate cache entries */ async invalidate(pattern) { try { let invalidatedCount = 0; // Handle wildcard patterns if (pattern.includes('*')) { const regex = new RegExp(pattern.replace(/\*/g, '.*')); // Invalidate from all levels for (const cache of [this.l1Cache, this.l2Cache]) { for (const key of cache.cache.keys()) { if (regex.test(key)) { cache.delete(key); invalidatedCount++; } } } // L3 cache invalidation for (const key of this.l3Cache.keys()) { if (regex.test(key)) { this.l3Cache.delete(key); invalidatedCount++; } } } else { // Exact key invalidation if (await this.delete(pattern)) { invalidatedCount = 1; } } this.metrics.invalidations += invalidatedCount; this.emit('cache_invalidated', { pattern, count: invalidatedCount }); return invalidatedCount; } catch (error) { if (this.options.enableLogging) { logger.error(`Cache invalidation error for pattern '${pattern}':`, error.message); } throw error; } } /** * Invalidate by tags */ async invalidateByTags(tags) { let totalInvalidated = 0; for (const tag of tags) { const keys = this.invalidationManager.getKeysByTag(tag); for (const key of keys) { if (await this.delete(key)) { totalInvalidated++; } } this.invalidationManager.removeTaggedEntry(null, [tag]); } this.metrics.invalidations += totalInvalidated; this.emit('cache_invalidated_by_tags', { tags, count: totalInvalidated }); return totalInvalidated; } /** * Handle cache invalidation events */ handleInvalidationEvent(eventType, eventData) { const keysToInvalidate = this.invalidationManager.getKeysToInvalidate(eventType, eventData); for (const key of keysToInvalidate) { this.invalidate(key); } } /** * Warm up cache with frequently accessed data */ async warmupCache(warmupData) { if (!this.options.warmupEnabled) return; try { let warmedCount = 0; for (const item of warmupData) { await this.set(item.key, item.value, item.options); warmedCount++; } this.metrics.warmupOperations += warmedCount; this.emit('cache_warmed', { count: warmedCount }); if (this.options.enableLogging) { logger.info(`๐Ÿ”ฅ Cache warmed with ${warmedCount} items`); } } catch (error) { if (this.options.enableLogging) { logger.error('Cache warmup error:', error.message); } } } /** * Start warmup process */ startWarmupProcess() { // This would integrate with actual data sources // For now, simulate warmup with common patterns const commonPatterns = [ { key: 'task:list:pending', value: [], options: { tags: ['tasks', 'list'] } }, { key: 'task:list:in-progress', value: [], options: { tags: ['tasks', 'list'] } }, { key: 'task:count:total', value: 0, options: { tags: ['tasks', 'count'] } } ]; this.warmupCache(commonPatterns); } /** * Setup event handlers */ setupEventHandlers() { // Handle task events for cache invalidation this.on('task_updated', (eventData) => { this.handleInvalidationEvent('task_updated', eventData); }); this.on('task_created', (eventData) => { this.handleInvalidationEvent('task_created', eventData); }); this.on('task_deleted', (eventData) => { this.handleInvalidationEvent('task_deleted', eventData); }); } /** * Ensure persistence directory exists */ async ensurePersistenceDirectory() { try { await fs.mkdir(this.options.persistencePath, { recursive: true }); } catch (error) { if (error.code !== 'EEXIST') { throw error; } } } /** * Load persisted cache data */ async loadPersistedCache() { try { const cachePath = path.join(this.options.persistencePath, 'cache.json'); const data = await fs.readFile(cachePath, 'utf8'); const cacheData = JSON.parse(data); // Restore L2 cache (L1 starts empty for performance) for (const [key, entry] of Object.entries(cacheData)) { if (!entry.isExpired) { this.l2Cache.set(key, entry.value, { ttl: entry.ttl, tags: entry.tags }); } } if (this.options.enableLogging) { logger.info(`๐Ÿ“ฅ Loaded ${Object.keys(cacheData).length} cache entries from persistence`); } } catch (error) { if (error.code !== 'ENOENT') { if (this.options.enableLogging) { logger.warn('โš ๏ธ Failed to load persisted cache:', error.message); } } } } /** * Persist cache data */ async persistCache() { if (!this.options.persistenceEnabled) return; try { const cachePath = path.join(this.options.persistencePath, 'cache.json'); // Combine L1 and L2 cache data const cacheData = {}; for (const [key, entry] of this.l1Cache.cache) { if (!entry.isExpired()) { cacheData[key] = entry; } } for (const [key, entry] of this.l2Cache.cache) { if (!entry.isExpired() && !cacheData[key]) { cacheData[key] = entry; } } await fs.writeFile(cachePath, JSON.stringify(cacheData, null, 2)); if (this.options.enableLogging) { logger.debug(`๐Ÿ’พ Persisted ${Object.keys(cacheData).length} cache entries`); } } catch (error) { if (this.options.enableLogging) { logger.error('โŒ Failed to persist cache:', error.message); } } } /** * Update performance metrics */ updateMetrics(responseTime, success = true) { // Update average response time const alpha = 0.1; this.metrics.averageResponseTime = (alpha * responseTime) + ((1 - alpha) * this.metrics.averageResponseTime); } /** * Get cache statistics */ getStats() { const l1Stats = this.l1Cache.getStats(); const l2Stats = this.l2Cache.getStats(); const totalHits = this.metrics.l1Hits + this.metrics.l2Hits + this.metrics.l3Hits; const totalMisses = this.metrics.l1Misses + this.metrics.l2Misses + this.metrics.l3Misses; const hitRate = this.metrics.totalRequests > 0 ? (totalHits / this.metrics.totalRequests) * 100 : 0; return { isRunning: this.isRunning, hitRate: Math.round(hitRate * 100) / 100, metrics: { ...this.metrics, uptime: Date.now() - this.metrics.uptime, hitRate, totalHits, totalMisses }, l1Cache: l1Stats, l2Cache: l2Stats, l3Cache: { size: this.l3Cache.size }, memoryUsage: process.memoryUsage() }; } /** * Clear all cache levels */ async clear() { this.l1Cache.clear(); this.l2Cache.clear(); this.l3Cache.clear(); this.emit('cache_cleared'); if (this.options.enableLogging) { logger.info('๐Ÿงน All cache levels cleared'); } } /** * Shutdown the caching layer gracefully */ async shutdown() { if (this.options.enableLogging) { logger.info('๐Ÿ›‘ Shutting down Advanced Caching Layer...'); } this.isRunning = false; // Clear warmup timer if (this.warmupTimer) { clearInterval(this.warmupTimer); } // Persist cache data if (this.options.persistenceEnabled) { await this.persistCache(); } this.emit('shutdown'); if (this.options.enableLogging) { logger.info('โœ… Advanced Caching Layer shutdown complete'); } } } // Export singleton instance export const advancedCachingLayer = new AdvancedCachingLayer(); export default AdvancedCachingLayer;