@effectionx/stream-helpers 0.7.1 → 0.7.3
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/README.md +149 -0
- package/batch.test.ts +2 -2
- package/batch.ts +0 -1
- package/dist/batch.d.ts.map +1 -1
- package/dist/batch.js +0 -1
- package/dist/drain.d.ts +24 -0
- package/dist/drain.d.ts.map +1 -0
- package/dist/drain.js +33 -0
- package/dist/first.d.ts +28 -0
- package/dist/first.d.ts.map +1 -0
- package/dist/first.js +67 -0
- package/dist/for-each.d.ts.map +1 -1
- package/dist/for-each.js +12 -8
- package/dist/last.d.ts +29 -0
- package/dist/last.d.ts.map +1 -0
- package/dist/last.js +81 -0
- package/dist/mod.d.ts +6 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +6 -0
- package/dist/subject.d.ts.map +1 -1
- package/dist/subject.js +0 -1
- package/dist/take-until.d.ts +46 -0
- package/dist/take-until.d.ts.map +1 -0
- package/dist/take-until.js +68 -0
- package/dist/take-while.d.ts +41 -0
- package/dist/take-while.d.ts.map +1 -0
- package/dist/take-while.js +64 -0
- package/dist/take.d.ts +36 -0
- package/dist/take.d.ts.map +1 -0
- package/dist/take.js +59 -0
- package/dist/test-helpers/faucet.d.ts.map +1 -1
- package/dist/test-helpers/faucet.js +28 -24
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/drain.test.ts +52 -0
- package/drain.ts +35 -0
- package/first.test.ts +53 -0
- package/first.ts +74 -0
- package/for-each.ts +12 -8
- package/last.test.ts +65 -0
- package/last.ts +96 -0
- package/mod.ts +6 -0
- package/package.json +5 -4
- package/subject.ts +0 -1
- package/take-until.test.ts +113 -0
- package/take-until.ts +76 -0
- package/take-while.test.ts +89 -0
- package/take-while.ts +74 -0
- package/take.test.ts +83 -0
- package/take.ts +67 -0
- package/test-helpers/faucet.test.ts +3 -3
- package/test-helpers/faucet.ts +28 -24
- package/tracker.test.ts +4 -4
- package/valve.test.ts +2 -2
package/last.test.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it } from "@effectionx/bdd";
|
|
2
|
+
import { expect } from "expect";
|
|
3
|
+
|
|
4
|
+
import { last } from "./last.ts";
|
|
5
|
+
import { streamOf } from "./stream-of.ts";
|
|
6
|
+
|
|
7
|
+
describe("last", () => {
|
|
8
|
+
it("should return the last value from the stream", function* () {
|
|
9
|
+
const stream = streamOf([1, 2, 3]);
|
|
10
|
+
const value = yield* last(stream);
|
|
11
|
+
expect(value).toBe(3);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return the only value when stream has one item", function* () {
|
|
15
|
+
const stream = streamOf([42]);
|
|
16
|
+
const value = yield* last(stream);
|
|
17
|
+
expect(value).toBe(42);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return undefined if stream closes without yielding any values", function* () {
|
|
21
|
+
const stream = streamOf([]);
|
|
22
|
+
const value = yield* last(stream);
|
|
23
|
+
expect(value).toBe(undefined);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should work with undefined as a valid value", function* () {
|
|
27
|
+
const stream = streamOf([1, 2, undefined]);
|
|
28
|
+
const value = yield* last(stream);
|
|
29
|
+
expect(value).toBe(undefined);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("expect", () => {
|
|
33
|
+
it("should return the last value from the stream", function* () {
|
|
34
|
+
const stream = streamOf([1, 2, 3]);
|
|
35
|
+
const value = yield* last.expect(stream);
|
|
36
|
+
expect(value).toBe(3);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return the only value when stream has one item", function* () {
|
|
40
|
+
const stream = streamOf([42]);
|
|
41
|
+
const value = yield* last.expect(stream);
|
|
42
|
+
expect(value).toBe(42);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should throw if stream closes without yielding any values", function* () {
|
|
46
|
+
const stream = streamOf([]);
|
|
47
|
+
|
|
48
|
+
let error: Error | undefined;
|
|
49
|
+
try {
|
|
50
|
+
yield* last.expect(stream);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
error = e as Error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
expect(error).toBeDefined();
|
|
56
|
+
expect(error?.message).toBe("Stream closed without yielding any values");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should work with undefined as a valid value", function* () {
|
|
60
|
+
const stream = streamOf([1, 2, undefined]);
|
|
61
|
+
const value = yield* last.expect(stream);
|
|
62
|
+
expect(value).toBe(undefined);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
package/last.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Operation, Stream } from "effection";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the last value yielded by a stream, or `undefined` if the stream
|
|
5
|
+
* closes without yielding any values.
|
|
6
|
+
*
|
|
7
|
+
* Exhausts the entire stream to find the last value.
|
|
8
|
+
* Use `last.expect()` if you want to throw an error when the stream is empty.
|
|
9
|
+
*
|
|
10
|
+
* @template T - The type of items in the stream
|
|
11
|
+
* @template TClose - The type of the close value (unused)
|
|
12
|
+
* @param stream - The stream to get the last value from
|
|
13
|
+
* @returns The last value yielded by the stream, or `undefined` if empty
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { last } from "./last.ts";
|
|
18
|
+
*
|
|
19
|
+
* const stream = streamOf([1, 2, 3]);
|
|
20
|
+
* const value = yield* last(stream); // returns 3
|
|
21
|
+
*
|
|
22
|
+
* const empty = streamOf([]);
|
|
23
|
+
* const value = yield* last(empty); // returns undefined
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function last<T, TClose>(
|
|
27
|
+
stream: Stream<T, TClose>,
|
|
28
|
+
): Operation<T | undefined> {
|
|
29
|
+
return {
|
|
30
|
+
*[Symbol.iterator]() {
|
|
31
|
+
const subscription = yield* stream;
|
|
32
|
+
const first = yield* subscription.next();
|
|
33
|
+
|
|
34
|
+
if (first.done) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let lastValue: T = first.value;
|
|
39
|
+
let result = yield* subscription.next();
|
|
40
|
+
|
|
41
|
+
while (!result.done) {
|
|
42
|
+
lastValue = result.value;
|
|
43
|
+
result = yield* subscription.next();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return lastValue;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns the last value yielded by a stream.
|
|
53
|
+
* Exhausts the entire stream to find the last value.
|
|
54
|
+
* Throws an error if the stream closes without yielding any values.
|
|
55
|
+
*
|
|
56
|
+
* @template T - The type of items in the stream
|
|
57
|
+
* @template TClose - The type of the close value (unused)
|
|
58
|
+
* @param stream - The stream to get the last value from
|
|
59
|
+
* @returns The last value yielded by the stream
|
|
60
|
+
* @throws Error if the stream closes without yielding any values
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import { last } from "./last.ts";
|
|
65
|
+
*
|
|
66
|
+
* const stream = streamOf([1, 2, 3]);
|
|
67
|
+
* const value = yield* last.expect(stream); // returns 3
|
|
68
|
+
*
|
|
69
|
+
* const empty = streamOf([]);
|
|
70
|
+
* const value = yield* last.expect(empty); // throws Error
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
last.expect = function expectLast<T, TClose>(
|
|
74
|
+
stream: Stream<T, TClose>,
|
|
75
|
+
): Operation<T> {
|
|
76
|
+
return {
|
|
77
|
+
*[Symbol.iterator]() {
|
|
78
|
+
const subscription = yield* stream;
|
|
79
|
+
const first = yield* subscription.next();
|
|
80
|
+
|
|
81
|
+
if (first.done) {
|
|
82
|
+
throw new Error("Stream closed without yielding any values");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let lastValue: T = first.value;
|
|
86
|
+
let result = yield* subscription.next();
|
|
87
|
+
|
|
88
|
+
while (!result.done) {
|
|
89
|
+
lastValue = result.value;
|
|
90
|
+
result = yield* subscription.next();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lastValue;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
};
|
package/mod.ts
CHANGED
|
@@ -8,3 +8,9 @@ export * from "./subject.ts";
|
|
|
8
8
|
export * from "./lines.ts";
|
|
9
9
|
export * from "./stream-of.ts";
|
|
10
10
|
export * from "./reduce.ts";
|
|
11
|
+
export * from "./drain.ts";
|
|
12
|
+
export * from "./first.ts";
|
|
13
|
+
export * from "./last.ts";
|
|
14
|
+
export * from "./take.ts";
|
|
15
|
+
export * from "./take-while.ts";
|
|
16
|
+
export * from "./take-until.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectionx/stream-helpers",
|
|
3
|
-
"
|
|
3
|
+
"description": "Type-safe stream operators like filter, map, reduce, and forEach",
|
|
4
|
+
"version": "0.7.3",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "./dist/mod.js",
|
|
6
7
|
"types": "./dist/mod.d.ts",
|
|
@@ -20,8 +21,8 @@
|
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"immutable": "^5",
|
|
22
23
|
"remeda": "^2",
|
|
23
|
-
"@effectionx/signals": "0.5.
|
|
24
|
-
"@effectionx/timebox": "0.4.
|
|
24
|
+
"@effectionx/signals": "0.5.1",
|
|
25
|
+
"@effectionx/timebox": "0.4.1"
|
|
25
26
|
},
|
|
26
27
|
"license": "MIT",
|
|
27
28
|
"author": "engineering@frontside.com",
|
|
@@ -37,6 +38,6 @@
|
|
|
37
38
|
},
|
|
38
39
|
"sideEffects": false,
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@effectionx/bdd": "0.4.
|
|
41
|
+
"@effectionx/bdd": "0.4.2"
|
|
41
42
|
}
|
|
42
43
|
}
|
package/subject.ts
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it } from "@effectionx/bdd";
|
|
2
|
+
import { expect } from "expect";
|
|
3
|
+
import { pipe } from "remeda";
|
|
4
|
+
|
|
5
|
+
import { forEach } from "./for-each.ts";
|
|
6
|
+
import { streamOf } from "./stream-of.ts";
|
|
7
|
+
import { takeUntil } from "./take-until.ts";
|
|
8
|
+
|
|
9
|
+
describe("takeUntil", () => {
|
|
10
|
+
it("should yield values until predicate is true, then close with matching value", function* () {
|
|
11
|
+
const values: { status: string }[] = [];
|
|
12
|
+
|
|
13
|
+
const closeValue = yield* forEach(
|
|
14
|
+
function* (value) {
|
|
15
|
+
values.push(value);
|
|
16
|
+
},
|
|
17
|
+
takeUntil((x: { status: string }) => x.status === "valid")(
|
|
18
|
+
streamOf([
|
|
19
|
+
{ status: "pending" },
|
|
20
|
+
{ status: "checking" },
|
|
21
|
+
{ status: "valid" },
|
|
22
|
+
{ status: "extra" },
|
|
23
|
+
]),
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(values).toEqual([{ status: "pending" }, { status: "checking" }]);
|
|
28
|
+
expect(closeValue).toEqual({ status: "valid" });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should return source close value if stream ends before predicate matches", function* () {
|
|
32
|
+
const values: { status: string }[] = [];
|
|
33
|
+
|
|
34
|
+
const stream = streamOf(
|
|
35
|
+
(function* () {
|
|
36
|
+
yield { status: "pending" };
|
|
37
|
+
yield { status: "checking" };
|
|
38
|
+
return "no-match";
|
|
39
|
+
})(),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const closeValue = yield* forEach(
|
|
43
|
+
function* (value) {
|
|
44
|
+
values.push(value);
|
|
45
|
+
},
|
|
46
|
+
takeUntil((x: { status: string }) => x.status === "valid")(stream),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(values).toEqual([{ status: "pending" }, { status: "checking" }]);
|
|
50
|
+
expect(closeValue).toBe("no-match");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should close immediately with first value if it matches predicate", function* () {
|
|
54
|
+
const values: { status: string }[] = [];
|
|
55
|
+
|
|
56
|
+
const closeValue = yield* forEach(
|
|
57
|
+
function* (value) {
|
|
58
|
+
values.push(value);
|
|
59
|
+
},
|
|
60
|
+
takeUntil((x: { status: string }) => x.status === "valid")(
|
|
61
|
+
streamOf([{ status: "valid" }, { status: "extra" }]),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(values).toEqual([]);
|
|
66
|
+
expect(closeValue).toEqual({ status: "valid" });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should work with validation progress pattern", function* () {
|
|
70
|
+
type ValidationProgress =
|
|
71
|
+
| { status: "validating" }
|
|
72
|
+
| { status: "checking-inventory" }
|
|
73
|
+
| { status: "valid"; data: string }
|
|
74
|
+
| { status: "invalid"; errors: string[] };
|
|
75
|
+
|
|
76
|
+
const progressStatuses: string[] = [];
|
|
77
|
+
|
|
78
|
+
const result = yield* forEach(
|
|
79
|
+
function* (progress) {
|
|
80
|
+
progressStatuses.push(progress.status);
|
|
81
|
+
},
|
|
82
|
+
takeUntil(
|
|
83
|
+
(p: ValidationProgress) =>
|
|
84
|
+
p.status === "valid" || p.status === "invalid",
|
|
85
|
+
)(
|
|
86
|
+
streamOf<ValidationProgress, void>([
|
|
87
|
+
{ status: "validating" },
|
|
88
|
+
{ status: "checking-inventory" },
|
|
89
|
+
{ status: "valid", data: "ok" },
|
|
90
|
+
]),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(progressStatuses).toEqual(["validating", "checking-inventory"]);
|
|
95
|
+
expect(result).toEqual({ status: "valid", data: "ok" });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should work with pipe", function* () {
|
|
99
|
+
const values: number[] = [];
|
|
100
|
+
|
|
101
|
+
const stream = pipe(
|
|
102
|
+
streamOf([1, 2, 3, 4, 5]),
|
|
103
|
+
takeUntil((x) => x === 4),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const closeValue = yield* forEach(function* (value) {
|
|
107
|
+
values.push(value);
|
|
108
|
+
}, stream);
|
|
109
|
+
|
|
110
|
+
expect(values).toEqual([1, 2, 3]);
|
|
111
|
+
expect(closeValue).toBe(4);
|
|
112
|
+
});
|
|
113
|
+
});
|
package/take-until.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Stream } from "effection";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a stream transformer that yields values from the source stream
|
|
5
|
+
* until the predicate returns true. Closes with the matching value when
|
|
6
|
+
* the predicate returns true.
|
|
7
|
+
*
|
|
8
|
+
* This is useful for "iterate until a condition is met" patterns, where
|
|
9
|
+
* the matching value is meaningful (e.g., a terminal status).
|
|
10
|
+
*
|
|
11
|
+
* If the source stream closes before the predicate returns true, the
|
|
12
|
+
* resulting stream closes with the source's close value.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of items in the stream
|
|
15
|
+
* @template TClose - The type of the close value
|
|
16
|
+
* @param predicate - A function that returns true to stop taking values
|
|
17
|
+
* @returns A stream transformer that yields values until predicate is true,
|
|
18
|
+
* closing with the matching value
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { takeUntil, forEach } from "@effectionx/stream-helpers";
|
|
23
|
+
*
|
|
24
|
+
* // Iterate validation progress until we get a terminal status
|
|
25
|
+
* const result = yield* forEach(function*(progress) {
|
|
26
|
+
* showSpinner(progress.status);
|
|
27
|
+
* }, takeUntil((p) => p.status === "valid" || p.status === "invalid")(channel));
|
|
28
|
+
*
|
|
29
|
+
* // result is the validation object with terminal status
|
|
30
|
+
* if (result.status === "valid") {
|
|
31
|
+
* // proceed
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { takeUntil, map } from "@effectionx/stream-helpers";
|
|
38
|
+
* import { pipe } from "remeda";
|
|
39
|
+
*
|
|
40
|
+
* const limited = pipe(
|
|
41
|
+
* source,
|
|
42
|
+
* takeUntil((x) => x.done),
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function takeUntil<T>(
|
|
47
|
+
predicate: (item: T) => boolean,
|
|
48
|
+
): <TClose>(stream: Stream<T, TClose>) => Stream<T, T | TClose> {
|
|
49
|
+
return <TClose>(stream: Stream<T, TClose>): Stream<T, T | TClose> => ({
|
|
50
|
+
*[Symbol.iterator]() {
|
|
51
|
+
const subscription = yield* stream;
|
|
52
|
+
let done = false;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
*next() {
|
|
56
|
+
if (done) {
|
|
57
|
+
return { done: true, value: undefined as unknown as T | TClose };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = yield* subscription.next();
|
|
61
|
+
if (result.done) {
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (predicate(result.value)) {
|
|
66
|
+
done = true;
|
|
67
|
+
// Close with the matching value
|
|
68
|
+
return { done: true, value: result.value };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it } from "@effectionx/bdd";
|
|
2
|
+
import { expect } from "expect";
|
|
3
|
+
import { pipe } from "remeda";
|
|
4
|
+
|
|
5
|
+
import { forEach } from "./for-each.ts";
|
|
6
|
+
import { streamOf } from "./stream-of.ts";
|
|
7
|
+
import { takeWhile } from "./take-while.ts";
|
|
8
|
+
|
|
9
|
+
describe("takeWhile", () => {
|
|
10
|
+
it("should yield values while predicate is true", function* () {
|
|
11
|
+
const values: number[] = [];
|
|
12
|
+
|
|
13
|
+
const closeValue = yield* forEach(
|
|
14
|
+
function* (value) {
|
|
15
|
+
values.push(value);
|
|
16
|
+
},
|
|
17
|
+
takeWhile((x: number) => x < 3)(streamOf([1, 2, 3, 4, 5])),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(values).toEqual([1, 2]);
|
|
21
|
+
expect(closeValue).toBe(undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return source close value if stream ends before predicate fails", function* () {
|
|
25
|
+
const values: number[] = [];
|
|
26
|
+
|
|
27
|
+
const stream = streamOf(
|
|
28
|
+
(function* () {
|
|
29
|
+
yield 1;
|
|
30
|
+
yield 2;
|
|
31
|
+
return "early-close";
|
|
32
|
+
})(),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const closeValue = yield* forEach(
|
|
36
|
+
function* (value) {
|
|
37
|
+
values.push(value);
|
|
38
|
+
},
|
|
39
|
+
takeWhile((x: number) => x < 10)(stream),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(values).toEqual([1, 2]);
|
|
43
|
+
expect(closeValue).toBe("early-close");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should not include the failing value", function* () {
|
|
47
|
+
const values: number[] = [];
|
|
48
|
+
|
|
49
|
+
const closeValue = yield* forEach(
|
|
50
|
+
function* (value) {
|
|
51
|
+
values.push(value);
|
|
52
|
+
},
|
|
53
|
+
takeWhile((x: number) => x < 50)(streamOf([1, 2, 100, 3])),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(values).toEqual([1, 2]);
|
|
57
|
+
expect(closeValue).toBe(undefined);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should stop immediately if first value fails predicate", function* () {
|
|
61
|
+
const values: number[] = [];
|
|
62
|
+
|
|
63
|
+
const closeValue = yield* forEach(
|
|
64
|
+
function* (value) {
|
|
65
|
+
values.push(value);
|
|
66
|
+
},
|
|
67
|
+
takeWhile((x: number) => x < 50)(streamOf([100, 1, 2])),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(values).toEqual([]);
|
|
71
|
+
expect(closeValue).toBe(undefined);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should work with pipe", function* () {
|
|
75
|
+
const values: number[] = [];
|
|
76
|
+
|
|
77
|
+
const stream = pipe(
|
|
78
|
+
streamOf([1, 2, 3, 4, 5]),
|
|
79
|
+
takeWhile((x) => x < 4),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const closeValue = yield* forEach(function* (value) {
|
|
83
|
+
values.push(value);
|
|
84
|
+
}, stream);
|
|
85
|
+
|
|
86
|
+
expect(values).toEqual([1, 2, 3]);
|
|
87
|
+
expect(closeValue).toBe(undefined);
|
|
88
|
+
});
|
|
89
|
+
});
|
package/take-while.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Stream } from "effection";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a stream transformer that yields values from the source stream
|
|
5
|
+
* while the predicate returns true. Closes when the predicate returns false,
|
|
6
|
+
* without including the failing value.
|
|
7
|
+
*
|
|
8
|
+
* When the predicate fails, the stream closes immediately without the failing
|
|
9
|
+
* value. The close value will be `undefined` since we don't have access to
|
|
10
|
+
* the source's close value without draining the entire stream.
|
|
11
|
+
*
|
|
12
|
+
* If the source stream closes before the predicate returns false, the
|
|
13
|
+
* resulting stream closes with the source's close value.
|
|
14
|
+
*
|
|
15
|
+
* @template T - The type of items in the stream
|
|
16
|
+
* @template TClose - The type of the close value
|
|
17
|
+
* @param predicate - A function that returns true to continue taking values
|
|
18
|
+
* @returns A stream transformer that yields values while predicate is true
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { takeWhile, streamOf } from "@effectionx/stream-helpers";
|
|
23
|
+
*
|
|
24
|
+
* const stream = streamOf([1, 2, 3, 4, 5]);
|
|
25
|
+
* // yields 1, 2 (stops when value >= 3)
|
|
26
|
+
* const limited = takeWhile((x: number) => x < 3)(stream);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { takeWhile, map } from "@effectionx/stream-helpers";
|
|
32
|
+
* import { pipe } from "remeda";
|
|
33
|
+
*
|
|
34
|
+
* const limited = pipe(
|
|
35
|
+
* source,
|
|
36
|
+
* map(function* (x) { return x * 2; }),
|
|
37
|
+
* takeWhile((x) => x < 100),
|
|
38
|
+
* );
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function takeWhile<T>(
|
|
42
|
+
predicate: (item: T) => boolean,
|
|
43
|
+
): <TClose>(stream: Stream<T, TClose>) => Stream<T, TClose | undefined> {
|
|
44
|
+
return <TClose>(
|
|
45
|
+
stream: Stream<T, TClose>,
|
|
46
|
+
): Stream<T, TClose | undefined> => ({
|
|
47
|
+
*[Symbol.iterator]() {
|
|
48
|
+
const subscription = yield* stream;
|
|
49
|
+
let done = false;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
*next() {
|
|
53
|
+
if (done) {
|
|
54
|
+
return { done: true, value: undefined as TClose | undefined };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = yield* subscription.next();
|
|
58
|
+
if (result.done) {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!predicate(result.value)) {
|
|
63
|
+
done = true;
|
|
64
|
+
// Close immediately without the failing value
|
|
65
|
+
// We return undefined as we don't drain the stream
|
|
66
|
+
return { done: true, value: undefined as TClose | undefined };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
package/take.test.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it } from "@effectionx/bdd";
|
|
2
|
+
import { expect } from "expect";
|
|
3
|
+
import { pipe } from "remeda";
|
|
4
|
+
|
|
5
|
+
import { forEach } from "./for-each.ts";
|
|
6
|
+
import { streamOf } from "./stream-of.ts";
|
|
7
|
+
import { take } from "./take.ts";
|
|
8
|
+
|
|
9
|
+
describe("take", () => {
|
|
10
|
+
it("should take first n values and close with the nth value", function* () {
|
|
11
|
+
const values: number[] = [];
|
|
12
|
+
|
|
13
|
+
const closeValue = yield* forEach(
|
|
14
|
+
function* (value) {
|
|
15
|
+
values.push(value);
|
|
16
|
+
},
|
|
17
|
+
take<number>(3)(streamOf([1, 2, 3, 4, 5])),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(values).toEqual([1, 2]);
|
|
21
|
+
expect(closeValue).toBe(3);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return source close value if stream ends before n values", function* () {
|
|
25
|
+
const values: number[] = [];
|
|
26
|
+
|
|
27
|
+
const stream = streamOf(
|
|
28
|
+
(function* () {
|
|
29
|
+
yield 1;
|
|
30
|
+
yield 2;
|
|
31
|
+
return "early-close";
|
|
32
|
+
})(),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const closeValue = yield* forEach(function* (value) {
|
|
36
|
+
values.push(value);
|
|
37
|
+
}, take<number>(5)(stream));
|
|
38
|
+
|
|
39
|
+
expect(values).toEqual([1, 2]);
|
|
40
|
+
expect(closeValue).toBe("early-close");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should work with n=1", function* () {
|
|
44
|
+
const values: number[] = [];
|
|
45
|
+
|
|
46
|
+
const closeValue = yield* forEach(
|
|
47
|
+
function* (value) {
|
|
48
|
+
values.push(value);
|
|
49
|
+
},
|
|
50
|
+
take<number>(1)(streamOf([42, 100])),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(values).toEqual([]);
|
|
54
|
+
expect(closeValue).toBe(42);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should work with n=0", function* () {
|
|
58
|
+
const values: number[] = [];
|
|
59
|
+
|
|
60
|
+
const closeValue = yield* forEach(
|
|
61
|
+
function* (value) {
|
|
62
|
+
values.push(value);
|
|
63
|
+
},
|
|
64
|
+
take<number>(0)(streamOf([1, 2, 3])),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(values).toEqual([]);
|
|
68
|
+
expect(closeValue).toBe(undefined);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should work with pipe", function* () {
|
|
72
|
+
const values: number[] = [];
|
|
73
|
+
|
|
74
|
+
const stream = pipe(streamOf([1, 2, 3, 4, 5]), take(2));
|
|
75
|
+
|
|
76
|
+
const closeValue = yield* forEach(function* (value) {
|
|
77
|
+
values.push(value);
|
|
78
|
+
}, stream);
|
|
79
|
+
|
|
80
|
+
expect(values).toEqual([1]);
|
|
81
|
+
expect(closeValue).toBe(2);
|
|
82
|
+
});
|
|
83
|
+
});
|