wu-framework 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.
@@ -0,0 +1,374 @@
1
+ /**
2
+ * 💾 WU-CACHE: INTERNAL FRAMEWORK CACHING
3
+ *
4
+ * Sistema de caché INTERNO para recursos del framework:
5
+ * - Manifests de microfrontends
6
+ * - Módulos cargados
7
+ * - Configuraciones de apps
8
+ *
9
+ * Features:
10
+ * - Cache persistente (localStorage/sessionStorage)
11
+ * - Cache en memoria (Map)
12
+ * - TTL (Time To Live) configurable
13
+ * - LRU (Least Recently Used) eviction
14
+ *
15
+ * ⚠️ USO INTERNO: No exponer en API pública
16
+ */
17
+
18
+ export class WuCache {
19
+ constructor(options = {}) {
20
+ this.config = {
21
+ maxSize: options.maxSize || 50, // MB
22
+ maxItems: options.maxItems || 100,
23
+ defaultTTL: options.defaultTTL || 3600000, // 1 hour
24
+ persistent: options.persistent !== false,
25
+ storage: options.storage || 'memory', // 'memory' | 'localStorage' | 'sessionStorage'
26
+ compression: options.compression || false
27
+ };
28
+
29
+ // Memory cache
30
+ this.memoryCache = new Map();
31
+
32
+ // LRU tracking
33
+ this.accessOrder = new Map(); // key -> timestamp
34
+
35
+ // Statistics
36
+ this.stats = {
37
+ hits: 0,
38
+ misses: 0,
39
+ sets: 0,
40
+ evictions: 0,
41
+ size: 0
42
+ };
43
+
44
+ console.log('[WuCache] 💾 Advanced cache system initialized');
45
+ }
46
+
47
+ /**
48
+ * 🔍 GET: Obtener valor del cache
49
+ * @param {string} key - Clave
50
+ * @returns {*} Valor cacheado o null
51
+ */
52
+ get(key) {
53
+ // 1. Buscar en memoria
54
+ if (this.memoryCache.has(key)) {
55
+ const entry = this.memoryCache.get(key);
56
+
57
+ // Verificar TTL
58
+ if (this.isExpired(entry)) {
59
+ this.delete(key);
60
+ this.stats.misses++;
61
+ return null;
62
+ }
63
+
64
+ // Actualizar acceso (LRU)
65
+ this.accessOrder.set(key, Date.now());
66
+ this.stats.hits++;
67
+
68
+ return entry.value;
69
+ }
70
+
71
+ // 2. Buscar en storage persistente
72
+ if (this.config.persistent) {
73
+ const stored = this.getFromStorage(key);
74
+ if (stored) {
75
+ // Restaurar a memoria
76
+ this.memoryCache.set(key, stored);
77
+ this.accessOrder.set(key, Date.now());
78
+ this.stats.hits++;
79
+ return stored.value;
80
+ }
81
+ }
82
+
83
+ this.stats.misses++;
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * 💾 SET: Guardar valor en cache
89
+ * @param {string} key - Clave
90
+ * @param {*} value - Valor
91
+ * @param {number} ttl - Time to live (ms)
92
+ * @returns {boolean}
93
+ */
94
+ set(key, value, ttl) {
95
+ try {
96
+ const entry = {
97
+ key,
98
+ value,
99
+ timestamp: Date.now(),
100
+ ttl: ttl || this.config.defaultTTL,
101
+ size: this.estimateSize(value)
102
+ };
103
+
104
+ // Verificar si necesitamos hacer espacio
105
+ this.ensureSpace(entry.size);
106
+
107
+ // Guardar en memoria
108
+ this.memoryCache.set(key, entry);
109
+ this.accessOrder.set(key, Date.now());
110
+
111
+ // Guardar en storage persistente
112
+ if (this.config.persistent) {
113
+ this.saveToStorage(key, entry);
114
+ }
115
+
116
+ this.stats.sets++;
117
+ this.stats.size += entry.size;
118
+
119
+ return true;
120
+ } catch (error) {
121
+ console.warn('[WuCache] ⚠️ Failed to set cache:', error);
122
+ return false;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 🗑️ DELETE: Eliminar del cache
128
+ * @param {string} key - Clave
129
+ */
130
+ delete(key) {
131
+ const entry = this.memoryCache.get(key);
132
+ if (entry) {
133
+ this.stats.size -= entry.size;
134
+ }
135
+
136
+ this.memoryCache.delete(key);
137
+ this.accessOrder.delete(key);
138
+
139
+ if (this.config.persistent) {
140
+ this.deleteFromStorage(key);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 🧹 CLEAR: Limpiar todo el cache
146
+ */
147
+ clear() {
148
+ this.memoryCache.clear();
149
+ this.accessOrder.clear();
150
+ this.stats.size = 0;
151
+
152
+ if (this.config.persistent) {
153
+ this.clearStorage();
154
+ }
155
+
156
+ console.log('[WuCache] 🧹 Cache cleared');
157
+ }
158
+
159
+ /**
160
+ * ⏰ IS EXPIRED: Verificar si entrada expiró
161
+ * @param {Object} entry - Entrada del cache
162
+ * @returns {boolean}
163
+ */
164
+ isExpired(entry) {
165
+ if (!entry.ttl) return false;
166
+ return Date.now() - entry.timestamp > entry.ttl;
167
+ }
168
+
169
+ /**
170
+ * 📏 ESTIMATE SIZE: Estimar tamaño de un valor
171
+ * @param {*} value - Valor
172
+ * @returns {number} Tamaño en bytes
173
+ */
174
+ estimateSize(value) {
175
+ if (typeof value === 'string') {
176
+ return value.length * 2; // UTF-16
177
+ }
178
+
179
+ if (typeof value === 'object') {
180
+ try {
181
+ return JSON.stringify(value).length * 2;
182
+ } catch {
183
+ return 1000; // Estimación por defecto
184
+ }
185
+ }
186
+
187
+ return 100; // Tamaño por defecto para primitivos
188
+ }
189
+
190
+ /**
191
+ * 🎯 ENSURE SPACE: Asegurar espacio en cache (LRU eviction)
192
+ * @param {number} neededSize - Tamaño necesario
193
+ */
194
+ ensureSpace(neededSize) {
195
+ const maxSizeBytes = this.config.maxSize * 1024 * 1024;
196
+
197
+ // Verificar si necesitamos limpiar
198
+ while (this.stats.size + neededSize > maxSizeBytes ||
199
+ this.memoryCache.size >= this.config.maxItems) {
200
+
201
+ // Encontrar entrada menos recientemente usada (LRU)
202
+ let oldestKey = null;
203
+ let oldestTime = Infinity;
204
+
205
+ for (const [key, time] of this.accessOrder) {
206
+ if (time < oldestTime) {
207
+ oldestTime = time;
208
+ oldestKey = key;
209
+ }
210
+ }
211
+
212
+ if (oldestKey) {
213
+ console.log(`[WuCache] 🗑️ Evicting LRU entry: ${oldestKey}`);
214
+ this.delete(oldestKey);
215
+ this.stats.evictions++;
216
+ } else {
217
+ break;
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * 💽 GET FROM STORAGE: Obtener del storage persistente
224
+ * @param {string} key - Clave
225
+ * @returns {Object|null}
226
+ */
227
+ getFromStorage(key) {
228
+ try {
229
+ const storage = this.getStorage();
230
+ const stored = storage.getItem(`wu_cache_${key}`);
231
+
232
+ if (stored) {
233
+ return JSON.parse(stored);
234
+ }
235
+ } catch (error) {
236
+ console.warn('[WuCache] ⚠️ Failed to get from storage:', error);
237
+ }
238
+ return null;
239
+ }
240
+
241
+ /**
242
+ * 💾 SAVE TO STORAGE: Guardar en storage persistente
243
+ * @param {string} key - Clave
244
+ * @param {Object} entry - Entrada
245
+ */
246
+ saveToStorage(key, entry) {
247
+ try {
248
+ const storage = this.getStorage();
249
+ storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
250
+ } catch (error) {
251
+ // Storage lleno, limpiar entradas antiguas
252
+ console.warn('[WuCache] ⚠️ Storage full, cleaning old entries');
253
+ this.cleanOldStorageEntries();
254
+
255
+ try {
256
+ storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
257
+ } catch {
258
+ console.warn('[WuCache] ⚠️ Failed to save to storage after cleanup');
259
+ }
260
+ }
261
+ }
262
+
263
+ /**
264
+ * 🗑️ DELETE FROM STORAGE: Eliminar del storage
265
+ * @param {string} key - Clave
266
+ */
267
+ deleteFromStorage(key) {
268
+ try {
269
+ const storage = this.getStorage();
270
+ storage.removeItem(`wu_cache_${key}`);
271
+ } catch (error) {
272
+ console.warn('[WuCache] ⚠️ Failed to delete from storage:', error);
273
+ }
274
+ }
275
+
276
+ /**
277
+ * 🧹 CLEAR STORAGE: Limpiar storage
278
+ */
279
+ clearStorage() {
280
+ try {
281
+ const storage = this.getStorage();
282
+ const keys = Object.keys(storage);
283
+
284
+ keys.forEach(key => {
285
+ if (key.startsWith('wu_cache_')) {
286
+ storage.removeItem(key);
287
+ }
288
+ });
289
+ } catch (error) {
290
+ console.warn('[WuCache] ⚠️ Failed to clear storage:', error);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * 🧹 CLEAN OLD STORAGE ENTRIES: Limpiar entradas antiguas del storage
296
+ */
297
+ cleanOldStorageEntries() {
298
+ try {
299
+ const storage = this.getStorage();
300
+ const keys = Object.keys(storage);
301
+ const entries = [];
302
+
303
+ // Recopilar todas las entradas con timestamp
304
+ keys.forEach(key => {
305
+ if (key.startsWith('wu_cache_')) {
306
+ try {
307
+ const entry = JSON.parse(storage.getItem(key));
308
+ entries.push({ key, timestamp: entry.timestamp });
309
+ } catch {}
310
+ }
311
+ });
312
+
313
+ // Ordenar por timestamp (más antiguas primero)
314
+ entries.sort((a, b) => a.timestamp - b.timestamp);
315
+
316
+ // Eliminar 25% de entradas más antiguas
317
+ const toRemove = Math.ceil(entries.length * 0.25);
318
+ for (let i = 0; i < toRemove; i++) {
319
+ storage.removeItem(entries[i].key);
320
+ }
321
+
322
+ console.log(`[WuCache] 🧹 Cleaned ${toRemove} old storage entries`);
323
+ } catch (error) {
324
+ console.warn('[WuCache] ⚠️ Failed to clean old storage entries:', error);
325
+ }
326
+ }
327
+
328
+ /**
329
+ * 💽 GET STORAGE: Obtener instancia de storage
330
+ * @returns {Storage}
331
+ */
332
+ getStorage() {
333
+ if (this.config.storage === 'localStorage') {
334
+ return window.localStorage;
335
+ } else if (this.config.storage === 'sessionStorage') {
336
+ return window.sessionStorage;
337
+ }
338
+ // Fallback a memoria
339
+ return {
340
+ getItem: () => null,
341
+ setItem: () => {},
342
+ removeItem: () => {},
343
+ clear: () => {}
344
+ };
345
+ }
346
+
347
+ /**
348
+ * 📊 GET STATS: Obtener estadísticas del cache
349
+ * @returns {Object}
350
+ */
351
+ getStats() {
352
+ const hitRate = this.stats.hits + this.stats.misses > 0
353
+ ? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2)
354
+ : 0;
355
+
356
+ return {
357
+ ...this.stats,
358
+ hitRate: `${hitRate}%`,
359
+ items: this.memoryCache.size,
360
+ sizeMB: (this.stats.size / 1024 / 1024).toFixed(2)
361
+ };
362
+ }
363
+
364
+ /**
365
+ * ⚙️ CONFIGURE: Actualizar configuración
366
+ * @param {Object} config - Nueva configuración
367
+ */
368
+ configure(config) {
369
+ this.config = {
370
+ ...this.config,
371
+ ...config
372
+ };
373
+ }
374
+ }