@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.
Files changed (58) hide show
  1. package/README.md +260 -0
  2. package/esm/batch.d.ts +21 -0
  3. package/esm/batch.d.ts.map +1 -0
  4. package/esm/batch.js +54 -0
  5. package/esm/filter.d.ts +23 -0
  6. package/esm/filter.d.ts.map +1 -0
  7. package/esm/filter.js +45 -0
  8. package/esm/map.d.ts +9 -0
  9. package/esm/map.d.ts.map +1 -0
  10. package/esm/map.js +27 -0
  11. package/esm/mod.d.ts +6 -0
  12. package/esm/mod.d.ts.map +1 -0
  13. package/esm/mod.js +5 -0
  14. package/esm/package.json +3 -0
  15. package/esm/signals.d.ts +23 -0
  16. package/esm/signals.d.ts.map +1 -0
  17. package/esm/signals.js +119 -0
  18. package/esm/test-helpers/faucet.d.ts +78 -0
  19. package/esm/test-helpers/faucet.d.ts.map +1 -0
  20. package/esm/test-helpers/faucet.js +72 -0
  21. package/esm/test-helpers.d.ts +2 -0
  22. package/esm/test-helpers.d.ts.map +1 -0
  23. package/esm/test-helpers.js +1 -0
  24. package/esm/tracker.d.ts +24 -0
  25. package/esm/tracker.d.ts.map +1 -0
  26. package/esm/tracker.js +39 -0
  27. package/esm/valve.d.ts +22 -0
  28. package/esm/valve.d.ts.map +1 -0
  29. package/esm/valve.js +48 -0
  30. package/package.json +30 -0
  31. package/script/batch.d.ts +21 -0
  32. package/script/batch.d.ts.map +1 -0
  33. package/script/batch.js +57 -0
  34. package/script/filter.d.ts +23 -0
  35. package/script/filter.d.ts.map +1 -0
  36. package/script/filter.js +48 -0
  37. package/script/map.d.ts +9 -0
  38. package/script/map.d.ts.map +1 -0
  39. package/script/map.js +30 -0
  40. package/script/mod.d.ts +6 -0
  41. package/script/mod.d.ts.map +1 -0
  42. package/script/mod.js +21 -0
  43. package/script/package.json +3 -0
  44. package/script/signals.d.ts +23 -0
  45. package/script/signals.d.ts.map +1 -0
  46. package/script/signals.js +125 -0
  47. package/script/test-helpers/faucet.d.ts +78 -0
  48. package/script/test-helpers/faucet.d.ts.map +1 -0
  49. package/script/test-helpers/faucet.js +75 -0
  50. package/script/test-helpers.d.ts +2 -0
  51. package/script/test-helpers.d.ts.map +1 -0
  52. package/script/test-helpers.js +17 -0
  53. package/script/tracker.d.ts +24 -0
  54. package/script/tracker.d.ts.map +1 -0
  55. package/script/tracker.js +42 -0
  56. package/script/valve.d.ts +22 -0
  57. package/script/valve.d.ts.map +1 -0
  58. 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
+ }
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ export * from "./batch.js";
2
+ export * from "./valve.js";
3
+ export * from "./map.js";
4
+ export * from "./filter.js";
5
+ export * from "./tracker.js";
6
+ //# sourceMappingURL=mod.d.ts.map
@@ -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
@@ -0,0 +1,5 @@
1
+ export * from "./batch.js";
2
+ export * from "./valve.js";
3
+ export * from "./map.js";
4
+ export * from "./filter.js";
5
+ export * from "./tracker.js";
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -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
+ }