"use client"; // Main-thread client for the FlexSearch web worker. Lazily spawns one module // worker and exposes an async request/response API (build / search / status / // drop) with build progress. The `new Worker(new URL(...))` form is what // Turbopack statically detects to bundle the worker into the static export. import type { IndexHit, WorkerRequest, WorkerResponse, } from "./searchIndexWorkerProtocol"; export type { IndexHit } from "./searchIndexWorkerProtocol"; // Distributive Omit so the discriminated-union request keeps its per-variant // fields (a plain Omit collapses to the common keys only). type DistributiveOmit = T extends unknown ? Omit : never; type Pending = { resolve: (value: WorkerResponse) => void; reject: (err: Error) => void; onProgress?: (done: number, total: number) => void; }; let worker: Worker | null = null; let nextReqId = 1; const pending = new Map(); export function workerSupported(): boolean { return typeof window !== "undefined" && typeof Worker !== "undefined"; } function getWorker(): Worker | null { if (!workerSupported()) return null; if (worker) return worker; worker = new Worker(new URL("./searchIndex.worker.ts", import.meta.url), { type: "module", }); worker.addEventListener("message", (e: MessageEvent) => { const msg = e.data; const p = pending.get(msg.reqId); if (!p) return; if (msg.type === "progress") { p.onProgress?.(msg.done, msg.total); return; // keep pending open until the terminal reply } pending.delete(msg.reqId); if (msg.type === "error") p.reject(new Error(msg.message)); else p.resolve(msg); }); return worker; } function request( req: DistributiveOmit, onProgress?: (done: number, total: number) => void, ): Promise { const w = getWorker(); if (!w) return Promise.reject(new Error("Web workers unavailable")); const reqId = nextReqId++; return new Promise((resolve, reject) => { pending.set(reqId, { resolve, reject, onProgress }); w.postMessage({ ...req, reqId } as WorkerRequest); }); } // Build (or rebuild) a channel's index in the worker from its transcript shards. // Resolves with the indexed document (cue) count. export async function buildChannelIndex( slug: string, onProgress?: (done: number, total: number) => void, ): Promise { const res = await request({ type: "build", slug }, onProgress); return res.type === "built" ? res.docCount : 0; } export async function searchChannel( slug: string, term: string, limit = 50, ): Promise { const res = await request({ type: "search", slug, term, limit }); return res.type === "result" ? res.hits : []; } export async function channelIndexStatus( slug: string, ): Promise<{ ready: boolean; docCount: number }> { const res = await request({ type: "status", slug }); return res.type === "status" ? { ready: res.ready, docCount: res.docCount } : { ready: false, docCount: 0 }; } export async function dropChannelIndex(slug: string): Promise { await request({ type: "drop", slug }); }