@promistream/from-node-stream 0.1.4 → 0.1.6
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/package.json +13 -11
- package/src/index.js +17 -7
- package/test.js +95 -3
package/package.json
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promistream/from-node-stream",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"main": "src/index.js",
|
|
5
|
-
"repository": "
|
|
5
|
+
"repository": "https://codeberg.org/promistream/from-node-stream.git",
|
|
6
6
|
"author": "Sven Slootweg <admin@cryto.net>",
|
|
7
7
|
"license": "WTFPL OR CC0-1.0",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@joepie91/consumable": "^1.0.1",
|
|
10
10
|
"@joepie91/unreachable": "^1.0.0",
|
|
11
11
|
"@joepie91/warning": "^1.1.0",
|
|
12
|
-
"@promistream/end-of-stream": "^0.1.
|
|
12
|
+
"@promistream/end-of-stream": "^0.1.3",
|
|
13
13
|
"@promistream/is-aborted": "^0.1.1",
|
|
14
14
|
"@promistream/is-end-of-stream": "^0.1.1",
|
|
15
15
|
"@promistream/propagate-abort": "^0.1.7",
|
|
16
|
-
"@promistream/propagate-peek": "^0.1.
|
|
17
|
-
"@promistream/simple-sink": "^0.3.
|
|
18
|
-
"@promistream/simple-source": "^0.1.
|
|
16
|
+
"@promistream/propagate-peek": "^0.1.2",
|
|
17
|
+
"@promistream/simple-sink": "^0.3.5",
|
|
18
|
+
"@promistream/simple-source": "^0.1.10",
|
|
19
19
|
"debug-instanced": "^1.0.0",
|
|
20
20
|
"promisify-event": "^1.0.0",
|
|
21
|
-
"push-buffer": "^1.5.
|
|
21
|
+
"push-buffer": "^1.5.7",
|
|
22
22
|
"single-concurrent": "^1.0.0",
|
|
23
23
|
"split-filter": "^1.1.3"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@promistream/buffered-map": "^0.1.1",
|
|
27
27
|
"@promistream/collect": "^0.1.2",
|
|
28
|
-
"@promistream/debug": "^0.1.
|
|
29
|
-
"@promistream/from-iterable": "^0.1.
|
|
30
|
-
"@promistream/map": "^0.1.
|
|
31
|
-
"@promistream/
|
|
28
|
+
"@promistream/debug": "^0.1.2",
|
|
29
|
+
"@promistream/from-iterable": "^0.1.1",
|
|
30
|
+
"@promistream/map": "^0.1.10",
|
|
31
|
+
"@promistream/parallelize": "^0.1.8",
|
|
32
|
+
"@promistream/pipe": "^0.1.8",
|
|
32
33
|
"@promistream/range-numbers": "^0.1.2",
|
|
33
34
|
"bluebird": "^3.7.2",
|
|
35
|
+
"p-defer": "^4.0.1",
|
|
34
36
|
"through2": "^4.0.2"
|
|
35
37
|
}
|
|
36
38
|
}
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const isEndOfStream = require("@promistream/is-end-of-stream");
|
|
|
9
9
|
const isAborted = require("@promistream/is-aborted");
|
|
10
10
|
const makeDebug = require("debug-instanced")("promistream:from-node-stream");
|
|
11
11
|
|
|
12
|
+
const { once } = require("node:events");
|
|
12
13
|
const pushBuffer = require("push-buffer");
|
|
13
14
|
const promisifyEvent = require("promisify-event");
|
|
14
15
|
const singleConcurrent = require("single-concurrent");
|
|
@@ -75,6 +76,7 @@ function wireReadable(debug, stream) {
|
|
|
75
76
|
// We have run out of values
|
|
76
77
|
if (isPaused && buffer.countLane().requests > 0) {
|
|
77
78
|
debug("Resuming underlying Readable stream, as our buffer ran out");
|
|
79
|
+
isPaused = false;
|
|
78
80
|
stream.resume();
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -125,6 +127,14 @@ function wireWritable(debug, stream) {
|
|
|
125
127
|
errorEncountered = ensureValidError(error);
|
|
126
128
|
});
|
|
127
129
|
|
|
130
|
+
async function maybeWaitForDrain() {
|
|
131
|
+
if (stream.writableNeedDrain) {
|
|
132
|
+
/* Wait for the write buffer to be fully processed */
|
|
133
|
+
// FIXME: Verify that this cannot cause a never-ending wait; if we wait to close the stream until it has drained, then during that time writes to it can continue to happen indefinitely, never letting it drain? Or is this prevented by the backpressure mechanisms we already have (re: .pause and .resume)?
|
|
134
|
+
await once(stream, "drain");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
128
138
|
// We're not wiring into the `finish` event here because we detect the ended stream on the next write attempt instead (inside of `tryWrite`).
|
|
129
139
|
|
|
130
140
|
return {
|
|
@@ -132,12 +142,14 @@ function wireWritable(debug, stream) {
|
|
|
132
142
|
// TODO: Do we need to detach any event handlers prior to destroying streams? ref. https://www.npmjs.com/package/yauzl:
|
|
133
143
|
// "You must unpipe() the readStream from any destination before calling readStream.destroy()."
|
|
134
144
|
// TODO: In a previous version, we skipped the .destroy step if the stream didn't have a .destroy method. What was this for? Streams v1?
|
|
145
|
+
await maybeWaitForDrain();
|
|
135
146
|
if (!isStdioStream(stream) && !isWritableStreamEnded(stream)) {
|
|
136
147
|
debug("Calling .destroy on Writable stream");
|
|
137
148
|
stream.destroy(error);
|
|
138
149
|
}
|
|
139
150
|
},
|
|
140
151
|
end: async function () {
|
|
152
|
+
await maybeWaitForDrain();
|
|
141
153
|
if (!isStdioStream(stream) && !isWritableStreamEnded(stream)) {
|
|
142
154
|
debug("Calling .end on Writable stream");
|
|
143
155
|
stream.end();
|
|
@@ -181,10 +193,11 @@ function convertFromWritable(stream) {
|
|
|
181
193
|
|
|
182
194
|
return simpleSink({
|
|
183
195
|
onAbort: async (error) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} else {
|
|
196
|
+
// TODO: this is mostly-duplicated logic with that in the transform stream implementation
|
|
197
|
+
if (error instanceof Error) {
|
|
187
198
|
return destroy(error);
|
|
199
|
+
} else {
|
|
200
|
+
return end();
|
|
188
201
|
}
|
|
189
202
|
},
|
|
190
203
|
onEnd: async () => {
|
|
@@ -213,10 +226,9 @@ function convertFromTransform(stream) {
|
|
|
213
226
|
await tryWrite(await lastSource.read());
|
|
214
227
|
}
|
|
215
228
|
} catch (error) {
|
|
216
|
-
if (endMarker == null && (isEndOfStream(error) || isAborted(error))) {
|
|
229
|
+
if (endMarker == null && (isEndOfStream(error) || isAborted(error))) { // FIXME: isExhausted?
|
|
217
230
|
endMarker = error;
|
|
218
231
|
|
|
219
|
-
// TODO: Is this necessary? Is this not already handled by wiring for the readable/writable interface?
|
|
220
232
|
if (isAborted(error)) {
|
|
221
233
|
if (error.cause instanceof Error) {
|
|
222
234
|
destroy(error);
|
|
@@ -228,8 +240,6 @@ function convertFromTransform(stream) {
|
|
|
228
240
|
end();
|
|
229
241
|
}
|
|
230
242
|
}
|
|
231
|
-
|
|
232
|
-
buffer.pushError(error);
|
|
233
243
|
}
|
|
234
244
|
});
|
|
235
245
|
|
package/test.js
CHANGED
|
@@ -5,11 +5,14 @@ const assert = require("node:assert");
|
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const stream = require("node:stream");
|
|
8
|
+
const net = require("node:net");
|
|
9
|
+
const pDefer = require("p-defer").default;
|
|
8
10
|
|
|
9
11
|
const pipe = require("@promistream/pipe");
|
|
10
12
|
const fromIterable = require("@promistream/from-iterable");
|
|
11
13
|
const fromNodeStream = require(".");
|
|
12
14
|
const collect = require("@promistream/collect");
|
|
15
|
+
const parallelize = require("@promistream/parallelize");
|
|
13
16
|
const debug = require("@promistream/debug");
|
|
14
17
|
|
|
15
18
|
test("readable stream", async () => {
|
|
@@ -55,9 +58,12 @@ test("transform stream", async () => {
|
|
|
55
58
|
fromNodeStream(new stream.Transform({
|
|
56
59
|
objectMode: true,
|
|
57
60
|
transform(chunk, _encoding, callback) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
/* Intentionally delay things, to validate that processing in the Node stream fully completes before the pipeline gets torn down */
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
this.push(chunk);
|
|
64
|
+
this.push(chunk * 2);
|
|
65
|
+
callback();
|
|
66
|
+
}, 10);
|
|
61
67
|
}
|
|
62
68
|
})),
|
|
63
69
|
collect()
|
|
@@ -65,3 +71,89 @@ test("transform stream", async () => {
|
|
|
65
71
|
|
|
66
72
|
assert.deepStrictEqual(result, [ 0, 0, 1, 2, 2, 4, 3, 6, 4, 8, 5, 10, 6, 12, 7, 14, 8, 16, 9, 18, 10, 20 ]);
|
|
67
73
|
});
|
|
74
|
+
|
|
75
|
+
test("transform stream with abort", async () => {
|
|
76
|
+
assert.rejects(async () => {
|
|
77
|
+
let pipeline = pipe([
|
|
78
|
+
fromIterable([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]),
|
|
79
|
+
fromNodeStream(new stream.Transform({
|
|
80
|
+
objectMode: true,
|
|
81
|
+
transform(chunk, _encoding, callback) {
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
this.push(chunk);
|
|
84
|
+
this.push(chunk * 2);
|
|
85
|
+
callback();
|
|
86
|
+
}, 10);
|
|
87
|
+
}
|
|
88
|
+
})),
|
|
89
|
+
collect()
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
pipeline.abort(new Error("Some arbitrary error"));
|
|
93
|
+
let result = await pipeline.read();
|
|
94
|
+
}, { message: "Some arbitrary error" });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("transform stream, parallel", async () => {
|
|
98
|
+
let result = await pipe([
|
|
99
|
+
fromIterable([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]),
|
|
100
|
+
fromNodeStream(new stream.Transform({
|
|
101
|
+
objectMode: true,
|
|
102
|
+
transform(chunk, _encoding, callback) {
|
|
103
|
+
/* Intentionally delay things, to validate that processing in the Node stream fully completes before the pipeline gets torn down */
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
this.push(chunk);
|
|
106
|
+
this.push(chunk * 2);
|
|
107
|
+
callback();
|
|
108
|
+
}, 10);
|
|
109
|
+
}
|
|
110
|
+
})),
|
|
111
|
+
parallelize(8),
|
|
112
|
+
collect()
|
|
113
|
+
]).read();
|
|
114
|
+
|
|
115
|
+
assert.deepStrictEqual(result, [ 0, 0, 1, 2, 2, 4, 3, 6, 4, 8, 5, 10, 6, 12, 7, 14, 8, 16, 9, 18, 10, 20 ]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/* FIXME: Do network socket test with both happy abort and error abort */
|
|
119
|
+
test("network socket", async () => {
|
|
120
|
+
let { resolve, promise } = pDefer();
|
|
121
|
+
|
|
122
|
+
let server = net.createServer((socket) => {
|
|
123
|
+
// console.log("connected", socket);
|
|
124
|
+
let serverRead = fromNodeStream.fromReadable(socket);
|
|
125
|
+
let serverWrite = fromNodeStream.fromWritable(socket);
|
|
126
|
+
resolve({ serverRead, serverWrite });
|
|
127
|
+
});
|
|
128
|
+
server.listen(14292);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
let client = net.connect({ host: "localhost", port: 14292 });
|
|
132
|
+
let clientRead = fromNodeStream.fromReadable(client);
|
|
133
|
+
let clientWrite = fromNodeStream.fromWritable(client);
|
|
134
|
+
let { serverRead, serverWrite } = await promise;
|
|
135
|
+
|
|
136
|
+
let serverPipe = pipe([ serverRead, collect() ]);
|
|
137
|
+
let serverWritePipe = pipe([ fromIterable([ Buffer.from("c"), Buffer.from("d") ]), serverWrite ]);
|
|
138
|
+
let clientPipe = pipe([ clientRead, collect() ]);
|
|
139
|
+
let clientWritePipe = pipe([ fromIterable([ Buffer.from("a"), Buffer.from("b") ]), clientWrite ]);
|
|
140
|
+
|
|
141
|
+
let clientWritePromise = clientWritePipe.read();
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 1)); /* give it a couple ticks to actually read from the iterable source before aborting */
|
|
143
|
+
clientWritePipe.abort(true);
|
|
144
|
+
|
|
145
|
+
let [ serverResult, serverWriteResult, clientResult, clientWriteResult ] = await Promise.all([
|
|
146
|
+
serverPipe.read(),
|
|
147
|
+
serverWritePipe.read(),
|
|
148
|
+
clientPipe.read(),
|
|
149
|
+
clientWritePromise,
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
assert.deepStrictEqual(Buffer.concat(serverResult), Buffer.from("ab"));
|
|
153
|
+
assert.deepStrictEqual(Buffer.concat(clientResult), Buffer.from("cd"));
|
|
154
|
+
assert.deepStrictEqual(serverWriteResult, undefined);
|
|
155
|
+
assert.deepStrictEqual(clientWriteResult, undefined);
|
|
156
|
+
} finally {
|
|
157
|
+
server.close();
|
|
158
|
+
}
|
|
159
|
+
});
|