tywrap 0.1.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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +263 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/index.d.ts +41 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +234 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/core/analyzer.d.ts +63 -0
  12. package/dist/core/analyzer.d.ts.map +1 -0
  13. package/dist/core/analyzer.js +676 -0
  14. package/dist/core/analyzer.js.map +1 -0
  15. package/dist/core/discovery.d.ts +85 -0
  16. package/dist/core/discovery.d.ts.map +1 -0
  17. package/dist/core/discovery.js +344 -0
  18. package/dist/core/discovery.js.map +1 -0
  19. package/dist/core/generator.d.ts +37 -0
  20. package/dist/core/generator.d.ts.map +1 -0
  21. package/dist/core/generator.js +368 -0
  22. package/dist/core/generator.js.map +1 -0
  23. package/dist/core/mapper.d.ts +40 -0
  24. package/dist/core/mapper.d.ts.map +1 -0
  25. package/dist/core/mapper.js +401 -0
  26. package/dist/core/mapper.js.map +1 -0
  27. package/dist/core/validation.d.ts +79 -0
  28. package/dist/core/validation.d.ts.map +1 -0
  29. package/dist/core/validation.js +486 -0
  30. package/dist/core/validation.js.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/base.d.ts +12 -0
  36. package/dist/runtime/base.d.ts.map +1 -0
  37. package/dist/runtime/base.js +6 -0
  38. package/dist/runtime/base.js.map +1 -0
  39. package/dist/runtime/errors.d.ts +17 -0
  40. package/dist/runtime/errors.d.ts.map +1 -0
  41. package/dist/runtime/errors.js +21 -0
  42. package/dist/runtime/errors.js.map +1 -0
  43. package/dist/runtime/http.d.ts +22 -0
  44. package/dist/runtime/http.d.ts.map +1 -0
  45. package/dist/runtime/http.js +79 -0
  46. package/dist/runtime/http.js.map +1 -0
  47. package/dist/runtime/index.d.ts +8 -0
  48. package/dist/runtime/index.d.ts.map +1 -0
  49. package/dist/runtime/index.js +17 -0
  50. package/dist/runtime/index.js.map +1 -0
  51. package/dist/runtime/node.d.ts +48 -0
  52. package/dist/runtime/node.d.ts.map +1 -0
  53. package/dist/runtime/node.js +328 -0
  54. package/dist/runtime/node.js.map +1 -0
  55. package/dist/runtime/optimized-node.d.ts +131 -0
  56. package/dist/runtime/optimized-node.d.ts.map +1 -0
  57. package/dist/runtime/optimized-node.js +622 -0
  58. package/dist/runtime/optimized-node.js.map +1 -0
  59. package/dist/runtime/protocol.d.ts +3 -0
  60. package/dist/runtime/protocol.d.ts.map +1 -0
  61. package/dist/runtime/protocol.js +3 -0
  62. package/dist/runtime/protocol.js.map +1 -0
  63. package/dist/runtime/pyodide.d.ts +27 -0
  64. package/dist/runtime/pyodide.d.ts.map +1 -0
  65. package/dist/runtime/pyodide.js +191 -0
  66. package/dist/runtime/pyodide.js.map +1 -0
  67. package/dist/tools/python_suite.js +173 -0
  68. package/dist/tools/python_suite.js.map +1 -0
  69. package/dist/types/index.d.ts +299 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +5 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/tywrap.d.ts +29 -0
  74. package/dist/tywrap.d.ts.map +1 -0
  75. package/dist/tywrap.js +589 -0
  76. package/dist/tywrap.js.map +1 -0
  77. package/dist/utils/bundle-optimizer.d.ts +182 -0
  78. package/dist/utils/bundle-optimizer.d.ts.map +1 -0
  79. package/dist/utils/bundle-optimizer.js +680 -0
  80. package/dist/utils/bundle-optimizer.js.map +1 -0
  81. package/dist/utils/cache.d.ts +149 -0
  82. package/dist/utils/cache.d.ts.map +1 -0
  83. package/dist/utils/cache.js +492 -0
  84. package/dist/utils/cache.js.map +1 -0
  85. package/dist/utils/codec.d.ts +94 -0
  86. package/dist/utils/codec.d.ts.map +1 -0
  87. package/dist/utils/codec.js +150 -0
  88. package/dist/utils/codec.js.map +1 -0
  89. package/dist/utils/logger.d.ts +42 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +140 -0
  92. package/dist/utils/logger.js.map +1 -0
  93. package/dist/utils/memory-profiler.d.ts +123 -0
  94. package/dist/utils/memory-profiler.d.ts.map +1 -0
  95. package/dist/utils/memory-profiler.js +426 -0
  96. package/dist/utils/memory-profiler.js.map +1 -0
  97. package/dist/utils/parallel-processor.d.ts +146 -0
  98. package/dist/utils/parallel-processor.d.ts.map +1 -0
  99. package/dist/utils/parallel-processor.js +636 -0
  100. package/dist/utils/parallel-processor.js.map +1 -0
  101. package/dist/utils/python.d.ts +8 -0
  102. package/dist/utils/python.d.ts.map +1 -0
  103. package/dist/utils/python.js +56 -0
  104. package/dist/utils/python.js.map +1 -0
  105. package/dist/utils/runtime.d.ts +153 -0
  106. package/dist/utils/runtime.d.ts.map +1 -0
  107. package/dist/utils/runtime.js +519 -0
  108. package/dist/utils/runtime.js.map +1 -0
  109. package/package.json +136 -0
  110. package/runtime/python_bridge.py +476 -0
  111. package/src/cli.ts +331 -0
  112. package/src/config/index.ts +289 -0
  113. package/src/core/analyzer.ts +788 -0
  114. package/src/core/discovery.ts +415 -0
  115. package/src/core/generator.ts +447 -0
  116. package/src/core/mapper.ts +504 -0
  117. package/src/core/validation.ts +693 -0
  118. package/src/index.ts +83 -0
  119. package/src/runtime/base.ts +32 -0
  120. package/src/runtime/errors.ts +19 -0
  121. package/src/runtime/http.ts +132 -0
  122. package/src/runtime/index.ts +24 -0
  123. package/src/runtime/node.ts +431 -0
  124. package/src/runtime/optimized-node.ts +838 -0
  125. package/src/runtime/protocol.ts +2 -0
  126. package/src/runtime/pyodide.ts +228 -0
  127. package/src/types/global.d.ts +60 -0
  128. package/src/types/index.ts +410 -0
  129. package/src/tywrap.ts +669 -0
  130. package/src/utils/bundle-optimizer.ts +876 -0
  131. package/src/utils/cache.ts +634 -0
  132. package/src/utils/codec.ts +275 -0
  133. package/src/utils/logger.ts +201 -0
  134. package/src/utils/memory-profiler.ts +582 -0
  135. package/src/utils/parallel-processor.ts +879 -0
  136. package/src/utils/python.ts +79 -0
  137. package/src/utils/runtime.ts +616 -0
@@ -0,0 +1,879 @@
1
+ /**
2
+ * Parallel Processing System for tywrap
3
+ * High-performance parallel processing for large Python codebases
4
+ */
5
+
6
+ import { EventEmitter } from 'events';
7
+ import { cpus } from 'os';
8
+ import { performance } from 'perf_hooks';
9
+ import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ import type { AnalysisResult, PythonModule, GeneratedCode } from '../types/index.js';
13
+
14
+ import { CodeGenerator } from '../core/generator.js';
15
+ import { PyAnalyzer } from '../core/analyzer.js';
16
+ import { globalCache } from './cache.js';
17
+ import { getComponentLogger } from './logger.js';
18
+
19
+ const log = getComponentLogger('ParallelProcessor');
20
+
21
+ export interface ParallelTask<Data = unknown> {
22
+ id: string;
23
+ type: 'analyze' | 'generate' | 'validate' | 'custom';
24
+ data: Data;
25
+ options?: Record<string, unknown>;
26
+ priority?: number;
27
+ timeout?: number;
28
+ }
29
+
30
+ export interface ParallelResult<T = unknown> {
31
+ taskId: string;
32
+ success: boolean;
33
+ result?: T;
34
+ error?: string;
35
+ duration: number;
36
+ memoryUsage?: number;
37
+ }
38
+
39
+ export interface WorkerStats {
40
+ workerId: string;
41
+ tasksCompleted: number;
42
+ totalTime: number;
43
+ averageTime: number;
44
+ errorCount: number;
45
+ memoryPeak: number;
46
+ isActive: boolean;
47
+ }
48
+
49
+ export interface ParallelProcessorOptions {
50
+ maxWorkers?: number;
51
+ taskTimeout?: number; // milliseconds
52
+ retryAttempts?: number;
53
+ enableMemoryMonitoring?: boolean;
54
+ enableCaching?: boolean;
55
+ workerScript?: string; // Custom worker script path
56
+ batchSize?: number; // Number of tasks to batch per worker
57
+ loadBalancing?: 'round-robin' | 'least-loaded' | 'weighted';
58
+ debug?: boolean;
59
+ }
60
+
61
+ export interface ParallelProcessorStats {
62
+ activeWorkers: number;
63
+ totalWorkers: number;
64
+ tasksCompleted: number;
65
+ totalErrors: number;
66
+ averageTaskTime: number;
67
+ queueLength: number;
68
+ activeTasks: number;
69
+ workerStats: WorkerStats[];
70
+ }
71
+
72
+ interface AnalyzeTaskData {
73
+ sources: Array<{ name: string; content: string; path?: string }>;
74
+ }
75
+
76
+ interface GenerationTaskData {
77
+ modules: Array<{ name: string; module: PythonModule; options?: Record<string, unknown> }>;
78
+ }
79
+
80
+ type ValidationTaskData = unknown;
81
+
82
+ interface AnalyzeTaskResult extends Array<ParallelResult<AnalysisResult>> {}
83
+ interface GenerateTaskResult extends Array<ParallelResult<GeneratedCode>> {}
84
+ interface ValidateTaskResult {
85
+ validated: true;
86
+ }
87
+
88
+ type AnalyzeTask = ParallelTask<AnalyzeTaskData> & { type: 'analyze' };
89
+ type GenerateTask = ParallelTask<GenerationTaskData> & { type: 'generate' };
90
+ type ValidateTask = ParallelTask<ValidationTaskData> & { type: 'validate' };
91
+ type CustomTask = ParallelTask<unknown> & { type: 'custom' };
92
+
93
+ type WorkerTask = AnalyzeTask | GenerateTask | ValidateTask | CustomTask;
94
+
95
+ interface WorkerTaskResult {
96
+ result: unknown;
97
+ error?: string;
98
+ duration: number;
99
+ memoryUsage: number;
100
+ }
101
+
102
+ type WorkerMessage =
103
+ | {
104
+ type: 'task_complete';
105
+ taskId: string;
106
+ result?: unknown;
107
+ error?: string;
108
+ duration: number;
109
+ memoryUsage?: number;
110
+ }
111
+ | {
112
+ type: 'worker_ready';
113
+ workerId: string;
114
+ };
115
+
116
+ type WorkerControlMessage = { type: 'task'; task: WorkerTask } | { type: 'shutdown' };
117
+
118
+ interface WorkerData {
119
+ workerId: string;
120
+ options?: {
121
+ enableMemoryMonitoring: boolean;
122
+ enableCaching: boolean;
123
+ };
124
+ }
125
+
126
+ export class ParallelProcessor extends EventEmitter {
127
+ private workers = new Map<string, Worker>();
128
+ private workerStats = new Map<string, WorkerStats>();
129
+ private taskQueue: ParallelTask[] = [];
130
+ private activeTasks = new Map<string, ParallelTask>();
131
+ private pendingResults = new Map<
132
+ string,
133
+ {
134
+ resolve: (result: ParallelResult<unknown>) => void;
135
+ reject: (error: Error) => void;
136
+ timeout?: NodeJS.Timeout;
137
+ }
138
+ >();
139
+ private roundRobinIndex = 0;
140
+ private options: Required<ParallelProcessorOptions>;
141
+ private disposed = false;
142
+ private debug = false;
143
+
144
+ constructor(options: ParallelProcessorOptions = {}) {
145
+ super();
146
+
147
+ const defaultWorkerScript = fileURLToPath(import.meta.url);
148
+ this.options = {
149
+ maxWorkers: options.maxWorkers ?? Math.min(cpus().length, 8),
150
+ taskTimeout: options.taskTimeout ?? 30000,
151
+ retryAttempts: options.retryAttempts ?? 2,
152
+ enableMemoryMonitoring: options.enableMemoryMonitoring ?? true,
153
+ enableCaching: options.enableCaching ?? true,
154
+ workerScript: options.workerScript ?? defaultWorkerScript,
155
+ batchSize: options.batchSize ?? 1,
156
+ loadBalancing: options.loadBalancing ?? 'least-loaded',
157
+ debug: options.debug ?? false,
158
+ };
159
+
160
+ this.debug = this.options.debug;
161
+
162
+ if (this.debug) {
163
+ this.debugLog(`🚀 Parallel processor initialized with ${this.options.maxWorkers} workers`);
164
+ }
165
+ }
166
+
167
+ private debugLog(message: string): void {
168
+ if (!this.debug) {
169
+ return;
170
+ }
171
+ process.stdout.write(`${message}\n`);
172
+ }
173
+
174
+ /**
175
+ * Initialize worker pool
176
+ */
177
+ async init(): Promise<void> {
178
+ if (this.disposed) {
179
+ throw new Error('Processor has been disposed');
180
+ }
181
+
182
+ // Spawn initial worker pool
183
+ for (let i = 0; i < this.options.maxWorkers; i++) {
184
+ await this.spawnWorker(`worker_${i}`);
185
+ }
186
+
187
+ // Start task processing
188
+ this.processQueue();
189
+
190
+ this.debugLog(`✅ Worker pool initialized with ${this.workers.size} workers`);
191
+ }
192
+
193
+ /**
194
+ * Process Python module analysis in parallel
195
+ */
196
+ async analyzeModulesParallel(
197
+ sources: Array<{ name: string; content: string; path?: string }>,
198
+ options: { chunkSize?: number } = {}
199
+ ): Promise<Array<ParallelResult<AnalysisResult>>> {
200
+ const chunkSize = options.chunkSize ?? Math.ceil(sources.length / this.options.maxWorkers);
201
+ const chunks = this.chunkArray(sources, chunkSize);
202
+
203
+ this.debugLog(`📊 Analyzing ${sources.length} modules in ${chunks.length} chunks`);
204
+
205
+ const tasks: Array<ParallelTask<AnalyzeTaskData>> = chunks.map((chunk, index) => ({
206
+ id: `analyze_chunk_${index}`,
207
+ type: 'analyze',
208
+ data: { sources: chunk },
209
+ priority: 1,
210
+ }));
211
+
212
+ const results = await this.executeTasks<AnalyzeTaskData, AnalyzeTaskResult>(tasks);
213
+
214
+ // Flatten chunked results
215
+ const flatResults: ParallelResult<AnalysisResult>[] = [];
216
+ for (const result of results) {
217
+ if (!result.success || !result.result) {
218
+ continue;
219
+ }
220
+ if (result.success && result.result) {
221
+ const chunkResults = result.result;
222
+ if (Array.isArray(chunkResults)) {
223
+ flatResults.push(...chunkResults);
224
+ }
225
+ }
226
+ }
227
+
228
+ return flatResults;
229
+ }
230
+
231
+ /**
232
+ * Generate TypeScript wrappers in parallel
233
+ */
234
+ async generateWrappersParallel(
235
+ modules: Array<{ name: string; module: PythonModule; options?: Record<string, unknown> }>,
236
+ options: { chunkSize?: number } = {}
237
+ ): Promise<Array<ParallelResult<GeneratedCode>>> {
238
+ const chunkSize = options.chunkSize ?? Math.ceil(modules.length / this.options.maxWorkers);
239
+ const chunks = this.chunkArray(modules, chunkSize);
240
+
241
+ this.debugLog(`🏗️ Generating ${modules.length} wrappers in ${chunks.length} chunks`);
242
+
243
+ const tasks: Array<ParallelTask<GenerationTaskData>> = chunks.map((chunk, index) => ({
244
+ id: `generate_chunk_${index}`,
245
+ type: 'generate',
246
+ data: { modules: chunk },
247
+ priority: 1,
248
+ }));
249
+
250
+ const results = await this.executeTasks<GenerationTaskData, GenerateTaskResult>(tasks);
251
+
252
+ // Flatten chunked results
253
+ const flatResults: ParallelResult<GeneratedCode>[] = [];
254
+ for (const result of results) {
255
+ if (result.success && result.result) {
256
+ const chunkResults = result.result;
257
+ if (Array.isArray(chunkResults)) {
258
+ flatResults.push(...chunkResults);
259
+ }
260
+ }
261
+ }
262
+
263
+ return flatResults;
264
+ }
265
+
266
+ /**
267
+ * Execute tasks in parallel with load balancing
268
+ */
269
+ async executeTasks<Data, Result>(
270
+ tasks: ParallelTask<Data>[]
271
+ ): Promise<Array<ParallelResult<Result>>> {
272
+ if (this.workers.size === 0) {
273
+ await this.init();
274
+ }
275
+
276
+ // Sort tasks by priority
277
+ const sortedTasks = [...tasks].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
278
+
279
+ // Execute tasks with batching
280
+ const results: Array<ParallelResult<Result>> = [];
281
+ const inFlight = new Set<Promise<ParallelResult<Result>>>();
282
+ const maxInFlight = this.workers.size * 2;
283
+
284
+ for (const task of sortedTasks) {
285
+ const promise = this.executeTask<Data, Result>(task);
286
+ inFlight.add(promise);
287
+ promise
288
+ .finally(() => {
289
+ inFlight.delete(promise);
290
+ })
291
+ .catch(() => {
292
+ // no-op: individual task failures are encoded in results
293
+ });
294
+
295
+ // Limit concurrent tasks to prevent overwhelming workers
296
+ if (inFlight.size >= maxInFlight) {
297
+ const completed = await Promise.race(inFlight);
298
+ results.push(completed);
299
+ }
300
+ }
301
+
302
+ // Wait for remaining tasks
303
+ const remaining = await Promise.all(inFlight);
304
+ results.push(...remaining);
305
+
306
+ return results;
307
+ }
308
+
309
+ /**
310
+ * Execute single task
311
+ */
312
+ private async executeTask<Data, Result>(
313
+ task: ParallelTask<Data>
314
+ ): Promise<ParallelResult<Result>> {
315
+ // Check cache first if enabled
316
+ if (this.options.enableCaching) {
317
+ const cacheKey = globalCache.generateKey('parallel_task', task.type, task.data);
318
+ const cached = await globalCache.get<ParallelResult<Result>>(cacheKey);
319
+ if (cached) {
320
+ this.debugLog(`🎯 Cache HIT for task ${task.id}`);
321
+ return cached;
322
+ }
323
+ }
324
+
325
+ let attempts = 0;
326
+ let lastError: Error | undefined;
327
+
328
+ while (attempts < this.options.retryAttempts) {
329
+ attempts++;
330
+
331
+ try {
332
+ const worker = this.selectOptimalWorker();
333
+ if (!worker) {
334
+ throw new Error('No available workers');
335
+ }
336
+
337
+ const result = await this.sendTaskToWorker<Data, Result>(worker, task);
338
+
339
+ // Cache successful results
340
+ if (this.options.enableCaching && result.success) {
341
+ const cacheKey = globalCache.generateKey('parallel_task', task.type, task.data);
342
+ await globalCache.set(cacheKey, result, { computeTime: result.duration });
343
+ }
344
+
345
+ return result;
346
+ } catch (error) {
347
+ lastError = error as Error;
348
+ log.warn('Task failed', { taskId: task.id, attempt: attempts, error: String(error) });
349
+
350
+ if (attempts < this.options.retryAttempts) {
351
+ // Exponential backoff
352
+ await this.sleep(Math.pow(2, attempts) * 1000);
353
+ }
354
+ }
355
+ }
356
+
357
+ // Return failure result
358
+ return {
359
+ taskId: task.id,
360
+ success: false,
361
+ error: lastError?.message ?? 'Task failed after retries',
362
+ duration: 0,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Select optimal worker using load balancing strategy
368
+ */
369
+ private selectOptimalWorker(): Worker | null {
370
+ const availableWorkers = Array.from(this.workers.values()).filter(
371
+ worker => worker.threadId > 0
372
+ );
373
+
374
+ if (availableWorkers.length === 0) {
375
+ return null;
376
+ }
377
+
378
+ switch (this.options.loadBalancing) {
379
+ case 'round-robin': {
380
+ const selected = availableWorkers[this.roundRobinIndex % availableWorkers.length];
381
+ this.roundRobinIndex = (this.roundRobinIndex + 1) % availableWorkers.length;
382
+ return selected ?? null;
383
+ }
384
+
385
+ case 'least-loaded': {
386
+ // Find worker with least active tasks
387
+ const workerLoads = availableWorkers.map(w => {
388
+ const workerId = this.getWorkerId(w);
389
+ const stats = this.workerStats.get(workerId);
390
+ return { worker: w, load: stats?.tasksCompleted ?? 0 };
391
+ });
392
+
393
+ workerLoads.sort((a, b) => a.load - b.load);
394
+ return workerLoads[0]?.worker ?? null;
395
+ }
396
+
397
+ case 'weighted': {
398
+ // Weight by average task completion time
399
+ const workerWeights = availableWorkers.map(w => {
400
+ const workerId = this.getWorkerId(w);
401
+ const stats = this.workerStats.get(workerId);
402
+ const avgTime = stats?.averageTime ?? 1000;
403
+ return { worker: w, weight: 1 / avgTime };
404
+ });
405
+
406
+ // Weighted random selection
407
+ const totalWeight = workerWeights.reduce((sum, w) => sum + w.weight, 0);
408
+ let random = Math.random() * totalWeight;
409
+
410
+ for (const { worker: candidate, weight } of workerWeights) {
411
+ random -= weight;
412
+ if (random <= 0) {
413
+ return candidate;
414
+ }
415
+ }
416
+
417
+ return workerWeights[0]?.worker ?? null;
418
+ }
419
+
420
+ default:
421
+ return availableWorkers[0] ?? null;
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Send task to specific worker
427
+ */
428
+ private async sendTaskToWorker<Data, Result>(
429
+ worker: Worker,
430
+ task: ParallelTask<Data>
431
+ ): Promise<ParallelResult<Result>> {
432
+ return new Promise((resolve, reject) => {
433
+ const timeoutMs = task.timeout ?? this.options.taskTimeout;
434
+ const timeout = setTimeout(() => {
435
+ this.pendingResults.delete(task.id);
436
+ reject(new Error(`Task ${task.id} timed out after ${timeoutMs}ms`));
437
+ }, timeoutMs);
438
+
439
+ this.pendingResults.set(task.id, {
440
+ resolve: resolve as unknown as (result: ParallelResult<unknown>) => void,
441
+ reject,
442
+ timeout,
443
+ });
444
+
445
+ this.activeTasks.set(task.id, task);
446
+
447
+ worker.postMessage({
448
+ type: 'task',
449
+ task,
450
+ });
451
+ });
452
+ }
453
+
454
+ /**
455
+ * Spawn new worker
456
+ */
457
+ private async spawnWorker(workerId: string): Promise<Worker> {
458
+ const worker = new Worker(this.options.workerScript, {
459
+ workerData: {
460
+ workerId,
461
+ options: {
462
+ enableMemoryMonitoring: this.options.enableMemoryMonitoring,
463
+ enableCaching: this.options.enableCaching,
464
+ },
465
+ },
466
+ });
467
+
468
+ // Setup worker message handling
469
+ worker.on('message', message => {
470
+ this.handleWorkerMessage(workerId, message);
471
+ });
472
+
473
+ // Setup worker error handling
474
+ worker.on('error', error => {
475
+ log.error('Worker error', { workerId, error: String(error) });
476
+ this.handleWorkerError(workerId, error);
477
+ });
478
+
479
+ // Setup worker exit handling
480
+ worker.on('exit', code => {
481
+ log.warn('Worker exited', { workerId, code });
482
+ this.handleWorkerExit(workerId, code);
483
+ });
484
+
485
+ this.workers.set(workerId, worker);
486
+ this.workerStats.set(workerId, {
487
+ workerId,
488
+ tasksCompleted: 0,
489
+ totalTime: 0,
490
+ averageTime: 0,
491
+ errorCount: 0,
492
+ memoryPeak: 0,
493
+ isActive: true,
494
+ });
495
+
496
+ this.debugLog(`👷 Spawned worker ${workerId}`);
497
+ return worker;
498
+ }
499
+
500
+ /**
501
+ * Handle message from worker
502
+ */
503
+ private handleWorkerMessage(workerId: string, message: WorkerMessage): void {
504
+ if (message.type === 'task_complete') {
505
+ const { taskId, result, error, duration, memoryUsage } = message;
506
+ const pending = this.pendingResults.get(taskId);
507
+ if (pending) {
508
+ this.pendingResults.delete(taskId);
509
+ this.activeTasks.delete(taskId);
510
+
511
+ if (pending.timeout) {
512
+ clearTimeout(pending.timeout);
513
+ }
514
+
515
+ // Update worker stats
516
+ const stats = this.workerStats.get(workerId);
517
+ if (stats) {
518
+ stats.tasksCompleted++;
519
+ stats.totalTime += duration;
520
+ stats.averageTime = stats.totalTime / stats.tasksCompleted;
521
+
522
+ if (error) {
523
+ stats.errorCount++;
524
+ }
525
+
526
+ if (memoryUsage && memoryUsage > stats.memoryPeak) {
527
+ stats.memoryPeak = memoryUsage;
528
+ }
529
+ }
530
+
531
+ const parallelResult: ParallelResult = {
532
+ taskId,
533
+ success: !error,
534
+ result,
535
+ error,
536
+ duration,
537
+ memoryUsage,
538
+ };
539
+
540
+ pending.resolve(parallelResult);
541
+ }
542
+ } else if (message.type === 'worker_ready') {
543
+ this.emit('worker_ready', workerId);
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Handle worker error
549
+ */
550
+ private handleWorkerError(workerId: string, error: Error): void {
551
+ const stats = this.workerStats.get(workerId);
552
+ if (stats) {
553
+ stats.errorCount++;
554
+ stats.isActive = false;
555
+ }
556
+
557
+ // Reject pending tasks for this worker
558
+ for (const [taskId, pending] of this.pendingResults) {
559
+ const task = this.activeTasks.get(taskId);
560
+ if (task) {
561
+ pending.reject(new Error(`Worker ${workerId} error: ${error.message}`));
562
+ this.pendingResults.delete(taskId);
563
+ this.activeTasks.delete(taskId);
564
+ }
565
+ }
566
+
567
+ this.emit('worker_error', workerId, error);
568
+ }
569
+
570
+ /**
571
+ * Handle worker exit
572
+ */
573
+ private handleWorkerExit(workerId: string, code: number): void {
574
+ this.workers.delete(workerId);
575
+
576
+ const stats = this.workerStats.get(workerId);
577
+ if (stats) {
578
+ stats.isActive = false;
579
+ }
580
+
581
+ // Respawn worker if not disposing
582
+ if (!this.disposed && this.workers.size < this.options.maxWorkers) {
583
+ this.spawnWorker(`${workerId}_respawn_${Date.now()}`).catch(error => {
584
+ log.error('Failed to respawn worker', { workerId, error: String(error) });
585
+ });
586
+ }
587
+
588
+ this.emit('worker_exit', workerId, code);
589
+ }
590
+
591
+ /**
592
+ * Process task queue
593
+ */
594
+ private processQueue(): void {
595
+ // Simple queue processing - could be enhanced with priority scheduling
596
+ setInterval(() => {
597
+ if (this.taskQueue.length > 0 && this.workers.size > 0) {
598
+ const task = this.taskQueue.shift();
599
+ if (task) {
600
+ this.executeTask(task).catch(error => {
601
+ log.error('Queue task execution failed', { error: String(error) });
602
+ });
603
+ }
604
+ }
605
+ }, 100);
606
+ }
607
+
608
+ /**
609
+ * Get worker statistics
610
+ */
611
+ getWorkerStats(): WorkerStats[] {
612
+ return Array.from(this.workerStats.values());
613
+ }
614
+
615
+ /**
616
+ * Get overall processing statistics
617
+ */
618
+ getStats(): ParallelProcessorStats {
619
+ const workers = Array.from(this.workerStats.values());
620
+ const totalTasks = workers.reduce((sum, w) => sum + w.tasksCompleted, 0);
621
+ const totalErrors = workers.reduce((sum, w) => sum + w.errorCount, 0);
622
+ const totalTime = workers.reduce((sum, w) => sum + w.totalTime, 0);
623
+ const avgTime = totalTasks > 0 ? totalTime / totalTasks : 0;
624
+
625
+ return {
626
+ activeWorkers: workers.filter(w => w.isActive).length,
627
+ totalWorkers: workers.length,
628
+ tasksCompleted: totalTasks,
629
+ totalErrors,
630
+ averageTaskTime: avgTime,
631
+ queueLength: this.taskQueue.length,
632
+ activeTasks: this.activeTasks.size,
633
+ workerStats: workers,
634
+ };
635
+ }
636
+
637
+ /**
638
+ * Utility methods
639
+ */
640
+ private chunkArray<T>(array: T[], chunkSize: number): T[][] {
641
+ const chunks: T[][] = [];
642
+ for (let i = 0; i < array.length; i += chunkSize) {
643
+ chunks.push(array.slice(i, i + chunkSize));
644
+ }
645
+ return chunks;
646
+ }
647
+
648
+ private getWorkerId(worker: Worker): string {
649
+ for (const [id, w] of this.workers) {
650
+ if (w === worker) {
651
+ return id;
652
+ }
653
+ }
654
+ return 'unknown';
655
+ }
656
+
657
+ private sleep(ms: number): Promise<void> {
658
+ return new Promise(resolve => setTimeout(resolve, ms));
659
+ }
660
+
661
+ /**
662
+ * Gracefully dispose all workers
663
+ */
664
+ async dispose(): Promise<void> {
665
+ if (this.disposed) {
666
+ return;
667
+ }
668
+
669
+ this.disposed = true;
670
+
671
+ this.debugLog('🛑 Disposing parallel processor...');
672
+
673
+ // Terminate all workers
674
+ const terminationPromises = Array.from(this.workers.values()).map(worker => {
675
+ return new Promise<void>(resolve => {
676
+ const timeout = setTimeout(() => {
677
+ worker.terminate();
678
+ resolve();
679
+ }, 5000);
680
+
681
+ worker.once('exit', () => {
682
+ clearTimeout(timeout);
683
+ resolve();
684
+ });
685
+
686
+ worker.postMessage({ type: 'shutdown' });
687
+ });
688
+ });
689
+
690
+ await Promise.all(terminationPromises);
691
+
692
+ // Clear data structures
693
+ this.workers.clear();
694
+ this.workerStats.clear();
695
+ this.taskQueue.length = 0;
696
+ this.activeTasks.clear();
697
+ this.pendingResults.clear();
698
+
699
+ this.removeAllListeners();
700
+
701
+ this.debugLog('✅ Parallel processor disposed');
702
+ }
703
+
704
+ setDebug(debug: boolean): void {
705
+ this.debug = debug;
706
+ }
707
+ }
708
+
709
+ // Worker script implementation (this runs in worker threads)
710
+ if (!isMainThread && parentPort) {
711
+ const port = parentPort;
712
+ const { workerId } = workerData as WorkerData;
713
+
714
+ let analyzer: PyAnalyzer | null = null;
715
+ let analyzerInitialized = false;
716
+ let generator: CodeGenerator | null = null;
717
+
718
+ const getAnalyzer = async (): Promise<PyAnalyzer> => {
719
+ analyzer ??= new PyAnalyzer();
720
+ if (!analyzerInitialized) {
721
+ await analyzer.initialize();
722
+ analyzerInitialized = true;
723
+ }
724
+ return analyzer;
725
+ };
726
+
727
+ const getGenerator = (): CodeGenerator => {
728
+ generator ??= new CodeGenerator();
729
+ return generator;
730
+ };
731
+
732
+ const createResultId = (prefix: string, name: string, index: number): string =>
733
+ `${prefix}:${name}:${index}`;
734
+
735
+ // Import required modules in worker context
736
+ const workerImplementation = {
737
+ async processTask(task: WorkerTask): Promise<WorkerTaskResult> {
738
+ const startTime = performance.now();
739
+ let result: unknown;
740
+ let error: string | undefined;
741
+
742
+ try {
743
+ switch (task.type) {
744
+ case 'analyze':
745
+ result = await this.processAnalysisTask(task.data);
746
+ break;
747
+ case 'generate':
748
+ result = await this.processGenerationTask(task.data);
749
+ break;
750
+ case 'validate':
751
+ result = await this.processValidationTask(task.data);
752
+ break;
753
+ default:
754
+ throw new Error(`Unknown task type: ${task.type}`);
755
+ }
756
+ } catch (err) {
757
+ error = String(err);
758
+ }
759
+
760
+ const duration = performance.now() - startTime;
761
+ const memoryUsage = process.memoryUsage().heapUsed;
762
+
763
+ return { result, error, duration, memoryUsage };
764
+ },
765
+
766
+ async processAnalysisTask(data: AnalyzeTaskData): Promise<AnalyzeTaskResult> {
767
+ const results: AnalyzeTaskResult = [];
768
+ const activeAnalyzer = await getAnalyzer();
769
+
770
+ for (const [index, source] of data.sources.entries()) {
771
+ const start = performance.now();
772
+ try {
773
+ const modulePath = source.path ?? `${source.name}.py`;
774
+ const analysis = await activeAnalyzer.analyzePythonModule(source.content, modulePath);
775
+ results.push({
776
+ taskId: createResultId('analyze', source.name, index),
777
+ success: true,
778
+ result: analysis,
779
+ duration: performance.now() - start,
780
+ memoryUsage: process.memoryUsage().heapUsed,
781
+ });
782
+ } catch (error) {
783
+ results.push({
784
+ taskId: createResultId('analyze', source.name, index),
785
+ success: false,
786
+ error: String(error),
787
+ duration: performance.now() - start,
788
+ memoryUsage: process.memoryUsage().heapUsed,
789
+ });
790
+ }
791
+ }
792
+
793
+ return results;
794
+ },
795
+
796
+ async processGenerationTask(data: GenerationTaskData): Promise<GenerateTaskResult> {
797
+ const results: GenerateTaskResult = [];
798
+ const activeGenerator = getGenerator();
799
+
800
+ for (const [index, moduleData] of data.modules.entries()) {
801
+ const start = performance.now();
802
+ try {
803
+ const generationOptions = {
804
+ moduleName: moduleData.name,
805
+ ...moduleData.options,
806
+ } as {
807
+ moduleName: string;
808
+ exportAll?: boolean;
809
+ annotatedJSDoc?: boolean;
810
+ };
811
+ const generated = await activeGenerator.generateModule(
812
+ moduleData.module,
813
+ generationOptions
814
+ );
815
+ results.push({
816
+ taskId: createResultId('generate', moduleData.name, index),
817
+ success: true,
818
+ result: generated,
819
+ duration: performance.now() - start,
820
+ memoryUsage: process.memoryUsage().heapUsed,
821
+ });
822
+ } catch (error) {
823
+ results.push({
824
+ taskId: createResultId('generate', moduleData.name, index),
825
+ success: false,
826
+ error: String(error),
827
+ duration: performance.now() - start,
828
+ memoryUsage: process.memoryUsage().heapUsed,
829
+ });
830
+ }
831
+ }
832
+
833
+ return results;
834
+ },
835
+
836
+ async processValidationTask(_data: ValidationTaskData): Promise<ValidateTaskResult> {
837
+ // Validation logic would go here
838
+ return { validated: true };
839
+ },
840
+ };
841
+
842
+ port.on('message', async (raw: unknown) => {
843
+ const message = raw as WorkerControlMessage;
844
+ if (!message || typeof message !== 'object') {
845
+ return;
846
+ }
847
+
848
+ if (message.type === 'task') {
849
+ try {
850
+ const taskResult = await workerImplementation.processTask(message.task);
851
+
852
+ port.postMessage({
853
+ type: 'task_complete',
854
+ taskId: message.task.id,
855
+ ...(typeof taskResult === 'object' && taskResult !== null
856
+ ? taskResult
857
+ : { result: taskResult }),
858
+ });
859
+ } catch (error) {
860
+ port.postMessage({
861
+ type: 'task_complete',
862
+ taskId: message.task.id,
863
+ error: String(error),
864
+ duration: 0,
865
+ });
866
+ }
867
+ } else if (message.type === 'shutdown') {
868
+ process.exit(0);
869
+ }
870
+ });
871
+
872
+ // Signal that worker is ready
873
+ port.postMessage({
874
+ type: 'worker_ready',
875
+ workerId,
876
+ });
877
+ }
878
+
879
+ export const globalParallelProcessor = new ParallelProcessor();