2026-07-09 11:02:20 +08:00
|
|
|
import path from 'node:path';
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
|
import { Worker } from 'node:worker_threads';
|
|
|
|
|
|
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
2026-06-13 03:42:01 +08:00
|
|
|
|
2026-07-09 16:25:59 +08:00
|
|
|
interface WorkerMessage {
|
|
|
|
|
id: number;
|
|
|
|
|
result?: unknown;
|
|
|
|
|
error?: { message: string; stack?: string };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface PendingBuild {
|
|
|
|
|
resolve: (value: unknown) => void;
|
|
|
|
|
reject: (error: Error) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface WorkerBuildIndexOptions {
|
|
|
|
|
workerPath?: string;
|
|
|
|
|
WorkerImpl?: typeof Worker;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 03:42:01 +08:00
|
|
|
function createWorkerBuildIndex({
|
2026-07-09 16:25:59 +08:00
|
|
|
// indexer-worker.js is the built worker output emitted next to this module.
|
2026-06-13 03:42:01 +08:00
|
|
|
workerPath = path.join(__dirname, 'indexer-worker.js'),
|
|
|
|
|
WorkerImpl = Worker,
|
2026-07-09 16:25:59 +08:00
|
|
|
}: WorkerBuildIndexOptions = {}) {
|
|
|
|
|
let worker: Worker | null = null;
|
2026-06-13 03:42:01 +08:00
|
|
|
let nextId = 1;
|
2026-07-09 16:25:59 +08:00
|
|
|
const pending = new Map<number, PendingBuild>();
|
2026-06-13 03:42:01 +08:00
|
|
|
|
2026-07-09 16:25:59 +08:00
|
|
|
const rejectPending = (error: Error) => {
|
2026-06-13 03:42:01 +08:00
|
|
|
for (const { reject } of pending.values()) reject(error);
|
|
|
|
|
pending.clear();
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-09 16:25:59 +08:00
|
|
|
const ensureWorker = (): Worker => {
|
2026-06-13 03:42:01 +08:00
|
|
|
if (worker) return worker;
|
2026-07-09 16:25:59 +08:00
|
|
|
const active = new WorkerImpl(workerPath, { type: 'module' } as ConstructorParameters<typeof Worker>[1]);
|
|
|
|
|
worker = active;
|
|
|
|
|
active.on('message', (message: WorkerMessage) => {
|
2026-06-13 03:42:01 +08:00
|
|
|
const current = pending.get(message.id);
|
|
|
|
|
if (!current) return;
|
|
|
|
|
pending.delete(message.id);
|
|
|
|
|
if (message.error) {
|
|
|
|
|
const error = new Error(message.error.message);
|
|
|
|
|
error.stack = message.error.stack;
|
|
|
|
|
current.reject(error);
|
|
|
|
|
} else {
|
|
|
|
|
current.resolve(message.result);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-07-09 16:25:59 +08:00
|
|
|
active.on('error', (error: Error) => {
|
2026-06-13 03:42:01 +08:00
|
|
|
rejectPending(error);
|
|
|
|
|
worker = null;
|
|
|
|
|
});
|
2026-07-09 16:25:59 +08:00
|
|
|
active.on('exit', (code: number) => {
|
2026-06-13 03:42:01 +08:00
|
|
|
if (pending.size) rejectPending(new Error(`Indexer worker exited with code ${code}`));
|
|
|
|
|
worker = null;
|
|
|
|
|
});
|
2026-07-09 16:25:59 +08:00
|
|
|
return active;
|
2026-06-13 03:42:01 +08:00
|
|
|
};
|
|
|
|
|
|
2026-07-09 16:25:59 +08:00
|
|
|
const buildIndex = (args: Record<string, unknown> = {}) => new Promise((resolve, reject) => {
|
2026-06-13 03:42:01 +08:00
|
|
|
const id = nextId++;
|
|
|
|
|
pending.set(id, { resolve, reject });
|
|
|
|
|
ensureWorker().postMessage({ id, args });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const stop = () => {
|
|
|
|
|
const current = worker;
|
|
|
|
|
worker = null;
|
2026-06-17 23:40:36 +08:00
|
|
|
const termination = current?.terminate ? Promise.resolve(current.terminate()) : Promise.resolve();
|
2026-06-13 03:42:01 +08:00
|
|
|
rejectPending(new Error('Indexer worker stopped'));
|
2026-06-17 23:40:36 +08:00
|
|
|
return termination;
|
2026-06-13 03:42:01 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { buildIndex, stop };
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-09 11:02:20 +08:00
|
|
|
export { createWorkerBuildIndex };
|