@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.
Files changed (171) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +227 -0
  4. package/dist/common.d.ts +4 -0
  5. package/dist/common.js +18 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.js +27 -0
  8. package/dist/master/get-bundle-url.browser.d.ts +3 -0
  9. package/dist/master/get-bundle-url.browser.js +29 -0
  10. package/dist/master/implementation.browser.d.ts +4 -0
  11. package/dist/master/implementation.browser.js +69 -0
  12. package/dist/master/implementation.d.ts +6 -0
  13. package/dist/master/implementation.js +41 -0
  14. package/dist/master/implementation.node.d.ts +5 -0
  15. package/dist/master/implementation.node.js +255 -0
  16. package/dist/master/index.d.ts +13 -0
  17. package/dist/master/index.js +16 -0
  18. package/dist/master/invocation-proxy.d.ts +3 -0
  19. package/dist/master/invocation-proxy.js +130 -0
  20. package/dist/master/pool-types.d.ts +65 -0
  21. package/dist/master/pool-types.js +15 -0
  22. package/dist/master/pool.d.ts +90 -0
  23. package/dist/master/pool.js +281 -0
  24. package/dist/master/register.d.ts +1 -0
  25. package/dist/master/register.js +12 -0
  26. package/dist/master/spawn.d.ts +20 -0
  27. package/dist/master/spawn.js +130 -0
  28. package/dist/master/thread.d.ts +12 -0
  29. package/dist/master/thread.js +22 -0
  30. package/dist/observable-promise.d.ts +38 -0
  31. package/dist/observable-promise.js +156 -0
  32. package/dist/observable.d.ts +19 -0
  33. package/dist/observable.js +43 -0
  34. package/dist/ponyfills.d.ts +8 -0
  35. package/dist/ponyfills.js +22 -0
  36. package/dist/promise.d.ts +5 -0
  37. package/dist/promise.js +29 -0
  38. package/dist/serializers.d.ts +16 -0
  39. package/dist/serializers.js +41 -0
  40. package/dist/symbols.d.ts +5 -0
  41. package/dist/symbols.js +8 -0
  42. package/dist/transferable.d.ts +42 -0
  43. package/dist/transferable.js +28 -0
  44. package/dist/types/master.d.ts +99 -0
  45. package/dist/types/master.js +14 -0
  46. package/dist/types/messages.d.ts +62 -0
  47. package/dist/types/messages.js +20 -0
  48. package/dist/types/worker.d.ts +11 -0
  49. package/dist/types/worker.js +2 -0
  50. package/dist/worker/bundle-entry.d.ts +1 -0
  51. package/dist/worker/bundle-entry.js +27 -0
  52. package/dist/worker/implementation.browser.d.ts +7 -0
  53. package/dist/worker/implementation.browser.js +28 -0
  54. package/dist/worker/implementation.d.ts +3 -0
  55. package/dist/worker/implementation.js +24 -0
  56. package/dist/worker/implementation.tiny-worker.d.ts +7 -0
  57. package/dist/worker/implementation.tiny-worker.js +38 -0
  58. package/dist/worker/implementation.worker_threads.d.ts +8 -0
  59. package/dist/worker/implementation.worker_threads.js +42 -0
  60. package/dist/worker/index.d.ts +13 -0
  61. package/dist/worker/index.js +195 -0
  62. package/dist/worker_threads.d.ts +8 -0
  63. package/dist/worker_threads.js +17 -0
  64. package/dist-esm/common.js +12 -0
  65. package/dist-esm/index.js +6 -0
  66. package/dist-esm/master/get-bundle-url.browser.js +25 -0
  67. package/dist-esm/master/implementation.browser.js +64 -0
  68. package/dist-esm/master/implementation.js +15 -0
  69. package/dist-esm/master/implementation.node.js +224 -0
  70. package/dist-esm/master/index.js +9 -0
  71. package/dist-esm/master/invocation-proxy.js +122 -0
  72. package/dist-esm/master/pool-types.js +12 -0
  73. package/dist-esm/master/pool.js +273 -0
  74. package/dist-esm/master/register.js +10 -0
  75. package/dist-esm/master/spawn.js +123 -0
  76. package/dist-esm/master/thread.js +19 -0
  77. package/dist-esm/observable-promise.js +152 -0
  78. package/dist-esm/observable.js +38 -0
  79. package/dist-esm/ponyfills.js +18 -0
  80. package/dist-esm/promise.js +25 -0
  81. package/dist-esm/serializers.js +37 -0
  82. package/dist-esm/symbols.js +5 -0
  83. package/dist-esm/transferable.js +23 -0
  84. package/dist-esm/types/master.js +11 -0
  85. package/dist-esm/types/messages.js +17 -0
  86. package/dist-esm/types/worker.js +1 -0
  87. package/dist-esm/worker/bundle-entry.js +11 -0
  88. package/dist-esm/worker/implementation.browser.js +26 -0
  89. package/dist-esm/worker/implementation.js +19 -0
  90. package/dist-esm/worker/implementation.tiny-worker.js +36 -0
  91. package/dist-esm/worker/implementation.worker_threads.js +37 -0
  92. package/dist-esm/worker/index.js +186 -0
  93. package/dist-esm/worker_threads.js +14 -0
  94. package/index.mjs +11 -0
  95. package/observable.d.ts +2 -0
  96. package/observable.js +3 -0
  97. package/observable.mjs +5 -0
  98. package/package.json +141 -0
  99. package/register.d.ts +3 -0
  100. package/register.js +3 -0
  101. package/register.mjs +2 -0
  102. package/rollup.config.js +16 -0
  103. package/src/common.ts +16 -0
  104. package/src/index.ts +8 -0
  105. package/src/master/get-bundle-url.browser.ts +31 -0
  106. package/src/master/implementation.browser.ts +80 -0
  107. package/src/master/implementation.node.ts +284 -0
  108. package/src/master/implementation.ts +21 -0
  109. package/src/master/index.ts +20 -0
  110. package/src/master/invocation-proxy.ts +146 -0
  111. package/src/master/pool-types.ts +83 -0
  112. package/src/master/pool.ts +391 -0
  113. package/src/master/register.ts +10 -0
  114. package/src/master/spawn.ts +172 -0
  115. package/src/master/thread.ts +26 -0
  116. package/src/observable-promise.ts +181 -0
  117. package/src/observable.ts +43 -0
  118. package/src/ponyfills.ts +31 -0
  119. package/src/promise.ts +26 -0
  120. package/src/serializers.ts +67 -0
  121. package/src/symbols.ts +5 -0
  122. package/src/transferable.ts +68 -0
  123. package/src/types/master.ts +130 -0
  124. package/src/types/messages.ts +81 -0
  125. package/src/types/worker.ts +14 -0
  126. package/src/worker/bundle-entry.ts +10 -0
  127. package/src/worker/implementation.browser.ts +40 -0
  128. package/src/worker/implementation.tiny-worker.ts +52 -0
  129. package/src/worker/implementation.ts +23 -0
  130. package/src/worker/implementation.worker_threads.ts +50 -0
  131. package/src/worker/index.ts +228 -0
  132. package/src/worker_threads.ts +28 -0
  133. package/test/lib/serialization.ts +38 -0
  134. package/test/observable-promise.test.ts +189 -0
  135. package/test/observable.test.ts +86 -0
  136. package/test/pool.test.ts +173 -0
  137. package/test/serialization.test.ts +21 -0
  138. package/test/spawn.chromium.mocha.ts +49 -0
  139. package/test/spawn.test.ts +71 -0
  140. package/test/streaming.test.ts +27 -0
  141. package/test/transferables.test.ts +69 -0
  142. package/test/workers/arraybuffer-xor.ts +11 -0
  143. package/test/workers/count-to-five.ts +13 -0
  144. package/test/workers/counter.ts +20 -0
  145. package/test/workers/faulty-function.ts +6 -0
  146. package/test/workers/hello-world.ts +6 -0
  147. package/test/workers/increment.ts +9 -0
  148. package/test/workers/minmax.ts +25 -0
  149. package/test/workers/serialization.ts +12 -0
  150. package/test/workers/top-level-throw.ts +1 -0
  151. package/test-tooling/rollup/app.js +20 -0
  152. package/test-tooling/rollup/rollup.config.ts +15 -0
  153. package/test-tooling/rollup/rollup.test.ts +44 -0
  154. package/test-tooling/rollup/worker.js +7 -0
  155. package/test-tooling/tsconfig/minimal-tsconfig.test.ts +7 -0
  156. package/test-tooling/tsconfig/minimal.ts +10 -0
  157. package/test-tooling/webpack/addition-worker.ts +10 -0
  158. package/test-tooling/webpack/app-with-inlined-worker.ts +29 -0
  159. package/test-tooling/webpack/app.ts +58 -0
  160. package/test-tooling/webpack/pool-worker.ts +6 -0
  161. package/test-tooling/webpack/raw-loader.d.ts +4 -0
  162. package/test-tooling/webpack/webpack.chromium.mocha.ts +21 -0
  163. package/test-tooling/webpack/webpack.node.config.js +38 -0
  164. package/test-tooling/webpack/webpack.test.ts +90 -0
  165. package/test-tooling/webpack/webpack.web.config.js +35 -0
  166. package/types/is-observable.d.ts +7 -0
  167. package/types/tiny-worker.d.ts +4 -0
  168. package/types/webworker.d.ts +9 -0
  169. package/worker.d.ts +2 -0
  170. package/worker.js +3 -0
  171. package/worker.mjs +7 -0
@@ -0,0 +1,130 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /// <reference lib="dom" />
3
+ // tslint:disable max-classes-per-file
4
+
5
+ // Cannot use `compilerOptions.esModuleInterop` and default import syntax
6
+ // See <https://github.com/microsoft/TypeScript/issues/28009>
7
+ import { Observable } from 'observable-fns'
8
+
9
+ import { ObservablePromise } from '../observable-promise'
10
+ import { $errors, $events, $terminate, $worker } from '../symbols'
11
+ import { TransferDescriptor } from '../transferable'
12
+
13
+ interface ObservableLikeSubscription {
14
+ unsubscribe(): any
15
+ }
16
+ interface ObservableLike<T> {
17
+ subscribe(onNext: (value: T) => any, onError?: (error: any) => any, onComplete?: () => any): ObservableLikeSubscription
18
+ subscribe(listeners: { complete?(): any; error?(error: any): any; next?(value: T): any }): ObservableLikeSubscription
19
+ }
20
+
21
+ export type StripAsync<Type> =
22
+ Type extends Promise<infer PromiseBaseType> ? PromiseBaseType
23
+ : Type extends ObservableLike<infer ObservableBaseType> ? ObservableBaseType
24
+ : Type
25
+
26
+ export type StripTransfer<Type> = Type extends TransferDescriptor<infer BaseType> ? BaseType : Type
27
+
28
+ export type ModuleMethods = { [methodName: string]: (...args: any) => any }
29
+
30
+ export type ProxyableArgs<Args extends any[]> =
31
+ Args extends [arg0: infer Arg0, ...rest: infer RestArgs] ? [Arg0 extends Transferable ? Arg0 | TransferDescriptor<Arg0> : Arg0, ...RestArgs] : Args
32
+
33
+ export type ProxyableFunction<Args extends any[], ReturnType> =
34
+ Args extends [] ? () => ObservablePromise<StripTransfer<StripAsync<ReturnType>>>
35
+ : (...args: ProxyableArgs<Args>) => ObservablePromise<StripTransfer<StripAsync<ReturnType>>>
36
+
37
+ export type ModuleProxy<Methods extends ModuleMethods> = {
38
+ [method in keyof Methods]: ProxyableFunction<Parameters<Methods[method]>, ReturnType<Methods[method]>>
39
+ }
40
+
41
+ export interface PrivateThreadProps {
42
+ [$errors]: Observable<Error>
43
+ [$events]: Observable<WorkerEvent>
44
+ [$terminate]: () => Promise<void>
45
+ [$worker]: Worker
46
+ }
47
+
48
+ export type FunctionThread<Args extends any[] = any[], ReturnType = any> = ProxyableFunction<Args, ReturnType> & PrivateThreadProps
49
+ export type ModuleThread<Methods extends ModuleMethods = any> = ModuleProxy<Methods> & PrivateThreadProps
50
+
51
+ // We have those extra interfaces to keep the general non-specific `Thread` type
52
+ // as an interface, so it's displayed concisely in any TypeScript compiler output.
53
+ interface AnyFunctionThread extends PrivateThreadProps {
54
+ (...args: any[]): ObservablePromise<any>
55
+ }
56
+
57
+ // tslint:disable-next-line no-empty-interface
58
+ interface AnyModuleThread extends PrivateThreadProps {
59
+ // Not specifying an index signature here as that would make `ModuleThread` incompatible
60
+ }
61
+
62
+ /** Worker thread. Either a `FunctionThread` or a `ModuleThread`. */
63
+ export type Thread = AnyFunctionThread | AnyModuleThread
64
+
65
+ export type TransferList = Transferable[]
66
+
67
+ /** Worker instance. Either a web worker or a node.js Worker provided by `worker_threads` or `tiny-worker`. */
68
+ export interface Worker extends EventTarget {
69
+ postMessage(value: any, transferList?: TransferList): void
70
+ /** In nodejs 10+ return type is Promise while with tiny-worker and in browser return type is void */
71
+ terminate(callback?: (error?: Error, exitCode?: number) => void): void | Promise<number>
72
+ }
73
+ export interface ThreadsWorkerOptions extends WorkerOptions {
74
+ /** Whether to apply CORS protection workaround. Defaults to true. */
75
+ CORSWorkaround?: boolean
76
+ /** Prefix for the path passed to the Worker constructor. Web worker only. */
77
+ _baseURL?: string
78
+ /** Resource limits passed on to Node worker_threads */
79
+ resourceLimits?: {
80
+ /** The size of a pre-allocated memory range used for generated code. */
81
+ codeRangeSizeMb?: number
82
+ /** The maximum size of the main heap in MB. */
83
+ maxOldGenerationSizeMb?: number
84
+ /** The maximum size of a heap space for recently created objects. */
85
+ maxYoungGenerationSizeMb?: number
86
+ }
87
+ /** Data passed on to node.js worker_threads. */
88
+ workerData?: any
89
+ }
90
+
91
+ /** Worker implementation. Either web worker or a node.js Worker class. */
92
+ export declare class WorkerImplementation extends EventTarget implements Worker {
93
+ constructor(path: string, options?: ThreadsWorkerOptions)
94
+ postMessage(value: any, transferList?: TransferList): void
95
+ terminate(): void | Promise<number>
96
+ }
97
+
98
+ /** Class to spawn workers from a blob or source string. */
99
+ export declare class BlobWorker extends WorkerImplementation {
100
+ constructor(blob: Blob, options?: ThreadsWorkerOptions)
101
+ static fromText(source: string, options?: ThreadsWorkerOptions): WorkerImplementation
102
+ }
103
+
104
+ export interface ImplementationExport {
105
+ blob: typeof BlobWorker
106
+ default: typeof WorkerImplementation
107
+ }
108
+
109
+ /** Event as emitted by worker thread. Subscribe to using `Thread.events(thread)`. */
110
+ export enum WorkerEventType {
111
+ internalError = 'internalError',
112
+ message = 'message',
113
+ termination = 'termination',
114
+ }
115
+
116
+ export interface WorkerInternalErrorEvent {
117
+ error: Error
118
+ type: WorkerEventType.internalError
119
+ }
120
+
121
+ export interface WorkerMessageEvent<Data> {
122
+ data: Data
123
+ type: WorkerEventType.message
124
+ }
125
+
126
+ export interface WorkerTerminationEvent {
127
+ type: WorkerEventType.termination
128
+ }
129
+
130
+ export type WorkerEvent = WorkerInternalErrorEvent | WorkerMessageEvent<any> | WorkerTerminationEvent
@@ -0,0 +1,81 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/member-ordering */
3
+ export interface SerializedError {
4
+ __error_marker: '$$error'
5
+ message: string
6
+ name: string
7
+ stack?: string
8
+ }
9
+
10
+ /////////////////////////////
11
+ // Messages sent by master:
12
+
13
+ export enum MasterMessageType {
14
+ cancel = 'cancel',
15
+ run = 'run',
16
+ }
17
+
18
+ export type MasterJobCancelMessage = {
19
+ type: MasterMessageType.cancel
20
+ uid: number
21
+ }
22
+
23
+ export type MasterJobRunMessage = {
24
+ type: MasterMessageType.run
25
+ uid: number
26
+ method?: string
27
+ args: any[]
28
+ }
29
+
30
+ export type MasterSentMessage = MasterJobCancelMessage | MasterJobRunMessage
31
+
32
+ ////////////////////////////
33
+ // Messages sent by worker:
34
+
35
+ export enum WorkerMessageType {
36
+ error = 'error',
37
+ init = 'init',
38
+ result = 'result',
39
+ running = 'running',
40
+ uncaughtError = 'uncaughtError',
41
+ }
42
+
43
+ export type WorkerUncaughtErrorMessage = {
44
+ type: WorkerMessageType.uncaughtError
45
+ error: {
46
+ message: string
47
+ name: string
48
+ stack?: string
49
+ }
50
+ }
51
+
52
+ export type WorkerInitMessage = {
53
+ type: WorkerMessageType.init
54
+ exposed: { type: 'function' } | { type: 'module'; methods: string[] }
55
+ }
56
+
57
+ export type WorkerJobErrorMessage = {
58
+ type: WorkerMessageType.error
59
+ uid: number
60
+ error: SerializedError
61
+ }
62
+
63
+ export type WorkerJobResultMessage = {
64
+ type: WorkerMessageType.result
65
+ uid: number
66
+ complete?: true
67
+ payload?: any
68
+ }
69
+
70
+ export type WorkerJobStartMessage = {
71
+ type: WorkerMessageType.running
72
+ uid: number
73
+ resultType: 'observable' | 'promise'
74
+ }
75
+
76
+ export type WorkerSentMessage =
77
+ | WorkerInitMessage
78
+ | WorkerJobErrorMessage
79
+ | WorkerJobResultMessage
80
+ | WorkerJobStartMessage
81
+ | WorkerUncaughtErrorMessage
@@ -0,0 +1,14 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ type UnsubscribeFn = () => void
3
+
4
+ export interface AbstractedWorkerAPI {
5
+ isWorkerRuntime(): boolean
6
+ postMessageToMaster(message: any, transferList?: Transferable[]): void
7
+ subscribeToMasterMessages(onMessage: (data: any) => void): UnsubscribeFn
8
+ }
9
+
10
+ export type WorkerFunction = ((...args: any[]) => any) | (() => any)
11
+
12
+ export type WorkerModule<Keys extends string> = {
13
+ [key in Keys]: WorkerFunction
14
+ }
@@ -0,0 +1,10 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { expose } from './index'
3
+ export * from './index'
4
+
5
+ if (typeof global !== 'undefined') {
6
+ ;(global as any).expose = expose
7
+ }
8
+ if (typeof self !== 'undefined') {
9
+ ;(self as any).expose = expose
10
+ }
@@ -0,0 +1,40 @@
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
+
6
+ import { AbstractedWorkerAPI } from '../types/worker'
7
+
8
+ interface WorkerGlobalScope {
9
+ addEventListener(eventName: string, listener: (event: Event) => void): void
10
+ postMessage(message: any, transferables?: any[]): void
11
+ removeEventListener(eventName: string, listener: (event: Event) => void): void
12
+ }
13
+
14
+ declare const self: WorkerGlobalScope
15
+
16
+ const isWorkerRuntime: AbstractedWorkerAPI['isWorkerRuntime'] = function isWorkerRuntime() {
17
+ const isWindowContext = self !== undefined && typeof Window !== 'undefined' && self instanceof Window
18
+ return self !== undefined && self['postMessage'] && !isWindowContext ? true : false
19
+ }
20
+
21
+ const postMessageToMaster: AbstractedWorkerAPI['postMessageToMaster'] = function postMessageToMaster(data, transferList?) {
22
+ self.postMessage(data, transferList)
23
+ }
24
+
25
+ const subscribeToMasterMessages: AbstractedWorkerAPI['subscribeToMasterMessages'] = function subscribeToMasterMessages(onMessage) {
26
+ const messageHandler = (messageEvent: MessageEvent) => {
27
+ onMessage(messageEvent.data)
28
+ }
29
+ const unsubscribe = () => {
30
+ self.removeEventListener('message', messageHandler as EventListener)
31
+ }
32
+ self.addEventListener('message', messageHandler as EventListener)
33
+ return unsubscribe
34
+ }
35
+
36
+ export default {
37
+ isWorkerRuntime,
38
+ postMessageToMaster,
39
+ subscribeToMasterMessages,
40
+ }
@@ -0,0 +1,52 @@
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
+
6
+ import { AbstractedWorkerAPI } from '../types/worker'
7
+
8
+ interface WorkerGlobalScope {
9
+ addEventListener(eventName: string, listener: (event: Event) => void): void
10
+ postMessage(message: any, transferables?: any[]): void
11
+ removeEventListener(eventName: string, listener: (event: Event) => void): void
12
+ }
13
+
14
+ declare const self: WorkerGlobalScope
15
+
16
+ if (self === undefined) {
17
+ ;(global as any).self = global
18
+ }
19
+
20
+ const isWorkerRuntime: AbstractedWorkerAPI['isWorkerRuntime'] = function isWorkerRuntime() {
21
+ return self !== undefined && self['postMessage'] ? true : false
22
+ }
23
+
24
+ const postMessageToMaster: AbstractedWorkerAPI['postMessageToMaster'] = function postMessageToMaster(data) {
25
+ // TODO: Warn that Transferables are not supported on first attempt to use feature
26
+ self.postMessage(data)
27
+ }
28
+
29
+ let muxingHandlerSetUp = false
30
+ const messageHandlers = new Set<(data: any) => void>()
31
+
32
+ const subscribeToMasterMessages: AbstractedWorkerAPI['subscribeToMasterMessages'] = function subscribeToMasterMessages(onMessage) {
33
+ if (!muxingHandlerSetUp) {
34
+ // We have one multiplexing message handler as tiny-worker's
35
+ // addEventListener() only allows you to set a single message handler
36
+ self.addEventListener('message', ((event: MessageEvent) => {
37
+ for (const handler of messageHandlers) handler(event.data)
38
+ }) as EventListener)
39
+ muxingHandlerSetUp = true
40
+ }
41
+
42
+ messageHandlers.add(onMessage)
43
+
44
+ const unsubscribe = () => messageHandlers.delete(onMessage)
45
+ return unsubscribe
46
+ }
47
+
48
+ export default {
49
+ isWorkerRuntime,
50
+ postMessageToMaster,
51
+ subscribeToMasterMessages,
52
+ }
@@ -0,0 +1,23 @@
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
+
7
+ import { AbstractedWorkerAPI } from '../types/worker'
8
+ import WebWorkerImplementation from './implementation.browser'
9
+ import TinyWorkerImplementation from './implementation.tiny-worker'
10
+ import WorkerThreadsImplementation from './implementation.worker_threads'
11
+
12
+ const runningInNode = typeof process !== 'undefined' && (process.arch as string) !== 'browser' && 'pid' in process
13
+
14
+ function selectNodeImplementation(): AbstractedWorkerAPI {
15
+ try {
16
+ WorkerThreadsImplementation.testImplementation()
17
+ return WorkerThreadsImplementation
18
+ } catch {
19
+ return TinyWorkerImplementation
20
+ }
21
+ }
22
+
23
+ export default runningInNode ? selectNodeImplementation() : WebWorkerImplementation
@@ -0,0 +1,50 @@
1
+ /* eslint-disable import/no-default-export */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ // tslint:disable no-shadowed-variable
4
+ import { MessagePort } from 'node:worker_threads'
5
+
6
+ import { AbstractedWorkerAPI } from '../types/worker'
7
+ import WorkerThreads from '../worker_threads'
8
+
9
+ function assertMessagePort(port: MessagePort | null | undefined): MessagePort {
10
+ if (!port) {
11
+ throw new Error('Invariant violation: MessagePort to parent is not available.')
12
+ }
13
+ return port
14
+ }
15
+
16
+ const isWorkerRuntime: AbstractedWorkerAPI['isWorkerRuntime'] = function isWorkerRuntime() {
17
+ return !WorkerThreads().isMainThread
18
+ }
19
+
20
+ const postMessageToMaster: AbstractedWorkerAPI['postMessageToMaster'] = function postMessageToMaster(data, transferList) {
21
+ assertMessagePort(WorkerThreads().parentPort).postMessage(data, transferList as any)
22
+ }
23
+
24
+ const subscribeToMasterMessages: AbstractedWorkerAPI['subscribeToMasterMessages'] = function subscribeToMasterMessages(onMessage) {
25
+ const parentPort = WorkerThreads().parentPort
26
+
27
+ if (!parentPort) {
28
+ throw new Error('Invariant violation: MessagePort to parent is not available.')
29
+ }
30
+ const messageHandler = (message: any) => {
31
+ onMessage(message)
32
+ }
33
+ const unsubscribe = () => {
34
+ assertMessagePort(parentPort).off('message', messageHandler)
35
+ }
36
+ assertMessagePort(parentPort).on('message', messageHandler)
37
+ return unsubscribe
38
+ }
39
+
40
+ function testImplementation() {
41
+ // Will throw if `worker_threads` are not available
42
+ WorkerThreads()
43
+ }
44
+
45
+ export default {
46
+ isWorkerRuntime,
47
+ postMessageToMaster,
48
+ subscribeToMasterMessages,
49
+ testImplementation,
50
+ }
@@ -0,0 +1,228 @@
1
+ /* eslint-disable @typescript-eslint/no-floating-promises */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import isSomeObservable from 'is-observable'
4
+ import { Observable, Subscription } from 'observable-fns'
5
+
6
+ import { deserialize, serialize } from '../common'
7
+ import { isTransferDescriptor, TransferDescriptor } from '../transferable'
8
+ import {
9
+ MasterJobCancelMessage,
10
+ MasterJobRunMessage,
11
+ MasterMessageType,
12
+ SerializedError,
13
+ WorkerInitMessage,
14
+ WorkerJobErrorMessage,
15
+ WorkerJobResultMessage,
16
+ WorkerJobStartMessage,
17
+ WorkerMessageType,
18
+ WorkerUncaughtErrorMessage,
19
+ } from '../types/messages'
20
+ import { WorkerFunction, WorkerModule } from '../types/worker'
21
+ import Implementation from './implementation'
22
+
23
+ export { registerSerializer } from '../common'
24
+ export { Transfer } from '../transferable'
25
+
26
+ /** Returns `true` if this code is currently running in a worker. */
27
+ export const isWorkerRuntime = Implementation.isWorkerRuntime
28
+
29
+ let exposeCalled = false
30
+
31
+ const activeSubscriptions = new Map<number, Subscription<any>>()
32
+
33
+ const isMasterJobCancelMessage = (thing: any): thing is MasterJobCancelMessage => thing && thing.type === MasterMessageType.cancel
34
+ const isMasterJobRunMessage = (thing: any): thing is MasterJobRunMessage => thing && thing.type === MasterMessageType.run
35
+
36
+ /**
37
+ * There are issues with `is-observable` not recognizing zen-observable's instances.
38
+ * We are using `observable-fns`, but it's based on zen-observable, too.
39
+ */
40
+ const isObservable = (thing: any): thing is Observable<any> => isSomeObservable(thing) || isZenObservable(thing)
41
+
42
+ function isZenObservable(thing: any): thing is Observable<any> {
43
+ return thing && typeof thing === 'object' && typeof thing.subscribe === 'function'
44
+ }
45
+
46
+ function deconstructTransfer(thing: any) {
47
+ return isTransferDescriptor(thing) ? { payload: thing.send, transferables: thing.transferables } : { payload: thing, transferables: undefined }
48
+ }
49
+
50
+ function postFunctionInitMessage() {
51
+ const initMessage: WorkerInitMessage = {
52
+ exposed: {
53
+ type: 'function',
54
+ },
55
+ type: WorkerMessageType.init,
56
+ }
57
+ Implementation.postMessageToMaster(initMessage)
58
+ }
59
+
60
+ function postModuleInitMessage(methodNames: string[]) {
61
+ const initMessage: WorkerInitMessage = {
62
+ exposed: {
63
+ methods: methodNames,
64
+ type: 'module',
65
+ },
66
+ type: WorkerMessageType.init,
67
+ }
68
+ Implementation.postMessageToMaster(initMessage)
69
+ }
70
+
71
+ function postJobErrorMessage(uid: number, rawError: Error | TransferDescriptor<Error>) {
72
+ const { payload: error, transferables } = deconstructTransfer(rawError)
73
+ const errorMessage: WorkerJobErrorMessage = {
74
+ error: serialize(error) as any as SerializedError,
75
+ type: WorkerMessageType.error,
76
+ uid,
77
+ }
78
+ Implementation.postMessageToMaster(errorMessage, transferables)
79
+ }
80
+
81
+ function postJobResultMessage(uid: number, completed: boolean, resultValue?: any) {
82
+ const { payload, transferables } = deconstructTransfer(resultValue)
83
+ const resultMessage: WorkerJobResultMessage = {
84
+ complete: completed ? true : undefined,
85
+ payload,
86
+ type: WorkerMessageType.result,
87
+ uid,
88
+ }
89
+ Implementation.postMessageToMaster(resultMessage, transferables)
90
+ }
91
+
92
+ function postJobStartMessage(uid: number, resultType: WorkerJobStartMessage['resultType']) {
93
+ const startMessage: WorkerJobStartMessage = {
94
+ resultType,
95
+ type: WorkerMessageType.running,
96
+ uid,
97
+ }
98
+ Implementation.postMessageToMaster(startMessage)
99
+ }
100
+
101
+ function postUncaughtErrorMessage(error: Error) {
102
+ try {
103
+ const errorMessage: WorkerUncaughtErrorMessage = {
104
+ error: serialize(error) as any as SerializedError,
105
+ type: WorkerMessageType.uncaughtError,
106
+ }
107
+ Implementation.postMessageToMaster(errorMessage)
108
+ } catch (subError) {
109
+ // tslint:disable-next-line no-console
110
+ console.error(
111
+ 'Not reporting uncaught error back to master thread as it ' + 'occured while reporting an uncaught error already.' + '\nLatest error:',
112
+ subError,
113
+ '\nOriginal error:',
114
+ error,
115
+ )
116
+ }
117
+ }
118
+
119
+ async function runFunction(jobUID: number, fn: WorkerFunction, args: any[]) {
120
+ let syncResult: any
121
+
122
+ try {
123
+ syncResult = fn(...args)
124
+ } catch (error) {
125
+ return postJobErrorMessage(jobUID, error)
126
+ }
127
+
128
+ const resultType = isObservable(syncResult) ? 'observable' : 'promise'
129
+ postJobStartMessage(jobUID, resultType)
130
+
131
+ if (isObservable(syncResult)) {
132
+ const subscription = syncResult.subscribe(
133
+ (value) => postJobResultMessage(jobUID, false, serialize(value)),
134
+ (error) => {
135
+ postJobErrorMessage(jobUID, serialize(error) as any)
136
+ activeSubscriptions.delete(jobUID)
137
+ },
138
+ () => {
139
+ postJobResultMessage(jobUID, true)
140
+ activeSubscriptions.delete(jobUID)
141
+ },
142
+ )
143
+ activeSubscriptions.set(jobUID, subscription)
144
+ } else {
145
+ try {
146
+ const result = await syncResult
147
+ postJobResultMessage(jobUID, true, serialize(result))
148
+ } catch (error) {
149
+ postJobErrorMessage(jobUID, serialize(error) as any)
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Expose a function or a module (an object whose values are functions)
156
+ * to the main thread. Must be called exactly once in every worker thread
157
+ * to signal its API to the main thread.
158
+ *
159
+ * @param exposed Function or object whose values are functions
160
+ */
161
+ export function expose(exposed: WorkerFunction | WorkerModule<any>) {
162
+ if (!Implementation.isWorkerRuntime()) {
163
+ throw new Error('expose() called in the master thread.')
164
+ }
165
+ if (exposeCalled) {
166
+ throw new Error('expose() called more than once. This is not possible. Pass an object to expose() if you want to expose multiple functions.')
167
+ }
168
+ exposeCalled = true
169
+
170
+ if (typeof exposed === 'function') {
171
+ Implementation.subscribeToMasterMessages((messageData) => {
172
+ if (isMasterJobRunMessage(messageData) && !messageData.method) {
173
+ runFunction(messageData.uid, exposed, messageData.args.map(deserialize))
174
+ }
175
+ })
176
+ postFunctionInitMessage()
177
+ } else if (typeof exposed === 'object' && exposed) {
178
+ Implementation.subscribeToMasterMessages((messageData) => {
179
+ if (isMasterJobRunMessage(messageData) && messageData.method) {
180
+ runFunction(messageData.uid, exposed[messageData.method], messageData.args.map(deserialize))
181
+ }
182
+ })
183
+
184
+ const methodNames = Object.keys(exposed).filter((key) => typeof exposed[key] === 'function')
185
+ postModuleInitMessage(methodNames)
186
+ } else {
187
+ throw new Error(`Invalid argument passed to expose(). Expected a function or an object, got: ${exposed}`)
188
+ }
189
+
190
+ Implementation.subscribeToMasterMessages((messageData) => {
191
+ if (isMasterJobCancelMessage(messageData)) {
192
+ const jobUID = messageData.uid
193
+ const subscription = activeSubscriptions.get(jobUID)
194
+
195
+ if (subscription) {
196
+ subscription.unsubscribe()
197
+ activeSubscriptions.delete(jobUID)
198
+ }
199
+ }
200
+ })
201
+ }
202
+
203
+ if (typeof self !== 'undefined' && typeof self.addEventListener === 'function' && Implementation.isWorkerRuntime()) {
204
+ self.addEventListener('error', (event) => {
205
+ // Post with some delay, so the master had some time to subscribe to messages
206
+ setTimeout(() => postUncaughtErrorMessage(event.error || event), 250)
207
+ })
208
+ self.addEventListener('unhandledrejection', (event) => {
209
+ const error = (event as any).reason
210
+ if (error && typeof (error as any).message === 'string') {
211
+ // Post with some delay, so the master had some time to subscribe to messages
212
+ setTimeout(() => postUncaughtErrorMessage(error), 250)
213
+ }
214
+ })
215
+ }
216
+
217
+ if (typeof process !== 'undefined' && typeof process.on === 'function' && Implementation.isWorkerRuntime()) {
218
+ process.on('uncaughtException', (error) => {
219
+ // Post with some delay, so the master had some time to subscribe to messages
220
+ setTimeout(() => postUncaughtErrorMessage(error), 250)
221
+ })
222
+ process.on('unhandledRejection', (error) => {
223
+ if (error && typeof (error as any).message === 'string') {
224
+ // Post with some delay, so the master had some time to subscribe to messages
225
+ setTimeout(() => postUncaughtErrorMessage(error as any), 250)
226
+ }
227
+ })
228
+ }
@@ -0,0 +1,28 @@
1
+ /* eslint-disable import/no-default-export */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ // Webpack hack
4
+ // tslint:disable no-eval
5
+
6
+ declare function __non_webpack_require__(module: string): any
7
+
8
+ // FIXME
9
+ type MessagePort = any
10
+
11
+ interface WorkerThreadsModule {
12
+ MessagePort: typeof MessagePort
13
+ isMainThread: boolean
14
+ parentPort: MessagePort
15
+ }
16
+
17
+ let implementation: WorkerThreadsModule | undefined
18
+
19
+ function selectImplementation(): WorkerThreadsModule {
20
+ return typeof __non_webpack_require__ === 'function' ? __non_webpack_require__('worker_threads') : eval('require')('worker_threads')
21
+ }
22
+
23
+ export default function getImplementation(): WorkerThreadsModule {
24
+ if (!implementation) {
25
+ implementation = selectImplementation()
26
+ }
27
+ return implementation
28
+ }