@promistream/from-node-stream 0.1.8 → 0.1.9

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.
Files changed (3) hide show
  1. package/package.json +2 -1
  2. package/src/index.js +21 -2
  3. package/test.js +77 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promistream/from-node-stream",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "main": "src/index.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,6 +33,7 @@
33
33
  "@promistream/parallelize": "^0.1.10",
34
34
  "@promistream/pipe": "^0.1.8",
35
35
  "@promistream/range-numbers": "^0.1.2",
36
+ "@promistream/spy": "^0.1.0",
36
37
  "@promistream/test-abort": "^0.1.0",
37
38
  "bluebird": "^3.7.2",
38
39
  "p-defer": "^4.0.1",
package/src/index.js CHANGED
@@ -123,11 +123,26 @@ function isWritableStreamEnded(stream) {
123
123
  }
124
124
 
125
125
  function wireWritable(debug, stream) {
126
- let errorEncountered;
126
+ let errorEncountered, lastAbort;
127
+ let aborted = false;
128
+
129
+ function abortPromistream(reason) {
130
+ /* If an abort/end of the underlying Node stream occurred for a reason that's entirely unrelated to our current pipeline - most notably, this happens if the error originates from the *read* side of a non-transform Duplex stream - then we want to make sure that we tear down the Promistream pipeline on this side too. */
131
+ if (lastAbort != null && !aborted) {
132
+ aborted = true;
133
+ lastAbort(reason);
134
+ }
135
+ }
127
136
 
128
137
  stream.on("error", (error) => {
129
138
  debug("Logging writable interface error:", error);
130
139
  errorEncountered = ensureValidError(error);
140
+ abortPromistream(error);
141
+ });
142
+
143
+ stream.on("end", () => {
144
+ debug("Reached end of underlying Writable stream");
145
+ abortPromistream(true);
131
146
  });
132
147
 
133
148
  async function maybeWaitForDrain() {
@@ -149,6 +164,7 @@ function wireWritable(debug, stream) {
149
164
  await maybeWaitForDrain();
150
165
  if (!isStdioStream(stream) && !isWritableStreamEnded(stream)) {
151
166
  debug("Calling .destroy on Writable stream");
167
+ aborted = true;
152
168
  stream.destroy(error);
153
169
  }
154
170
  },
@@ -156,10 +172,13 @@ function wireWritable(debug, stream) {
156
172
  await maybeWaitForDrain();
157
173
  if (!isStdioStream(stream) && !isWritableStreamEnded(stream)) {
158
174
  debug("Calling .end on Writable stream");
175
+ aborted = true;
159
176
  stream.end();
160
177
  }
161
178
  },
162
179
  tryWrite: async function (value, abort) {
180
+ lastAbort = abort;
181
+
163
182
  if (isWritableStreamEnded(stream)) {
164
183
  debug("Could not write because underlying stream has ended");
165
184
  if (errorEncountered != null) {
@@ -227,7 +246,7 @@ function convertFromTransform(stream) {
227
246
  // This is essentially a 'background process' of sorts, with manual error handling wiring.
228
247
  try {
229
248
  while (endMarker == null) {
230
- await tryWrite(await lastSource.read());
249
+ await tryWrite(await lastSource.read(), lastSource.abort);
231
250
  }
232
251
  } catch (error) {
233
252
  if (endMarker == null && (isEndOfStream(error) || isAborted(error))) { // FIXME: isExhausted?
package/test.js CHANGED
@@ -16,6 +16,7 @@ const parallelize = require("@promistream/parallelize");
16
16
  const testAbort = require("@promistream/test-abort");
17
17
  const debug = require("@promistream/debug");
18
18
  const { once } = require("node:events");
19
+ const spy = require("@promistream/spy");
19
20
 
20
21
  test("readable stream", async () => {
21
22
  let testFile = path.join(__dirname, "pnpm-lock.yaml"); // Big-ish file, so we're likely to read in multiple chunks
@@ -60,7 +61,6 @@ test("readable stream with happy abort", { timeout: 500 }, async () => {
60
61
  assert.deepStrictEqual(result, []);
61
62
  });
62
63
 
63
-
64
64
  test("writable stream", async () => {
65
65
  let buffer = Buffer.alloc(400000);
66
66
 
@@ -86,6 +86,34 @@ test("writable stream", async () => {
86
86
  assert.strictEqual(endReached, true);
87
87
  });
88
88
 
89
+ test("writable stream with error abort", async () => {
90
+ let buffer = Buffer.alloc(400000);
91
+
92
+ let writableBuffer = Buffer.alloc(0);
93
+ let endReached = false;
94
+ let nodeWritable = new stream.Writable({
95
+ write(chunk, _encoding, callback) {
96
+ writableBuffer = Buffer.concat([ writableBuffer, chunk ]);
97
+ callback();
98
+ },
99
+ final(callback) {
100
+ endReached = true;
101
+ callback();
102
+ }
103
+ });
104
+
105
+ await assert.rejects(async () => {
106
+ await pipe([
107
+ fromIterable([ buffer, buffer, buffer ]),
108
+ testAbort(0, new Error("sample error")),
109
+ fromNodeStream(nodeWritable)
110
+ ]).read();
111
+ }, { message: "sample error" });
112
+
113
+ assert.strictEqual(writableBuffer.length, 0);
114
+ assert.strictEqual(endReached, false);
115
+ });
116
+
89
117
  test("transform stream", async () => {
90
118
  let result = await pipe([
91
119
  fromIterable([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]),
@@ -107,7 +135,7 @@ test("transform stream", async () => {
107
135
  });
108
136
 
109
137
  test("transform stream with abort", async () => {
110
- assert.rejects(async () => {
138
+ await assert.rejects(async () => {
111
139
  let pipeline = pipe([
112
140
  fromIterable([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]),
113
141
  fromNodeStream(new stream.Transform({
@@ -203,3 +231,50 @@ test("network socket", async () => {
203
231
  server.close();
204
232
  }
205
233
  });
234
+
235
+ async function wait10() {
236
+ await new Promise((resolve) => setTimeout(resolve, 10));
237
+ }
238
+
239
+ test("network socket with error abort", { timeout: 500 }, async () => {
240
+ let { resolve, promise } = pDefer();
241
+
242
+ let server = net.createServer((socket) => {
243
+ let serverRead = fromNodeStream.fromReadable(socket);
244
+ let serverWrite = fromNodeStream.fromWritable(socket);
245
+ resolve({ serverRead, serverWrite });
246
+ });
247
+ server.listen(14292);
248
+
249
+ try {
250
+ let client = net.connect({ host: "localhost", port: 14292 });
251
+ let clientRead = fromNodeStream.fromReadable(client);
252
+ let clientWrite = fromNodeStream.fromWritable(client);
253
+ let { serverRead, serverWrite } = await promise;
254
+
255
+ let serverPipe = pipe([ serverRead, collect() ]);
256
+ let serverWritePipe = pipe([
257
+ fromIterable([ Buffer.from("c"), Buffer.from("d") ]),
258
+ testAbort(0, new Error("sample error")),
259
+ serverWrite
260
+ ]);
261
+
262
+ let clientPipe = pipe([ clientRead, collect() ]);
263
+ let clientWritePipe = pipe([
264
+ fromIterable([ Buffer.from("a"), Buffer.from("b") ]),
265
+ /* Give the client side some time to notice that the server side closed the connection */
266
+ spy(async(value) => { await wait10(); return value; }),
267
+ clientWrite
268
+ ]);
269
+
270
+ let results = await Promise.all([
271
+ assert.rejects(() => serverPipe.read(), { message: "sample error" }),
272
+ assert.rejects(() => serverWritePipe.read(), { message: "sample error" }),
273
+ /* The other side of the connection closes normally, like it would for any disconnection */
274
+ clientPipe.read(),
275
+ clientWritePipe.read(),
276
+ ]);
277
+ } finally {
278
+ server.close();
279
+ }
280
+ });