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.
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const { test } = require('tap')
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
- t.equal(code, 0)
17
+ assert.strictEqual(code, 0)
17
18
 
18
19
  const data = await readFile(dest, 'utf8')
19
- t.equal(data, 'hello world\n')
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
- t.equal(err.message, 'the worker thread exited')
39
+ assert.strictEqual(err.message, 'the worker thread exited')
34
40
 
35
41
  stream.write('noop');
36
42
  [err] = await once(stream, 'error')
37
- t.equal(err.message, 'the worker has exited')
43
+ assert.strictEqual(err.message, 'the worker has exited')
38
44
 
39
45
  stream.write('noop');
40
46
  [err] = await once(stream, 'error')
41
- t.equal(err.message, 'the worker has exited')
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
- t.equal(err.message, 'kaboom')
68
+ assert.strictEqual(err.message, 'kaboom')
56
69
 
57
70
  stream.write('noop');
58
71
  [err] = await once(stream, 'error')
59
- t.equal(err.message, 'the worker has exited')
72
+ assert.strictEqual(err.message, 'the worker has exited')
60
73
 
61
74
  stream.write('noop');
62
75
  [err] = await once(stream, 'error')
63
- t.equal(err.message, 'the worker has exited')
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
- t.equal(err.message, 'kaboom')
97
+ assert.strictEqual(err.message, 'kaboom')
78
98
 
79
99
  stream.write('noop');
80
100
  [err] = await once(stream, 'error')
81
- t.equal(err.message, 'the worker has exited')
101
+ assert.strictEqual(err.message, 'the worker has exited')
82
102
 
83
103
  stream.write('noop');
84
104
  [err] = await once(stream, 'error')
85
- t.equal(err.message, 'the worker has exited')
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
- t.equal(err.message, 'kaboom')
126
+ assert.strictEqual(err.message, 'kaboom')
100
127
 
101
128
  stream.write('noop');
102
129
  [err] = await once(stream, 'error')
103
- t.equal(err.message, 'the worker has exited')
130
+ assert.strictEqual(err.message, 'the worker has exited')
104
131
 
105
132
  stream.write('noop');
106
133
  [err] = await once(stream, 'error')
107
- t.equal(err.message, 'the worker has exited')
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
- t.equal(code, 0)
146
+ assert.strictEqual(code, 0)
118
147
 
119
148
  const data = await readFile(dest, 'utf8')
120
- t.equal(data, 'hello world\n')
149
+ assert.strictEqual(data, 'hello world\n')
121
150
  })
@@ -1,14 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { test } = require('tap')
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 (t) {
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
- t.same(stream.writableEnded, false)
20
+ assert.deepStrictEqual(stream.writableEnded, false)
22
21
  stream.end()
23
- t.same(stream.writableEnded, true)
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
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
-
4
- npm test
package/.taprc DELETED
@@ -1,4 +0,0 @@
1
- jobs: 1
2
- check-coverage: false
3
- # in seconds
4
- timeout: 60
@@ -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
- })