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