UNPKG

@xylabs/threads

Version:

Web workers & worker threads as simple as a function call

327 lines (319 loc) 11.9 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/worker/expose.ts import isSomeObservable from "is-observable-2-1-0"; // src/serializers.ts function extendSerializer(extend, implementation) { const fallbackDeserializer = extend.deserialize.bind(extend); const fallbackSerializer = extend.serialize.bind(extend); return { deserialize(message) { return implementation.deserialize(message, fallbackDeserializer); }, serialize(input) { return implementation.serialize(input, fallbackSerializer); } }; } __name(extendSerializer, "extendSerializer"); var DefaultErrorSerializer = { deserialize(message) { return Object.assign(new Error(message.message), { name: message.name, stack: message.stack }); }, serialize(error) { return { __error_marker: "$$error", message: error.message, name: error.name, stack: error.stack }; } }; var isSerializedError = /* @__PURE__ */ __name((thing) => thing && typeof thing === "object" && "__error_marker" in thing && thing.__error_marker === "$$error", "isSerializedError"); var DefaultSerializer = { deserialize(message) { return isSerializedError(message) ? DefaultErrorSerializer.deserialize(message) : message; }, serialize(input) { return input instanceof Error ? DefaultErrorSerializer.serialize(input) : input; } }; // src/common.ts globalThis.registeredSerializer = globalThis.registeredSerializer ?? DefaultSerializer; function registerSerializer(serializer) { globalThis.registeredSerializer = extendSerializer(globalThis.registeredSerializer, serializer); } __name(registerSerializer, "registerSerializer"); function deserialize(message) { return globalThis.registeredSerializer.deserialize(message); } __name(deserialize, "deserialize"); function serialize(input) { return globalThis.registeredSerializer.serialize(input); } __name(serialize, "serialize"); // src/symbols.ts var $errors = Symbol("thread.errors"); var $events = Symbol("thread.events"); var $terminate = Symbol("thread.terminate"); var $transferable = Symbol("thread.transferable"); var $worker = Symbol("thread.worker"); // src/transferable.ts function isTransferable(thing) { if (!thing || typeof thing !== "object") return false; return true; } __name(isTransferable, "isTransferable"); function isTransferDescriptor(thing) { return thing && typeof thing === "object" && thing[$transferable]; } __name(isTransferDescriptor, "isTransferDescriptor"); function Transfer(payload, transferables) { console.log("Transfer"); if (!transferables) { if (!isTransferable(payload)) throw new Error("Not transferable"); transferables = [ payload ]; } return { [$transferable]: true, send: payload, transferables }; } __name(Transfer, "Transfer"); // src/types/messages.ts var MasterMessageType = /* @__PURE__ */ function(MasterMessageType2) { MasterMessageType2["cancel"] = "cancel"; MasterMessageType2["run"] = "run"; return MasterMessageType2; }({}); var WorkerMessageType = /* @__PURE__ */ function(WorkerMessageType2) { WorkerMessageType2["error"] = "error"; WorkerMessageType2["init"] = "init"; WorkerMessageType2["result"] = "result"; WorkerMessageType2["running"] = "running"; WorkerMessageType2["uncaughtError"] = "uncaughtError"; return WorkerMessageType2; }({}); // src/worker/expose.ts var isErrorEvent = /* @__PURE__ */ __name((value) => value && value.error, "isErrorEvent"); function createExpose(implementation, self2) { let exposeCalled = false; const activeSubscriptions = /* @__PURE__ */ new Map(); const isMasterJobCancelMessage = /* @__PURE__ */ __name((thing) => thing && thing.type === MasterMessageType.cancel, "isMasterJobCancelMessage"); const isMasterJobRunMessage = /* @__PURE__ */ __name((thing) => thing && thing.type === MasterMessageType.run, "isMasterJobRunMessage"); const isObservable = /* @__PURE__ */ __name((thing) => isSomeObservable(thing) || isZenObservable(thing), "isObservable"); function isZenObservable(thing) { return thing && typeof thing === "object" && typeof thing.subscribe === "function"; } __name(isZenObservable, "isZenObservable"); function deconstructTransfer(thing) { return isTransferDescriptor(thing) ? { payload: thing.send, transferables: thing.transferables } : { payload: thing, transferables: void 0 }; } __name(deconstructTransfer, "deconstructTransfer"); function postFunctionInitMessage() { const initMessage = { exposed: { type: "function" }, type: WorkerMessageType.init }; implementation.postMessageToMaster(initMessage); } __name(postFunctionInitMessage, "postFunctionInitMessage"); function postModuleInitMessage(methodNames) { const initMessage = { exposed: { methods: methodNames, type: "module" }, type: WorkerMessageType.init }; implementation.postMessageToMaster(initMessage); } __name(postModuleInitMessage, "postModuleInitMessage"); function postJobErrorMessage(uid, rawError) { const { payload: error, transferables } = deconstructTransfer(rawError); const errorMessage = { error: serialize(error), type: WorkerMessageType.error, uid }; implementation.postMessageToMaster(errorMessage, transferables); } __name(postJobErrorMessage, "postJobErrorMessage"); function postJobResultMessage(uid, completed, resultValue) { const { payload, transferables } = deconstructTransfer(resultValue); const resultMessage = { complete: completed ? true : void 0, payload, type: WorkerMessageType.result, uid }; implementation.postMessageToMaster(resultMessage, transferables); } __name(postJobResultMessage, "postJobResultMessage"); function postJobStartMessage(uid, resultType) { const startMessage = { resultType, type: WorkerMessageType.running, uid }; implementation.postMessageToMaster(startMessage); } __name(postJobStartMessage, "postJobStartMessage"); function postUncaughtErrorMessage(error) { try { const errorMessage = { error: serialize(error), type: WorkerMessageType.uncaughtError }; implementation.postMessageToMaster(errorMessage); } catch (subError) { console.error("Not reporting uncaught error back to master thread as it occured while reporting an uncaught error already.\nLatest error:", subError, "\nOriginal error:", error); } } __name(postUncaughtErrorMessage, "postUncaughtErrorMessage"); async function runFunction(jobUID, fn, args) { let syncResult; try { syncResult = fn(...args); } catch (ex) { const error = ex; return postJobErrorMessage(jobUID, error); } const resultType = isObservable(syncResult) ? "observable" : "promise"; postJobStartMessage(jobUID, resultType); if (isObservable(syncResult)) { const subscription = syncResult.subscribe((value) => postJobResultMessage(jobUID, false, serialize(value)), (error) => { postJobErrorMessage(jobUID, serialize(error)); activeSubscriptions.delete(jobUID); }, () => { postJobResultMessage(jobUID, true); activeSubscriptions.delete(jobUID); }); activeSubscriptions.set(jobUID, subscription); } else { try { const result = await syncResult; postJobResultMessage(jobUID, true, serialize(result)); } catch (error) { postJobErrorMessage(jobUID, serialize(error)); } } } __name(runFunction, "runFunction"); const expose2 = /* @__PURE__ */ __name((exposed) => { if (!implementation.isWorkerRuntime()) { throw new Error("expose() called in the master thread."); } if (exposeCalled) { throw new Error("expose() called more than once. This is not possible. Pass an object to expose() if you want to expose multiple functions."); } exposeCalled = true; if (typeof exposed === "function") { implementation.subscribeToMasterMessages((messageData) => { if (isMasterJobRunMessage(messageData) && !messageData.method) { runFunction(messageData.uid, exposed, messageData.args.map(deserialize)); } }); postFunctionInitMessage(); } else if (typeof exposed === "object" && exposed) { implementation.subscribeToMasterMessages((messageData) => { if (isMasterJobRunMessage(messageData) && messageData.method) { runFunction(messageData.uid, exposed[messageData.method], messageData.args.map(deserialize)); } }); const methodNames = Object.keys(exposed).filter((key) => typeof exposed[key] === "function"); postModuleInitMessage(methodNames); } else { throw new Error(`Invalid argument passed to expose(). Expected a function or an object, got: ${exposed}`); } implementation.subscribeToMasterMessages((messageData) => { if (isMasterJobCancelMessage(messageData)) { const jobUID = messageData.uid; const subscription = activeSubscriptions.get(jobUID); if (subscription) { subscription.unsubscribe(); activeSubscriptions.delete(jobUID); } } }); }, "expose"); if (typeof globalThis !== "undefined" && typeof self2.addEventListener === "function" && implementation.isWorkerRuntime()) { self2.addEventListener("error", (event) => { setTimeout(() => postUncaughtErrorMessage(isErrorEvent(event) ? event.error : event), 250); }); self2.addEventListener("unhandledrejection", (event) => { const error = event.reason; if (error && typeof error.message === "string") { setTimeout(() => postUncaughtErrorMessage(error), 250); } }); } if (typeof process !== "undefined" && typeof process.on === "function" && implementation.isWorkerRuntime()) { process.on("uncaughtException", (error) => { setTimeout(() => postUncaughtErrorMessage(error), 250); }); process.on("unhandledRejection", (error) => { if (error && typeof error.message === "string") { setTimeout(() => postUncaughtErrorMessage(error), 250); } }); } return expose2; } __name(createExpose, "createExpose"); // src/worker/worker.browser.ts var isWorkerRuntime = /* @__PURE__ */ __name(function isWorkerRuntime2() { const isWindowContext = self !== void 0 && typeof Window !== "undefined" && self instanceof Window; return self !== void 0 && self["postMessage"] && !isWindowContext ? true : false; }, "isWorkerRuntime"); var postMessageToMaster = /* @__PURE__ */ __name(function postMessageToMaster2(data, transferList) { self.postMessage(data, transferList); }, "postMessageToMaster"); var subscribeToMasterMessages = /* @__PURE__ */ __name(function subscribeToMasterMessages2(onMessage) { const messageHandler = /* @__PURE__ */ __name((messageEvent) => { onMessage(messageEvent.data); }, "messageHandler"); const unsubscribe = /* @__PURE__ */ __name(() => { self.removeEventListener("message", messageHandler); }, "unsubscribe"); self.addEventListener("message", messageHandler); return unsubscribe; }, "subscribeToMasterMessages"); var addEventListener = self.addEventListener.bind(void 0); var postMessage = self.postMessage.bind(void 0); var removeEventListener = self.removeEventListener.bind(void 0); var expose = createExpose({ isWorkerRuntime, postMessageToMaster, subscribeToMasterMessages }, { addEventListener, postMessage, removeEventListener }); export { Transfer, addEventListener, expose, isWorkerRuntime, postMessage, postMessageToMaster, registerSerializer, removeEventListener, subscribeToMasterMessages }; //# sourceMappingURL=worker.browser.mjs.map