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
JavaScript
'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
*/