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,213 @@
1
+ /**
2
+ * 🔌 WU-PLUGIN: EXTENSIBLE PLUGIN SYSTEM
3
+ *
4
+ * Sistema de plugins para extender wu-framework sin modificar el core
5
+ * - Plugin lifecycle (install, beforeMount, afterMount, etc.)
6
+ * - Dependency injection
7
+ * - Plugin communication
8
+ */
9
+
10
+ export class WuPluginSystem {
11
+ constructor(core) {
12
+ this.core = core;
13
+ this.plugins = new Map(); // name -> plugin instance
14
+ this.hooks = new Map(); // hook name -> [callbacks]
15
+
16
+ // Hooks disponibles
17
+ this.availableHooks = [
18
+ 'beforeInit',
19
+ 'afterInit',
20
+ 'beforeMount',
21
+ 'afterMount',
22
+ 'beforeUnmount',
23
+ 'afterUnmount',
24
+ 'onError',
25
+ 'onDestroy'
26
+ ];
27
+
28
+ // Inicializar hooks
29
+ this.availableHooks.forEach(hook => {
30
+ this.hooks.set(hook, []);
31
+ });
32
+
33
+ console.log('[WuPlugin] 🔌 Plugin system initialized');
34
+ }
35
+
36
+ /**
37
+ * 📦 USE: Instalar plugin
38
+ * @param {Object|Function} plugin - Plugin o factory function
39
+ * @param {Object} options - Opciones del plugin
40
+ */
41
+ use(plugin, options = {}) {
42
+ // Si es una función, ejecutarla para obtener el plugin
43
+ if (typeof plugin === 'function') {
44
+ plugin = plugin(this.core, options);
45
+ }
46
+
47
+ // Validar plugin
48
+ if (!plugin.name) {
49
+ throw new Error('[WuPlugin] Plugin must have a name property');
50
+ }
51
+
52
+ // Verificar si ya está instalado
53
+ if (this.plugins.has(plugin.name)) {
54
+ console.warn(`[WuPlugin] Plugin "${plugin.name}" already installed`);
55
+ return;
56
+ }
57
+
58
+ // Ejecutar install del plugin
59
+ if (plugin.install) {
60
+ plugin.install(this.core, options);
61
+ }
62
+
63
+ // Registrar hooks del plugin
64
+ this.availableHooks.forEach(hookName => {
65
+ if (typeof plugin[hookName] === 'function') {
66
+ this.registerHook(hookName, plugin[hookName].bind(plugin));
67
+ }
68
+ });
69
+
70
+ // Guardar plugin
71
+ this.plugins.set(plugin.name, {
72
+ plugin,
73
+ options,
74
+ installedAt: Date.now()
75
+ });
76
+
77
+ console.log(`[WuPlugin] ✅ Plugin "${plugin.name}" installed`);
78
+ }
79
+
80
+ /**
81
+ * 🪝 REGISTER HOOK: Registrar callback en un hook
82
+ * @param {string} hookName - Nombre del hook
83
+ * @param {Function} callback - Función callback
84
+ */
85
+ registerHook(hookName, callback) {
86
+ if (!this.hooks.has(hookName)) {
87
+ console.warn(`[WuPlugin] Unknown hook: ${hookName}`);
88
+ return;
89
+ }
90
+
91
+ this.hooks.get(hookName).push(callback);
92
+ }
93
+
94
+ /**
95
+ * 🎯 CALL HOOK: Ejecutar todos los callbacks de un hook
96
+ * @param {string} hookName - Nombre del hook
97
+ * @param {*} context - Contexto a pasar a los callbacks
98
+ * @returns {Promise<boolean>} false si algún callback cancela la operación
99
+ */
100
+ async callHook(hookName, context) {
101
+ const callbacks = this.hooks.get(hookName) || [];
102
+
103
+ for (const callback of callbacks) {
104
+ try {
105
+ const result = await callback(context);
106
+
107
+ // Si retorna false, cancelar operación
108
+ if (result === false) {
109
+ console.log(`[WuPlugin] Hook ${hookName} cancelled by plugin`);
110
+ return false;
111
+ }
112
+ } catch (error) {
113
+ console.error(`[WuPlugin] Error in hook ${hookName}:`, error);
114
+
115
+ // Llamar hook de error
116
+ await this.callHook('onError', { hookName, error, context });
117
+ }
118
+ }
119
+
120
+ return true;
121
+ }
122
+
123
+ /**
124
+ * 🗑️ UNINSTALL: Desinstalar plugin
125
+ * @param {string} pluginName - Nombre del plugin
126
+ */
127
+ uninstall(pluginName) {
128
+ const pluginData = this.plugins.get(pluginName);
129
+
130
+ if (!pluginData) {
131
+ console.warn(`[WuPlugin] Plugin "${pluginName}" not found`);
132
+ return;
133
+ }
134
+
135
+ const { plugin } = pluginData;
136
+
137
+ // Ejecutar uninstall si existe
138
+ if (plugin.uninstall) {
139
+ plugin.uninstall(this.core);
140
+ }
141
+
142
+ // Remover hooks del plugin
143
+ this.availableHooks.forEach(hookName => {
144
+ if (typeof plugin[hookName] === 'function') {
145
+ const callbacks = this.hooks.get(hookName);
146
+ const index = callbacks.indexOf(plugin[hookName]);
147
+ if (index > -1) {
148
+ callbacks.splice(index, 1);
149
+ }
150
+ }
151
+ });
152
+
153
+ // Remover plugin
154
+ this.plugins.delete(pluginName);
155
+
156
+ console.log(`[WuPlugin] ✅ Plugin "${pluginName}" uninstalled`);
157
+ }
158
+
159
+ /**
160
+ * 📋 GET PLUGIN: Obtener plugin instalado
161
+ * @param {string} pluginName - Nombre del plugin
162
+ * @returns {Object}
163
+ */
164
+ getPlugin(pluginName) {
165
+ return this.plugins.get(pluginName)?.plugin;
166
+ }
167
+
168
+ /**
169
+ * 📊 GET STATS: Estadísticas de plugins
170
+ * @returns {Object}
171
+ */
172
+ getStats() {
173
+ return {
174
+ totalPlugins: this.plugins.size,
175
+ plugins: Array.from(this.plugins.entries()).map(([name, data]) => ({
176
+ name,
177
+ installedAt: data.installedAt,
178
+ hasUninstall: typeof data.plugin.uninstall === 'function'
179
+ })),
180
+ hooks: Array.from(this.hooks.entries()).map(([name, callbacks]) => ({
181
+ name,
182
+ callbacks: callbacks.length
183
+ }))
184
+ };
185
+ }
186
+
187
+ /**
188
+ * 🧹 CLEANUP: Limpiar todos los plugins
189
+ */
190
+ cleanup() {
191
+ for (const [name] of this.plugins) {
192
+ this.uninstall(name);
193
+ }
194
+
195
+ console.log('[WuPlugin] 🧹 All plugins cleaned up');
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 📦 PLUGIN HELPER: Helper para crear plugins fácilmente
201
+ */
202
+ export const createPlugin = (config) => {
203
+ return {
204
+ name: config.name,
205
+ install: config.install,
206
+ uninstall: config.uninstall,
207
+ beforeMount: config.beforeMount,
208
+ afterMount: config.afterMount,
209
+ beforeUnmount: config.beforeUnmount,
210
+ afterUnmount: config.afterUnmount,
211
+ onError: config.onError
212
+ };
213
+ };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * 🛡️ WU-PROXY-SANDBOX: JavaScript Isolation con Proxy
3
+ * Basado en video-code - Aislamiento real de variables globales
4
+ *
5
+ * Este sandbox usa ES6 Proxy para interceptar accesos a window
6
+ * y aislar completamente el JavaScript entre micro-apps
7
+ */
8
+
9
+ export class WuProxySandbox {
10
+ constructor(appName) {
11
+ this.appName = appName;
12
+ this.proxy = null;
13
+ this.fakeWindow = Object.create(null); // Namespace aislado
14
+ this.active = false;
15
+ this.modifiedKeys = new Set(); // Track de propiedades modificadas
16
+
17
+ console.log(`[WuProxySandbox] 🛡️ Creating proxy sandbox for: ${appName}`);
18
+ }
19
+
20
+ /**
21
+ * Activar sandbox - Crea el Proxy que aísla window
22
+ */
23
+ activate() {
24
+ if (this.active) {
25
+ console.warn(`[WuProxySandbox] Sandbox already active for ${this.appName}`);
26
+ return this.proxy;
27
+ }
28
+
29
+ this.proxy = new Proxy(window, {
30
+ get: (target, prop) => {
31
+ // 🔍 Primero buscar en el namespace aislado
32
+ if (prop in this.fakeWindow) {
33
+ return this.fakeWindow[prop];
34
+ }
35
+
36
+ // 🎯 Propiedades del window real
37
+ const value = target[prop];
38
+
39
+ // ⚠️ CRÍTICO: Bind de funciones para mantener contexto
40
+ if (typeof value === 'function' && !this.isConstructor(value)) {
41
+ return value.bind(target);
42
+ }
43
+
44
+ return value;
45
+ },
46
+
47
+ set: (target, prop, value) => {
48
+ // 🛡️ AISLAMIENTO: Todo set va al namespace aislado
49
+ this.fakeWindow[prop] = value;
50
+ this.modifiedKeys.add(prop);
51
+
52
+ console.log(`[WuProxySandbox] 📝 ${this.appName} set: ${String(prop)}`);
53
+ return true;
54
+ },
55
+
56
+ has: (target, prop) => {
57
+ // Verificar tanto en fakeWindow como en window real
58
+ return prop in this.fakeWindow || prop in target;
59
+ },
60
+
61
+ deleteProperty: (target, prop) => {
62
+ // Solo permitir delete en el namespace aislado
63
+ if (prop in this.fakeWindow) {
64
+ delete this.fakeWindow[prop];
65
+ this.modifiedKeys.delete(prop);
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ });
71
+
72
+ this.active = true;
73
+ console.log(`[WuProxySandbox] ✅ Proxy sandbox activated for ${this.appName}`);
74
+
75
+ return this.proxy;
76
+ }
77
+
78
+ /**
79
+ * Desactivar sandbox - Limpia el namespace aislado
80
+ */
81
+ deactivate() {
82
+ if (!this.active) {
83
+ console.warn(`[WuProxySandbox] Sandbox not active for ${this.appName}`);
84
+ return;
85
+ }
86
+
87
+ console.log(`[WuProxySandbox] 🧹 Deactivating sandbox for ${this.appName}`);
88
+
89
+ // 🧹 Limpiar todas las propiedades modificadas
90
+ this.fakeWindow = Object.create(null);
91
+ this.modifiedKeys.clear();
92
+ this.proxy = null;
93
+ this.active = false;
94
+
95
+ console.log(`[WuProxySandbox] ✅ Sandbox deactivated for ${this.appName}`);
96
+ }
97
+
98
+ /**
99
+ * Verificar si un valor es un constructor (class)
100
+ */
101
+ isConstructor(fn) {
102
+ try {
103
+ // Detectar constructores/classes por su prototipo
104
+ return fn.prototype && fn.prototype.constructor === fn;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Obtener el proxy activo (o null si no está activo)
112
+ */
113
+ getProxy() {
114
+ return this.active ? this.proxy : null;
115
+ }
116
+
117
+ /**
118
+ * Verificar si el sandbox está activo
119
+ */
120
+ isActive() {
121
+ return this.active;
122
+ }
123
+
124
+ /**
125
+ * Obtener estadísticas del sandbox
126
+ */
127
+ getStats() {
128
+ return {
129
+ appName: this.appName,
130
+ active: this.active,
131
+ modifiedKeys: Array.from(this.modifiedKeys),
132
+ isolatedPropsCount: Object.keys(this.fakeWindow).length
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Limpiar propiedades específicas del namespace
138
+ */
139
+ clearProperty(prop) {
140
+ if (prop in this.fakeWindow) {
141
+ delete this.fakeWindow[prop];
142
+ this.modifiedKeys.delete(prop);
143
+ console.log(`[WuProxySandbox] 🗑️ Cleared property: ${String(prop)}`);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Obtener todas las propiedades aisladas
149
+ */
150
+ getIsolatedProperties() {
151
+ return { ...this.fakeWindow };
152
+ }
153
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * 🔔 WU-REGISTRY: EVENT-BASED APP REGISTRATION SYSTEM
3
+ *
4
+ * Reemplaza el polling con Custom Events para mejor performance
5
+ */
6
+
7
+ export class WuRegistry {
8
+ constructor() {
9
+ this.registeredApps = new Set();
10
+ this.waitingPromises = new Map();
11
+
12
+ // Event listeners para registro
13
+ this.setupEventListeners();
14
+
15
+ console.log('[WuRegistry] 🔔 Event-based registration system initialized');
16
+ }
17
+
18
+ /**
19
+ * 📡 SETUP EVENT LISTENERS
20
+ */
21
+ setupEventListeners() {
22
+ // Escuchar eventos de registro de apps
23
+ window.addEventListener('wu:app:ready', (event) => {
24
+ const { appName } = event.detail;
25
+ console.log(`[WuRegistry] ✅ App registered via event: ${appName}`);
26
+
27
+ this.registeredApps.add(appName);
28
+
29
+ // Resolver promesas pendientes
30
+ if (this.waitingPromises.has(appName)) {
31
+ const { resolve } = this.waitingPromises.get(appName);
32
+ resolve();
33
+ this.waitingPromises.delete(appName);
34
+ }
35
+ });
36
+
37
+ // Exponer API global para microfrontends
38
+ if (!window.wu) {
39
+ window.wu = {};
40
+ }
41
+
42
+ // API para que apps notifiquen que están listas
43
+ window.wu.notifyReady = (appName) => {
44
+ console.log(`[WuRegistry] 📢 App ${appName} called notifyReady()`);
45
+
46
+ const event = new CustomEvent('wu:app:ready', {
47
+ detail: { appName, timestamp: Date.now() }
48
+ });
49
+
50
+ window.dispatchEvent(event);
51
+ };
52
+ }
53
+
54
+ /**
55
+ * ⏳ WAIT FOR APP: Promise-based waiting
56
+ * @param {string} appName - Nombre de la app
57
+ * @param {number} timeout - Timeout en ms (default: 10000)
58
+ * @returns {Promise}
59
+ */
60
+ waitForApp(appName, timeout = 10000) {
61
+ // Si ya está registrada, resolver inmediatamente
62
+ if (this.registeredApps.has(appName)) {
63
+ console.log(`[WuRegistry] ⚡ App ${appName} already registered`);
64
+ return Promise.resolve();
65
+ }
66
+
67
+ console.log(`[WuRegistry] ⏳ Waiting for app ${appName} to register...`);
68
+
69
+ return new Promise((resolve, reject) => {
70
+ // Guardar promesa
71
+ this.waitingPromises.set(appName, { resolve, reject });
72
+
73
+ // Timeout
74
+ const timeoutId = setTimeout(() => {
75
+ if (this.waitingPromises.has(appName)) {
76
+ this.waitingPromises.delete(appName);
77
+
78
+ const error = new Error(
79
+ `App '${appName}' failed to register within ${timeout}ms.\n\n` +
80
+ `Possible causes:\n` +
81
+ ` - Module failed to load\n` +
82
+ ` - wu.define() was not called\n` +
83
+ ` - Check browser console for import errors\n\n` +
84
+ `Make sure your app calls:\n` +
85
+ ` wu.define('${appName}', { mount, unmount })`
86
+ );
87
+
88
+ reject(error);
89
+ }
90
+ }, timeout);
91
+
92
+ // Limpiar timeout cuando se resuelva
93
+ this.waitingPromises.get(appName).timeoutId = timeoutId;
94
+ });
95
+ }
96
+
97
+ /**
98
+ * ✅ MARK AS REGISTERED
99
+ * @param {string} appName
100
+ */
101
+ markAsRegistered(appName) {
102
+ this.registeredApps.add(appName);
103
+ console.log(`[WuRegistry] ✅ App ${appName} marked as registered`);
104
+ }
105
+
106
+ /**
107
+ * ❌ UNREGISTER APP
108
+ * @param {string} appName
109
+ */
110
+ unregister(appName) {
111
+ this.registeredApps.delete(appName);
112
+ console.log(`[WuRegistry] ❌ App ${appName} unregistered`);
113
+ }
114
+
115
+ /**
116
+ * 🧹 CLEANUP
117
+ */
118
+ cleanup() {
119
+ // Rechazar todas las promesas pendientes
120
+ for (const [appName, { reject, timeoutId }] of this.waitingPromises) {
121
+ clearTimeout(timeoutId);
122
+ reject(new Error(`Registry cleanup: ${appName}`));
123
+ }
124
+
125
+ this.waitingPromises.clear();
126
+ this.registeredApps.clear();
127
+
128
+ console.log('[WuRegistry] 🧹 Registry cleaned up');
129
+ }
130
+ }