thread-stream 3.1.0 → 4.0.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/.github/workflows/ci.yml +3 -23
- package/CLAUDE.md +53 -0
- package/eslint.config.js +10 -0
- package/index.js +4 -0
- package/lib/wait.js +50 -43
- package/lib/worker.js +18 -13
- package/package.json +14 -19
- package/test/base.test.js +60 -86
- package/test/bench.test.js +3 -3
- package/test/bundlers.test.js +8 -9
- package/test/commonjs-fallback.test.js +17 -22
- package/test/context.test.js +6 -6
- package/test/end.test.js +22 -27
- package/test/esm.test.mjs +8 -9
- package/test/event.test.js +10 -9
- package/test/helper.js +1 -10
- package/test/indexes.test.js +4 -4
- package/test/multibyte-chars.test.mjs +14 -13
- package/test/pkg/index.js +23 -23
- package/test/pkg/pkg.config.json +3 -4
- package/test/pkg/pkg.test.js +5 -6
- package/test/post-message.test.js +4 -5
- package/test/string-limit-2.test.js +24 -30
- package/test/string-limit.test.js +24 -29
- package/test/thread-management.test.js +34 -17
- package/test/transpiled.test.js +5 -6
- package/test/ts-native.test.mjs +35 -0
- package/test/ts-node-fallback.test.js +35 -0
- package/.taprc +0 -4
- package/test/never-drain.test.js +0 -57
- package/test/ts.test.ts +0 -33
package/.github/workflows/ci.yml
CHANGED
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
contents: read
|
|
25
25
|
steps:
|
|
26
26
|
- name: Check out repo
|
|
27
|
-
uses: actions/checkout@
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
28
|
with:
|
|
29
29
|
persist-credentials: false
|
|
30
30
|
|
|
@@ -38,15 +38,12 @@ jobs:
|
|
|
38
38
|
contents: read
|
|
39
39
|
strategy:
|
|
40
40
|
matrix:
|
|
41
|
-
node-version: [
|
|
41
|
+
node-version: [20, 22, 24]
|
|
42
42
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
43
|
-
exclude:
|
|
44
|
-
- os: windows-latest
|
|
45
|
-
node-version: 22
|
|
46
43
|
|
|
47
44
|
steps:
|
|
48
45
|
- name: Check out repo
|
|
49
|
-
uses: actions/checkout@
|
|
46
|
+
uses: actions/checkout@v4
|
|
50
47
|
with:
|
|
51
48
|
persist-credentials: false
|
|
52
49
|
|
|
@@ -61,23 +58,6 @@ jobs:
|
|
|
61
58
|
- name: Run tests
|
|
62
59
|
run: npm run test:ci
|
|
63
60
|
|
|
64
|
-
- name: Coveralls Parallel
|
|
65
|
-
uses: coverallsapp/github-action@v2.3.0
|
|
66
|
-
with:
|
|
67
|
-
github-token: ${{ secrets.github_token }}
|
|
68
|
-
parallel: true
|
|
69
|
-
flag-name: run-${{ matrix.node-version }}-${{ matrix.os }}
|
|
70
|
-
|
|
71
|
-
coverage:
|
|
72
|
-
needs: test
|
|
73
|
-
runs-on: ubuntu-latest
|
|
74
|
-
steps:
|
|
75
|
-
- name: Coveralls Finished
|
|
76
|
-
uses: coverallsapp/github-action@v2.3.0
|
|
77
|
-
with:
|
|
78
|
-
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
79
|
-
parallel-finished: true
|
|
80
|
-
|
|
81
61
|
automerge:
|
|
82
62
|
name: Automerge Dependabot PRs
|
|
83
63
|
if: >
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Build & Test Commands
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm test # Run linter (standard), type check, transpile, and all tests
|
|
9
|
+
npm run build # Type check only (tsc --noEmit)
|
|
10
|
+
npm run transpile # Generate transpiled test files for various ES targets
|
|
11
|
+
|
|
12
|
+
# Run a single test file
|
|
13
|
+
npx tap test/base.test.js
|
|
14
|
+
|
|
15
|
+
# Run only JavaScript tests (faster)
|
|
16
|
+
npx tap "test/**/*.test.*js"
|
|
17
|
+
|
|
18
|
+
# Run only TypeScript tests
|
|
19
|
+
npx tap --ts test/*.test.*ts
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Code Style
|
|
23
|
+
|
|
24
|
+
This project uses [Standard](https://standardjs.com/) for linting. No semicolons, 2-space indentation.
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
thread-stream is a library for streaming data to a Node.js Worker Thread using SharedArrayBuffer for high-performance inter-thread communication.
|
|
29
|
+
|
|
30
|
+
### Core Components
|
|
31
|
+
|
|
32
|
+
- **index.js** - Main `ThreadStream` class extending EventEmitter. Manages the worker lifecycle, handles writes via SharedArrayBuffer, and coordinates synchronization using Atomics.
|
|
33
|
+
|
|
34
|
+
- **lib/worker.js** - Runs in the Worker Thread. Loads the user-provided destination module, reads data from SharedArrayBuffer, and writes to the destination stream. Handles both ESM and CommonJS modules, including yarn PnP compatibility.
|
|
35
|
+
|
|
36
|
+
- **lib/indexes.js** - Defines SharedArrayBuffer index constants (`WRITE_INDEX`, `READ_INDEX`) used for Atomics coordination.
|
|
37
|
+
|
|
38
|
+
- **lib/wait.js** - Async polling utilities (`wait`, `waitDiff`) for cross-thread synchronization without blocking the main thread.
|
|
39
|
+
|
|
40
|
+
### Data Flow
|
|
41
|
+
|
|
42
|
+
1. Main thread writes strings to an internal buffer, then copies to SharedArrayBuffer
|
|
43
|
+
2. Atomics.notify signals the worker that data is available
|
|
44
|
+
3. Worker reads from SharedArrayBuffer via Atomics.load and writes to destination stream
|
|
45
|
+
4. Worker updates READ_INDEX and notifies main thread when done
|
|
46
|
+
5. Special index values (-1 for end, -2 for error) signal stream termination
|
|
47
|
+
|
|
48
|
+
### Key Features
|
|
49
|
+
|
|
50
|
+
- **Sync/async modes** - `sync: true` blocks until data is written; async mode uses `setImmediate` batching
|
|
51
|
+
- **Backpressure** - Handles `drain` events from destination streams
|
|
52
|
+
- **GC cleanup** - Uses FinalizationRegistry to terminate orphaned workers
|
|
53
|
+
- **TypeScript support** - Workers can be `.ts` files (requires ts-node)
|
package/eslint.config.js
ADDED
package/index.js
CHANGED
|
@@ -122,6 +122,7 @@ function nextFlush (stream) {
|
|
|
122
122
|
|
|
123
123
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
124
124
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
125
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
125
126
|
|
|
126
127
|
// Find a toWrite length that fits the buffer
|
|
127
128
|
// it must exists as the buffer is at least 4 bytes length
|
|
@@ -143,6 +144,7 @@ function nextFlush (stream) {
|
|
|
143
144
|
stream.flush(() => {
|
|
144
145
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
145
146
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
147
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
146
148
|
nextFlush(stream)
|
|
147
149
|
})
|
|
148
150
|
} else {
|
|
@@ -468,6 +470,7 @@ function writeSync (stream) {
|
|
|
468
470
|
flushSync(stream)
|
|
469
471
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
470
472
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
473
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
471
474
|
continue
|
|
472
475
|
} else if (leftover < 0) {
|
|
473
476
|
// stream should never happen
|
|
@@ -485,6 +488,7 @@ function writeSync (stream) {
|
|
|
485
488
|
flushSync(stream)
|
|
486
489
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
487
490
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
491
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
488
492
|
|
|
489
493
|
// Find a toWrite length that fits the buffer
|
|
490
494
|
// it must exists as the buffer is at least 4 bytes length
|
package/lib/wait.js
CHANGED
|
@@ -1,61 +1,68 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Maximum wait time for a single waitAsync call
|
|
4
|
+
// Used as a fallback poll interval in case notifications are missed
|
|
5
|
+
// Keep this low enough for good throughput but high enough to not busy-loop
|
|
6
|
+
const WAIT_MS = 10000
|
|
4
7
|
|
|
5
8
|
function wait (state, index, expected, timeout, done) {
|
|
6
|
-
const max = Date.now() + timeout
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const max = timeout === Infinity ? Infinity : Date.now() + timeout
|
|
10
|
+
|
|
11
|
+
const check = () => {
|
|
12
|
+
const current = Atomics.load(state, index)
|
|
13
|
+
if (current === expected) {
|
|
14
|
+
done(null, 'ok')
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (max !== Infinity && Date.now() > max) {
|
|
15
19
|
done(null, 'timed-out')
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Wait for any change from current value
|
|
24
|
+
const remaining = max === Infinity ? WAIT_MS : Math.min(WAIT_MS, Math.max(1, max - Date.now()))
|
|
25
|
+
const result = Atomics.waitAsync(state, index, current, remaining)
|
|
26
|
+
|
|
27
|
+
if (result.async) {
|
|
28
|
+
result.value.then(check)
|
|
16
29
|
} else {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
current = Atomics.load(state, index)
|
|
20
|
-
if (current === prior) {
|
|
21
|
-
check(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2)
|
|
22
|
-
} else {
|
|
23
|
-
if (current === expected) done(null, 'ok')
|
|
24
|
-
else done(null, 'not-equal')
|
|
25
|
-
}
|
|
26
|
-
}, backoff)
|
|
30
|
+
// Value already changed (not-equal) - recheck on next tick
|
|
31
|
+
setImmediate(check)
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
|
-
|
|
34
|
+
|
|
35
|
+
check()
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
// let waitDiffCount = 0
|
|
33
38
|
function waitDiff (state, index, expected, timeout, done) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// process._rawDebug('' + backoff)
|
|
45
|
-
if (Date.now() > max) {
|
|
39
|
+
const max = timeout === Infinity ? Infinity : Date.now() + timeout
|
|
40
|
+
|
|
41
|
+
const check = () => {
|
|
42
|
+
const current = Atomics.load(state, index)
|
|
43
|
+
if (current !== expected) {
|
|
44
|
+
done(null, 'ok')
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (max !== Infinity && Date.now() > max) {
|
|
46
49
|
done(null, 'timed-out')
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Wait for value to change from expected
|
|
54
|
+
const remaining = max === Infinity ? WAIT_MS : Math.min(WAIT_MS, Math.max(1, max - Date.now()))
|
|
55
|
+
const result = Atomics.waitAsync(state, index, expected, remaining)
|
|
56
|
+
|
|
57
|
+
if (result.async) {
|
|
58
|
+
result.value.then(check)
|
|
47
59
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (current !== expected) {
|
|
51
|
-
done(null, 'ok')
|
|
52
|
-
} else {
|
|
53
|
-
check(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2)
|
|
54
|
-
}
|
|
55
|
-
}, backoff)
|
|
60
|
+
// Value already changed (not-equal) - recheck on next tick
|
|
61
|
+
setImmediate(check)
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
|
-
|
|
64
|
+
|
|
65
|
+
check()
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
module.exports = { wait, waitDiff }
|
package/lib/worker.js
CHANGED
|
@@ -16,22 +16,13 @@ let destination
|
|
|
16
16
|
const state = new Int32Array(stateBuf)
|
|
17
17
|
const data = Buffer.from(dataBuf)
|
|
18
18
|
|
|
19
|
+
// Keep the event loop alive - Atomics.waitAsync promises don't prevent worker exit
|
|
20
|
+
const keepAlive = setInterval(() => {}, 60 * 60 * 1000)
|
|
21
|
+
|
|
19
22
|
async function start () {
|
|
20
23
|
let worker
|
|
21
24
|
try {
|
|
22
|
-
|
|
23
|
-
// TODO: add support for the TSM modules loader ( https://github.com/lukeed/tsm ).
|
|
24
|
-
if (!process[Symbol.for('ts-node.register.instance')]) {
|
|
25
|
-
realRequire('ts-node/register')
|
|
26
|
-
} else if (process.env.TS_NODE_DEV) {
|
|
27
|
-
realRequire('ts-node-dev')
|
|
28
|
-
}
|
|
29
|
-
// TODO: Support ES imports once tsc, tap & ts-node provide better compatibility guarantees.
|
|
30
|
-
// Remove extra forwardslash on Windows
|
|
31
|
-
worker = realRequire(decodeURIComponent(filename.replace(process.platform === 'win32' ? 'file:///' : 'file://', '')))
|
|
32
|
-
} else {
|
|
33
|
-
worker = (await realImport(filename))
|
|
34
|
-
}
|
|
25
|
+
worker = (await realImport(filename))
|
|
35
26
|
} catch (error) {
|
|
36
27
|
// A yarn user that tries to start a ThreadStream for an external module
|
|
37
28
|
// provides a filename pointing to a zip file.
|
|
@@ -53,6 +44,19 @@ async function start () {
|
|
|
53
44
|
} catch {
|
|
54
45
|
throw error
|
|
55
46
|
}
|
|
47
|
+
} else if (filename.endsWith('.ts') || filename.endsWith('.cts')) {
|
|
48
|
+
// Native TypeScript import failed (type stripping not enabled).
|
|
49
|
+
// Fall back to ts-node for TypeScript files.
|
|
50
|
+
try {
|
|
51
|
+
if (!process[Symbol.for('ts-node.register.instance')]) {
|
|
52
|
+
realRequire('ts-node/register')
|
|
53
|
+
} else if (process.env.TS_NODE_DEV) {
|
|
54
|
+
realRequire('ts-node-dev')
|
|
55
|
+
}
|
|
56
|
+
worker = realRequire(decodeURIComponent(filename.replace(process.platform === 'win32' ? 'file:///' : 'file://', '')))
|
|
57
|
+
} catch {
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
56
60
|
} else {
|
|
57
61
|
throw error
|
|
58
62
|
}
|
|
@@ -84,6 +88,7 @@ async function start () {
|
|
|
84
88
|
const end = Atomics.load(state, WRITE_INDEX)
|
|
85
89
|
Atomics.store(state, READ_INDEX, end)
|
|
86
90
|
Atomics.notify(state, READ_INDEX)
|
|
91
|
+
clearInterval(keepAlive)
|
|
87
92
|
setImmediate(() => {
|
|
88
93
|
process.exit(0)
|
|
89
94
|
})
|
package/package.json
CHANGED
|
@@ -1,43 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thread-stream",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
7
10
|
"dependencies": {
|
|
8
11
|
"real-require": "^0.2.0"
|
|
9
12
|
},
|
|
10
13
|
"devDependencies": {
|
|
11
|
-
"@types/node": "^
|
|
12
|
-
"@
|
|
13
|
-
"
|
|
14
|
+
"@types/node": "^22.0.0",
|
|
15
|
+
"@yao-pkg/pkg": "^6.0.0",
|
|
16
|
+
"borp": "^0.21.0",
|
|
14
17
|
"desm": "^1.3.0",
|
|
18
|
+
"eslint": "^9.39.1",
|
|
15
19
|
"fastbench": "^1.0.1",
|
|
16
20
|
"husky": "^9.0.6",
|
|
21
|
+
"neostandard": "^0.12.2",
|
|
17
22
|
"pino-elasticsearch": "^8.0.0",
|
|
18
23
|
"sonic-boom": "^4.0.1",
|
|
19
|
-
"standard": "^17.0.0",
|
|
20
|
-
"tap": "^16.2.0",
|
|
21
24
|
"ts-node": "^10.8.0",
|
|
22
|
-
"typescript": "
|
|
23
|
-
"why-is-node-running": "^2.2.2"
|
|
25
|
+
"typescript": "~5.7.3"
|
|
24
26
|
},
|
|
25
27
|
"scripts": {
|
|
26
28
|
"build": "tsc --noEmit",
|
|
27
|
-
"
|
|
28
|
-
"test
|
|
29
|
-
"test:ci
|
|
30
|
-
"test:
|
|
31
|
-
"test:yarn": "npm run transpile && tap \"test/**/*.test.js\" --no-check-coverage",
|
|
29
|
+
"lint": "eslint",
|
|
30
|
+
"test": "npm run lint && npm run build && npm run transpile && borp --pattern 'test/*.test.{js,mjs}'",
|
|
31
|
+
"test:ci": "npm run lint && npm run transpile && borp --pattern 'test/*.test.{js,mjs}'",
|
|
32
|
+
"test:yarn": "npm run transpile && borp --pattern 'test/*.test.js'",
|
|
32
33
|
"transpile": "sh ./test/ts/transpile.sh",
|
|
33
34
|
"prepare": "husky install"
|
|
34
35
|
},
|
|
35
|
-
"standard": {
|
|
36
|
-
"ignore": [
|
|
37
|
-
"test/ts/**/*",
|
|
38
|
-
"test/syntax-error.mjs"
|
|
39
|
-
]
|
|
40
|
-
},
|
|
41
36
|
"repository": {
|
|
42
37
|
"type": "git",
|
|
43
38
|
"url": "git+https://github.com/mcollina/thread-stream.git"
|
package/test/base.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { test } = require('
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
4
5
|
const { join } = require('path')
|
|
5
6
|
const { readFile } = require('fs')
|
|
6
7
|
const { file } = require('./helper')
|
|
@@ -8,9 +9,7 @@ const ThreadStream = require('..')
|
|
|
8
9
|
const { MessageChannel } = require('worker_threads')
|
|
9
10
|
const { once } = require('events')
|
|
10
11
|
|
|
11
|
-
test('base sync=true', function (t) {
|
|
12
|
-
t.plan(15)
|
|
13
|
-
|
|
12
|
+
test('base sync=true', function (t, done) {
|
|
14
13
|
const dest = file()
|
|
15
14
|
const stream = new ThreadStream({
|
|
16
15
|
filename: join(__dirname, 'to-file.js'),
|
|
@@ -18,37 +17,35 @@ test('base sync=true', function (t) {
|
|
|
18
17
|
sync: true
|
|
19
18
|
})
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
assert.deepStrictEqual(stream.writableObjectMode, false)
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
assert.deepStrictEqual(stream.writableFinished, false)
|
|
24
23
|
stream.on('finish', () => {
|
|
25
|
-
|
|
24
|
+
assert.deepStrictEqual(stream.writableFinished, true)
|
|
26
25
|
readFile(dest, 'utf8', (err, data) => {
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
assert.ifError(err)
|
|
27
|
+
assert.strictEqual(data, 'hello world\nsomething else\n')
|
|
29
28
|
})
|
|
30
29
|
})
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
assert.deepStrictEqual(stream.closed, false)
|
|
33
32
|
stream.on('close', () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
assert.deepStrictEqual(stream.closed, true)
|
|
34
|
+
assert.ok(!stream.writable)
|
|
35
|
+
done()
|
|
37
36
|
})
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
assert.deepStrictEqual(stream.writableNeedDrain, false)
|
|
39
|
+
assert.ok(stream.write('hello world\n'))
|
|
40
|
+
assert.ok(stream.write('something else\n'))
|
|
41
|
+
assert.ok(stream.writable)
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
assert.deepStrictEqual(stream.writableEnded, false)
|
|
45
44
|
stream.end()
|
|
46
|
-
|
|
45
|
+
assert.deepStrictEqual(stream.writableEnded, true)
|
|
47
46
|
})
|
|
48
47
|
|
|
49
|
-
test('overflow sync=true', function (t) {
|
|
50
|
-
t.plan(3)
|
|
51
|
-
|
|
48
|
+
test('overflow sync=true', function (t, done) {
|
|
52
49
|
const dest = file()
|
|
53
50
|
const stream = new ThreadStream({
|
|
54
51
|
bufferSize: 128,
|
|
@@ -73,19 +70,16 @@ test('overflow sync=true', function (t) {
|
|
|
73
70
|
|
|
74
71
|
write()
|
|
75
72
|
|
|
76
|
-
stream.on('finish', () => {
|
|
77
|
-
t.pass('finish emitted')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
73
|
stream.on('close', () => {
|
|
81
74
|
readFile(dest, 'utf8', (err, data) => {
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
assert.ifError(err)
|
|
76
|
+
assert.strictEqual(data.length, 200)
|
|
77
|
+
done()
|
|
84
78
|
})
|
|
85
79
|
})
|
|
86
80
|
})
|
|
87
81
|
|
|
88
|
-
test('overflow sync=false', function (t) {
|
|
82
|
+
test('overflow sync=false', function (t, done) {
|
|
89
83
|
const dest = file()
|
|
90
84
|
const stream = new ThreadStream({
|
|
91
85
|
bufferSize: 128,
|
|
@@ -96,18 +90,17 @@ test('overflow sync=false', function (t) {
|
|
|
96
90
|
|
|
97
91
|
let count = 0
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
assert.deepStrictEqual(stream.writableNeedDrain, false)
|
|
100
94
|
|
|
101
95
|
// Write 10 chars, 20 times
|
|
102
96
|
function write () {
|
|
103
97
|
if (count++ === 20) {
|
|
104
|
-
t.pass('end sent')
|
|
105
98
|
stream.end()
|
|
106
99
|
return
|
|
107
100
|
}
|
|
108
101
|
|
|
109
102
|
if (!stream.write('aaaaaaaaaa')) {
|
|
110
|
-
|
|
103
|
+
assert.deepStrictEqual(stream.writableNeedDrain, true)
|
|
111
104
|
}
|
|
112
105
|
// do not wait for drain event
|
|
113
106
|
setImmediate(write)
|
|
@@ -116,26 +109,19 @@ test('overflow sync=false', function (t) {
|
|
|
116
109
|
write()
|
|
117
110
|
|
|
118
111
|
stream.on('drain', () => {
|
|
119
|
-
|
|
120
|
-
t.pass('drain')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
stream.on('finish', () => {
|
|
124
|
-
t.pass('finish emitted')
|
|
112
|
+
assert.deepStrictEqual(stream.writableNeedDrain, false)
|
|
125
113
|
})
|
|
126
114
|
|
|
127
115
|
stream.on('close', () => {
|
|
128
116
|
readFile(dest, 'utf8', (err, data) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
assert.ifError(err)
|
|
118
|
+
assert.strictEqual(data.length, 200)
|
|
119
|
+
done()
|
|
132
120
|
})
|
|
133
121
|
})
|
|
134
122
|
})
|
|
135
123
|
|
|
136
|
-
test('over the bufferSize at startup', function (t) {
|
|
137
|
-
t.plan(6)
|
|
138
|
-
|
|
124
|
+
test('over the bufferSize at startup', function (t, done) {
|
|
139
125
|
const dest = file()
|
|
140
126
|
const stream = new ThreadStream({
|
|
141
127
|
bufferSize: 10,
|
|
@@ -146,25 +132,23 @@ test('over the bufferSize at startup', function (t) {
|
|
|
146
132
|
|
|
147
133
|
stream.on('finish', () => {
|
|
148
134
|
readFile(dest, 'utf8', (err, data) => {
|
|
149
|
-
|
|
150
|
-
|
|
135
|
+
assert.ifError(err)
|
|
136
|
+
assert.strictEqual(data, 'hello world\nsomething else\n')
|
|
151
137
|
})
|
|
152
138
|
})
|
|
153
139
|
|
|
154
140
|
stream.on('close', () => {
|
|
155
|
-
|
|
141
|
+
done()
|
|
156
142
|
})
|
|
157
143
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
144
|
+
assert.ok(stream.write('hello'))
|
|
145
|
+
assert.ok(stream.write(' world\n'))
|
|
146
|
+
assert.ok(stream.write('something else\n'))
|
|
161
147
|
|
|
162
148
|
stream.end()
|
|
163
149
|
})
|
|
164
150
|
|
|
165
|
-
test('over the bufferSize at startup (async)', function (t) {
|
|
166
|
-
t.plan(6)
|
|
167
|
-
|
|
151
|
+
test('over the bufferSize at startup (async)', function (t, done) {
|
|
168
152
|
const dest = file()
|
|
169
153
|
const stream = new ThreadStream({
|
|
170
154
|
bufferSize: 10,
|
|
@@ -173,25 +157,25 @@ test('over the bufferSize at startup (async)', function (t) {
|
|
|
173
157
|
sync: false
|
|
174
158
|
})
|
|
175
159
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
160
|
+
assert.ok(stream.write('hello'))
|
|
161
|
+
assert.ok(!stream.write(' world\n'))
|
|
162
|
+
assert.ok(!stream.write('something else\n'))
|
|
179
163
|
|
|
180
164
|
stream.end()
|
|
181
165
|
|
|
182
166
|
stream.on('finish', () => {
|
|
183
167
|
readFile(dest, 'utf8', (err, data) => {
|
|
184
|
-
|
|
185
|
-
|
|
168
|
+
assert.ifError(err)
|
|
169
|
+
assert.strictEqual(data, 'hello world\nsomething else\n')
|
|
186
170
|
})
|
|
187
171
|
})
|
|
188
172
|
|
|
189
173
|
stream.on('close', () => {
|
|
190
|
-
|
|
174
|
+
done()
|
|
191
175
|
})
|
|
192
176
|
})
|
|
193
177
|
|
|
194
|
-
test('flushSync sync=false', function (t) {
|
|
178
|
+
test('flushSync sync=false', function (t, done) {
|
|
195
179
|
const dest = file()
|
|
196
180
|
const stream = new ThreadStream({
|
|
197
181
|
bufferSize: 128,
|
|
@@ -201,19 +185,14 @@ test('flushSync sync=false', function (t) {
|
|
|
201
185
|
})
|
|
202
186
|
|
|
203
187
|
stream.on('drain', () => {
|
|
204
|
-
t.pass('drain')
|
|
205
188
|
stream.end()
|
|
206
189
|
})
|
|
207
190
|
|
|
208
|
-
stream.on('finish', () => {
|
|
209
|
-
t.pass('finish emitted')
|
|
210
|
-
})
|
|
211
|
-
|
|
212
191
|
stream.on('close', () => {
|
|
213
192
|
readFile(dest, 'utf8', (err, data) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
193
|
+
assert.ifError(err)
|
|
194
|
+
assert.strictEqual(data.length, 200)
|
|
195
|
+
done()
|
|
217
196
|
})
|
|
218
197
|
})
|
|
219
198
|
|
|
@@ -224,8 +203,6 @@ test('flushSync sync=false', function (t) {
|
|
|
224
203
|
})
|
|
225
204
|
|
|
226
205
|
test('pass down MessagePorts', async function (t) {
|
|
227
|
-
t.plan(3)
|
|
228
|
-
|
|
229
206
|
const { port1, port2 } = new MessageChannel()
|
|
230
207
|
const stream = new ThreadStream({
|
|
231
208
|
filename: join(__dirname, 'port.js'),
|
|
@@ -235,21 +212,19 @@ test('pass down MessagePorts', async function (t) {
|
|
|
235
212
|
},
|
|
236
213
|
sync: false
|
|
237
214
|
})
|
|
238
|
-
t.
|
|
215
|
+
t.after(() => {
|
|
239
216
|
stream.end()
|
|
240
217
|
})
|
|
241
218
|
|
|
242
|
-
|
|
243
|
-
|
|
219
|
+
assert.ok(stream.write('hello world\n'))
|
|
220
|
+
assert.ok(stream.write('something else\n'))
|
|
244
221
|
|
|
245
222
|
const [strings] = await once(port2, 'message')
|
|
246
223
|
|
|
247
|
-
|
|
224
|
+
assert.strictEqual(strings, 'hello world\nsomething else\n')
|
|
248
225
|
})
|
|
249
226
|
|
|
250
|
-
test('destroy does not error', function (t) {
|
|
251
|
-
t.plan(5)
|
|
252
|
-
|
|
227
|
+
test('destroy does not error', function (t, done) {
|
|
253
228
|
const dest = file()
|
|
254
229
|
const stream = new ThreadStream({
|
|
255
230
|
filename: join(__dirname, 'to-file.js'),
|
|
@@ -258,28 +233,27 @@ test('destroy does not error', function (t) {
|
|
|
258
233
|
})
|
|
259
234
|
|
|
260
235
|
stream.on('ready', () => {
|
|
261
|
-
t.pass('ready emitted')
|
|
262
236
|
stream.worker.terminate()
|
|
263
237
|
})
|
|
264
238
|
|
|
265
239
|
stream.on('error', (err) => {
|
|
266
|
-
|
|
240
|
+
assert.strictEqual(err.message, 'the worker thread exited')
|
|
267
241
|
stream.flush((err) => {
|
|
268
|
-
|
|
242
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
269
243
|
})
|
|
270
|
-
|
|
271
|
-
|
|
244
|
+
assert.doesNotThrow(() => stream.flushSync())
|
|
245
|
+
assert.doesNotThrow(() => stream.end())
|
|
246
|
+
done()
|
|
272
247
|
})
|
|
273
248
|
})
|
|
274
249
|
|
|
275
|
-
test('syntax error', function (t) {
|
|
276
|
-
t.plan(1)
|
|
277
|
-
|
|
250
|
+
test('syntax error', function (t, done) {
|
|
278
251
|
const stream = new ThreadStream({
|
|
279
252
|
filename: join(__dirname, 'syntax-error.mjs')
|
|
280
253
|
})
|
|
281
254
|
|
|
282
255
|
stream.on('error', (err) => {
|
|
283
|
-
|
|
256
|
+
assert.strictEqual(err.message, 'Unexpected end of input')
|
|
257
|
+
done()
|
|
284
258
|
})
|
|
285
259
|
})
|