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