@soffinal/stream 0.1.0 → 0.1.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 CHANGED
@@ -1,290 +1,183 @@
1
1
  # @soffinal/stream
2
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:
3
+ [![npm version](https://badge.fury.io/js/@soffinal%2Fstream.svg)](https://badge.fury.io/js/@soffinal%2Fstream)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@soffinal/stream)](https://bundlephobia.com/package/@soffinal/stream)
7
+
8
+ > **High-performance reactive streaming library for TypeScript/JavaScript**
9
+
10
+ A modern, async-first streaming library that treats asynchronous data flow as a first-class citizen. Built for real-time applications, event-driven architectures, and reactive programming patterns.
11
+
12
+ ## Table of Contents
13
+
14
+ - [Features](#features)
15
+ - [Quick Start](#quick-start)
16
+ - [Installation](#installation)
17
+ - [Core Concepts](#core-concepts)
18
+ - [API Reference](#api-reference)
19
+ - [Examples](#examples)
20
+ - [Performance](#performance)
21
+ - [Browser Support](#browser-support)
22
+ - [Migration Guide](#migration-guide)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
25
+
26
+ ## Features
27
+
28
+ - 🚀 **High Performance** - Optimized for speed and efficiency
29
+ - 🔄 **Multicast by Default** - One stream, many consumers
30
+ - ⏳ **Awaitable Streams** - `await stream` for next value
31
+ - 🔁 **Async Iterable** - Use `for await` loops naturally
32
+ - 🧠 **Smart Caching** - Operations run once, results shared
33
+ - 🗑️ **Auto Cleanup** - Memory management handled automatically
34
+ - 🔧 **Dual Programming Paradigms** - Method chaining or functional pipe composition
35
+ - 🛠️ **Custom Transformers** - Create reusable transformers in both OOP and functional styles
36
+ - 📊 **Reactive Collections** - Lists, Maps, Sets with fine-grained change events
37
+ - 🔀 **Stateful Operations** - Built-in support for stateful filtering, mapping, and grouping
38
+ - 📦 **Zero Dependencies** - Lightweight (~6KB minified, ~2KB gzipped) and tree-shakeable
39
+ - 🌐 **Universal** - Node.js, browsers, Deno, Bun, Cloudflare Workers
40
+ - 📘 **Full TypeScript** - Complete type safety and inference
41
+
42
+ ## Quick Start
53
43
 
54
44
  ```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
- })();
45
+ import { Stream, State } from "@soffinal/stream";
64
46
 
65
- messageStream.push("user-login", "data-sync", "shutdown");
66
- ```
47
+ // Create reactive data streams
48
+ const events = new Stream<string>();
49
+ const counter = new State(0);
67
50
 
68
- ### Generator-based Streams: Infinite Data Sources
51
+ // Transform and filter data
52
+ const processed = events.filter((msg) => msg.length > 3).map((msg) => msg.toUpperCase());
69
53
 
70
- Create infinite, lazy data sources using async generators:
54
+ // Multiple consumers
55
+ processed.listen((msg) => console.log("Logger:", msg));
56
+ processed.listen((msg) => updateUI(msg));
71
57
 
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
- });
58
+ // Push data through the pipeline
59
+ events.push("hello", "hi", "world"); // Only 'HELLO' and 'WORLD' processed
81
60
 
82
- sensorStream.listen((data) => console.log("Sensor reading:", data));
61
+ // Reactive state management
62
+ counter.listen((count) => (document.title = `Count: ${count}`));
63
+ counter.value++; // UI updates automatically
83
64
  ```
84
65
 
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
- );
66
+ ## Installation
107
67
 
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
- ```
68
+ ### Package Managers
114
69
 
115
- #### Map: Data Transformation Pipeline
70
+ ```bash
71
+ # npm
72
+ npm install @soffinal/stream
116
73
 
117
- Mapping transforms data while maintaining the stream's async nature:
74
+ # yarn
75
+ yarn add @soffinal/stream
118
76
 
119
- ```typescript
120
- const rawEvents = new Stream<string>();
77
+ # pnpm
78
+ pnpm add @soffinal/stream
121
79
 
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
- );
80
+ # bun
81
+ bun add @soffinal/stream
130
82
 
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
- });
83
+ # Deno
84
+ deno add jsr:@soffinal/stream
136
85
  ```
137
86
 
138
- #### Merge: Combine Multiple Streams
87
+ ### CDN (Browser)
139
88
 
140
- Combine multiple streams into a single output stream:
89
+ ```html
90
+ <!-- Production (minified) -->
91
+ <script type="module">
92
+ import { Stream, State } from "https://cdn.jsdelivr.net/npm/@soffinal/stream@latest/dist/index.js";
93
+ </script>
141
94
 
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");
95
+ <!-- Alternative CDNs -->
96
+ <script type="module">
97
+ import { Stream } from "https://esm.sh/@soffinal/stream";
98
+ import { Stream } from "https://cdn.skypack.dev/@soffinal/stream";
99
+ </script>
151
100
  ```
152
101
 
153
- #### Group: Intelligent Batching
154
-
155
- Grouping allows you to batch data based on complex conditions:
102
+ ## Core Concepts
156
103
 
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
104
+ ### Streams: Async Data Pipelines
179
105
 
180
- Flatten nested arrays in stream values:
106
+ A `Stream` is an async iterable that can push values to multiple listeners while also being awaitable for the next value.
181
107
 
182
108
  ```typescript
183
- const arrays = new Stream<number[]>();
184
- const flattened = arrays.flat();
109
+ const userEvents = new Stream<UserEvent>();
185
110
 
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>();
111
+ // Multiple consumers
112
+ userEvents.listen((event) => analytics.track(event));
113
+ userEvents.listen((event) => notifications.send(event));
114
+ userEvents.listen((event) => database.save(event));
201
115
 
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
- })();
116
+ // Or await the next event
117
+ const nextEvent = await userEvents;
213
118
  ```
214
119
 
215
- #### Listener Events
120
+ ### Transformers: Fluent & Functional Styles
216
121
 
217
- React to listener lifecycle changes on streams:
122
+ #### Method Chaining (Fluent)
218
123
 
219
124
  ```typescript
220
125
  const stream = new Stream<number>();
221
126
 
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
127
+ // Simple transformations
128
+ const result = stream
129
+ .filter((x) => x > 0)
130
+ .map((x) => x * 2)
131
+ .group((batch) => batch.length >= 5);
249
132
 
250
- result.listen((value) => console.log("Processed:", value));
133
+ // Stateful operations with initial state
134
+ const stateful = stream
135
+ .filter(0, (prev, curr) => [curr > prev, Math.max(prev, curr)]) // Only increasing values
136
+ .map([], (history, value) => {
137
+ const newHistory = [...history, value].slice(-3); // Keep last 3
138
+ const average = newHistory.reduce((a, b) => a + b, 0) / newHistory.length;
139
+ return [{ value, average, history: newHistory }, newHistory];
140
+ })
141
+ .group(5, (count, item) => [count >= 5, count >= 5 ? 0 : count + 1]); // Batch every 5
251
142
  ```
252
143
 
253
- ### Built-in Transformer Functions
254
-
255
- The library provides irreducible building block transformers that work seamlessly with pipe:
144
+ #### Functional Composition (Pipe)
256
145
 
257
146
  ```typescript
258
- // Import transformer functions
259
- import { map, filter, group, merge, flat } from "@soffinal/stream";
260
-
261
- const stream = new Stream<number>();
147
+ import { filter, map, group, merge } from "@soffinal/stream";
262
148
 
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>
149
+ // Simple functional composition
150
+ const result = stream
151
+ .pipe(filter((x) => x > 0))
152
+ .pipe(map((x) => x * 2))
153
+ .pipe(group((batch) => batch.length >= 5));
268
154
 
269
- // Stateful transformations
270
- stream
155
+ // Stateful functional transformers
156
+ const advanced = stream
271
157
  .pipe(filter(0, (prev, curr) => [curr > prev, Math.max(prev, curr)]))
272
- .pipe(map(0, (sum, n) => [sum + n, sum + n]));
158
+ .pipe(
159
+ map([], (acc, value) => {
160
+ const newAcc = [...acc, value].slice(-10);
161
+ const sum = newAcc.reduce((a, b) => a + b, 0);
162
+ return [{ value, sum, count: newAcc.length }, newAcc];
163
+ })
164
+ )
165
+ .pipe(group(0, (count, item) => [count >= 3, count >= 3 ? 0 : count + 1]));
273
166
 
274
- // Advanced transformations
167
+ // Combining multiple streams
275
168
  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
169
+ const merged = stream
170
+ .pipe(filter((x) => x % 2 === 0))
171
+ .pipe(merge(stream2))
172
+ .pipe(map((x) => x.toString()));
280
173
  ```
281
174
 
282
- ### Custom Transformers: Building Your Own
175
+ ### Custom Transformers
283
176
 
284
- You can create custom transformers using generator functions:
177
+ Create reusable transformers for both paradigms:
285
178
 
286
179
  ```typescript
287
- // DistinctUntilChanged transformer - only emit when value changes
180
+ // Custom transformer: distinctUntilChanged
288
181
  const distinctUntilChanged = <T>(stream: Stream<T>): Stream<T> => {
289
182
  return new Stream<T>(async function* () {
290
183
  let prev: T;
@@ -300,7 +193,7 @@ const distinctUntilChanged = <T>(stream: Stream<T>): Stream<T> => {
300
193
  });
301
194
  };
302
195
 
303
- // Rate limiting transformer
196
+ // Custom transformer: throttle
304
197
  const throttle =
305
198
  <T>(ms: number) =>
306
199
  (stream: Stream<T>): Stream<T> => {
@@ -320,272 +213,126 @@ const throttle =
320
213
  // Usage with perfect type inference
321
214
  const searchInput = new Stream<string>();
322
215
  const searchResults = searchInput
323
- .pipe(distinctUntilChanged) // Only emit when search term changes
324
- .pipe(throttle(1000)) // Limit API calls
216
+ .pipe(distinctUntilChanged)
217
+ .pipe(throttle(300))
325
218
  .pipe(map((query) => searchAPI(query)));
326
219
  ```
327
220
 
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
221
+ ### Reactive State
364
222
 
365
- State objects are streams that hold current values, enabling reactive programming patterns:
223
+ State objects hold current values and notify dependents of changes:
366
224
 
367
225
  ```typescript
368
- import { State } from "@soffinal/stream";
369
-
370
- // Application state
371
226
  const user = new State<User | null>(null);
372
227
  const theme = new State<"light" | "dark">("light");
373
- const notifications = new State<Notification[]>([]);
374
228
 
375
- // Reactive computations
229
+ // Derived state with transformations
376
230
  const isLoggedIn = user.map((u) => u !== null);
377
- const unreadCount = notifications.map((n) => n.filter((x) => !x.read).length);
231
+ const userProfile = user
232
+ .filter((u): u is User => u !== null)
233
+ .map((u) => ({ ...u, displayName: u.firstName + " " + u.lastName }));
378
234
 
379
235
  // Automatic UI updates
380
236
  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();
237
+ document.body.classList.toggle("authenticated", loggedIn);
453
238
  });
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
239
  ```
469
240
 
470
- ### Map: Reactive Key-Value Store
241
+ ### Reactive Collections
471
242
 
472
- Key-value store that emits events on changes:
243
+ Collections that emit fine-grained change events:
473
244
 
474
245
  ```typescript
475
- import { Map } from "@soffinal/stream";
476
-
246
+ const todos = new List<Todo>();
477
247
  const cache = new Map<string, any>();
248
+ const activeUsers = new Set<string>();
478
249
 
479
- // Listen to cache updates (only emits on actual changes)
480
- cache.set.listen(([key, value]) => {
481
- console.log(`Cache updated: ${key} = ${value}`);
482
- });
250
+ // React to changes
251
+ todos.insert.listen(([index, todo]) => renderTodo(todo, index));
252
+ cache.set.listen(([key, value]) => console.log(`Cached: ${key}`));
253
+ activeUsers.add.listen((userId) => showOnlineStatus(userId));
254
+ ```
483
255
 
484
- // Listen to cache evictions
485
- cache.delete.listen(([key, value]) => {
486
- console.log(`Cache evicted: ${key}`);
487
- });
256
+ ## API Reference
488
257
 
489
- // Listen to clear events
490
- cache.clear.listen(() => console.log("Cache cleared"));
258
+ ### Stream\<T>
491
259
 
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)
260
+ #### Properties
496
261
 
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
- ```
262
+ - `hasListeners: boolean` - Whether stream has active listeners
263
+ - `listenerAdded: Stream<void>` - Emits when listener is added
264
+ - `listenerRemoved: Stream<void>` - Emits when listener is removed
504
265
 
505
- ### Set: Reactive Unique Collections
266
+ #### Methods
506
267
 
507
- Unique value collection that emits events on changes:
268
+ - `push(...values: T[]): void` - Emit values to all listeners
269
+ - `listen(callback: (value: T) => void, signal?: AbortSignal): () => void` - Add listener
270
+ - `filter(predicate: (value: T) => boolean): Stream<T>` - Filter values
271
+ - `map<U>(mapper: (value: T) => U): Stream<U>` - Transform values
272
+ - `merge(...streams: Stream<T>[]): Stream<T>` - Combine streams
273
+ - `group(predicate: (batch: T[]) => boolean): Stream<T[]>` - Batch values
274
+ - `flat(depth?: number): Stream<U>` - Flatten arrays
275
+ - `pipe<U>(transformer: (stream: Stream<T>) => Stream<U>): Stream<U>` - Apply transformer
508
276
 
509
- ```typescript
510
- import { Set } from "@soffinal/stream";
277
+ #### Async Interface
511
278
 
512
- const activeUsers = new Set<string>();
279
+ - `then<U>(callback?: (value: T) => U): Promise<U>` - Promise interface
280
+ - `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration
513
281
 
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
- });
282
+ ### State\<T> extends Stream\<T>
519
283
 
520
- // Listen to deletions
521
- activeUsers.delete.listen((userId) => {
522
- console.log(`User ${userId} went offline`);
523
- broadcastUserStatus(userId, "offline");
524
- });
284
+ #### Properties
525
285
 
526
- // Listen to clear events
527
- activeUsers.clear.listen(() => console.log("All users cleared"));
286
+ - `value: T` - Current state value (get/set)
528
287
 
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)
288
+ ### Transformer Functions
533
289
 
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
- ```
290
+ Functional transformers for use with `pipe()`:
541
291
 
542
- ## Use Cases
292
+ - `filter<T>(predicate: (value: T) => boolean): (stream: Stream<T>) => Stream<T>`
293
+ - `map<T, U>(mapper: (value: T) => U): (stream: Stream<T>) => Stream<U>`
294
+ - `group<T>(predicate: (batch: T[]) => boolean): (stream: Stream<T>) => Stream<T[]>`
295
+ - `merge<T>(...streams: Stream<T>[]): (stream: Stream<T>) => Stream<T>`
296
+ - `flat<T>(depth?: number): (stream: Stream<T>) => Stream<U>`
543
297
 
544
- Practical examples of building reactive applications.
298
+ ### Reactive Collections
545
299
 
546
- ### Real-time Data Processing
300
+ #### List\<T>
547
301
 
548
- Process sensor data with filtering and transformation:
302
+ - `insert: Stream<[number, T]>` - Insertion events
303
+ - `delete: Stream<[number, T]>` - Deletion events
304
+ - `clear: Stream<void>` - Clear events
549
305
 
550
- ```typescript
551
- const sensorData = new Stream<{ temperature: number; humidity: number }>();
306
+ #### Map\<K,V> extends globalThis.Map\<K,V>
552
307
 
553
- // Alert system
554
- sensorData.filter((data) => data.temperature > 30).listen((data) => console.log("High temperature alert:", data));
308
+ - `set: Stream<[K, V]>` - Set events (only on changes)
309
+ - `delete: Stream<[K, V]>` - Delete events
310
+ - `clear: Stream<void>` - Clear events
555
311
 
556
- // Data logging
557
- sensorData.map((data) => ({ ...data, timestamp: Date.now() })).listen((data) => saveToDatabase(data));
312
+ #### Set\<T> extends globalThis.Set\<T>
558
313
 
559
- // Simulate sensor readings
560
- setInterval(() => {
561
- sensorData.push({
562
- temperature: Math.random() * 40,
563
- humidity: Math.random() * 100,
564
- });
565
- }, 1000);
566
- ```
314
+ - `add: Stream<T>` - Add events (only new values)
315
+ - `delete: Stream<T>` - Delete events
316
+ - `clear: Stream<void>` - Clear events
567
317
 
568
- ### Event Aggregation
318
+ ## Examples
569
319
 
570
- Collect and batch user events for analytics:
320
+ ### Real-time Data Processing
571
321
 
572
322
  ```typescript
573
- const userActions = new Stream<{ userId: string; action: string }>();
323
+ const sensorData = new Stream<SensorReading>();
574
324
 
575
- // Group actions by time windows
576
- const actionBatches = userActions.group(0, (count, action) => {
577
- return count >= 10 ? [true, 0] : [false, count + 1];
578
- });
325
+ // Multi-stage processing pipeline
326
+ const alerts = sensorData
327
+ .filter((reading) => reading.temperature > 30)
328
+ .map((reading) => ({ ...reading, timestamp: Date.now() }))
329
+ .group((batch) => batch.length >= 5);
579
330
 
580
- actionBatches.listen((count) => {
581
- console.log(`Processed ${count} actions`);
582
- });
331
+ alerts.listen((batch) => sendAlertNotification(batch));
583
332
  ```
584
333
 
585
334
  ### WebSocket Integration
586
335
 
587
- Create reactive streams from WebSocket connections:
588
-
589
336
  ```typescript
590
337
  const wsStream = new Stream<MessageEvent>(async function* () {
591
338
  const ws = new WebSocket("wss://api.example.com");
@@ -597,180 +344,150 @@ const wsStream = new Stream<MessageEvent>(async function* () {
597
344
  }
598
345
  });
599
346
 
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);
347
+ const messages = wsStream.map((event) => JSON.parse(event.data)).filter((data) => data.type === "update");
645
348
 
646
- // Multiple consumers for different purposes
647
- processedData.listen((anomalies) => sendAlerts(anomalies));
648
- processedData.listen((anomalies) => updateDashboard(anomalies));
649
- processedData.listen((anomalies) => logToDatabase(anomalies));
349
+ messages.listen((update) => handleUpdate(update));
650
350
  ```
651
351
 
652
- ### Cleanup and Memory Management
653
-
654
- Properly clean up listeners and prevent memory leaks:
352
+ ### State Management
655
353
 
656
354
  ```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);
355
+ interface AppState {
356
+ user: User | null;
357
+ theme: "light" | "dark";
358
+ notifications: Notification[];
359
+ }
665
360
 
666
- // Or manual cleanup
667
- const cleanup = stream.listen((value) => console.log(value));
668
- setTimeout(cleanup, 5000);
669
- ```
361
+ const appState = new State<AppState>({
362
+ user: null,
363
+ theme: "light",
364
+ notifications: [],
365
+ });
670
366
 
671
- ## Installation
367
+ // Derived state
368
+ const unreadCount = appState.map((state) => state.notifications.filter((n) => !n.read).length);
672
369
 
673
- ```bash
674
- bun add @soffinal/stream
370
+ // Reactive UI
371
+ unreadCount.listen((count) => {
372
+ document.title = count > 0 ? `(${count}) App` : "App";
373
+ });
675
374
  ```
676
375
 
677
- ```bash
678
- npm install @soffinal/stream
376
+ ### Browser Counter Example
377
+
378
+ ```html
379
+ <!DOCTYPE html>
380
+ <html>
381
+ <head>
382
+ <title>Reactive Counter</title>
383
+ </head>
384
+ <body>
385
+ <div id="counter">0</div>
386
+ <button id="increment">+</button>
387
+ <button id="decrement">-</button>
388
+
389
+ <script type="module">
390
+ import { State } from "https://esm.sh/@soffinal/stream";
391
+
392
+ const count = new State(0);
393
+ const display = document.getElementById("counter");
394
+
395
+ // Reactive UI updates
396
+ count.listen((value) => (display.textContent = value));
397
+
398
+ // User interactions
399
+ document.getElementById("increment").onclick = () => count.value++;
400
+ document.getElementById("decrement").onclick = () => count.value--;
401
+ </script>
402
+ </body>
403
+ </html>
679
404
  ```
680
405
 
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:**
406
+ ## Performance
711
407
 
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
408
+ @soffinal/stream delivers exceptional performance:
721
409
 
722
- ### State<T> extends Stream<T>
410
+ - **High throughput** for basic operations
411
+ - **Efficient processing** for complex pipelines
412
+ - **Memory efficient** with automatic cleanup
413
+ - **Zero overhead** for unused features
723
414
 
724
- **Properties:**
415
+ ### Performance Characteristics
725
416
 
726
- - `value: T` - Get/set current state value
417
+ - **Optimized data structures** for minimal memory allocation
418
+ - **Efficient event dispatch** with smart caching
419
+ - **Lazy evaluation** reduces unnecessary computations
420
+ - **Automatic cleanup** prevents memory leaks
727
421
 
728
- **Methods:**
422
+ ## Browser Support
729
423
 
730
- - `push(...values: T[]): void` - Update state with multiple values sequentially
424
+ - **Modern browsers** supporting ES2020+
425
+ - **Node.js** 16+
426
+ - **Deno** 1.0+
427
+ - **Bun** 1.0+
428
+ - **Cloudflare Workers**
731
429
 
732
- ### List<T>
430
+ ## Migration Guide
733
431
 
734
- **Properties:**
432
+ ### From RxJS
735
433
 
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:**
434
+ ```typescript
435
+ // RxJS
436
+ import { Subject, map, filter } from "rxjs";
437
+ const subject = new Subject();
438
+ subject
439
+ .pipe(
440
+ filter((x) => x > 0),
441
+ map((x) => x * 2)
442
+ )
443
+ .subscribe(console.log);
743
444
 
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
445
+ // @soffinal/stream
446
+ import { Stream } from "@soffinal/stream";
447
+ const stream = new Stream();
448
+ stream
449
+ .filter((x) => x > 0)
450
+ .map((x) => x * 2)
451
+ .listen(console.log);
452
+ ```
749
453
 
750
- ### Map<K,V> extends globalThis.Map<K,V>
454
+ ### From EventEmitter
751
455
 
752
- **Stream Properties:**
456
+ ```typescript
457
+ // EventEmitter
458
+ import { EventEmitter } from "events";
459
+ const emitter = new EventEmitter();
460
+ emitter.on("data", console.log);
461
+ emitter.emit("data", "hello");
753
462
 
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)
463
+ // @soffinal/stream
464
+ import { Stream } from "@soffinal/stream";
465
+ const stream = new Stream();
466
+ stream.listen(console.log);
467
+ stream.push("hello");
468
+ ```
757
469
 
758
- **Methods:**
470
+ ## Contributing
759
471
 
760
- - All native Map methods plus reactive stream properties
472
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
761
473
 
762
- ### Set<T> extends globalThis.Set<T>
474
+ ### Development Setup
763
475
 
764
- **Stream Properties:**
476
+ ```bash
477
+ git clone https://github.com/soffinal/stream.git
478
+ cd stream
479
+ bun install
480
+ bun test
481
+ ```
765
482
 
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)
483
+ ## License
769
484
 
770
- **Methods:**
485
+ MIT © [Soffinal](https://github.com/soffinal)
771
486
 
772
- - All native Set methods plus reactive stream properties
487
+ Contact: <smari.sofiane@gmail.com>
773
488
 
774
- MIT License
489
+ ---
775
490
 
776
- Copyright (c) 2024 Soffinal <https://github.com/soffinal> <smari.sofiane@gmail.com>
491
+ <div align="center">
492
+ <strong>Built with ❤️ for the JavaScript community</strong>
493
+ </div>