@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.
Files changed (231) hide show
  1. package/dist/{types/common.d.ts → common.d.ts} +1 -5
  2. package/dist/common.js +16 -0
  3. package/dist/esm/common.js +16 -0
  4. package/dist/esm/index.js +26 -0
  5. package/dist/esm/master/get-bundle-url.browser.js +25 -0
  6. package/dist/esm/master/implementation.browser.js +65 -0
  7. package/dist/esm/master/implementation.js +43 -0
  8. package/dist/esm/master/implementation.node.js +205 -0
  9. package/dist/esm/master/index.js +14 -0
  10. package/dist/esm/master/invocation-proxy.js +121 -0
  11. package/dist/esm/master/pool-types.js +14 -0
  12. package/dist/esm/master/pool.js +262 -0
  13. package/dist/esm/master/register.js +11 -0
  14. package/dist/esm/master/spawn.js +114 -0
  15. package/dist/esm/master/thread.js +18 -0
  16. package/dist/esm/observable-promise.js +132 -0
  17. package/dist/esm/observable.js +33 -0
  18. package/dist/esm/ponyfills.js +20 -0
  19. package/dist/esm/promise.js +23 -0
  20. package/dist/esm/serializers.js +41 -0
  21. package/dist/esm/symbols.js +8 -0
  22. package/dist/esm/transferable.js +25 -0
  23. package/dist/esm/types/master.js +9 -0
  24. package/dist/esm/types/messages.js +16 -0
  25. package/dist/esm/types/worker.js +2 -0
  26. package/dist/esm/worker/bundle-entry.js +26 -0
  27. package/dist/esm/worker/implementation.browser.js +24 -0
  28. package/dist/esm/worker/implementation.js +19 -0
  29. package/dist/esm/worker/implementation.tiny-worker.js +37 -0
  30. package/dist/esm/worker/implementation.worker_threads.js +41 -0
  31. package/dist/esm/worker/index.js +174 -0
  32. package/dist/esm/worker_threads.js +13 -0
  33. package/dist/index.d.ts +7 -0
  34. package/dist/index.js +26 -0
  35. package/dist/{types/master → master}/get-bundle-url.browser.d.ts +0 -1
  36. package/dist/master/get-bundle-url.browser.js +25 -0
  37. package/dist/{types/master → master}/implementation.browser.d.ts +1 -2
  38. package/dist/master/implementation.browser.js +65 -0
  39. package/dist/{types/master → master}/implementation.d.ts +1 -4
  40. package/dist/master/implementation.js +43 -0
  41. package/dist/{types/master → master}/implementation.node.d.ts +1 -2
  42. package/dist/master/implementation.node.js +205 -0
  43. package/dist/master/index.d.ts +10 -0
  44. package/dist/master/index.js +14 -0
  45. package/dist/{types/master → master}/invocation-proxy.d.ts +1 -2
  46. package/dist/master/invocation-proxy.js +121 -0
  47. package/dist/{types/master → master}/pool-types.d.ts +1 -16
  48. package/dist/master/pool-types.js +14 -0
  49. package/dist/master/pool.d.ts +50 -0
  50. package/dist/master/pool.js +262 -0
  51. package/dist/master/register.d.ts +1 -0
  52. package/dist/master/register.js +11 -0
  53. package/dist/{types/master → master}/spawn.d.ts +2 -12
  54. package/dist/master/spawn.js +114 -0
  55. package/dist/master/thread.d.ts +8 -0
  56. package/dist/master/thread.js +18 -0
  57. package/dist/{types/observable-promise.d.ts → observable-promise.d.ts} +0 -14
  58. package/dist/observable-promise.js +132 -0
  59. package/dist/observable.d.ts +11 -0
  60. package/dist/observable.js +33 -0
  61. package/dist/{types/ponyfills.d.ts → ponyfills.d.ts} +0 -1
  62. package/dist/ponyfills.js +20 -0
  63. package/dist/promise.d.ts +1 -0
  64. package/dist/promise.js +23 -0
  65. package/dist/{types/serializers.d.ts → serializers.d.ts} +0 -1
  66. package/dist/serializers.js +41 -0
  67. package/dist/{types/symbols.d.ts → symbols.d.ts} +0 -1
  68. package/dist/symbols.js +8 -0
  69. package/dist/transferable.d.ts +9 -0
  70. package/dist/transferable.js +25 -0
  71. package/dist/types/{types/master.d.ts → master.d.ts} +3 -17
  72. package/dist/types/master.js +9 -0
  73. package/dist/types/{types/messages.d.ts → messages.d.ts} +0 -1
  74. package/dist/types/messages.js +16 -0
  75. package/dist/types/{types/worker.d.ts → worker.d.ts} +0 -1
  76. package/dist/types/worker.js +2 -0
  77. package/dist/worker/bundle-entry.d.ts +1 -0
  78. package/dist/worker/bundle-entry.js +26 -0
  79. package/dist/worker/implementation.browser.d.ts +6 -0
  80. package/dist/worker/implementation.browser.js +24 -0
  81. package/dist/worker/implementation.d.ts +3 -0
  82. package/dist/worker/implementation.js +19 -0
  83. package/dist/worker/implementation.tiny-worker.d.ts +6 -0
  84. package/dist/worker/implementation.tiny-worker.js +37 -0
  85. package/dist/worker/implementation.worker_threads.d.ts +8 -0
  86. package/dist/worker/implementation.worker_threads.js +41 -0
  87. package/dist/worker/index.d.ts +5 -0
  88. package/dist/worker/index.js +174 -0
  89. package/dist/worker_threads.d.ts +8 -0
  90. package/dist/worker_threads.js +13 -0
  91. package/eslint.config.mjs +35 -0
  92. package/index.mjs +10 -0
  93. package/observable.d.ts +2 -0
  94. package/observable.js +2 -0
  95. package/observable.mjs +4 -0
  96. package/package.json +93 -53
  97. package/register.d.ts +2 -0
  98. package/register.js +2 -0
  99. package/register.mjs +1 -0
  100. package/rollup.config.js +16 -0
  101. package/src/common.ts +19 -0
  102. package/src/index.ts +10 -0
  103. package/src/master/get-bundle-url.browser.ts +31 -0
  104. package/src/master/implementation.browser.ts +82 -0
  105. package/src/master/implementation.node.ts +285 -0
  106. package/src/master/implementation.ts +21 -0
  107. package/src/master/index.ts +19 -0
  108. package/src/master/invocation-proxy.ts +151 -0
  109. package/src/master/pool-types.ts +83 -0
  110. package/src/master/pool.ts +399 -0
  111. package/src/master/register.ts +10 -0
  112. package/src/master/spawn.ts +172 -0
  113. package/src/master/thread.ts +29 -0
  114. package/src/observable-promise.ts +183 -0
  115. package/src/observable.ts +44 -0
  116. package/src/ponyfills.ts +31 -0
  117. package/src/promise.ts +26 -0
  118. package/src/serializers.ts +68 -0
  119. package/src/symbols.ts +5 -0
  120. package/{dist/types/transferable.d.ts → src/transferable.ts} +33 -8
  121. package/src/types/master.ts +132 -0
  122. package/src/types/messages.ts +72 -0
  123. package/src/types/worker.ts +14 -0
  124. package/src/worker/bundle-entry.ts +10 -0
  125. package/src/worker/implementation.browser.ts +40 -0
  126. package/src/worker/implementation.tiny-worker.ts +55 -0
  127. package/src/worker/implementation.ts +23 -0
  128. package/src/worker/implementation.worker_threads.ts +50 -0
  129. package/src/worker/index.ts +230 -0
  130. package/src/worker_threads.ts +27 -0
  131. package/test/lib/index.ts +1 -0
  132. package/test/lib/serialization.ts +38 -0
  133. package/test/observable-promise.test.ts +205 -0
  134. package/test/observable.test.ts +87 -0
  135. package/test/pool.test.ts +183 -0
  136. package/test/serialization.test.ts +23 -0
  137. package/test/spawn.chromium.mocha.ts +53 -0
  138. package/test/spawn.test.ts +87 -0
  139. package/test/streaming.test.ts +29 -0
  140. package/test/transferables.test.ts +71 -0
  141. package/test/workers/arraybuffer-xor.ts +10 -0
  142. package/test/workers/count-to-five.ts +12 -0
  143. package/test/workers/counter.ts +19 -0
  144. package/test/workers/faulty-function.ts +5 -0
  145. package/test/workers/hello-world.ts +5 -0
  146. package/test/workers/increment.ts +8 -0
  147. package/test/workers/minmax.ts +25 -0
  148. package/test/workers/serialization.ts +13 -0
  149. package/test/workers/top-level-throw.ts +1 -0
  150. package/test-tooling/rollup/app.js +21 -0
  151. package/test-tooling/rollup/rollup.config.ts +14 -0
  152. package/test-tooling/rollup/worker.js +7 -0
  153. package/test-tooling/tsconfig/minimal.ts +12 -0
  154. package/test-tooling/webpack/addition-worker.ts +9 -0
  155. package/test-tooling/webpack/app-with-inlined-worker.ts +28 -0
  156. package/test-tooling/webpack/app.ts +61 -0
  157. package/test-tooling/webpack/pool-worker.ts +5 -0
  158. package/test-tooling/webpack/raw-loader.d.ts +4 -0
  159. package/test-tooling/webpack/webpack.chromium.mocha.ts +21 -0
  160. package/test-tooling/webpack/webpack.node.config.js +29 -0
  161. package/test-tooling/webpack/webpack.web.config.js +28 -0
  162. package/types/is-observable.d.ts +7 -0
  163. package/types/tiny-worker.d.ts +4 -0
  164. package/types/webworker.d.ts +9 -0
  165. package/worker.d.ts +2 -0
  166. package/worker.js +2 -0
  167. package/worker.mjs +6 -0
  168. package/dist/browser/master/implementation.browser.mjs +0 -89
  169. package/dist/browser/master/implementation.browser.mjs.map +0 -1
  170. package/dist/browser/worker/worker.browser.mjs +0 -291
  171. package/dist/browser/worker/worker.browser.mjs.map +0 -1
  172. package/dist/neutral/index.mjs +0 -1022
  173. package/dist/neutral/index.mjs.map +0 -1
  174. package/dist/neutral/master/implementation.mjs +0 -264
  175. package/dist/neutral/master/implementation.mjs.map +0 -1
  176. package/dist/neutral/master/index.mjs +0 -988
  177. package/dist/neutral/master/index.mjs.map +0 -1
  178. package/dist/neutral/master/pool.mjs +0 -579
  179. package/dist/neutral/master/pool.mjs.map +0 -1
  180. package/dist/neutral/master/register.mjs +0 -272
  181. package/dist/neutral/master/register.mjs.map +0 -1
  182. package/dist/neutral/master/spawn.mjs +0 -412
  183. package/dist/neutral/master/spawn.mjs.map +0 -1
  184. package/dist/neutral/master/thread.mjs +0 -29
  185. package/dist/neutral/master/thread.mjs.map +0 -1
  186. package/dist/neutral/observable-promise.mjs +0 -132
  187. package/dist/neutral/observable-promise.mjs.map +0 -1
  188. package/dist/neutral/observable.mjs +0 -31
  189. package/dist/neutral/observable.mjs.map +0 -1
  190. package/dist/node/master/implementation.node.mjs +0 -154
  191. package/dist/node/master/implementation.node.mjs.map +0 -1
  192. package/dist/node/worker/worker.node.mjs +0 -304
  193. package/dist/node/worker/worker.node.mjs.map +0 -1
  194. package/dist/types/common.d.ts.map +0 -1
  195. package/dist/types/index.d.ts +0 -9
  196. package/dist/types/index.d.ts.map +0 -1
  197. package/dist/types/master/get-bundle-url.browser.d.ts.map +0 -1
  198. package/dist/types/master/implementation.browser.d.ts.map +0 -1
  199. package/dist/types/master/implementation.d.ts.map +0 -1
  200. package/dist/types/master/implementation.node.d.ts.map +0 -1
  201. package/dist/types/master/index.d.ts +0 -13
  202. package/dist/types/master/index.d.ts.map +0 -1
  203. package/dist/types/master/invocation-proxy.d.ts.map +0 -1
  204. package/dist/types/master/pool-types.d.ts.map +0 -1
  205. package/dist/types/master/pool.d.ts +0 -93
  206. package/dist/types/master/pool.d.ts.map +0 -1
  207. package/dist/types/master/register.d.ts +0 -2
  208. package/dist/types/master/register.d.ts.map +0 -1
  209. package/dist/types/master/spawn.d.ts.map +0 -1
  210. package/dist/types/master/thread.d.ts +0 -13
  211. package/dist/types/master/thread.d.ts.map +0 -1
  212. package/dist/types/observable-promise.d.ts.map +0 -1
  213. package/dist/types/observable.d.ts +0 -21
  214. package/dist/types/observable.d.ts.map +0 -1
  215. package/dist/types/ponyfills.d.ts.map +0 -1
  216. package/dist/types/promise.d.ts +0 -6
  217. package/dist/types/promise.d.ts.map +0 -1
  218. package/dist/types/serializers.d.ts.map +0 -1
  219. package/dist/types/symbols.d.ts.map +0 -1
  220. package/dist/types/transferable.d.ts.map +0 -1
  221. package/dist/types/types/master.d.ts.map +0 -1
  222. package/dist/types/types/messages.d.ts.map +0 -1
  223. package/dist/types/types/worker.d.ts.map +0 -1
  224. package/dist/types/worker/WorkerGlobalScope.d.ts +0 -6
  225. package/dist/types/worker/WorkerGlobalScope.d.ts.map +0 -1
  226. package/dist/types/worker/expose.d.ts +0 -4
  227. package/dist/types/worker/expose.d.ts.map +0 -1
  228. package/dist/types/worker/worker.browser.d.ts +0 -14
  229. package/dist/types/worker/worker.browser.d.ts.map +0 -1
  230. package/dist/types/worker/worker.node.d.ts +0 -25
  231. 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'
@@ -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
@@ -0,0 +1,5 @@
1
+ export const $errors = Symbol('thread.errors')
2
+ export const $events = Symbol('thread.events')
3
+ export const $terminate = Symbol('thread.terminate')
4
+ export const $transferable = Symbol('thread.transferable')
5
+ export const $worker = Symbol('thread.worker')
@@ -1,10 +1,22 @@
1
- import { $transferable } from './symbols.ts';
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { $transferable } from './symbols'
3
+
2
4
  export interface TransferDescriptor<T = any> {
3
- [$transferable]: true;
4
- send: T;
5
- transferables: Transferable[];
5
+ [$transferable]: true
6
+ send: T
7
+ transferables: Transferable[]
6
8
  }
7
- export declare function isTransferDescriptor(thing: any): thing is TransferDescriptor;
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 declare function Transfer(transferable: Transferable): TransferDescriptor;
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 declare function Transfer<T>(payload: T, transferables: Transferable[]): TransferDescriptor;
43
- //# sourceMappingURL=transferable.d.ts.map
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
+ }