@soffinal/stream 0.1.3 → 0.2.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/CHANGELOG.md +22 -0
- package/README.md +324 -269
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +12 -8
- package/dist/reactive/index.d.ts +5 -0
- package/dist/reactive/index.d.ts.map +1 -0
- package/dist/{list.d.ts → reactive/list.d.ts} +1 -1
- package/dist/reactive/list.d.ts.map +1 -0
- package/dist/{map.d.ts → reactive/map.d.ts} +1 -1
- package/dist/reactive/map.d.ts.map +1 -0
- package/dist/{set.d.ts → reactive/set.d.ts} +1 -1
- package/dist/reactive/set.d.ts.map +1 -0
- package/dist/{state.d.ts → reactive/state.d.ts} +1 -1
- package/dist/reactive/state.d.ts.map +1 -0
- package/dist/stream.d.ts +1 -279
- package/dist/stream.d.ts.map +1 -1
- package/dist/transformers/filter.d.ts +64 -0
- package/dist/transformers/filter.d.ts.map +1 -0
- package/dist/transformers/flat.d.ts +29 -0
- package/dist/transformers/flat.d.ts.map +1 -0
- package/dist/transformers/index.d.ts +5 -0
- package/dist/transformers/index.d.ts.map +1 -0
- package/dist/transformers/map.d.ts +64 -0
- package/dist/transformers/map.d.ts.map +1 -0
- package/dist/transformers/merge.d.ts +33 -0
- package/dist/transformers/merge.d.ts.map +1 -0
- package/package.json +5 -8
- package/src/transformers/filter.md +161 -0
- package/src/transformers/flat.md +56 -0
- package/src/transformers/map.md +184 -0
- package/src/transformers/merge.md +79 -0
- package/dist/benchmark.d.ts +0 -16
- package/dist/benchmark.d.ts.map +0 -1
- package/dist/list.d.ts.map +0 -1
- package/dist/map.d.ts.map +0 -1
- package/dist/set.d.ts.map +0 -1
- package/dist/state.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://bundlephobia.com/package/@soffinal/stream)
|
|
7
7
|
|
|
8
|
-
> **
|
|
8
|
+
> **A reactive streaming library with Adaptive Constraints**
|
|
9
9
|
|
|
10
|
-
A
|
|
10
|
+
A groundbreaking streaming library that introduces **Adaptive Reactive Programming** - where transformers maintain state and evolve their behavior based on stream history. Built with four universal primitives that compose into infinite possibilities.
|
|
11
11
|
|
|
12
12
|
## Table of Contents
|
|
13
13
|
|
|
@@ -20,45 +20,73 @@ A modern, async-first streaming library that treats asynchronous data flow as a
|
|
|
20
20
|
- [Performance](#performance)
|
|
21
21
|
- [Browser Support](#browser-support)
|
|
22
22
|
- [Migration Guide](#migration-guide)
|
|
23
|
+
- [Documentation](#documentation)
|
|
23
24
|
- [Contributing](#contributing)
|
|
24
25
|
- [License](#license)
|
|
25
26
|
|
|
26
27
|
## Features
|
|
27
28
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
- 🛠️ **
|
|
36
|
-
- 📊 **Reactive
|
|
37
|
-
-
|
|
38
|
-
-
|
|
29
|
+
- 🧠 **Adaptive Constraints** - Transformers that learn and evolve based on stream history
|
|
30
|
+
- 🔧 **Universal Primitives** - Four algebraic primitives: `filter`, `map`, `merge`, `flat`
|
|
31
|
+
- 📚 **Documentation-as-Distribution** - Copy-paste transformers embedded in JSDoc, no separate packages needed
|
|
32
|
+
- ⚡ **Async-First** - All operations support async with order preservation
|
|
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** - Functional transformer composition
|
|
37
|
+
- 📊 **Reactive State** - Stateful values with automatic change propagation
|
|
38
|
+
- 📋 **Reactive Collections** - Lists, Maps, Sets with fine-grained events
|
|
39
|
+
- 🗑️ **Stream Termination** - Declarative stream lifecycle control
|
|
40
|
+
- 📦 **Zero Dependencies** - Lightweight and tree-shakeable
|
|
39
41
|
- 🌐 **Universal** - Node.js, browsers, Deno, Bun, Cloudflare Workers
|
|
40
|
-
- 📘 **Full TypeScript** - Complete type safety
|
|
42
|
+
- 📘 **Full TypeScript** - Complete type safety without the burden
|
|
41
43
|
|
|
42
44
|
## Quick Start
|
|
43
45
|
|
|
44
46
|
```typescript
|
|
45
|
-
import { Stream, State } from "@soffinal/stream";
|
|
47
|
+
import { Stream, State, filter, map, merge } from "@soffinal/stream";
|
|
46
48
|
|
|
47
|
-
// Create reactive
|
|
49
|
+
// Create reactive streams
|
|
48
50
|
const events = new Stream<string>();
|
|
49
|
-
const
|
|
51
|
+
const numbers = new Stream<number>();
|
|
50
52
|
|
|
51
|
-
//
|
|
52
|
-
const processed = events
|
|
53
|
+
// Pipe-based transformation with Adaptive Constraints
|
|
54
|
+
const processed = events
|
|
55
|
+
.pipe(simpleFilter((msg) => msg.length > 3)) // Simple filtering
|
|
56
|
+
.pipe(simpleMap((msg) => msg.toUpperCase())); // Transform to uppercase
|
|
53
57
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
// Stateful transformers that learn and adapt
|
|
59
|
+
const runningAverage = numbers
|
|
60
|
+
.pipe(
|
|
61
|
+
filter({ count: 0 }, (state, value) => {
|
|
62
|
+
// Only pass every 3rd number, terminate after 10
|
|
63
|
+
if (state.count >= 10) return; // Stream termination
|
|
64
|
+
return [(state.count + 1) % 3 === 0, { count: state.count + 1 }];
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
.pipe(
|
|
68
|
+
map({ sum: 0, count: 0 }, (state, value) => {
|
|
69
|
+
const newSum = state.sum + value;
|
|
70
|
+
const newCount = state.count + 1;
|
|
71
|
+
const average = newSum / newCount;
|
|
72
|
+
return [
|
|
73
|
+
{ value, average },
|
|
74
|
+
{ sum: newSum, count: newCount },
|
|
75
|
+
];
|
|
76
|
+
})
|
|
77
|
+
);
|
|
57
78
|
|
|
58
|
-
//
|
|
59
|
-
|
|
79
|
+
// Copy-paste transformers from JSDoc
|
|
80
|
+
const limited = numbers.pipe(take(5)); // Limit to 5 items
|
|
81
|
+
const indexed = events.pipe(withIndex()); // Add indices
|
|
82
|
+
const delayed = processed.pipe(delay(100)); // Delay each value
|
|
83
|
+
|
|
84
|
+
// Multiple consumers
|
|
85
|
+
processed.listen((msg) => console.log("Processed:", msg));
|
|
86
|
+
runningAverage.listen(({ value, average }) => console.log(`Value: ${value}, Running Average: ${average}`));
|
|
60
87
|
|
|
61
|
-
// Reactive state
|
|
88
|
+
// Reactive state
|
|
89
|
+
const counter = new State(0);
|
|
62
90
|
counter.listen((count) => (document.title = `Count: ${count}`));
|
|
63
91
|
counter.value++; // UI updates automatically
|
|
64
92
|
```
|
|
@@ -101,199 +129,319 @@ deno add jsr:@soffinal/stream
|
|
|
101
129
|
|
|
102
130
|
## Core Concepts
|
|
103
131
|
|
|
104
|
-
### Streams:
|
|
132
|
+
### Streams: Multicast Event Pipelines
|
|
105
133
|
|
|
106
|
-
A `Stream` is
|
|
134
|
+
A `Stream` is a multicast, async iterable that pushes values to multiple listeners while being awaitable for the next value.
|
|
107
135
|
|
|
108
136
|
```typescript
|
|
109
137
|
const userEvents = new Stream<UserEvent>();
|
|
110
138
|
|
|
111
|
-
// Multiple consumers
|
|
139
|
+
// Multiple consumers automatically share the same data
|
|
112
140
|
userEvents.listen((event) => analytics.track(event));
|
|
113
141
|
userEvents.listen((event) => notifications.send(event));
|
|
114
142
|
userEvents.listen((event) => database.save(event));
|
|
115
143
|
|
|
116
|
-
//
|
|
144
|
+
// Await the next event
|
|
117
145
|
const nextEvent = await userEvents;
|
|
146
|
+
|
|
147
|
+
// Async iteration
|
|
148
|
+
for await (const event of userEvents) {
|
|
149
|
+
if (event.type === "critical") break;
|
|
150
|
+
processEvent(event);
|
|
151
|
+
}
|
|
118
152
|
```
|
|
119
153
|
|
|
120
|
-
###
|
|
154
|
+
### Universal Primitives: The Four Algebraic Operations
|
|
155
|
+
|
|
156
|
+
All stream operations are built from four universal primitives with **Adaptive Constraints**:
|
|
121
157
|
|
|
122
|
-
####
|
|
158
|
+
#### 1. Filter - Adaptive Gatekeeper
|
|
123
159
|
|
|
124
160
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Simple
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
import { filter } from "@soffinal/stream";
|
|
162
|
+
|
|
163
|
+
// Simple filtering
|
|
164
|
+
stream.pipe(filter({}, (_, value) => [value > 0, {}]));
|
|
165
|
+
|
|
166
|
+
// Stateful filtering with termination
|
|
167
|
+
stream.pipe(
|
|
168
|
+
filter({ count: 0 }, (state, value) => {
|
|
169
|
+
if (state.count >= 10) return; // Terminate after 10 items
|
|
170
|
+
return [value > 0, { count: state.count + 1 }];
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Async filtering
|
|
175
|
+
stream.pipe(
|
|
176
|
+
filter({}, async (_, value) => {
|
|
177
|
+
const isValid = await validateAsync(value);
|
|
178
|
+
return [isValid, {}];
|
|
140
179
|
})
|
|
141
|
-
|
|
180
|
+
);
|
|
142
181
|
```
|
|
143
182
|
|
|
144
|
-
|
|
183
|
+
**[📖 Complete Filter Documentation →](src/transformers/filter.md)**
|
|
184
|
+
|
|
185
|
+
#### 2. Map - Adaptive Transformer
|
|
145
186
|
|
|
146
187
|
```typescript
|
|
147
|
-
import {
|
|
188
|
+
import { map } from "@soffinal/stream";
|
|
148
189
|
|
|
149
|
-
// Simple
|
|
150
|
-
|
|
151
|
-
.pipe(filter((x) => x > 0))
|
|
152
|
-
.pipe(map((x) => x * 2))
|
|
153
|
-
.pipe(group((batch) => batch.length >= 5));
|
|
190
|
+
// Simple transformation
|
|
191
|
+
stream.pipe(map({}, (_, value) => [value * 2, {}]));
|
|
154
192
|
|
|
155
|
-
// Stateful
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.pipe(filter((x) => x % 2 === 0))
|
|
171
|
-
.pipe(merge(stream2))
|
|
172
|
-
.pipe(map((x) => x.toString()));
|
|
193
|
+
// Stateful transformation with context
|
|
194
|
+
stream.pipe(
|
|
195
|
+
map({ sum: 0 }, (state, value) => {
|
|
196
|
+
const newSum = state.sum + value;
|
|
197
|
+
return [{ value, runningSum: newSum }, { sum: newSum }];
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Async transformation with order preservation
|
|
202
|
+
stream.pipe(
|
|
203
|
+
map({}, async (_, value) => {
|
|
204
|
+
const enriched = await enrichWithAPI(value);
|
|
205
|
+
return [{ original: value, enriched }, {}];
|
|
206
|
+
})
|
|
207
|
+
);
|
|
173
208
|
```
|
|
174
209
|
|
|
175
|
-
|
|
210
|
+
**[📖 Complete Map Documentation →](src/transformers/map.md)**
|
|
176
211
|
|
|
177
|
-
|
|
212
|
+
#### 3. Merge - Stream Orchestration
|
|
178
213
|
|
|
179
214
|
```typescript
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
//
|
|
215
|
+
import { merge } from "@soffinal/stream";
|
|
216
|
+
|
|
217
|
+
const stream1 = new Stream<number>();
|
|
218
|
+
const stream2 = new Stream<string>();
|
|
219
|
+
|
|
220
|
+
// Combine multiple streams with type safety
|
|
221
|
+
const combined = stream1.pipe(merge(stream2));
|
|
222
|
+
// Type: Stream<number | string>
|
|
223
|
+
|
|
224
|
+
combined.listen((value) => {
|
|
225
|
+
if (typeof value === "number") {
|
|
226
|
+
console.log("Number:", value);
|
|
227
|
+
} else {
|
|
228
|
+
console.log("String:", value);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**[📖 Complete Merge Documentation →](src/transformers/merge.md)**
|
|
234
|
+
|
|
235
|
+
#### 4. Flat - Event Multiplication
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { flat } from "@soffinal/stream";
|
|
239
|
+
|
|
240
|
+
// Transform 1 array event → N individual events
|
|
241
|
+
const arrayStream = new Stream<number[]>();
|
|
242
|
+
const individualNumbers = arrayStream.pipe(flat());
|
|
243
|
+
|
|
244
|
+
arrayStream.push([1, 2, 3]); // Emits: 1, 2, 3 as separate events
|
|
245
|
+
|
|
246
|
+
// Configurable depth flattening
|
|
247
|
+
const deepArrays = new Stream<number[][][]>();
|
|
248
|
+
const flattened = deepArrays.pipe(flat(2)); // Flatten 2 levels deep
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**[📖 Complete Flat Documentation →](src/transformers/flat.md)**
|
|
252
|
+
|
|
253
|
+
### Documentation-as-Distribution: Revolutionary Copy-Paste Transformers
|
|
254
|
+
|
|
255
|
+
**🚀 World's First Documentation-as-Distribution System**
|
|
256
|
+
|
|
257
|
+
No separate repos, no CLI tools, no package management - just copy-paste ready transformers embedded in JSDoc!
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// 📦 All transformers are copy-pastable from IntelliSense!
|
|
261
|
+
// Just hover over any primitive in your IDE to see the transformer library
|
|
262
|
+
|
|
263
|
+
// Example: Essential transformers available via autocomplete
|
|
214
264
|
const searchInput = new Stream<string>();
|
|
215
265
|
const searchResults = searchInput
|
|
216
|
-
.pipe(
|
|
217
|
-
.pipe(
|
|
218
|
-
.pipe(
|
|
266
|
+
.pipe(distinct()) // Copy from filter() JSDoc
|
|
267
|
+
.pipe(simpleFilter((q) => q.length > 2)) // Copy from filter() JSDoc
|
|
268
|
+
.pipe(take(10)) // Copy from filter() JSDoc
|
|
269
|
+
.pipe(delay(300)) // Copy from map() JSDoc
|
|
270
|
+
.pipe(simpleMap((query) => searchAPI(query))); // Copy from map() JSDoc
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Benefits:**
|
|
274
|
+
|
|
275
|
+
- ✅ **Zero friction** - Copy-paste ready transformers
|
|
276
|
+
- ✅ **Perfect discoverability** - IntelliSense shows all available transformers
|
|
277
|
+
- ✅ **Always up-to-date** - Examples match current API version
|
|
278
|
+
- ✅ **No ecosystem fragmentation** - Everything in one place
|
|
279
|
+
- ✅ **Self-documenting** - Usage examples included
|
|
280
|
+
|
|
281
|
+
**How it works:**
|
|
282
|
+
|
|
283
|
+
1. Hover over `filter()`, `map()`, `merge()`, or `flat()` in your IDE
|
|
284
|
+
2. Browse the 📦 COPY-PASTE TRANSFORMER examples
|
|
285
|
+
3. Copy the transformer you need
|
|
286
|
+
4. Use immediately - perfect TypeScript inference included!
|
|
287
|
+
|
|
288
|
+
**Available Transformers (via JSDoc):**
|
|
289
|
+
|
|
290
|
+
- `take(n)`, `skip(n)`, `distinct()` - Essential filtering
|
|
291
|
+
- `withIndex()`, `delay(ms)`, `pluck(key)` - Common transformations
|
|
292
|
+
- `simpleFilter(predicate)` - Convenient filtering without state
|
|
293
|
+
- `simpleMap(fn)` - Convenient mapping without state
|
|
294
|
+
- More transformers added with each release!
|
|
295
|
+
|
|
296
|
+
**📊 Bundle Size Impact:**
|
|
297
|
+
|
|
298
|
+
- **Package size**: Currently ~15KB, grows with JSDoc transformer examples over time
|
|
299
|
+
- **Your app bundle**: Always only 5.5KB (runtime code only, zero JSDoc overhead)
|
|
300
|
+
- **Tree-shaking**: Only imported functions included in final bundle
|
|
301
|
+
- **JSDoc transformers**: "Free" - rich transformer library without production cost
|
|
302
|
+
|
|
303
|
+
### Manual Composition
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// You can still build transformers manually
|
|
307
|
+
const customTransformer = <T>(count: number) =>
|
|
308
|
+
filter<T, { taken: number }>({ taken: 0 }, (state, value) => {
|
|
309
|
+
if (state.taken >= count) return; // Terminate after N items
|
|
310
|
+
return [true, { taken: state.taken + 1 }];
|
|
311
|
+
});
|
|
219
312
|
```
|
|
220
313
|
|
|
221
|
-
### Reactive State
|
|
314
|
+
### Reactive State: Stateful Values
|
|
222
315
|
|
|
223
|
-
State
|
|
316
|
+
`State` extends `Stream` with a current value that can be read and written:
|
|
224
317
|
|
|
225
318
|
```typescript
|
|
226
319
|
const user = new State<User | null>(null);
|
|
227
320
|
const theme = new State<"light" | "dark">("light");
|
|
321
|
+
const counter = new State(0);
|
|
228
322
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
323
|
+
// Read current value
|
|
324
|
+
console.log(counter.value); // 0
|
|
325
|
+
|
|
326
|
+
// Write triggers all listeners
|
|
327
|
+
counter.value = 5;
|
|
328
|
+
|
|
329
|
+
// Derived state using transformers
|
|
330
|
+
const isLoggedIn = user.pipe(map({}, (_, u) => [u !== null, {}]));
|
|
331
|
+
|
|
332
|
+
const userDisplayName = user.pipe(
|
|
333
|
+
filter({}, (_, u) => [u !== null, {}]),
|
|
334
|
+
map({}, (_, u) => [`${u.firstName} ${u.lastName}`, {}])
|
|
335
|
+
);
|
|
234
336
|
|
|
235
337
|
// Automatic UI updates
|
|
236
338
|
isLoggedIn.listen((loggedIn) => {
|
|
237
339
|
document.body.classList.toggle("authenticated", loggedIn);
|
|
238
340
|
});
|
|
341
|
+
|
|
342
|
+
// State changes propagate through the pipeline
|
|
343
|
+
user.value = { firstName: "John", lastName: "Doe" };
|
|
344
|
+
// Triggers: isLoggedIn → true, userDisplayName → 'John Doe'
|
|
239
345
|
```
|
|
240
346
|
|
|
241
|
-
### Reactive Collections
|
|
347
|
+
### Reactive Collections: Fine-Grained Change Events
|
|
242
348
|
|
|
243
|
-
Collections that emit
|
|
349
|
+
Collections that emit specific change events for efficient UI updates:
|
|
244
350
|
|
|
245
351
|
```typescript
|
|
352
|
+
import { List, Map, Set } from "@soffinal/stream";
|
|
353
|
+
|
|
246
354
|
const todos = new List<Todo>();
|
|
247
|
-
const
|
|
355
|
+
const userCache = new Map<string, User>();
|
|
248
356
|
const activeUsers = new Set<string>();
|
|
249
357
|
|
|
250
|
-
// React to
|
|
251
|
-
todos.insert.listen(([index, todo]) =>
|
|
252
|
-
|
|
253
|
-
|
|
358
|
+
// React to specific operations
|
|
359
|
+
todos.insert.listen(([index, todo]) => {
|
|
360
|
+
console.log(`Todo inserted at ${index}:`, todo);
|
|
361
|
+
renderTodoAtIndex(index, todo);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
todos.delete.listen(([index, todo]) => {
|
|
365
|
+
console.log(`Todo removed from ${index}:`, todo);
|
|
366
|
+
removeTodoFromDOM(index);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Map changes
|
|
370
|
+
userCache.set.listen(([key, user]) => {
|
|
371
|
+
console.log(`User cached: ${key}`, user);
|
|
372
|
+
updateUserInUI(key, user);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Set changes
|
|
376
|
+
activeUsers.add.listen((userId) => {
|
|
377
|
+
console.log(`User ${userId} came online`);
|
|
378
|
+
showOnlineIndicator(userId);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
activeUsers.delete.listen((userId) => {
|
|
382
|
+
console.log(`User ${userId} went offline`);
|
|
383
|
+
hideOnlineIndicator(userId);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Use like normal collections
|
|
387
|
+
todos.push({ id: 1, text: "Learn streams", done: false });
|
|
388
|
+
userCache.set("user1", { name: "Alice", email: "alice@example.com" });
|
|
389
|
+
activeUsers.add("user1");
|
|
254
390
|
```
|
|
255
391
|
|
|
256
392
|
## API Reference
|
|
257
393
|
|
|
258
394
|
### Stream\<T>
|
|
259
395
|
|
|
396
|
+
#### Core Methods
|
|
397
|
+
|
|
398
|
+
- `push(...values: T[]): void` - Emit values to all listeners
|
|
399
|
+
- `listen(callback: (value: T) => void, signal?: AbortSignal | Stream<any>): () => void` - Add listener, returns cleanup
|
|
400
|
+
- `pipe<U>(transformer: (stream: Stream<T>) => Stream<U>): Stream<U>` - Apply functional transformer
|
|
401
|
+
|
|
402
|
+
#### Async Interface
|
|
403
|
+
|
|
404
|
+
- `then<U>(callback?: (value: T) => U): Promise<U>` - Promise interface for next value
|
|
405
|
+
- `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration support
|
|
406
|
+
|
|
260
407
|
#### Properties
|
|
261
408
|
|
|
262
409
|
- `hasListeners: boolean` - Whether stream has active listeners
|
|
263
410
|
- `listenerAdded: Stream<void>` - Emits when listener is added
|
|
264
411
|
- `listenerRemoved: Stream<void>` - Emits when listener is removed
|
|
265
412
|
|
|
266
|
-
|
|
413
|
+
### State\<T> extends Stream\<T>
|
|
267
414
|
|
|
268
|
-
|
|
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
|
|
415
|
+
#### Additional Properties
|
|
276
416
|
|
|
277
|
-
|
|
417
|
+
- `value: T` - Current state value (get/set)
|
|
278
418
|
|
|
279
|
-
|
|
280
|
-
- `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration
|
|
419
|
+
### Universal Transformers
|
|
281
420
|
|
|
282
|
-
|
|
421
|
+
#### filter(initialState, accumulator)
|
|
283
422
|
|
|
284
|
-
|
|
423
|
+
- **Simple**: `filter({}, (_, value) => [boolean, {}])`
|
|
424
|
+
- **Stateful**: `filter(state, (state, value) => [boolean, newState])`
|
|
425
|
+
- **Async**: `filter({}, async (_, value) => [boolean, {}])`
|
|
426
|
+
- **Termination**: Return `undefined` to terminate stream
|
|
285
427
|
|
|
286
|
-
|
|
428
|
+
#### map(initialState, accumulator)
|
|
429
|
+
|
|
430
|
+
- **Simple**: `map({}, (_, value) => [newValue, {}])`
|
|
431
|
+
- **Stateful**: `map(state, (state, value) => [newValue, newState])`
|
|
432
|
+
- **Async**: `map({}, async (_, value) => [newValue, {}])`
|
|
433
|
+
|
|
434
|
+
#### merge(...streams)
|
|
287
435
|
|
|
288
|
-
|
|
436
|
+
- **Basic**: `stream.pipe(merge(stream2, stream3))`
|
|
437
|
+
- **Type-Safe**: Automatically creates union types
|
|
438
|
+
- **Temporal Order**: Maintains chronological sequence
|
|
289
439
|
|
|
290
|
-
|
|
440
|
+
#### flat(depth?)
|
|
291
441
|
|
|
292
|
-
- `
|
|
293
|
-
- `
|
|
294
|
-
-
|
|
295
|
-
- `merge<T>(...streams: Stream<T>[]): (stream: Stream<T>) => Stream<T>`
|
|
296
|
-
- `flat<T>(depth?: number): (stream: Stream<T>) => Stream<U>`
|
|
442
|
+
- **Basic**: `stream.pipe(flat())` - Flatten one level
|
|
443
|
+
- **Deep**: `stream.pipe(flat(2))` - Flatten N levels
|
|
444
|
+
- **Event Multiplication**: 1 array event → N individual events
|
|
297
445
|
|
|
298
446
|
### Reactive Collections
|
|
299
447
|
|
|
@@ -315,111 +463,22 @@ Functional transformers for use with `pipe()`:
|
|
|
315
463
|
- `delete: Stream<T>` - Delete events
|
|
316
464
|
- `clear: Stream<void>` - Clear events
|
|
317
465
|
|
|
318
|
-
## Examples
|
|
319
|
-
|
|
320
|
-
### Real-time Data Processing
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
const sensorData = new Stream<SensorReading>();
|
|
324
|
-
|
|
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);
|
|
330
|
-
|
|
331
|
-
alerts.listen((batch) => sendAlertNotification(batch));
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### WebSocket Integration
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
const wsStream = new Stream<MessageEvent>(async function* () {
|
|
338
|
-
const ws = new WebSocket("wss://api.example.com");
|
|
339
|
-
|
|
340
|
-
while (ws.readyState !== WebSocket.CLOSED) {
|
|
341
|
-
yield await new Promise((resolve) => {
|
|
342
|
-
ws.onmessage = resolve;
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const messages = wsStream.map((event) => JSON.parse(event.data)).filter((data) => data.type === "update");
|
|
348
|
-
|
|
349
|
-
messages.listen((update) => handleUpdate(update));
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### State Management
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
interface AppState {
|
|
356
|
-
user: User | null;
|
|
357
|
-
theme: "light" | "dark";
|
|
358
|
-
notifications: Notification[];
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const appState = new State<AppState>({
|
|
362
|
-
user: null,
|
|
363
|
-
theme: "light",
|
|
364
|
-
notifications: [],
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// Derived state
|
|
368
|
-
const unreadCount = appState.map((state) => state.notifications.filter((n) => !n.read).length);
|
|
369
|
-
|
|
370
|
-
// Reactive UI
|
|
371
|
-
unreadCount.listen((count) => {
|
|
372
|
-
document.title = count > 0 ? `(${count}) App` : "App";
|
|
373
|
-
});
|
|
374
|
-
```
|
|
375
|
-
|
|
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>
|
|
404
|
-
```
|
|
405
|
-
|
|
406
466
|
## Performance
|
|
407
467
|
|
|
408
|
-
|
|
468
|
+
### Bundle Size
|
|
409
469
|
|
|
410
|
-
- **
|
|
411
|
-
- **
|
|
412
|
-
- **
|
|
413
|
-
- **
|
|
470
|
+
- **Runtime bundle** - 5.5KB minified, 1.6KB gzipped
|
|
471
|
+
- **Package size** - Starts small, grows with JSDoc transformer library
|
|
472
|
+
- **Your production app** - Always gets only the 5.5KB runtime code
|
|
473
|
+
- **Tree-shakeable** - Import only what you use
|
|
414
474
|
|
|
415
|
-
###
|
|
475
|
+
### Benchmarks
|
|
416
476
|
|
|
417
|
-
- **
|
|
418
|
-
- **Efficient
|
|
419
|
-
- **
|
|
420
|
-
- **Automatic cleanup** prevents memory leaks
|
|
477
|
+
- **Fast startup** - Zero dependencies, instant initialization
|
|
478
|
+
- **Efficient pipelines** - Optimized transformer composition
|
|
479
|
+
- **Memory bounded** - Built-in backpressure handling
|
|
421
480
|
|
|
422
|
-
##
|
|
481
|
+
## Runtime Support
|
|
423
482
|
|
|
424
483
|
- **Modern browsers** supporting ES2020+
|
|
425
484
|
- **Node.js** 16+
|
|
@@ -429,28 +488,6 @@ unreadCount.listen((count) => {
|
|
|
429
488
|
|
|
430
489
|
## Migration Guide
|
|
431
490
|
|
|
432
|
-
### From RxJS
|
|
433
|
-
|
|
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);
|
|
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);
|
|
452
|
-
```
|
|
453
|
-
|
|
454
491
|
### From EventEmitter
|
|
455
492
|
|
|
456
493
|
```typescript
|
|
@@ -467,6 +504,24 @@ stream.listen(console.log);
|
|
|
467
504
|
stream.push("hello");
|
|
468
505
|
```
|
|
469
506
|
|
|
507
|
+
## Documentation
|
|
508
|
+
|
|
509
|
+
### Transformer Guides
|
|
510
|
+
|
|
511
|
+
- **[Filter Transformer →](src/transformers/filter.md)** - Adaptive constraints and stream termination
|
|
512
|
+
- **[Map Transformer →](src/transformers/map.md)** - Stateful transformations and async processing
|
|
513
|
+
- **[Merge Transformer →](src/transformers/merge.md)** - Stream orchestration and type-safe combination
|
|
514
|
+
- **[Flat Transformer →](src/transformers/flat.md)** - Event multiplication and array flattening
|
|
515
|
+
|
|
516
|
+
### Philosophy
|
|
517
|
+
|
|
518
|
+
**Adaptive Reactive Programming** - A new paradigm where transformers maintain state and evolve their behavior based on stream history. This enables:
|
|
519
|
+
|
|
520
|
+
- **Learning transformers** that adapt to data patterns
|
|
521
|
+
- **Stateful operations** with memory between events
|
|
522
|
+
- **Stream termination** for lifecycle control
|
|
523
|
+
- **Zero-overhead types** with perfect inference
|
|
524
|
+
|
|
470
525
|
## Contributing
|
|
471
526
|
|
|
472
527
|
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
@@ -489,5 +544,5 @@ Contact: <smari.sofiane@gmail.com>
|
|
|
489
544
|
---
|
|
490
545
|
|
|
491
546
|
<div align="center">
|
|
492
|
-
<strong>
|
|
547
|
+
<strong>Pioneering Adaptive Reactive Programming</strong>
|
|
493
548
|
</div>
|