versacompiler 2.0.0 → 2.0.2

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,845 @@
1
+ /**
2
+ * TypeScript Worker Pool - Pool de workers para compilación paralela
3
+ * Reemplaza el worker único con múltiples workers para aprovecha la concurrencia real
4
+ */
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import * as process from 'node:process';
8
+ import { Worker } from 'node:worker_threads';
9
+ import { validateTypesWithLanguageService } from './typescript-sync-validator.js';
10
+ /**
11
+ * Pool de workers para compilación TypeScript paralela
12
+ * Distribuye las tareas entre múltiples workers para mayor rendimiento
13
+ */
14
+ export class TypeScriptWorkerPool {
15
+ static instance;
16
+ workers = [];
17
+ poolSize;
18
+ workerPath;
19
+ initPromise = null;
20
+ isInitialized = false; // Configuración optimizada con reciclaje de workers
21
+ TASK_TIMEOUT = 10000; // 10 segundos por tarea
22
+ WORKER_INIT_TIMEOUT = 5000; // 5 segundos para inicializar
23
+ MAX_TASKS_PER_WORKER = 50; // Máximo de tareas por worker antes de reciclaje
24
+ WORKER_MEMORY_CHECK_INTERVAL = 100; // Verificar cada 100 tareas
25
+ // Métricas de rendimiento
26
+ totalTasks = 0;
27
+ completedTasks = 0;
28
+ failedTasks = 0;
29
+ constructor() {
30
+ // Determinar tamaño óptimo del pool
31
+ const cpuCount = os.cpus().length;
32
+ this.poolSize = Math.min(Math.max(cpuCount - 1, 2), 8); // Entre 2 y 8 workers
33
+ this.workerPath = path.join(process.env.PATH_PROY || path.join(process.cwd(), 'src'), 'compiler', 'typescript-worker-thread.cjs');
34
+ // ✨ ISSUE #4: Configurar monitoreo de memoria automático
35
+ this.startMemoryMonitoring();
36
+ }
37
+ // ✨ ISSUE #4: Métodos de control de memoria y timeouts
38
+ /**
39
+ * Inicia el monitoreo automático de memoria de workers
40
+ */
41
+ startMemoryMonitoring() {
42
+ // Monitoreo cada 30 segundos
43
+ setInterval(() => {
44
+ this.checkWorkersMemory();
45
+ }, 30000);
46
+ // Limpieza de workers inactivos cada 5 minutos
47
+ setInterval(() => {
48
+ this.cleanupInactiveWorkers();
49
+ }, 300000);
50
+ }
51
+ /**
52
+ * Verifica el uso de memoria de todos los workers con medición real
53
+ */
54
+ async checkWorkersMemory() {
55
+ const now = Date.now();
56
+ for (const poolWorker of this.workers) {
57
+ // Actualizar tiempo de última verificación
58
+ poolWorker.lastMemoryCheck = now;
59
+ try {
60
+ // ✨ ISSUE #4: Obtener memoria real del worker
61
+ const memoryInfo = await this.getWorkerMemoryUsage(poolWorker);
62
+ poolWorker.memoryUsage = memoryInfo.heapUsed;
63
+ // Verificar límites de memoria y reciclaje automático
64
+ if (this.shouldRecycleWorker(poolWorker)) {
65
+ const reason = this.getRecycleReason(poolWorker);
66
+ console.warn(`[WorkerPool] Worker ${poolWorker.id} requiere reciclaje: ${reason}`);
67
+ await this.recycleWorker(poolWorker);
68
+ }
69
+ }
70
+ catch (error) {
71
+ console.warn(`[WorkerPool] Error verificando memoria del worker ${poolWorker.id}:`, error);
72
+ }
73
+ }
74
+ }
75
+ /**
76
+ * Obtiene el uso real de memoria de un worker
77
+ */
78
+ async getWorkerMemoryUsage(poolWorker) {
79
+ return new Promise(resolve => {
80
+ const timeout = setTimeout(() => {
81
+ // Fallback con estimación si no hay respuesta
82
+ resolve({
83
+ heapUsed: poolWorker.tasksProcessed * 2048, // 2KB por tarea
84
+ heapTotal: poolWorker.tasksProcessed * 3072, // 3KB total estimado
85
+ rss: poolWorker.tasksProcessed * 4096, // 4KB RSS estimado
86
+ });
87
+ }, 1000);
88
+ // Solicitar memoria real del worker
89
+ const memoryRequestId = `memory-${poolWorker.id}-${Date.now()}`;
90
+ const handler = (response) => {
91
+ if (response.id === memoryRequestId &&
92
+ response.type === 'memory-usage') {
93
+ clearTimeout(timeout);
94
+ poolWorker.worker.off('message', handler);
95
+ resolve({
96
+ heapUsed: response.memoryUsage?.heapUsed ||
97
+ poolWorker.tasksProcessed * 2048,
98
+ heapTotal: response.memoryUsage?.heapTotal ||
99
+ poolWorker.tasksProcessed * 3072,
100
+ rss: response.memoryUsage?.rss ||
101
+ poolWorker.tasksProcessed * 4096,
102
+ });
103
+ }
104
+ };
105
+ poolWorker.worker.on('message', handler);
106
+ poolWorker.worker.postMessage({
107
+ type: 'get-memory-usage',
108
+ id: memoryRequestId,
109
+ });
110
+ });
111
+ }
112
+ /**
113
+ * Obtiene la razón por la cual un worker debe ser reciclado
114
+ */
115
+ getRecycleReason(poolWorker) {
116
+ const now = Date.now();
117
+ const MEMORY_LIMIT = 50 * 1024 * 1024; // 50MB
118
+ const TIME_LIMIT = 30 * 60 * 1000; // 30 minutos
119
+ const TASK_LIMIT = this.MAX_TASKS_PER_WORKER;
120
+ const reasons = [];
121
+ if (poolWorker.memoryUsage > MEMORY_LIMIT) {
122
+ reasons.push(`memoria excede ${Math.round(MEMORY_LIMIT / 1024 / 1024)}MB (actual: ${Math.round(poolWorker.memoryUsage / 1024 / 1024)}MB)`);
123
+ }
124
+ if (now - poolWorker.creationTime > TIME_LIMIT) {
125
+ reasons.push(`tiempo de vida excede ${Math.round(TIME_LIMIT / 60000)} minutos`);
126
+ }
127
+ if (poolWorker.tasksProcessed >= TASK_LIMIT) {
128
+ reasons.push(`tareas procesadas exceden ${TASK_LIMIT} (actual: ${poolWorker.tasksProcessed})`);
129
+ }
130
+ return reasons.join(', ');
131
+ }
132
+ /**
133
+ * Limpia workers que han estado inactivos por mucho tiempo
134
+ */
135
+ async cleanupInactiveWorkers() {
136
+ const now = Date.now();
137
+ const INACTIVE_TIMEOUT = 10 * 60 * 1000; // 10 minutos
138
+ for (const poolWorker of this.workers) {
139
+ const timeSinceLastActivity = now - poolWorker.lastActivityTime;
140
+ if (timeSinceLastActivity > INACTIVE_TIMEOUT &&
141
+ !poolWorker.busy &&
142
+ poolWorker.pendingTasks.size === 0) {
143
+ console.info(`[WorkerPool] Worker ${poolWorker.id} inactivo por ${Math.round(timeSinceLastActivity / 60000)} minutos, reciclando...`);
144
+ await this.recycleWorker(poolWorker);
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Verifica si un worker debe ser reciclado por límites de memoria/tiempo
150
+ */
151
+ shouldRecycleWorker(poolWorker) {
152
+ const now = Date.now();
153
+ const MEMORY_LIMIT = 50 * 1024 * 1024; // 50MB
154
+ const TIME_LIMIT = 30 * 60 * 1000; // 30 minutos
155
+ const TASK_LIMIT = this.MAX_TASKS_PER_WORKER;
156
+ return (poolWorker.memoryUsage > MEMORY_LIMIT ||
157
+ now - poolWorker.creationTime > TIME_LIMIT ||
158
+ poolWorker.tasksProcessed >= TASK_LIMIT);
159
+ }
160
+ /**
161
+ * Obtiene la instancia singleton del Worker Pool
162
+ */
163
+ static getInstance() {
164
+ if (!TypeScriptWorkerPool.instance) {
165
+ TypeScriptWorkerPool.instance = new TypeScriptWorkerPool();
166
+ }
167
+ return TypeScriptWorkerPool.instance;
168
+ }
169
+ /**
170
+ * Configura el modo de operación del pool
171
+ */
172
+ setMode(mode) {
173
+ // Ajustar configuración según el modo
174
+ switch (mode) {
175
+ case 'batch':
176
+ // Para modo batch, optimizar para throughput
177
+ this.poolSize = Math.min(os.cpus().length, 12);
178
+ break;
179
+ case 'watch':
180
+ // Para modo watch, menos workers pero más responsivos
181
+ this.poolSize = Math.min(Math.max(os.cpus().length / 2, 2), 6);
182
+ break;
183
+ case 'individual':
184
+ // Para individual, pool pequeño
185
+ this.poolSize = Math.min(4, os.cpus().length);
186
+ break;
187
+ }
188
+ }
189
+ /**
190
+ * Inicializa el pool de workers
191
+ */
192
+ async initializePool() {
193
+ if (this.initPromise) {
194
+ return this.initPromise;
195
+ }
196
+ this.initPromise = this._performPoolInitialization();
197
+ return this.initPromise;
198
+ }
199
+ /**
200
+ * Realiza la inicialización del pool de workers
201
+ */
202
+ async _performPoolInitialization() {
203
+ try {
204
+ // Verificar que el archivo del worker existe
205
+ const fs = await import('node:fs');
206
+ const exists = fs.existsSync(this.workerPath);
207
+ if (!exists) {
208
+ throw new Error(`Worker thread file not found: ${this.workerPath}`);
209
+ }
210
+ // Crear workers en paralelo
211
+ const workerPromises = Array.from({ length: this.poolSize }, (_, index) => this.createWorker(index));
212
+ this.workers = await Promise.all(workerPromises);
213
+ this.isInitialized = true;
214
+ }
215
+ catch (error) {
216
+ this.isInitialized = false;
217
+ throw error;
218
+ }
219
+ }
220
+ /**
221
+ * Crea un worker individual
222
+ */
223
+ async createWorker(workerId) {
224
+ return new Promise((resolve, reject) => {
225
+ try {
226
+ const worker = new Worker(this.workerPath, {
227
+ env: {
228
+ ...process.env,
229
+ NODE_OPTIONS: '',
230
+ WORKER_ID: workerId.toString(),
231
+ },
232
+ });
233
+ const poolWorker = {
234
+ worker,
235
+ id: workerId,
236
+ busy: false,
237
+ pendingTasks: new Map(),
238
+ taskCounter: 0,
239
+ // ✨ ISSUE #4: Inicializar controles de memoria
240
+ memoryUsage: 0,
241
+ lastMemoryCheck: Date.now(),
242
+ tasksProcessed: 0,
243
+ creationTime: Date.now(),
244
+ lastActivityTime: Date.now(),
245
+ };
246
+ // Configurar listeners
247
+ this.setupWorkerListeners(poolWorker);
248
+ // Timeout para inicialización
249
+ const initTimeout = setTimeout(() => {
250
+ reject(new Error(`Worker ${workerId} initialization timeout`));
251
+ }, this.WORKER_INIT_TIMEOUT);
252
+ // Esperar que el worker esté listo
253
+ const checkReady = () => {
254
+ worker.postMessage({ type: 'ping' });
255
+ };
256
+ worker.on('message', function readyHandler(response) {
257
+ if (response.id === 'worker-ready' ||
258
+ response.message === 'pong') {
259
+ clearTimeout(initTimeout);
260
+ worker.off('message', readyHandler);
261
+ resolve(poolWorker);
262
+ }
263
+ });
264
+ worker.on('error', error => {
265
+ clearTimeout(initTimeout);
266
+ reject(error);
267
+ });
268
+ // Intentar conectar
269
+ checkReady();
270
+ }
271
+ catch (error) {
272
+ reject(error);
273
+ }
274
+ });
275
+ }
276
+ /**
277
+ * Configura los listeners para un worker individual
278
+ */
279
+ setupWorkerListeners(poolWorker) {
280
+ const { worker } = poolWorker;
281
+ worker.on('message', (response) => {
282
+ try {
283
+ // Ignorar mensajes de control
284
+ if (response.id === 'worker-ready' ||
285
+ response.message === 'pong') {
286
+ return;
287
+ }
288
+ // ✨ ISSUE #4: Manejar reportes de memoria del worker
289
+ if (response.type === 'memory-usage') {
290
+ return; // Ya manejado en getWorkerMemoryUsage
291
+ }
292
+ // Buscar la tarea pendiente
293
+ const pendingTask = poolWorker.pendingTasks.get(response.id);
294
+ if (!pendingTask) {
295
+ return;
296
+ }
297
+ // Limpiar timeout y eliminar tarea
298
+ clearTimeout(pendingTask.timeout);
299
+ poolWorker.pendingTasks.delete(response.id);
300
+ // Marcar worker como disponible si no tiene más tareas
301
+ if (poolWorker.pendingTasks.size === 0) {
302
+ poolWorker.busy = false;
303
+ }
304
+ // Actualizar actividad del worker
305
+ poolWorker.lastActivityTime = Date.now();
306
+ // ✨ ISSUE #4: Manejo mejorado de errores de TypeScript
307
+ if (response.success &&
308
+ response.diagnostics !== undefined &&
309
+ response.hasErrors !== undefined) {
310
+ this.completedTasks++;
311
+ // Analizar y categorizar errores de TypeScript
312
+ const categorizedResult = this.categorizeTypeScriptErrors({
313
+ diagnostics: response.diagnostics,
314
+ hasErrors: response.hasErrors,
315
+ }, pendingTask.fileName);
316
+ pendingTask.resolve(categorizedResult);
317
+ }
318
+ else {
319
+ this.failedTasks++;
320
+ const errorMessage = response.error || 'Error desconocido del worker';
321
+ // ✨ Crear error categorizado
322
+ const categorizedError = this.createCategorizedError(errorMessage, pendingTask.fileName, response);
323
+ pendingTask.reject(categorizedError);
324
+ }
325
+ }
326
+ catch {
327
+ // Error silencioso - no imprimir cada error
328
+ }
329
+ });
330
+ worker.on('error', async (error) => {
331
+ await this.handleWorkerError(poolWorker, error);
332
+ });
333
+ worker.on('exit', async (code) => {
334
+ await this.handleWorkerExit(poolWorker, code);
335
+ });
336
+ }
337
+ /**
338
+ * Maneja errores de un worker específico con cleanup completo
339
+ */
340
+ async handleWorkerError(poolWorker, error) {
341
+ console.warn(`[WorkerPool] Manejando error del worker ${poolWorker.id}:`, error.message);
342
+ // 1. Rechazar todas las tareas pendientes con cleanup de timeouts
343
+ poolWorker.pendingTasks.forEach(task => {
344
+ clearTimeout(task.timeout);
345
+ this.failedTasks++;
346
+ task.reject(new Error(`Worker ${poolWorker.id} failed: ${error.message}`));
347
+ });
348
+ poolWorker.pendingTasks.clear();
349
+ // 2. Terminar worker correctamente para evitar memory leaks
350
+ try {
351
+ poolWorker.worker.removeAllListeners();
352
+ await poolWorker.worker.terminate();
353
+ }
354
+ catch (terminateError) {
355
+ console.error(`[WorkerPool] Error terminando worker ${poolWorker.id}:`, terminateError);
356
+ }
357
+ // 3. Marcar como no disponible
358
+ poolWorker.busy = false;
359
+ // 4. Recrear worker si el pool está activo
360
+ if (this.isInitialized && this.workers.length > 0) {
361
+ try {
362
+ const newWorker = await this.createWorker(poolWorker.id);
363
+ const index = this.workers.findIndex(w => w.id === poolWorker.id);
364
+ if (index !== -1) {
365
+ this.workers[index] = newWorker;
366
+ }
367
+ }
368
+ catch (recreateError) {
369
+ console.error(`[WorkerPool] No se pudo recrear worker ${poolWorker.id}:`, recreateError);
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * Maneja la salida inesperada de un worker con cleanup completo
375
+ */
376
+ async handleWorkerExit(poolWorker, code) {
377
+ console.warn(`[WorkerPool] Worker ${poolWorker.id} salió con código ${code}`);
378
+ // 1. Rechazar tareas pendientes con cleanup
379
+ poolWorker.pendingTasks.forEach(task => {
380
+ clearTimeout(task.timeout);
381
+ this.failedTasks++;
382
+ task.reject(new Error(`Worker ${poolWorker.id} exited with code ${code}`));
383
+ });
384
+ poolWorker.pendingTasks.clear();
385
+ poolWorker.busy = false;
386
+ // 2. Limpiar listeners para evitar memory leaks
387
+ try {
388
+ poolWorker.worker.removeAllListeners();
389
+ }
390
+ catch {
391
+ // Error silencioso en cleanup
392
+ }
393
+ // 3. Recrear worker si es necesario y el pool está activo
394
+ if (this.isInitialized && this.workers.length > 0) {
395
+ try {
396
+ await this.recreateWorker(poolWorker);
397
+ }
398
+ catch (recreateError) {
399
+ console.error(`[WorkerPool] Error recreando worker ${poolWorker.id}:`, recreateError);
400
+ }
401
+ }
402
+ }
403
+ /**
404
+ * Recrea un worker que falló
405
+ */
406
+ async recreateWorker(failedWorker) {
407
+ try {
408
+ const newWorker = await this.createWorker(failedWorker.id);
409
+ // Reemplazar en el array
410
+ const index = this.workers.findIndex(w => w.id === failedWorker.id);
411
+ if (index !== -1) {
412
+ this.workers[index] = newWorker;
413
+ }
414
+ }
415
+ catch {
416
+ // Error silencioso en recreación
417
+ }
418
+ }
419
+ /**
420
+ * Recicla un worker para prevenir memory leaks
421
+ */
422
+ async recycleWorker(poolWorker) {
423
+ try {
424
+ console.log(`[WorkerPool] Reciclando worker ${poolWorker.id} después de ${poolWorker.taskCounter} tareas`);
425
+ // 1. Esperar a que termine tareas pendientes (timeout corto)
426
+ const maxWait = 2000; // 2 segundos máximo
427
+ const startTime = Date.now();
428
+ await new Promise(resolve => {
429
+ const checkPending = () => {
430
+ if (poolWorker.pendingTasks.size === 0 ||
431
+ Date.now() - startTime >= maxWait) {
432
+ resolve();
433
+ }
434
+ else {
435
+ setTimeout(checkPending, 100);
436
+ }
437
+ };
438
+ checkPending();
439
+ });
440
+ // 2. Si aún hay tareas pendientes, rechazarlas
441
+ if (poolWorker.pendingTasks.size > 0) {
442
+ poolWorker.pendingTasks.forEach(task => {
443
+ clearTimeout(task.timeout);
444
+ task.reject(new Error('Worker being recycled'));
445
+ });
446
+ poolWorker.pendingTasks.clear();
447
+ }
448
+ // 3. Terminar worker actual
449
+ poolWorker.worker.removeAllListeners();
450
+ await poolWorker.worker.terminate();
451
+ // 4. Crear nuevo worker
452
+ const newWorker = await this.createWorker(poolWorker.id);
453
+ // 5. Reemplazar en el array
454
+ const index = this.workers.findIndex(w => w.id === poolWorker.id);
455
+ if (index !== -1) {
456
+ this.workers[index] = newWorker;
457
+ }
458
+ }
459
+ catch (error) {
460
+ console.error(`[WorkerPool] Error reciclando worker ${poolWorker.id}:`, error);
461
+ }
462
+ }
463
+ /**
464
+ * Encuentra el worker menos ocupado
465
+ */
466
+ findAvailableWorker() {
467
+ // Buscar worker completamente libre
468
+ const freeWorker = this.workers.find(w => !w.busy && w.pendingTasks.size === 0);
469
+ if (freeWorker) {
470
+ return freeWorker;
471
+ }
472
+ // Si no hay workers libres, buscar el menos ocupado
473
+ const leastBusyWorker = this.workers.reduce((least, current) => {
474
+ if (current.pendingTasks.size < least.pendingTasks.size) {
475
+ return current;
476
+ }
477
+ return least;
478
+ });
479
+ // Solo devolver si no está demasiado ocupado
480
+ if (leastBusyWorker.pendingTasks.size < 5) {
481
+ return leastBusyWorker;
482
+ }
483
+ return null; // Todos los workers están muy ocupados
484
+ }
485
+ /**
486
+ * Realiza type checking usando el pool de workers
487
+ */
488
+ async typeCheck(fileName, content, compilerOptions) {
489
+ // Asegurar que el pool esté inicializado
490
+ await this.initializePool();
491
+ if (!this.isInitialized) {
492
+ return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
493
+ }
494
+ // Buscar worker disponible
495
+ const availableWorker = this.findAvailableWorker();
496
+ if (!availableWorker) {
497
+ return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
498
+ }
499
+ try {
500
+ this.totalTasks++;
501
+ return await this.typeCheckWithWorker(availableWorker, fileName, content, compilerOptions);
502
+ }
503
+ catch {
504
+ return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
505
+ }
506
+ }
507
+ /**
508
+ * Realiza type checking usando un worker específico con reciclaje automático
509
+ */
510
+ async typeCheckWithWorker(poolWorker, fileName, content, compilerOptions) {
511
+ // Verificar si el worker necesita reciclaje por número de tareas
512
+ if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER) {
513
+ await this.recycleWorker(poolWorker);
514
+ }
515
+ return new Promise((resolve, reject) => {
516
+ const taskId = `task-${poolWorker.id}-${++poolWorker.taskCounter}-${Date.now()}`;
517
+ // Marcar worker como ocupado y actualizar actividad
518
+ poolWorker.busy = true;
519
+ poolWorker.lastActivityTime = Date.now();
520
+ poolWorker.tasksProcessed++;
521
+ // ✨ ISSUE #4: Timeout dinámico basado en complejidad del archivo
522
+ const dynamicTimeout = this.calculateDynamicTimeout(fileName, content, compilerOptions);
523
+ // Configurar timeout para la tarea
524
+ const timeout = setTimeout(() => {
525
+ poolWorker.pendingTasks.delete(taskId);
526
+ if (poolWorker.pendingTasks.size === 0) {
527
+ poolWorker.busy = false;
528
+ }
529
+ reject(new Error(`Timeout (${dynamicTimeout}ms) en type checking para ${fileName} - archivo complejo detectado`));
530
+ }, dynamicTimeout);
531
+ // Agregar tarea a la lista de pendientes del worker
532
+ poolWorker.pendingTasks.set(taskId, {
533
+ resolve,
534
+ reject,
535
+ timeout,
536
+ fileName,
537
+ startTime: Date.now(),
538
+ });
539
+ try {
540
+ // Crear mensaje para el worker con configuración de timeout
541
+ const message = {
542
+ id: taskId,
543
+ fileName,
544
+ content,
545
+ compilerOptions: {
546
+ ...compilerOptions,
547
+ // ✨ Añadir timeout al worker para manejo interno
548
+ _workerTimeout: dynamicTimeout - 1000, // 1 segundo menos para cleanup interno
549
+ },
550
+ };
551
+ // Enviar mensaje al worker
552
+ poolWorker.worker.postMessage(message);
553
+ }
554
+ catch (error) {
555
+ // Limpiar en caso de error
556
+ clearTimeout(timeout);
557
+ poolWorker.pendingTasks.delete(taskId);
558
+ if (poolWorker.pendingTasks.size === 0) {
559
+ poolWorker.busy = false;
560
+ }
561
+ reject(error);
562
+ }
563
+ });
564
+ }
565
+ /**
566
+ * Calcula un timeout dinámico basado en la complejidad del archivo TypeScript
567
+ */
568
+ calculateDynamicTimeout(fileName, content, compilerOptions) {
569
+ const baseTimeout = this.TASK_TIMEOUT; // 10 segundos base
570
+ let complexityMultiplier = 1;
571
+ // Factor 1: Tamaño del archivo
572
+ const contentLength = content.length;
573
+ if (contentLength > 100000) {
574
+ // Archivos > 100KB
575
+ complexityMultiplier += 1.5;
576
+ }
577
+ else if (contentLength > 50000) {
578
+ // Archivos > 50KB
579
+ complexityMultiplier += 1;
580
+ }
581
+ else if (contentLength > 20000) {
582
+ // Archivos > 20KB
583
+ complexityMultiplier += 0.5;
584
+ }
585
+ // Factor 2: Complejidad sintáctica
586
+ const importCount = (content.match(/^import\s+/gm) || []).length;
587
+ const typeCount = (content.match(/\btype\s+\w+/g) || []).length;
588
+ const interfaceCount = (content.match(/\binterface\s+\w+/g) || [])
589
+ .length;
590
+ const genericCount = (content.match(/<[^>]*>/g) || []).length;
591
+ const complexConstructs = importCount + typeCount + interfaceCount + genericCount * 0.5;
592
+ if (complexConstructs > 100) {
593
+ complexityMultiplier += 2;
594
+ }
595
+ else if (complexConstructs > 50) {
596
+ complexityMultiplier += 1;
597
+ }
598
+ else if (complexConstructs > 20) {
599
+ complexityMultiplier += 0.5;
600
+ }
601
+ // Factor 3: Configuración de TypeScript estricta
602
+ if (compilerOptions?.strict || compilerOptions?.noImplicitAny) {
603
+ complexityMultiplier += 0.3;
604
+ }
605
+ // Factor 4: Extensión de archivo compleja (.d.ts, .vue.ts, etc.)
606
+ if (fileName.includes('.d.ts')) {
607
+ complexityMultiplier += 1; // Los archivos de definición son más complejos
608
+ }
609
+ else if (fileName.includes('.vue')) {
610
+ complexityMultiplier += 0.5; // Los archivos Vue requieren procesamiento adicional
611
+ }
612
+ // Aplicar límites razonables
613
+ complexityMultiplier = Math.min(complexityMultiplier, 5); // Máximo 5x el timeout base
614
+ complexityMultiplier = Math.max(complexityMultiplier, 0.5); // Mínimo 0.5x el timeout base
615
+ const finalTimeout = Math.round(baseTimeout * complexityMultiplier);
616
+ return Math.min(finalTimeout, 60000); // Máximo absoluto de 60 segundos
617
+ }
618
+ /**
619
+ * Fallback síncrono para type checking
620
+ */
621
+ typeCheckWithSyncFallback(fileName, content, compilerOptions) {
622
+ try {
623
+ return validateTypesWithLanguageService(fileName, content, compilerOptions);
624
+ }
625
+ catch {
626
+ return {
627
+ diagnostics: [],
628
+ hasErrors: false,
629
+ };
630
+ }
631
+ }
632
+ /**
633
+ * Cierra todos los workers del pool con cleanup completo
634
+ */
635
+ async terminate() {
636
+ console.log('[WorkerPool] Cerrando pool de workers...');
637
+ // 1. Rechazar todas las tareas pendientes con cleanup
638
+ let totalPendingTasks = 0;
639
+ for (const poolWorker of this.workers) {
640
+ totalPendingTasks += poolWorker.pendingTasks.size;
641
+ poolWorker.pendingTasks.forEach(task => {
642
+ clearTimeout(task.timeout);
643
+ task.reject(new Error('Worker pool cerrado'));
644
+ });
645
+ poolWorker.pendingTasks.clear();
646
+ // Limpiar listeners para evitar memory leaks
647
+ try {
648
+ poolWorker.worker.removeAllListeners();
649
+ }
650
+ catch {
651
+ // Error silencioso en cleanup
652
+ }
653
+ }
654
+ if (totalPendingTasks > 0) {
655
+ console.log(`[WorkerPool] Se cancelaron ${totalPendingTasks} tareas pendientes`);
656
+ }
657
+ // 2. Cerrar todos los workers con manejo de errores
658
+ const terminatePromises = this.workers.map(async (poolWorker) => {
659
+ try {
660
+ await poolWorker.worker.terminate();
661
+ }
662
+ catch (error) {
663
+ console.warn(`[WorkerPool] Error terminando worker ${poolWorker.id}:`, error);
664
+ }
665
+ });
666
+ await Promise.allSettled(terminatePromises);
667
+ // 3. Limpiar estado
668
+ this.workers = [];
669
+ this.isInitialized = false;
670
+ this.initPromise = null;
671
+ console.log(`[WorkerPool] Pool cerrado. Estadísticas finales: ${this.completedTasks} completadas, ${this.failedTasks} fallidas`);
672
+ }
673
+ /**
674
+ * Obtiene estadísticas del pool
675
+ */
676
+ getStats() {
677
+ const busyWorkers = this.workers.filter(w => w.busy).length;
678
+ const totalPendingTasks = this.workers.reduce((sum, w) => sum + w.pendingTasks.size, 0);
679
+ const successRate = this.totalTasks > 0
680
+ ? Math.round((this.completedTasks / this.totalTasks) * 100)
681
+ : 0;
682
+ return {
683
+ poolSize: this.workers.length,
684
+ busyWorkers,
685
+ totalPendingTasks,
686
+ totalTasks: this.totalTasks,
687
+ completedTasks: this.completedTasks,
688
+ failedTasks: this.failedTasks,
689
+ successRate,
690
+ };
691
+ }
692
+ // ✨ ISSUE #4: Métodos de categorización de errores de TypeScript
693
+ /**
694
+ * Categoriza y mejora los errores de TypeScript para mejor debugging
695
+ */
696
+ categorizeTypeScriptErrors(result, fileName) {
697
+ if (!result.hasErrors || !result.diagnostics?.length) {
698
+ return result;
699
+ }
700
+ const categorizedDiagnostics = result.diagnostics.map(diagnostic => {
701
+ // Añadir metadatos útiles para debugging
702
+ const enhanced = {
703
+ ...diagnostic,
704
+ _category: this.getErrorCategory(diagnostic),
705
+ _severity: this.getErrorSeverity(diagnostic),
706
+ _fileName: fileName,
707
+ _timestamp: Date.now(),
708
+ };
709
+ return enhanced;
710
+ });
711
+ return {
712
+ ...result,
713
+ diagnostics: categorizedDiagnostics,
714
+ // Añadir estadísticas de errores
715
+ _errorStats: this.calculateErrorStats(categorizedDiagnostics),
716
+ };
717
+ } /**
718
+ * Determina la categoría de un error de TypeScript
719
+ */
720
+ getErrorCategory(diagnostic) {
721
+ const code = diagnostic.code;
722
+ // Categorización basada en códigos de error comunes
723
+ if ([2304, 2339, 2346].includes(code)) {
724
+ return 'MISSING_DECLARATION'; // No puede encontrar nombre/propiedad
725
+ }
726
+ else if ([2322, 2322, 2345].includes(code)) {
727
+ return 'TYPE_MISMATCH'; // Error de tipos
728
+ }
729
+ else if ([2307, 2317].includes(code)) {
730
+ return 'MODULE_RESOLUTION'; // Error de resolución de módulos
731
+ }
732
+ else if ([2552, 2551].includes(code)) {
733
+ return 'PROPERTY_ACCESS'; // Error de acceso a propiedades
734
+ }
735
+ else if (code >= 1000 && code < 2000) {
736
+ return 'SYNTAX_ERROR'; // Errores de sintaxis
737
+ }
738
+ else if (code >= 2000 && code < 3000) {
739
+ return 'SEMANTIC_ERROR'; // Errores semánticos
740
+ }
741
+ else if (code >= 4000) {
742
+ return 'CONFIG_ERROR'; // Errores de configuración
743
+ }
744
+ return 'OTHER';
745
+ }
746
+ /**
747
+ * Determina la severidad de un error de TypeScript
748
+ */
749
+ getErrorSeverity(diagnostic) {
750
+ const category = diagnostic.category;
751
+ switch (category) {
752
+ case 1:
753
+ return 'ERROR'; // typescript.DiagnosticCategory.Error
754
+ case 2:
755
+ return 'WARNING'; // typescript.DiagnosticCategory.Warning
756
+ case 3:
757
+ return 'INFO'; // typescript.DiagnosticCategory.Message
758
+ default:
759
+ return 'ERROR';
760
+ }
761
+ }
762
+ /**
763
+ * Calcula estadísticas de errores para análisis
764
+ */
765
+ calculateErrorStats(diagnostics) {
766
+ const stats = {
767
+ totalErrors: diagnostics.length,
768
+ errorsByCategory: {},
769
+ errorsBySeverity: {},
770
+ mostCommonError: null,
771
+ };
772
+ // Contar por categoría y severidad
773
+ diagnostics.forEach(diag => {
774
+ const category = diag._category || 'OTHER';
775
+ const severity = diag._severity || 'ERROR';
776
+ stats.errorsByCategory[category] =
777
+ (stats.errorsByCategory[category] || 0) + 1;
778
+ stats.errorsBySeverity[severity] =
779
+ (stats.errorsBySeverity[severity] || 0) + 1;
780
+ }); // Encontrar el error más común
781
+ const errorCounts = {};
782
+ diagnostics.forEach(diag => {
783
+ const code = String(diag.code);
784
+ errorCounts[code] = (errorCounts[code] || 0) + 1;
785
+ });
786
+ const errorCountKeys = Object.keys(errorCounts);
787
+ if (errorCountKeys.length > 0) {
788
+ const mostCommonCode = errorCountKeys.reduce((a, b) => (errorCounts[a] || 0) > (errorCounts[b] || 0) ? a : b);
789
+ stats.mostCommonError = {
790
+ code: mostCommonCode,
791
+ count: errorCounts[mostCommonCode],
792
+ message: diagnostics.find(d => String(d.code) === mostCommonCode)?.messageText,
793
+ };
794
+ }
795
+ return stats;
796
+ }
797
+ /**
798
+ * Crea un error categorizado con información de contexto
799
+ */
800
+ createCategorizedError(errorMessage, fileName, response) {
801
+ const error = new Error(errorMessage);
802
+ // Añadir metadatos del error
803
+ error.fileName = fileName;
804
+ error.timestamp = Date.now();
805
+ error.workerResponse = response;
806
+ error.category = this.categorizeGenericError(errorMessage);
807
+ error.isRecoverable = this.isRecoverableError(errorMessage);
808
+ return error;
809
+ }
810
+ /**
811
+ * Categoriza errores genéricos del worker
812
+ */
813
+ categorizeGenericError(errorMessage) {
814
+ if (errorMessage.includes('timeout') ||
815
+ errorMessage.includes('Timeout')) {
816
+ return 'TIMEOUT';
817
+ }
818
+ else if (errorMessage.includes('memory') ||
819
+ errorMessage.includes('Memory')) {
820
+ return 'MEMORY';
821
+ }
822
+ else if (errorMessage.includes('Worker') &&
823
+ errorMessage.includes('exited')) {
824
+ return 'WORKER_CRASH';
825
+ }
826
+ else if (errorMessage.includes('initialization') ||
827
+ errorMessage.includes('init')) {
828
+ return 'INITIALIZATION';
829
+ }
830
+ return 'UNKNOWN';
831
+ }
832
+ /**
833
+ * Determina si un error es recuperable
834
+ */
835
+ isRecoverableError(errorMessage) {
836
+ const recoverablePatterns = [
837
+ 'timeout',
838
+ 'Worker being recycled',
839
+ 'Worker pool cerrado',
840
+ 'temporary',
841
+ ];
842
+ return recoverablePatterns.some(pattern => errorMessage.toLowerCase().includes(pattern.toLowerCase()));
843
+ }
844
+ }
845
+ //# sourceMappingURL=typescript-worker-pool.js.map