@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
package/index.mjs ADDED
@@ -0,0 +1,11 @@
1
+ /* eslint-disable import/no-internal-modules */
2
+ import * as Threads from './dist/index.js'
3
+
4
+ export const registerSerializer = Threads.registerSerializer
5
+ export const spawn = Threads.spawn
6
+ export const BlobWorker = Threads.BlobWorker
7
+ export const DefaultSerializer = Threads.DefaultSerializer
8
+ export const Pool = Threads.Pool
9
+ export const Thread = Threads.Thread
10
+ export const Transfer = Threads.Transfer
11
+ export const Worker = Threads.Worker
@@ -0,0 +1,2 @@
1
+ /* eslint-disable import/no-internal-modules */
2
+ export * from './dist/observable'
package/observable.js ADDED
@@ -0,0 +1,3 @@
1
+ /* eslint-disable import/no-internal-modules */
2
+ /* eslint-disable no-undef */
3
+ module.exports = require('./dist/observable')
package/observable.mjs ADDED
@@ -0,0 +1,5 @@
1
+ /* eslint-disable import/no-internal-modules */
2
+ import * as Observables from './dist/observable.js'
3
+
4
+ export const Observable = Observables.Observable
5
+ export const Subject = Observables.Subject
package/package.json ADDED
@@ -0,0 +1,141 @@
1
+ {
2
+ "name": "@xylabs/threads",
3
+ "version": "3.0.4",
4
+ "description": "Web workers & worker threads as simple as a function call",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "module": "dist-esm/index.js",
8
+ "scripts": {
9
+ "package-compile": "yarn build",
10
+ "clean": "rimraf dist/ dist-esm/",
11
+ "dev": "npm run clean && tsc -p tsconfig.json --watch",
12
+ "build": "npm run clean && npm run build:cjs && npm run build:es",
13
+ "build:cjs": "tsc -p tsconfig.json",
14
+ "build:es": "tsc -p tsconfig-esm.json",
15
+ "postbuild": "npm run bundle",
16
+ "bundle": "rollup -c -f umd --file=bundle/worker.js --name=threads --silent -- dist-esm/worker/bundle-entry.js",
17
+ "test": "npm run test:library && npm run test:tooling && npm run test:puppeteer:basic && npm run test:puppeteer:webpack",
18
+ "test:library": "cross-env TS_NODE_FILES=true ava ./test/**/*.test.ts",
19
+ "test:tooling": "cross-env TS_NODE_FILES=true ava ./test-tooling/**/*.test.ts",
20
+ "test:puppeteer:basic": "puppet-run --plugin=mocha --bundle=./test/workers/:workers/ --serve=./bundle/worker.js:/worker.js ./test/*.chromium*.ts",
21
+ "test:puppeteer:webpack": "puppet-run --serve ./test-tooling/webpack/dist/app.web/0.worker.js --serve ./test-tooling/webpack/dist/app.web/1.worker.js --plugin=mocha ./test-tooling/webpack/webpack.chromium.mocha.ts",
22
+ "prepare": "npm run build"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "require": "./dist/index.js",
27
+ "default": "./index.mjs"
28
+ },
29
+ "./observable": {
30
+ "require": "./observable.js",
31
+ "default": "./observable.mjs"
32
+ },
33
+ "./register": {
34
+ "require": "./register.js",
35
+ "default": "./register.mjs"
36
+ },
37
+ "./worker": {
38
+ "require": "./worker.js",
39
+ "default": "./worker.mjs"
40
+ }
41
+ },
42
+ "sideEffects": [
43
+ "./dist*/master/register.js",
44
+ "./dist*/worker/index.js",
45
+ "./register.*js",
46
+ "./worker.*js"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/andywer/threads.js.git"
51
+ },
52
+ "author": "Andy Wermke (https://github.com/andywer)",
53
+ "bugs": {
54
+ "url": "https://github.com/andywer/threads.js/issues"
55
+ },
56
+ "funding": "https://github.com/andywer/threads.js?sponsor=1",
57
+ "homepage": "https://threads.js.org",
58
+ "keywords": [
59
+ "thread",
60
+ "worker",
61
+ "pool",
62
+ "spawn",
63
+ "isomorphic",
64
+ "parallel",
65
+ "observable",
66
+ "worker_threads"
67
+ ],
68
+ "dependencies": {
69
+ "callsites": "^3.1.0",
70
+ "debug": "^4.2.0",
71
+ "is-observable": "^2.1.0",
72
+ "observable-fns": "^0.6.1"
73
+ },
74
+ "devDependencies": {
75
+ "@rollup/plugin-commonjs": "^16.0.0",
76
+ "@rollup/plugin-node-resolve": "^10.0.0",
77
+ "@types/chai": "^4.2.14",
78
+ "@types/debug": "^4.1.5",
79
+ "@types/execa": "^2.0.0",
80
+ "@types/node": "^20",
81
+ "@types/webpack": "^4.41.23",
82
+ "ava": "^3.13.0",
83
+ "chai": "^4.2.0",
84
+ "cross-env": "^7.0.2",
85
+ "execa": "^4.0.3",
86
+ "mocha": "^8.2.0",
87
+ "puppet-run": "^0.11.3",
88
+ "puppet-run-plugin-mocha": "^0.10.0-alpha",
89
+ "raw-loader": "^4.0.2",
90
+ "rimraf": "^3.0.2",
91
+ "rollup": "^2.32.1",
92
+ "threads-plugin": "^1.3.3",
93
+ "tiny-worker": "^2.2.0",
94
+ "ts-loader": "^8.0.7",
95
+ "ts-node": "^9.0.0",
96
+ "typescript": "^4.2.3",
97
+ "wavy": "^1.0.4",
98
+ "webpack": "^4.44.2",
99
+ "worker-plugin": "^5.0.0"
100
+ },
101
+ "optionalDependencies": {
102
+ "tiny-worker": ">= 2"
103
+ },
104
+ "ava": {
105
+ "extensions": [
106
+ "ts"
107
+ ],
108
+ "files": [
109
+ "./test/**/*.test.ts",
110
+ "./test-tooling/**/*.test.ts"
111
+ ],
112
+ "require": [
113
+ "ts-node/register"
114
+ ],
115
+ "serial": true
116
+ },
117
+ "browser": {
118
+ "./dist-esm/master/implementation.js": "./dist-esm/master/implementation.browser.js",
119
+ "./dist-esm/master/implementation.node.js": false,
120
+ "./dist-esm/worker/implementation.js": "./dist-esm/worker/implementation.browser.js",
121
+ "./dist-esm/worker/implementation.tiny-worker.js": false,
122
+ "./dist-esm/worker/implementation.worker_threads.js": false,
123
+ "./dist/master/implementation.js": "./dist/master/implementation.browser.js",
124
+ "./dist/master/implementation.node.js": false,
125
+ "./dist/worker/implementation.js": "./dist/worker/implementation.browser.js",
126
+ "./dist/worker/implementation.tiny-worker.js": false,
127
+ "./dist/worker/implementation.worker_threads.js": false,
128
+ "callsites": false,
129
+ "tiny-worker": false,
130
+ "ts-node": false,
131
+ "ts-node/register": false,
132
+ "worker_threads": false
133
+ },
134
+ "files": [
135
+ "dist/**",
136
+ "dist-esm/**",
137
+ "*.js",
138
+ "*.mjs",
139
+ "*.ts"
140
+ ]
141
+ }
package/register.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /* eslint-disable import/export */
2
+ /* eslint-disable import/no-internal-modules */
3
+ export * from './dist/master/register'
package/register.js ADDED
@@ -0,0 +1,3 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-disable import/no-internal-modules */
3
+ require('./dist/master/register')
package/register.mjs ADDED
@@ -0,0 +1,2 @@
1
+ /* eslint-disable import/no-internal-modules */
2
+ import './dist/master/register.js'
@@ -0,0 +1,16 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-disable @typescript-eslint/no-var-requires */
3
+ const commonjs = require('@rollup/plugin-commonjs')
4
+ const { nodeResolve } = require('@rollup/plugin-node-resolve')
5
+
6
+ module.exports = {
7
+ plugins: [
8
+ nodeResolve({
9
+ browser: true,
10
+ mainFields: ['module', 'main'],
11
+ preferBuiltins: true,
12
+ }),
13
+
14
+ commonjs(),
15
+ ],
16
+ }
package/src/common.ts ADDED
@@ -0,0 +1,16 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { DefaultSerializer, extendSerializer, JsonSerializable, Serializer, SerializerImplementation } from './serializers'
3
+
4
+ let registeredSerializer: Serializer<JsonSerializable> = DefaultSerializer
5
+
6
+ export function registerSerializer(serializer: SerializerImplementation<JsonSerializable>) {
7
+ registeredSerializer = extendSerializer(registeredSerializer, serializer)
8
+ }
9
+
10
+ export function deserialize(message: JsonSerializable): any {
11
+ return registeredSerializer.deserialize(message)
12
+ }
13
+
14
+ export function serialize(input: any): JsonSerializable {
15
+ return registeredSerializer.serialize(input)
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ /* eslint-disable import/no-internal-modules */
2
+ export { registerSerializer } from './common'
3
+ export * from './master/index'
4
+ export { QueuedTask } from './master/pool'
5
+ export { ExposedToThreadType as ExposedAs } from './master/spawn'
6
+ export { DefaultSerializer, JsonSerializable, Serializer, SerializerImplementation } from './serializers'
7
+ export { Transfer, TransferDescriptor } from './transferable'
8
+ export { expose } from './worker/index'
@@ -0,0 +1,31 @@
1
+ // Source: <https://github.com/parcel-bundler/parcel/blob/master/packages/core/parcel-bundler/src/builtins/bundle-url.js>
2
+
3
+ let bundleURL: string | undefined
4
+
5
+ function getBundleURLCached(): string {
6
+ if (!bundleURL) {
7
+ bundleURL = getBundleURL()
8
+ }
9
+
10
+ return bundleURL
11
+ }
12
+
13
+ function getBundleURL(): string {
14
+ // Attempt to find the URL of the current script and use that as the base URL
15
+ try {
16
+ throw new Error('getBundleURL failed')
17
+ } catch (err) {
18
+ const matches = ('' + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\/\/[^\n)]+/g)
19
+ if (matches) {
20
+ return getBaseURL(matches[0])
21
+ }
22
+ }
23
+
24
+ return '/'
25
+ }
26
+
27
+ function getBaseURL(url: string): string {
28
+ return ('' + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\/\/.+)?\/[^/]+(?:\?.*)?$/, '$1') + '/'
29
+ }
30
+
31
+ export { getBaseURL, getBundleURLCached as getBundleURL }
@@ -0,0 +1,80 @@
1
+ // tslint:disable max-classes-per-file
2
+
3
+ import { ImplementationExport, ThreadsWorkerOptions } from '../types/master'
4
+ import { getBundleURL } from './get-bundle-url.browser'
5
+
6
+ export const defaultPoolSize = typeof navigator !== 'undefined' && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 4
7
+
8
+ const isAbsoluteURL = (value: string) => /^[A-Za-z][\d+.A-Za-z\-]*:/.test(value)
9
+
10
+ function createSourceBlobURL(code: string): string {
11
+ const blob = new Blob([code], { type: 'application/javascript' })
12
+ return URL.createObjectURL(blob)
13
+ }
14
+
15
+ function selectWorkerImplementation(): ImplementationExport {
16
+ if (typeof Worker === 'undefined') {
17
+ // Might happen on Safari, for instance
18
+ // The idea is to only fail if the constructor is actually used
19
+ return class NoWebWorker {
20
+ constructor() {
21
+ throw new Error(
22
+ "No web worker implementation available. You might have tried to spawn a worker within a worker in a browser that doesn't support workers in workers.",
23
+ )
24
+ }
25
+ } as unknown as ImplementationExport
26
+ }
27
+
28
+ class WebWorker extends Worker {
29
+ constructor(url: string | URL, options?: ThreadsWorkerOptions) {
30
+ if (typeof url === 'string' && options && options._baseURL) {
31
+ url = new URL(url, options._baseURL)
32
+ } else if (typeof url === 'string' && !isAbsoluteURL(url) && /^file:\/\//i.test(getBundleURL())) {
33
+ url = new URL(url, getBundleURL().replace(/\/[^/]+$/, '/'))
34
+ if (options?.CORSWorkaround ?? true) {
35
+ url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`)
36
+ }
37
+ }
38
+ if (
39
+ typeof url === 'string' &&
40
+ isAbsoluteURL(url) && // Create source code blob loading JS file via `importScripts()`
41
+ // to circumvent worker CORS restrictions
42
+ (options?.CORSWorkaround ?? true)
43
+ ) {
44
+ url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`)
45
+ }
46
+ super(url, options)
47
+ }
48
+ }
49
+
50
+ class BlobWorker extends WebWorker {
51
+ constructor(blob: Blob, options?: ThreadsWorkerOptions) {
52
+ const url = window.URL.createObjectURL(blob)
53
+ super(url, options)
54
+ }
55
+
56
+ static fromText(source: string, options?: ThreadsWorkerOptions): WebWorker {
57
+ const blob = new window.Blob([source], { type: 'text/javascript' })
58
+ return new BlobWorker(blob, options)
59
+ }
60
+ }
61
+
62
+ return {
63
+ blob: BlobWorker,
64
+ default: WebWorker,
65
+ }
66
+ }
67
+
68
+ let implementation: ImplementationExport
69
+
70
+ export function getWorkerImplementation(): ImplementationExport {
71
+ if (!implementation) {
72
+ implementation = selectWorkerImplementation()
73
+ }
74
+ return implementation
75
+ }
76
+
77
+ export function isWorkerRuntime() {
78
+ const isWindowContext = typeof self !== 'undefined' && typeof Window !== 'undefined' && self instanceof Window
79
+ return typeof self !== 'undefined' && self['postMessage'] && !isWindowContext ? true : false
80
+ }
@@ -0,0 +1,284 @@
1
+ /* eslint-disable unicorn/prefer-logical-operator-over-ternary */
2
+ /* eslint-disable unicorn/prefer-regexp-test */
3
+ /* eslint-disable @typescript-eslint/no-var-requires */
4
+ /* eslint-disable unicorn/prefer-add-event-listener */
5
+ /* eslint-disable unicorn/prefer-event-target */
6
+ /* eslint-disable @typescript-eslint/no-explicit-any */
7
+ /* eslint-disable unicorn/text-encoding-identifier-case */
8
+ /* eslint-disable unicorn/no-process-exit */
9
+ /// <reference lib="dom" />
10
+
11
+ import { EventEmitter } from 'node:events'
12
+ import { cpus } from 'node:os'
13
+ import * as path from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
15
+
16
+ import getCallsites, { CallSite } from 'callsites'
17
+
18
+ import { ImplementationExport, ThreadsWorkerOptions, WorkerImplementation } from '../types/master'
19
+
20
+ interface WorkerGlobalScope {
21
+ addEventListener(eventName: string, listener: (event: Event) => void): void
22
+ postMessage(message: any, transferables?: any[]): void
23
+ removeEventListener(eventName: string, listener: (event: Event) => void): void
24
+ }
25
+
26
+ declare const __non_webpack_require__: typeof require
27
+ declare const self: WorkerGlobalScope
28
+
29
+ type WorkerEventName = 'error' | 'message'
30
+
31
+ let tsNodeAvailable: boolean | undefined
32
+
33
+ export const defaultPoolSize = cpus().length
34
+
35
+ function detectTsNode() {
36
+ if (typeof __non_webpack_require__ === 'function') {
37
+ // Webpack build: => No ts-node required or possible
38
+ return false
39
+ }
40
+ if (tsNodeAvailable) {
41
+ return tsNodeAvailable
42
+ }
43
+
44
+ try {
45
+ eval('require').resolve('ts-node')
46
+ tsNodeAvailable = true
47
+ } catch (error) {
48
+ if (error && error.code === 'MODULE_NOT_FOUND') {
49
+ tsNodeAvailable = false
50
+ } else {
51
+ // Re-throw
52
+ throw error
53
+ }
54
+ }
55
+ return tsNodeAvailable
56
+ }
57
+
58
+ function createTsNodeModule(scriptPath: string) {
59
+ const content = `
60
+ require("ts-node/register/transpile-only");
61
+ require(${JSON.stringify(scriptPath)});
62
+ `
63
+ return content
64
+ }
65
+
66
+ function rebaseScriptPath(scriptPath: string, ignoreRegex: RegExp) {
67
+ const parentCallSite = getCallsites().find((callsite: CallSite) => {
68
+ const filename = callsite.getFileName()
69
+ return Boolean(
70
+ filename && !filename.match(ignoreRegex) && !/[/\\]master[/\\]implementation/.test(filename) && !/^internal\/process/.test(filename),
71
+ )
72
+ })
73
+
74
+ const rawCallerPath = parentCallSite ? parentCallSite.getFileName() : null
75
+ let callerPath = rawCallerPath ? rawCallerPath : null
76
+ if (callerPath && callerPath.startsWith('file:')) {
77
+ callerPath = fileURLToPath(callerPath)
78
+ }
79
+ const rebasedScriptPath = callerPath ? path.join(path.dirname(callerPath), scriptPath) : scriptPath
80
+
81
+ return rebasedScriptPath
82
+ }
83
+
84
+ function resolveScriptPath(scriptPath: string, baseURL?: string | undefined) {
85
+ const makeRelative = (filePath: string) => {
86
+ // eval() hack is also webpack-related
87
+ return path.isAbsolute(filePath) ? filePath : path.join(baseURL || eval('__dirname'), filePath)
88
+ }
89
+
90
+ const workerFilePath =
91
+ typeof __non_webpack_require__ === 'function' ?
92
+ __non_webpack_require__.resolve(makeRelative(scriptPath))
93
+ : eval('require').resolve(makeRelative(rebaseScriptPath(scriptPath, /[/\\]worker_threads[/\\]/)))
94
+
95
+ return workerFilePath
96
+ }
97
+
98
+ function initWorkerThreadsWorker(): ImplementationExport {
99
+ // Webpack hack
100
+ const NativeWorker =
101
+ typeof __non_webpack_require__ === 'function' ? __non_webpack_require__('worker_threads').Worker : eval('require')('worker_threads').Worker
102
+
103
+ let allWorkers: Array<typeof NativeWorker> = []
104
+
105
+ class Worker extends NativeWorker {
106
+ private mappedEventListeners: WeakMap<EventListener, EventListener>
107
+
108
+ constructor(scriptPath: string, options?: ThreadsWorkerOptions & { fromSource: boolean }) {
109
+ const resolvedScriptPath = options && options.fromSource ? null : resolveScriptPath(scriptPath, (options || {})._baseURL)
110
+
111
+ if (!resolvedScriptPath) {
112
+ // `options.fromSource` is true
113
+ const sourceCode = scriptPath
114
+ super(sourceCode, { ...options, eval: true })
115
+ } else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
116
+ super(createTsNodeModule(resolvedScriptPath), { ...options, eval: true })
117
+ } else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
118
+ // See <https://github.com/andywer/threads-plugin/issues/17>
119
+ super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), options)
120
+ } else {
121
+ super(resolvedScriptPath, options)
122
+ }
123
+
124
+ this.mappedEventListeners = new WeakMap()
125
+ allWorkers.push(this)
126
+ }
127
+
128
+ addEventListener(eventName: string, rawListener: EventListener) {
129
+ const listener = (message: any) => {
130
+ rawListener({ data: message } as any)
131
+ }
132
+ this.mappedEventListeners.set(rawListener, listener)
133
+ this.on(eventName, listener)
134
+ }
135
+
136
+ removeEventListener(eventName: string, rawListener: EventListener) {
137
+ const listener = this.mappedEventListeners.get(rawListener) || rawListener
138
+ this.off(eventName, listener)
139
+ }
140
+ }
141
+
142
+ const terminateWorkersAndMaster = () => {
143
+ // we should terminate all workers and then gracefully shutdown self process
144
+ Promise.all(allWorkers.map((worker) => worker.terminate())).then(
145
+ () => process.exit(0),
146
+ () => process.exit(1),
147
+ )
148
+ allWorkers = []
149
+ }
150
+
151
+ // Take care to not leave orphaned processes behind. See #147.
152
+ process.on('SIGINT', () => terminateWorkersAndMaster())
153
+ process.on('SIGTERM', () => terminateWorkersAndMaster())
154
+
155
+ class BlobWorker extends Worker {
156
+ constructor(blob: Uint8Array, options?: ThreadsWorkerOptions) {
157
+ super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true })
158
+ }
159
+
160
+ static fromText(source: string, options?: ThreadsWorkerOptions): WorkerImplementation {
161
+ return new Worker(source, { ...options, fromSource: true }) as any
162
+ }
163
+ }
164
+
165
+ return {
166
+ blob: BlobWorker as any,
167
+ default: Worker as any,
168
+ }
169
+ }
170
+
171
+ function initTinyWorker(): ImplementationExport {
172
+ const TinyWorker = require('tiny-worker')
173
+
174
+ let allWorkers: Array<typeof TinyWorker> = []
175
+
176
+ class Worker extends TinyWorker {
177
+ private emitter: EventEmitter
178
+
179
+ constructor(scriptPath: string, options?: ThreadsWorkerOptions & { fromSource?: boolean }) {
180
+ // Need to apply a work-around for Windows or it will choke upon the absolute path
181
+ // (`Error [ERR_INVALID_PROTOCOL]: Protocol 'c:' not supported`)
182
+ const resolvedScriptPath =
183
+ options && options.fromSource ? null
184
+ : process.platform === 'win32' ? `file:///${resolveScriptPath(scriptPath).replaceAll('\\', '/')}`
185
+ : resolveScriptPath(scriptPath)
186
+
187
+ if (!resolvedScriptPath) {
188
+ // `options.fromSource` is true
189
+ const sourceCode = scriptPath
190
+ super(new Function(sourceCode), [], { esm: true })
191
+ } else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
192
+ super(new Function(createTsNodeModule(resolveScriptPath(scriptPath))), [], { esm: true })
193
+ } else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
194
+ // See <https://github.com/andywer/threads-plugin/issues/17>
195
+ super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), [], { esm: true })
196
+ } else {
197
+ super(resolvedScriptPath, [], { esm: true })
198
+ }
199
+
200
+ allWorkers.push(this)
201
+
202
+ this.emitter = new EventEmitter()
203
+ this.onerror = (error: Error) => this.emitter.emit('error', error)
204
+ this.onmessage = (message: MessageEvent) => this.emitter.emit('message', message)
205
+ }
206
+
207
+ addEventListener(eventName: WorkerEventName, listener: EventListener) {
208
+ this.emitter.addListener(eventName, listener)
209
+ }
210
+
211
+ removeEventListener(eventName: WorkerEventName, listener: EventListener) {
212
+ this.emitter.removeListener(eventName, listener)
213
+ }
214
+
215
+ terminate() {
216
+ allWorkers = allWorkers.filter((worker) => worker !== this)
217
+ return super.terminate()
218
+ }
219
+ }
220
+
221
+ const terminateWorkersAndMaster = () => {
222
+ // we should terminate all workers and then gracefully shutdown self process
223
+ Promise.all(allWorkers.map((worker) => worker.terminate())).then(
224
+ () => process.exit(0),
225
+ () => process.exit(1),
226
+ )
227
+ allWorkers = []
228
+ }
229
+
230
+ // Take care to not leave orphaned processes behind
231
+ // See <https://github.com/avoidwork/tiny-worker#faq>
232
+ process.on('SIGINT', () => terminateWorkersAndMaster())
233
+ process.on('SIGTERM', () => terminateWorkersAndMaster())
234
+
235
+ class BlobWorker extends Worker {
236
+ constructor(blob: Uint8Array, options?: ThreadsWorkerOptions) {
237
+ super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true })
238
+ }
239
+
240
+ static fromText(source: string, options?: ThreadsWorkerOptions): WorkerImplementation {
241
+ return new Worker(source, { ...options, fromSource: true }) as any
242
+ }
243
+ }
244
+
245
+ return {
246
+ blob: BlobWorker as any,
247
+ default: Worker as any,
248
+ }
249
+ }
250
+
251
+ let implementation: ImplementationExport
252
+ let isTinyWorker: boolean
253
+
254
+ function selectWorkerImplementation(): ImplementationExport {
255
+ try {
256
+ isTinyWorker = false
257
+ return initWorkerThreadsWorker()
258
+ } catch {
259
+ // tslint:disable-next-line no-console
260
+ console.debug('Node worker_threads not available. Trying to fall back to tiny-worker polyfill...')
261
+ isTinyWorker = true
262
+ return initTinyWorker()
263
+ }
264
+ }
265
+
266
+ export function getWorkerImplementation(): ImplementationExport {
267
+ if (!implementation) {
268
+ implementation = selectWorkerImplementation()
269
+ }
270
+ return implementation
271
+ }
272
+
273
+ export function isWorkerRuntime() {
274
+ if (isTinyWorker) {
275
+ return self !== undefined && self['postMessage'] ? true : false
276
+ } else {
277
+ // Webpack hack
278
+ const isMainThread =
279
+ typeof __non_webpack_require__ === 'function' ?
280
+ __non_webpack_require__('worker_threads').isMainThread
281
+ : eval('require')('worker_threads').isMainThread
282
+ return !isMainThread
283
+ }
284
+ }
@@ -0,0 +1,21 @@
1
+ /*
2
+ * This file is only a stub to make './implementation' resolve to the right module.
3
+ */
4
+
5
+ // We alias `src/master/implementation` to `src/master/implementation.browser` for web
6
+ // browsers already in the package.json, so if get here, it's safe to pass-through the
7
+ // node implementation
8
+
9
+ import * as BrowserImplementation from './implementation.browser'
10
+ import * as NodeImplementation from './implementation.node'
11
+
12
+ const runningInNode = typeof process !== 'undefined' && (process.arch as string) !== 'browser' && 'pid' in process
13
+ const implementation = runningInNode ? NodeImplementation : BrowserImplementation
14
+
15
+ /** Default size of pools. Depending on the platform the value might vary from device to device. */
16
+ export const defaultPoolSize = implementation.defaultPoolSize
17
+
18
+ export const getWorkerImplementation = implementation.getWorkerImplementation
19
+
20
+ /** Returns `true` if this code is currently running in a worker. */
21
+ export const isWorkerRuntime = implementation.isWorkerRuntime
@@ -0,0 +1,20 @@
1
+ // tslint:disable no-duplicate-imports
2
+ import type { BlobWorker as BlobWorkerClass } from '../types/master'
3
+ import { Worker as WorkerType } from '../types/master'
4
+ import { getWorkerImplementation } from './implementation'
5
+
6
+ export { FunctionThread, ModuleThread } from '../types/master'
7
+ export { Pool } from './pool'
8
+ export { spawn } from './spawn'
9
+ export { Thread } from './thread'
10
+
11
+ export type BlobWorker = typeof BlobWorkerClass
12
+ export type Worker = WorkerType
13
+
14
+ /** Separate class to spawn workers from source code blobs or strings. */
15
+ export const BlobWorker = getWorkerImplementation().blob
16
+
17
+ /** Worker implementation. Either web worker or a node.js Worker class. */
18
+ export const Worker = getWorkerImplementation().default
19
+
20
+ export { isWorkerRuntime } from './implementation'