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