import { fork, type ChildProcess } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import type { CachedResult } from '@wordle/shared'; interface SolverTask { target: string; allWords: string[]; mode: 'fast' | 'full'; } interface WorkerMessage { ok: boolean; value?: CachedResult; error?: string; } const workerPath = fileURLToPath(new URL('./solver-worker.ts', import.meta.url)); /** * Run solver computation in a forked child process so long-running entropy * calculations never block the main event loop. * * Uses child_process.fork() rather than worker_threads because the solver * modules are TypeScript and need tsx's --import hook — which fork() can * inject via execArgv while worker_threads cannot. * * Spawns a short-lived child per call; ~100 ms overhead is negligible * compared to the 14 s entropy run. */ export function runSolverInWorker(task: SolverTask): Promise { return new Promise((resolve, reject) => { let child: ChildProcess | null = null; const done = (err: Error | null, result?: CachedResult) => { if (child) { child.kill(); child = null; } if (err) reject(err); else resolve(result!); }; child = fork(workerPath, [], { execArgv: ['--import', 'tsx'], stdio: 'pipe', }); child.on('message', (msg: WorkerMessage) => { if (msg.ok) done(null, msg.value); else done(new Error(msg.error ?? 'unknown worker error')); }); child.on('error', (err) => done(err)); child.on('exit', (code, signal) => { if (child) done(new Error(`Worker exited code=${code} signal=${signal}`)); }); child.send(task); }); }