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,1296 @@
1
+ /**
2
+ * 🚀 WU-FRAMEWORK: UNIVERSAL MICROFRONTENDS
3
+ * Motor principal agnóstico - Funciona con cualquier framework
4
+ */
5
+
6
+ import { WuLoader } from './wu-loader.js';
7
+ import { WuSandbox } from './wu-sandbox.js';
8
+ import { WuManifest } from './wu-manifest.js';
9
+ import { logger } from './wu-logger.js';
10
+ import { default as store } from './wu-store.js';
11
+ import { WuApp } from './wu-app.js';
12
+ import { WuCache } from './wu-cache.js';
13
+ import { WuEventBus } from './wu-event-bus.js';
14
+ import { WuPerformance } from './wu-performance.js';
15
+ import { WuPluginSystem } from './wu-plugin.js';
16
+ import { WuLoadingStrategy } from './wu-strategies.js';
17
+ import { WuErrorBoundary } from './wu-error-boundary.js';
18
+ import { WuLifecycleHooks } from './wu-hooks.js';
19
+ import { WuSandboxPool } from './wu-sandbox-pool.js';
20
+ import { WuRegistry } from './wu-registry.js';
21
+
22
+ export class WuCore {
23
+ constructor(options = {}) {
24
+ // Registros principales
25
+ this.apps = new Map(); // Apps registradas
26
+ this.definitions = new Map(); // Definiciones de lifecycle
27
+ this.manifests = new Map(); // Manifiestos cargados
28
+ this.mounted = new Map(); // Apps montadas
29
+
30
+ // Componentes core
31
+ this.loader = new WuLoader();
32
+ this.sandbox = new WuSandbox();
33
+ this.manifest = new WuManifest();
34
+ this.store = store;
35
+
36
+ // 🚀 SISTEMAS ESENCIALES
37
+ this.cache = new WuCache({ storage: 'localStorage', maxSize: 100 }); // 100MB cache
38
+ this.eventBus = new WuEventBus();
39
+ this.performance = new WuPerformance();
40
+
41
+ // 🎯 ADVANCED SYSTEMS
42
+ this.pluginSystem = new WuPluginSystem(this);
43
+ this.strategies = new WuLoadingStrategy(this);
44
+ this.errorBoundary = new WuErrorBoundary(this);
45
+ this.hooks = new WuLifecycleHooks(this);
46
+ this.sandboxPool = new WuSandboxPool(this);
47
+ this.registry = new WuRegistry();
48
+
49
+ // 🔄 HEALTH MONITORING CONFIG
50
+ this.healthConfig = {
51
+ enabled: options.healthMonitoring !== false, // Default: enabled
52
+ autoHeal: options.autoHeal !== false, // Default: enabled
53
+ agingThreshold: options.agingThreshold || Infinity, // Default: never refresh
54
+ checkInterval: options.healthCheckInterval || 60000 // Default: 60s
55
+ };
56
+
57
+ // 🔄 HEALTH MONITORING STATE
58
+ this.healthState = {
59
+ monitor: null,
60
+ healingInProgress: new Set(),
61
+ systemStable: true,
62
+ lastHealthCheck: Date.now(),
63
+ mutationObserver: null,
64
+ mutationCheckTimeout: null
65
+ };
66
+
67
+ // Estado
68
+ this.isInitialized = false;
69
+
70
+ // 🚀 Initialize self-healing system (ONLY if enabled)
71
+ if (this.healthConfig.enabled) {
72
+ this.initializeHealthMonitoring();
73
+ logger.wuInfo('🚀 Wu Framework initialized - Universal Microfrontends with Self-Healing');
74
+ } else {
75
+ logger.wuInfo('🚀 Wu Framework initialized - Universal Microfrontends (Health Monitoring Disabled)');
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 🔄 HEALTH MONITORING INITIALIZATION
81
+ */
82
+ initializeHealthMonitoring() {
83
+ // 💫 Optimized health monitor with MutationObserver + reduced polling
84
+ this.healthState.monitor = setInterval(() => {
85
+ this.performHealthCheck();
86
+ }, this.healthConfig.checkInterval);
87
+
88
+ // 🔍 Smart MutationObserver for real-time DOM changes
89
+ this.initializeMutationObserver();
90
+
91
+ // 🛡️ Global error recovery (ONLY if autoHeal enabled)
92
+ if (this.healthConfig.autoHeal) {
93
+ window.addEventListener('error', (event) => {
94
+ this.handleErrorEvent(event);
95
+ });
96
+
97
+ // 🌟 Unhandled promise rejection recovery
98
+ window.addEventListener('unhandledrejection', (event) => {
99
+ this.handleRejection(event);
100
+ });
101
+ }
102
+
103
+ logger.wuInfo(`🔄 Health monitoring initialized - Interval: ${this.healthConfig.checkInterval}ms, AutoHeal: ${this.healthConfig.autoHeal}`);
104
+ }
105
+
106
+ /**
107
+ * 🔍 SMART MUTATION OBSERVER: Real-time DOM monitoring for efficiency
108
+ */
109
+ initializeMutationObserver() {
110
+ if (!window.MutationObserver) return; // Fallback for older browsers
111
+
112
+ this.healthState.mutationObserver = new MutationObserver((mutations) => {
113
+ let shouldCheckHealth = false;
114
+
115
+ for (const mutation of mutations) {
116
+ // Only check health if it affects our mounted apps
117
+ if (mutation.type === 'childList') {
118
+ const removedNodes = Array.from(mutation.removedNodes);
119
+ const affectedApps = removedNodes.some(node => {
120
+ if (node.nodeType === Node.ELEMENT_NODE) {
121
+ // Check if removed node contains any of our mounted apps
122
+ for (const [appName, mounted] of this.mounted) {
123
+ if (node.contains && node.contains(mounted.container)) {
124
+ return true;
125
+ }
126
+ if (node === mounted.container) {
127
+ return true;
128
+ }
129
+ }
130
+ }
131
+ return false;
132
+ });
133
+
134
+ if (affectedApps) {
135
+ shouldCheckHealth = true;
136
+ break;
137
+ }
138
+ }
139
+ }
140
+
141
+ // Debounced health check only when necessary
142
+ if (shouldCheckHealth) {
143
+ clearTimeout(this.healthState.mutationCheckTimeout);
144
+ this.healthState.mutationCheckTimeout = setTimeout(() => {
145
+ this.performHealthCheck();
146
+ }, 1000); // Wait 1 second to batch multiple mutations
147
+ }
148
+ });
149
+
150
+ // Observe the entire document but only care about child changes
151
+ this.healthState.mutationObserver.observe(document.body, {
152
+ childList: true,
153
+ subtree: true
154
+ });
155
+
156
+ logger.wuDebug('🔍 MutationObserver initialized for smart DOM monitoring');
157
+ }
158
+
159
+ /**
160
+ * 💊 HEALTH CHECK: Continuous app monitoring
161
+ */
162
+ async performHealthCheck() {
163
+ try {
164
+ const now = Date.now();
165
+ const healthIssues = [];
166
+
167
+ // Check mounted apps vitality
168
+ for (const [appName, mountedApp] of this.mounted) {
169
+ const age = now - mountedApp.timestamp;
170
+ const container = mountedApp.container;
171
+
172
+ // 🛡️ ENHANCED CHECK: Verify container exists and is properly connected
173
+ // IMPORTANT: Check multiple conditions to avoid false positives
174
+ let isOrphaned = false;
175
+
176
+ try {
177
+ // Check 1: Direct DOM connection
178
+ const containerInDOM = document.contains(container);
179
+
180
+ // Check 2: Parent element connection (for shadow DOM)
181
+ const parentInDOM = container.parentElement && document.contains(container.parentElement);
182
+
183
+ // Check 3: Shadow root connection
184
+ const shadowHost = mountedApp.sandbox?.shadowRoot?.host;
185
+ const shadowHostInDOM = shadowHost && document.contains(shadowHost);
186
+
187
+ // Check 4: Container still has valid properties (not detached)
188
+ const hasValidParent = container.parentElement !== null || container.parentNode !== null;
189
+
190
+ // Only mark as orphaned if ALL checks fail
191
+ if (!containerInDOM && !parentInDOM && !shadowHostInDOM && !hasValidParent) {
192
+ isOrphaned = true;
193
+ }
194
+ } catch (error) {
195
+ // If checking fails, assume NOT orphaned (conservative approach)
196
+ console.warn(`[Wu] ⚠️ Container check failed for ${appName}:`, error);
197
+ isOrphaned = false;
198
+ }
199
+
200
+ if (isOrphaned) {
201
+ healthIssues.push({
202
+ type: 'orphaned_container',
203
+ appName,
204
+ severity: 'high'
205
+ });
206
+ }
207
+
208
+ // Check app state - ONLY if it's truly unstable, not 'refreshed'
209
+ // 'refreshed' is a normal state after aging refresh
210
+ if (mountedApp.state && mountedApp.state !== 'stable' && mountedApp.state !== 'refreshed') {
211
+ healthIssues.push({
212
+ type: 'unstable_state',
213
+ appName,
214
+ severity: 'medium'
215
+ });
216
+ }
217
+
218
+ // 🕰️ Check for long-running apps that might need refresh
219
+ // ONLY if agingThreshold is configured (default: Infinity = never)
220
+ if (age > this.healthConfig.agingThreshold) {
221
+ healthIssues.push({
222
+ type: 'aging_app',
223
+ appName,
224
+ severity: 'low',
225
+ age: age
226
+ });
227
+ console.log(`[Wu] ⏰ App ${appName} has been running for ${(age / 1000 / 60).toFixed(1)} minutes`);
228
+ }
229
+ }
230
+
231
+ // Process health issues
232
+ if (healthIssues.length > 0) {
233
+ await this.processHealthIssues(healthIssues);
234
+ }
235
+
236
+ this.healthState.lastHealthCheck = now;
237
+
238
+ } catch (error) {
239
+ console.warn('[Wu] ⚠️ Health check failed:', error);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * 🛠️ HEALTH ISSUE PROCESSOR
245
+ */
246
+ async processHealthIssues(issues) {
247
+ for (const issue of issues) {
248
+ if (this.healthState.healingInProgress.has(issue.appName)) {
249
+ continue; // Already healing
250
+ }
251
+
252
+ console.log(`[Wu] 🩺 Health issue detected:`, issue);
253
+
254
+ switch (issue.type) {
255
+ case 'orphaned_container':
256
+ await this.healOrphanedContainer(issue.appName);
257
+ break;
258
+ case 'unstable_state':
259
+ await this.stabilizeAppState(issue.appName);
260
+ break;
261
+ case 'aging_app':
262
+ await this.refreshAgingApp(issue.appName);
263
+ break;
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * 🔄 ORPHANED CONTAINER HEALING
270
+ */
271
+ async healOrphanedContainer(appName) {
272
+ this.healthState.healingInProgress.add(appName);
273
+
274
+ try {
275
+ console.log(`[Wu] 🔄 Healing orphaned container for ${appName}`);
276
+
277
+ // Clean up orphaned state
278
+ await this.unmount(appName);
279
+
280
+ // 🔍 Find a suitable container to remount
281
+ const suitableContainers = [
282
+ document.querySelector(`[data-wu-app="${appName}"]`),
283
+ document.querySelector(`#${appName}-container`),
284
+ document.querySelector(`.${appName}-container`),
285
+ document.querySelector(`[id*="${appName}"]`)
286
+ ].filter(Boolean);
287
+
288
+ if (suitableContainers.length > 0) {
289
+ const container = suitableContainers[0];
290
+ const containerSelector = container.id ? `#${container.id}` : `.${container.className.split(' ')[0]}`;
291
+
292
+ // 🎯 Only attempt re-mount if selector is valid
293
+ if (containerSelector && containerSelector !== '#' && containerSelector !== '.') {
294
+ await this.mount(appName, containerSelector);
295
+ console.log(`[Wu] ✨ Successfully healed orphaned ${appName} in ${containerSelector}`);
296
+ } else {
297
+ console.warn(`[Wu] ⚠️ Could not determine valid selector for ${appName}`);
298
+ }
299
+ } else {
300
+ console.warn(`[Wu] ⚠️ No suitable container found for healing ${appName}`);
301
+ }
302
+
303
+ } catch (error) {
304
+ console.warn(`[Wu] ⚠️ Failed to heal orphaned ${appName}:`, error);
305
+ } finally {
306
+ this.healthState.healingInProgress.delete(appName);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * ⚖️ APP STATE STABILIZATION
312
+ */
313
+ async stabilizeAppState(appName) {
314
+ this.healthState.healingInProgress.add(appName);
315
+
316
+ try {
317
+ console.log(`[Wu] ⚖️ Stabilizing app state for ${appName}`);
318
+
319
+ const mounted = this.mounted.get(appName);
320
+ if (mounted) {
321
+ mounted.state = 'stable';
322
+ // ⚠️ DO NOT RESET TIMESTAMP - preserves app age for aging checks
323
+ console.log(`[Wu] ✨ App state stabilized for ${appName}`);
324
+ }
325
+
326
+ } catch (error) {
327
+ console.warn(`[Wu] ⚠️ Failed to stabilize ${appName}:`, error);
328
+ } finally {
329
+ this.healthState.healingInProgress.delete(appName);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * 🕰️ AGING APP REFRESH
335
+ * NOTE: This should ONLY trigger if agingThreshold is explicitly configured
336
+ */
337
+ async refreshAgingApp(appName) {
338
+ // Safety check: Don't refresh if aging is disabled (Infinity)
339
+ if (this.healthConfig.agingThreshold === Infinity) {
340
+ console.warn(`[Wu] ⚠️ refreshAgingApp called but agingThreshold is Infinity - skipping`);
341
+ return;
342
+ }
343
+
344
+ this.healthState.healingInProgress.add(appName);
345
+
346
+ try {
347
+ console.log(`[Wu] 🕰️ Refreshing aging app for ${appName}`);
348
+
349
+ const mounted = this.mounted.get(appName);
350
+ if (mounted) {
351
+ // Gentle refresh without full remount
352
+ // Reset timestamp ONLY when explicitly configured aging refresh
353
+ mounted.timestamp = Date.now();
354
+ mounted.state = 'refreshed';
355
+
356
+ // Trigger a soft refresh of the app if it supports it
357
+ if (mounted.lifecycle.refresh) {
358
+ await mounted.lifecycle.refresh(mounted.container);
359
+ }
360
+
361
+ console.log(`[Wu] ✨ App refreshed for ${appName}`);
362
+ }
363
+
364
+ } catch (error) {
365
+ console.warn(`[Wu] ⚠️ Failed to refresh ${appName}:`, error);
366
+ } finally {
367
+ this.healthState.healingInProgress.delete(appName);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * 💥 ERROR EVENT HANDLER
373
+ */
374
+ handleErrorEvent(event) {
375
+ console.log('[Wu] 💥 Error event detected:', event.error);
376
+
377
+ // Check if error is related to any mounted apps
378
+ for (const [appName, mounted] of this.mounted) {
379
+ if (event.filename && event.filename.includes(mounted.app.url)) {
380
+ console.log(`[Wu] 🎯 Error traced to ${appName}, initiating recovery...`);
381
+
382
+ // Schedule healing
383
+ setTimeout(() => {
384
+ this.healOrphanedContainer(appName);
385
+ }, 1000);
386
+
387
+ break;
388
+ }
389
+ }
390
+ }
391
+
392
+ /**
393
+ * 🚫 REJECTION HANDLER
394
+ */
395
+ handleRejection(event) {
396
+ console.log('[Wu] 🚫 Promise rejection detected:', event.reason);
397
+
398
+ // Prevent default unhandled rejection
399
+ event.preventDefault();
400
+
401
+ // Mark system as unstable temporarily
402
+ this.healthState.systemStable = false;
403
+
404
+ // Schedule stability restoration
405
+ setTimeout(() => {
406
+ this.healthState.systemStable = true;
407
+ console.log('[Wu] ✨ System stability restored');
408
+ }, 5000);
409
+ }
410
+
411
+ /**
412
+ * Inicializar wu-framework con configuración de apps
413
+ * @param {Object} config - Configuración { apps: [{name, url}, ...] }
414
+ */
415
+ async init(config) {
416
+ if (this.isInitialized) {
417
+ console.warn('[Wu] Framework already initialized');
418
+ return;
419
+ }
420
+
421
+ logger.wuDebug('🔧 Initializing with apps:', config.apps?.map(app => app.name));
422
+
423
+ try {
424
+ // 🪝 Execute beforeInit hooks
425
+ const beforeInitResult = await this.hooks.execute('beforeInit', { config });
426
+ if (beforeInitResult.cancelled) {
427
+ console.warn('[Wu] Initialization cancelled by beforeInit hook');
428
+ return;
429
+ }
430
+
431
+ // 🔌 Call plugin beforeInit hooks
432
+ await this.pluginSystem.callHook('beforeInit', { config });
433
+
434
+ // 🏊 Initialize sandbox pool
435
+ await this.sandboxPool.init();
436
+
437
+ // Registrar todas las apps
438
+ for (const appConfig of config.apps || []) {
439
+ await this.registerApp(appConfig);
440
+ }
441
+
442
+ // 🎯 Preload apps with eager/preload strategies
443
+ await this.strategies.preload(config.apps || []);
444
+
445
+ this.isInitialized = true;
446
+
447
+ // 🪝 Execute afterInit hooks
448
+ await this.hooks.execute('afterInit', { config });
449
+
450
+ // 🔌 Call plugin afterInit hooks
451
+ await this.pluginSystem.callHook('afterInit', { config });
452
+
453
+ logger.wuInfo('✅ Framework initialized successfully');
454
+ } catch (error) {
455
+ console.error('[Wu] ❌ Initialization failed:', error);
456
+
457
+ // 🔌 Call plugin error hooks
458
+ await this.pluginSystem.callHook('onError', { phase: 'init', error });
459
+
460
+ throw error;
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Registrar una aplicación
466
+ * @param {Object} appConfig - { name, url }
467
+ */
468
+ async registerApp(appConfig) {
469
+ const { name, url } = appConfig;
470
+
471
+ try {
472
+ logger.wuDebug(`📦 Registering app: ${name} from ${url}`);
473
+
474
+ // Cargar manifest
475
+ const manifestData = await this.manifest.load(url);
476
+ this.manifests.set(name, manifestData);
477
+
478
+ // Registrar la app
479
+ this.apps.set(name, {
480
+ name,
481
+ url,
482
+ manifest: manifestData,
483
+ status: 'registered'
484
+ });
485
+
486
+ logger.wuDebug(`✅ App ${name} registered successfully`);
487
+ } catch (error) {
488
+ console.error(`[Wu] ❌ Failed to register app ${name}:`, error);
489
+ throw error;
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Definir lifecycle de una micro-app
495
+ * @param {string} appName - Nombre de la app
496
+ * @param {Object} lifecycle - { mount, unmount }
497
+ */
498
+ define(appName, lifecycle) {
499
+ if (!lifecycle.mount) {
500
+ throw new Error(`[Wu] Mount function required for app: ${appName}`);
501
+ }
502
+
503
+ this.definitions.set(appName, lifecycle);
504
+
505
+ // 🔔 Notify registry that app is ready
506
+ this.registry.markAsRegistered(appName);
507
+
508
+ // 📡 Dispatch custom event for external listeners
509
+ const event = new CustomEvent('wu:app:ready', {
510
+ detail: { appName, timestamp: Date.now() }
511
+ });
512
+ window.dispatchEvent(event);
513
+
514
+ logger.wuDebug(`📋 Lifecycle defined for: ${appName}`);
515
+ }
516
+
517
+ /**
518
+ * 🚀 MOUNT APP: Multi-retry app mounting with self-healing
519
+ * @param {string} appName - Nombre de la app
520
+ * @param {string} containerSelector - Selector del contenedor
521
+ */
522
+ async mount(appName, containerSelector) {
523
+ return await this.mountWithRecovery(appName, containerSelector, 0);
524
+ }
525
+
526
+ /**
527
+ * 🔄 MOUNT WITH RECOVERY: Self-healing app mounting
528
+ */
529
+ async mountWithRecovery(appName, containerSelector, attempt = 0) {
530
+ const maxAttempts = 3;
531
+
532
+ try {
533
+ // ⚡ Start performance measurement
534
+ this.performance.startMeasure('mount', appName);
535
+
536
+ logger.wuDebug(`🔗 Mounting ${appName} in ${containerSelector} (attempt ${attempt + 1})`);
537
+
538
+ // 🪝 Execute beforeLoad hooks
539
+ const beforeLoadResult = await this.hooks.execute('beforeLoad', { appName, containerSelector, attempt });
540
+ if (beforeLoadResult.cancelled) {
541
+ console.warn('[Wu] Mount cancelled by beforeLoad hook');
542
+ return;
543
+ }
544
+
545
+ // 🔌 Call plugin beforeMount hooks
546
+ const pluginBeforeMount = await this.pluginSystem.callHook('beforeMount', { appName, containerSelector });
547
+ if (pluginBeforeMount === false) {
548
+ console.warn('[Wu] Mount cancelled by plugin beforeMount hook');
549
+ return;
550
+ }
551
+
552
+ // 🔮 Quantum state verification
553
+ const app = this.apps.get(appName);
554
+ if (!app) {
555
+ throw new Error(`App ${appName} not registered. Call wu.init() first.`);
556
+ }
557
+
558
+ // 🌟 Container reality check
559
+ const container = document.querySelector(containerSelector);
560
+ if (!container) {
561
+ throw new Error(`Container not found: ${containerSelector}`);
562
+ }
563
+
564
+ // 🏊 Acquire sandbox from pool (if configured)
565
+ const poolSandbox = this.sandboxPool.acquire(appName);
566
+
567
+ // 🛡️ Quantum sandbox creation
568
+ const sandbox = this.sandbox.create(appName, container);
569
+
570
+ // 🪝 Execute afterLoad hooks
571
+ await this.hooks.execute('afterLoad', { appName, containerSelector, sandbox });
572
+
573
+ // 🚀 Transcendent lifecycle resolution
574
+ let lifecycle = this.definitions.get(appName);
575
+ if (!lifecycle) {
576
+ // Load remote app
577
+ await this.loadAndMountRemoteApp(app, sandbox);
578
+ lifecycle = this.definitions.get(appName);
579
+
580
+ if (!lifecycle) {
581
+ throw new Error(`App ${appName} did not register with wu.define()`);
582
+ }
583
+ }
584
+
585
+ // 🪝 Execute beforeMount hooks
586
+ const beforeMountResult = await this.hooks.execute('beforeMount', { appName, containerSelector, sandbox, lifecycle });
587
+ if (beforeMountResult.cancelled) {
588
+ console.warn('[Wu] Mount cancelled by beforeMount hook');
589
+ return;
590
+ }
591
+
592
+ // 🎨 Wait for styles to be ready before mounting
593
+ if (sandbox.stylesReady) {
594
+ console.log(`[Wu] ⏳ Waiting for styles to be ready for ${appName}...`);
595
+ await sandbox.stylesReady;
596
+ console.log(`[Wu] ✅ Styles ready for ${appName}`);
597
+ }
598
+
599
+ // ⚡ Quantum mounting execution
600
+ await lifecycle.mount(sandbox.container);
601
+
602
+ // 🌌 Registration in mounted dimension
603
+ this.mounted.set(appName, {
604
+ app,
605
+ sandbox,
606
+ poolSandbox,
607
+ lifecycle,
608
+ container: sandbox.container,
609
+ timestamp: Date.now(),
610
+ state: 'stable'
611
+ });
612
+
613
+ // ⚡ End performance measurement
614
+ const mountTime = this.performance.endMeasure('mount', appName);
615
+
616
+ // 🪝 Execute afterMount hooks
617
+ await this.hooks.execute('afterMount', { appName, containerSelector, sandbox, mountTime });
618
+
619
+ // 🔌 Call plugin afterMount hooks
620
+ await this.pluginSystem.callHook('afterMount', { appName, containerSelector, mountTime });
621
+
622
+ // 📢 Emit mount event
623
+ this.eventBus.emit('app:mounted', { appName, mountTime, attempt }, { appName });
624
+
625
+ logger.wuInfo(`✅ ${appName} mounted successfully in ${mountTime.toFixed(2)}ms`);
626
+
627
+ } catch (error) {
628
+ console.error(`[Wu] ❌ Mount attempt ${attempt + 1} failed for ${appName}:`, error);
629
+
630
+ // 🛡️ Use error boundary for intelligent error handling
631
+ const errorResult = await this.errorBoundary.handle(error, {
632
+ appName,
633
+ containerSelector,
634
+ retryCount: attempt,
635
+ container: containerSelector
636
+ });
637
+
638
+ // Si el error boundary recuperó el error, no necesitamos reintentar
639
+ if (errorResult.recovered) {
640
+ console.log(`[Wu] ✨ Error recovered by error boundary`);
641
+ return;
642
+ }
643
+
644
+ // 🔄 RECOVERY PROTOCOL
645
+ if (attempt < maxAttempts - 1 && errorResult.action === 'retry') {
646
+ console.log(`[Wu] 🌟 Initiating recovery protocol...`);
647
+
648
+ // 🛠️ Clean app state
649
+ await this.appStateCleanup(appName, containerSelector);
650
+
651
+ // ⏱️ Temporal stabilization
652
+ await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
653
+
654
+ // 🚀 Recursive mounting with recovery
655
+ return await this.mountWithRecovery(appName, containerSelector, attempt + 1);
656
+ }
657
+
658
+ // 🔌 Call plugin error hooks
659
+ await this.pluginSystem.callHook('onError', { phase: 'mount', error, appName });
660
+
661
+ // 💥 Final mount failure - error boundary already handled fallback UI
662
+ throw error;
663
+ }
664
+ }
665
+
666
+ /**
667
+ * 🛠️ APP STATE CLEANUP: Enhanced container cleanup with framework protection
668
+ */
669
+ async appStateCleanup(appName, containerSelector) {
670
+ try {
671
+ console.log(`[Wu] 🧹 Starting app state cleanup for ${appName}...`);
672
+
673
+ // 🚀 Clear any existing mounted state safely
674
+ if (this.mounted.has(appName)) {
675
+ try {
676
+ await this.unmount(appName);
677
+ } catch (unmountError) {
678
+ console.warn(`[Wu] ⚠️ Unmount failed during cleanup:`, unmountError);
679
+ }
680
+ }
681
+
682
+ // 🌌 Enhanced container cleanup with Vue safety measures
683
+ const container = document.querySelector(containerSelector);
684
+ if (container) {
685
+ // 🛡️ Protect Vue's reactivity system
686
+ if (container.shadowRoot) {
687
+ try {
688
+ // Clear shadow root content safely
689
+ const shadowChildren = Array.from(container.shadowRoot.children);
690
+ shadowChildren.forEach(child => {
691
+ try {
692
+ child.remove();
693
+ } catch (removeError) {
694
+ console.warn(`[Wu] ⚠️ Failed to remove shadow child:`, removeError);
695
+ }
696
+ });
697
+ } catch (shadowError) {
698
+ console.warn(`[Wu] ⚠️ Shadow root cleanup failed:`, shadowError);
699
+ }
700
+ }
701
+
702
+ // 🔮 Clear any direct children if no shadow root
703
+ if (!container.shadowRoot && container.children.length > 0) {
704
+ try {
705
+ container.innerHTML = '';
706
+ } catch (htmlError) {
707
+ console.warn(`[Wu] ⚠️ Container innerHTML cleanup failed:`, htmlError);
708
+ }
709
+ }
710
+
711
+ // 🌟 Reset container attributes
712
+ container.removeAttribute('data-wu-app');
713
+ container.removeAttribute('data-quantum-state');
714
+ container.removeAttribute('wu-debug');
715
+ }
716
+
717
+ // 🎯 Reset definition state
718
+ this.definitions.delete(appName);
719
+
720
+ // 🚀 Clear sandbox registry
721
+ if (this.sandbox && this.sandbox.sandboxes) {
722
+ this.sandbox.sandboxes.delete(appName);
723
+ }
724
+
725
+ console.log(`[Wu] ✅ App state cleaned successfully for ${appName}`);
726
+
727
+ } catch (cleanupError) {
728
+ console.warn(`[Wu] ⚠️ App cleanup partial failure for ${appName}:`, cleanupError);
729
+
730
+ // 🌌 Emergency cleanup - force clear everything
731
+ try {
732
+ const container = document.querySelector(containerSelector);
733
+ if (container) {
734
+ container.style.display = 'none';
735
+ setTimeout(() => {
736
+ if (container) {
737
+ container.style.display = '';
738
+ }
739
+ }, 100);
740
+ }
741
+ } catch (emergencyError) {
742
+ console.error(`[Wu] 💥 Emergency cleanup failed:`, emergencyError);
743
+ }
744
+ }
745
+ }
746
+
747
+ /**
748
+ * 💥 ERROR STATE RENDERER: Visual error manifestation
749
+ */
750
+ async renderErrorState(containerSelector, appName, error) {
751
+ try {
752
+ const container = document.querySelector(containerSelector);
753
+ if (!container) return;
754
+
755
+ // 🛡️ Clear container safely
756
+ container.innerHTML = '';
757
+
758
+ // 🌌 Create quantum error visualization with safe DOM manipulation
759
+ const errorContainer = document.createElement('div');
760
+ errorContainer.className = 'quantum-error-state';
761
+
762
+ // Apply styles programmatically
763
+ Object.assign(errorContainer.style, {
764
+ padding: '2rem',
765
+ borderRadius: '12px',
766
+ background: 'linear-gradient(135deg, #1a1a2e, #16213e)',
767
+ border: '2px solid #ff6b6b',
768
+ color: '#fff',
769
+ fontFamily: '"Courier New", monospace',
770
+ textAlign: 'center',
771
+ boxShadow: '0 0 20px rgba(255, 107, 107, 0.3)'
772
+ });
773
+
774
+ // Create elements safely
775
+ const icon = document.createElement('div');
776
+ icon.textContent = '🌌';
777
+ Object.assign(icon.style, {
778
+ fontSize: '3rem',
779
+ marginBottom: '1rem'
780
+ });
781
+
782
+ const title = document.createElement('h3');
783
+ title.textContent = 'MOUNT ERROR DETECTED';
784
+ Object.assign(title.style, {
785
+ color: '#ff6b6b',
786
+ margin: '0 0 1rem 0'
787
+ });
788
+
789
+ const description = document.createElement('p');
790
+ description.textContent = `App "${appName}" failed to mount in the container`;
791
+ Object.assign(description.style, {
792
+ margin: '0 0 1rem 0',
793
+ opacity: '0.8'
794
+ });
795
+
796
+ const details = document.createElement('details');
797
+ Object.assign(details.style, {
798
+ margin: '1rem 0',
799
+ textAlign: 'left'
800
+ });
801
+
802
+ const summary = document.createElement('summary');
803
+ summary.textContent = '🔍 Debug Info';
804
+ Object.assign(summary.style, {
805
+ cursor: 'pointer',
806
+ color: '#4ecdc4'
807
+ });
808
+
809
+ const pre = document.createElement('pre');
810
+ pre.textContent = error.message; // Safe text content, no HTML injection
811
+ Object.assign(pre.style, {
812
+ background: 'rgba(0,0,0,0.3)',
813
+ padding: '1rem',
814
+ borderRadius: '6px',
815
+ marginTop: '0.5rem',
816
+ overflow: 'auto',
817
+ fontSize: '0.8rem'
818
+ });
819
+
820
+ const button = document.createElement('button');
821
+ button.textContent = '🔄 Reload Page';
822
+ Object.assign(button.style, {
823
+ background: 'linear-gradient(45deg, #4ecdc4, #44a08d)',
824
+ border: 'none',
825
+ padding: '0.8rem 1.5rem',
826
+ borderRadius: '6px',
827
+ color: 'white',
828
+ cursor: 'pointer',
829
+ fontWeight: 'bold',
830
+ transition: 'transform 0.2s'
831
+ });
832
+
833
+ // Add safe event listeners
834
+ button.addEventListener('click', () => window.location.reload());
835
+ button.addEventListener('mouseenter', () => button.style.transform = 'scale(1.05)');
836
+ button.addEventListener('mouseleave', () => button.style.transform = 'scale(1)');
837
+
838
+ // Assemble DOM structure
839
+ details.appendChild(summary);
840
+ details.appendChild(pre);
841
+
842
+ errorContainer.appendChild(icon);
843
+ errorContainer.appendChild(title);
844
+ errorContainer.appendChild(description);
845
+ errorContainer.appendChild(details);
846
+ errorContainer.appendChild(button);
847
+
848
+ container.appendChild(errorContainer);
849
+
850
+ } catch (renderError) {
851
+ console.error(`[Wu] 💥 Failed to render error state:`, renderError);
852
+ }
853
+ }
854
+
855
+ /**
856
+ * 📦 REMOTE APP LOADER: Intelligent remote app loading with path resolution
857
+ * @param {Object} app - Información de la app
858
+ * @param {Object} sandbox - Sandbox creado
859
+ */
860
+ async loadAndMountRemoteApp(app, sandbox) {
861
+ // 🔍 SMART PATH RESOLUTION: Multi-path URL construction with validation
862
+ const moduleUrl = await this.resolveModulePath(app);
863
+
864
+ console.log(`[Wu] 📦 Loading ES module: ${moduleUrl}`);
865
+
866
+ try {
867
+ // 🌟 MODULE LOADING: Multi-path module resolution
868
+ await this.moduleLoader(moduleUrl, app.name);
869
+
870
+ console.log(`[Wu] ✅ ES module loaded: ${app.name}`);
871
+
872
+ } catch (error) {
873
+ console.error(`[Wu] ❌ Failed to load ES module ${moduleUrl}:`, error);
874
+ throw error;
875
+ }
876
+ }
877
+
878
+ /**
879
+ * 🔍 MODULE PATH RESOLVER: Intelligent URL construction with fallback
880
+ * Intelligently resolves module paths with real-time validation
881
+ */
882
+ async resolveModulePath(app) {
883
+ let entryFile = app.manifest?.entry || 'main.js';
884
+ const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
885
+
886
+ // 🔧 NORMALIZE PATH: Remove duplicated directories
887
+ // If entry already starts with 'src/', 'dist/', etc., use it as-is
888
+ const hasFolderPrefix = /^(src|dist|public|build|assets|lib|es)\//.test(entryFile);
889
+
890
+ if (hasFolderPrefix) {
891
+ console.log(`[Wu] 🔧 Entry already has folder prefix: ${entryFile}`);
892
+ // Entry already has folder, just use baseUrl + entryFile
893
+ const directPath = `${baseUrl}/${entryFile}`;
894
+ console.log(`[Wu] 🎯 Using direct path: ${directPath}`);
895
+ return directPath;
896
+ }
897
+
898
+ // 🔍 Multi-path candidates (in order of preference)
899
+ const pathCandidates = [
900
+ `${baseUrl}/src/${entryFile}`, // Standard structure
901
+ `${baseUrl}/${entryFile}`, // Root level
902
+ `${baseUrl}/dist/${entryFile}`, // Built version
903
+ `${baseUrl}/public/${entryFile}`, // Public folder
904
+ `${baseUrl}/build/${entryFile}`, // Build folder
905
+ `${baseUrl}/assets/${entryFile}`, // Assets folder
906
+ `${baseUrl}/lib/${entryFile}`, // Library folder
907
+ `${baseUrl}/es/${entryFile}` // ES modules folder
908
+ ];
909
+
910
+ console.log(`[Wu] 🔍 Attempting path resolution for ${app.name}...`);
911
+
912
+ // 🚀 SMART PATH DISCOVERY: Try each candidate with validation
913
+ for (let i = 0; i < pathCandidates.length; i++) {
914
+ const candidate = pathCandidates[i];
915
+
916
+ try {
917
+ console.log(`[Wu] 🎯 Testing path candidate ${i + 1}/${pathCandidates.length}: ${candidate}`);
918
+
919
+ // 🌟 Path validation with enhanced verification
920
+ const isValid = await this.validatePath(candidate);
921
+
922
+ if (isValid) {
923
+ console.log(`[Wu] ✅ Path resolved successfully: ${candidate}`);
924
+ return candidate;
925
+ } else {
926
+ console.log(`[Wu] ❌ Path candidate ${i + 1} failed validation: ${candidate}`);
927
+ }
928
+
929
+ } catch (error) {
930
+ console.log(`[Wu] ⚠️ Path candidate ${i + 1} threw error: ${candidate} - ${error.message}`);
931
+ continue;
932
+ }
933
+ }
934
+
935
+ // 🌌 FALLBACK: If all candidates fail, use the first one and let the error bubble up
936
+ const fallbackPath = pathCandidates[0];
937
+ console.warn(`[Wu] 🚨 All path candidates failed, using fallback: ${fallbackPath}`);
938
+ return fallbackPath;
939
+ }
940
+
941
+ /**
942
+ * 🔧 PATH VALIDATOR: Smart existence verification with module testing
943
+ * Validates if a path exists and can be loaded as an ES module
944
+ */
945
+ async validatePath(url) {
946
+ try {
947
+ // 🌟 ENHANCED VALIDATION: Try actual module import for reliable verification
948
+ console.log(`[Wu] 🔍 Testing path: ${url}`);
949
+
950
+ // 🚀 First, try a GET request to check if file exists and is accessible
951
+ const response = await fetch(url, {
952
+ method: 'GET',
953
+ cache: 'no-cache',
954
+ signal: AbortSignal.timeout(2000) // 2 second timeout
955
+ });
956
+
957
+ if (!response.ok) {
958
+ console.log(`[Wu] ❌ Path validation failed - HTTP ${response.status}: ${url}`);
959
+ return false;
960
+ }
961
+
962
+ // 🎯 Check content type and file extension
963
+ const contentType = response.headers.get('content-type') || '';
964
+ const isJavaScript =
965
+ contentType.includes('javascript') ||
966
+ contentType.includes('module') ||
967
+ contentType.includes('text/plain') || // Some servers serve JS as plain text
968
+ url.endsWith('.js') ||
969
+ url.endsWith('.mjs');
970
+
971
+ if (!isJavaScript) {
972
+ console.log(`[Wu] ❌ Path validation failed - Invalid content type '${contentType}': ${url}`);
973
+ return false;
974
+ }
975
+
976
+ // 🌌 FINAL VERIFICATION: Check if content looks like a valid module
977
+ const content = await response.text();
978
+
979
+ // 🚫 DETECT HTML FALLBACK: Check if server returned HTML instead of JS
980
+ const isHtmlFallback =
981
+ content.includes('<!doctype') ||
982
+ content.includes('<html') ||
983
+ content.includes('<head>') ||
984
+ content.includes('<body>') ||
985
+ (content.includes('<') && content.includes('>') && content.length > 200);
986
+
987
+ if (isHtmlFallback) {
988
+ console.log(`[Wu] ❌ Path validation failed - Server returned HTML fallback page: ${url}`);
989
+ return false;
990
+ }
991
+
992
+ // 🎯 Check for valid JavaScript module content
993
+ const hasModuleContent =
994
+ content.includes('export') ||
995
+ content.includes('import') ||
996
+ content.includes('wu.define') ||
997
+ content.includes('module.exports') ||
998
+ content.includes('console.log') ||
999
+ (content.includes('function') && content.length > 10);
1000
+
1001
+ if (!hasModuleContent) {
1002
+ console.log(`[Wu] ❌ Path validation failed - No valid module content: ${url}`);
1003
+ console.log(`[Wu] 🔍 Content preview: ${content.substring(0, 100)}...`);
1004
+ return false;
1005
+ }
1006
+
1007
+ console.log(`[Wu] ✅ Path validation successful: ${url} (${content.length} chars)`);
1008
+ return true;
1009
+
1010
+ } catch (error) {
1011
+ // 🚫 Network, timeout, or parsing error means path is invalid
1012
+ console.log(`[Wu] ❌ Path validation failed for ${url}: ${error.message}`);
1013
+ return false;
1014
+ }
1015
+ }
1016
+
1017
+ /**
1018
+ * 📦 MODULE LOADER: Advanced registration patterns
1019
+ * Handles asynchronous registration with timing synchronization
1020
+ */
1021
+ async moduleLoader(moduleUrl, appName) {
1022
+ // ✅ Check if already registered
1023
+ if (this.definitions.has(appName)) {
1024
+ console.log(`[Wu] ⚡ App ${appName} already registered`);
1025
+ return;
1026
+ }
1027
+
1028
+ console.log(`[Wu] 📡 Using event-based registration for ${appName}`);
1029
+
1030
+ // 🔔 Use event-based waiting (no polling!)
1031
+ const registrationPromise = this.registry.waitForApp(appName, 10000);
1032
+
1033
+ // 🚀 Load module asynchronously
1034
+ const moduleLoadPromise = import(/* @vite-ignore */ moduleUrl);
1035
+
1036
+ // 🌌 Wait for both operations to complete
1037
+ await Promise.all([moduleLoadPromise, registrationPromise]);
1038
+
1039
+ console.log(`[Wu] ✅ App ${appName} loaded and registered via events`);
1040
+ }
1041
+
1042
+ /**
1043
+ * Desmontar una aplicación
1044
+ * @param {string} appName - Nombre de la app
1045
+ */
1046
+ async unmount(appName) {
1047
+ try {
1048
+ console.log(`[Wu] 🗑️ Unmounting ${appName}`);
1049
+
1050
+ const mounted = this.mounted.get(appName);
1051
+ if (!mounted) {
1052
+ console.warn(`[Wu] App ${appName} not mounted`);
1053
+ return;
1054
+ }
1055
+
1056
+ // 🪝 Execute beforeUnmount hooks
1057
+ const beforeUnmountResult = await this.hooks.execute('beforeUnmount', { appName, mounted });
1058
+ if (beforeUnmountResult.cancelled) {
1059
+ console.warn('[Wu] Unmount cancelled by beforeUnmount hook');
1060
+ return;
1061
+ }
1062
+
1063
+ // 🔌 Call plugin beforeUnmount hooks
1064
+ const pluginBeforeUnmount = await this.pluginSystem.callHook('beforeUnmount', { appName });
1065
+ if (pluginBeforeUnmount === false) {
1066
+ console.warn('[Wu] Unmount cancelled by plugin beforeUnmount hook');
1067
+ return;
1068
+ }
1069
+
1070
+ // Ejecutar unmount del lifecycle si existe
1071
+ if (mounted.lifecycle?.unmount) {
1072
+ await mounted.lifecycle.unmount(mounted.container);
1073
+ }
1074
+
1075
+ // Limpiar sandbox
1076
+ this.sandbox.cleanup(mounted.sandbox);
1077
+
1078
+ // 🏊 Release sandbox to pool
1079
+ if (mounted.poolSandbox) {
1080
+ this.sandboxPool.release(appName);
1081
+ }
1082
+
1083
+ // Remover del registro de montadas
1084
+ this.mounted.delete(appName);
1085
+
1086
+ // 🪝 Execute afterUnmount hooks
1087
+ await this.hooks.execute('afterUnmount', { appName });
1088
+
1089
+ // 🔌 Call plugin afterUnmount hooks
1090
+ await this.pluginSystem.callHook('afterUnmount', { appName });
1091
+
1092
+ // 📢 Emit unmount event
1093
+ this.eventBus.emit('app:unmounted', { appName }, { appName });
1094
+
1095
+ console.log(`[Wu] ✅ ${appName} unmounted successfully`);
1096
+ } catch (error) {
1097
+ console.error(`[Wu] ❌ Failed to unmount ${appName}:`, error);
1098
+
1099
+ // 🔌 Call plugin error hooks
1100
+ await this.pluginSystem.callHook('onError', { phase: 'unmount', error, appName });
1101
+
1102
+ // 📢 Emit error event
1103
+ this.eventBus.emit('app:error', { appName, error: error.message }, { appName });
1104
+ throw error;
1105
+ }
1106
+ }
1107
+
1108
+ /**
1109
+ * Cargar componente compartido (para imports/exports)
1110
+ * @param {string} componentPath - Ruta del componente (ej: "shared.Button")
1111
+ */
1112
+ async use(componentPath) {
1113
+ const [appName, componentName] = componentPath.split('.');
1114
+
1115
+ if (!appName || !componentName) {
1116
+ throw new Error(`Invalid component path: ${componentPath}. Use format "app.component"`);
1117
+ }
1118
+
1119
+ const app = this.apps.get(appName);
1120
+ if (!app) {
1121
+ throw new Error(`App ${appName} not registered`);
1122
+ }
1123
+
1124
+ const manifest = this.manifests.get(appName);
1125
+ const exportPath = manifest?.wu?.exports?.[componentName];
1126
+
1127
+ if (!exportPath) {
1128
+ throw new Error(`Component ${componentName} not exported by ${appName}`);
1129
+ }
1130
+
1131
+ // Cargar componente
1132
+ return await this.loader.loadComponent(app.url, exportPath);
1133
+ }
1134
+
1135
+ /**
1136
+ * Obtener información de una app
1137
+ * @param {string} appName - Nombre de la app
1138
+ */
1139
+ getAppInfo(appName) {
1140
+ return {
1141
+ registered: this.apps.get(appName),
1142
+ manifest: this.manifests.get(appName),
1143
+ mounted: this.mounted.get(appName),
1144
+ definition: this.definitions.get(appName)
1145
+ };
1146
+ }
1147
+
1148
+ /**
1149
+ * Obtener estadísticas del framework
1150
+ */
1151
+ getStats() {
1152
+ return {
1153
+ registered: this.apps.size,
1154
+ defined: this.definitions.size,
1155
+ mounted: this.mounted.size,
1156
+ apps: Array.from(this.apps.keys())
1157
+ };
1158
+ }
1159
+
1160
+ /**
1161
+ * 🏪 STORE METHODS: Convenience methods for state management
1162
+ */
1163
+
1164
+ /**
1165
+ * Get value from global store
1166
+ * @param {string} path - Dot notation path
1167
+ * @returns {*} Value at path
1168
+ */
1169
+ getState(path) {
1170
+ return this.store.get(path);
1171
+ }
1172
+
1173
+ /**
1174
+ * Set value in global store
1175
+ * @param {string} path - Dot notation path
1176
+ * @param {*} value - Value to set
1177
+ * @returns {number} Sequence number
1178
+ */
1179
+ setState(path, value) {
1180
+ return this.store.set(path, value);
1181
+ }
1182
+
1183
+ /**
1184
+ * Subscribe to state changes
1185
+ * @param {string} pattern - Path or pattern
1186
+ * @param {Function} callback - Callback function
1187
+ * @returns {Function} Unsubscribe function
1188
+ */
1189
+ onStateChange(pattern, callback) {
1190
+ return this.store.on(pattern, callback);
1191
+ }
1192
+
1193
+ /**
1194
+ * Batch set multiple state values
1195
+ * @param {Object} updates - Object with path:value pairs
1196
+ * @returns {Array} Sequence numbers
1197
+ */
1198
+ batchState(updates) {
1199
+ return this.store.batch(updates);
1200
+ }
1201
+
1202
+ /**
1203
+ * Get store metrics
1204
+ * @returns {Object} Performance metrics
1205
+ */
1206
+ getStoreMetrics() {
1207
+ return this.store.getMetrics();
1208
+ }
1209
+
1210
+ /**
1211
+ * Clear all state
1212
+ */
1213
+ clearState() {
1214
+ this.store.clear();
1215
+ }
1216
+
1217
+ /**
1218
+ * 🎯 SIMPLIFIED API: Create WuApp instance for declarative usage
1219
+ * @param {string} name - App name
1220
+ * @param {Object} config - Configuration { url, container, autoInit }
1221
+ * @returns {WuApp} WuApp instance
1222
+ */
1223
+ app(name, config) {
1224
+ return new WuApp(name, config, this);
1225
+ }
1226
+
1227
+ /**
1228
+ * Limpiar todo el framework
1229
+ */
1230
+ async destroy() {
1231
+ console.log('[Wu] 🧹 Destroying framework...');
1232
+
1233
+ try {
1234
+ // 🪝 Execute beforeDestroy hooks
1235
+ await this.hooks.execute('beforeDestroy', {});
1236
+
1237
+ // 🔌 Call plugin onDestroy hooks
1238
+ await this.pluginSystem.callHook('onDestroy', {});
1239
+
1240
+ // Limpiar health monitor
1241
+ if (this.healthState.monitor) {
1242
+ clearInterval(this.healthState.monitor);
1243
+ this.healthState.monitor = null;
1244
+ }
1245
+
1246
+ // Limpiar MutationObserver
1247
+ if (this.healthState.mutationObserver) {
1248
+ this.healthState.mutationObserver.disconnect();
1249
+ this.healthState.mutationObserver = null;
1250
+ }
1251
+
1252
+ // Limpiar timeouts pendientes
1253
+ if (this.healthState.mutationCheckTimeout) {
1254
+ clearTimeout(this.healthState.mutationCheckTimeout);
1255
+ }
1256
+
1257
+ // Desmontar todas las apps
1258
+ for (const appName of this.mounted.keys()) {
1259
+ await this.unmount(appName);
1260
+ }
1261
+
1262
+ // 🧹 Limpiar sistemas esenciales
1263
+ this.cache.clear();
1264
+ this.eventBus.removeAll();
1265
+ this.eventBus.clearHistory();
1266
+ this.performance.clearMetrics();
1267
+
1268
+ // 🧹 Limpiar advanced systems
1269
+ this.pluginSystem.cleanup();
1270
+ this.strategies.cleanup();
1271
+ this.errorBoundary.cleanup();
1272
+ this.hooks.cleanup();
1273
+ this.sandboxPool.cleanup();
1274
+ this.registry.cleanup();
1275
+
1276
+ // Limpiar registros
1277
+ this.apps.clear();
1278
+ this.definitions.clear();
1279
+ this.manifests.clear();
1280
+ this.mounted.clear();
1281
+
1282
+ // Limpiar store
1283
+ this.store.clear();
1284
+
1285
+ this.isInitialized = false;
1286
+
1287
+ // 🪝 Execute afterDestroy hooks
1288
+ await this.hooks.execute('afterDestroy', {});
1289
+
1290
+ console.log('[Wu] ✅ Framework destroyed');
1291
+ } catch (error) {
1292
+ console.error('[Wu] ❌ Error during destroy:', error);
1293
+ throw error;
1294
+ }
1295
+ }
1296
+ }