@sc4rfurryx/proteusjs 1.0.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/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,601 @@
|
|
1
|
+
/**
|
2
|
+
* Cache Optimization System for ProteusJS
|
3
|
+
* Intelligent caching with memory pressure-based eviction
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface CacheConfig {
|
7
|
+
maxSize: number;
|
8
|
+
maxAge: number;
|
9
|
+
memoryThreshold: number;
|
10
|
+
evictionStrategy: 'lru' | 'lfu' | 'ttl' | 'adaptive';
|
11
|
+
compressionEnabled: boolean;
|
12
|
+
persistentStorage: boolean;
|
13
|
+
storageKey: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
export interface CacheEntry<T> {
|
17
|
+
key: string;
|
18
|
+
value: T;
|
19
|
+
timestamp: number;
|
20
|
+
lastAccessed: number;
|
21
|
+
accessCount: number;
|
22
|
+
size: number;
|
23
|
+
ttl?: number;
|
24
|
+
compressed?: boolean;
|
25
|
+
}
|
26
|
+
|
27
|
+
export interface CacheMetrics {
|
28
|
+
totalEntries: number;
|
29
|
+
totalSize: number;
|
30
|
+
hitRate: number;
|
31
|
+
missRate: number;
|
32
|
+
evictions: number;
|
33
|
+
compressionRatio: number;
|
34
|
+
}
|
35
|
+
|
36
|
+
export class CacheOptimizationSystem<T = any> {
|
37
|
+
private config: Required<CacheConfig>;
|
38
|
+
private cache: Map<string, CacheEntry<T>> = new Map();
|
39
|
+
private accessOrder: string[] = [];
|
40
|
+
private metrics: CacheMetrics;
|
41
|
+
private cleanupTimer: number | null = null;
|
42
|
+
|
43
|
+
constructor(config: Partial<CacheConfig> = {}) {
|
44
|
+
this.config = {
|
45
|
+
maxSize: 100,
|
46
|
+
maxAge: 300000, // 5 minutes
|
47
|
+
memoryThreshold: 10 * 1024 * 1024, // 10MB
|
48
|
+
evictionStrategy: 'adaptive',
|
49
|
+
compressionEnabled: true,
|
50
|
+
persistentStorage: false,
|
51
|
+
storageKey: 'proteus-cache',
|
52
|
+
...config
|
53
|
+
};
|
54
|
+
|
55
|
+
this.metrics = this.createInitialMetrics();
|
56
|
+
this.loadFromStorage();
|
57
|
+
this.startCleanupTimer();
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Get value from cache
|
62
|
+
*/
|
63
|
+
public get(key: string): T | null {
|
64
|
+
const entry = this.cache.get(key);
|
65
|
+
|
66
|
+
if (!entry) {
|
67
|
+
this.metrics.missRate++;
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
|
71
|
+
// Check TTL
|
72
|
+
if (entry.ttl && Date.now() > entry.timestamp + entry.ttl) {
|
73
|
+
this.delete(key);
|
74
|
+
this.metrics.missRate++;
|
75
|
+
return null;
|
76
|
+
}
|
77
|
+
|
78
|
+
// Update access info
|
79
|
+
entry.lastAccessed = Date.now();
|
80
|
+
entry.accessCount++;
|
81
|
+
this.updateAccessOrder(key);
|
82
|
+
|
83
|
+
this.metrics.hitRate++;
|
84
|
+
|
85
|
+
// Decompress if needed
|
86
|
+
let value = entry.value;
|
87
|
+
if (entry.compressed && this.config.compressionEnabled) {
|
88
|
+
value = this.decompress(value);
|
89
|
+
}
|
90
|
+
|
91
|
+
return value;
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Set value in cache
|
96
|
+
*/
|
97
|
+
public set(key: string, value: T, ttl?: number): void {
|
98
|
+
// Check if we need to evict entries
|
99
|
+
if (this.cache.size >= this.config.maxSize) {
|
100
|
+
this.evictEntries(1);
|
101
|
+
}
|
102
|
+
|
103
|
+
// Compress value if enabled
|
104
|
+
let finalValue = value;
|
105
|
+
let compressed = false;
|
106
|
+
if (this.config.compressionEnabled && this.shouldCompress(value)) {
|
107
|
+
finalValue = this.compress(value);
|
108
|
+
compressed = true;
|
109
|
+
}
|
110
|
+
|
111
|
+
const size = this.calculateSize(finalValue);
|
112
|
+
const entry: CacheEntry<T> = {
|
113
|
+
key,
|
114
|
+
value: finalValue,
|
115
|
+
timestamp: Date.now(),
|
116
|
+
lastAccessed: Date.now(),
|
117
|
+
accessCount: 1,
|
118
|
+
size,
|
119
|
+
...(ttl !== undefined && { ttl }),
|
120
|
+
compressed
|
121
|
+
};
|
122
|
+
|
123
|
+
// Remove existing entry if it exists
|
124
|
+
if (this.cache.has(key)) {
|
125
|
+
this.delete(key);
|
126
|
+
}
|
127
|
+
|
128
|
+
this.cache.set(key, entry);
|
129
|
+
this.updateAccessOrder(key);
|
130
|
+
this.updateMetrics();
|
131
|
+
|
132
|
+
// Check memory pressure
|
133
|
+
this.checkMemoryPressure();
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Delete entry from cache
|
138
|
+
*/
|
139
|
+
public delete(key: string): boolean {
|
140
|
+
const deleted = this.cache.delete(key);
|
141
|
+
if (deleted) {
|
142
|
+
this.removeFromAccessOrder(key);
|
143
|
+
this.updateMetrics();
|
144
|
+
}
|
145
|
+
return deleted;
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Check if key exists in cache
|
150
|
+
*/
|
151
|
+
public has(key: string): boolean {
|
152
|
+
const entry = this.cache.get(key);
|
153
|
+
if (!entry) return false;
|
154
|
+
|
155
|
+
// Check TTL
|
156
|
+
if (entry.ttl && Date.now() > entry.timestamp + entry.ttl) {
|
157
|
+
this.delete(key);
|
158
|
+
return false;
|
159
|
+
}
|
160
|
+
|
161
|
+
return true;
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Clear all cache entries
|
166
|
+
*/
|
167
|
+
public clear(): void {
|
168
|
+
this.cache.clear();
|
169
|
+
this.accessOrder = [];
|
170
|
+
this.updateMetrics();
|
171
|
+
}
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Get cache metrics
|
175
|
+
*/
|
176
|
+
public getMetrics(): CacheMetrics {
|
177
|
+
return { ...this.metrics };
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Cache calculation result with automatic key generation
|
182
|
+
*/
|
183
|
+
public cacheCalculation<R>(
|
184
|
+
fn: () => R,
|
185
|
+
keyParts: (string | number)[],
|
186
|
+
ttl?: number
|
187
|
+
): R {
|
188
|
+
const key = this.generateKey('calc', keyParts);
|
189
|
+
|
190
|
+
let result = this.get(key) as R;
|
191
|
+
if (result === null) {
|
192
|
+
result = fn();
|
193
|
+
this.set(key, result as unknown as T, ttl);
|
194
|
+
}
|
195
|
+
|
196
|
+
return result;
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Cache layout measurement
|
201
|
+
*/
|
202
|
+
public cacheLayoutMeasurement(
|
203
|
+
element: Element,
|
204
|
+
measurement: () => DOMRect | number,
|
205
|
+
ttl: number = 1000 // Short TTL for layout measurements
|
206
|
+
): DOMRect | number {
|
207
|
+
const key = this.generateKey('layout', [
|
208
|
+
element.tagName,
|
209
|
+
element.className,
|
210
|
+
element.id || 'no-id'
|
211
|
+
]);
|
212
|
+
|
213
|
+
let result = this.get(key) as DOMRect | number;
|
214
|
+
if (result === null) {
|
215
|
+
result = measurement();
|
216
|
+
this.set(key, result as unknown as T, ttl);
|
217
|
+
}
|
218
|
+
|
219
|
+
return result;
|
220
|
+
}
|
221
|
+
|
222
|
+
/**
|
223
|
+
* Cache container state
|
224
|
+
*/
|
225
|
+
public cacheContainerState(
|
226
|
+
containerId: string,
|
227
|
+
state: any,
|
228
|
+
ttl: number = 5000
|
229
|
+
): void {
|
230
|
+
const key = this.generateKey('container', [containerId]);
|
231
|
+
this.set(key, state, ttl);
|
232
|
+
}
|
233
|
+
|
234
|
+
/**
|
235
|
+
* Get cached container state
|
236
|
+
*/
|
237
|
+
public getCachedContainerState(containerId: string): any {
|
238
|
+
const key = this.generateKey('container', [containerId]);
|
239
|
+
return this.get(key);
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* Cache computed styles
|
244
|
+
*/
|
245
|
+
public cacheComputedStyles(
|
246
|
+
element: Element,
|
247
|
+
properties: string[],
|
248
|
+
ttl: number = 2000
|
249
|
+
): CSSStyleDeclaration | null {
|
250
|
+
const key = this.generateKey('styles', [
|
251
|
+
element.tagName,
|
252
|
+
element.className,
|
253
|
+
properties.join(',')
|
254
|
+
]);
|
255
|
+
|
256
|
+
let styles = this.get(key) as CSSStyleDeclaration;
|
257
|
+
if (styles === null) {
|
258
|
+
styles = window.getComputedStyle(element);
|
259
|
+
// Create a plain object to avoid caching live CSSStyleDeclaration
|
260
|
+
const stylesObj: Record<string, string> = {};
|
261
|
+
properties.forEach(prop => {
|
262
|
+
stylesObj[prop] = styles.getPropertyValue(prop);
|
263
|
+
});
|
264
|
+
this.set(key, stylesObj as any, ttl);
|
265
|
+
return stylesObj as any;
|
266
|
+
}
|
267
|
+
|
268
|
+
return styles;
|
269
|
+
}
|
270
|
+
|
271
|
+
/**
|
272
|
+
* Optimize cache based on memory pressure
|
273
|
+
*/
|
274
|
+
public optimize(): void {
|
275
|
+
// Remove expired entries
|
276
|
+
this.removeExpiredEntries();
|
277
|
+
|
278
|
+
// Check memory pressure and evict if needed
|
279
|
+
this.checkMemoryPressure();
|
280
|
+
|
281
|
+
// Compress large entries
|
282
|
+
this.compressLargeEntries();
|
283
|
+
|
284
|
+
// Update metrics
|
285
|
+
this.updateMetrics();
|
286
|
+
}
|
287
|
+
|
288
|
+
/**
|
289
|
+
* Destroy cache system
|
290
|
+
*/
|
291
|
+
public destroy(): void {
|
292
|
+
this.stopCleanupTimer();
|
293
|
+
this.saveToStorage();
|
294
|
+
this.clear();
|
295
|
+
}
|
296
|
+
|
297
|
+
/**
|
298
|
+
* Evict entries based on strategy
|
299
|
+
*/
|
300
|
+
private evictEntries(count: number): void {
|
301
|
+
const strategy = this.config.evictionStrategy;
|
302
|
+
let keysToEvict: string[] = [];
|
303
|
+
|
304
|
+
switch (strategy) {
|
305
|
+
case 'lru':
|
306
|
+
keysToEvict = this.getLRUKeys(count);
|
307
|
+
break;
|
308
|
+
case 'lfu':
|
309
|
+
keysToEvict = this.getLFUKeys(count);
|
310
|
+
break;
|
311
|
+
case 'ttl':
|
312
|
+
keysToEvict = this.getTTLKeys(count);
|
313
|
+
break;
|
314
|
+
case 'adaptive':
|
315
|
+
keysToEvict = this.getAdaptiveKeys(count);
|
316
|
+
break;
|
317
|
+
}
|
318
|
+
|
319
|
+
keysToEvict.forEach(key => {
|
320
|
+
this.delete(key);
|
321
|
+
this.metrics.evictions++;
|
322
|
+
});
|
323
|
+
}
|
324
|
+
|
325
|
+
/**
|
326
|
+
* Get LRU (Least Recently Used) keys
|
327
|
+
*/
|
328
|
+
private getLRUKeys(count: number): string[] {
|
329
|
+
return this.accessOrder.slice(0, count);
|
330
|
+
}
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Get LFU (Least Frequently Used) keys
|
334
|
+
*/
|
335
|
+
private getLFUKeys(count: number): string[] {
|
336
|
+
const entries = Array.from(this.cache.entries());
|
337
|
+
entries.sort((a, b) => a[1].accessCount - b[1].accessCount);
|
338
|
+
return entries.slice(0, count).map(([key]) => key);
|
339
|
+
}
|
340
|
+
|
341
|
+
/**
|
342
|
+
* Get TTL-based keys (oldest first)
|
343
|
+
*/
|
344
|
+
private getTTLKeys(count: number): string[] {
|
345
|
+
const entries = Array.from(this.cache.entries());
|
346
|
+
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
347
|
+
return entries.slice(0, count).map(([key]) => key);
|
348
|
+
}
|
349
|
+
|
350
|
+
/**
|
351
|
+
* Get adaptive eviction keys (combination of strategies)
|
352
|
+
*/
|
353
|
+
private getAdaptiveKeys(count: number): string[] {
|
354
|
+
const entries = Array.from(this.cache.entries());
|
355
|
+
|
356
|
+
// Score based on age, access frequency, and size
|
357
|
+
entries.forEach(([key, entry]) => {
|
358
|
+
const age = Date.now() - entry.lastAccessed;
|
359
|
+
const frequency = entry.accessCount;
|
360
|
+
const size = entry.size;
|
361
|
+
|
362
|
+
// Lower score = higher priority for eviction
|
363
|
+
(entry as any).score = (age / 1000) + (size / 1024) - (frequency * 10);
|
364
|
+
});
|
365
|
+
|
366
|
+
entries.sort((a, b) => (b[1] as any).score - (a[1] as any).score);
|
367
|
+
return entries.slice(0, count).map(([key]) => key);
|
368
|
+
}
|
369
|
+
|
370
|
+
/**
|
371
|
+
* Check memory pressure and evict if needed
|
372
|
+
*/
|
373
|
+
private checkMemoryPressure(): void {
|
374
|
+
const totalSize = this.getTotalSize();
|
375
|
+
|
376
|
+
if (totalSize > this.config.memoryThreshold) {
|
377
|
+
const evictCount = Math.ceil(this.cache.size * 0.2); // Evict 20%
|
378
|
+
this.evictEntries(evictCount);
|
379
|
+
}
|
380
|
+
}
|
381
|
+
|
382
|
+
/**
|
383
|
+
* Remove expired entries
|
384
|
+
*/
|
385
|
+
private removeExpiredEntries(): void {
|
386
|
+
const now = Date.now();
|
387
|
+
const expiredKeys: string[] = [];
|
388
|
+
|
389
|
+
this.cache.forEach((entry, key) => {
|
390
|
+
const isExpired = entry.ttl
|
391
|
+
? now > entry.timestamp + entry.ttl
|
392
|
+
: now > entry.timestamp + this.config.maxAge;
|
393
|
+
|
394
|
+
if (isExpired) {
|
395
|
+
expiredKeys.push(key);
|
396
|
+
}
|
397
|
+
});
|
398
|
+
|
399
|
+
expiredKeys.forEach(key => this.delete(key));
|
400
|
+
}
|
401
|
+
|
402
|
+
/**
|
403
|
+
* Compress large entries
|
404
|
+
*/
|
405
|
+
private compressLargeEntries(): void {
|
406
|
+
if (!this.config.compressionEnabled) return;
|
407
|
+
|
408
|
+
this.cache.forEach((entry, key) => {
|
409
|
+
if (!entry.compressed && entry.size > 1024) { // 1KB threshold
|
410
|
+
const compressed = this.compress(entry.value);
|
411
|
+
const compressedSize = this.calculateSize(compressed);
|
412
|
+
|
413
|
+
if (compressedSize < entry.size * 0.8) { // Only if 20% reduction
|
414
|
+
entry.value = compressed;
|
415
|
+
entry.size = compressedSize;
|
416
|
+
entry.compressed = true;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
});
|
420
|
+
}
|
421
|
+
|
422
|
+
/**
|
423
|
+
* Update access order for LRU
|
424
|
+
*/
|
425
|
+
private updateAccessOrder(key: string): void {
|
426
|
+
this.removeFromAccessOrder(key);
|
427
|
+
this.accessOrder.push(key);
|
428
|
+
}
|
429
|
+
|
430
|
+
/**
|
431
|
+
* Remove key from access order
|
432
|
+
*/
|
433
|
+
private removeFromAccessOrder(key: string): void {
|
434
|
+
const index = this.accessOrder.indexOf(key);
|
435
|
+
if (index > -1) {
|
436
|
+
this.accessOrder.splice(index, 1);
|
437
|
+
}
|
438
|
+
}
|
439
|
+
|
440
|
+
/**
|
441
|
+
* Generate cache key
|
442
|
+
*/
|
443
|
+
private generateKey(prefix: string, parts: (string | number)[]): string {
|
444
|
+
return `${prefix}:${parts.join(':')}`;
|
445
|
+
}
|
446
|
+
|
447
|
+
/**
|
448
|
+
* Calculate size of value
|
449
|
+
*/
|
450
|
+
private calculateSize(value: any): number {
|
451
|
+
if (typeof value === 'string') {
|
452
|
+
return value.length * 2; // Approximate UTF-16 size
|
453
|
+
}
|
454
|
+
|
455
|
+
try {
|
456
|
+
return JSON.stringify(value).length * 2;
|
457
|
+
} catch {
|
458
|
+
return 1024; // Default estimate
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
/**
|
463
|
+
* Get total cache size
|
464
|
+
*/
|
465
|
+
private getTotalSize(): number {
|
466
|
+
let total = 0;
|
467
|
+
this.cache.forEach(entry => {
|
468
|
+
total += entry.size;
|
469
|
+
});
|
470
|
+
return total;
|
471
|
+
}
|
472
|
+
|
473
|
+
/**
|
474
|
+
* Check if value should be compressed
|
475
|
+
*/
|
476
|
+
private shouldCompress(value: any): boolean {
|
477
|
+
const size = this.calculateSize(value);
|
478
|
+
return size > 512; // Compress values larger than 512 bytes
|
479
|
+
}
|
480
|
+
|
481
|
+
/**
|
482
|
+
* Compress value (simple implementation)
|
483
|
+
*/
|
484
|
+
private compress(value: any): any {
|
485
|
+
// In a real implementation, you might use a compression library
|
486
|
+
// For now, we'll just stringify and indicate it's compressed
|
487
|
+
return {
|
488
|
+
__compressed: true,
|
489
|
+
data: JSON.stringify(value)
|
490
|
+
};
|
491
|
+
}
|
492
|
+
|
493
|
+
/**
|
494
|
+
* Decompress value
|
495
|
+
*/
|
496
|
+
private decompress(value: any): any {
|
497
|
+
if (value && value.__compressed) {
|
498
|
+
return JSON.parse(value.data);
|
499
|
+
}
|
500
|
+
return value;
|
501
|
+
}
|
502
|
+
|
503
|
+
/**
|
504
|
+
* Update cache metrics
|
505
|
+
*/
|
506
|
+
private updateMetrics(): void {
|
507
|
+
this.metrics.totalEntries = this.cache.size;
|
508
|
+
this.metrics.totalSize = this.getTotalSize();
|
509
|
+
|
510
|
+
const totalRequests = this.metrics.hitRate + this.metrics.missRate;
|
511
|
+
if (totalRequests > 0) {
|
512
|
+
this.metrics.hitRate = this.metrics.hitRate / totalRequests;
|
513
|
+
this.metrics.missRate = this.metrics.missRate / totalRequests;
|
514
|
+
}
|
515
|
+
|
516
|
+
// Calculate compression ratio
|
517
|
+
let totalOriginalSize = 0;
|
518
|
+
let totalCompressedSize = 0;
|
519
|
+
let compressedCount = 0;
|
520
|
+
|
521
|
+
this.cache.forEach(entry => {
|
522
|
+
if (entry.compressed) {
|
523
|
+
compressedCount++;
|
524
|
+
totalCompressedSize += entry.size;
|
525
|
+
// Estimate original size (compressed data is typically 30-70% of original)
|
526
|
+
totalOriginalSize += entry.size / 0.5; // Assume 50% compression
|
527
|
+
}
|
528
|
+
});
|
529
|
+
|
530
|
+
if (compressedCount > 0 && totalOriginalSize > 0) {
|
531
|
+
this.metrics.compressionRatio = totalCompressedSize / totalOriginalSize;
|
532
|
+
}
|
533
|
+
}
|
534
|
+
|
535
|
+
/**
|
536
|
+
* Start cleanup timer
|
537
|
+
*/
|
538
|
+
private startCleanupTimer(): void {
|
539
|
+
this.cleanupTimer = window.setInterval(() => {
|
540
|
+
this.optimize();
|
541
|
+
}, 30000); // Cleanup every 30 seconds
|
542
|
+
}
|
543
|
+
|
544
|
+
/**
|
545
|
+
* Stop cleanup timer
|
546
|
+
*/
|
547
|
+
private stopCleanupTimer(): void {
|
548
|
+
if (this.cleanupTimer) {
|
549
|
+
clearInterval(this.cleanupTimer);
|
550
|
+
this.cleanupTimer = null;
|
551
|
+
}
|
552
|
+
}
|
553
|
+
|
554
|
+
/**
|
555
|
+
* Load cache from persistent storage
|
556
|
+
*/
|
557
|
+
private loadFromStorage(): void {
|
558
|
+
if (!this.config.persistentStorage) return;
|
559
|
+
|
560
|
+
try {
|
561
|
+
const stored = localStorage.getItem(this.config.storageKey);
|
562
|
+
if (stored) {
|
563
|
+
const data = JSON.parse(stored);
|
564
|
+
data.forEach((entry: CacheEntry<T>) => {
|
565
|
+
this.cache.set(entry.key, entry);
|
566
|
+
this.accessOrder.push(entry.key);
|
567
|
+
});
|
568
|
+
}
|
569
|
+
} catch (error) {
|
570
|
+
console.warn('Failed to load cache from storage:', error);
|
571
|
+
}
|
572
|
+
}
|
573
|
+
|
574
|
+
/**
|
575
|
+
* Save cache to persistent storage
|
576
|
+
*/
|
577
|
+
private saveToStorage(): void {
|
578
|
+
if (!this.config.persistentStorage) return;
|
579
|
+
|
580
|
+
try {
|
581
|
+
const data = Array.from(this.cache.values());
|
582
|
+
localStorage.setItem(this.config.storageKey, JSON.stringify(data));
|
583
|
+
} catch (error) {
|
584
|
+
console.warn('Failed to save cache to storage:', error);
|
585
|
+
}
|
586
|
+
}
|
587
|
+
|
588
|
+
/**
|
589
|
+
* Create initial metrics
|
590
|
+
*/
|
591
|
+
private createInitialMetrics(): CacheMetrics {
|
592
|
+
return {
|
593
|
+
totalEntries: 0,
|
594
|
+
totalSize: 0,
|
595
|
+
hitRate: 0,
|
596
|
+
missRate: 0,
|
597
|
+
evictions: 0,
|
598
|
+
compressionRatio: 0
|
599
|
+
};
|
600
|
+
}
|
601
|
+
}
|