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,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🏊 WU-SANDBOX-POOL: VIRTUAL SANDBOX POOL
|
|
3
|
+
*
|
|
4
|
+
* Object Pool pattern para reutilizar sandboxes:
|
|
5
|
+
* - Reduce memory usage
|
|
6
|
+
* - Reduce GC pressure
|
|
7
|
+
* - Mejora performance de mount/unmount
|
|
8
|
+
* - Auto-cleanup de sandboxes inactivos
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class WuSandboxPool {
|
|
12
|
+
constructor(core) {
|
|
13
|
+
this.core = core;
|
|
14
|
+
|
|
15
|
+
// Pool configuration
|
|
16
|
+
this.config = {
|
|
17
|
+
minSize: 2, // Mínimo de sandboxes en pool
|
|
18
|
+
maxSize: 10, // Máximo de sandboxes en pool
|
|
19
|
+
idleTimeout: 30000, // Tiempo antes de limpiar sandbox (30s)
|
|
20
|
+
warmupOnInit: true // Crear sandboxes al inicializar
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Pool state
|
|
24
|
+
this.available = []; // Sandboxes disponibles
|
|
25
|
+
this.inUse = new Map(); // Sandboxes en uso: appName -> sandbox
|
|
26
|
+
this.totalCreated = 0;
|
|
27
|
+
this.totalReused = 0;
|
|
28
|
+
|
|
29
|
+
// Cleanup timer
|
|
30
|
+
this.cleanupTimer = null;
|
|
31
|
+
|
|
32
|
+
console.log('[WuSandboxPool] 🏊 Sandbox pool initialized');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 🚀 INIT: Inicializar pool
|
|
37
|
+
*/
|
|
38
|
+
async init() {
|
|
39
|
+
if (this.config.warmupOnInit) {
|
|
40
|
+
await this.warmup();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Iniciar cleanup periódico
|
|
44
|
+
this.startCleanupTimer();
|
|
45
|
+
|
|
46
|
+
console.log('[WuSandboxPool] Pool ready');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 🔥 WARMUP: Pre-crear sandboxes
|
|
51
|
+
*/
|
|
52
|
+
async warmup() {
|
|
53
|
+
console.log(`[WuSandboxPool] Warming up pool with ${this.config.minSize} sandboxes`);
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < this.config.minSize; i++) {
|
|
56
|
+
const sandbox = this.createSandbox();
|
|
57
|
+
this.available.push(sandbox);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`[WuSandboxPool] ✅ Pool warmed up with ${this.available.length} sandboxes`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 🎯 CREATE SANDBOX: Crear nuevo sandbox
|
|
65
|
+
* @returns {Object} Sandbox instance
|
|
66
|
+
*/
|
|
67
|
+
createSandbox() {
|
|
68
|
+
// Crear container temporal
|
|
69
|
+
const container = document.createElement('div');
|
|
70
|
+
container.className = 'wu-sandbox-container';
|
|
71
|
+
container.style.display = 'none';
|
|
72
|
+
|
|
73
|
+
// Crear shadow root
|
|
74
|
+
const shadowRoot = container.attachShadow({ mode: 'open' });
|
|
75
|
+
|
|
76
|
+
// Crear mount point dentro del shadow
|
|
77
|
+
const mountPoint = document.createElement('div');
|
|
78
|
+
mountPoint.className = 'wu-mount-point';
|
|
79
|
+
shadowRoot.appendChild(mountPoint);
|
|
80
|
+
|
|
81
|
+
const sandbox = {
|
|
82
|
+
id: `sandbox_${this.totalCreated++}`,
|
|
83
|
+
container,
|
|
84
|
+
shadowRoot,
|
|
85
|
+
mountPoint,
|
|
86
|
+
createdAt: Date.now(),
|
|
87
|
+
lastUsed: Date.now(),
|
|
88
|
+
timesUsed: 0,
|
|
89
|
+
currentApp: null
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
console.log(`[WuSandboxPool] Created sandbox: ${sandbox.id}`);
|
|
93
|
+
|
|
94
|
+
return sandbox;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 🔓 ACQUIRE: Obtener sandbox del pool
|
|
99
|
+
* @param {string} appName - Nombre de la app
|
|
100
|
+
* @returns {Object} Sandbox instance
|
|
101
|
+
*/
|
|
102
|
+
acquire(appName) {
|
|
103
|
+
// Verificar si ya tiene un sandbox
|
|
104
|
+
if (this.inUse.has(appName)) {
|
|
105
|
+
console.warn(`[WuSandboxPool] App "${appName}" already has a sandbox`);
|
|
106
|
+
return this.inUse.get(appName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let sandbox;
|
|
110
|
+
|
|
111
|
+
// Intentar reutilizar sandbox disponible
|
|
112
|
+
if (this.available.length > 0) {
|
|
113
|
+
sandbox = this.available.pop();
|
|
114
|
+
this.totalReused++;
|
|
115
|
+
|
|
116
|
+
console.log(`[WuSandboxPool] ♻️ Reusing sandbox ${sandbox.id} for ${appName}`);
|
|
117
|
+
}
|
|
118
|
+
// Si no hay disponibles y no excedemos máximo, crear nuevo
|
|
119
|
+
else if (this.inUse.size < this.config.maxSize) {
|
|
120
|
+
sandbox = this.createSandbox();
|
|
121
|
+
|
|
122
|
+
console.log(`[WuSandboxPool] 🆕 Creating new sandbox ${sandbox.id} for ${appName}`);
|
|
123
|
+
}
|
|
124
|
+
// Si excedemos máximo, crear temporal (no pooleable)
|
|
125
|
+
else {
|
|
126
|
+
sandbox = this.createSandbox();
|
|
127
|
+
sandbox.temporary = true;
|
|
128
|
+
|
|
129
|
+
console.warn(`[WuSandboxPool] ⚠️ Pool exhausted, creating temporary sandbox for ${appName}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Preparar sandbox para uso
|
|
133
|
+
this.prepareSandbox(sandbox, appName);
|
|
134
|
+
|
|
135
|
+
// Marcar como en uso
|
|
136
|
+
sandbox.currentApp = appName;
|
|
137
|
+
sandbox.lastUsed = Date.now();
|
|
138
|
+
sandbox.timesUsed++;
|
|
139
|
+
|
|
140
|
+
this.inUse.set(appName, sandbox);
|
|
141
|
+
|
|
142
|
+
return sandbox;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 🔒 RELEASE: Liberar sandbox al pool
|
|
147
|
+
* @param {string} appName - Nombre de la app
|
|
148
|
+
*/
|
|
149
|
+
release(appName) {
|
|
150
|
+
const sandbox = this.inUse.get(appName);
|
|
151
|
+
|
|
152
|
+
if (!sandbox) {
|
|
153
|
+
console.warn(`[WuSandboxPool] App "${appName}" has no sandbox to release`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Limpiar sandbox
|
|
158
|
+
this.cleanSandbox(sandbox);
|
|
159
|
+
|
|
160
|
+
// Remover de uso
|
|
161
|
+
this.inUse.delete(appName);
|
|
162
|
+
sandbox.currentApp = null;
|
|
163
|
+
sandbox.lastUsed = Date.now();
|
|
164
|
+
|
|
165
|
+
// Si es temporal, destruir
|
|
166
|
+
if (sandbox.temporary) {
|
|
167
|
+
this.destroySandbox(sandbox);
|
|
168
|
+
console.log(`[WuSandboxPool] 🗑️ Destroyed temporary sandbox ${sandbox.id}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Si pool no está lleno, devolver al pool
|
|
173
|
+
if (this.available.length < this.config.maxSize) {
|
|
174
|
+
this.available.push(sandbox);
|
|
175
|
+
console.log(`[WuSandboxPool] ✅ Released sandbox ${sandbox.id} back to pool`);
|
|
176
|
+
}
|
|
177
|
+
// Si pool está lleno, destruir
|
|
178
|
+
else {
|
|
179
|
+
this.destroySandbox(sandbox);
|
|
180
|
+
console.log(`[WuSandboxPool] 🗑️ Pool full, destroyed sandbox ${sandbox.id}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 🔧 PREPARE SANDBOX: Preparar sandbox para uso
|
|
186
|
+
* @param {Object} sandbox - Sandbox instance
|
|
187
|
+
* @param {string} appName - Nombre de la app
|
|
188
|
+
*/
|
|
189
|
+
prepareSandbox(sandbox, appName) {
|
|
190
|
+
// Mostrar container
|
|
191
|
+
sandbox.container.style.display = 'block';
|
|
192
|
+
|
|
193
|
+
// Agregar identificador de app
|
|
194
|
+
sandbox.container.setAttribute('data-wu-app', appName);
|
|
195
|
+
|
|
196
|
+
console.log(`[WuSandboxPool] Prepared sandbox ${sandbox.id} for ${appName}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 🧹 CLEAN SANDBOX: Limpiar sandbox para reutilización
|
|
201
|
+
* @param {Object} sandbox - Sandbox instance
|
|
202
|
+
*/
|
|
203
|
+
cleanSandbox(sandbox) {
|
|
204
|
+
// Limpiar contenido del mount point
|
|
205
|
+
if (sandbox.mountPoint) {
|
|
206
|
+
sandbox.mountPoint.innerHTML = '';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Limpiar estilos del shadow root
|
|
210
|
+
if (sandbox.shadowRoot) {
|
|
211
|
+
const styles = sandbox.shadowRoot.querySelectorAll('style');
|
|
212
|
+
styles.forEach(style => style.remove());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Ocultar container
|
|
216
|
+
sandbox.container.style.display = 'none';
|
|
217
|
+
|
|
218
|
+
// Remover identificador de app
|
|
219
|
+
sandbox.container.removeAttribute('data-wu-app');
|
|
220
|
+
|
|
221
|
+
console.log(`[WuSandboxPool] Cleaned sandbox ${sandbox.id}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 💥 DESTROY SANDBOX: Destruir sandbox completamente
|
|
226
|
+
* @param {Object} sandbox - Sandbox instance
|
|
227
|
+
*/
|
|
228
|
+
destroySandbox(sandbox) {
|
|
229
|
+
// Limpiar contenido
|
|
230
|
+
this.cleanSandbox(sandbox);
|
|
231
|
+
|
|
232
|
+
// Remover del DOM si está adjuntado
|
|
233
|
+
if (sandbox.container.parentNode) {
|
|
234
|
+
sandbox.container.parentNode.removeChild(sandbox.container);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Limpiar referencias
|
|
238
|
+
sandbox.shadowRoot = null;
|
|
239
|
+
sandbox.mountPoint = null;
|
|
240
|
+
sandbox.container = null;
|
|
241
|
+
|
|
242
|
+
console.log(`[WuSandboxPool] Destroyed sandbox ${sandbox.id}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* ⏰ START CLEANUP TIMER: Iniciar limpieza periódica
|
|
247
|
+
*/
|
|
248
|
+
startCleanupTimer() {
|
|
249
|
+
// Limpiar timer anterior si existe
|
|
250
|
+
if (this.cleanupTimer) {
|
|
251
|
+
clearInterval(this.cleanupTimer);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Limpiar cada minuto
|
|
255
|
+
this.cleanupTimer = setInterval(() => {
|
|
256
|
+
this.cleanupIdleSandboxes();
|
|
257
|
+
}, 60000);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 🧹 CLEANUP IDLE SANDBOXES: Limpiar sandboxes inactivos
|
|
262
|
+
*/
|
|
263
|
+
cleanupIdleSandboxes() {
|
|
264
|
+
const now = Date.now();
|
|
265
|
+
const threshold = now - this.config.idleTimeout;
|
|
266
|
+
|
|
267
|
+
// Filtrar sandboxes que no han sido usados recientemente
|
|
268
|
+
const toKeep = [];
|
|
269
|
+
const toDestroy = [];
|
|
270
|
+
|
|
271
|
+
this.available.forEach(sandbox => {
|
|
272
|
+
if (sandbox.lastUsed < threshold && this.available.length > this.config.minSize) {
|
|
273
|
+
toDestroy.push(sandbox);
|
|
274
|
+
} else {
|
|
275
|
+
toKeep.push(sandbox);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Destruir sandboxes idle
|
|
280
|
+
toDestroy.forEach(sandbox => {
|
|
281
|
+
this.destroySandbox(sandbox);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
this.available = toKeep;
|
|
285
|
+
|
|
286
|
+
if (toDestroy.length > 0) {
|
|
287
|
+
console.log(`[WuSandboxPool] 🧹 Cleaned up ${toDestroy.length} idle sandboxes`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Si quedamos por debajo del mínimo, crear nuevos
|
|
291
|
+
if (this.available.length < this.config.minSize) {
|
|
292
|
+
const needed = this.config.minSize - this.available.length;
|
|
293
|
+
console.log(`[WuSandboxPool] Creating ${needed} sandboxes to maintain minimum`);
|
|
294
|
+
|
|
295
|
+
for (let i = 0; i < needed; i++) {
|
|
296
|
+
this.available.push(this.createSandbox());
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* ⚙️ CONFIGURE: Configurar pool
|
|
303
|
+
* @param {Object} config - Nueva configuración
|
|
304
|
+
*/
|
|
305
|
+
configure(config) {
|
|
306
|
+
const oldMinSize = this.config.minSize;
|
|
307
|
+
|
|
308
|
+
this.config = {
|
|
309
|
+
...this.config,
|
|
310
|
+
...config
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Si aumentó el minSize, crear más sandboxes
|
|
314
|
+
if (this.config.minSize > oldMinSize) {
|
|
315
|
+
const needed = this.config.minSize - this.available.length;
|
|
316
|
+
if (needed > 0) {
|
|
317
|
+
console.log(`[WuSandboxPool] Creating ${needed} sandboxes for new minSize`);
|
|
318
|
+
for (let i = 0; i < needed; i++) {
|
|
319
|
+
this.available.push(this.createSandbox());
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Si disminuyó el minSize o maxSize, limpiar exceso
|
|
325
|
+
if (this.available.length > this.config.maxSize) {
|
|
326
|
+
const excess = this.available.length - this.config.maxSize;
|
|
327
|
+
const toDestroy = this.available.splice(this.config.maxSize, excess);
|
|
328
|
+
toDestroy.forEach(sandbox => this.destroySandbox(sandbox));
|
|
329
|
+
|
|
330
|
+
console.log(`[WuSandboxPool] Destroyed ${excess} excess sandboxes`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
console.log('[WuSandboxPool] Configuration updated:', this.config);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 📊 GET STATS: Estadísticas del pool
|
|
338
|
+
* @returns {Object}
|
|
339
|
+
*/
|
|
340
|
+
getStats() {
|
|
341
|
+
const totalSandboxes = this.available.length + this.inUse.size;
|
|
342
|
+
const utilization = totalSandboxes > 0
|
|
343
|
+
? ((this.inUse.size / totalSandboxes) * 100).toFixed(1)
|
|
344
|
+
: 0;
|
|
345
|
+
|
|
346
|
+
const avgTimesUsed = totalSandboxes > 0
|
|
347
|
+
? [...this.available, ...this.inUse.values()]
|
|
348
|
+
.reduce((sum, s) => sum + s.timesUsed, 0) / totalSandboxes
|
|
349
|
+
: 0;
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
available: this.available.length,
|
|
353
|
+
inUse: this.inUse.size,
|
|
354
|
+
totalSandboxes,
|
|
355
|
+
utilization: `${utilization}%`,
|
|
356
|
+
totalCreated: this.totalCreated,
|
|
357
|
+
totalReused: this.totalReused,
|
|
358
|
+
reuseRate: this.totalCreated > 0
|
|
359
|
+
? `${((this.totalReused / this.totalCreated) * 100).toFixed(1)}%`
|
|
360
|
+
: '0%',
|
|
361
|
+
avgTimesUsed: avgTimesUsed.toFixed(1),
|
|
362
|
+
config: this.config,
|
|
363
|
+
appsInUse: Array.from(this.inUse.keys())
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* 🧹 CLEANUP: Limpiar todo el pool
|
|
369
|
+
*/
|
|
370
|
+
cleanup() {
|
|
371
|
+
// Detener cleanup timer
|
|
372
|
+
if (this.cleanupTimer) {
|
|
373
|
+
clearInterval(this.cleanupTimer);
|
|
374
|
+
this.cleanupTimer = null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Destruir todos los sandboxes disponibles
|
|
378
|
+
this.available.forEach(sandbox => this.destroySandbox(sandbox));
|
|
379
|
+
this.available = [];
|
|
380
|
+
|
|
381
|
+
// Destruir sandboxes en uso (forzado)
|
|
382
|
+
this.inUse.forEach((sandbox, appName) => {
|
|
383
|
+
console.warn(`[WuSandboxPool] Force destroying sandbox for active app: ${appName}`);
|
|
384
|
+
this.destroySandbox(sandbox);
|
|
385
|
+
});
|
|
386
|
+
this.inUse.clear();
|
|
387
|
+
|
|
388
|
+
console.log('[WuSandboxPool] 🧹 Pool cleaned up');
|
|
389
|
+
}
|
|
390
|
+
}
|