Files
2026-07-29 18:42:09 +02:00

86 lines
3.0 KiB
JavaScript

import { createSourceSearchIndex, searchSource } from "./search.js";
const CATALOG_URL = import.meta.resolve("./catalog.jsonl.gz");
/** @type {Promise<{ sha: string, files: { path: string, text: string }[] }> | undefined} */
let snapshotPromise;
/** @type {ReturnType<typeof createSourceSearchIndex> | undefined} */
let sourceSearchIndex;
/** @param {unknown} error */
function errorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
/** @param {(loaded: number, total: number) => void} reportProgress */
async function loadSnapshot(reportProgress) {
const response = await fetch(CATALOG_URL);
if (!response.ok || !response.body) {
throw new Error(`Source catalog unavailable (${response.status})`);
}
const decompressed = response.body.pipeThrough(new DecompressionStream("gzip"));
const text = await new Response(decompressed).text();
const [rawHeader, ...lines] = text.split("\n");
const header = /** @type {{ revision: string, count: number }} */ (JSON.parse(rawHeader));
reportProgress(0, header.count);
const files = lines.filter(Boolean).map((line) => {
const [path, source] = /** @type {[string, string]} */ (JSON.parse(line));
return { path, text: source };
});
reportProgress(files.length, header.count);
return { sha: header.revision, files };
}
/** @param {(loaded: number, total: number) => void} reportProgress */
function ensureSnapshot(reportProgress) {
snapshotPromise ??= loadSnapshot(reportProgress).catch((error) => {
snapshotPromise = undefined;
throw error;
});
return snapshotPromise;
}
/** @param {(loaded: number, total: number) => void} reportProgress */
async function prewarm(reportProgress) {
const snapshot = await ensureSnapshot(reportProgress);
sourceSearchIndex ??= createSourceSearchIndex(snapshot.files);
return true;
}
/**
* @param {{ query: string, path?: string, focus?: "definition" | "implementation" | "usage" | "availability" }} args
* @param {(loaded: number, total: number) => void} reportProgress
*/
async function search(args, reportProgress) {
const pathPrefix = String(args.path ?? "").replace(/^\/+/, "");
const snapshot = await ensureSnapshot(reportProgress);
sourceSearchIndex ??= createSourceSearchIndex(snapshot.files);
return {
revision: snapshot.sha,
matches: searchSource(
sourceSearchIndex,
String(args.query ?? ""),
pathPrefix,
args.focus,
),
};
}
self.addEventListener("message", async (event) => {
const { id, type, data } = event.data;
const reportProgress = (/** @type {number} */ loaded, /** @type {number} */ total) => {
self.postMessage({ id, status: "progress", loaded, total });
};
try {
let result;
if (type === "prewarm") result = await prewarm(reportProgress);
else if (type === "search") result = await search(data, reportProgress);
else throw new Error(`Unknown source request: ${type}`);
self.postMessage({ id, status: "complete", data: result });
} catch (error) {
self.postMessage({ id, status: "error", data: errorMessage(error) });
}
});