@effectionx/stream-helpers 0.1.0
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 +260 -0
- package/esm/batch.d.ts +21 -0
- package/esm/batch.d.ts.map +1 -0
- package/esm/batch.js +54 -0
- package/esm/filter.d.ts +23 -0
- package/esm/filter.d.ts.map +1 -0
- package/esm/filter.js +45 -0
- package/esm/map.d.ts +9 -0
- package/esm/map.d.ts.map +1 -0
- package/esm/map.js +27 -0
- package/esm/mod.d.ts +6 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +5 -0
- package/esm/package.json +3 -0
- package/esm/signals.d.ts +23 -0
- package/esm/signals.d.ts.map +1 -0
- package/esm/signals.js +119 -0
- package/esm/test-helpers/faucet.d.ts +78 -0
- package/esm/test-helpers/faucet.d.ts.map +1 -0
- package/esm/test-helpers/faucet.js +72 -0
- package/esm/test-helpers.d.ts +2 -0
- package/esm/test-helpers.d.ts.map +1 -0
- package/esm/test-helpers.js +1 -0
- package/esm/tracker.d.ts +24 -0
- package/esm/tracker.d.ts.map +1 -0
- package/esm/tracker.js +39 -0
- package/esm/valve.d.ts +22 -0
- package/esm/valve.d.ts.map +1 -0
- package/esm/valve.js +48 -0
- package/package.json +30 -0
- package/script/batch.d.ts +21 -0
- package/script/batch.d.ts.map +1 -0
- package/script/batch.js +57 -0
- package/script/filter.d.ts +23 -0
- package/script/filter.d.ts.map +1 -0
- package/script/filter.js +48 -0
- package/script/map.d.ts +9 -0
- package/script/map.d.ts.map +1 -0
- package/script/map.js +30 -0
- package/script/mod.d.ts +6 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +21 -0
- package/script/package.json +3 -0
- package/script/signals.d.ts +23 -0
- package/script/signals.d.ts.map +1 -0
- package/script/signals.js +125 -0
- package/script/test-helpers/faucet.d.ts +78 -0
- package/script/test-helpers/faucet.d.ts.map +1 -0
- package/script/test-helpers/faucet.js +75 -0
- package/script/test-helpers.d.ts +2 -0
- package/script/test-helpers.d.ts.map +1 -0
- package/script/test-helpers.js +17 -0
- package/script/tracker.d.ts +24 -0
- package/script/tracker.d.ts.map +1 -0
- package/script/tracker.js +42 -0
- package/script/valve.d.ts +22 -0
- package/script/valve.d.ts.map +1 -0
- package/script/valve.js +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Stream Helpers
|
|
2
|
+
|
|
3
|
+
A collection of type-safe stream helpers built on top of
|
|
4
|
+
[Effection](https://github.com/thefrontside/effection) for efficient and
|
|
5
|
+
controlled stream processing.
|
|
6
|
+
|
|
7
|
+
## Included Helpers
|
|
8
|
+
|
|
9
|
+
### Filter
|
|
10
|
+
|
|
11
|
+
The `filter` helper narrows a stream by only passing through items that match a
|
|
12
|
+
predicate.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { filter } from "@effectionx/stream-helpers";
|
|
16
|
+
import { each } from "effection";
|
|
17
|
+
|
|
18
|
+
// Example: Synchronous filtering
|
|
19
|
+
function* syncExample(source: Stream<number, unknown>) {
|
|
20
|
+
|
|
21
|
+
const gt5 = filter<number>(function* (x) { return x > 5 });
|
|
22
|
+
|
|
23
|
+
for (const value of yield* each(gt5(stream))) {
|
|
24
|
+
console.log(value); // Only values > 5
|
|
25
|
+
yield* each.next();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Example: Asynchronous filtering
|
|
30
|
+
function* asyncExample(source: Stream<number, unknown>) {
|
|
31
|
+
|
|
32
|
+
const evensOf = filter<number>(function* (x) {
|
|
33
|
+
yield* sleep(100); // Simulate async operation
|
|
34
|
+
return x % 2 === 0; // Keep only even numbers
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
for (const value of yield* each(evensOf(stream))) {
|
|
38
|
+
console.log(value); // Only even numbers
|
|
39
|
+
yield* each.next();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Map
|
|
45
|
+
|
|
46
|
+
The `map` helper transforms each item in a stream using a provided function.
|
|
47
|
+
This is useful for data transformation operations where you need to process each
|
|
48
|
+
item individually.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { map } from "@effectionx/stream-helpers";
|
|
52
|
+
import { each } from "effection";
|
|
53
|
+
|
|
54
|
+
function* example(stream: Stream<number, unknown>) {
|
|
55
|
+
const double = map<number>(function* (x) {
|
|
56
|
+
return x * 2;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
for (const value of yield* each(double(stream))) {
|
|
60
|
+
console.log(value); // Each value is doubled
|
|
61
|
+
yield* each.next();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Batch
|
|
67
|
+
|
|
68
|
+
The `batch` helper is useful when you want to convert individual items passing
|
|
69
|
+
through the stream into arrays of items. The batches can be created either by
|
|
70
|
+
specifying a maximum time or a maximum size. If both are specified, the batch
|
|
71
|
+
will be created when either condition is met.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { batch } from "@effectionx/stream-helpers";
|
|
75
|
+
import { each } from "effection";
|
|
76
|
+
|
|
77
|
+
// Example: Batch by size
|
|
78
|
+
function* exampleBySize(stream: Stream<number, unknown>) {
|
|
79
|
+
const byThree = batch({ maxSize: 3});
|
|
80
|
+
|
|
81
|
+
for (const items of yield* each(byThree(stream))) {
|
|
82
|
+
console.log(batch); // [1, 2, 3], [4, 5, 6], ...
|
|
83
|
+
yield* each.next();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Example: Batch by time
|
|
88
|
+
function* exampleByTime(stream: Stream<number, unknown>) {
|
|
89
|
+
const stream = batch({ maxTime: 1000 })(sourceStream);
|
|
90
|
+
|
|
91
|
+
for (const batch of yield* each(stream)) {
|
|
92
|
+
console.log(batch); // Items received within 1 second
|
|
93
|
+
yield* each.next();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Example: Combined batching
|
|
98
|
+
function* exampleCombined(stream: Stream<number, unknown>) {
|
|
99
|
+
|
|
100
|
+
const batched = batch({
|
|
101
|
+
maxSize: 5,
|
|
102
|
+
maxTime: 1000,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
for (const batch of yield* each(batched(stream))) {
|
|
106
|
+
console.log(batch); // Up to 5 items within 1 second
|
|
107
|
+
yield* each.next();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Valve
|
|
113
|
+
|
|
114
|
+
Allows to apply backpressure to the source stream to prevent overwhelming the
|
|
115
|
+
downstream consumer. This is useful with any stream that generates items faster
|
|
116
|
+
than the consumer can consume them. It was originally designed for use with
|
|
117
|
+
Kafka where the producer can cause the service to run out of memory when the
|
|
118
|
+
producer produces many faster than the consumer to process the messages. It can
|
|
119
|
+
be used as a buffer for any infinite stream.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { valve } from "@effectionx/stream-helpers";
|
|
123
|
+
import { each } from "effection";
|
|
124
|
+
|
|
125
|
+
function* example() {
|
|
126
|
+
const regulated = valve({
|
|
127
|
+
// buffer size threshold when close operation will invoked
|
|
128
|
+
closeAt: 1000,
|
|
129
|
+
*close() {
|
|
130
|
+
// pause the source stream
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// buffer size threshold when open operation will be invoked
|
|
134
|
+
openAt: 100,
|
|
135
|
+
*open() {
|
|
136
|
+
// resume the source stream
|
|
137
|
+
},
|
|
138
|
+
})(stream);
|
|
139
|
+
|
|
140
|
+
for (const value of yield* each(regulated)) {
|
|
141
|
+
console.log(value);
|
|
142
|
+
yield* each.next();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Passthrough Tracker
|
|
148
|
+
|
|
149
|
+
Passthrough Tracker stream helper provides a way to know if all items that
|
|
150
|
+
passed through the stream have been handled. This is especially helpful when you
|
|
151
|
+
want to ensure that all items were processed before completing an operation.
|
|
152
|
+
|
|
153
|
+
It's different from other stream helpers because you must first call
|
|
154
|
+
`createTracker` function which retuns an object. The actual helper is on the
|
|
155
|
+
`passthrough` method which you can call and chain as you would with other
|
|
156
|
+
helpers.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { each, signal } from "effection";
|
|
160
|
+
import { createTracker } from "@ffectionx/stream-helpers"
|
|
161
|
+
|
|
162
|
+
const source = signal(0);
|
|
163
|
+
|
|
164
|
+
// create the tracker
|
|
165
|
+
const tracker = yield* createTracker();
|
|
166
|
+
|
|
167
|
+
// create passthrough stream helper
|
|
168
|
+
const track = tracker.passthrough();
|
|
169
|
+
|
|
170
|
+
for (const value of yield* each(track(source))) {
|
|
171
|
+
// mark items
|
|
172
|
+
tracker.markOne(value);
|
|
173
|
+
yield* each.next();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// will resolve when all items that passed through the stream were seen
|
|
177
|
+
yield* tracker;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Composing stream helpers
|
|
181
|
+
|
|
182
|
+
You can use a simple `pipe()` to compose a series of stream helpers together. In
|
|
183
|
+
this example, we use one from [remeda](https://remedajs.com/docs/#pipe),
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { batch, filter, map, valve } from "@effectionx/stream-helpers";
|
|
187
|
+
import { each } from "effection";
|
|
188
|
+
// any standard pipe function should work
|
|
189
|
+
import { pipe } from "remeda";
|
|
190
|
+
|
|
191
|
+
function* example(source: Stream<number, unknown>) {
|
|
192
|
+
// Compose stream helpers using pipe
|
|
193
|
+
const stream = pipe(
|
|
194
|
+
source,
|
|
195
|
+
valve({ open, close, openAt: 100, closeAt: 100 }),
|
|
196
|
+
filter(function* (x) {
|
|
197
|
+
return x > 0;
|
|
198
|
+
}),
|
|
199
|
+
map(function* (x) {
|
|
200
|
+
return x * 20;
|
|
201
|
+
}),
|
|
202
|
+
batch({ maxSize: 50 }),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
for (const value of yield* each(stream)) {
|
|
206
|
+
console.log(value);
|
|
207
|
+
yield* each.next();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Testing Streams
|
|
213
|
+
|
|
214
|
+
The library includes testing utilities to help you test your stream processing
|
|
215
|
+
code. These are available in `@effectionx/stream-helpers/test-helpers` export.
|
|
216
|
+
|
|
217
|
+
### Faucet
|
|
218
|
+
|
|
219
|
+
The `useFaucet` function creates a stream that can be used to test the behavior
|
|
220
|
+
of streams that use backpressure. It's particularly useful in tests where you
|
|
221
|
+
need a controllable source stream.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { useFaucet } from "@effectionx/stream-helpers/test-helpers";
|
|
225
|
+
import { each, run, spawn } from "effection";
|
|
226
|
+
|
|
227
|
+
await run(function* () {
|
|
228
|
+
const faucet = yield* useFaucet<number>({ open: true });
|
|
229
|
+
|
|
230
|
+
// Remember to spawn the stream subscription before sending items to the stream
|
|
231
|
+
yield* spawn(function* () {
|
|
232
|
+
for (let i of yield* each(faucet)) {
|
|
233
|
+
console.log(i);
|
|
234
|
+
yield* each.next();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Pass an array of items to send items to the stream one at a time synchronously
|
|
239
|
+
yield* faucet.pour([1, 2, 3]);
|
|
240
|
+
|
|
241
|
+
// Pass an operation to control the rate at which items are sent to the stream
|
|
242
|
+
yield* faucet.pour(function* (send) {
|
|
243
|
+
yield* sleep(10);
|
|
244
|
+
send(5);
|
|
245
|
+
yield* sleep(30);
|
|
246
|
+
send(6);
|
|
247
|
+
yield* sleep(10);
|
|
248
|
+
send(7);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// You can close the faucet to stop items from being sent
|
|
252
|
+
faucet.close();
|
|
253
|
+
|
|
254
|
+
// And open it again when needed
|
|
255
|
+
faucet.open();
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Items sent to the faucet stream while it's closed are not buffered, in other
|
|
260
|
+
words, they'll be dropped.
|
package/esm/batch.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Stream } from "effection";
|
|
2
|
+
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & {
|
|
3
|
+
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
|
|
4
|
+
}[Keys];
|
|
5
|
+
export interface BatchOptions {
|
|
6
|
+
readonly maxTime: number;
|
|
7
|
+
readonly maxSize: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates batches of items from the source stream. The batches can be created either by
|
|
11
|
+
* specifying a maximum time or a maximum size. If both are specified, the batch will be
|
|
12
|
+
* created when either condition is met.
|
|
13
|
+
*
|
|
14
|
+
* @param options - The options for the batch.
|
|
15
|
+
* @param options.maxTime - The maximum time to wait for a batch.
|
|
16
|
+
* @param options.maxSize - The maximum size of a batch.
|
|
17
|
+
* @returns A stream of arrays of items from the source stream.
|
|
18
|
+
*/
|
|
19
|
+
export declare function batch(options: RequireAtLeastOne<BatchOptions>): <T>(stream: Stream<T, never>) => Stream<T[], never>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAGlE,KAAK,iBAAiB,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,CAAC,GAAG,MAAM,CAAC,IACpD,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GAC/B;KACC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;CACzE,CAAC,IAAI,CAAC,CAAC;AAEV,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CACnB,OAAO,EAAE,iBAAiB,CAAC,YAAY,CAAC,GACvC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CA4CrD"}
|
package/esm/batch.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { each, race, sleep, spawn } from "effection";
|
|
2
|
+
import { createArraySignal, is } from "./signals.js";
|
|
3
|
+
/**
|
|
4
|
+
* Creates batches of items from the source stream. The batches can be created either by
|
|
5
|
+
* specifying a maximum time or a maximum size. If both are specified, the batch will be
|
|
6
|
+
* created when either condition is met.
|
|
7
|
+
*
|
|
8
|
+
* @param options - The options for the batch.
|
|
9
|
+
* @param options.maxTime - The maximum time to wait for a batch.
|
|
10
|
+
* @param options.maxSize - The maximum size of a batch.
|
|
11
|
+
* @returns A stream of arrays of items from the source stream.
|
|
12
|
+
*/
|
|
13
|
+
export function batch(options) {
|
|
14
|
+
return function (stream) {
|
|
15
|
+
return {
|
|
16
|
+
*[Symbol.iterator]() {
|
|
17
|
+
let batch = yield* createArraySignal([]);
|
|
18
|
+
yield* spawn(function* () {
|
|
19
|
+
for (let item of yield* each(stream)) {
|
|
20
|
+
batch.push(item);
|
|
21
|
+
if (options.maxSize && batch.length >= options.maxSize) {
|
|
22
|
+
// wait until it's drained
|
|
23
|
+
yield* is(batch, (batch) => batch.length === 0);
|
|
24
|
+
}
|
|
25
|
+
yield* each.next();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
function drain() {
|
|
29
|
+
let value = batch.valueOf();
|
|
30
|
+
batch.set([]);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
*next() {
|
|
35
|
+
yield* is(batch, (batch) => batch.length >= 1);
|
|
36
|
+
if (options.maxTime && options.maxSize) {
|
|
37
|
+
yield* race([
|
|
38
|
+
is(batch, (batch) => batch.length === options.maxSize),
|
|
39
|
+
sleep(options.maxTime),
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
else if (options.maxTime) {
|
|
43
|
+
yield* sleep(options.maxTime);
|
|
44
|
+
}
|
|
45
|
+
else if (options.maxSize) {
|
|
46
|
+
yield* is(batch, (batch) => batch.length === options.maxSize);
|
|
47
|
+
}
|
|
48
|
+
return { done: false, value: drain() };
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}
|
package/esm/filter.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Operation, Stream } from "effection";
|
|
2
|
+
/**
|
|
3
|
+
* Filters items from the stream based on a predicate function.
|
|
4
|
+
*
|
|
5
|
+
* @param predicate - The function to test each item
|
|
6
|
+
* @returns A stream transformer that only emits items that pass the predicate
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { filter } from "@effectionx/stream-helpers";
|
|
11
|
+
* import { run, each } from "effection";
|
|
12
|
+
*
|
|
13
|
+
* await run(function* () {
|
|
14
|
+
* const stream = filter((x: number) => x > 5)(sourceStream);
|
|
15
|
+
*
|
|
16
|
+
* for (const value of yield* each(stream)) {
|
|
17
|
+
* console.log(value); // Only values > 5
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function filter<T>(predicate: (value: T) => Operation<boolean>): <TDone>(stream: Stream<T, TDone>) => Stream<T, TDone>;
|
|
23
|
+
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACtB,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC,OAAO,CAAC,GAC1C,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAyBvD"}
|
package/esm/filter.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters items from the stream based on a predicate function.
|
|
3
|
+
*
|
|
4
|
+
* @param predicate - The function to test each item
|
|
5
|
+
* @returns A stream transformer that only emits items that pass the predicate
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { filter } from "@effectionx/stream-helpers";
|
|
10
|
+
* import { run, each } from "effection";
|
|
11
|
+
*
|
|
12
|
+
* await run(function* () {
|
|
13
|
+
* const stream = filter((x: number) => x > 5)(sourceStream);
|
|
14
|
+
*
|
|
15
|
+
* for (const value of yield* each(stream)) {
|
|
16
|
+
* console.log(value); // Only values > 5
|
|
17
|
+
* }
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function filter(predicate) {
|
|
22
|
+
return function (stream) {
|
|
23
|
+
return {
|
|
24
|
+
*[Symbol.iterator]() {
|
|
25
|
+
const subscription = yield* stream;
|
|
26
|
+
return {
|
|
27
|
+
*next() {
|
|
28
|
+
while (true) {
|
|
29
|
+
const next = yield* subscription.next();
|
|
30
|
+
if (next.done) {
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
if (yield* predicate(next.value)) {
|
|
34
|
+
return {
|
|
35
|
+
done: false,
|
|
36
|
+
value: next.value,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
package/esm/map.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Operation, Stream } from "effection";
|
|
2
|
+
/**
|
|
3
|
+
* Transforms each item in the stream using the provided function.
|
|
4
|
+
*
|
|
5
|
+
* @param fn - The function to transform each item
|
|
6
|
+
* @returns A stream transformer that applies the function to each item
|
|
7
|
+
*/
|
|
8
|
+
export declare function map<A, B>(fn: (value: A) => Operation<B>): <TClose>(stream: Stream<A, TClose>) => Stream<B, TClose>;
|
|
9
|
+
//# sourceMappingURL=map.d.ts.map
|
package/esm/map.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,CAAC,EACtB,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAC7B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAsB1D"}
|
package/esm/map.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transforms each item in the stream using the provided function.
|
|
3
|
+
*
|
|
4
|
+
* @param fn - The function to transform each item
|
|
5
|
+
* @returns A stream transformer that applies the function to each item
|
|
6
|
+
*/
|
|
7
|
+
export function map(fn) {
|
|
8
|
+
return function (stream) {
|
|
9
|
+
return {
|
|
10
|
+
*[Symbol.iterator]() {
|
|
11
|
+
const subscription = yield* stream;
|
|
12
|
+
return {
|
|
13
|
+
*next() {
|
|
14
|
+
const next = yield* subscription.next();
|
|
15
|
+
if (next.done) {
|
|
16
|
+
return next;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
done: false,
|
|
20
|
+
value: yield* fn(next.value),
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
package/esm/mod.d.ts
ADDED
package/esm/mod.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
package/esm/mod.js
ADDED
package/esm/package.json
ADDED
package/esm/signals.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Operation, type Stream } from "effection";
|
|
2
|
+
interface ValueStream<T> extends Stream<T, void> {
|
|
3
|
+
valueOf(): T;
|
|
4
|
+
}
|
|
5
|
+
interface Settable<T> {
|
|
6
|
+
set(value: T): T;
|
|
7
|
+
}
|
|
8
|
+
export interface SettableValue<T> extends Settable<T>, ValueStream<T> {
|
|
9
|
+
}
|
|
10
|
+
interface ArraySignal<T> extends SettableValue<T[]> {
|
|
11
|
+
push(item: T): number;
|
|
12
|
+
shift(): Operation<T>;
|
|
13
|
+
valueOf(): T[];
|
|
14
|
+
get length(): number;
|
|
15
|
+
}
|
|
16
|
+
export declare function createArraySignal<T>(initial: Iterable<T>): Operation<ArraySignal<T>>;
|
|
17
|
+
export declare function is<T>(array: ValueStream<T>, predicate: (item: T) => boolean): Generator<any, void, any>;
|
|
18
|
+
export interface BooleanSignal extends SettableValue<boolean> {
|
|
19
|
+
}
|
|
20
|
+
export declare function createBoolean(initial?: boolean): any;
|
|
21
|
+
export declare function createSetSignal<T>(initial?: Array<T>): any;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,MAAM,EACZ,MAAM,WAAW,CAAC;AAGnB,UAAU,WAAW,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC;IAC9C,OAAO,IAAI,CAAC,CAAC;CACd;AAED,UAAU,QAAQ,CAAC,CAAC;IAClB,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;CAAG;AAExE,UAAU,WAAW,CAAC,CAAC,CAAE,SAAQ,aAAa,CAAC,CAAC,EAAE,CAAC;IACjD,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC;IACtB,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC,EAAE,CAAC;IACf,IAAI,MAAM,IAAI,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GACnB,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAwC3B;AAED,wBAAiB,EAAE,CAAC,CAAC,EACnB,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,6BAahC;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa,CAAC,OAAO,CAAC;CAC5D;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,OAAe,OA0BrD;AASD,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAE,KAAK,CAAC,CAAC,CAAM,OAwCxD"}
|
package/esm/signals.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createSignal, each, resource, } from "effection";
|
|
2
|
+
import { List, Set } from "immutable";
|
|
3
|
+
export function createArraySignal(initial) {
|
|
4
|
+
return resource(function* (provide) {
|
|
5
|
+
const signal = createSignal();
|
|
6
|
+
const ref = {
|
|
7
|
+
current: List.of(...initial),
|
|
8
|
+
};
|
|
9
|
+
const array = {
|
|
10
|
+
[Symbol.iterator]: signal[Symbol.iterator],
|
|
11
|
+
set(value) {
|
|
12
|
+
ref.current = List.of(...value);
|
|
13
|
+
signal.send(ref.current.toArray());
|
|
14
|
+
return ref.current.toArray();
|
|
15
|
+
},
|
|
16
|
+
push(item) {
|
|
17
|
+
ref.current = ref.current.push(item);
|
|
18
|
+
signal.send(ref.current.toArray());
|
|
19
|
+
return ref.current.size;
|
|
20
|
+
},
|
|
21
|
+
*shift() {
|
|
22
|
+
yield* is(array, (array) => array.length > 0);
|
|
23
|
+
let value = ref.current.first();
|
|
24
|
+
ref.current = ref.current.shift();
|
|
25
|
+
signal.send(ref.current.toArray());
|
|
26
|
+
return value;
|
|
27
|
+
},
|
|
28
|
+
valueOf() {
|
|
29
|
+
return ref.current.toArray();
|
|
30
|
+
},
|
|
31
|
+
get length() {
|
|
32
|
+
return ref.current.size;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
yield* provide(array);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
signal.close();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function* is(array, predicate) {
|
|
44
|
+
const result = predicate(array.valueOf());
|
|
45
|
+
if (result) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for (const value of yield* each(array)) {
|
|
49
|
+
const result = predicate(value);
|
|
50
|
+
if (result) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
yield* each.next();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function createBoolean(initial = false) {
|
|
57
|
+
return resource(function* (provide) {
|
|
58
|
+
const signal = createSignal();
|
|
59
|
+
const ref = { current: initial };
|
|
60
|
+
try {
|
|
61
|
+
yield* provide({
|
|
62
|
+
[Symbol.iterator]: signal[Symbol.iterator],
|
|
63
|
+
set(value) {
|
|
64
|
+
if (value !== ref.current) {
|
|
65
|
+
ref.current = value;
|
|
66
|
+
signal.send(ref.current);
|
|
67
|
+
}
|
|
68
|
+
return ref.current;
|
|
69
|
+
},
|
|
70
|
+
valueOf() {
|
|
71
|
+
return ref.current;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
signal.close();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function createSetSignal(initial = []) {
|
|
81
|
+
return resource(function* (provide) {
|
|
82
|
+
const signal = createSignal();
|
|
83
|
+
const ref = { current: Set.of(...initial) };
|
|
84
|
+
try {
|
|
85
|
+
yield* provide({
|
|
86
|
+
[Symbol.iterator]: signal[Symbol.iterator],
|
|
87
|
+
set(value) {
|
|
88
|
+
ref.current = Set.of(...value);
|
|
89
|
+
signal.send(ref.current.toSet());
|
|
90
|
+
return ref.current;
|
|
91
|
+
},
|
|
92
|
+
add(item) {
|
|
93
|
+
ref.current = ref.current.add(item);
|
|
94
|
+
signal.send(ref.current.toSet());
|
|
95
|
+
return ref.current.toSet();
|
|
96
|
+
},
|
|
97
|
+
difference(items) {
|
|
98
|
+
ref.current = ref.current.subtract(items);
|
|
99
|
+
signal.send(ref.current.toSet());
|
|
100
|
+
return ref.current.toSet();
|
|
101
|
+
},
|
|
102
|
+
delete(item) {
|
|
103
|
+
if (ref.current.has(item)) {
|
|
104
|
+
ref.current = ref.current.delete(item);
|
|
105
|
+
signal.send(ref.current.toSet());
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
},
|
|
110
|
+
valueOf() {
|
|
111
|
+
return ref.current.toSet();
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
signal.close();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|