thread-stream 3.1.0 → 4.1.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 +4 -24
- package/README.md +4 -1
- package/eslint.config.js +10 -0
- package/index.d.ts +7 -0
- package/index.js +124 -21
- package/lib/wait.js +50 -43
- package/lib/worker.js +119 -13
- package/package.json +17 -24
- 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/flush-worker.js +68 -0
- package/test/flush.test.js +112 -0
- package/test/helper.js +1 -10
- package/test/indexes.test.js +4 -4
- package/test/message-without-code.js +19 -0
- 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/report-thread-name.js +16 -0
- package/test/string-limit-2.test.js +24 -30
- package/test/string-limit.test.js +24 -29
- package/test/thread-management.test.js +46 -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/test/watch-mode.test.js +30 -0
- package/test/worker-name.test.js +43 -0
- package/.husky/pre-commit +0 -4
- package/.taprc +0 -4
- package/test/never-drain.test.js +0 -57
- package/test/ts.test.ts +0 -33
|
@@ -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 { fork } = require('child_process')
|
|
5
6
|
const { join } = require('path')
|
|
6
7
|
const { readFile } = require('fs').promises
|
|
@@ -13,10 +14,10 @@ test('exits with 0', async function (t) {
|
|
|
13
14
|
const child = fork(join(__dirname, 'create-and-exit.js'), [dest])
|
|
14
15
|
|
|
15
16
|
const [code] = await once(child, 'exit')
|
|
16
|
-
|
|
17
|
+
assert.strictEqual(code, 0)
|
|
17
18
|
|
|
18
19
|
const data = await readFile(dest, 'utf8')
|
|
19
|
-
|
|
20
|
+
assert.strictEqual(data, 'hello world\n')
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
test('emit error if thread exits', async function (t) {
|
|
@@ -25,20 +26,27 @@ test('emit error if thread exits', async function (t) {
|
|
|
25
26
|
sync: true
|
|
26
27
|
})
|
|
27
28
|
|
|
29
|
+
const closed = once(stream, 'close').catch(() => {})
|
|
30
|
+
// Keep a persistent error listener to avoid unhandled late error events
|
|
31
|
+
// reported as asynchronous activity by stricter test runners.
|
|
32
|
+
stream.on('error', () => {})
|
|
33
|
+
|
|
28
34
|
stream.on('ready', () => {
|
|
29
35
|
stream.write('hello world\n')
|
|
30
36
|
})
|
|
31
37
|
|
|
32
38
|
let [err] = await once(stream, 'error')
|
|
33
|
-
|
|
39
|
+
assert.strictEqual(err.message, 'the worker thread exited')
|
|
34
40
|
|
|
35
41
|
stream.write('noop');
|
|
36
42
|
[err] = await once(stream, 'error')
|
|
37
|
-
|
|
43
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
38
44
|
|
|
39
45
|
stream.write('noop');
|
|
40
46
|
[err] = await once(stream, 'error')
|
|
41
|
-
|
|
47
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
48
|
+
|
|
49
|
+
await closed
|
|
42
50
|
})
|
|
43
51
|
|
|
44
52
|
test('emit error if thread have unhandledRejection', async function (t) {
|
|
@@ -47,20 +55,27 @@ test('emit error if thread have unhandledRejection', async function (t) {
|
|
|
47
55
|
sync: true
|
|
48
56
|
})
|
|
49
57
|
|
|
58
|
+
const closed = once(stream, 'close').catch(() => {})
|
|
59
|
+
// Keep a persistent error listener to avoid unhandled late error events
|
|
60
|
+
// reported as asynchronous activity by stricter test runners.
|
|
61
|
+
stream.on('error', () => {})
|
|
62
|
+
|
|
50
63
|
stream.on('ready', () => {
|
|
51
64
|
stream.write('hello world\n')
|
|
52
65
|
})
|
|
53
66
|
|
|
54
67
|
let [err] = await once(stream, 'error')
|
|
55
|
-
|
|
68
|
+
assert.strictEqual(err.message, 'kaboom')
|
|
56
69
|
|
|
57
70
|
stream.write('noop');
|
|
58
71
|
[err] = await once(stream, 'error')
|
|
59
|
-
|
|
72
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
60
73
|
|
|
61
74
|
stream.write('noop');
|
|
62
75
|
[err] = await once(stream, 'error')
|
|
63
|
-
|
|
76
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
77
|
+
|
|
78
|
+
await closed
|
|
64
79
|
})
|
|
65
80
|
|
|
66
81
|
test('emit error if worker stream emit error', async function (t) {
|
|
@@ -69,20 +84,27 @@ test('emit error if worker stream emit error', async function (t) {
|
|
|
69
84
|
sync: true
|
|
70
85
|
})
|
|
71
86
|
|
|
87
|
+
const closed = once(stream, 'close').catch(() => {})
|
|
88
|
+
// Keep a persistent error listener to avoid unhandled late error events
|
|
89
|
+
// reported as asynchronous activity by stricter test runners.
|
|
90
|
+
stream.on('error', () => {})
|
|
91
|
+
|
|
72
92
|
stream.on('ready', () => {
|
|
73
93
|
stream.write('hello world\n')
|
|
74
94
|
})
|
|
75
95
|
|
|
76
96
|
let [err] = await once(stream, 'error')
|
|
77
|
-
|
|
97
|
+
assert.strictEqual(err.message, 'kaboom')
|
|
78
98
|
|
|
79
99
|
stream.write('noop');
|
|
80
100
|
[err] = await once(stream, 'error')
|
|
81
|
-
|
|
101
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
82
102
|
|
|
83
103
|
stream.write('noop');
|
|
84
104
|
[err] = await once(stream, 'error')
|
|
85
|
-
|
|
105
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
106
|
+
|
|
107
|
+
await closed
|
|
86
108
|
})
|
|
87
109
|
|
|
88
110
|
test('emit error if thread have uncaughtException', async function (t) {
|
|
@@ -91,20 +113,27 @@ test('emit error if thread have uncaughtException', async function (t) {
|
|
|
91
113
|
sync: true
|
|
92
114
|
})
|
|
93
115
|
|
|
116
|
+
const closed = once(stream, 'close').catch(() => {})
|
|
117
|
+
// Keep a persistent error listener to avoid unhandled late error events
|
|
118
|
+
// reported as asynchronous activity by stricter test runners.
|
|
119
|
+
stream.on('error', () => {})
|
|
120
|
+
|
|
94
121
|
stream.on('ready', () => {
|
|
95
122
|
stream.write('hello world\n')
|
|
96
123
|
})
|
|
97
124
|
|
|
98
125
|
let [err] = await once(stream, 'error')
|
|
99
|
-
|
|
126
|
+
assert.strictEqual(err.message, 'kaboom')
|
|
100
127
|
|
|
101
128
|
stream.write('noop');
|
|
102
129
|
[err] = await once(stream, 'error')
|
|
103
|
-
|
|
130
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
104
131
|
|
|
105
132
|
stream.write('noop');
|
|
106
133
|
[err] = await once(stream, 'error')
|
|
107
|
-
|
|
134
|
+
assert.strictEqual(err.message, 'the worker has exited')
|
|
135
|
+
|
|
136
|
+
await closed
|
|
108
137
|
})
|
|
109
138
|
|
|
110
139
|
test('close the work if out of scope on gc', { skip: !global.WeakRef }, async function (t) {
|
|
@@ -114,8 +143,8 @@ test('close the work if out of scope on gc', { skip: !global.WeakRef }, async fu
|
|
|
114
143
|
})
|
|
115
144
|
|
|
116
145
|
const [code] = await once(child, 'exit')
|
|
117
|
-
|
|
146
|
+
assert.strictEqual(code, 0)
|
|
118
147
|
|
|
119
148
|
const data = await readFile(dest, 'utf8')
|
|
120
|
-
|
|
149
|
+
assert.strictEqual(data, 'hello world\n')
|
|
121
150
|
})
|
package/test/transpiled.test.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
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 { file } = require('./helper')
|
|
6
7
|
const ThreadStream = require('..')
|
|
7
8
|
|
|
8
9
|
function basic (esVersion) {
|
|
9
|
-
test(`transpiled-ts-to-${esVersion}`, function (
|
|
10
|
-
t.plan(2)
|
|
11
|
-
|
|
10
|
+
test(`transpiled-ts-to-${esVersion}`, function () {
|
|
12
11
|
const dest = file()
|
|
13
12
|
const stream = new ThreadStream({
|
|
14
13
|
filename: join(__dirname, 'ts', `to-file.${esVersion}.cjs`),
|
|
@@ -18,9 +17,9 @@ function basic (esVersion) {
|
|
|
18
17
|
|
|
19
18
|
// There are arbitrary checks, the important aspect of this test is to ensure
|
|
20
19
|
// that we can properly load the transpiled file into our worker thread.
|
|
21
|
-
|
|
20
|
+
assert.deepStrictEqual(stream.writableEnded, false)
|
|
22
21
|
stream.end()
|
|
23
|
-
|
|
22
|
+
assert.deepStrictEqual(stream.writableEnded, true)
|
|
24
23
|
})
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import { readFile } from 'fs/promises'
|
|
4
|
+
import ThreadStream from '../index.js'
|
|
5
|
+
import { join } from 'desm'
|
|
6
|
+
import { file } from './helper.js'
|
|
7
|
+
|
|
8
|
+
const nodeVersion = parseInt(process.versions.node.split('.')[0], 10)
|
|
9
|
+
|
|
10
|
+
// Native TypeScript stripping (--experimental-strip-types) is only available in Node 22.6+
|
|
11
|
+
test('typescript module with native type stripping', { skip: nodeVersion < 22 }, async function (t) {
|
|
12
|
+
const dest = file()
|
|
13
|
+
const stream = new ThreadStream({
|
|
14
|
+
filename: join(import.meta.url, 'ts', 'to-file.ts'),
|
|
15
|
+
workerData: { dest },
|
|
16
|
+
workerOpts: {
|
|
17
|
+
execArgv: ['--experimental-strip-types', '--disable-warning=ExperimentalWarning']
|
|
18
|
+
},
|
|
19
|
+
sync: false
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
t.after(() => stream.end())
|
|
23
|
+
|
|
24
|
+
assert.ok(stream.write('hello world\n'))
|
|
25
|
+
assert.ok(stream.write('something else\n'))
|
|
26
|
+
|
|
27
|
+
stream.end()
|
|
28
|
+
|
|
29
|
+
await new Promise((resolve) => {
|
|
30
|
+
stream.on('close', resolve)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const data = await readFile(dest, 'utf8')
|
|
34
|
+
assert.strictEqual(data, 'hello world\nsomething else\n')
|
|
35
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
5
|
+
const { readFile } = require('fs/promises')
|
|
6
|
+
const { join } = require('path')
|
|
7
|
+
const { file } = require('./helper')
|
|
8
|
+
const ThreadStream = require('..')
|
|
9
|
+
|
|
10
|
+
// This test verifies that TypeScript files can be loaded via ts-node
|
|
11
|
+
// when native type stripping is not enabled in the worker thread.
|
|
12
|
+
// Unlike ts.test.ts which passes --experimental-strip-types via execArgv,
|
|
13
|
+
// this test does NOT pass that flag, so the worker will fall back to ts-node.
|
|
14
|
+
test('typescript module with ts-node fallback', async function (t) {
|
|
15
|
+
const dest = file()
|
|
16
|
+
const stream = new ThreadStream({
|
|
17
|
+
filename: join(__dirname, 'ts', 'to-file.ts'),
|
|
18
|
+
workerData: { dest },
|
|
19
|
+
sync: false
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
t.after(() => stream.end())
|
|
23
|
+
|
|
24
|
+
assert.ok(stream.write('hello world\n'))
|
|
25
|
+
assert.ok(stream.write('something else\n'))
|
|
26
|
+
|
|
27
|
+
stream.end()
|
|
28
|
+
|
|
29
|
+
await new Promise((resolve) => {
|
|
30
|
+
stream.on('close', resolve)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const data = await readFile(dest, 'utf8')
|
|
34
|
+
assert.strictEqual(data, 'hello world\nsomething else\n')
|
|
35
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
5
|
+
const { once } = require('events')
|
|
6
|
+
const { join } = require('path')
|
|
7
|
+
const ThreadStream = require('..')
|
|
8
|
+
|
|
9
|
+
test('ignores worker messages without a protocol code', async function () {
|
|
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
|
+
const ready = once(stream, 'ready')
|
|
21
|
+
const close = once(stream, 'close')
|
|
22
|
+
|
|
23
|
+
assert.ok(stream.write('hello world\n'))
|
|
24
|
+
stream.end()
|
|
25
|
+
|
|
26
|
+
await ready
|
|
27
|
+
await close
|
|
28
|
+
|
|
29
|
+
assert.deepStrictEqual(errors, [])
|
|
30
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
5
|
+
const { join } = require('path')
|
|
6
|
+
const { once } = require('events')
|
|
7
|
+
const { MessageChannel } = require('worker_threads')
|
|
8
|
+
const ThreadStream = require('..')
|
|
9
|
+
|
|
10
|
+
// threadName was added in Node.js v22.20.0 and v24.6.0
|
|
11
|
+
const [major, minor] = process.versions.node.split('.').map(Number)
|
|
12
|
+
const supportsThreadName = (major === 22 && minor >= 20) || major >= 24
|
|
13
|
+
|
|
14
|
+
test('worker has default name "thread-stream"', { skip: !supportsThreadName }, async function (t) {
|
|
15
|
+
const { port1, port2 } = new MessageChannel()
|
|
16
|
+
const stream = new ThreadStream({
|
|
17
|
+
filename: join(__dirname, 'report-thread-name.js'),
|
|
18
|
+
sync: true
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
t.after(() => stream.end())
|
|
22
|
+
|
|
23
|
+
stream.emit('message', { port: port1 }, [port1])
|
|
24
|
+
const [{ threadName }] = await once(port2, 'message')
|
|
25
|
+
assert.strictEqual(threadName, 'thread-stream')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('worker name can be overridden via workerOpts', { skip: !supportsThreadName }, async function (t) {
|
|
29
|
+
const { port1, port2 } = new MessageChannel()
|
|
30
|
+
const stream = new ThreadStream({
|
|
31
|
+
filename: join(__dirname, 'report-thread-name.js'),
|
|
32
|
+
workerOpts: {
|
|
33
|
+
name: 'my-custom-worker'
|
|
34
|
+
},
|
|
35
|
+
sync: true
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
t.after(() => stream.end())
|
|
39
|
+
|
|
40
|
+
stream.emit('message', { port: port1 }, [port1])
|
|
41
|
+
const [{ threadName }] = await once(port2, 'message')
|
|
42
|
+
assert.strictEqual(threadName, 'my-custom-worker')
|
|
43
|
+
})
|
package/.husky/pre-commit
DELETED
package/.taprc
DELETED
package/test/never-drain.test.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const { test } = require('tap')
|
|
2
|
-
const ThreadStream = require('../index')
|
|
3
|
-
const { join } = require('path')
|
|
4
|
-
|
|
5
|
-
function retryUntilTimeout (fn, timeout) {
|
|
6
|
-
const start = Date.now()
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
async function run () {
|
|
9
|
-
if (fn()) {
|
|
10
|
-
resolve()
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (Date.now() - start >= timeout) {
|
|
15
|
-
reject(new Error('timeout'))
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
setTimeout(run, 10)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
run()
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const isNode18 = process.version.indexOf('v18') === 0
|
|
26
|
-
|
|
27
|
-
test('emit warning when the worker gracefully exit without the stream ended', { skip: !isNode18 }, async function (t) {
|
|
28
|
-
const expectedWarning = 'ThreadStream: process exited before destination stream was drained. this may indicate that the destination stream try to write to a another missing stream'
|
|
29
|
-
const stream = new ThreadStream({
|
|
30
|
-
filename: join(__dirname, 'to-next.js')
|
|
31
|
-
})
|
|
32
|
-
stream.unref()
|
|
33
|
-
|
|
34
|
-
let streamWarning
|
|
35
|
-
function saveWarning (e) {
|
|
36
|
-
if (e.message === expectedWarning) {
|
|
37
|
-
streamWarning = e
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
process.on('warning', saveWarning)
|
|
41
|
-
|
|
42
|
-
const data = 'hello'.repeat(10)
|
|
43
|
-
for (let i = 0; i < 1000; i++) {
|
|
44
|
-
if (streamWarning?.message === expectedWarning) {
|
|
45
|
-
break
|
|
46
|
-
}
|
|
47
|
-
stream.write(data)
|
|
48
|
-
await new Promise((resolve) => {
|
|
49
|
-
setTimeout(resolve, 1)
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
process.off('warning', saveWarning)
|
|
54
|
-
t.equal(streamWarning?.message, expectedWarning)
|
|
55
|
-
|
|
56
|
-
await retryUntilTimeout(() => stream.worker.exited === true, 3000)
|
|
57
|
-
})
|
package/test/ts.test.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { test } from 'tap'
|
|
2
|
-
import { readFile } from 'fs'
|
|
3
|
-
import ThreadStream from '../index.js'
|
|
4
|
-
import { join } from 'path'
|
|
5
|
-
import { file } from './helper.js'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
test('typescript module', function (t) {
|
|
9
|
-
t.plan(5)
|
|
10
|
-
|
|
11
|
-
const dest = file()
|
|
12
|
-
const stream = new ThreadStream({
|
|
13
|
-
filename: join(__dirname, 'ts', 'to-file.ts'),
|
|
14
|
-
workerData: { dest },
|
|
15
|
-
sync: true
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
stream.on('finish', () => {
|
|
19
|
-
readFile(dest, 'utf8', (err, data) => {
|
|
20
|
-
t.error(err)
|
|
21
|
-
t.equal(data, 'hello world\nsomething else\n')
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
stream.on('close', () => {
|
|
26
|
-
t.pass('close emitted')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
t.ok(stream.write('hello world\n'))
|
|
30
|
-
t.ok(stream.write('something else\n'))
|
|
31
|
-
|
|
32
|
-
stream.end()
|
|
33
|
-
})
|