thread-stream 3.0.2 → 3.2.0
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/.claude/settings.local.json +15 -0
- package/.github/workflows/ci.yml +2 -2
- package/CLAUDE.md +64 -0
- package/README.md +2 -1
- package/index.js +41 -13
- package/lib/indexes.js +3 -1
- package/lib/worker.js +25 -9
- package/package.json +3 -2
- package/test/base.test.js +67 -82
- package/test/message-without-code.js +19 -0
- package/test/syntax-error.mjs +2 -0
- package/test/watch-mode.test.js +28 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -62,7 +62,7 @@ jobs:
|
|
|
62
62
|
run: npm run test:ci
|
|
63
63
|
|
|
64
64
|
- name: Coveralls Parallel
|
|
65
|
-
uses: coverallsapp/github-action@v2.
|
|
65
|
+
uses: coverallsapp/github-action@v2.3.0
|
|
66
66
|
with:
|
|
67
67
|
github-token: ${{ secrets.github_token }}
|
|
68
68
|
parallel: true
|
|
@@ -73,7 +73,7 @@ jobs:
|
|
|
73
73
|
runs-on: ubuntu-latest
|
|
74
74
|
steps:
|
|
75
75
|
- name: Coveralls Finished
|
|
76
|
-
uses: coverallsapp/github-action@v2.
|
|
76
|
+
uses: coverallsapp/github-action@v2.3.0
|
|
77
77
|
with:
|
|
78
78
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
79
79
|
parallel-finished: true
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
thread-stream is a library for streaming data to a Node.js Worker Thread. It uses SharedArrayBuffer and Atomics for efficient inter-thread communication, enabling high-performance data streaming to worker threads.
|
|
8
|
+
|
|
9
|
+
## Build & Test Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm test # Run linting (standard), type checking, and all tests
|
|
13
|
+
npm run build # Type check only (tsc --noEmit)
|
|
14
|
+
npm run test:ci # CI-specific test run
|
|
15
|
+
|
|
16
|
+
# Run a single test file
|
|
17
|
+
node --test test/<filename>.test.js
|
|
18
|
+
node --test test/<filename>.test.ts # For TypeScript tests
|
|
19
|
+
|
|
20
|
+
# Lint
|
|
21
|
+
npx standard
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
### Core Components
|
|
27
|
+
|
|
28
|
+
- **index.js**: Main `ThreadStream` class extending EventEmitter. Manages shared memory buffers, worker lifecycle, and provides stream-like write/flush/end API.
|
|
29
|
+
|
|
30
|
+
- **lib/worker.js**: Runs inside the Worker Thread. Loads the user-provided destination module, reads from shared buffer, and writes to the destination stream.
|
|
31
|
+
|
|
32
|
+
- **lib/indexes.js**: Defines shared buffer index constants (`WRITE_INDEX`, `READ_INDEX`) used for Atomics-based synchronization.
|
|
33
|
+
|
|
34
|
+
- **lib/wait.js**: Provides `wait()` and `waitDiff()` utilities for async waiting on Atomics state changes with exponential backoff.
|
|
35
|
+
|
|
36
|
+
### Shared Memory Communication
|
|
37
|
+
|
|
38
|
+
The main thread and worker communicate via two SharedArrayBuffers:
|
|
39
|
+
1. **stateBuf**: Int32Array for READ_INDEX and WRITE_INDEX positions
|
|
40
|
+
2. **dataBuf**: Buffer for actual string data (default 4MB)
|
|
41
|
+
|
|
42
|
+
Write flow: Main thread writes to dataBuf, updates WRITE_INDEX, worker reads data between READ_INDEX and WRITE_INDEX, updates READ_INDEX when consumed.
|
|
43
|
+
|
|
44
|
+
### Worker Module Interface
|
|
45
|
+
|
|
46
|
+
User-provided worker modules must export an async function that receives `workerData` and returns a writable stream:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
async function run(opts) {
|
|
50
|
+
const stream = fs.createWriteStream(opts.dest)
|
|
51
|
+
await once(stream, 'open')
|
|
52
|
+
return stream
|
|
53
|
+
}
|
|
54
|
+
module.exports = run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Sync vs Async Modes
|
|
58
|
+
|
|
59
|
+
- `sync: true`: Blocking writes using flushSync, waits for worker to consume
|
|
60
|
+
- `sync: false` (default): Non-blocking writes with drain events when buffer fills
|
|
61
|
+
|
|
62
|
+
## Code Style
|
|
63
|
+
|
|
64
|
+
Uses [Standard](https://standardjs.com/) for linting. Test files in `test/ts/**/*` and `test/syntax-error.mjs` are excluded from linting.
|
package/README.md
CHANGED
|
@@ -87,7 +87,8 @@ This module works with `yarn` in PnP (plug'n play) mode too!
|
|
|
87
87
|
### Emit events
|
|
88
88
|
|
|
89
89
|
You can emit events on the ThreadStream from your worker using [`worker.parentPort.postMessage()`](https://nodejs.org/api/worker_threads.html#workerparentport).
|
|
90
|
-
|
|
90
|
+
Messages that do not carry a thread-stream protocol `code` are ignored.
|
|
91
|
+
For custom events, the message (JSON object) must have the following data structure:
|
|
91
92
|
|
|
92
93
|
```js
|
|
93
94
|
parentPort.postMessage({
|
package/index.js
CHANGED
|
@@ -8,7 +8,8 @@ const { pathToFileURL } = require('url')
|
|
|
8
8
|
const { wait } = require('./lib/wait')
|
|
9
9
|
const {
|
|
10
10
|
WRITE_INDEX,
|
|
11
|
-
READ_INDEX
|
|
11
|
+
READ_INDEX,
|
|
12
|
+
SEQ_INDEX
|
|
12
13
|
} = require('./lib/indexes')
|
|
13
14
|
const buffer = require('buffer')
|
|
14
15
|
const assert = require('assert')
|
|
@@ -18,6 +19,13 @@ const kImpl = Symbol('kImpl')
|
|
|
18
19
|
// V8 limit for string size
|
|
19
20
|
const MAX_STRING = buffer.constants.MAX_STRING_LENGTH
|
|
20
21
|
|
|
22
|
+
function updateState (stream, fn) {
|
|
23
|
+
Atomics.add(stream[kImpl].state, SEQ_INDEX, 1)
|
|
24
|
+
fn()
|
|
25
|
+
Atomics.add(stream[kImpl].state, SEQ_INDEX, 1)
|
|
26
|
+
Atomics.notify(stream[kImpl].state, SEQ_INDEX)
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
class FakeWeakRef {
|
|
22
30
|
constructor (value) {
|
|
23
31
|
this._value = value
|
|
@@ -120,8 +128,11 @@ function nextFlush (stream) {
|
|
|
120
128
|
return
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
updateState(stream, () => {
|
|
132
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
133
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
134
|
+
})
|
|
135
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
125
136
|
|
|
126
137
|
// Find a toWrite length that fits the buffer
|
|
127
138
|
// it must exists as the buffer is at least 4 bytes length
|
|
@@ -141,8 +152,11 @@ function nextFlush (stream) {
|
|
|
141
152
|
return
|
|
142
153
|
}
|
|
143
154
|
stream.flush(() => {
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
updateState(stream, () => {
|
|
156
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
157
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
158
|
+
})
|
|
159
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
146
160
|
nextFlush(stream)
|
|
147
161
|
})
|
|
148
162
|
} else {
|
|
@@ -160,6 +174,12 @@ function onWorkerMessage (msg) {
|
|
|
160
174
|
return
|
|
161
175
|
}
|
|
162
176
|
|
|
177
|
+
// Node.js watch mode may send internal worker messages that do not
|
|
178
|
+
// participate in thread-stream's worker protocol.
|
|
179
|
+
if (msg?.code == null) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
163
183
|
switch (msg.code) {
|
|
164
184
|
case 'READY':
|
|
165
185
|
// Replace the FakeWeakRef with a
|
|
@@ -401,8 +421,9 @@ function write (stream, data, cb) {
|
|
|
401
421
|
const current = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
402
422
|
const length = Buffer.byteLength(data)
|
|
403
423
|
stream[kImpl].data.write(data, current)
|
|
404
|
-
|
|
405
|
-
|
|
424
|
+
updateState(stream, () => {
|
|
425
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, current + length)
|
|
426
|
+
})
|
|
406
427
|
cb()
|
|
407
428
|
return true
|
|
408
429
|
}
|
|
@@ -419,9 +440,10 @@ function end (stream) {
|
|
|
419
440
|
let readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
|
|
420
441
|
|
|
421
442
|
// process._rawDebug('writing index')
|
|
422
|
-
|
|
443
|
+
updateState(stream, () => {
|
|
444
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, -1)
|
|
445
|
+
})
|
|
423
446
|
// process._rawDebug(`(end) readIndex (${Atomics.load(stream.state, READ_INDEX)}) writeIndex (${Atomics.load(stream.state, WRITE_INDEX)})`)
|
|
424
|
-
Atomics.notify(stream[kImpl].state, WRITE_INDEX)
|
|
425
447
|
|
|
426
448
|
// Wait for the process to complete
|
|
427
449
|
let spins = 0
|
|
@@ -466,8 +488,11 @@ function writeSync (stream) {
|
|
|
466
488
|
let leftover = stream[kImpl].data.length - writeIndex
|
|
467
489
|
if (leftover === 0) {
|
|
468
490
|
flushSync(stream)
|
|
469
|
-
|
|
470
|
-
|
|
491
|
+
updateState(stream, () => {
|
|
492
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
493
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
494
|
+
})
|
|
495
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
471
496
|
continue
|
|
472
497
|
} else if (leftover < 0) {
|
|
473
498
|
// stream should never happen
|
|
@@ -483,8 +508,11 @@ function writeSync (stream) {
|
|
|
483
508
|
} else {
|
|
484
509
|
// multi-byte utf-8
|
|
485
510
|
flushSync(stream)
|
|
486
|
-
|
|
487
|
-
|
|
511
|
+
updateState(stream, () => {
|
|
512
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
513
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
514
|
+
})
|
|
515
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
488
516
|
|
|
489
517
|
// Find a toWrite length that fits the buffer
|
|
490
518
|
// it must exists as the buffer is at least 4 bytes length
|
package/lib/indexes.js
CHANGED
package/lib/worker.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { realImport, realRequire } = require('real-require')
|
|
4
4
|
const { workerData, parentPort } = require('worker_threads')
|
|
5
|
-
const { WRITE_INDEX, READ_INDEX } = require('./indexes')
|
|
5
|
+
const { WRITE_INDEX, READ_INDEX, SEQ_INDEX } = require('./indexes')
|
|
6
6
|
const { waitDiff } = require('./wait')
|
|
7
7
|
|
|
8
8
|
const {
|
|
@@ -48,7 +48,11 @@ async function start () {
|
|
|
48
48
|
// When bundled with pkg, an undefined error is thrown when called with realImport
|
|
49
49
|
// When bundled with pkg and using node v20, an ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING error is thrown when called with realImport
|
|
50
50
|
// More info at: https://github.com/pinojs/thread-stream/issues/143
|
|
51
|
-
|
|
51
|
+
try {
|
|
52
|
+
worker = realRequire(decodeURIComponent(filename.replace(process.platform === 'win32' ? 'file:///' : 'file://', '')))
|
|
53
|
+
} catch {
|
|
54
|
+
throw error
|
|
55
|
+
}
|
|
52
56
|
} else {
|
|
53
57
|
throw error
|
|
54
58
|
}
|
|
@@ -97,18 +101,30 @@ start().then(function () {
|
|
|
97
101
|
process.nextTick(run)
|
|
98
102
|
})
|
|
99
103
|
|
|
104
|
+
function readState () {
|
|
105
|
+
while (true) {
|
|
106
|
+
const seq = Atomics.load(state, SEQ_INDEX)
|
|
107
|
+
|
|
108
|
+
if ((seq & 1) !== 0) {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const current = Atomics.load(state, READ_INDEX)
|
|
113
|
+
const end = Atomics.load(state, WRITE_INDEX)
|
|
114
|
+
|
|
115
|
+
if (seq === Atomics.load(state, SEQ_INDEX)) {
|
|
116
|
+
return { current, end, seq }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
100
121
|
function run () {
|
|
101
|
-
const current =
|
|
102
|
-
const end = Atomics.load(state, WRITE_INDEX)
|
|
122
|
+
const { current, end, seq } = readState()
|
|
103
123
|
|
|
104
124
|
// process._rawDebug(`pre state ${current} ${end}`)
|
|
105
125
|
|
|
106
126
|
if (end === current) {
|
|
107
|
-
|
|
108
|
-
waitDiff(state, READ_INDEX, end, Infinity, run)
|
|
109
|
-
} else {
|
|
110
|
-
waitDiff(state, WRITE_INDEX, end, Infinity, run)
|
|
111
|
-
}
|
|
127
|
+
waitDiff(state, SEQ_INDEX, seq, Infinity, run)
|
|
112
128
|
return
|
|
113
129
|
}
|
|
114
130
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thread-stream",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "A streaming way to send data to a Node.js Worker Thread",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"standard": {
|
|
36
36
|
"ignore": [
|
|
37
|
-
"test/ts/**/*"
|
|
37
|
+
"test/ts/**/*",
|
|
38
|
+
"test/syntax-error.mjs"
|
|
38
39
|
]
|
|
39
40
|
},
|
|
40
41
|
"repository": {
|
package/test/base.test.js
CHANGED
|
@@ -8,9 +8,19 @@ const ThreadStream = require('..')
|
|
|
8
8
|
const { MessageChannel } = require('worker_threads')
|
|
9
9
|
const { once } = require('events')
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
function readFileAsync (path) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
readFile(path, 'utf8', (err, data) => {
|
|
14
|
+
if (err) {
|
|
15
|
+
reject(err)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
resolve(data)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
}
|
|
13
22
|
|
|
23
|
+
test('base sync=true', async function (t) {
|
|
14
24
|
const dest = file()
|
|
15
25
|
const stream = new ThreadStream({
|
|
16
26
|
filename: join(__dirname, 'to-file.js'),
|
|
@@ -18,24 +28,12 @@ test('base sync=true', function (t) {
|
|
|
18
28
|
sync: true
|
|
19
29
|
})
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
const finish = once(stream, 'finish')
|
|
32
|
+
const close = once(stream, 'close')
|
|
22
33
|
|
|
34
|
+
t.same(stream.writableObjectMode, false)
|
|
23
35
|
t.same(stream.writableFinished, false)
|
|
24
|
-
stream.on('finish', () => {
|
|
25
|
-
t.same(stream.writableFinished, true)
|
|
26
|
-
readFile(dest, 'utf8', (err, data) => {
|
|
27
|
-
t.error(err)
|
|
28
|
-
t.equal(data, 'hello world\nsomething else\n')
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
36
|
t.same(stream.closed, false)
|
|
33
|
-
stream.on('close', () => {
|
|
34
|
-
t.same(stream.closed, true)
|
|
35
|
-
t.notOk(stream.writable)
|
|
36
|
-
t.pass('close emitted')
|
|
37
|
-
})
|
|
38
|
-
|
|
39
37
|
t.same(stream.writableNeedDrain, false)
|
|
40
38
|
t.ok(stream.write('hello world\n'))
|
|
41
39
|
t.ok(stream.write('something else\n'))
|
|
@@ -44,11 +42,19 @@ test('base sync=true', function (t) {
|
|
|
44
42
|
t.same(stream.writableEnded, false)
|
|
45
43
|
stream.end()
|
|
46
44
|
t.same(stream.writableEnded, true)
|
|
47
|
-
})
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
t.
|
|
46
|
+
await finish
|
|
47
|
+
t.same(stream.writableFinished, true)
|
|
48
|
+
|
|
49
|
+
await close
|
|
50
|
+
t.same(stream.closed, true)
|
|
51
|
+
t.notOk(stream.writable)
|
|
52
|
+
|
|
53
|
+
const data = await readFileAsync(dest)
|
|
54
|
+
t.equal(data, 'hello world\nsomething else\n')
|
|
55
|
+
})
|
|
51
56
|
|
|
57
|
+
test('overflow sync=true', async function (t) {
|
|
52
58
|
const dest = file()
|
|
53
59
|
const stream = new ThreadStream({
|
|
54
60
|
bufferSize: 128,
|
|
@@ -57,9 +63,9 @@ test('overflow sync=true', function (t) {
|
|
|
57
63
|
sync: true
|
|
58
64
|
})
|
|
59
65
|
|
|
66
|
+
const close = once(stream, 'close')
|
|
60
67
|
let count = 0
|
|
61
68
|
|
|
62
|
-
// Write 10 chars, 20 times
|
|
63
69
|
function write () {
|
|
64
70
|
if (count++ === 20) {
|
|
65
71
|
stream.end()
|
|
@@ -67,25 +73,17 @@ test('overflow sync=true', function (t) {
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
stream.write('aaaaaaaaaa')
|
|
70
|
-
// do not wait for drain event
|
|
71
76
|
setImmediate(write)
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
write()
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
stream.on('close', () => {
|
|
81
|
-
readFile(dest, 'utf8', (err, data) => {
|
|
82
|
-
t.error(err)
|
|
83
|
-
t.equal(data.length, 200)
|
|
84
|
-
})
|
|
85
|
-
})
|
|
81
|
+
await close
|
|
82
|
+
const data = await readFileAsync(dest)
|
|
83
|
+
t.equal(data.length, 200)
|
|
86
84
|
})
|
|
87
85
|
|
|
88
|
-
test('overflow sync=false', function (t) {
|
|
86
|
+
test('overflow sync=false', async function (t) {
|
|
89
87
|
const dest = file()
|
|
90
88
|
const stream = new ThreadStream({
|
|
91
89
|
bufferSize: 128,
|
|
@@ -94,14 +92,13 @@ test('overflow sync=false', function (t) {
|
|
|
94
92
|
sync: false
|
|
95
93
|
})
|
|
96
94
|
|
|
95
|
+
const close = once(stream, 'close')
|
|
97
96
|
let count = 0
|
|
98
97
|
|
|
99
98
|
t.same(stream.writableNeedDrain, false)
|
|
100
99
|
|
|
101
|
-
// Write 10 chars, 20 times
|
|
102
100
|
function write () {
|
|
103
101
|
if (count++ === 20) {
|
|
104
|
-
t.pass('end sent')
|
|
105
102
|
stream.end()
|
|
106
103
|
return
|
|
107
104
|
}
|
|
@@ -109,7 +106,6 @@ test('overflow sync=false', function (t) {
|
|
|
109
106
|
if (!stream.write('aaaaaaaaaa')) {
|
|
110
107
|
t.same(stream.writableNeedDrain, true)
|
|
111
108
|
}
|
|
112
|
-
// do not wait for drain event
|
|
113
109
|
setImmediate(write)
|
|
114
110
|
}
|
|
115
111
|
|
|
@@ -117,24 +113,15 @@ test('overflow sync=false', function (t) {
|
|
|
117
113
|
|
|
118
114
|
stream.on('drain', () => {
|
|
119
115
|
t.same(stream.writableNeedDrain, false)
|
|
120
|
-
t.pass('drain')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
stream.on('finish', () => {
|
|
124
|
-
t.pass('finish emitted')
|
|
125
116
|
})
|
|
126
117
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
t.equal(data.length, 200)
|
|
131
|
-
t.end()
|
|
132
|
-
})
|
|
133
|
-
})
|
|
118
|
+
await close
|
|
119
|
+
const data = await readFileAsync(dest)
|
|
120
|
+
t.equal(data.length, 200)
|
|
134
121
|
})
|
|
135
122
|
|
|
136
123
|
test('over the bufferSize at startup', function (t) {
|
|
137
|
-
t.plan(
|
|
124
|
+
t.plan(5)
|
|
138
125
|
|
|
139
126
|
const dest = file()
|
|
140
127
|
const stream = new ThreadStream({
|
|
@@ -151,10 +138,6 @@ test('over the bufferSize at startup', function (t) {
|
|
|
151
138
|
})
|
|
152
139
|
})
|
|
153
140
|
|
|
154
|
-
stream.on('close', () => {
|
|
155
|
-
t.pass('close emitted')
|
|
156
|
-
})
|
|
157
|
-
|
|
158
141
|
t.ok(stream.write('hello'))
|
|
159
142
|
t.ok(stream.write(' world\n'))
|
|
160
143
|
t.ok(stream.write('something else\n'))
|
|
@@ -163,7 +146,7 @@ test('over the bufferSize at startup', function (t) {
|
|
|
163
146
|
})
|
|
164
147
|
|
|
165
148
|
test('over the bufferSize at startup (async)', function (t) {
|
|
166
|
-
t.plan(
|
|
149
|
+
t.plan(5)
|
|
167
150
|
|
|
168
151
|
const dest = file()
|
|
169
152
|
const stream = new ThreadStream({
|
|
@@ -185,13 +168,11 @@ test('over the bufferSize at startup (async)', function (t) {
|
|
|
185
168
|
t.equal(data, 'hello world\nsomething else\n')
|
|
186
169
|
})
|
|
187
170
|
})
|
|
188
|
-
|
|
189
|
-
stream.on('close', () => {
|
|
190
|
-
t.pass('close emitted')
|
|
191
|
-
})
|
|
192
171
|
})
|
|
193
172
|
|
|
194
173
|
test('flushSync sync=false', function (t) {
|
|
174
|
+
t.plan(2)
|
|
175
|
+
|
|
195
176
|
const dest = file()
|
|
196
177
|
const stream = new ThreadStream({
|
|
197
178
|
bufferSize: 128,
|
|
@@ -200,32 +181,26 @@ test('flushSync sync=false', function (t) {
|
|
|
200
181
|
sync: false
|
|
201
182
|
})
|
|
202
183
|
|
|
203
|
-
stream.on('
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
184
|
+
stream.on('ready', () => {
|
|
185
|
+
for (let count = 0; count < 20; count++) {
|
|
186
|
+
stream.write('aaaaaaaaaa')
|
|
187
|
+
}
|
|
207
188
|
|
|
208
|
-
|
|
209
|
-
|
|
189
|
+
stream.flushSync()
|
|
190
|
+
setImmediate(() => {
|
|
191
|
+
stream.end()
|
|
192
|
+
})
|
|
210
193
|
})
|
|
211
194
|
|
|
212
|
-
stream.on('
|
|
195
|
+
stream.on('finish', () => {
|
|
213
196
|
readFile(dest, 'utf8', (err, data) => {
|
|
214
197
|
t.error(err)
|
|
215
198
|
t.equal(data.length, 200)
|
|
216
|
-
t.end()
|
|
217
199
|
})
|
|
218
200
|
})
|
|
219
|
-
|
|
220
|
-
for (let count = 0; count < 20; count++) {
|
|
221
|
-
stream.write('aaaaaaaaaa')
|
|
222
|
-
}
|
|
223
|
-
stream.flushSync()
|
|
224
201
|
})
|
|
225
202
|
|
|
226
203
|
test('pass down MessagePorts', async function (t) {
|
|
227
|
-
t.plan(3)
|
|
228
|
-
|
|
229
204
|
const { port1, port2 } = new MessageChannel()
|
|
230
205
|
const stream = new ThreadStream({
|
|
231
206
|
filename: join(__dirname, 'port.js'),
|
|
@@ -235,6 +210,7 @@ test('pass down MessagePorts', async function (t) {
|
|
|
235
210
|
},
|
|
236
211
|
sync: false
|
|
237
212
|
})
|
|
213
|
+
|
|
238
214
|
t.teardown(() => {
|
|
239
215
|
stream.end()
|
|
240
216
|
})
|
|
@@ -243,13 +219,10 @@ test('pass down MessagePorts', async function (t) {
|
|
|
243
219
|
t.ok(stream.write('something else\n'))
|
|
244
220
|
|
|
245
221
|
const [strings] = await once(port2, 'message')
|
|
246
|
-
|
|
247
222
|
t.equal(strings, 'hello world\nsomething else\n')
|
|
248
223
|
})
|
|
249
224
|
|
|
250
|
-
test('destroy does not error', function (t) {
|
|
251
|
-
t.plan(5)
|
|
252
|
-
|
|
225
|
+
test('destroy does not error', async function (t) {
|
|
253
226
|
const dest = file()
|
|
254
227
|
const stream = new ThreadStream({
|
|
255
228
|
filename: join(__dirname, 'to-file.js'),
|
|
@@ -258,16 +231,28 @@ test('destroy does not error', function (t) {
|
|
|
258
231
|
})
|
|
259
232
|
|
|
260
233
|
stream.on('ready', () => {
|
|
261
|
-
t.pass('ready emitted')
|
|
262
234
|
stream.worker.terminate()
|
|
263
235
|
})
|
|
264
236
|
|
|
265
|
-
stream
|
|
266
|
-
|
|
237
|
+
const [err] = await once(stream, 'error')
|
|
238
|
+
t.equal(err.message, 'the worker thread exited')
|
|
239
|
+
|
|
240
|
+
await new Promise((resolve) => {
|
|
267
241
|
stream.flush((err) => {
|
|
268
242
|
t.equal(err.message, 'the worker has exited')
|
|
243
|
+
resolve()
|
|
269
244
|
})
|
|
270
|
-
t.doesNotThrow(() => stream.flushSync())
|
|
271
|
-
t.doesNotThrow(() => stream.end())
|
|
272
245
|
})
|
|
246
|
+
|
|
247
|
+
t.doesNotThrow(() => stream.flushSync())
|
|
248
|
+
t.doesNotThrow(() => stream.end())
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test('syntax error', async function (t) {
|
|
252
|
+
const stream = new ThreadStream({
|
|
253
|
+
filename: join(__dirname, 'syntax-error.mjs')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const [err] = await once(stream, 'error')
|
|
257
|
+
t.equal(err.message, 'Unexpected end of input')
|
|
273
258
|
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Writable } = require('stream')
|
|
4
|
+
const { parentPort } = require('worker_threads')
|
|
5
|
+
|
|
6
|
+
async function run () {
|
|
7
|
+
parentPort.postMessage({
|
|
8
|
+
internal: 'watch-mode'
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
return new Writable({
|
|
12
|
+
autoDestroy: true,
|
|
13
|
+
write (chunk, enc, cb) {
|
|
14
|
+
cb()
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = run
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const { join } = require('path')
|
|
5
|
+
const ThreadStream = require('..')
|
|
6
|
+
|
|
7
|
+
test('ignores worker messages without a protocol code', function (t) {
|
|
8
|
+
t.plan(2)
|
|
9
|
+
|
|
10
|
+
const stream = new ThreadStream({
|
|
11
|
+
filename: join(__dirname, 'message-without-code.js'),
|
|
12
|
+
sync: false
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const errors = []
|
|
16
|
+
stream.on('error', err => {
|
|
17
|
+
errors.push(err)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
stream.on('ready', () => {
|
|
21
|
+
t.ok(stream.write('hello world\n'))
|
|
22
|
+
stream.end()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
stream.on('finish', () => {
|
|
26
|
+
t.same(errors, [])
|
|
27
|
+
})
|
|
28
|
+
})
|