sflow
Version:
sflow is a powerful and highly-extensible library designed for processing and manipulating streams of data effortlessly. Inspired by the functional programming paradigm, it provides a rich set of utilities for transforming streams, including chunking, fil
165 lines (147 loc) • 5.75 kB
text/typescript
import { range } from "rambda";
import { forEachs } from "./forEachs";
import { mergeDescends } from "./mergeAscends";
import { mergeStreamsByAscend } from "./mergeStreamsBy";
import { sflow } from "./sflow";
it("merge asc", async () => {
const req1 = sflow([0, 1, 2]);
const req2 = sflow([1, 2, 3]);
const req3 = sflow([0, 4, 5]);
const ret = [0, 0, 1, 1, 2, 2, 3, 4, 5];
expect(
await sflow(mergeStreamsByAscend((x) => x, [req1, req2, req3]))
.log()
// .peek(console.log)
.toArray(),
).toEqual(ret);
});
it("drains correctly for different length flow", async () => {
const s = [0, 1, 2].map(() => jest.fn());
const f = [0, 1, 2].map(() => jest.fn());
const end = jest.fn();
const c = sflow(
mergeStreamsByAscend(
(x) => x,
[
sflow([1]).onStart(s[0]).onFlush(f[0]),
sflow([4, 5]).onStart(s[1]).onFlush(f[1]),
sflow([7, 8, 9]).onStart(s[2]).onFlush(f[2]),
],
),
).onFlush(end);
// all streams started
expect(s[0]).toHaveBeenCalled();
expect(s[1]).toHaveBeenCalled();
expect(s[2]).toHaveBeenCalled();
const r = c.getReader();
// all streams not flushed
expect(f[0]).not.toHaveBeenCalled();
expect(f[1]).not.toHaveBeenCalled();
expect(f[2]).not.toHaveBeenCalled();
expect((await r.read()).value).toBe(1); // pulled from [0|1|2] got [1,4,7] emit 1, f0 drain
expect((await r.read()).value).toBe(4); // pulled from [0| | ] got [_,4,7] emit 4,
expect(f[0]).toHaveBeenCalled(); // stream [1] flushed
expect((await r.read()).value).toBe(5); // pulled from [ |1| ] got [_,5,7] emit 5
expect((await r.read()).value).toBe(7); // pulled from [ | |2] got [_,_,7] emit 7
expect(f[1]).toHaveBeenCalled(); // stream [4,5] flushed
expect((await r.read()).value).toBe(8); // pulled from [ | |2] got [_,_,8] emit 8
expect((await r.read()).value).toBe(9); // pulled from [ | |2] got [_,_,9] emit 9
expect(end).not.toHaveBeenCalled();
expect((await r.read()).done).toBe(true); // pulled from [ | |2] got [_,_,_] drain
expect(f[2]).toHaveBeenCalled(); // stream [7,8,9] flushed
expect(end).toHaveBeenCalled();
});
it("merge asc lazy", async () => {
const fn1 = jest.fn();
const req1 = sflow([0, 1, 2]).byLazy(forEachs(fn1));
const fn2 = jest.fn();
const req2 = sflow([1, 2, 3]).byLazy(forEachs(fn2));
const fn3 = jest.fn();
const req3 = sflow([0, 4, 5]).byLazy(forEachs(fn3));
const _ret = [0, 0, 1, 1, 2, 2, 3, 4, 5];
const _emi = [1, 3, 2, 1, 2, 1, 2, 3, 3]; // emit order
const r = sflow(mergeStreamsByAscend((x) => x, [req1, req2, req3]));
expect(fn1).toHaveBeenCalledTimes(0);
expect(fn2).toHaveBeenCalledTimes(0);
expect(fn3).toHaveBeenCalledTimes(0);
const reader = r.getReader();
expect(fn1).toHaveBeenCalledTimes(0);
expect(fn2).toHaveBeenCalledTimes(0);
expect(fn3).toHaveBeenCalledTimes(0);
await reader.read(); // slots: [0,1,0] => emit[0] 1 => [1,1,0]
expect(fn1).toHaveBeenCalledTimes(1);
expect(fn2).toHaveBeenCalledTimes(1);
expect(fn3).toHaveBeenCalledTimes(1);
await reader.read(); // slots: [1,1,0] => emit[2] 0 => [1,1,4]
expect(fn1).toHaveBeenCalledTimes(2);
expect(fn2).toHaveBeenCalledTimes(1);
expect(fn3).toHaveBeenCalledTimes(1);
await reader.read(); // slots: [1,1,4] => emit[0] 1 => [2,1,4]
expect(fn1).toHaveBeenCalledTimes(2);
expect(fn2).toHaveBeenCalledTimes(1);
expect(fn3).toHaveBeenCalledTimes(2);
});
// it("curried", async () => {
// const req1 = sflow([0, 1, 2]);
// const req2 = sflow([1, 2, 3]);
// const req3 = sflow([0, 4, 5]);
// const ret = [0, 0, 1, 1, 2, 2, 3, 4, 5];
// expect(
// await sflow([req1, req2, req3])
// .through(mergeStreamsByAscend((x: number) => x)) // merge all flows into one by ascend order
// .toArray()
// ).toEqual(ret);
// });
it("merge desc by invert use of asc", async () => {
const req1 = sflow([0, 1, 2].toReversed());
const req2 = sflow([1, 2, 3].toReversed());
const req3 = sflow([0, 4, 5].toReversed());
const ret = [0, 0, 1, 1, 2, 2, 3, 4, 5].toReversed();
expect(
await sflow(mergeStreamsByAscend((x) => -x, [req1, req2, req3]))
// .peek(console.log)
.toArray(),
).toEqual(ret);
});
it("merge desc by desc export", async () => {
const req1 = sflow([0, 1, 2].toReversed());
const req2 = sflow([1, 2, 3].toReversed());
const req3 = sflow([0, 4, 5].toReversed());
const ret = [0, 0, 1, 1, 2, 2, 3, 4, 5].toReversed();
expect(
await sflow(mergeDescends((x) => x, [req1, req2, req3]))
// .peek(console.log)
.toArray(),
).toEqual(ret);
});
it("merge a super long asc", async () => {
const req1 = sflow(range(0, 9999).map((e) => 1 + e * 2)); // 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 ...
const req2 = sflow(range(0, 9999).map((e) => 2 + e * 3)); // 2, 5, 8, 11, 14, 17, 20, 23, 26, 29 ...
const req3 = sflow(range(0, 9999).map((e) => 3 + e * 5)); // 3, 8, 13, 18, 23, 28, 33, 38, 43, 48 ...
const ret = range(0, 9999)
.flatMap((e) => [1 + e * 2, 2 + e * 3, 3 + e * 5])
.sort((a, b) => a - b);
expect(
await sflow(mergeStreamsByAscend((x) => x, [req1, req2, req3]))
// .peek(console.log)
.toArray(),
).toEqual(ret); // cost about 60ms in my machine
});
it("works asc", async () => {
const req1 = sflow([1, 2, 3]);
const req2 = sflow([0, 4, 5]);
expect(
await sflow(mergeStreamsByAscend((x) => x, [req1, req2]))
// .peek(console.log)
.toArray(),
).toEqual([0, 1, 2, 3, 4, 5]);
});
it("throws not asc", async () => {
const req1 = sflow([1, 2, 0]); // not asc
const req2 = sflow([0, 4, 5]);
await expect(
sflow(mergeStreamsByAscend((x) => x, [req1, req2]))
// .peek(console.log)
.toArray(),
).rejects.toThrow("ascending");
});