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,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
+ }