@xylabs/threads 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/common.d.ts +4 -0
- package/dist/common.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +27 -0
- package/dist/master/get-bundle-url.browser.d.ts +3 -0
- package/dist/master/get-bundle-url.browser.js +29 -0
- package/dist/master/implementation.browser.d.ts +4 -0
- package/dist/master/implementation.browser.js +69 -0
- package/dist/master/implementation.d.ts +6 -0
- package/dist/master/implementation.js +41 -0
- package/dist/master/implementation.node.d.ts +5 -0
- package/dist/master/implementation.node.js +255 -0
- package/dist/master/index.d.ts +13 -0
- package/dist/master/index.js +16 -0
- package/dist/master/invocation-proxy.d.ts +3 -0
- package/dist/master/invocation-proxy.js +130 -0
- package/dist/master/pool-types.d.ts +65 -0
- package/dist/master/pool-types.js +15 -0
- package/dist/master/pool.d.ts +90 -0
- package/dist/master/pool.js +281 -0
- package/dist/master/register.d.ts +1 -0
- package/dist/master/register.js +12 -0
- package/dist/master/spawn.d.ts +20 -0
- package/dist/master/spawn.js +130 -0
- package/dist/master/thread.d.ts +12 -0
- package/dist/master/thread.js +22 -0
- package/dist/observable-promise.d.ts +38 -0
- package/dist/observable-promise.js +156 -0
- package/dist/observable.d.ts +19 -0
- package/dist/observable.js +43 -0
- package/dist/ponyfills.d.ts +8 -0
- package/dist/ponyfills.js +22 -0
- package/dist/promise.d.ts +5 -0
- package/dist/promise.js +29 -0
- package/dist/serializers.d.ts +16 -0
- package/dist/serializers.js +41 -0
- package/dist/symbols.d.ts +5 -0
- package/dist/symbols.js +8 -0
- package/dist/transferable.d.ts +42 -0
- package/dist/transferable.js +28 -0
- package/dist/types/master.d.ts +99 -0
- package/dist/types/master.js +14 -0
- package/dist/types/messages.d.ts +62 -0
- package/dist/types/messages.js +20 -0
- package/dist/types/worker.d.ts +11 -0
- package/dist/types/worker.js +2 -0
- package/dist/worker/bundle-entry.d.ts +1 -0
- package/dist/worker/bundle-entry.js +27 -0
- package/dist/worker/implementation.browser.d.ts +7 -0
- package/dist/worker/implementation.browser.js +28 -0
- package/dist/worker/implementation.d.ts +3 -0
- package/dist/worker/implementation.js +24 -0
- package/dist/worker/implementation.tiny-worker.d.ts +7 -0
- package/dist/worker/implementation.tiny-worker.js +38 -0
- package/dist/worker/implementation.worker_threads.d.ts +8 -0
- package/dist/worker/implementation.worker_threads.js +42 -0
- package/dist/worker/index.d.ts +13 -0
- package/dist/worker/index.js +195 -0
- package/dist/worker_threads.d.ts +8 -0
- package/dist/worker_threads.js +17 -0
- package/dist-esm/common.js +12 -0
- package/dist-esm/index.js +6 -0
- package/dist-esm/master/get-bundle-url.browser.js +25 -0
- package/dist-esm/master/implementation.browser.js +64 -0
- package/dist-esm/master/implementation.js +15 -0
- package/dist-esm/master/implementation.node.js +224 -0
- package/dist-esm/master/index.js +9 -0
- package/dist-esm/master/invocation-proxy.js +122 -0
- package/dist-esm/master/pool-types.js +12 -0
- package/dist-esm/master/pool.js +273 -0
- package/dist-esm/master/register.js +10 -0
- package/dist-esm/master/spawn.js +123 -0
- package/dist-esm/master/thread.js +19 -0
- package/dist-esm/observable-promise.js +152 -0
- package/dist-esm/observable.js +38 -0
- package/dist-esm/ponyfills.js +18 -0
- package/dist-esm/promise.js +25 -0
- package/dist-esm/serializers.js +37 -0
- package/dist-esm/symbols.js +5 -0
- package/dist-esm/transferable.js +23 -0
- package/dist-esm/types/master.js +11 -0
- package/dist-esm/types/messages.js +17 -0
- package/dist-esm/types/worker.js +1 -0
- package/dist-esm/worker/bundle-entry.js +11 -0
- package/dist-esm/worker/implementation.browser.js +26 -0
- package/dist-esm/worker/implementation.js +19 -0
- package/dist-esm/worker/implementation.tiny-worker.js +36 -0
- package/dist-esm/worker/implementation.worker_threads.js +37 -0
- package/dist-esm/worker/index.js +186 -0
- package/dist-esm/worker_threads.js +14 -0
- package/index.mjs +11 -0
- package/observable.d.ts +2 -0
- package/observable.js +3 -0
- package/observable.mjs +5 -0
- package/package.json +141 -0
- package/register.d.ts +3 -0
- package/register.js +3 -0
- package/register.mjs +2 -0
- package/rollup.config.js +16 -0
- package/src/common.ts +16 -0
- package/src/index.ts +8 -0
- package/src/master/get-bundle-url.browser.ts +31 -0
- package/src/master/implementation.browser.ts +80 -0
- package/src/master/implementation.node.ts +284 -0
- package/src/master/implementation.ts +21 -0
- package/src/master/index.ts +20 -0
- package/src/master/invocation-proxy.ts +146 -0
- package/src/master/pool-types.ts +83 -0
- package/src/master/pool.ts +391 -0
- package/src/master/register.ts +10 -0
- package/src/master/spawn.ts +172 -0
- package/src/master/thread.ts +26 -0
- package/src/observable-promise.ts +181 -0
- package/src/observable.ts +43 -0
- package/src/ponyfills.ts +31 -0
- package/src/promise.ts +26 -0
- package/src/serializers.ts +67 -0
- package/src/symbols.ts +5 -0
- package/src/transferable.ts +68 -0
- package/src/types/master.ts +130 -0
- package/src/types/messages.ts +81 -0
- package/src/types/worker.ts +14 -0
- package/src/worker/bundle-entry.ts +10 -0
- package/src/worker/implementation.browser.ts +40 -0
- package/src/worker/implementation.tiny-worker.ts +52 -0
- package/src/worker/implementation.ts +23 -0
- package/src/worker/implementation.worker_threads.ts +50 -0
- package/src/worker/index.ts +228 -0
- package/src/worker_threads.ts +28 -0
- package/test/lib/serialization.ts +38 -0
- package/test/observable-promise.test.ts +189 -0
- package/test/observable.test.ts +86 -0
- package/test/pool.test.ts +173 -0
- package/test/serialization.test.ts +21 -0
- package/test/spawn.chromium.mocha.ts +49 -0
- package/test/spawn.test.ts +71 -0
- package/test/streaming.test.ts +27 -0
- package/test/transferables.test.ts +69 -0
- package/test/workers/arraybuffer-xor.ts +11 -0
- package/test/workers/count-to-five.ts +13 -0
- package/test/workers/counter.ts +20 -0
- package/test/workers/faulty-function.ts +6 -0
- package/test/workers/hello-world.ts +6 -0
- package/test/workers/increment.ts +9 -0
- package/test/workers/minmax.ts +25 -0
- package/test/workers/serialization.ts +12 -0
- package/test/workers/top-level-throw.ts +1 -0
- package/test-tooling/rollup/app.js +20 -0
- package/test-tooling/rollup/rollup.config.ts +15 -0
- package/test-tooling/rollup/rollup.test.ts +44 -0
- package/test-tooling/rollup/worker.js +7 -0
- package/test-tooling/tsconfig/minimal-tsconfig.test.ts +7 -0
- package/test-tooling/tsconfig/minimal.ts +10 -0
- package/test-tooling/webpack/addition-worker.ts +10 -0
- package/test-tooling/webpack/app-with-inlined-worker.ts +29 -0
- package/test-tooling/webpack/app.ts +58 -0
- package/test-tooling/webpack/pool-worker.ts +6 -0
- package/test-tooling/webpack/raw-loader.d.ts +4 -0
- package/test-tooling/webpack/webpack.chromium.mocha.ts +21 -0
- package/test-tooling/webpack/webpack.node.config.js +38 -0
- package/test-tooling/webpack/webpack.test.ts +90 -0
- package/test-tooling/webpack/webpack.web.config.js +35 -0
- package/types/is-observable.d.ts +7 -0
- package/types/tiny-worker.d.ts +4 -0
- package/types/webworker.d.ts +9 -0
- package/worker.d.ts +2 -0
- package/worker.js +3 -0
- package/worker.mjs +7 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-thenable */
|
|
2
|
+
/* eslint-disable @typescript-eslint/member-ordering */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-this-alias */
|
|
6
|
+
/* eslint-disable unicorn/no-this-assignment */
|
|
7
|
+
import { Observable } from 'observable-fns';
|
|
8
|
+
const doNothing = () => { };
|
|
9
|
+
const returnInput = (input) => input;
|
|
10
|
+
const runDeferred = (fn) => Promise.resolve().then(fn);
|
|
11
|
+
function fail(error) {
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
function isThenable(thing) {
|
|
15
|
+
return thing && typeof thing.then === 'function';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a hybrid, combining the APIs of an Observable and a Promise.
|
|
19
|
+
*
|
|
20
|
+
* It is used to proxy async process states when we are initially not sure
|
|
21
|
+
* if that async process will yield values once (-> Promise) or multiple
|
|
22
|
+
* times (-> Observable).
|
|
23
|
+
*
|
|
24
|
+
* Note that the observable promise inherits some of the observable's characteristics:
|
|
25
|
+
* The `init` function will be called *once for every time anyone subscribes to it*.
|
|
26
|
+
*
|
|
27
|
+
* If this is undesired, derive a hot observable from it using `makeHot()` and
|
|
28
|
+
* subscribe to that.
|
|
29
|
+
*/
|
|
30
|
+
export class ObservablePromise extends Observable {
|
|
31
|
+
[Symbol.toStringTag] = '[object ObservablePromise]';
|
|
32
|
+
initHasRun = false;
|
|
33
|
+
fulfillmentCallbacks = [];
|
|
34
|
+
rejectionCallbacks = [];
|
|
35
|
+
firstValue;
|
|
36
|
+
firstValueSet = false;
|
|
37
|
+
rejection;
|
|
38
|
+
state = 'pending';
|
|
39
|
+
constructor(init) {
|
|
40
|
+
super((originalObserver) => {
|
|
41
|
+
// tslint:disable-next-line no-this-assignment
|
|
42
|
+
const self = this;
|
|
43
|
+
const observer = {
|
|
44
|
+
...originalObserver,
|
|
45
|
+
complete() {
|
|
46
|
+
originalObserver.complete();
|
|
47
|
+
self.onCompletion();
|
|
48
|
+
},
|
|
49
|
+
error(error) {
|
|
50
|
+
originalObserver.error(error);
|
|
51
|
+
self.onError(error);
|
|
52
|
+
},
|
|
53
|
+
next(value) {
|
|
54
|
+
originalObserver.next(value);
|
|
55
|
+
self.onNext(value);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
this.initHasRun = true;
|
|
60
|
+
return init(observer);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
observer.error(error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
onNext(value) {
|
|
68
|
+
if (!this.firstValueSet) {
|
|
69
|
+
this.firstValue = value;
|
|
70
|
+
this.firstValueSet = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
onError(error) {
|
|
74
|
+
this.state = 'rejected';
|
|
75
|
+
this.rejection = error;
|
|
76
|
+
for (const onRejected of this.rejectionCallbacks) {
|
|
77
|
+
// Promisifying the call to turn errors into unhandled promise rejections
|
|
78
|
+
// instead of them failing sync and cancelling the iteration
|
|
79
|
+
runDeferred(() => onRejected(error));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
onCompletion() {
|
|
83
|
+
this.state = 'fulfilled';
|
|
84
|
+
for (const onFulfilled of this.fulfillmentCallbacks) {
|
|
85
|
+
// Promisifying the call to turn errors into unhandled promise rejections
|
|
86
|
+
// instead of them failing sync and cancelling the iteration
|
|
87
|
+
runDeferred(() => onFulfilled(this.firstValue));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
then(onFulfilledRaw, onRejectedRaw) {
|
|
91
|
+
const onFulfilled = onFulfilledRaw || returnInput;
|
|
92
|
+
const onRejected = onRejectedRaw || fail;
|
|
93
|
+
let onRejectedCalled = false;
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const rejectionCallback = (error) => {
|
|
96
|
+
if (onRejectedCalled)
|
|
97
|
+
return;
|
|
98
|
+
onRejectedCalled = true;
|
|
99
|
+
try {
|
|
100
|
+
resolve(onRejected(error));
|
|
101
|
+
}
|
|
102
|
+
catch (anotherError) {
|
|
103
|
+
reject(anotherError);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const fulfillmentCallback = (value) => {
|
|
107
|
+
try {
|
|
108
|
+
resolve(onFulfilled(value));
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
rejectionCallback(error);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
if (!this.initHasRun) {
|
|
115
|
+
this.subscribe({ error: rejectionCallback });
|
|
116
|
+
}
|
|
117
|
+
if (this.state === 'fulfilled') {
|
|
118
|
+
return resolve(onFulfilled(this.firstValue));
|
|
119
|
+
}
|
|
120
|
+
if (this.state === 'rejected') {
|
|
121
|
+
onRejectedCalled = true;
|
|
122
|
+
return resolve(onRejected(this.rejection));
|
|
123
|
+
}
|
|
124
|
+
this.fulfillmentCallbacks.push(fulfillmentCallback);
|
|
125
|
+
this.rejectionCallbacks.push(rejectionCallback);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch(onRejected) {
|
|
129
|
+
return this.then(undefined, onRejected);
|
|
130
|
+
}
|
|
131
|
+
finally(onCompleted) {
|
|
132
|
+
const handler = onCompleted || doNothing;
|
|
133
|
+
return this.then((value) => {
|
|
134
|
+
handler();
|
|
135
|
+
return value;
|
|
136
|
+
}, () => handler());
|
|
137
|
+
}
|
|
138
|
+
static from(thing) {
|
|
139
|
+
return isThenable(thing) ?
|
|
140
|
+
new ObservablePromise((observer) => {
|
|
141
|
+
const onFulfilled = (value) => {
|
|
142
|
+
observer.next(value);
|
|
143
|
+
observer.complete();
|
|
144
|
+
};
|
|
145
|
+
const onRejected = (error) => {
|
|
146
|
+
observer.error(error);
|
|
147
|
+
};
|
|
148
|
+
thing.then(onFulfilled, onRejected);
|
|
149
|
+
})
|
|
150
|
+
: super.from(thing);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Observable } from 'observable-fns';
|
|
3
|
+
const $observers = Symbol('observers');
|
|
4
|
+
/**
|
|
5
|
+
* Observable subject. Implements the Observable interface, but also exposes
|
|
6
|
+
* the `next()`, `error()`, `complete()` methods to initiate observable
|
|
7
|
+
* updates "from the outside".
|
|
8
|
+
*
|
|
9
|
+
* Use `Observable.from(subject)` to derive an observable that proxies all
|
|
10
|
+
* values, errors and the completion raised on this subject, but does not
|
|
11
|
+
* expose the `next()`, `error()`, `complete()` methods.
|
|
12
|
+
*/
|
|
13
|
+
export class Subject extends Observable {
|
|
14
|
+
[$observers];
|
|
15
|
+
constructor() {
|
|
16
|
+
super((observer) => {
|
|
17
|
+
this[$observers] = [...(this[$observers] || []), observer];
|
|
18
|
+
const unsubscribe = () => {
|
|
19
|
+
this[$observers] = this[$observers].filter((someObserver) => someObserver !== observer);
|
|
20
|
+
};
|
|
21
|
+
return unsubscribe;
|
|
22
|
+
});
|
|
23
|
+
this[$observers] = [];
|
|
24
|
+
}
|
|
25
|
+
complete() {
|
|
26
|
+
for (const observer of this[$observers])
|
|
27
|
+
observer.complete();
|
|
28
|
+
}
|
|
29
|
+
error(error) {
|
|
30
|
+
for (const observer of this[$observers])
|
|
31
|
+
observer.error(error);
|
|
32
|
+
}
|
|
33
|
+
next(value) {
|
|
34
|
+
for (const observer of this[$observers])
|
|
35
|
+
observer.next(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export { Observable } from 'observable-fns';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Based on <https://github.com/es-shims/Promise.allSettled/blob/master/implementation.js>
|
|
2
|
+
export function allSettled(values) {
|
|
3
|
+
return Promise.all(values.map((item) => {
|
|
4
|
+
const onFulfill = (value) => {
|
|
5
|
+
return { status: 'fulfilled', value };
|
|
6
|
+
};
|
|
7
|
+
const onReject = (reason) => {
|
|
8
|
+
return { reason, status: 'rejected' };
|
|
9
|
+
};
|
|
10
|
+
const itemPromise = Promise.resolve(item);
|
|
11
|
+
try {
|
|
12
|
+
return itemPromise.then(onFulfill, onReject);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return Promise.reject(error);
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
2
|
+
const doNothing = () => undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new promise and exposes its resolver function.
|
|
5
|
+
* Use with care!
|
|
6
|
+
*/
|
|
7
|
+
export function createPromiseWithResolver() {
|
|
8
|
+
let alreadyResolved = false;
|
|
9
|
+
let resolvedTo;
|
|
10
|
+
let resolver = doNothing;
|
|
11
|
+
const promise = new Promise((resolve) => {
|
|
12
|
+
if (alreadyResolved) {
|
|
13
|
+
resolve(resolvedTo);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
resolver = resolve;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const exposedResolver = (value) => {
|
|
20
|
+
alreadyResolved = true;
|
|
21
|
+
resolvedTo = value;
|
|
22
|
+
resolver(resolvedTo);
|
|
23
|
+
};
|
|
24
|
+
return [promise, exposedResolver];
|
|
25
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function extendSerializer(extend, implementation) {
|
|
2
|
+
const fallbackDeserializer = extend.deserialize.bind(extend);
|
|
3
|
+
const fallbackSerializer = extend.serialize.bind(extend);
|
|
4
|
+
return {
|
|
5
|
+
deserialize(message) {
|
|
6
|
+
return implementation.deserialize(message, fallbackDeserializer);
|
|
7
|
+
},
|
|
8
|
+
serialize(input) {
|
|
9
|
+
return implementation.serialize(input, fallbackSerializer);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const DefaultErrorSerializer = {
|
|
14
|
+
deserialize(message) {
|
|
15
|
+
return Object.assign(Error(message.message), {
|
|
16
|
+
name: message.name,
|
|
17
|
+
stack: message.stack,
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
serialize(error) {
|
|
21
|
+
return {
|
|
22
|
+
__error_marker: '$$error',
|
|
23
|
+
message: error.message,
|
|
24
|
+
name: error.name,
|
|
25
|
+
stack: error.stack,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const isSerializedError = (thing) => thing && typeof thing === 'object' && '__error_marker' in thing && thing.__error_marker === '$$error';
|
|
30
|
+
export const DefaultSerializer = {
|
|
31
|
+
deserialize(message) {
|
|
32
|
+
return isSerializedError(message) ? DefaultErrorSerializer.deserialize(message) : message;
|
|
33
|
+
},
|
|
34
|
+
serialize(input) {
|
|
35
|
+
return input instanceof Error ? DefaultErrorSerializer.serialize(input) : input;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { $transferable } from './symbols';
|
|
3
|
+
function isTransferable(thing) {
|
|
4
|
+
if (!thing || typeof thing !== 'object')
|
|
5
|
+
return false;
|
|
6
|
+
// Don't check too thoroughly, since the list of transferable things in JS might grow over time
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
export function isTransferDescriptor(thing) {
|
|
10
|
+
return thing && typeof thing === 'object' && thing[$transferable];
|
|
11
|
+
}
|
|
12
|
+
export function Transfer(payload, transferables) {
|
|
13
|
+
if (!transferables) {
|
|
14
|
+
if (!isTransferable(payload))
|
|
15
|
+
throw new Error('Not transferable');
|
|
16
|
+
transferables = [payload];
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
[$transferable]: true,
|
|
20
|
+
send: payload,
|
|
21
|
+
transferables,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/// <reference lib="dom" />
|
|
3
|
+
// tslint:disable max-classes-per-file
|
|
4
|
+
import { $errors, $events, $terminate, $worker } from '../symbols';
|
|
5
|
+
/** Event as emitted by worker thread. Subscribe to using `Thread.events(thread)`. */
|
|
6
|
+
export var WorkerEventType;
|
|
7
|
+
(function (WorkerEventType) {
|
|
8
|
+
WorkerEventType["internalError"] = "internalError";
|
|
9
|
+
WorkerEventType["message"] = "message";
|
|
10
|
+
WorkerEventType["termination"] = "termination";
|
|
11
|
+
})(WorkerEventType || (WorkerEventType = {}));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/////////////////////////////
|
|
2
|
+
// Messages sent by master:
|
|
3
|
+
export var MasterMessageType;
|
|
4
|
+
(function (MasterMessageType) {
|
|
5
|
+
MasterMessageType["cancel"] = "cancel";
|
|
6
|
+
MasterMessageType["run"] = "run";
|
|
7
|
+
})(MasterMessageType || (MasterMessageType = {}));
|
|
8
|
+
////////////////////////////
|
|
9
|
+
// Messages sent by worker:
|
|
10
|
+
export var WorkerMessageType;
|
|
11
|
+
(function (WorkerMessageType) {
|
|
12
|
+
WorkerMessageType["error"] = "error";
|
|
13
|
+
WorkerMessageType["init"] = "init";
|
|
14
|
+
WorkerMessageType["result"] = "result";
|
|
15
|
+
WorkerMessageType["running"] = "running";
|
|
16
|
+
WorkerMessageType["uncaughtError"] = "uncaughtError";
|
|
17
|
+
})(WorkerMessageType || (WorkerMessageType = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { expose } from './index';
|
|
3
|
+
export * from './index';
|
|
4
|
+
if (typeof global !== 'undefined') {
|
|
5
|
+
;
|
|
6
|
+
global.expose = expose;
|
|
7
|
+
}
|
|
8
|
+
if (typeof self !== 'undefined') {
|
|
9
|
+
;
|
|
10
|
+
self.expose = expose;
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* eslint-disable import/no-default-export */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/// <reference lib="dom" />
|
|
4
|
+
// tslint:disable no-shadowed-variable
|
|
5
|
+
const isWorkerRuntime = function isWorkerRuntime() {
|
|
6
|
+
const isWindowContext = self !== undefined && typeof Window !== 'undefined' && self instanceof Window;
|
|
7
|
+
return self !== undefined && self['postMessage'] && !isWindowContext ? true : false;
|
|
8
|
+
};
|
|
9
|
+
const postMessageToMaster = function postMessageToMaster(data, transferList) {
|
|
10
|
+
self.postMessage(data, transferList);
|
|
11
|
+
};
|
|
12
|
+
const subscribeToMasterMessages = function subscribeToMasterMessages(onMessage) {
|
|
13
|
+
const messageHandler = (messageEvent) => {
|
|
14
|
+
onMessage(messageEvent.data);
|
|
15
|
+
};
|
|
16
|
+
const unsubscribe = () => {
|
|
17
|
+
self.removeEventListener('message', messageHandler);
|
|
18
|
+
};
|
|
19
|
+
self.addEventListener('message', messageHandler);
|
|
20
|
+
return unsubscribe;
|
|
21
|
+
};
|
|
22
|
+
export default {
|
|
23
|
+
isWorkerRuntime,
|
|
24
|
+
postMessageToMaster,
|
|
25
|
+
subscribeToMasterMessages,
|
|
26
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* eslint-disable import/no-default-export */
|
|
2
|
+
// tslint:disable no-var-requires
|
|
3
|
+
/*
|
|
4
|
+
* This file is only a stub to make './implementation' resolve to the right module.
|
|
5
|
+
*/
|
|
6
|
+
import WebWorkerImplementation from './implementation.browser';
|
|
7
|
+
import TinyWorkerImplementation from './implementation.tiny-worker';
|
|
8
|
+
import WorkerThreadsImplementation from './implementation.worker_threads';
|
|
9
|
+
const runningInNode = typeof process !== 'undefined' && process.arch !== 'browser' && 'pid' in process;
|
|
10
|
+
function selectNodeImplementation() {
|
|
11
|
+
try {
|
|
12
|
+
WorkerThreadsImplementation.testImplementation();
|
|
13
|
+
return WorkerThreadsImplementation;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return TinyWorkerImplementation;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export default runningInNode ? selectNodeImplementation() : WebWorkerImplementation;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* eslint-disable import/no-default-export */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/// <reference lib="dom" />
|
|
4
|
+
// tslint:disable no-shadowed-variable
|
|
5
|
+
if (self === undefined) {
|
|
6
|
+
;
|
|
7
|
+
global.self = global;
|
|
8
|
+
}
|
|
9
|
+
const isWorkerRuntime = function isWorkerRuntime() {
|
|
10
|
+
return self !== undefined && self['postMessage'] ? true : false;
|
|
11
|
+
};
|
|
12
|
+
const postMessageToMaster = function postMessageToMaster(data) {
|
|
13
|
+
// TODO: Warn that Transferables are not supported on first attempt to use feature
|
|
14
|
+
self.postMessage(data);
|
|
15
|
+
};
|
|
16
|
+
let muxingHandlerSetUp = false;
|
|
17
|
+
const messageHandlers = new Set();
|
|
18
|
+
const subscribeToMasterMessages = function subscribeToMasterMessages(onMessage) {
|
|
19
|
+
if (!muxingHandlerSetUp) {
|
|
20
|
+
// We have one multiplexing message handler as tiny-worker's
|
|
21
|
+
// addEventListener() only allows you to set a single message handler
|
|
22
|
+
self.addEventListener('message', ((event) => {
|
|
23
|
+
for (const handler of messageHandlers)
|
|
24
|
+
handler(event.data);
|
|
25
|
+
}));
|
|
26
|
+
muxingHandlerSetUp = true;
|
|
27
|
+
}
|
|
28
|
+
messageHandlers.add(onMessage);
|
|
29
|
+
const unsubscribe = () => messageHandlers.delete(onMessage);
|
|
30
|
+
return unsubscribe;
|
|
31
|
+
};
|
|
32
|
+
export default {
|
|
33
|
+
isWorkerRuntime,
|
|
34
|
+
postMessageToMaster,
|
|
35
|
+
subscribeToMasterMessages,
|
|
36
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import WorkerThreads from '../worker_threads';
|
|
2
|
+
function assertMessagePort(port) {
|
|
3
|
+
if (!port) {
|
|
4
|
+
throw new Error('Invariant violation: MessagePort to parent is not available.');
|
|
5
|
+
}
|
|
6
|
+
return port;
|
|
7
|
+
}
|
|
8
|
+
const isWorkerRuntime = function isWorkerRuntime() {
|
|
9
|
+
return !WorkerThreads().isMainThread;
|
|
10
|
+
};
|
|
11
|
+
const postMessageToMaster = function postMessageToMaster(data, transferList) {
|
|
12
|
+
assertMessagePort(WorkerThreads().parentPort).postMessage(data, transferList);
|
|
13
|
+
};
|
|
14
|
+
const subscribeToMasterMessages = function subscribeToMasterMessages(onMessage) {
|
|
15
|
+
const parentPort = WorkerThreads().parentPort;
|
|
16
|
+
if (!parentPort) {
|
|
17
|
+
throw new Error('Invariant violation: MessagePort to parent is not available.');
|
|
18
|
+
}
|
|
19
|
+
const messageHandler = (message) => {
|
|
20
|
+
onMessage(message);
|
|
21
|
+
};
|
|
22
|
+
const unsubscribe = () => {
|
|
23
|
+
assertMessagePort(parentPort).off('message', messageHandler);
|
|
24
|
+
};
|
|
25
|
+
assertMessagePort(parentPort).on('message', messageHandler);
|
|
26
|
+
return unsubscribe;
|
|
27
|
+
};
|
|
28
|
+
function testImplementation() {
|
|
29
|
+
// Will throw if `worker_threads` are not available
|
|
30
|
+
WorkerThreads();
|
|
31
|
+
}
|
|
32
|
+
export default {
|
|
33
|
+
isWorkerRuntime,
|
|
34
|
+
postMessageToMaster,
|
|
35
|
+
subscribeToMasterMessages,
|
|
36
|
+
testImplementation,
|
|
37
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import isSomeObservable from 'is-observable';
|
|
4
|
+
import { deserialize, serialize } from '../common';
|
|
5
|
+
import { isTransferDescriptor } from '../transferable';
|
|
6
|
+
import { MasterMessageType, WorkerMessageType, } from '../types/messages';
|
|
7
|
+
import Implementation from './implementation';
|
|
8
|
+
export { registerSerializer } from '../common';
|
|
9
|
+
export { Transfer } from '../transferable';
|
|
10
|
+
/** Returns `true` if this code is currently running in a worker. */
|
|
11
|
+
export const isWorkerRuntime = Implementation.isWorkerRuntime;
|
|
12
|
+
let exposeCalled = false;
|
|
13
|
+
const activeSubscriptions = new Map();
|
|
14
|
+
const isMasterJobCancelMessage = (thing) => thing && thing.type === MasterMessageType.cancel;
|
|
15
|
+
const isMasterJobRunMessage = (thing) => thing && thing.type === MasterMessageType.run;
|
|
16
|
+
/**
|
|
17
|
+
* There are issues with `is-observable` not recognizing zen-observable's instances.
|
|
18
|
+
* We are using `observable-fns`, but it's based on zen-observable, too.
|
|
19
|
+
*/
|
|
20
|
+
const isObservable = (thing) => isSomeObservable(thing) || isZenObservable(thing);
|
|
21
|
+
function isZenObservable(thing) {
|
|
22
|
+
return thing && typeof thing === 'object' && typeof thing.subscribe === 'function';
|
|
23
|
+
}
|
|
24
|
+
function deconstructTransfer(thing) {
|
|
25
|
+
return isTransferDescriptor(thing) ? { payload: thing.send, transferables: thing.transferables } : { payload: thing, transferables: undefined };
|
|
26
|
+
}
|
|
27
|
+
function postFunctionInitMessage() {
|
|
28
|
+
const initMessage = {
|
|
29
|
+
exposed: {
|
|
30
|
+
type: 'function',
|
|
31
|
+
},
|
|
32
|
+
type: WorkerMessageType.init,
|
|
33
|
+
};
|
|
34
|
+
Implementation.postMessageToMaster(initMessage);
|
|
35
|
+
}
|
|
36
|
+
function postModuleInitMessage(methodNames) {
|
|
37
|
+
const initMessage = {
|
|
38
|
+
exposed: {
|
|
39
|
+
methods: methodNames,
|
|
40
|
+
type: 'module',
|
|
41
|
+
},
|
|
42
|
+
type: WorkerMessageType.init,
|
|
43
|
+
};
|
|
44
|
+
Implementation.postMessageToMaster(initMessage);
|
|
45
|
+
}
|
|
46
|
+
function postJobErrorMessage(uid, rawError) {
|
|
47
|
+
const { payload: error, transferables } = deconstructTransfer(rawError);
|
|
48
|
+
const errorMessage = {
|
|
49
|
+
error: serialize(error),
|
|
50
|
+
type: WorkerMessageType.error,
|
|
51
|
+
uid,
|
|
52
|
+
};
|
|
53
|
+
Implementation.postMessageToMaster(errorMessage, transferables);
|
|
54
|
+
}
|
|
55
|
+
function postJobResultMessage(uid, completed, resultValue) {
|
|
56
|
+
const { payload, transferables } = deconstructTransfer(resultValue);
|
|
57
|
+
const resultMessage = {
|
|
58
|
+
complete: completed ? true : undefined,
|
|
59
|
+
payload,
|
|
60
|
+
type: WorkerMessageType.result,
|
|
61
|
+
uid,
|
|
62
|
+
};
|
|
63
|
+
Implementation.postMessageToMaster(resultMessage, transferables);
|
|
64
|
+
}
|
|
65
|
+
function postJobStartMessage(uid, resultType) {
|
|
66
|
+
const startMessage = {
|
|
67
|
+
resultType,
|
|
68
|
+
type: WorkerMessageType.running,
|
|
69
|
+
uid,
|
|
70
|
+
};
|
|
71
|
+
Implementation.postMessageToMaster(startMessage);
|
|
72
|
+
}
|
|
73
|
+
function postUncaughtErrorMessage(error) {
|
|
74
|
+
try {
|
|
75
|
+
const errorMessage = {
|
|
76
|
+
error: serialize(error),
|
|
77
|
+
type: WorkerMessageType.uncaughtError,
|
|
78
|
+
};
|
|
79
|
+
Implementation.postMessageToMaster(errorMessage);
|
|
80
|
+
}
|
|
81
|
+
catch (subError) {
|
|
82
|
+
// tslint:disable-next-line no-console
|
|
83
|
+
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);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function runFunction(jobUID, fn, args) {
|
|
87
|
+
let syncResult;
|
|
88
|
+
try {
|
|
89
|
+
syncResult = fn(...args);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return postJobErrorMessage(jobUID, error);
|
|
93
|
+
}
|
|
94
|
+
const resultType = isObservable(syncResult) ? 'observable' : 'promise';
|
|
95
|
+
postJobStartMessage(jobUID, resultType);
|
|
96
|
+
if (isObservable(syncResult)) {
|
|
97
|
+
const subscription = syncResult.subscribe((value) => postJobResultMessage(jobUID, false, serialize(value)), (error) => {
|
|
98
|
+
postJobErrorMessage(jobUID, serialize(error));
|
|
99
|
+
activeSubscriptions.delete(jobUID);
|
|
100
|
+
}, () => {
|
|
101
|
+
postJobResultMessage(jobUID, true);
|
|
102
|
+
activeSubscriptions.delete(jobUID);
|
|
103
|
+
});
|
|
104
|
+
activeSubscriptions.set(jobUID, subscription);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
try {
|
|
108
|
+
const result = await syncResult;
|
|
109
|
+
postJobResultMessage(jobUID, true, serialize(result));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
postJobErrorMessage(jobUID, serialize(error));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Expose a function or a module (an object whose values are functions)
|
|
118
|
+
* to the main thread. Must be called exactly once in every worker thread
|
|
119
|
+
* to signal its API to the main thread.
|
|
120
|
+
*
|
|
121
|
+
* @param exposed Function or object whose values are functions
|
|
122
|
+
*/
|
|
123
|
+
export function expose(exposed) {
|
|
124
|
+
if (!Implementation.isWorkerRuntime()) {
|
|
125
|
+
throw new Error('expose() called in the master thread.');
|
|
126
|
+
}
|
|
127
|
+
if (exposeCalled) {
|
|
128
|
+
throw new Error('expose() called more than once. This is not possible. Pass an object to expose() if you want to expose multiple functions.');
|
|
129
|
+
}
|
|
130
|
+
exposeCalled = true;
|
|
131
|
+
if (typeof exposed === 'function') {
|
|
132
|
+
Implementation.subscribeToMasterMessages((messageData) => {
|
|
133
|
+
if (isMasterJobRunMessage(messageData) && !messageData.method) {
|
|
134
|
+
runFunction(messageData.uid, exposed, messageData.args.map(deserialize));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
postFunctionInitMessage();
|
|
138
|
+
}
|
|
139
|
+
else if (typeof exposed === 'object' && exposed) {
|
|
140
|
+
Implementation.subscribeToMasterMessages((messageData) => {
|
|
141
|
+
if (isMasterJobRunMessage(messageData) && messageData.method) {
|
|
142
|
+
runFunction(messageData.uid, exposed[messageData.method], messageData.args.map(deserialize));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const methodNames = Object.keys(exposed).filter((key) => typeof exposed[key] === 'function');
|
|
146
|
+
postModuleInitMessage(methodNames);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
throw new Error(`Invalid argument passed to expose(). Expected a function or an object, got: ${exposed}`);
|
|
150
|
+
}
|
|
151
|
+
Implementation.subscribeToMasterMessages((messageData) => {
|
|
152
|
+
if (isMasterJobCancelMessage(messageData)) {
|
|
153
|
+
const jobUID = messageData.uid;
|
|
154
|
+
const subscription = activeSubscriptions.get(jobUID);
|
|
155
|
+
if (subscription) {
|
|
156
|
+
subscription.unsubscribe();
|
|
157
|
+
activeSubscriptions.delete(jobUID);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (typeof self !== 'undefined' && typeof self.addEventListener === 'function' && Implementation.isWorkerRuntime()) {
|
|
163
|
+
self.addEventListener('error', (event) => {
|
|
164
|
+
// Post with some delay, so the master had some time to subscribe to messages
|
|
165
|
+
setTimeout(() => postUncaughtErrorMessage(event.error || event), 250);
|
|
166
|
+
});
|
|
167
|
+
self.addEventListener('unhandledrejection', (event) => {
|
|
168
|
+
const error = event.reason;
|
|
169
|
+
if (error && typeof error.message === 'string') {
|
|
170
|
+
// Post with some delay, so the master had some time to subscribe to messages
|
|
171
|
+
setTimeout(() => postUncaughtErrorMessage(error), 250);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (typeof process !== 'undefined' && typeof process.on === 'function' && Implementation.isWorkerRuntime()) {
|
|
176
|
+
process.on('uncaughtException', (error) => {
|
|
177
|
+
// Post with some delay, so the master had some time to subscribe to messages
|
|
178
|
+
setTimeout(() => postUncaughtErrorMessage(error), 250);
|
|
179
|
+
});
|
|
180
|
+
process.on('unhandledRejection', (error) => {
|
|
181
|
+
if (error && typeof error.message === 'string') {
|
|
182
|
+
// Post with some delay, so the master had some time to subscribe to messages
|
|
183
|
+
setTimeout(() => postUncaughtErrorMessage(error), 250);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable import/no-default-export */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
// Webpack hack
|
|
4
|
+
// tslint:disable no-eval
|
|
5
|
+
let implementation;
|
|
6
|
+
function selectImplementation() {
|
|
7
|
+
return typeof __non_webpack_require__ === 'function' ? __non_webpack_require__('worker_threads') : eval('require')('worker_threads');
|
|
8
|
+
}
|
|
9
|
+
export default function getImplementation() {
|
|
10
|
+
if (!implementation) {
|
|
11
|
+
implementation = selectImplementation();
|
|
12
|
+
}
|
|
13
|
+
return implementation;
|
|
14
|
+
}
|