thread-stream 1.0.0 → 2.0.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.
@@ -1,4 +1,5 @@
1
1
  name: CI
2
+
2
3
  on:
3
4
  push:
4
5
  paths-ignore:
@@ -8,35 +9,53 @@ on:
8
9
  paths-ignore:
9
10
  - 'docs/**'
10
11
  - '*.md'
12
+
13
+ # This allows a subsequently queued workflow run to interrupt previous runs
14
+ concurrency:
15
+ group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
16
+ cancel-in-progress: true
17
+
11
18
  jobs:
19
+ dependency-review:
20
+ name: Dependency Review
21
+ if: github.event_name == 'pull_request'
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ steps:
26
+ - name: Check out repo
27
+ uses: actions/checkout@v3
28
+ with:
29
+ persist-credentials: false
30
+
31
+ - name: Dependency review
32
+ uses: actions/dependency-review-action@v2
33
+
12
34
  test:
35
+ name: Test
13
36
  runs-on: ${{ matrix.os }}
14
-
15
37
  permissions:
16
38
  contents: read
17
-
18
39
  strategy:
19
40
  matrix:
20
41
  node-version: [14, 16, 18]
21
42
  os: [macos-latest, ubuntu-latest, windows-latest]
22
-
23
43
  steps:
44
+ - name: Check out repo
45
+ uses: actions/checkout@v3
46
+ with:
47
+ persist-credentials: false
24
48
 
25
- - uses: actions/checkout@v3
26
-
27
- - name: Use Node.js
49
+ - name: Setup Node ${{ matrix.node-version }}
28
50
  uses: actions/setup-node@v3
29
51
  with:
30
52
  node-version: ${{ matrix.node-version }}
31
53
 
32
- - name: Install
33
- run: |
34
- npm install --ignore-scripts
54
+ - name: Install dependencies
55
+ run: npm i --ignore-scripts
35
56
 
36
57
  - name: Run tests
37
- shell: bash
38
- run: |
39
- npm run test:ci
58
+ run: npm run test:ci
40
59
 
41
60
  - name: Coveralls Parallel
42
61
  uses: coverallsapp/github-action@1.1.3
@@ -56,11 +75,15 @@ jobs:
56
75
  parallel-finished: true
57
76
 
58
77
  automerge:
78
+ name: Automerge Dependabot PRs
79
+ if: >
80
+ github.event_name == 'pull_request' &&
81
+ github.event.pull_request.user.login == 'dependabot[bot]'
59
82
  needs: test
60
- runs-on: ubuntu-latest
61
83
  permissions:
62
84
  pull-requests: write
63
85
  contents: write
86
+ runs-on: ubuntu-latest
64
87
  steps:
65
88
  - uses: fastify/github-action-merge-dependabot@v3
66
89
  with:
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ${{ matrix.os }}
15
15
  strategy:
16
16
  matrix:
17
- os: [macOS-latest, windows-latest]
17
+ os: [ubuntu-latest]
18
18
  node-version: [14, 16, 18]
19
19
  steps:
20
20
  - uses: actions/checkout@v3
@@ -23,7 +23,7 @@ jobs:
23
23
  with:
24
24
  node-version: ${{ matrix.node-version }}
25
25
  - name: Use pnpm
26
- uses: pnpm/action-setup@v2.2.1
26
+ uses: pnpm/action-setup@v2.2.2
27
27
  with:
28
28
  version: ^6.0.0
29
29
  - name: Install dependancies
@@ -37,7 +37,7 @@ jobs:
37
37
  runs-on: ${{ matrix.os }}
38
38
  strategy:
39
39
  matrix:
40
- os: [macOS-latest]
40
+ os: [ubuntu-latest]
41
41
  node-version: [14, 16, 18]
42
42
  steps:
43
43
  - uses: actions/checkout@v3
@@ -51,6 +51,7 @@ jobs:
51
51
  yarn set version berry
52
52
  echo "nodeLinker: pnp" >> .yarnrc.yml
53
53
  echo "pnpMode: loose" >> .yarnrc.yml
54
+ echo "pnpEnableEsmLoader: false" >> .yarnrc.yml
54
55
  yarn add -D pino-elasticsearch@^6.0.0
55
56
  yarn install
56
57
  env:
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # thread-stream
2
2
  [![npm version](https://img.shields.io/npm/v/thread-stream)](https://www.npmjs.com/package/thread-stream)
3
3
  [![Build Status](https://img.shields.io/github/workflow/status/pinojs/thread-stream/CI)](https://github.com/pinojs/thread-stream/actions)
4
- [![Known Vulnerabilities](https://snyk.io/test/github/pinojs/thread-stream/badge.svg)](https://snyk.io/test/github/pinojs/thread-stream)
5
4
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
6
5
 
7
6
  A streaming way to send data to a Node.js Worker Thread.
package/index.d.ts CHANGED
@@ -1,9 +1,73 @@
1
1
  import { EventEmitter } from 'events'
2
+ import workerThreads from 'worker_threads'
3
+
4
+ interface ThreadStreamOptions {
5
+ /**
6
+ * The size (in bytes) of the buffer.
7
+ * Must be greater than 4 (i.e. it must at least fit a 4-byte utf-8 char).
8
+ *
9
+ * Default: `4 * 1024 * 1024` = `4194304`
10
+ */
11
+ bufferSize?: number,
12
+ /**
13
+ * The path to the Worker's main script or module.
14
+ * Must be either an absolute path or a relative path (i.e. relative to the current working directory) starting with ./ or ../, or a WHATWG URL object using file: or data: protocol.
15
+ * When using a data: URL, the data is interpreted based on MIME type using the ECMAScript module loader.
16
+ *
17
+ * {@link workerThreads.Worker()}
18
+ */
19
+ filename: string | URL,
20
+ /**
21
+ * If `true`, write data synchronously; otherwise write data asynchronously.
22
+ *
23
+ * Default: `false`.
24
+ */
25
+ sync?: boolean,
26
+ /**
27
+ * {@link workerThreads.WorkerOptions.workerData}
28
+ *
29
+ * Default: `{}`
30
+ */
31
+ workerData?: any,
32
+ /**
33
+ * {@link workerThreads.WorkerOptions}
34
+ *
35
+ * Default: `{}`
36
+ */
37
+ workerOpts?: workerThreads.WorkerOptions
38
+ }
39
+
2
40
 
3
41
  declare class ThreadStream extends EventEmitter {
4
- constructor(opts: {})
5
- write (data: string): boolean
6
- end (): void
42
+ /**
43
+ * @param {ThreadStreamOptions} opts
44
+ */
45
+ constructor(opts: ThreadStreamOptions)
46
+ /**
47
+ * Write some data to the stream.
48
+ *
49
+ * **Please note that this method should not throw an {Error} if something goes wrong but emit an error event.**
50
+ * @param {string} data data to write.
51
+ * @returns {boolean} false if the stream wishes for the calling code to wait for the 'drain' event to be emitted before continuing to write additional data or if it fails to write; otherwise true.
52
+ */
53
+ write(data: string): boolean
54
+ /**
55
+ * Signal that no more data will be written.
56
+ *
57
+ * **Please note that this method should not throw an {Error} if something goes wrong but emit an error event.**
58
+ *
59
+ * Calling the {@link write()} method after calling {@link end()} will emit an error.
60
+ */
61
+ end(): void
62
+ /**
63
+ * Flush the stream synchronously.
64
+ * This method should be called in the shutdown phase to make sure that all data has been flushed.
65
+ *
66
+ * **Please note that this method will throw an {Error} if something goes wrong.**
67
+ *
68
+ * @throws {Error} if the stream is already flushing, if it fails to flush or if it takes more than 10 seconds to flush.
69
+ */
70
+ flushSync(): void
7
71
  }
8
72
 
9
73
  export = ThreadStream;
package/index.js CHANGED
@@ -136,7 +136,7 @@ function nextFlush (stream) {
136
136
  })
137
137
  } else {
138
138
  // This should never happen
139
- throw new Error('overwritten')
139
+ destroy(stream, new Error('overwritten'))
140
140
  }
141
141
  }
142
142
 
@@ -164,7 +164,7 @@ function onWorkerMessage (msg) {
164
164
  destroy(stream, msg.err)
165
165
  break
166
166
  default:
167
- throw new Error('this should not happen: ' + msg.code)
167
+ destroy(stream, new Error('this should not happen: ' + msg.code))
168
168
  }
169
169
  }
170
170
 
@@ -177,7 +177,7 @@ function onWorkerExit (code) {
177
177
  registry.unregister(stream)
178
178
  stream.worker.exited = true
179
179
  stream.worker.off('exit', onWorkerExit)
180
- destroy(stream, code !== 0 ? new Error('The worker thread exited') : null)
180
+ destroy(stream, code !== 0 ? new Error('the worker thread exited') : null)
181
181
  }
182
182
 
183
183
  class ThreadStream extends EventEmitter {
@@ -211,11 +211,13 @@ class ThreadStream extends EventEmitter {
211
211
 
212
212
  write (data) {
213
213
  if (this[kImpl].destroyed) {
214
- throw new Error('the worker has exited')
214
+ error(this, new Error('the worker has exited'))
215
+ return false
215
216
  }
216
217
 
217
218
  if (this[kImpl].ending) {
218
- throw new Error('the worker is ending')
219
+ error(this, new Error('the worker is ending'))
220
+ return false
219
221
  }
220
222
 
221
223
  if (this[kImpl].flushing && this[kImpl].buf.length + data.length >= MAX_STRING) {
@@ -338,6 +340,12 @@ class ThreadStream extends EventEmitter {
338
340
  }
339
341
  }
340
342
 
343
+ function error (stream, err) {
344
+ setImmediate(() => {
345
+ stream.emit('error', err)
346
+ })
347
+ }
348
+
341
349
  function destroy (stream, err) {
342
350
  if (stream[kImpl].destroyed) {
343
351
  return
@@ -346,7 +354,7 @@ function destroy (stream, err) {
346
354
 
347
355
  if (err) {
348
356
  stream[kImpl].errored = err
349
- stream.emit('error', err)
357
+ error(stream, err)
350
358
  }
351
359
 
352
360
  if (!stream.worker.exited) {
@@ -399,11 +407,13 @@ function end (stream) {
399
407
  readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
400
408
 
401
409
  if (readIndex === -2) {
402
- throw new Error('end() failed')
410
+ destroy(stream, new Error('end() failed'))
411
+ return
403
412
  }
404
413
 
405
414
  if (++spins === 10) {
406
- throw new Error('end() took too long (10s)')
415
+ destroy(stream, new Error('end() took too long (10s)'))
416
+ return
407
417
  }
408
418
  }
409
419
 
@@ -482,7 +492,7 @@ function flushSync (stream) {
482
492
  const readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
483
493
 
484
494
  if (readIndex === -2) {
485
- throw new Error('_flushSync failed')
495
+ throw Error('_flushSync failed')
486
496
  }
487
497
 
488
498
  // process._rawDebug(`(flushSync) readIndex (${readIndex}) writeIndex (${writeIndex})`)
package/lib/worker.js CHANGED
@@ -32,12 +32,6 @@ async function start () {
32
32
  } else {
33
33
  fn = (await realImport(filename))
34
34
  }
35
-
36
- // Depending on how the default export is performed, and on how the code is
37
- // transpiled, we may find cases of two nested "default" objects.
38
- // See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762
39
- if (typeof fn === 'object') fn = fn.default
40
- if (typeof fn === 'object') fn = fn.default
41
35
  } catch (error) {
42
36
  // A yarn user that tries to start a ThreadStream for an external module
43
37
  // provides a filename pointing to a zip file.
@@ -50,10 +44,20 @@ async function start () {
50
44
  if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND') &&
51
45
  filename.startsWith('file://')) {
52
46
  fn = realRequire(decodeURIComponent(filename.replace('file://', '')))
47
+ } else if (error.code === undefined) {
48
+ // When bundled with pkg, an undefined error is thrown when called with realImport
49
+ fn = realRequire(decodeURIComponent(filename.replace(process.platform === 'win32' ? 'file:///' : 'file://', '')))
53
50
  } else {
54
51
  throw error
55
52
  }
56
53
  }
54
+
55
+ // Depending on how the default export is performed, and on how the code is
56
+ // transpiled, we may find cases of two nested "default" objects.
57
+ // See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762
58
+ if (typeof fn === 'object') fn = fn.default
59
+ if (typeof fn === 'object') fn = fn.default
60
+
57
61
  destination = await fn(workerData.workerData)
58
62
 
59
63
  destination.on('error', function (err) {
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "thread-stream",
3
- "version": "1.0.0",
3
+ "version": "2.0.1",
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
7
  "dependencies": {
8
- "real-require": "^0.1.0"
8
+ "real-require": "^0.2.0"
9
9
  },
10
10
  "devDependencies": {
11
- "@types/node": "^17.0.37",
11
+ "@types/node": "^18.0.0",
12
12
  "@types/tap": "^15.0.0",
13
13
  "desm": "^1.3.0",
14
14
  "fastbench": "^1.0.1",
15
15
  "husky": "^8.0.1",
16
- "sonic-boom": "^2.8.0",
16
+ "sonic-boom": "^3.0.0",
17
17
  "standard": "^17.0.0",
18
18
  "tap": "^16.2.0",
19
19
  "ts-node": "^10.8.0",
package/test/base.test.js CHANGED
@@ -263,7 +263,7 @@ test('destroy does not error', function (t) {
263
263
  })
264
264
 
265
265
  stream.on('error', (err) => {
266
- t.equal(err.message, 'The worker thread exited')
266
+ t.equal(err.message, 'the worker thread exited')
267
267
  stream.flush((err) => {
268
268
  t.equal(err.message, 'the worker has exited')
269
269
  })
@@ -5,7 +5,7 @@ const { join } = require('path')
5
5
  const { file } = require('./helper')
6
6
  const ThreadStream = require('..')
7
7
 
8
- test('bundlers support', function (t) {
8
+ test('bundlers support with .js file', function (t) {
9
9
  t.plan(1)
10
10
 
11
11
  globalThis.__bundlerPathsOverrides = {
@@ -31,3 +31,30 @@ test('bundlers support', function (t) {
31
31
 
32
32
  stream.end()
33
33
  })
34
+
35
+ test('bundlers support with .mjs file', function (t) {
36
+ t.plan(1)
37
+
38
+ globalThis.__bundlerPathsOverrides = {
39
+ 'thread-stream-worker': join(__dirname, 'custom-worker.js')
40
+ }
41
+
42
+ const dest = file()
43
+
44
+ process.on('uncaughtException', error => {
45
+ console.log(error)
46
+ })
47
+
48
+ const stream = new ThreadStream({
49
+ filename: join(__dirname, 'to-file.mjs'),
50
+ workerData: { dest },
51
+ sync: true
52
+ })
53
+
54
+ stream.worker.removeAllListeners('message')
55
+ stream.worker.once('message', message => {
56
+ t.equal(message.code, 'CUSTOM-WORKER-CALLED')
57
+ })
58
+
59
+ stream.end()
60
+ })
@@ -54,3 +54,27 @@ test('yarn module resolution for directories with special characters', { skip: !
54
54
 
55
55
  t.equal(strings, 'hello world\nsomething else\n')
56
56
  })
57
+
58
+ test('yarn module resolution for typescript commonjs modules', { skip: !isYarnPnp }, async t => {
59
+ t.plan(3)
60
+
61
+ const { port1, port2 } = new MessageChannel()
62
+ const stream = new ThreadStream({
63
+ filename: join(__dirname, 'ts-commonjs-default-export.zip', 'worker.js'),
64
+ workerData: { port: port1 },
65
+ workerOpts: {
66
+ transferList: [port1]
67
+ },
68
+ sync: false
69
+ })
70
+ t.teardown(() => {
71
+ stream.end()
72
+ })
73
+
74
+ t.ok(stream.write('hello world\n'))
75
+ t.ok(stream.write('something else\n'))
76
+
77
+ const [strings] = await once(port2, 'message')
78
+
79
+ t.equal(strings, 'hello world\nsomething else\n')
80
+ })
@@ -1,6 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const t = require('tap')
4
+
5
+ if (process.env.CI) {
6
+ t.skip('skip on CI')
7
+ process.exit(0)
8
+ }
9
+
4
10
  const { join } = require('path')
5
11
  const { file } = require('./helper')
6
12
  const { createReadStream } = require('fs')
@@ -1,6 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const t = require('tap')
4
+
5
+ if (process.env.CI) {
6
+ t.skip('skip on CI')
7
+ process.exit(0)
8
+ }
9
+
4
10
  const { join } = require('path')
5
11
  const { file } = require('./helper')
6
12
  const { stat } = require('fs')
@@ -29,50 +29,16 @@ test('emit error if thread exits', async function (t) {
29
29
  stream.write('hello world\n')
30
30
  })
31
31
 
32
- const [err] = await once(stream, 'error')
33
- t.equal(err.message, 'The worker thread exited')
34
-
35
- try {
36
- stream.write('noop')
37
- t.fail('unreacheable')
38
- } catch (err) {
39
- t.equal(err.message, 'the worker has exited')
40
- }
41
-
42
- try {
43
- stream.write('noop')
44
- t.fail('unreacheable')
45
- } catch (err) {
46
- t.equal(err.message, 'the worker has exited')
47
- }
48
- })
32
+ let [err] = await once(stream, 'error')
33
+ t.equal(err.message, 'the worker thread exited')
49
34
 
50
- test('emit error if thread exits', async function (t) {
51
- const stream = new ThreadStream({
52
- filename: join(__dirname, 'exit.js'),
53
- sync: true
54
- })
35
+ stream.write('noop');
36
+ [err] = await once(stream, 'error')
37
+ t.equal(err.message, 'the worker has exited')
55
38
 
56
- stream.on('ready', () => {
57
- stream.write('hello world\n')
58
- })
59
-
60
- const [err] = await once(stream, 'error')
61
- t.equal(err.message, 'The worker thread exited')
62
-
63
- try {
64
- stream.write('noop')
65
- t.fail('unreacheable')
66
- } catch (err) {
67
- t.equal(err.message, 'the worker has exited')
68
- }
69
-
70
- try {
71
- stream.write('noop')
72
- t.fail('unreacheable')
73
- } catch (err) {
74
- t.equal(err.message, 'the worker has exited')
75
- }
39
+ stream.write('noop');
40
+ [err] = await once(stream, 'error')
41
+ t.equal(err.message, 'the worker has exited')
76
42
  })
77
43
 
78
44
  test('emit error if thread have unhandledRejection', async function (t) {
@@ -85,22 +51,16 @@ test('emit error if thread have unhandledRejection', async function (t) {
85
51
  stream.write('hello world\n')
86
52
  })
87
53
 
88
- const [err] = await once(stream, 'error')
54
+ let [err] = await once(stream, 'error')
89
55
  t.equal(err.message, 'kaboom')
90
56
 
91
- try {
92
- stream.write('noop')
93
- t.fail('unreacheable')
94
- } catch (err) {
95
- t.equal(err.message, 'the worker has exited')
96
- }
97
-
98
- try {
99
- stream.write('noop')
100
- t.fail('unreacheable')
101
- } catch (err) {
102
- t.equal(err.message, 'the worker has exited')
103
- }
57
+ stream.write('noop');
58
+ [err] = await once(stream, 'error')
59
+ t.equal(err.message, 'the worker has exited')
60
+
61
+ stream.write('noop');
62
+ [err] = await once(stream, 'error')
63
+ t.equal(err.message, 'the worker has exited')
104
64
  })
105
65
 
106
66
  test('emit error if worker stream emit error', async function (t) {
@@ -113,22 +73,16 @@ test('emit error if worker stream emit error', async function (t) {
113
73
  stream.write('hello world\n')
114
74
  })
115
75
 
116
- const [err] = await once(stream, 'error')
76
+ let [err] = await once(stream, 'error')
117
77
  t.equal(err.message, 'kaboom')
118
78
 
119
- try {
120
- stream.write('noop')
121
- t.fail('unreacheable')
122
- } catch (err) {
123
- t.equal(err.message, 'the worker has exited')
124
- }
125
-
126
- try {
127
- stream.write('noop')
128
- t.fail('unreacheable')
129
- } catch (err) {
130
- t.equal(err.message, 'the worker has exited')
131
- }
79
+ stream.write('noop');
80
+ [err] = await once(stream, 'error')
81
+ t.equal(err.message, 'the worker has exited')
82
+
83
+ stream.write('noop');
84
+ [err] = await once(stream, 'error')
85
+ t.equal(err.message, 'the worker has exited')
132
86
  })
133
87
 
134
88
  test('emit error if thread have uncaughtException', async function (t) {
@@ -141,22 +95,16 @@ test('emit error if thread have uncaughtException', async function (t) {
141
95
  stream.write('hello world\n')
142
96
  })
143
97
 
144
- const [err] = await once(stream, 'error')
98
+ let [err] = await once(stream, 'error')
145
99
  t.equal(err.message, 'kaboom')
146
100
 
147
- try {
148
- stream.write('noop')
149
- t.fail('unreacheable')
150
- } catch (err) {
151
- t.equal(err.message, 'the worker has exited')
152
- }
153
-
154
- try {
155
- stream.write('noop')
156
- t.fail('unreacheable')
157
- } catch (err) {
158
- t.equal(err.message, 'the worker has exited')
159
- }
101
+ stream.write('noop');
102
+ [err] = await once(stream, 'error')
103
+ t.equal(err.message, 'the worker has exited')
104
+
105
+ stream.write('noop');
106
+ [err] = await once(stream, 'error')
107
+ t.equal(err.message, 'the worker has exited')
160
108
  })
161
109
 
162
110
  test('close the work if out of scope on gc', { skip: !global.WeakRef }, async function (t) {