Add tests for demux
This commit is contained in:
parent
599ba16d48
commit
0067ba6a7c
@ -33,10 +33,12 @@
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/node": "^12.7.2",
|
||||
"@types/sinon": "^7.0.13",
|
||||
"ava": "^1.0.0-rc.2",
|
||||
"chai": "^4.2.0",
|
||||
"mhysa": "./",
|
||||
"prettier": "^1.14.3",
|
||||
"sinon": "^7.4.2",
|
||||
"ts-node": "^8.3.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-prettier": "^1.16.0",
|
||||
|
@ -29,6 +29,7 @@ enum EventSubscription {
|
||||
All,
|
||||
Self,
|
||||
}
|
||||
|
||||
const eventsTarget = {
|
||||
close: EventSubscription.Last,
|
||||
data: EventSubscription.Last,
|
||||
|
@ -1,5 +1,27 @@
|
||||
import { WritableOptions, Writable } from "stream";
|
||||
|
||||
enum EventSubscription {
|
||||
Last = 0,
|
||||
First,
|
||||
All,
|
||||
Self,
|
||||
Unhandled,
|
||||
}
|
||||
|
||||
const eventsTarget = {
|
||||
close: EventSubscription.Self,
|
||||
data: EventSubscription.All,
|
||||
drain: EventSubscription.Self,
|
||||
end: EventSubscription.Self,
|
||||
error: EventSubscription.Self,
|
||||
finish: EventSubscription.Self,
|
||||
pause: EventSubscription.Self,
|
||||
pipe: EventSubscription.Unhandled,
|
||||
readable: EventSubscription.Self,
|
||||
resume: EventSubscription.Self,
|
||||
unpipe: EventSubscription.Unhandled,
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Duplex stream that is pushed data from multiple sources
|
||||
* @param streams Source streams to multiplex
|
||||
@ -76,4 +98,24 @@ class Demux extends Writable {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public on(event: string, cb: any) {
|
||||
switch (eventsTarget[event]) {
|
||||
case EventSubscription.Self:
|
||||
super.on(event, cb);
|
||||
break;
|
||||
case EventSubscription.All:
|
||||
Object.keys(this.streamsByKey).forEach(key =>
|
||||
this.streamsByKey[key].stream.on(event, cb),
|
||||
);
|
||||
break;
|
||||
case EventSubscription.Unhandled:
|
||||
throw new Error(
|
||||
"Stream must be multiplexed before handling this event",
|
||||
);
|
||||
default:
|
||||
super.on(event, cb);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
const test = require("ava");
|
||||
const { expect } = require("chai");
|
||||
const { compose, composeDuplex, map, rate } = require("../src");
|
||||
const { compose, map } = require("../src");
|
||||
const { sleep } = require("../src/helpers");
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
@ -308,12 +308,12 @@ test.cb(
|
||||
t => {
|
||||
t.plan(6);
|
||||
interface Chunk {
|
||||
index: number;
|
||||
mapped: string[];
|
||||
key: string;
|
||||
mapped: number[];
|
||||
}
|
||||
const first = map(
|
||||
async (chunk: Chunk) => {
|
||||
chunk.mapped.push("first");
|
||||
chunk.mapped.push(1);
|
||||
return chunk;
|
||||
},
|
||||
{
|
||||
@ -324,9 +324,10 @@ test.cb(
|
||||
const second = map(
|
||||
async (chunk: Chunk) => {
|
||||
pendingReads--;
|
||||
await sleep(500);
|
||||
await sleep(200);
|
||||
expect(second._writableState.length).to.be.equal(1);
|
||||
expect(first._readableState.length).to.equal(pendingReads);
|
||||
chunk.mapped.push("second");
|
||||
chunk.mapped.push(2);
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: true, highWaterMark: 1 },
|
||||
@ -342,27 +343,25 @@ test.cb(
|
||||
|
||||
composed.on("drain", () => {
|
||||
expect(composed._writableState.length).to.be.equal(0);
|
||||
expect(performance.now() - start).to.be.lessThan(100);
|
||||
expect(performance.now() - start).to.be.lessThan(50);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
composed.on("data", (chunk: Chunk) => {
|
||||
// Since second is bottleneck, composed will write into first immediately. Buffer should be empty.
|
||||
expect(composed._writableState.length).to.be.equal(0);
|
||||
expect(chunk.mapped.length).to.equal(2);
|
||||
expect(chunk.mapped).to.deep.equal(["first", "second"]);
|
||||
t.pass();
|
||||
if (chunk.index === 5) {
|
||||
if (chunk.key === "e") {
|
||||
t.end();
|
||||
}
|
||||
});
|
||||
|
||||
const input = [
|
||||
{ index: 1, mapped: [] },
|
||||
{ index: 2, mapped: [] },
|
||||
{ index: 3, mapped: [] },
|
||||
{ index: 4, mapped: [] },
|
||||
{ index: 5, mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "b", mapped: [] },
|
||||
{ key: "c", mapped: [] },
|
||||
{ key: "d", mapped: [] },
|
||||
{ key: "e", mapped: [] },
|
||||
];
|
||||
let pendingReads = input.length;
|
||||
|
||||
|
@ -1,53 +1,38 @@
|
||||
import test from "ava";
|
||||
import { expect } from "chai";
|
||||
import { demux, map } from "../src";
|
||||
const { demux, map } = require("../src");
|
||||
import { Writable } from "stream";
|
||||
const sinon = require("sinon");
|
||||
const { sleep } = require("../src/helpers");
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
interface Test {
|
||||
key: string;
|
||||
val: number;
|
||||
visited: number[];
|
||||
}
|
||||
test.cb("should spread per key", t => {
|
||||
t.plan(5);
|
||||
test.cb("demux() constructor should be called once per key", t => {
|
||||
t.plan(1);
|
||||
const input = [
|
||||
{ key: "a", val: 1 },
|
||||
{ key: "b", val: 2 },
|
||||
{ key: "a", val: 3 },
|
||||
{ key: "c", val: 4 },
|
||||
{ key: "a", visited: [] },
|
||||
{ key: "b", visited: [] },
|
||||
{ key: "a", visited: [] },
|
||||
{ key: "c", visited: [] },
|
||||
];
|
||||
const results = [
|
||||
{ key: "a", val: 2 },
|
||||
{ key: "b", val: 3 },
|
||||
{ key: "a", val: 4 },
|
||||
{ key: "c", val: 5 },
|
||||
];
|
||||
const destinationStreamKeys = [];
|
||||
let i = 0;
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, enc, cb) {
|
||||
expect(results).to.deep.include(chunk);
|
||||
expect(input).to.not.deep.include(chunk);
|
||||
t.pass();
|
||||
cb();
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
destinationStreamKeys.push(destKey);
|
||||
const construct = sinon.spy((destKey: string) => {
|
||||
const dest = map((chunk: Test) => {
|
||||
return {
|
||||
...chunk,
|
||||
val: chunk.val + 1,
|
||||
};
|
||||
chunk.visited.push(1);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
dest.pipe(sink);
|
||||
return dest;
|
||||
};
|
||||
});
|
||||
|
||||
const demuxed = demux(construct, { key: "key" }, { objectMode: true });
|
||||
|
||||
demuxed.on("finish", () => {
|
||||
expect(destinationStreamKeys).to.deep.equal(["a", "b", "c"]);
|
||||
expect(construct.withArgs("a").callCount).to.equal(1);
|
||||
expect(construct.withArgs("b").callCount).to.equal(1);
|
||||
expect(construct.withArgs("c").callCount).to.equal(1);
|
||||
t.pass();
|
||||
t.end();
|
||||
});
|
||||
@ -56,50 +41,34 @@ test.cb("should spread per key", t => {
|
||||
demuxed.end();
|
||||
});
|
||||
|
||||
test.cb("should spread per key using keyBy", t => {
|
||||
t.plan(5);
|
||||
test.cb("demux() constructor should be called once per key using keyBy", t => {
|
||||
t.plan(1);
|
||||
const input = [
|
||||
{ key: "a", val: 1 },
|
||||
{ key: "b", val: 2 },
|
||||
{ key: "a", val: 3 },
|
||||
{ key: "c", val: 4 },
|
||||
{ key: "a", visited: [] },
|
||||
{ key: "b", visited: [] },
|
||||
{ key: "a", visited: [] },
|
||||
{ key: "c", visited: [] },
|
||||
];
|
||||
const results = [
|
||||
{ key: "a", val: 2 },
|
||||
{ key: "b", val: 3 },
|
||||
{ key: "a", val: 4 },
|
||||
{ key: "c", val: 5 },
|
||||
];
|
||||
const destinationStreamKeys = [];
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, enc, cb) {
|
||||
expect(results).to.deep.include(chunk);
|
||||
expect(input).to.not.deep.include(chunk);
|
||||
t.pass();
|
||||
cb();
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
destinationStreamKeys.push(destKey);
|
||||
|
||||
const construct = sinon.spy((destKey: string) => {
|
||||
const dest = map((chunk: Test) => {
|
||||
return {
|
||||
...chunk,
|
||||
val: chunk.val + 1,
|
||||
};
|
||||
chunk.visited.push(1);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
dest.pipe(sink);
|
||||
return dest;
|
||||
};
|
||||
});
|
||||
|
||||
const demuxed = demux(
|
||||
construct,
|
||||
{ keyBy: (chunk: any) => chunk.key },
|
||||
{ keyBy: item => item.key },
|
||||
{ objectMode: true },
|
||||
);
|
||||
|
||||
demuxed.on("finish", () => {
|
||||
expect(destinationStreamKeys).to.deep.equal(["a", "b", "c"]);
|
||||
expect(construct.withArgs("a").callCount).to.equal(1);
|
||||
expect(construct.withArgs("b").callCount).to.equal(1);
|
||||
expect(construct.withArgs("c").callCount).to.equal(1);
|
||||
t.pass();
|
||||
t.end();
|
||||
});
|
||||
@ -110,17 +79,18 @@ test.cb("should spread per key using keyBy", t => {
|
||||
|
||||
test.cb("should emit errors", t => {
|
||||
t.plan(2);
|
||||
let index = 0;
|
||||
const input = [
|
||||
{ key: "a", val: 1 },
|
||||
{ key: "b", val: 2 },
|
||||
{ key: "a", val: 3 },
|
||||
{ key: "a", val: 4 },
|
||||
{ key: "a", visited: [] },
|
||||
{ key: "b", visited: [] },
|
||||
{ key: "a", visited: [] },
|
||||
{ key: "a", visited: [] },
|
||||
];
|
||||
const results = [
|
||||
{ key: "a", val: 2 },
|
||||
{ key: "b", val: 3 },
|
||||
{ key: "a", val: 4 },
|
||||
{ key: "a", val: 5 },
|
||||
{ key: "a", visited: [0] },
|
||||
{ key: "b", visited: [1] },
|
||||
{ key: "a", visited: [2] },
|
||||
{ key: "a", visited: [3] },
|
||||
];
|
||||
const destinationStreamKeys = [];
|
||||
const sink = new Writable({
|
||||
@ -131,7 +101,7 @@ test.cb("should emit errors", t => {
|
||||
t.pass();
|
||||
cb();
|
||||
},
|
||||
}).on("unpipe", e => console.log("sink err"));
|
||||
});
|
||||
|
||||
const construct = (destKey: string) => {
|
||||
destinationStreamKeys.push(destKey);
|
||||
@ -139,11 +109,12 @@ test.cb("should emit errors", t => {
|
||||
if (chunk.key === "b") {
|
||||
throw new Error("Caught object with key 'b'");
|
||||
}
|
||||
return {
|
||||
...chunk,
|
||||
val: chunk.val + 1,
|
||||
};
|
||||
}).on("error", e => console.log("got err"));
|
||||
|
||||
const _chunk = { ...chunk, visited: [] };
|
||||
_chunk.visited.push(index);
|
||||
index++;
|
||||
return _chunk;
|
||||
}).on("error", () => {});
|
||||
|
||||
dest.pipe(sink);
|
||||
return dest;
|
||||
@ -162,3 +133,374 @@ test.cb("should emit errors", t => {
|
||||
input.forEach(event => demuxed.write(event));
|
||||
demuxed.end();
|
||||
});
|
||||
|
||||
test("compose() should emit drain event ~rate * highWaterMark ms for every write that causes backpressure", async t => {
|
||||
t.plan(7);
|
||||
const highWaterMark = 5;
|
||||
const _rate = 25;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
interface Chunk {
|
||||
key: string;
|
||||
mapped: number[];
|
||||
}
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, encoding, cb) {
|
||||
cb();
|
||||
t.pass();
|
||||
pendingReads--;
|
||||
if (pendingReads === 0) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
const first = map(async (chunk: Chunk) => {
|
||||
await sleep(_rate);
|
||||
chunk.mapped.push(1);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
const second = map(async (chunk: Chunk) => {
|
||||
chunk.mapped.push(2);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
first.pipe(second).pipe(sink);
|
||||
return first;
|
||||
};
|
||||
const _demux = demux(
|
||||
construct,
|
||||
{ key: "key" },
|
||||
{
|
||||
objectMode: true,
|
||||
highWaterMark,
|
||||
},
|
||||
);
|
||||
_demux.on("error", err => {
|
||||
reject();
|
||||
});
|
||||
|
||||
_demux.on("drain", () => {
|
||||
expect(_demux._writableState.length).to.be.equal(0);
|
||||
expect(performance.now() - start).to.be.greaterThan(_rate);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
const input = [
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
];
|
||||
let pendingReads = input.length;
|
||||
|
||||
let start = performance.now();
|
||||
for (const item of input) {
|
||||
const res = _demux.write(item);
|
||||
expect(_demux._writableState.length).to.be.at.most(highWaterMark);
|
||||
if (!res) {
|
||||
start = performance.now();
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("demux() should emit one drain event when writing 6 items with highWaterMark of 5", t => {
|
||||
t.plan(7);
|
||||
const highWaterMark = 5;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
interface Chunk {
|
||||
key: string;
|
||||
mapped: number[];
|
||||
}
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, encoding, cb) {
|
||||
cb();
|
||||
t.pass();
|
||||
if (chunk.key === "f") {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
const first = map(async (chunk: Chunk) => {
|
||||
chunk.mapped.push(1);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
const second = map(async (chunk: Chunk) => {
|
||||
chunk.mapped.push(2);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
first.pipe(second).pipe(sink);
|
||||
return first;
|
||||
};
|
||||
const _demux = demux(
|
||||
construct,
|
||||
{ key: "key" },
|
||||
{
|
||||
objectMode: true,
|
||||
highWaterMark,
|
||||
},
|
||||
);
|
||||
_demux.on("error", err => {
|
||||
reject();
|
||||
});
|
||||
|
||||
_demux.on("drain", () => {
|
||||
expect(_demux._writableState.length).to.be.equal(0);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
const input = [
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "b", mapped: [] },
|
||||
{ key: "c", mapped: [] },
|
||||
{ key: "d", mapped: [] },
|
||||
{ key: "e", mapped: [] },
|
||||
{ key: "f", mapped: [] },
|
||||
];
|
||||
|
||||
for (const item of input) {
|
||||
const res = _demux.write(item);
|
||||
expect(_demux._writableState.length).to.be.at.most(highWaterMark);
|
||||
if (!res) {
|
||||
await sleep(10);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.cb(
|
||||
"demux() should emit drain event after 500 ms when writing 5 items that take 100ms to process with a highWaterMark of 5 ",
|
||||
t => {
|
||||
t.plan(6);
|
||||
const _rate = 100;
|
||||
const highWaterMark = 5;
|
||||
interface Chunk {
|
||||
key: string;
|
||||
mapped: number[];
|
||||
}
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, encoding, cb) {
|
||||
t.pass();
|
||||
cb();
|
||||
if (pendingReads === 0) {
|
||||
t.end();
|
||||
}
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
const first = map(
|
||||
async (chunk: Chunk) => {
|
||||
chunk.mapped.push(1);
|
||||
await sleep(_rate);
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: true },
|
||||
);
|
||||
|
||||
const second = map(
|
||||
(chunk: Chunk) => {
|
||||
pendingReads--;
|
||||
chunk.mapped.push(2);
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: true, highWaterMark: 1 },
|
||||
);
|
||||
|
||||
first.pipe(second).pipe(sink);
|
||||
return first;
|
||||
};
|
||||
const _demux = demux(
|
||||
construct,
|
||||
{ key: "key" },
|
||||
{
|
||||
objectMode: true,
|
||||
highWaterMark,
|
||||
},
|
||||
);
|
||||
_demux.on("error", err => {
|
||||
t.end(err);
|
||||
});
|
||||
|
||||
_demux.on("drain", () => {
|
||||
expect(_demux._writableState.length).to.be.equal(0);
|
||||
expect(performance.now() - start).to.be.greaterThan(
|
||||
_rate * input.length,
|
||||
);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
const input = [
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
];
|
||||
|
||||
let pendingReads = input.length;
|
||||
input.forEach(item => {
|
||||
_demux.write(item);
|
||||
});
|
||||
const start = performance.now();
|
||||
},
|
||||
);
|
||||
test.cb(
|
||||
"demux() should emit drain event immediately when second stream is bottleneck",
|
||||
t => {
|
||||
t.plan(6);
|
||||
const highWaterMark = 5;
|
||||
interface Chunk {
|
||||
key: string;
|
||||
mapped: number[];
|
||||
}
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, encoding, cb) {
|
||||
t.pass();
|
||||
cb();
|
||||
if (pendingReads === 0) {
|
||||
t.end();
|
||||
}
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
const first = map(
|
||||
(chunk: Chunk) => {
|
||||
chunk.mapped.push(1);
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: true },
|
||||
);
|
||||
|
||||
const second = map(
|
||||
async (chunk: Chunk) => {
|
||||
pendingReads--;
|
||||
await sleep(200);
|
||||
chunk.mapped.push(2);
|
||||
expect(second._writableState.length).to.be.equal(1);
|
||||
expect(first._readableState.length).to.equal(pendingReads);
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: true, highWaterMark: 1 },
|
||||
);
|
||||
|
||||
first.pipe(second).pipe(sink);
|
||||
return first;
|
||||
};
|
||||
const _demux = demux(
|
||||
construct,
|
||||
{ key: "key" },
|
||||
{
|
||||
objectMode: true,
|
||||
highWaterMark,
|
||||
},
|
||||
);
|
||||
_demux.on("error", err => {
|
||||
t.end(err);
|
||||
});
|
||||
|
||||
_demux.on("drain", () => {
|
||||
expect(_demux._writableState.length).to.be.equal(0);
|
||||
expect(performance.now() - start).to.be.lessThan(50);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
const input = [
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
];
|
||||
|
||||
let pendingReads = input.length;
|
||||
input.forEach(item => {
|
||||
_demux.write(item);
|
||||
});
|
||||
const start = performance.now();
|
||||
},
|
||||
);
|
||||
|
||||
test("demux() should emit drain event and first should contain up to highWaterMark items in readable state when second is bottleneck", t => {
|
||||
t.plan(6);
|
||||
const highWaterMark = 5;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
interface Chunk {
|
||||
key: string;
|
||||
mapped: number[];
|
||||
}
|
||||
const sink = new Writable({
|
||||
objectMode: true,
|
||||
write(chunk, encoding, cb) {
|
||||
t.pass();
|
||||
cb();
|
||||
if (pendingReads === 0) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
const construct = (destKey: string) => {
|
||||
const first = map(
|
||||
(chunk: Chunk) => {
|
||||
expect(first._readableState.length).to.be.at.most(2);
|
||||
chunk.mapped.push(1);
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: 2, highWaterMark: 2 },
|
||||
);
|
||||
|
||||
const second = map(
|
||||
async (chunk: Chunk) => {
|
||||
chunk.mapped.push(2);
|
||||
expect(second._writableState.length).to.be.equal(1);
|
||||
await sleep(100);
|
||||
pendingReads--;
|
||||
return chunk;
|
||||
},
|
||||
{ objectMode: 2, highWaterMark: 2 },
|
||||
);
|
||||
|
||||
first.pipe(second).pipe(sink);
|
||||
return first;
|
||||
};
|
||||
const _demux = demux(
|
||||
construct,
|
||||
{ key: "key" },
|
||||
{
|
||||
objectMode: true,
|
||||
highWaterMark,
|
||||
},
|
||||
);
|
||||
_demux.on("error", err => {
|
||||
reject();
|
||||
});
|
||||
|
||||
_demux.on("drain", () => {
|
||||
expect(_demux._writableState.length).to.be.equal(0);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
const input = [
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
{ key: "a", mapped: [] },
|
||||
];
|
||||
let pendingReads = input.length;
|
||||
|
||||
input.forEach(item => {
|
||||
_demux.write(item);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
93
yarn.lock
93
yarn.lock
@ -326,6 +326,35 @@
|
||||
dependencies:
|
||||
arrify "^1.0.1"
|
||||
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.6.0.tgz#ec7670432ae9c8eb710400d112c201a362d83393"
|
||||
integrity sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/formatio@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.1.tgz#52310f2f9bcbc67bdac18c94ad4901b95fde267e"
|
||||
integrity sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^3.1.0"
|
||||
|
||||
"@sinonjs/samsam@^3.1.0", "@sinonjs/samsam@^3.3.3":
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a"
|
||||
integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.3.0"
|
||||
array-from "^2.1.1"
|
||||
lodash "^4.17.15"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/chai@^4.1.7":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.0.tgz#2478260021408dec32c123a7cad3414beb811a07"
|
||||
@ -355,6 +384,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44"
|
||||
integrity sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==
|
||||
|
||||
"@types/sinon@^7.0.13":
|
||||
version "7.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313"
|
||||
integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung==
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@ -452,6 +486,11 @@ array-find-index@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
||||
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
|
||||
|
||||
array-from@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
|
||||
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
|
||||
|
||||
array-union@^1.0.1, array-union@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
|
||||
@ -1113,7 +1152,7 @@ detect-libc@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
||||
|
||||
diff@^3.2.0:
|
||||
diff@^3.2.0, diff@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
@ -1826,6 +1865,11 @@ is-windows@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@ -1893,6 +1937,11 @@ json5@^2.1.0:
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
|
||||
integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==
|
||||
|
||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
@ -2011,7 +2060,7 @@ lodash.merge@^4.6.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash@^4.17.13:
|
||||
lodash@^4.17.13, lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
@ -2023,6 +2072,11 @@ log-symbols@^2.2.0:
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
|
||||
lolex@^4.1.0, lolex@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.2.0.tgz#ddbd7f6213ca1ea5826901ab1222b65d714b3cd7"
|
||||
integrity sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==
|
||||
|
||||
loud-rejection@^1.0.0, loud-rejection@^1.2.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||
@ -2253,6 +2307,17 @@ needle@^2.2.1:
|
||||
iconv-lite "^0.4.4"
|
||||
sax "^1.2.4"
|
||||
|
||||
nise@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.2.tgz#b6d29af10e48b321b307e10e065199338eeb2652"
|
||||
integrity sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==
|
||||
dependencies:
|
||||
"@sinonjs/formatio" "^3.2.1"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
lolex "^4.1.0"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
node-pre-gyp@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
|
||||
@ -2545,6 +2610,13 @@ path-parse@^1.0.6:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
||||
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
|
||||
@ -2906,6 +2978,19 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
sinon@^7.4.2:
|
||||
version "7.4.2"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.4.2.tgz#ecd54158fef2fcfbdb231a3fa55140e8cb02ad6c"
|
||||
integrity sha512-pY5RY99DKelU3pjNxcWo6XqeB1S118GBcVIIdDi6V+h6hevn1izcg2xv1hTHW/sViRXU7sUOxt4wTUJ3gsW2CQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.4.0"
|
||||
"@sinonjs/formatio" "^3.2.1"
|
||||
"@sinonjs/samsam" "^3.3.3"
|
||||
diff "^3.5.0"
|
||||
lolex "^4.2.0"
|
||||
nise "^1.5.2"
|
||||
supports-color "^5.5.0"
|
||||
|
||||
slash@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||
@ -3126,7 +3211,7 @@ supertap@^1.0.0:
|
||||
serialize-error "^2.1.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
@ -3281,7 +3366,7 @@ tsutils@^2.29.0:
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
Loading…
Reference in New Issue
Block a user