reactor-core-ts 1.0.8-beta → 2.0.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 +587 -117
- package/dist/index.d.mts +1085 -706
- package/dist/index.d.ts +1085 -706
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -1,145 +1,615 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
1
|
+
# reactor-core-ts
|
|
2
|
+
|
|
3
|
+
A TypeScript implementation of [Reactive Streams](https://www.reactive-streams.org/), inspired by [Project Reactor](https://projectreactor.io/). Provides `Flux` and `Mono` publishers with full backpressure support, a suite of composable operators, programmable Sinks, and pluggable Schedulers.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [Flux](#flux)
|
|
10
|
+
- [Factories](#flux-factories)
|
|
11
|
+
- [Transformation](#flux-transformation)
|
|
12
|
+
- [Filtering](#flux-filtering)
|
|
13
|
+
- [Aggregation](#flux-aggregation)
|
|
14
|
+
- [Combining](#flux-combining)
|
|
15
|
+
- [Side effects](#flux-side-effects)
|
|
16
|
+
- [Scheduling](#flux-scheduling)
|
|
17
|
+
- [Utilities](#flux-utilities)
|
|
18
|
+
- [Subscribe](#flux-subscribe)
|
|
19
|
+
- [Mono](#mono)
|
|
20
|
+
- [Factories](#mono-factories)
|
|
21
|
+
- [Transformation](#mono-transformation)
|
|
22
|
+
- [Filtering](#mono-filtering)
|
|
23
|
+
- [Combining](#mono-combining)
|
|
24
|
+
- [Side effects](#mono-side-effects)
|
|
25
|
+
- [Scheduling](#mono-scheduling)
|
|
26
|
+
- [Utilities](#mono-utilities)
|
|
27
|
+
- [Subscribe](#mono-subscribe)
|
|
28
|
+
- [Sinks](#sinks)
|
|
29
|
+
- [Schedulers](#schedulers)
|
|
30
|
+
- [TypeScript generics](#typescript-generics)
|
|
31
|
+
- [Backpressure](#backpressure)
|
|
32
|
+
- [Contributing](#contributing)
|
|
33
|
+
- [License](#license)
|
|
34
|
+
|
|
35
|
+
---
|
|
12
36
|
|
|
13
37
|
## Installation
|
|
14
38
|
|
|
15
|
-
To install the package using **npm**:
|
|
16
|
-
|
|
17
39
|
```bash
|
|
18
40
|
npm install reactor-core-ts
|
|
41
|
+
# or
|
|
42
|
+
pnpm add reactor-core-ts
|
|
43
|
+
# or
|
|
44
|
+
yarn add reactor-core-ts
|
|
19
45
|
```
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
---
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
## Core Concepts
|
|
50
|
+
|
|
51
|
+
| Concept | Description |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **Publisher\<T\>** | Source of a data stream. Emits items on subscriber demand. |
|
|
54
|
+
| **Subscriber\<T\>** | Consumer of a stream. Receives `onSubscribe`, `onNext`, `onError`, `onComplete`. |
|
|
55
|
+
| **Subscription** | Handle returned by `subscribe()`. Call `request(n)` to pull items, `unsubscribe()` to cancel. |
|
|
56
|
+
| **Flux\<T\>** | 0…N items publisher. The multi-value building block. |
|
|
57
|
+
| **Mono\<T\>** | 0…1 item publisher. Ideal for single async results. |
|
|
58
|
+
| **Sink\<T\>** | Imperative push interface: `next(v)`, `error(e)`, `complete()`. |
|
|
59
|
+
| **SinkPublisher\<T\>** | A `Sink<T>` that is also a `Publisher<T>` — both push and subscribe sides in one object. |
|
|
60
|
+
| **Scheduler** | Abstraction over task execution (sync, micro-task, macro-task, delayed). |
|
|
61
|
+
|
|
62
|
+
### Backpressure
|
|
63
|
+
|
|
64
|
+
This library implements [Reactive Streams specification](https://www.reactive-streams.org/). Items are **never** pushed to a subscriber unless it has issued a `request(n)`. The convenience `subscribe()` overloads (`onNext`, `onError`, `onComplete` callbacks) issue `request(Number.MAX_SAFE_INTEGER)` for Flux and `request(1)` for Mono automatically.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Flux
|
|
69
|
+
|
|
70
|
+
`Flux<T>` is a cold publisher of 0 to N items. Each subscriber gets an independent run of the stream.
|
|
71
|
+
|
|
72
|
+
### Flux Factories
|
|
73
|
+
|
|
74
|
+
| Method | Description |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `Flux.just(...items)` | Emit each provided value in order, then complete. |
|
|
77
|
+
| `Flux.range(start, count)` | Emit integers `[start, start+count)`. |
|
|
78
|
+
| `Flux.fromIterable(iterable)` | Emit all items from any `Iterable<T>`. |
|
|
79
|
+
| `Flux.generate(sink => …)` | Imperative generator. Call `sink.next()` / `sink.complete()` / `sink.error()`. Delivery is deferred until downstream requests. |
|
|
80
|
+
| `Flux.from(publisher)` | Wrap any `Publisher<T>` as a `Flux<T>`. |
|
|
81
|
+
| `Flux.defer(factory)` | Lazily create a new Flux per subscription via the factory function. |
|
|
82
|
+
| `Flux.empty()` | Complete immediately without emitting. |
|
|
83
|
+
| `Flux.never()` | Never emit any signal. |
|
|
84
|
+
| `Flux.error(err)` | Signal `onError` immediately. |
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { Flux } from 'reactor-core-ts';
|
|
88
|
+
|
|
89
|
+
Flux.just(1, 2, 3).subscribe(v => console.log(v));
|
|
90
|
+
// 1 2 3
|
|
91
|
+
|
|
92
|
+
Flux.range(0, 5).subscribe(v => console.log(v));
|
|
93
|
+
// 0 1 2 3 4
|
|
94
|
+
|
|
95
|
+
Flux.fromIterable(['a', 'b', 'c']).subscribe(v => console.log(v));
|
|
96
|
+
// a b c
|
|
97
|
+
|
|
98
|
+
Flux.generate<number>(sink => {
|
|
99
|
+
sink.next(10);
|
|
100
|
+
sink.next(20);
|
|
101
|
+
sink.complete();
|
|
102
|
+
}).subscribe(v => console.log(v));
|
|
103
|
+
// 10 20
|
|
25
104
|
```
|
|
26
105
|
|
|
27
|
-
|
|
106
|
+
### Flux Transformation
|
|
107
|
+
|
|
108
|
+
| Method | Description |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `.map(fn)` | Transform each item `T → R`. |
|
|
111
|
+
| `.mapNotNull(fn)` | Like `map`, but `null`/`undefined` results are filtered; upstream demand is replenished for each skipped item. |
|
|
112
|
+
| `.flatMap(fn)` | Map each item to a `Flux<R>` or `Mono<R>`, subscribing to all concurrently (merge semantics). |
|
|
113
|
+
| `.concatMap(fn)` | Like `flatMap` but subscribes to each inner publisher sequentially, preserving order. |
|
|
114
|
+
| `.switchMap(fn)` | Like `flatMap` but cancels the previous inner subscription when a new outer item arrives. |
|
|
115
|
+
| `.handle(handler)` | Fine-grained per-item transform via a `Sink<R>`. The handler may emit 0 or 1 item. |
|
|
116
|
+
| `.scan(reducer)` | Emit running accumulation; first item is used as initial accumulator. |
|
|
117
|
+
| `.scanWith(seedFactory, reducer)` | Emit running accumulation starting from an explicit seed value. |
|
|
118
|
+
| `.cast<R>()` | Unsafe type cast — changes the declared element type without any runtime conversion. |
|
|
119
|
+
| `.indexed()` | Pair each item with its zero-based index: `Flux<[number, T]>`. |
|
|
28
120
|
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
const mono = Mono.just(42);
|
|
50
|
-
|
|
51
|
-
// Creating a Flux
|
|
52
|
-
const flux = Flux.range(1, 5);
|
|
53
|
-
|
|
54
|
-
// Using Schedulers for task execution
|
|
55
|
-
const immediateScheduler = Schedulers.immediate();
|
|
56
|
-
const microScheduler = Schedulers.micro();
|
|
57
|
-
const macroScheduler = Schedulers.macro();
|
|
58
|
-
const delayScheduler = Schedulers.delay(1000); // 1-second delay
|
|
59
|
-
|
|
60
|
-
// Schedule a simple task immediately
|
|
61
|
-
immediateScheduler.schedule(() => console.log("Immediate task executed"));
|
|
62
|
-
|
|
63
|
-
// Schedule a microtask
|
|
64
|
-
microScheduler.schedule(() => console.log("Microtask executed"));
|
|
65
|
-
|
|
66
|
-
// Schedule a macrotask
|
|
67
|
-
macroScheduler.schedule(() => console.log("Macrotask executed"));
|
|
68
|
-
|
|
69
|
-
// Schedule a delayed task and cancel it before execution
|
|
70
|
-
const delayedTask = delayScheduler.schedule(() => console.log("Delayed task executed"));
|
|
71
|
-
delayedTask.cancel(); // Cancels the task before it runs
|
|
72
|
-
|
|
73
|
-
// Pipe a Publisher
|
|
74
|
-
flux
|
|
75
|
-
.doFinally(() => console.log('finally')) // Executes after the pipeline completes
|
|
76
|
-
.map(value => value * 2) // Transforms each value by multiplying by 2
|
|
77
|
-
.filter(value => value % 2 === 0) // Filters even numbers
|
|
78
|
-
.concatWith(Flux.range(5, 5)) // Concatenates with another range
|
|
79
|
-
.mergeWith(Flux.range(10, 5)) // Merges with another range
|
|
80
|
-
.delayElements(5000) // Delays each emitted element by 5 seconds
|
|
81
|
-
.doFirst(() => console.log('first')) // Executes before the first emission
|
|
82
|
-
// ... for more pipes, see the JSDoc
|
|
83
|
-
// Subscribe to the publisher
|
|
84
|
-
.subscribe({
|
|
85
|
-
onNext: (value) => {
|
|
86
|
-
console.log(value);
|
|
87
|
-
},
|
|
88
|
-
onError: (error) => {
|
|
89
|
-
console.error(error);
|
|
90
|
-
},
|
|
91
|
-
onComplete: () => {
|
|
92
|
-
console.log('complete');
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
// Do not forget to request
|
|
96
|
-
.request(Number.MAX_SAFE_INTEGER);
|
|
97
|
-
|
|
98
|
-
// Sinks
|
|
99
|
-
const sink = Sinks.many().multicast<number>();
|
|
100
|
-
let count = 0;
|
|
101
|
-
|
|
102
|
-
setInterval(() => {
|
|
103
|
-
sink.next(count++); // Emit incrementing values every interval
|
|
104
|
-
}, 1000);
|
|
105
|
-
|
|
106
|
-
Flux.from(sink)
|
|
107
|
-
.subscribe({
|
|
108
|
-
onNext: (value) => console.log("Sink value:", value),
|
|
109
|
-
onError: (error) => console.error("Sink error:", error),
|
|
110
|
-
onComplete: () => console.log("Sink complete")
|
|
111
|
-
})
|
|
112
|
-
.request(5); // Request 5 values from the sink
|
|
121
|
+
```typescript
|
|
122
|
+
Flux.just(1, 2, 3)
|
|
123
|
+
.map(n => n * 2)
|
|
124
|
+
.subscribe(v => console.log(v));
|
|
125
|
+
// 2 4 6
|
|
126
|
+
|
|
127
|
+
Flux.just('hello', '', 'world')
|
|
128
|
+
.mapNotNull(s => s.length > 0 ? s.toUpperCase() : null)
|
|
129
|
+
.subscribe(v => console.log(v));
|
|
130
|
+
// HELLO WORLD
|
|
131
|
+
|
|
132
|
+
Flux.just(1, 2, 3)
|
|
133
|
+
.flatMap(n => Flux.just(n, n * 10))
|
|
134
|
+
.subscribe(v => console.log(v));
|
|
135
|
+
// 1 10 2 20 3 30 (order may vary with async inner publishers)
|
|
136
|
+
|
|
137
|
+
Flux.just(1, 2, 3, 4)
|
|
138
|
+
.scan((acc, n) => acc + n)
|
|
139
|
+
.subscribe(v => console.log(v));
|
|
140
|
+
// 1 3 6 10
|
|
113
141
|
```
|
|
114
142
|
|
|
115
|
-
|
|
143
|
+
### Flux Filtering
|
|
144
|
+
|
|
145
|
+
| Method | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `.filter(predicate)` | Pass only items matching the predicate; replenishes demand for dropped items. |
|
|
148
|
+
| `.filterWhen(predicate)` | Async filter — for each item, subscribe to the `Publisher<boolean>` returned by `predicate`; forward item only if it emits `true`. |
|
|
149
|
+
| `.take(n)` | Emit at most `n` items, then cancel upstream and complete. |
|
|
150
|
+
| `.takeWhile(predicate)` | Emit items while `predicate` returns `true`; complete on the first `false`. |
|
|
151
|
+
| `.takeUntilOther(trigger)` | Emit items until `trigger` emits any signal, then cancel and complete. |
|
|
152
|
+
| `.skip(n)` | Skip the first `n` items. |
|
|
153
|
+
| `.skipWhile(predicate)` | Skip items while `predicate` returns `true`; then pass through. |
|
|
154
|
+
| `.skipUntil(other)` | Skip items until `other` emits; then pass remaining items through. |
|
|
155
|
+
| `.distinct()` | Suppress duplicate items (equality checked by `===`). |
|
|
156
|
+
| `.distinctUntilChanged(comparator?)` | Suppress consecutive duplicate items. Custom `comparator(a, b)` returns `true` when items are considered equal. |
|
|
157
|
+
| `.defaultIfEmpty(value)` | Emit `value` if the source completes without emitting anything. |
|
|
116
158
|
|
|
117
|
-
|
|
159
|
+
```typescript
|
|
160
|
+
Flux.just(1, 2, 3, 4, 5, 6)
|
|
161
|
+
.filter(n => n % 2 === 0)
|
|
162
|
+
.subscribe(v => console.log(v));
|
|
163
|
+
// 2 4 6
|
|
164
|
+
|
|
165
|
+
Flux.just(1, 2, 3, 4, 5)
|
|
166
|
+
.take(3)
|
|
167
|
+
.subscribe(v => console.log(v));
|
|
168
|
+
// 1 2 3
|
|
169
|
+
|
|
170
|
+
Flux.just(1, 1, 2, 2, 3, 1)
|
|
171
|
+
.distinctUntilChanged()
|
|
172
|
+
.subscribe(v => console.log(v));
|
|
173
|
+
// 1 2 3 1
|
|
174
|
+
```
|
|
118
175
|
|
|
119
|
-
|
|
120
|
-
|
|
176
|
+
### Flux Aggregation
|
|
177
|
+
|
|
178
|
+
These operators reduce a Flux to a Mono.
|
|
179
|
+
|
|
180
|
+
| Method | Description |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `.count()` | `Mono<number>` — total number of items emitted. |
|
|
183
|
+
| `.first()` | `Mono<T>` — the first item, or empty if source is empty. |
|
|
184
|
+
| `.last()` | `Mono<T>` — the last item, or empty if source is empty. |
|
|
185
|
+
| `.elementAt(index, default?)` | `Mono<T>` — item at zero-based `index`. Emits `default` if out of bounds. |
|
|
186
|
+
| `.hasElements()` | `Mono<boolean>` — `true` if source emits at least one item. |
|
|
187
|
+
| `.any(predicate)` | `Mono<boolean>` — `true` if any item satisfies the predicate. |
|
|
188
|
+
| `.all(predicate)` | `Mono<boolean>` — `true` if all items satisfy the predicate. |
|
|
189
|
+
| `.none(predicate)` | `Mono<boolean>` — `true` if no item satisfies the predicate. |
|
|
190
|
+
| `.reduce(reducer)` | `Mono<T>` — reduce all items to a single value (first item is the initial accumulator). |
|
|
191
|
+
| `.reduceWith(seedFactory, reducer)` | `Mono<A>` — reduce with an explicit seed of type `A`. |
|
|
192
|
+
| `.collect()` | `Mono<T[]>` — collect all items into an array. |
|
|
193
|
+
| `.collectList()` | Alias for `.collect()`. |
|
|
194
|
+
| `.sort(comparator?)` | `Flux<T>` — collect then re-emit items in sorted order. |
|
|
195
|
+
| `.buffer(maxSize)` | `Flux<T[]>` — group items into fixed-size arrays. |
|
|
196
|
+
| `.then()` | `Mono<void>` — wait for completion; ignore all items. |
|
|
197
|
+
| `.thenEmpty(other)` | `Mono<void>` — wait for completion then subscribe to `other`. |
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
Flux.just(1, 2, 3, 4)
|
|
201
|
+
.reduce((acc, n) => acc + n)
|
|
202
|
+
.subscribe(sum => console.log(sum));
|
|
203
|
+
// 10
|
|
204
|
+
|
|
205
|
+
Flux.just('a', 'b', 'c')
|
|
206
|
+
.collect()
|
|
207
|
+
.subscribe(arr => console.log(arr));
|
|
208
|
+
// ['a', 'b', 'c']
|
|
209
|
+
|
|
210
|
+
Flux.just(3, 1, 4, 1, 5)
|
|
211
|
+
.sort()
|
|
212
|
+
.subscribe(v => console.log(v));
|
|
213
|
+
// 1 1 3 4 5
|
|
121
214
|
```
|
|
122
215
|
|
|
123
|
-
###
|
|
216
|
+
### Flux Combining
|
|
124
217
|
|
|
125
|
-
|
|
126
|
-
|
|
218
|
+
| Method | Description |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `.concatWith(other)` | Append `other` after `this` completes (sequential). |
|
|
221
|
+
| `.mergeWith(other)` | Merge `other` with `this` concurrently; items interleave as they arrive. |
|
|
222
|
+
| `.zipWith(other, combiner)` | Pair items from `this` and `other` one-by-one using `combiner(a, b) → V`. |
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
Flux.just(1, 2).concatWith(Flux.just(3, 4)).subscribe(v => console.log(v));
|
|
226
|
+
// 1 2 3 4
|
|
227
|
+
|
|
228
|
+
Flux.just(1, 2, 3).zipWith(Flux.just('a', 'b', 'c'), (n, s) => `${n}${s}`)
|
|
229
|
+
.subscribe(v => console.log(v));
|
|
230
|
+
// '1a' '2b' '3c'
|
|
127
231
|
```
|
|
128
232
|
|
|
129
|
-
###
|
|
233
|
+
### Flux Side Effects
|
|
130
234
|
|
|
131
|
-
|
|
132
|
-
|
|
235
|
+
These operators observe signals without modifying the stream.
|
|
236
|
+
|
|
237
|
+
| Method | Description |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `.doFirst(fn)` | Run `fn` before the first item is emitted. |
|
|
240
|
+
| `.doOnNext(fn)` | Run `fn` for each item. |
|
|
241
|
+
| `.doOnError(fn)` | Run `fn` when `onError` is received. |
|
|
242
|
+
| `.doOnComplete(fn)` | Run `fn` when `onComplete` is received. |
|
|
243
|
+
| `.doOnTerminate(fn)` | Run `fn` on both `onComplete` and `onError`. |
|
|
244
|
+
| `.doOnCancel(fn)` | Run `fn` when the subscription is cancelled. |
|
|
245
|
+
| `.doFinally(fn)` | Run `fn` after the stream terminates for any reason (complete, error, or cancel). |
|
|
246
|
+
| `.doOnSubscribe(fn)` | Run `fn` when `onSubscribe` is received, receiving the `Subscription` object. |
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
Flux.just(1, 2, 3)
|
|
250
|
+
.doOnNext(v => console.log('next:', v))
|
|
251
|
+
.doOnComplete(() => console.log('done'))
|
|
252
|
+
.subscribe();
|
|
253
|
+
// next: 1 next: 2 next: 3 done
|
|
133
254
|
```
|
|
134
255
|
|
|
135
|
-
|
|
256
|
+
### Flux Scheduling
|
|
257
|
+
|
|
258
|
+
| Method | Description |
|
|
259
|
+
|---|---|
|
|
260
|
+
| `.publishOn(scheduler)` | Deliver `onNext`, `onError`, `onComplete` signals through the given `Scheduler`. |
|
|
261
|
+
| `.subscribeOn(scheduler)` | Perform the upstream subscription (and therefore the source work) on the given `Scheduler`. |
|
|
262
|
+
| `.delayElements(ms)` | Delay each item by `ms` milliseconds using `Schedulers.delay(ms)`. |
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const asyncScheduler = Schedulers.macro(); // setTimeout-based
|
|
266
|
+
|
|
267
|
+
Flux.just(1, 2, 3)
|
|
268
|
+
.publishOn(asyncScheduler)
|
|
269
|
+
.subscribe(v => console.log('received asynchronously:', v));
|
|
270
|
+
|
|
271
|
+
Flux.just('a', 'b', 'c')
|
|
272
|
+
.delayElements(500)
|
|
273
|
+
.subscribe(v => console.log(v)); // each item arrives 500 ms after the previous
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Flux Utilities
|
|
277
|
+
|
|
278
|
+
| Method | Description |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `.retry(maxRetries?)` | Re-subscribe on error up to `maxRetries` times (default: unbounded). |
|
|
281
|
+
| `.cache()` | Subscribe to the source once; replay all items to subsequent subscribers. |
|
|
282
|
+
| `.switchIfEmpty(alternative)` | Switch to `alternative` if source completes without emitting. |
|
|
283
|
+
| `.onErrorReturn(replacement)` | On error, switch to `replacement` publisher. |
|
|
284
|
+
| `.onErrorContinue(predicate)` | If `predicate(err)` is `true`, complete; otherwise re-throw. |
|
|
285
|
+
| `.pipe(producer, onRequest, onUnsubscribe)` | Low-level escape hatch for building custom downstream operators. |
|
|
286
|
+
|
|
287
|
+
### Flux Subscribe
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// Full Subscriber — full backpressure control
|
|
291
|
+
const sub = flux.subscribe({
|
|
292
|
+
onSubscribe(s) { s.request(10); },
|
|
293
|
+
onNext(v) { console.log(v); },
|
|
294
|
+
onError(e) { console.error(e); },
|
|
295
|
+
onComplete() { console.log('done'); },
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Callback convenience — auto-requests Number.MAX_SAFE_INTEGER
|
|
299
|
+
flux.subscribe(
|
|
300
|
+
v => console.log(v), // onNext (optional)
|
|
301
|
+
e => console.error(e), // onError (optional, defaults to re-throw)
|
|
302
|
+
() => console.log('done'), // onComplete (optional)
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Minimal — just consume items
|
|
306
|
+
flux.subscribe(v => console.log(v));
|
|
307
|
+
|
|
308
|
+
// No callbacks — drain silently
|
|
309
|
+
flux.subscribe();
|
|
310
|
+
|
|
311
|
+
// All overloads return a Subscription
|
|
312
|
+
sub.unsubscribe(); // cancel at any time
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Mono
|
|
318
|
+
|
|
319
|
+
`Mono<T>` is a cold publisher of at most 1 item. Each subscriber gets an independent run.
|
|
320
|
+
|
|
321
|
+
### Mono Factories
|
|
322
|
+
|
|
323
|
+
| Method | Description |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `Mono.just(value)` | Emit exactly one value, then complete. |
|
|
326
|
+
| `Mono.justOrEmpty(value)` | Emit `value` if not `null`/`undefined`, otherwise complete empty. |
|
|
327
|
+
| `Mono.fromPromise(promise)` | Wrap a `Promise<T>` — resolves to `onNext` + `onComplete`, rejects to `onError`. |
|
|
328
|
+
| `Mono.generate(sink => …)` | Imperative generator. Call `sink.next()` exactly once (or `sink.error()`/`sink.complete()`). |
|
|
329
|
+
| `Mono.from(publisher)` | Wrap any `Publisher<T>` as a `Mono<T>` (takes only the first item). |
|
|
330
|
+
| `Mono.defer(factory)` | Lazily create a new Mono per subscription via the factory function. |
|
|
331
|
+
| `Mono.empty()` | Complete immediately without emitting. |
|
|
332
|
+
| `Mono.error(err)` | Signal `onError` immediately. |
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { Mono } from 'reactor-core-ts';
|
|
336
|
+
|
|
337
|
+
Mono.just(42).subscribe(v => console.log(v));
|
|
338
|
+
// 42
|
|
339
|
+
|
|
340
|
+
Mono.fromPromise(fetch('/api/user').then(r => r.json()))
|
|
341
|
+
.subscribe(user => console.log(user));
|
|
342
|
+
|
|
343
|
+
Mono.justOrEmpty(null).subscribe(
|
|
344
|
+
v => console.log('got:', v),
|
|
345
|
+
_e => {},
|
|
346
|
+
() => console.log('empty'),
|
|
347
|
+
);
|
|
348
|
+
// empty
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Mono Transformation
|
|
352
|
+
|
|
353
|
+
| Method | Description |
|
|
354
|
+
|---|---|
|
|
355
|
+
| `.map(fn)` | Transform the value `T → R`. |
|
|
356
|
+
| `.mapNotNull(fn)` | Transform `T → R | null | undefined`; if result is null/undefined, complete empty. |
|
|
357
|
+
| `.flatMap(fn)` | Map the value to a `Mono<R>`, then subscribe to that inner Mono. |
|
|
358
|
+
| `.flatMapMany(fn)` | Map the value to a `Flux<R>` or `Mono<R>`, returning a `Flux<R>`. |
|
|
359
|
+
| `.cast<R>()` | Unsafe type cast. |
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
Mono.just(42)
|
|
363
|
+
.map(n => n.toString())
|
|
364
|
+
.subscribe(s => console.log(s)); // '42'
|
|
365
|
+
|
|
366
|
+
Mono.just(5)
|
|
367
|
+
.flatMapMany(n => Flux.range(0, n))
|
|
368
|
+
.subscribe(v => console.log(v));
|
|
369
|
+
// 0 1 2 3 4
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Mono Filtering
|
|
373
|
+
|
|
374
|
+
| Method | Description |
|
|
375
|
+
|---|---|
|
|
376
|
+
| `.filter(predicate)` | Pass the value only if `predicate` returns `true`; otherwise complete empty. |
|
|
377
|
+
| `.filterWhen(predicate)` | Async filter — subscribe to `predicate(value)` and pass the value only if it emits `true`. |
|
|
378
|
+
|
|
379
|
+
### Mono Combining
|
|
380
|
+
|
|
381
|
+
| Method | Description |
|
|
382
|
+
|---|---|
|
|
383
|
+
| `.zipWith(other)` | Concurrently subscribe to this and `other`; combine their values into `[T, R]`. |
|
|
384
|
+
| `.zipWhen(fn)` | Emit value `v`, then derive `fn(v)` as a second Mono; combine into `[T, R]`. |
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
Mono.just(1).zipWith(Mono.just('a'))
|
|
388
|
+
.subscribe(([n, s]) => console.log(n, s));
|
|
389
|
+
// 1 'a'
|
|
390
|
+
|
|
391
|
+
Mono.just(42)
|
|
392
|
+
.zipWhen(n => Mono.just(n.toString()))
|
|
393
|
+
.subscribe(([n, s]) => console.log(n, s));
|
|
394
|
+
// 42 '42'
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Mono Side Effects
|
|
398
|
+
|
|
399
|
+
Same as Flux: `.doOnNext`, `.doOnError`, `.doOnSubscribe`, `.doFirst`, `.doFinally`.
|
|
400
|
+
|
|
401
|
+
### Mono Scheduling
|
|
136
402
|
|
|
137
|
-
|
|
403
|
+
Same as Flux: `.publishOn(scheduler)`, `.subscribeOn(scheduler)`.
|
|
138
404
|
|
|
139
|
-
|
|
405
|
+
### Mono Utilities
|
|
140
406
|
|
|
141
|
-
|
|
407
|
+
| Method | Description |
|
|
408
|
+
|---|---|
|
|
409
|
+
| `.hasElement()` | `Mono<boolean>` — `true` if a value is emitted, `false` if it completes empty. |
|
|
410
|
+
| `.toPromise()` | `Promise<T | null>` — resolves with the emitted value or `null` if empty. |
|
|
411
|
+
| `.switchIfEmpty(alternative)` | Switch to `alternative` Mono if this completes empty. |
|
|
412
|
+
| `.onErrorReturn(replacement)` | On error, switch to `replacement` publisher. |
|
|
413
|
+
| `.retry(maxRetries?)` | Re-subscribe on error. |
|
|
414
|
+
| `.pipe(producer, onRequest, onUnsubscribe)` | Low-level custom operator builder. |
|
|
415
|
+
|
|
416
|
+
### Mono Subscribe
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// Full Subscriber — full backpressure control
|
|
420
|
+
mono.subscribe({
|
|
421
|
+
onSubscribe(s) { s.request(1); },
|
|
422
|
+
onNext(v) { console.log(v); },
|
|
423
|
+
onError(e) { console.error(e); },
|
|
424
|
+
onComplete() { console.log('done'); },
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Callback convenience — auto-requests 1
|
|
428
|
+
mono.subscribe(
|
|
429
|
+
v => console.log(v), // onNext (optional)
|
|
430
|
+
e => console.error(e), // onError (optional)
|
|
431
|
+
() => console.log('done'), // onComplete (optional)
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
// Promise bridge
|
|
435
|
+
const value: number | null = await Mono.just(42).toPromise();
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Sinks
|
|
441
|
+
|
|
442
|
+
Sinks are imperative bridges that let external code push values into a reactive stream. The `Sinks` factory returns a `SinkPublisher<T>` — an object that implements both `Sink<T>` (push API) and `Publisher<T>` (subscribe API).
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { Sinks, Flux } from 'reactor-core-ts';
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### `Sinks.empty<T>()`
|
|
449
|
+
|
|
450
|
+
Completes immediately on subscribe. Useful as a sentinel or no-op source.
|
|
451
|
+
|
|
452
|
+
### `Sinks.one<T>()`
|
|
453
|
+
|
|
454
|
+
Accepts a single `next(v)` call. Further calls are ignored. Suitable for request/response patterns.
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
const sink = Sinks.one<number>();
|
|
458
|
+
|
|
459
|
+
Flux.from(sink).subscribe(v => console.log('got:', v));
|
|
460
|
+
|
|
461
|
+
sink.next(42); // got: 42
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### `Sinks.many().unicast()`
|
|
465
|
+
|
|
466
|
+
Only one subscriber allowed. Queues items until the subscriber requests them.
|
|
467
|
+
|
|
468
|
+
| Variant | Description |
|
|
469
|
+
|---|---|
|
|
470
|
+
| `.onBackpressureBuffer<T>()` | Buffer all items until subscriber requests them. |
|
|
471
|
+
| `.onBackpressureError<T>()` | Drop items with an error when the subscriber has no pending demand. |
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
const sink = Sinks.many().unicast().onBackpressureBuffer<number>();
|
|
475
|
+
|
|
476
|
+
Flux.from(sink).subscribe(v => console.log(v));
|
|
477
|
+
|
|
478
|
+
sink.next(1);
|
|
479
|
+
sink.next(2);
|
|
480
|
+
sink.next(3);
|
|
481
|
+
sink.complete();
|
|
482
|
+
// 1 2 3
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### `Sinks.many().multicast()`
|
|
486
|
+
|
|
487
|
+
Multiple subscribers share the same stream. Items are dispatched to all current subscribers simultaneously.
|
|
488
|
+
|
|
489
|
+
| Variant | Description |
|
|
490
|
+
|---|---|
|
|
491
|
+
| `.directAllOrNothing<T>()` | Deliver to all subscribers if all have demand; otherwise signal an error. |
|
|
492
|
+
| `.directBestEffort<T>()` | Deliver to subscribers that have demand; silently skip those that don't. |
|
|
493
|
+
| `.onBackpressureBuffer<T>(bufferSize?, autoCancel?)` | Buffer items per-subscriber (up to `bufferSize`, default 256). When `autoCancel` is `true` (default), upstream is cancelled when all subscribers unsubscribe. |
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
const sink = Sinks.many().multicast().onBackpressureBuffer<string>();
|
|
497
|
+
|
|
498
|
+
Flux.from(sink).subscribe(v => console.log('A:', v));
|
|
499
|
+
Flux.from(sink).subscribe(v => console.log('B:', v));
|
|
500
|
+
|
|
501
|
+
sink.next('hello');
|
|
502
|
+
// A: hello
|
|
503
|
+
// B: hello
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### `Sinks.many().replay()`
|
|
507
|
+
|
|
508
|
+
Cache emitted items and replay them to new subscribers.
|
|
509
|
+
|
|
510
|
+
| Variant | Description |
|
|
511
|
+
|---|---|
|
|
512
|
+
| `.all<T>()` | Replay every item ever emitted. |
|
|
513
|
+
| `.latest<T>(limit)` | Replay the last `limit` items. |
|
|
514
|
+
| `.latestOrDefault<T>(value)` | Replay the latest item, or `value` if nothing has been emitted yet. |
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
const sink = Sinks.many().replay().all<number>();
|
|
518
|
+
|
|
519
|
+
sink.next(1);
|
|
520
|
+
sink.next(2);
|
|
521
|
+
sink.next(3);
|
|
522
|
+
|
|
523
|
+
// New subscriber receives all previously emitted items
|
|
524
|
+
Flux.from(sink).subscribe(v => console.log(v));
|
|
525
|
+
// 1 2 3
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Schedulers
|
|
531
|
+
|
|
532
|
+
Schedulers control the thread/task context in which work runs.
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
import { Schedulers } from 'reactor-core-ts';
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
| Factory | Backed by | Use case |
|
|
539
|
+
|---|---|---|
|
|
540
|
+
| `Schedulers.immediate()` | Synchronous call | Inline execution — no scheduling overhead. |
|
|
541
|
+
| `Schedulers.micro()` | `queueMicrotask` | After the current task but before any macro-tasks. |
|
|
542
|
+
| `Schedulers.macro()` | `setTimeout(fn, 0)` | Next event loop iteration. |
|
|
543
|
+
| `Schedulers.delay(ms)` | `setTimeout(fn, ms)` | One-shot delayed execution. Returns `{ cancel() }`. |
|
|
544
|
+
| `Schedulers.interval(ms)` | `setInterval(fn, ms)` | Repeating execution. Returns `{ cancel() }`. |
|
|
545
|
+
|
|
546
|
+
All schedulers implement `Scheduler` (`schedule(fn)`). The `delay` and `interval` schedulers additionally implement `CancellableScheduler` (return `{ cancel() }` from `schedule`).
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
// Deliver items on the next event-loop tick
|
|
550
|
+
Flux.just(1, 2, 3)
|
|
551
|
+
.publishOn(Schedulers.macro())
|
|
552
|
+
.subscribe(v => console.log(v));
|
|
553
|
+
|
|
554
|
+
// Run the subscription (and source work) asynchronously
|
|
555
|
+
Flux.range(0, 1000)
|
|
556
|
+
.subscribeOn(Schedulers.micro())
|
|
557
|
+
.subscribe(v => process(v));
|
|
558
|
+
|
|
559
|
+
// One-shot delay
|
|
560
|
+
const task = Schedulers.delay(2000).schedule(() => console.log('2 s later'));
|
|
561
|
+
task.cancel(); // cancel before it fires
|
|
562
|
+
|
|
563
|
+
// Custom scheduler from any object that has a schedule(fn) method
|
|
564
|
+
const myScheduler = { schedule: (fn: () => void) => requestAnimationFrame(fn) };
|
|
565
|
+
Flux.just(1).publishOn(myScheduler).subscribe(v => console.log(v));
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Backpressure
|
|
571
|
+
|
|
572
|
+
Every subscription starts with zero demand. Items flow only after `request(n)` is called.
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
const sub = Flux.range(0, 100).subscribe({
|
|
576
|
+
onSubscribe(s) {
|
|
577
|
+
// request 10 items to start
|
|
578
|
+
s.request(10);
|
|
579
|
+
},
|
|
580
|
+
onNext(v) {
|
|
581
|
+
console.log(v);
|
|
582
|
+
// request the next batch when processing is done
|
|
583
|
+
},
|
|
584
|
+
onError(e) { console.error(e); },
|
|
585
|
+
onComplete() { console.log('done'); },
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// cancel any time
|
|
589
|
+
sub.unsubscribe();
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
The convenience callback overloads issue `request(Number.MAX_SAFE_INTEGER)` for `Flux` and `request(1)` for `Mono`, effectively making them "unbounded" without any ceremony.
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## Contributing
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
git clone https://github.com/CKATEPTb/reactor-core-ts.git
|
|
600
|
+
cd reactor-core-ts
|
|
601
|
+
pnpm install
|
|
602
|
+
|
|
603
|
+
pnpm run build # compile
|
|
604
|
+
pnpm test # run all tests (Jest + TCK)
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Feel free to open issues and submit pull requests.
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## License
|
|
142
612
|
|
|
143
|
-
|
|
613
|
+
LGPL-3.0-only. See [LICENSE.md](LICENSE.md) for details.
|
|
144
614
|
|
|
145
|
-
|
|
615
|
+
**Author**: CKATEPTb
|