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,366 @@
1
+ /**
2
+ * 📋 WU-MANIFEST: SISTEMA DE MANIFIESTOS SIMPLIFICADO
3
+ * Maneja wu.json sin complicaciones innecesarias
4
+ */
5
+
6
+ export class WuManifest {
7
+ constructor() {
8
+ this.cache = new Map();
9
+ this.schemas = new Map();
10
+
11
+ // Definir schema básico
12
+ this.defineSchema();
13
+
14
+ console.log('[WuManifest] 📋 Manifest system initialized');
15
+ }
16
+
17
+ /**
18
+ * Definir schema de validación para wu.json
19
+ */
20
+ defineSchema() {
21
+ this.schemas.set('wu.json', {
22
+ required: ['name', 'entry'],
23
+ optional: ['wu'],
24
+ wu: {
25
+ optional: ['exports', 'imports', 'routes', 'permissions'],
26
+ exports: 'object',
27
+ imports: 'array',
28
+ routes: 'array',
29
+ permissions: 'array'
30
+ }
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Cargar manifest desde URL
36
+ * @param {string} appUrl - URL base de la aplicación
37
+ * @returns {Object} Manifest parseado y validado
38
+ */
39
+ async load(appUrl) {
40
+ const manifestUrl = `${appUrl}/wu.json`;
41
+
42
+ console.log(`[WuManifest] 📥 Loading manifest: ${manifestUrl}`);
43
+
44
+ try {
45
+ // Verificar cache
46
+ if (this.cache.has(manifestUrl)) {
47
+ console.log(`[WuManifest] ⚡ Cache hit: ${manifestUrl}`);
48
+ return this.cache.get(manifestUrl);
49
+ }
50
+
51
+ // Cargar manifest
52
+ const response = await fetch(manifestUrl, {
53
+ cache: 'no-cache',
54
+ headers: {
55
+ 'Accept': 'application/json'
56
+ }
57
+ });
58
+
59
+ if (!response.ok) {
60
+ // Si no hay manifest, crear uno básico
61
+ if (response.status === 404) {
62
+ console.log(`[WuManifest] 📄 No manifest found, creating default for: ${appUrl}`);
63
+ return this.createDefaultManifest(appUrl);
64
+ }
65
+
66
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
67
+ }
68
+
69
+ const manifestText = await response.text();
70
+ const manifest = JSON.parse(manifestText);
71
+
72
+ // Validar manifest
73
+ const validatedManifest = this.validate(manifest);
74
+
75
+ // Cachear resultado
76
+ this.cache.set(manifestUrl, validatedManifest);
77
+
78
+ console.log(`[WuManifest] ✅ Manifest loaded: ${manifest.name}`);
79
+ return validatedManifest;
80
+
81
+ } catch (error) {
82
+ console.error(`[WuManifest] ❌ Failed to load manifest: ${manifestUrl}`, error);
83
+
84
+ // En caso de error, intentar crear manifest por defecto
85
+ try {
86
+ return this.createDefaultManifest(appUrl);
87
+ } catch (defaultError) {
88
+ throw new Error(`Failed to load manifest from ${manifestUrl}: ${error.message}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Crear manifest por defecto cuando no existe wu.json
95
+ * @param {string} appUrl - URL de la aplicación
96
+ * @returns {Object} Manifest por defecto
97
+ */
98
+ createDefaultManifest(appUrl) {
99
+ // Extraer nombre de la app de la URL
100
+ const appName = this.extractAppNameFromUrl(appUrl);
101
+
102
+ const defaultManifest = {
103
+ name: appName,
104
+ entry: 'index.js',
105
+ wu: {
106
+ exports: {},
107
+ imports: [],
108
+ routes: [],
109
+ permissions: []
110
+ }
111
+ };
112
+
113
+ console.log(`[WuManifest] 🔧 Created default manifest for: ${appName}`);
114
+ return defaultManifest;
115
+ }
116
+
117
+ /**
118
+ * Extraer nombre de app desde URL
119
+ * @param {string} url - URL de la aplicación
120
+ * @returns {string} Nombre de la aplicación
121
+ */
122
+ extractAppNameFromUrl(url) {
123
+ try {
124
+ const urlObj = new URL(url);
125
+ const pathSegments = urlObj.pathname.split('/').filter(Boolean);
126
+
127
+ // Usar el último segmento como nombre de la app
128
+ return pathSegments[pathSegments.length - 1] || 'unknown-app';
129
+ } catch {
130
+ // Si no es una URL válida, usar como está
131
+ return url.replace(/[^a-zA-Z0-9-]/g, '') || 'unknown-app';
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Validar manifest contra schema
137
+ * @param {Object} manifest - Manifest a validar
138
+ * @returns {Object} Manifest validado
139
+ */
140
+ validate(manifest) {
141
+ const schema = this.schemas.get('wu.json');
142
+
143
+ // Verificar campos requeridos
144
+ for (const field of schema.required) {
145
+ if (!manifest[field]) {
146
+ throw new Error(`Required field missing: ${field}`);
147
+ }
148
+ }
149
+
150
+ // Verificar tipos en sección wu
151
+ if (manifest.wu) {
152
+ const wu = manifest.wu;
153
+ const wuSchema = schema.wu;
154
+
155
+ if (wu.exports && typeof wu.exports !== 'object') {
156
+ throw new Error('wu.exports must be an object');
157
+ }
158
+
159
+ if (wu.imports && !Array.isArray(wu.imports)) {
160
+ throw new Error('wu.imports must be an array');
161
+ }
162
+
163
+ if (wu.routes && !Array.isArray(wu.routes)) {
164
+ throw new Error('wu.routes must be an array');
165
+ }
166
+
167
+ if (wu.permissions && !Array.isArray(wu.permissions)) {
168
+ throw new Error('wu.permissions must be an array');
169
+ }
170
+ }
171
+
172
+ // Normalizar y limpiar manifest
173
+ return this.normalize(manifest);
174
+ }
175
+
176
+ /**
177
+ * Normalizar manifest
178
+ * @param {Object} manifest - Manifest a normalizar
179
+ * @returns {Object} Manifest normalizado
180
+ */
181
+ normalize(manifest) {
182
+ const normalized = {
183
+ name: manifest.name.trim(),
184
+ entry: this.normalizeEntry(manifest.entry),
185
+ wu: {
186
+ exports: manifest.wu?.exports || {},
187
+ imports: manifest.wu?.imports || [],
188
+ routes: manifest.wu?.routes || [],
189
+ permissions: manifest.wu?.permissions || []
190
+ }
191
+ };
192
+
193
+ // Normalizar exports
194
+ if (normalized.wu.exports) {
195
+ const normalizedExports = {};
196
+ for (const [key, path] of Object.entries(normalized.wu.exports)) {
197
+ normalizedExports[key] = this.normalizeComponentPath(path);
198
+ }
199
+ normalized.wu.exports = normalizedExports;
200
+ }
201
+
202
+ // Validar imports
203
+ normalized.wu.imports = normalized.wu.imports.filter(imp => {
204
+ if (typeof imp !== 'string' || !imp.includes('.')) {
205
+ console.warn(`[WuManifest] Invalid import format: ${imp}`);
206
+ return false;
207
+ }
208
+ return true;
209
+ });
210
+
211
+ return normalized;
212
+ }
213
+
214
+ /**
215
+ * Normalizar entry path
216
+ * @param {string} entry - Entry path
217
+ * @returns {string} Entry normalizado
218
+ */
219
+ normalizeEntry(entry) {
220
+ if (!entry) return 'index.js';
221
+
222
+ let normalized = entry.trim();
223
+
224
+ // Remover ./ inicial si está presente
225
+ if (normalized.startsWith('./')) {
226
+ normalized = normalized.substring(2);
227
+ }
228
+
229
+ // Agregar extensión si no la tiene
230
+ if (!normalized.includes('.')) {
231
+ normalized += '.js';
232
+ }
233
+
234
+ return normalized;
235
+ }
236
+
237
+ /**
238
+ * Normalizar path de componente
239
+ * @param {string} path - Path del componente
240
+ * @returns {string} Path normalizado
241
+ */
242
+ normalizeComponentPath(path) {
243
+ if (!path) return '';
244
+
245
+ let normalized = path.trim();
246
+
247
+ // Remover ./ inicial si está presente
248
+ if (normalized.startsWith('./')) {
249
+ normalized = normalized.substring(2);
250
+ }
251
+
252
+ // Agregar extensión si no la tiene
253
+ if (!normalized.includes('.')) {
254
+ normalized += '.js';
255
+ }
256
+
257
+ return normalized;
258
+ }
259
+
260
+ /**
261
+ * Validar dependencias de imports
262
+ * @param {Array} imports - Lista de imports
263
+ * @param {Map} availableApps - Apps disponibles
264
+ * @returns {Object} Resultado de validación
265
+ */
266
+ validateDependencies(imports, availableApps) {
267
+ const result = {
268
+ valid: [],
269
+ invalid: [],
270
+ missing: []
271
+ };
272
+
273
+ for (const importPath of imports) {
274
+ const [appName, componentName] = importPath.split('.');
275
+
276
+ if (!appName || !componentName) {
277
+ result.invalid.push({
278
+ import: importPath,
279
+ reason: 'Invalid format. Use "app.component"'
280
+ });
281
+ continue;
282
+ }
283
+
284
+ const app = availableApps.get(appName);
285
+ if (!app) {
286
+ result.missing.push({
287
+ import: importPath,
288
+ app: appName,
289
+ reason: 'App not registered'
290
+ });
291
+ continue;
292
+ }
293
+
294
+ const manifest = app.manifest;
295
+ const exportExists = manifest?.wu?.exports?.[componentName];
296
+
297
+ if (!exportExists) {
298
+ result.invalid.push({
299
+ import: importPath,
300
+ reason: `Component ${componentName} not exported by ${appName}`
301
+ });
302
+ continue;
303
+ }
304
+
305
+ result.valid.push({
306
+ import: importPath,
307
+ app: appName,
308
+ component: componentName,
309
+ path: exportExists
310
+ });
311
+ }
312
+
313
+ return result;
314
+ }
315
+
316
+ /**
317
+ * Crear manifest programáticamente
318
+ * @param {string} name - Nombre de la app
319
+ * @param {Object} config - Configuración
320
+ * @returns {Object} Manifest creado
321
+ */
322
+ create(name, config = {}) {
323
+ const manifest = {
324
+ name: name,
325
+ entry: config.entry || 'index.js',
326
+ wu: {
327
+ exports: config.exports || {},
328
+ imports: config.imports || [],
329
+ routes: config.routes || [],
330
+ permissions: config.permissions || []
331
+ }
332
+ };
333
+
334
+ return this.normalize(manifest);
335
+ }
336
+
337
+ /**
338
+ * Limpiar cache de manifests
339
+ * @param {string} pattern - Patrón opcional para limpiar URLs específicas
340
+ */
341
+ clearCache(pattern) {
342
+ if (pattern) {
343
+ const regex = new RegExp(pattern);
344
+ for (const [url] of this.cache) {
345
+ if (regex.test(url)) {
346
+ this.cache.delete(url);
347
+ console.log(`[WuManifest] 🗑️ Cleared cache for: ${url}`);
348
+ }
349
+ }
350
+ } else {
351
+ this.cache.clear();
352
+ console.log(`[WuManifest] 🗑️ Manifest cache cleared completely`);
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Obtener estadísticas del sistema de manifests
358
+ */
359
+ getStats() {
360
+ return {
361
+ cached: this.cache.size,
362
+ schemas: this.schemas.size,
363
+ cacheKeys: Array.from(this.cache.keys())
364
+ };
365
+ }
366
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * ⚡ WU-PERFORMANCE: MICROFRONTEND LIFECYCLE MONITORING
3
+ *
4
+ * Monitoreo de performance específico para operaciones del framework:
5
+ * - Tiempos de mount/unmount
6
+ * - Tiempos de carga de módulos
7
+ * - Estadísticas por app
8
+ */
9
+
10
+ export class WuPerformance {
11
+ constructor() {
12
+ this.metrics = new Map(); // appName -> metrics
13
+ this.measurements = [];
14
+ this.marks = new Map();
15
+
16
+ this.config = {
17
+ enabled: true,
18
+ maxMeasurements: 1000
19
+ };
20
+
21
+ this.thresholds = {
22
+ mount: 3000, // ms
23
+ unmount: 1000, // ms
24
+ load: 5000 // ms
25
+ };
26
+
27
+ console.log('[WuPerformance] ⚡ Framework performance monitoring initialized');
28
+ }
29
+
30
+ /**
31
+ * 📊 START MEASURE: Iniciar medición
32
+ * @param {string} name - Nombre de la medición
33
+ * @param {string} appName - Nombre de la app (opcional)
34
+ */
35
+ startMeasure(name, appName = 'global') {
36
+ const markName = `${appName}:${name}:start`;
37
+ this.marks.set(markName, performance.now());
38
+
39
+ console.log(`[WuPerformance] 📊 Measure started: ${markName}`);
40
+ }
41
+
42
+ /**
43
+ * ⏹️ END MEASURE: Finalizar medición
44
+ * @param {string} name - Nombre de la medición
45
+ * @param {string} appName - Nombre de la app (opcional)
46
+ * @returns {number} Duración en ms
47
+ */
48
+ endMeasure(name, appName = 'global') {
49
+ const markName = `${appName}:${name}:start`;
50
+ const startTime = this.marks.get(markName);
51
+
52
+ if (!startTime) {
53
+ console.warn(`[WuPerformance] ⚠️ No start mark found for: ${markName}`);
54
+ return 0;
55
+ }
56
+
57
+ const duration = performance.now() - startTime;
58
+ this.marks.delete(markName);
59
+
60
+ // Registrar medición
61
+ this.recordMeasurement({
62
+ name,
63
+ appName,
64
+ duration,
65
+ timestamp: Date.now(),
66
+ type: 'duration'
67
+ });
68
+
69
+ // Verificar threshold
70
+ if (this.checkThreshold(name, duration)) {
71
+ console.warn(`[WuPerformance] ⚠️ Threshold exceeded for ${name}: ${duration.toFixed(2)}ms`);
72
+ }
73
+
74
+ console.log(`[WuPerformance] ⏹️ Measure ended: ${markName} (${duration.toFixed(2)}ms)`);
75
+ return duration;
76
+ }
77
+
78
+ /**
79
+ * 📝 RECORD MEASUREMENT: Registrar medición
80
+ * @param {Object} measurement - Medición
81
+ */
82
+ recordMeasurement(measurement) {
83
+ this.measurements.push(measurement);
84
+
85
+ // Mantener tamaño máximo
86
+ if (this.measurements.length > this.config.maxMeasurements) {
87
+ this.measurements.shift();
88
+ }
89
+
90
+ // Actualizar métricas de la app
91
+ if (!this.metrics.has(measurement.appName)) {
92
+ this.metrics.set(measurement.appName, {
93
+ appName: measurement.appName,
94
+ measurements: [],
95
+ stats: {}
96
+ });
97
+ }
98
+
99
+ const appMetrics = this.metrics.get(measurement.appName);
100
+ appMetrics.measurements.push(measurement);
101
+
102
+ // Calcular estadísticas
103
+ this.calculateStats(measurement.appName);
104
+ }
105
+
106
+ /**
107
+ * 📊 CALCULATE STATS: Calcular estadísticas
108
+ * @param {string} appName - Nombre de la app
109
+ */
110
+ calculateStats(appName) {
111
+ const appMetrics = this.metrics.get(appName);
112
+ if (!appMetrics) return;
113
+
114
+ const measurements = appMetrics.measurements;
115
+ if (measurements.length === 0) return;
116
+
117
+ // Agrupar por tipo de medición
118
+ const byType = {};
119
+ measurements.forEach(m => {
120
+ if (!byType[m.name]) byType[m.name] = [];
121
+ byType[m.name].push(m.duration);
122
+ });
123
+
124
+ // Calcular estadísticas para cada tipo
125
+ appMetrics.stats = {};
126
+ Object.entries(byType).forEach(([name, durations]) => {
127
+ appMetrics.stats[name] = {
128
+ count: durations.length,
129
+ avg: durations.reduce((a, b) => a + b, 0) / durations.length,
130
+ min: Math.min(...durations),
131
+ max: Math.max(...durations),
132
+ last: durations[durations.length - 1]
133
+ };
134
+ });
135
+ }
136
+
137
+ /**
138
+ * 🎯 CHECK THRESHOLD: Verificar si se excedió threshold
139
+ * @param {string} name - Nombre de la medición
140
+ * @param {number} value - Valor
141
+ * @returns {boolean}
142
+ */
143
+ checkThreshold(name, value) {
144
+ const threshold = this.thresholds[name];
145
+ return threshold && value > threshold;
146
+ }
147
+
148
+ /**
149
+ * 📊 GENERATE REPORT: Generar reporte de performance del framework
150
+ * @returns {Object}
151
+ */
152
+ generateReport() {
153
+ const report = {
154
+ timestamp: Date.now(),
155
+ totalMeasurements: this.measurements.length,
156
+ apps: {}
157
+ };
158
+
159
+ // Agregar métricas por app
160
+ for (const [appName, metrics] of this.metrics) {
161
+ report.apps[appName] = {
162
+ measurementCount: metrics.measurements.length,
163
+ stats: metrics.stats
164
+ };
165
+ }
166
+
167
+ return report;
168
+ }
169
+
170
+ /**
171
+ * 📋 GET METRICS: Obtener métricas de una app
172
+ * @param {string} appName - Nombre de la app
173
+ * @returns {Object}
174
+ */
175
+ getMetrics(appName) {
176
+ return this.metrics.get(appName) || null;
177
+ }
178
+
179
+ /**
180
+ * 📊 GET ALL METRICS: Obtener todas las métricas
181
+ * @returns {Object}
182
+ */
183
+ getAllMetrics() {
184
+ const allMetrics = {};
185
+
186
+ for (const [appName, metrics] of this.metrics) {
187
+ allMetrics[appName] = metrics;
188
+ }
189
+
190
+ return allMetrics;
191
+ }
192
+
193
+ /**
194
+ * 🧹 CLEAR METRICS: Limpiar métricas
195
+ * @param {string} appName - Nombre de la app (opcional)
196
+ */
197
+ clearMetrics(appName) {
198
+ if (appName) {
199
+ this.metrics.delete(appName);
200
+ this.measurements = this.measurements.filter(m => m.appName !== appName);
201
+ } else {
202
+ this.metrics.clear();
203
+ this.measurements = [];
204
+ }
205
+
206
+ console.log(`[WuPerformance] 🧹 Metrics cleared${appName ? ` for ${appName}` : ''}`);
207
+ }
208
+
209
+ /**
210
+ * ⚙️ CONFIGURE: Configurar performance monitor
211
+ * @param {Object} config - Nueva configuración
212
+ */
213
+ configure(config) {
214
+ this.config = {
215
+ ...this.config,
216
+ ...config
217
+ };
218
+
219
+ if (config.thresholds) {
220
+ this.thresholds = {
221
+ ...this.thresholds,
222
+ ...config.thresholds
223
+ };
224
+ }
225
+ }
226
+ }