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 +12 -66
- package/package.json +4 -4
- package/stream-consumer.js +172 -0
- package/test/test.js +69 -0
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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": "
|
|
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": "^
|
|
28
|
+
"mocha": "^10.2.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"consumable-stream": "^
|
|
32
|
-
"writable-consumable-stream": "^
|
|
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);
|