@xylabs/threads 4.7.0-rc.1 → 4.7.1
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/dist/{types/common.d.ts → common.d.ts} +1 -5
- package/dist/common.js +16 -0
- package/dist/esm/common.js +16 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/master/get-bundle-url.browser.js +25 -0
- package/dist/esm/master/implementation.browser.js +65 -0
- package/dist/esm/master/implementation.js +43 -0
- package/dist/esm/master/implementation.node.js +205 -0
- package/dist/esm/master/index.js +14 -0
- package/dist/esm/master/invocation-proxy.js +121 -0
- package/dist/esm/master/pool-types.js +14 -0
- package/dist/esm/master/pool.js +262 -0
- package/dist/esm/master/register.js +11 -0
- package/dist/esm/master/spawn.js +114 -0
- package/dist/esm/master/thread.js +18 -0
- package/dist/esm/observable-promise.js +132 -0
- package/dist/esm/observable.js +33 -0
- package/dist/esm/ponyfills.js +20 -0
- package/dist/esm/promise.js +23 -0
- package/dist/esm/serializers.js +41 -0
- package/dist/esm/symbols.js +8 -0
- package/dist/esm/transferable.js +25 -0
- package/dist/esm/types/master.js +9 -0
- package/dist/esm/types/messages.js +16 -0
- package/dist/esm/types/worker.js +2 -0
- package/dist/esm/worker/bundle-entry.js +26 -0
- package/dist/esm/worker/implementation.browser.js +24 -0
- package/dist/esm/worker/implementation.js +19 -0
- package/dist/esm/worker/implementation.tiny-worker.js +37 -0
- package/dist/esm/worker/implementation.worker_threads.js +41 -0
- package/dist/esm/worker/index.js +174 -0
- package/dist/esm/worker_threads.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +26 -0
- package/dist/{types/master → master}/get-bundle-url.browser.d.ts +0 -1
- package/dist/master/get-bundle-url.browser.js +25 -0
- package/dist/{types/master → master}/implementation.browser.d.ts +1 -2
- package/dist/master/implementation.browser.js +65 -0
- package/dist/{types/master → master}/implementation.d.ts +1 -4
- package/dist/master/implementation.js +43 -0
- package/dist/{types/master → master}/implementation.node.d.ts +1 -2
- package/dist/master/implementation.node.js +205 -0
- package/dist/master/index.d.ts +10 -0
- package/dist/master/index.js +14 -0
- package/dist/{types/master → master}/invocation-proxy.d.ts +1 -2
- package/dist/master/invocation-proxy.js +121 -0
- package/dist/{types/master → master}/pool-types.d.ts +1 -16
- package/dist/master/pool-types.js +14 -0
- package/dist/master/pool.d.ts +50 -0
- package/dist/master/pool.js +262 -0
- package/dist/master/register.d.ts +1 -0
- package/dist/master/register.js +11 -0
- package/dist/{types/master → master}/spawn.d.ts +2 -12
- package/dist/master/spawn.js +114 -0
- package/dist/master/thread.d.ts +8 -0
- package/dist/master/thread.js +18 -0
- package/dist/{types/observable-promise.d.ts → observable-promise.d.ts} +0 -14
- package/dist/observable-promise.js +132 -0
- package/dist/observable.d.ts +11 -0
- package/dist/observable.js +33 -0
- package/dist/{types/ponyfills.d.ts → ponyfills.d.ts} +0 -1
- package/dist/ponyfills.js +20 -0
- package/dist/promise.d.ts +1 -0
- package/dist/promise.js +23 -0
- package/dist/{types/serializers.d.ts → serializers.d.ts} +0 -1
- package/dist/serializers.js +41 -0
- package/dist/{types/symbols.d.ts → symbols.d.ts} +0 -1
- package/dist/symbols.js +8 -0
- package/dist/transferable.d.ts +9 -0
- package/dist/transferable.js +25 -0
- package/dist/types/{types/master.d.ts → master.d.ts} +3 -17
- package/dist/types/master.js +9 -0
- package/dist/types/{types/messages.d.ts → messages.d.ts} +0 -1
- package/dist/types/messages.js +16 -0
- package/dist/types/{types/worker.d.ts → worker.d.ts} +0 -1
- package/dist/types/worker.js +2 -0
- package/dist/worker/bundle-entry.d.ts +1 -0
- package/dist/worker/bundle-entry.js +26 -0
- package/dist/worker/implementation.browser.d.ts +6 -0
- package/dist/worker/implementation.browser.js +24 -0
- package/dist/worker/implementation.d.ts +3 -0
- package/dist/worker/implementation.js +19 -0
- package/dist/worker/implementation.tiny-worker.d.ts +6 -0
- package/dist/worker/implementation.tiny-worker.js +37 -0
- package/dist/worker/implementation.worker_threads.d.ts +8 -0
- package/dist/worker/implementation.worker_threads.js +41 -0
- package/dist/worker/index.d.ts +5 -0
- package/dist/worker/index.js +174 -0
- package/dist/worker_threads.d.ts +8 -0
- package/dist/worker_threads.js +13 -0
- package/eslint.config.mjs +35 -0
- package/index.mjs +10 -0
- package/observable.d.ts +2 -0
- package/observable.js +2 -0
- package/observable.mjs +4 -0
- package/package.json +93 -53
- package/register.d.ts +2 -0
- package/register.js +2 -0
- package/register.mjs +1 -0
- package/rollup.config.js +16 -0
- package/src/common.ts +19 -0
- package/src/index.ts +10 -0
- package/src/master/get-bundle-url.browser.ts +31 -0
- package/src/master/implementation.browser.ts +82 -0
- package/src/master/implementation.node.ts +285 -0
- package/src/master/implementation.ts +21 -0
- package/src/master/index.ts +19 -0
- package/src/master/invocation-proxy.ts +151 -0
- package/src/master/pool-types.ts +83 -0
- package/src/master/pool.ts +399 -0
- package/src/master/register.ts +10 -0
- package/src/master/spawn.ts +172 -0
- package/src/master/thread.ts +29 -0
- package/src/observable-promise.ts +183 -0
- package/src/observable.ts +44 -0
- package/src/ponyfills.ts +31 -0
- package/src/promise.ts +26 -0
- package/src/serializers.ts +68 -0
- package/src/symbols.ts +5 -0
- package/{dist/types/transferable.d.ts → src/transferable.ts} +33 -8
- package/src/types/master.ts +132 -0
- package/src/types/messages.ts +72 -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 +55 -0
- package/src/worker/implementation.ts +23 -0
- package/src/worker/implementation.worker_threads.ts +50 -0
- package/src/worker/index.ts +230 -0
- package/src/worker_threads.ts +27 -0
- package/test/lib/index.ts +1 -0
- package/test/lib/serialization.ts +38 -0
- package/test/observable-promise.test.ts +205 -0
- package/test/observable.test.ts +87 -0
- package/test/pool.test.ts +183 -0
- package/test/serialization.test.ts +23 -0
- package/test/spawn.chromium.mocha.ts +53 -0
- package/test/spawn.test.ts +87 -0
- package/test/streaming.test.ts +29 -0
- package/test/transferables.test.ts +71 -0
- package/test/workers/arraybuffer-xor.ts +10 -0
- package/test/workers/count-to-five.ts +12 -0
- package/test/workers/counter.ts +19 -0
- package/test/workers/faulty-function.ts +5 -0
- package/test/workers/hello-world.ts +5 -0
- package/test/workers/increment.ts +8 -0
- package/test/workers/minmax.ts +25 -0
- package/test/workers/serialization.ts +13 -0
- package/test/workers/top-level-throw.ts +1 -0
- package/test-tooling/rollup/app.js +21 -0
- package/test-tooling/rollup/rollup.config.ts +14 -0
- package/test-tooling/rollup/worker.js +7 -0
- package/test-tooling/tsconfig/minimal.ts +12 -0
- package/test-tooling/webpack/addition-worker.ts +9 -0
- package/test-tooling/webpack/app-with-inlined-worker.ts +28 -0
- package/test-tooling/webpack/app.ts +61 -0
- package/test-tooling/webpack/pool-worker.ts +5 -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 +29 -0
- package/test-tooling/webpack/webpack.web.config.js +28 -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 +2 -0
- package/worker.mjs +6 -0
- package/dist/browser/master/implementation.browser.mjs +0 -89
- package/dist/browser/master/implementation.browser.mjs.map +0 -1
- package/dist/browser/worker/worker.browser.mjs +0 -291
- package/dist/browser/worker/worker.browser.mjs.map +0 -1
- package/dist/neutral/index.mjs +0 -1022
- package/dist/neutral/index.mjs.map +0 -1
- package/dist/neutral/master/implementation.mjs +0 -264
- package/dist/neutral/master/implementation.mjs.map +0 -1
- package/dist/neutral/master/index.mjs +0 -988
- package/dist/neutral/master/index.mjs.map +0 -1
- package/dist/neutral/master/pool.mjs +0 -579
- package/dist/neutral/master/pool.mjs.map +0 -1
- package/dist/neutral/master/register.mjs +0 -272
- package/dist/neutral/master/register.mjs.map +0 -1
- package/dist/neutral/master/spawn.mjs +0 -412
- package/dist/neutral/master/spawn.mjs.map +0 -1
- package/dist/neutral/master/thread.mjs +0 -29
- package/dist/neutral/master/thread.mjs.map +0 -1
- package/dist/neutral/observable-promise.mjs +0 -132
- package/dist/neutral/observable-promise.mjs.map +0 -1
- package/dist/neutral/observable.mjs +0 -31
- package/dist/neutral/observable.mjs.map +0 -1
- package/dist/node/master/implementation.node.mjs +0 -154
- package/dist/node/master/implementation.node.mjs.map +0 -1
- package/dist/node/worker/worker.node.mjs +0 -304
- package/dist/node/worker/worker.node.mjs.map +0 -1
- package/dist/types/common.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -9
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/master/get-bundle-url.browser.d.ts.map +0 -1
- package/dist/types/master/implementation.browser.d.ts.map +0 -1
- package/dist/types/master/implementation.d.ts.map +0 -1
- package/dist/types/master/implementation.node.d.ts.map +0 -1
- package/dist/types/master/index.d.ts +0 -13
- package/dist/types/master/index.d.ts.map +0 -1
- package/dist/types/master/invocation-proxy.d.ts.map +0 -1
- package/dist/types/master/pool-types.d.ts.map +0 -1
- package/dist/types/master/pool.d.ts +0 -93
- package/dist/types/master/pool.d.ts.map +0 -1
- package/dist/types/master/register.d.ts +0 -2
- package/dist/types/master/register.d.ts.map +0 -1
- package/dist/types/master/spawn.d.ts.map +0 -1
- package/dist/types/master/thread.d.ts +0 -13
- package/dist/types/master/thread.d.ts.map +0 -1
- package/dist/types/observable-promise.d.ts.map +0 -1
- package/dist/types/observable.d.ts +0 -21
- package/dist/types/observable.d.ts.map +0 -1
- package/dist/types/ponyfills.d.ts.map +0 -1
- package/dist/types/promise.d.ts +0 -6
- package/dist/types/promise.d.ts.map +0 -1
- package/dist/types/serializers.d.ts.map +0 -1
- package/dist/types/symbols.d.ts.map +0 -1
- package/dist/types/transferable.d.ts.map +0 -1
- package/dist/types/types/master.d.ts.map +0 -1
- package/dist/types/types/messages.d.ts.map +0 -1
- package/dist/types/types/worker.d.ts.map +0 -1
- package/dist/types/worker/WorkerGlobalScope.d.ts +0 -6
- package/dist/types/worker/WorkerGlobalScope.d.ts.map +0 -1
- package/dist/types/worker/expose.d.ts +0 -4
- package/dist/types/worker/expose.d.ts.map +0 -1
- package/dist/types/worker/worker.browser.d.ts +0 -14
- package/dist/types/worker/worker.browser.d.ts.map +0 -1
- package/dist/types/worker/worker.node.d.ts +0 -25
- package/dist/types/worker/worker.node.d.ts.map +0 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* eslint-disable import-x/no-internal-modules */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
4
|
+
import DebugLogger from 'debug'
|
|
5
|
+
import { Observable } from 'observable-fns'
|
|
6
|
+
|
|
7
|
+
import { deserialize } from '../common'
|
|
8
|
+
import { createPromiseWithResolver } from '../promise'
|
|
9
|
+
import {
|
|
10
|
+
$errors, $events, $terminate, $worker,
|
|
11
|
+
} from '../symbols'
|
|
12
|
+
import type {
|
|
13
|
+
FunctionThread,
|
|
14
|
+
ModuleThread,
|
|
15
|
+
PrivateThreadProps,
|
|
16
|
+
StripAsync,
|
|
17
|
+
Worker as WorkerType,
|
|
18
|
+
WorkerEvent,
|
|
19
|
+
WorkerInternalErrorEvent,
|
|
20
|
+
WorkerMessageEvent,
|
|
21
|
+
WorkerTerminationEvent,
|
|
22
|
+
} from '../types/master'
|
|
23
|
+
import { WorkerEventType } from '../types/master'
|
|
24
|
+
import type { WorkerInitMessage, WorkerUncaughtErrorMessage } from '../types/messages'
|
|
25
|
+
import type { WorkerFunction, WorkerModule } from '../types/worker'
|
|
26
|
+
import { createProxyFunction, createProxyModule } from './invocation-proxy'
|
|
27
|
+
|
|
28
|
+
type ArbitraryWorkerInterface = WorkerFunction & WorkerModule<string> & { somekeythatisneverusedinproductioncode123: 'magicmarker123' }
|
|
29
|
+
type ArbitraryThreadType = FunctionThread<any, any> & ModuleThread<any>
|
|
30
|
+
|
|
31
|
+
export type ExposedToThreadType<Exposed extends WorkerFunction | WorkerModule<any>> =
|
|
32
|
+
Exposed extends ArbitraryWorkerInterface ? ArbitraryThreadType
|
|
33
|
+
: Exposed extends WorkerFunction ? FunctionThread<Parameters<Exposed>, StripAsync<ReturnType<Exposed>>>
|
|
34
|
+
: Exposed extends WorkerModule<any> ? ModuleThread<Exposed>
|
|
35
|
+
: never
|
|
36
|
+
|
|
37
|
+
const debugMessages = DebugLogger('threads:master:messages')
|
|
38
|
+
const debugSpawn = DebugLogger('threads:master:spawn')
|
|
39
|
+
const debugThreadUtils = DebugLogger('threads:master:thread-utils')
|
|
40
|
+
|
|
41
|
+
const isInitMessage = (data: any): data is WorkerInitMessage => data && data.type === ('init' as const)
|
|
42
|
+
const isUncaughtErrorMessage = (data: any): data is WorkerUncaughtErrorMessage => data && data.type === ('uncaughtError' as const)
|
|
43
|
+
|
|
44
|
+
const initMessageTimeout
|
|
45
|
+
= typeof process !== 'undefined' && process.env !== undefined && process.env.THREADS_WORKER_INIT_TIMEOUT
|
|
46
|
+
? Number.parseInt(process.env.THREADS_WORKER_INIT_TIMEOUT, 10)
|
|
47
|
+
: 10_000
|
|
48
|
+
|
|
49
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutInMs: number, errorMessage: string): Promise<T> {
|
|
50
|
+
let timeoutHandle: any
|
|
51
|
+
|
|
52
|
+
const timeout = new Promise<never>((resolve, reject) => {
|
|
53
|
+
timeoutHandle = setTimeout(() => reject(new Error(errorMessage)), timeoutInMs)
|
|
54
|
+
})
|
|
55
|
+
const result = await Promise.race([promise, timeout])
|
|
56
|
+
|
|
57
|
+
clearTimeout(timeoutHandle)
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function receiveInitMessage(worker: WorkerType): Promise<WorkerInitMessage> {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const messageHandler = ((event: MessageEvent) => {
|
|
64
|
+
debugMessages('Message from worker before finishing initialization:', event.data)
|
|
65
|
+
if (isInitMessage(event.data)) {
|
|
66
|
+
worker.removeEventListener('message', messageHandler)
|
|
67
|
+
resolve(event.data)
|
|
68
|
+
} else if (isUncaughtErrorMessage(event.data)) {
|
|
69
|
+
worker.removeEventListener('message', messageHandler)
|
|
70
|
+
reject(deserialize(event.data.error))
|
|
71
|
+
}
|
|
72
|
+
}) as EventListener
|
|
73
|
+
worker.addEventListener('message', messageHandler)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createEventObservable(worker: WorkerType, workerTermination: Promise<any>): Observable<WorkerEvent> {
|
|
78
|
+
return new Observable<WorkerEvent>((observer) => {
|
|
79
|
+
const messageHandler = ((messageEvent: MessageEvent) => {
|
|
80
|
+
const workerEvent: WorkerMessageEvent<any> = {
|
|
81
|
+
data: messageEvent.data,
|
|
82
|
+
type: WorkerEventType.message,
|
|
83
|
+
}
|
|
84
|
+
observer.next(workerEvent)
|
|
85
|
+
}) as EventListener
|
|
86
|
+
const rejectionHandler = ((errorEvent: PromiseRejectionEvent) => {
|
|
87
|
+
debugThreadUtils('Unhandled promise rejection event in thread:', errorEvent)
|
|
88
|
+
const workerEvent: WorkerInternalErrorEvent = {
|
|
89
|
+
error: new Error(errorEvent.reason),
|
|
90
|
+
type: WorkerEventType.internalError,
|
|
91
|
+
}
|
|
92
|
+
observer.next(workerEvent)
|
|
93
|
+
}) as EventListener
|
|
94
|
+
worker.addEventListener('message', messageHandler)
|
|
95
|
+
worker.addEventListener('unhandledrejection', rejectionHandler)
|
|
96
|
+
|
|
97
|
+
workerTermination.then(() => {
|
|
98
|
+
const terminationEvent: WorkerTerminationEvent = { type: WorkerEventType.termination }
|
|
99
|
+
worker.removeEventListener('message', messageHandler)
|
|
100
|
+
worker.removeEventListener('unhandledrejection', rejectionHandler)
|
|
101
|
+
observer.next(terminationEvent)
|
|
102
|
+
observer.complete()
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createTerminator(worker: WorkerType): { terminate: () => Promise<void>; termination: Promise<void> } {
|
|
108
|
+
const [termination, resolver] = createPromiseWithResolver<void>()
|
|
109
|
+
const terminate = async () => {
|
|
110
|
+
debugThreadUtils('Terminating worker')
|
|
111
|
+
// Newer versions of worker_threads workers return a promise
|
|
112
|
+
await worker.terminate()
|
|
113
|
+
resolver()
|
|
114
|
+
}
|
|
115
|
+
return { terminate, termination }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function setPrivateThreadProps<T>(
|
|
119
|
+
raw: T,
|
|
120
|
+
worker: WorkerType,
|
|
121
|
+
workerEvents: Observable<WorkerEvent>,
|
|
122
|
+
terminate: () => Promise<void>,
|
|
123
|
+
): T & PrivateThreadProps {
|
|
124
|
+
const workerErrors = workerEvents
|
|
125
|
+
.filter(event => event.type === WorkerEventType.internalError)
|
|
126
|
+
.map(errorEvent => (errorEvent as WorkerInternalErrorEvent).error)
|
|
127
|
+
|
|
128
|
+
return Object.assign(raw as any, {
|
|
129
|
+
[$errors]: workerErrors,
|
|
130
|
+
[$events]: workerEvents,
|
|
131
|
+
[$terminate]: terminate,
|
|
132
|
+
[$worker]: worker,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Spawn a new thread. Takes a fresh worker instance, wraps it in a thin
|
|
138
|
+
* abstraction layer to provide the transparent API and verifies that
|
|
139
|
+
* the worker has initialized successfully.
|
|
140
|
+
*
|
|
141
|
+
* @param worker Instance of `Worker`. Either a web worker, `worker_threads` worker or `tiny-worker` worker.
|
|
142
|
+
* @param [options]
|
|
143
|
+
* @param [options.timeout] Init message timeout. Default: 10000 or set by environment variable.
|
|
144
|
+
*/
|
|
145
|
+
export async function spawn<Exposed extends WorkerFunction | WorkerModule<any> = ArbitraryWorkerInterface>(
|
|
146
|
+
worker: WorkerType,
|
|
147
|
+
options?: { timeout?: number },
|
|
148
|
+
): Promise<ExposedToThreadType<Exposed>> {
|
|
149
|
+
debugSpawn('Initializing new thread')
|
|
150
|
+
|
|
151
|
+
const timeout = options && options.timeout ? options.timeout : initMessageTimeout
|
|
152
|
+
const initMessage = await withTimeout(
|
|
153
|
+
receiveInitMessage(worker),
|
|
154
|
+
timeout,
|
|
155
|
+
`Timeout: Did not receive an init message from worker after ${timeout}ms. Make sure the worker calls expose().`,
|
|
156
|
+
)
|
|
157
|
+
const exposed = initMessage.exposed
|
|
158
|
+
|
|
159
|
+
const { termination, terminate } = createTerminator(worker)
|
|
160
|
+
const events = createEventObservable(worker, termination)
|
|
161
|
+
|
|
162
|
+
if (exposed.type === 'function') {
|
|
163
|
+
const proxy = createProxyFunction(worker)
|
|
164
|
+
return setPrivateThreadProps(proxy, worker, events, terminate) as ExposedToThreadType<Exposed>
|
|
165
|
+
} else if (exposed.type === 'module') {
|
|
166
|
+
const proxy = createProxyModule(worker, exposed.methods)
|
|
167
|
+
return setPrivateThreadProps(proxy, worker, events, terminate) as ExposedToThreadType<Exposed>
|
|
168
|
+
} else {
|
|
169
|
+
const type = (exposed as WorkerInitMessage['exposed']).type
|
|
170
|
+
throw new Error(`Worker init message states unexpected type of expose(): ${type}`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* eslint-disable import-x/no-internal-modules */
|
|
2
|
+
import type { Observable } from 'observable-fns'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
$errors, $events, $terminate,
|
|
6
|
+
} from '../symbols'
|
|
7
|
+
import type { Thread as ThreadType, WorkerEvent } from '../types/master'
|
|
8
|
+
|
|
9
|
+
function fail(message: string): never {
|
|
10
|
+
throw new Error(message)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Thread = ThreadType
|
|
14
|
+
|
|
15
|
+
/** Thread utility functions. Use them to manage or inspect a `spawn()`-ed thread. */
|
|
16
|
+
export const Thread = {
|
|
17
|
+
/** Return an observable that can be used to subscribe to all errors happening in the thread. */
|
|
18
|
+
errors<ThreadT extends ThreadType>(thread: ThreadT): Observable<Error> {
|
|
19
|
+
return thread[$errors] || fail('Error observable not found. Make sure to pass a thread instance as returned by the spawn() promise.')
|
|
20
|
+
},
|
|
21
|
+
/** Return an observable that can be used to subscribe to internal events happening in the thread. Useful for debugging. */
|
|
22
|
+
events<ThreadT extends ThreadType>(thread: ThreadT): Observable<WorkerEvent> {
|
|
23
|
+
return thread[$events] || fail('Events observable not found. Make sure to pass a thread instance as returned by the spawn() promise.')
|
|
24
|
+
},
|
|
25
|
+
/** Terminate a thread. Remember to terminate every thread when you are done using it. */
|
|
26
|
+
terminate<ThreadT extends ThreadType>(thread: ThreadT) {
|
|
27
|
+
return thread[$terminate]()
|
|
28
|
+
},
|
|
29
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-thenable */
|
|
2
|
+
/* eslint-disable @typescript-eslint/member-ordering */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-this-alias */
|
|
7
|
+
/* eslint-disable unicorn/no-this-assignment */
|
|
8
|
+
import type { ObservableLike, SubscriptionObserver } from 'observable-fns'
|
|
9
|
+
import { Observable } from 'observable-fns'
|
|
10
|
+
|
|
11
|
+
type OnFulfilled<T, Result = void> = (value: T) => Result
|
|
12
|
+
type OnRejected<Result = void> = (error: Error) => Result
|
|
13
|
+
|
|
14
|
+
type Initializer<T> = (observer: SubscriptionObserver<T>) => UnsubscribeFn | void
|
|
15
|
+
|
|
16
|
+
type Thenable<T> = { then: (onFulfilled?: (value: T) => any, onRejected?: (error: any) => any) => any }
|
|
17
|
+
|
|
18
|
+
type UnsubscribeFn = () => void
|
|
19
|
+
|
|
20
|
+
const doNothing = () => {}
|
|
21
|
+
const returnInput = <T>(input: T): T => input
|
|
22
|
+
const runDeferred = (fn: () => void) => Promise.resolve().then(fn)
|
|
23
|
+
|
|
24
|
+
function fail(error: Error): never {
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isThenable(thing: any): thing is Thenable<any> {
|
|
29
|
+
return thing && typeof thing.then === 'function'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a hybrid, combining the APIs of an Observable and a Promise.
|
|
34
|
+
*
|
|
35
|
+
* It is used to proxy async process states when we are initially not sure
|
|
36
|
+
* if that async process will yield values once (-> Promise) or multiple
|
|
37
|
+
* times (-> Observable).
|
|
38
|
+
*
|
|
39
|
+
* Note that the observable promise inherits some of the observable's characteristics:
|
|
40
|
+
* The `init` function will be called *once for every time anyone subscribes to it*.
|
|
41
|
+
*
|
|
42
|
+
* If this is undesired, derive a hot observable from it using `makeHot()` and
|
|
43
|
+
* subscribe to that.
|
|
44
|
+
*/
|
|
45
|
+
export class ObservablePromise<T> extends Observable<T> implements Promise<T> {
|
|
46
|
+
readonly [Symbol.toStringTag] = '[object ObservablePromise]'
|
|
47
|
+
private initHasRun = false
|
|
48
|
+
private fulfillmentCallbacks: Array<OnFulfilled<T>> = []
|
|
49
|
+
private rejectionCallbacks: OnRejected[] = []
|
|
50
|
+
|
|
51
|
+
private firstValue: T | undefined
|
|
52
|
+
private firstValueSet = false
|
|
53
|
+
private rejection: Error | undefined
|
|
54
|
+
private state: 'fulfilled' | 'pending' | 'rejected' = 'pending'
|
|
55
|
+
|
|
56
|
+
constructor(init: Initializer<T>) {
|
|
57
|
+
super((originalObserver: SubscriptionObserver<T>) => {
|
|
58
|
+
// tslint:disable-next-line no-this-assignment
|
|
59
|
+
const self = this
|
|
60
|
+
const observer: SubscriptionObserver<T> = {
|
|
61
|
+
...originalObserver,
|
|
62
|
+
complete() {
|
|
63
|
+
originalObserver.complete()
|
|
64
|
+
self.onCompletion()
|
|
65
|
+
},
|
|
66
|
+
error(error: Error) {
|
|
67
|
+
originalObserver.error(error)
|
|
68
|
+
self.onError(error)
|
|
69
|
+
},
|
|
70
|
+
next(value: T) {
|
|
71
|
+
originalObserver.next(value)
|
|
72
|
+
self.onNext(value)
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
this.initHasRun = true
|
|
78
|
+
return init(observer)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
observer.error(error)
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private onNext(value: T) {
|
|
86
|
+
if (!this.firstValueSet) {
|
|
87
|
+
this.firstValue = value
|
|
88
|
+
this.firstValueSet = true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private onError(error: Error) {
|
|
93
|
+
this.state = 'rejected'
|
|
94
|
+
this.rejection = error
|
|
95
|
+
|
|
96
|
+
for (const onRejected of this.rejectionCallbacks) {
|
|
97
|
+
// Promisifying the call to turn errors into unhandled promise rejections
|
|
98
|
+
// instead of them failing sync and cancelling the iteration
|
|
99
|
+
runDeferred(() => onRejected(error))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private onCompletion() {
|
|
104
|
+
this.state = 'fulfilled'
|
|
105
|
+
|
|
106
|
+
for (const onFulfilled of this.fulfillmentCallbacks) {
|
|
107
|
+
// Promisifying the call to turn errors into unhandled promise rejections
|
|
108
|
+
// instead of them failing sync and cancelling the iteration
|
|
109
|
+
runDeferred(() => onFulfilled(this.firstValue as T))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
then<TResult1 = T, TResult2 = never>(
|
|
114
|
+
onFulfilledRaw?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
|
115
|
+
onRejectedRaw?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
|
|
116
|
+
): Promise<TResult1 | TResult2> {
|
|
117
|
+
const onFulfilled: OnFulfilled<T, TResult1> = onFulfilledRaw || (returnInput as any)
|
|
118
|
+
const onRejected = onRejectedRaw || fail
|
|
119
|
+
let onRejectedCalled = false
|
|
120
|
+
|
|
121
|
+
return new Promise<TResult1 | TResult2>((resolve, reject) => {
|
|
122
|
+
const rejectionCallback = (error: Error) => {
|
|
123
|
+
if (onRejectedCalled) return
|
|
124
|
+
onRejectedCalled = true
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
resolve(onRejected(error))
|
|
128
|
+
} catch (anotherError) {
|
|
129
|
+
reject(anotherError)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const fulfillmentCallback = (value: T) => {
|
|
133
|
+
try {
|
|
134
|
+
resolve(onFulfilled(value))
|
|
135
|
+
} catch (error) {
|
|
136
|
+
rejectionCallback(error)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!this.initHasRun) {
|
|
140
|
+
this.subscribe({ error: rejectionCallback })
|
|
141
|
+
}
|
|
142
|
+
if (this.state === 'fulfilled') {
|
|
143
|
+
return resolve(onFulfilled(this.firstValue as T))
|
|
144
|
+
}
|
|
145
|
+
if (this.state === 'rejected') {
|
|
146
|
+
onRejectedCalled = true
|
|
147
|
+
return resolve(onRejected(this.rejection as Error))
|
|
148
|
+
}
|
|
149
|
+
this.fulfillmentCallbacks.push(fulfillmentCallback)
|
|
150
|
+
this.rejectionCallbacks.push(rejectionCallback)
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
catch<Result = never>(onRejected: ((error: Error) => Promise<Result> | Result) | null | undefined) {
|
|
155
|
+
return this.then(undefined, onRejected) as Promise<Result>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
finally(onCompleted?: (() => void) | null | undefined) {
|
|
159
|
+
const handler = onCompleted || doNothing
|
|
160
|
+
return this.then(
|
|
161
|
+
(value: T) => {
|
|
162
|
+
handler()
|
|
163
|
+
return value
|
|
164
|
+
},
|
|
165
|
+
() => handler(),
|
|
166
|
+
) as Promise<T>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static from<T>(thing: Observable<T> | ObservableLike<T> | ArrayLike<T> | Thenable<T>): ObservablePromise<T> {
|
|
170
|
+
return isThenable(thing)
|
|
171
|
+
? new ObservablePromise((observer) => {
|
|
172
|
+
const onFulfilled = (value: T) => {
|
|
173
|
+
observer.next(value)
|
|
174
|
+
observer.complete()
|
|
175
|
+
}
|
|
176
|
+
const onRejected = (error: any) => {
|
|
177
|
+
observer.error(error)
|
|
178
|
+
}
|
|
179
|
+
thing.then(onFulfilled, onRejected)
|
|
180
|
+
})
|
|
181
|
+
: (super.from(thing) as ObservablePromise<T>)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type { ObservableLike, SubscriptionObserver } from 'observable-fns'
|
|
3
|
+
import { Observable } from 'observable-fns'
|
|
4
|
+
|
|
5
|
+
const $observers = Symbol('observers')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Observable subject. Implements the Observable interface, but also exposes
|
|
9
|
+
* the `next()`, `error()`, `complete()` methods to initiate observable
|
|
10
|
+
* updates "from the outside".
|
|
11
|
+
*
|
|
12
|
+
* Use `Observable.from(subject)` to derive an observable that proxies all
|
|
13
|
+
* values, errors and the completion raised on this subject, but does not
|
|
14
|
+
* expose the `next()`, `error()`, `complete()` methods.
|
|
15
|
+
*/
|
|
16
|
+
export class Subject<T> extends Observable<T> implements ObservableLike<T> {
|
|
17
|
+
private [$observers]: Array<SubscriptionObserver<T>>
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super((observer) => {
|
|
21
|
+
this[$observers] = [...(this[$observers] || []), observer]
|
|
22
|
+
const unsubscribe = () => {
|
|
23
|
+
this[$observers] = this[$observers].filter(someObserver => someObserver !== observer)
|
|
24
|
+
}
|
|
25
|
+
return unsubscribe
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
this[$observers] = []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
complete() {
|
|
32
|
+
for (const observer of this[$observers]) observer.complete()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
error(error: any) {
|
|
36
|
+
for (const observer of this[$observers]) observer.error(error)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
next(value: T) {
|
|
40
|
+
for (const observer of this[$observers]) observer.next(value)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { Observable } from 'observable-fns'
|
package/src/ponyfills.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export type SettlementResult<T> =
|
|
3
|
+
| {
|
|
4
|
+
status: 'fulfilled'
|
|
5
|
+
value: T
|
|
6
|
+
}
|
|
7
|
+
| {
|
|
8
|
+
reason: any
|
|
9
|
+
status: 'rejected'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Based on <https://github.com/es-shims/Promise.allSettled/blob/master/implementation.js>
|
|
13
|
+
export function allSettled<T>(values: T[]): Promise<Array<SettlementResult<T>>> {
|
|
14
|
+
return Promise.all(
|
|
15
|
+
values.map((item) => {
|
|
16
|
+
const onFulfill = (value: T) => {
|
|
17
|
+
return { status: 'fulfilled', value } as const
|
|
18
|
+
}
|
|
19
|
+
const onReject = (reason: any) => {
|
|
20
|
+
return { reason, status: 'rejected' } as const
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const itemPromise = Promise.resolve(item)
|
|
24
|
+
try {
|
|
25
|
+
return itemPromise.then(onFulfill, onReject)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return Promise.reject(error)
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
}
|
package/src/promise.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
2
|
+
const doNothing = () => undefined
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new promise and exposes its resolver function.
|
|
6
|
+
* Use with care!
|
|
7
|
+
*/
|
|
8
|
+
export function createPromiseWithResolver<T>(): [Promise<T>, (result: T) => void] {
|
|
9
|
+
let alreadyResolved = false
|
|
10
|
+
let resolvedTo: T
|
|
11
|
+
let resolver: (value: T | PromiseLike<T>) => void = doNothing
|
|
12
|
+
|
|
13
|
+
const promise = new Promise<T>((resolve) => {
|
|
14
|
+
if (alreadyResolved) {
|
|
15
|
+
resolve(resolvedTo)
|
|
16
|
+
} else {
|
|
17
|
+
resolver = resolve
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
const exposedResolver = (value: T) => {
|
|
21
|
+
alreadyResolved = true
|
|
22
|
+
resolvedTo = value
|
|
23
|
+
resolver(resolvedTo)
|
|
24
|
+
}
|
|
25
|
+
return [promise, exposedResolver]
|
|
26
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* eslint-disable import-x/no-internal-modules */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import type { SerializedError } from './types/messages'
|
|
4
|
+
|
|
5
|
+
export interface Serializer<Msg = JsonSerializable, Input = any> {
|
|
6
|
+
deserialize(message: Msg): Input
|
|
7
|
+
serialize(input: Input): Msg
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SerializerImplementation<Msg = JsonSerializable, Input = any> {
|
|
11
|
+
deserialize(message: Msg, defaultDeserialize: (msg: Msg) => Input): Input
|
|
12
|
+
serialize(input: Input, defaultSerialize: (inp: Input) => Msg): Msg
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function extendSerializer<MessageType, InputType = any>(
|
|
16
|
+
extend: Serializer<MessageType, InputType>,
|
|
17
|
+
implementation: SerializerImplementation<MessageType, InputType>,
|
|
18
|
+
): Serializer<MessageType, InputType> {
|
|
19
|
+
const fallbackDeserializer = extend.deserialize.bind(extend)
|
|
20
|
+
const fallbackSerializer = extend.serialize.bind(extend)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
deserialize(message: MessageType): InputType {
|
|
24
|
+
return implementation.deserialize(message, fallbackDeserializer)
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
serialize(input: InputType): MessageType {
|
|
28
|
+
return implementation.serialize(input, fallbackSerializer)
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type JsonSerializablePrimitive = string | number | boolean | null
|
|
34
|
+
|
|
35
|
+
type JsonSerializableObject = {
|
|
36
|
+
[key: string]: JsonSerializablePrimitive | JsonSerializablePrimitive[] | JsonSerializableObject | JsonSerializableObject[] | undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type JsonSerializable = JsonSerializablePrimitive | JsonSerializablePrimitive[] | JsonSerializableObject | JsonSerializableObject[]
|
|
40
|
+
|
|
41
|
+
const DefaultErrorSerializer: Serializer<SerializedError, Error> = {
|
|
42
|
+
deserialize(message: SerializedError): Error {
|
|
43
|
+
return Object.assign(new Error(message.message), {
|
|
44
|
+
name: message.name,
|
|
45
|
+
stack: message.stack,
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
serialize(error: Error): SerializedError {
|
|
49
|
+
return {
|
|
50
|
+
__error_marker: '$$error',
|
|
51
|
+
message: error.message,
|
|
52
|
+
name: error.name,
|
|
53
|
+
stack: error.stack,
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const isSerializedError = (thing: any): thing is SerializedError =>
|
|
59
|
+
thing && typeof thing === 'object' && '__error_marker' in thing && thing.__error_marker === '$$error'
|
|
60
|
+
|
|
61
|
+
export const DefaultSerializer: Serializer<JsonSerializable> = {
|
|
62
|
+
deserialize(message: JsonSerializable): any {
|
|
63
|
+
return isSerializedError(message) ? DefaultErrorSerializer.deserialize(message) : message
|
|
64
|
+
},
|
|
65
|
+
serialize(input: any): JsonSerializable {
|
|
66
|
+
return input instanceof Error ? (DefaultErrorSerializer.serialize(input) as any as JsonSerializable) : input
|
|
67
|
+
},
|
|
68
|
+
}
|
package/src/symbols.ts
ADDED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { $transferable } from './symbols'
|
|
3
|
+
|
|
2
4
|
export interface TransferDescriptor<T = any> {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
[$transferable]: true
|
|
6
|
+
send: T
|
|
7
|
+
transferables: Transferable[]
|
|
6
8
|
}
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
function isTransferable(thing: any): thing is Transferable {
|
|
11
|
+
if (!thing || typeof thing !== 'object') return false
|
|
12
|
+
// Don't check too thoroughly, since the list of transferable things in JS might grow over time
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isTransferDescriptor(thing: any): thing is TransferDescriptor {
|
|
17
|
+
return thing && typeof thing === 'object' && thing[$transferable]
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
/**
|
|
9
21
|
* Mark a transferable object as such, so it will no be serialized and
|
|
10
22
|
* deserialized on messaging with the main thread, but to transfer
|
|
@@ -21,7 +33,8 @@ export declare function isTransferDescriptor(thing: any): thing is TransferDescr
|
|
|
21
33
|
* @param transferable Array buffer, message port or similar.
|
|
22
34
|
* @see <https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast>
|
|
23
35
|
*/
|
|
24
|
-
export
|
|
36
|
+
export function Transfer(transferable: Transferable): TransferDescriptor
|
|
37
|
+
|
|
25
38
|
/**
|
|
26
39
|
* Mark transferable objects within an arbitrary object or array as
|
|
27
40
|
* being a transferable object. They will then not be serialized
|
|
@@ -39,5 +52,17 @@ export declare function Transfer(transferable: Transferable): TransferDescriptor
|
|
|
39
52
|
* @param transferable Array buffer, message port or similar.
|
|
40
53
|
* @see <https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast>
|
|
41
54
|
*/
|
|
42
|
-
export
|
|
43
|
-
|
|
55
|
+
export function Transfer<T>(payload: T, transferables: Transferable[]): TransferDescriptor
|
|
56
|
+
|
|
57
|
+
export function Transfer<T>(payload: T, transferables?: Transferable[]): TransferDescriptor {
|
|
58
|
+
if (!transferables) {
|
|
59
|
+
if (!isTransferable(payload)) throw new Error('Not transferable')
|
|
60
|
+
transferables = [payload]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
[$transferable]: true,
|
|
65
|
+
send: payload,
|
|
66
|
+
transferables,
|
|
67
|
+
}
|
|
68
|
+
}
|