@soffinal/stream 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 ADDED
@@ -0,0 +1,776 @@
1
+ # @soffinal/stream
2
+
3
+ Reactive async-first streaming library with functional transformations and reactive data structures. Build real-time apps with streams, state management, and event-driven collections.
4
+
5
+ ## Key Features
6
+
7
+ - **Multicast by Default**: One stream, many consumers - perfect for event systems
8
+ - **Awaitable Streams**: Simply `await stream` to get the next value
9
+ - **Async Iterable**: Use `for await` loops to process data as it flows
10
+ - **Lazy & Shared**: All operation run once per event, results shared across all listeners
11
+ - **Auto Cleanup**: Resources freed automatically when no longer needed
12
+ - **Async & Stateful Transformers**: Filter, map, group accept sync/async predicates and can maintain state
13
+ - **Custom Transformers**: Build your own transformers like throttle, debounce, distinctUntilChanged
14
+ - **Reactive State**: State objects that automatically notify dependents of changes
15
+ - **Reactive Collections**: Lists, Maps, Sets that emit change events
16
+
17
+ **@soffinal/stream** treats asynchronous data flow as the primary concern, making it useful for:
18
+
19
+ - Real-time data processing
20
+ - Event-driven architectures
21
+ - Streaming APIs and WebSockets
22
+ - State management
23
+ - Memory-efficient data pipelines
24
+ - UI updates
25
+
26
+ ## The Stream: Your Data Pipeline Foundation
27
+
28
+ A `Stream` is an async iterable that can push values to multiple listeners, while also being a promise for the next value. Think of it as a data pipe where information flows through - multiple consumers can tap into the same flow to receive all values, or you can simply await the stream to get the next single value.
29
+
30
+ ```typescript
31
+ import { Stream } from "@soffinal/stream";
32
+
33
+ // Create a data pipeline
34
+ const userEvents = new Stream<{ userId: string; action: string }>();
35
+
36
+ // Multiple consumers can listen to the same stream
37
+ userEvents.listen((event) => logToAnalytics(event));
38
+ userEvents.listen((event) => updateUserActivity(event));
39
+ userEvents.listen((event) => triggerNotifications(event));
40
+
41
+ // Push data through the pipeline
42
+ userEvents.push({ userId: "alice", action: "login" }, { userId: "bob", action: "purchase" });
43
+ // All three listeners receive both events
44
+
45
+ // Stream is also a promise for the next value
46
+ const nextEvent = await userEvents; // Waits for next pushed value
47
+ console.log("Next event:", nextEvent);
48
+ ```
49
+
50
+ ### Async Iteration: Processing Data as it Flows
51
+
52
+ Streams implement `AsyncIterable`, allowing you to process data as it arrives using `for await` loops:
53
+
54
+ ```typescript
55
+ const messageStream = new Stream<string>();
56
+
57
+ // Process messages as they arrive
58
+ (async () => {
59
+ for await (const message of messageStream) {
60
+ await processMessage(message);
61
+ if (message === "shutdown") break;
62
+ }
63
+ })();
64
+
65
+ messageStream.push("user-login", "data-sync", "shutdown");
66
+ ```
67
+
68
+ ### Generator-based Streams: Infinite Data Sources
69
+
70
+ Create infinite, lazy data sources using async generators:
71
+
72
+ ```typescript
73
+ const sensorStream = new Stream(async function* () {
74
+ while (true) {
75
+ const temperature = await readTemperatureSensor();
76
+ const humidity = await readHumiditySensor();
77
+ yield { temperature, humidity, timestamp: Date.now() };
78
+ await new Promise((resolve) => setTimeout(resolve, 1000));
79
+ }
80
+ });
81
+
82
+ sensorStream.listen((data) => console.log("Sensor reading:", data));
83
+ ```
84
+
85
+ ### Instance Transformers
86
+
87
+ Streams support method chaining for familiar object-oriented programming patterns:
88
+
89
+ #### Filter: Smart Data Selection
90
+
91
+ Filtering goes beyond simple predicates - it supports stateful filtering and async predicates:
92
+
93
+ ```typescript
94
+ const events = new Stream<{ type: string; userId: string; data: any }>();
95
+
96
+ // Simple filtering
97
+ const loginEvents = events.filter((e) => e.type === "login");
98
+
99
+ // Stateful filtering - only allow increasing user IDs
100
+ const validEvents = events.filter(
101
+ 0, // initial state
102
+ (lastUserId, event) => {
103
+ const currentId = parseInt(event.userId);
104
+ return [currentId > lastUserId, currentId];
105
+ }
106
+ );
107
+
108
+ // Async filtering - validate against external service
109
+ const authorizedEvents = events.filter(async (event) => {
110
+ const isAuthorized = await checkUserPermissions(event.userId);
111
+ return isAuthorized;
112
+ });
113
+ ```
114
+
115
+ #### Map: Data Transformation Pipeline
116
+
117
+ Mapping transforms data while maintaining the stream's async nature:
118
+
119
+ ```typescript
120
+ const rawEvents = new Stream<string>();
121
+
122
+ // Simple transformation
123
+ const parsedEvents = rawEvents.map((json) => JSON.parse(json));
124
+
125
+ // Stateful transformation - add sequence numbers
126
+ const sequencedEvents = rawEvents.map(
127
+ 0, // initial sequence
128
+ (seq, event) => [{ ...event, sequence: seq + 1 }, seq + 1]
129
+ );
130
+
131
+ // Async transformation - enrich with external data
132
+ const enrichedEvents = parsedEvents.map(async (event) => {
133
+ const userProfile = await getUserProfile(event.userId);
134
+ return { ...event, user: userProfile };
135
+ });
136
+ ```
137
+
138
+ #### Merge: Combine Multiple Streams
139
+
140
+ Combine multiple streams into a single output stream:
141
+
142
+ ```typescript
143
+ const stream1 = new Stream<string>();
144
+ const stream2 = new Stream<string>();
145
+ const merged = stream1.merge(stream2);
146
+
147
+ merged.listen((msg) => console.log("Merged:", msg));
148
+
149
+ stream1.push("from stream1");
150
+ stream2.push("from stream2");
151
+ ```
152
+
153
+ #### Group: Intelligent Batching
154
+
155
+ Grouping allows you to batch data based on complex conditions:
156
+
157
+ ```typescript
158
+ const transactions = new Stream<{ amount: number; userId: string }>();
159
+
160
+ // Batch by count
161
+ const countBatches = transactions.group((batch) => batch.length >= 100);
162
+
163
+ // Batch by total amount
164
+ const amountBatches = transactions.group(
165
+ 0, // initial sum
166
+ (sum, transaction) => {
167
+ const newSum = sum + transaction.amount;
168
+ return [newSum >= 10000, newSum >= 10000 ? 0 : newSum];
169
+ }
170
+ );
171
+
172
+ // Process batches efficiently
173
+ amountBatches.listen(async (totalAmount) => {
174
+ await processBulkTransaction(totalAmount);
175
+ });
176
+ ```
177
+
178
+ #### Flat: Flatten Nested Arrays
179
+
180
+ Flatten nested arrays in stream values:
181
+
182
+ ```typescript
183
+ const arrays = new Stream<number[]>();
184
+ const flattened = arrays.flat();
185
+
186
+ flattened.listen((n) => console.log("Flat:", n));
187
+ arrays.push([1, 2], [3, 4]);
188
+ // Output: Flat: 1, Flat: 2, Flat: 3, Flat: 4
189
+
190
+ // Deep flattening
191
+ const nested = new Stream<number[][][]>();
192
+ const deepFlat = nested.flat(2);
193
+ ```
194
+
195
+ #### Promise Interface
196
+
197
+ Streams are directly awaitable for the next event/data:
198
+
199
+ ```typescript
200
+ const stream = new Stream<number>();
201
+
202
+ setTimeout(() => {
203
+ // Both resolve when stream.push() is called
204
+ stream.push(5); // nextValue = 5, doubled = 10
205
+ })(async () => {
206
+ // Simply await the stream for the next value
207
+ const nextValue = await stream;
208
+ console.log("Received:", nextValue);
209
+ })()(async () => {
210
+ // Or use .then() for transformation
211
+ const doubled = await stream.then((x) => x * 2);
212
+ })();
213
+ ```
214
+
215
+ #### Listener Events
216
+
217
+ React to listener lifecycle changes on streams:
218
+
219
+ ```typescript
220
+ const stream = new Stream<number>();
221
+
222
+ // Listen to listener lifecycle
223
+ stream.listenerAdded.listen(() => console.log("Listener added"));
224
+ stream.listenerRemoved.listen(() => console.log("Listener removed"));
225
+
226
+ const cleanup = stream.listen((value) => console.log(value));
227
+ cleanup(); // Triggers 'Listener removed'
228
+ ```
229
+
230
+ ## Functional Programming style: Composable Data Transformations
231
+
232
+ The library provides functional programming patterns through the `pipe` method and transformer functions, enabling powerful composition and reusable transformation pipelines.
233
+
234
+ ### Pipe: Functional Composition Made Easy
235
+
236
+ The `pipe` method enables functional composition of stream transformations:
237
+
238
+ ```typescript
239
+ import { Stream, map, filter, group, merge, flat } from "@soffinal/stream";
240
+
241
+ const numbers = new Stream<number>();
242
+
243
+ // Functional composition with built-in transformers
244
+ const result = numbers
245
+ .pipe(filter((n) => n > 0)) // Remove negative numbers
246
+ .pipe(map((n) => n * 2)) // Double each value
247
+ .pipe(group((batch) => batch.length >= 5)) // Group into batches of 5
248
+ .pipe(flat()); // Flatten the batches
249
+
250
+ result.listen((value) => console.log("Processed:", value));
251
+ ```
252
+
253
+ ### Built-in Transformer Functions
254
+
255
+ The library provides irreducible building block transformers that work seamlessly with pipe:
256
+
257
+ ```typescript
258
+ // Import transformer functions
259
+ import { map, filter, group, merge, flat } from "@soffinal/stream";
260
+
261
+ const stream = new Stream<number>();
262
+
263
+ // Simple transformations
264
+ stream
265
+ .pipe(map((n) => n.toString())) // Stream<string>
266
+ .pipe(filter((s) => s.length > 1)) // Stream<string>
267
+ .pipe(map((s) => parseInt(s))); // Stream<number>
268
+
269
+ // Stateful transformations
270
+ stream
271
+ .pipe(filter(0, (prev, curr) => [curr > prev, Math.max(prev, curr)]))
272
+ .pipe(map(0, (sum, n) => [sum + n, sum + n]));
273
+
274
+ // Advanced transformations
275
+ const stream2 = new Stream<number>();
276
+ stream
277
+ .pipe(merge(stream2)) // Merge multiple streams
278
+ .pipe(group((batch) => batch.length >= 10)) // Batch processing
279
+ .pipe(flat()); // Flatten results
280
+ ```
281
+
282
+ ### Custom Transformers: Building Your Own
283
+
284
+ You can create custom transformers using generator functions:
285
+
286
+ ```typescript
287
+ // DistinctUntilChanged transformer - only emit when value changes
288
+ const distinctUntilChanged = <T>(stream: Stream<T>): Stream<T> => {
289
+ return new Stream<T>(async function* () {
290
+ let prev: T;
291
+ let first = true;
292
+
293
+ for await (const value of stream) {
294
+ if (first || value !== prev) {
295
+ yield value;
296
+ prev = value;
297
+ first = false;
298
+ }
299
+ }
300
+ });
301
+ };
302
+
303
+ // Rate limiting transformer
304
+ const throttle =
305
+ <T>(ms: number) =>
306
+ (stream: Stream<T>): Stream<T> => {
307
+ return new Stream<T>(async function* () {
308
+ let lastEmit = 0;
309
+
310
+ for await (const value of stream) {
311
+ const now = Date.now();
312
+ if (now - lastEmit >= ms) {
313
+ yield value;
314
+ lastEmit = now;
315
+ }
316
+ }
317
+ });
318
+ };
319
+
320
+ // Usage with perfect type inference
321
+ const searchInput = new Stream<string>();
322
+ const searchResults = searchInput
323
+ .pipe(distinctUntilChanged) // Only emit when search term changes
324
+ .pipe(throttle(1000)) // Limit API calls
325
+ .pipe(map((query) => searchAPI(query)));
326
+ ```
327
+
328
+ ### Dual Programming Paradigms
329
+
330
+ @soffinal/stream supports both Object-Oriented and Functional programming styles:
331
+
332
+ #### Object-Oriented Style (Method Chaining)
333
+
334
+ ```typescript
335
+ // Familiar method chaining
336
+ const result = stream
337
+ .filter((x) => x > 0)
338
+ .map((x) => x * 2)
339
+ .group((batch) => batch.length >= 5);
340
+
341
+ // Rich instance methods with overloads
342
+ stream
343
+ .filter(0, (prev, curr) => [curr > prev, curr]) // Stateful filtering
344
+ .map(async (x) => await enrich(x)); // Async mapping
345
+ ```
346
+
347
+ #### Functional Style (Pipe Composition)
348
+
349
+ ```typescript
350
+ // Composable transformers
351
+ const result = stream
352
+ .pipe(filter((x) => x > 0))
353
+ .pipe(map((x) => x * 2))
354
+ .pipe(group((batch) => batch.length >= 5));
355
+
356
+ // Reusable transformation pipelines
357
+ const processNumbers = (stream: Stream<number>) => stream.pipe(filter((x) => x > 0)).pipe(map((x) => x * 2));
358
+
359
+ const result1 = stream1.pipe(processNumbers);
360
+ const result2 = stream2.pipe(processNumbers);
361
+ ```
362
+
363
+ ## State: Reactive State Management
364
+
365
+ State objects are streams that hold current values, enabling reactive programming patterns:
366
+
367
+ ```typescript
368
+ import { State } from "@soffinal/stream";
369
+
370
+ // Application state
371
+ const user = new State<User | null>(null);
372
+ const theme = new State<"light" | "dark">("light");
373
+ const notifications = new State<Notification[]>([]);
374
+
375
+ // Reactive computations
376
+ const isLoggedIn = user.map((u) => u !== null);
377
+ const unreadCount = notifications.map((n) => n.filter((x) => !x.read).length);
378
+
379
+ // Automatic UI updates
380
+ isLoggedIn.listen((loggedIn) => {
381
+ document.body.classList.toggle("logged-in", loggedIn);
382
+ });
383
+
384
+ unreadCount.listen((count) => {
385
+ document.title = count > 0 ? `(${count}) My App` : "My App";
386
+ });
387
+ ```
388
+
389
+ ### State Management Example
390
+
391
+ ```typescript
392
+ interface AppState {
393
+ user: { id: string; name: string } | null;
394
+ notifications: string[];
395
+ theme: "light" | "dark";
396
+ }
397
+
398
+ const appState = new State<AppState>({
399
+ user: null,
400
+ notifications: [],
401
+ theme: "light",
402
+ });
403
+
404
+ const isLoggedIn = appState.map((state) => state.user !== null);
405
+ const notificationCount = appState.map((state) => state.notifications.length);
406
+
407
+ // React to login state
408
+ isLoggedIn.listen((loggedIn) => {
409
+ if (loggedIn) {
410
+ console.log("User logged in");
411
+ loadUserData();
412
+ }
413
+ });
414
+
415
+ // Update state immutably
416
+ function login(user: { id: string; name: string }) {
417
+ appState.value = {
418
+ ...appState.value,
419
+ user,
420
+ };
421
+ }
422
+
423
+ function addNotification(message: string) {
424
+ appState.value = {
425
+ ...appState.value,
426
+ notifications: [...appState.value.notifications, message],
427
+ };
428
+ }
429
+ ```
430
+
431
+ ## Reactive Collections: Data Structures That Notify
432
+
433
+ The library extends JavaScript's native collections with reactive capabilities. Every mutation emits events, allowing you to build reactive UIs and data synchronization systems.
434
+
435
+ ### List: Reactive Arrays
436
+
437
+ Reactive Lists provide array-like functionality with fine-grained change notifications:
438
+
439
+ ```typescript
440
+ import { List } from "@soffinal/stream";
441
+
442
+ // Create a reactive todo list
443
+ const todos = new List<{ id: string; text: string; done: boolean }>();
444
+
445
+ // Build reactive UI components
446
+ todos.insert.listen(([index, todo]) => {
447
+ const element = createTodoElement(todo);
448
+ todoContainer.insertBefore(element, todoContainer.children[index]);
449
+ });
450
+
451
+ todos.delete.listen(([index, todo]) => {
452
+ todoContainer.children[index].remove();
453
+ });
454
+
455
+ // Reactive operations
456
+ const completedCount = new State(0);
457
+ todos.insert.listen(() => updateCompletedCount());
458
+ todos.delete.listen(() => updateCompletedCount());
459
+
460
+ function updateCompletedCount() {
461
+ const completed = [...todos].filter((t) => t.done).length;
462
+ completedCount.value = completed;
463
+ }
464
+
465
+ // Automatic persistence
466
+ todos.insert.listen(() => saveTodosToStorage([...todos]));
467
+ todos.delete.listen(() => saveTodosToStorage([...todos]));
468
+ ```
469
+
470
+ ### Map: Reactive Key-Value Store
471
+
472
+ Key-value store that emits events on changes:
473
+
474
+ ```typescript
475
+ import { Map } from "@soffinal/stream";
476
+
477
+ const cache = new Map<string, any>();
478
+
479
+ // Listen to cache updates (only emits on actual changes)
480
+ cache.set.listen(([key, value]) => {
481
+ console.log(`Cache updated: ${key} = ${value}`);
482
+ });
483
+
484
+ // Listen to cache evictions
485
+ cache.delete.listen(([key, value]) => {
486
+ console.log(`Cache evicted: ${key}`);
487
+ });
488
+
489
+ // Listen to clear events
490
+ cache.clear.listen(() => console.log("Cache cleared"));
491
+
492
+ cache.set("user:123", { name: "John" }); // Cache updated: user:123 = {...}
493
+ cache.set("user:123", { name: "John" }); // No emission (same value)
494
+ cache.delete("user:123"); // Cache evicted: user:123
495
+ cache.delete("nonexistent"); // No emission (didn't exist)
496
+
497
+ // All native Map methods available
498
+ console.log(cache.size);
499
+ console.log(cache.has("key"));
500
+ for (const [key, value] of cache) {
501
+ console.log(key, value);
502
+ }
503
+ ```
504
+
505
+ ### Set: Reactive Unique Collections
506
+
507
+ Unique value collection that emits events on changes:
508
+
509
+ ```typescript
510
+ import { Set } from "@soffinal/stream";
511
+
512
+ const activeUsers = new Set<string>();
513
+
514
+ // Listen to additions (only emits for new values)
515
+ activeUsers.add.listen((userId) => {
516
+ console.log(`User ${userId} came online`);
517
+ broadcastUserStatus(userId, "online");
518
+ });
519
+
520
+ // Listen to deletions
521
+ activeUsers.delete.listen((userId) => {
522
+ console.log(`User ${userId} went offline`);
523
+ broadcastUserStatus(userId, "offline");
524
+ });
525
+
526
+ // Listen to clear events
527
+ activeUsers.clear.listen(() => console.log("All users cleared"));
528
+
529
+ activeUsers.add("alice"); // User alice came online
530
+ activeUsers.add("alice"); // No emission (duplicate)
531
+ activeUsers.delete("alice"); // User alice went offline
532
+ activeUsers.delete("bob"); // No emission (didn't exist)
533
+
534
+ // All native Set methods available
535
+ console.log(activeUsers.size);
536
+ console.log(activeUsers.has("alice"));
537
+ for (const user of activeUsers) {
538
+ console.log(user);
539
+ }
540
+ ```
541
+
542
+ ## Use Cases
543
+
544
+ Practical examples of building reactive applications.
545
+
546
+ ### Real-time Data Processing
547
+
548
+ Process sensor data with filtering and transformation:
549
+
550
+ ```typescript
551
+ const sensorData = new Stream<{ temperature: number; humidity: number }>();
552
+
553
+ // Alert system
554
+ sensorData.filter((data) => data.temperature > 30).listen((data) => console.log("High temperature alert:", data));
555
+
556
+ // Data logging
557
+ sensorData.map((data) => ({ ...data, timestamp: Date.now() })).listen((data) => saveToDatabase(data));
558
+
559
+ // Simulate sensor readings
560
+ setInterval(() => {
561
+ sensorData.push({
562
+ temperature: Math.random() * 40,
563
+ humidity: Math.random() * 100,
564
+ });
565
+ }, 1000);
566
+ ```
567
+
568
+ ### Event Aggregation
569
+
570
+ Collect and batch user events for analytics:
571
+
572
+ ```typescript
573
+ const userActions = new Stream<{ userId: string; action: string }>();
574
+
575
+ // Group actions by time windows
576
+ const actionBatches = userActions.group(0, (count, action) => {
577
+ return count >= 10 ? [true, 0] : [false, count + 1];
578
+ });
579
+
580
+ actionBatches.listen((count) => {
581
+ console.log(`Processed ${count} actions`);
582
+ });
583
+ ```
584
+
585
+ ### WebSocket Integration
586
+
587
+ Create reactive streams from WebSocket connections:
588
+
589
+ ```typescript
590
+ const wsStream = new Stream<MessageEvent>(async function* () {
591
+ const ws = new WebSocket("wss://api.example.com");
592
+
593
+ while (ws.readyState !== WebSocket.CLOSED) {
594
+ yield await new Promise((resolve) => {
595
+ ws.onmessage = resolve;
596
+ });
597
+ }
598
+ });
599
+
600
+ wsStream
601
+ .map((event) => JSON.parse(event.data))
602
+ .filter((data) => data.type === "update")
603
+ .listen((update) => handleUpdate(update));
604
+ ```
605
+
606
+ ### Real-time Data Processing Pipeline
607
+
608
+ Build sophisticated data processing systems:
609
+
610
+ ```typescript
611
+ // Raw sensor data stream
612
+ const sensorData = new Stream<SensorReading>();
613
+
614
+ // Multi-stage processing pipeline
615
+ const processedData = sensorData
616
+ // 1. Validate data quality
617
+ .filter(async (reading) => {
618
+ return await validateSensorReading(reading);
619
+ })
620
+ // 2. Normalize and enrich
621
+ .map(async (reading) => {
622
+ const location = await getLocationData(reading.sensorId);
623
+ return {
624
+ ...reading,
625
+ location,
626
+ normalized: normalizeReading(reading.value),
627
+ };
628
+ })
629
+ // 3. Detect anomalies using sliding window
630
+ .group(
631
+ { readings: [], sum: 0 }, // sliding window state
632
+ (window, reading) => {
633
+ const newWindow = {
634
+ readings: [...window.readings, reading].slice(-10), // keep last 10
635
+ sum: window.sum + reading.normalized,
636
+ };
637
+ const average = newWindow.sum / newWindow.readings.length;
638
+ const isAnomaly = Math.abs(reading.normalized - average) > threshold;
639
+
640
+ return [isAnomaly, newWindow];
641
+ }
642
+ )
643
+ // 4. Batch anomalies for processing
644
+ .group((batch) => batch.length >= 5 || Date.now() - batch[0]?.timestamp > 30000);
645
+
646
+ // Multiple consumers for different purposes
647
+ processedData.listen((anomalies) => sendAlerts(anomalies));
648
+ processedData.listen((anomalies) => updateDashboard(anomalies));
649
+ processedData.listen((anomalies) => logToDatabase(anomalies));
650
+ ```
651
+
652
+ ### Cleanup and Memory Management
653
+
654
+ Properly clean up listeners and prevent memory leaks:
655
+
656
+ ```typescript
657
+ const stream = new Stream<number>();
658
+
659
+ // Automatic cleanup with AbortSignal
660
+ const controller = new AbortController();
661
+ stream.listen((value) => console.log(value), controller.signal);
662
+
663
+ // Clean up when component unmounts
664
+ setTimeout(() => controller.abort(), 5000);
665
+
666
+ // Or manual cleanup
667
+ const cleanup = stream.listen((value) => console.log(value));
668
+ setTimeout(cleanup, 5000);
669
+ ```
670
+
671
+ ## Installation
672
+
673
+ ```bash
674
+ bun add @soffinal/stream
675
+ ```
676
+
677
+ ```bash
678
+ npm install @soffinal/stream
679
+ ```
680
+
681
+ ## API Reference
682
+
683
+ ### Stream<T>
684
+
685
+ **Properties:**
686
+
687
+ - `hasListeners: boolean` - True if stream has active listeners
688
+ - `listenerAdded: Stream<void>` - Emits when listener is added
689
+ - `listenerRemoved: Stream<void>` - Emits when listener is removed
690
+
691
+ **Methods:**
692
+
693
+ - `push(...values: T[]): void` - Emit values to all listeners
694
+ - `listen(callback: (value: T) => void, signal?: AbortSignal): () => void` - Add listener, returns cleanup function
695
+ - `filter<U extends T>(predicate: (value: T) => value is U): Stream<U>` - Filter values with type guard
696
+ - `filter(predicate: (value: T) => boolean | Promise<boolean>): Stream<T>` - Filter values with predicate
697
+ - `filter<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): Stream<T>` - Stateful filtering
698
+ - `map<U>(mapper: (value: T) => U | Promise<U>): Stream<U>` - Transform values
699
+ - `map<S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): Stream<U>` - Stateful mapping
700
+ - `merge(...streams: Stream<T>[]): Stream<T>` - Combine multiple streams
701
+ - `group(predicate: (batch: T[]) => boolean): Stream<T[]>` - Group values into batches
702
+ - `group<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S]): Stream<T[]>` - Stateful grouping
703
+ - `flat<U>(depth?: number): Stream<U>` - Flatten array values (default depth: 0)
704
+ - `pipe<U>(transformer: (stream: Stream<T>) => Stream<U>): Stream<U>` - Apply functional transformer
705
+ - `then<U>(callback?: (value: T) => U): Promise<U>` - Promise-like interface for first value
706
+ - `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration support
707
+
708
+ ### Transformer Functions
709
+
710
+ **Built-in transformers for functional composition:**
711
+
712
+ - `map<T, U>(mapper: (value: T) => U | Promise<U>): (stream: Stream<T>) => Stream<U>` - Transform values
713
+ - `map<T, S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): (stream: Stream<T>) => Stream<U>` - Stateful mapping
714
+ - `filter<T, U extends T>(predicate: (value: T) => value is U): (stream: Stream<T>) => Stream<U>` - Type guard filtering
715
+ - `filter<T>(predicate: (value: T) => boolean | Promise<boolean>): (stream: Stream<T>) => Stream<T>` - Predicate filtering
716
+ - `filter<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): (stream: Stream<T>) => Stream<T>` - Stateful filtering
717
+ - `group<T>(predicate: (batch: T[]) => boolean): (stream: Stream<T>) => Stream<T[]>` - Group values into batches
718
+ - `group<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S]): (stream: Stream<T>) => Stream<T[]>` - Stateful grouping
719
+ - `merge<T>(...streams: Stream<T>[]): (stream: Stream<T>) => Stream<T>` - Merge multiple streams into one
720
+ - `flat<T, U>(depth?: number): (stream: Stream<T>) => Stream<U>` - Flatten array values with configurable depth
721
+
722
+ ### State<T> extends Stream<T>
723
+
724
+ **Properties:**
725
+
726
+ - `value: T` - Get/set current state value
727
+
728
+ **Methods:**
729
+
730
+ - `push(...values: T[]): void` - Update state with multiple values sequentially
731
+
732
+ ### List<T>
733
+
734
+ **Properties:**
735
+
736
+ - `length: number` - Current list length
737
+ - `insert: Stream<[number, T]>` - Emits on insertions
738
+ - `delete: Stream<[number, T]>` - Emits on deletions
739
+ - `clear: Stream<void>` - Emits on clear
740
+ - `[index]: T` - Index access with modulo wrapping
741
+
742
+ **Methods:**
743
+
744
+ - `insert(index: number, value: T): void` - Insert value at index
745
+ - `delete(index: number): T | undefined` - Delete value at index, returns deleted value
746
+ - `clear(): void` - Clear all items
747
+ - `get(index: number): T | undefined` - Get value without modulo wrapping
748
+ - `values(): IterableIterator<T>` - Iterator for values
749
+
750
+ ### Map<K,V> extends globalThis.Map<K,V>
751
+
752
+ **Stream Properties:**
753
+
754
+ - `set: Stream<[K, V]>` - Emits on set operation (only actual changes)
755
+ - `delete: Stream<[K, V]>` - Emits on successful deletions
756
+ - `clear: Stream<void>` - Emits on clear (only if not empty)
757
+
758
+ **Methods:**
759
+
760
+ - All native Map methods plus reactive stream properties
761
+
762
+ ### Set<T> extends globalThis.Set<T>
763
+
764
+ **Stream Properties:**
765
+
766
+ - `add: Stream<T>` - Emits on successful additions (no duplicates)
767
+ - `delete: Stream<T>` - Emits on successful deletions
768
+ - `clear: Stream<void>` - Emits on clear (only if not empty)
769
+
770
+ **Methods:**
771
+
772
+ - All native Set methods plus reactive stream properties
773
+
774
+ MIT License
775
+
776
+ Copyright (c) 2024 Soffinal <https://github.com/soffinal> <smari.sofiane@gmail.com>