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
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@v6
|
|
28
28
|
with:
|
|
29
29
|
persist-credentials: false
|
|
30
30
|
|
|
@@ -38,20 +38,17 @@ jobs:
|
|
|
38
38
|
contents: read
|
|
39
39
|
strategy:
|
|
40
40
|
matrix:
|
|
41
|
-
node-version: [
|
|
41
|
+
node-version: [20, 22, 24, 26]
|
|
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@v6
|
|
50
47
|
with:
|
|
51
48
|
persist-credentials: false
|
|
52
49
|
|
|
53
50
|
- name: Setup Node ${{ matrix.node-version }}
|
|
54
|
-
uses: actions/setup-node@
|
|
51
|
+
uses: actions/setup-node@v6
|
|
55
52
|
with:
|
|
56
53
|
node-version: ${{ matrix.node-version }}
|
|
57
54
|
|
|
@@ -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/README.md
CHANGED
|
@@ -39,6 +39,8 @@ stream.flush(function () {
|
|
|
39
39
|
})
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
`flush(cb)` waits for the worker destination flush when supported (`flush`, `flushSync`, or pending `drain`).
|
|
43
|
+
|
|
42
44
|
In `worker.js`:
|
|
43
45
|
|
|
44
46
|
```js
|
|
@@ -87,7 +89,8 @@ This module works with `yarn` in PnP (plug'n play) mode too!
|
|
|
87
89
|
### Emit events
|
|
88
90
|
|
|
89
91
|
You can emit events on the ThreadStream from your worker using [`worker.parentPort.postMessage()`](https://nodejs.org/api/worker_threads.html#workerparentport).
|
|
90
|
-
|
|
92
|
+
Messages that do not carry a thread-stream protocol `code` are ignored.
|
|
93
|
+
For custom events, the message (JSON object) must have the following data structure:
|
|
91
94
|
|
|
92
95
|
```js
|
|
93
96
|
parentPort.postMessage({
|
package/eslint.config.js
ADDED
package/index.d.ts
CHANGED
|
@@ -59,6 +59,13 @@ declare class ThreadStream extends EventEmitter {
|
|
|
59
59
|
* Calling the {@link write()} method after calling {@link end()} will emit an error.
|
|
60
60
|
*/
|
|
61
61
|
end(): void
|
|
62
|
+
/**
|
|
63
|
+
* Flush the stream asynchronously.
|
|
64
|
+
*
|
|
65
|
+
* The callback is invoked once data has been consumed by the worker and the
|
|
66
|
+
* worker destination has acknowledged the flush.
|
|
67
|
+
*/
|
|
68
|
+
flush(cb?: (err?: Error) => void): void
|
|
62
69
|
/**
|
|
63
70
|
* Flush the stream synchronously.
|
|
64
71
|
* This method should be called in the shutdown phase to make sure that all data has been flushed.
|
package/index.js
CHANGED
|
@@ -18,6 +18,8 @@ const kImpl = Symbol('kImpl')
|
|
|
18
18
|
// V8 limit for string size
|
|
19
19
|
const MAX_STRING = buffer.constants.MAX_STRING_LENGTH
|
|
20
20
|
|
|
21
|
+
function noop () {}
|
|
22
|
+
|
|
21
23
|
class FakeWeakRef {
|
|
22
24
|
constructor (value) {
|
|
23
25
|
this._value = value
|
|
@@ -54,6 +56,7 @@ function createWorker (stream, opts) {
|
|
|
54
56
|
|
|
55
57
|
const worker = new Worker(toExecute, {
|
|
56
58
|
...opts.workerOpts,
|
|
59
|
+
name: opts.workerOpts?.name || 'thread-stream',
|
|
57
60
|
trackUnmanagedFds: false,
|
|
58
61
|
workerData: {
|
|
59
62
|
filename: filename.indexOf('file://') === 0
|
|
@@ -114,14 +117,15 @@ function nextFlush (stream) {
|
|
|
114
117
|
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
115
118
|
} else {
|
|
116
119
|
// multi-byte utf-8
|
|
117
|
-
stream
|
|
118
|
-
// err is already handled in
|
|
120
|
+
waitForRead(stream, () => {
|
|
121
|
+
// err is already handled in waitForRead()
|
|
119
122
|
if (stream.destroyed) {
|
|
120
123
|
return
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
124
127
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
128
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
125
129
|
|
|
126
130
|
// Find a toWrite length that fits the buffer
|
|
127
131
|
// it must exists as the buffer is at least 4 bytes length
|
|
@@ -140,9 +144,10 @@ function nextFlush (stream) {
|
|
|
140
144
|
// we had a flushSync in the meanwhile
|
|
141
145
|
return
|
|
142
146
|
}
|
|
143
|
-
stream
|
|
147
|
+
waitForRead(stream, () => {
|
|
144
148
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
145
149
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
150
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
146
151
|
nextFlush(stream)
|
|
147
152
|
})
|
|
148
153
|
} else {
|
|
@@ -160,13 +165,19 @@ function onWorkerMessage (msg) {
|
|
|
160
165
|
return
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
// Node.js watch mode may send internal worker messages that do not
|
|
169
|
+
// participate in thread-stream's worker protocol.
|
|
170
|
+
if (msg?.code == null) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
163
174
|
switch (msg.code) {
|
|
164
175
|
case 'READY':
|
|
165
176
|
// Replace the FakeWeakRef with a
|
|
166
177
|
// proper one.
|
|
167
178
|
this.stream = new WeakRef(stream)
|
|
168
179
|
|
|
169
|
-
stream
|
|
180
|
+
waitForRead(stream, () => {
|
|
170
181
|
stream[kImpl].ready = true
|
|
171
182
|
stream.emit('ready')
|
|
172
183
|
})
|
|
@@ -181,6 +192,19 @@ function onWorkerMessage (msg) {
|
|
|
181
192
|
stream.emit(msg.name, msg.args)
|
|
182
193
|
}
|
|
183
194
|
break
|
|
195
|
+
case 'FLUSHED': {
|
|
196
|
+
if (msg.context !== 'thread-stream') {
|
|
197
|
+
destroy(stream, new Error('this should not happen: ' + msg.code))
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cb = stream[kImpl].flushCallbacks.get(msg.id)
|
|
202
|
+
if (cb) {
|
|
203
|
+
stream[kImpl].flushCallbacks.delete(msg.id)
|
|
204
|
+
process.nextTick(cb)
|
|
205
|
+
}
|
|
206
|
+
break
|
|
207
|
+
}
|
|
184
208
|
case 'WARNING':
|
|
185
209
|
process.emitWarning(msg.err)
|
|
186
210
|
break
|
|
@@ -225,6 +249,8 @@ class ThreadStream extends EventEmitter {
|
|
|
225
249
|
this[kImpl].errored = null
|
|
226
250
|
this[kImpl].closed = false
|
|
227
251
|
this[kImpl].buf = ''
|
|
252
|
+
this[kImpl].flushCallbacks = new Map()
|
|
253
|
+
this[kImpl].nextFlushId = 0
|
|
228
254
|
|
|
229
255
|
// TODO (fix): Make private?
|
|
230
256
|
this.worker = createWorker(this, opts) // TODO (fix): make private
|
|
@@ -285,28 +311,15 @@ class ThreadStream extends EventEmitter {
|
|
|
285
311
|
}
|
|
286
312
|
|
|
287
313
|
flush (cb) {
|
|
288
|
-
|
|
289
|
-
if (typeof cb === 'function') {
|
|
290
|
-
process.nextTick(cb, new Error('the worker has exited'))
|
|
291
|
-
}
|
|
292
|
-
return
|
|
293
|
-
}
|
|
314
|
+
cb = typeof cb === 'function' ? cb : noop
|
|
294
315
|
|
|
295
|
-
|
|
296
|
-
const writeIndex = Atomics.load(this[kImpl].state, WRITE_INDEX)
|
|
297
|
-
// process._rawDebug(`(flush) readIndex (${Atomics.load(this.state, READ_INDEX)}) writeIndex (${Atomics.load(this.state, WRITE_INDEX)})`)
|
|
298
|
-
wait(this[kImpl].state, READ_INDEX, writeIndex, Infinity, (err, res) => {
|
|
316
|
+
flushBuffer(this, (err) => {
|
|
299
317
|
if (err) {
|
|
300
|
-
destroy(this, err)
|
|
301
318
|
process.nextTick(cb, err)
|
|
302
319
|
return
|
|
303
320
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
this.flush(cb)
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
process.nextTick(cb)
|
|
321
|
+
|
|
322
|
+
requestWorkerFlush(this, cb)
|
|
310
323
|
})
|
|
311
324
|
}
|
|
312
325
|
|
|
@@ -364,6 +377,93 @@ class ThreadStream extends EventEmitter {
|
|
|
364
377
|
}
|
|
365
378
|
}
|
|
366
379
|
|
|
380
|
+
function flushBuffer (stream, cb) {
|
|
381
|
+
if (stream[kImpl].destroyed) {
|
|
382
|
+
process.nextTick(cb, new Error('the worker has exited'))
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!stream[kImpl].sync && (stream[kImpl].flushing || stream[kImpl].buf.length > 0)) {
|
|
387
|
+
setImmediate(flushBuffer, stream, cb)
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
waitForRead(stream, cb)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function waitForRead (stream, cb) {
|
|
395
|
+
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
396
|
+
wait(stream[kImpl].state, READ_INDEX, writeIndex, Infinity, (err, res) => {
|
|
397
|
+
if (err) {
|
|
398
|
+
destroy(stream, err)
|
|
399
|
+
cb(err)
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (res !== 'ok') {
|
|
404
|
+
waitForRead(stream, cb)
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
cb()
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function requestWorkerFlush (stream, cb) {
|
|
413
|
+
if (stream[kImpl].destroyed) {
|
|
414
|
+
process.nextTick(cb, new Error('the worker has exited'))
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!stream[kImpl].ready) {
|
|
419
|
+
const onReady = () => {
|
|
420
|
+
cleanup()
|
|
421
|
+
requestWorkerFlush(stream, cb)
|
|
422
|
+
}
|
|
423
|
+
const onClose = () => {
|
|
424
|
+
cleanup()
|
|
425
|
+
process.nextTick(cb, new Error('the worker has exited'))
|
|
426
|
+
}
|
|
427
|
+
const cleanup = () => {
|
|
428
|
+
stream.off('ready', onReady)
|
|
429
|
+
stream.off('close', onClose)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
stream.once('ready', onReady)
|
|
433
|
+
stream.once('close', onClose)
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const id = ++stream[kImpl].nextFlushId
|
|
438
|
+
stream[kImpl].flushCallbacks.set(id, cb)
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
stream.worker.postMessage({
|
|
442
|
+
code: 'FLUSH',
|
|
443
|
+
context: 'thread-stream',
|
|
444
|
+
id
|
|
445
|
+
})
|
|
446
|
+
} catch (err) {
|
|
447
|
+
stream[kImpl].flushCallbacks.delete(id)
|
|
448
|
+
destroy(stream, err)
|
|
449
|
+
process.nextTick(cb, err)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function failPendingFlushCallbacks (stream, err) {
|
|
454
|
+
const callbacks = stream[kImpl].flushCallbacks
|
|
455
|
+
if (callbacks.size === 0) {
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const flushErr = err || new Error('the worker has exited')
|
|
460
|
+
|
|
461
|
+
for (const cb of callbacks.values()) {
|
|
462
|
+
process.nextTick(cb, flushErr)
|
|
463
|
+
}
|
|
464
|
+
callbacks.clear()
|
|
465
|
+
}
|
|
466
|
+
|
|
367
467
|
function error (stream, err) {
|
|
368
468
|
setImmediate(() => {
|
|
369
469
|
stream.emit('error', err)
|
|
@@ -375,6 +475,7 @@ function destroy (stream, err) {
|
|
|
375
475
|
return
|
|
376
476
|
}
|
|
377
477
|
stream[kImpl].destroyed = true
|
|
478
|
+
failPendingFlushCallbacks(stream, err)
|
|
378
479
|
|
|
379
480
|
if (err) {
|
|
380
481
|
stream[kImpl].errored = err
|
|
@@ -468,6 +569,7 @@ function writeSync (stream) {
|
|
|
468
569
|
flushSync(stream)
|
|
469
570
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
470
571
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
572
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
471
573
|
continue
|
|
472
574
|
} else if (leftover < 0) {
|
|
473
575
|
// stream should never happen
|
|
@@ -485,6 +587,7 @@ function writeSync (stream) {
|
|
|
485
587
|
flushSync(stream)
|
|
486
588
|
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
487
589
|
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
590
|
+
Atomics.notify(stream[kImpl].state, READ_INDEX)
|
|
488
591
|
|
|
489
592
|
// Find a toWrite length that fits the buffer
|
|
490
593
|
// 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
|
@@ -12,26 +12,114 @@ const {
|
|
|
12
12
|
} = workerData
|
|
13
13
|
|
|
14
14
|
let destination
|
|
15
|
+
const flushQueue = []
|
|
16
|
+
let flushing = false
|
|
15
17
|
|
|
16
18
|
const state = new Int32Array(stateBuf)
|
|
17
19
|
const data = Buffer.from(dataBuf)
|
|
18
20
|
|
|
21
|
+
// Keep the event loop alive - Atomics.waitAsync promises don't prevent worker exit
|
|
22
|
+
const keepAlive = setInterval(() => {}, 60 * 60 * 1000)
|
|
23
|
+
|
|
24
|
+
function onParentPortMessage (msg) {
|
|
25
|
+
if (!msg || msg.code !== 'FLUSH' || msg.context !== 'thread-stream') {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
flushQueue.push(msg.id)
|
|
30
|
+
processFlushQueue()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function processFlushQueue () {
|
|
34
|
+
if (flushing || !destination) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const id = flushQueue.shift()
|
|
39
|
+
if (id === undefined) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
flushing = true
|
|
44
|
+
flushDestination((err) => {
|
|
45
|
+
flushing = false
|
|
46
|
+
|
|
47
|
+
if (err) {
|
|
48
|
+
parentPort.postMessage({
|
|
49
|
+
code: 'ERROR',
|
|
50
|
+
err
|
|
51
|
+
})
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
parentPort.postMessage({
|
|
56
|
+
code: 'FLUSHED',
|
|
57
|
+
context: 'thread-stream',
|
|
58
|
+
id
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
processFlushQueue()
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function flushDestination (cb) {
|
|
66
|
+
if (typeof destination?.flush === 'function') {
|
|
67
|
+
if (destination.flush.length === 0) {
|
|
68
|
+
try {
|
|
69
|
+
const result = destination.flush()
|
|
70
|
+
if (result && typeof result.then === 'function') {
|
|
71
|
+
result.then(() => cb(), cb)
|
|
72
|
+
} else {
|
|
73
|
+
cb()
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
cb(err)
|
|
77
|
+
}
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let done = false
|
|
82
|
+
const onDone = (err) => {
|
|
83
|
+
if (done) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
done = true
|
|
87
|
+
cb(err)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const result = destination.flush(onDone)
|
|
92
|
+
if (result && typeof result.then === 'function') {
|
|
93
|
+
result.then(() => onDone(), onDone)
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
onDone(err)
|
|
97
|
+
}
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof destination?.flushSync === 'function') {
|
|
102
|
+
try {
|
|
103
|
+
destination.flushSync()
|
|
104
|
+
cb()
|
|
105
|
+
} catch (err) {
|
|
106
|
+
cb(err)
|
|
107
|
+
}
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (destination?.writableNeedDrain && !destination?.writableEnded) {
|
|
112
|
+
destination.once('drain', cb)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
cb()
|
|
117
|
+
}
|
|
118
|
+
|
|
19
119
|
async function start () {
|
|
20
120
|
let worker
|
|
21
121
|
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
|
-
}
|
|
122
|
+
worker = (await realImport(filename))
|
|
35
123
|
} catch (error) {
|
|
36
124
|
// A yarn user that tries to start a ThreadStream for an external module
|
|
37
125
|
// provides a filename pointing to a zip file.
|
|
@@ -53,6 +141,19 @@ async function start () {
|
|
|
53
141
|
} catch {
|
|
54
142
|
throw error
|
|
55
143
|
}
|
|
144
|
+
} else if (filename.endsWith('.ts') || filename.endsWith('.cts')) {
|
|
145
|
+
// Native TypeScript import failed (type stripping not enabled).
|
|
146
|
+
// Fall back to ts-node for TypeScript files.
|
|
147
|
+
try {
|
|
148
|
+
if (!process[Symbol.for('ts-node.register.instance')]) {
|
|
149
|
+
realRequire('ts-node/register')
|
|
150
|
+
} else if (process.env.TS_NODE_DEV) {
|
|
151
|
+
realRequire('ts-node-dev')
|
|
152
|
+
}
|
|
153
|
+
worker = realRequire(decodeURIComponent(filename.replace(process.platform === 'win32' ? 'file:///' : 'file://', '')))
|
|
154
|
+
} catch {
|
|
155
|
+
throw error
|
|
156
|
+
}
|
|
56
157
|
} else {
|
|
57
158
|
throw error
|
|
58
159
|
}
|
|
@@ -84,16 +185,21 @@ async function start () {
|
|
|
84
185
|
const end = Atomics.load(state, WRITE_INDEX)
|
|
85
186
|
Atomics.store(state, READ_INDEX, end)
|
|
86
187
|
Atomics.notify(state, READ_INDEX)
|
|
188
|
+
clearInterval(keepAlive)
|
|
87
189
|
setImmediate(() => {
|
|
88
190
|
process.exit(0)
|
|
89
191
|
})
|
|
90
192
|
})
|
|
193
|
+
|
|
194
|
+
processFlushQueue()
|
|
91
195
|
}
|
|
92
196
|
|
|
93
197
|
// No .catch() handler,
|
|
94
198
|
// in case there is an error it goes
|
|
95
199
|
// to unhandledRejection
|
|
96
200
|
start().then(function () {
|
|
201
|
+
parentPort.on('message', onParentPortMessage)
|
|
202
|
+
|
|
97
203
|
parentPort.postMessage({
|
|
98
204
|
code: 'READY'
|
|
99
205
|
})
|
package/package.json
CHANGED
|
@@ -1,42 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thread-stream",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.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
|
-
"real-require": "^0.
|
|
11
|
+
"real-require": "^1.0.0"
|
|
9
12
|
},
|
|
10
13
|
"devDependencies": {
|
|
11
|
-
"@types/node": "^
|
|
12
|
-
"@
|
|
13
|
-
"
|
|
14
|
+
"@types/node": "^25.0.2",
|
|
15
|
+
"@yao-pkg/pkg": "^6.0.0",
|
|
16
|
+
"borp": "^1.0.0",
|
|
14
17
|
"desm": "^1.3.0",
|
|
18
|
+
"eslint": "^9.39.1",
|
|
15
19
|
"fastbench": "^1.0.1",
|
|
16
|
-
"
|
|
20
|
+
"neostandard": "^0.13.0",
|
|
17
21
|
"pino-elasticsearch": "^8.0.0",
|
|
18
|
-
"sonic-boom": "^
|
|
19
|
-
"standard": "^17.0.0",
|
|
20
|
-
"tap": "^16.2.0",
|
|
22
|
+
"sonic-boom": "^5.0.0",
|
|
21
23
|
"ts-node": "^10.8.0",
|
|
22
|
-
"typescript": "
|
|
23
|
-
"why-is-node-running": "^2.2.2"
|
|
24
|
+
"typescript": "~5.7.3"
|
|
24
25
|
},
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsc --noEmit",
|
|
27
|
-
"
|
|
28
|
-
"test
|
|
29
|
-
"test:ci
|
|
30
|
-
"test:
|
|
31
|
-
"
|
|
32
|
-
"transpile": "sh ./test/ts/transpile.sh",
|
|
33
|
-
"prepare": "husky install"
|
|
34
|
-
},
|
|
35
|
-
"standard": {
|
|
36
|
-
"ignore": [
|
|
37
|
-
"test/ts/**/*",
|
|
38
|
-
"test/syntax-error.mjs"
|
|
39
|
-
]
|
|
28
|
+
"lint": "eslint",
|
|
29
|
+
"test": "npm run lint && npm run build && npm run transpile && borp --pattern \"test/*.test.{js,mjs}\"",
|
|
30
|
+
"test:ci": "npm run lint && npm run transpile && borp --pattern \"test/*.test.{js,mjs}\"",
|
|
31
|
+
"test:yarn": "npm run transpile && borp --pattern \"test/*.test.js\"",
|
|
32
|
+
"transpile": "sh ./test/ts/transpile.sh"
|
|
40
33
|
},
|
|
41
34
|
"repository": {
|
|
42
35
|
"type": "git",
|