bare-worker 4.1.9 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # bare-worker
2
2
 
3
- Higher-level worker threads for JavaScript.
3
+ Higher-level worker threads for JavaScript. Built on top of <https://github.com/holepunchto/bare-thread> and <https://github.com/holepunchto/bare-channel>, it provides a `Worker` class together with `MessageChannel` and `MessagePort` primitives for passing structured messages between threads.
4
4
 
5
5
  ```
6
6
  npm i bare-worker
@@ -22,6 +22,170 @@ if (Worker.isMainThread) {
22
22
  }
23
23
  ```
24
24
 
25
+ ## API
26
+
27
+ #### `const worker = new Worker(entry[, options])`
28
+
29
+ Spawn a new worker thread that loads and runs the module at `entry`. `Worker` extends `MessagePort`, so the returned instance can be used to exchange messages with the worker.
30
+
31
+ Options include:
32
+
33
+ ```js
34
+ options = {
35
+ workerData: null
36
+ }
37
+ ```
38
+
39
+ `workerData` is an arbitrary value cloned into the worker and made available there as `Worker.workerData`.
40
+
41
+ #### `worker.detached`
42
+
43
+ Whether the underlying message port has been detached for transfer. See `MessagePort`.
44
+
45
+ #### `await worker.terminate()`
46
+
47
+ Stop the worker as soon as possible. Returns a `Promise` that resolves with the worker's exit code once it has fully closed. Safe to call multiple times; subsequent calls resolve with the same exit code.
48
+
49
+ #### `event: 'online'`
50
+
51
+ Emitted when the worker thread has started executing.
52
+
53
+ #### `event: 'message'`
54
+
55
+ Emitted for each message sent from the worker with `parentPort.postMessage()`. The message value is passed to the listener.
56
+
57
+ #### `event: 'error'`
58
+
59
+ Emitted when an uncaught exception or unhandled rejection occurs in the worker. The error is passed to the listener and the worker exits with code `1`.
60
+
61
+ #### `event: 'exit'`
62
+
63
+ Emitted when the worker has stopped. The exit code is passed to the listener.
64
+
65
+ #### `Worker.isMainThread`
66
+
67
+ `true` when the current thread is the main thread, `false` when running inside a worker.
68
+
69
+ #### `Worker.parentPort`
70
+
71
+ Within a worker, the `MessagePort` connected to the parent thread. `null` on the main thread.
72
+
73
+ #### `Worker.workerData`
74
+
75
+ Within a worker, the `workerData` value passed when the worker was spawned. `null` on the main thread.
76
+
77
+ #### `Worker.MessageChannel`
78
+
79
+ The `MessageChannel` class. See below.
80
+
81
+ #### `Worker.MessagePort`
82
+
83
+ The `MessagePort` class. See below.
84
+
85
+ #### `Worker.BroadcastChannel`
86
+
87
+ The `BroadcastChannel` class. See below.
88
+
89
+ #### `Worker.preload(entry)`
90
+
91
+ Register the module at `entry` to be loaded in every worker before its own entry module runs. Preloads are inherited by nested workers.
92
+
93
+ #### `Worker.setEnvironmentData(key[, value])`
94
+
95
+ Set a value on the environment data shared with workers. The data is cloned into each worker as it is spawned. Omitting `value` removes `key`.
96
+
97
+ #### `Worker.getEnvironmentData(key)`
98
+
99
+ Get a value previously set with `Worker.setEnvironmentData()`.
100
+
101
+ ### `MessageChannel`
102
+
103
+ #### `const channel = new MessageChannel()`
104
+
105
+ Create a pair of connected message ports for two-way communication. A port may be transferred to a worker by including it in the transfer list of a `postMessage()` call.
106
+
107
+ #### `channel.port1`
108
+
109
+ One end of the channel, a `MessagePort`.
110
+
111
+ #### `channel.port2`
112
+
113
+ The other end of the channel, a `MessagePort`.
114
+
115
+ ### `MessagePort`
116
+
117
+ The endpoint of a message channel. `MessagePort` extends `EventEmitter`. A port begins receiving messages once it is started, which happens automatically when a `'message'` listener is added.
118
+
119
+ #### `port.detached`
120
+
121
+ Whether the port has been detached for transfer to another thread.
122
+
123
+ #### `port.postMessage(message[, transferList])`
124
+
125
+ Send `message` to the other end of the channel. `transferList` is an optional array of transferable objects, such as other `MessagePort` instances, whose ownership is moved to the receiving thread rather than cloned.
126
+
127
+ #### `port.start()`
128
+
129
+ Begin receiving messages on the port. Called automatically when a `'message'` listener is added, but may be called explicitly to start before any listener is attached.
130
+
131
+ #### `port.close()`
132
+
133
+ Close the port. No further messages will be sent or received.
134
+
135
+ #### `port.ref()`
136
+
137
+ Keep the underlying I/O resource referenced so it prevents the thread from exiting while the port is open.
138
+
139
+ #### `port.unref()`
140
+
141
+ Allow the thread to exit even while the port is open. Inflight writes are still awaited.
142
+
143
+ #### `port.hasRef()`
144
+
145
+ Whether the port is currently referenced.
146
+
147
+ #### `event: 'message'`
148
+
149
+ Emitted for each message received on the port. The message value is passed to the listener.
150
+
151
+ #### `event: 'close'`
152
+
153
+ Emitted when the port has closed.
154
+
155
+ ### `BroadcastChannel`
156
+
157
+ A named channel for broadcasting messages to every other `BroadcastChannel` with the same name across the entire worker tree, including the main thread and nested workers. `BroadcastChannel` extends `EventEmitter` and follows the semantics of the Web `BroadcastChannel`: a channel never receives its own messages, but every other channel sharing its name does.
158
+
159
+ ```js
160
+ const Worker = require('bare-worker')
161
+
162
+ const channel = new Worker.BroadcastChannel('updates')
163
+
164
+ channel.on('message', console.log)
165
+
166
+ channel.postMessage('Hello everyone')
167
+ ```
168
+
169
+ #### `const channel = new BroadcastChannel(name)`
170
+
171
+ Create a channel bound to `name`. All channels constructed with the same `name`, in any thread of the worker tree, are connected.
172
+
173
+ #### `channel.name`
174
+
175
+ The name the channel is bound to.
176
+
177
+ #### `channel.postMessage(message)`
178
+
179
+ Broadcast `message` to every other connected channel sharing this channel's name. The message is cloned; unlike `MessagePort.postMessage()`, transferring is not supported. Throws if the channel has been closed.
180
+
181
+ #### `channel.close()`
182
+
183
+ Close the channel, disconnecting it from the others. No further messages will be sent or received.
184
+
185
+ #### `event: 'message'`
186
+
187
+ Emitted for each message broadcast to the channel by another channel sharing its name. The message value is passed to the listener.
188
+
25
189
  ## License
26
190
 
27
191
  Apache-2.0
package/global.js CHANGED
@@ -1,4 +1,5 @@
1
- const { MessageChannel, MessagePort } = require('.')
1
+ const { MessageChannel, MessagePort, BroadcastChannel } = require('.')
2
2
 
3
3
  global.MessageChannel = MessageChannel
4
4
  global.MessagePort = MessagePort
5
+ global.BroadcastChannel = BroadcastChannel
package/index.js CHANGED
@@ -1,12 +1,15 @@
1
1
  const Thread = require('bare-thread')
2
2
  const Channel = require('bare-channel')
3
+ const Broadcast = require('bare-broadcast-channel')
3
4
  const WorkerState = require('./lib/worker-state')
4
5
  const MessageChannel = require('./lib/message-channel')
5
6
  const MessagePort = require('./lib/message-port')
7
+ const BroadcastChannel = require('./lib/broadcast-channel')
6
8
  const constants = require('./lib/constants')
7
9
 
8
10
  const preloads = new Map()
9
11
 
12
+ let broadcast = null
10
13
  let environmentData = new Map()
11
14
  let parentPort = null
12
15
  let workerData = null
@@ -16,11 +19,18 @@ if (WorkerState.parent) {
16
19
  preloads.set(entry, source)
17
20
  }
18
21
 
22
+ broadcast = WorkerState.parent.broadcast
19
23
  parentPort = WorkerState.parent.port
20
24
  workerData = WorkerState.parent.data
21
25
  environmentData = WorkerState.parent.environmentData
26
+ } else {
27
+ broadcast = new Broadcast()
22
28
  }
23
29
 
30
+ // Every named broadcast channel on this thread connects to the same underlying
31
+ // channel, which is shared across the whole worker tree.
32
+ BroadcastChannel._channel = broadcast
33
+
24
34
  const worker = Thread.prepare(require.resolve('./lib/worker-thread'), { shared: true })
25
35
 
26
36
  module.exports = exports = class Worker extends MessagePort {
@@ -37,6 +47,7 @@ module.exports = exports = class Worker extends MessagePort {
37
47
  data: {
38
48
  source: Thread.prepare(entry, { shared: true }),
39
49
  channel: channel.handle,
50
+ broadcast: broadcast.handle,
40
51
  data: workerData,
41
52
  preloads,
42
53
  environmentData
@@ -97,6 +108,7 @@ exports.Worker = exports
97
108
 
98
109
  exports.MessageChannel = MessageChannel
99
110
  exports.MessagePort = MessagePort
111
+ exports.BroadcastChannel = BroadcastChannel
100
112
 
101
113
  exports.parentPort = parentPort
102
114
  exports.workerData = workerData
@@ -0,0 +1,103 @@
1
+ const EventEmitter = require('bare-events')
2
+ const errors = require('./errors')
3
+
4
+ module.exports = exports = class BroadcastChannel extends EventEmitter {
5
+ constructor(name) {
6
+ super()
7
+
8
+ this.name = String(name)
9
+
10
+ this._port = exports._channel.connect()
11
+ this._inflight = 0
12
+ this._closed = false
13
+ this._reading = false
14
+
15
+ // The read loop terminates once the last peer leaves, so restart it when
16
+ // peers reappear to resume receiving broadcasts.
17
+ this._port.on('peers', this._onpeers.bind(this))
18
+
19
+ // An idle channel without listeners should not hold the thread alive.
20
+ this._port.unref()
21
+
22
+ this.on('newListener', this._onnewlistener).on('removeListener', this._onremovelistener)
23
+
24
+ this._read()
25
+ }
26
+
27
+ postMessage(message) {
28
+ if (this._closed) {
29
+ throw errors.CHANNEL_CLOSED('BroadcastChannel is closed')
30
+ }
31
+
32
+ this._write(message)
33
+ }
34
+
35
+ close() {
36
+ if (this._closed) return
37
+
38
+ this._closed = true
39
+
40
+ this._port.close()
41
+ }
42
+
43
+ [Symbol.for('bare.inspect')]() {
44
+ return {
45
+ __proto__: { constructor: BroadcastChannel },
46
+
47
+ name: this.name
48
+ }
49
+ }
50
+
51
+ async _write(message) {
52
+ this._inflight++
53
+ this._port.ref()
54
+
55
+ try {
56
+ await this._port.write({ name: this.name, message })
57
+ } finally {
58
+ this._inflight--
59
+ this._unref()
60
+ }
61
+ }
62
+
63
+ _unref() {
64
+ if (this._inflight === 0 && this.listenerCount('message') === 0) this._port.unref()
65
+ }
66
+
67
+ async _read() {
68
+ if (this._reading) return
69
+
70
+ this._reading = true
71
+
72
+ try {
73
+ for await (const data of this._port) {
74
+ // The channel shares a single underlying broadcast channel across the
75
+ // whole worker tree, so messages are tagged with their channel name and
76
+ // filtered on read.
77
+ if (data.name === this.name) this.emit('message', data.message)
78
+ }
79
+ } finally {
80
+ this._reading = false
81
+ }
82
+ }
83
+
84
+ _onpeers(peers) {
85
+ if (peers > 0 && !this._closed) this._read()
86
+ }
87
+
88
+ _onnewlistener(name) {
89
+ if (name !== 'message') return
90
+
91
+ if (this.listenerCount('message') === 0) this._port.ref()
92
+ }
93
+
94
+ _onremovelistener(name) {
95
+ if (name !== 'message') return
96
+
97
+ if (this.listenerCount('message') === 0) this._unref()
98
+ }
99
+
100
+ // The underlying broadcast channel shared by every named channel on this
101
+ // thread. Set when the worker module is loaded.
102
+ static _channel = null
103
+ }
package/lib/errors.js CHANGED
@@ -14,4 +14,8 @@ module.exports = class WorkerError extends Error {
14
14
  static ALREADY_STARTED(msg) {
15
15
  return new WorkerError(msg, WorkerError.ALREADY_STARTED)
16
16
  }
17
+
18
+ static CHANNEL_CLOSED(msg) {
19
+ return new WorkerError(msg, WorkerError.CHANNEL_CLOSED)
20
+ }
17
21
  }
@@ -84,6 +84,10 @@ module.exports = exports = class MessagePort extends EventEmitter {
84
84
  return handle
85
85
  }
86
86
 
87
+ static get [Symbol.for('bare.interface')]() {
88
+ return Symbol.for('bare.worker.message-port')
89
+ }
90
+
87
91
  static [Symbol.for('bare.attach')](handle) {
88
92
  return new MessagePort(Channel.from(handle, { interfaces: [MessagePort] }))
89
93
  }
@@ -1,4 +1,5 @@
1
1
  const Channel = require('bare-channel')
2
+ const Broadcast = require('bare-broadcast-channel')
2
3
  const MessagePort = require('./message-port')
3
4
 
4
5
  const state = Symbol.for('bare.worker.state')
@@ -6,7 +7,7 @@ const kind = Symbol.for('bare.worker.state.kind')
6
7
 
7
8
  module.exports = class WorkerState {
8
9
  static get [kind]() {
9
- return 0 // Compatibility version
10
+ return 1 // Compatibility version
10
11
  }
11
12
 
12
13
  static get parent() {
@@ -20,12 +21,20 @@ module.exports = class WorkerState {
20
21
  }
21
22
 
22
23
  constructor() {
23
- const { source, preloads, data, environmentData, channel: handle } = Bare.Thread.self.data
24
+ const {
25
+ source,
26
+ preloads,
27
+ data,
28
+ environmentData,
29
+ channel: handle,
30
+ broadcast: broadcastHandle
31
+ } = Bare.Thread.self.data
24
32
 
25
33
  this.source = source
26
34
  this.preloads = preloads
27
35
  this.data = Bare.Thread.self.data = data
28
36
  this.environmentData = environmentData
37
+ this.broadcast = Broadcast.from(broadcastHandle)
29
38
 
30
39
  const channel = Channel.from(handle, { interfaces: [MessagePort] })
31
40
 
@@ -8,11 +8,17 @@ Bare.on('newListener', onnewlistener)
8
8
  .on('uncaughtException', onerror)
9
9
  .on('unhandledRejection', onerror)
10
10
 
11
+ const cache = Object.create(null)
12
+
11
13
  for (const [, source] of state.preloads) {
12
- Module.load(new URL(`bare:/worker/preload-${Math.random().toString(16).slice(2)}.bundle`), source)
14
+ Module.load(
15
+ new URL(`bare:/worker/preload-${Math.random().toString(16).slice(2)}.bundle`),
16
+ source,
17
+ { cache }
18
+ )
13
19
  }
14
20
 
15
- Module.load(new URL('bare:/worker.bundle'), state.source)
21
+ Module.load(new URL('bare:/worker.bundle'), state.source, { cache })
16
22
 
17
23
  function onnewlistener(name, fn) {
18
24
  if (fn === onremovelistener || fn === onerror) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-worker",
3
- "version": "4.1.9",
3
+ "version": "4.2.1",
4
4
  "description": "Higher-level worker threads for JavaScript",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -9,7 +9,8 @@
9
9
  "./constants": "./lib/constants.js",
10
10
  "./errors": "./lib/errors.js",
11
11
  "./message-channel": "./lib/message-channel.js",
12
- "./message-port": "./lib/message-port.js"
12
+ "./message-port": "./lib/message-port.js",
13
+ "./broadcast-channel": "./lib/broadcast-channel.js"
13
14
  },
14
15
  "files": [
15
16
  "index.js",
@@ -32,9 +33,10 @@
32
33
  },
33
34
  "homepage": "https://github.com/holepunchto/bare-worker#readme",
34
35
  "dependencies": {
36
+ "bare-broadcast-channel": "^0.2.0",
35
37
  "bare-channel": "^5.1.5",
36
38
  "bare-events": "^2.2.1",
37
- "bare-module": "^6.0.1",
39
+ "bare-module": "^6.4.0",
38
40
  "bare-thread": "^1.2.2"
39
41
  },
40
42
  "devDependencies": {