@effectionx/stream-helpers 0.7.1 → 0.7.2

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/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,6 @@ 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";
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@effectionx/stream-helpers",
3
- "version": "0.7.1",
3
+ "description": "Type-safe stream operators like filter, map, reduce, and forEach",
4
+ "version": "0.7.2",
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.0",
24
- "@effectionx/timebox": "0.4.0"
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.1"
41
+ "@effectionx/bdd": "0.4.2"
41
42
  }
42
43
  }
@@ -19,7 +19,7 @@ describe("useFaucet", () => {
19
19
  });
20
20
 
21
21
  yield* spawn(function* () {
22
- yield* sleep(1);
22
+ yield* sleep(0);
23
23
  // Pour an array of items
24
24
  yield* faucet.pour([1, 2, 3]);
25
25
  });
@@ -71,7 +71,7 @@ describe("useFaucet", () => {
71
71
 
72
72
  // Pour using a generator function
73
73
  yield* spawn(function* () {
74
- yield* sleep(1);
74
+ yield* sleep(0);
75
75
  yield* faucet.pour(function* (send) {
76
76
  yield* send(1);
77
77
  yield* sleep(10);
@@ -100,7 +100,7 @@ describe("useFaucet", () => {
100
100
 
101
101
  // Start pouring with a generator
102
102
  yield* spawn(function* () {
103
- yield* sleep(1);
103
+ yield* sleep(0);
104
104
  yield* faucet.pour(function* (send) {
105
105
  yield* send(1);
106
106
  yield* sleep(10);
@@ -1,5 +1,5 @@
1
- import { createChannel, type Operation, type Stream } from "effection";
2
1
  import { createBooleanSignal, is } from "@effectionx/signals";
2
+ import { type Operation, type Stream, createChannel } from "effection";
3
3
 
4
4
  /**
5
5
  * Interface of the stream returned by `useFaucet`.
@@ -80,30 +80,34 @@ export interface FaucetOptions {
80
80
  * @param options.open - Whether the faucet is open.
81
81
  * @returns stream of items coming from the faucet
82
82
  */
83
- export function* useFaucet<T>(options: FaucetOptions): Operation<Faucet<T>> {
84
- let signal = createChannel<T, never>();
85
- let open = yield* createBooleanSignal(options.open);
86
-
83
+ export function useFaucet<T>(options: FaucetOptions): Operation<Faucet<T>> {
87
84
  return {
88
- [Symbol.iterator]: signal[Symbol.iterator],
89
- *pour(items) {
90
- if (Array.isArray(items)) {
91
- for (let i of items) {
92
- yield* is(open, (open) => open);
93
- yield* signal.send(i);
94
- }
95
- } else {
96
- yield* items(function* (item) {
97
- yield* is(open, (open) => open);
98
- yield* signal.send(item);
99
- });
100
- }
101
- },
102
- close() {
103
- open.set(false);
104
- },
105
- open() {
106
- open.set(true);
85
+ *[Symbol.iterator]() {
86
+ let signal = createChannel<T, never>();
87
+ let open = yield* createBooleanSignal(options.open);
88
+
89
+ return {
90
+ [Symbol.iterator]: signal[Symbol.iterator],
91
+ *pour(items) {
92
+ if (Array.isArray(items)) {
93
+ for (let i of items) {
94
+ yield* is(open, (open) => open);
95
+ yield* signal.send(i);
96
+ }
97
+ } else {
98
+ yield* items(function* (item) {
99
+ yield* is(open, (open) => open);
100
+ yield* signal.send(item);
101
+ });
102
+ }
103
+ },
104
+ close() {
105
+ open.set(false);
106
+ },
107
+ open() {
108
+ open.set(true);
109
+ },
110
+ };
107
111
  },
108
112
  };
109
113
  }
package/tracker.test.ts CHANGED
@@ -33,13 +33,13 @@ describe("tracker", () => {
33
33
  }
34
34
  });
35
35
 
36
- yield* sleep(1);
36
+ yield* sleep(0);
37
37
 
38
38
  yield* faucet.pour(function* (send) {
39
39
  yield* send(1);
40
- yield* sleep(1);
40
+ yield* sleep(0);
41
41
  yield* send(2);
42
- yield* sleep(1);
42
+ yield* sleep(0);
43
43
  yield* send(3);
44
44
  });
45
45
 
@@ -75,7 +75,7 @@ describe("tracker", () => {
75
75
  }
76
76
  });
77
77
 
78
- yield* sleep(1);
78
+ yield* sleep(0);
79
79
 
80
80
  yield* faucet.pour(function* (send) {
81
81
  yield* send(1);
package/valve.test.ts CHANGED
@@ -33,12 +33,12 @@ describe("valve", () => {
33
33
  yield* spawn(function* () {
34
34
  for (const value of yield* each(stream(faucet))) {
35
35
  values.push(value);
36
- yield* sleep(1);
36
+ yield* sleep(0);
37
37
  yield* each.next();
38
38
  }
39
39
  });
40
40
 
41
- yield* sleep(1);
41
+ yield* sleep(0);
42
42
 
43
43
  yield* faucet.pour([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
44
44