wu-framework 1.1.14 → 1.1.16

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.
Files changed (90) hide show
  1. package/LICENSE +39 -39
  2. package/README.md +408 -408
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15151 -15151
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js.map +1 -1
  7. package/dist/wu-framework.umd.js.map +1 -1
  8. package/integrations/astro/README.md +127 -127
  9. package/integrations/astro/WuApp.astro +63 -63
  10. package/integrations/astro/WuShell.astro +39 -39
  11. package/integrations/astro/index.js +68 -68
  12. package/integrations/astro/package.json +38 -38
  13. package/integrations/astro/types.d.ts +53 -53
  14. package/package.json +161 -161
  15. package/src/adapters/angular/ai.js +30 -30
  16. package/src/adapters/angular/index.d.ts +154 -154
  17. package/src/adapters/angular/index.js +932 -932
  18. package/src/adapters/angular.d.ts +3 -3
  19. package/src/adapters/angular.js +3 -3
  20. package/src/adapters/index.js +168 -168
  21. package/src/adapters/lit/ai.js +20 -20
  22. package/src/adapters/lit/index.d.ts +120 -120
  23. package/src/adapters/lit/index.js +721 -721
  24. package/src/adapters/lit.d.ts +3 -3
  25. package/src/adapters/lit.js +3 -3
  26. package/src/adapters/preact/ai.js +33 -33
  27. package/src/adapters/preact/index.d.ts +108 -108
  28. package/src/adapters/preact/index.js +661 -661
  29. package/src/adapters/preact.d.ts +3 -3
  30. package/src/adapters/preact.js +3 -3
  31. package/src/adapters/react/index.js +48 -54
  32. package/src/adapters/react.d.ts +3 -3
  33. package/src/adapters/react.js +3 -3
  34. package/src/adapters/shared.js +64 -64
  35. package/src/adapters/solid/ai.js +32 -32
  36. package/src/adapters/solid/index.d.ts +101 -101
  37. package/src/adapters/solid/index.js +586 -586
  38. package/src/adapters/solid.d.ts +3 -3
  39. package/src/adapters/solid.js +3 -3
  40. package/src/adapters/svelte/ai.js +31 -31
  41. package/src/adapters/svelte/index.d.ts +166 -166
  42. package/src/adapters/svelte/index.js +798 -798
  43. package/src/adapters/svelte.d.ts +3 -3
  44. package/src/adapters/svelte.js +3 -3
  45. package/src/adapters/vanilla/ai.js +30 -30
  46. package/src/adapters/vanilla/index.d.ts +179 -179
  47. package/src/adapters/vanilla/index.js +785 -785
  48. package/src/adapters/vanilla.d.ts +3 -3
  49. package/src/adapters/vanilla.js +3 -3
  50. package/src/adapters/vue/ai.js +52 -52
  51. package/src/adapters/vue/index.d.ts +299 -299
  52. package/src/adapters/vue/index.js +610 -610
  53. package/src/adapters/vue.d.ts +3 -3
  54. package/src/adapters/vue.js +3 -3
  55. package/src/ai/wu-ai-actions.js +261 -261
  56. package/src/ai/wu-ai-agent.js +546 -546
  57. package/src/ai/wu-ai-browser-primitives.js +354 -354
  58. package/src/ai/wu-ai-browser.js +380 -380
  59. package/src/ai/wu-ai-context.js +332 -332
  60. package/src/ai/wu-ai-conversation.js +613 -613
  61. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  62. package/src/ai/wu-ai-permissions.js +381 -381
  63. package/src/ai/wu-ai-provider.js +700 -700
  64. package/src/ai/wu-ai-schema.js +225 -225
  65. package/src/ai/wu-ai-triggers.js +396 -396
  66. package/src/ai/wu-ai.js +804 -804
  67. package/src/core/wu-app.js +236 -236
  68. package/src/core/wu-cache.js +477 -477
  69. package/src/core/wu-core.js +1398 -1398
  70. package/src/core/wu-error-boundary.js +382 -382
  71. package/src/core/wu-event-bus.js +348 -348
  72. package/src/core/wu-hooks.js +350 -350
  73. package/src/core/wu-html-parser.js +190 -190
  74. package/src/core/wu-iframe-sandbox.js +328 -328
  75. package/src/core/wu-loader.js +272 -272
  76. package/src/core/wu-logger.js +134 -134
  77. package/src/core/wu-manifest.js +509 -509
  78. package/src/core/wu-mcp-bridge.js +432 -432
  79. package/src/core/wu-overrides.js +510 -510
  80. package/src/core/wu-performance.js +228 -228
  81. package/src/core/wu-plugin.js +348 -348
  82. package/src/core/wu-prefetch.js +414 -414
  83. package/src/core/wu-proxy-sandbox.js +476 -476
  84. package/src/core/wu-sandbox.js +779 -779
  85. package/src/core/wu-script-executor.js +113 -113
  86. package/src/core/wu-snapshot-sandbox.js +227 -227
  87. package/src/core/wu-strategies.js +256 -256
  88. package/src/core/wu-style-bridge.js +477 -477
  89. package/src/index.js +224 -224
  90. package/src/utils/dependency-resolver.js +327 -327
@@ -1,477 +1,477 @@
1
- /**
2
- * 💾 WU-CACHE: SECURE INTERNAL CACHING
3
- *
4
- * Sistema de caché INTERNO con rate limiting
5
- * - Rate limiting para prevenir abuso
6
- * - Cache persistente y en memoria
7
- * - TTL y LRU eviction
8
- *
9
- * ⚠️ USO INTERNO: No exponer en API pública
10
- */
11
-
12
- import { logger } from './wu-logger.js';
13
-
14
- export class WuCache {
15
- constructor(options = {}) {
16
- this.config = {
17
- maxSize: options.maxSize || 50, // MB
18
- maxItems: options.maxItems || 100,
19
- defaultTTL: options.defaultTTL || 3600000, // 1 hour
20
- persistent: options.persistent !== false,
21
- storage: options.storage || 'memory'
22
- };
23
-
24
- // 🔐 Rate limiting configuration
25
- this.rateLimiting = {
26
- enabled: options.rateLimiting !== false,
27
- maxOpsPerSecond: options.maxOpsPerSecond || 100,
28
- windowMs: 1000, // 1 second window
29
- cooldownMs: options.cooldownMs || 5000, // 5 second cooldown after limit
30
- operations: [],
31
- inCooldown: false,
32
- cooldownUntil: 0
33
- };
34
-
35
- // Memory cache
36
- this.memoryCache = new Map();
37
-
38
- // LRU tracking
39
- this.accessOrder = new Map();
40
-
41
- // Statistics
42
- this.stats = {
43
- hits: 0,
44
- misses: 0,
45
- sets: 0,
46
- evictions: 0,
47
- size: 0,
48
- rateLimited: 0 // 🔐 Contador de operaciones rechazadas
49
- };
50
- }
51
-
52
- /**
53
- * 🔐 CHECK RATE LIMIT: Verificar si la operación está permitida
54
- * @returns {boolean} true si la operación está permitida
55
- */
56
- _checkRateLimit() {
57
- if (!this.rateLimiting.enabled) return true;
58
-
59
- const now = Date.now();
60
-
61
- // Verificar si estamos en cooldown
62
- if (this.rateLimiting.inCooldown) {
63
- if (now < this.rateLimiting.cooldownUntil) {
64
- this.stats.rateLimited++;
65
- return false;
66
- }
67
- // Cooldown terminado
68
- this.rateLimiting.inCooldown = false;
69
- this.rateLimiting.operations = [];
70
- }
71
-
72
- // Limpiar operaciones antiguas (fuera de la ventana)
73
- const windowStart = now - this.rateLimiting.windowMs;
74
- this.rateLimiting.operations = this.rateLimiting.operations.filter(
75
- ts => ts > windowStart
76
- );
77
-
78
- // Verificar límite
79
- if (this.rateLimiting.operations.length >= this.rateLimiting.maxOpsPerSecond) {
80
- // Activar cooldown
81
- this.rateLimiting.inCooldown = true;
82
- this.rateLimiting.cooldownUntil = now + this.rateLimiting.cooldownMs;
83
- this.stats.rateLimited++;
84
- logger.warn(`[WuCache] 🚫 Rate limit exceeded. Cooldown for ${this.rateLimiting.cooldownMs}ms`);
85
- return false;
86
- }
87
-
88
- // Registrar operación
89
- this.rateLimiting.operations.push(now);
90
- return true;
91
- }
92
-
93
- /**
94
- * 🔐 GET RATE LIMIT STATUS
95
- */
96
- getRateLimitStatus() {
97
- const now = Date.now();
98
- return {
99
- enabled: this.rateLimiting.enabled,
100
- inCooldown: this.rateLimiting.inCooldown,
101
- cooldownRemaining: this.rateLimiting.inCooldown
102
- ? Math.max(0, this.rateLimiting.cooldownUntil - now)
103
- : 0,
104
- currentOps: this.rateLimiting.operations.length,
105
- maxOps: this.rateLimiting.maxOpsPerSecond,
106
- rateLimited: this.stats.rateLimited
107
- };
108
- }
109
-
110
- /**
111
- * 🔍 GET: Obtener valor del cache
112
- * @param {string} key - Clave
113
- * @returns {*} Valor cacheado o null
114
- */
115
- get(key) {
116
- // 🔐 Check rate limit
117
- if (!this._checkRateLimit()) {
118
- return null; // Silently fail on rate limit
119
- }
120
-
121
- // 1. Buscar en memoria
122
- if (this.memoryCache.has(key)) {
123
- const entry = this.memoryCache.get(key);
124
-
125
- // Verificar TTL
126
- if (this.isExpired(entry)) {
127
- this.delete(key);
128
- this.stats.misses++;
129
- return null;
130
- }
131
-
132
- // Actualizar acceso (LRU)
133
- this.accessOrder.set(key, Date.now());
134
- this.stats.hits++;
135
-
136
- return entry.value;
137
- }
138
-
139
- // 2. Buscar en storage persistente
140
- if (this.config.persistent) {
141
- const stored = this.getFromStorage(key);
142
- if (stored) {
143
- // Restaurar a memoria
144
- this.memoryCache.set(key, stored);
145
- this.accessOrder.set(key, Date.now());
146
- this.stats.hits++;
147
- return stored.value;
148
- }
149
- }
150
-
151
- this.stats.misses++;
152
- return null;
153
- }
154
-
155
- /**
156
- * 💾 SET: Guardar valor en cache
157
- * @param {string} key - Clave
158
- * @param {*} value - Valor
159
- * @param {number} ttl - Time to live (ms)
160
- * @returns {boolean}
161
- */
162
- set(key, value, ttl) {
163
- // 🔐 Check rate limit
164
- if (!this._checkRateLimit()) {
165
- return false; // Reject on rate limit
166
- }
167
-
168
- try {
169
- const entry = {
170
- key,
171
- value,
172
- timestamp: Date.now(),
173
- ttl: ttl || this.config.defaultTTL,
174
- size: this.estimateSize(value)
175
- };
176
-
177
- // Verificar si necesitamos hacer espacio
178
- const hasSpace = this.ensureSpace(entry.size);
179
- if (hasSpace === false) {
180
- logger.warn(`[WuCache] ⚠️ Cannot cache item: ${key} (too large)`);
181
- return false;
182
- }
183
-
184
- // Guardar en memoria
185
- this.memoryCache.set(key, entry);
186
- this.accessOrder.set(key, Date.now());
187
-
188
- // Guardar en storage persistente
189
- if (this.config.persistent) {
190
- this.saveToStorage(key, entry);
191
- }
192
-
193
- this.stats.sets++;
194
- this.stats.size += entry.size;
195
-
196
- return true;
197
- } catch (error) {
198
- logger.warn('[WuCache] ⚠️ Failed to set cache:', error);
199
- return false;
200
- }
201
- }
202
-
203
- /**
204
- * 🗑️ DELETE: Eliminar del cache
205
- * @param {string} key - Clave
206
- */
207
- delete(key) {
208
- const entry = this.memoryCache.get(key);
209
- if (entry) {
210
- this.stats.size -= entry.size;
211
- }
212
-
213
- this.memoryCache.delete(key);
214
- this.accessOrder.delete(key);
215
-
216
- if (this.config.persistent) {
217
- this.deleteFromStorage(key);
218
- }
219
- }
220
-
221
- /**
222
- * 🧹 CLEAR: Limpiar todo el cache
223
- */
224
- clear() {
225
- this.memoryCache.clear();
226
- this.accessOrder.clear();
227
- this.stats.size = 0;
228
-
229
- if (this.config.persistent) {
230
- this.clearStorage();
231
- }
232
-
233
- logger.debug('[WuCache] 🧹 Cache cleared');
234
- }
235
-
236
- /**
237
- * ⏰ IS EXPIRED: Verificar si entrada expiró
238
- * @param {Object} entry - Entrada del cache
239
- * @returns {boolean}
240
- */
241
- isExpired(entry) {
242
- if (!entry.ttl) return false;
243
- return Date.now() - entry.timestamp > entry.ttl;
244
- }
245
-
246
- /**
247
- * 📏 ESTIMATE SIZE: Estimar tamaño de un valor
248
- * @param {*} value - Valor
249
- * @returns {number} Tamaño en bytes
250
- */
251
- estimateSize(value) {
252
- if (typeof value === 'string') {
253
- return value.length * 2; // UTF-16
254
- }
255
-
256
- if (typeof value === 'object') {
257
- try {
258
- return JSON.stringify(value).length * 2;
259
- } catch {
260
- return 1000; // Estimación por defecto
261
- }
262
- }
263
-
264
- return 100; // Tamaño por defecto para primitivos
265
- }
266
-
267
- /**
268
- * 🎯 ENSURE SPACE: Asegurar espacio en cache (LRU eviction)
269
- * @param {number} neededSize - Tamaño necesario
270
- */
271
- ensureSpace(neededSize) {
272
- const maxSizeBytes = this.config.maxSize * 1024 * 1024;
273
-
274
- // 🛡️ FIX: Validar que el item no sea más grande que el máximo permitido
275
- if (neededSize > maxSizeBytes) {
276
- logger.warn(`[WuCache] ⚠️ Item size (${neededSize}) exceeds max cache size (${maxSizeBytes}). Skipping.`);
277
- return false;
278
- }
279
-
280
- // 🛡️ FIX: Límite de iteraciones para evitar loop infinito
281
- const maxIterations = this.config.maxItems + 10;
282
- let iterations = 0;
283
-
284
- // Verificar si necesitamos limpiar
285
- while ((this.stats.size + neededSize > maxSizeBytes ||
286
- this.memoryCache.size >= this.config.maxItems) &&
287
- iterations < maxIterations) {
288
-
289
- iterations++;
290
-
291
- // 🛡️ FIX: Si el cache está vacío pero aún no hay espacio, salir
292
- if (this.memoryCache.size === 0) {
293
- logger.warn('[WuCache] ⚠️ Cache empty but still no space. Breaking loop.');
294
- break;
295
- }
296
-
297
- // Encontrar entrada menos recientemente usada (LRU)
298
- let oldestKey = null;
299
- let oldestTime = Infinity;
300
-
301
- for (const [key, time] of this.accessOrder) {
302
- if (time < oldestTime) {
303
- oldestTime = time;
304
- oldestKey = key;
305
- }
306
- }
307
-
308
- if (oldestKey) {
309
- logger.debug(`[WuCache] 🗑️ Evicting LRU entry: ${oldestKey}`);
310
- this.delete(oldestKey);
311
- this.stats.evictions++;
312
- } else {
313
- break;
314
- }
315
- }
316
-
317
- // 🛡️ FIX: Log si alcanzamos el límite de iteraciones
318
- if (iterations >= maxIterations) {
319
- console.error(`[WuCache] 🚨 Max eviction iterations reached (${maxIterations}). Possible infinite loop prevented.`);
320
- }
321
-
322
- return true;
323
- }
324
-
325
- /**
326
- * 💽 GET FROM STORAGE: Obtener del storage persistente
327
- * @param {string} key - Clave
328
- * @returns {Object|null}
329
- */
330
- getFromStorage(key) {
331
- try {
332
- const storage = this.getStorage();
333
- const stored = storage.getItem(`wu_cache_${key}`);
334
-
335
- if (stored) {
336
- return JSON.parse(stored);
337
- }
338
- } catch (error) {
339
- logger.warn('[WuCache] ⚠️ Failed to get from storage:', error);
340
- }
341
- return null;
342
- }
343
-
344
- /**
345
- * 💾 SAVE TO STORAGE: Guardar en storage persistente
346
- * @param {string} key - Clave
347
- * @param {Object} entry - Entrada
348
- */
349
- saveToStorage(key, entry) {
350
- const storage = this.getStorage();
351
- try {
352
- storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
353
- } catch (error) {
354
- // Storage lleno, limpiar entradas antiguas
355
- logger.warn('[WuCache] ⚠️ Storage full, cleaning old entries');
356
- this.cleanOldStorageEntries();
357
-
358
- try {
359
- storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
360
- } catch {
361
- logger.warn('[WuCache] ⚠️ Failed to save to storage after cleanup');
362
- }
363
- }
364
- }
365
-
366
- /**
367
- * 🗑️ DELETE FROM STORAGE: Eliminar del storage
368
- * @param {string} key - Clave
369
- */
370
- deleteFromStorage(key) {
371
- try {
372
- const storage = this.getStorage();
373
- storage.removeItem(`wu_cache_${key}`);
374
- } catch (error) {
375
- logger.warn('[WuCache] ⚠️ Failed to delete from storage:', error);
376
- }
377
- }
378
-
379
- /**
380
- * 🧹 CLEAR STORAGE: Limpiar storage
381
- */
382
- clearStorage() {
383
- try {
384
- const storage = this.getStorage();
385
- const keys = Object.keys(storage);
386
-
387
- keys.forEach(key => {
388
- if (key.startsWith('wu_cache_')) {
389
- storage.removeItem(key);
390
- }
391
- });
392
- } catch (error) {
393
- logger.warn('[WuCache] ⚠️ Failed to clear storage:', error);
394
- }
395
- }
396
-
397
- /**
398
- * 🧹 CLEAN OLD STORAGE ENTRIES: Limpiar entradas antiguas del storage
399
- */
400
- cleanOldStorageEntries() {
401
- try {
402
- const storage = this.getStorage();
403
- const keys = Object.keys(storage);
404
- const entries = [];
405
-
406
- // Recopilar todas las entradas con timestamp
407
- keys.forEach(key => {
408
- if (key.startsWith('wu_cache_')) {
409
- try {
410
- const entry = JSON.parse(storage.getItem(key));
411
- entries.push({ key, timestamp: entry.timestamp });
412
- } catch {}
413
- }
414
- });
415
-
416
- // Ordenar por timestamp (más antiguas primero)
417
- entries.sort((a, b) => a.timestamp - b.timestamp);
418
-
419
- // Eliminar 25% de entradas más antiguas
420
- const toRemove = Math.ceil(entries.length * 0.25);
421
- for (let i = 0; i < toRemove; i++) {
422
- storage.removeItem(entries[i].key);
423
- }
424
-
425
- logger.debug(`[WuCache] 🧹 Cleaned ${toRemove} old storage entries`);
426
- } catch (error) {
427
- logger.warn('[WuCache] ⚠️ Failed to clean old storage entries:', error);
428
- }
429
- }
430
-
431
- /**
432
- * 💽 GET STORAGE: Obtener instancia de storage
433
- * @returns {Storage}
434
- */
435
- getStorage() {
436
- if (this.config.storage === 'localStorage') {
437
- return window.localStorage;
438
- } else if (this.config.storage === 'sessionStorage') {
439
- return window.sessionStorage;
440
- }
441
- // Fallback a memoria
442
- return {
443
- getItem: () => null,
444
- setItem: () => {},
445
- removeItem: () => {},
446
- clear: () => {}
447
- };
448
- }
449
-
450
- /**
451
- * 📊 GET STATS: Obtener estadísticas del cache
452
- * @returns {Object}
453
- */
454
- getStats() {
455
- const hitRate = this.stats.hits + this.stats.misses > 0
456
- ? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2)
457
- : 0;
458
-
459
- return {
460
- ...this.stats,
461
- hitRate: `${hitRate}%`,
462
- items: this.memoryCache.size,
463
- sizeMB: (this.stats.size / 1024 / 1024).toFixed(2)
464
- };
465
- }
466
-
467
- /**
468
- * ⚙️ CONFIGURE: Actualizar configuración
469
- * @param {Object} config - Nueva configuración
470
- */
471
- configure(config) {
472
- this.config = {
473
- ...this.config,
474
- ...config
475
- };
476
- }
477
- }
1
+ /**
2
+ * 💾 WU-CACHE: SECURE INTERNAL CACHING
3
+ *
4
+ * Sistema de caché INTERNO con rate limiting
5
+ * - Rate limiting para prevenir abuso
6
+ * - Cache persistente y en memoria
7
+ * - TTL y LRU eviction
8
+ *
9
+ * ⚠️ USO INTERNO: No exponer en API pública
10
+ */
11
+
12
+ import { logger } from './wu-logger.js';
13
+
14
+ export class WuCache {
15
+ constructor(options = {}) {
16
+ this.config = {
17
+ maxSize: options.maxSize || 50, // MB
18
+ maxItems: options.maxItems || 100,
19
+ defaultTTL: options.defaultTTL || 3600000, // 1 hour
20
+ persistent: options.persistent !== false,
21
+ storage: options.storage || 'memory'
22
+ };
23
+
24
+ // 🔐 Rate limiting configuration
25
+ this.rateLimiting = {
26
+ enabled: options.rateLimiting !== false,
27
+ maxOpsPerSecond: options.maxOpsPerSecond || 100,
28
+ windowMs: 1000, // 1 second window
29
+ cooldownMs: options.cooldownMs || 5000, // 5 second cooldown after limit
30
+ operations: [],
31
+ inCooldown: false,
32
+ cooldownUntil: 0
33
+ };
34
+
35
+ // Memory cache
36
+ this.memoryCache = new Map();
37
+
38
+ // LRU tracking
39
+ this.accessOrder = new Map();
40
+
41
+ // Statistics
42
+ this.stats = {
43
+ hits: 0,
44
+ misses: 0,
45
+ sets: 0,
46
+ evictions: 0,
47
+ size: 0,
48
+ rateLimited: 0 // 🔐 Contador de operaciones rechazadas
49
+ };
50
+ }
51
+
52
+ /**
53
+ * 🔐 CHECK RATE LIMIT: Verificar si la operación está permitida
54
+ * @returns {boolean} true si la operación está permitida
55
+ */
56
+ _checkRateLimit() {
57
+ if (!this.rateLimiting.enabled) return true;
58
+
59
+ const now = Date.now();
60
+
61
+ // Verificar si estamos en cooldown
62
+ if (this.rateLimiting.inCooldown) {
63
+ if (now < this.rateLimiting.cooldownUntil) {
64
+ this.stats.rateLimited++;
65
+ return false;
66
+ }
67
+ // Cooldown terminado
68
+ this.rateLimiting.inCooldown = false;
69
+ this.rateLimiting.operations = [];
70
+ }
71
+
72
+ // Limpiar operaciones antiguas (fuera de la ventana)
73
+ const windowStart = now - this.rateLimiting.windowMs;
74
+ this.rateLimiting.operations = this.rateLimiting.operations.filter(
75
+ ts => ts > windowStart
76
+ );
77
+
78
+ // Verificar límite
79
+ if (this.rateLimiting.operations.length >= this.rateLimiting.maxOpsPerSecond) {
80
+ // Activar cooldown
81
+ this.rateLimiting.inCooldown = true;
82
+ this.rateLimiting.cooldownUntil = now + this.rateLimiting.cooldownMs;
83
+ this.stats.rateLimited++;
84
+ logger.warn(`[WuCache] 🚫 Rate limit exceeded. Cooldown for ${this.rateLimiting.cooldownMs}ms`);
85
+ return false;
86
+ }
87
+
88
+ // Registrar operación
89
+ this.rateLimiting.operations.push(now);
90
+ return true;
91
+ }
92
+
93
+ /**
94
+ * 🔐 GET RATE LIMIT STATUS
95
+ */
96
+ getRateLimitStatus() {
97
+ const now = Date.now();
98
+ return {
99
+ enabled: this.rateLimiting.enabled,
100
+ inCooldown: this.rateLimiting.inCooldown,
101
+ cooldownRemaining: this.rateLimiting.inCooldown
102
+ ? Math.max(0, this.rateLimiting.cooldownUntil - now)
103
+ : 0,
104
+ currentOps: this.rateLimiting.operations.length,
105
+ maxOps: this.rateLimiting.maxOpsPerSecond,
106
+ rateLimited: this.stats.rateLimited
107
+ };
108
+ }
109
+
110
+ /**
111
+ * 🔍 GET: Obtener valor del cache
112
+ * @param {string} key - Clave
113
+ * @returns {*} Valor cacheado o null
114
+ */
115
+ get(key) {
116
+ // 🔐 Check rate limit
117
+ if (!this._checkRateLimit()) {
118
+ return null; // Silently fail on rate limit
119
+ }
120
+
121
+ // 1. Buscar en memoria
122
+ if (this.memoryCache.has(key)) {
123
+ const entry = this.memoryCache.get(key);
124
+
125
+ // Verificar TTL
126
+ if (this.isExpired(entry)) {
127
+ this.delete(key);
128
+ this.stats.misses++;
129
+ return null;
130
+ }
131
+
132
+ // Actualizar acceso (LRU)
133
+ this.accessOrder.set(key, Date.now());
134
+ this.stats.hits++;
135
+
136
+ return entry.value;
137
+ }
138
+
139
+ // 2. Buscar en storage persistente
140
+ if (this.config.persistent) {
141
+ const stored = this.getFromStorage(key);
142
+ if (stored) {
143
+ // Restaurar a memoria
144
+ this.memoryCache.set(key, stored);
145
+ this.accessOrder.set(key, Date.now());
146
+ this.stats.hits++;
147
+ return stored.value;
148
+ }
149
+ }
150
+
151
+ this.stats.misses++;
152
+ return null;
153
+ }
154
+
155
+ /**
156
+ * 💾 SET: Guardar valor en cache
157
+ * @param {string} key - Clave
158
+ * @param {*} value - Valor
159
+ * @param {number} ttl - Time to live (ms)
160
+ * @returns {boolean}
161
+ */
162
+ set(key, value, ttl) {
163
+ // 🔐 Check rate limit
164
+ if (!this._checkRateLimit()) {
165
+ return false; // Reject on rate limit
166
+ }
167
+
168
+ try {
169
+ const entry = {
170
+ key,
171
+ value,
172
+ timestamp: Date.now(),
173
+ ttl: ttl || this.config.defaultTTL,
174
+ size: this.estimateSize(value)
175
+ };
176
+
177
+ // Verificar si necesitamos hacer espacio
178
+ const hasSpace = this.ensureSpace(entry.size);
179
+ if (hasSpace === false) {
180
+ logger.warn(`[WuCache] ⚠️ Cannot cache item: ${key} (too large)`);
181
+ return false;
182
+ }
183
+
184
+ // Guardar en memoria
185
+ this.memoryCache.set(key, entry);
186
+ this.accessOrder.set(key, Date.now());
187
+
188
+ // Guardar en storage persistente
189
+ if (this.config.persistent) {
190
+ this.saveToStorage(key, entry);
191
+ }
192
+
193
+ this.stats.sets++;
194
+ this.stats.size += entry.size;
195
+
196
+ return true;
197
+ } catch (error) {
198
+ logger.warn('[WuCache] ⚠️ Failed to set cache:', error);
199
+ return false;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * 🗑️ DELETE: Eliminar del cache
205
+ * @param {string} key - Clave
206
+ */
207
+ delete(key) {
208
+ const entry = this.memoryCache.get(key);
209
+ if (entry) {
210
+ this.stats.size -= entry.size;
211
+ }
212
+
213
+ this.memoryCache.delete(key);
214
+ this.accessOrder.delete(key);
215
+
216
+ if (this.config.persistent) {
217
+ this.deleteFromStorage(key);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * 🧹 CLEAR: Limpiar todo el cache
223
+ */
224
+ clear() {
225
+ this.memoryCache.clear();
226
+ this.accessOrder.clear();
227
+ this.stats.size = 0;
228
+
229
+ if (this.config.persistent) {
230
+ this.clearStorage();
231
+ }
232
+
233
+ logger.debug('[WuCache] 🧹 Cache cleared');
234
+ }
235
+
236
+ /**
237
+ * ⏰ IS EXPIRED: Verificar si entrada expiró
238
+ * @param {Object} entry - Entrada del cache
239
+ * @returns {boolean}
240
+ */
241
+ isExpired(entry) {
242
+ if (!entry.ttl) return false;
243
+ return Date.now() - entry.timestamp > entry.ttl;
244
+ }
245
+
246
+ /**
247
+ * 📏 ESTIMATE SIZE: Estimar tamaño de un valor
248
+ * @param {*} value - Valor
249
+ * @returns {number} Tamaño en bytes
250
+ */
251
+ estimateSize(value) {
252
+ if (typeof value === 'string') {
253
+ return value.length * 2; // UTF-16
254
+ }
255
+
256
+ if (typeof value === 'object') {
257
+ try {
258
+ return JSON.stringify(value).length * 2;
259
+ } catch {
260
+ return 1000; // Estimación por defecto
261
+ }
262
+ }
263
+
264
+ return 100; // Tamaño por defecto para primitivos
265
+ }
266
+
267
+ /**
268
+ * 🎯 ENSURE SPACE: Asegurar espacio en cache (LRU eviction)
269
+ * @param {number} neededSize - Tamaño necesario
270
+ */
271
+ ensureSpace(neededSize) {
272
+ const maxSizeBytes = this.config.maxSize * 1024 * 1024;
273
+
274
+ // 🛡️ FIX: Validar que el item no sea más grande que el máximo permitido
275
+ if (neededSize > maxSizeBytes) {
276
+ logger.warn(`[WuCache] ⚠️ Item size (${neededSize}) exceeds max cache size (${maxSizeBytes}). Skipping.`);
277
+ return false;
278
+ }
279
+
280
+ // 🛡️ FIX: Límite de iteraciones para evitar loop infinito
281
+ const maxIterations = this.config.maxItems + 10;
282
+ let iterations = 0;
283
+
284
+ // Verificar si necesitamos limpiar
285
+ while ((this.stats.size + neededSize > maxSizeBytes ||
286
+ this.memoryCache.size >= this.config.maxItems) &&
287
+ iterations < maxIterations) {
288
+
289
+ iterations++;
290
+
291
+ // 🛡️ FIX: Si el cache está vacío pero aún no hay espacio, salir
292
+ if (this.memoryCache.size === 0) {
293
+ logger.warn('[WuCache] ⚠️ Cache empty but still no space. Breaking loop.');
294
+ break;
295
+ }
296
+
297
+ // Encontrar entrada menos recientemente usada (LRU)
298
+ let oldestKey = null;
299
+ let oldestTime = Infinity;
300
+
301
+ for (const [key, time] of this.accessOrder) {
302
+ if (time < oldestTime) {
303
+ oldestTime = time;
304
+ oldestKey = key;
305
+ }
306
+ }
307
+
308
+ if (oldestKey) {
309
+ logger.debug(`[WuCache] 🗑️ Evicting LRU entry: ${oldestKey}`);
310
+ this.delete(oldestKey);
311
+ this.stats.evictions++;
312
+ } else {
313
+ break;
314
+ }
315
+ }
316
+
317
+ // 🛡️ FIX: Log si alcanzamos el límite de iteraciones
318
+ if (iterations >= maxIterations) {
319
+ console.error(`[WuCache] 🚨 Max eviction iterations reached (${maxIterations}). Possible infinite loop prevented.`);
320
+ }
321
+
322
+ return true;
323
+ }
324
+
325
+ /**
326
+ * 💽 GET FROM STORAGE: Obtener del storage persistente
327
+ * @param {string} key - Clave
328
+ * @returns {Object|null}
329
+ */
330
+ getFromStorage(key) {
331
+ try {
332
+ const storage = this.getStorage();
333
+ const stored = storage.getItem(`wu_cache_${key}`);
334
+
335
+ if (stored) {
336
+ return JSON.parse(stored);
337
+ }
338
+ } catch (error) {
339
+ logger.warn('[WuCache] ⚠️ Failed to get from storage:', error);
340
+ }
341
+ return null;
342
+ }
343
+
344
+ /**
345
+ * 💾 SAVE TO STORAGE: Guardar en storage persistente
346
+ * @param {string} key - Clave
347
+ * @param {Object} entry - Entrada
348
+ */
349
+ saveToStorage(key, entry) {
350
+ const storage = this.getStorage();
351
+ try {
352
+ storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
353
+ } catch (error) {
354
+ // Storage lleno, limpiar entradas antiguas
355
+ logger.warn('[WuCache] ⚠️ Storage full, cleaning old entries');
356
+ this.cleanOldStorageEntries();
357
+
358
+ try {
359
+ storage.setItem(`wu_cache_${key}`, JSON.stringify(entry));
360
+ } catch {
361
+ logger.warn('[WuCache] ⚠️ Failed to save to storage after cleanup');
362
+ }
363
+ }
364
+ }
365
+
366
+ /**
367
+ * 🗑️ DELETE FROM STORAGE: Eliminar del storage
368
+ * @param {string} key - Clave
369
+ */
370
+ deleteFromStorage(key) {
371
+ try {
372
+ const storage = this.getStorage();
373
+ storage.removeItem(`wu_cache_${key}`);
374
+ } catch (error) {
375
+ logger.warn('[WuCache] ⚠️ Failed to delete from storage:', error);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * 🧹 CLEAR STORAGE: Limpiar storage
381
+ */
382
+ clearStorage() {
383
+ try {
384
+ const storage = this.getStorage();
385
+ const keys = Object.keys(storage);
386
+
387
+ keys.forEach(key => {
388
+ if (key.startsWith('wu_cache_')) {
389
+ storage.removeItem(key);
390
+ }
391
+ });
392
+ } catch (error) {
393
+ logger.warn('[WuCache] ⚠️ Failed to clear storage:', error);
394
+ }
395
+ }
396
+
397
+ /**
398
+ * 🧹 CLEAN OLD STORAGE ENTRIES: Limpiar entradas antiguas del storage
399
+ */
400
+ cleanOldStorageEntries() {
401
+ try {
402
+ const storage = this.getStorage();
403
+ const keys = Object.keys(storage);
404
+ const entries = [];
405
+
406
+ // Recopilar todas las entradas con timestamp
407
+ keys.forEach(key => {
408
+ if (key.startsWith('wu_cache_')) {
409
+ try {
410
+ const entry = JSON.parse(storage.getItem(key));
411
+ entries.push({ key, timestamp: entry.timestamp });
412
+ } catch {}
413
+ }
414
+ });
415
+
416
+ // Ordenar por timestamp (más antiguas primero)
417
+ entries.sort((a, b) => a.timestamp - b.timestamp);
418
+
419
+ // Eliminar 25% de entradas más antiguas
420
+ const toRemove = Math.ceil(entries.length * 0.25);
421
+ for (let i = 0; i < toRemove; i++) {
422
+ storage.removeItem(entries[i].key);
423
+ }
424
+
425
+ logger.debug(`[WuCache] 🧹 Cleaned ${toRemove} old storage entries`);
426
+ } catch (error) {
427
+ logger.warn('[WuCache] ⚠️ Failed to clean old storage entries:', error);
428
+ }
429
+ }
430
+
431
+ /**
432
+ * 💽 GET STORAGE: Obtener instancia de storage
433
+ * @returns {Storage}
434
+ */
435
+ getStorage() {
436
+ if (this.config.storage === 'localStorage') {
437
+ return window.localStorage;
438
+ } else if (this.config.storage === 'sessionStorage') {
439
+ return window.sessionStorage;
440
+ }
441
+ // Fallback a memoria
442
+ return {
443
+ getItem: () => null,
444
+ setItem: () => {},
445
+ removeItem: () => {},
446
+ clear: () => {}
447
+ };
448
+ }
449
+
450
+ /**
451
+ * 📊 GET STATS: Obtener estadísticas del cache
452
+ * @returns {Object}
453
+ */
454
+ getStats() {
455
+ const hitRate = this.stats.hits + this.stats.misses > 0
456
+ ? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2)
457
+ : 0;
458
+
459
+ return {
460
+ ...this.stats,
461
+ hitRate: `${hitRate}%`,
462
+ items: this.memoryCache.size,
463
+ sizeMB: (this.stats.size / 1024 / 1024).toFixed(2)
464
+ };
465
+ }
466
+
467
+ /**
468
+ * ⚙️ CONFIGURE: Actualizar configuración
469
+ * @param {Object} config - Nueva configuración
470
+ */
471
+ configure(config) {
472
+ this.config = {
473
+ ...this.config,
474
+ ...config
475
+ };
476
+ }
477
+ }