stream-demux 8.1.0 → 9.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.
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const WritableConsumableStream = require('writable-consumable-stream');
2
2
  const DemuxedConsumableStream = require('./demuxed-consumable-stream');
3
+ const StreamConsumer = require('./stream-consumer');
3
4
 
4
5
  class StreamDemux {
5
6
  constructor() {
@@ -120,74 +121,19 @@ class StreamDemux {
120
121
  return this.getConsumerStatsListAll().length;
121
122
  }
122
123
 
123
- createConsumer(streamName, timeout) {
124
- let mainStreamConsumer = this._mainStream.createConsumer(timeout);
125
-
126
- let consumerNext = mainStreamConsumer.next;
127
- mainStreamConsumer.next = async function () {
128
- while (true) {
129
- let packet = await consumerNext.apply(this, arguments);
130
- if (packet.value) {
131
- if (
132
- packet.value.stream === streamName ||
133
- packet.value.consumerId === this.id
134
- ) {
135
- if (packet.value.data.done) {
136
- this.return();
137
- }
138
- return packet.value.data;
139
- }
140
- }
141
- if (packet.done) {
142
- return packet;
143
- }
144
- }
145
- };
146
-
147
- let consumerGetStats = mainStreamConsumer.getStats;
148
- mainStreamConsumer.getStats = function () {
149
- let stats = consumerGetStats.apply(this, arguments);
150
- stats.stream = streamName;
151
- return stats;
152
- };
153
-
154
- let consumerApplyBackpressure = mainStreamConsumer.applyBackpressure;
155
- mainStreamConsumer.applyBackpressure = function (packet) {
156
- if (packet.value) {
157
- if (
158
- packet.value.stream === streamName ||
159
- packet.value.consumerId === this.id
160
- ) {
161
- consumerApplyBackpressure.apply(this, arguments);
162
-
163
- return;
164
- }
165
- }
166
- if (packet.done) {
167
- consumerApplyBackpressure.apply(this, arguments);
168
- }
169
- };
170
-
171
- let consumerReleaseBackpressure = mainStreamConsumer.releaseBackpressure;
172
- mainStreamConsumer.releaseBackpressure = function (packet) {
173
- if (packet.value) {
174
- if (
175
- packet.value.stream === streamName ||
176
- packet.value.consumerId === this.id
177
- ) {
178
- consumerReleaseBackpressure.apply(this, arguments);
179
-
180
- return;
181
- }
182
- }
183
- if (packet.done) {
184
- consumerReleaseBackpressure.apply(this, arguments);
185
- }
186
- };
187
-
188
- return mainStreamConsumer;
124
+ createConsumer(streamName, timeout, usabilityMode) {
125
+ return new StreamConsumer(
126
+ this._mainStream,
127
+ this._mainStream.nextConsumerId++,
128
+ this._mainStream.tailNode,
129
+ streamName,
130
+ timeout,
131
+ usabilityMode
132
+ );
189
133
  }
190
134
 
135
+ // Unlike individual consumers, consumable streams support being iterated
136
+ // over by multiple for-await-of loops in parallel.
191
137
  stream(streamName) {
192
138
  return new DemuxedConsumableStream(this, streamName);
193
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-demux",
3
- "version": "8.1.0",
3
+ "version": "9.0.1",
4
4
  "description": "A consumable stream demultiplexer.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -25,10 +25,10 @@
25
25
  },
26
26
  "homepage": "https://github.com/SocketCluster/stream-demux#readme",
27
27
  "devDependencies": {
28
- "mocha": "^5.0.5"
28
+ "mocha": "^10.2.0"
29
29
  },
30
30
  "dependencies": {
31
- "consumable-stream": "^2.0.0",
32
- "writable-consumable-stream": "^3.0.1"
31
+ "consumable-stream": "^3.0.0",
32
+ "writable-consumable-stream": "^4.0.1"
33
33
  }
34
34
  }
@@ -0,0 +1,172 @@
1
+ class StreamConsumer {
2
+ constructor(mainStream, id, startNode, streamName, timeout, usabilityMode) {
3
+ this.id = id;
4
+ this._backpressure = 0;
5
+ this.currentNode = startNode;
6
+ this.timeout = timeout;
7
+ this.isAlive = true;
8
+ this._mainStream = mainStream;
9
+ this._mainStream.setConsumer(this.id, this);
10
+ this.streamName = streamName;
11
+ this.usabilityMode = !!usabilityMode;
12
+ }
13
+
14
+ getStats() {
15
+ let stats = {
16
+ id: this.id,
17
+ backpressure: this._backpressure,
18
+ stream: this.streamName
19
+ };
20
+ if (this.timeout != null) {
21
+ stats.timeout = this.timeout;
22
+ }
23
+ return stats;
24
+ }
25
+
26
+ _resetBackpressure() {
27
+ this._backpressure = 0;
28
+ }
29
+
30
+ applyBackpressure(packet) {
31
+ this._backpressure++;
32
+ }
33
+
34
+ releaseBackpressure(packet) {
35
+ this._backpressure--;
36
+ }
37
+
38
+ getBackpressure() {
39
+ return this._backpressure;
40
+ }
41
+
42
+ clearActiveTimeout() {
43
+ clearTimeout(this._timeoutId);
44
+ delete this._timeoutId;
45
+ }
46
+
47
+ write(packet) {
48
+ if (packet.done || packet.value.stream === this.streamName) {
49
+ if (this._timeoutId !== undefined) {
50
+ this.clearActiveTimeout(packet);
51
+ }
52
+ this.applyBackpressure(packet);
53
+ }
54
+ if (this._resolve) {
55
+ this._resolve();
56
+ delete this._resolve;
57
+ }
58
+ }
59
+
60
+ kill(value) {
61
+ this._killPacket = {value, done: true};
62
+ if (this._timeoutId !== undefined) {
63
+ this.clearActiveTimeout(this._killPacket);
64
+ }
65
+ this._destroy();
66
+
67
+ if (this._resolve) {
68
+ this._resolve();
69
+ delete this._resolve;
70
+ }
71
+ }
72
+
73
+ _destroy() {
74
+ this.isAlive = false;
75
+ this._resetBackpressure();
76
+ this._mainStream.removeConsumer(this.id);
77
+ }
78
+
79
+ async _waitForNextItem(timeout) {
80
+ return new Promise((resolve, reject) => {
81
+ this._resolve = resolve;
82
+ let timeoutId;
83
+ if (timeout != null) {
84
+ // Create the error object in the outer scope in order
85
+ // to get the full stack trace.
86
+ let error = new Error('Stream consumer iteration timed out');
87
+ (async () => {
88
+ let delay = wait(timeout);
89
+ timeoutId = delay.timeoutId;
90
+ await delay.promise;
91
+ error.name = 'TimeoutError';
92
+ delete this._resolve;
93
+ reject(error);
94
+ })();
95
+ }
96
+ this._timeoutId = timeoutId;
97
+ });
98
+ }
99
+
100
+ async next() {
101
+ this._mainStream.setConsumer(this.id, this);
102
+
103
+ while (true) {
104
+ if (!this.currentNode.next) {
105
+ try {
106
+ await this._waitForNextItem(this.timeout);
107
+ } catch (error) {
108
+ this._destroy();
109
+ throw error;
110
+ }
111
+ }
112
+ if (this._killPacket) {
113
+ this._destroy();
114
+ let killPacket = this._killPacket;
115
+ delete this._killPacket;
116
+
117
+ return killPacket;
118
+ }
119
+
120
+ // Skip over nodes which belong to different streams.
121
+ while (
122
+ this.currentNode.next?.data?.value &&
123
+ this.currentNode.next.data.value.stream !== this.streamName &&
124
+ this.currentNode.next.consumerId !== this.id &&
125
+ !this.currentNode.next.data.done
126
+ ) {
127
+ this.currentNode = this.currentNode.next;
128
+ this.usabilityMode && await wait(0);
129
+ }
130
+
131
+ if (!this.currentNode.next) {
132
+ continue;
133
+ }
134
+
135
+ this.currentNode = this.currentNode.next;
136
+
137
+ this.releaseBackpressure(this.currentNode.data);
138
+
139
+ if (this.currentNode.data?.value?.data?.done) {
140
+ this._destroy();
141
+ return this.currentNode.data.value.data;
142
+ }
143
+
144
+ if (this.currentNode.data.done) {
145
+ this._destroy();
146
+ return this.currentNode.data;
147
+ }
148
+
149
+ return this.currentNode.data.value.data;
150
+ }
151
+ }
152
+
153
+ return() {
154
+ delete this.currentNode;
155
+ this._destroy();
156
+ return {};
157
+ }
158
+
159
+ [Symbol.asyncIterator]() {
160
+ return this;
161
+ }
162
+ }
163
+
164
+ function wait(timeout) {
165
+ let timeoutId;
166
+ let promise = new Promise((resolve) => {
167
+ timeoutId = setTimeout(resolve, timeout);
168
+ });
169
+ return {timeoutId, promise};
170
+ }
171
+
172
+ module.exports = StreamConsumer;
package/test/test.js CHANGED
@@ -296,6 +296,74 @@ describe('StreamDemux', () => {
296
296
  assert.equal(packet, null);
297
297
  });
298
298
 
299
+ it('should prevent stream.once() timeout from being reset when writing to other streams', async () => {
300
+ (async () => {
301
+ for (let i = 0; i < 15; i++) {
302
+ await wait(20);
303
+ demux.write('hi', 'test');
304
+ }
305
+ })();
306
+
307
+ let substream = demux.stream('hello');
308
+
309
+ let packet;
310
+ let error;
311
+ try {
312
+ packet = await substream.once(200);
313
+ } catch (err) {
314
+ error = err;
315
+ }
316
+
317
+ assert.notEqual(error, null);
318
+ assert.equal(error.name, 'TimeoutError');
319
+ assert.equal(packet, null);
320
+ });
321
+
322
+ it('should prevent stream.once() timeout from being reset when killing other streams', async () => {
323
+ (async () => {
324
+ for (let i = 0; i < 15; i++) {
325
+ await wait(20);
326
+ demux.kill('hi' + i, 'test');
327
+ }
328
+ })();
329
+
330
+ let substream = demux.stream('hello');
331
+
332
+ let packet;
333
+ let error;
334
+ try {
335
+ packet = await substream.once(200);
336
+ } catch (err) {
337
+ error = err;
338
+ }
339
+
340
+ assert.notEqual(error, null);
341
+ assert.equal(error.name, 'TimeoutError');
342
+ assert.equal(packet, null);
343
+ });
344
+
345
+ it('should support stream.once() timeout after killing the stream', async () => {
346
+ (async () => {
347
+ await wait(20);
348
+ demux.kill('hello', 'test');
349
+ })();
350
+
351
+ let substream = demux.stream('hello');
352
+
353
+ let start = Date.now();
354
+
355
+ let packet;
356
+ let error;
357
+ try {
358
+ packet = await substream.once(200);
359
+ } catch (err) {
360
+ error = err;
361
+ }
362
+ assert.notEqual(error, null);
363
+ assert.equal(error.name, 'TimeoutError');
364
+ assert.equal(packet, null);
365
+ });
366
+
299
367
  it('should support stream.next() method with close command', async () => {
300
368
  (async () => {
301
369
  for (let i = 0; i < 3; i++) {
@@ -343,6 +411,7 @@ describe('StreamDemux', () => {
343
411
  let consumer = demux.stream('hello').createConsumer();
344
412
 
345
413
  (async () => {
414
+ await wait(50);
346
415
  for (let i = 0; i < 10; i++) {
347
416
  await wait(10);
348
417
  demux.writeToConsumer(consumer.id, 'world' + i);