@json-to-office/shared 0.1.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/LICENSE +18 -0
- package/README.md +9 -0
- package/dist/cache/index.d.ts +464 -0
- package/dist/cache/index.js +738 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/chunk-244MHDOZ.js +39 -0
- package/dist/chunk-244MHDOZ.js.map +1 -0
- package/dist/chunk-5J43F4XD.js +181 -0
- package/dist/chunk-5J43F4XD.js.map +1 -0
- package/dist/chunk-ZKD5BAMU.js +382 -0
- package/dist/chunk-ZKD5BAMU.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/schema-utils-CbCi6dEk.d.ts +43 -0
- package/dist/schemas/schema-utils.d.ts +2 -0
- package/dist/schemas/schema-utils.js +15 -0
- package/dist/schemas/schema-utils.js.map +1 -0
- package/dist/types/warnings.d.ts +9 -0
- package/dist/types/warnings.js +1 -0
- package/dist/types/warnings.js.map +1 -0
- package/dist/utils/semver.d.ts +11 -0
- package/dist/utils/semver.js +13 -0
- package/dist/utils/semver.js.map +1 -0
- package/dist/validation/unified/index.d.ts +66 -0
- package/dist/validation/unified/index.js +41 -0
- package/dist/validation/unified/index.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
// src/cache/manager.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
var ComponentCacheManager = class extends EventEmitter {
|
|
4
|
+
config;
|
|
5
|
+
stats;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super();
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.stats = this.initializeStats();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get cache statistics with deep immutability
|
|
13
|
+
*/
|
|
14
|
+
getStats() {
|
|
15
|
+
const componentStatsCopy = /* @__PURE__ */ new Map();
|
|
16
|
+
this.stats.componentStats.forEach((value, key) => {
|
|
17
|
+
componentStatsCopy.set(key, { ...value });
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
...this.stats,
|
|
21
|
+
componentStats: componentStatsCopy
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get configuration with deep immutability
|
|
26
|
+
*/
|
|
27
|
+
getConfig() {
|
|
28
|
+
const configCopy = {
|
|
29
|
+
enabled: this.config.enabled,
|
|
30
|
+
evictionPolicy: this.config.evictionPolicy,
|
|
31
|
+
memory: { ...this.config.memory },
|
|
32
|
+
performance: { ...this.config.performance }
|
|
33
|
+
};
|
|
34
|
+
if (this.config.disk) {
|
|
35
|
+
configCopy.disk = { ...this.config.disk };
|
|
36
|
+
}
|
|
37
|
+
if (this.config.componentConfig) {
|
|
38
|
+
configCopy.componentConfig = {};
|
|
39
|
+
for (const [key, value] of Object.entries(this.config.componentConfig)) {
|
|
40
|
+
configCopy.componentConfig[key] = { ...value };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return configCopy;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get multiple entries (batch operation)
|
|
47
|
+
*/
|
|
48
|
+
async getMany(keys) {
|
|
49
|
+
const results = /* @__PURE__ */ new Map();
|
|
50
|
+
if (this.config.performance.parallelProcessing) {
|
|
51
|
+
const promises = keys.map(async (key) => {
|
|
52
|
+
const value = await this.get(key);
|
|
53
|
+
if (value) results.set(key, value);
|
|
54
|
+
});
|
|
55
|
+
await Promise.all(promises);
|
|
56
|
+
} else {
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
const value = await this.get(key);
|
|
59
|
+
if (value) results.set(key, value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set multiple entries (batch operation)
|
|
66
|
+
*/
|
|
67
|
+
async setMany(entries) {
|
|
68
|
+
if (this.config.performance.parallelProcessing) {
|
|
69
|
+
const promises = entries.map(([key, value]) => this.set(key, value));
|
|
70
|
+
await Promise.all(promises);
|
|
71
|
+
} else {
|
|
72
|
+
for (const [key, value] of entries) {
|
|
73
|
+
await this.set(key, value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Delete multiple entries
|
|
79
|
+
*/
|
|
80
|
+
async deleteMany(keys) {
|
|
81
|
+
let deleted = 0;
|
|
82
|
+
if (this.config.performance.parallelProcessing) {
|
|
83
|
+
const results = await Promise.all(keys.map((key) => this.delete(key)));
|
|
84
|
+
deleted = results.filter(Boolean).length;
|
|
85
|
+
} else {
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
if (await this.delete(key)) {
|
|
88
|
+
deleted++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return deleted;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Initialize statistics
|
|
96
|
+
*/
|
|
97
|
+
initializeStats() {
|
|
98
|
+
return {
|
|
99
|
+
entries: 0,
|
|
100
|
+
totalSize: 0,
|
|
101
|
+
hitRate: 0,
|
|
102
|
+
missRate: 0,
|
|
103
|
+
totalHits: 0,
|
|
104
|
+
totalMisses: 0,
|
|
105
|
+
avgResponseTime: 0,
|
|
106
|
+
evictions: 0,
|
|
107
|
+
componentStats: /* @__PURE__ */ new Map()
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Update statistics on cache hit
|
|
112
|
+
*/
|
|
113
|
+
updateHitStats(key, component) {
|
|
114
|
+
this.stats.totalHits++;
|
|
115
|
+
this.updateComponentStats(component.componentName, "hit");
|
|
116
|
+
this.recalculateRates();
|
|
117
|
+
this.emit("hit", key, component);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Update statistics on cache miss
|
|
121
|
+
*/
|
|
122
|
+
updateMissStats(key, componentName) {
|
|
123
|
+
this.stats.totalMisses++;
|
|
124
|
+
if (componentName) {
|
|
125
|
+
this.updateComponentStats(componentName, "miss");
|
|
126
|
+
}
|
|
127
|
+
this.recalculateRates();
|
|
128
|
+
this.emit("miss", key);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Update component-specific statistics
|
|
132
|
+
*/
|
|
133
|
+
updateComponentStats(componentName, event) {
|
|
134
|
+
if (!this.stats.componentStats.has(componentName)) {
|
|
135
|
+
this.stats.componentStats.set(componentName, {
|
|
136
|
+
name: componentName,
|
|
137
|
+
hits: 0,
|
|
138
|
+
misses: 0,
|
|
139
|
+
avgProcessTime: 0,
|
|
140
|
+
avgSize: 0,
|
|
141
|
+
entries: 0
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const stats = this.stats.componentStats.get(componentName);
|
|
145
|
+
if (event === "hit") {
|
|
146
|
+
stats.hits++;
|
|
147
|
+
} else {
|
|
148
|
+
stats.misses++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Recalculate hit/miss rates
|
|
153
|
+
*/
|
|
154
|
+
recalculateRates() {
|
|
155
|
+
const total = this.stats.totalHits + this.stats.totalMisses;
|
|
156
|
+
if (total > 0) {
|
|
157
|
+
this.stats.hitRate = this.stats.totalHits / total;
|
|
158
|
+
this.stats.missRate = this.stats.totalMisses / total;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Emit statistics periodically
|
|
163
|
+
*/
|
|
164
|
+
emitStats() {
|
|
165
|
+
this.emit("stats", this.getStats());
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// src/cache/memory-cache.ts
|
|
170
|
+
var LRUNode = class {
|
|
171
|
+
key;
|
|
172
|
+
value;
|
|
173
|
+
prev = null;
|
|
174
|
+
next = null;
|
|
175
|
+
constructor(key, value) {
|
|
176
|
+
this.key = key;
|
|
177
|
+
this.value = value;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var MemoryCache = class extends ComponentCacheManager {
|
|
181
|
+
cache;
|
|
182
|
+
head = null;
|
|
183
|
+
tail = null;
|
|
184
|
+
currentSize = 0;
|
|
185
|
+
keyToComponentName = /* @__PURE__ */ new Map();
|
|
186
|
+
cleanupTimer;
|
|
187
|
+
constructor(config) {
|
|
188
|
+
super(config);
|
|
189
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
190
|
+
if (config.memory.cleanupInterval > 0) {
|
|
191
|
+
this.startCleanupTimer();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async get(key) {
|
|
195
|
+
const node = this.cache.get(key);
|
|
196
|
+
if (!node) {
|
|
197
|
+
const componentName = this.keyToComponentName.get(key) || this.extractComponentNameFromKey(key);
|
|
198
|
+
this.updateMissStats(key, componentName);
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
const component = node.value;
|
|
202
|
+
if (this.isExpired(component)) {
|
|
203
|
+
await this.delete(key);
|
|
204
|
+
this.updateMissStats(key, component.componentName);
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
component.lastAccessed = Date.now();
|
|
208
|
+
component.hits++;
|
|
209
|
+
this.moveToHead(node);
|
|
210
|
+
this.updateHitStats(key, component);
|
|
211
|
+
return component;
|
|
212
|
+
}
|
|
213
|
+
async set(key, component) {
|
|
214
|
+
if (!this.isCacheable(component.componentName)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.keyToComponentName.set(key, component.componentName);
|
|
218
|
+
if (this.cache.has(key)) {
|
|
219
|
+
await this.delete(key);
|
|
220
|
+
}
|
|
221
|
+
await this.ensureSpace(component.size);
|
|
222
|
+
const node = new LRUNode(key, component);
|
|
223
|
+
this.cache.set(key, node);
|
|
224
|
+
this.addToHead(node);
|
|
225
|
+
this.currentSize += component.size;
|
|
226
|
+
this.stats.entries++;
|
|
227
|
+
this.stats.totalSize = this.currentSize;
|
|
228
|
+
let componentStats = this.stats.componentStats.get(component.componentName);
|
|
229
|
+
if (!componentStats) {
|
|
230
|
+
componentStats = {
|
|
231
|
+
name: component.componentName,
|
|
232
|
+
hits: 0,
|
|
233
|
+
misses: 0,
|
|
234
|
+
entries: 0,
|
|
235
|
+
avgSize: 0,
|
|
236
|
+
avgProcessTime: 0
|
|
237
|
+
};
|
|
238
|
+
this.stats.componentStats.set(component.componentName, componentStats);
|
|
239
|
+
}
|
|
240
|
+
componentStats.entries++;
|
|
241
|
+
componentStats.avgSize = (componentStats.avgSize * (componentStats.entries - 1) + component.size) / componentStats.entries;
|
|
242
|
+
this.emit("set", key, component);
|
|
243
|
+
}
|
|
244
|
+
async has(key) {
|
|
245
|
+
const node = this.cache.get(key);
|
|
246
|
+
if (!node) return false;
|
|
247
|
+
return !this.isExpired(node.value);
|
|
248
|
+
}
|
|
249
|
+
async delete(key) {
|
|
250
|
+
const node = this.cache.get(key);
|
|
251
|
+
if (!node) return false;
|
|
252
|
+
this.removeNode(node);
|
|
253
|
+
this.cache.delete(key);
|
|
254
|
+
this.keyToComponentName.delete(key);
|
|
255
|
+
this.currentSize -= node.value.size;
|
|
256
|
+
this.stats.entries--;
|
|
257
|
+
this.stats.totalSize = this.currentSize;
|
|
258
|
+
const componentStats = this.stats.componentStats.get(node.value.componentName);
|
|
259
|
+
if (componentStats && componentStats.entries > 0) {
|
|
260
|
+
componentStats.entries--;
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
async clear() {
|
|
265
|
+
this.cache.clear();
|
|
266
|
+
this.keyToComponentName.clear();
|
|
267
|
+
this.head = null;
|
|
268
|
+
this.tail = null;
|
|
269
|
+
this.currentSize = 0;
|
|
270
|
+
this.stats = this.initializeStats();
|
|
271
|
+
}
|
|
272
|
+
async getKeys() {
|
|
273
|
+
return Array.from(this.cache.keys());
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Extract component name from cache key
|
|
277
|
+
*/
|
|
278
|
+
extractComponentNameFromKey(key) {
|
|
279
|
+
const parts = key.split(":");
|
|
280
|
+
return parts.length >= 2 ? parts[1] : void 0;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if component is expired
|
|
284
|
+
*/
|
|
285
|
+
isExpired(component) {
|
|
286
|
+
if (!component.ttl) return false;
|
|
287
|
+
return Date.now() - component.timestamp > component.ttl * 1e3;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if component is cacheable
|
|
291
|
+
*/
|
|
292
|
+
isCacheable(componentName) {
|
|
293
|
+
const componentConfig = this.config.componentConfig?.[componentName];
|
|
294
|
+
return componentConfig?.cacheable !== false;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Ensure enough space for new entry
|
|
298
|
+
*/
|
|
299
|
+
async ensureSpace(requiredSize) {
|
|
300
|
+
const maxSize = this.config.memory.maxSize * 1024 * 1024;
|
|
301
|
+
while (this.currentSize + requiredSize > maxSize && this.tail) {
|
|
302
|
+
const keyToEvict = this.tail.key;
|
|
303
|
+
await this.delete(keyToEvict);
|
|
304
|
+
this.stats.evictions++;
|
|
305
|
+
this.emit("evict", keyToEvict, "size");
|
|
306
|
+
}
|
|
307
|
+
const maxEntries = this.config.memory.maxEntries;
|
|
308
|
+
while (this.stats.entries >= maxEntries && this.tail) {
|
|
309
|
+
const keyToEvict = this.tail.key;
|
|
310
|
+
await this.delete(keyToEvict);
|
|
311
|
+
this.stats.evictions++;
|
|
312
|
+
this.emit("evict", keyToEvict, "size");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* LRU operations - Add node to head
|
|
317
|
+
*/
|
|
318
|
+
addToHead(node) {
|
|
319
|
+
node.prev = null;
|
|
320
|
+
node.next = this.head;
|
|
321
|
+
if (this.head) {
|
|
322
|
+
this.head.prev = node;
|
|
323
|
+
}
|
|
324
|
+
this.head = node;
|
|
325
|
+
if (!this.tail) {
|
|
326
|
+
this.tail = node;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Remove node from list
|
|
331
|
+
*/
|
|
332
|
+
removeNode(node) {
|
|
333
|
+
if (node.prev) {
|
|
334
|
+
node.prev.next = node.next;
|
|
335
|
+
} else {
|
|
336
|
+
this.head = node.next;
|
|
337
|
+
}
|
|
338
|
+
if (node.next) {
|
|
339
|
+
node.next.prev = node.prev;
|
|
340
|
+
} else {
|
|
341
|
+
this.tail = node.prev;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Move node to head
|
|
346
|
+
*/
|
|
347
|
+
moveToHead(node) {
|
|
348
|
+
if (node === this.head) return;
|
|
349
|
+
this.removeNode(node);
|
|
350
|
+
this.addToHead(node);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Start cleanup timer for expired entries
|
|
354
|
+
*/
|
|
355
|
+
startCleanupTimer() {
|
|
356
|
+
this.cleanupTimer = setInterval(
|
|
357
|
+
async () => {
|
|
358
|
+
const keysToDelete = [];
|
|
359
|
+
for (const [key, node] of this.cache) {
|
|
360
|
+
if (this.isExpired(node.value)) {
|
|
361
|
+
keysToDelete.push(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const key of keysToDelete) {
|
|
365
|
+
await this.delete(key);
|
|
366
|
+
this.stats.evictions++;
|
|
367
|
+
this.emit("evict", key, "ttl");
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
(this.config.memory?.cleanupInterval || 300) * 1e3
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Stop cleanup timer
|
|
375
|
+
*/
|
|
376
|
+
destroy() {
|
|
377
|
+
if (this.cleanupTimer) {
|
|
378
|
+
clearInterval(this.cleanupTimer);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/cache/config.ts
|
|
384
|
+
var DEFAULT_CACHE_CONFIG = {
|
|
385
|
+
enabled: true,
|
|
386
|
+
memory: {
|
|
387
|
+
enabled: true,
|
|
388
|
+
maxSize: 100,
|
|
389
|
+
// 100MB
|
|
390
|
+
maxEntries: 1e3,
|
|
391
|
+
defaultTTL: 3600,
|
|
392
|
+
// 1 hour
|
|
393
|
+
cleanupInterval: 300
|
|
394
|
+
// 5 minutes
|
|
395
|
+
},
|
|
396
|
+
evictionPolicy: "lru",
|
|
397
|
+
componentConfig: {
|
|
398
|
+
// Static content - longer TTL
|
|
399
|
+
text: { cacheable: true, ttl: 7200 },
|
|
400
|
+
// 2 hours
|
|
401
|
+
heading: { cacheable: true, ttl: 7200 },
|
|
402
|
+
columns: { cacheable: true, ttl: 3600 },
|
|
403
|
+
// 1 hour
|
|
404
|
+
section: { cacheable: true, ttl: 3600 },
|
|
405
|
+
// Dynamic content - shorter TTL
|
|
406
|
+
"custom-data": { cacheable: true, ttl: 300 },
|
|
407
|
+
// 5 minutes
|
|
408
|
+
"api-content": { cacheable: true, ttl: 180 },
|
|
409
|
+
// 3 minutes
|
|
410
|
+
// Resource-intensive components - medium TTL
|
|
411
|
+
image: { cacheable: true, ttl: 1800 },
|
|
412
|
+
// 30 minutes
|
|
413
|
+
table: { cacheable: true, ttl: 1800 }
|
|
414
|
+
},
|
|
415
|
+
performance: {
|
|
416
|
+
trackMetrics: true,
|
|
417
|
+
metricsSampleRate: 1,
|
|
418
|
+
enableWarming: false,
|
|
419
|
+
parallelProcessing: true
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
function getCacheConfigFromEnv() {
|
|
423
|
+
const config = {};
|
|
424
|
+
if (process.env.CACHE_ENABLED !== void 0) {
|
|
425
|
+
config.enabled = process.env.CACHE_ENABLED !== "false";
|
|
426
|
+
}
|
|
427
|
+
if (process.env.CACHE_MAX_SIZE || process.env.CACHE_MAX_ENTRIES || process.env.CACHE_TTL) {
|
|
428
|
+
config.memory = {
|
|
429
|
+
enabled: true,
|
|
430
|
+
maxSize: parseInt(process.env.CACHE_MAX_SIZE || "100"),
|
|
431
|
+
maxEntries: parseInt(process.env.CACHE_MAX_ENTRIES || "1000"),
|
|
432
|
+
defaultTTL: parseInt(process.env.CACHE_TTL || "3600"),
|
|
433
|
+
cleanupInterval: parseInt(process.env.CACHE_CLEANUP_INTERVAL || "300")
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
if (process.env.CACHE_TRACK_METRICS !== void 0 || process.env.CACHE_WARMING !== void 0) {
|
|
437
|
+
config.performance = {
|
|
438
|
+
trackMetrics: process.env.CACHE_TRACK_METRICS !== "false",
|
|
439
|
+
metricsSampleRate: parseFloat(process.env.CACHE_SAMPLE_RATE || "1.0"),
|
|
440
|
+
enableWarming: process.env.CACHE_WARMING === "true",
|
|
441
|
+
parallelProcessing: process.env.CACHE_PARALLEL !== "false"
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return config;
|
|
445
|
+
}
|
|
446
|
+
function mergeConfigs(...configs) {
|
|
447
|
+
const merged = { ...DEFAULT_CACHE_CONFIG };
|
|
448
|
+
for (const config of configs) {
|
|
449
|
+
if (config.enabled !== void 0) {
|
|
450
|
+
merged.enabled = config.enabled;
|
|
451
|
+
}
|
|
452
|
+
if (config.memory) {
|
|
453
|
+
merged.memory = { ...merged.memory, ...config.memory };
|
|
454
|
+
}
|
|
455
|
+
if (config.disk) {
|
|
456
|
+
merged.disk = { ...merged.disk, ...config.disk };
|
|
457
|
+
}
|
|
458
|
+
if (config.evictionPolicy) {
|
|
459
|
+
merged.evictionPolicy = config.evictionPolicy;
|
|
460
|
+
}
|
|
461
|
+
if (config.componentConfig) {
|
|
462
|
+
merged.componentConfig = { ...merged.componentConfig, ...config.componentConfig };
|
|
463
|
+
}
|
|
464
|
+
if (config.performance) {
|
|
465
|
+
merged.performance = { ...merged.performance, ...config.performance };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return merged;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/cache/analytics.ts
|
|
472
|
+
var ComponentCacheAnalytics = class {
|
|
473
|
+
historyWindow = 36e5;
|
|
474
|
+
// 1 hour in milliseconds
|
|
475
|
+
metricsHistory = /* @__PURE__ */ new Map();
|
|
476
|
+
performanceBaseline = /* @__PURE__ */ new Map();
|
|
477
|
+
/**
|
|
478
|
+
* Analyze cache statistics and generate comprehensive report
|
|
479
|
+
*/
|
|
480
|
+
analyzeCache(stats) {
|
|
481
|
+
const componentMetrics = this.calculateComponentMetrics(stats);
|
|
482
|
+
const trends = this.calculateTrends(stats);
|
|
483
|
+
const recommendations = this.generateRecommendations(componentMetrics, trends);
|
|
484
|
+
const healthScore = this.calculateHealthScore(componentMetrics);
|
|
485
|
+
const overallEfficiency = this.calculateOverallEfficiency(componentMetrics);
|
|
486
|
+
const performanceGain = this.calculatePerformanceGain(componentMetrics);
|
|
487
|
+
const sortedByEfficiency = [...componentMetrics].sort(
|
|
488
|
+
(a, b) => b.efficiencyScore - a.efficiencyScore
|
|
489
|
+
);
|
|
490
|
+
const topPerformers = sortedByEfficiency.slice(0, 3).map((m) => m.componentName);
|
|
491
|
+
const needsAttention = sortedByEfficiency.filter((m) => m.efficiencyScore < 50).map((m) => m.componentName);
|
|
492
|
+
return {
|
|
493
|
+
timestamp: Date.now(),
|
|
494
|
+
healthScore,
|
|
495
|
+
overallEfficiency,
|
|
496
|
+
componentMetrics,
|
|
497
|
+
trends,
|
|
498
|
+
recommendations,
|
|
499
|
+
topPerformers,
|
|
500
|
+
needsAttention,
|
|
501
|
+
performanceGain
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Calculate detailed metrics for each component
|
|
506
|
+
*/
|
|
507
|
+
calculateComponentMetrics(stats) {
|
|
508
|
+
const metrics = [];
|
|
509
|
+
stats.componentStats.forEach((componentStats, componentName) => {
|
|
510
|
+
const totalRequests = componentStats.hits + componentStats.misses;
|
|
511
|
+
const hitRate = totalRequests > 0 ? componentStats.hits / totalRequests : 0;
|
|
512
|
+
const efficiencyScore = this.calculateEfficiencyScore(
|
|
513
|
+
hitRate,
|
|
514
|
+
componentStats.avgProcessTime,
|
|
515
|
+
componentStats.avgSize,
|
|
516
|
+
componentStats.entries
|
|
517
|
+
);
|
|
518
|
+
const timeSaved = componentStats.hits * componentStats.avgProcessTime;
|
|
519
|
+
const memoryCost = componentStats.entries * componentStats.avgSize;
|
|
520
|
+
const timeBenefit = timeSaved;
|
|
521
|
+
const costBenefitRatio = memoryCost > 0 ? timeBenefit / memoryCost : 0;
|
|
522
|
+
metrics.push({
|
|
523
|
+
componentName,
|
|
524
|
+
hitRate,
|
|
525
|
+
totalRequests,
|
|
526
|
+
avgHitTime: 1,
|
|
527
|
+
// Cache hits are typically ~1ms
|
|
528
|
+
avgMissTime: componentStats.avgProcessTime,
|
|
529
|
+
efficiencyScore,
|
|
530
|
+
memoryUsage: componentStats.entries * componentStats.avgSize,
|
|
531
|
+
timeSaved,
|
|
532
|
+
costBenefitRatio
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
return metrics;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Calculate efficiency score for a component
|
|
539
|
+
*/
|
|
540
|
+
calculateEfficiencyScore(hitRate, avgProcessTime, avgSize, entries) {
|
|
541
|
+
const hitRateWeight = 0.4;
|
|
542
|
+
const processingTimeWeight = 0.3;
|
|
543
|
+
const memorySizeWeight = 0.2;
|
|
544
|
+
const utilizationWeight = 0.1;
|
|
545
|
+
const hitRateScore = hitRate * 100;
|
|
546
|
+
const processingTimeScore = Math.min(100, avgProcessTime / 10 * 100);
|
|
547
|
+
const memorySizeScore = Math.max(0, 100 - avgSize / 1e4 * 100);
|
|
548
|
+
const utilizationScore = Math.min(100, entries / 100 * 100);
|
|
549
|
+
const score = hitRateScore * hitRateWeight + processingTimeScore * processingTimeWeight + memorySizeScore * memorySizeWeight + utilizationScore * utilizationWeight;
|
|
550
|
+
return Math.round(score);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Calculate trends over time
|
|
554
|
+
*/
|
|
555
|
+
calculateTrends(stats) {
|
|
556
|
+
const trends = [];
|
|
557
|
+
const now = Date.now();
|
|
558
|
+
stats.componentStats.forEach((componentStats, componentName) => {
|
|
559
|
+
const historyKey = `${componentName}_hitRate`;
|
|
560
|
+
if (!this.metricsHistory.has(historyKey)) {
|
|
561
|
+
this.metricsHistory.set(historyKey, []);
|
|
562
|
+
}
|
|
563
|
+
const history = this.metricsHistory.get(historyKey);
|
|
564
|
+
const totalRequests = componentStats.hits + componentStats.misses;
|
|
565
|
+
const hitRate = totalRequests > 0 ? componentStats.hits / totalRequests : 0;
|
|
566
|
+
history.push({ timestamp: now, value: hitRate });
|
|
567
|
+
const cutoff = now - this.historyWindow;
|
|
568
|
+
const cleanedHistory = history.filter(
|
|
569
|
+
(point) => point.timestamp > cutoff
|
|
570
|
+
);
|
|
571
|
+
this.metricsHistory.set(historyKey, cleanedHistory);
|
|
572
|
+
trends.push({
|
|
573
|
+
componentName,
|
|
574
|
+
hitRateTrend: [...cleanedHistory],
|
|
575
|
+
requestVolumeTrend: this.getOrCreateHistory(
|
|
576
|
+
`${componentName}_volume`,
|
|
577
|
+
totalRequests
|
|
578
|
+
),
|
|
579
|
+
responseTimeTrend: this.getOrCreateHistory(
|
|
580
|
+
`${componentName}_response`,
|
|
581
|
+
componentStats.avgProcessTime
|
|
582
|
+
),
|
|
583
|
+
memoryUsageTrend: this.getOrCreateHistory(
|
|
584
|
+
`${componentName}_memory`,
|
|
585
|
+
componentStats.entries * componentStats.avgSize
|
|
586
|
+
)
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
return trends;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get or create history for a metric
|
|
593
|
+
*/
|
|
594
|
+
getOrCreateHistory(key, currentValue) {
|
|
595
|
+
const now = Date.now();
|
|
596
|
+
if (!this.metricsHistory.has(key)) {
|
|
597
|
+
this.metricsHistory.set(key, []);
|
|
598
|
+
}
|
|
599
|
+
const history = this.metricsHistory.get(key);
|
|
600
|
+
history.push({ timestamp: now, value: currentValue });
|
|
601
|
+
const cutoff = now - this.historyWindow;
|
|
602
|
+
const cleanedHistory = history.filter((point) => point.timestamp > cutoff);
|
|
603
|
+
this.metricsHistory.set(key, cleanedHistory);
|
|
604
|
+
return [...cleanedHistory];
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Generate optimization recommendations
|
|
608
|
+
*/
|
|
609
|
+
generateRecommendations(metrics, trends) {
|
|
610
|
+
const recommendations = [];
|
|
611
|
+
metrics.forEach((metric) => {
|
|
612
|
+
const trend = trends.find((t) => t.componentName === metric.componentName);
|
|
613
|
+
if (metric.hitRate < 0.3 && metric.totalRequests > 10) {
|
|
614
|
+
recommendations.push({
|
|
615
|
+
componentName: metric.componentName,
|
|
616
|
+
type: "increase_ttl",
|
|
617
|
+
description: `Increase TTL for ${metric.componentName} components to improve hit rate`,
|
|
618
|
+
expectedImprovement: 20,
|
|
619
|
+
priority: 4,
|
|
620
|
+
reasoning: `Current hit rate of ${(metric.hitRate * 100).toFixed(1)}% is below optimal threshold. Increasing TTL could improve cache effectiveness.`
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (metric.memoryUsage > 1e6 && metric.hitRate < 0.5) {
|
|
624
|
+
recommendations.push({
|
|
625
|
+
componentName: metric.componentName,
|
|
626
|
+
type: "decrease_ttl",
|
|
627
|
+
description: `Reduce cache size for ${metric.componentName} components`,
|
|
628
|
+
expectedImprovement: 15,
|
|
629
|
+
priority: 3,
|
|
630
|
+
reasoning: `High memory usage (${(metric.memoryUsage / 1024 / 1024).toFixed(2)}MB) with moderate hit rate suggests over-caching.`
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
if (metric.efficiencyScore < 30) {
|
|
634
|
+
recommendations.push({
|
|
635
|
+
componentName: metric.componentName,
|
|
636
|
+
type: "disable_cache",
|
|
637
|
+
description: `Consider disabling cache for ${metric.componentName} components`,
|
|
638
|
+
expectedImprovement: 10,
|
|
639
|
+
priority: 2,
|
|
640
|
+
reasoning: `Efficiency score of ${metric.efficiencyScore} indicates caching may not be beneficial for this component.`
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
if (trend && this.isDecliningSlopbankTrend(trend.hitRateTrend)) {
|
|
644
|
+
recommendations.push({
|
|
645
|
+
componentName: metric.componentName,
|
|
646
|
+
type: "increase_size",
|
|
647
|
+
description: `Increase cache capacity for ${metric.componentName} components`,
|
|
648
|
+
expectedImprovement: 25,
|
|
649
|
+
priority: 5,
|
|
650
|
+
reasoning: "Hit rate is declining over time, suggesting cache capacity may be insufficient."
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
return recommendations.sort((a, b) => b.priority - a.priority);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Check if a trend is declining
|
|
658
|
+
*/
|
|
659
|
+
isDecliningSlopbankTrend(trend) {
|
|
660
|
+
if (trend.length < 3) return false;
|
|
661
|
+
const n = trend.length;
|
|
662
|
+
const recent = trend.slice(-Math.min(10, n));
|
|
663
|
+
if (recent.length < 3) return false;
|
|
664
|
+
const firstValue = recent[0].value;
|
|
665
|
+
const lastValue = recent[recent.length - 1].value;
|
|
666
|
+
return (firstValue - lastValue) / firstValue > 0.1;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Calculate overall cache health score
|
|
670
|
+
*/
|
|
671
|
+
calculateHealthScore(metrics) {
|
|
672
|
+
if (metrics.length === 0) return 0;
|
|
673
|
+
const avgEfficiency = metrics.reduce((sum, m) => sum + m.efficiencyScore, 0) / metrics.length;
|
|
674
|
+
const avgHitRate = metrics.reduce((sum, m) => sum + m.hitRate, 0) / metrics.length;
|
|
675
|
+
const healthScore = avgEfficiency * 0.6 + avgHitRate * 100 * 0.4;
|
|
676
|
+
return Math.round(healthScore);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Calculate overall cache efficiency
|
|
680
|
+
*/
|
|
681
|
+
calculateOverallEfficiency(metrics) {
|
|
682
|
+
if (metrics.length === 0) return 0;
|
|
683
|
+
const totalTimeSaved = metrics.reduce((sum, m) => sum + m.timeSaved, 0);
|
|
684
|
+
const totalMemoryUsed = metrics.reduce((sum, m) => sum + m.memoryUsage, 0);
|
|
685
|
+
const totalRequests = metrics.reduce((sum, m) => sum + m.totalRequests, 0);
|
|
686
|
+
const totalHits = metrics.reduce(
|
|
687
|
+
(sum, m) => sum + m.hitRate * m.totalRequests,
|
|
688
|
+
0
|
|
689
|
+
);
|
|
690
|
+
if (totalRequests === 0) return 0;
|
|
691
|
+
const hitRateEfficiency = totalHits / totalRequests * 100;
|
|
692
|
+
const resourceEfficiency = totalMemoryUsed > 0 ? Math.min(100, totalTimeSaved / totalMemoryUsed * 1e3) : 0;
|
|
693
|
+
return Math.round(hitRateEfficiency * 0.7 + resourceEfficiency * 0.3);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Calculate performance gain from caching
|
|
697
|
+
*/
|
|
698
|
+
calculatePerformanceGain(metrics) {
|
|
699
|
+
const totalTimeSaved = metrics.reduce((sum, m) => sum + m.timeSaved, 0);
|
|
700
|
+
const totalProcessingTime = metrics.reduce(
|
|
701
|
+
(sum, m) => sum + m.totalRequests * m.avgMissTime,
|
|
702
|
+
0
|
|
703
|
+
);
|
|
704
|
+
const cpuReduction = totalProcessingTime > 0 ? Math.round(totalTimeSaved / totalProcessingTime * 100) : 0;
|
|
705
|
+
const uniqueEntries = metrics.reduce(
|
|
706
|
+
(sum, m) => sum + m.memoryUsage / m.avgHitTime,
|
|
707
|
+
0
|
|
708
|
+
);
|
|
709
|
+
const totalDataProcessed = metrics.reduce(
|
|
710
|
+
(sum, m) => sum + m.totalRequests * m.memoryUsage,
|
|
711
|
+
0
|
|
712
|
+
);
|
|
713
|
+
const memoryOptimization = totalDataProcessed > 0 ? Math.round(
|
|
714
|
+
(totalDataProcessed - uniqueEntries) / totalDataProcessed * 100
|
|
715
|
+
) : 0;
|
|
716
|
+
return {
|
|
717
|
+
timeReduction: Math.round(totalTimeSaved),
|
|
718
|
+
cpuReduction: Math.min(100, cpuReduction),
|
|
719
|
+
memoryOptimization: Math.min(100, memoryOptimization)
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Reset analytics history
|
|
724
|
+
*/
|
|
725
|
+
reset() {
|
|
726
|
+
this.metricsHistory.clear();
|
|
727
|
+
this.performanceBaseline.clear();
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
export {
|
|
731
|
+
ComponentCacheAnalytics,
|
|
732
|
+
ComponentCacheManager,
|
|
733
|
+
DEFAULT_CACHE_CONFIG,
|
|
734
|
+
MemoryCache,
|
|
735
|
+
getCacheConfigFromEnv,
|
|
736
|
+
mergeConfigs
|
|
737
|
+
};
|
|
738
|
+
//# sourceMappingURL=index.js.map
|