@promistream/from-node-stream 0.1.7 → 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 +11 -6
  2. package/src/index.js +27 -4
  3. package/test.js +123 -2
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@promistream/from-node-stream",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "main": "src/index.js",
5
- "repository": "https://codeberg.org/promistream/from-node-stream.git",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://codeberg.org/promistream/from-node-stream.git"
8
+ },
6
9
  "author": "Sven Slootweg <admin@cryto.net>",
7
10
  "license": "WTFPL OR CC0-1.0",
8
11
  "dependencies": {
@@ -15,7 +18,7 @@
15
18
  "@promistream/propagate-abort": "^0.1.7",
16
19
  "@promistream/propagate-peek": "^0.1.2",
17
20
  "@promistream/simple-sink": "^0.3.5",
18
- "@promistream/simple-source": "^0.1.10",
21
+ "@promistream/simple-source": "^0.1.11",
19
22
  "debug-instanced": "^1.0.0",
20
23
  "push-buffer": "^1.5.7",
21
24
  "single-concurrent": "^1.0.0",
@@ -23,15 +26,17 @@
23
26
  },
24
27
  "devDependencies": {
25
28
  "@promistream/buffered-map": "^0.1.1",
26
- "@promistream/collect": "^0.1.2",
29
+ "@promistream/collect": "^0.1.4",
27
30
  "@promistream/debug": "^0.1.2",
28
31
  "@promistream/from-iterable": "^0.1.1",
29
32
  "@promistream/map": "^0.1.10",
30
- "@promistream/parallelize": "^0.1.8",
33
+ "@promistream/parallelize": "^0.1.10",
31
34
  "@promistream/pipe": "^0.1.8",
32
35
  "@promistream/range-numbers": "^0.1.2",
36
+ "@promistream/spy": "^0.1.0",
37
+ "@promistream/test-abort": "^0.1.0",
33
38
  "bluebird": "^3.7.2",
34
39
  "p-defer": "^4.0.1",
35
40
  "through2": "^4.0.2"
36
41
  }
37
- }
42
+ }
package/src/index.js CHANGED
@@ -105,8 +105,12 @@ function convertFromReadable(stream) {
105
105
  onRequest: () => {
106
106
  return read();
107
107
  },
108
- onAbort: () => {
109
- return abort();
108
+ onAbort: (reason) => {
109
+ if (reason === true) {
110
+ return abort();
111
+ } else {
112
+ return abort(reason);
113
+ }
110
114
  }
111
115
  });
112
116
  }
@@ -119,11 +123,26 @@ function isWritableStreamEnded(stream) {
119
123
  }
120
124
 
121
125
  function wireWritable(debug, stream) {
122
- 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
+ }
123
136
 
124
137
  stream.on("error", (error) => {
125
138
  debug("Logging writable interface error:", error);
126
139
  errorEncountered = ensureValidError(error);
140
+ abortPromistream(error);
141
+ });
142
+
143
+ stream.on("end", () => {
144
+ debug("Reached end of underlying Writable stream");
145
+ abortPromistream(true);
127
146
  });
128
147
 
129
148
  async function maybeWaitForDrain() {
@@ -145,6 +164,7 @@ function wireWritable(debug, stream) {
145
164
  await maybeWaitForDrain();
146
165
  if (!isStdioStream(stream) && !isWritableStreamEnded(stream)) {
147
166
  debug("Calling .destroy on Writable stream");
167
+ aborted = true;
148
168
  stream.destroy(error);
149
169
  }
150
170
  },
@@ -152,10 +172,13 @@ function wireWritable(debug, stream) {
152
172
  await maybeWaitForDrain();
153
173
  if (!isStdioStream(stream) && !isWritableStreamEnded(stream)) {
154
174
  debug("Calling .end on Writable stream");
175
+ aborted = true;
155
176
  stream.end();
156
177
  }
157
178
  },
158
179
  tryWrite: async function (value, abort) {
180
+ lastAbort = abort;
181
+
159
182
  if (isWritableStreamEnded(stream)) {
160
183
  debug("Could not write because underlying stream has ended");
161
184
  if (errorEncountered != null) {
@@ -223,7 +246,7 @@ function convertFromTransform(stream) {
223
246
  // This is essentially a 'background process' of sorts, with manual error handling wiring.
224
247
  try {
225
248
  while (endMarker == null) {
226
- await tryWrite(await lastSource.read());
249
+ await tryWrite(await lastSource.read(), lastSource.abort);
227
250
  }
228
251
  } catch (error) {
229
252
  if (endMarker == null && (isEndOfStream(error) || isAborted(error))) { // FIXME: isExhausted?
package/test.js CHANGED
@@ -13,7 +13,10 @@ const fromIterable = require("@promistream/from-iterable");
13
13
  const fromNodeStream = require(".");
14
14
  const collect = require("@promistream/collect");
15
15
  const parallelize = require("@promistream/parallelize");
16
+ const testAbort = require("@promistream/test-abort");
16
17
  const debug = require("@promistream/debug");
18
+ const { once } = require("node:events");
19
+ const spy = require("@promistream/spy");
17
20
 
18
21
  test("readable stream", async () => {
19
22
  let testFile = path.join(__dirname, "pnpm-lock.yaml"); // Big-ish file, so we're likely to read in multiple chunks
@@ -24,7 +27,38 @@ test("readable stream", async () => {
24
27
  collect()
25
28
  ]).read();
26
29
 
27
- assert.deepStrictEqual(result, [ expected ]);
30
+ assert.deepStrictEqual(result, [ expected ]);
31
+ });
32
+
33
+ test("readable stream with error abort", { timeout: 500 }, async () => {
34
+ let testFile = path.join(__dirname, "pnpm-lock.yaml");
35
+ let nodeStream = fs.createReadStream(testFile);
36
+
37
+ let errorFromNodeStreamPromise = once(nodeStream, "error");
38
+
39
+ assert.rejects(async () => {
40
+ let result = await pipe([
41
+ fromNodeStream.fromReadable(nodeStream),
42
+ testAbort(0, new Error("sample error")),
43
+ collect()
44
+ ]).read();
45
+ }, { message: "sample error" });
46
+
47
+ let [ errorFromNodeStream ] = await errorFromNodeStreamPromise;
48
+ assert.strictEqual(errorFromNodeStream.message, "sample error");
49
+ });
50
+
51
+
52
+ test("readable stream with happy abort", { timeout: 500 }, async () => {
53
+ let testFile = path.join(__dirname, "pnpm-lock.yaml");
54
+
55
+ let result = await pipe([
56
+ fromNodeStream.fromReadable(fs.createReadStream(testFile)),
57
+ testAbort(0, true),
58
+ collect()
59
+ ]).read();
60
+
61
+ assert.deepStrictEqual(result, []);
28
62
  });
29
63
 
30
64
  test("writable stream", async () => {
@@ -52,6 +86,34 @@ test("writable stream", async () => {
52
86
  assert.strictEqual(endReached, true);
53
87
  });
54
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
+
55
117
  test("transform stream", async () => {
56
118
  let result = await pipe([
57
119
  fromIterable([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]),
@@ -73,7 +135,7 @@ test("transform stream", async () => {
73
135
  });
74
136
 
75
137
  test("transform stream with abort", async () => {
76
- assert.rejects(async () => {
138
+ await assert.rejects(async () => {
77
139
  let pipeline = pipe([
78
140
  fromIterable([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]),
79
141
  fromNodeStream(new stream.Transform({
@@ -116,6 +178,18 @@ test("transform stream, parallel", async () => {
116
178
  });
117
179
 
118
180
  /* FIXME: Do network socket test with both happy abort and error abort */
181
+ /* TODO: Flaky test behaviour was observed for the below test:
182
+
183
+ ✖ network socket (9.891608ms)
184
+ AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
185
+ + actual - expected
186
+
187
+ + Buffer(0) [Uint8Array] []
188
+ - Buffer(2) [Uint8Array] [
189
+ - 99,
190
+ - 100
191
+ - ]
192
+ */
119
193
  test("network socket", async () => {
120
194
  let { resolve, promise } = pDefer();
121
195
 
@@ -157,3 +231,50 @@ test("network socket", async () => {
157
231
  server.close();
158
232
  }
159
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
+ });