@soffinal/stream 0.1.2 → 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.
Files changed (2) hide show
  1. package/README.md +310 -627
  2. package/package.json +1 -1
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>();
45
+ import { Stream, State } from "@soffinal/stream";
56
46
 
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
- ```
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
- );
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
- ```
66
+ ## Installation
114
67
 
115
- #### Map: Data Transformation Pipeline
68
+ ### Package Managers
116
69
 
117
- Mapping transforms data while maintaining the stream's async nature:
70
+ ```bash
71
+ # npm
72
+ npm install @soffinal/stream
118
73
 
119
- ```typescript
120
- const rawEvents = new Stream<string>();
74
+ # yarn
75
+ yarn add @soffinal/stream
121
76
 
122
- // Simple transformation
123
- const parsedEvents = rawEvents.map((json) => JSON.parse(json));
77
+ # pnpm
78
+ pnpm add @soffinal/stream
124
79
 
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
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);
87
+ ### CDN (Browser)
146
88
 
147
- merged.listen((msg) => console.log("Merged:", msg));
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>
148
94
 
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
102
+ ## Core Concepts
154
103
 
155
- Grouping allows you to batch data based on complex conditions:
104
+ ### Streams: Async Data Pipelines
156
105
 
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:
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
111
+ // Multiple consumers
112
+ userEvents.listen((event) => analytics.track(event));
113
+ userEvents.listen((event) => notifications.send(event));
114
+ userEvents.listen((event) => database.save(event));
189
115
 
190
- // Deep flattening
191
- const nested = new Stream<number[][][]>();
192
- const deepFlat = nested.flat(2);
116
+ // Or await the next event
117
+ const nextEvent = await userEvents;
193
118
  ```
194
119
 
195
- #### Promise Interface
120
+ ### Transformers: Fluent & Functional Styles
196
121
 
197
- Streams are directly awaitable for the next event/data:
122
+ #### Method Chaining (Fluent)
198
123
 
199
124
  ```typescript
200
125
  const stream = new Stream<number>();
201
126
 
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
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:
221
+ ### Reactive State
331
222
 
332
- #### Object-Oriented Style (Method Chaining)
223
+ State objects hold current values and notify dependents of changes:
333
224
 
334
225
  ```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
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";
237
+ document.body.classList.toggle("authenticated", loggedIn);
386
238
  });
387
239
  ```
388
240
 
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
- });
241
+ ### Reactive Collections
403
242
 
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:
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,214 +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>();
355
+ interface AppState {
356
+ user: User | null;
357
+ theme: "light" | "dark";
358
+ notifications: Notification[];
359
+ }
658
360
 
659
- // Automatic cleanup with AbortSignal
660
- const controller = new AbortController();
661
- stream.listen((value) => console.log(value), controller.signal);
361
+ const appState = new State<AppState>({
362
+ user: null,
363
+ theme: "light",
364
+ notifications: [],
365
+ });
662
366
 
663
- // Clean up when component unmounts
664
- setTimeout(() => controller.abort(), 5000);
367
+ // Derived state
368
+ const unreadCount = appState.map((state) => state.notifications.filter((n) => !n.read).length);
665
369
 
666
- // Or manual cleanup
667
- const cleanup = stream.listen((value) => console.log(value));
668
- setTimeout(cleanup, 5000);
370
+ // Reactive UI
371
+ unreadCount.listen((count) => {
372
+ document.title = count > 0 ? `(${count}) App` : "App";
373
+ });
669
374
  ```
670
375
 
671
- ## Installation
672
-
673
- # bun
674
-
675
- ```bash
676
- bun add @soffinal/stream
677
- # or from JSR
678
- bunx jsr add @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
- # Deno
406
+ ## Performance
682
407
 
683
- ```bash
684
- deno add npm:@soffinal/stream
685
- # or from JSR
686
- deno add jsr:@soffinal/stream
408
+ @soffinal/stream delivers exceptional performance:
687
409
 
688
- ```
410
+ - **High throughput** for basic operations
411
+ - **Efficient processing** for complex pipelines
412
+ - **Memory efficient** with automatic cleanup
413
+ - **Zero overhead** for unused features
689
414
 
690
- # npm
415
+ ### Performance Characteristics
691
416
 
692
- ```bash
693
- npm install @soffinal/stream
694
- # or from JSR
695
- npx jsr add @soffinal/stream
696
- ```
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
697
421
 
698
- # pnpm
422
+ ## Browser Support
699
423
 
700
- ```bash
701
- pnpm install @soffinal/stream
702
- # or from JSR
703
- pnpm install jsr:@soffinal/stream
424
+ - **Modern browsers** supporting ES2020+
425
+ - **Node.js** 16+
426
+ - **Deno** 1.0+
427
+ - **Bun** 1.0+
428
+ - **Cloudflare Workers**
704
429
 
705
- ```
430
+ ## Migration Guide
706
431
 
707
- # yarn
432
+ ### From RxJS
708
433
 
709
- ```bash
710
- yarn add @soffinal/stream
711
- yarn add jsr:@soffinal/stream
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);
712
444
 
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);
713
452
  ```
714
453
 
715
- ## API Reference
716
-
717
- ### Stream<T>
718
-
719
- **Properties:**
720
-
721
- - `hasListeners: boolean` - True if stream has active listeners
722
- - `listenerAdded: Stream<void>` - Emits when listener is added
723
- - `listenerRemoved: Stream<void>` - Emits when listener is removed
724
-
725
- **Methods:**
726
-
727
- - `push(...values: T[]): void` - Emit values to all listeners
728
- - `listen(callback: (value: T) => void, signal?: AbortSignal): () => void` - Add listener, returns cleanup function
729
- - `filter<U extends T>(predicate: (value: T) => value is U): Stream<U>` - Filter values with type guard
730
- - `filter(predicate: (value: T) => boolean | Promise<boolean>): Stream<T>` - Filter values with predicate
731
- - `filter<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): Stream<T>` - Stateful filtering
732
- - `map<U>(mapper: (value: T) => U | Promise<U>): Stream<U>` - Transform values
733
- - `map<S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): Stream<U>` - Stateful mapping
734
- - `merge(...streams: Stream<T>[]): Stream<T>` - Combine multiple streams
735
- - `group(predicate: (batch: T[]) => boolean): Stream<T[]>` - Group values into batches
736
- - `group<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S]): Stream<T[]>` - Stateful grouping
737
- - `flat<U>(depth?: number): Stream<U>` - Flatten array values (default depth: 0)
738
- - `pipe<U>(transformer: (stream: Stream<T>) => Stream<U>): Stream<U>` - Apply functional transformer
739
- - `then<U>(callback?: (value: T) => U): Promise<U>` - Promise-like interface for first value
740
- - `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration support
741
-
742
- ### Transformer Functions
743
-
744
- **Built-in transformers for functional composition:**
745
-
746
- - `map<T, U>(mapper: (value: T) => U | Promise<U>): (stream: Stream<T>) => Stream<U>` - Transform values
747
- - `map<T, S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): (stream: Stream<T>) => Stream<U>` - Stateful mapping
748
- - `filter<T, U extends T>(predicate: (value: T) => value is U): (stream: Stream<T>) => Stream<U>` - Type guard filtering
749
- - `filter<T>(predicate: (value: T) => boolean | Promise<boolean>): (stream: Stream<T>) => Stream<T>` - Predicate filtering
750
- - `filter<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): (stream: Stream<T>) => Stream<T>` - Stateful filtering
751
- - `group<T>(predicate: (batch: T[]) => boolean): (stream: Stream<T>) => Stream<T[]>` - Group values into batches
752
- - `group<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S]): (stream: Stream<T>) => Stream<T[]>` - Stateful grouping
753
- - `merge<T>(...streams: Stream<T>[]): (stream: Stream<T>) => Stream<T>` - Merge multiple streams into one
754
- - `flat<T, U>(depth?: number): (stream: Stream<T>) => Stream<U>` - Flatten array values with configurable depth
454
+ ### From EventEmitter
755
455
 
756
- ### State<T> extends Stream<T>
757
-
758
- **Properties:**
759
-
760
- - `value: T` - Get/set current state value
761
-
762
- **Methods:**
763
-
764
- - `push(...values: T[]): void` - Update state with multiple values sequentially
765
-
766
- ### List<T>
767
-
768
- **Properties:**
769
-
770
- - `length: number` - Current list length
771
- - `insert: Stream<[number, T]>` - Emits on insertions
772
- - `delete: Stream<[number, T]>` - Emits on deletions
773
- - `clear: Stream<void>` - Emits on clear
774
- - `[index]: T` - Index access with modulo wrapping
775
-
776
- **Methods:**
777
-
778
- - `insert(index: number, value: T): void` - Insert value at index
779
- - `delete(index: number): T | undefined` - Delete value at index, returns deleted value
780
- - `clear(): void` - Clear all items
781
- - `get(index: number): T | undefined` - Get value without modulo wrapping
782
- - `values(): IterableIterator<T>` - Iterator for values
783
-
784
- ### Map<K,V> extends globalThis.Map<K,V>
785
-
786
- **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");
787
462
 
788
- - `set: Stream<[K, V]>` - Emits on set operation (only actual changes)
789
- - `delete: Stream<[K, V]>` - Emits on successful deletions
790
- - `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
+ ```
791
469
 
792
- **Methods:**
470
+ ## Contributing
793
471
 
794
- - All native Map methods plus reactive stream properties
472
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
795
473
 
796
- ### Set<T> extends globalThis.Set<T>
474
+ ### Development Setup
797
475
 
798
- **Stream Properties:**
476
+ ```bash
477
+ git clone https://github.com/soffinal/stream.git
478
+ cd stream
479
+ bun install
480
+ bun test
481
+ ```
799
482
 
800
- - `add: Stream<T>` - Emits on successful additions (no duplicates)
801
- - `delete: Stream<T>` - Emits on successful deletions
802
- - `clear: Stream<void>` - Emits on clear (only if not empty)
483
+ ## License
803
484
 
804
- **Methods:**
485
+ MIT © [Soffinal](https://github.com/soffinal)
805
486
 
806
- - All native Set methods plus reactive stream properties
487
+ Contact: <smari.sofiane@gmail.com>
807
488
 
808
- MIT License
489
+ ---
809
490
 
810
- 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>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@soffinal/stream",
3
3
  "module": "./dist/index.js",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "description": "A reactive event streaming library for TypeScript/JavaScript",
6
6
  "type": "module",
7
7
  "devDependencies": {