@soffinal/stream 0.1.2 → 0.1.4
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 +340 -621
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,290 +1,201 @@
|
|
|
1
1
|
# @soffinal/stream
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
[](https://badge.fury.io/js/@soffinal%2Fstream)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://bundlephobia.com/package/@soffinal/stream)
|
|
7
|
+
|
|
8
|
+
> **High-performance reactive streaming library for TypeScript/JavaScript**
|
|
9
|
+
|
|
10
|
+
A modern, async-first streaming library that treats asynchronous data flow as a first-class citizen. Built for real-time applications, event-driven architectures, and reactive programming patterns.
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Core Concepts](#core-concepts)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
- [Examples](#examples)
|
|
20
|
+
- [Performance](#performance)
|
|
21
|
+
- [Browser Support](#browser-support)
|
|
22
|
+
- [Migration Guide](#migration-guide)
|
|
23
|
+
- [Contributing](#contributing)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- 🚀 **High Performance** - Optimized for speed and efficiency
|
|
29
|
+
- 🔄 **Multicast by Default** - One stream, many consumers
|
|
30
|
+
- ⏳ **Awaitable Streams** - `await stream` for next value
|
|
31
|
+
- 🔁 **Async Iterable** - Use `for await` loops naturally
|
|
32
|
+
- ⚡ **Async Operations** - All transformers support async functions while maintaining order
|
|
33
|
+
- 🧠 **Smart Caching** - Operations run once, results shared
|
|
34
|
+
- 🗑️ **Auto Cleanup** - Memory management handled automatically
|
|
35
|
+
- 🔧 **Dual Programming Paradigms** - Method chaining or functional pipe composition
|
|
36
|
+
- 🛠️ **Custom Transformers** - Create reusable transformers in both OOP and functional styles
|
|
37
|
+
- 📊 **Reactive Collections** - Lists, Maps, Sets with fine-grained change events
|
|
38
|
+
- 🔀 **Stateful Operations** - Built-in support for stateful filtering, mapping, and grouping
|
|
39
|
+
- 📦 **Zero Dependencies** - Lightweight (~6KB minified, ~2KB gzipped) and tree-shakeable
|
|
40
|
+
- 🌐 **Universal** - Node.js, browsers, Deno, Bun, Cloudflare Workers
|
|
41
|
+
- 📘 **Full TypeScript** - Complete type safety and inference
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
29
44
|
|
|
30
45
|
```typescript
|
|
31
|
-
import { Stream } from "@soffinal/stream";
|
|
32
|
-
|
|
33
|
-
// Create a data pipeline
|
|
34
|
-
const userEvents = new Stream<{ userId: string; action: string }>();
|
|
35
|
-
|
|
36
|
-
// Multiple consumers can listen to the same stream
|
|
37
|
-
userEvents.listen((event) => logToAnalytics(event));
|
|
38
|
-
userEvents.listen((event) => updateUserActivity(event));
|
|
39
|
-
userEvents.listen((event) => triggerNotifications(event));
|
|
46
|
+
import { Stream, State } from "@soffinal/stream";
|
|
40
47
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Stream is also a promise for the next value
|
|
46
|
-
const nextEvent = await userEvents; // Waits for next pushed value
|
|
47
|
-
console.log("Next event:", nextEvent);
|
|
48
|
-
```
|
|
48
|
+
// Create reactive data streams
|
|
49
|
+
const events = new Stream<string>();
|
|
50
|
+
const counter = new State(0);
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
// Transform and filter data
|
|
53
|
+
const processed = events.filter((msg) => msg.length > 3).map((msg) => msg.toUpperCase());
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const messageStream = new Stream<string>();
|
|
56
|
-
|
|
57
|
-
// Process messages as they arrive
|
|
58
|
-
(async () => {
|
|
59
|
-
for await (const message of messageStream) {
|
|
60
|
-
await processMessage(message);
|
|
61
|
-
if (message === "shutdown") break;
|
|
62
|
-
}
|
|
63
|
-
})();
|
|
64
|
-
|
|
65
|
-
messageStream.push("user-login", "data-sync", "shutdown");
|
|
66
|
-
```
|
|
55
|
+
// Multiple consumers
|
|
56
|
+
processed.listen((msg) => console.log("Logger:", msg));
|
|
57
|
+
processed.listen((msg) => updateUI(msg));
|
|
67
58
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Create infinite, lazy data sources using async generators:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
const sensorStream = new Stream(async function* () {
|
|
74
|
-
while (true) {
|
|
75
|
-
const temperature = await readTemperatureSensor();
|
|
76
|
-
const humidity = await readHumiditySensor();
|
|
77
|
-
yield { temperature, humidity, timestamp: Date.now() };
|
|
78
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
79
|
-
}
|
|
80
|
-
});
|
|
59
|
+
// Push data through the pipeline
|
|
60
|
+
events.push("hello", "hi", "world"); // Only 'HELLO' and 'WORLD' processed
|
|
81
61
|
|
|
82
|
-
|
|
62
|
+
// Reactive state management
|
|
63
|
+
counter.listen((count) => (document.title = `Count: ${count}`));
|
|
64
|
+
counter.value++; // UI updates automatically
|
|
83
65
|
```
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Streams support method chaining for familiar object-oriented programming patterns:
|
|
88
|
-
|
|
89
|
-
#### Filter: Smart Data Selection
|
|
90
|
-
|
|
91
|
-
Filtering goes beyond simple predicates - it supports stateful filtering and async predicates:
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
const events = new Stream<{ type: string; userId: string; data: any }>();
|
|
95
|
-
|
|
96
|
-
// Simple filtering
|
|
97
|
-
const loginEvents = events.filter((e) => e.type === "login");
|
|
98
|
-
|
|
99
|
-
// Stateful filtering - only allow increasing user IDs
|
|
100
|
-
const validEvents = events.filter(
|
|
101
|
-
0, // initial state
|
|
102
|
-
(lastUserId, event) => {
|
|
103
|
-
const currentId = parseInt(event.userId);
|
|
104
|
-
return [currentId > lastUserId, currentId];
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
// Async filtering - validate against external service
|
|
109
|
-
const authorizedEvents = events.filter(async (event) => {
|
|
110
|
-
const isAuthorized = await checkUserPermissions(event.userId);
|
|
111
|
-
return isAuthorized;
|
|
112
|
-
});
|
|
113
|
-
```
|
|
67
|
+
## Installation
|
|
114
68
|
|
|
115
|
-
|
|
69
|
+
### Package Managers
|
|
116
70
|
|
|
117
|
-
|
|
71
|
+
```bash
|
|
72
|
+
# npm
|
|
73
|
+
npm install @soffinal/stream
|
|
118
74
|
|
|
119
|
-
|
|
120
|
-
|
|
75
|
+
# yarn
|
|
76
|
+
yarn add @soffinal/stream
|
|
121
77
|
|
|
122
|
-
|
|
123
|
-
|
|
78
|
+
# pnpm
|
|
79
|
+
pnpm add @soffinal/stream
|
|
124
80
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
0, // initial sequence
|
|
128
|
-
(seq, event) => [{ ...event, sequence: seq + 1 }, seq + 1]
|
|
129
|
-
);
|
|
81
|
+
# bun
|
|
82
|
+
bun add @soffinal/stream
|
|
130
83
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const userProfile = await getUserProfile(event.userId);
|
|
134
|
-
return { ...event, user: userProfile };
|
|
135
|
-
});
|
|
84
|
+
# Deno
|
|
85
|
+
deno add jsr:@soffinal/stream
|
|
136
86
|
```
|
|
137
87
|
|
|
138
|
-
|
|
88
|
+
### CDN (Browser)
|
|
139
89
|
|
|
140
|
-
|
|
90
|
+
```html
|
|
91
|
+
<!-- Production (minified) -->
|
|
92
|
+
<script type="module">
|
|
93
|
+
import { Stream, State } from "https://cdn.jsdelivr.net/npm/@soffinal/stream@latest/dist/index.js";
|
|
94
|
+
</script>
|
|
141
95
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
merged.listen((msg) => console.log("Merged:", msg));
|
|
148
|
-
|
|
149
|
-
stream1.push("from stream1");
|
|
150
|
-
stream2.push("from stream2");
|
|
96
|
+
<!-- Alternative CDNs -->
|
|
97
|
+
<script type="module">
|
|
98
|
+
import { Stream } from "https://esm.sh/@soffinal/stream";
|
|
99
|
+
import { Stream } from "https://cdn.skypack.dev/@soffinal/stream";
|
|
100
|
+
</script>
|
|
151
101
|
```
|
|
152
102
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Grouping allows you to batch data based on complex conditions:
|
|
103
|
+
## Core Concepts
|
|
156
104
|
|
|
157
|
-
|
|
158
|
-
const transactions = new Stream<{ amount: number; userId: string }>();
|
|
159
|
-
|
|
160
|
-
// Batch by count
|
|
161
|
-
const countBatches = transactions.group((batch) => batch.length >= 100);
|
|
105
|
+
### Streams: Async Data Pipelines
|
|
162
106
|
|
|
163
|
-
|
|
164
|
-
const amountBatches = transactions.group(
|
|
165
|
-
0, // initial sum
|
|
166
|
-
(sum, transaction) => {
|
|
167
|
-
const newSum = sum + transaction.amount;
|
|
168
|
-
return [newSum >= 10000, newSum >= 10000 ? 0 : newSum];
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// Process batches efficiently
|
|
173
|
-
amountBatches.listen(async (totalAmount) => {
|
|
174
|
-
await processBulkTransaction(totalAmount);
|
|
175
|
-
});
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
#### Flat: Flatten Nested Arrays
|
|
179
|
-
|
|
180
|
-
Flatten nested arrays in stream values:
|
|
107
|
+
A `Stream` is an async iterable that can push values to multiple listeners while also being awaitable for the next value.
|
|
181
108
|
|
|
182
109
|
```typescript
|
|
183
|
-
const
|
|
184
|
-
const flattened = arrays.flat();
|
|
110
|
+
const userEvents = new Stream<UserEvent>();
|
|
185
111
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
112
|
+
// Multiple consumers
|
|
113
|
+
userEvents.listen((event) => analytics.track(event));
|
|
114
|
+
userEvents.listen((event) => notifications.send(event));
|
|
115
|
+
userEvents.listen((event) => database.save(event));
|
|
189
116
|
|
|
190
|
-
//
|
|
191
|
-
const
|
|
192
|
-
const deepFlat = nested.flat(2);
|
|
117
|
+
// Or await the next event
|
|
118
|
+
const nextEvent = await userEvents;
|
|
193
119
|
```
|
|
194
120
|
|
|
195
|
-
|
|
121
|
+
### Transformers: Fluent & Functional Styles
|
|
196
122
|
|
|
197
|
-
|
|
123
|
+
#### Method Chaining (Fluent)
|
|
198
124
|
|
|
199
125
|
```typescript
|
|
200
126
|
const stream = new Stream<number>();
|
|
201
127
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const nextValue = await stream;
|
|
208
|
-
console.log("Received:", nextValue);
|
|
209
|
-
})()(async () => {
|
|
210
|
-
// Or use .then() for transformation
|
|
211
|
-
const doubled = await stream.then((x) => x * 2);
|
|
212
|
-
})();
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
#### Listener Events
|
|
216
|
-
|
|
217
|
-
React to listener lifecycle changes on streams:
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
const stream = new Stream<number>();
|
|
221
|
-
|
|
222
|
-
// Listen to listener lifecycle
|
|
223
|
-
stream.listenerAdded.listen(() => console.log("Listener added"));
|
|
224
|
-
stream.listenerRemoved.listen(() => console.log("Listener removed"));
|
|
225
|
-
|
|
226
|
-
const cleanup = stream.listen((value) => console.log(value));
|
|
227
|
-
cleanup(); // Triggers 'Listener removed'
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Functional Programming style: Composable Data Transformations
|
|
231
|
-
|
|
232
|
-
The library provides functional programming patterns through the `pipe` method and transformer functions, enabling powerful composition and reusable transformation pipelines.
|
|
233
|
-
|
|
234
|
-
### Pipe: Functional Composition Made Easy
|
|
235
|
-
|
|
236
|
-
The `pipe` method enables functional composition of stream transformations:
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
import { Stream, map, filter, group, merge, flat } from "@soffinal/stream";
|
|
240
|
-
|
|
241
|
-
const numbers = new Stream<number>();
|
|
128
|
+
// Simple transformations
|
|
129
|
+
const result = stream
|
|
130
|
+
.filter((x) => x > 0)
|
|
131
|
+
.map((x) => x * 2)
|
|
132
|
+
.group((batch) => batch.length >= 5);
|
|
242
133
|
|
|
243
|
-
//
|
|
244
|
-
const
|
|
245
|
-
.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
134
|
+
// Async operations with order preservation
|
|
135
|
+
const asyncProcessed = stream
|
|
136
|
+
.filter(async (value) => {
|
|
137
|
+
const isValid = await validateAsync(value);
|
|
138
|
+
return isValid;
|
|
139
|
+
})
|
|
140
|
+
.map(async (value) => {
|
|
141
|
+
const enriched = await enrichWithExternalData(value);
|
|
142
|
+
return { original: value, enriched };
|
|
143
|
+
});
|
|
249
144
|
|
|
250
|
-
|
|
145
|
+
// Stateful operations with initial state
|
|
146
|
+
const stateful = stream
|
|
147
|
+
.filter(0, (prev, curr) => [curr > prev, Math.max(prev, curr)]) // Only increasing values
|
|
148
|
+
.map([], (history, value) => {
|
|
149
|
+
const newHistory = [...history, value].slice(-3); // Keep last 3
|
|
150
|
+
const average = newHistory.reduce((a, b) => a + b, 0) / newHistory.length;
|
|
151
|
+
return [{ value, average, history: newHistory }, newHistory];
|
|
152
|
+
})
|
|
153
|
+
.group(5, (count, item) => [count >= 5, count >= 5 ? 0 : count + 1]); // Batch every 5
|
|
251
154
|
```
|
|
252
155
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
The library provides irreducible building block transformers that work seamlessly with pipe:
|
|
156
|
+
#### Functional Composition (Pipe)
|
|
256
157
|
|
|
257
158
|
```typescript
|
|
258
|
-
|
|
259
|
-
import { map, filter, group, merge, flat } from "@soffinal/stream";
|
|
159
|
+
import { filter, map, group, merge } from "@soffinal/stream";
|
|
260
160
|
|
|
261
|
-
|
|
161
|
+
// Simple functional composition
|
|
162
|
+
const result = stream
|
|
163
|
+
.pipe(filter((x) => x > 0))
|
|
164
|
+
.pipe(map((x) => x * 2))
|
|
165
|
+
.pipe(group((batch) => batch.length >= 5));
|
|
262
166
|
|
|
263
|
-
//
|
|
264
|
-
stream
|
|
265
|
-
.pipe(
|
|
266
|
-
.pipe(
|
|
267
|
-
.pipe(
|
|
167
|
+
// Async functional transformers (order preserved)
|
|
168
|
+
const asyncPipeline = stream
|
|
169
|
+
.pipe(filter(async (x) => await isValidAsync(x)))
|
|
170
|
+
.pipe(map(async (x) => await processAsync(x)))
|
|
171
|
+
.pipe(group(batch => batch.length >= 5));
|
|
268
172
|
|
|
269
|
-
// Stateful
|
|
270
|
-
stream
|
|
173
|
+
// Stateful functional transformers
|
|
174
|
+
const advanced = stream
|
|
271
175
|
.pipe(filter(0, (prev, curr) => [curr > prev, Math.max(prev, curr)]))
|
|
272
|
-
.pipe(
|
|
176
|
+
.pipe(
|
|
177
|
+
map([], (acc, value) => {
|
|
178
|
+
const newAcc = [...acc, value].slice(-10);
|
|
179
|
+
const sum = newAcc.reduce((a, b) => a + b, 0);
|
|
180
|
+
return [{ value, sum, count: newAcc.length }, newAcc];
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
.pipe(group(0, (count, item) => [count >= 3, count >= 3 ? 0 : count + 1]));
|
|
273
184
|
|
|
274
|
-
//
|
|
185
|
+
// Combining multiple streams
|
|
275
186
|
const stream2 = new Stream<number>();
|
|
276
|
-
stream
|
|
277
|
-
.pipe(
|
|
278
|
-
.pipe(
|
|
279
|
-
.pipe(
|
|
187
|
+
const merged = stream
|
|
188
|
+
.pipe(filter((x) => x % 2 === 0))
|
|
189
|
+
.pipe(merge(stream2))
|
|
190
|
+
.pipe(map((x) => x.toString()));
|
|
280
191
|
```
|
|
281
192
|
|
|
282
|
-
### Custom Transformers
|
|
193
|
+
### Custom Transformers
|
|
283
194
|
|
|
284
|
-
|
|
195
|
+
Create reusable transformers for both paradigms:
|
|
285
196
|
|
|
286
197
|
```typescript
|
|
287
|
-
//
|
|
198
|
+
// Custom transformer: distinctUntilChanged
|
|
288
199
|
const distinctUntilChanged = <T>(stream: Stream<T>): Stream<T> => {
|
|
289
200
|
return new Stream<T>(async function* () {
|
|
290
201
|
let prev: T;
|
|
@@ -300,7 +211,7 @@ const distinctUntilChanged = <T>(stream: Stream<T>): Stream<T> => {
|
|
|
300
211
|
});
|
|
301
212
|
};
|
|
302
213
|
|
|
303
|
-
//
|
|
214
|
+
// Custom transformer: throttle
|
|
304
215
|
const throttle =
|
|
305
216
|
<T>(ms: number) =>
|
|
306
217
|
(stream: Stream<T>): Stream<T> => {
|
|
@@ -320,272 +231,144 @@ const throttle =
|
|
|
320
231
|
// Usage with perfect type inference
|
|
321
232
|
const searchInput = new Stream<string>();
|
|
322
233
|
const searchResults = searchInput
|
|
323
|
-
.pipe(distinctUntilChanged)
|
|
324
|
-
.pipe(throttle(
|
|
234
|
+
.pipe(distinctUntilChanged)
|
|
235
|
+
.pipe(throttle(300))
|
|
325
236
|
.pipe(map((query) => searchAPI(query)));
|
|
326
237
|
```
|
|
327
238
|
|
|
328
|
-
###
|
|
329
|
-
|
|
330
|
-
@soffinal/stream supports both Object-Oriented and Functional programming styles:
|
|
331
|
-
|
|
332
|
-
#### Object-Oriented Style (Method Chaining)
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
// Familiar method chaining
|
|
336
|
-
const result = stream
|
|
337
|
-
.filter((x) => x > 0)
|
|
338
|
-
.map((x) => x * 2)
|
|
339
|
-
.group((batch) => batch.length >= 5);
|
|
340
|
-
|
|
341
|
-
// Rich instance methods with overloads
|
|
342
|
-
stream
|
|
343
|
-
.filter(0, (prev, curr) => [curr > prev, curr]) // Stateful filtering
|
|
344
|
-
.map(async (x) => await enrich(x)); // Async mapping
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
#### Functional Style (Pipe Composition)
|
|
239
|
+
### Reactive State
|
|
348
240
|
|
|
349
|
-
|
|
350
|
-
// Composable transformers
|
|
351
|
-
const result = stream
|
|
352
|
-
.pipe(filter((x) => x > 0))
|
|
353
|
-
.pipe(map((x) => x * 2))
|
|
354
|
-
.pipe(group((batch) => batch.length >= 5));
|
|
355
|
-
|
|
356
|
-
// Reusable transformation pipelines
|
|
357
|
-
const processNumbers = (stream: Stream<number>) => stream.pipe(filter((x) => x > 0)).pipe(map((x) => x * 2));
|
|
358
|
-
|
|
359
|
-
const result1 = stream1.pipe(processNumbers);
|
|
360
|
-
const result2 = stream2.pipe(processNumbers);
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
## State: Reactive State Management
|
|
364
|
-
|
|
365
|
-
State objects are streams that hold current values, enabling reactive programming patterns:
|
|
241
|
+
State objects hold current values and notify dependents of changes:
|
|
366
242
|
|
|
367
243
|
```typescript
|
|
368
|
-
import { State } from "@soffinal/stream";
|
|
369
|
-
|
|
370
|
-
// Application state
|
|
371
244
|
const user = new State<User | null>(null);
|
|
372
245
|
const theme = new State<"light" | "dark">("light");
|
|
373
|
-
const notifications = new State<Notification[]>([]);
|
|
374
246
|
|
|
375
|
-
//
|
|
247
|
+
// Derived state with transformations
|
|
376
248
|
const isLoggedIn = user.map((u) => u !== null);
|
|
377
|
-
const
|
|
249
|
+
const userProfile = user
|
|
250
|
+
.filter((u): u is User => u !== null)
|
|
251
|
+
.map((u) => ({ ...u, displayName: u.firstName + " " + u.lastName }));
|
|
378
252
|
|
|
379
253
|
// Automatic UI updates
|
|
380
254
|
isLoggedIn.listen((loggedIn) => {
|
|
381
|
-
document.body.classList.toggle("
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
unreadCount.listen((count) => {
|
|
385
|
-
document.title = count > 0 ? `(${count}) My App` : "My App";
|
|
255
|
+
document.body.classList.toggle("authenticated", loggedIn);
|
|
386
256
|
});
|
|
387
257
|
```
|
|
388
258
|
|
|
389
|
-
###
|
|
259
|
+
### Reactive Collections
|
|
390
260
|
|
|
391
|
-
|
|
392
|
-
interface AppState {
|
|
393
|
-
user: { id: string; name: string } | null;
|
|
394
|
-
notifications: string[];
|
|
395
|
-
theme: "light" | "dark";
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const appState = new State<AppState>({
|
|
399
|
-
user: null,
|
|
400
|
-
notifications: [],
|
|
401
|
-
theme: "light",
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
const isLoggedIn = appState.map((state) => state.user !== null);
|
|
405
|
-
const notificationCount = appState.map((state) => state.notifications.length);
|
|
406
|
-
|
|
407
|
-
// React to login state
|
|
408
|
-
isLoggedIn.listen((loggedIn) => {
|
|
409
|
-
if (loggedIn) {
|
|
410
|
-
console.log("User logged in");
|
|
411
|
-
loadUserData();
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Update state immutably
|
|
416
|
-
function login(user: { id: string; name: string }) {
|
|
417
|
-
appState.value = {
|
|
418
|
-
...appState.value,
|
|
419
|
-
user,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function addNotification(message: string) {
|
|
424
|
-
appState.value = {
|
|
425
|
-
...appState.value,
|
|
426
|
-
notifications: [...appState.value.notifications, message],
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
## Reactive Collections: Data Structures That Notify
|
|
432
|
-
|
|
433
|
-
The library extends JavaScript's native collections with reactive capabilities. Every mutation emits events, allowing you to build reactive UIs and data synchronization systems.
|
|
434
|
-
|
|
435
|
-
### List: Reactive Arrays
|
|
436
|
-
|
|
437
|
-
Reactive Lists provide array-like functionality with fine-grained change notifications:
|
|
261
|
+
Collections that emit fine-grained change events:
|
|
438
262
|
|
|
439
263
|
```typescript
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const todos = new List<{ id: string; text: string; done: boolean }>();
|
|
444
|
-
|
|
445
|
-
// Build reactive UI components
|
|
446
|
-
todos.insert.listen(([index, todo]) => {
|
|
447
|
-
const element = createTodoElement(todo);
|
|
448
|
-
todoContainer.insertBefore(element, todoContainer.children[index]);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
todos.delete.listen(([index, todo]) => {
|
|
452
|
-
todoContainer.children[index].remove();
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Reactive operations
|
|
456
|
-
const completedCount = new State(0);
|
|
457
|
-
todos.insert.listen(() => updateCompletedCount());
|
|
458
|
-
todos.delete.listen(() => updateCompletedCount());
|
|
459
|
-
|
|
460
|
-
function updateCompletedCount() {
|
|
461
|
-
const completed = [...todos].filter((t) => t.done).length;
|
|
462
|
-
completedCount.value = completed;
|
|
463
|
-
}
|
|
264
|
+
const todos = new List<Todo>();
|
|
265
|
+
const cache = new Map<string, any>();
|
|
266
|
+
const activeUsers = new Set<string>();
|
|
464
267
|
|
|
465
|
-
//
|
|
466
|
-
todos.insert.listen(() =>
|
|
467
|
-
|
|
268
|
+
// React to changes
|
|
269
|
+
todos.insert.listen(([index, todo]) => renderTodo(todo, index));
|
|
270
|
+
cache.set.listen(([key, value]) => console.log(`Cached: ${key}`));
|
|
271
|
+
activeUsers.add.listen((userId) => showOnlineStatus(userId));
|
|
468
272
|
```
|
|
469
273
|
|
|
470
|
-
|
|
274
|
+
## API Reference
|
|
471
275
|
|
|
472
|
-
|
|
276
|
+
### Stream\<T>
|
|
473
277
|
|
|
474
|
-
|
|
475
|
-
import { Map } from "@soffinal/stream";
|
|
278
|
+
#### Properties
|
|
476
279
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
cache.set.listen(([key, value]) => {
|
|
481
|
-
console.log(`Cache updated: ${key} = ${value}`);
|
|
482
|
-
});
|
|
280
|
+
- `hasListeners: boolean` - Whether stream has active listeners
|
|
281
|
+
- `listenerAdded: Stream<void>` - Emits when listener is added
|
|
282
|
+
- `listenerRemoved: Stream<void>` - Emits when listener is removed
|
|
483
283
|
|
|
484
|
-
|
|
485
|
-
cache.delete.listen(([key, value]) => {
|
|
486
|
-
console.log(`Cache evicted: ${key}`);
|
|
487
|
-
});
|
|
284
|
+
#### Methods
|
|
488
285
|
|
|
489
|
-
|
|
490
|
-
|
|
286
|
+
- `push(...values: T[]): void` - Emit values to all listeners
|
|
287
|
+
- `listen(callback: (value: T) => void, signal?: AbortSignal): () => void` - Add listener, returns cleanup function
|
|
288
|
+
- `filter<U extends T>(predicate: (value: T) => value is U): Stream<U>` - Filter with type guard
|
|
289
|
+
- `filter(predicate: (value: T) => boolean | Promise<boolean>): Stream<T>` - Filter with async predicate
|
|
290
|
+
- `filter<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): Stream<T>` - Stateful filtering
|
|
291
|
+
- `map<U>(mapper: (value: T) => U | Promise<U>): Stream<U>` - Transform with async mapper
|
|
292
|
+
- `map<S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): Stream<U>` - Stateful mapping
|
|
293
|
+
- `group(predicate: (batch: T[]) => boolean | Promise<boolean>): Stream<T[]>` - Group into batches
|
|
294
|
+
- `group<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): Stream<S>` - Stateful grouping
|
|
295
|
+
- `merge<STREAMS extends [Stream<any>, ...Stream<any>[]]>(...streams: STREAMS): Stream<T | ValueOf<STREAMS[number]>>` - Merge multiple streams
|
|
296
|
+
- `flat<DEPTH extends number = 0>(depth?: DEPTH): Stream<FlatArray<T, DEPTH>>` - Flatten arrays with configurable depth
|
|
297
|
+
- `pipe<U>(transformer: (stream: Stream<T>) => Stream<U>): Stream<U>` - Apply functional transformer
|
|
491
298
|
|
|
492
|
-
|
|
493
|
-
cache.set("user:123", { name: "John" }); // No emission (same value)
|
|
494
|
-
cache.delete("user:123"); // Cache evicted: user:123
|
|
495
|
-
cache.delete("nonexistent"); // No emission (didn't exist)
|
|
299
|
+
#### Async Interface
|
|
496
300
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
console.log(cache.has("key"));
|
|
500
|
-
for (const [key, value] of cache) {
|
|
501
|
-
console.log(key, value);
|
|
502
|
-
}
|
|
503
|
-
```
|
|
301
|
+
- `then<U>(callback?: (value: T) => U): Promise<U>` - Promise interface
|
|
302
|
+
- `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration
|
|
504
303
|
|
|
505
|
-
###
|
|
304
|
+
### State\<T> extends Stream\<T>
|
|
506
305
|
|
|
507
|
-
|
|
306
|
+
#### Properties
|
|
508
307
|
|
|
509
|
-
|
|
510
|
-
import { Set } from "@soffinal/stream";
|
|
308
|
+
- `value: T` - Current state value (get/set)
|
|
511
309
|
|
|
512
|
-
|
|
310
|
+
### Transformer Functions
|
|
513
311
|
|
|
514
|
-
|
|
515
|
-
activeUsers.add.listen((userId) => {
|
|
516
|
-
console.log(`User ${userId} came online`);
|
|
517
|
-
broadcastUserStatus(userId, "online");
|
|
518
|
-
});
|
|
312
|
+
Functional transformers for use with `pipe()` - all support async operations:
|
|
519
313
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
});
|
|
314
|
+
#### filter
|
|
315
|
+
- `filter<T, U extends T>(predicate: (value: T) => value is U): (stream: Stream<T>) => Stream<U>`
|
|
316
|
+
- `filter<T>(predicate: (value: T) => boolean | Promise<boolean>): (stream: Stream<T>) => Stream<T>`
|
|
317
|
+
- `filter<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): (stream: Stream<T>) => Stream<T>`
|
|
525
318
|
|
|
526
|
-
|
|
527
|
-
|
|
319
|
+
#### map
|
|
320
|
+
- `map<T, U>(mapper: (value: T) => U | Promise<U>): (stream: Stream<T>) => Stream<U>`
|
|
321
|
+
- `map<T, S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): (stream: Stream<T>) => Stream<U>`
|
|
528
322
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
activeUsers.delete("bob"); // No emission (didn't exist)
|
|
323
|
+
#### group
|
|
324
|
+
- `group<T>(predicate: (batch: T[]) => boolean | Promise<boolean>): (stream: Stream<T>) => Stream<T[]>`
|
|
325
|
+
- `group<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): (stream: Stream<T>) => Stream<S>`
|
|
533
326
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
console.log(activeUsers.has("alice"));
|
|
537
|
-
for (const user of activeUsers) {
|
|
538
|
-
console.log(user);
|
|
539
|
-
}
|
|
540
|
-
```
|
|
327
|
+
#### merge
|
|
328
|
+
- `merge<STREAMS extends [Stream<any>, ...Stream<any>[]]>(...streams: STREAMS): <T>(stream: Stream<T>) => Stream<T | ValueOf<STREAMS[number]>>`
|
|
541
329
|
|
|
542
|
-
|
|
330
|
+
#### flat
|
|
331
|
+
- `flat(): <T>(stream: Stream<T>) => Stream<FlatArray<T, 0>>`
|
|
332
|
+
- `flat<DEPTH extends number>(depth: DEPTH): <T>(stream: Stream<T>) => Stream<FlatArray<T, DEPTH>>`
|
|
543
333
|
|
|
544
|
-
|
|
334
|
+
### Reactive Collections
|
|
545
335
|
|
|
546
|
-
|
|
336
|
+
#### List\<T>
|
|
547
337
|
|
|
548
|
-
|
|
338
|
+
- `insert: Stream<[number, T]>` - Insertion events
|
|
339
|
+
- `delete: Stream<[number, T]>` - Deletion events
|
|
340
|
+
- `clear: Stream<void>` - Clear events
|
|
549
341
|
|
|
550
|
-
|
|
551
|
-
const sensorData = new Stream<{ temperature: number; humidity: number }>();
|
|
342
|
+
#### Map\<K,V> extends globalThis.Map\<K,V>
|
|
552
343
|
|
|
553
|
-
|
|
554
|
-
|
|
344
|
+
- `set: Stream<[K, V]>` - Set events (only on changes)
|
|
345
|
+
- `delete: Stream<[K, V]>` - Delete events
|
|
346
|
+
- `clear: Stream<void>` - Clear events
|
|
555
347
|
|
|
556
|
-
|
|
557
|
-
sensorData.map((data) => ({ ...data, timestamp: Date.now() })).listen((data) => saveToDatabase(data));
|
|
348
|
+
#### Set\<T> extends globalThis.Set\<T>
|
|
558
349
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
temperature: Math.random() * 40,
|
|
563
|
-
humidity: Math.random() * 100,
|
|
564
|
-
});
|
|
565
|
-
}, 1000);
|
|
566
|
-
```
|
|
350
|
+
- `add: Stream<T>` - Add events (only new values)
|
|
351
|
+
- `delete: Stream<T>` - Delete events
|
|
352
|
+
- `clear: Stream<void>` - Clear events
|
|
567
353
|
|
|
568
|
-
|
|
354
|
+
## Examples
|
|
569
355
|
|
|
570
|
-
|
|
356
|
+
### Real-time Data Processing
|
|
571
357
|
|
|
572
358
|
```typescript
|
|
573
|
-
const
|
|
359
|
+
const sensorData = new Stream<SensorReading>();
|
|
574
360
|
|
|
575
|
-
//
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
})
|
|
361
|
+
// Multi-stage processing pipeline
|
|
362
|
+
const alerts = sensorData
|
|
363
|
+
.filter((reading) => reading.temperature > 30)
|
|
364
|
+
.map((reading) => ({ ...reading, timestamp: Date.now() }))
|
|
365
|
+
.group((batch) => batch.length >= 5);
|
|
579
366
|
|
|
580
|
-
|
|
581
|
-
console.log(`Processed ${count} actions`);
|
|
582
|
-
});
|
|
367
|
+
alerts.listen((batch) => sendAlertNotification(batch));
|
|
583
368
|
```
|
|
584
369
|
|
|
585
370
|
### WebSocket Integration
|
|
586
371
|
|
|
587
|
-
Create reactive streams from WebSocket connections:
|
|
588
|
-
|
|
589
372
|
```typescript
|
|
590
373
|
const wsStream = new Stream<MessageEvent>(async function* () {
|
|
591
374
|
const ws = new WebSocket("wss://api.example.com");
|
|
@@ -597,214 +380,150 @@ const wsStream = new Stream<MessageEvent>(async function* () {
|
|
|
597
380
|
}
|
|
598
381
|
});
|
|
599
382
|
|
|
600
|
-
wsStream
|
|
601
|
-
.map((event) => JSON.parse(event.data))
|
|
602
|
-
.filter((data) => data.type === "update")
|
|
603
|
-
.listen((update) => handleUpdate(update));
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
### Real-time Data Processing Pipeline
|
|
607
|
-
|
|
608
|
-
Build sophisticated data processing systems:
|
|
383
|
+
const messages = wsStream.map((event) => JSON.parse(event.data)).filter((data) => data.type === "update");
|
|
609
384
|
|
|
610
|
-
|
|
611
|
-
// Raw sensor data stream
|
|
612
|
-
const sensorData = new Stream<SensorReading>();
|
|
613
|
-
|
|
614
|
-
// Multi-stage processing pipeline
|
|
615
|
-
const processedData = sensorData
|
|
616
|
-
// 1. Validate data quality
|
|
617
|
-
.filter(async (reading) => {
|
|
618
|
-
return await validateSensorReading(reading);
|
|
619
|
-
})
|
|
620
|
-
// 2. Normalize and enrich
|
|
621
|
-
.map(async (reading) => {
|
|
622
|
-
const location = await getLocationData(reading.sensorId);
|
|
623
|
-
return {
|
|
624
|
-
...reading,
|
|
625
|
-
location,
|
|
626
|
-
normalized: normalizeReading(reading.value),
|
|
627
|
-
};
|
|
628
|
-
})
|
|
629
|
-
// 3. Detect anomalies using sliding window
|
|
630
|
-
.group(
|
|
631
|
-
{ readings: [], sum: 0 }, // sliding window state
|
|
632
|
-
(window, reading) => {
|
|
633
|
-
const newWindow = {
|
|
634
|
-
readings: [...window.readings, reading].slice(-10), // keep last 10
|
|
635
|
-
sum: window.sum + reading.normalized,
|
|
636
|
-
};
|
|
637
|
-
const average = newWindow.sum / newWindow.readings.length;
|
|
638
|
-
const isAnomaly = Math.abs(reading.normalized - average) > threshold;
|
|
639
|
-
|
|
640
|
-
return [isAnomaly, newWindow];
|
|
641
|
-
}
|
|
642
|
-
)
|
|
643
|
-
// 4. Batch anomalies for processing
|
|
644
|
-
.group((batch) => batch.length >= 5 || Date.now() - batch[0]?.timestamp > 30000);
|
|
645
|
-
|
|
646
|
-
// Multiple consumers for different purposes
|
|
647
|
-
processedData.listen((anomalies) => sendAlerts(anomalies));
|
|
648
|
-
processedData.listen((anomalies) => updateDashboard(anomalies));
|
|
649
|
-
processedData.listen((anomalies) => logToDatabase(anomalies));
|
|
385
|
+
messages.listen((update) => handleUpdate(update));
|
|
650
386
|
```
|
|
651
387
|
|
|
652
|
-
###
|
|
653
|
-
|
|
654
|
-
Properly clean up listeners and prevent memory leaks:
|
|
388
|
+
### State Management
|
|
655
389
|
|
|
656
390
|
```typescript
|
|
657
|
-
|
|
391
|
+
interface AppState {
|
|
392
|
+
user: User | null;
|
|
393
|
+
theme: "light" | "dark";
|
|
394
|
+
notifications: Notification[];
|
|
395
|
+
}
|
|
658
396
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
397
|
+
const appState = new State<AppState>({
|
|
398
|
+
user: null,
|
|
399
|
+
theme: "light",
|
|
400
|
+
notifications: [],
|
|
401
|
+
});
|
|
662
402
|
|
|
663
|
-
//
|
|
664
|
-
|
|
403
|
+
// Derived state
|
|
404
|
+
const unreadCount = appState.map((state) => state.notifications.filter((n) => !n.read).length);
|
|
665
405
|
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
|
|
406
|
+
// Reactive UI
|
|
407
|
+
unreadCount.listen((count) => {
|
|
408
|
+
document.title = count > 0 ? `(${count}) App` : "App";
|
|
409
|
+
});
|
|
669
410
|
```
|
|
670
411
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
412
|
+
### Browser Counter Example
|
|
413
|
+
|
|
414
|
+
```html
|
|
415
|
+
<!DOCTYPE html>
|
|
416
|
+
<html>
|
|
417
|
+
<head>
|
|
418
|
+
<title>Reactive Counter</title>
|
|
419
|
+
</head>
|
|
420
|
+
<body>
|
|
421
|
+
<div id="counter">0</div>
|
|
422
|
+
<button id="increment">+</button>
|
|
423
|
+
<button id="decrement">-</button>
|
|
424
|
+
|
|
425
|
+
<script type="module">
|
|
426
|
+
import { State } from "https://esm.sh/@soffinal/stream";
|
|
427
|
+
|
|
428
|
+
const count = new State(0);
|
|
429
|
+
const display = document.getElementById("counter");
|
|
430
|
+
|
|
431
|
+
// Reactive UI updates
|
|
432
|
+
count.listen((value) => (display.textContent = value));
|
|
433
|
+
|
|
434
|
+
// User interactions
|
|
435
|
+
document.getElementById("increment").onclick = () => count.value++;
|
|
436
|
+
document.getElementById("decrement").onclick = () => count.value--;
|
|
437
|
+
</script>
|
|
438
|
+
</body>
|
|
439
|
+
</html>
|
|
679
440
|
```
|
|
680
441
|
|
|
681
|
-
|
|
442
|
+
## Performance
|
|
682
443
|
|
|
683
|
-
|
|
684
|
-
deno add npm:@soffinal/stream
|
|
685
|
-
# or from JSR
|
|
686
|
-
deno add jsr:@soffinal/stream
|
|
444
|
+
@soffinal/stream delivers exceptional performance:
|
|
687
445
|
|
|
688
|
-
|
|
446
|
+
- **High throughput** for basic operations
|
|
447
|
+
- **Efficient processing** for complex pipelines
|
|
448
|
+
- **Memory efficient** with automatic cleanup
|
|
449
|
+
- **Zero overhead** for unused features
|
|
689
450
|
|
|
690
|
-
|
|
451
|
+
### Performance Characteristics
|
|
691
452
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
```
|
|
453
|
+
- **Optimized data structures** for minimal memory allocation
|
|
454
|
+
- **Efficient event dispatch** with smart caching
|
|
455
|
+
- **Lazy evaluation** reduces unnecessary computations
|
|
456
|
+
- **Automatic cleanup** prevents memory leaks
|
|
697
457
|
|
|
698
|
-
|
|
458
|
+
## Browser Support
|
|
699
459
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
460
|
+
- **Modern browsers** supporting ES2020+
|
|
461
|
+
- **Node.js** 16+
|
|
462
|
+
- **Deno** 1.0+
|
|
463
|
+
- **Bun** 1.0+
|
|
464
|
+
- **Cloudflare Workers**
|
|
704
465
|
|
|
705
|
-
|
|
466
|
+
## Migration Guide
|
|
706
467
|
|
|
707
|
-
|
|
468
|
+
### From RxJS
|
|
708
469
|
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
|
|
470
|
+
```typescript
|
|
471
|
+
// RxJS
|
|
472
|
+
import { Subject, map, filter } from "rxjs";
|
|
473
|
+
const subject = new Subject();
|
|
474
|
+
subject
|
|
475
|
+
.pipe(
|
|
476
|
+
filter((x) => x > 0),
|
|
477
|
+
map((x) => x * 2)
|
|
478
|
+
)
|
|
479
|
+
.subscribe(console.log);
|
|
712
480
|
|
|
481
|
+
// @soffinal/stream
|
|
482
|
+
import { Stream } from "@soffinal/stream";
|
|
483
|
+
const stream = new Stream();
|
|
484
|
+
stream
|
|
485
|
+
.filter((x) => x > 0)
|
|
486
|
+
.map((x) => x * 2)
|
|
487
|
+
.listen(console.log);
|
|
713
488
|
```
|
|
714
489
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
### Stream<T>
|
|
718
|
-
|
|
719
|
-
**Properties:**
|
|
720
|
-
|
|
721
|
-
- `hasListeners: boolean` - True if stream has active listeners
|
|
722
|
-
- `listenerAdded: Stream<void>` - Emits when listener is added
|
|
723
|
-
- `listenerRemoved: Stream<void>` - Emits when listener is removed
|
|
724
|
-
|
|
725
|
-
**Methods:**
|
|
726
|
-
|
|
727
|
-
- `push(...values: T[]): void` - Emit values to all listeners
|
|
728
|
-
- `listen(callback: (value: T) => void, signal?: AbortSignal): () => void` - Add listener, returns cleanup function
|
|
729
|
-
- `filter<U extends T>(predicate: (value: T) => value is U): Stream<U>` - Filter values with type guard
|
|
730
|
-
- `filter(predicate: (value: T) => boolean | Promise<boolean>): Stream<T>` - Filter values with predicate
|
|
731
|
-
- `filter<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S] | Promise<[boolean, S]>): Stream<T>` - Stateful filtering
|
|
732
|
-
- `map<U>(mapper: (value: T) => U | Promise<U>): Stream<U>` - Transform values
|
|
733
|
-
- `map<S, U>(initialState: S, accumulator: (state: S, value: T) => [U, S] | Promise<[U, S]>): Stream<U>` - Stateful mapping
|
|
734
|
-
- `merge(...streams: Stream<T>[]): Stream<T>` - Combine multiple streams
|
|
735
|
-
- `group(predicate: (batch: T[]) => boolean): Stream<T[]>` - Group values into batches
|
|
736
|
-
- `group<S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S]): Stream<T[]>` - Stateful grouping
|
|
737
|
-
- `flat<U>(depth?: number): Stream<U>` - Flatten array values (default depth: 0)
|
|
738
|
-
- `pipe<U>(transformer: (stream: Stream<T>) => Stream<U>): Stream<U>` - Apply functional transformer
|
|
739
|
-
- `then<U>(callback?: (value: T) => U): Promise<U>` - Promise-like interface for first value
|
|
740
|
-
- `[Symbol.asyncIterator](): AsyncIterator<T>` - Async iteration support
|
|
741
|
-
|
|
742
|
-
### Transformer Functions
|
|
743
|
-
|
|
744
|
-
**Built-in transformers for functional composition:**
|
|
490
|
+
### From EventEmitter
|
|
745
491
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
- `group<T, S>(initialState: S, accumulator: (state: S, value: T) => [boolean, S]): (stream: Stream<T>) => Stream<T[]>` - Stateful grouping
|
|
753
|
-
- `merge<T>(...streams: Stream<T>[]): (stream: Stream<T>) => Stream<T>` - Merge multiple streams into one
|
|
754
|
-
- `flat<T, U>(depth?: number): (stream: Stream<T>) => Stream<U>` - Flatten array values with configurable depth
|
|
755
|
-
|
|
756
|
-
### State<T> extends Stream<T>
|
|
757
|
-
|
|
758
|
-
**Properties:**
|
|
759
|
-
|
|
760
|
-
- `value: T` - Get/set current state value
|
|
761
|
-
|
|
762
|
-
**Methods:**
|
|
763
|
-
|
|
764
|
-
- `push(...values: T[]): void` - Update state with multiple values sequentially
|
|
765
|
-
|
|
766
|
-
### List<T>
|
|
767
|
-
|
|
768
|
-
**Properties:**
|
|
769
|
-
|
|
770
|
-
- `length: number` - Current list length
|
|
771
|
-
- `insert: Stream<[number, T]>` - Emits on insertions
|
|
772
|
-
- `delete: Stream<[number, T]>` - Emits on deletions
|
|
773
|
-
- `clear: Stream<void>` - Emits on clear
|
|
774
|
-
- `[index]: T` - Index access with modulo wrapping
|
|
775
|
-
|
|
776
|
-
**Methods:**
|
|
777
|
-
|
|
778
|
-
- `insert(index: number, value: T): void` - Insert value at index
|
|
779
|
-
- `delete(index: number): T | undefined` - Delete value at index, returns deleted value
|
|
780
|
-
- `clear(): void` - Clear all items
|
|
781
|
-
- `get(index: number): T | undefined` - Get value without modulo wrapping
|
|
782
|
-
- `values(): IterableIterator<T>` - Iterator for values
|
|
783
|
-
|
|
784
|
-
### Map<K,V> extends globalThis.Map<K,V>
|
|
785
|
-
|
|
786
|
-
**Stream Properties:**
|
|
492
|
+
```typescript
|
|
493
|
+
// EventEmitter
|
|
494
|
+
import { EventEmitter } from "events";
|
|
495
|
+
const emitter = new EventEmitter();
|
|
496
|
+
emitter.on("data", console.log);
|
|
497
|
+
emitter.emit("data", "hello");
|
|
787
498
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
499
|
+
// @soffinal/stream
|
|
500
|
+
import { Stream } from "@soffinal/stream";
|
|
501
|
+
const stream = new Stream();
|
|
502
|
+
stream.listen(console.log);
|
|
503
|
+
stream.push("hello");
|
|
504
|
+
```
|
|
791
505
|
|
|
792
|
-
|
|
506
|
+
## Contributing
|
|
793
507
|
|
|
794
|
-
|
|
508
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
795
509
|
|
|
796
|
-
###
|
|
510
|
+
### Development Setup
|
|
797
511
|
|
|
798
|
-
|
|
512
|
+
```bash
|
|
513
|
+
git clone https://github.com/soffinal/stream.git
|
|
514
|
+
cd stream
|
|
515
|
+
bun install
|
|
516
|
+
bun test
|
|
517
|
+
```
|
|
799
518
|
|
|
800
|
-
|
|
801
|
-
- `delete: Stream<T>` - Emits on successful deletions
|
|
802
|
-
- `clear: Stream<void>` - Emits on clear (only if not empty)
|
|
519
|
+
## License
|
|
803
520
|
|
|
804
|
-
|
|
521
|
+
MIT © [Soffinal](https://github.com/soffinal)
|
|
805
522
|
|
|
806
|
-
|
|
523
|
+
Contact: <smari.sofiane@gmail.com>
|
|
807
524
|
|
|
808
|
-
|
|
525
|
+
---
|
|
809
526
|
|
|
810
|
-
|
|
527
|
+
<div align="center">
|
|
528
|
+
<strong>Built with ❤️ for the JavaScript community</strong>
|
|
529
|
+
</div>
|