import { asModel } from "../persist.js";
/**
* Returns the shared client that runs create* functions (and other worker-side operations)
* in a separate thread — a Web Worker in the browser, or a worker_threads Worker in Node —
* via Comlink. Environment is detected automatically so the same call site works in both.
*
* The underlying worker is created once and reused by every caller: worker-side module state
* (e.g. the location template registry used to expand shimmed locations) must stay consistent
* across calls, so different parts of the app can't each get their own worker.
*
* @example
* const client = await createWorkerClient();
* const location = await client.call("createLocation", { type: "Farm", history: true });
* await client.terminate();
*
* @returns {Promise<{call: Function, expand: Function, terminate: Function}>}
*/
export function createWorkerClient() {
if (!clientPromise) {
const isBrowser = typeof window !== "undefined" && typeof Worker !== "undefined";
clientPromise = isBrowser ? createBrowserClient() : createNodeClient();
}
return clientPromise;
}
let clientPromise = null;
class WorkerClient {
constructor(worker, api) {
this.worker = worker;
this.api = api;
}
async call(functionName, args) {
const json = await this.api.call(functionName, args).then();
return asModel(json);
}
async expand(location) {
const json = await this.api.expand(location).then();
return asModel(json);
}
/**
* Callers are responsible for terminating explicitly when they're done with the client —
* there's no reliable hook to do this automatically, though in the browser we also hook
* into "beforeunload" which is sufficient unless working in something like an SPA for a
* long period of time.
*/
async terminate() {
await this.worker.terminate();
clientPromise = null;
}
}
async function createBrowserClient() {
const Comlink = await import("/lib/comlink.mjs");
const worker = new Worker(new URL("./browser_worker.js", import.meta.url), {
type: "module",
});
const api = Comlink.wrap(worker);
const client = new WorkerClient(worker, api);
window.addEventListener("beforeunload", () => client.terminate());
return client;
}
async function createNodeClient() {
const { Worker: NodeWorker } = await import("node:worker_threads");
const Comlink = await import("comlink");
const { default: nodeEndpoint } = await import("comlink/dist/esm/node-adapter.mjs");
const worker = new NodeWorker(new URL("./node_worker.js", import.meta.url));
const api = Comlink.wrap(nodeEndpoint(worker));
return new WorkerClient(worker, api);
}