wu-framework 1.1.16 → 1.1.18

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 (51) hide show
  1. package/README.md +52 -20
  2. package/package.json +48 -3
  3. package/src/adapters/alpine/index.d.ts +60 -0
  4. package/src/adapters/alpine/index.js +231 -0
  5. package/src/adapters/alpine.d.ts +3 -0
  6. package/src/adapters/alpine.js +3 -0
  7. package/src/adapters/htmx/index.d.ts +60 -0
  8. package/src/adapters/htmx/index.js +242 -0
  9. package/src/adapters/htmx.d.ts +3 -0
  10. package/src/adapters/htmx.js +3 -0
  11. package/src/adapters/index.js +60 -3
  12. package/src/adapters/qwik/index.d.ts +52 -0
  13. package/src/adapters/qwik/index.js +214 -0
  14. package/src/adapters/qwik.d.ts +3 -0
  15. package/src/adapters/qwik.js +3 -0
  16. package/src/adapters/react/ai.js +135 -135
  17. package/src/adapters/react/index.d.ts +246 -246
  18. package/src/adapters/react/index.js +695 -695
  19. package/src/adapters/stencil/index.d.ts +54 -0
  20. package/src/adapters/stencil/index.js +228 -0
  21. package/src/adapters/stencil.d.ts +3 -0
  22. package/src/adapters/stencil.js +3 -0
  23. package/src/adapters/stimulus/index.d.ts +60 -0
  24. package/src/adapters/stimulus/index.js +255 -0
  25. package/src/adapters/stimulus.d.ts +3 -0
  26. package/src/adapters/stimulus.js +3 -0
  27. package/src/adapters/svelte/index.js +1 -1
  28. package/src/adapters/vanilla/index.js +1 -1
  29. package/src/adapters/vue/index.js +8 -0
  30. package/src/core/wu-cache.js +24 -3
  31. package/src/core/wu-core.js +15 -1
  32. package/src/core/wu-error-boundary.js +17 -3
  33. package/src/core/wu-event-bus.js +43 -1
  34. package/src/core/wu-html-parser.js +13 -4
  35. package/src/core/wu-loader.js +162 -50
  36. package/src/core/wu-logger.js +21 -13
  37. package/src/core/wu-manifest.js +23 -0
  38. package/src/core/wu-plugin.js +57 -4
  39. package/src/core/wu-proxy-sandbox.js +2 -1
  40. package/src/core/wu-script-executor.js +48 -0
  41. package/src/core/wu-store.js +13 -3
  42. package/src/index.d.ts +317 -0
  43. package/src/index.js +11 -1
  44. package/dist/wu-framework.cjs.js +0 -3
  45. package/dist/wu-framework.cjs.js.map +0 -1
  46. package/dist/wu-framework.dev.js +0 -15302
  47. package/dist/wu-framework.dev.js.map +0 -1
  48. package/dist/wu-framework.esm.js +0 -3
  49. package/dist/wu-framework.esm.js.map +0 -1
  50. package/dist/wu-framework.umd.js +0 -3
  51. package/dist/wu-framework.umd.js.map +0 -1
@@ -32,6 +32,9 @@ export class WuCache {
32
32
  cooldownUntil: 0
33
33
  };
34
34
 
35
+ // Rate limit notification flag (log only once per cooldown)
36
+ this._rateLimitNotified = false;
37
+
35
38
  // Memory cache
36
39
  this.memoryCache = new Map();
37
40
 
@@ -66,6 +69,7 @@ export class WuCache {
66
69
  }
67
70
  // Cooldown terminado
68
71
  this.rateLimiting.inCooldown = false;
72
+ this._rateLimitNotified = false;
69
73
  this.rateLimiting.operations = [];
70
74
  }
71
75
 
@@ -91,7 +95,22 @@ export class WuCache {
91
95
  }
92
96
 
93
97
  /**
94
- * 🔐 GET RATE LIMIT STATUS
98
+ * Handle rate-limited operations: log once per cooldown, return diagnostic object
99
+ * @param {string} operation - The operation that was rate limited ('get' or 'set')
100
+ * @param {string} key - The cache key that was rejected
101
+ * @returns {{ rateLimited: true, operation: string, key: string }}
102
+ */
103
+ _onRateLimited(operation, key) {
104
+ if (!this._rateLimitNotified) {
105
+ const cooldownRemaining = Math.max(0, this.rateLimiting.cooldownUntil - Date.now());
106
+ logger.warn(`[WuCache] Rate limited: ${operation} for key "${key}" rejected. ${cooldownRemaining}ms remaining in cooldown.`);
107
+ this._rateLimitNotified = true;
108
+ }
109
+ return { rateLimited: true, operation, key };
110
+ }
111
+
112
+ /**
113
+ * GET RATE LIMIT STATUS
95
114
  */
96
115
  getRateLimitStatus() {
97
116
  const now = Date.now();
@@ -115,7 +134,8 @@ export class WuCache {
115
134
  get(key) {
116
135
  // 🔐 Check rate limit
117
136
  if (!this._checkRateLimit()) {
118
- return null; // Silently fail on rate limit
137
+ this._onRateLimited('get', key);
138
+ return null;
119
139
  }
120
140
 
121
141
  // 1. Buscar en memoria
@@ -162,7 +182,8 @@ export class WuCache {
162
182
  set(key, value, ttl) {
163
183
  // 🔐 Check rate limit
164
184
  if (!this._checkRateLimit()) {
165
- return false; // Reject on rate limit
185
+ this._onRateLimited('set', key);
186
+ return false;
166
187
  }
167
188
 
168
189
  try {
@@ -333,6 +333,20 @@ export class WuCore {
333
333
  } catch (error) {
334
334
  logger.wuError(`Mount attempt ${attempt + 1} failed for ${appName}:`, error);
335
335
 
336
+ // Cleanup sandbox to prevent orphaned shadow DOMs
337
+ try {
338
+ if (this.sandbox && this.sandbox.sandboxes && this.sandbox.sandboxes.has(appName)) {
339
+ const sb = this.sandbox.sandboxes.get(appName);
340
+ if (sb && sb.proxySandbox) {
341
+ sb.proxySandbox.deactivate();
342
+ }
343
+ this.sandbox.sandboxes.delete(appName);
344
+ logger.wuDebug(`Sandbox cleaned up after mount failure for ${appName}`);
345
+ }
346
+ } catch (cleanupError) {
347
+ logger.wuWarn(`Sandbox cleanup failed for ${appName}:`, cleanupError);
348
+ }
349
+
336
350
  // Use error boundary for intelligent error handling
337
351
  const errorResult = await this.errorBoundary.handle(error, {
338
352
  appName,
@@ -671,7 +685,7 @@ export class WuCore {
671
685
  * Intelligently resolves module paths with real-time validation
672
686
  */
673
687
  async resolveModulePath(app) {
674
- let entryFile = app.manifest?.entry || 'main.js';
688
+ const entryFile = app.manifest?.entry || 'main.js';
675
689
  const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
676
690
 
677
691
  // Normalize path: Remove duplicated directories
@@ -314,19 +314,33 @@ export class WuErrorBoundary {
314
314
  * @param {Object} context - Contexto
315
315
  */
316
316
  logError(error, context) {
317
+ // Truncate stack to first 5 lines to prevent retaining large object references
318
+ const stack = error.stack ? error.stack.split('\n').slice(0, 5).join('\n') : '';
319
+
320
+ // Shallow-copy context to avoid retaining references to live objects
321
+ const safeContext = {};
322
+ for (const key of Object.keys(context)) {
323
+ const val = context[key];
324
+ if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val === null) {
325
+ safeContext[key] = val;
326
+ } else {
327
+ safeContext[key] = String(val);
328
+ }
329
+ }
330
+
317
331
  const errorEntry = {
318
332
  error: {
319
333
  name: error.name,
320
334
  message: error.message,
321
- stack: error.stack
335
+ stack
322
336
  },
323
- context,
337
+ context: safeContext,
324
338
  timestamp: Date.now()
325
339
  };
326
340
 
327
341
  this.errorLog.push(errorEntry);
328
342
 
329
- // Mantener límite de log
343
+ // Maintain log limit
330
344
  if (this.errorLog.length > this.maxErrorLog) {
331
345
  this.errorLog.shift();
332
346
  }
@@ -10,6 +10,25 @@
10
10
  */
11
11
  import { logger } from './wu-logger.js';
12
12
 
13
+ /**
14
+ * @typedef {Object} WuEvent
15
+ * @property {string} name - Event name
16
+ * @property {*} data - Event payload
17
+ * @property {number} timestamp - Event timestamp
18
+ * @property {string} appName - Source app name
19
+ * @property {Object} meta - Additional metadata
20
+ * @property {boolean} verified - Whether origin was verified
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} WuEventBusConfig
25
+ * @property {number} [maxHistory=100] - Maximum events in history
26
+ * @property {boolean} [enableReplay=true] - Enable event replay
27
+ * @property {boolean} [enableWildcards=true] - Enable wildcard matching
28
+ * @property {boolean} [logEvents=false] - Log all events
29
+ * @property {boolean} [strictMode=false] - Reject unauthorized events
30
+ * @property {boolean} [validateOrigin=true] - Validate event origins
31
+ */
13
32
 
14
33
  export class WuEventBus {
15
34
  constructor() {
@@ -20,16 +39,21 @@ export class WuEventBus {
20
39
  this.authorizedApps = new Map(); // appName -> { token, permissions }
21
40
  this.trustedEvents = new Set(['wu:*', 'system:*']); // Eventos del sistema
22
41
 
42
+ // Auto-detect production environment for strictMode default
43
+ const isProduction = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';
44
+
23
45
  this.config = {
24
46
  maxHistory: 100,
25
47
  enableReplay: true,
26
48
  enableWildcards: true,
27
49
  logEvents: false,
28
50
  // 🔐 Opciones de seguridad
29
- strictMode: false, // Si true, rechaza eventos de apps no autorizadas
51
+ strictMode: isProduction, // Auto-enabled in production, permissive in development
30
52
  validateOrigin: true // Valida que appName sea una app registrada
31
53
  };
32
54
 
55
+ this._permissiveWarned = false;
56
+
33
57
  this.stats = {
34
58
  emitted: 0,
35
59
  subscriptions: 0,
@@ -127,6 +151,19 @@ export class WuEventBus {
127
151
  return `wu_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
128
152
  }
129
153
 
154
+ /**
155
+ * WARN PERMISSIVE MODE: Log a one-time warning when strictMode is off
156
+ * Alerts developers that events are flowing without authorization checks
157
+ */
158
+ _warnPermissiveMode() {
159
+ if (this._permissiveWarned) return;
160
+ this._permissiveWarned = true;
161
+ logger.warn(
162
+ '[WuEventBus] strictMode is disabled. Events are emitted without authorization checks. ' +
163
+ 'Enable strictMode for production by calling enableStrictMode() or setting NODE_ENV=production.'
164
+ );
165
+ }
166
+
130
167
  /**
131
168
  * 📢 EMIT: Emitir evento con validación de origen
132
169
  * @param {string} eventName - Nombre del evento
@@ -136,6 +173,11 @@ export class WuEventBus {
136
173
  emit(eventName, data, options = {}) {
137
174
  const appName = options.appName || 'unknown';
138
175
 
176
+ // Warn once if running in permissive mode (strictMode off)
177
+ if (!this.config.strictMode) {
178
+ this._warnPermissiveMode();
179
+ }
180
+
139
181
  // 🔐 Validar origen si está habilitado
140
182
  if (this.config.validateOrigin && this.config.strictMode) {
141
183
  if (!this._validateOrigin(eventName, appName, options.token)) {
@@ -62,19 +62,28 @@ export class WuHtmlParser {
62
62
  return this._cache.get(cacheKey);
63
63
  }
64
64
 
65
- const temp = document.createElement('div');
66
- temp.innerHTML = html;
65
+ const parser = new DOMParser();
66
+ const doc = parser.parseFromString(html, 'text/html');
67
67
 
68
68
  const inlineScripts = [];
69
69
  const externalScripts = [];
70
70
  const inlineStyles = [];
71
71
  const externalStyles = [];
72
72
 
73
- this._extractResources(temp, {
73
+ const ctx = {
74
74
  inlineScripts, externalScripts,
75
75
  inlineStyles, externalStyles,
76
76
  baseUrl
77
- });
77
+ };
78
+
79
+ // DOMParser moves <style>, <link>, and some <script> tags to <head>.
80
+ // Extract resources from both head and body to capture everything.
81
+ if (doc.head) {
82
+ this._extractResources(doc.head, ctx);
83
+ }
84
+
85
+ const temp = doc.body || doc.documentElement;
86
+ this._extractResources(temp, ctx);
78
87
 
79
88
  const result = {
80
89
  dom: temp.innerHTML,
@@ -1,68 +1,177 @@
1
1
  /**
2
- * 🚀 WU-LOADER: SISTEMA DE CARGA DINÁMICA UNIVERSAL
2
+ * WU-LOADER: SISTEMA DE CARGA DINAMICA UNIVERSAL
3
3
  * Carga aplicaciones y componentes sin depender del framework
4
+ *
5
+ * Cache strategy: LRU with TTL eviction.
6
+ * Entries track lastAccess time. When the cache reaches maxCacheSize,
7
+ * the least recently accessed entry is evicted. Entries older than
8
+ * cacheTTL are treated as stale and removed on access or eviction.
4
9
  */
5
10
 
6
11
  import { logger } from './wu-logger.js';
7
12
 
13
+ /**
14
+ * @typedef {Object} WuLoaderOptions
15
+ * @property {number} [maxCacheSize=50] - Maximum cache entries
16
+ * @property {number} [cacheTTL=1800000] - Cache TTL in ms (default 30min)
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} WuLoaderStats
21
+ * @property {number} cached - Number of cached entries
22
+ * @property {number} loading - Number of in-flight loads
23
+ * @property {number} maxCacheSize - Max cache size setting
24
+ * @property {number} cacheTTL - Cache TTL setting
25
+ * @property {string[]} cacheKeys - Cached URL keys
26
+ */
27
+
8
28
  export class WuLoader {
9
- constructor() {
29
+ /**
30
+ * @param {Object} options
31
+ * @param {number} [options.maxCacheSize=50] - Maximum number of entries in the cache
32
+ * @param {number} [options.cacheTTL=1800000] - Time-to-live for cache entries in ms (default 30 minutes)
33
+ */
34
+ constructor(options = {}) {
35
+ this.maxCacheSize = options.maxCacheSize ?? 50;
36
+ this.cacheTTL = options.cacheTTL ?? 1800000;
10
37
  this.cache = new Map();
11
38
  this.loadingPromises = new Map();
12
39
 
13
- logger.debug('[WuLoader] 📦 Dynamic loader initialized');
40
+ logger.debug('[WuLoader] Dynamic loader initialized');
41
+ }
42
+
43
+ /**
44
+ * Read from cache with TTL validation and LRU access tracking.
45
+ * Returns undefined if the entry does not exist or has expired.
46
+ * @param {string} key
47
+ * @returns {string|undefined}
48
+ */
49
+ _cacheGet(key) {
50
+ if (!this.cache.has(key)) {
51
+ return undefined;
52
+ }
53
+
54
+ const entry = this.cache.get(key);
55
+ const now = Date.now();
56
+
57
+ if (now - entry.timestamp > this.cacheTTL) {
58
+ this.cache.delete(key);
59
+ logger.debug(`[WuLoader] Cache expired for: ${key}`);
60
+ return undefined;
61
+ }
62
+
63
+ // Promote: delete and re-insert so iteration order reflects recency.
64
+ // Map iteration order in JS follows insertion order, so the oldest
65
+ // inserted entry is always first -- exactly what we need for LRU eviction.
66
+ this.cache.delete(key);
67
+ entry.lastAccess = now;
68
+ this.cache.set(key, entry);
69
+
70
+ return entry.code;
71
+ }
72
+
73
+ /**
74
+ * Write to cache. Evicts stale and LRU entries before inserting.
75
+ * @param {string} key
76
+ * @param {string} code
77
+ */
78
+ _cacheSet(key, code) {
79
+ // If the key already exists, remove it first so re-insertion
80
+ // moves it to the end (most-recently-used position).
81
+ if (this.cache.has(key)) {
82
+ this.cache.delete(key);
83
+ }
84
+
85
+ this._evictIfNeeded();
86
+
87
+ const now = Date.now();
88
+ this.cache.set(key, {
89
+ code,
90
+ timestamp: now,
91
+ lastAccess: now
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Evict entries until cache is below maxCacheSize.
97
+ *
98
+ * Two-pass strategy:
99
+ * 1. Remove all expired entries (TTL exceeded).
100
+ * 2. If still at capacity, remove the least recently accessed entry.
101
+ * Because Map preserves insertion order and _cacheGet promotes on
102
+ * access, the first key from the iterator is always the LRU entry.
103
+ */
104
+ _evictIfNeeded() {
105
+ const now = Date.now();
106
+
107
+ // Pass 1: purge expired entries
108
+ for (const [key, entry] of this.cache) {
109
+ if (now - entry.timestamp > this.cacheTTL) {
110
+ this.cache.delete(key);
111
+ logger.debug(`[WuLoader] Evicted expired entry: ${key}`);
112
+ }
113
+ }
114
+
115
+ // Pass 2: evict LRU entries until we are under the limit
116
+ while (this.cache.size >= this.maxCacheSize) {
117
+ // Map.keys().next() gives us the oldest-inserted key (LRU)
118
+ const oldestKey = this.cache.keys().next().value;
119
+ this.cache.delete(oldestKey);
120
+ logger.debug(`[WuLoader] Evicted LRU entry: ${oldestKey}`);
121
+ }
14
122
  }
15
123
 
16
124
  /**
17
- * Cargar aplicación completa
18
- * @param {string} appUrl - URL base de la aplicación
19
- * @param {Object} manifest - Manifest de la aplicación
20
- * @returns {string} Código JavaScript de la aplicación
125
+ * Cargar aplicacion completa
126
+ * @param {string} appUrl - URL base de la aplicacion
127
+ * @param {Object} manifest - Manifest de la aplicacion
128
+ * @returns {string} Codigo JavaScript de la aplicacion
21
129
  */
22
130
  async loadApp(appUrl, manifest) {
23
131
  const entryFile = manifest?.entry || 'index.js';
24
132
  const fullUrl = `${appUrl}/${entryFile}`;
25
133
 
26
- logger.debug(`[WuLoader] 📥 Loading app from: ${fullUrl}`);
134
+ logger.debug(`[WuLoader] Loading app from: ${fullUrl}`);
27
135
 
28
136
  try {
29
- // Verificar cache
30
- if (this.cache.has(fullUrl)) {
31
- logger.debug(`[WuLoader] Cache hit for: ${fullUrl}`);
32
- return this.cache.get(fullUrl);
137
+ // Check cache with TTL and LRU tracking
138
+ const cached = this._cacheGet(fullUrl);
139
+ if (cached !== undefined) {
140
+ logger.debug(`[WuLoader] Cache hit for: ${fullUrl}`);
141
+ return cached;
33
142
  }
34
143
 
35
- // Verificar si ya está cargando
144
+ // Check if already loading
36
145
  if (this.loadingPromises.has(fullUrl)) {
37
- logger.debug(`[WuLoader] Loading in progress for: ${fullUrl}`);
146
+ logger.debug(`[WuLoader] Loading in progress for: ${fullUrl}`);
38
147
  return await this.loadingPromises.get(fullUrl);
39
148
  }
40
149
 
41
- // Crear promesa de carga
150
+ // Create loading promise
42
151
  const loadingPromise = this.fetchCode(fullUrl);
43
152
  this.loadingPromises.set(fullUrl, loadingPromise);
44
153
 
45
154
  const code = await loadingPromise;
46
155
 
47
- // Limpiar promesa de carga y cachear resultado
156
+ // Clean up loading promise and cache result
48
157
  this.loadingPromises.delete(fullUrl);
49
- this.cache.set(fullUrl, code);
158
+ this._cacheSet(fullUrl, code);
50
159
 
51
- logger.debug(`[WuLoader] App loaded successfully: ${fullUrl}`);
160
+ logger.debug(`[WuLoader] App loaded successfully: ${fullUrl}`);
52
161
  return code;
53
162
 
54
163
  } catch (error) {
55
164
  this.loadingPromises.delete(fullUrl);
56
- console.error(`[WuLoader] Failed to load app: ${fullUrl}`, error);
165
+ console.error(`[WuLoader] Failed to load app: ${fullUrl}`, error);
57
166
  throw new Error(`Failed to load app from ${fullUrl}: ${error.message}`);
58
167
  }
59
168
  }
60
169
 
61
170
  /**
62
- * Cargar componente específico
63
- * @param {string} appUrl - URL base de la aplicación
171
+ * Cargar componente especifico
172
+ * @param {string} appUrl - URL base de la aplicacion
64
173
  * @param {string} componentPath - Ruta del componente
65
- * @returns {Function} Función del componente
174
+ * @returns {Function} Funcion del componente
66
175
  */
67
176
  async loadComponent(appUrl, componentPath) {
68
177
  // Normalizar ruta del componente
@@ -76,13 +185,13 @@ export class WuLoader {
76
185
 
77
186
  const fullUrl = `${appUrl}/${normalizedPath}`;
78
187
 
79
- logger.debug(`[WuLoader] 🧩 Loading component from: ${fullUrl}`);
188
+ logger.debug(`[WuLoader] Loading component from: ${fullUrl}`);
80
189
 
81
190
  try {
82
- // Cargar código del componente
191
+ // Cargar codigo del componente
83
192
  const code = await this.loadCode(fullUrl);
84
193
 
85
- // Crear función que retorna el componente
194
+ // Crear funcion que retorna el componente
86
195
  const componentFunction = new Function('require', 'module', 'exports', `
87
196
  ${code}
88
197
  return typeof module.exports === 'function' ? module.exports :
@@ -99,39 +208,40 @@ export class WuLoader {
99
208
 
100
209
  const component = componentFunction(fakeRequire, fakeModule, fakeModule.exports);
101
210
 
102
- logger.debug(`[WuLoader] Component loaded: ${componentPath}`);
211
+ logger.debug(`[WuLoader] Component loaded: ${componentPath}`);
103
212
  return component;
104
213
 
105
214
  } catch (error) {
106
- console.error(`[WuLoader] Failed to load component: ${componentPath}`, error);
215
+ console.error(`[WuLoader] Failed to load component: ${componentPath}`, error);
107
216
  throw new Error(`Failed to load component ${componentPath}: ${error.message}`);
108
217
  }
109
218
  }
110
219
 
111
220
  /**
112
- * Cargar código con cache
221
+ * Cargar codigo con cache
113
222
  * @param {string} url - URL del archivo
114
- * @returns {string} Código JavaScript
223
+ * @returns {string} Codigo JavaScript
115
224
  */
116
225
  async loadCode(url) {
117
- // Verificar cache
118
- if (this.cache.has(url)) {
119
- return this.cache.get(url);
226
+ // Check cache with TTL and LRU tracking
227
+ const cached = this._cacheGet(url);
228
+ if (cached !== undefined) {
229
+ return cached;
120
230
  }
121
231
 
122
- // Verificar si ya está cargando
232
+ // Check if already loading
123
233
  if (this.loadingPromises.has(url)) {
124
234
  return await this.loadingPromises.get(url);
125
235
  }
126
236
 
127
- // Crear promesa de carga
237
+ // Create loading promise
128
238
  const loadingPromise = this.fetchCode(url);
129
239
  this.loadingPromises.set(url, loadingPromise);
130
240
 
131
241
  try {
132
242
  const code = await loadingPromise;
133
243
  this.loadingPromises.delete(url);
134
- this.cache.set(url, code);
244
+ this._cacheSet(url, code);
135
245
  return code;
136
246
  } catch (error) {
137
247
  this.loadingPromises.delete(url);
@@ -140,9 +250,9 @@ export class WuLoader {
140
250
  }
141
251
 
142
252
  /**
143
- * Realizar fetch del código
253
+ * Realizar fetch del codigo
144
254
  * @param {string} url - URL del archivo
145
- * @returns {string} Código JavaScript
255
+ * @returns {string} Codigo JavaScript
146
256
  */
147
257
  async fetchCode(url) {
148
258
  const response = await fetch(url, {
@@ -170,25 +280,25 @@ export class WuLoader {
170
280
  * @param {Array} appConfigs - Configuraciones de aplicaciones
171
281
  */
172
282
  async preload(appConfigs) {
173
- logger.debug(`[WuLoader] 🚀 Preloading ${appConfigs.length} apps...`);
283
+ logger.debug(`[WuLoader] Preloading ${appConfigs.length} apps...`);
174
284
 
175
285
  const preloadPromises = appConfigs.map(async (config) => {
176
286
  try {
177
287
  await this.loadApp(config.url, config.manifest);
178
- logger.debug(`[WuLoader] Preloaded: ${config.name}`);
288
+ logger.debug(`[WuLoader] Preloaded: ${config.name}`);
179
289
  } catch (error) {
180
- logger.warn(`[WuLoader] ⚠️ Failed to preload ${config.name}:`, error.message);
290
+ logger.warn(`[WuLoader] Failed to preload ${config.name}:`, error.message);
181
291
  }
182
292
  });
183
293
 
184
294
  await Promise.allSettled(preloadPromises);
185
- logger.debug(`[WuLoader] 🎉 Preload completed`);
295
+ logger.debug(`[WuLoader] Preload completed`);
186
296
  }
187
297
 
188
298
  /**
189
- * Verificar si una URL está disponible
299
+ * Verificar si una URL esta disponible
190
300
  * @param {string} url - URL a verificar
191
- * @returns {boolean} True si está disponible
301
+ * @returns {boolean} True si esta disponible
192
302
  */
193
303
  async isAvailable(url) {
194
304
  try {
@@ -232,9 +342,9 @@ export class WuLoader {
232
342
  try {
233
343
  const component = await this.loadComponent(app.url, exportPath);
234
344
  resolved.set(importPath, component);
235
- logger.debug(`[WuLoader] Resolved dependency: ${importPath}`);
345
+ logger.debug(`[WuLoader] Resolved dependency: ${importPath}`);
236
346
  } catch (error) {
237
- console.error(`[WuLoader] Failed to resolve: ${importPath}`, error);
347
+ console.error(`[WuLoader] Failed to resolve: ${importPath}`, error);
238
348
  }
239
349
  }
240
350
 
@@ -243,7 +353,7 @@ export class WuLoader {
243
353
 
244
354
  /**
245
355
  * Limpiar cache
246
- * @param {string} pattern - Patrón opcional para limpiar URLs específicas
356
+ * @param {string} pattern - Patron opcional para limpiar URLs especificas
247
357
  */
248
358
  clearCache(pattern) {
249
359
  if (pattern) {
@@ -251,23 +361,25 @@ export class WuLoader {
251
361
  for (const [url] of this.cache) {
252
362
  if (regex.test(url)) {
253
363
  this.cache.delete(url);
254
- logger.debug(`[WuLoader] 🗑️ Cleared cache for: ${url}`);
364
+ logger.debug(`[WuLoader] Cleared cache for: ${url}`);
255
365
  }
256
366
  }
257
367
  } else {
258
368
  this.cache.clear();
259
- logger.debug(`[WuLoader] 🗑️ Cache cleared completely`);
369
+ logger.debug(`[WuLoader] Cache cleared completely`);
260
370
  }
261
371
  }
262
372
 
263
373
  /**
264
- * Obtener estadísticas del loader
374
+ * Obtener estadisticas del loader
265
375
  */
266
376
  getStats() {
267
377
  return {
268
378
  cached: this.cache.size,
379
+ maxCacheSize: this.maxCacheSize,
380
+ cacheTTL: this.cacheTTL,
269
381
  loading: this.loadingPromises.size,
270
382
  cacheKeys: Array.from(this.cache.keys())
271
383
  };
272
384
  }
273
- }
385
+ }
@@ -23,19 +23,27 @@ export class WuLogger {
23
23
  * Detectar si estamos en desarrollo
24
24
  */
25
25
  detectEnvironment() {
26
- // Múltiples formas de detectar desarrollo
27
- return (
28
- // Vite development
29
- window.location.hostname === 'localhost' ||
30
- window.location.hostname === '127.0.0.1' ||
31
- window.location.port !== '' ||
32
- // NODE_ENV si está disponible
33
- (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') ||
34
- // URL params para forzar debug
35
- new URLSearchParams(window.location.search).has('wu-debug') ||
36
- // Manual override
37
- window.WU_DEBUG === true
38
- );
26
+ // 1. Explicit flag takes priority
27
+ if (typeof window !== 'undefined' && window.WU_DEBUG === true) return true;
28
+ if (typeof window !== 'undefined' && window.WU_DEBUG === false) return false;
29
+
30
+ // 2. NODE_ENV check (works in bundlers and Node)
31
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') return false;
32
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') return true;
33
+
34
+ // 3. Browser heuristics (only if window exists)
35
+ if (typeof window !== 'undefined' && window.location) {
36
+ const hostname = window.location.hostname;
37
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') return true;
38
+
39
+ // URL param override
40
+ try {
41
+ if (new URLSearchParams(window.location.search).has('wu-debug')) return true;
42
+ } catch {}
43
+ }
44
+
45
+ // 4. Default: assume production
46
+ return false;
39
47
  }
40
48
 
41
49
  /**
@@ -302,6 +302,29 @@ export class WuManifest {
302
302
  }
303
303
  }
304
304
 
305
+ // Validate optional fields
306
+ if (manifest.styleMode !== undefined) {
307
+ const validModes = ['shared', 'isolated', 'fully-isolated'];
308
+ if (!validModes.includes(manifest.styleMode)) {
309
+ logger.warn(`[WuManifest] Invalid styleMode "${manifest.styleMode}", defaulting to "shared". Valid: ${validModes.join(', ')}`);
310
+ manifest.styleMode = 'shared';
311
+ }
312
+ }
313
+
314
+ if (manifest.version !== undefined && typeof manifest.version !== 'string') {
315
+ logger.warn('[WuManifest] version must be a string, ignoring');
316
+ delete manifest.version;
317
+ }
318
+
319
+ if (manifest.folder !== undefined) {
320
+ if (typeof manifest.folder !== 'string') {
321
+ logger.warn('[WuManifest] folder must be a string, ignoring');
322
+ delete manifest.folder;
323
+ } else if (this._hasDangerousPatterns(manifest.folder)) {
324
+ throw new Error('folder contains dangerous patterns');
325
+ }
326
+ }
327
+
305
328
  // Normalizar y limpiar manifest
306
329
  return this.normalize(manifest);
307
330
  }