@xylabs/threads
Version:
Web workers & worker threads as simple as a function call
917 lines (898 loc) • 30.6 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/serializers.ts
function extendSerializer(extend, implementation2) {
const fallbackDeserializer = extend.deserialize.bind(extend);
const fallbackSerializer = extend.serialize.bind(extend);
return {
deserialize(message) {
return implementation2.deserialize(message, fallbackDeserializer);
},
serialize(input) {
return implementation2.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/master/get-bundle-url.browser.ts
var bundleURL;
function getBundleURLCached() {
if (!bundleURL) {
bundleURL = getBundleURL();
}
return bundleURL;
}
__name(getBundleURLCached, "getBundleURLCached");
function getBundleURL() {
try {
throw new Error("getBundleURL failed");
} catch (ex) {
const err = ex;
const matches = ("" + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\/\/[^\n)]+/g);
if (matches) {
return getBaseURL(matches[0]);
}
}
return "/";
}
__name(getBundleURL, "getBundleURL");
function getBaseURL(url) {
return ("" + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\/\/.+)?\/[^/]+(?:\?.*)?$/, "$1") + "/";
}
__name(getBaseURL, "getBaseURL");
// src/master/implementation.browser.ts
var defaultPoolSize = typeof navigator !== "undefined" && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 4;
var isAbsoluteURL = /* @__PURE__ */ __name((value) => /^[A-Za-z][\d+.A-Za-z\-]*:/.test(value), "isAbsoluteURL");
function createSourceBlobURL(code) {
const blob = new Blob([
code
], {
type: "application/javascript"
});
return URL.createObjectURL(blob);
}
__name(createSourceBlobURL, "createSourceBlobURL");
function selectWorkerImplementation() {
if (typeof Worker === "undefined") {
return class NoWebWorker {
static {
__name(this, "NoWebWorker");
}
constructor() {
throw new Error("No web worker implementation available. You might have tried to spawn a worker within a worker in a browser that doesn't support workers in workers.");
}
};
}
let WebWorker = class WebWorker extends Worker {
static {
__name(this, "WebWorker");
}
constructor(url, options) {
if (typeof url === "string" && options && options._baseURL) {
url = new URL(url, options._baseURL);
} else if (typeof url === "string" && !isAbsoluteURL(url) && /^file:\/\//i.test(getBundleURLCached())) {
url = new URL(url, getBundleURLCached().replace(/\/[^/]+$/, "/"));
if (options?.CORSWorkaround ?? true) {
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`);
}
}
if (typeof url === "string" && isAbsoluteURL(url) && (options?.CORSWorkaround ?? true)) {
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`);
}
super(url, options);
}
};
let BlobWorker2 = class BlobWorker3 extends WebWorker {
static {
__name(this, "BlobWorker");
}
constructor(blob, options) {
const url = globalThis.URL.createObjectURL(blob);
super(url, options);
}
static fromText(source, options) {
const blob = new globalThis.Blob([
source
], {
type: "text/javascript"
});
return new BlobWorker3(blob, options);
}
};
return {
blob: BlobWorker2,
default: WebWorker
};
}
__name(selectWorkerImplementation, "selectWorkerImplementation");
var implementation;
function getWorkerImplementation() {
if (!implementation) {
implementation = selectWorkerImplementation();
}
return implementation;
}
__name(getWorkerImplementation, "getWorkerImplementation");
function isWorkerRuntime() {
const isWindowContext = typeof globalThis !== "undefined" && typeof Window !== "undefined" && globalThis instanceof Window;
return typeof globalThis !== "undefined" && self["postMessage"] && !isWindowContext ? true : false;
}
__name(isWorkerRuntime, "isWorkerRuntime");
// src/master/pool-browser.ts
import DebugLogger from "debug";
import { multicast, Observable, Subject } from "observable-fns";
// src/master/pool-types.ts
var PoolEventType = /* @__PURE__ */ function(PoolEventType2) {
PoolEventType2["initialized"] = "initialized";
PoolEventType2["taskCanceled"] = "taskCanceled";
PoolEventType2["taskCompleted"] = "taskCompleted";
PoolEventType2["taskFailed"] = "taskFailed";
PoolEventType2["taskQueued"] = "taskQueued";
PoolEventType2["taskQueueDrained"] = "taskQueueDrained";
PoolEventType2["taskStart"] = "taskStart";
PoolEventType2["terminated"] = "terminated";
return PoolEventType2;
}({});
// 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/master/thread.ts
function fail(message) {
throw new Error(message);
}
__name(fail, "fail");
var Thread = {
/** Return an observable that can be used to subscribe to all errors happening in the thread. */
errors(thread) {
return thread[$errors] || fail("Error observable not found. Make sure to pass a thread instance as returned by the spawn() promise.");
},
/** Return an observable that can be used to subscribe to internal events happening in the thread. Useful for debugging. */
events(thread) {
return thread[$events] || fail("Events observable not found. Make sure to pass a thread instance as returned by the spawn() promise.");
},
/** Terminate a thread. Remember to terminate every thread when you are done using it. */
terminate(thread) {
return thread[$terminate]();
}
};
// src/master/pool-browser.ts
var nextPoolID = 1;
function createArray(size) {
const array = [];
for (let index = 0; index < size; index++) {
array.push(index);
}
return array;
}
__name(createArray, "createArray");
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
__name(delay, "delay");
function flatMap(array, mapper) {
return array.reduce((flattened, element) => [
...flattened,
...mapper(element)
], []);
}
__name(flatMap, "flatMap");
function slugify(text) {
return text.replaceAll(/\W/g, " ").trim().replaceAll(/\s+/g, "-");
}
__name(slugify, "slugify");
function spawnWorkers(spawnWorker, count) {
return createArray(count).map(() => ({
init: spawnWorker(),
runningTasks: []
}));
}
__name(spawnWorkers, "spawnWorkers");
var WorkerPool = class WorkerPool2 {
static {
__name(this, "WorkerPool");
}
static EventType = PoolEventType;
debug;
eventObservable;
options;
workers;
eventSubject = new Subject();
initErrors = [];
isClosing = false;
nextTaskID = 1;
taskQueue = [];
constructor(spawnWorker, optionsOrSize) {
const options = typeof optionsOrSize === "number" ? {
size: optionsOrSize
} : optionsOrSize || {};
const { size = defaultPoolSize } = options;
this.debug = DebugLogger(`threads:pool:${slugify(options.name || String(nextPoolID++))}`);
this.options = options;
this.workers = spawnWorkers(spawnWorker, size);
this.eventObservable = multicast(Observable.from(this.eventSubject));
Promise.all(this.workers.map((worker) => worker.init)).then(() => this.eventSubject.next({
size: this.workers.length,
type: PoolEventType.initialized
}), (error) => {
this.debug("Error while initializing pool worker:", error);
this.eventSubject.error(error);
this.initErrors.push(error);
});
}
findIdlingWorker() {
const { concurrency = 1 } = this.options;
return this.workers.find((worker) => worker.runningTasks.length < concurrency);
}
async runPoolTask(worker, task) {
const workerID = this.workers.indexOf(worker) + 1;
this.debug(`Running task #${task.id} on worker #${workerID}...`);
this.eventSubject.next({
taskID: task.id,
type: PoolEventType.taskStart,
workerID
});
try {
const returnValue = await task.run(await worker.init);
this.debug(`Task #${task.id} completed successfully`);
this.eventSubject.next({
returnValue,
taskID: task.id,
type: PoolEventType.taskCompleted,
workerID
});
} catch (ex) {
const error = ex;
this.debug(`Task #${task.id} failed`);
this.eventSubject.next({
error,
taskID: task.id,
type: PoolEventType.taskFailed,
workerID
});
}
}
run(worker, task) {
const runPromise = (async () => {
const removeTaskFromWorkersRunningTasks = /* @__PURE__ */ __name(() => {
worker.runningTasks = worker.runningTasks.filter((someRunPromise) => someRunPromise !== runPromise);
}, "removeTaskFromWorkersRunningTasks");
await delay(0);
try {
await this.runPoolTask(worker, task);
} finally {
removeTaskFromWorkersRunningTasks();
if (!this.isClosing) {
this.scheduleWork();
}
}
})();
worker.runningTasks.push(runPromise);
}
scheduleWork() {
this.debug("Attempt de-queueing a task in order to run it...");
const availableWorker = this.findIdlingWorker();
if (!availableWorker) return;
const nextTask = this.taskQueue.shift();
if (!nextTask) {
this.debug("Task queue is empty");
this.eventSubject.next({
type: PoolEventType.taskQueueDrained
});
return;
}
this.run(availableWorker, nextTask);
}
taskCompletion(taskID) {
return new Promise((resolve, reject) => {
const eventSubscription = this.events().subscribe((event) => {
if (event.type === PoolEventType.taskCompleted && event.taskID === taskID) {
eventSubscription.unsubscribe();
resolve(event.returnValue);
} else if (event.type === PoolEventType.taskFailed && event.taskID === taskID) {
eventSubscription.unsubscribe();
reject(event.error);
} else if (event.type === PoolEventType.terminated) {
eventSubscription.unsubscribe();
reject(new Error("Pool has been terminated before task was run."));
}
});
});
}
async settled(allowResolvingImmediately = false) {
const getCurrentlyRunningTasks = /* @__PURE__ */ __name(() => flatMap(this.workers, (worker) => worker.runningTasks), "getCurrentlyRunningTasks");
const taskFailures = [];
const failureSubscription = this.eventObservable.subscribe((event) => {
if (event.type === PoolEventType.taskFailed) {
taskFailures.push(event.error);
}
});
if (this.initErrors.length > 0) {
throw this.initErrors[0];
}
if (allowResolvingImmediately && this.taskQueue.length === 0) {
await Promise.allSettled(getCurrentlyRunningTasks());
return taskFailures;
}
await new Promise((resolve, reject) => {
const subscription = this.eventObservable.subscribe({
error: reject,
next(event) {
if (event.type === PoolEventType.taskQueueDrained) {
subscription.unsubscribe();
resolve(void 0);
}
}
});
});
await Promise.allSettled(getCurrentlyRunningTasks());
failureSubscription.unsubscribe();
return taskFailures;
}
async completed(allowResolvingImmediately = false) {
const settlementPromise = this.settled(allowResolvingImmediately);
const earlyExitPromise = new Promise((resolve, reject) => {
const subscription = this.eventObservable.subscribe({
error: reject,
next(event) {
if (event.type === PoolEventType.taskQueueDrained) {
subscription.unsubscribe();
resolve(settlementPromise);
} else if (event.type === PoolEventType.taskFailed) {
subscription.unsubscribe();
reject(event.error);
}
}
});
});
const errors = await Promise.race([
settlementPromise,
earlyExitPromise
]);
if (errors.length > 0) {
throw errors[0];
}
}
events() {
return this.eventObservable;
}
queue(taskFunction) {
const { maxQueuedJobs = Number.POSITIVE_INFINITY } = this.options;
if (this.isClosing) {
throw new Error("Cannot schedule pool tasks after terminate() has been called.");
}
if (this.initErrors.length > 0) {
throw this.initErrors[0];
}
const taskID = this.nextTaskID++;
const taskCompletion = this.taskCompletion(taskID);
taskCompletion.catch((error) => {
this.debug(`Task #${taskID} errored:`, error);
});
const task = {
cancel: /* @__PURE__ */ __name(() => {
if (!this.taskQueue.includes(task)) return;
this.taskQueue = this.taskQueue.filter((someTask) => someTask !== task);
this.eventSubject.next({
taskID: task.id,
type: PoolEventType.taskCanceled
});
}, "cancel"),
id: taskID,
run: taskFunction,
then: taskCompletion.then.bind(taskCompletion)
};
if (this.taskQueue.length >= maxQueuedJobs) {
throw new Error("Maximum number of pool tasks queued. Refusing to queue another one.\nThis usually happens for one of two reasons: We are either at peak workload right now or some tasks just won't finish, thus blocking the pool.");
}
this.debug(`Queueing task #${task.id}...`);
this.taskQueue.push(task);
this.eventSubject.next({
taskID: task.id,
type: PoolEventType.taskQueued
});
this.scheduleWork();
return task;
}
async terminate(force) {
this.isClosing = true;
if (!force) {
await this.completed(true);
}
this.eventSubject.next({
remainingQueue: [
...this.taskQueue
],
type: PoolEventType.terminated
});
this.eventSubject.complete();
await Promise.all(this.workers.map(async (worker) => Thread.terminate(await worker.init)));
}
};
function PoolConstructor(spawnWorker, optionsOrSize) {
return new WorkerPool(spawnWorker, optionsOrSize);
}
__name(PoolConstructor, "PoolConstructor");
PoolConstructor.EventType = PoolEventType;
var Pool = PoolConstructor;
// src/master/spawn.ts
import DebugLogger3 from "debug";
import { Observable as Observable4 } from "observable-fns";
// src/promise.ts
var doNothing = /* @__PURE__ */ __name(() => void 0, "doNothing");
function createPromiseWithResolver() {
let alreadyResolved = false;
let resolvedTo;
let resolver = doNothing;
const promise = new Promise((resolve) => {
if (alreadyResolved) {
resolve(resolvedTo);
} else {
resolver = resolve;
}
});
const exposedResolver = /* @__PURE__ */ __name((value) => {
alreadyResolved = true;
resolvedTo = value;
resolver(resolvedTo);
}, "exposedResolver");
return [
promise,
exposedResolver
];
}
__name(createPromiseWithResolver, "createPromiseWithResolver");
// src/types/master.ts
var WorkerEventType = /* @__PURE__ */ function(WorkerEventType2) {
WorkerEventType2["internalError"] = "internalError";
WorkerEventType2["message"] = "message";
WorkerEventType2["termination"] = "termination";
return WorkerEventType2;
}({});
// src/master/invocation-proxy.ts
import DebugLogger2 from "debug";
import { multicast as multicast2, Observable as Observable3 } from "observable-fns";
// src/observable-promise.ts
import { Observable as Observable2 } from "observable-fns";
var doNothing2 = /* @__PURE__ */ __name(() => {
}, "doNothing");
var returnInput = /* @__PURE__ */ __name((input) => input, "returnInput");
var runDeferred = /* @__PURE__ */ __name((fn) => Promise.resolve().then(fn), "runDeferred");
function fail2(error) {
throw error;
}
__name(fail2, "fail");
function isThenable(thing) {
return thing && typeof thing.then === "function";
}
__name(isThenable, "isThenable");
var ObservablePromise = class _ObservablePromise extends Observable2 {
static {
__name(this, "ObservablePromise");
}
[Symbol.toStringTag] = "[object ObservablePromise]";
initHasRun = false;
fulfillmentCallbacks = [];
rejectionCallbacks = [];
firstValue;
firstValueSet = false;
rejection;
state = "pending";
constructor(init) {
super((originalObserver) => {
const self2 = this;
const observer = {
...originalObserver,
complete() {
originalObserver.complete();
self2.onCompletion();
},
error(error) {
originalObserver.error(error);
self2.onError(error);
},
next(value) {
originalObserver.next(value);
self2.onNext(value);
}
};
try {
this.initHasRun = true;
return init(observer);
} catch (error) {
observer.error(error);
}
});
}
onNext(value) {
if (!this.firstValueSet) {
this.firstValue = value;
this.firstValueSet = true;
}
}
onError(error) {
this.state = "rejected";
this.rejection = error;
for (const onRejected of this.rejectionCallbacks) {
runDeferred(() => onRejected(error));
}
}
onCompletion() {
this.state = "fulfilled";
for (const onFulfilled of this.fulfillmentCallbacks) {
runDeferred(() => onFulfilled(this.firstValue));
}
}
then(onFulfilledRaw, onRejectedRaw) {
const onFulfilled = onFulfilledRaw || returnInput;
const onRejected = onRejectedRaw || fail2;
let onRejectedCalled = false;
return new Promise((resolve, reject) => {
const rejectionCallback = /* @__PURE__ */ __name((error) => {
if (onRejectedCalled) return;
onRejectedCalled = true;
try {
resolve(onRejected(error));
} catch (anotherError) {
reject(anotherError);
}
}, "rejectionCallback");
const fulfillmentCallback = /* @__PURE__ */ __name((value) => {
try {
resolve(onFulfilled(value));
} catch (ex) {
const error = ex;
rejectionCallback(error);
}
}, "fulfillmentCallback");
if (!this.initHasRun) {
this.subscribe({
error: rejectionCallback
});
}
if (this.state === "fulfilled") {
return resolve(onFulfilled(this.firstValue));
}
if (this.state === "rejected") {
onRejectedCalled = true;
return resolve(onRejected(this.rejection));
}
this.fulfillmentCallbacks.push(fulfillmentCallback);
this.rejectionCallbacks.push(rejectionCallback);
});
}
catch(onRejected) {
return this.then(void 0, onRejected);
}
finally(onCompleted) {
const handler = onCompleted || doNothing2;
return this.then((value) => {
handler();
return value;
}, () => handler());
}
static from(thing) {
return isThenable(thing) ? new _ObservablePromise((observer) => {
const onFulfilled = /* @__PURE__ */ __name((value) => {
observer.next(value);
observer.complete();
}, "onFulfilled");
const onRejected = /* @__PURE__ */ __name((error) => {
observer.error(error);
}, "onRejected");
thing.then(onFulfilled, onRejected);
}) : super.from(thing);
}
};
// 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/master/invocation-proxy.ts
var debugMessages = DebugLogger2("threads:master:messages");
var nextJobUID = 1;
var dedupe = /* @__PURE__ */ __name((array) => [
...new Set(array)
], "dedupe");
var isJobErrorMessage = /* @__PURE__ */ __name((data) => data && data.type === WorkerMessageType.error, "isJobErrorMessage");
var isJobResultMessage = /* @__PURE__ */ __name((data) => data && data.type === WorkerMessageType.result, "isJobResultMessage");
var isJobStartMessage = /* @__PURE__ */ __name((data) => data && data.type === WorkerMessageType.running, "isJobStartMessage");
function createObservableForJob(worker, jobUID) {
return new Observable3((observer) => {
let asyncType;
const messageHandler = /* @__PURE__ */ __name((event) => {
debugMessages("Message from worker:", event.data);
if (!event.data || event.data.uid !== jobUID) return;
if (isJobStartMessage(event.data)) {
asyncType = event.data.resultType;
} else if (isJobResultMessage(event.data)) {
if (asyncType === "promise") {
if (event.data.payload !== void 0) {
observer.next(deserialize(event.data.payload));
}
observer.complete();
worker.removeEventListener("message", messageHandler);
} else {
if (event.data.payload) {
observer.next(deserialize(event.data.payload));
}
if (event.data.complete) {
observer.complete();
worker.removeEventListener("message", messageHandler);
}
}
} else if (isJobErrorMessage(event.data)) {
const error = deserialize(event.data.error);
if (asyncType === "promise" || !asyncType) {
observer.error(error);
} else {
observer.error(error);
}
worker.removeEventListener("message", messageHandler);
}
}, "messageHandler");
worker.addEventListener("message", messageHandler);
return () => {
if (asyncType === "observable" || !asyncType) {
const cancelMessage = {
type: MasterMessageType.cancel,
uid: jobUID
};
worker.postMessage(cancelMessage);
}
worker.removeEventListener("message", messageHandler);
};
});
}
__name(createObservableForJob, "createObservableForJob");
function prepareArguments(rawArgs) {
if (rawArgs.length === 0) {
return {
args: [],
transferables: []
};
}
const args = [];
const transferables = [];
for (const arg of rawArgs) {
if (isTransferDescriptor(arg)) {
args.push(serialize(arg.send));
transferables.push(...arg.transferables);
} else {
args.push(serialize(arg));
}
}
return {
args,
transferables: transferables.length === 0 ? transferables : dedupe(transferables)
};
}
__name(prepareArguments, "prepareArguments");
function createProxyFunction(worker, method) {
return (...rawArgs) => {
const uid = nextJobUID++;
const { args, transferables } = prepareArguments(rawArgs);
const runMessage = {
args,
method,
type: MasterMessageType.run,
uid
};
debugMessages("Sending command to run function to worker:", runMessage);
try {
worker.postMessage(runMessage, transferables);
} catch (error) {
return ObservablePromise.from(Promise.reject(error));
}
return ObservablePromise.from(multicast2(createObservableForJob(worker, uid)));
};
}
__name(createProxyFunction, "createProxyFunction");
function createProxyModule(worker, methodNames) {
const proxy = {};
for (const methodName of methodNames) {
proxy[methodName] = createProxyFunction(worker, methodName);
}
return proxy;
}
__name(createProxyModule, "createProxyModule");
// src/master/spawn.ts
var debugMessages2 = DebugLogger3("threads:master:messages");
var debugSpawn = DebugLogger3("threads:master:spawn");
var debugThreadUtils = DebugLogger3("threads:master:thread-utils");
var isInitMessage = /* @__PURE__ */ __name((data) => data && data.type === "init", "isInitMessage");
var isUncaughtErrorMessage = /* @__PURE__ */ __name((data) => data && data.type === "uncaughtError", "isUncaughtErrorMessage");
var initMessageTimeout = typeof process !== "undefined" && process.env !== void 0 && process.env.THREADS_WORKER_INIT_TIMEOUT ? Number.parseInt(process.env.THREADS_WORKER_INIT_TIMEOUT, 10) : 1e4;
async function withTimeout(promise, timeoutInMs, errorMessage) {
let timeoutHandle;
const timeout = new Promise((resolve, reject) => {
timeoutHandle = setTimeout(() => reject(new Error(errorMessage)), timeoutInMs);
});
const result = await Promise.race([
promise,
timeout
]);
clearTimeout(timeoutHandle);
return result;
}
__name(withTimeout, "withTimeout");
function receiveInitMessage(worker) {
return new Promise((resolve, reject) => {
const messageHandler = /* @__PURE__ */ __name((event) => {
debugMessages2("Message from worker before finishing initialization:", event.data);
if (isInitMessage(event.data)) {
worker.removeEventListener("message", messageHandler);
resolve(event.data);
} else if (isUncaughtErrorMessage(event.data)) {
worker.removeEventListener("message", messageHandler);
reject(deserialize(event.data.error));
}
}, "messageHandler");
worker.addEventListener("message", messageHandler);
});
}
__name(receiveInitMessage, "receiveInitMessage");
function createEventObservable(worker, workerTermination) {
return new Observable4((observer) => {
const messageHandler = /* @__PURE__ */ __name((messageEvent) => {
const workerEvent = {
data: messageEvent.data,
type: WorkerEventType.message
};
observer.next(workerEvent);
}, "messageHandler");
const rejectionHandler = /* @__PURE__ */ __name((errorEvent) => {
debugThreadUtils("Unhandled promise rejection event in thread:", errorEvent);
const workerEvent = {
error: new Error(errorEvent.reason),
type: WorkerEventType.internalError
};
observer.next(workerEvent);
}, "rejectionHandler");
worker.addEventListener("message", messageHandler);
worker.addEventListener("unhandledrejection", rejectionHandler);
workerTermination.then(() => {
const terminationEvent = {
type: WorkerEventType.termination
};
worker.removeEventListener("message", messageHandler);
worker.removeEventListener("unhandledrejection", rejectionHandler);
observer.next(terminationEvent);
observer.complete();
});
});
}
__name(createEventObservable, "createEventObservable");
function createTerminator(worker) {
const [termination, resolver] = createPromiseWithResolver();
const terminate = /* @__PURE__ */ __name(async () => {
debugThreadUtils("Terminating worker");
await worker.terminate();
resolver();
}, "terminate");
return {
terminate,
termination
};
}
__name(createTerminator, "createTerminator");
function setPrivateThreadProps(raw, worker, workerEvents, terminate) {
const workerErrors = workerEvents.filter((event) => event.type === WorkerEventType.internalError).map((errorEvent) => errorEvent.error);
return Object.assign(raw, {
[$errors]: workerErrors,
[$events]: workerEvents,
[$terminate]: terminate,
[$worker]: worker
});
}
__name(setPrivateThreadProps, "setPrivateThreadProps");
async function spawn(worker, options) {
debugSpawn("Initializing new thread");
const timeout = options && options.timeout ? options.timeout : initMessageTimeout;
const initMessage = await withTimeout(receiveInitMessage(worker), timeout, `Timeout: Did not receive an init message from worker after ${timeout}ms. Make sure the worker calls expose().`);
const exposed = initMessage.exposed;
const { termination, terminate } = createTerminator(worker);
const events = createEventObservable(worker, termination);
if (exposed.type === "function") {
const proxy = createProxyFunction(worker);
return setPrivateThreadProps(proxy, worker, events, terminate);
} else if (exposed.type === "module") {
const proxy = createProxyModule(worker, exposed.methods);
return setPrivateThreadProps(proxy, worker, events, terminate);
} else {
const type = exposed.type;
throw new Error(`Worker init message states unexpected type of expose(): ${type}`);
}
}
__name(spawn, "spawn");
// src/master/index-browser.ts
var BlobWorker = getWorkerImplementation().blob;
var Worker2 = getWorkerImplementation().default;
export {
BlobWorker,
DefaultSerializer,
Pool,
Thread,
Transfer,
Worker2 as Worker,
isWorkerRuntime,
registerSerializer,
spawn
};
//# sourceMappingURL=index-browser.mjs.map