UNPKG

rezilient.js

Version:

Rezilient.js - Revolutionary offline-first framework with AI-awareness, principle-driven development, carbon-conscious computing, and self-healing capabilities

564 lines (469 loc) 15.2 kB
// src/cache/CacheManager.js import { BrowserAPICompat, EnvironmentDetector } from '../utils/environment.js'; /** * @class CacheManager * Advanced caching strategies for Aether.js applications * Implements smart cache invalidation, predictive pre-caching, and carbon-aware scheduling * Universal compatibility across all JavaScript environments */ export class CacheManager { constructor(options = {}) { this.options = { maxCacheSize: options.maxCacheSize || 50 * 1024 * 1024, // 50MB default maxCacheAge: options.maxCacheAge || 7 * 24 * 60 * 60 * 1000, // 7 days enablePredictiveCaching: options.enablePredictiveCaching !== false, enableCarbonAware: options.enableCarbonAware !== false, cacheStrategies: { static: 'cache-first', api: 'stale-while-revalidate', dynamic: 'network-first', ...options.cacheStrategies } }; // Cache statistics and analytics this.stats = { hits: 0, misses: 0, evictions: 0, totalSize: 0, lastCleanup: Date.now() }; // Usage patterns for predictive caching this.usagePatterns = new Map(); this.accessHistory = []; // Carbon awareness data this.carbonData = { lastUpdate: 0, intensity: 'medium', // low, medium, high renewablePercentage: 50 }; this.initialize(); } /** * Initialize cache manager * @private */ async initialize() { await this.loadCacheStats(); await this.loadUsagePatterns(); await this.updateCarbonData(); // Setup periodic maintenance this.setupMaintenanceSchedule(); // Setup carbon data updates this.setupCarbonUpdates(); } /** * Smart cache invalidation based on content analysis and usage patterns */ async invalidateCache(pattern, options = {}) { const { force = false, preserveFrequent = true, maxAge = this.options.maxCacheAge } = options; const caches = await window.caches.keys(); const results = { invalidated: 0, preserved: 0, errors: [] }; for (const cacheName of caches) { try { const cache = await window.caches.open(cacheName); const requests = await cache.keys(); for (const request of requests) { const url = request.url; // Check if URL matches pattern if (this.matchesPattern(url, pattern)) { const shouldInvalidate = await this.shouldInvalidateEntry( request, cache, { force, preserveFrequent, maxAge } ); if (shouldInvalidate) { await cache.delete(request); results.invalidated++; } else { results.preserved++; } } } } catch (error) { results.errors.push({ cacheName, error: error.message }); } } // Update statistics this.stats.evictions += results.invalidated; await this.saveCacheStats(); return results; } /** * Predictive pre-caching based on user behavior analysis */ async predictivePreCache(currentUrl) { if (!this.options.enablePredictiveCaching) return; // Record access for pattern analysis this.recordAccess(currentUrl); // Analyze patterns and predict next likely resources const predictions = this.analyzePredictions(currentUrl); // Pre-cache predicted resources during low carbon intensity if (this.shouldPreCache()) { await this.preCacheResources(predictions); } } /** * Carbon-aware sync scheduling */ async scheduleSync(syncFunction, priority = 'normal') { const carbonIntensity = await this.getCarbonIntensity(); const networkCondition = this.getNetworkCondition(); const delay = this.calculateOptimalDelay(carbonIntensity, networkCondition, priority); if (delay === 0) { // Execute immediately return await syncFunction(); } else { // Schedule for later execution return this.scheduleDelayedSync(syncFunction, delay, priority); } } /** * Advanced cache strategies implementation */ async handleRequest(request, strategy = null) { const url = new URL(request.url); const cacheStrategy = strategy || this.determineCacheStrategy(url); // Record access for analytics this.recordAccess(url.href); switch (cacheStrategy) { case 'cache-first': return this.cacheFirstStrategy(request); case 'network-first': return this.networkFirstStrategy(request); case 'stale-while-revalidate': return this.staleWhileRevalidateStrategy(request); case 'cache-only': return this.cacheOnlyStrategy(request); case 'network-only': return this.networkOnlyStrategy(request); default: return this.staleWhileRevalidateStrategy(request); } } /** * Cache-first strategy with intelligent fallback */ async cacheFirstStrategy(request) { try { const cachedResponse = await caches.match(request); if (cachedResponse) { this.stats.hits++; // Check if cached response is stale and schedule background update if (this.isStale(cachedResponse)) { this.scheduleBackgroundUpdate(request); } return cachedResponse; } // Cache miss - fetch from network this.stats.misses++; const networkResponse = await fetch(request); if (networkResponse.ok) { await this.cacheResponse(request, networkResponse.clone()); } return networkResponse; } catch (error) { // Network error - try to serve stale content const staleResponse = await this.getStaleResponse(request); if (staleResponse) { return staleResponse; } throw error; } } /** * Stale-while-revalidate strategy with carbon awareness */ async staleWhileRevalidateStrategy(request) { const cachedResponse = await caches.match(request); if (cachedResponse) { this.stats.hits++; // Serve cached response immediately const responsePromise = Promise.resolve(cachedResponse); // Revalidate in background if carbon conditions are favorable if (this.shouldRevalidate()) { this.revalidateInBackground(request); } return responsePromise; } // No cached response - fetch from network this.stats.misses++; const networkResponse = await fetch(request); if (networkResponse.ok) { await this.cacheResponse(request, networkResponse.clone()); } return networkResponse; } /** * Network-first strategy with intelligent caching */ async networkFirstStrategy(request) { try { const networkResponse = await fetch(request); if (networkResponse.ok) { // Cache successful responses await this.cacheResponse(request, networkResponse.clone()); } return networkResponse; } catch (error) { // Network failed - try cache const cachedResponse = await caches.match(request); if (cachedResponse) { this.stats.hits++; return cachedResponse; } this.stats.misses++; throw error; } } /** * Intelligent cache response with size and age management */ async cacheResponse(request, response) { const cacheName = this.determineCacheName(request); const cache = await caches.open(cacheName); // Check cache size limits if (await this.shouldEvictForSpace()) { await this.evictLeastUsed(); } // Add metadata for intelligent management const enhancedResponse = this.enhanceResponse(response); await cache.put(request, enhancedResponse); this.updateCacheStats(request, response); } /** * Determine optimal cache strategy based on resource type and patterns */ determineCacheStrategy(url) { // Static assets if (this.isStaticAsset(url)) { return this.options.cacheStrategies.static; } // API endpoints if (this.isApiEndpoint(url)) { return this.options.cacheStrategies.api; } // Dynamic content return this.options.cacheStrategies.dynamic; } /** * Carbon intensity awareness */ async getCarbonIntensity() { // Update carbon data if stale if (Date.now() - this.carbonData.lastUpdate > 60 * 60 * 1000) { // 1 hour await this.updateCarbonData(); } return this.carbonData.intensity; } /** * Update carbon intensity data from external APIs */ async updateCarbonData() { if (!this.options.enableCarbonAware) return; try { // In a real implementation, this would call carbon intensity APIs // For now, simulate based on time of day const hour = new Date().getHours(); // Simulate renewable energy patterns (more renewable during day) if (hour >= 10 && hour <= 16) { this.carbonData.intensity = 'low'; this.carbonData.renewablePercentage = 70; } else if (hour >= 6 && hour <= 22) { this.carbonData.intensity = 'medium'; this.carbonData.renewablePercentage = 50; } else { this.carbonData.intensity = 'high'; this.carbonData.renewablePercentage = 30; } this.carbonData.lastUpdate = Date.now(); } catch (error) { console.warn('Failed to update carbon data:', error); } } /** * Calculate optimal delay for carbon-aware operations */ calculateOptimalDelay(carbonIntensity, networkCondition, priority) { if (priority === 'urgent') return 0; let baseDelay = 0; // Carbon intensity factor switch (carbonIntensity) { case 'high': baseDelay += priority === 'low' ? 60 * 60 * 1000 : 30 * 60 * 1000; // 1h or 30min break; case 'medium': baseDelay += priority === 'low' ? 15 * 60 * 1000 : 5 * 60 * 1000; // 15min or 5min break; case 'low': baseDelay = 0; // Execute immediately break; } // Network condition factor if (networkCondition === 'slow') { baseDelay += 5 * 60 * 1000; // Additional 5 minutes } return baseDelay; } /** * Usage pattern analysis for predictive caching */ analyzePredictions(currentUrl) { const pattern = this.usagePatterns.get(currentUrl) || { next: new Map(), count: 0 }; // Get most likely next resources const predictions = Array.from(pattern.next.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([url, probability]) => ({ url, probability })); return predictions; } /** * Record access patterns for machine learning */ recordAccess(url) { const now = Date.now(); // Add to access history this.accessHistory.push({ url, timestamp: now }); // Keep only recent history (last 1000 accesses) if (this.accessHistory.length > 1000) { this.accessHistory = this.accessHistory.slice(-1000); } // Update usage patterns this.updateUsagePatterns(url); } /** * Update usage patterns for predictive analysis */ updateUsagePatterns(currentUrl) { // Find previous URL in recent history const recentAccess = this.accessHistory.slice(-10); const previousAccess = recentAccess[recentAccess.length - 2]; if (previousAccess) { const previousUrl = previousAccess.url; if (!this.usagePatterns.has(previousUrl)) { this.usagePatterns.set(previousUrl, { next: new Map(), count: 0 }); } const pattern = this.usagePatterns.get(previousUrl); pattern.count++; const nextCount = pattern.next.get(currentUrl) || 0; pattern.next.set(currentUrl, nextCount + 1); } } /** * Utility methods */ isStaticAsset(url) { return /\.(js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|ico)$/i.test(url.pathname); } isApiEndpoint(url) { return url.pathname.startsWith('/api/') || url.pathname.startsWith('/graphql'); } matchesPattern(url, pattern) { if (typeof pattern === 'string') { return url.includes(pattern); } if (pattern instanceof RegExp) { return pattern.test(url); } return false; } shouldPreCache() { return this.carbonData.intensity === 'low' && navigator.onLine; } shouldRevalidate() { return this.carbonData.intensity !== 'high' && navigator.onLine; } getNetworkCondition() { if (navigator.connection) { const effectiveType = navigator.connection.effectiveType; return ['slow-2g', '2g'].includes(effectiveType) ? 'slow' : 'fast'; } return 'unknown'; } /** * Maintenance and cleanup */ setupMaintenanceSchedule() { // Run maintenance every hour setInterval(() => { this.performMaintenance(); }, 60 * 60 * 1000); } setupCarbonUpdates() { // Update carbon data every 30 minutes setInterval(() => { this.updateCarbonData(); }, 30 * 60 * 1000); } async performMaintenance() { await this.cleanupExpiredEntries(); await this.optimizeCacheSize(); await this.saveCacheStats(); await this.saveUsagePatterns(); } /** * Persistence methods */ async loadCacheStats() { try { // Use compatibility layer for universal storage access const storage = BrowserAPICompat.getLocalStorage(); const saved = storage.getItem('aether-cache-stats'); if (saved) { this.stats = { ...this.stats, ...JSON.parse(saved) }; } } catch (error) { console.warn('Failed to load cache stats:', error); } } async saveCacheStats() { try { // Use compatibility layer for universal storage access const storage = BrowserAPICompat.getLocalStorage(); storage.setItem('aether-cache-stats', JSON.stringify(this.stats)); } catch (error) { console.warn('Failed to save cache stats:', error); } } async loadUsagePatterns() { try { const saved = localStorage.getItem('aether-usage-patterns'); if (saved) { const data = JSON.parse(saved); this.usagePatterns = new Map(data); } } catch (error) { console.warn('Failed to load usage patterns:', error); } } async saveUsagePatterns() { try { const data = Array.from(this.usagePatterns.entries()); localStorage.setItem('aether-usage-patterns', JSON.stringify(data)); } catch (error) { console.warn('Failed to save usage patterns:', error); } } /** * Get cache statistics and health information */ getStats() { const hitRate = this.stats.hits / (this.stats.hits + this.stats.misses) || 0; return { ...this.stats, hitRate: Math.round(hitRate * 100), carbonIntensity: this.carbonData.intensity, renewablePercentage: this.carbonData.renewablePercentage, patternsLearned: this.usagePatterns.size, recentAccesses: this.accessHistory.length }; } }