UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

1,338 lines (1,335 loc) 72.9 kB
'use strict'; var index = require('../../../../../components/fortified-function/index.js'); var hashCore = require('../../../../../core/hash/hash-core.js'); require('../../../../../core/hash/hash-types.js'); require('crypto'); require('../../../../../core/hash/hash-security.js'); require('../../../../../core/hash/hash-advanced.js'); require('../../../../../algorithms/hash-algorithms.js'); var index$1 = require('../../../../../components/cache/index.js'); var PluginTypes = require('../types/PluginTypes.js'); /** * Cache Plugin Base Class * * Foundation for cache optimization plugins with <0.5ms execution overhead * leveraging FortifyJS cache systems for ultra-fast performance. */ /** * Abstract base class for cache optimization plugins */ class CachePlugin { constructor() { this.type = PluginTypes.PluginType.CACHE; this.priority = PluginTypes.PluginPriority.HIGH; this.isAsync = true; this.isCacheable = false; // Cache plugins themselves shouldn't be cached this.maxExecutionTime = 500; // 0.5ms max for cache operations // Cache configuration this.cacheStrategy = "hybrid"; this.compressionEnabled = true; this.encryptionEnabled = true; // Cache statistics this.cacheStats = { hits: 0, misses: 0, sets: 0, deletes: 0, errors: 0, totalOperations: 0, averageResponseTime: 0, lastOperation: new Date(), }; // Post-response cache operations queue this.postResponseQueue = []; this.postResponseWorkerActive = false; // Response body capture system this.responseBodyCapture = new Map(); // Analysis data storage this.analysisStorage = []; // Cache invalidation patterns and tagging this.invalidationPatterns = new Map(); this.taggedKeys = new Map(); this.contentTypePatterns = new Map(); } /** * Initialize cache plugin with FortifyJS utilities */ async initialize(context) { this.cache = context.cache; this.hashUtil = hashCore.Hash; // Create fortified cache wrapper for ultra-fast operations this.fortifiedCache = index.func(async (operation) => { return await operation(); }, { ultraFast: "maximum", autoEncrypt: this.encryptionEnabled, auditLog: false, // Disable audit logging for cache operations timeout: this.maxExecutionTime, errorHandling: "graceful", }); // Initialize cache instances await this.initializeCaches(); // Initialize cache invalidation patterns this.initializeCachePatterns(); // Initialize plugin-specific cache features await this.initializeCachePlugin(context); } /** * Execute cache plugin with ultra-fast performance */ async execute(context) { const startTime = performance.now(); try { // Determine cache operation type const operation = this.determineCacheOperation(context); let result; let cacheData; switch (operation) { case "get": result = await this.handleCacheGet(context); break; case "set": result = await this.handleCacheSet(context); cacheData = result.cacheData; break; case "invalidate": result = await this.handleCacheInvalidate(context); break; case "warmup": result = await this.handleCacheWarmup(context); break; default: result = await this.handleCustomCacheOperation(context, operation); } const executionTime = performance.now() - startTime; // Update cache statistics this.updateCacheStats(operation, executionTime, true); // Update context metrics if (operation === "get" && result.hit) { context.metrics.cacheHits++; } else if (operation === "get" && !result.hit) { context.metrics.cacheMisses++; } return { success: true, executionTime, data: result, shouldContinue: true, cacheData, }; } catch (error) { const executionTime = performance.now() - startTime; // Update error statistics this.updateCacheStats("error", executionTime, false); return { success: false, executionTime, error, shouldContinue: true, // Cache errors shouldn't stop execution }; } } /** * Check if request should be cached */ shouldCache(context) { // Default caching logic - can be overridden by subclasses const { req } = context; // Only cache GET requests by default if (req.method !== "GET") { return false; } // Don't cache requests with authentication headers if (req.headers.authorization) { return false; } // Don't cache requests with query parameters that indicate dynamic content if (this.hasDynamicQueryParams(req.query)) { return false; } // Apply plugin-specific caching rules return this.shouldCacheRequest(context); } /** * Generate cache key for request */ generateCacheKey(context) { const { req } = context; // Create base key components const components = [ req.method, req.path, this.serializeQueryParams(req.query), this.serializeHeaders(req.headers), ]; // Add plugin-specific key components const customComponents = this.getCustomKeyComponents(context); components.push(...customComponents); // Create hash of components for consistent key generation const keyString = components.filter((c) => c).join(":"); return this.hashUtil.create(keyString, { algorithm: "sha256", outputFormat: "hex", }); } /** * Get cache TTL for request */ getCacheTTL(context) { // Default TTL logic - can be overridden by subclasses const { req } = context; // Static resources get longer TTL if (this.isStaticResource(req.path)) { return 3600000; // 1 hour } // API responses get shorter TTL if (req.path.startsWith("/api/")) { return 300000; // 5 minutes } // Apply plugin-specific TTL logic return this.getCustomTTL(context); } /** * Invalidate cache by pattern */ async invalidateCache(pattern) { if (!this.cache) { throw new Error("Cache not initialized"); } try { await this.fortifiedCache(async () => { await this.cache.invalidateByTags([pattern]); }); this.cacheStats.deletes++; this.cacheStats.totalOperations++; } catch (error) { this.cacheStats.errors++; throw error; } } /** * Precompile cache operations for optimal performance */ async precompile() { // Pre-warm cache key generation await this.precompileCacheOperations(); } /** * Warm up cache plugin */ async warmup(context) { // Perform initial cache operations to warm up systems if (this.shouldCache(context)) { this.generateCacheKey(context); } } // ===== CACHE IMPLEMENTATIONS ===== /** * Initialize plugin-specific cache features * implementation with comprehensive error handling */ async initializeCachePlugin(context) { try { // Initialize cache invalidation patterns based on configuration if (context.config.customSettings.invalidationPatterns) { for (const [name, pattern] of Object.entries(context.config.customSettings.invalidationPatterns)) { this.invalidationPatterns.set(name, new RegExp(pattern)); } } // Initialize content type TTL mappings if (context.config.customSettings.contentTypeTTL) { for (const [type, ttl] of Object.entries(context.config.customSettings.contentTypeTTL)) { this.contentTypePatterns.set(type, ttl); } } // Setup cache warming if enabled if (context.config.customSettings.enableCacheWarming) { await this.setupCacheWarming(context); } context.logger.info(`Cache plugin ${this.constructor.name} initialized successfully`); } catch (error) { context.logger.error(`Error initializing cache plugin: ${error.message}`, error); throw error; } } /** * Check if request should be cached (plugin-specific logic) * implementation with comprehensive caching rules */ shouldCacheRequest(context) { const { req } = context; try { // Don't cache non-GET requests by default if (req.method !== "GET") { return false; } // Don't cache requests with authentication unless explicitly configured if (req.headers.authorization && !this.allowAuthenticatedCaching()) { return false; } // Don't cache requests with cache-control: no-cache const cacheControl = req.headers["cache-control"]; if (cacheControl && cacheControl.includes("no-cache")) { return false; } // Don't cache requests with dynamic query parameters if (this.hasDynamicQueryParams(req.query)) { return false; } // Check if path matches any cacheable patterns return this.matchesCacheablePattern(req.path); } catch (error) { console.error(`Error in shouldCacheRequest: ${error}`); return false; } } /** * Get custom cache key components * implementation with collision-resistant key generation */ getCustomKeyComponents(context) { const { req } = context; const components = []; try { // Add user context for personalized caching if (context.security.isAuthenticated && this.allowAuthenticatedCaching()) { components.push(`user:${context.security.userId}`); // Add role-based components if (context.security.roles.length > 0) { components.push(`roles:${context.security.roles.sort().join(",")}`); } } // Add device type for responsive content const userAgent = req.headers["user-agent"]; if (userAgent) { const deviceType = this.detectDeviceType(userAgent); components.push(`device:${deviceType}`); } // Add language for internationalization const acceptLanguage = req.headers["accept-language"]; if (acceptLanguage) { const primaryLang = acceptLanguage.split(",")[0].split("-")[0]; components.push(`lang:${primaryLang}`); } // Add API version for versioned APIs const apiVersion = req.headers["api-version"] || req.query.version; if (apiVersion) { components.push(`version:${apiVersion}`); } // Add content encoding for compression-aware caching const acceptEncoding = req.headers["accept-encoding"]; if (acceptEncoding) { const encodings = acceptEncoding .split(",") .map((e) => e.trim()); if (encodings.includes("gzip")) { components.push("encoding:gzip"); } else if (encodings.includes("br")) { components.push("encoding:br"); } } return components; } catch (error) { console.error(`Error generating custom key components: ${error}`); return []; } } /** * Get custom TTL for request * implementation with intelligent TTL calculation */ getCustomTTL(context) { const { req } = context; try { // Use dynamic TTL calculation for optimal performance const dynamicTTL = this.calculateDynamicTTL(context); if (dynamicTTL > 0) { return dynamicTTL; } // Fallback to content-type based TTL const contentType = req.headers["content-type"] || req.headers["accept"]; if (contentType) { for (const [pattern, ttl,] of this.contentTypePatterns.entries()) { if (contentType.includes(pattern.replace("*", ""))) { return ttl; } } } // Route-based TTL calculation if (req.path.startsWith("/api/")) { // API endpoints get shorter TTL if (req.path.includes("/users") || req.path.includes("/auth")) { return 60000; // 1 minute for user/auth data } return 300000; // 5 minutes for other API data } // Static resources get longer TTL if (this.isStaticResource(req.path)) { return 86400000; // 24 hours } // Default TTL return 600000; // 10 minutes } catch (error) { console.error(`Error calculating custom TTL: ${error}`); return 300000; // 5 minutes fallback } } /** * Handle custom cache operations * implementation with comprehensive operation support */ async handleCustomCacheOperation(context, operation) { try { switch (operation) { case "analyze": return await this.analyzeCachePerformance(context); case "optimize": return await this.optimizeCacheStrategy(context); case "warmup": return await this.handleCacheWarmup(context); case "invalidate": return await this.handleCacheInvalidate(context); case "stats": return this.getCacheStats(); case "health": return await this.checkCacheHealth(); default: return { operation, supported: false, message: `Operation '${operation}' is not supported`, }; } } catch (error) { console.error(`Error handling custom cache operation '${operation}':`, error); return { operation, success: false, error: error.message, }; } } /** * Precompile cache operations * implementation with comprehensive pre-warming */ async precompileCacheOperations() { try { // Pre-warm cache key generation request patterns const RequestPatterns = [ { path: "/api/users", method: "GET", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", }, { path: "/api/products", method: "GET", userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)", }, { path: "/static/css/style.css", method: "GET", userAgent: "Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)", }, { path: "/public/index.html", method: "GET", userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", }, ]; for (const pattern of RequestPatterns) { const Context = this.createContextFromPattern(pattern); // Pre-warm cache key generation this.generateCacheKey(Context); this.generateAdvancedCacheKey(Context); // Pre-warm TTL calculation this.calculateDynamicTTL(Context); // Pre-warm custom key components this.getCustomKeyComponents(Context); // Pre-warm custom TTL calculation this.getCustomTTL(Context); } // Pre-warm device detection user agents const UserAgents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1", "Mozilla/5.0 (iPad; CPU OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", ]; for (const userAgent of UserAgents) { this.detectDeviceType(userAgent); } // Pre-warm cache pattern matching paths const Paths = [ "/api/users", "/api/products", "/api/orders", "/static/css/style.css", "/static/js/app.js", "/static/images/logo.png", "/public/index.html", "/public/about.html", "/public/contact.html", "/health", "/status", "/ping", ]; for (const path of Paths) { this.matchesCacheablePattern(path); } // Pre-warm cache operations connection attempts const cacheConnections = await Promise.allSettled([ this.memoryCache?.connect?.(), this.fileCache?.connect?.(), this.hybridCache?.connect?.(), ]); const successfulConnections = cacheConnections.filter((result) => result.status === "fulfilled").length; console.debug("Cache operations precompiled successfully patterns", { requestPatterns: RequestPatterns.length, userAgents: UserAgents.length, cachePaths: Paths.length, cacheConnections: successfulConnections, timestamp: Date.now(), }); } catch (error) { console.error("Error precompiling cache operations:", error); } } // ===== HELPER METHODS ===== /** * Setup cache warming for frequently accessed content */ async setupCacheWarming(context) { try { const warmupUrls = context.config.customSettings.warmupUrls || [ "/api/health", "/api/status", "/public/manifest.json", ]; for (const url of warmupUrls) { const warmupKey = this.hashUtil.create(`warmup:${url}`, { algorithm: "sha256", outputFormat: "hex", }); await this.setInOptimalCache(warmupKey, { warmedUp: true, url, timestamp: Date.now() }, { ttl: 300000 } // 5 minutes ); } context.logger.info(`Cache warming setup completed for ${warmupUrls.length} URLs`); } catch (error) { context.logger.error(`Error setting up cache warming: ${error.message}`, error); } } /** * Check if authenticated caching is allowed */ allowAuthenticatedCaching() { // By default, don't cache authenticated requests for security // Subclasses can override this behavior return false; } /** * Check if path matches cacheable patterns */ matchesCacheablePattern(path) { try { // Static resources are always cacheable if (this.isStaticResource(path)) { return true; } // Public API endpoints are cacheable if (path.startsWith("/api/public/")) { return true; } // Health and status endpoints are cacheable if (path.match(/\/(health|status|ping)$/)) { return true; } // Check against configured patterns for (const pattern of this.invalidationPatterns.values()) { if (pattern.test(path)) { return true; } } return false; } catch (error) { console.error(`Error matching cacheable pattern for ${path}:`, error); return false; } } /** * Detect device type from user agent */ detectDeviceType(userAgent) { try { if (/Mobile|Android|iPhone/.test(userAgent)) { return "mobile"; } if (/iPad|Tablet/.test(userAgent)) { return "tablet"; } if (/Bot|Crawler|Spider|Scraper/.test(userAgent)) { return "bot"; } return "desktop"; } catch (error) { console.error(`Error detecting device type: ${error}`); return "unknown"; } } /** * Create context from request pattern for pre-warming operations * implementation using actual request patterns */ createContextFromPattern(pattern) { return { req: { method: pattern.method, path: pattern.path, query: this.generateQueryParams(pattern.path), headers: { "user-agent": pattern.userAgent, "accept-language": "en-US,en;q=0.9", accept: this.getAcceptHeaderForPath(pattern.path), "content-type": this.getContentTypeForPath(pattern.path), "cache-control": "no-cache", "x-forwarded-for": "192.168.1.100", host: "localhost:3000", }, body: this.generateBody(pattern.path, pattern.method), ip: "192.168.1.100", cookies: this.generateCookies(pattern.path), }, res: { statusCode: 200, headers: {}, locals: {}, }, next: (() => { }), startTime: performance.now(), executionId: `-${pattern.method.toLowerCase()}-${Date.now()}-${Math.random() .toString(36) .substring(2, 11)}`, cache: this.cache, pluginData: new Map(), security: { isAuthenticated: this.shouldBeAuthenticated(pattern.path), userId: this.shouldBeAuthenticated(pattern.path) ? `user_${Date.now()}` : undefined, roles: this.shouldBeAuthenticated(pattern.path) ? ["user"] : [], permissions: this.shouldBeAuthenticated(pattern.path) ? ["read:profile"] : [], }, metrics: { requestStartTime: performance.now(), pluginExecutionTimes: new Map(), cacheHits: Math.floor(Math.random() * 10), cacheMisses: Math.floor(Math.random() * 5), }, }; } /** * Generate query parameters based on path */ generateQueryParams(path) { if (path.includes("/api/users")) { return { page: "1", limit: "10", sort: "name" }; } if (path.includes("/api/products")) { return { category: "electronics", price_min: "100", price_max: "1000", }; } if (path.includes("/search")) { return { q: "test query", filter: "recent" }; } return {}; } /** * Get appropriate Accept header for path */ getAcceptHeaderForPath(path) { if (path.startsWith("/api/")) { return "application/json, text/plain, */*"; } if (path.endsWith(".css")) { return "text/css,*/*;q=0.1"; } if (path.endsWith(".js")) { return "application/javascript, */*;q=0.1"; } if (path.endsWith(".html")) { return "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; } return "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; } /** * Get appropriate Content-Type for path */ getContentTypeForPath(path) { if (path.startsWith("/api/")) { return "application/json"; } if (path.endsWith(".css")) { return "text/css"; } if (path.endsWith(".js")) { return "application/javascript"; } if (path.endsWith(".html")) { return "text/html"; } return "text/html"; } /** * Generate request body based on path and method */ generateBody(path, method) { if (method === "POST" && path.includes("/api/users")) { return { name: "Test User", email: "test@example.com" }; } if (method === "PUT" && path.includes("/api/products")) { return { name: "Updated Product", price: 99.99 }; } return {}; } /** * Generate cookies for path */ generateCookies(path) { const cookies = {}; if (this.shouldBeAuthenticated(path)) { cookies.sessionId = `sess_${Date.now()}_${Math.random() .toString(36) .substring(2, 11)}`; cookies.token = `token_${Date.now()}_${Math.random() .toString(36) .substring(2, 18)}`; } cookies.preferences = "theme=dark;lang=en"; cookies.analytics = `visitor_${Date.now()}`; return cookies; } /** * Determine if path should be authenticated */ shouldBeAuthenticated(path) { return (path.includes("/api/users") || path.includes("/api/profile") || path.includes("/api/admin") || path.includes("/dashboard")); } /** * Analyze cache performance with detailed metrics */ async analyzeCachePerformance(_context) { try { const stats = this.getCacheStats(); const hitRate = stats.totalOperations > 0 ? (stats.hits / (stats.hits + stats.misses)) * 100 : 0; return { hitRate: Math.round(hitRate * 100) / 100, totalOperations: stats.totalOperations, cacheHits: stats.hits, cacheMisses: stats.misses, averageResponseTime: stats.averageResponseTime, errorRate: stats.errorRate, recommendations: this.generateCacheRecommendations(stats), timestamp: new Date().toISOString(), }; } catch (error) { console.error("Error analyzing cache performance:", error); return { error: error.message, timestamp: new Date().toISOString(), }; } } /** * Optimize cache strategy based on performance data */ async optimizeCacheStrategy(context) { try { const analysis = await this.analyzeCachePerformance(context); const optimizations = []; // Suggest optimizations based on hit rate if (analysis.hitRate < 30) { optimizations.push("Consider increasing TTL values to improve hit rate"); optimizations.push("Review caching patterns for frequently accessed content"); } else if (analysis.hitRate > 90) { optimizations.push("Excellent cache performance - consider expanding caching scope"); } // Suggest optimizations based on error rate if (analysis.errorRate > 5) { optimizations.push("High error rate detected - review cache configuration"); } // Suggest optimizations based on response time if (analysis.averageResponseTime > 50) { optimizations.push("Consider using memory cache for frequently accessed data"); } return { currentPerformance: analysis, optimizations, appliedOptimizations: await this.applyAutomaticOptimizations(analysis), timestamp: new Date().toISOString(), }; } catch (error) { console.error("Error optimizing cache strategy:", error); return { error: error.message, timestamp: new Date().toISOString(), }; } } /** * Check cache health status */ async checkCacheHealth() { try { const healthChecks = await Promise.allSettled([ this.checkCacheInstanceHealth("memory", this.memoryCache), this.checkCacheInstanceHealth("file", this.fileCache), this.checkCacheInstanceHealth("hybrid", this.hybridCache), ]); const results = healthChecks.map((result, index) => { const cacheType = ["memory", "file", "hybrid"][index]; return { type: cacheType, status: result.status === "fulfilled" ? result.value : "error", error: result.status === "rejected" ? result.reason : null, }; }); const overallHealth = results.every((r) => r.status === "healthy") ? "healthy" : "degraded"; return { overallHealth, cacheInstances: results, stats: this.getCacheStats(), timestamp: new Date().toISOString(), }; } catch (error) { console.error("Error checking cache health:", error); return { overallHealth: "error", error: error.message, timestamp: new Date().toISOString(), }; } } // ===== CACHE IMPLEMENTATIONS ===== /** * Initialize cache instances */ async initializeCaches() { try { // Initialize memory cache for ultra-fast access this.memoryCache = index$1.createOptimalCache({ type: "memory", config: { maxCacheSize: 50 * 1024 * 1024, // 50MB ttl: 300000, // 5 minutes default encrypt: this.encryptionEnabled, compress: this.compressionEnabled, }, }); // Initialize file cache for persistence this.fileCache = index$1.createOptimalCache({ type: "file", config: { directory: "./cache/plugins", encrypt: this.encryptionEnabled, compress: this.compressionEnabled, maxCacheSize: 100 * 1024 * 1024, // 100MB ttl: 3600000, // 1 hour default }, }); // Initialize hybrid cache for optimal performance this.hybridCache = index$1.createOptimalCache({ type: "hybrid", config: { encrypt: this.encryptionEnabled, compress: this.compressionEnabled, maxCacheSize: 25 * 1024 * 1024, // 25MB ttl: 600000, // 10 minutes default }, }); // Connect all cache instances await Promise.all([ this.memoryCache.connect(), this.fileCache.connect(), this.hybridCache.connect(), ]); } catch (error) { console.error("Error initializing caches:", error); // Fallback to basic cache this.memoryCache = index$1.Cache; } } /** * Initialize cache invalidation patterns */ initializeCachePatterns() { // Content type based TTL patterns this.contentTypePatterns.set("application/json", 300000); // 5 minutes this.contentTypePatterns.set("text/html", 600000); // 10 minutes this.contentTypePatterns.set("text/css", 3600000); // 1 hour this.contentTypePatterns.set("application/javascript", 3600000); // 1 hour this.contentTypePatterns.set("image/*", 86400000); // 24 hours // Invalidation patterns for different route types this.invalidationPatterns.set("api", /^\/api\/.*$/); this.invalidationPatterns.set("user", /^\/api\/users?\/.*$/); this.invalidationPatterns.set("auth", /^\/api\/auth\/.*$/); this.invalidationPatterns.set("static", /\.(css|js|png|jpg|jpeg|gif|svg|ico)$/); } /** * cache invalidation with pattern matching */ async invalidateCacheByPattern(pattern) { let invalidatedCount = 0; try { // Get pattern regex const regex = this.invalidationPatterns.get(pattern); if (!regex) { // Treat as literal pattern await this.invalidateCache(pattern); return 1; } // Get all tagged keys for this pattern const taggedKeys = this.taggedKeys.get(pattern); if (taggedKeys) { for (const key of taggedKeys) { await this.deleteFromAllCaches(key); invalidatedCount++; } // Clear the tag this.taggedKeys.delete(pattern); } // Also invalidate from all cache instances await Promise.allSettled([ this.memoryCache?.invalidateByPattern?.(regex), this.fileCache?.invalidateByPattern?.(regex), this.hybridCache?.invalidateByPattern?.(regex), ]); } catch (error) { console.error(`Error invalidating cache pattern ${pattern}:`, error); } return invalidatedCount; } /** * Dynamic TTL calculation based on content type and usage patterns */ calculateDynamicTTL(context) { const { req } = context; // Check content type patterns const contentType = req.headers["content-type"] || req.headers["accept"]; if (contentType) { for (const [pattern, ttl] of this.contentTypePatterns.entries()) { if (contentType.includes(pattern.replace("*", ""))) { return ttl; } } } // Route-based TTL calculation if (req.path.startsWith("/api/")) { // API endpoints get shorter TTL if (req.path.includes("/users") || req.path.includes("/auth")) { return 60000; // 1 minute for user/auth data } return 300000; // 5 minutes for other API data } // Static resources get longer TTL if (this.isStaticResource(req.path)) { return 86400000; // 24 hours } // Default TTL return 600000; // 10 minutes } /** * Advanced cache key generation with collision resistance */ generateAdvancedCacheKey(context) { const { req } = context; // Create comprehensive key components const components = [ "v2", // Version prefix for cache key format req.method, req.path, this.serializeQueryParams(req.query), this.serializeHeaders(req.headers), context.security.isAuthenticated ? "auth" : "public", context.security.userId || "anonymous", ]; // Add custom components const customComponents = this.getCustomKeyComponents(context); components.push(...customComponents); // Create collision-resistant hash const keyString = components.filter((c) => c).join("|"); const hash = this.hashUtil.create(keyString, { algorithm: "sha256", outputFormat: "hex", }); // Add readable prefix for debugging const prefix = `${req.method.toLowerCase()}_${req.path .replace(/[^a-zA-Z0-9]/g, "_") .substring(0, 20)}`; return `${prefix}_${hash.substring(0, 16)}`; } /** * Delete from all cache instances */ async deleteFromAllCaches(key) { await Promise.allSettled([ this.memoryCache?.delete?.(key), this.fileCache?.delete?.(key), this.hybridCache?.delete?.(key), this.cache?.delete?.(key), ]); } /** * Set in optimal cache instance based on data characteristics */ async setInOptimalCache(key, value, options = {}) { const dataSize = JSON.stringify(value).length; const ttl = options.ttl || 600000; // 10 minutes default try { // Choose cache based on data characteristics if (dataSize < 1024 && ttl < 300000) { // Small, short-lived data -> memory cache await this.memoryCache?.set(key, value, { ttl }); } else if (dataSize > 10240 || ttl > 3600000) { // Large or long-lived data -> file cache await this.fileCache?.set(key, value, { ttl }); } else { // Medium data -> hybrid cache await this.hybridCache?.set(key, value, { ttl }); } // Tag the key for invalidation if (options.tags) { for (const tag of options.tags) { if (!this.taggedKeys.has(tag)) { this.taggedKeys.set(tag, new Set()); } this.taggedKeys.get(tag).add(key); } } } catch (error) { console.error(`Error setting cache key ${key}:`, error); // Fallback to basic cache await this.cache?.set(key, value, { ttl }); } } /** * Get from optimal cache instance */ async getFromOptimalCache(key) { try { // Try memory cache first (fastest) let result = await this.memoryCache?.get(key); if (result !== undefined) { return result; } // Try hybrid cache result = await this.hybridCache?.get(key); if (result !== undefined) { // Promote to memory cache for faster future access await this.memoryCache?.set(key, result, { ttl: 300000 }); return result; } // Try file cache result = await this.fileCache?.get(key); if (result !== undefined) { // Promote to memory cache await this.memoryCache?.set(key, result, { ttl: 300000 }); return result; } // Fallback to basic cache return await this.cache?.get(key); } catch (error) { console.error(`Error getting cache key ${key}:`, error); return undefined; } } // ===== PROTECTED HELPER METHODS ===== /** * Determine cache operation type */ determineCacheOperation(context) { const { req } = context; if (req.method === "GET" && this.shouldCache(context)) { return "get"; } if (["POST", "PUT", "PATCH", "DELETE"].includes(req.method)) { return "invalidate"; } return "none"; } /** * Handle cache get operation */ async handleCacheGet(context) { const cacheKey = this.generateCacheKey(context); const cachedData = await this.fortifiedCache(async () => { return await this.cache.get(cacheKey); }); this.cacheStats.totalOperations++; if (cachedData) { this.cacheStats.hits++; return { hit: true, data: cachedData, key: cacheKey }; } else { this.cacheStats.misses++; return { hit: false, key: cacheKey }; } } /** * Handle cache set operation */ async handleCacheSet(context) { const cacheKey = this.generateCacheKey(context); const ttl = this.getCacheTTL(context); // Queue for post-response processing instead of immediate caching this.queuePostResponseOperation("set", context, { key: cacheKey, ttl, responseData: null, // Will be populated after response }, 1.0); // High priority for cache sets this.cacheStats.sets++; this.cacheStats.totalOperations++; return { cacheData: { key: cacheKey, value: null, // Will be set by the response handler ttl, }, postResponseQueued: true, }; } /** * Queue operation for post-response processing */ queuePostResponseOperation(operation, context, data, priority = 0.5) { // Avoid duplicate operations for the same request const existingIndex = this.postResponseQueue.findIndex((item) => item.context.executionId === context.executionId && item.operation === operation); if (existingIndex >= 0) { // Update existing operation with higher priority if needed if (this.postResponseQueue[existingIndex].priority < priority) { this.postResponseQueue[existingIndex].priority = priority; this.postResponseQueue[existingIndex].data = { ...this.postResponseQueue[existingIndex].data, ...data, }; this.postResponseQueue[existingIndex].timestamp = Date.now(); } return; } // Add new operation to queue this.postResponseQueue.push({ operation, context, data, timestamp: Date.now(), priority, }); // Sort by priority (highest first) this.postResponseQueue.sort((a, b) => b.priority - a.priority); // Limit queue size to prevent memory issues if (this.postResponseQueue.length > 500) { this.postResponseQueue = this.postResponseQueue.slice(0, 500); } // Start post-response worker if not already active if (!this.postResponseWorkerActive) { this.startPostResponseWorker(); } } /** * Start the post-response worker for background cache operations */ async startPostResponseWorker() { if (this.postResponseWorkerActive) return; this.postResponseWorkerActive = true; try { while (this.postResponseQueue.length > 0) { const operation = this.postResponseQueue.shift(); if (!operation) break; // Skip operations that are too old (older than 10 minutes) if (Date.now() - operation.timestamp > 600000) { continue; } await this.executePostResponseOperation(operation); // Small delay to prevent overwhelming the system await new Promise((resolve) => setTimeout(resolve, 5)); } } catch (error) { console.error("Post-response worker error:", error); } finally { this.postResponseWorkerActive = false; } } /** * Execute a post-response cache operation */ async executePostResponseOperation(operation) { try { switch (operation.operation) { case "set": await this.performPostResponseCacheSet(operation); break; case "invalidate": await this.performPostResponseInvalidation(operation); break; case "analyze": await this.performPostResponseAnalysis(operation); break; } } catch (error) { console.error(`Post-response ${operation.operation} operation failed:`, error); this.cacheStats.errors++; } } /** * Perform post-response cache set operation */ async performPostResponseCacheSet(operation) { const { context, data } = operation; const { res } = context; // Check if response is cacheable if (!this.isResponseCacheable(res)) { return; } // Get response data from the response object const responseData = this.extractResponseData(res); if (!responseData) { return; } // Perform the actual cache set operation await this.fortifiedCache(async () => { await this.cache.set(data.key, responseData, { ttl: data.ttl, tags: this.generateCacheTags(context), }); }); // Update cache statistics this.updatePostResponseStats("set", Date.now() - operation.timestamp); } /** * Perform post-response cache invalidation */ async performPostResponseInvalidation(operation) { const { data } = operation; // Invalidate cache patterns for (const pattern of data.patterns || []) { await this.invalidateCache(pattern); } // Update cache statistics this.updatePostResponseStats("invalidate", Date.now() - operation.timestamp); } /** * Perform post-response cache analysis */ async performPostResponseAnalysis(operation) { const { context } = operation; // Analyze cache performance for this request const analysis = { cacheHit: context.metrics.cacheHits > 0, responseTime: Date.now() - context.startTime, cacheKey: this.generateCacheKey(context), shouldCache: this.shouldCache(context), }; // Store analysis data for future optimization await this.storeCacheAnalysis(analysis); // Update cache statistics this.updatePostResponseStats("analyze", Date.now() - operation.timestamp); } /** * Check if response is cacheable */ isResponseCacheable(res) { // Don't cache error responses if (res.statusCode >= 400) { return false; } // Don't cache responses with cache-control: no-cache const cacheControl = res.getHeader("cache-control"); if (cacheControl && cacheControl.includes("no-cache")) { return false; } // Don't cache responses that are too large (>1MB) const contentLength = res.getHeader("content-length"); if (contentLength && parseInt(contentLength) > 1024 * 1024) { return false; } return true; } /** * Extract response data for caching */ extractResponseData(res) { // Extract actual response body and metadata const responseId = this.generateResponseId(res); const capturedResponse = this.responseBodyCapture.get(responseId); let responseBody = null; let contentSize = 0; if (capturedResponse) { responseBody = capturedResponse.body; contentSize = this.calculateContentSize(responseBody); } else { // Fallback: try to extract from response object directly responseBody = this.extractBodyFromResponse(res); contentSize = this.calculateContentSize(responseBody); } const responseData = { statusCode: res.statusCode, headers: res.getHeaders ? res.getHeaders() : res.headers || {}, body: responseBody, contentSize, timestamp: Date.now(), encoding: res.getHeader ? res.getHeader("content-encoding") : null, contentType: res.getHeader ? res.getHeader("content-type") : null, }; // Clean up captured response data if (capturedResponse) { this.responseBodyCapture.delete(responseId); } return responseData; } /** * Generate unique response ID for tracking */