/** Resident RAM query projection; PostgreSQL is the only persistent data source. */ import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { createInterface } from 'node:readline'; export function unifiedReader({ python, unifiedConfig, timeoutMs = 120000 } = {}) { let child, nextId = 0, closed = false; const pending = new Map(); function start() { const worker = spawn(python, ['-B', '-X', 'utf8', fileURLToPath(new URL('./voc-library/unified_worker.py', import.meta.url))], { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, KSF_UNIFIED_DB_CONFIG: unifiedConfig } }); child = worker; worker.stderr.resume(); worker.stdin.on('error', () => {}); const terminate = () => { if (child !== worker) return; for (const item of pending.values()) { clearTimeout(item.timer); item.reject(Error('UNIFIED_WORKER_UNAVAILABLE')); } pending.clear(); child = undefined; }; worker.on('error', terminate); worker.on('exit', terminate); const lines = createInterface({ input: worker.stdout }); lines.on('line', line => { if (child !== worker) return; if (Buffer.byteLength(line) > 6000000) return worker.kill(); try { const data = JSON.parse(line), item = pending.get(data.id); if (item) { clearTimeout(item.timer); pending.delete(data.id); item.resolve(data.result); } } catch { worker.kill(); } }); } const read = (engine, config, request) => new Promise((resolve, reject) => { if (closed) return reject(Error('UNIFIED_WORKER_CLOSED')); if (pending.size >= 4) return reject(Error('LIBRARY_BUSY')); if (!child) start(); const id = ++nextId; const worker = child; const timer = setTimeout(() => { worker.kill(); }, timeoutMs); pending.set(id, { resolve, reject, timer }); child.stdin.write(JSON.stringify({ id, engine, config, request }) + '\n'); }); read.close = () => { closed = true; child?.kill(); }; return read; }