@xylabs/threads
Version:
Web workers & worker threads as simple as a function call
327 lines (319 loc) • 11.9 kB
JavaScript
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