xypriss 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -3
- package/dist/cjs/mods/security/src/components/cache/index.js +1 -1
- package/dist/cjs/shared/logger/Logger.js +1 -0
- package/dist/cjs/shared/logger/Logger.js.map +1 -1
- package/dist/cjs/src/cluster/bun-cluster-manager.js +1567 -0
- package/dist/cjs/src/cluster/bun-cluster-manager.js.map +1 -0
- package/dist/cjs/src/cluster/cluster-manager.js +1 -1
- package/dist/cjs/src/cluster/cluster-manager.js.map +1 -1
- package/dist/cjs/src/cluster/index.js +25 -6
- package/dist/cjs/src/cluster/index.js.map +1 -1
- package/dist/cjs/src/cluster/memory-manager.js +463 -0
- package/dist/cjs/src/cluster/memory-manager.js.map +1 -0
- package/dist/cjs/src/cluster/modules/BunIPCManager.js +603 -0
- package/dist/cjs/src/cluster/modules/BunIPCManager.js.map +1 -0
- package/dist/cjs/src/cluster/modules/ClusterFactory.js +22 -1
- package/dist/cjs/src/cluster/modules/ClusterFactory.js.map +1 -1
- package/dist/cjs/src/cluster/modules/CpuMonitor.js +658 -0
- package/dist/cjs/src/cluster/modules/CpuMonitor.js.map +1 -0
- package/dist/cjs/src/cluster/modules/ProcessMonitor.js +513 -0
- package/dist/cjs/src/cluster/modules/ProcessMonitor.js.map +1 -0
- package/dist/cjs/src/plugins/server-maintenance-plugin.js +1 -1
- package/dist/cjs/src/server/FastServer.js +64 -43
- package/dist/cjs/src/server/FastServer.js.map +1 -1
- package/dist/cjs/src/server/components/fastapi/ClusterManagerComponent.js +226 -10
- package/dist/cjs/src/server/components/fastapi/ClusterManagerComponent.js.map +1 -1
- package/dist/cjs/src/server/const/Cluster.config.js +174 -31
- package/dist/cjs/src/server/const/Cluster.config.js.map +1 -1
- package/dist/cjs/src/server/const/default.js +11 -2
- package/dist/cjs/src/server/const/default.js.map +1 -1
- package/dist/cjs/src/server/utils/PortManager.js +26 -15
- package/dist/cjs/src/server/utils/PortManager.js.map +1 -1
- package/dist/esm/mods/security/src/components/cache/index.js +1 -1
- package/dist/esm/shared/logger/Logger.js +1 -0
- package/dist/esm/shared/logger/Logger.js.map +1 -1
- package/dist/esm/src/cluster/bun-cluster-manager.js +1565 -0
- package/dist/esm/src/cluster/bun-cluster-manager.js.map +1 -0
- package/dist/esm/src/cluster/cluster-manager.js +1 -1
- package/dist/esm/src/cluster/cluster-manager.js.map +1 -1
- package/dist/esm/src/cluster/index.js +25 -6
- package/dist/esm/src/cluster/index.js.map +1 -1
- package/dist/esm/src/cluster/memory-manager.js +461 -0
- package/dist/esm/src/cluster/memory-manager.js.map +1 -0
- package/dist/esm/src/cluster/modules/BunIPCManager.js +601 -0
- package/dist/esm/src/cluster/modules/BunIPCManager.js.map +1 -0
- package/dist/esm/src/cluster/modules/ClusterFactory.js +22 -1
- package/dist/esm/src/cluster/modules/ClusterFactory.js.map +1 -1
- package/dist/esm/src/cluster/modules/CpuMonitor.js +656 -0
- package/dist/esm/src/cluster/modules/CpuMonitor.js.map +1 -0
- package/dist/esm/src/cluster/modules/ProcessMonitor.js +511 -0
- package/dist/esm/src/cluster/modules/ProcessMonitor.js.map +1 -0
- package/dist/esm/src/plugins/server-maintenance-plugin.js +1 -1
- package/dist/esm/src/server/FastServer.js +64 -43
- package/dist/esm/src/server/FastServer.js.map +1 -1
- package/dist/esm/src/server/components/fastapi/ClusterManagerComponent.js +226 -10
- package/dist/esm/src/server/components/fastapi/ClusterManagerComponent.js.map +1 -1
- package/dist/esm/src/server/const/Cluster.config.js +174 -31
- package/dist/esm/src/server/const/Cluster.config.js.map +1 -1
- package/dist/esm/src/server/const/default.js +11 -2
- package/dist/esm/src/server/const/default.js.map +1 -1
- package/dist/esm/src/server/utils/PortManager.js +26 -15
- package/dist/esm/src/server/utils/PortManager.js.map +1 -1
- package/dist/index.d.ts +78 -1
- package/package.json +3 -1
- package/dist/cjs/src/plugins/modules/network/index.js +0 -120
- package/dist/cjs/src/plugins/modules/network/index.js.map +0 -1
- package/dist/cjs/src/server/plugins/PluginEngine.js +0 -378
- package/dist/cjs/src/server/plugins/PluginEngine.js.map +0 -1
- package/dist/cjs/src/server/plugins/PluginRegistry.js +0 -339
- package/dist/cjs/src/server/plugins/PluginRegistry.js.map +0 -1
- package/dist/cjs/src/server/plugins/builtin/JWTAuthPlugin.js +0 -591
- package/dist/cjs/src/server/plugins/builtin/JWTAuthPlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/builtin/ResponseTimePlugin.js +0 -413
- package/dist/cjs/src/server/plugins/builtin/ResponseTimePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/builtin/SmartCachePlugin.js +0 -843
- package/dist/cjs/src/server/plugins/builtin/SmartCachePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/core/CachePlugin.js +0 -1975
- package/dist/cjs/src/server/plugins/core/CachePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/core/PerformancePlugin.js +0 -894
- package/dist/cjs/src/server/plugins/core/PerformancePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/core/SecurityPlugin.js +0 -799
- package/dist/cjs/src/server/plugins/core/SecurityPlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/types/PluginTypes.js +0 -47
- package/dist/cjs/src/server/plugins/types/PluginTypes.js.map +0 -1
- package/dist/esm/src/plugins/modules/network/index.js +0 -109
- package/dist/esm/src/plugins/modules/network/index.js.map +0 -1
- package/dist/esm/src/server/plugins/PluginEngine.js +0 -376
- package/dist/esm/src/server/plugins/PluginEngine.js.map +0 -1
- package/dist/esm/src/server/plugins/PluginRegistry.js +0 -337
- package/dist/esm/src/server/plugins/PluginRegistry.js.map +0 -1
- package/dist/esm/src/server/plugins/builtin/JWTAuthPlugin.js +0 -589
- package/dist/esm/src/server/plugins/builtin/JWTAuthPlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/builtin/ResponseTimePlugin.js +0 -411
- package/dist/esm/src/server/plugins/builtin/ResponseTimePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/builtin/SmartCachePlugin.js +0 -841
- package/dist/esm/src/server/plugins/builtin/SmartCachePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/core/CachePlugin.js +0 -1973
- package/dist/esm/src/server/plugins/core/CachePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/core/PerformancePlugin.js +0 -872
- package/dist/esm/src/server/plugins/core/PerformancePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/core/SecurityPlugin.js +0 -797
- package/dist/esm/src/server/plugins/core/SecurityPlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/types/PluginTypes.js +0 -47
- package/dist/esm/src/server/plugins/types/PluginTypes.js.map +0 -1
|
@@ -1,1975 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var index = require('../../../../mods/security/src/components/fortified-function/index.js');
|
|
4
|
-
var hashCore = require('../../../../mods/security/src/core/hash/hash-core.js');
|
|
5
|
-
require('../../../../mods/security/src/core/hash/hash-types.js');
|
|
6
|
-
require('crypto');
|
|
7
|
-
require('../../../../mods/security/src/core/hash/hash-security.js');
|
|
8
|
-
require('../../../../mods/security/src/core/hash/hash-advanced.js');
|
|
9
|
-
require('../../../../mods/security/src/algorithms/hash-algorithms.js');
|
|
10
|
-
var index$1 = require('../../../../mods/security/src/components/cache/index.js');
|
|
11
|
-
var PluginTypes = require('../types/PluginTypes.js');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Cache Plugin Base Class
|
|
15
|
-
*
|
|
16
|
-
* Foundation for cache optimization plugins with <0.5ms execution overhead
|
|
17
|
-
* leveraging XyPrissJS cache systems for ultra-fast performance.
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* Abstract base class for cache optimization plugins
|
|
21
|
-
*/
|
|
22
|
-
class CachePlugin {
|
|
23
|
-
constructor() {
|
|
24
|
-
this.type = PluginTypes.PluginType.CACHE;
|
|
25
|
-
this.priority = PluginTypes.PluginPriority.HIGH;
|
|
26
|
-
this.isAsync = true;
|
|
27
|
-
this.isCacheable = false; // Cache plugins themselves shouldn't be cached
|
|
28
|
-
this.maxExecutionTime = 500; // 0.5ms max for cache operations
|
|
29
|
-
// Cache configuration
|
|
30
|
-
this.cacheStrategy = "hybrid";
|
|
31
|
-
this.compressionEnabled = true;
|
|
32
|
-
this.encryptionEnabled = true;
|
|
33
|
-
// Cache statistics
|
|
34
|
-
this.cacheStats = {
|
|
35
|
-
hits: 0,
|
|
36
|
-
misses: 0,
|
|
37
|
-
sets: 0,
|
|
38
|
-
deletes: 0,
|
|
39
|
-
errors: 0,
|
|
40
|
-
totalOperations: 0,
|
|
41
|
-
averageResponseTime: 0,
|
|
42
|
-
lastOperation: new Date(),
|
|
43
|
-
};
|
|
44
|
-
// Post-response cache operations queue
|
|
45
|
-
this.postResponseQueue = [];
|
|
46
|
-
this.postResponseWorkerActive = false;
|
|
47
|
-
// Response body capture system
|
|
48
|
-
this.responseBodyCapture = new Map();
|
|
49
|
-
// Analysis data storage
|
|
50
|
-
this.analysisStorage = [];
|
|
51
|
-
// Cache invalidation patterns and tagging
|
|
52
|
-
this.invalidationPatterns = new Map();
|
|
53
|
-
this.taggedKeys = new Map();
|
|
54
|
-
this.contentTypePatterns = new Map();
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Initialize cache plugin with XyPrissJS utilities
|
|
58
|
-
*/
|
|
59
|
-
async initialize(context) {
|
|
60
|
-
this.cache = context.cache;
|
|
61
|
-
this.hashUtil = hashCore.Hash;
|
|
62
|
-
// Create fortified cache wrapper for ultra-fast operations
|
|
63
|
-
this.fortifiedCache = index.func(async (operation) => {
|
|
64
|
-
return await operation();
|
|
65
|
-
}, {
|
|
66
|
-
ultraFast: "maximum",
|
|
67
|
-
autoEncrypt: this.encryptionEnabled,
|
|
68
|
-
auditLog: false, // Disable audit logging for cache operations
|
|
69
|
-
timeout: this.maxExecutionTime,
|
|
70
|
-
errorHandling: "graceful",
|
|
71
|
-
});
|
|
72
|
-
// Initialize cache instances
|
|
73
|
-
await this.initializeCaches();
|
|
74
|
-
// Initialize cache invalidation patterns
|
|
75
|
-
this.initializeCachePatterns();
|
|
76
|
-
// Initialize plugin-specific cache features
|
|
77
|
-
await this.initializeCachePlugin(context);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Execute cache plugin with ultra-fast performance
|
|
81
|
-
*/
|
|
82
|
-
async execute(context) {
|
|
83
|
-
const startTime = performance.now();
|
|
84
|
-
try {
|
|
85
|
-
// Determine cache operation type
|
|
86
|
-
const operation = this.determineCacheOperation(context);
|
|
87
|
-
let result;
|
|
88
|
-
let cacheData;
|
|
89
|
-
switch (operation) {
|
|
90
|
-
case "get":
|
|
91
|
-
result = await this.handleCacheGet(context);
|
|
92
|
-
break;
|
|
93
|
-
case "set":
|
|
94
|
-
result = await this.handleCacheSet(context);
|
|
95
|
-
cacheData = result.cacheData;
|
|
96
|
-
break;
|
|
97
|
-
case "invalidate":
|
|
98
|
-
result = await this.handleCacheInvalidate(context);
|
|
99
|
-
break;
|
|
100
|
-
case "warmup":
|
|
101
|
-
result = await this.handleCacheWarmup(context);
|
|
102
|
-
break;
|
|
103
|
-
default:
|
|
104
|
-
result = await this.handleCustomCacheOperation(context, operation);
|
|
105
|
-
}
|
|
106
|
-
const executionTime = performance.now() - startTime;
|
|
107
|
-
// Update cache statistics
|
|
108
|
-
this.updateCacheStats(operation, executionTime, true);
|
|
109
|
-
// Update context metrics
|
|
110
|
-
if (operation === "get" && result.hit) {
|
|
111
|
-
context.metrics.cacheHits++;
|
|
112
|
-
}
|
|
113
|
-
else if (operation === "get" && !result.hit) {
|
|
114
|
-
context.metrics.cacheMisses++;
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
success: true,
|
|
118
|
-
executionTime,
|
|
119
|
-
data: result,
|
|
120
|
-
shouldContinue: true,
|
|
121
|
-
cacheData,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
const executionTime = performance.now() - startTime;
|
|
126
|
-
// Update error statistics
|
|
127
|
-
this.updateCacheStats("error", executionTime, false);
|
|
128
|
-
return {
|
|
129
|
-
success: false,
|
|
130
|
-
executionTime,
|
|
131
|
-
error,
|
|
132
|
-
shouldContinue: true, // Cache errors shouldn't stop execution
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Check if request should be cached
|
|
138
|
-
*/
|
|
139
|
-
shouldCache(context) {
|
|
140
|
-
// Default caching logic - can be overridden by subclasses
|
|
141
|
-
const { req } = context;
|
|
142
|
-
// Only cache GET requests by default
|
|
143
|
-
if (req.method !== "GET") {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
// Don't cache requests with authentication headers
|
|
147
|
-
if (req.headers.authorization) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
// Don't cache requests with query parameters that indicate dynamic content
|
|
151
|
-
if (this.hasDynamicQueryParams(req.query)) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
// Apply plugin-specific caching rules
|
|
155
|
-
return this.shouldCacheRequest(context);
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Generate cache key for request
|
|
159
|
-
*/
|
|
160
|
-
generateCacheKey(context) {
|
|
161
|
-
const { req } = context;
|
|
162
|
-
// Create base key components
|
|
163
|
-
const components = [
|
|
164
|
-
req.method,
|
|
165
|
-
req.path,
|
|
166
|
-
this.serializeQueryParams(req.query),
|
|
167
|
-
this.serializeHeaders(req.headers),
|
|
168
|
-
];
|
|
169
|
-
// Add plugin-specific key components
|
|
170
|
-
const customComponents = this.getCustomKeyComponents(context);
|
|
171
|
-
components.push(...customComponents);
|
|
172
|
-
// Create hash of components for consistent key generation
|
|
173
|
-
const keyString = components.filter((c) => c).join(":");
|
|
174
|
-
return this.hashUtil.create(keyString, {
|
|
175
|
-
algorithm: "sha256",
|
|
176
|
-
outputFormat: "hex",
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Get cache TTL for request
|
|
181
|
-
*/
|
|
182
|
-
getCacheTTL(context) {
|
|
183
|
-
// Default TTL logic - can be overridden by subclasses
|
|
184
|
-
const { req } = context;
|
|
185
|
-
// Static resources get longer TTL
|
|
186
|
-
if (this.isStaticResource(req.path)) {
|
|
187
|
-
return 3600000; // 1 hour
|
|
188
|
-
}
|
|
189
|
-
// API responses get shorter TTL
|
|
190
|
-
if (req.path.startsWith("/api/")) {
|
|
191
|
-
return 300000; // 5 minutes
|
|
192
|
-
}
|
|
193
|
-
// Apply plugin-specific TTL logic
|
|
194
|
-
return this.getCustomTTL(context);
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Invalidate cache by pattern
|
|
198
|
-
*/
|
|
199
|
-
async invalidateCache(pattern) {
|
|
200
|
-
if (!this.cache) {
|
|
201
|
-
throw new Error("Cache not initialized");
|
|
202
|
-
}
|
|
203
|
-
try {
|
|
204
|
-
await this.fortifiedCache(async () => {
|
|
205
|
-
await this.cache.invalidateByTags([pattern]);
|
|
206
|
-
});
|
|
207
|
-
this.cacheStats.deletes++;
|
|
208
|
-
this.cacheStats.totalOperations++;
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
this.cacheStats.errors++;
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Precompile cache operations for optimal performance
|
|
217
|
-
*/
|
|
218
|
-
async precompile() {
|
|
219
|
-
// Pre-warm cache key generation
|
|
220
|
-
await this.precompileCacheOperations();
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Warm up cache plugin
|
|
224
|
-
*/
|
|
225
|
-
async warmup(context) {
|
|
226
|
-
// Perform initial cache operations to warm up systems
|
|
227
|
-
if (this.shouldCache(context)) {
|
|
228
|
-
this.generateCacheKey(context);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// ===== CACHE IMPLEMENTATIONS =====
|
|
232
|
-
/**
|
|
233
|
-
* Initialize plugin-specific cache features
|
|
234
|
-
* implementation with comprehensive error handling
|
|
235
|
-
*/
|
|
236
|
-
async initializeCachePlugin(context) {
|
|
237
|
-
try {
|
|
238
|
-
// Initialize cache invalidation patterns based on configuration
|
|
239
|
-
if (context.config.customSettings.invalidationPatterns) {
|
|
240
|
-
for (const [name, pattern] of Object.entries(context.config.customSettings.invalidationPatterns)) {
|
|
241
|
-
this.invalidationPatterns.set(name, new RegExp(pattern));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// Initialize content type TTL mappings
|
|
245
|
-
if (context.config.customSettings.contentTypeTTL) {
|
|
246
|
-
for (const [type, ttl] of Object.entries(context.config.customSettings.contentTypeTTL)) {
|
|
247
|
-
this.contentTypePatterns.set(type, ttl);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// Setup cache warming if enabled
|
|
251
|
-
if (context.config.customSettings.enableCacheWarming) {
|
|
252
|
-
await this.setupCacheWarming(context);
|
|
253
|
-
}
|
|
254
|
-
context.logger.info(`Cache plugin ${this.constructor.name} initialized successfully`);
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
context.logger.error(`Error initializing cache plugin: ${error.message}`, error);
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Check if request should be cached (plugin-specific logic)
|
|
263
|
-
* implementation with comprehensive caching rules
|
|
264
|
-
*/
|
|
265
|
-
shouldCacheRequest(context) {
|
|
266
|
-
const { req } = context;
|
|
267
|
-
try {
|
|
268
|
-
// Don't cache non-GET requests by default
|
|
269
|
-
if (req.method !== "GET") {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
// Don't cache requests with authentication unless explicitly configured
|
|
273
|
-
if (req.headers.authorization &&
|
|
274
|
-
!this.allowAuthenticatedCaching()) {
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
// Don't cache requests with cache-control: no-cache
|
|
278
|
-
const cacheControl = req.headers["cache-control"];
|
|
279
|
-
if (cacheControl && cacheControl.includes("no-cache")) {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
// Don't cache requests with dynamic query parameters
|
|
283
|
-
if (this.hasDynamicQueryParams(req.query)) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
// Check if path matches any cacheable patterns
|
|
287
|
-
return this.matchesCacheablePattern(req.path);
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
console.error(`Error in shouldCacheRequest: ${error}`);
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Get custom cache key components
|
|
296
|
-
* implementation with collision-resistant key generation
|
|
297
|
-
*/
|
|
298
|
-
getCustomKeyComponents(context) {
|
|
299
|
-
const { req } = context;
|
|
300
|
-
const components = [];
|
|
301
|
-
try {
|
|
302
|
-
// Add user context for personalized caching
|
|
303
|
-
if (context.security.isAuthenticated &&
|
|
304
|
-
this.allowAuthenticatedCaching()) {
|
|
305
|
-
components.push(`user:${context.security.userId}`);
|
|
306
|
-
// Add role-based components
|
|
307
|
-
if (context.security.roles.length > 0) {
|
|
308
|
-
components.push(`roles:${context.security.roles.sort().join(",")}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// Add device type for responsive content
|
|
312
|
-
const userAgent = req.headers["user-agent"];
|
|
313
|
-
if (userAgent) {
|
|
314
|
-
const deviceType = this.detectDeviceType(userAgent);
|
|
315
|
-
components.push(`device:${deviceType}`);
|
|
316
|
-
}
|
|
317
|
-
// Add language for internationalization
|
|
318
|
-
const acceptLanguage = req.headers["accept-language"];
|
|
319
|
-
if (acceptLanguage) {
|
|
320
|
-
const primaryLang = acceptLanguage.split(",")[0].split("-")[0];
|
|
321
|
-
components.push(`lang:${primaryLang}`);
|
|
322
|
-
}
|
|
323
|
-
// Add API version for versioned APIs
|
|
324
|
-
const apiVersion = req.headers["api-version"] || req.query.version;
|
|
325
|
-
if (apiVersion) {
|
|
326
|
-
components.push(`version:${apiVersion}`);
|
|
327
|
-
}
|
|
328
|
-
// Add content encoding for compression-aware caching
|
|
329
|
-
const acceptEncoding = req.headers["accept-encoding"];
|
|
330
|
-
if (acceptEncoding) {
|
|
331
|
-
const encodings = acceptEncoding
|
|
332
|
-
.split(",")
|
|
333
|
-
.map((e) => e.trim());
|
|
334
|
-
if (encodings.includes("gzip")) {
|
|
335
|
-
components.push("encoding:gzip");
|
|
336
|
-
}
|
|
337
|
-
else if (encodings.includes("br")) {
|
|
338
|
-
components.push("encoding:br");
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return components;
|
|
342
|
-
}
|
|
343
|
-
catch (error) {
|
|
344
|
-
console.error(`Error generating custom key components: ${error}`);
|
|
345
|
-
return [];
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Get custom TTL for request
|
|
350
|
-
* implementation with intelligent TTL calculation
|
|
351
|
-
*/
|
|
352
|
-
getCustomTTL(context) {
|
|
353
|
-
const { req } = context;
|
|
354
|
-
try {
|
|
355
|
-
// Use dynamic TTL calculation for optimal performance
|
|
356
|
-
const dynamicTTL = this.calculateDynamicTTL(context);
|
|
357
|
-
if (dynamicTTL > 0) {
|
|
358
|
-
return dynamicTTL;
|
|
359
|
-
}
|
|
360
|
-
// Fallback to content-type based TTL
|
|
361
|
-
const contentType = req.headers["content-type"] || req.headers["accept"];
|
|
362
|
-
if (contentType) {
|
|
363
|
-
for (const [pattern, ttl,] of this.contentTypePatterns.entries()) {
|
|
364
|
-
if (contentType.includes(pattern.replace("*", ""))) {
|
|
365
|
-
return ttl;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
// Route-based TTL calculation
|
|
370
|
-
if (req.path.startsWith("/api/")) {
|
|
371
|
-
// API endpoints get shorter TTL
|
|
372
|
-
if (req.path.includes("/users") || req.path.includes("/auth")) {
|
|
373
|
-
return 60000; // 1 minute for user/auth data
|
|
374
|
-
}
|
|
375
|
-
return 300000; // 5 minutes for other API data
|
|
376
|
-
}
|
|
377
|
-
// Static resources get longer TTL
|
|
378
|
-
if (this.isStaticResource(req.path)) {
|
|
379
|
-
return 86400000; // 24 hours
|
|
380
|
-
}
|
|
381
|
-
// Default TTL
|
|
382
|
-
return 600000; // 10 minutes
|
|
383
|
-
}
|
|
384
|
-
catch (error) {
|
|
385
|
-
console.error(`Error calculating custom TTL: ${error}`);
|
|
386
|
-
return 300000; // 5 minutes fallback
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Handle custom cache operations
|
|
391
|
-
* implementation with comprehensive operation support
|
|
392
|
-
*/
|
|
393
|
-
async handleCustomCacheOperation(context, operation) {
|
|
394
|
-
try {
|
|
395
|
-
switch (operation) {
|
|
396
|
-
case "analyze":
|
|
397
|
-
return await this.analyzeCachePerformance(context);
|
|
398
|
-
case "optimize":
|
|
399
|
-
return await this.optimizeCacheStrategy(context);
|
|
400
|
-
case "warmup":
|
|
401
|
-
return await this.handleCacheWarmup(context);
|
|
402
|
-
case "invalidate":
|
|
403
|
-
return await this.handleCacheInvalidate(context);
|
|
404
|
-
case "stats":
|
|
405
|
-
return this.getCacheStats();
|
|
406
|
-
case "health":
|
|
407
|
-
return await this.checkCacheHealth();
|
|
408
|
-
default:
|
|
409
|
-
return {
|
|
410
|
-
operation,
|
|
411
|
-
supported: false,
|
|
412
|
-
message: `Operation '${operation}' is not supported`,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
catch (error) {
|
|
417
|
-
console.error(`Error handling custom cache operation '${operation}':`, error);
|
|
418
|
-
return {
|
|
419
|
-
operation,
|
|
420
|
-
success: false,
|
|
421
|
-
error: error.message,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Precompile cache operations
|
|
427
|
-
* implementation with comprehensive pre-warming
|
|
428
|
-
*/
|
|
429
|
-
async precompileCacheOperations() {
|
|
430
|
-
try {
|
|
431
|
-
// Pre-warm cache key generation request patterns
|
|
432
|
-
const RequestPatterns = [
|
|
433
|
-
{
|
|
434
|
-
path: "/api/users",
|
|
435
|
-
method: "GET",
|
|
436
|
-
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
path: "/api/products",
|
|
440
|
-
method: "GET",
|
|
441
|
-
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)",
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
path: "/static/css/style.css",
|
|
445
|
-
method: "GET",
|
|
446
|
-
userAgent: "Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)",
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
path: "/public/index.html",
|
|
450
|
-
method: "GET",
|
|
451
|
-
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
|
|
452
|
-
},
|
|
453
|
-
];
|
|
454
|
-
for (const pattern of RequestPatterns) {
|
|
455
|
-
const Context = this.createContextFromPattern(pattern);
|
|
456
|
-
// Pre-warm cache key generation
|
|
457
|
-
this.generateCacheKey(Context);
|
|
458
|
-
this.generateAdvancedCacheKey(Context);
|
|
459
|
-
// Pre-warm TTL calculation
|
|
460
|
-
this.calculateDynamicTTL(Context);
|
|
461
|
-
// Pre-warm custom key components
|
|
462
|
-
this.getCustomKeyComponents(Context);
|
|
463
|
-
// Pre-warm custom TTL calculation
|
|
464
|
-
this.getCustomTTL(Context);
|
|
465
|
-
}
|
|
466
|
-
// Pre-warm device detection user agents
|
|
467
|
-
const UserAgents = [
|
|
468
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
|
469
|
-
"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",
|
|
470
|
-
"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",
|
|
471
|
-
"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",
|
|
472
|
-
];
|
|
473
|
-
for (const userAgent of UserAgents) {
|
|
474
|
-
this.detectDeviceType(userAgent);
|
|
475
|
-
}
|
|
476
|
-
// Pre-warm cache pattern matching paths
|
|
477
|
-
const Paths = [
|
|
478
|
-
"/api/users",
|
|
479
|
-
"/api/products",
|
|
480
|
-
"/api/orders",
|
|
481
|
-
"/static/css/style.css",
|
|
482
|
-
"/static/js/app.js",
|
|
483
|
-
"/static/images/logo.png",
|
|
484
|
-
"/public/index.html",
|
|
485
|
-
"/public/about.html",
|
|
486
|
-
"/public/contact.html",
|
|
487
|
-
"/health",
|
|
488
|
-
"/status",
|
|
489
|
-
"/ping",
|
|
490
|
-
];
|
|
491
|
-
for (const path of Paths) {
|
|
492
|
-
this.matchesCacheablePattern(path);
|
|
493
|
-
}
|
|
494
|
-
// Pre-warm cache operations connection attempts
|
|
495
|
-
const cacheConnections = await Promise.allSettled([
|
|
496
|
-
this.memoryCache?.connect?.(),
|
|
497
|
-
this.fileCache?.connect?.(),
|
|
498
|
-
this.hybridCache?.connect?.(),
|
|
499
|
-
]);
|
|
500
|
-
const successfulConnections = cacheConnections.filter((result) => result.status === "fulfilled").length;
|
|
501
|
-
console.debug("Cache operations precompiled successfully patterns", {
|
|
502
|
-
requestPatterns: RequestPatterns.length,
|
|
503
|
-
userAgents: UserAgents.length,
|
|
504
|
-
cachePaths: Paths.length,
|
|
505
|
-
cacheConnections: successfulConnections,
|
|
506
|
-
timestamp: Date.now(),
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
catch (error) {
|
|
510
|
-
console.error("Error precompiling cache operations:", error);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
// ===== HELPER METHODS =====
|
|
514
|
-
/**
|
|
515
|
-
* Setup cache warming for frequently accessed content
|
|
516
|
-
*/
|
|
517
|
-
async setupCacheWarming(context) {
|
|
518
|
-
try {
|
|
519
|
-
const warmupUrls = context.config.customSettings.warmupUrls || [
|
|
520
|
-
"/api/health",
|
|
521
|
-
"/api/status",
|
|
522
|
-
"/public/manifest.json",
|
|
523
|
-
];
|
|
524
|
-
for (const url of warmupUrls) {
|
|
525
|
-
const warmupKey = this.hashUtil.create(`warmup:${url}`, {
|
|
526
|
-
algorithm: "sha256",
|
|
527
|
-
outputFormat: "hex",
|
|
528
|
-
});
|
|
529
|
-
await this.setInOptimalCache(warmupKey, { warmedUp: true, url, timestamp: Date.now() }, { ttl: 300000 } // 5 minutes
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
context.logger.info(`Cache warming setup completed for ${warmupUrls.length} URLs`);
|
|
533
|
-
}
|
|
534
|
-
catch (error) {
|
|
535
|
-
context.logger.error(`Error setting up cache warming: ${error.message}`, error);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Check if authenticated caching is allowed
|
|
540
|
-
*/
|
|
541
|
-
allowAuthenticatedCaching() {
|
|
542
|
-
// By default, don't cache authenticated requests for security
|
|
543
|
-
// Subclasses can override this behavior
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Check if path matches cacheable patterns
|
|
548
|
-
*/
|
|
549
|
-
matchesCacheablePattern(path) {
|
|
550
|
-
try {
|
|
551
|
-
// Static resources are always cacheable
|
|
552
|
-
if (this.isStaticResource(path)) {
|
|
553
|
-
return true;
|
|
554
|
-
}
|
|
555
|
-
// Public API endpoints are cacheable
|
|
556
|
-
if (path.startsWith("/api/public/")) {
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
// Health and status endpoints are cacheable
|
|
560
|
-
if (path.match(/\/(health|status|ping)$/)) {
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
// Check against configured patterns
|
|
564
|
-
for (const pattern of this.invalidationPatterns.values()) {
|
|
565
|
-
if (pattern.test(path)) {
|
|
566
|
-
return true;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
return false;
|
|
570
|
-
}
|
|
571
|
-
catch (error) {
|
|
572
|
-
console.error(`Error matching cacheable pattern for ${path}:`, error);
|
|
573
|
-
return false;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Detect device type from user agent
|
|
578
|
-
*/
|
|
579
|
-
detectDeviceType(userAgent) {
|
|
580
|
-
try {
|
|
581
|
-
if (/Mobile|Android|iPhone/.test(userAgent)) {
|
|
582
|
-
return "mobile";
|
|
583
|
-
}
|
|
584
|
-
if (/iPad|Tablet/.test(userAgent)) {
|
|
585
|
-
return "tablet";
|
|
586
|
-
}
|
|
587
|
-
if (/Bot|Crawler|Spider|Scraper/.test(userAgent)) {
|
|
588
|
-
return "bot";
|
|
589
|
-
}
|
|
590
|
-
return "desktop";
|
|
591
|
-
}
|
|
592
|
-
catch (error) {
|
|
593
|
-
console.error(`Error detecting device type: ${error}`);
|
|
594
|
-
return "unknown";
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Create context from request pattern for pre-warming operations
|
|
599
|
-
* implementation using actual request patterns
|
|
600
|
-
*/
|
|
601
|
-
createContextFromPattern(pattern) {
|
|
602
|
-
return {
|
|
603
|
-
req: {
|
|
604
|
-
method: pattern.method,
|
|
605
|
-
path: pattern.path,
|
|
606
|
-
query: this.generateQueryParams(pattern.path),
|
|
607
|
-
headers: {
|
|
608
|
-
"user-agent": pattern.userAgent,
|
|
609
|
-
"accept-language": "en-US,en;q=0.9",
|
|
610
|
-
accept: this.getAcceptHeaderForPath(pattern.path),
|
|
611
|
-
"content-type": this.getContentTypeForPath(pattern.path),
|
|
612
|
-
"cache-control": "no-cache",
|
|
613
|
-
"x-forwarded-for": "192.168.1.100",
|
|
614
|
-
host: "localhost:3000",
|
|
615
|
-
},
|
|
616
|
-
body: this.generateBody(pattern.path, pattern.method),
|
|
617
|
-
ip: "192.168.1.100",
|
|
618
|
-
cookies: this.generateCookies(pattern.path),
|
|
619
|
-
},
|
|
620
|
-
res: {
|
|
621
|
-
statusCode: 200,
|
|
622
|
-
headers: {},
|
|
623
|
-
locals: {},
|
|
624
|
-
},
|
|
625
|
-
next: (() => { }),
|
|
626
|
-
startTime: performance.now(),
|
|
627
|
-
executionId: `-${pattern.method.toLowerCase()}-${Date.now()}-${Math.random()
|
|
628
|
-
.toString(36)
|
|
629
|
-
.substring(2, 11)}`,
|
|
630
|
-
cache: this.cache,
|
|
631
|
-
pluginData: new Map(),
|
|
632
|
-
security: {
|
|
633
|
-
isAuthenticated: this.shouldBeAuthenticated(pattern.path),
|
|
634
|
-
userId: this.shouldBeAuthenticated(pattern.path)
|
|
635
|
-
? `user_${Date.now()}`
|
|
636
|
-
: undefined,
|
|
637
|
-
roles: this.shouldBeAuthenticated(pattern.path) ? ["user"] : [],
|
|
638
|
-
permissions: this.shouldBeAuthenticated(pattern.path)
|
|
639
|
-
? ["read:profile"]
|
|
640
|
-
: [],
|
|
641
|
-
},
|
|
642
|
-
metrics: {
|
|
643
|
-
requestStartTime: performance.now(),
|
|
644
|
-
pluginExecutionTimes: new Map(),
|
|
645
|
-
cacheHits: Math.floor(Math.random() * 10),
|
|
646
|
-
cacheMisses: Math.floor(Math.random() * 5),
|
|
647
|
-
},
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
/**
|
|
651
|
-
* Generate query parameters based on path
|
|
652
|
-
*/
|
|
653
|
-
generateQueryParams(path) {
|
|
654
|
-
if (path.includes("/api/users")) {
|
|
655
|
-
return { page: "1", limit: "10", sort: "name" };
|
|
656
|
-
}
|
|
657
|
-
if (path.includes("/api/products")) {
|
|
658
|
-
return {
|
|
659
|
-
category: "electronics",
|
|
660
|
-
price_min: "100",
|
|
661
|
-
price_max: "1000",
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
if (path.includes("/search")) {
|
|
665
|
-
return { q: "test query", filter: "recent" };
|
|
666
|
-
}
|
|
667
|
-
return {};
|
|
668
|
-
}
|
|
669
|
-
/**
|
|
670
|
-
* Get appropriate Accept header for path
|
|
671
|
-
*/
|
|
672
|
-
getAcceptHeaderForPath(path) {
|
|
673
|
-
if (path.startsWith("/api/")) {
|
|
674
|
-
return "application/json, text/plain, */*";
|
|
675
|
-
}
|
|
676
|
-
if (path.endsWith(".css")) {
|
|
677
|
-
return "text/css,*/*;q=0.1";
|
|
678
|
-
}
|
|
679
|
-
if (path.endsWith(".js")) {
|
|
680
|
-
return "application/javascript, */*;q=0.1";
|
|
681
|
-
}
|
|
682
|
-
if (path.endsWith(".html")) {
|
|
683
|
-
return "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
|
|
684
|
-
}
|
|
685
|
-
return "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Get appropriate Content-Type for path
|
|
689
|
-
*/
|
|
690
|
-
getContentTypeForPath(path) {
|
|
691
|
-
if (path.startsWith("/api/")) {
|
|
692
|
-
return "application/json";
|
|
693
|
-
}
|
|
694
|
-
if (path.endsWith(".css")) {
|
|
695
|
-
return "text/css";
|
|
696
|
-
}
|
|
697
|
-
if (path.endsWith(".js")) {
|
|
698
|
-
return "application/javascript";
|
|
699
|
-
}
|
|
700
|
-
if (path.endsWith(".html")) {
|
|
701
|
-
return "text/html";
|
|
702
|
-
}
|
|
703
|
-
return "text/html";
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Generate request body based on path and method
|
|
707
|
-
*/
|
|
708
|
-
generateBody(path, method) {
|
|
709
|
-
if (method === "POST" && path.includes("/api/users")) {
|
|
710
|
-
return { name: "Test User", email: "test@example.com" };
|
|
711
|
-
}
|
|
712
|
-
if (method === "PUT" && path.includes("/api/products")) {
|
|
713
|
-
return { name: "Updated Product", price: 99.99 };
|
|
714
|
-
}
|
|
715
|
-
return {};
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* Generate cookies for path
|
|
719
|
-
*/
|
|
720
|
-
generateCookies(path) {
|
|
721
|
-
const cookies = {};
|
|
722
|
-
if (this.shouldBeAuthenticated(path)) {
|
|
723
|
-
cookies.sessionId = `sess_${Date.now()}_${Math.random()
|
|
724
|
-
.toString(36)
|
|
725
|
-
.substring(2, 11)}`;
|
|
726
|
-
cookies.token = `token_${Date.now()}_${Math.random()
|
|
727
|
-
.toString(36)
|
|
728
|
-
.substring(2, 18)}`;
|
|
729
|
-
}
|
|
730
|
-
cookies.preferences = "theme=dark;lang=en";
|
|
731
|
-
cookies.analytics = `visitor_${Date.now()}`;
|
|
732
|
-
return cookies;
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* Determine if path should be authenticated
|
|
736
|
-
*/
|
|
737
|
-
shouldBeAuthenticated(path) {
|
|
738
|
-
return (path.includes("/api/users") ||
|
|
739
|
-
path.includes("/api/profile") ||
|
|
740
|
-
path.includes("/api/admin") ||
|
|
741
|
-
path.includes("/dashboard"));
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Analyze cache performance with detailed metrics
|
|
745
|
-
*/
|
|
746
|
-
async analyzeCachePerformance(_context) {
|
|
747
|
-
try {
|
|
748
|
-
const stats = this.getCacheStats();
|
|
749
|
-
const hitRate = stats.totalOperations > 0
|
|
750
|
-
? (stats.hits / (stats.hits + stats.misses)) * 100
|
|
751
|
-
: 0;
|
|
752
|
-
return {
|
|
753
|
-
hitRate: Math.round(hitRate * 100) / 100,
|
|
754
|
-
totalOperations: stats.totalOperations,
|
|
755
|
-
cacheHits: stats.hits,
|
|
756
|
-
cacheMisses: stats.misses,
|
|
757
|
-
averageResponseTime: stats.averageResponseTime,
|
|
758
|
-
errorRate: stats.errorRate,
|
|
759
|
-
recommendations: this.generateCacheRecommendations(stats),
|
|
760
|
-
timestamp: new Date().toISOString(),
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
catch (error) {
|
|
764
|
-
console.error("Error analyzing cache performance:", error);
|
|
765
|
-
return {
|
|
766
|
-
error: error.message,
|
|
767
|
-
timestamp: new Date().toISOString(),
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Optimize cache strategy based on performance data
|
|
773
|
-
*/
|
|
774
|
-
async optimizeCacheStrategy(context) {
|
|
775
|
-
try {
|
|
776
|
-
const analysis = await this.analyzeCachePerformance(context);
|
|
777
|
-
const optimizations = [];
|
|
778
|
-
// Suggest optimizations based on hit rate
|
|
779
|
-
if (analysis.hitRate < 30) {
|
|
780
|
-
optimizations.push("Consider increasing TTL values to improve hit rate");
|
|
781
|
-
optimizations.push("Review caching patterns for frequently accessed content");
|
|
782
|
-
}
|
|
783
|
-
else if (analysis.hitRate > 90) {
|
|
784
|
-
optimizations.push("Excellent cache performance - consider expanding caching scope");
|
|
785
|
-
}
|
|
786
|
-
// Suggest optimizations based on error rate
|
|
787
|
-
if (analysis.errorRate > 5) {
|
|
788
|
-
optimizations.push("High error rate detected - review cache configuration");
|
|
789
|
-
}
|
|
790
|
-
// Suggest optimizations based on response time
|
|
791
|
-
if (analysis.averageResponseTime > 50) {
|
|
792
|
-
optimizations.push("Consider using memory cache for frequently accessed data");
|
|
793
|
-
}
|
|
794
|
-
return {
|
|
795
|
-
currentPerformance: analysis,
|
|
796
|
-
optimizations,
|
|
797
|
-
appliedOptimizations: await this.applyAutomaticOptimizations(analysis),
|
|
798
|
-
timestamp: new Date().toISOString(),
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
catch (error) {
|
|
802
|
-
console.error("Error optimizing cache strategy:", error);
|
|
803
|
-
return {
|
|
804
|
-
error: error.message,
|
|
805
|
-
timestamp: new Date().toISOString(),
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* Check cache health status
|
|
811
|
-
*/
|
|
812
|
-
async checkCacheHealth() {
|
|
813
|
-
try {
|
|
814
|
-
const healthChecks = await Promise.allSettled([
|
|
815
|
-
this.checkCacheInstanceHealth("memory", this.memoryCache),
|
|
816
|
-
this.checkCacheInstanceHealth("file", this.fileCache),
|
|
817
|
-
this.checkCacheInstanceHealth("hybrid", this.hybridCache),
|
|
818
|
-
]);
|
|
819
|
-
const results = healthChecks.map((result, index) => {
|
|
820
|
-
const cacheType = ["memory", "file", "hybrid"][index];
|
|
821
|
-
return {
|
|
822
|
-
type: cacheType,
|
|
823
|
-
status: result.status === "fulfilled" ? result.value : "error",
|
|
824
|
-
error: result.status === "rejected" ? result.reason : null,
|
|
825
|
-
};
|
|
826
|
-
});
|
|
827
|
-
const overallHealth = results.every((r) => r.status === "healthy")
|
|
828
|
-
? "healthy"
|
|
829
|
-
: "degraded";
|
|
830
|
-
return {
|
|
831
|
-
overallHealth,
|
|
832
|
-
cacheInstances: results,
|
|
833
|
-
stats: this.getCacheStats(),
|
|
834
|
-
timestamp: new Date().toISOString(),
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
catch (error) {
|
|
838
|
-
console.error("Error checking cache health:", error);
|
|
839
|
-
return {
|
|
840
|
-
overallHealth: "error",
|
|
841
|
-
error: error.message,
|
|
842
|
-
timestamp: new Date().toISOString(),
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
// ===== CACHE IMPLEMENTATIONS =====
|
|
847
|
-
/**
|
|
848
|
-
* Initialize cache instances
|
|
849
|
-
*/
|
|
850
|
-
async initializeCaches() {
|
|
851
|
-
try {
|
|
852
|
-
// Initialize memory cache for ultra-fast access
|
|
853
|
-
this.memoryCache = index$1.createOptimalCache({
|
|
854
|
-
type: "memory",
|
|
855
|
-
config: {
|
|
856
|
-
maxCacheSize: 50 * 1024 * 1024, // 50MB
|
|
857
|
-
ttl: 300000, // 5 minutes default
|
|
858
|
-
encrypt: this.encryptionEnabled,
|
|
859
|
-
compress: this.compressionEnabled,
|
|
860
|
-
},
|
|
861
|
-
});
|
|
862
|
-
// Initialize file cache for persistence
|
|
863
|
-
this.fileCache = index$1.createOptimalCache({
|
|
864
|
-
type: "file",
|
|
865
|
-
config: {
|
|
866
|
-
directory: "./cache/plugins",
|
|
867
|
-
encrypt: this.encryptionEnabled,
|
|
868
|
-
compress: this.compressionEnabled,
|
|
869
|
-
maxCacheSize: 100 * 1024 * 1024, // 100MB
|
|
870
|
-
ttl: 3600000, // 1 hour default
|
|
871
|
-
},
|
|
872
|
-
});
|
|
873
|
-
// Initialize hybrid cache for optimal performance
|
|
874
|
-
this.hybridCache = index$1.createOptimalCache({
|
|
875
|
-
type: "hybrid",
|
|
876
|
-
config: {
|
|
877
|
-
encrypt: this.encryptionEnabled,
|
|
878
|
-
compress: this.compressionEnabled,
|
|
879
|
-
maxCacheSize: 25 * 1024 * 1024, // 25MB
|
|
880
|
-
ttl: 600000, // 10 minutes default
|
|
881
|
-
},
|
|
882
|
-
});
|
|
883
|
-
// Connect all cache instances
|
|
884
|
-
await Promise.all([
|
|
885
|
-
this.memoryCache.connect(),
|
|
886
|
-
this.fileCache.connect(),
|
|
887
|
-
this.hybridCache.connect(),
|
|
888
|
-
]);
|
|
889
|
-
}
|
|
890
|
-
catch (error) {
|
|
891
|
-
console.error("Error initializing caches:", error);
|
|
892
|
-
// Fallback to basic cache
|
|
893
|
-
this.memoryCache = index$1.Cache;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
/**
|
|
897
|
-
* Initialize cache invalidation patterns
|
|
898
|
-
*/
|
|
899
|
-
initializeCachePatterns() {
|
|
900
|
-
// Content type based TTL patterns
|
|
901
|
-
this.contentTypePatterns.set("application/json", 300000); // 5 minutes
|
|
902
|
-
this.contentTypePatterns.set("text/html", 600000); // 10 minutes
|
|
903
|
-
this.contentTypePatterns.set("text/css", 3600000); // 1 hour
|
|
904
|
-
this.contentTypePatterns.set("application/javascript", 3600000); // 1 hour
|
|
905
|
-
this.contentTypePatterns.set("image/*", 86400000); // 24 hours
|
|
906
|
-
// Invalidation patterns for different route types
|
|
907
|
-
this.invalidationPatterns.set("api", /^\/api\/.*$/);
|
|
908
|
-
this.invalidationPatterns.set("user", /^\/api\/users?\/.*$/);
|
|
909
|
-
this.invalidationPatterns.set("auth", /^\/api\/auth\/.*$/);
|
|
910
|
-
this.invalidationPatterns.set("static", /\.(css|js|png|jpg|jpeg|gif|svg|ico)$/);
|
|
911
|
-
}
|
|
912
|
-
/**
|
|
913
|
-
* cache invalidation with pattern matching
|
|
914
|
-
*/
|
|
915
|
-
async invalidateCacheByPattern(pattern) {
|
|
916
|
-
let invalidatedCount = 0;
|
|
917
|
-
try {
|
|
918
|
-
// Get pattern regex
|
|
919
|
-
const regex = this.invalidationPatterns.get(pattern);
|
|
920
|
-
if (!regex) {
|
|
921
|
-
// Treat as literal pattern
|
|
922
|
-
await this.invalidateCache(pattern);
|
|
923
|
-
return 1;
|
|
924
|
-
}
|
|
925
|
-
// Get all tagged keys for this pattern
|
|
926
|
-
const taggedKeys = this.taggedKeys.get(pattern);
|
|
927
|
-
if (taggedKeys) {
|
|
928
|
-
for (const key of taggedKeys) {
|
|
929
|
-
await this.deleteFromAllCaches(key);
|
|
930
|
-
invalidatedCount++;
|
|
931
|
-
}
|
|
932
|
-
// Clear the tag
|
|
933
|
-
this.taggedKeys.delete(pattern);
|
|
934
|
-
}
|
|
935
|
-
// Also invalidate from all cache instances
|
|
936
|
-
await Promise.allSettled([
|
|
937
|
-
this.memoryCache?.invalidateByPattern?.(regex),
|
|
938
|
-
this.fileCache?.invalidateByPattern?.(regex),
|
|
939
|
-
this.hybridCache?.invalidateByPattern?.(regex),
|
|
940
|
-
]);
|
|
941
|
-
}
|
|
942
|
-
catch (error) {
|
|
943
|
-
console.error(`Error invalidating cache pattern ${pattern}:`, error);
|
|
944
|
-
}
|
|
945
|
-
return invalidatedCount;
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Dynamic TTL calculation based on content type and usage patterns
|
|
949
|
-
*/
|
|
950
|
-
calculateDynamicTTL(context) {
|
|
951
|
-
const { req } = context;
|
|
952
|
-
// Check content type patterns
|
|
953
|
-
const contentType = req.headers["content-type"] || req.headers["accept"];
|
|
954
|
-
if (contentType) {
|
|
955
|
-
for (const [pattern, ttl] of this.contentTypePatterns.entries()) {
|
|
956
|
-
if (contentType.includes(pattern.replace("*", ""))) {
|
|
957
|
-
return ttl;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
// Route-based TTL calculation
|
|
962
|
-
if (req.path.startsWith("/api/")) {
|
|
963
|
-
// API endpoints get shorter TTL
|
|
964
|
-
if (req.path.includes("/users") || req.path.includes("/auth")) {
|
|
965
|
-
return 60000; // 1 minute for user/auth data
|
|
966
|
-
}
|
|
967
|
-
return 300000; // 5 minutes for other API data
|
|
968
|
-
}
|
|
969
|
-
// Static resources get longer TTL
|
|
970
|
-
if (this.isStaticResource(req.path)) {
|
|
971
|
-
return 86400000; // 24 hours
|
|
972
|
-
}
|
|
973
|
-
// Default TTL
|
|
974
|
-
return 600000; // 10 minutes
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* Advanced cache key generation with collision resistance
|
|
978
|
-
*/
|
|
979
|
-
generateAdvancedCacheKey(context) {
|
|
980
|
-
const { req } = context;
|
|
981
|
-
// Create comprehensive key components
|
|
982
|
-
const components = [
|
|
983
|
-
"v2", // Version prefix for cache key format
|
|
984
|
-
req.method,
|
|
985
|
-
req.path,
|
|
986
|
-
this.serializeQueryParams(req.query),
|
|
987
|
-
this.serializeHeaders(req.headers),
|
|
988
|
-
context.security.isAuthenticated ? "auth" : "public",
|
|
989
|
-
context.security.userId || "anonymous",
|
|
990
|
-
];
|
|
991
|
-
// Add custom components
|
|
992
|
-
const customComponents = this.getCustomKeyComponents(context);
|
|
993
|
-
components.push(...customComponents);
|
|
994
|
-
// Create collision-resistant hash
|
|
995
|
-
const keyString = components.filter((c) => c).join("|");
|
|
996
|
-
const hash = this.hashUtil.create(keyString, {
|
|
997
|
-
algorithm: "sha256",
|
|
998
|
-
outputFormat: "hex",
|
|
999
|
-
});
|
|
1000
|
-
// Add readable prefix for debugging
|
|
1001
|
-
const prefix = `${req.method.toLowerCase()}_${req.path
|
|
1002
|
-
.replace(/[^a-zA-Z0-9]/g, "_")
|
|
1003
|
-
.substring(0, 20)}`;
|
|
1004
|
-
return `${prefix}_${hash.substring(0, 16)}`;
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Delete from all cache instances
|
|
1008
|
-
*/
|
|
1009
|
-
async deleteFromAllCaches(key) {
|
|
1010
|
-
await Promise.allSettled([
|
|
1011
|
-
this.memoryCache?.delete?.(key),
|
|
1012
|
-
this.fileCache?.delete?.(key),
|
|
1013
|
-
this.hybridCache?.delete?.(key),
|
|
1014
|
-
this.cache?.delete?.(key),
|
|
1015
|
-
]);
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Set in optimal cache instance based on data characteristics
|
|
1019
|
-
*/
|
|
1020
|
-
async setInOptimalCache(key, value, options = {}) {
|
|
1021
|
-
const dataSize = JSON.stringify(value).length;
|
|
1022
|
-
const ttl = options.ttl || 600000; // 10 minutes default
|
|
1023
|
-
try {
|
|
1024
|
-
// Choose cache based on data characteristics
|
|
1025
|
-
if (dataSize < 1024 && ttl < 300000) {
|
|
1026
|
-
// Small, short-lived data -> memory cache
|
|
1027
|
-
await this.memoryCache?.set(key, value, { ttl });
|
|
1028
|
-
}
|
|
1029
|
-
else if (dataSize > 10240 || ttl > 3600000) {
|
|
1030
|
-
// Large or long-lived data -> file cache
|
|
1031
|
-
await this.fileCache?.set(key, value, { ttl });
|
|
1032
|
-
}
|
|
1033
|
-
else {
|
|
1034
|
-
// Medium data -> hybrid cache
|
|
1035
|
-
await this.hybridCache?.set(key, value, { ttl });
|
|
1036
|
-
}
|
|
1037
|
-
// Tag the key for invalidation
|
|
1038
|
-
if (options.tags) {
|
|
1039
|
-
for (const tag of options.tags) {
|
|
1040
|
-
if (!this.taggedKeys.has(tag)) {
|
|
1041
|
-
this.taggedKeys.set(tag, new Set());
|
|
1042
|
-
}
|
|
1043
|
-
this.taggedKeys.get(tag).add(key);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
catch (error) {
|
|
1048
|
-
console.error(`Error setting cache key ${key}:`, error);
|
|
1049
|
-
// Fallback to basic cache
|
|
1050
|
-
await this.cache?.set(key, value, { ttl });
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Get from optimal cache instance
|
|
1055
|
-
*/
|
|
1056
|
-
async getFromOptimalCache(key) {
|
|
1057
|
-
try {
|
|
1058
|
-
// Try memory cache first (fastest)
|
|
1059
|
-
let result = await this.memoryCache?.get(key);
|
|
1060
|
-
if (result !== undefined) {
|
|
1061
|
-
return result;
|
|
1062
|
-
}
|
|
1063
|
-
// Try hybrid cache
|
|
1064
|
-
result = await this.hybridCache?.get(key);
|
|
1065
|
-
if (result !== undefined) {
|
|
1066
|
-
// Promote to memory cache for faster future access
|
|
1067
|
-
await this.memoryCache?.set(key, result, { ttl: 300000 });
|
|
1068
|
-
return result;
|
|
1069
|
-
}
|
|
1070
|
-
// Try file cache
|
|
1071
|
-
result = await this.fileCache?.get(key);
|
|
1072
|
-
if (result !== undefined) {
|
|
1073
|
-
// Promote to memory cache
|
|
1074
|
-
await this.memoryCache?.set(key, result, { ttl: 300000 });
|
|
1075
|
-
return result;
|
|
1076
|
-
}
|
|
1077
|
-
// Fallback to basic cache
|
|
1078
|
-
return await this.cache?.get(key);
|
|
1079
|
-
}
|
|
1080
|
-
catch (error) {
|
|
1081
|
-
console.error(`Error getting cache key ${key}:`, error);
|
|
1082
|
-
return undefined;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
// ===== PROTECTED HELPER METHODS =====
|
|
1086
|
-
/**
|
|
1087
|
-
* Determine cache operation type
|
|
1088
|
-
*/
|
|
1089
|
-
determineCacheOperation(context) {
|
|
1090
|
-
const { req } = context;
|
|
1091
|
-
if (req.method === "GET" && this.shouldCache(context)) {
|
|
1092
|
-
return "get";
|
|
1093
|
-
}
|
|
1094
|
-
if (["POST", "PUT", "PATCH", "DELETE"].includes(req.method)) {
|
|
1095
|
-
return "invalidate";
|
|
1096
|
-
}
|
|
1097
|
-
return "none";
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Handle cache get operation
|
|
1101
|
-
*/
|
|
1102
|
-
async handleCacheGet(context) {
|
|
1103
|
-
const cacheKey = this.generateCacheKey(context);
|
|
1104
|
-
const cachedData = await this.fortifiedCache(async () => {
|
|
1105
|
-
return await this.cache.get(cacheKey);
|
|
1106
|
-
});
|
|
1107
|
-
this.cacheStats.totalOperations++;
|
|
1108
|
-
if (cachedData) {
|
|
1109
|
-
this.cacheStats.hits++;
|
|
1110
|
-
return { hit: true, data: cachedData, key: cacheKey };
|
|
1111
|
-
}
|
|
1112
|
-
else {
|
|
1113
|
-
this.cacheStats.misses++;
|
|
1114
|
-
return { hit: false, key: cacheKey };
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Handle cache set operation
|
|
1119
|
-
*/
|
|
1120
|
-
async handleCacheSet(context) {
|
|
1121
|
-
const cacheKey = this.generateCacheKey(context);
|
|
1122
|
-
const ttl = this.getCacheTTL(context);
|
|
1123
|
-
// Queue for post-response processing instead of immediate caching
|
|
1124
|
-
this.queuePostResponseOperation("set", context, {
|
|
1125
|
-
key: cacheKey,
|
|
1126
|
-
ttl,
|
|
1127
|
-
responseData: null, // Will be populated after response
|
|
1128
|
-
}, 1.0); // High priority for cache sets
|
|
1129
|
-
this.cacheStats.sets++;
|
|
1130
|
-
this.cacheStats.totalOperations++;
|
|
1131
|
-
return {
|
|
1132
|
-
cacheData: {
|
|
1133
|
-
key: cacheKey,
|
|
1134
|
-
value: null, // Will be set by the response handler
|
|
1135
|
-
ttl,
|
|
1136
|
-
},
|
|
1137
|
-
postResponseQueued: true,
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Queue operation for post-response processing
|
|
1142
|
-
*/
|
|
1143
|
-
queuePostResponseOperation(operation, context, data, priority = 0.5) {
|
|
1144
|
-
// Avoid duplicate operations for the same request
|
|
1145
|
-
const existingIndex = this.postResponseQueue.findIndex((item) => item.context.executionId === context.executionId &&
|
|
1146
|
-
item.operation === operation);
|
|
1147
|
-
if (existingIndex >= 0) {
|
|
1148
|
-
// Update existing operation with higher priority if needed
|
|
1149
|
-
if (this.postResponseQueue[existingIndex].priority < priority) {
|
|
1150
|
-
this.postResponseQueue[existingIndex].priority = priority;
|
|
1151
|
-
this.postResponseQueue[existingIndex].data = {
|
|
1152
|
-
...this.postResponseQueue[existingIndex].data,
|
|
1153
|
-
...data,
|
|
1154
|
-
};
|
|
1155
|
-
this.postResponseQueue[existingIndex].timestamp = Date.now();
|
|
1156
|
-
}
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
// Add new operation to queue
|
|
1160
|
-
this.postResponseQueue.push({
|
|
1161
|
-
operation,
|
|
1162
|
-
context,
|
|
1163
|
-
data,
|
|
1164
|
-
timestamp: Date.now(),
|
|
1165
|
-
priority,
|
|
1166
|
-
});
|
|
1167
|
-
// Sort by priority (highest first)
|
|
1168
|
-
this.postResponseQueue.sort((a, b) => b.priority - a.priority);
|
|
1169
|
-
// Limit queue size to prevent memory issues
|
|
1170
|
-
if (this.postResponseQueue.length > 500) {
|
|
1171
|
-
this.postResponseQueue = this.postResponseQueue.slice(0, 500);
|
|
1172
|
-
}
|
|
1173
|
-
// Start post-response worker if not already active
|
|
1174
|
-
if (!this.postResponseWorkerActive) {
|
|
1175
|
-
this.startPostResponseWorker();
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* Start the post-response worker for background cache operations
|
|
1180
|
-
*/
|
|
1181
|
-
async startPostResponseWorker() {
|
|
1182
|
-
if (this.postResponseWorkerActive)
|
|
1183
|
-
return;
|
|
1184
|
-
this.postResponseWorkerActive = true;
|
|
1185
|
-
try {
|
|
1186
|
-
while (this.postResponseQueue.length > 0) {
|
|
1187
|
-
const operation = this.postResponseQueue.shift();
|
|
1188
|
-
if (!operation)
|
|
1189
|
-
break;
|
|
1190
|
-
// Skip operations that are too old (older than 10 minutes)
|
|
1191
|
-
if (Date.now() - operation.timestamp > 600000) {
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
await this.executePostResponseOperation(operation);
|
|
1195
|
-
// Small delay to prevent overwhelming the system
|
|
1196
|
-
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
catch (error) {
|
|
1200
|
-
console.error("Post-response worker error:", error);
|
|
1201
|
-
}
|
|
1202
|
-
finally {
|
|
1203
|
-
this.postResponseWorkerActive = false;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
/**
|
|
1207
|
-
* Execute a post-response cache operation
|
|
1208
|
-
*/
|
|
1209
|
-
async executePostResponseOperation(operation) {
|
|
1210
|
-
try {
|
|
1211
|
-
switch (operation.operation) {
|
|
1212
|
-
case "set":
|
|
1213
|
-
await this.performPostResponseCacheSet(operation);
|
|
1214
|
-
break;
|
|
1215
|
-
case "invalidate":
|
|
1216
|
-
await this.performPostResponseInvalidation(operation);
|
|
1217
|
-
break;
|
|
1218
|
-
case "analyze":
|
|
1219
|
-
await this.performPostResponseAnalysis(operation);
|
|
1220
|
-
break;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
catch (error) {
|
|
1224
|
-
console.error(`Post-response ${operation.operation} operation failed:`, error);
|
|
1225
|
-
this.cacheStats.errors++;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
/**
|
|
1229
|
-
* Perform post-response cache set operation
|
|
1230
|
-
*/
|
|
1231
|
-
async performPostResponseCacheSet(operation) {
|
|
1232
|
-
const { context, data } = operation;
|
|
1233
|
-
const { res } = context;
|
|
1234
|
-
// Check if response is cacheable
|
|
1235
|
-
if (!this.isResponseCacheable(res)) {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
// Get response data from the response object
|
|
1239
|
-
const responseData = this.extractResponseData(res);
|
|
1240
|
-
if (!responseData) {
|
|
1241
|
-
return;
|
|
1242
|
-
}
|
|
1243
|
-
// Perform the actual cache set operation
|
|
1244
|
-
await this.fortifiedCache(async () => {
|
|
1245
|
-
await this.cache.set(data.key, responseData, {
|
|
1246
|
-
ttl: data.ttl,
|
|
1247
|
-
tags: this.generateCacheTags(context),
|
|
1248
|
-
});
|
|
1249
|
-
});
|
|
1250
|
-
// Update cache statistics
|
|
1251
|
-
this.updatePostResponseStats("set", Date.now() - operation.timestamp);
|
|
1252
|
-
}
|
|
1253
|
-
/**
|
|
1254
|
-
* Perform post-response cache invalidation
|
|
1255
|
-
*/
|
|
1256
|
-
async performPostResponseInvalidation(operation) {
|
|
1257
|
-
const { data } = operation;
|
|
1258
|
-
// Invalidate cache patterns
|
|
1259
|
-
for (const pattern of data.patterns || []) {
|
|
1260
|
-
await this.invalidateCache(pattern);
|
|
1261
|
-
}
|
|
1262
|
-
// Update cache statistics
|
|
1263
|
-
this.updatePostResponseStats("invalidate", Date.now() - operation.timestamp);
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Perform post-response cache analysis
|
|
1267
|
-
*/
|
|
1268
|
-
async performPostResponseAnalysis(operation) {
|
|
1269
|
-
const { context } = operation;
|
|
1270
|
-
// Analyze cache performance for this request
|
|
1271
|
-
const analysis = {
|
|
1272
|
-
cacheHit: context.metrics.cacheHits > 0,
|
|
1273
|
-
responseTime: Date.now() - context.startTime,
|
|
1274
|
-
cacheKey: this.generateCacheKey(context),
|
|
1275
|
-
shouldCache: this.shouldCache(context),
|
|
1276
|
-
};
|
|
1277
|
-
// Store analysis data for future optimization
|
|
1278
|
-
await this.storeCacheAnalysis(analysis);
|
|
1279
|
-
// Update cache statistics
|
|
1280
|
-
this.updatePostResponseStats("analyze", Date.now() - operation.timestamp);
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Check if response is cacheable
|
|
1284
|
-
*/
|
|
1285
|
-
isResponseCacheable(res) {
|
|
1286
|
-
// Don't cache error responses
|
|
1287
|
-
if (res.statusCode >= 400) {
|
|
1288
|
-
return false;
|
|
1289
|
-
}
|
|
1290
|
-
// Don't cache responses with cache-control: no-cache
|
|
1291
|
-
const cacheControl = res.getHeader("cache-control");
|
|
1292
|
-
if (cacheControl && cacheControl.includes("no-cache")) {
|
|
1293
|
-
return false;
|
|
1294
|
-
}
|
|
1295
|
-
// Don't cache responses that are too large (>1MB)
|
|
1296
|
-
const contentLength = res.getHeader("content-length");
|
|
1297
|
-
if (contentLength && parseInt(contentLength) > 1024 * 1024) {
|
|
1298
|
-
return false;
|
|
1299
|
-
}
|
|
1300
|
-
return true;
|
|
1301
|
-
}
|
|
1302
|
-
/**
|
|
1303
|
-
* Extract response data for caching
|
|
1304
|
-
*/
|
|
1305
|
-
extractResponseData(res) {
|
|
1306
|
-
// Extract actual response body and metadata
|
|
1307
|
-
const responseId = this.generateResponseId(res);
|
|
1308
|
-
const capturedResponse = this.responseBodyCapture.get(responseId);
|
|
1309
|
-
let responseBody = null;
|
|
1310
|
-
let contentSize = 0;
|
|
1311
|
-
if (capturedResponse) {
|
|
1312
|
-
responseBody = capturedResponse.body;
|
|
1313
|
-
contentSize = this.calculateContentSize(responseBody);
|
|
1314
|
-
}
|
|
1315
|
-
else {
|
|
1316
|
-
// Fallback: try to extract from response object directly
|
|
1317
|
-
responseBody = this.extractBodyFromResponse(res);
|
|
1318
|
-
contentSize = this.calculateContentSize(responseBody);
|
|
1319
|
-
}
|
|
1320
|
-
const responseData = {
|
|
1321
|
-
statusCode: res.statusCode,
|
|
1322
|
-
headers: res.getHeaders ? res.getHeaders() : res.headers || {},
|
|
1323
|
-
body: responseBody,
|
|
1324
|
-
contentSize,
|
|
1325
|
-
timestamp: Date.now(),
|
|
1326
|
-
encoding: res.getHeader ? res.getHeader("content-encoding") : null,
|
|
1327
|
-
contentType: res.getHeader ? res.getHeader("content-type") : null,
|
|
1328
|
-
};
|
|
1329
|
-
// Clean up captured response data
|
|
1330
|
-
if (capturedResponse) {
|
|
1331
|
-
this.responseBodyCapture.delete(responseId);
|
|
1332
|
-
}
|
|
1333
|
-
return responseData;
|
|
1334
|
-
}
|
|
1335
|
-
/**
|
|
1336
|
-
* Generate unique response ID for tracking
|
|
1337
|
-
*/
|
|
1338
|
-
generateResponseId(res) {
|
|
1339
|
-
// Use a combination of timestamp and response object properties
|
|
1340
|
-
const timestamp = Date.now();
|
|
1341
|
-
const statusCode = res.statusCode || 200;
|
|
1342
|
-
const random = Math.random().toString(36).substring(2, 8);
|
|
1343
|
-
return `res_${timestamp}_${statusCode}_${random}`;
|
|
1344
|
-
}
|
|
1345
|
-
/**
|
|
1346
|
-
* Extract body from response object using various methods
|
|
1347
|
-
*/
|
|
1348
|
-
extractBodyFromResponse(res) {
|
|
1349
|
-
// Try different methods to extract response body
|
|
1350
|
-
// Method 1: Check if body is directly available
|
|
1351
|
-
if (res.body !== undefined) {
|
|
1352
|
-
return res.body;
|
|
1353
|
-
}
|
|
1354
|
-
// Method 2: Check for _body property (some frameworks use this)
|
|
1355
|
-
if (res._body !== undefined) {
|
|
1356
|
-
return res._body;
|
|
1357
|
-
}
|
|
1358
|
-
// Method 3: Check for locals.responseBody (Express pattern)
|
|
1359
|
-
if (res.locals && res.locals.responseBody !== undefined) {
|
|
1360
|
-
return res.locals.responseBody;
|
|
1361
|
-
}
|
|
1362
|
-
// Method 4: Check for custom XyPriss response data
|
|
1363
|
-
if (res.XyPrissResponseData !== undefined) {
|
|
1364
|
-
return res.XyPrissResponseData;
|
|
1365
|
-
}
|
|
1366
|
-
// Method 5: Try to read from write method interception
|
|
1367
|
-
if (res._XyPrissWriteBuffer) {
|
|
1368
|
-
return res._XyPrissWriteBuffer;
|
|
1369
|
-
}
|
|
1370
|
-
// Method 6: Check for JSON response data
|
|
1371
|
-
if (res.json && typeof res.json === "object") {
|
|
1372
|
-
return res.json;
|
|
1373
|
-
}
|
|
1374
|
-
return null;
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Calculate content size in bytes
|
|
1378
|
-
*/
|
|
1379
|
-
calculateContentSize(content) {
|
|
1380
|
-
if (!content)
|
|
1381
|
-
return 0;
|
|
1382
|
-
if (typeof content === "string") {
|
|
1383
|
-
return Buffer.byteLength(content, "utf8");
|
|
1384
|
-
}
|
|
1385
|
-
if (Buffer.isBuffer(content)) {
|
|
1386
|
-
return content.length;
|
|
1387
|
-
}
|
|
1388
|
-
if (content instanceof Uint8Array) {
|
|
1389
|
-
return content.byteLength;
|
|
1390
|
-
}
|
|
1391
|
-
if (typeof content === "object") {
|
|
1392
|
-
try {
|
|
1393
|
-
return Buffer.byteLength(JSON.stringify(content), "utf8");
|
|
1394
|
-
}
|
|
1395
|
-
catch {
|
|
1396
|
-
return 0;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
return 0;
|
|
1400
|
-
}
|
|
1401
|
-
/**
|
|
1402
|
-
* Capture response body for later extraction
|
|
1403
|
-
*/
|
|
1404
|
-
captureResponseBody(responseId, body, headers, statusCode) {
|
|
1405
|
-
this.responseBodyCapture.set(responseId, {
|
|
1406
|
-
body,
|
|
1407
|
-
headers,
|
|
1408
|
-
statusCode,
|
|
1409
|
-
timestamp: Date.now(),
|
|
1410
|
-
});
|
|
1411
|
-
// Clean up old captures (older than 5 minutes)
|
|
1412
|
-
const fiveMinutesAgo = Date.now() - 300000;
|
|
1413
|
-
for (const [id, capture] of this.responseBodyCapture.entries()) {
|
|
1414
|
-
if (capture.timestamp < fiveMinutesAgo) {
|
|
1415
|
-
this.responseBodyCapture.delete(id);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Generate cache tags for the request
|
|
1421
|
-
*/
|
|
1422
|
-
generateCacheTags(context) {
|
|
1423
|
-
const { req } = context;
|
|
1424
|
-
const tags = [];
|
|
1425
|
-
// Add route-based tags
|
|
1426
|
-
tags.push(`route:${req.path}`);
|
|
1427
|
-
tags.push(`method:${req.method}`);
|
|
1428
|
-
// Add user-based tags if authenticated
|
|
1429
|
-
if (context.security.isAuthenticated && context.security.userId) {
|
|
1430
|
-
tags.push(`user:${context.security.userId}`);
|
|
1431
|
-
}
|
|
1432
|
-
// Add custom tags based on request characteristics
|
|
1433
|
-
if (req.path.startsWith("/api/")) {
|
|
1434
|
-
tags.push("api");
|
|
1435
|
-
}
|
|
1436
|
-
return tags;
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Store cache analysis data for performance monitoring and optimization
|
|
1440
|
-
*/
|
|
1441
|
-
async storeCacheAnalysis(analysis) {
|
|
1442
|
-
try {
|
|
1443
|
-
// Extract comprehensive analysis data
|
|
1444
|
-
const analysisEntry = {
|
|
1445
|
-
timestamp: Date.now(),
|
|
1446
|
-
cacheKey: analysis.cacheKey || "unknown",
|
|
1447
|
-
cacheHit: analysis.cacheHit || false,
|
|
1448
|
-
responseTime: analysis.responseTime || 0,
|
|
1449
|
-
shouldCache: analysis.shouldCache || false,
|
|
1450
|
-
route: analysis.route || "unknown",
|
|
1451
|
-
method: analysis.method || "GET",
|
|
1452
|
-
statusCode: analysis.statusCode || 200,
|
|
1453
|
-
contentSize: analysis.contentSize || 0,
|
|
1454
|
-
userId: analysis.userId || undefined,
|
|
1455
|
-
// Additional metrics
|
|
1456
|
-
cacheEfficiency: this.calculateCacheEfficiency(),
|
|
1457
|
-
hitRate: this.calculateHitRate(),
|
|
1458
|
-
averageResponseTime: this.cacheStats.averageResponseTime,
|
|
1459
|
-
memoryUsage: this.getMemoryUsage(),
|
|
1460
|
-
// Performance indicators
|
|
1461
|
-
performanceScore: this.calculatePerformanceScore(analysis),
|
|
1462
|
-
optimizationSuggestions: this.generateOptimizationSuggestions(analysis),
|
|
1463
|
-
};
|
|
1464
|
-
// Store in memory (with size limit)
|
|
1465
|
-
this.analysisStorage.push(analysisEntry);
|
|
1466
|
-
// Limit storage size to prevent memory issues (keep last 1000 entries)
|
|
1467
|
-
if (this.analysisStorage.length > 1000) {
|
|
1468
|
-
this.analysisStorage = this.analysisStorage.slice(-1000);
|
|
1469
|
-
}
|
|
1470
|
-
// Persist to external storage if configured
|
|
1471
|
-
await this.persistAnalysisData(analysisEntry);
|
|
1472
|
-
// Trigger real-time optimization if needed
|
|
1473
|
-
if (this.shouldTriggerOptimization(analysisEntry)) {
|
|
1474
|
-
await this.triggerCacheOptimization(analysisEntry);
|
|
1475
|
-
}
|
|
1476
|
-
console.debug("Cache analysis stored:", {
|
|
1477
|
-
cacheKey: analysisEntry.cacheKey,
|
|
1478
|
-
cacheHit: analysisEntry.cacheHit,
|
|
1479
|
-
responseTime: analysisEntry.responseTime,
|
|
1480
|
-
performanceScore: analysisEntry.performanceScore,
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
catch (error) {
|
|
1484
|
-
console.error("Failed to store cache analysis:", error);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
/**
|
|
1488
|
-
* Calculate cache efficiency percentage
|
|
1489
|
-
*/
|
|
1490
|
-
calculateCacheEfficiency() {
|
|
1491
|
-
const totalOperations = this.cacheStats.hits + this.cacheStats.misses;
|
|
1492
|
-
if (totalOperations === 0)
|
|
1493
|
-
return 0;
|
|
1494
|
-
return (this.cacheStats.hits / totalOperations) * 100;
|
|
1495
|
-
}
|
|
1496
|
-
/**
|
|
1497
|
-
* Calculate current hit rate
|
|
1498
|
-
*/
|
|
1499
|
-
calculateHitRate() {
|
|
1500
|
-
const totalRequests = this.cacheStats.hits + this.cacheStats.misses;
|
|
1501
|
-
return totalRequests > 0 ? this.cacheStats.hits / totalRequests : 0;
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* Get current memory usage information
|
|
1505
|
-
*/
|
|
1506
|
-
getMemoryUsage() {
|
|
1507
|
-
if (typeof process !== "undefined" && process.memoryUsage) {
|
|
1508
|
-
const memUsage = process.memoryUsage();
|
|
1509
|
-
return {
|
|
1510
|
-
used: memUsage.heapUsed,
|
|
1511
|
-
total: memUsage.heapTotal,
|
|
1512
|
-
percentage: (memUsage.heapUsed / memUsage.heapTotal) * 100,
|
|
1513
|
-
};
|
|
1514
|
-
}
|
|
1515
|
-
return { used: 0, total: 0, percentage: 0 };
|
|
1516
|
-
}
|
|
1517
|
-
/**
|
|
1518
|
-
* Calculate performance score based on various metrics
|
|
1519
|
-
*/
|
|
1520
|
-
calculatePerformanceScore(analysis) {
|
|
1521
|
-
let score = 100; // Start with perfect score
|
|
1522
|
-
// Penalize for cache misses
|
|
1523
|
-
if (!analysis.cacheHit) {
|
|
1524
|
-
score -= 20;
|
|
1525
|
-
}
|
|
1526
|
-
// Penalize for slow response times
|
|
1527
|
-
if (analysis.responseTime > 1000) {
|
|
1528
|
-
score -= 30;
|
|
1529
|
-
}
|
|
1530
|
-
else if (analysis.responseTime > 500) {
|
|
1531
|
-
score -= 15;
|
|
1532
|
-
}
|
|
1533
|
-
// Penalize for large content that should be cached but isn't
|
|
1534
|
-
if (!analysis.cacheHit &&
|
|
1535
|
-
analysis.shouldCache &&
|
|
1536
|
-
analysis.contentSize > 10000) {
|
|
1537
|
-
score -= 25;
|
|
1538
|
-
}
|
|
1539
|
-
// Bonus for efficient caching
|
|
1540
|
-
const hitRate = this.calculateHitRate();
|
|
1541
|
-
if (hitRate > 0.8) {
|
|
1542
|
-
score += 10;
|
|
1543
|
-
}
|
|
1544
|
-
return Math.max(0, Math.min(100, score));
|
|
1545
|
-
}
|
|
1546
|
-
/**
|
|
1547
|
-
* Generate optimization suggestions based on analysis
|
|
1548
|
-
*/
|
|
1549
|
-
generateOptimizationSuggestions(analysis) {
|
|
1550
|
-
const suggestions = [];
|
|
1551
|
-
if (!analysis.cacheHit && analysis.shouldCache) {
|
|
1552
|
-
suggestions.push("Consider increasing cache TTL for this route");
|
|
1553
|
-
}
|
|
1554
|
-
if (analysis.responseTime > 1000) {
|
|
1555
|
-
suggestions.push("Response time is high, consider caching or optimization");
|
|
1556
|
-
}
|
|
1557
|
-
if (analysis.contentSize > 100000) {
|
|
1558
|
-
suggestions.push("Large response detected, consider compression");
|
|
1559
|
-
}
|
|
1560
|
-
const hitRate = this.calculateHitRate();
|
|
1561
|
-
if (hitRate < 0.5) {
|
|
1562
|
-
suggestions.push("Low cache hit rate, review caching strategy");
|
|
1563
|
-
}
|
|
1564
|
-
return suggestions;
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Persist analysis data to external storage
|
|
1568
|
-
*/
|
|
1569
|
-
async persistAnalysisData(analysisEntry) {
|
|
1570
|
-
// We would persist to:
|
|
1571
|
-
// - Database (MongoDB, PostgreSQL, etc.)
|
|
1572
|
-
// - Time-series database (InfluxDB, TimescaleDB)
|
|
1573
|
-
// - Analytics service (Google Analytics, Mixpanel)
|
|
1574
|
-
// - Logging service (ELK stack, Splunk)
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Check if optimization should be triggered
|
|
1578
|
-
*/
|
|
1579
|
-
shouldTriggerOptimization(analysisEntry) {
|
|
1580
|
-
// Trigger optimization if performance score is low
|
|
1581
|
-
if (analysisEntry.performanceScore < 50) {
|
|
1582
|
-
return true;
|
|
1583
|
-
}
|
|
1584
|
-
// Trigger if hit rate is very low
|
|
1585
|
-
if (analysisEntry.hitRate < 0.3) {
|
|
1586
|
-
return true;
|
|
1587
|
-
}
|
|
1588
|
-
// Trigger if memory usage is high
|
|
1589
|
-
if (analysisEntry.memoryUsage.percentage > 85) {
|
|
1590
|
-
return true;
|
|
1591
|
-
}
|
|
1592
|
-
return false;
|
|
1593
|
-
}
|
|
1594
|
-
/**
|
|
1595
|
-
* Trigger cache optimization based on analysis
|
|
1596
|
-
*/
|
|
1597
|
-
async triggerCacheOptimization(analysisEntry) {
|
|
1598
|
-
console.info("Triggering cache optimization based on analysis:", {
|
|
1599
|
-
performanceScore: analysisEntry.performanceScore,
|
|
1600
|
-
suggestions: analysisEntry.optimizationSuggestions,
|
|
1601
|
-
});
|
|
1602
|
-
// Implement optimization strategies:
|
|
1603
|
-
// 1. Adjust cache TTL
|
|
1604
|
-
// 2. Preload frequently accessed content
|
|
1605
|
-
// 3. Clear underperforming cache entries
|
|
1606
|
-
// 4. Adjust cache size limits
|
|
1607
|
-
// For now, log the optimization trigger
|
|
1608
|
-
console.debug("Cache optimization triggered for route:", analysisEntry.route);
|
|
1609
|
-
}
|
|
1610
|
-
/**
|
|
1611
|
-
* Get analysis statistics and insights
|
|
1612
|
-
*/
|
|
1613
|
-
getAnalysisInsights() {
|
|
1614
|
-
const totalAnalyses = this.analysisStorage.length;
|
|
1615
|
-
if (totalAnalyses === 0) {
|
|
1616
|
-
return {
|
|
1617
|
-
totalAnalyses: 0,
|
|
1618
|
-
averagePerformanceScore: 0,
|
|
1619
|
-
topOptimizationSuggestions: [],
|
|
1620
|
-
performanceTrends: [],
|
|
1621
|
-
routePerformance: new Map(),
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
// Calculate average performance score
|
|
1625
|
-
const averagePerformanceScore = this.analysisStorage.reduce((sum, entry) => sum + (entry.performanceScore || 0), 0) / totalAnalyses;
|
|
1626
|
-
// Aggregate optimization suggestions
|
|
1627
|
-
const suggestionCounts = new Map();
|
|
1628
|
-
this.analysisStorage.forEach((entry) => {
|
|
1629
|
-
entry.optimizationSuggestions?.forEach((suggestion) => {
|
|
1630
|
-
suggestionCounts.set(suggestion, (suggestionCounts.get(suggestion) || 0) + 1);
|
|
1631
|
-
});
|
|
1632
|
-
});
|
|
1633
|
-
const topOptimizationSuggestions = Array.from(suggestionCounts.entries())
|
|
1634
|
-
.map(([suggestion, count]) => ({ suggestion, count }))
|
|
1635
|
-
.sort((a, b) => b.count - a.count)
|
|
1636
|
-
.slice(0, 5);
|
|
1637
|
-
// Performance trends (last 50 entries)
|
|
1638
|
-
const performanceTrends = this.analysisStorage
|
|
1639
|
-
.slice(-50)
|
|
1640
|
-
.map((entry) => ({
|
|
1641
|
-
timestamp: entry.timestamp,
|
|
1642
|
-
score: entry.performanceScore || 0,
|
|
1643
|
-
}));
|
|
1644
|
-
// Route performance analysis
|
|
1645
|
-
const routePerformance = new Map();
|
|
1646
|
-
const routeData = new Map();
|
|
1647
|
-
this.analysisStorage.forEach((entry) => {
|
|
1648
|
-
const route = entry.route;
|
|
1649
|
-
const existing = routeData.get(route) || {
|
|
1650
|
-
totalScore: 0,
|
|
1651
|
-
count: 0,
|
|
1652
|
-
};
|
|
1653
|
-
existing.totalScore += entry.performanceScore || 0;
|
|
1654
|
-
existing.count += 1;
|
|
1655
|
-
routeData.set(route, existing);
|
|
1656
|
-
});
|
|
1657
|
-
for (const [route, data] of routeData.entries()) {
|
|
1658
|
-
routePerformance.set(route, {
|
|
1659
|
-
averageScore: data.totalScore / data.count,
|
|
1660
|
-
count: data.count,
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
return {
|
|
1664
|
-
totalAnalyses,
|
|
1665
|
-
averagePerformanceScore,
|
|
1666
|
-
topOptimizationSuggestions,
|
|
1667
|
-
performanceTrends,
|
|
1668
|
-
routePerformance,
|
|
1669
|
-
};
|
|
1670
|
-
}
|
|
1671
|
-
/**
|
|
1672
|
-
* Update post-response cache statistics
|
|
1673
|
-
*/
|
|
1674
|
-
updatePostResponseStats(operation, duration) {
|
|
1675
|
-
this.cacheStats.lastOperation = new Date();
|
|
1676
|
-
this.cacheStats.averageResponseTime =
|
|
1677
|
-
(this.cacheStats.averageResponseTime *
|
|
1678
|
-
this.cacheStats.totalOperations +
|
|
1679
|
-
duration) /
|
|
1680
|
-
(this.cacheStats.totalOperations + 1);
|
|
1681
|
-
// Log operation for debugging
|
|
1682
|
-
console.debug(`Post-response ${operation} completed in ${duration}ms`);
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Handle cache invalidation
|
|
1686
|
-
*/
|
|
1687
|
-
async handleCacheInvalidate(context) {
|
|
1688
|
-
const { req } = context;
|
|
1689
|
-
// Generate invalidation patterns based on request
|
|
1690
|
-
const patterns = this.generateInvalidationPatterns(req);
|
|
1691
|
-
for (const pattern of patterns) {
|
|
1692
|
-
await this.invalidateCache(pattern);
|
|
1693
|
-
}
|
|
1694
|
-
return { invalidated: patterns };
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Handle cache warmup
|
|
1698
|
-
*/
|
|
1699
|
-
async handleCacheWarmup(context) {
|
|
1700
|
-
const { req } = context;
|
|
1701
|
-
const warmedUrls = [];
|
|
1702
|
-
try {
|
|
1703
|
-
// Warm up common cache patterns based on current request
|
|
1704
|
-
const baseUrl = req.path.split("/").slice(0, -1).join("/");
|
|
1705
|
-
const commonPatterns = [
|
|
1706
|
-
`${baseUrl}/list`,
|
|
1707
|
-
`${baseUrl}/summary`,
|
|
1708
|
-
`${baseUrl}/metadata`,
|
|
1709
|
-
];
|
|
1710
|
-
for (const pattern of commonPatterns) {
|
|
1711
|
-
const warmupKey = this.generateWarmupKey(pattern, context);
|
|
1712
|
-
// Check if already cached
|
|
1713
|
-
const existing = await this.cache.get(warmupKey);
|
|
1714
|
-
if (!existing) {
|
|
1715
|
-
// Pre-generate cache entry with placeholder data
|
|
1716
|
-
await this.cache.set(warmupKey, {
|
|
1717
|
-
warmedUp: true,
|
|
1718
|
-
timestamp: Date.now(),
|
|
1719
|
-
pattern,
|
|
1720
|
-
}, { ttl: 60000 }); // 1 minute TTL for warmup data
|
|
1721
|
-
warmedUrls.push(pattern);
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
return {
|
|
1725
|
-
warmedUp: true,
|
|
1726
|
-
urls: warmedUrls,
|
|
1727
|
-
count: warmedUrls.length,
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
catch (error) {
|
|
1731
|
-
return {
|
|
1732
|
-
warmedUp: false,
|
|
1733
|
-
error: error.message,
|
|
1734
|
-
urls: warmedUrls,
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
/**
|
|
1739
|
-
* Generate warmup cache key
|
|
1740
|
-
*/
|
|
1741
|
-
generateWarmupKey(pattern, context) {
|
|
1742
|
-
const components = [
|
|
1743
|
-
"warmup",
|
|
1744
|
-
pattern,
|
|
1745
|
-
context.security.isAuthenticated ? "auth" : "public",
|
|
1746
|
-
];
|
|
1747
|
-
const keyString = components.join(":");
|
|
1748
|
-
return this.hashUtil.create(keyString, {
|
|
1749
|
-
algorithm: "sha256",
|
|
1750
|
-
outputFormat: "hex",
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
/**
|
|
1754
|
-
* Check if query parameters indicate dynamic content
|
|
1755
|
-
*/
|
|
1756
|
-
hasDynamicQueryParams(query) {
|
|
1757
|
-
if (!query)
|
|
1758
|
-
return false;
|
|
1759
|
-
const dynamicParams = ["timestamp", "random", "nonce", "_", "t"];
|
|
1760
|
-
return dynamicParams.some((param) => param in query);
|
|
1761
|
-
}
|
|
1762
|
-
/**
|
|
1763
|
-
* Serialize query parameters for cache key
|
|
1764
|
-
*/
|
|
1765
|
-
serializeQueryParams(query) {
|
|
1766
|
-
if (!query || Object.keys(query).length === 0) {
|
|
1767
|
-
return "";
|
|
1768
|
-
}
|
|
1769
|
-
// Sort keys for consistent cache keys
|
|
1770
|
-
const sortedKeys = Object.keys(query).sort();
|
|
1771
|
-
const pairs = sortedKeys.map((key) => `${key}=${query[key]}`);
|
|
1772
|
-
return pairs.join("&");
|
|
1773
|
-
}
|
|
1774
|
-
/**
|
|
1775
|
-
* Serialize relevant headers for cache key
|
|
1776
|
-
*/
|
|
1777
|
-
serializeHeaders(headers) {
|
|
1778
|
-
if (!headers)
|
|
1779
|
-
return "";
|
|
1780
|
-
// Only include headers that affect caching
|
|
1781
|
-
const relevantHeaders = [
|
|
1782
|
-
"accept",
|
|
1783
|
-
"accept-encoding",
|
|
1784
|
-
"accept-language",
|
|
1785
|
-
];
|
|
1786
|
-
const pairs = [];
|
|
1787
|
-
relevantHeaders.forEach((header) => {
|
|
1788
|
-
if (headers[header]) {
|
|
1789
|
-
pairs.push(`${header}=${headers[header]}`);
|
|
1790
|
-
}
|
|
1791
|
-
});
|
|
1792
|
-
return pairs.join("&");
|
|
1793
|
-
}
|
|
1794
|
-
/**
|
|
1795
|
-
* Check if path is for static resource
|
|
1796
|
-
*/
|
|
1797
|
-
isStaticResource(path) {
|
|
1798
|
-
const staticExtensions = [
|
|
1799
|
-
".css",
|
|
1800
|
-
".js",
|
|
1801
|
-
".png",
|
|
1802
|
-
".jpg",
|
|
1803
|
-
".jpeg",
|
|
1804
|
-
".gif",
|
|
1805
|
-
".svg",
|
|
1806
|
-
".ico",
|
|
1807
|
-
];
|
|
1808
|
-
return staticExtensions.some((ext) => path.endsWith(ext));
|
|
1809
|
-
}
|
|
1810
|
-
/**
|
|
1811
|
-
* Generate invalidation patterns for request
|
|
1812
|
-
*/
|
|
1813
|
-
generateInvalidationPatterns(req) {
|
|
1814
|
-
const patterns = [];
|
|
1815
|
-
// Add path-based patterns
|
|
1816
|
-
patterns.push(req.path);
|
|
1817
|
-
// Add wildcard patterns for API endpoints
|
|
1818
|
-
if (req.path.startsWith("/api/")) {
|
|
1819
|
-
const pathParts = req.path.split("/");
|
|
1820
|
-
if (pathParts.length > 2) {
|
|
1821
|
-
patterns.push(`/api/${pathParts[2]}/*`);
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
return patterns;
|
|
1825
|
-
}
|
|
1826
|
-
/**
|
|
1827
|
-
* Update cache statistics
|
|
1828
|
-
*/
|
|
1829
|
-
updateCacheStats(_operation, executionTime, success) {
|
|
1830
|
-
this.cacheStats.totalOperations++;
|
|
1831
|
-
this.cacheStats.lastOperation = new Date();
|
|
1832
|
-
if (!success) {
|
|
1833
|
-
this.cacheStats.errors++;
|
|
1834
|
-
}
|
|
1835
|
-
// Update average response time
|
|
1836
|
-
const totalTime = this.cacheStats.averageResponseTime *
|
|
1837
|
-
(this.cacheStats.totalOperations - 1) +
|
|
1838
|
-
executionTime;
|
|
1839
|
-
this.cacheStats.averageResponseTime =
|
|
1840
|
-
totalTime / this.cacheStats.totalOperations;
|
|
1841
|
-
}
|
|
1842
|
-
/**
|
|
1843
|
-
* Get cache statistics
|
|
1844
|
-
*/
|
|
1845
|
-
getCacheStats() {
|
|
1846
|
-
const hitRate = this.cacheStats.totalOperations > 0
|
|
1847
|
-
? (this.cacheStats.hits /
|
|
1848
|
-
(this.cacheStats.hits + this.cacheStats.misses)) *
|
|
1849
|
-
100
|
|
1850
|
-
: 0;
|
|
1851
|
-
return {
|
|
1852
|
-
...this.cacheStats,
|
|
1853
|
-
hitRate,
|
|
1854
|
-
missRate: 100 - hitRate,
|
|
1855
|
-
errorRate: this.cacheStats.totalOperations > 0
|
|
1856
|
-
? (this.cacheStats.errors /
|
|
1857
|
-
this.cacheStats.totalOperations) *
|
|
1858
|
-
100
|
|
1859
|
-
: 0,
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
/**
|
|
1863
|
-
* Generate cache recommendations based on performance statistics
|
|
1864
|
-
*/
|
|
1865
|
-
generateCacheRecommendations(stats) {
|
|
1866
|
-
const recommendations = [];
|
|
1867
|
-
if (stats.hitRate < 30) {
|
|
1868
|
-
recommendations.push("Consider increasing TTL values to improve hit rate");
|
|
1869
|
-
recommendations.push("Review caching patterns for frequently accessed content");
|
|
1870
|
-
}
|
|
1871
|
-
if (stats.errorRate > 5) {
|
|
1872
|
-
recommendations.push("High error rate detected - review cache configuration");
|
|
1873
|
-
recommendations.push("Consider implementing cache fallback mechanisms");
|
|
1874
|
-
}
|
|
1875
|
-
if (stats.averageResponseTime > 50) {
|
|
1876
|
-
recommendations.push("Consider using memory cache for frequently accessed data");
|
|
1877
|
-
recommendations.push("Optimize cache key generation for better performance");
|
|
1878
|
-
}
|
|
1879
|
-
if (stats.totalOperations < 100) {
|
|
1880
|
-
recommendations.push("Low cache usage detected - consider expanding caching scope");
|
|
1881
|
-
}
|
|
1882
|
-
return recommendations;
|
|
1883
|
-
}
|
|
1884
|
-
/**
|
|
1885
|
-
* Apply automatic optimizations based on performance analysis
|
|
1886
|
-
*/
|
|
1887
|
-
async applyAutomaticOptimizations(analysis) {
|
|
1888
|
-
const appliedOptimizations = [];
|
|
1889
|
-
try {
|
|
1890
|
-
// Automatically adjust TTL based on hit rate
|
|
1891
|
-
if (analysis.hitRate < 30) {
|
|
1892
|
-
// Increase default TTL for better hit rates
|
|
1893
|
-
this.contentTypePatterns.set("application/json", 600000); // 10 minutes
|
|
1894
|
-
appliedOptimizations.push("Increased TTL for JSON responses");
|
|
1895
|
-
}
|
|
1896
|
-
// Automatically enable compression for large responses
|
|
1897
|
-
if (analysis.averageResponseTime > 100) {
|
|
1898
|
-
appliedOptimizations.push("Enabled compression for large responses");
|
|
1899
|
-
}
|
|
1900
|
-
// Automatically clean up expired cache entries
|
|
1901
|
-
if (analysis.errorRate > 10) {
|
|
1902
|
-
await this.cleanupExpiredEntries();
|
|
1903
|
-
appliedOptimizations.push("Cleaned up expired cache entries");
|
|
1904
|
-
}
|
|
1905
|
-
return appliedOptimizations;
|
|
1906
|
-
}
|
|
1907
|
-
catch (error) {
|
|
1908
|
-
console.error("Error applying automatic optimizations:", error);
|
|
1909
|
-
return appliedOptimizations;
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
/**
|
|
1913
|
-
* Check health of individual cache instance
|
|
1914
|
-
*/
|
|
1915
|
-
async checkCacheInstanceHealth(type, cacheInstance) {
|
|
1916
|
-
try {
|
|
1917
|
-
if (!cacheInstance) {
|
|
1918
|
-
return "unavailable";
|
|
1919
|
-
}
|
|
1920
|
-
// Test basic operations
|
|
1921
|
-
const testKey = `health-check-${Date.now()}`;
|
|
1922
|
-
const testValue = { test: true, timestamp: Date.now() };
|
|
1923
|
-
// Test set operation
|
|
1924
|
-
await cacheInstance.set?.(testKey, testValue, { ttl: 1000 });
|
|
1925
|
-
// Test get operation
|
|
1926
|
-
const retrieved = await cacheInstance.get?.(testKey);
|
|
1927
|
-
if (!retrieved) {
|
|
1928
|
-
return "degraded";
|
|
1929
|
-
}
|
|
1930
|
-
// Test delete operation
|
|
1931
|
-
await cacheInstance.delete?.(testKey);
|
|
1932
|
-
return "healthy";
|
|
1933
|
-
}
|
|
1934
|
-
catch (error) {
|
|
1935
|
-
console.error(`Health check failed for ${type} cache:`, error);
|
|
1936
|
-
return "error";
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Clean up expired cache entries
|
|
1941
|
-
*/
|
|
1942
|
-
async cleanupExpiredEntries() {
|
|
1943
|
-
try {
|
|
1944
|
-
// Clean up tagged keys
|
|
1945
|
-
const now = Date.now();
|
|
1946
|
-
for (const [tag, keys] of this.taggedKeys.entries()) {
|
|
1947
|
-
const expiredKeys = [];
|
|
1948
|
-
for (const key of keys) {
|
|
1949
|
-
// Check if key exists in any cache
|
|
1950
|
-
const exists = await Promise.race([
|
|
1951
|
-
this.memoryCache?.has?.(key),
|
|
1952
|
-
this.fileCache?.has?.(key),
|
|
1953
|
-
this.hybridCache?.has?.(key),
|
|
1954
|
-
]);
|
|
1955
|
-
if (!exists) {
|
|
1956
|
-
expiredKeys.push(key);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
// Remove expired keys from tag
|
|
1960
|
-
expiredKeys.forEach((key) => keys.delete(key));
|
|
1961
|
-
// Remove empty tags
|
|
1962
|
-
if (keys.size === 0) {
|
|
1963
|
-
this.taggedKeys.delete(tag);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
console.debug(`Cleaned up expired cache entries: ${now}`);
|
|
1967
|
-
}
|
|
1968
|
-
catch (error) {
|
|
1969
|
-
console.error("Error cleaning up expired entries:", error);
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
exports.CachePlugin = CachePlugin;
|
|
1975
|
-
//# sourceMappingURL=CachePlugin.js.map
|