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.
- package/README.md +345 -69
- package/dist/compiler/compile.js +762 -183
- package/dist/compiler/minify.js +199 -10
- package/dist/compiler/module-resolution-optimizer.js +822 -0
- package/dist/compiler/parser.js +179 -6
- package/dist/compiler/performance-monitor.js +192 -0
- package/dist/compiler/transform-optimizer.js +392 -0
- package/dist/compiler/transforms.js +124 -118
- package/dist/compiler/{typescript.js → typescript-compiler.js} +27 -30
- package/dist/compiler/typescript-error-parser.js +6 -7
- package/dist/compiler/typescript-manager.js +378 -0
- package/dist/compiler/typescript-sync-validator.js +13 -15
- package/dist/compiler/typescript-worker-pool.js +845 -0
- package/dist/compiler/typescript-worker.js +51 -21
- package/dist/compiler/vuejs.js +131 -37
- package/dist/main.js +5 -6
- package/dist/servicios/browserSync.js +313 -21
- package/dist/servicios/file-watcher.js +367 -0
- package/dist/servicios/logger.js +1 -0
- package/dist/servicios/readConfig.js +1 -0
- package/dist/utils/excluded-modules.js +36 -0
- package/dist/utils/module-resolver.js +9 -48
- package/dist/utils/promptUser.js +1 -1
- package/dist/utils/resolve-bin.js +28 -9
- package/package.json +8 -7
- package/dist/servicios/chokidar.js +0 -178
|
@@ -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
|