reactor-core-ts 2.1.5 → 3.2.1
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 +927 -651
- package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/defineProperty.js +12 -0
- package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/toPrimitive.js +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/toPropertyKey.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/typeof.js +11 -0
- package/dist/context/context-view.d.ts +31 -0
- package/dist/context/context-view.d.ts.map +1 -0
- package/dist/context/context-view.js +58 -0
- package/dist/context/context-view.js.map +1 -0
- package/dist/context/context.d.ts +46 -0
- package/dist/context/context.d.ts.map +1 -0
- package/dist/context/context.js +125 -0
- package/dist/context/context.js.map +1 -0
- package/dist/context/types.d.ts +7 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/core/boolean-disposable.d.ts +19 -0
- package/dist/core/boolean-disposable.d.ts.map +1 -0
- package/dist/core/boolean-disposable.js +37 -0
- package/dist/core/boolean-disposable.js.map +1 -0
- package/dist/core/composite-disposable.d.ts +21 -0
- package/dist/core/composite-disposable.d.ts.map +1 -0
- package/dist/core/composite-disposable.js +64 -0
- package/dist/core/composite-disposable.js.map +1 -0
- package/dist/core/demand.d.ts +9 -0
- package/dist/core/demand.d.ts.map +1 -0
- package/dist/core/demand.js +21 -0
- package/dist/core/demand.js.map +1 -0
- package/dist/core/disposables.d.ts +12 -0
- package/dist/core/disposables.d.ts.map +1 -0
- package/dist/core/disposables.js +37 -0
- package/dist/core/disposables.js.map +1 -0
- package/dist/core/teardown.d.ts +4 -0
- package/dist/core/teardown.d.ts.map +1 -0
- package/dist/core/types.d.ts +14 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/errors/classes.d.ts +37 -0
- package/dist/errors/classes.d.ts.map +1 -0
- package/dist/errors/classes.js +53 -0
- package/dist/errors/classes.js.map +1 -0
- package/dist/errors/exceptions.d.ts +15 -0
- package/dist/errors/exceptions.d.ts.map +1 -0
- package/dist/errors/helpers.d.ts +9 -0
- package/dist/errors/helpers.d.ts.map +1 -0
- package/dist/errors/helpers.js +18 -0
- package/dist/errors/helpers.js.map +1 -0
- package/dist/index.d.ts +17 -2534
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -2
- package/dist/internal/abort.d.ts +5 -0
- package/dist/internal/abort.d.ts.map +1 -0
- package/dist/internal/abort.js +36 -0
- package/dist/internal/abort.js.map +1 -0
- package/dist/internal/async-queue.d.ts +48 -0
- package/dist/internal/async-queue.d.ts.map +1 -0
- package/dist/internal/async-queue.js +187 -0
- package/dist/internal/async-queue.js.map +1 -0
- package/dist/internal/iterable-terminal.d.ts +12 -0
- package/dist/internal/iterable-terminal.d.ts.map +1 -0
- package/dist/internal/iterable-terminal.js +49 -0
- package/dist/internal/iterable-terminal.js.map +1 -0
- package/dist/internal/iterable-transform.d.ts +24 -0
- package/dist/internal/iterable-transform.d.ts.map +1 -0
- package/dist/internal/iterable-transform.js +159 -0
- package/dist/internal/iterable-transform.js.map +1 -0
- package/dist/internal/iterable.d.ts +23 -0
- package/dist/internal/iterable.d.ts.map +1 -0
- package/dist/internal/iterable.js +81 -0
- package/dist/internal/iterable.js.map +1 -0
- package/dist/internal/microtask.d.ts +7 -0
- package/dist/internal/microtask.d.ts.map +1 -0
- package/dist/internal/microtask.js +19 -0
- package/dist/internal/microtask.js.map +1 -0
- package/dist/publisher/browser-sources.d.ts +15 -0
- package/dist/publisher/browser-sources.d.ts.map +1 -0
- package/dist/publisher/browser-sources.js +92 -0
- package/dist/publisher/browser-sources.js.map +1 -0
- package/dist/publisher/flux-entrypoint.d.ts +7 -0
- package/dist/publisher/flux-entrypoint.d.ts.map +1 -0
- package/dist/publisher/flux-entrypoint.js +11 -0
- package/dist/publisher/flux-entrypoint.js.map +1 -0
- package/dist/publisher/flux.d.ts +121 -0
- package/dist/publisher/flux.d.ts.map +1 -0
- package/dist/publisher/flux.js +697 -0
- package/dist/publisher/flux.js.map +1 -0
- package/dist/publisher/helpers.d.ts +19 -0
- package/dist/publisher/helpers.d.ts.map +1 -0
- package/dist/publisher/helpers.js +84 -0
- package/dist/publisher/helpers.js.map +1 -0
- package/dist/publisher/mono-entrypoint.d.ts +7 -0
- package/dist/publisher/mono-entrypoint.d.ts.map +1 -0
- package/dist/publisher/mono-entrypoint.js +11 -0
- package/dist/publisher/mono-entrypoint.js.map +1 -0
- package/dist/publisher/mono.d.ts +72 -0
- package/dist/publisher/mono.d.ts.map +1 -0
- package/dist/publisher/mono.js +373 -0
- package/dist/publisher/mono.js.map +1 -0
- package/dist/publisher/operators/aggregate.d.ts +35 -0
- package/dist/publisher/operators/aggregate.d.ts.map +1 -0
- package/dist/publisher/operators/aggregate.js +217 -0
- package/dist/publisher/operators/aggregate.js.map +1 -0
- package/dist/publisher/operators/compat/flux.d.ts +56 -0
- package/dist/publisher/operators/compat/flux.d.ts.map +1 -0
- package/dist/publisher/operators/compat/flux.js +156 -0
- package/dist/publisher/operators/compat/flux.js.map +1 -0
- package/dist/publisher/operators/compat/mono.d.ts +82 -0
- package/dist/publisher/operators/compat/mono.d.ts.map +1 -0
- package/dist/publisher/operators/compat/mono.js +125 -0
- package/dist/publisher/operators/compat/mono.js.map +1 -0
- package/dist/publisher/operators/coordination.d.ts +82 -0
- package/dist/publisher/operators/coordination.d.ts.map +1 -0
- package/dist/publisher/operators/coordination.js +345 -0
- package/dist/publisher/operators/coordination.js.map +1 -0
- package/dist/publisher/operators/error.d.ts +17 -0
- package/dist/publisher/operators/error.d.ts.map +1 -0
- package/dist/publisher/operators/error.js +48 -0
- package/dist/publisher/operators/error.js.map +1 -0
- package/dist/publisher/operators/flatten.d.ts +19 -0
- package/dist/publisher/operators/flatten.d.ts.map +1 -0
- package/dist/publisher/operators/flatten.js +127 -0
- package/dist/publisher/operators/flatten.js.map +1 -0
- package/dist/publisher/operators/lifecycle.d.ts +103 -0
- package/dist/publisher/operators/lifecycle.d.ts.map +1 -0
- package/dist/publisher/operators/lifecycle.js +237 -0
- package/dist/publisher/operators/lifecycle.js.map +1 -0
- package/dist/publisher/operators/mono-parity.d.ts +109 -0
- package/dist/publisher/operators/mono-parity.d.ts.map +1 -0
- package/dist/publisher/operators/mono-parity.js +144 -0
- package/dist/publisher/operators/mono-parity.js.map +1 -0
- package/dist/publisher/operators/mono.d.ts +30 -0
- package/dist/publisher/operators/mono.d.ts.map +1 -0
- package/dist/publisher/operators/mono.js +37 -0
- package/dist/publisher/operators/mono.js.map +1 -0
- package/dist/publisher/operators/register.d.ts +20 -0
- package/dist/publisher/operators/register.d.ts.map +1 -0
- package/dist/publisher/operators/register.js +23 -0
- package/dist/publisher/operators/register.js.map +1 -0
- package/dist/publisher/operators/selection.d.ts +25 -0
- package/dist/publisher/operators/selection.d.ts.map +1 -0
- package/dist/publisher/operators/selection.js +108 -0
- package/dist/publisher/operators/selection.js.map +1 -0
- package/dist/publisher/operators/side-effect.d.ts +15 -0
- package/dist/publisher/operators/side-effect.d.ts.map +1 -0
- package/dist/publisher/operators/side-effect.js +56 -0
- package/dist/publisher/operators/side-effect.js.map +1 -0
- package/dist/publisher/operators/terminal.d.ts +16 -0
- package/dist/publisher/operators/terminal.d.ts.map +1 -0
- package/dist/publisher/operators/terminal.js +47 -0
- package/dist/publisher/operators/terminal.js.map +1 -0
- package/dist/publisher/operators/time.d.ts +20 -0
- package/dist/publisher/operators/time.d.ts.map +1 -0
- package/dist/publisher/operators/time.js +92 -0
- package/dist/publisher/operators/time.js.map +1 -0
- package/dist/publisher/operators/transform.d.ts +31 -0
- package/dist/publisher/operators/transform.d.ts.map +1 -0
- package/dist/publisher/operators/transform.js +74 -0
- package/dist/publisher/operators/transform.js.map +1 -0
- package/dist/publisher/operators/windowing.d.ts +28 -0
- package/dist/publisher/operators/windowing.d.ts.map +1 -0
- package/dist/publisher/operators/windowing.js +148 -0
- package/dist/publisher/operators/windowing.js.map +1 -0
- package/dist/publisher/publisher.d.ts +11 -0
- package/dist/publisher/publisher.d.ts.map +1 -0
- package/dist/publisher/types.d.ts +51 -0
- package/dist/publisher/types.d.ts.map +1 -0
- package/dist/schedulers/animation-frame-scheduler.d.ts +11 -0
- package/dist/schedulers/animation-frame-scheduler.d.ts.map +1 -0
- package/dist/schedulers/animation-frame-scheduler.js +30 -0
- package/dist/schedulers/animation-frame-scheduler.js.map +1 -0
- package/dist/schedulers/base-scheduler.d.ts +28 -0
- package/dist/schedulers/base-scheduler.d.ts.map +1 -0
- package/dist/schedulers/base-scheduler.js +77 -0
- package/dist/schedulers/base-scheduler.js.map +1 -0
- package/dist/schedulers/basic-worker.d.ts +22 -0
- package/dist/schedulers/basic-worker.d.ts.map +1 -0
- package/dist/schedulers/basic-worker.js +71 -0
- package/dist/schedulers/basic-worker.js.map +1 -0
- package/dist/schedulers/delay-scheduler.d.ts +12 -0
- package/dist/schedulers/delay-scheduler.d.ts.map +1 -0
- package/dist/schedulers/delay-scheduler.js +17 -0
- package/dist/schedulers/delay-scheduler.js.map +1 -0
- package/dist/schedulers/disposed.d.ts +4 -0
- package/dist/schedulers/disposed.d.ts.map +1 -0
- package/dist/schedulers/disposed.js +14 -0
- package/dist/schedulers/disposed.js.map +1 -0
- package/dist/schedulers/duration.d.ts +8 -0
- package/dist/schedulers/duration.d.ts.map +1 -0
- package/dist/schedulers/duration.js +11 -0
- package/dist/schedulers/duration.js.map +1 -0
- package/dist/schedulers/executor-scheduler.d.ts +13 -0
- package/dist/schedulers/executor-scheduler.d.ts.map +1 -0
- package/dist/schedulers/executor-scheduler.js +43 -0
- package/dist/schedulers/executor-scheduler.js.map +1 -0
- package/dist/schedulers/immediate-scheduler.d.ts +15 -0
- package/dist/schedulers/immediate-scheduler.d.ts.map +1 -0
- package/dist/schedulers/immediate-scheduler.js +24 -0
- package/dist/schedulers/immediate-scheduler.js.map +1 -0
- package/dist/schedulers/index.d.ts +7 -0
- package/dist/schedulers/index.d.ts.map +1 -0
- package/dist/schedulers/index.js +2 -0
- package/dist/schedulers/interval-scheduler.d.ts +12 -0
- package/dist/schedulers/interval-scheduler.d.ts.map +1 -0
- package/dist/schedulers/interval-scheduler.js +17 -0
- package/dist/schedulers/interval-scheduler.js.map +1 -0
- package/dist/schedulers/macro-scheduler.d.ts +19 -0
- package/dist/schedulers/macro-scheduler.d.ts.map +1 -0
- package/dist/schedulers/macro-scheduler.js +30 -0
- package/dist/schedulers/macro-scheduler.js.map +1 -0
- package/dist/schedulers/microtask-scheduler.d.ts +11 -0
- package/dist/schedulers/microtask-scheduler.d.ts.map +1 -0
- package/dist/schedulers/microtask-scheduler.js +34 -0
- package/dist/schedulers/microtask-scheduler.js.map +1 -0
- package/dist/schedulers/schedulers.d.ts +39 -0
- package/dist/schedulers/schedulers.d.ts.map +1 -0
- package/dist/schedulers/schedulers.js +83 -0
- package/dist/schedulers/schedulers.js.map +1 -0
- package/dist/schedulers/timeout-scheduler.d.ts +11 -0
- package/dist/schedulers/timeout-scheduler.d.ts.map +1 -0
- package/dist/schedulers/types.d.ts +35 -0
- package/dist/schedulers/types.d.ts.map +1 -0
- package/dist/signal/signal-type.d.ts +26 -0
- package/dist/signal/signal-type.d.ts.map +1 -0
- package/dist/signal/signal-type.js +31 -0
- package/dist/signal/signal-type.js.map +1 -0
- package/dist/signal/signal.d.ts +33 -0
- package/dist/signal/signal.d.ts.map +1 -0
- package/dist/signal/signal.js +70 -0
- package/dist/signal/signal.js.map +1 -0
- package/dist/sinks/index.d.ts +7 -0
- package/dist/sinks/index.d.ts.map +1 -0
- package/dist/sinks/index.js +2 -0
- package/dist/sinks/many.d.ts +66 -0
- package/dist/sinks/many.d.ts.map +1 -0
- package/dist/sinks/many.js +193 -0
- package/dist/sinks/many.js.map +1 -0
- package/dist/sinks/one.d.ts +41 -0
- package/dist/sinks/one.d.ts.map +1 -0
- package/dist/sinks/one.js +144 -0
- package/dist/sinks/one.js.map +1 -0
- package/dist/sinks/specs.d.ts +25 -0
- package/dist/sinks/specs.d.ts.map +1 -0
- package/dist/sinks/specs.js +126 -0
- package/dist/sinks/specs.js.map +1 -0
- package/dist/sinks/subscribers.d.ts +16 -0
- package/dist/sinks/subscribers.d.ts.map +1 -0
- package/dist/sinks/subscribers.js +32 -0
- package/dist/sinks/subscribers.js.map +1 -0
- package/dist/sinks/types.d.ts +137 -0
- package/dist/sinks/types.d.ts.map +1 -0
- package/dist/sinks/types.js +58 -0
- package/dist/sinks/types.js.map +1 -0
- package/dist/subscription/core-subscriber.d.ts +12 -0
- package/dist/subscription/core-subscriber.d.ts.map +1 -0
- package/dist/subscription/iterable-subscription.d.ts +64 -0
- package/dist/subscription/iterable-subscription.d.ts.map +1 -0
- package/dist/subscription/iterable-subscription.js +281 -0
- package/dist/subscription/iterable-subscription.js.map +1 -0
- package/dist/subscription/reactive-streams.d.ts +8 -0
- package/dist/subscription/reactive-streams.d.ts.map +1 -0
- package/dist/subscription/reactive-streams.js +0 -0
- package/dist/subscription/subscriber.d.ts +17 -0
- package/dist/subscription/subscriber.d.ts.map +1 -0
- package/dist/subscription/subscription.d.ts +12 -0
- package/dist/subscription/subscription.d.ts.map +1 -0
- package/package.json +99 -33
- package/LICENSE.md +0 -56
- package/dist/index.d.mts +0 -2535
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,760 +1,1036 @@
|
|
|
1
1
|
# reactor-core-ts
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [Subscribe](#flux-subscribe)
|
|
21
|
-
- [Mono](#mono)
|
|
22
|
-
- [Factories](#mono-factories)
|
|
23
|
-
- [Transformation](#mono-transformation)
|
|
24
|
-
- [Filtering](#mono-filtering)
|
|
25
|
-
- [Combining](#mono-combining)
|
|
26
|
-
- [Side effects](#mono-side-effects)
|
|
27
|
-
- [Error handling](#mono-error-handling)
|
|
28
|
-
- [Scheduling](#mono-scheduling)
|
|
29
|
-
- [Utilities](#mono-utilities)
|
|
30
|
-
- [Subscribe](#mono-subscribe)
|
|
31
|
-
- [Sinks](#sinks)
|
|
32
|
-
- [Schedulers](#schedulers)
|
|
33
|
-
- [TypeScript generics](#typescript-generics)
|
|
34
|
-
- [Backpressure](#backpressure)
|
|
35
|
-
- [Contributing](#contributing)
|
|
36
|
-
- [License](#license)
|
|
37
|
-
|
|
38
|
-
---
|
|
3
|
+
Browser-first TypeScript implementation inspired by Project Reactor Core.
|
|
4
|
+
|
|
5
|
+
`reactor-core-ts` gives you `Mono`, `Flux`, `Sinks`, `Schedulers` and immutable
|
|
6
|
+
`Context` for building lazy asynchronous pipelines in browser applications. The
|
|
7
|
+
runtime is intentionally small: no external runtime dependencies, no Node-only
|
|
8
|
+
APIs in the core path, and scheduling built on standard browser primitives such
|
|
9
|
+
as `queueMicrotask`, `setTimeout` and `requestAnimationFrame`.
|
|
10
|
+
|
|
11
|
+
The project is useful when you want a Reactor-like programming model in
|
|
12
|
+
TypeScript:
|
|
13
|
+
|
|
14
|
+
- `Mono<T>` is a lazy publisher that can complete with zero or one value.
|
|
15
|
+
- `Flux<T>` is a lazy publisher that can complete with zero, one or many values.
|
|
16
|
+
- `Sinks` let imperative code push values into a `Mono` or `Flux`.
|
|
17
|
+
- `Schedulers` control when work is executed.
|
|
18
|
+
- `Context` carries request-scoped metadata through a chain without adding more
|
|
19
|
+
parameters to every function.
|
|
39
20
|
|
|
40
21
|
## Installation
|
|
41
22
|
|
|
42
|
-
|
|
23
|
+
For a consumer project:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
43
26
|
npm install reactor-core-ts
|
|
44
|
-
# or
|
|
45
|
-
pnpm add reactor-core-ts
|
|
46
|
-
# or
|
|
47
|
-
yarn add reactor-core-ts
|
|
48
27
|
```
|
|
49
28
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## Core Concepts
|
|
53
|
-
|
|
54
|
-
| Concept | Description |
|
|
55
|
-
|---|---|
|
|
56
|
-
| **Publisher\<T\>** | Source of a data stream. Emits items on subscriber demand. |
|
|
57
|
-
| **Subscriber\<T\>** | Consumer of a stream. Receives `onSubscribe`, `onNext`, `onError`, `onComplete`. |
|
|
58
|
-
| **Subscription** | Handle returned by `subscribe()`. Call `request(n)` to pull items, `unsubscribe()` to cancel. |
|
|
59
|
-
| **Flux\<T\>** | 0…N items publisher. The multi-value building block. |
|
|
60
|
-
| **Mono\<T\>** | 0…1 item publisher. Ideal for single async results. |
|
|
61
|
-
| **Sink\<T\>** | Imperative push interface: `next(v)`, `error(e)`, `complete()`. |
|
|
62
|
-
| **SinkPublisher\<T\>** | A `Sink<T>` that is also a `Publisher<T>` — both push and subscribe sides in one object. |
|
|
63
|
-
| **Scheduler** | Abstraction over task execution (sync, micro-task, macro-task, delayed). |
|
|
64
|
-
|
|
65
|
-
### Backpressure
|
|
66
|
-
|
|
67
|
-
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.
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## Flux
|
|
72
|
-
|
|
73
|
-
`Flux<T>` is a cold publisher of 0 to N items. Each subscriber gets an independent run of the stream.
|
|
74
|
-
|
|
75
|
-
### Flux Factories
|
|
76
|
-
|
|
77
|
-
| Method | Description |
|
|
78
|
-
|---|---|
|
|
79
|
-
| `Flux.just(...items)` | Emit each provided value in order, then complete. |
|
|
80
|
-
| `Flux.range(start, count)` | Emit integers `[start, start+count)`. |
|
|
81
|
-
| `Flux.fromIterable(iterable)` | Emit all items from any `Iterable<T>`. |
|
|
82
|
-
| `Flux.generate(sink => …)` | Imperative generator. Call `sink.next()` / `sink.complete()` / `sink.error()`. Delivery is deferred until downstream requests. |
|
|
83
|
-
| `Flux.create(emitter => …)` | Push-based bridge via [`FluxSink`](#fluxsink) — supports `onRequest`, `onCancel`, `onDispose` callbacks. Suitable for event listeners and callback APIs. |
|
|
84
|
-
| `Flux.from(publisher)` | Wrap any `Publisher<T>` as a `Flux<T>`. |
|
|
85
|
-
| `Flux.defer(factory)` | Lazily create a new Flux per subscription via the factory function. |
|
|
86
|
-
| `Flux.empty()` | Complete immediately without emitting. |
|
|
87
|
-
| `Flux.never()` | Never emit any signal. |
|
|
88
|
-
| `Flux.error(err)` | Signal `onError` immediately. |
|
|
89
|
-
| `Flux.interval(ms)` | Emit an incrementing counter (0, 1, 2, …) at a fixed `ms` rate. Items are dropped when downstream has no demand. |
|
|
90
|
-
| `Flux.merge(...sources)` | Subscribe to all sources concurrently; items interleave as they arrive. |
|
|
91
|
-
| `Flux.zip(sources, combiner)` | Combine items from 2–4 sources positionally via `combiner`. Overloads for 2, 3, and 4 typed sources. |
|
|
92
|
-
| `Flux.combineLatest(sources, combiner)` | Emit a new value whenever any source emits, combining the **latest** values from each. Overloads for 2, 3, and 4 sources. |
|
|
93
|
-
| `Flux.firstWithValue(...sources)` | Race: emit the first value to arrive from any source, then cancel the rest. |
|
|
94
|
-
| `Flux.using(resourceSupplier, sourceFactory, cleanup)` | Manage a resource lifecycle: acquire, stream, release on termination. |
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
import { Flux } from 'reactor-core-ts';
|
|
98
|
-
|
|
99
|
-
Flux.just(1, 2, 3).subscribe(v => console.log(v));
|
|
100
|
-
// 1 2 3
|
|
101
|
-
|
|
102
|
-
Flux.range(0, 5).subscribe(v => console.log(v));
|
|
103
|
-
// 0 1 2 3 4
|
|
104
|
-
|
|
105
|
-
Flux.fromIterable(['a', 'b', 'c']).subscribe(v => console.log(v));
|
|
106
|
-
// a b c
|
|
107
|
-
|
|
108
|
-
Flux.generate<number>(sink => {
|
|
109
|
-
sink.next(10);
|
|
110
|
-
sink.next(20);
|
|
111
|
-
sink.complete();
|
|
112
|
-
}).subscribe(v => console.log(v));
|
|
113
|
-
// 10 20
|
|
114
|
-
```
|
|
29
|
+
For this repository:
|
|
115
30
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
| `.map(fn)` | Transform each item `T → R`. |
|
|
121
|
-
| `.mapNotNull(fn)` | Like `map`, but `null`/`undefined` results are filtered; upstream demand is replenished for each skipped item. |
|
|
122
|
-
| `.flatMap(fn)` | Map each item to a `Flux<R>` or `Mono<R>`, subscribing to all concurrently (merge semantics). |
|
|
123
|
-
| `.concatMap(fn)` | Like `flatMap` but subscribes to each inner publisher sequentially, preserving order. |
|
|
124
|
-
| `.switchMap(fn)` | Like `flatMap` but cancels the previous inner subscription when a new outer item arrives. |
|
|
125
|
-
| `.handle(handler)` | Fine-grained per-item transform via a `Sink<R>`. The handler may emit 0 or 1 item. |
|
|
126
|
-
| `.scan(reducer)` | Emit running accumulation; first item is used as initial accumulator. |
|
|
127
|
-
| `.scanWith(seedFactory, reducer)` | Emit running accumulation starting from an explicit seed value. |
|
|
128
|
-
| `.cast<R>()` | Unsafe type cast — changes the declared element type without any runtime conversion. |
|
|
129
|
-
| `.indexed()` | Pair each item with its zero-based index: `Flux<[number, T]>`. |
|
|
130
|
-
| `.groupBy(keyFn)` | Route items into `GroupedFlux<K, T>` streams keyed by `keyFn(item)`. Each group is a self-contained `Flux`. |
|
|
131
|
-
| `.expand(fn)` | Breadth-first recursive expansion — each item is expanded via `fn`, which returns a `Publisher<T>` of child items. |
|
|
132
|
-
| `.sample(trigger)` | Emit the latest item seen whenever `trigger` emits. |
|
|
133
|
-
| `.bufferTimeout(maxSize, ms)` | Collect items into arrays; flush when the buffer reaches `maxSize` or `ms` elapses. |
|
|
134
|
-
| `.elapsed()` | Pair each item with the milliseconds elapsed since the previous item: `Flux<[number, T]>`. |
|
|
135
|
-
| `.timestamp()` | Pair each item with the current timestamp in ms: `Flux<[number, T]>`. |
|
|
136
|
-
| `.materialize()` | Wrap each signal (`next`, `error`, `complete`) as a `Signal<T>` item. |
|
|
137
|
-
| `.dematerialize()` | Restore a `Flux<Signal<R>>` back into a live reactive stream. |
|
|
138
|
-
| `.transformDeferred(fn)` | Apply a reusable operator function lazily per subscription. |
|
|
139
|
-
| `.delayUntil(triggerFn)` | Hold each item until the trigger publisher returned by `triggerFn(item)` emits or completes. |
|
|
140
|
-
| `.ofType(constructor)` | Keep only items that are `instanceof` the given constructor, with a typed cast to `R`. |
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
Flux.just(1, 2, 3)
|
|
144
|
-
.map(n => n * 2)
|
|
145
|
-
.subscribe(v => console.log(v));
|
|
146
|
-
// 2 4 6
|
|
147
|
-
|
|
148
|
-
Flux.just('hello', '', 'world')
|
|
149
|
-
.mapNotNull(s => s.length > 0 ? s.toUpperCase() : null)
|
|
150
|
-
.subscribe(v => console.log(v));
|
|
151
|
-
// HELLO WORLD
|
|
152
|
-
|
|
153
|
-
Flux.just(1, 2, 3)
|
|
154
|
-
.flatMap(n => Flux.just(n, n * 10))
|
|
155
|
-
.subscribe(v => console.log(v));
|
|
156
|
-
// 1 10 2 20 3 30 (order may vary with async inner publishers)
|
|
157
|
-
|
|
158
|
-
Flux.just(1, 2, 3, 4)
|
|
159
|
-
.scan((acc, n) => acc + n)
|
|
160
|
-
.subscribe(v => console.log(v));
|
|
161
|
-
// 1 3 6 10
|
|
31
|
+
```sh
|
|
32
|
+
npm install
|
|
33
|
+
npm run build
|
|
34
|
+
npm test
|
|
162
35
|
```
|
|
163
36
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| Method | Description |
|
|
167
|
-
|---|---|
|
|
168
|
-
| `.filter(predicate)` | Pass only items matching the predicate; replenishes demand for dropped items. |
|
|
169
|
-
| `.filterWhen(predicate)` | Async filter — for each item, subscribe to the `Publisher<boolean>` returned by `predicate`; forward item only if it emits `true`. |
|
|
170
|
-
| `.take(n)` | Emit at most `n` items, then cancel upstream and complete. |
|
|
171
|
-
| `.takeWhile(predicate)` | Emit items while `predicate` returns `true`; complete on the first `false`. |
|
|
172
|
-
| `.takeUntilOther(trigger)` | Emit items until `trigger` emits any signal, then cancel and complete. |
|
|
173
|
-
| `.skip(n)` | Skip the first `n` items. |
|
|
174
|
-
| `.skipWhile(predicate)` | Skip items while `predicate` returns `true`; then pass through. |
|
|
175
|
-
| `.skipUntil(other)` | Skip items until `other` emits; then pass remaining items through. |
|
|
176
|
-
| `.distinct()` | Suppress duplicate items (equality checked by `===`). |
|
|
177
|
-
| `.distinctUntilChanged(comparator?)` | Suppress consecutive duplicate items. Custom `comparator(a, b)` returns `true` when items are considered equal. |
|
|
178
|
-
| `.defaultIfEmpty(value)` | Emit `value` if the source completes without emitting anything. |
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
Flux.just(1, 2, 3, 4, 5, 6)
|
|
182
|
-
.filter(n => n % 2 === 0)
|
|
183
|
-
.subscribe(v => console.log(v));
|
|
184
|
-
// 2 4 6
|
|
185
|
-
|
|
186
|
-
Flux.just(1, 2, 3, 4, 5)
|
|
187
|
-
.take(3)
|
|
188
|
-
.subscribe(v => console.log(v));
|
|
189
|
-
// 1 2 3
|
|
190
|
-
|
|
191
|
-
Flux.just(1, 1, 2, 2, 3, 1)
|
|
192
|
-
.distinctUntilChanged()
|
|
193
|
-
.subscribe(v => console.log(v));
|
|
194
|
-
// 1 2 3 1
|
|
195
|
-
```
|
|
37
|
+
## Public Imports
|
|
196
38
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
These operators reduce a Flux to a Mono.
|
|
200
|
-
|
|
201
|
-
| Method | Description |
|
|
202
|
-
|---|---|
|
|
203
|
-
| `.count()` | `Mono<number>` — total number of items emitted. |
|
|
204
|
-
| `.first()` | `Mono<T>` — the first item, or empty if source is empty. |
|
|
205
|
-
| `.last()` | `Mono<T>` — the last item, or empty if source is empty. |
|
|
206
|
-
| `.elementAt(index, default?)` | `Mono<T>` — item at zero-based `index`. Emits `default` if out of bounds. |
|
|
207
|
-
| `.hasElements()` | `Mono<boolean>` — `true` if source emits at least one item. |
|
|
208
|
-
| `.any(predicate)` | `Mono<boolean>` — `true` if any item satisfies the predicate. |
|
|
209
|
-
| `.all(predicate)` | `Mono<boolean>` — `true` if all items satisfy the predicate. |
|
|
210
|
-
| `.none(predicate)` | `Mono<boolean>` — `true` if no item satisfies the predicate. |
|
|
211
|
-
| `.reduce(reducer)` | `Mono<T>` — reduce all items to a single value (first item is the initial accumulator). |
|
|
212
|
-
| `.reduceWith(seedFactory, reducer)` | `Mono<A>` — reduce with an explicit seed of type `A`. |
|
|
213
|
-
| `.collect()` | `Mono<T[]>` — collect all items into an array. |
|
|
214
|
-
| `.collectList()` | Alias for `.collect()`. |
|
|
215
|
-
| `.sort(comparator?)` | `Flux<T>` — collect then re-emit items in sorted order. |
|
|
216
|
-
| `.buffer(maxSize)` | `Flux<T[]>` — group items into fixed-size arrays. |
|
|
217
|
-
| `.then()` | `Mono<void>` — wait for completion; ignore all items. |
|
|
218
|
-
| `.thenEmpty(other)` | `Mono<void>` — wait for completion then subscribe to `other`. |
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
Flux.just(1, 2, 3, 4)
|
|
222
|
-
.reduce((acc, n) => acc + n)
|
|
223
|
-
.subscribe(sum => console.log(sum));
|
|
224
|
-
// 10
|
|
225
|
-
|
|
226
|
-
Flux.just('a', 'b', 'c')
|
|
227
|
-
.collect()
|
|
228
|
-
.subscribe(arr => console.log(arr));
|
|
229
|
-
// ['a', 'b', 'c']
|
|
230
|
-
|
|
231
|
-
Flux.just(3, 1, 4, 1, 5)
|
|
232
|
-
.sort()
|
|
233
|
-
.subscribe(v => console.log(v));
|
|
234
|
-
// 1 1 3 4 5
|
|
235
|
-
```
|
|
39
|
+
Prefer the package root. Runtime exports are deliberately small, and Reactive
|
|
40
|
+
Streams contracts are exported as TypeScript types from the same root.
|
|
236
41
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|---|---|
|
|
241
|
-
| `.concatWith(other)` | Append `other` after `this` completes (sequential). |
|
|
242
|
-
| `.mergeWith(other)` | Merge `other` with `this` concurrently; items interleave as they arrive. |
|
|
243
|
-
| `.zipWith(other, combiner)` | Pair items from `this` and `other` one-by-one using `combiner(a, b) → V`. |
|
|
244
|
-
| `Flux.merge(...sources)` | Static N-source merge — subscribe to all and forward items as they arrive. |
|
|
245
|
-
| `Flux.zip(sources, combiner)` | Static positional combine: waits for one item from each source, then calls `combiner`. |
|
|
246
|
-
| `Flux.combineLatest(sources, combiner)` | Emit a combined value whenever any source emits; uses the latest from all others. |
|
|
247
|
-
| `Flux.firstWithValue(...sources)` | Race: emits the first value from any source, cancelling all others. |
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
Flux.just(1, 2).concatWith(Flux.just(3, 4)).subscribe(v => console.log(v));
|
|
251
|
-
// 1 2 3 4
|
|
252
|
-
|
|
253
|
-
Flux.just(1, 2, 3).zipWith(Flux.just('a', 'b', 'c'), (n, s) => `${n}${s}`)
|
|
254
|
-
.subscribe(v => console.log(v));
|
|
255
|
-
// '1a' '2b' '3c'
|
|
256
|
-
|
|
257
|
-
Flux.zip([Flux.just(1, 2), Flux.just('a', 'b')], (n, s) => `${n}${s}`)
|
|
258
|
-
.subscribe(v => console.log(v));
|
|
259
|
-
// '1a' '2b'
|
|
260
|
-
|
|
261
|
-
Flux.combineLatest([Flux.just(1), Flux.just(2)], (a, b) => a + b)
|
|
262
|
-
.subscribe(v => console.log(v));
|
|
263
|
-
// 3
|
|
42
|
+
```ts
|
|
43
|
+
import { Context, Flux, Mono, Schedulers, Sinks } from "reactor-core-ts";
|
|
44
|
+
import type { Disposable, Publisher, Subscriber, Subscription } from "reactor-core-ts";
|
|
264
45
|
```
|
|
265
46
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
| `.onBackpressureBuffer(maxSize?)` | Buffer all items when downstream has no demand. Signals error when `maxSize` is exceeded. |
|
|
271
|
-
| `.onBackpressureDrop(onDrop?)` | Silently drop items when downstream has no demand. Optional `onDrop(item)` callback. |
|
|
272
|
-
| `.onBackpressureLatest()` | Keep only the latest item when downstream has no demand; older items are discarded. |
|
|
273
|
-
| `.limitRate(n)` | Prefetch items from upstream in batches of `n`, replenishing at 75% consumption. |
|
|
274
|
-
|
|
275
|
-
### Flux Side Effects
|
|
276
|
-
|
|
277
|
-
These operators observe signals without modifying the stream.
|
|
278
|
-
|
|
279
|
-
| Method | Description |
|
|
280
|
-
|---|---|
|
|
281
|
-
| `.doFirst(fn)` | Run `fn` before the first item is emitted. |
|
|
282
|
-
| `.doOnNext(fn)` | Run `fn` for each item. |
|
|
283
|
-
| `.doOnError(fn)` | Run `fn` when `onError` is received. |
|
|
284
|
-
| `.doOnComplete(fn)` | Run `fn` when `onComplete` is received. |
|
|
285
|
-
| `.doOnTerminate(fn)` | Run `fn` on both `onComplete` and `onError`. |
|
|
286
|
-
| `.doOnCancel(fn)` | Run `fn` when the subscription is cancelled. |
|
|
287
|
-
| `.doFinally(fn)` | Run `fn` after the stream terminates for any reason (complete, error, or cancel). |
|
|
288
|
-
| `.doOnSubscribe(fn)` | Run `fn` when `onSubscribe` is received, receiving the `Subscription` object. |
|
|
289
|
-
| `.doOnRequest(fn)` | Run `fn(n)` each time downstream calls `request(n)`. |
|
|
290
|
-
| `.doOnEach(fn)` | Run `fn(signal)` for every signal — next, error, and complete — as a `Signal<T>`. |
|
|
291
|
-
| `.log(label?)` | Console-log every signal with an optional label prefix. |
|
|
292
|
-
|
|
293
|
-
### Flux Error Handling
|
|
294
|
-
|
|
295
|
-
| Method | Description |
|
|
296
|
-
|---|---|
|
|
297
|
-
| `.retry(maxRetries?)` | Re-subscribe on error up to `maxRetries` times (default: unbounded). |
|
|
298
|
-
| `.retryWhen(fn)` | Controlled retry — each error is pushed to a `Flux<Error>` returned by `fn`; re-subscribe when the control emits. |
|
|
299
|
-
| `.repeatWhen(fn)` | Controlled repeat — each completion is pushed to a `Flux<void>`; re-subscribe when the control emits. |
|
|
300
|
-
| `.onErrorReturn(replacement)` | On error, switch to `replacement` publisher. |
|
|
301
|
-
| `.onErrorResume(fn)` | On error, switch to the publisher returned by `fn(error)`. |
|
|
302
|
-
| `.onErrorMap(fn)` | Transform the error without changing the stream type. |
|
|
303
|
-
| `.onErrorContinue(predicate)` | If `predicate(err)` is `true`, complete; otherwise re-throw. |
|
|
304
|
-
| `.timeout(ms, fallback?)` | Error with `TimeoutError` if no item arrives within `ms`; optionally switch to `fallback`. |
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
Flux.just(1, 2, 3)
|
|
308
|
-
.doOnNext(v => console.log('next:', v))
|
|
309
|
-
.doOnComplete(() => console.log('done'))
|
|
310
|
-
.subscribe();
|
|
311
|
-
// next: 1 next: 2 next: 3 done
|
|
312
|
-
```
|
|
47
|
+
Use `import type` for `Publisher`, `Subscriber`, `Subscription` and
|
|
48
|
+
`Disposable`. They are compile-time contracts, not runtime objects.
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
313
51
|
|
|
314
|
-
|
|
52
|
+
```ts
|
|
53
|
+
import { Flux, Mono, Sinks } from "reactor-core-ts";
|
|
315
54
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
55
|
+
const numbers = await Flux.range(1, 6)
|
|
56
|
+
.filter(value => value % 2 === 0)
|
|
57
|
+
.map(value => value * 10)
|
|
58
|
+
.collectList()
|
|
59
|
+
.block();
|
|
321
60
|
|
|
322
|
-
|
|
323
|
-
const asyncScheduler = Schedulers.macro(); // setTimeout-based
|
|
61
|
+
console.log(numbers); // [20, 40, 60]
|
|
324
62
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
63
|
+
const user = await Mono.fromPromise(fetch("/api/user").then(response => response.json()))
|
|
64
|
+
.timeout(3_000)
|
|
65
|
+
.onErrorResume(() => Mono.just({ anonymous: true }))
|
|
66
|
+
.block();
|
|
328
67
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
68
|
+
const sink = Sinks.many().replay().latest<number>();
|
|
69
|
+
sink.emitNext(1);
|
|
70
|
+
sink.emitNext(2);
|
|
71
|
+
sink.emitComplete();
|
|
72
|
+
|
|
73
|
+
console.log(await sink.asFlux().toArray()); // [2]
|
|
332
74
|
```
|
|
333
75
|
|
|
334
|
-
|
|
76
|
+
Methods named `block`, `blockFirst` and `blockLast` do not block the JavaScript
|
|
77
|
+
thread. In this TypeScript/browser implementation they return `Promise`.
|
|
335
78
|
|
|
336
|
-
|
|
337
|
-
|---|---|
|
|
338
|
-
| `.cache()` | Subscribe to the source once; replay all items to subsequent subscribers. |
|
|
339
|
-
| `.share()` | Hot multicast with refCounting — connects on first subscriber, disconnects when the last one leaves. Items are delivered best-effort (no buffering). Re-subscribes to upstream when a new subscriber arrives after all others have left. |
|
|
340
|
-
| `.switchIfEmpty(alternative)` | Switch to `alternative` if source completes without emitting. |
|
|
341
|
-
| `.delaySubscription(ms)` | Delay the actual subscription to the source by `ms` milliseconds. |
|
|
342
|
-
| `.pipe(producer, onRequest, onUnsubscribe)` | Low-level escape hatch for building custom downstream operators. |
|
|
79
|
+
## What Reactive Streams Are
|
|
343
80
|
|
|
344
|
-
|
|
345
|
-
// share() — multiple subscribers observe the same live upstream
|
|
346
|
-
const hot = Flux.interval(500).share();
|
|
81
|
+
Reactive Streams are a simple contract for asynchronous data.
|
|
347
82
|
|
|
348
|
-
|
|
349
|
-
setTimeout(() => hot.subscribe(v => console.log('B:', v)), 1200);
|
|
350
|
-
// A: 0, A: 1, A: 2, B: 2, A: 3, B: 3, …
|
|
351
|
-
```
|
|
83
|
+
Think of a stream as a lazy pipeline:
|
|
352
84
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|---|---|
|
|
359
|
-
| `.next(value)` | Push a value. Buffered when downstream has no demand. |
|
|
360
|
-
| `.error(err)` | Terminate with an error. |
|
|
361
|
-
| `.complete()` | Complete normally. |
|
|
362
|
-
| `.requested` | Current downstream demand count. |
|
|
363
|
-
| `.onRequest(fn)` | Register a callback invoked each time downstream requests more items. |
|
|
364
|
-
| `.onCancel(fn)` | Register a cancellation callback. |
|
|
365
|
-
| `.onDispose(fn)` | Register a callback invoked on any terminal event (cancel, error, complete). |
|
|
366
|
-
|
|
367
|
-
```typescript
|
|
368
|
-
Flux.create<number>(sink => {
|
|
369
|
-
const id = setInterval(() => {
|
|
370
|
-
if (sink.requested > 0) sink.next(Math.random());
|
|
371
|
-
}, 100);
|
|
372
|
-
sink.onDispose(() => clearInterval(id));
|
|
373
|
-
}).take(5).subscribe(v => console.log(v));
|
|
374
|
-
```
|
|
85
|
+
1. A `Publisher` knows how to produce values.
|
|
86
|
+
2. A `Subscriber` receives values.
|
|
87
|
+
3. A `Subscription` connects them.
|
|
88
|
+
4. The subscriber calls `request(n)` to say how many values it is ready for.
|
|
89
|
+
5. The subscriber can call `cancel()` to stop the work.
|
|
375
90
|
|
|
376
|
-
|
|
91
|
+
The four basic signals are:
|
|
377
92
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
onNext(v) { console.log(v); },
|
|
383
|
-
onError(e) { console.error(e); },
|
|
384
|
-
onComplete() { console.log('done'); },
|
|
385
|
-
});
|
|
93
|
+
- `onSubscribe(subscription)`: the connection is ready.
|
|
94
|
+
- `onNext(value)`: one value arrived.
|
|
95
|
+
- `onError(error)`: the stream failed and is now finished.
|
|
96
|
+
- `onComplete()`: the stream finished successfully.
|
|
386
97
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
e => console.error(e), // onError (optional, defaults to re-throw)
|
|
391
|
-
() => console.log('done'), // onComplete (optional)
|
|
392
|
-
);
|
|
98
|
+
`Mono` and `Flux` are higher-level APIs built on this idea. You usually compose
|
|
99
|
+
operators such as `map`, `filter`, `flatMap`, `timeout`, `retry` and
|
|
100
|
+
`collectList` instead of writing a raw subscriber.
|
|
393
101
|
|
|
394
|
-
|
|
395
|
-
|
|
102
|
+
```ts
|
|
103
|
+
const result = await Flux.just("1", "2", "bad", "3")
|
|
104
|
+
.flatMap(value => Mono.fromCallable(() => Number.parseInt(value, 10)))
|
|
105
|
+
.filter(value => Number.isFinite(value))
|
|
106
|
+
.collectList()
|
|
107
|
+
.block();
|
|
108
|
+
```
|
|
396
109
|
|
|
397
|
-
|
|
398
|
-
|
|
110
|
+
### Why Use Reactive Streams
|
|
111
|
+
|
|
112
|
+
- Lazy by default: nothing runs until somebody subscribes or awaits a terminal
|
|
113
|
+
operation.
|
|
114
|
+
- One model for sync values, promises, async iterables and custom publishers.
|
|
115
|
+
- Cancellation is part of the contract.
|
|
116
|
+
- Backpressure is part of the contract at the `Subscription` level.
|
|
117
|
+
- Errors are values in the pipeline, so fallback and retry logic stays local.
|
|
118
|
+
- `Context` can carry request metadata through nested async work.
|
|
119
|
+
- The same operators work for tiny one-value tasks and long-lived streams.
|
|
120
|
+
|
|
121
|
+
### Tradeoffs
|
|
122
|
+
|
|
123
|
+
- The mental model is more complex than `await fetch(...)`.
|
|
124
|
+
- Debugging long chains takes discipline. Use small functions and meaningful
|
|
125
|
+
operator boundaries.
|
|
126
|
+
- It is not free. Every operator adds a small amount of work.
|
|
127
|
+
- Browser APIs cannot always be paused. Some external sources may still produce
|
|
128
|
+
values faster than your code consumes them.
|
|
129
|
+
- Some Reactor compatibility operators are intentionally lightweight in the
|
|
130
|
+
browser implementation. For example, `metrics`, `name`, `tag`, `parallel` and
|
|
131
|
+
some backpressure operators mostly preserve API shape.
|
|
132
|
+
- For one tiny promise, plain `await` may be clearer.
|
|
133
|
+
|
|
134
|
+
## When To Use Mono, Flux, Sinks And Context
|
|
135
|
+
|
|
136
|
+
| Need | Use |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| One async result or no result | `Mono<T>` |
|
|
139
|
+
| Many values over time | `Flux<T>` |
|
|
140
|
+
| Adapt callback or event code into a stream | `Sinks` or `Flux.create` |
|
|
141
|
+
| Carry request/user/correlation metadata | `Context` |
|
|
142
|
+
| Change async execution timing | `Schedulers` |
|
|
143
|
+
| Implement a low-level stream bridge | `Publisher`, `Subscriber`, `Subscription` |
|
|
144
|
+
|
|
145
|
+
## Low-Level Reactive Streams Example
|
|
146
|
+
|
|
147
|
+
Most application code should use `Mono` and `Flux`. The low-level contracts are
|
|
148
|
+
there for adapters and integrations.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { Flux } from "reactor-core-ts";
|
|
152
|
+
import type { Subscriber, Subscription } from "reactor-core-ts";
|
|
153
|
+
|
|
154
|
+
const subscriber: Subscriber<number> = {
|
|
155
|
+
onSubscribe(subscription: Subscription) {
|
|
156
|
+
subscription.request(2);
|
|
157
|
+
},
|
|
158
|
+
onNext(value) {
|
|
159
|
+
console.log("value", value);
|
|
160
|
+
},
|
|
161
|
+
onError(error) {
|
|
162
|
+
console.error(error);
|
|
163
|
+
},
|
|
164
|
+
onComplete() {
|
|
165
|
+
console.log("done");
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
Flux.range(1, 3).subscribe(subscriber);
|
|
170
|
+
```
|
|
399
171
|
|
|
400
|
-
|
|
401
|
-
|
|
172
|
+
## Mono Static Factories
|
|
173
|
+
|
|
174
|
+
Each row includes a small example. The examples are intentionally short so the
|
|
175
|
+
table can stay usable as a reference.
|
|
176
|
+
|
|
177
|
+
| Static method | What it does and why | Example |
|
|
178
|
+
| --- | --- | --- |
|
|
179
|
+
| `Mono.just(value)` | Create a `Mono` with one known non-null value. | `await Mono.just(1).block()` |
|
|
180
|
+
| `Mono.empty()` | Complete without a value. Useful for optional work. | `await Mono.empty<number>().defaultIfEmpty(0).block()` |
|
|
181
|
+
| `Mono.never()` | Never emit and never complete. Useful in timeout tests. | `Mono.never<number>().timeout(100, Mono.just(0))` |
|
|
182
|
+
| `Mono.error(error)` | Fail when subscribed. | `Mono.error(new Error("boom")).onErrorReturn(0)` |
|
|
183
|
+
| `Mono.defer(supplier)` | Build the real source per subscription. | `Mono.defer(() => Mono.just(Date.now()))` |
|
|
184
|
+
| `Mono.deferContextual(supplier)` | Build the source from `Context`. | `Mono.deferContextual(ctx => Mono.just(ctx.get("id")))` |
|
|
185
|
+
| `Mono.first(...sources)` | Use the first source that signals. | `Mono.first(Mono.delay(10), Mono.just(1))` |
|
|
186
|
+
| `Mono.from(input)` | Adapt a publisher, iterable, async iterable or promise and keep at most one value. | `Mono.from(Promise.resolve(1))` |
|
|
187
|
+
| `Mono.fromPromise(promise)` | Adapt a `PromiseLike<T>`. | `Mono.fromPromise(fetch("/api"))` |
|
|
188
|
+
| `Mono.fromCompletionStage(stage)` | Reactor-compatible promise adapter. | `Mono.fromCompletionStage(() => Promise.resolve(1))` |
|
|
189
|
+
| `Mono.fromFuture(future)` | Reactor-compatible future adapter. | `Mono.fromFuture(() => Promise.resolve(1))` |
|
|
190
|
+
| `Mono.fromDirect(input)` | Adapt a source directly and keep Mono cardinality. | `Mono.fromDirect(Flux.just(1, 2))` |
|
|
191
|
+
| `Mono.fromSupplier(supplier)` | Run a synchronous supplier on subscription. Nullish means empty. | `Mono.fromSupplier(() => localStorage.getItem("token"))` |
|
|
192
|
+
| `Mono.fromCallable(callable)` | Run a synchronous callable and capture thrown errors. | `Mono.fromCallable(() => JSON.parse(text))` |
|
|
193
|
+
| `Mono.fromRunnable(runnable)` | Run side-effect work and complete empty. | `Mono.fromRunnable(() => cache.clear())` |
|
|
194
|
+
| `Mono.justOrEmpty(value)` | Emit the value only when it is not `null` or `undefined`. | `Mono.justOrEmpty(maybeUser)` |
|
|
195
|
+
| `Mono.ignoreElements(source)` | Wait for a source to finish and drop its values. | `Mono.ignoreElements(Flux.just(1, 2))` |
|
|
196
|
+
| `Mono.sequenceEqual(left, right)` | Compare two publishers item by item. | `Mono.sequenceEqual(Flux.just(1), Flux.just(1))` |
|
|
197
|
+
| `Mono.create(callback)` | Bridge callback-style code into a `Mono`. | `Mono.create(sink => sink.success(1))` |
|
|
198
|
+
| `Mono.delay(duration, scheduler?)` | Emit `0` after a delay. | `Mono.delay({ seconds: 1 })` |
|
|
199
|
+
| `Mono.when(...sources)` | Complete when all sources complete, failing fast on error. | `Mono.when(saveA(), saveB())` |
|
|
200
|
+
| `Mono.whenDelayError(...sources)` | Wait for all sources, then report delayed errors. | `Mono.whenDelayError(taskA(), taskB())` |
|
|
201
|
+
| `Mono.firstWithSignal(...sources)` | Mirror the first source to signal value, error or completion. | `Mono.firstWithSignal(slow(), fast())` |
|
|
202
|
+
| `Mono.firstWithValue(...sources)` | Use the first source that emits a value. Empty sources are skipped. | `Mono.firstWithValue(Mono.empty(), Mono.just(1))` |
|
|
203
|
+
| `Mono.zip(sources, combinator?)` | Wait for all Monos and combine their values. | `Mono.zip([userMono, rolesMono], ([u, r]) => ({ u, r }))` |
|
|
204
|
+
| `Mono.zipDelayError(sources, combinator?)` | Zip values but delay errors until all sources finish. | `Mono.zipDelayError([a, b])` |
|
|
205
|
+
| `Mono.using(resource, source, cleanup)` | Create and clean up a synchronous resource. | `Mono.using(open, r => Mono.just(r.id), close)` |
|
|
206
|
+
| `Mono.usingWhen(resourceMono, source, cleanup...)` | Create and clean up an async resource. | `Mono.usingWhen(connect(), c => query(c), closeAsync)` |
|
|
207
|
+
|
|
208
|
+
## Flux Static Factories
|
|
209
|
+
|
|
210
|
+
| Static method | What it does and why | Example |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| `Flux.just(...values)` | Emit known values and complete. | `Flux.just(1, 2, 3)` |
|
|
213
|
+
| `Flux.fromArray(values)` | Emit an array. | `Flux.fromArray([1, 2])` |
|
|
214
|
+
| `Flux.fromStream(stream)` | Emit an iterable or iterable supplier. | `Flux.fromStream(() => new Set([1, 2]))` |
|
|
215
|
+
| `Flux.fromIterable(values)` | Emit any iterable. | `Flux.fromIterable(new Map().keys())` |
|
|
216
|
+
| `Flux.from(input)` | Adapt publisher, iterable, async iterable or promise. | `Flux.from(fetch("/api").then(r => r.json()))` |
|
|
217
|
+
| `Flux.fromEvent(target, type, options?)` | Listen to a browser `EventTarget` and remove the listener on cancellation. | `Flux.fromEvent(button, "click").take(1)` |
|
|
218
|
+
| `Flux.fromWebSocket(socketOrUrl, options?)` | Listen to browser WebSocket messages and clean up listeners on cancellation. | `Flux.fromWebSocket<string>("wss://example.com/socket")` |
|
|
219
|
+
| `Flux.empty()` | Complete without values. | `Flux.empty<number>()` |
|
|
220
|
+
| `Flux.never()` | Never emit and never complete. | `Flux.never().timeout(100, Flux.just("fallback"))` |
|
|
221
|
+
| `Flux.error(error)` | Fail when subscribed. | `Flux.error(new Error("boom"))` |
|
|
222
|
+
| `Flux.defer(supplier)` | Create the real source for each subscription. | `Flux.defer(() => Flux.just(Date.now()))` |
|
|
223
|
+
| `Flux.deferContextual(supplier)` | Create the source from `Context`. | `Flux.deferContextual(ctx => Flux.just(ctx.get("id")))` |
|
|
224
|
+
| `Flux.create(callback)` | Bridge imperative multi-value code. | `Flux.create(s => { s.next(1); s.complete(); })` |
|
|
225
|
+
| `Flux.push(callback)` | Alias-style bridge for push sources. | `Flux.push(s => s.next(1))` |
|
|
226
|
+
| `Flux.generate(generator)` | Produce values synchronously through a sink. | `Flux.generate(s => { s.next(1); s.complete(); })` |
|
|
227
|
+
| `Flux.range(start, count)` | Emit consecutive numbers. | `Flux.range(10, 3)` |
|
|
228
|
+
| `Flux.interval(period, scheduler?)` | Emit increasing numbers on a timer. | `Flux.interval(100).take(3)` |
|
|
229
|
+
| `Flux.merge(...sources)` | Subscribe to sources and interleave values. | `Flux.merge(Flux.just(1), Flux.just(2))` |
|
|
230
|
+
| `Flux.mergeDelayError(...sources)` | Merge while delaying errors. | `Flux.mergeDelayError(a, b)` |
|
|
231
|
+
| `Flux.mergeSequential(...sources)` | Subscribe eagerly but emit in source order. | `Flux.mergeSequential(a, b)` |
|
|
232
|
+
| `Flux.mergeSequentialDelayError(...sources)` | Sequential merge with delayed errors. | `Flux.mergeSequentialDelayError(a, b)` |
|
|
233
|
+
| `Flux.mergeComparing(comparator, ...sources)` | Merge comparable sources by comparator. | `Flux.mergeComparing((a, b) => a - b, a, b)` |
|
|
234
|
+
| `Flux.mergeComparingDelayError(comparator, ...sources)` | Comparator merge with delayed errors. | `Flux.mergeComparingDelayError(compare, a, b)` |
|
|
235
|
+
| `Flux.mergeOrdered(comparator, ...sources)` | Ordered merge by comparator. | `Flux.mergeOrdered(compare, a, b)` |
|
|
236
|
+
| `Flux.mergePriority(comparator, ...sources)` | Priority-style merge by comparator. | `Flux.mergePriority(compare, a, b)` |
|
|
237
|
+
| `Flux.mergePriorityDelayError(comparator, ...sources)` | Priority merge with delayed errors. | `Flux.mergePriorityDelayError(compare, a, b)` |
|
|
238
|
+
| `Flux.concat(...sources)` | Run sources one after another. | `Flux.concat(Flux.just(1), Flux.just(2))` |
|
|
239
|
+
| `Flux.concatDelayError(...sources)` | Concat and delay errors. | `Flux.concatDelayError(a, b)` |
|
|
240
|
+
| `Flux.first(...sources)` | Mirror the first source that signals. | `Flux.first(slow, fast)` |
|
|
241
|
+
| `Flux.firstWithSignal(...sources)` | Same idea with explicit Reactor name. | `Flux.firstWithSignal(a, b)` |
|
|
242
|
+
| `Flux.firstWithValue(...sources)` | Use first source that emits a value. | `Flux.firstWithValue(Flux.empty(), Flux.just(1))` |
|
|
243
|
+
| `Flux.zip(sources, combinator?)` | Combine values position by position. | `Flux.zip([Flux.just(1), Flux.just(2)], ([a, b]) => a + b)` |
|
|
244
|
+
| `Flux.combineLatest(sources, combinator?)` | Combine latest values whenever a source updates. | `Flux.combineLatest([a, b])` |
|
|
245
|
+
| `Flux.switchOnNext(sources)` | Switch to the latest inner publisher. | `Flux.switchOnNext(Flux.just(Flux.just(1)))` |
|
|
246
|
+
| `Flux.using(resource, source, cleanup)` | Use and clean up a synchronous resource. | `Flux.using(open, r => Flux.just(r.id), close)` |
|
|
247
|
+
| `Flux.usingWhen(resource, source, cleanup...)` | Use and clean up an async resource. | `Flux.usingWhen(connect(), c => queryMany(c), closeAsync)` |
|
|
248
|
+
|
|
249
|
+
### Browser Source Examples
|
|
250
|
+
|
|
251
|
+
`Flux.fromEvent` is the small, direct bridge for DOM events. It adds one event
|
|
252
|
+
listener per subscription and removes it when the subscription is cancelled, for
|
|
253
|
+
example by `take(1)`, `timeout`, or manual cancellation.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const button = document.querySelector<HTMLButtonElement>("#save")!;
|
|
257
|
+
|
|
258
|
+
Flux.fromEvent(button, "click")
|
|
259
|
+
.map(event => ({ x: event.clientX, y: event.clientY }))
|
|
260
|
+
.take(1)
|
|
261
|
+
.subscribe(point => console.log("clicked", point));
|
|
402
262
|
```
|
|
403
263
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
| Method | Description |
|
|
413
|
-
|---|---|
|
|
414
|
-
| `Mono.just(value)` | Emit exactly one value, then complete. |
|
|
415
|
-
| `Mono.justOrEmpty(value)` | Emit `value` if not `null`/`undefined`, otherwise complete empty. |
|
|
416
|
-
| `Mono.fromPromise(promise)` | Wrap a `Promise<T>` — resolves to `onNext` + `onComplete`, rejects to `onError`. |
|
|
417
|
-
| `Mono.generate(sink => …)` | Imperative generator. Call `sink.next()` exactly once (or `sink.error()`/`sink.complete()`). |
|
|
418
|
-
| `Mono.create(sink => …)` | Alias for `Mono.generate`. |
|
|
419
|
-
| `Mono.fromCallable(fn)` | Lazy synchronous factory — calls `fn()` at subscribe time; propagates throws as `onError`. |
|
|
420
|
-
| `Mono.delay(ms)` | Emit `0` after `ms` milliseconds. |
|
|
421
|
-
| `Mono.from(publisher)` | Wrap any `Publisher<T>` as a `Mono<T>` (takes only the first item). |
|
|
422
|
-
| `Mono.defer(factory)` | Lazily create a new Mono per subscription via the factory function. |
|
|
423
|
-
| `Mono.empty()` | Complete immediately without emitting. |
|
|
424
|
-
| `Mono.error(err)` | Signal `onError` immediately. |
|
|
425
|
-
| `Mono.firstWithValue(...sources)` | Race: emit the first value from any of the given `Mono` sources. |
|
|
426
|
-
| `Mono.when(...sources)` | `Mono<void>` that completes when **all** given publishers complete. Values are ignored; the first error is propagated and all other sources are cancelled. |
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
429
|
-
import { Mono } from 'reactor-core-ts';
|
|
430
|
-
|
|
431
|
-
Mono.just(42).subscribe(v => console.log(v));
|
|
432
|
-
// 42
|
|
433
|
-
|
|
434
|
-
Mono.fromPromise(fetch('/api/user').then(r => r.json()))
|
|
435
|
-
.subscribe(user => console.log(user));
|
|
436
|
-
|
|
437
|
-
Mono.justOrEmpty(null).subscribe(
|
|
438
|
-
v => console.log('got:', v),
|
|
439
|
-
_e => {},
|
|
440
|
-
() => console.log('empty'),
|
|
441
|
-
);
|
|
442
|
-
// empty
|
|
443
|
-
|
|
444
|
-
// Mono.when — wait for multiple async operations to finish
|
|
445
|
-
Mono.when(
|
|
446
|
-
Mono.fromPromise(saveUser(user)),
|
|
447
|
-
Mono.fromPromise(sendEmail(user)),
|
|
448
|
-
).subscribe(undefined, undefined, () => console.log('all done'));
|
|
264
|
+
Use the generic overload when the target is only known as a plain
|
|
265
|
+
`EventTarget`.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
Flux.fromEvent<InputEvent>(searchInput, "input")
|
|
269
|
+
.map(event => (event.currentTarget as HTMLInputElement).value)
|
|
270
|
+
.filter(value => value.length > 1)
|
|
271
|
+
.subscribe(value => console.log("search", value));
|
|
449
272
|
```
|
|
450
273
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
| `.thenReturn(value)` | Ignore the emitted value; emit `value` after source completes. |
|
|
462
|
-
| `.delayElement(ms)` | Delay the emitted value by `ms` milliseconds. |
|
|
463
|
-
| `.delayUntil(triggerFn)` | Hold the emitted value until the trigger publisher fires, then forward it. |
|
|
464
|
-
| `.cast<R>()` | Unsafe type cast. |
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
Mono.just(42)
|
|
468
|
-
.map(n => n.toString())
|
|
469
|
-
.subscribe(s => console.log(s)); // '42'
|
|
470
|
-
|
|
471
|
-
Mono.just(5)
|
|
472
|
-
.flatMapMany(n => Flux.range(0, n))
|
|
473
|
-
.subscribe(v => console.log(v));
|
|
474
|
-
// 0 1 2 3 4
|
|
475
|
-
|
|
476
|
-
// then() — sequence operations, ignore intermediate values
|
|
477
|
-
Mono.just('step 1')
|
|
478
|
-
.then(Mono.just('step 2'))
|
|
479
|
-
.then(Mono.just('step 3'))
|
|
480
|
-
.subscribe(v => console.log(v));
|
|
481
|
-
// 'step 3'
|
|
482
|
-
|
|
483
|
-
// then() with no argument — completion signal only
|
|
484
|
-
Mono.fromPromise(deleteRecord(id))
|
|
485
|
-
.then()
|
|
486
|
-
.subscribe(undefined, undefined, () => console.log('deleted'));
|
|
274
|
+
`Flux.fromWebSocket` turns browser `message` events into stream values. Passing a
|
|
275
|
+
URL makes the Flux own the socket and close it on cancellation. Passing an
|
|
276
|
+
existing socket leaves it open unless `closeOnCancel: true` is set.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
Flux.fromWebSocket<string>("wss://example.com/feed")
|
|
280
|
+
.map(event => JSON.parse(event.data) as { type: string })
|
|
281
|
+
.filter(message => message.type === "price")
|
|
282
|
+
.take(10)
|
|
283
|
+
.subscribe(message => console.log(message));
|
|
487
284
|
```
|
|
488
285
|
|
|
489
|
-
|
|
286
|
+
```ts
|
|
287
|
+
const socket = new WebSocket("wss://example.com/feed");
|
|
288
|
+
|
|
289
|
+
Flux.fromWebSocket<ArrayBuffer>(socket, {
|
|
290
|
+
binaryType: "arraybuffer",
|
|
291
|
+
closeOnCancel: true
|
|
292
|
+
})
|
|
293
|
+
.map(event => event.data.byteLength)
|
|
294
|
+
.timeout(5000)
|
|
295
|
+
.subscribe(size => console.log("bytes", size));
|
|
296
|
+
```
|
|
490
297
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
298
|
+
## Mono Methods
|
|
299
|
+
|
|
300
|
+
`Mono` has at most one value. Many methods preserve that shape and return
|
|
301
|
+
`Mono`. Some methods intentionally return `Flux`, for example `repeat`,
|
|
302
|
+
`flatMapMany`, `concatWith` and `mergeWith`, because they can produce many
|
|
303
|
+
values.
|
|
304
|
+
|
|
305
|
+
Every row below includes a minimal example.
|
|
306
|
+
|
|
307
|
+
| Method | What it does and why | Example |
|
|
308
|
+
| --- | --- | --- |
|
|
309
|
+
| `subscribe` | Start the `Mono` with callbacks or a `Subscriber`. | `Mono.just(1).subscribe(console.log)` |
|
|
310
|
+
| `iterate` | Low-level iteration with `AbortSignal` and `Context`. | `Mono.just(1).iterate(signal, Context.empty())` |
|
|
311
|
+
| `toIterable` | Expose the Mono as an async iterable. | `for await (const v of Mono.just(1).toIterable()) {}` |
|
|
312
|
+
| `toArray` | Collect zero or one value into an array. | `await Mono.just(1).toArray()` |
|
|
313
|
+
| `toPromise` | Resolve to the value or `undefined`. | `await Mono.empty<number>().toPromise()` |
|
|
314
|
+
| `block` | Promise for the value or `undefined`. | `await Mono.just(1).block()` |
|
|
315
|
+
| `and` | Wait for this and another source, drop values. | `Mono.just(1).and(save())` |
|
|
316
|
+
| `as` | Pass the Mono to a custom transformer and return anything. | `Mono.just(1).as(source => source.block())` |
|
|
317
|
+
| `cast` | Type-cast the value. | `Mono.just("x").cast<string>()` |
|
|
318
|
+
| `concatWith` | Emit this value, then another publisher. Returns `Flux`. | `Mono.just(1).concatWith(Flux.just(2))` |
|
|
319
|
+
| `doFinally` | Run a callback after complete, error or cancel. | `Mono.just(1).doFinally(signal => console.log(signal))` |
|
|
320
|
+
| `doOnError` | Observe errors without handling them. | `Mono.error(error).doOnError(console.error)` |
|
|
321
|
+
| `doOnNext` | Observe the value without changing it. | `Mono.just(1).doOnNext(console.log)` |
|
|
322
|
+
| `doOnSuccess` | Observe success with value or `undefined`. | `Mono.empty().doOnSuccess(value => console.log(value))` |
|
|
323
|
+
| `doOnTerminate` | Run on complete or error. | `Mono.just(1).doOnTerminate(() => console.log("done"))` |
|
|
324
|
+
| `elapsed` | Pair the value with elapsed time since subscription. | `Mono.delay(10).elapsed()` |
|
|
325
|
+
| `flux` | Convert to `Flux`. | `Mono.just(1).flux()` |
|
|
326
|
+
| `handle` | Map, drop or error through a synchronous sink. | `Mono.just(1).handle((v, s) => s.next(v * 2))` |
|
|
327
|
+
| `hasElement` | Tell whether a value exists. | `Mono.empty().hasElement()` |
|
|
328
|
+
| `ignoreElement` | Drop the value and keep only completion or error. | `Mono.just(1).ignoreElement()` |
|
|
329
|
+
| `materialize` | Convert terminal/value signals into `Signal` objects. | `Mono.just(1).materialize()` |
|
|
330
|
+
| `mergeWith` | Merge this value with another source. Returns `Flux`. | `Mono.just(1).mergeWith(Flux.just(2))` |
|
|
331
|
+
| `onErrorMap` | Convert one error into another. | `Mono.error("x").onErrorMap(e => new Error(String(e)))` |
|
|
332
|
+
| `onErrorResume` | Recover with another publisher. | `Mono.error(error).onErrorResume(() => Mono.just(0))` |
|
|
333
|
+
| `onErrorReturn` | Recover with one fixed value. | `Mono.error(error).onErrorReturn(0)` |
|
|
334
|
+
| `publishOn` | Move downstream value delivery to a scheduler. | `Mono.just(1).publishOn(Schedulers.microtask())` |
|
|
335
|
+
| `repeat` | Re-subscribe and return a `Flux` of repeated values. | `Mono.just(1).repeat(2)` |
|
|
336
|
+
| `retry` | Retry after errors. | `unstableMono.retry(3)` |
|
|
337
|
+
| `single` | Assert that the Mono has one value. | `Mono.just(1).single()` |
|
|
338
|
+
| `subscribeOn` | Start subscription on a scheduler. | `Mono.fromCallable(load).subscribeOn(Schedulers.timeout())` |
|
|
339
|
+
| `subscribeWith` | Subscribe and return the same subscriber. | `Mono.just(1).subscribeWith(subscriber)` |
|
|
340
|
+
| `take` | Keep the value if present. | `Mono.just(1).take()` |
|
|
341
|
+
| `thenEmpty` | Wait for this, then wait for another empty source. | `Mono.just(1).thenEmpty(save())` |
|
|
342
|
+
| `thenMany` | Ignore this value, then run another publisher. | `Mono.just(1).thenMany(Flux.range(1, 3))` |
|
|
343
|
+
| `timeout` | Fail or switch to fallback if too slow. | `Mono.never().timeout(100, Mono.just(0))` |
|
|
344
|
+
| `timestamp` | Pair the value with scheduler time. | `Mono.just("a").timestamp()` |
|
|
345
|
+
| `transform` | Apply an assembly-time transformer. | `Mono.just(1).transform(m => m.map(v => v + 1))` |
|
|
346
|
+
| `transformDeferred` | Apply the transformer per subscription. | `Mono.just(1).transformDeferred(m => m.map(v => v + Date.now()))` |
|
|
347
|
+
| `transformDeferredContextual` | Transform per subscription with `Context`. | `Mono.just(1).transformDeferredContextual((m, ctx) => m.map(v => [v, ctx.get("id")]))` |
|
|
348
|
+
| `cache` | Reuse the result for later subscribers. | `expensiveMono.cache()` |
|
|
349
|
+
| `cacheInvalidateIf` | Cache with predicate-shaped invalidation support. | `expensiveMono.cacheInvalidateIf(value => value.stale)` |
|
|
350
|
+
| `cacheInvalidateWhen` | Cache with trigger-shaped invalidation support. | `expensiveMono.cacheInvalidateWhen(value => refreshSignal(value))` |
|
|
351
|
+
| `cancelOn` | Schedule cancellation work. | `Mono.never().cancelOn(Schedulers.timeout())` |
|
|
352
|
+
| `checkpoint` | Add a Reactor-style debugging boundary. | `Mono.just(1).checkpoint("load user")` |
|
|
353
|
+
| `contextCapture` | Reactor-compatible context capture hook. | `Mono.just(1).contextCapture()` |
|
|
354
|
+
| `contextWrite` | Add or change `Context` for upstream operators. | `Mono.deferContextual(ctx => Mono.just(ctx.get("id"))).contextWrite(Context.of("id", 1))` |
|
|
355
|
+
| `delayElement` | Delay the value. | `Mono.just(1).delayElement(100)` |
|
|
356
|
+
| `delaySubscription` | Delay subscription start. | `Mono.just(1).delaySubscription(100)` |
|
|
357
|
+
| `delayUntil` | Wait for a trigger derived from the value. | `Mono.just(user).delayUntil(user => audit(user))` |
|
|
358
|
+
| `dematerialize` | Convert `Signal` objects back into normal signals. | `Mono.just(signal).dematerialize()` |
|
|
359
|
+
| `doAfterTerminate` | Run after completion or error has been sent. | `Mono.just(1).doAfterTerminate(cleanup)` |
|
|
360
|
+
| `doFirst` | Run before subscription work starts. | `Mono.just(1).doFirst(() => console.log("first"))` |
|
|
361
|
+
| `doOnCancel` | Observe cancellation. | `Mono.never().doOnCancel(cleanup)` |
|
|
362
|
+
| `doOnDiscard` | Observe discarded values of a type. | `Mono.just(file).doOnDiscard(File, closeFile)` |
|
|
363
|
+
| `doOnEach` | Observe every `Signal`. | `Mono.just(1).doOnEach(signal => console.log(signal.type))` |
|
|
364
|
+
| `doOnRequest` | Observe downstream demand. | `Mono.just(1).doOnRequest(n => console.log(n))` |
|
|
365
|
+
| `doOnSubscribe` | Observe subscription creation. | `Mono.just(1).doOnSubscribe(s => s.request(1))` |
|
|
366
|
+
| `expand` | Breadth-first recursive expansion. Returns `Flux`. | `Mono.just(1).expand(v => v < 3 ? Mono.just(v + 1) : Mono.empty())` |
|
|
367
|
+
| `expandDeep` | Depth-first recursive expansion. Returns `Flux`. | `Mono.just(1).expandDeep(nextNode)` |
|
|
368
|
+
| `filterWhen` | Keep the value only if an async predicate emits true. | `Mono.just(user).filterWhen(user => canView(user))` |
|
|
369
|
+
| `flatMapIterable` | Turn one value into many iterable values. | `Mono.just([1, 2]).flatMapIterable(values => values)` |
|
|
370
|
+
| `hide` | Hide implementation identity. | `Mono.just(1).hide()` |
|
|
371
|
+
| `log` | Debug signals to `console.debug`. | `Mono.just(1).log("user")` |
|
|
372
|
+
| `mapNotNull` | Map and drop `null` or `undefined`. | `Mono.just(user).mapNotNull(user => user.name)` |
|
|
373
|
+
| `metrics` | Reactor-compatible metrics hook placeholder. | `Mono.just(1).metrics()` |
|
|
374
|
+
| `name` | Reactor-compatible name metadata hook. | `Mono.just(1).name("loadUser")` |
|
|
375
|
+
| `ofType` | Keep the value only if it is an instance of a class. | `Mono.just(value).ofType(User)` |
|
|
376
|
+
| `onErrorComplete` | Turn matching errors into empty completion. | `Mono.error(error).onErrorComplete()` |
|
|
377
|
+
| `onErrorContinue` | Observe an error and continue where supported. | `Mono.error(error).onErrorContinue((e, v) => console.log(e))` |
|
|
378
|
+
| `onErrorStop` | Restore stop-on-error behavior. | `Mono.just(1).onErrorStop()` |
|
|
379
|
+
| `onTerminateDetach` | Reactor-compatible detach hook. | `Mono.just(1).onTerminateDetach()` |
|
|
380
|
+
| `or` | Race with another publisher. | `Mono.never().or(Mono.just(1))` |
|
|
381
|
+
| `publish` | Share this Mono with a transformer. | `Mono.just(1).publish(shared => shared.map(v => v + 1))` |
|
|
382
|
+
| `repeatWhen` | Repeat under companion control. Returns `Flux`. | `Mono.just(1).repeatWhen(signals => signals.take(1))` |
|
|
383
|
+
| `repeatWhenEmpty` | Repeat when empty. | `Mono.empty<number>().repeatWhenEmpty(signals => signals.take(1))` |
|
|
384
|
+
| `retryWhen` | Retry under companion control. | `unstableMono.retryWhen(errors => errors.take(3))` |
|
|
385
|
+
| `share` | Share the Mono result shape. | `Mono.just(1).share()` |
|
|
386
|
+
| `singleOptional` | Emit the value or `undefined`. | `Mono.empty<number>().singleOptional()` |
|
|
387
|
+
| `tag` | Reactor-compatible metadata hook. | `Mono.just(1).tag("kind", "demo")` |
|
|
388
|
+
| `takeUntilOther` | Stop when another publisher signals. | `Mono.never().takeUntilOther(Mono.delay(100))` |
|
|
389
|
+
| `tap` | Observe lifecycle through a listener object. | `Mono.just(1).tap({ onNext: console.log })` |
|
|
390
|
+
| `timed` | Wrap the value in timing metadata. | `Mono.just(1).timed()` |
|
|
391
|
+
| `toFuture` | Promise alias for the value or `undefined`. | `await Mono.just(1).toFuture()` |
|
|
392
|
+
| `zipWhen` | Generate a second source from the first value and combine. | `Mono.just(user).zipWhen(user => loadRoles(user.id))` |
|
|
393
|
+
| `map` | Transform the value. | `Mono.just(1).map(v => v + 1)` |
|
|
394
|
+
| `flatMap` | Transform the value into another single-value source. | `Mono.just(1).flatMap(v => Mono.just(v + 1))` |
|
|
395
|
+
| `flatMapMany` | Transform the value into a many-value source. | `Mono.just(3).flatMapMany(n => Flux.range(1, n))` |
|
|
396
|
+
| `filter` | Keep the value only when the predicate is true. | `Mono.just(1).filter(v => v > 0)` |
|
|
397
|
+
| `defaultIfEmpty` | Replace empty completion with a value. | `Mono.empty<number>().defaultIfEmpty(0)` |
|
|
398
|
+
| `switchIfEmpty` | Replace empty completion with another source. | `Mono.empty<number>().switchIfEmpty(Mono.just(1))` |
|
|
399
|
+
| `then` | Ignore the value and complete empty. | `Mono.just(1).then()` |
|
|
400
|
+
| `thenReturn` | Ignore the value and emit a fixed value. | `Mono.just(1).thenReturn("done")` |
|
|
401
|
+
| `zipWith` | Combine this value with another publisher. | `Mono.just(1).zipWith(Mono.just(2), (a, b) => a + b)` |
|
|
402
|
+
|
|
403
|
+
## Flux Methods
|
|
404
|
+
|
|
405
|
+
`Flux` is for zero to many values. Every row includes a small example.
|
|
406
|
+
|
|
407
|
+
### Flux Core And Terminal Helpers
|
|
408
|
+
|
|
409
|
+
| Method | What it does and why | Example |
|
|
410
|
+
| --- | --- | --- |
|
|
411
|
+
| `subscribe` | Start the stream with callbacks or a `Subscriber`. | `Flux.just(1).subscribe(console.log)` |
|
|
412
|
+
| `iterate` | Low-level iteration with `AbortSignal` and `Context`. | `Flux.just(1).iterate(signal, Context.empty())` |
|
|
413
|
+
| `toIterable` | Expose as an async iterable. | `for await (const v of Flux.just(1).toIterable()) {}` |
|
|
414
|
+
| `toStream` | Alias-style async iterable export. | `for await (const v of Flux.just(1).toStream()) {}` |
|
|
415
|
+
| `toArray` | Collect all values into an array. | `await Flux.range(1, 3).toArray()` |
|
|
416
|
+
| `toPromise` | Resolve to the last value or `undefined`. | `await Flux.just(1, 2).toPromise()` |
|
|
417
|
+
| `blockFirst` | Promise for the first value. | `await Flux.just(1, 2).blockFirst()` |
|
|
418
|
+
| `blockLast` | Promise for the last value. | `await Flux.just(1, 2).blockLast()` |
|
|
419
|
+
| `subscribeWith` | Subscribe and return the same subscriber. | `Flux.just(1).subscribeWith(subscriber)` |
|
|
420
|
+
| `as` | Pass the Flux to a custom transformer and return anything. | `Flux.just(1).as(source => source.toArray())` |
|
|
421
|
+
|
|
422
|
+
### Flux Transform And Selection Methods
|
|
423
|
+
|
|
424
|
+
| Method | What it does and why | Example |
|
|
425
|
+
| --- | --- | --- |
|
|
426
|
+
| `map` | Transform each value. | `Flux.just(1).map(v => v + 1)` |
|
|
427
|
+
| `cast` | Type-cast values. | `Flux.just("a").cast<string>()` |
|
|
428
|
+
| `filter` | Keep values matching a predicate. | `Flux.range(1, 5).filter(v => v % 2 === 0)` |
|
|
429
|
+
| `filterWhen` | Keep values matching an async predicate. | `Flux.just(user).filterWhen(u => canView(u))` |
|
|
430
|
+
| `handle` | Map, drop or error through a synchronous sink. | `Flux.just(1).handle((v, s) => s.next(v * 2))` |
|
|
431
|
+
| `mapNotNull` | Map and drop `null` or `undefined`. | `Flux.just("a", "").mapNotNull(v => v || undefined)` |
|
|
432
|
+
| `ofType` | Keep only instances of a class. | `Flux.just(value).ofType(User)` |
|
|
433
|
+
| `take` | Keep the first `n` values. | `Flux.range(1, 10).take(3)` |
|
|
434
|
+
| `takeLast` | Keep the last `n` values. | `Flux.range(1, 10).takeLast(3)` |
|
|
435
|
+
| `takeWhile` | Keep values while a predicate is true. | `Flux.range(1, 10).takeWhile(v => v < 5)` |
|
|
436
|
+
| `takeUntil` | Keep values until a predicate becomes true. | `Flux.range(1, 10).takeUntil(v => v === 5)` |
|
|
437
|
+
| `takeUntilOther` | Stop when another publisher signals. | `Flux.interval(100).takeUntilOther(Mono.delay(500))` |
|
|
438
|
+
| `skip` | Drop the first `n` values. | `Flux.range(1, 5).skip(2)` |
|
|
439
|
+
| `skipLast` | Drop the last `n` values. | `Flux.range(1, 5).skipLast(2)` |
|
|
440
|
+
| `skipWhile` | Drop values while a predicate is true. | `Flux.range(1, 5).skipWhile(v => v < 3)` |
|
|
441
|
+
| `skipUntil` | Drop values until a predicate becomes true. | `Flux.range(1, 5).skipUntil(v => v === 3)` |
|
|
442
|
+
| `skipUntilOther` | Drop values until another publisher signals. | `Flux.interval(100).skipUntilOther(Mono.delay(300))` |
|
|
443
|
+
| `distinct` | Keep the first value for each key. | `Flux.just(1, 1, 2).distinct()` |
|
|
444
|
+
| `distinctUntilChanged` | Drop only adjacent duplicates. | `Flux.just(1, 1, 2, 1).distinctUntilChanged()` |
|
|
445
|
+
| `elementAt` | Pick a value by index. | `Flux.just("a", "b").elementAt(1)` |
|
|
446
|
+
| `defaultIfEmpty` | Emit a fallback value if empty. | `Flux.empty<number>().defaultIfEmpty(0)` |
|
|
447
|
+
| `switchIfEmpty` | Switch to another source if empty. | `Flux.empty<number>().switchIfEmpty(Flux.just(1))` |
|
|
448
|
+
| `materialize` | Turn stream signals into `Signal` values. | `Flux.just(1).materialize()` |
|
|
449
|
+
| `dematerialize` | Turn `Signal` values back into normal signals. | `Flux.just(signal).dematerialize()` |
|
|
450
|
+
| `transform` | Apply an assembly-time transformer. | `Flux.just(1).transform(f => f.map(v => v + 1))` |
|
|
451
|
+
| `transformDeferred` | Apply a transformer for each subscription. | `Flux.just(1).transformDeferred(f => f.map(v => v + Date.now()))` |
|
|
452
|
+
| `transformDeferredContextual` | Transform per subscription with `Context`. | `Flux.just(1).transformDeferredContextual((f, ctx) => f.map(v => [v, ctx.get("id")]))` |
|
|
453
|
+
| `hide` | Hide implementation identity. | `Flux.just(1).hide()` |
|
|
454
|
+
|
|
455
|
+
### Flux Flattening And Combining Methods
|
|
456
|
+
|
|
457
|
+
| Method | What it does and why | Example |
|
|
458
|
+
| --- | --- | --- |
|
|
459
|
+
| `flatMap` | Map each value to a publisher and merge results. | `Flux.just(1, 2).flatMap(v => Flux.just(v, v * 10))` |
|
|
460
|
+
| `flatMapDelayError` | Flat-map while delaying errors. | `Flux.just(1).flatMapDelayError(load)` |
|
|
461
|
+
| `flatMapSequential` | Flat-map with ordered output. | `Flux.just(1, 2).flatMapSequential(load)` |
|
|
462
|
+
| `flatMapSequentialDelayError` | Ordered flat-map with delayed errors. | `Flux.just(1, 2).flatMapSequentialDelayError(load)` |
|
|
463
|
+
| `flatMapIterable` | Map each value to an iterable and flatten. | `Flux.just([1, 2]).flatMapIterable(v => v)` |
|
|
464
|
+
| `concatMap` | Map each value to a publisher, one at a time. | `Flux.just(1, 2).concatMap(v => Flux.just(v, v + 10))` |
|
|
465
|
+
| `concatMapDelayError` | Concat-map while delaying errors. | `Flux.just(1, 2).concatMapDelayError(load)` |
|
|
466
|
+
| `concatMapIterable` | Map to iterables and concatenate them. | `Flux.just([1], [2]).concatMapIterable(v => v)` |
|
|
467
|
+
| `switchMap` | Switch to the newest inner publisher. | `Flux.just("a", "b").switchMap(load)` |
|
|
468
|
+
| `mergeWith` | Merge this source with others. | `Flux.just(1).mergeWith(Flux.just(2))` |
|
|
469
|
+
| `mergeComparingWith` | Merge with others using a comparator. | `Flux.just(1).mergeComparingWith((a, b) => a - b, Flux.just(2))` |
|
|
470
|
+
| `mergeOrderedWith` | Ordered comparator merge with this source. | `Flux.just(1).mergeOrderedWith((a, b) => a - b, Flux.just(2))` |
|
|
471
|
+
| `concatWith` | Append other publishers after this one. | `Flux.just(1).concatWith(Flux.just(2))` |
|
|
472
|
+
| `concatWithValues` | Append literal values. | `Flux.just(1).concatWithValues(2, 3)` |
|
|
473
|
+
| `startWith` | Prepend literal values. | `Flux.just(3).startWith(1, 2)` |
|
|
474
|
+
| `zipWith` | Zip this source with another publisher. | `Flux.just(1).zipWith(Flux.just(2), (a, b) => a + b)` |
|
|
475
|
+
| `zipWithIterable` | Zip this source with an iterable. | `Flux.just("a", "b").zipWithIterable([1, 2])` |
|
|
476
|
+
| `withLatestFrom` | Combine each value with latest value from another source. | `Flux.just(1).withLatestFrom(Flux.just(2))` |
|
|
477
|
+
| `switchOnFirst` | Inspect the first signal and choose a new source. | `Flux.just(1, 2).switchOnFirst((signal, source) => source)` |
|
|
478
|
+
| `or` | Race this source with another source. | `Flux.never().or(Flux.just(1))` |
|
|
479
|
+
|
|
480
|
+
### Flux Aggregation And Reduction Methods
|
|
481
|
+
|
|
482
|
+
| Method | What it does and why | Example |
|
|
483
|
+
| --- | --- | --- |
|
|
484
|
+
| `buffer` | Collect fixed-size arrays. | `Flux.range(1, 5).buffer(2)` |
|
|
485
|
+
| `bufferTimeout` | Buffer by max size or max time. | `Flux.interval(10).bufferTimeout(3, 100)` |
|
|
486
|
+
| `bufferUntil` | Buffer until a predicate matches. | `Flux.range(1, 5).bufferUntil(v => v % 2 === 0)` |
|
|
487
|
+
| `bufferUntilChanged` | Buffer adjacent values with the same key. | `Flux.just(1, 1, 2).bufferUntilChanged()` |
|
|
488
|
+
| `bufferWhen` | Open and close a buffer with publishers. | `Flux.just(1, 2).bufferWhen(Mono.just(0), () => Mono.delay(100))` |
|
|
489
|
+
| `bufferWhile` | Buffer while a predicate is true. | `Flux.range(1, 5).bufferWhile(v => v < 4)` |
|
|
490
|
+
| `window` | Split into fixed-size inner `Flux` windows. | `Flux.range(1, 5).window(2)` |
|
|
491
|
+
| `windowTimeout` | Window by max size or max time. | `Flux.interval(10).windowTimeout(3, 100)` |
|
|
492
|
+
| `windowUntil` | Window until a predicate matches. | `Flux.range(1, 5).windowUntil(v => v % 2 === 0)` |
|
|
493
|
+
| `windowUntilChanged` | Window adjacent values with the same key. | `Flux.just(1, 1, 2).windowUntilChanged()` |
|
|
494
|
+
| `windowWhen` | Open and close windows with publishers. | `Flux.just(1, 2).windowWhen(Mono.just(0), () => Mono.delay(100))` |
|
|
495
|
+
| `windowWhile` | Window while a predicate is true. | `Flux.range(1, 5).windowWhile(v => v < 4)` |
|
|
496
|
+
| `groupBy` | Group all values by key into grouped `Flux` values. | `Flux.just("a", "bb").groupBy(v => v.length)` |
|
|
497
|
+
| `scan` | Emit running accumulation. | `Flux.range(1, 3).scan((a, b) => a + b)` |
|
|
498
|
+
| `scanWith` | Emit running accumulation with supplied seed. | `Flux.range(1, 3).scanWith(() => 0, (a, b) => a + b)` |
|
|
499
|
+
| `reduce` | Reduce all values to one `Mono`. | `Flux.range(1, 3).reduce((a, b) => a + b)` |
|
|
500
|
+
| `reduceWith` | Reduce with a seed supplier. | `Flux.range(1, 3).reduceWith(() => 0, (a, b) => a + b)` |
|
|
501
|
+
| `collect` | Collect into a custom mutable container. | `Flux.just(1).collect(() => new Set<number>(), (set, v) => set.add(v))` |
|
|
502
|
+
| `collectList` | Collect all values into an array. | `Flux.range(1, 3).collectList()` |
|
|
503
|
+
| `collectMap` | Collect into a `Map` by key. | `Flux.just(user).collectMap(user => user.id)` |
|
|
504
|
+
| `collectMultimap` | Collect into a `Map<K, V[]>`. | `Flux.just(user).collectMultimap(user => user.role)` |
|
|
505
|
+
| `collectSortedList` | Collect and sort values. | `Flux.just(3, 1, 2).collectSortedList((a, b) => a - b)` |
|
|
506
|
+
| `sort` | Sort and re-emit values. | `Flux.just(3, 1, 2).sort((a, b) => a - b)` |
|
|
507
|
+
| `count` | Count values. | `Flux.just("a", "b").count()` |
|
|
508
|
+
| `any` | True if any value matches. | `Flux.range(1, 5).any(v => v > 3)` |
|
|
509
|
+
| `all` | True if all values match. | `Flux.range(1, 5).all(v => v > 0)` |
|
|
510
|
+
| `hasElement` | True if a specific value appears. | `Flux.just(1, 2).hasElement(2)` |
|
|
511
|
+
| `hasElements` | True if at least one value appears. | `Flux.empty().hasElements()` |
|
|
512
|
+
| `next` | Convert first value to `Mono`. | `Flux.just(1, 2).next()` |
|
|
513
|
+
| `single` | Expect one value, optionally with default. | `Flux.just(1).single()` |
|
|
514
|
+
| `singleOrEmpty` | Expect zero or one value. | `Flux.empty<number>().singleOrEmpty()` |
|
|
515
|
+
| `last` | Convert last value to `Mono`. | `Flux.just(1, 2).last()` |
|
|
516
|
+
| `ignoreElements` | Drop values and keep completion or error. | `Flux.just(1, 2).ignoreElements()` |
|
|
517
|
+
| `then` | Drop values and complete as `Mono<void>`. | `Flux.just(1, 2).then()` |
|
|
518
|
+
| `thenEmpty` | Wait for this, then another empty source. | `Flux.just(1).thenEmpty(save())` |
|
|
519
|
+
| `thenMany` | Wait for this, then run another publisher. | `Flux.just(1).thenMany(Flux.just(2))` |
|
|
520
|
+
| `thenReturn` | Wait for this, then emit one value. | `Flux.just(1).thenReturn("done")` |
|
|
521
|
+
|
|
522
|
+
### Flux Error And Repetition Methods
|
|
523
|
+
|
|
524
|
+
| Method | What it does and why | Example |
|
|
525
|
+
| --- | --- | --- |
|
|
526
|
+
| `onErrorResume` | Recover with another publisher. | `Flux.error(error).onErrorResume(() => Flux.just(0))` |
|
|
527
|
+
| `onErrorReturn` | Recover with a fixed value. | `Flux.error(error).onErrorReturn(0)` |
|
|
528
|
+
| `onErrorMap` | Convert an error into another error. | `Flux.error("x").onErrorMap(e => new Error(String(e)))` |
|
|
529
|
+
| `onErrorComplete` | Turn matching errors into completion. | `Flux.error(error).onErrorComplete()` |
|
|
530
|
+
| `onErrorContinue` | Observe an error and continue where supported. | `Flux.error(error).onErrorContinue((e, v) => console.log(e))` |
|
|
531
|
+
| `onErrorStop` | Restore stop-on-error behavior. | `Flux.just(1).onErrorStop()` |
|
|
532
|
+
| `retry` | Retry after failure. | `unstableFlux.retry(3)` |
|
|
533
|
+
| `retryWhen` | Retry under companion control. | `unstableFlux.retryWhen(errors => errors.take(3))` |
|
|
534
|
+
| `repeat` | Re-subscribe after completion. | `Flux.just(1).repeat(2)` |
|
|
535
|
+
| `repeatWhen` | Repeat under companion control. | `Flux.just(1).repeatWhen(signals => signals.take(1))` |
|
|
536
|
+
|
|
537
|
+
### Flux Lifecycle, Context And Metadata Methods
|
|
538
|
+
|
|
539
|
+
| Method | What it does and why | Example |
|
|
540
|
+
| --- | --- | --- |
|
|
541
|
+
| `cache` | Replay the result to later subscribers. | `loadUsers().cache()` |
|
|
542
|
+
| `share` | Share one subscription among subscribers where possible. | `Flux.interval(100).share()` |
|
|
543
|
+
| `shareNext` | Share and expose the next value as `Mono`. | `Flux.interval(100).shareNext()` |
|
|
544
|
+
| `publish` | Share with a transformer. | `Flux.just(1).publish(shared => shared.map(v => v + 1))` |
|
|
545
|
+
| `publishNext` | Publish the first value as a `Mono`. | `Flux.just(1, 2).publishNext()` |
|
|
546
|
+
| `replay` | Reactor-compatible replay hook. | `Flux.just(1).replay()` |
|
|
547
|
+
| `contextWrite` | Add or change `Context` for upstream operators. | `Flux.deferContextual(ctx => Flux.just(ctx.get("id"))).contextWrite(Context.of("id", 1))` |
|
|
548
|
+
| `contextCapture` | Reactor-compatible context capture hook. | `Flux.just(1).contextCapture()` |
|
|
549
|
+
| `doFirst` | Run before subscription work starts. | `Flux.just(1).doFirst(() => console.log("first"))` |
|
|
550
|
+
| `doOnSubscribe` | Observe subscription creation. | `Flux.just(1).doOnSubscribe(s => console.log(s))` |
|
|
551
|
+
| `doOnRequest` | Observe downstream demand. | `Flux.just(1).doOnRequest(n => console.log(n))` |
|
|
552
|
+
| `doOnNext` | Observe each value. | `Flux.just(1).doOnNext(console.log)` |
|
|
553
|
+
| `doOnError` | Observe errors. | `Flux.error(error).doOnError(console.error)` |
|
|
554
|
+
| `doOnComplete` | Observe successful completion. | `Flux.just(1).doOnComplete(() => console.log("done"))` |
|
|
555
|
+
| `doOnTerminate` | Run on complete or error. | `Flux.just(1).doOnTerminate(cleanup)` |
|
|
556
|
+
| `doAfterTerminate` | Run after termination was sent. | `Flux.just(1).doAfterTerminate(cleanup)` |
|
|
557
|
+
| `doFinally` | Run after complete, error or cancel. | `Flux.just(1).doFinally(signal => console.log(signal))` |
|
|
558
|
+
| `doOnCancel` | Observe cancellation. | `Flux.never().doOnCancel(cleanup)` |
|
|
559
|
+
| `doOnDiscard` | Observe discarded values of a type. | `Flux.just(file).doOnDiscard(File, closeFile)` |
|
|
560
|
+
| `doOnEach` | Observe every `Signal`. | `Flux.just(1).doOnEach(signal => console.log(signal.type))` |
|
|
561
|
+
| `tap` | Observe lifecycle with a listener object. | `Flux.just(1).tap({ onNext: console.log })` |
|
|
562
|
+
| `log` | Debug signals to `console.debug`. | `Flux.just(1).log("demo")` |
|
|
563
|
+
| `checkpoint` | Add a Reactor-style debugging boundary. | `Flux.just(1).checkpoint("after load")` |
|
|
564
|
+
| `name` | Reactor-compatible name metadata hook. | `Flux.just(1).name("numbers")` |
|
|
565
|
+
| `tag` | Reactor-compatible key/value metadata hook. | `Flux.just(1).tag("kind", "demo")` |
|
|
566
|
+
| `metrics` | Reactor-compatible metrics hook placeholder. | `Flux.just(1).metrics()` |
|
|
567
|
+
| `onTerminateDetach` | Reactor-compatible detach hook. | `Flux.just(1).onTerminateDetach()` |
|
|
568
|
+
| `limitRate` | Reactor-compatible request-shaping hook. | `Flux.range(1, 10).limitRate(2)` |
|
|
569
|
+
| `limitRequest` | Limit total requested values by taking `n`. | `Flux.range(1, 10).limitRequest(3)` |
|
|
570
|
+
| `onBackpressureBuffer` | Reactor-compatible backpressure buffer hook. | `Flux.just(1).onBackpressureBuffer()` |
|
|
571
|
+
| `onBackpressureDrop` | Reactor-compatible drop hook. | `Flux.just(1).onBackpressureDrop(v => console.log(v))` |
|
|
572
|
+
| `onBackpressureError` | Reactor-compatible backpressure error hook. | `Flux.just(1).onBackpressureError()` |
|
|
573
|
+
| `onBackpressureLatest` | Reactor-compatible latest-value hook. | `Flux.just(1).onBackpressureLatest()` |
|
|
574
|
+
| `cancelOn` | Schedule cancellation work. | `Flux.never().cancelOn(Schedulers.timeout())` |
|
|
575
|
+
|
|
576
|
+
### Flux Time, Scheduler And Coordination Methods
|
|
577
|
+
|
|
578
|
+
| Method | What it does and why | Example |
|
|
579
|
+
| --- | --- | --- |
|
|
580
|
+
| `delayElements` | Delay each value. | `Flux.just(1, 2).delayElements(100)` |
|
|
581
|
+
| `delaySequence` | Delay the whole sequence. | `Flux.just(1, 2).delaySequence(100)` |
|
|
582
|
+
| `delaySubscription` | Delay subscription start. | `Flux.just(1).delaySubscription(100)` |
|
|
583
|
+
| `delayUntil` | Wait for a trigger per value. | `Flux.just(user).delayUntil(user => audit(user))` |
|
|
584
|
+
| `timeout` | Fail or switch to fallback if values are too slow. | `Flux.never().timeout(100, Flux.just(0))` |
|
|
585
|
+
| `publishOn` | Move downstream delivery to a scheduler. | `Flux.just(1).publishOn(Schedulers.microtask())` |
|
|
586
|
+
| `subscribeOn` | Start subscription on a scheduler. | `Flux.from(fetchData()).subscribeOn(Schedulers.timeout())` |
|
|
587
|
+
| `timestamp` | Pair each value with scheduler time. | `Flux.just("a").timestamp()` |
|
|
588
|
+
| `elapsed` | Pair each value with elapsed time since previous value. | `Flux.just("a").elapsed()` |
|
|
589
|
+
| `timed` | Wrap each value in timing metadata. | `Flux.just(1).timed()` |
|
|
590
|
+
| `sample` | Emit latest values when a timer or sampler signals. | `Flux.interval(10).sample(100)` |
|
|
591
|
+
| `sampleFirst` | Emit first value in each time window. | `Flux.interval(10).sampleFirst(100)` |
|
|
592
|
+
| `sampleTimeout` | Emit a value if its throttler completes before a newer value arrives. | `Flux.just("a").sampleTimeout(() => Mono.delay(100))` |
|
|
593
|
+
| `groupJoin` | Join with grouped right-side values. | `left.groupJoin(right, lEnd, rEnd, (l, rs) => [l, rs])` |
|
|
594
|
+
| `join` | Join this source with another source. | `left.join(right, lEnd, rEnd, (l, r) => [l, r])` |
|
|
595
|
+
| `index` | Add a zero-based index. | `Flux.just("a", "b").index()` |
|
|
596
|
+
| `expand` | Breadth-first recursive expansion. | `Flux.just(1).expand(v => v < 3 ? Flux.just(v + 1) : Flux.empty())` |
|
|
597
|
+
| `expandDeep` | Depth-first recursive expansion. | `Flux.just(root).expandDeep(node => node.children)` |
|
|
598
|
+
| `parallel` | Reactor-compatible parallel hook; browser version stays lightweight. | `Flux.range(1, 3).parallel()` |
|
|
599
|
+
|
|
600
|
+
## Context
|
|
601
|
+
|
|
602
|
+
`Context` is an immutable key/value store that travels with a subscription. It
|
|
603
|
+
is useful for metadata that many functions need but that should not be threaded
|
|
604
|
+
through every function parameter.
|
|
605
|
+
|
|
606
|
+
Good examples:
|
|
607
|
+
|
|
608
|
+
- request id
|
|
609
|
+
- auth/session data
|
|
610
|
+
- tenant id
|
|
611
|
+
- locale
|
|
612
|
+
- tracing data
|
|
613
|
+
|
|
614
|
+
Bad examples:
|
|
615
|
+
|
|
616
|
+
- large mutable objects
|
|
617
|
+
- frequently changing state
|
|
618
|
+
- data that belongs in the actual stream value
|
|
619
|
+
|
|
620
|
+
### Context Basics
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
import { Context, Mono } from "reactor-core-ts";
|
|
624
|
+
|
|
625
|
+
const context = Context.empty()
|
|
626
|
+
.put("requestId", "req-123")
|
|
627
|
+
.put("locale", "en");
|
|
628
|
+
|
|
629
|
+
console.log(context.get("requestId")); // "req-123"
|
|
630
|
+
console.log(context.getOrDefault("missing", "fallback")); // "fallback"
|
|
631
|
+
console.log(context.hasKey("locale")); // true
|
|
632
|
+
```
|
|
495
633
|
|
|
496
|
-
|
|
634
|
+
`Context` is immutable. `put`, `putAll`, `putNonNull` and `delete` return a new
|
|
635
|
+
context.
|
|
497
636
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
| `.zipWhen(fn)` | Emit value `v`, then derive `fn(v)` as a second Mono; combine into `[T, R]`. |
|
|
637
|
+
```ts
|
|
638
|
+
const base = Context.of("requestId", "req-1");
|
|
639
|
+
const next = base.put("userId", 42);
|
|
502
640
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
// 1 'a'
|
|
641
|
+
console.log(base.hasKey("userId")); // false
|
|
642
|
+
console.log(next.get("userId")); // 42
|
|
643
|
+
```
|
|
507
644
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
645
|
+
### Context API
|
|
646
|
+
|
|
647
|
+
| Method | What it does | Example |
|
|
648
|
+
| --- | --- | --- |
|
|
649
|
+
| `Context.empty()` | Shared empty context. | `Context.empty()` |
|
|
650
|
+
| `Context.of(...)` | Build from key/value pairs. | `Context.of("id", 1, "role", "admin")` |
|
|
651
|
+
| `Context.from(entries)` | Build from an iterable or object. | `Context.from({ id: 1 })` |
|
|
652
|
+
| `put(key, value)` | Return a context with one value added or replaced. | `ctx.put("id", 1)` |
|
|
653
|
+
| `putNonNull(key, value)` | Add only when value is not `null` or `undefined`. | `ctx.putNonNull("id", maybeId)` |
|
|
654
|
+
| `putAll(other)` | Merge another context or entries. | `ctx.putAll(Context.of("role", "admin"))` |
|
|
655
|
+
| `delete(key)` | Return a context without a key. | `ctx.delete("id")` |
|
|
656
|
+
| `readOnly()` | Return a read-only `ContextView`. | `ctx.readOnly()` |
|
|
657
|
+
| `get(key)` | Read a required value or throw. | `ctx.get<string>("id")` |
|
|
658
|
+
| `getOrDefault(key, fallback)` | Read a value or fallback. | `ctx.getOrDefault("id", "none")` |
|
|
659
|
+
| `getOrEmpty(key)` | Read a value or `undefined`. | `ctx.getOrEmpty("id")` |
|
|
660
|
+
| `hasKey(key)` | Check whether a key exists. | `ctx.hasKey("id")` |
|
|
661
|
+
| `isEmpty()` | Check whether the context is empty. | `ctx.isEmpty()` |
|
|
662
|
+
| `size()` | Count entries. | `ctx.size()` |
|
|
663
|
+
| `forEach(consumer)` | Iterate entries. | `ctx.forEach((key, value) => console.log(key, value))` |
|
|
664
|
+
| `stream()` | Get an iterable of entries. | `[...ctx.stream()]` |
|
|
665
|
+
| `toMap()` | Get a read-only `Map`. | `ctx.toMap()` |
|
|
666
|
+
|
|
667
|
+
### Context In A Publisher
|
|
668
|
+
|
|
669
|
+
Use `contextWrite` near subscription side and `deferContextual` where you need
|
|
670
|
+
to read the values.
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
const message = await Mono.deferContextual(context =>
|
|
674
|
+
Mono.just(`request=${context.get("requestId")}`)
|
|
675
|
+
)
|
|
676
|
+
.contextWrite(Context.of("requestId", "req-123"))
|
|
677
|
+
.block();
|
|
678
|
+
|
|
679
|
+
console.log(message); // "request=req-123"
|
|
512
680
|
```
|
|
513
681
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
| Method | Description |
|
|
517
|
-
|---|---|
|
|
518
|
-
| `.doOnSuccess(fn)` | Run `fn(value)` when the Mono emits a value. Exceptions propagate as `onError`. |
|
|
519
|
-
| `.doFirst(fn)` | Run `fn` before the first item is emitted. |
|
|
520
|
-
| `.doOnNext(fn)` | Run `fn` for the emitted item. |
|
|
521
|
-
| `.doOnError(fn)` | Run `fn` when `onError` is received. |
|
|
522
|
-
| `.doOnSubscribe(fn)` | Run `fn` when `onSubscribe` is received. |
|
|
523
|
-
| `.doFinally(fn)` | Run `fn` after the stream terminates (complete or error). |
|
|
524
|
-
| `.doOnRequest(fn)` | Run `fn(n)` when downstream calls `request(n)`. |
|
|
525
|
-
| `.doOnEach(fn)` | Run `fn(signal)` for every signal as a `Signal<T>`. |
|
|
526
|
-
| `.log(label?)` | Console-log every signal. |
|
|
527
|
-
|
|
528
|
-
### Mono Error Handling
|
|
529
|
-
|
|
530
|
-
| Method | Description |
|
|
531
|
-
|---|---|
|
|
532
|
-
| `.retry(maxRetries?)` | Re-subscribe on error up to `maxRetries` times. |
|
|
533
|
-
| `.retryWhen(fn)` | Controlled retry — each error is pushed to a `Flux<Error>`; re-subscribe when the control emits. |
|
|
534
|
-
| `.onErrorReturn(replacement)` | On error, switch to `replacement` publisher. |
|
|
535
|
-
| `.onErrorResume(fn)` | On error, switch to the publisher returned by `fn(error)`. |
|
|
536
|
-
| `.onErrorMap(fn)` | Transform the error. |
|
|
537
|
-
| `.onErrorComplete(predicate?)` | Convert an error to a normal completion (optionally filtered by `predicate`). |
|
|
538
|
-
| `.timeout(ms, fallback?)` | Error with `TimeoutError` if no item arrives within `ms`. |
|
|
539
|
-
|
|
540
|
-
### Mono Scheduling
|
|
541
|
-
|
|
542
|
-
Same as Flux: `.publishOn(scheduler)`, `.subscribeOn(scheduler)`.
|
|
543
|
-
|
|
544
|
-
### Mono Utilities
|
|
545
|
-
|
|
546
|
-
| Method | Description |
|
|
547
|
-
|---|---|
|
|
548
|
-
| `.hasElement()` | `Mono<boolean>` — `true` if a value is emitted, `false` if it completes empty. |
|
|
549
|
-
| `.toPromise()` | `Promise<T | null>` — resolves with the emitted value or `null` if empty. |
|
|
550
|
-
| `.toFuture()` | Alias for `.toPromise()`. |
|
|
551
|
-
| `.or(other)` | Fall back to `other` Mono if this completes empty. |
|
|
552
|
-
| `.switchIfEmpty(alternative)` | Switch to `alternative` Mono if this completes empty. |
|
|
553
|
-
| `.pipe(producer, onRequest, onUnsubscribe)` | Low-level custom operator builder. |
|
|
554
|
-
|
|
555
|
-
### Mono Subscribe
|
|
556
|
-
|
|
557
|
-
```typescript
|
|
558
|
-
// Full Subscriber — full backpressure control
|
|
559
|
-
mono.subscribe({
|
|
560
|
-
onSubscribe(s) { s.request(1); },
|
|
561
|
-
onNext(v) { console.log(v); },
|
|
562
|
-
onError(e) { console.error(e); },
|
|
563
|
-
onComplete() { console.log('done'); },
|
|
564
|
-
});
|
|
682
|
+
Context flows through nested publishers:
|
|
565
683
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
)
|
|
684
|
+
```ts
|
|
685
|
+
const values = await Flux.just(1, 2)
|
|
686
|
+
.flatMap(value =>
|
|
687
|
+
Mono.deferContextual(context =>
|
|
688
|
+
Mono.just(`${context.get("requestId")}:${value}`)
|
|
689
|
+
)
|
|
690
|
+
)
|
|
691
|
+
.contextWrite(Context.of("requestId", "req-123"))
|
|
692
|
+
.collectList()
|
|
693
|
+
.block();
|
|
572
694
|
|
|
573
|
-
//
|
|
574
|
-
const value: number | null = await Mono.just(42).toPromise();
|
|
695
|
+
console.log(values); // ["req-123:1", "req-123:2"]
|
|
575
696
|
```
|
|
576
697
|
|
|
577
|
-
|
|
698
|
+
Use functions with `contextWrite` when you want to derive from an existing
|
|
699
|
+
context:
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
const value = await Mono.deferContextual(context =>
|
|
703
|
+
Mono.just(context.get("requestId"))
|
|
704
|
+
)
|
|
705
|
+
.contextWrite(context => context.put("requestId", "inner"))
|
|
706
|
+
.block();
|
|
707
|
+
```
|
|
578
708
|
|
|
579
709
|
## Sinks
|
|
580
710
|
|
|
581
|
-
|
|
711
|
+
A `Sink` is an imperative bridge. Use it when values come from callbacks,
|
|
712
|
+
browser events, WebSocket handlers or other code that pushes data into your app.
|
|
582
713
|
|
|
583
|
-
|
|
584
|
-
|
|
714
|
+
Normal `Mono` and `Flux` are usually cold: they do work when subscribed. A sink
|
|
715
|
+
is usually hot: you call `emitNext`, `emitValue`, `emitComplete` or `emitError`
|
|
716
|
+
from the outside.
|
|
585
717
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
718
|
+
### Emission Styles
|
|
719
|
+
|
|
720
|
+
Sink APIs come in two styles:
|
|
721
|
+
|
|
722
|
+
| Style | Behavior | Example |
|
|
723
|
+
| --- | --- | --- |
|
|
724
|
+
| `tryEmit...` | Return an `EmitResult`; you decide what to do. | `sink.tryEmitNext(1)` |
|
|
725
|
+
| `emit...` | Use an emission failure handler and throw only when the failure is not handled. | `sink.emitNext(1)` |
|
|
592
726
|
|
|
593
|
-
|
|
727
|
+
Common results:
|
|
594
728
|
|
|
595
|
-
|
|
729
|
+
| Result | Meaning |
|
|
730
|
+
| --- | --- |
|
|
731
|
+
| `OK` | The signal was accepted. |
|
|
732
|
+
| `FAIL_TERMINATED` | The sink already completed or failed. |
|
|
733
|
+
| `FAIL_ZERO_SUBSCRIBER` | This sink variant requires a subscriber but none exists. |
|
|
734
|
+
| `FAIL_OVERFLOW` | Buffer capacity was exhausted. |
|
|
735
|
+
| `FAIL_CANCELLED` | The sink was cancelled. |
|
|
736
|
+
| `FAIL_NON_SERIALIZED` | Concurrent emission was detected. |
|
|
596
737
|
|
|
597
|
-
###
|
|
738
|
+
### Empty And One Sinks
|
|
598
739
|
|
|
599
|
-
|
|
740
|
+
| Sink | What it is for | Example |
|
|
741
|
+
| --- | --- | --- |
|
|
742
|
+
| `Sinks.empty<T>()` | Only complete or fail a `Mono`; it never emits a value. | `const sink = Sinks.empty<void>(); sink.emitEmpty(); await sink.asMono().block();` |
|
|
743
|
+
| `Sinks.one<T>()` | Complete a `Mono` with one value, empty completion or error. | `const sink = Sinks.one<number>(); sink.emitValue(1); await sink.asMono().block();` |
|
|
600
744
|
|
|
601
|
-
|
|
745
|
+
`Sinks.one` methods:
|
|
746
|
+
|
|
747
|
+
```ts
|
|
602
748
|
const sink = Sinks.one<number>();
|
|
603
749
|
|
|
604
|
-
|
|
750
|
+
sink.tryEmitValue(1); // Emit one value.
|
|
751
|
+
sink.tryEmitEmpty(); // Complete without a value.
|
|
752
|
+
sink.tryEmitError(error); // Fail the Mono.
|
|
753
|
+
|
|
754
|
+
sink.emitValue(1); // Handler-based emission API.
|
|
755
|
+
sink.emitEmpty();
|
|
756
|
+
sink.emitError(error);
|
|
605
757
|
|
|
606
|
-
sink.
|
|
758
|
+
const mono = sink.asMono();
|
|
759
|
+
const count = sink.currentSubscriberCount();
|
|
607
760
|
```
|
|
608
761
|
|
|
609
|
-
|
|
762
|
+
`Sinks.empty` has the same terminal methods except value emission:
|
|
610
763
|
|
|
611
|
-
|
|
764
|
+
```ts
|
|
765
|
+
const sink = Sinks.empty<void>();
|
|
612
766
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
| `.onBackpressureError<T>()` | Drop items with an error when the subscriber has no pending demand. |
|
|
767
|
+
sink.emitEmpty();
|
|
768
|
+
await sink.asMono().block();
|
|
769
|
+
```
|
|
617
770
|
|
|
618
|
-
|
|
619
|
-
const sink = Sinks.many().unicast().onBackpressureBuffer<number>();
|
|
771
|
+
### Many Sink Variations
|
|
620
772
|
|
|
621
|
-
|
|
773
|
+
| Sink factory | What it does | Example |
|
|
774
|
+
| --- | --- | --- |
|
|
775
|
+
| `Sinks.many().unicast().onBackpressureBuffer<T>()` | One subscriber. Values emitted before the subscriber are buffered. | `const sink = Sinks.many().unicast().onBackpressureBuffer<number>();` |
|
|
776
|
+
| `Sinks.many().unicast().onBackpressureError<T>()` | One subscriber. Emission fails when no subscriber can receive it. | `sink.tryEmitNext(1) === "FAIL_ZERO_SUBSCRIBER"` |
|
|
777
|
+
| `Sinks.many().multicast().onBackpressureBuffer<T>()` | Many subscribers. Values can be buffered before subscribers appear. | `const sink = Sinks.many().multicast().onBackpressureBuffer<number>();` |
|
|
778
|
+
| `Sinks.many().multicast().directAllOrNothing<T>()` | Many subscribers. Fails when no subscriber can receive the value. | `Sinks.many().multicast().directAllOrNothing<number>()` |
|
|
779
|
+
| `Sinks.many().multicast().directBestEffort<T>()` | Many subscribers. Emits to currently available subscribers. | `Sinks.many().multicast().directBestEffort<number>()` |
|
|
780
|
+
| `Sinks.many().replay().all<T>()` | Remember every emitted value for late subscribers. | `Sinks.many().replay().all<number>()` |
|
|
781
|
+
| `Sinks.many().replay().limit<T>(historySize)` | Remember only the last `historySize` values. | `Sinks.many().replay().limit<number>(10)` |
|
|
782
|
+
| `Sinks.many().replay().latest<T>()` | Remember only the latest emitted value. | `Sinks.many().replay().latest<number>()` |
|
|
783
|
+
| `Sinks.many().replay().latestOrDefault<T>(value)` | Start with a default latest value. | `Sinks.many().replay().latestOrDefault<number>(0)` |
|
|
784
|
+
| `Sinks.unsafe().manyWithUpstream().multicastOnBackpressureBuffer<T>()` | Unsafe variant that can subscribe to one upstream publisher. | `Sinks.unsafe().manyWithUpstream().multicastOnBackpressureBuffer<number>()` |
|
|
622
785
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
sink.
|
|
627
|
-
|
|
786
|
+
### Many Sink Common Methods
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
const sink = Sinks.many().multicast().onBackpressureBuffer<number>();
|
|
790
|
+
|
|
791
|
+
sink.tryEmitNext(1);
|
|
792
|
+
sink.tryEmitComplete();
|
|
793
|
+
sink.tryEmitError(new Error("boom"));
|
|
794
|
+
|
|
795
|
+
sink.emitNext(2);
|
|
796
|
+
sink.emitComplete();
|
|
797
|
+
sink.emitError(new Error("boom"));
|
|
798
|
+
|
|
799
|
+
const flux = sink.asFlux();
|
|
800
|
+
const count = sink.currentSubscriberCount();
|
|
801
|
+
|
|
802
|
+
sink.subscribeTo(Flux.range(1, 3));
|
|
628
803
|
```
|
|
629
804
|
|
|
630
|
-
###
|
|
805
|
+
### Sinks.one Example
|
|
806
|
+
|
|
807
|
+
Use `Sinks.one` when a callback completes once.
|
|
631
808
|
|
|
632
|
-
|
|
809
|
+
```ts
|
|
810
|
+
function geolocation(): Mono<GeolocationPosition> {
|
|
811
|
+
const sink = Sinks.one<GeolocationPosition>();
|
|
633
812
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
813
|
+
navigator.geolocation.getCurrentPosition(
|
|
814
|
+
position => sink.emitValue(position),
|
|
815
|
+
error => sink.emitError(error)
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
return sink.asMono();
|
|
819
|
+
}
|
|
820
|
+
```
|
|
639
821
|
|
|
640
|
-
|
|
641
|
-
const sink = Sinks.many().multicast().onBackpressureBuffer<string>();
|
|
822
|
+
### Unicast Example
|
|
642
823
|
|
|
643
|
-
|
|
644
|
-
Flux.from(sink).subscribe(v => console.log('B:', v));
|
|
824
|
+
Use unicast when exactly one consumer should read the stream.
|
|
645
825
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
826
|
+
```ts
|
|
827
|
+
const sink = Sinks.many().unicast().onBackpressureBuffer<string>();
|
|
828
|
+
|
|
829
|
+
sink.emitNext("queued-before-subscribe");
|
|
830
|
+
|
|
831
|
+
const valuesPromise = sink.asFlux().take(2).toArray();
|
|
832
|
+
|
|
833
|
+
sink.emitNext("live");
|
|
834
|
+
sink.emitComplete();
|
|
835
|
+
|
|
836
|
+
console.log(await valuesPromise); // ["queued-before-subscribe", "live"]
|
|
649
837
|
```
|
|
650
838
|
|
|
651
|
-
###
|
|
839
|
+
### Multicast Example
|
|
652
840
|
|
|
653
|
-
|
|
841
|
+
Use multicast when multiple current subscribers should receive live values.
|
|
654
842
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
| `.all<T>()` | Replay every item ever emitted. |
|
|
658
|
-
| `.latest<T>(limit)` | Replay the last `limit` items. |
|
|
659
|
-
| `.latestOrDefault<T>(value)` | Replay the latest item, or `value` if nothing has been emitted yet. |
|
|
843
|
+
```ts
|
|
844
|
+
const sink = Sinks.many().multicast().onBackpressureBuffer<number>();
|
|
660
845
|
|
|
661
|
-
|
|
662
|
-
const
|
|
846
|
+
const first = sink.asFlux().take(2).toArray();
|
|
847
|
+
const second = sink.asFlux().take(2).toArray();
|
|
663
848
|
|
|
664
|
-
sink.
|
|
665
|
-
sink.
|
|
666
|
-
sink.
|
|
849
|
+
sink.emitNext(1);
|
|
850
|
+
sink.emitNext(2);
|
|
851
|
+
sink.emitComplete();
|
|
667
852
|
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
// 1 2 3
|
|
853
|
+
console.log(await first); // [1, 2]
|
|
854
|
+
console.log(await second); // [1, 2]
|
|
671
855
|
```
|
|
672
856
|
|
|
673
|
-
|
|
857
|
+
### Replay Examples
|
|
674
858
|
|
|
675
|
-
|
|
859
|
+
Use replay when late subscribers must see previous values.
|
|
676
860
|
|
|
677
|
-
|
|
861
|
+
```ts
|
|
862
|
+
const all = Sinks.many().replay().all<number>();
|
|
863
|
+
all.emitNext(1);
|
|
864
|
+
all.emitNext(2);
|
|
865
|
+
all.emitComplete();
|
|
678
866
|
|
|
679
|
-
|
|
680
|
-
import { Schedulers } from 'reactor-core-ts';
|
|
867
|
+
console.log(await all.asFlux().toArray()); // [1, 2]
|
|
681
868
|
```
|
|
682
869
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
```typescript
|
|
694
|
-
// Deliver items on the next event-loop tick
|
|
695
|
-
Flux.just(1, 2, 3)
|
|
696
|
-
.publishOn(Schedulers.macro())
|
|
697
|
-
.subscribe(v => console.log(v));
|
|
698
|
-
|
|
699
|
-
// Run the subscription (and source work) asynchronously
|
|
700
|
-
Flux.range(0, 1000)
|
|
701
|
-
.subscribeOn(Schedulers.micro())
|
|
702
|
-
.subscribe(v => process(v));
|
|
703
|
-
|
|
704
|
-
// One-shot delay
|
|
705
|
-
const task = Schedulers.delay(2000).schedule(() => console.log('2 s later'));
|
|
706
|
-
task.cancel(); // cancel before it fires
|
|
707
|
-
|
|
708
|
-
// Custom scheduler from any object that has a schedule(fn) method
|
|
709
|
-
const myScheduler = { schedule: (fn: () => void) => requestAnimationFrame(fn) };
|
|
710
|
-
Flux.just(1).publishOn(myScheduler).subscribe(v => console.log(v));
|
|
870
|
+
```ts
|
|
871
|
+
const latest = Sinks.many().replay().latestOrDefault<number>(0);
|
|
872
|
+
|
|
873
|
+
console.log(await latest.asFlux().take(1).toArray()); // [0]
|
|
874
|
+
|
|
875
|
+
latest.emitNext(10);
|
|
876
|
+
latest.emitComplete();
|
|
877
|
+
|
|
878
|
+
console.log(await latest.asFlux().toArray()); // [10]
|
|
711
879
|
```
|
|
712
880
|
|
|
713
|
-
|
|
881
|
+
### Choosing A Sink
|
|
882
|
+
|
|
883
|
+
| Situation | Recommended sink |
|
|
884
|
+
| --- | --- |
|
|
885
|
+
| One callback result | `Sinks.one()` |
|
|
886
|
+
| Only completion or error matters | `Sinks.empty()` |
|
|
887
|
+
| One consumer and values may arrive early | `unicast().onBackpressureBuffer()` |
|
|
888
|
+
| One consumer and values must not arrive early | `unicast().onBackpressureError()` |
|
|
889
|
+
| Several live subscribers | `multicast().onBackpressureBuffer()` |
|
|
890
|
+
| Late subscribers need history | `replay().all()` or `replay().limit(n)` |
|
|
891
|
+
| Late subscribers need only latest state | `replay().latest()` or `latestOrDefault(value)` |
|
|
892
|
+
|
|
893
|
+
Performance warning: replay sinks store history. Do not use `replay().all()` for
|
|
894
|
+
unbounded streams unless you really want to keep every value in memory.
|
|
895
|
+
|
|
896
|
+
## Schedulers
|
|
897
|
+
|
|
898
|
+
A scheduler controls when a task runs. In the browser there are no JVM-style
|
|
899
|
+
thread pools, so the primary schedulers are browser queues and timers. The
|
|
900
|
+
Reactor-style names remain available as aliases where they make migration
|
|
901
|
+
easier.
|
|
714
902
|
|
|
715
|
-
|
|
903
|
+
Durations can be a number of milliseconds or an object:
|
|
716
904
|
|
|
717
|
-
|
|
905
|
+
```ts
|
|
906
|
+
Mono.delay(250);
|
|
907
|
+
Mono.delay({ seconds: 1 });
|
|
908
|
+
Mono.delay({ minutes: 1, seconds: 30 });
|
|
909
|
+
```
|
|
718
910
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
911
|
+
### Scheduler Table
|
|
912
|
+
|
|
913
|
+
| Scheduler | Browser implementation | Use it when | Example |
|
|
914
|
+
| --- | --- | --- | --- |
|
|
915
|
+
| `Schedulers.immediate()` | Runs synchronously. | You want no async boundary. | `Schedulers.immediate().schedule(task)` |
|
|
916
|
+
| `Schedulers.micro()` | Uses `queueMicrotask`. | You want to yield to the microtask queue. | `Flux.just(1).publishOn(Schedulers.micro())` |
|
|
917
|
+
| `Schedulers.macro()` | Uses `setTimeout`. | You need a macrotask boundary. | `Mono.delay(100, Schedulers.macro())` |
|
|
918
|
+
| `Schedulers.delay(ms)` | Uses `setTimeout` with a default delay. | You want a reusable one-shot delay scheduler. | `Schedulers.delay(250).schedule(task)` |
|
|
919
|
+
| `Schedulers.interval(ms)` | Uses `setInterval` for periodic scheduling defaults. | You want a reusable periodic cadence. | `Schedulers.interval(1000).schedulePeriodically(tick)` |
|
|
920
|
+
| `Schedulers.animationFrame()` | Uses `requestAnimationFrame` when available. | UI work should align with painting. | `Schedulers.animationFrame().schedule(render)` |
|
|
921
|
+
| `Schedulers.microtask()` | Alias of `micro()`. | Legacy explicit microtask naming. | `Flux.just(1).publishOn(Schedulers.microtask())` |
|
|
922
|
+
| `Schedulers.timeout()` | Alias of `macro()`. | Legacy timeout-backed scheduling. | `Mono.delay(100, Schedulers.timeout())` |
|
|
923
|
+
| `Schedulers.single()` | Alias of `micro()`. | Reactor-style single scheduler in browser code. | `Flux.just(1).subscribeOn(Schedulers.single())` |
|
|
924
|
+
| `Schedulers.parallel()` | Alias of `micro()`. | Reactor-style parallel API shape without browser threads. | `Flux.just(1).publishOn(Schedulers.parallel())` |
|
|
925
|
+
| `Schedulers.boundedElastic()` | Alias of `macro()`. | Timer-backed async boundaries. | `Mono.fromCallable(load).subscribeOn(Schedulers.boundedElastic())` |
|
|
926
|
+
| `Schedulers.newSingle(name?)` | New microtask scheduler instance. | You want a named isolated scheduler object. | `Schedulers.newSingle("ui")` |
|
|
927
|
+
| `Schedulers.newParallel(name?)` | New microtask scheduler instance. | You want Reactor-compatible naming. | `Schedulers.newParallel("workers")` |
|
|
928
|
+
| `Schedulers.newBoundedElastic(name?)` | New macrotask scheduler instance. | You want a named timer-backed scheduler. | `Schedulers.newBoundedElastic("io")` |
|
|
929
|
+
| `Schedulers.fromExecutor(executor, name?)` | Wraps your executor function. | You need custom scheduling. | `Schedulers.fromExecutor(task => postTask(task), "postTask")` |
|
|
930
|
+
|
|
931
|
+
### Standalone Scheduler Usage
|
|
932
|
+
|
|
933
|
+
```ts
|
|
934
|
+
const scheduler = Schedulers.delay(250);
|
|
935
|
+
|
|
936
|
+
const delayed: Disposable = scheduler.schedule(() => {
|
|
937
|
+
console.log("runs later");
|
|
731
938
|
});
|
|
732
939
|
|
|
733
|
-
//
|
|
734
|
-
|
|
940
|
+
// Cancel before it runs.
|
|
941
|
+
delayed.dispose();
|
|
735
942
|
```
|
|
736
943
|
|
|
737
|
-
|
|
944
|
+
```ts
|
|
945
|
+
const worker = Schedulers.interval(1_000).createWorker();
|
|
738
946
|
|
|
739
|
-
|
|
947
|
+
worker.schedule(() => console.log("first"), 100);
|
|
948
|
+
worker.schedulePeriodically(() => console.log("tick"));
|
|
740
949
|
|
|
741
|
-
|
|
950
|
+
// Disposes every task scheduled by this worker.
|
|
951
|
+
worker.dispose();
|
|
952
|
+
```
|
|
742
953
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
npm install
|
|
954
|
+
### Scheduler Usage In Publishers
|
|
955
|
+
|
|
956
|
+
Use scheduler-aware factories and operators when timing matters.
|
|
747
957
|
|
|
748
|
-
|
|
749
|
-
|
|
958
|
+
```ts
|
|
959
|
+
const ticks = await Flux.interval(100, Schedulers.macro())
|
|
960
|
+
.take(3)
|
|
961
|
+
.toArray();
|
|
962
|
+
|
|
963
|
+
console.log(ticks); // [0, 1, 2]
|
|
750
964
|
```
|
|
751
965
|
|
|
752
|
-
|
|
966
|
+
```ts
|
|
967
|
+
const value = await Mono.fromCallable(() => JSON.parse(raw))
|
|
968
|
+
.subscribeOn(Schedulers.macro())
|
|
969
|
+
.publishOn(Schedulers.micro())
|
|
970
|
+
.timeout({ seconds: 1 }, Mono.just({ fallback: true }))
|
|
971
|
+
.block();
|
|
972
|
+
```
|
|
753
973
|
|
|
754
|
-
|
|
974
|
+
`subscribeOn` affects where subscription work starts. `publishOn` affects where
|
|
975
|
+
downstream delivery continues.
|
|
755
976
|
|
|
756
|
-
|
|
977
|
+
```ts
|
|
978
|
+
await Flux.just(1, 2)
|
|
979
|
+
.doOnNext(value => console.log("before", value))
|
|
980
|
+
.publishOn(Schedulers.micro())
|
|
981
|
+
.doOnNext(value => console.log("after", value))
|
|
982
|
+
.then()
|
|
983
|
+
.block();
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
For UI work, `animationFrame` is often a better boundary than `timeout`:
|
|
757
987
|
|
|
758
|
-
|
|
988
|
+
```ts
|
|
989
|
+
Flux.interval(16, Schedulers.animationFrame())
|
|
990
|
+
.take(10)
|
|
991
|
+
.subscribe(frame => draw(frame));
|
|
992
|
+
```
|
|
759
993
|
|
|
760
|
-
|
|
994
|
+
## Performance Notes For Browser Apps
|
|
995
|
+
|
|
996
|
+
- Prefer `Mono` when the source can only produce one value. It communicates
|
|
997
|
+
intent and avoids accidental many-value chains.
|
|
998
|
+
- Prefer `Flux.fromArray(array)` or `Flux.fromIterable(iterable)` for existing
|
|
999
|
+
in-memory data.
|
|
1000
|
+
- Keep chains lazy. Build pipelines once, subscribe when you need results.
|
|
1001
|
+
- Put `take`, `timeout` or cancellation around long-lived streams.
|
|
1002
|
+
- Avoid `replay().all()` for unbounded data.
|
|
1003
|
+
- Avoid heavy synchronous work in `map` on the UI path. Use a scheduler boundary
|
|
1004
|
+
or move the work outside the hot interaction path.
|
|
1005
|
+
- Use `Context` for small metadata only.
|
|
1006
|
+
- Use `Sinks` at the boundary of imperative systems. Inside your domain code,
|
|
1007
|
+
prefer pure `Mono` and `Flux` transformations.
|
|
1008
|
+
- Remember that browser JavaScript has one main thread unless you explicitly use
|
|
1009
|
+
Workers. `Schedulers.parallel()` provides API compatibility, not CPU
|
|
1010
|
+
parallelism by itself.
|
|
1011
|
+
|
|
1012
|
+
## Project Scripts
|
|
1013
|
+
|
|
1014
|
+
| Script | Purpose |
|
|
1015
|
+
| --- | --- |
|
|
1016
|
+
| `npm run build` | Clean `dist`, build the package with Vite and emit declarations. |
|
|
1017
|
+
| `npm test` | Run type-checking, build, Vitest, Java oracle, API parity and package dry-run checks. |
|
|
1018
|
+
| `npm run generate-fixtures` | Manually download Maven Reactor artifacts and regenerate the committed fixture. |
|
|
1019
|
+
|
|
1020
|
+
Tests read only the committed fixture in `test/fixtures`. They never expect a
|
|
1021
|
+
local Project Reactor checkout next to this repository. The `generate-fixtures`
|
|
1022
|
+
script is manual and refreshes that fixture from Maven artifacts.
|
|
1023
|
+
|
|
1024
|
+
## Practical Rules
|
|
1025
|
+
|
|
1026
|
+
- Start with `Mono` for one result and `Flux` for many results.
|
|
1027
|
+
- Use `map` for synchronous value changes.
|
|
1028
|
+
- Use `flatMap` when the next step returns a `Mono`, `Flux`, promise, iterable
|
|
1029
|
+
or async iterable.
|
|
1030
|
+
- Use `concatMap` when order matters more than concurrency.
|
|
1031
|
+
- Use `switchMap` when only the newest request matters, such as typeahead.
|
|
1032
|
+
- Use `timeout` around remote or long-lived work.
|
|
1033
|
+
- Use `onErrorResume` for real fallback logic.
|
|
1034
|
+
- Use `doOn...` methods for observation, not business logic.
|
|
1035
|
+
- Use `Context` for metadata, not data flow.
|
|
1036
|
+
- Use `Sinks` sparingly, mostly at callback/event boundaries.
|