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,348 @@
1
+ /**
2
+ * 🪝 WU-HOOKS: LIFECYCLE MIDDLEWARE SYSTEM
3
+ *
4
+ * Sistema de hooks basado en middleware pattern para control fino:
5
+ * - Middleware chain con next()
6
+ * - Puede cancelar operaciones (no llamar next)
7
+ * - Puede modificar contexto
8
+ * - Prioridad de hooks
9
+ * - Async/await support
10
+ */
11
+
12
+ export class WuLifecycleHooks {
13
+ constructor(core) {
14
+ this.core = core;
15
+ this.hooks = new Map();
16
+ this.executionLog = [];
17
+ this.maxLogSize = 100;
18
+
19
+ // Lifecycle phases disponibles
20
+ this.lifecyclePhases = [
21
+ 'beforeInit', // Antes de inicializar framework
22
+ 'afterInit', // Después de inicializar
23
+ 'beforeLoad', // Antes de cargar una app
24
+ 'afterLoad', // Después de cargar
25
+ 'beforeMount', // Antes de montar
26
+ 'afterMount', // Después de montar
27
+ 'beforeUnmount', // Antes de desmontar
28
+ 'afterUnmount', // Después de desmontar
29
+ 'beforeDestroy', // Antes de destruir framework
30
+ 'afterDestroy' // Después de destruir
31
+ ];
32
+
33
+ // Inicializar hooks
34
+ this.lifecyclePhases.forEach(phase => {
35
+ this.hooks.set(phase, []);
36
+ });
37
+
38
+ console.log('[WuHooks] 🪝 Lifecycle hooks initialized');
39
+ }
40
+
41
+ /**
42
+ * 📦 USE: Registrar middleware hook
43
+ * @param {string} phase - Fase del lifecycle
44
+ * @param {Function} middleware - Función middleware (context, next)
45
+ * @param {Object} options - { priority, name }
46
+ */
47
+ use(phase, middleware, options = {}) {
48
+ if (!this.hooks.has(phase)) {
49
+ throw new Error(`[WuHooks] Unknown lifecycle phase: ${phase}`);
50
+ }
51
+
52
+ if (typeof middleware !== 'function') {
53
+ throw new Error('[WuHooks] Middleware must be a function');
54
+ }
55
+
56
+ const hook = {
57
+ middleware,
58
+ name: options.name || `hook_${Date.now()}`,
59
+ priority: options.priority || 0,
60
+ registeredAt: Date.now()
61
+ };
62
+
63
+ const hooks = this.hooks.get(phase);
64
+ hooks.push(hook);
65
+
66
+ // Ordenar por prioridad (mayor primero)
67
+ hooks.sort((a, b) => b.priority - a.priority);
68
+
69
+ console.log(`[WuHooks] Hook "${hook.name}" registered for ${phase} (priority: ${hook.priority})`);
70
+
71
+ // Retornar función para desregistrar
72
+ return () => this.remove(phase, hook.name);
73
+ }
74
+
75
+ /**
76
+ * 🗑️ REMOVE: Remover hook
77
+ * @param {string} phase - Fase del lifecycle
78
+ * @param {string} name - Nombre del hook
79
+ */
80
+ remove(phase, name) {
81
+ if (!this.hooks.has(phase)) return;
82
+
83
+ const hooks = this.hooks.get(phase);
84
+ const index = hooks.findIndex(h => h.name === name);
85
+
86
+ if (index > -1) {
87
+ hooks.splice(index, 1);
88
+ console.log(`[WuHooks] Hook "${name}" removed from ${phase}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 🎯 EXECUTE: Ejecutar middleware chain
94
+ * @param {string} phase - Fase del lifecycle
95
+ * @param {Object} context - Contexto a pasar
96
+ * @returns {Promise<Object>} Contexto modificado o { cancelled: true }
97
+ */
98
+ async execute(phase, context = {}) {
99
+ const hooks = this.hooks.get(phase);
100
+
101
+ if (!hooks || hooks.length === 0) {
102
+ return context;
103
+ }
104
+
105
+ console.log(`[WuHooks] Executing ${hooks.length} hooks for ${phase}`);
106
+
107
+ // Log para debugging
108
+ const executionEntry = {
109
+ phase,
110
+ timestamp: Date.now(),
111
+ hooksCount: hooks.length,
112
+ hookNames: hooks.map(h => h.name)
113
+ };
114
+
115
+ let currentContext = { ...context };
116
+ let cancelled = false;
117
+
118
+ // Crear cadena de middleware
119
+ const executeChain = async (index) => {
120
+ // Si llegamos al final de la cadena, retornar contexto
121
+ if (index >= hooks.length) {
122
+ return currentContext;
123
+ }
124
+
125
+ const hook = hooks[index];
126
+ const startTime = Date.now();
127
+
128
+ try {
129
+ let nextCalled = false;
130
+
131
+ // Función next
132
+ const next = async (modifiedContext) => {
133
+ nextCalled = true;
134
+
135
+ // Si se pasa un contexto modificado, usarlo
136
+ if (modifiedContext !== undefined) {
137
+ currentContext = { ...currentContext, ...modifiedContext };
138
+ }
139
+
140
+ // Continuar con siguiente hook
141
+ return await executeChain(index + 1);
142
+ };
143
+
144
+ // Ejecutar middleware
145
+ await hook.middleware(currentContext, next);
146
+
147
+ // Si no se llamó next(), la operación fue cancelada
148
+ if (!nextCalled) {
149
+ console.log(`[WuHooks] Hook "${hook.name}" cancelled execution`);
150
+ cancelled = true;
151
+ return { cancelled: true };
152
+ }
153
+
154
+ const duration = Date.now() - startTime;
155
+ console.log(`[WuHooks] Hook "${hook.name}" executed in ${duration}ms`);
156
+
157
+ } catch (error) {
158
+ console.error(`[WuHooks] Error in hook "${hook.name}":`, error);
159
+
160
+ // Si hay error, pasar al siguiente hook
161
+ return await executeChain(index + 1);
162
+ }
163
+
164
+ return currentContext;
165
+ };
166
+
167
+ // Ejecutar cadena
168
+ const result = await executeChain(0);
169
+
170
+ // Completar log
171
+ executionEntry.duration = Date.now() - executionEntry.timestamp;
172
+ executionEntry.cancelled = cancelled;
173
+ executionEntry.success = !cancelled;
174
+
175
+ this.executionLog.push(executionEntry);
176
+
177
+ // Mantener límite de log
178
+ if (this.executionLog.length > this.maxLogSize) {
179
+ this.executionLog.shift();
180
+ }
181
+
182
+ return result;
183
+ }
184
+
185
+ /**
186
+ * 🚀 HELPER: Registrar hook para múltiples fases
187
+ * @param {Array<string>} phases - Fases del lifecycle
188
+ * @param {Function} middleware - Función middleware
189
+ * @param {Object} options - Opciones
190
+ * @returns {Function} Función para desregistrar de todas las fases
191
+ */
192
+ useMultiple(phases, middleware, options = {}) {
193
+ const unregisterFns = phases.map(phase =>
194
+ this.use(phase, middleware, { ...options, name: `${options.name}_${phase}` })
195
+ );
196
+
197
+ // Retornar función que desregistra de todas las fases
198
+ return () => unregisterFns.forEach(fn => fn());
199
+ }
200
+
201
+ /**
202
+ * 📋 GET HOOKS: Obtener hooks registrados
203
+ * @param {string} phase - Fase del lifecycle (opcional)
204
+ * @returns {Object|Array}
205
+ */
206
+ getHooks(phase) {
207
+ if (phase) {
208
+ return this.hooks.get(phase) || [];
209
+ }
210
+
211
+ // Retornar todos los hooks
212
+ const allHooks = {};
213
+ this.hooks.forEach((hooks, phase) => {
214
+ allHooks[phase] = hooks.map(h => ({
215
+ name: h.name,
216
+ priority: h.priority,
217
+ registeredAt: h.registeredAt
218
+ }));
219
+ });
220
+
221
+ return allHooks;
222
+ }
223
+
224
+ /**
225
+ * 📊 GET STATS: Estadísticas de hooks
226
+ * @returns {Object}
227
+ */
228
+ getStats() {
229
+ const totalHooks = Array.from(this.hooks.values())
230
+ .reduce((sum, hooks) => sum + hooks.length, 0);
231
+
232
+ const executionsByPhase = {};
233
+ this.executionLog.forEach(entry => {
234
+ executionsByPhase[entry.phase] = (executionsByPhase[entry.phase] || 0) + 1;
235
+ });
236
+
237
+ const avgDuration = this.executionLog.length > 0
238
+ ? this.executionLog.reduce((sum, entry) => sum + entry.duration, 0) / this.executionLog.length
239
+ : 0;
240
+
241
+ const cancelledCount = this.executionLog.filter(entry => entry.cancelled).length;
242
+
243
+ return {
244
+ totalHooks,
245
+ totalExecutions: this.executionLog.length,
246
+ executionsByPhase,
247
+ avgDuration: Math.round(avgDuration),
248
+ cancelledCount,
249
+ recentExecutions: this.executionLog.slice(-10)
250
+ };
251
+ }
252
+
253
+ /**
254
+ * 🧹 CLEANUP: Limpiar todos los hooks
255
+ * @param {string} phase - Fase específica (opcional)
256
+ */
257
+ cleanup(phase) {
258
+ if (phase) {
259
+ this.hooks.set(phase, []);
260
+ console.log(`[WuHooks] Hooks cleaned for ${phase}`);
261
+ } else {
262
+ this.lifecyclePhases.forEach(p => {
263
+ this.hooks.set(p, []);
264
+ });
265
+ this.executionLog = [];
266
+ console.log('[WuHooks] 🧹 All hooks cleaned');
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * 🔧 HELPER: Crear middleware hooks fácilmente
273
+ */
274
+
275
+ /**
276
+ * Crear hook simple que siempre llama next
277
+ * @param {Function} fn - Función a ejecutar
278
+ * @returns {Function} Middleware function
279
+ */
280
+ export const createSimpleHook = (fn) => {
281
+ return async (context, next) => {
282
+ await fn(context);
283
+ await next();
284
+ };
285
+ };
286
+
287
+ /**
288
+ * Crear hook condicional
289
+ * @param {Function} condition - Función de condición (context) => boolean
290
+ * @param {Function} fn - Función a ejecutar si condición es true
291
+ * @returns {Function} Middleware function
292
+ */
293
+ export const createConditionalHook = (condition, fn) => {
294
+ return async (context, next) => {
295
+ if (await condition(context)) {
296
+ await fn(context);
297
+ }
298
+ await next();
299
+ };
300
+ };
301
+
302
+ /**
303
+ * Crear hook que puede cancelar operación
304
+ * @param {Function} shouldContinue - Función que retorna true para continuar
305
+ * @returns {Function} Middleware function
306
+ */
307
+ export const createGuardHook = (shouldContinue) => {
308
+ return async (context, next) => {
309
+ if (await shouldContinue(context)) {
310
+ await next();
311
+ }
312
+ // Si no retorna true, no llama next() y cancela
313
+ };
314
+ };
315
+
316
+ /**
317
+ * Crear hook que modifica contexto
318
+ * @param {Function} transformer - Función que transforma el contexto
319
+ * @returns {Function} Middleware function
320
+ */
321
+ export const createTransformHook = (transformer) => {
322
+ return async (context, next) => {
323
+ const modified = await transformer(context);
324
+ await next(modified);
325
+ };
326
+ };
327
+
328
+ /**
329
+ * Crear hook con timeout
330
+ * @param {Function} fn - Función a ejecutar
331
+ * @param {number} timeout - Timeout en ms
332
+ * @returns {Function} Middleware function
333
+ */
334
+ export const createTimedHook = (fn, timeout = 5000) => {
335
+ return async (context, next) => {
336
+ const timeoutPromise = new Promise((_, reject) =>
337
+ setTimeout(() => reject(new Error('Hook timeout')), timeout)
338
+ );
339
+
340
+ try {
341
+ await Promise.race([fn(context), timeoutPromise]);
342
+ await next();
343
+ } catch (error) {
344
+ console.error('[WuHooks] Timed hook failed:', error);
345
+ await next(); // Continuar a pesar del error
346
+ }
347
+ };
348
+ };
@@ -0,0 +1,280 @@
1
+ /**
2
+ * 📄 WU-HTML-PARSER: Parser inteligente de HTML para micro-apps
3
+ * Basado en video-code - Extrae DOM, scripts y estilos
4
+ */
5
+
6
+ export class WuHtmlParser {
7
+ constructor() {
8
+ this.cache = new Map(); // Cache de HTML parseado
9
+ console.log('[WuHtmlParser] 📄 HTML parsing system initialized');
10
+ }
11
+
12
+ /**
13
+ * Parsear HTML completo de una micro-app
14
+ * @param {string} html - HTML a parsear
15
+ * @param {string} appName - Nombre de la app
16
+ * @param {string} baseUrl - URL base para resolver recursos
17
+ * @returns {Object} { dom, scripts, styles, externalScripts, externalStyles }
18
+ */
19
+ parse(html, appName, baseUrl) {
20
+ console.log(`[WuHtmlParser] 📄 Parsing HTML for ${appName}`);
21
+
22
+ // Verificar cache
23
+ const cacheKey = `${appName}:${html.length}`;
24
+ if (this.cache.has(cacheKey)) {
25
+ console.log(`[WuHtmlParser] ⚡ Cache hit for ${appName}`);
26
+ return this.cache.get(cacheKey);
27
+ }
28
+
29
+ try {
30
+ // Crear contenedor temporal para parsear
31
+ const tempDiv = document.createElement('div');
32
+ tempDiv.innerHTML = html;
33
+
34
+ // Extraer recursos
35
+ const inlineScripts = [];
36
+ const externalScripts = [];
37
+ const inlineStyles = [];
38
+ const externalStyles = [];
39
+
40
+ // 🔍 Parsear recursivamente el DOM
41
+ this.deepParse(tempDiv, {
42
+ inlineScripts,
43
+ externalScripts,
44
+ inlineStyles,
45
+ externalStyles,
46
+ baseUrl,
47
+ appName
48
+ });
49
+
50
+ // Obtener DOM limpio
51
+ const cleanedDom = tempDiv.innerHTML;
52
+
53
+ const result = {
54
+ dom: cleanedDom,
55
+ scripts: {
56
+ inline: inlineScripts,
57
+ external: externalScripts,
58
+ all: [...inlineScripts, ...externalScripts]
59
+ },
60
+ styles: {
61
+ inline: inlineStyles,
62
+ external: externalStyles,
63
+ all: [...inlineStyles, ...externalStyles]
64
+ }
65
+ };
66
+
67
+ // Cachear resultado
68
+ this.cache.set(cacheKey, result);
69
+
70
+ console.log(`[WuHtmlParser] ✅ Parsed ${appName}: ${inlineScripts.length} inline scripts, ${externalScripts.length} external scripts`);
71
+
72
+ return result;
73
+
74
+ } catch (error) {
75
+ console.error(`[WuHtmlParser] ❌ Failed to parse HTML for ${appName}:`, error);
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Parsear recursivamente el DOM extrayendo scripts y estilos
82
+ * @param {HTMLElement} element - Elemento a parsear
83
+ * @param {Object} context - Contexto de parsing
84
+ */
85
+ deepParse(element, context) {
86
+ const { inlineScripts, externalScripts, inlineStyles, externalStyles, baseUrl, appName } = context;
87
+ const children = Array.from(element.children);
88
+
89
+ for (const child of children) {
90
+ const tagName = child.nodeName.toLowerCase();
91
+
92
+ // 📜 SCRIPT TAGS
93
+ if (tagName === 'script') {
94
+ this.parseScriptTag(child, inlineScripts, externalScripts, baseUrl, appName);
95
+
96
+ // Reemplazar script con comentario
97
+ const comment = document.createComment(`Wu: script removed from ${appName}`);
98
+ child.parentElement?.replaceChild(comment, child);
99
+ continue;
100
+ }
101
+
102
+ // 🎨 STYLE TAGS
103
+ if (tagName === 'style') {
104
+ const styleContent = child.textContent || '';
105
+ if (styleContent.trim()) {
106
+ inlineStyles.push(styleContent);
107
+ console.log(`[WuHtmlParser] 🎨 Extracted inline style (${styleContent.length} chars)`);
108
+ }
109
+
110
+ // Reemplazar style con comentario
111
+ const comment = document.createComment(`Wu: style removed from ${appName}`);
112
+ child.parentElement?.replaceChild(comment, child);
113
+ continue;
114
+ }
115
+
116
+ // 🔗 LINK TAGS (external styles)
117
+ if (tagName === 'link') {
118
+ const rel = child.getAttribute('rel');
119
+ const href = child.getAttribute('href');
120
+
121
+ if (rel === 'stylesheet' && href) {
122
+ const absoluteUrl = this.resolveUrl(href, baseUrl);
123
+ externalStyles.push(absoluteUrl);
124
+ console.log(`[WuHtmlParser] 🔗 Extracted external style: ${absoluteUrl}`);
125
+
126
+ // Reemplazar link con comentario
127
+ const comment = document.createComment(`Wu: link removed from ${appName}`);
128
+ child.parentElement?.replaceChild(comment, child);
129
+ continue;
130
+ }
131
+ }
132
+
133
+ // Recursión para elementos hijos
134
+ if (child.children.length > 0) {
135
+ this.deepParse(child, context);
136
+ }
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Parsear tag <script>
142
+ */
143
+ parseScriptTag(scriptElement, inlineScripts, externalScripts, baseUrl, appName) {
144
+ const src = scriptElement.getAttribute('src');
145
+ const type = scriptElement.getAttribute('type') || 'text/javascript';
146
+
147
+ // Ignorar module scripts (se cargan por import)
148
+ if (type === 'module') {
149
+ console.log(`[WuHtmlParser] ⏭️ Skipping module script for ${appName}`);
150
+ return;
151
+ }
152
+
153
+ if (src) {
154
+ // Script externo
155
+ const absoluteUrl = this.resolveUrl(src, baseUrl);
156
+ externalScripts.push(absoluteUrl);
157
+ console.log(`[WuHtmlParser] 📜 Extracted external script: ${absoluteUrl}`);
158
+ } else {
159
+ // Script inline
160
+ const scriptContent = scriptElement.textContent || '';
161
+ if (scriptContent.trim()) {
162
+ inlineScripts.push(scriptContent);
163
+ console.log(`[WuHtmlParser] 📜 Extracted inline script (${scriptContent.length} chars)`);
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Resolver URL relativa a absoluta
170
+ */
171
+ resolveUrl(url, baseUrl) {
172
+ // Ya es absoluta
173
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
174
+ return url.startsWith('//') ? `https:${url}` : url;
175
+ }
176
+
177
+ // Resolver relativa
178
+ const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
179
+
180
+ if (url.startsWith('/')) {
181
+ // Relativa a raíz
182
+ const urlObj = new URL(baseUrl);
183
+ return `${urlObj.protocol}//${urlObj.host}${url}`;
184
+ } else {
185
+ // Relativa a base
186
+ return `${base}${url}`;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Cargar HTML desde URL
192
+ */
193
+ async fetchHtml(url, appName) {
194
+ console.log(`[WuHtmlParser] 🌐 Fetching HTML for ${appName} from ${url}`);
195
+
196
+ try {
197
+ const response = await fetch(url, {
198
+ method: 'GET',
199
+ cache: 'no-cache',
200
+ headers: {
201
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
202
+ }
203
+ });
204
+
205
+ if (!response.ok) {
206
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
207
+ }
208
+
209
+ const html = await response.text();
210
+
211
+ if (!html || html.trim().length === 0) {
212
+ throw new Error('Empty HTML response');
213
+ }
214
+
215
+ console.log(`[WuHtmlParser] ✅ Fetched HTML (${html.length} chars)`);
216
+
217
+ return html;
218
+
219
+ } catch (error) {
220
+ console.error(`[WuHtmlParser] ❌ Failed to fetch HTML from ${url}:`, error);
221
+ throw error;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Parsear y cargar HTML desde URL
227
+ */
228
+ async parseFromUrl(url, appName) {
229
+ const html = await this.fetchHtml(url, appName);
230
+ return this.parse(html, appName, url);
231
+ }
232
+
233
+ /**
234
+ * Limpiar cache
235
+ */
236
+ clearCache(pattern) {
237
+ if (pattern) {
238
+ const regex = new RegExp(pattern);
239
+ for (const [key] of this.cache) {
240
+ if (regex.test(key)) {
241
+ this.cache.delete(key);
242
+ console.log(`[WuHtmlParser] 🗑️ Cleared cache for: ${key}`);
243
+ }
244
+ }
245
+ } else {
246
+ this.cache.clear();
247
+ console.log(`[WuHtmlParser] 🗑️ Cache cleared completely`);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Obtener estadísticas
253
+ */
254
+ getStats() {
255
+ return {
256
+ cacheSize: this.cache.size,
257
+ cacheKeys: Array.from(this.cache.keys())
258
+ };
259
+ }
260
+ }
261
+
262
+ /**
263
+ * 🎯 EXPORTS DE CONVENIENCIA
264
+ */
265
+
266
+ /**
267
+ * Parsear HTML
268
+ */
269
+ export function parseHtml(html, appName, baseUrl) {
270
+ const parser = new WuHtmlParser();
271
+ return parser.parse(html, appName, baseUrl);
272
+ }
273
+
274
+ /**
275
+ * Parsear HTML desde URL
276
+ */
277
+ export async function parseHtmlFromUrl(url, appName) {
278
+ const parser = new WuHtmlParser();
279
+ return await parser.parseFromUrl(url, appName);
280
+ }