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.
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/package.json +84 -0
- package/src/api/wu-simple.js +316 -0
- package/src/core/wu-app.js +192 -0
- package/src/core/wu-cache.js +374 -0
- package/src/core/wu-core.js +1296 -0
- package/src/core/wu-error-boundary.js +380 -0
- package/src/core/wu-event-bus.js +257 -0
- package/src/core/wu-hooks.js +348 -0
- package/src/core/wu-html-parser.js +280 -0
- package/src/core/wu-loader.js +271 -0
- package/src/core/wu-logger.js +119 -0
- package/src/core/wu-manifest.js +366 -0
- package/src/core/wu-performance.js +226 -0
- package/src/core/wu-plugin.js +213 -0
- package/src/core/wu-proxy-sandbox.js +153 -0
- package/src/core/wu-registry.js +130 -0
- package/src/core/wu-sandbox-pool.js +390 -0
- package/src/core/wu-sandbox.js +720 -0
- package/src/core/wu-script-executor.js +216 -0
- package/src/core/wu-snapshot-sandbox.js +184 -0
- package/src/core/wu-store.js +297 -0
- package/src/core/wu-strategies.js +241 -0
- package/src/core/wu-style-bridge.js +357 -0
- package/src/index.js +690 -0
- package/src/utils/dependency-resolver.js +326 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🛡️ WU-ERROR-BOUNDARY: ADVANCED ERROR HANDLING
|
|
3
|
+
*
|
|
4
|
+
* Sistema de error boundaries con:
|
|
5
|
+
* - Chain of Responsibility pattern
|
|
6
|
+
* - Recovery strategies
|
|
7
|
+
* - Error classification
|
|
8
|
+
* - Fallback rendering
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class WuErrorBoundary {
|
|
12
|
+
constructor(core) {
|
|
13
|
+
this.core = core;
|
|
14
|
+
this.handlers = [];
|
|
15
|
+
this.errorLog = [];
|
|
16
|
+
this.maxErrorLog = 100;
|
|
17
|
+
|
|
18
|
+
this.config = {
|
|
19
|
+
maxRetries: 3,
|
|
20
|
+
retryDelay: 1000,
|
|
21
|
+
showErrorUI: true
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.registerDefaultHandlers();
|
|
25
|
+
|
|
26
|
+
console.log('[WuErrorBoundary] 🛡️ Error boundary initialized');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 📋 REGISTER DEFAULT HANDLERS: Chain of responsibility
|
|
31
|
+
*/
|
|
32
|
+
registerDefaultHandlers() {
|
|
33
|
+
// 1. Network Error Handler
|
|
34
|
+
this.register({
|
|
35
|
+
name: 'network',
|
|
36
|
+
canHandle: (error) => {
|
|
37
|
+
return error.name === 'TypeError' &&
|
|
38
|
+
(error.message.includes('fetch') || error.message.includes('network'));
|
|
39
|
+
},
|
|
40
|
+
handle: async (error, context) => {
|
|
41
|
+
console.log('[ErrorHandler:Network] Handling network error');
|
|
42
|
+
|
|
43
|
+
// Retry con backoff
|
|
44
|
+
if (context.retryCount < this.config.maxRetries) {
|
|
45
|
+
const delay = this.config.retryDelay * Math.pow(2, context.retryCount);
|
|
46
|
+
console.log(`[ErrorHandler:Network] Retrying in ${delay}ms...`);
|
|
47
|
+
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
recovered: true,
|
|
52
|
+
action: 'retry',
|
|
53
|
+
retryCount: context.retryCount + 1
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
recovered: false,
|
|
59
|
+
action: 'fallback',
|
|
60
|
+
message: 'Network error: Please check your connection'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 2. Script Load Error Handler
|
|
66
|
+
this.register({
|
|
67
|
+
name: 'script-load',
|
|
68
|
+
canHandle: (error) => {
|
|
69
|
+
return error.name === 'Error' &&
|
|
70
|
+
(error.message.includes('Loading') ||
|
|
71
|
+
error.message.includes('Failed to fetch'));
|
|
72
|
+
},
|
|
73
|
+
handle: async (error, context) => {
|
|
74
|
+
console.log('[ErrorHandler:ScriptLoad] Handling script load error');
|
|
75
|
+
|
|
76
|
+
// Intentar URL alternativa si existe
|
|
77
|
+
if (context.fallbackUrl) {
|
|
78
|
+
console.log('[ErrorHandler:ScriptLoad] Trying fallback URL');
|
|
79
|
+
return {
|
|
80
|
+
recovered: true,
|
|
81
|
+
action: 'use-fallback-url',
|
|
82
|
+
url: context.fallbackUrl
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
recovered: false,
|
|
88
|
+
action: 'fallback',
|
|
89
|
+
message: 'Failed to load microfrontend'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 3. Mount Error Handler
|
|
95
|
+
this.register({
|
|
96
|
+
name: 'mount',
|
|
97
|
+
canHandle: (error) => {
|
|
98
|
+
return error.message && error.message.includes('mount');
|
|
99
|
+
},
|
|
100
|
+
handle: async (error, context) => {
|
|
101
|
+
console.log('[ErrorHandler:Mount] Handling mount error');
|
|
102
|
+
|
|
103
|
+
// Limpiar y reintentar
|
|
104
|
+
if (context.retryCount < 2) {
|
|
105
|
+
console.log('[ErrorHandler:Mount] Cleaning up and retrying...');
|
|
106
|
+
|
|
107
|
+
// Cleanup
|
|
108
|
+
try {
|
|
109
|
+
await this.core.unmount(context.appName);
|
|
110
|
+
} catch (cleanupError) {
|
|
111
|
+
console.warn('[ErrorHandler:Mount] Cleanup failed:', cleanupError);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
recovered: true,
|
|
118
|
+
action: 'retry',
|
|
119
|
+
retryCount: context.retryCount + 1
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
recovered: false,
|
|
125
|
+
action: 'fallback',
|
|
126
|
+
message: 'Failed to mount application'
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// 4. Timeout Error Handler
|
|
132
|
+
this.register({
|
|
133
|
+
name: 'timeout',
|
|
134
|
+
canHandle: (error) => {
|
|
135
|
+
return error.name === 'TimeoutError' ||
|
|
136
|
+
error.message.includes('timeout');
|
|
137
|
+
},
|
|
138
|
+
handle: async (error, context) => {
|
|
139
|
+
console.log('[ErrorHandler:Timeout] Handling timeout error');
|
|
140
|
+
|
|
141
|
+
// Aumentar timeout y reintentar
|
|
142
|
+
if (context.retryCount < 2) {
|
|
143
|
+
return {
|
|
144
|
+
recovered: true,
|
|
145
|
+
action: 'retry-with-longer-timeout',
|
|
146
|
+
timeout: (context.timeout || 5000) * 2,
|
|
147
|
+
retryCount: context.retryCount + 1
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
recovered: false,
|
|
153
|
+
action: 'fallback',
|
|
154
|
+
message: 'Operation timed out'
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 5. Generic Error Handler (fallback)
|
|
160
|
+
this.register({
|
|
161
|
+
name: 'generic',
|
|
162
|
+
canHandle: () => true, // Maneja todo
|
|
163
|
+
handle: async (error, context) => {
|
|
164
|
+
console.log('[ErrorHandler:Generic] Handling generic error');
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
recovered: false,
|
|
168
|
+
action: 'fallback',
|
|
169
|
+
message: error.message || 'An unexpected error occurred'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 📦 REGISTER: Registrar error handler
|
|
177
|
+
* @param {Object} handler - Error handler { name, canHandle, handle }
|
|
178
|
+
*/
|
|
179
|
+
register(handler) {
|
|
180
|
+
if (!handler.name || !handler.canHandle || !handler.handle) {
|
|
181
|
+
throw new Error('[WuErrorBoundary] Handler must have name, canHandle, and handle');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.handlers.push(handler);
|
|
185
|
+
console.log(`[WuErrorBoundary] Handler "${handler.name}" registered`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 🎯 HANDLE: Manejar error con chain of responsibility
|
|
190
|
+
* @param {Error} error - Error a manejar
|
|
191
|
+
* @param {Object} context - Contexto del error
|
|
192
|
+
* @returns {Promise<Object>} Recovery result
|
|
193
|
+
*/
|
|
194
|
+
async handle(error, context = {}) {
|
|
195
|
+
// Agregar valores por defecto
|
|
196
|
+
context = {
|
|
197
|
+
retryCount: 0,
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
...context
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Log error
|
|
203
|
+
this.logError(error, context);
|
|
204
|
+
|
|
205
|
+
// Buscar handler que pueda manejar este error
|
|
206
|
+
for (const handler of this.handlers) {
|
|
207
|
+
try {
|
|
208
|
+
if (handler.canHandle(error, context)) {
|
|
209
|
+
console.log(`[WuErrorBoundary] Using handler: ${handler.name}`);
|
|
210
|
+
|
|
211
|
+
const result = await handler.handle(error, context);
|
|
212
|
+
|
|
213
|
+
if (result.recovered) {
|
|
214
|
+
console.log(`[WuErrorBoundary] ✅ Error recovered by ${handler.name}`);
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Si no se recuperó, renderizar fallback
|
|
219
|
+
if (result.action === 'fallback' && this.config.showErrorUI) {
|
|
220
|
+
this.renderFallback(context, result);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
} catch (handlerError) {
|
|
226
|
+
console.error(`[WuErrorBoundary] Handler "${handler.name}" failed:`, handlerError);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// No handler pudo manejar el error
|
|
231
|
+
console.error('[WuErrorBoundary] ❌ No handler could handle the error');
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
recovered: false,
|
|
235
|
+
action: 'unhandled',
|
|
236
|
+
message: 'Unhandled error'
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 🎨 RENDER FALLBACK: Renderizar UI de error
|
|
242
|
+
* @param {Object} context - Contexto del error
|
|
243
|
+
* @param {Object} result - Resultado del handler
|
|
244
|
+
*/
|
|
245
|
+
renderFallback(context, result) {
|
|
246
|
+
if (!context.container) {
|
|
247
|
+
console.warn('[WuErrorBoundary] No container to render fallback');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const container = typeof context.container === 'string'
|
|
252
|
+
? document.querySelector(context.container)
|
|
253
|
+
: context.container;
|
|
254
|
+
|
|
255
|
+
if (!container) return;
|
|
256
|
+
|
|
257
|
+
// Limpiar container
|
|
258
|
+
container.innerHTML = '';
|
|
259
|
+
|
|
260
|
+
// Crear UI de error
|
|
261
|
+
const errorUI = document.createElement('div');
|
|
262
|
+
errorUI.className = 'wu-error-boundary';
|
|
263
|
+
|
|
264
|
+
Object.assign(errorUI.style, {
|
|
265
|
+
padding: '2rem',
|
|
266
|
+
borderRadius: '8px',
|
|
267
|
+
background: '#fff3cd',
|
|
268
|
+
border: '1px solid #ffc107',
|
|
269
|
+
color: '#856404',
|
|
270
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
271
|
+
textAlign: 'center'
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const icon = document.createElement('div');
|
|
275
|
+
icon.textContent = '⚠️';
|
|
276
|
+
icon.style.fontSize = '3rem';
|
|
277
|
+
icon.style.marginBottom = '1rem';
|
|
278
|
+
|
|
279
|
+
const title = document.createElement('h3');
|
|
280
|
+
title.textContent = 'Application Error';
|
|
281
|
+
title.style.margin = '0 0 0.5rem 0';
|
|
282
|
+
|
|
283
|
+
const message = document.createElement('p');
|
|
284
|
+
message.textContent = result.message || 'An error occurred';
|
|
285
|
+
message.style.margin = '0 0 1rem 0';
|
|
286
|
+
|
|
287
|
+
const button = document.createElement('button');
|
|
288
|
+
button.textContent = '🔄 Reload';
|
|
289
|
+
Object.assign(button.style, {
|
|
290
|
+
padding: '0.5rem 1rem',
|
|
291
|
+
background: '#ffc107',
|
|
292
|
+
border: 'none',
|
|
293
|
+
borderRadius: '4px',
|
|
294
|
+
cursor: 'pointer',
|
|
295
|
+
fontWeight: 'bold',
|
|
296
|
+
color: '#000'
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
button.addEventListener('click', () => window.location.reload());
|
|
300
|
+
|
|
301
|
+
errorUI.appendChild(icon);
|
|
302
|
+
errorUI.appendChild(title);
|
|
303
|
+
errorUI.appendChild(message);
|
|
304
|
+
errorUI.appendChild(button);
|
|
305
|
+
|
|
306
|
+
container.appendChild(errorUI);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 📝 LOG ERROR: Registrar error
|
|
311
|
+
* @param {Error} error - Error
|
|
312
|
+
* @param {Object} context - Contexto
|
|
313
|
+
*/
|
|
314
|
+
logError(error, context) {
|
|
315
|
+
const errorEntry = {
|
|
316
|
+
error: {
|
|
317
|
+
name: error.name,
|
|
318
|
+
message: error.message,
|
|
319
|
+
stack: error.stack
|
|
320
|
+
},
|
|
321
|
+
context,
|
|
322
|
+
timestamp: Date.now()
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
this.errorLog.push(errorEntry);
|
|
326
|
+
|
|
327
|
+
// Mantener límite de log
|
|
328
|
+
if (this.errorLog.length > this.maxErrorLog) {
|
|
329
|
+
this.errorLog.shift();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 📋 GET ERROR LOG: Obtener log de errores
|
|
335
|
+
* @param {number} limit - Límite de errores a retornar
|
|
336
|
+
* @returns {Array}
|
|
337
|
+
*/
|
|
338
|
+
getErrorLog(limit = 10) {
|
|
339
|
+
return this.errorLog.slice(-limit);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 📊 GET STATS: Estadísticas de errores
|
|
344
|
+
* @returns {Object}
|
|
345
|
+
*/
|
|
346
|
+
getStats() {
|
|
347
|
+
const errorsByType = {};
|
|
348
|
+
|
|
349
|
+
this.errorLog.forEach(entry => {
|
|
350
|
+
const type = entry.error.name || 'Unknown';
|
|
351
|
+
errorsByType[type] = (errorsByType[type] || 0) + 1;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
totalErrors: this.errorLog.length,
|
|
356
|
+
handlers: this.handlers.length,
|
|
357
|
+
errorsByType,
|
|
358
|
+
recentErrors: this.getErrorLog(5)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* ⚙️ CONFIGURE: Configurar error boundary
|
|
364
|
+
* @param {Object} config - Nueva configuración
|
|
365
|
+
*/
|
|
366
|
+
configure(config) {
|
|
367
|
+
this.config = {
|
|
368
|
+
...this.config,
|
|
369
|
+
...config
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 🧹 CLEANUP: Limpiar error boundary
|
|
375
|
+
*/
|
|
376
|
+
cleanup() {
|
|
377
|
+
this.errorLog = [];
|
|
378
|
+
console.log('[WuErrorBoundary] 🧹 Error boundary cleaned up');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📡 WU-EVENT-BUS: ADVANCED PUB/SUB SYSTEM
|
|
3
|
+
*
|
|
4
|
+
* Sistema de eventos para comunicación entre microfrontends
|
|
5
|
+
* - Pub/Sub pattern
|
|
6
|
+
* - Event namespaces
|
|
7
|
+
* - Wildcards
|
|
8
|
+
* - Event replay
|
|
9
|
+
* - Performance optimizado
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class WuEventBus {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.listeners = new Map(); // eventName -> Set<callback>
|
|
15
|
+
this.history = []; // Event history para replay
|
|
16
|
+
this.config = {
|
|
17
|
+
maxHistory: 100,
|
|
18
|
+
enableReplay: true,
|
|
19
|
+
enableWildcards: true,
|
|
20
|
+
logEvents: false
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this.stats = {
|
|
24
|
+
emitted: 0,
|
|
25
|
+
subscriptions: 0
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
console.log('[WuEventBus] 📡 Advanced event bus initialized');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 📢 EMIT: Emitir evento
|
|
33
|
+
* @param {string} eventName - Nombre del evento
|
|
34
|
+
* @param {*} data - Datos del evento
|
|
35
|
+
* @param {Object} options - Opciones { appName, timestamp, meta }
|
|
36
|
+
*/
|
|
37
|
+
emit(eventName, data, options = {}) {
|
|
38
|
+
const event = {
|
|
39
|
+
name: eventName,
|
|
40
|
+
data,
|
|
41
|
+
timestamp: options.timestamp || Date.now(),
|
|
42
|
+
appName: options.appName || 'unknown',
|
|
43
|
+
meta: options.meta || {}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Agregar a historial
|
|
47
|
+
if (this.config.enableReplay) {
|
|
48
|
+
this.addToHistory(event);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Log si está habilitado
|
|
52
|
+
if (this.config.logEvents) {
|
|
53
|
+
console.log(`[WuEventBus] 📢 ${eventName}`, data);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Notificar listeners exactos
|
|
57
|
+
const exactListeners = this.listeners.get(eventName);
|
|
58
|
+
if (exactListeners) {
|
|
59
|
+
exactListeners.forEach(callback => {
|
|
60
|
+
try {
|
|
61
|
+
callback(event);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`[WuEventBus] ❌ Error in listener for ${eventName}:`, error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Notificar listeners con wildcards
|
|
69
|
+
if (this.config.enableWildcards) {
|
|
70
|
+
this.notifyWildcardListeners(eventName, event);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.stats.emitted++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 👂 ON: Suscribirse a evento
|
|
78
|
+
* @param {string} eventName - Nombre del evento (puede usar wildcards: 'app.*', '*.update')
|
|
79
|
+
* @param {Function} callback - Callback a ejecutar
|
|
80
|
+
* @returns {Function} Función para desuscribirse
|
|
81
|
+
*/
|
|
82
|
+
on(eventName, callback) {
|
|
83
|
+
if (!this.listeners.has(eventName)) {
|
|
84
|
+
this.listeners.set(eventName, new Set());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.listeners.get(eventName).add(callback);
|
|
88
|
+
this.stats.subscriptions++;
|
|
89
|
+
|
|
90
|
+
// Retornar función de desuscripción
|
|
91
|
+
return () => this.off(eventName, callback);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 🔇 OFF: Desuscribirse de evento
|
|
96
|
+
* @param {string} eventName - Nombre del evento
|
|
97
|
+
* @param {Function} callback - Callback a remover
|
|
98
|
+
*/
|
|
99
|
+
off(eventName, callback) {
|
|
100
|
+
const listeners = this.listeners.get(eventName);
|
|
101
|
+
if (listeners) {
|
|
102
|
+
listeners.delete(callback);
|
|
103
|
+
|
|
104
|
+
// Limpiar si no quedan listeners
|
|
105
|
+
if (listeners.size === 0) {
|
|
106
|
+
this.listeners.delete(eventName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.stats.subscriptions--;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 🎯 ONCE: Suscribirse una sola vez
|
|
115
|
+
* @param {string} eventName - Nombre del evento
|
|
116
|
+
* @param {Function} callback - Callback a ejecutar
|
|
117
|
+
* @returns {Function} Función para desuscribirse
|
|
118
|
+
*/
|
|
119
|
+
once(eventName, callback) {
|
|
120
|
+
const wrappedCallback = (event) => {
|
|
121
|
+
callback(event);
|
|
122
|
+
this.off(eventName, wrappedCallback);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return this.on(eventName, wrappedCallback);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 🌟 WILDCARD LISTENERS: Notificar listeners con wildcards
|
|
130
|
+
* @param {string} eventName - Nombre del evento emitido
|
|
131
|
+
* @param {Object} event - Objeto del evento
|
|
132
|
+
*/
|
|
133
|
+
notifyWildcardListeners(eventName, event) {
|
|
134
|
+
for (const [pattern, listeners] of this.listeners) {
|
|
135
|
+
if (this.matchesWildcard(eventName, pattern)) {
|
|
136
|
+
listeners.forEach(callback => {
|
|
137
|
+
try {
|
|
138
|
+
callback(event);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`[WuEventBus] ❌ Error in wildcard listener for ${pattern}:`, error);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 🎯 MATCHES WILDCARD: Verificar si evento coincide con patrón wildcard
|
|
149
|
+
* @param {string} eventName - Nombre del evento
|
|
150
|
+
* @param {string} pattern - Patrón con wildcards
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
matchesWildcard(eventName, pattern) {
|
|
154
|
+
// Si no hay wildcard, ya se procesó en listeners exactos
|
|
155
|
+
if (!pattern.includes('*')) return false;
|
|
156
|
+
|
|
157
|
+
// Convertir pattern a regex
|
|
158
|
+
const regexPattern = pattern
|
|
159
|
+
.replace(/\./g, '\\.')
|
|
160
|
+
.replace(/\*/g, '.*');
|
|
161
|
+
|
|
162
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
163
|
+
return regex.test(eventName);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 📝 ADD TO HISTORY: Agregar evento al historial
|
|
168
|
+
* @param {Object} event - Evento
|
|
169
|
+
*/
|
|
170
|
+
addToHistory(event) {
|
|
171
|
+
this.history.push(event);
|
|
172
|
+
|
|
173
|
+
// Mantener tamaño máximo
|
|
174
|
+
if (this.history.length > this.config.maxHistory) {
|
|
175
|
+
this.history.shift();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 🔄 REPLAY: Reproducir eventos del historial
|
|
181
|
+
* @param {string} eventNameOrPattern - Nombre o patrón de eventos a reproducir
|
|
182
|
+
* @param {Function} callback - Callback para cada evento
|
|
183
|
+
*/
|
|
184
|
+
replay(eventNameOrPattern, callback) {
|
|
185
|
+
const events = this.history.filter(event => {
|
|
186
|
+
if (eventNameOrPattern.includes('*')) {
|
|
187
|
+
return this.matchesWildcard(event.name, eventNameOrPattern);
|
|
188
|
+
}
|
|
189
|
+
return event.name === eventNameOrPattern;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
console.log(`[WuEventBus] 🔄 Replaying ${events.length} events for ${eventNameOrPattern}`);
|
|
193
|
+
|
|
194
|
+
events.forEach(event => {
|
|
195
|
+
try {
|
|
196
|
+
callback(event);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error(`[WuEventBus] ❌ Error replaying event:`, error);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 🧹 CLEAR HISTORY: Limpiar historial de eventos
|
|
205
|
+
* @param {string} eventNameOrPattern - Patrón de eventos a limpiar (opcional)
|
|
206
|
+
*/
|
|
207
|
+
clearHistory(eventNameOrPattern) {
|
|
208
|
+
if (!eventNameOrPattern) {
|
|
209
|
+
this.history = [];
|
|
210
|
+
console.log('[WuEventBus] 🧹 Event history cleared');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.history = this.history.filter(event => {
|
|
215
|
+
if (eventNameOrPattern.includes('*')) {
|
|
216
|
+
return !this.matchesWildcard(event.name, eventNameOrPattern);
|
|
217
|
+
}
|
|
218
|
+
return event.name !== eventNameOrPattern;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 📊 GET STATS: Obtener estadísticas
|
|
224
|
+
* @returns {Object}
|
|
225
|
+
*/
|
|
226
|
+
getStats() {
|
|
227
|
+
return {
|
|
228
|
+
...this.stats,
|
|
229
|
+
activeListeners: this.listeners.size,
|
|
230
|
+
historySize: this.history.length,
|
|
231
|
+
listenersByEvent: Array.from(this.listeners.entries()).map(([event, listeners]) => ({
|
|
232
|
+
event,
|
|
233
|
+
listeners: listeners.size
|
|
234
|
+
}))
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* ⚙️ CONFIGURE: Configurar event bus
|
|
240
|
+
* @param {Object} config - Nueva configuración
|
|
241
|
+
*/
|
|
242
|
+
configure(config) {
|
|
243
|
+
this.config = {
|
|
244
|
+
...this.config,
|
|
245
|
+
...config
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 🗑️ REMOVE ALL: Remover todos los listeners
|
|
251
|
+
*/
|
|
252
|
+
removeAll() {
|
|
253
|
+
this.listeners.clear();
|
|
254
|
+
this.stats.subscriptions = 0;
|
|
255
|
+
console.log('[WuEventBus] 🗑️ All listeners removed');
|
|
256
|
+
}
|
|
257
|
+
}
|