@soffinal/stream 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +135 -637
  2. package/dist/index.d.ts +0 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/index.js.map +10 -12
  6. package/dist/stream.d.ts +25 -6
  7. package/dist/stream.d.ts.map +1 -1
  8. package/dist/transformers/filter/filter.d.ts +18 -0
  9. package/dist/transformers/filter/filter.d.ts.map +1 -0
  10. package/dist/transformers/filter/index.d.ts +2 -0
  11. package/dist/transformers/filter/index.d.ts.map +1 -0
  12. package/dist/transformers/flat/flat.d.ts +3 -0
  13. package/dist/transformers/flat/flat.d.ts.map +1 -0
  14. package/dist/transformers/flat/index.d.ts +2 -0
  15. package/dist/transformers/flat/index.d.ts.map +1 -0
  16. package/dist/transformers/gate/gate.d.ts +43 -0
  17. package/dist/transformers/gate/gate.d.ts.map +1 -0
  18. package/dist/transformers/gate/index.d.ts +2 -0
  19. package/dist/transformers/gate/index.d.ts.map +1 -0
  20. package/dist/transformers/index.d.ts +6 -4
  21. package/dist/transformers/index.d.ts.map +1 -1
  22. package/dist/transformers/map/index.d.ts +2 -0
  23. package/dist/transformers/map/index.d.ts.map +1 -0
  24. package/dist/transformers/map/map.d.ts +14 -0
  25. package/dist/transformers/map/map.d.ts.map +1 -0
  26. package/dist/transformers/merge/index.d.ts +2 -0
  27. package/dist/transformers/merge/index.d.ts.map +1 -0
  28. package/dist/transformers/merge/merge.d.ts +3 -0
  29. package/dist/transformers/merge/merge.d.ts.map +1 -0
  30. package/dist/transformers/state/index.d.ts +2 -0
  31. package/dist/transformers/state/index.d.ts.map +1 -0
  32. package/dist/transformers/state/state.d.ts +35 -0
  33. package/dist/transformers/state/state.d.ts.map +1 -0
  34. package/package.json +15 -17
  35. package/dist/reactive/index.d.ts +0 -5
  36. package/dist/reactive/index.d.ts.map +0 -1
  37. package/dist/reactive/list.d.ts +0 -171
  38. package/dist/reactive/list.d.ts.map +0 -1
  39. package/dist/reactive/map.d.ts +0 -107
  40. package/dist/reactive/map.d.ts.map +0 -1
  41. package/dist/reactive/set.d.ts +0 -102
  42. package/dist/reactive/set.d.ts.map +0 -1
  43. package/dist/reactive/state.d.ts +0 -79
  44. package/dist/reactive/state.d.ts.map +0 -1
  45. package/dist/transformers/filter.d.ts +0 -96
  46. package/dist/transformers/filter.d.ts.map +0 -1
  47. package/dist/transformers/flat.d.ts +0 -31
  48. package/dist/transformers/flat.d.ts.map +0 -1
  49. package/dist/transformers/map.d.ts +0 -106
  50. package/dist/transformers/map.d.ts.map +0 -1
  51. package/dist/transformers/merge.d.ts +0 -33
  52. package/dist/transformers/merge.d.ts.map +0 -1
  53. package/src/transformers/filter.md +0 -326
  54. package/src/transformers/flat.md +0 -56
  55. package/src/transformers/map.md +0 -430
  56. package/src/transformers/merge.md +0 -79
package/README.md CHANGED
@@ -3,45 +3,10 @@
3
3
  [![npm version](https://badge.fury.io/js/@soffinal%2Fstream.svg)](https://badge.fury.io/js/@soffinal%2Fstream)
4
4
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
5
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
- > **Type-safe event emitters that scale**
9
-
10
- Stream is like EventEmitter, but better. Send events to multiple listeners, transform data with `filter` and `map`, and never worry about memory leaks. Works with DOM elements, WebSockets, user interactions, or any async data source. Fully typed, zero dependencies, 5.5KB.
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
- - [Migration Guide](#migration-guide)
22
- - [Documentation](#documentation)
23
- - [Contributing](#contributing)
24
- - [License](#license)
25
-
26
- ## Features
27
-
28
- - **Adaptive Constraints** - Transformers that learn and evolve based on stream history
29
- - **Universal Primitives** - Four primitives: `filter`, `map`, `merge`, `flat`
30
- - **Documentation-as-Distribution** - Copy-paste transformers embedded in JSDoc, no separate packages needed
31
- - **Async-First** - Native async/await support with configurable concurrency control
32
- - **Concurrency Strategies** - Sequential, concurrent-unordered, concurrent-ordered processing
33
- - **Automatic Cleanup** - WeakRef-based listener cleanup prevents memory leaks
34
- - **Multicast Streams** - One stream, unlimited consumers
35
- - **Awaitable** - `await stream` for next value
36
- - **Async Iterable** - Native `for await` loop support
37
- - **Pipe Composition** - Stream-to-stream functional composition
38
- - **Type Guards** - Built-in TypeScript type narrowing support
39
- - **Reactive State** - Stateful values with automatic change propagation
40
- - **Reactive Collections** - Lists, Maps, Sets with fine-grained events
41
- - **Stream Termination** - Declarative stream lifecycle control
42
- - **Zero Dependencies** - Lightweight and tree-shakeable
43
- - **Universal** - Node.js, browsers, Deno, Bun, Cloudflare Workers
44
- - **Full TypeScript** - Complete type safety without the burden
6
+
7
+ > **Composable reactive primitives for TypeScript**
8
+
9
+ Minimal reactive streaming library: type-safe events, stateful transformations, async iteration, automatic cleanup.
45
10
 
46
11
  ## Quick Start
47
12
 
@@ -50,704 +15,237 @@ import { Stream } from "@soffinal/stream";
50
15
 
51
16
  const events = new Stream<string>();
52
17
 
53
- events.listen(console.log);
18
+ events.listen((value) => console.log(value));
19
+ events.push("Hello", "World");
20
+ // Output: Hello, World
21
+ ```
22
+
23
+ ## Installation
54
24
 
55
- events.push("Hello"); //log: Hello
25
+ ```bash
26
+ npm install @soffinal/stream
56
27
  ```
57
28
 
58
- ## Examples
29
+ ## Core Concepts
30
+
31
+ ### 1. Stream - Push & Pull
59
32
 
60
33
  ```typescript
61
- import { Stream, State, filter, map, merge } from "@soffinal/stream";
34
+ // Push: Emit values manually
35
+ const stream = new Stream<number>();
36
+ stream.listen((n) => console.log(n));
37
+ stream.push(1, 2, 3);
62
38
 
63
- // Create streams
64
- const events = new Stream<string>();
65
- const numbers = new Stream<number>();
39
+ // Pull: Generate values from async source
40
+ const countdown = new Stream(async function* () {
41
+ for (let i = 5; i > 0; i--) {
42
+ yield i;
43
+ await new Promise(resolve => setTimeout(resolve, 1000));
44
+ }
45
+ });
66
46
 
67
- // Pull-based stream from async generator
68
- const websocketStream = new Stream(async function* () {
69
- const ws = new WebSocket("ws://localhost:8080");
47
+ // Both: Push to a pull-based stream
48
+ const messages = new Stream(async function* () {
49
+ const ws = new WebSocket("ws://localhost");
70
50
  while (ws.readyState === WebSocket.OPEN) {
71
- yield await new Promise((resolve) => {
72
- ws.onmessage = (event) => resolve(JSON.parse(event.data));
51
+ yield await new Promise(resolve => {
52
+ ws.onmessage = e => resolve(e.data);
73
53
  });
74
54
  }
75
55
  });
76
56
 
77
- // Simple transformations
78
- const processed = events
79
- .pipe(filter((msg) => msg.length > 3)) // Simple filtering
80
- .pipe(map((msg) => msg.toUpperCase())); // Transform to uppercase
81
-
82
- // Async transformations with concurrency
83
- const validated = events.pipe(
84
- filter(
85
- async (msg) => {
86
- const isValid = await validateAsync(msg);
87
- return isValid;
88
- },
89
- { strategy: "concurrent-ordered" }
90
- ) // Parallel validation, ordered results
91
- );
92
-
93
- // Stateful transformers that learn and adapt
94
- const runningAverage = numbers
95
- .pipe(
96
- filter({ count: 0 }, (state, value) => {
97
- // Only pass every 3rd number, terminate after 10
98
- if (state.count >= 10) return; // Stream termination
99
- return [(state.count + 1) % 3 === 0, { count: state.count + 1 }];
100
- })
101
- )
102
- .pipe(
103
- map({ sum: 0, count: 0 }, (state, value) => {
104
- const newSum = state.sum + value;
105
- const newCount = state.count + 1;
106
- const average = newSum / newCount;
107
- return [
108
- { value, average },
109
- { sum: newSum, count: newCount },
110
- ];
111
- })
112
- );
113
-
114
- // Automatic cleanup with DOM elements
115
- const element = document.createElement('div');
116
- events.listen(value => {
117
- element.textContent = value;
118
- }, element); // Auto-removed when element is GC'd
119
-
120
- // Copy-paste transformers from JSDoc
121
- const limited = numbers.pipe(take(5)); // Limit to 5 items
122
- const indexed = events.pipe(withIndex()); // Add indices
123
- const delayed = processed.pipe(delay(100)); // Delay each value
124
-
125
- // Multiple consumers
126
- processed.listen((msg) => console.log("Processed:", msg));
127
- validated.listen((msg) => console.log("Validated:", msg));
128
- runningAverage.listen(({ value, average }) => console.log(`Value: ${value}, Running Average: ${average}`));
129
-
130
- // Reactive state
131
- const counter = new State(0);
132
- counter.listen((count) => (document.title = `Count: ${count}`));
133
- counter.value++; // UI updates automatically
57
+ messages.push("manual event"); // Can still push!
134
58
  ```
135
59
 
136
- ## Installation
137
-
138
- ### Package Managers
139
-
140
- ```bash
141
- # npm
142
- npm install @soffinal/stream
60
+ ### 2. Automatic Cleanup
143
61
 
144
- # yarn
145
- yarn add @soffinal/stream
146
-
147
- # pnpm
148
- pnpm add @soffinal/stream
149
-
150
- # bun
151
- bun add @soffinal/stream
152
-
153
- # Deno
154
- deno add jsr:@soffinal/stream
155
- ```
62
+ ```typescript
63
+ // Disposable pattern
64
+ using stream.listen(handler);
156
65
 
157
- ### CDN (Browser)
66
+ // Manual cleanup
67
+ const cleanup = stream.listen(handler);
68
+ cleanup();
158
69
 
159
- ```html
160
- <!-- Production (minified) -->
161
- <script type="module">
162
- import { Stream, State } from "https://cdn.jsdelivr.net/npm/@soffinal/stream@latest/dist/index.js";
163
- </script>
70
+ // AbortSignal
71
+ stream.listen(handler, controller.signal);
164
72
 
165
- <!-- Alternative CDNs -->
166
- <script type="module">
167
- import { Stream } from "https://esm.sh/@soffinal/stream";
168
- import { Stream } from "https://cdn.skypack.dev/@soffinal/stream";
169
- </script>
73
+ // Stream as signal
74
+ const stop = new Stream<void>();
75
+ stream.listen(handler, stop);
76
+ stop.push(); // Remove listener
170
77
  ```
171
78
 
172
- ## Core Concepts
173
-
174
- ### Streams: Multicast Event Pipelines
175
-
176
- A `Stream` is a multicast, async iterable that pushes values to multiple listeners while being awaitable for the next value.
79
+ ### 3. Transform with pipe
177
80
 
178
81
  ```typescript
179
- const userEvents = new Stream<UserEvent>();
180
-
181
- // Multiple consumers automatically share the same data
182
- userEvents.listen((event) => analytics.track(event));
183
- userEvents.listen((event) => notifications.send(event));
184
- userEvents.listen((event) => database.save(event));
185
-
186
- // Await the next event
187
- const nextEvent = await userEvents;
188
-
189
- // Async iteration
190
- for await (const event of userEvents) {
191
- if (event.type === "critical") break;
192
- processEvent(event);
193
- }
194
- ```
195
-
196
- ### Automatic Listener Cleanup
82
+ const numbers = new Stream<number>();
197
83
 
198
- Stream provides three cleanup mechanisms to prevent memory leaks:
84
+ const doubled = numbers.pipe(filter((n) => n > 0)).pipe(map((n) => n * 2));
199
85
 
200
- ```typescript
201
- const stream = new Stream<string>();
202
-
203
- // 1. Manual cleanup
204
- const cleanup = stream.listen(value => console.log(value));
205
- cleanup(); // Remove listener
206
-
207
- // 2. AbortSignal cleanup
208
- const controller = new AbortController();
209
- stream.listen(value => console.log(value), controller.signal);
210
- controller.abort(); // Remove listener
211
-
212
- // 3. WeakRef automatic cleanup (NEW!)
213
- const element = document.createElement('div');
214
- stream.listen(value => {
215
- element.textContent = value;
216
- }, element);
217
- // Listener automatically removed when element is garbage collected
218
- // Perfect for DOM elements, components, and temporary objects
86
+ doubled.listen((n) => console.log(n));
87
+ numbers.push(-1, 2, 3);
88
+ // Output: 4, 6
219
89
  ```
220
90
 
221
- **WeakRef Benefits:**
222
- - Zero memory leaks with DOM elements
223
- - No manual cleanup needed
224
- - Works with any object (components, instances, etc.)
225
- - Leverages JavaScript's garbage collector
226
- - Ideal for UI frameworks (React, Vue, Svelte, etc.)
91
+ ### 4. Async Iteration
227
92
 
228
93
  ```typescript
229
- // Real-world example: Component lifecycle
230
- function createComponent() {
231
- const element = document.createElement('div');
232
- const dataStream = new Stream<Data>();
233
-
234
- // Auto-cleanup when component unmounts
235
- dataStream.listen(data => {
236
- element.innerHTML = renderTemplate(data);
237
- }, element);
238
-
239
- return element;
94
+ for await (const value of stream) {
95
+ console.log(value);
96
+ if (done) break;
240
97
  }
241
-
242
- // When element is removed from DOM and GC'd, listener is automatically cleaned up
243
98
  ```
244
99
 
245
- ### Pipe: Stream-to-Stream Composition
100
+ ## Transformers
246
101
 
247
- The `pipe` method enforces composition - it only accepts functions that return Stream instances, maintaining the infinite pipeline:
102
+ ### state - Reactive State
248
103
 
249
104
  ```typescript
250
- // All transformers return Streams - infinite chaining
251
- stream.pipe(filter((v) => v > 0)); // → Stream<T>
252
- stream.pipe(map((v) => v.toString())); // → Stream<string>
253
- stream.pipe(toState("initial")); // State<string> (extends Stream)
254
-
255
- // Infinite chaining - every pipe returns a Stream
256
- const result = stream
257
- .pipe(filter((v) => v > 0))
258
- .pipe(map((v) => v * 2))
259
- .pipe(take(5))
260
- .pipe(delay(100))
261
- .pipe(distinct()); // Always chainable
105
+ const counter = new Stream<number>().pipe(state(0));
106
+
107
+ counter.listen((n) => console.log(n));
108
+ counter.state.value = 5; // Triggers listener
109
+ console.log(counter.state.value); // 5
262
110
  ```
263
111
 
264
- **Streams are infinite** - Like event emitters, they don't terminate naturally. The `pipe` constraint ensures you maintain the reactive paradigm throughout your entire pipeline.
112
+ **[📖 Full Documentation →](src/transformers/state/state.md)**
265
113
 
266
- **Perfect TypeScript inference** - no annotations needed:
114
+ ### gate - Flow Control
267
115
 
268
116
  ```typescript
269
- const numbers = new Stream<number>();
117
+ const stream = new Stream<number>().pipe(gate());
270
118
 
271
- // TypeScript knows these are all Streams
272
- const doubled = numbers.pipe(map((n) => n * 2)); // Stream<number>
273
- const strings = numbers.pipe(map((n) => n.toString())); // Stream<string>
274
- const state = numbers.pipe(toState(0)); // State<number>
119
+ stream.listen((n) => console.log(n));
120
+ stream.push(1); // Logs: 1
121
+ stream.gate.close();
122
+ stream.push(2); // Blocked
123
+ stream.gate.open();
124
+ stream.push(3); // Logs: 3
275
125
  ```
276
126
 
277
- ### Universal Primitives: The Four Algebraic Operations
127
+ **[📖 Full Documentation →](src/transformers/gate/gate.md)**
278
128
 
279
- All stream operations are built from four universal primitives with **Adaptive Constraints**:
280
-
281
- #### 1. Filter
129
+ ### filter - Remove Values
282
130
 
283
131
  ```typescript
284
- import { filter } from "@soffinal/stream";
285
-
286
- // Simple filtering
287
- stream.pipe(filter((value) => value > 0));
288
-
289
- // Type guard filtering
290
- stream.pipe(filter((value): value is number => typeof value === "number"));
132
+ // Simple
133
+ stream.pipe(filter((n) => n > 0));
291
134
 
292
- // Async filtering with concurrency strategies
293
- stream.pipe(
294
- filter(
295
- async (value) => {
296
- const isValid = await validateAsync(value);
297
- return isValid;
298
- },
299
- { strategy: "concurrent-ordered" }
300
- ) // Parallel validation, ordered results
301
- );
135
+ // Async
136
+ stream.pipe(filter(async (n) => await validate(n)));
302
137
 
303
- // Stateful filtering with termination
138
+ // Stateful
304
139
  stream.pipe(
305
- filter({ count: 0 }, (state, value) => {
306
- if (state.count >= 10) return; // Terminate after 10 items
307
- return [value > 0, { count: state.count + 1 }];
308
- })
140
+ filter({ count: 0 }, (state, n) => {
141
+ if (state.count >= 10) return; // Stop after 10
142
+ return [n > 0, { count: state.count + 1 }];
143
+ }),
309
144
  );
310
145
  ```
311
146
 
312
- **[📖 Complete Filter Documentation →](src/transformers/filter.md)**
147
+ **[📖 Full Documentation →](src/transformers/filter/filter.md)**
313
148
 
314
- #### 2. Map - Adaptive Transformer
149
+ ### map - Transform Values
315
150
 
316
151
  ```typescript
317
- import { map } from "@soffinal/stream";
152
+ // Simple
153
+ stream.pipe(map((n) => n * 2));
318
154
 
319
- // Simple transformation
320
- stream.pipe(map((value) => value * 2));
155
+ // Async
156
+ stream.pipe(map(async (n) => await process(n)));
321
157
 
322
- // Type transformation
323
- stream.pipe(map((value: number) => value.toString()));
324
-
325
- // Async transformation with concurrency strategies
158
+ // Stateful
326
159
  stream.pipe(
327
- map(
328
- async (value) => {
329
- const enriched = await enrichWithAPI(value);
330
- return enriched;
331
- },
332
- { strategy: "concurrent-unordered" }
333
- ) // Parallel processing, results as completed
334
- );
335
-
336
- // Stateful transformation with context
337
- stream.pipe(
338
- map({ sum: 0 }, (state, value) => {
339
- const newSum = state.sum + value;
340
- return [{ value, runningSum: newSum }, { sum: newSum }];
341
- })
160
+ map({ sum: 0 }, (state, n) => {
161
+ const newSum = state.sum + n;
162
+ return [newSum, { sum: newSum }];
163
+ }),
342
164
  );
343
165
  ```
344
166
 
345
- **[📖 Complete Map Documentation →](src/transformers/map.md)**
167
+ **[📖 Full Documentation →](src/transformers/map/map.md)**
346
168
 
347
- #### 3. Merge - Stream Orchestration
169
+ ### merge - Combine Streams
348
170
 
349
171
  ```typescript
350
- import { merge } from "@soffinal/stream";
351
-
352
- const stream1 = new Stream<number>();
353
- const stream2 = new Stream<string>();
172
+ const numbers = new Stream<number>();
173
+ const strings = new Stream<string>();
354
174
 
355
- // Combine multiple streams with type safety
356
- const combined = stream1.pipe(merge(stream2));
175
+ const combined = numbers.pipe(merge(strings));
357
176
  // Type: Stream<number | string>
358
-
359
- combined.listen((value) => {
360
- if (typeof value === "number") {
361
- console.log("Number:", value);
362
- } else {
363
- console.log("String:", value);
364
- }
365
- });
366
177
  ```
367
178
 
368
- **[📖 Complete Merge Documentation →](src/transformers/merge.md)**
179
+ **[📖 Full Documentation →](src/transformers/merge/merge.md)**
369
180
 
370
- #### 4. Flat - Event Multiplication
181
+ ### flat - Flatten Arrays
371
182
 
372
183
  ```typescript
373
- import { flat } from "@soffinal/stream";
374
-
375
- // Transform 1 array event → N individual events
376
- const arrayStream = new Stream<number[]>();
377
- const individualNumbers = arrayStream.pipe(flat());
184
+ const arrays = new Stream<number[]>();
185
+ const numbers = arrays.pipe(flat());
378
186
 
379
- arrayStream.push([1, 2, 3]); // Emits: 1, 2, 3 as separate events
380
- // Type: Stream<number>
381
-
382
- // Configurable depth flattening
383
- const deepArrays = new Stream<number[][][]>();
384
- const flattened = deepArrays.pipe(flat(2)); // Flatten 2 levels deep
385
- // Type: Stream<number>
187
+ arrays.push([1, 2, 3]); // Emits: 1, 2, 3
386
188
  ```
387
189
 
388
- **[📖 Complete Flat Documentation →](src/transformers/flat.md)**
389
-
390
- ### Documentation-as-Distribution: Copy-Paste Transformers
391
-
392
- No separate repos, no CLI tools, no package management - just copy-paste ready transformers embedded in JSDoc!
393
-
394
- #### The Educational Transparency
395
-
396
- Our approach makes **every implementation pattern visible and learnable**:
397
-
398
- ```typescript
399
- // 📦 All transformers are copy-pastable from IntelliSense!
400
- // Hover over 'Stream' to see the complete transformers library
401
-
402
- // Example: Users don't just get functions - they get implementation education
403
- const searchInput = new Stream<string>(); // ← Hover here for full library
404
- const searchResults = searchInput
405
- .pipe(distinct()) // Copy from Stream JSDoc - learn deduplication patterns
406
- .pipe(take(10)) // Copy from Stream JSDoc - learn termination patterns
407
- .pipe(delay(300)) // Copy from Stream JSDoc - learn async transformation
408
- .pipe(simpleMap((query) => searchAPI(query))); // Copy from Stream JSDoc - learn mapping patterns
409
- ```
410
-
411
- #### What Users Actually Learn
412
-
413
- When users hover over any function in JSDoc, they see **complete implementation patterns**:
414
-
415
- ```typescript
416
- // Users see EXACTLY how to build transformers
417
- const take = <T>(n: number) =>
418
- filter<T, { count: number }>({ count: 0 }, (state, value) => {
419
- if (state.count >= n) return; // ← Learn termination patterns
420
- return [true, { count: state.count + 1 }]; // ← Learn state evolution
421
- });
422
-
423
- const distinct = <T>() =>
424
- filter<T, { seen: Set<T> }>({ seen: new Set() }, (state, value) => {
425
- if (state.seen.has(value)) return [false, state]; // ← Learn deduplication logic
426
- state.seen.add(value); // ← Learn state mutation patterns
427
- return [true, state];
428
- });
429
- ```
430
-
431
- #### From Consumers to Creators
432
-
433
- This transparency empowers users to become **transformer architects**:
434
-
435
- ```typescript
436
- // After learning from JSDoc examples, users create their own:
437
- const withTimestamp = <T>() =>
438
- map<T, {}, { value: T; timestamp: number }>(
439
- {}, // ← Learned: empty state when no memory needed
440
- (_, value) => [
441
- { value, timestamp: Date.now() }, // ← Learned: transformation pattern
442
- {}, // ← Learned: state management
443
- ]
444
- );
445
-
446
- const rateLimited = <T>(maxPerSecond: number) =>
447
- filter<T, { timestamps: number[] }>({ timestamps: [] }, (state, value) => {
448
- const now = Date.now();
449
- const recent = state.timestamps.filter((t) => now - t < 1000);
450
- if (recent.length >= maxPerSecond) return [false, { timestamps: recent }];
451
- return [true, { timestamps: [...recent, now] }];
452
- });
453
- ```
454
-
455
- #### Benefits Beyond Bundle Size
456
-
457
- - ✅ **Zero friction** - Copy-paste ready transformers
458
- - ✅ **Perfect discoverability** - IntelliSense shows all available transformers
459
- - ✅ **Always up-to-date** - Examples match current API version
460
- - ✅ **No ecosystem fragmentation** - Everything in one place
461
- - ✅ **Educational transparency** - Users learn implementation patterns
462
- - ✅ **Infinite extensibility** - Users become transformer creators
463
- - ✅ **Self-documenting** - Usage examples included with working code
464
- - ✅ **Zero bundle cost** - JSDoc stripped at compile time
190
+ **[📖 Full Documentation →](src/transformers/flat/flat.md)**
465
191
 
466
- #### The Network Effect
192
+ ## Write Your Own
467
193
 
468
- Documentation-as-Distribution creates **multiplicative value**:
469
-
470
- 1. **User discovers** transformer in JSDoc
471
- 2. **User learns** implementation pattern
472
- 3. **User creates** custom transformers for their domain
473
- 4. **User shares** patterns with their team
474
- 5. **Team creates** hundreds of variations
475
- 6. **Knowledge multiplies** exponentially across the community
476
-
477
- **How it works:**
478
-
479
- 1. Hover over `Stream` in your IDE to see the complete transformers library
480
- 2. Or hover over individual functions for quick references
481
- 3. Copy the transformer you need
482
- 4. Use immediately - perfect TypeScript inference included!
483
- 5. **Learn the patterns** and create your own infinite variations
484
-
485
- **Available Transformers (via JSDoc):**
486
-
487
- - `take(n)`, `skip(n)`, `distinct()`, `tap(fn)` - Essential filtering patterns
488
- - `withIndex()`, `delay(ms)`, `pluck(key)`, `scan(fn, initial)` - Common transformation patterns
489
- - `toState(initialValue)` - Convert streams to reactive state
490
- - More transformers added with each release!
491
-
492
- **📊 Bundle Size Impact:**
493
-
494
- - **Package size**: Currently ~15KB, grows with JSDoc transformer examples over time
495
- - **Your app bundle**: Always only 5.5KB (runtime code only, zero JSDoc overhead)
496
- - **Tree-shaking**: Only imported functions included in final bundle
497
- - **JSDoc transformers**: "Free" - rich transformer library without production cost
498
-
499
- **You're not just building applications - you're learning a paradigm that scales infinitely.**
500
-
501
- ### Manual Composition
194
+ A transformer is just a function:
502
195
 
503
196
  ```typescript
504
- // You can still build transformers manually
505
- const customTransformer = <T>(count: number) =>
506
- filter<T, { taken: number }>({ taken: 0 }, (state, value) => {
507
- if (state.taken >= count) return; // Terminate after N items
508
- return [true, { taken: state.taken + 1 }];
197
+ const double = (stream: Stream<number>) =>
198
+ new Stream(async function* () {
199
+ for await (const n of stream) yield n * 2;
509
200
  });
510
- ```
511
-
512
- ### Reactive State: Stateful Values
513
-
514
- `State` extends `Stream` with a current value that can be read and written:
515
-
516
- ```typescript
517
- const user = new State<User | null>(null);
518
- const theme = new State<"light" | "dark">("light");
519
- const counter = new State(0);
520
-
521
- // Read current value
522
- console.log(counter.value); // 0
523
-
524
- // Write triggers all listeners
525
- counter.value = 5;
526
-
527
- // State from transformed streams
528
- const source = new Stream<number>();
529
- const derivedState = new State(0, source.pipe(map((v) => v * 2)));
530
-
531
- // Derived state using transformers
532
- const isLoggedIn = user.pipe(map((u) => u !== null));
533
-
534
- const userDisplayName = user.pipe(
535
- filter((u) => u !== null),
536
- map((u) => `${u.firstName} ${u.lastName}`)
537
- );
538
-
539
- // Convert streams to state with toState transformer
540
- const processedState = source
541
- .pipe(filter((v) => v > 0))
542
- .pipe(map((v) => v.toString()))
543
- .pipe(toState("0")); // Explicit initial value
544
-
545
- // Automatic UI updates
546
- isLoggedIn.listen((loggedIn) => {
547
- document.body.classList.toggle("authenticated", loggedIn);
548
- });
549
-
550
- // State changes propagate through the pipeline
551
- user.value = { firstName: "John", lastName: "Doe" };
552
- // Triggers: isLoggedIn → true, userDisplayName → 'John Doe'
553
- ```
554
-
555
- ### Reactive Collections: Fine-Grained Change Events
556
-
557
- Collections that emit specific change events for efficient UI updates:
558
-
559
- ```typescript
560
- import { List, Map, Set } from "@soffinal/stream";
561
-
562
- const todos = new List<Todo>();
563
- const userCache = new Map<string, User>();
564
- const activeUsers = new Set<string>();
565
-
566
- // React to specific operations
567
- todos.insert.listen(([index, todo]) => {
568
- console.log(`Todo inserted at ${index}:`, todo);
569
- renderTodoAtIndex(index, todo);
570
- });
571
-
572
- todos.delete.listen(([index, todo]) => {
573
- console.log(`Todo removed from ${index}:`, todo);
574
- removeTodoFromDOM(index);
575
- });
576
-
577
- // Map changes
578
- userCache.set.listen(([key, user]) => {
579
- console.log(`User cached: ${key}`, user);
580
- updateUserInUI(key, user);
581
- });
582
-
583
- // Set changes
584
- activeUsers.add.listen((userId) => {
585
- console.log(`User ${userId} came online`);
586
- showOnlineIndicator(userId);
587
- });
588
-
589
- activeUsers.delete.listen((userId) => {
590
- console.log(`User ${userId} went offline`);
591
- hideOnlineIndicator(userId);
592
- });
593
201
 
594
- // Use like normal collections
595
- todos.push({ id: 1, text: "Learn streams", done: false });
596
- userCache.set("user1", { name: "Alice", email: "alice@example.com" });
597
- activeUsers.add("user1");
202
+ stream.pipe(double);
598
203
  ```
599
204
 
600
- ## API Reference
205
+ ## API
601
206
 
602
207
  ### Stream\<T>
603
208
 
604
- #### Core Methods
605
-
606
- - `push(...values: T[]): void` - Emit values to all listeners (auto-removes GC'd listeners)
607
- - `listen(callback: (value: T) => void, context?: AbortSignal | Stream<any> | object): () => void` - Add listener with optional cleanup
608
- - `pipe<OUTPUT extends Stream<any>>(transformer: (stream: this) => OUTPUT): OUTPUT` - Apply any transformer
609
- - `withContext(context: object): AsyncIterator<T>` - Async iterator bound to context lifetime
610
-
611
- #### Async Interface
612
-
613
- - `then<U>(callback?: (value: T) => U): Promise<U>` - Promise interface for next value
614
- - `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration support
615
-
616
- #### Properties
617
-
618
- - `hasListeners: boolean` - Whether stream has active listeners
619
- - `listenerAdded: Stream<void>` - Emits when listener is added
620
- - `listenerRemoved: Stream<void>` - Emits when listener is removed
621
-
622
- ### State\<T> extends Stream\<T>
623
-
624
- #### Constructor
625
-
626
- - `new State(initialValue: T)` - Create state with initial value
627
- - `new State(initialValue: T, stream: Stream<T>)` - Create state from stream
628
-
629
- #### Additional Properties
630
-
631
- - `value: T` - Current state value (get/set)
632
-
633
- ### Universal Transformers
634
-
635
- #### filter(predicate, options?)
636
-
637
- - **Simple**: `filter((value) => boolean)`
638
- - **Type Guard**: `filter((value): value is Type => boolean)` (sync only)
639
- - **Async**: `filter(async (value) => boolean, { strategy? })` with concurrency options
640
- - **Stateful**: `filter(state, (state, value) => [boolean, newState])` (always sequential)
641
- - **Termination**: Return `undefined` to terminate stream
642
- - **Strategies**: `"sequential"` | `"concurrent-unordered"` | `"concurrent-ordered"`
643
-
644
- #### map(mapper, options?)
645
-
646
- - **Simple**: `map((value) => newValue)`
647
- - **Async**: `map(async (value) => newValue, { strategy? })` with concurrency options
648
- - **Stateful**: `map(state, (state, value) => [newValue, newState])` (always sequential)
649
- - **Strategies**: `"sequential"` | `"concurrent-unordered"` | `"concurrent-ordered"`
650
-
651
- #### merge(...streams)
652
-
653
- - **Basic**: `stream.pipe(merge(stream2, stream3))`
654
- - **Type-Safe**: Automatically creates union types
655
- - **Temporal Order**: Maintains chronological sequence
656
-
657
- #### flat(depth?)
658
-
659
- - **Basic**: `stream.pipe(flat())` - Flatten one level
660
- - **Deep**: `stream.pipe(flat(2))` - Flatten N levels
661
- - **Event Multiplication**: 1 array event → N individual events
209
+ - `push(...values: T[])` - Emit values
210
+ - `listen(callback, context?)` - Add listener
211
+ - `pipe(transformer)` - Transform stream
212
+ - `clear()` - Remove all listeners
662
213
 
663
- ### Reactive Collections
214
+ ### Async
664
215
 
665
- #### List\<T>
216
+ - `await stream` - Wait for next value
217
+ - `for await (const value of stream)` - Iterate
666
218
 
667
- - `insert: Stream<[number, T]>` - Insertion events
668
- - `delete: Stream<[number, T]>` - Deletion events
669
- - `clear: Stream<void>` - Clear events
219
+ ## Philosophy
670
220
 
671
- #### Map\<K,V> extends globalThis.Map\<K,V>
221
+ **2 primitives**: `Stream` + `pipe`
672
222
 
673
- - `set: Stream<[K, V]>` - Set events (only on changes)
674
- - `delete: Stream<[K, V]>` - Delete events
675
- - `clear: Stream<void>` - Clear events
223
+ **6 transformers**: `state`, `gate`, `filter`, `map`, `merge`, `flat`
676
224
 
677
- #### Set\<T> extends globalThis.Set\<T>
225
+ Everything else you compose yourself.
678
226
 
679
- - `add: Stream<T>` - Add events (only new values)
680
- - `delete: Stream<T>` - Delete events
681
- - `clear: Stream<void>` - Clear events
682
-
683
- ## Performance
684
-
685
- ### Bundle Size
686
-
687
- - **Runtime bundle** - 5.5KB minified, 1.6KB gzipped
688
- - **Package size** - Starts small, grows with JSDoc transformer library
689
- - **Your production app** - Always gets only the 5.5KB runtime code
690
- - **Tree-shakeable** - Import only what you use
691
-
692
- ### Benchmarks
693
-
694
- - **Fast startup** - Zero dependencies, instant initialization
695
- - **Efficient pipelines** - Optimized transformer composition
696
- - **Memory bounded** - Built-in backpressure handling
697
- - **Automatic cleanup** - WeakRef prevents memory leaks
698
-
699
- ## Runtime Support
700
-
701
- - **Modern browsers** supporting ES2020+ (WeakRef support)
702
- - **Node.js** 16+
703
- - **Deno** 1.0+
704
- - **Bun** 1.0+
705
- - **Cloudflare Workers**
706
-
707
- ## Migration Guide
708
-
709
- ### From EventEmitter
227
+ **Efficient by design**: Transformers execute once per value. Multiple listeners share the same computation:
710
228
 
711
229
  ```typescript
712
- // EventEmitter
713
- import { EventEmitter } from "events";
714
- const emitter = new EventEmitter();
715
- emitter.on("data", console.log);
716
- emitter.emit("data", "hello");
230
+ const expensive = source.pipe(map(async v => await heavyComputation(v)));
717
231
 
718
- // @soffinal/stream
719
- import { Stream } from "@soffinal/stream";
720
- const stream = new Stream();
721
- stream.listen(console.log);
722
- stream.push("hello");
232
+ expensive.listen(v => updateUI(v));
233
+ expensive.listen(v => logToAnalytics(v));
234
+ expensive.listen(v => saveToCache(v));
235
+ // heavyComputation() runs ONCE per value, not 3 times
723
236
  ```
724
237
 
725
- ## Documentation
726
-
727
- ### Transformer Guides
728
-
729
- - **[Filter Transformer →](src/transformers/filter.md)** - Concurrency strategies, type guards, stateful filtering, and stream termination
730
- - **[Map Transformer →](src/transformers/map.md)** - Concurrency strategies, type transformations, stateful mapping, and performance optimization
731
- - **[Merge Transformer →](src/transformers/merge.md)** - Stream orchestration and type-safe combination
732
- - **[Flat Transformer →](src/transformers/flat.md)** - Event multiplication and array flattening
238
+ ## Performance
733
239
 
734
- ## Contributing
240
+ - 1.6KB gzipped
241
+ - Zero dependencies
242
+ - Automatic memory management
243
+ - Tree-shakeable
735
244
 
736
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
245
+ ## Browser Support
737
246
 
738
- ### Development Setup
739
-
740
- ```bash
741
- git clone https://github.com/soffinal/stream.git
742
- cd stream
743
- bun install
744
- bun test
745
- ```
247
+ Chrome 84+, Firefox 79+, Safari 14.1+, Node.js 16+, Deno, Bun
746
248
 
747
249
  ## License
748
250
 
749
251
  MIT © [Soffinal](https://github.com/soffinal)
750
-
751
- Contact: <smari.sofiane@gmail.com>
752
-
753
- ---