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.
Files changed (269) hide show
  1. package/README.md +927 -651
  2. package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/defineProperty.js +12 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/toPrimitive.js +14 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/toPropertyKey.js +9 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/typeof.js +11 -0
  6. package/dist/context/context-view.d.ts +31 -0
  7. package/dist/context/context-view.d.ts.map +1 -0
  8. package/dist/context/context-view.js +58 -0
  9. package/dist/context/context-view.js.map +1 -0
  10. package/dist/context/context.d.ts +46 -0
  11. package/dist/context/context.d.ts.map +1 -0
  12. package/dist/context/context.js +125 -0
  13. package/dist/context/context.js.map +1 -0
  14. package/dist/context/types.d.ts +7 -0
  15. package/dist/context/types.d.ts.map +1 -0
  16. package/dist/core/boolean-disposable.d.ts +19 -0
  17. package/dist/core/boolean-disposable.d.ts.map +1 -0
  18. package/dist/core/boolean-disposable.js +37 -0
  19. package/dist/core/boolean-disposable.js.map +1 -0
  20. package/dist/core/composite-disposable.d.ts +21 -0
  21. package/dist/core/composite-disposable.d.ts.map +1 -0
  22. package/dist/core/composite-disposable.js +64 -0
  23. package/dist/core/composite-disposable.js.map +1 -0
  24. package/dist/core/demand.d.ts +9 -0
  25. package/dist/core/demand.d.ts.map +1 -0
  26. package/dist/core/demand.js +21 -0
  27. package/dist/core/demand.js.map +1 -0
  28. package/dist/core/disposables.d.ts +12 -0
  29. package/dist/core/disposables.d.ts.map +1 -0
  30. package/dist/core/disposables.js +37 -0
  31. package/dist/core/disposables.js.map +1 -0
  32. package/dist/core/teardown.d.ts +4 -0
  33. package/dist/core/teardown.d.ts.map +1 -0
  34. package/dist/core/types.d.ts +14 -0
  35. package/dist/core/types.d.ts.map +1 -0
  36. package/dist/errors/classes.d.ts +37 -0
  37. package/dist/errors/classes.d.ts.map +1 -0
  38. package/dist/errors/classes.js +53 -0
  39. package/dist/errors/classes.js.map +1 -0
  40. package/dist/errors/exceptions.d.ts +15 -0
  41. package/dist/errors/exceptions.d.ts.map +1 -0
  42. package/dist/errors/helpers.d.ts +9 -0
  43. package/dist/errors/helpers.d.ts.map +1 -0
  44. package/dist/errors/helpers.js +18 -0
  45. package/dist/errors/helpers.js.map +1 -0
  46. package/dist/index.d.ts +17 -2534
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +8 -2
  49. package/dist/internal/abort.d.ts +5 -0
  50. package/dist/internal/abort.d.ts.map +1 -0
  51. package/dist/internal/abort.js +36 -0
  52. package/dist/internal/abort.js.map +1 -0
  53. package/dist/internal/async-queue.d.ts +48 -0
  54. package/dist/internal/async-queue.d.ts.map +1 -0
  55. package/dist/internal/async-queue.js +187 -0
  56. package/dist/internal/async-queue.js.map +1 -0
  57. package/dist/internal/iterable-terminal.d.ts +12 -0
  58. package/dist/internal/iterable-terminal.d.ts.map +1 -0
  59. package/dist/internal/iterable-terminal.js +49 -0
  60. package/dist/internal/iterable-terminal.js.map +1 -0
  61. package/dist/internal/iterable-transform.d.ts +24 -0
  62. package/dist/internal/iterable-transform.d.ts.map +1 -0
  63. package/dist/internal/iterable-transform.js +159 -0
  64. package/dist/internal/iterable-transform.js.map +1 -0
  65. package/dist/internal/iterable.d.ts +23 -0
  66. package/dist/internal/iterable.d.ts.map +1 -0
  67. package/dist/internal/iterable.js +81 -0
  68. package/dist/internal/iterable.js.map +1 -0
  69. package/dist/internal/microtask.d.ts +7 -0
  70. package/dist/internal/microtask.d.ts.map +1 -0
  71. package/dist/internal/microtask.js +19 -0
  72. package/dist/internal/microtask.js.map +1 -0
  73. package/dist/publisher/browser-sources.d.ts +15 -0
  74. package/dist/publisher/browser-sources.d.ts.map +1 -0
  75. package/dist/publisher/browser-sources.js +92 -0
  76. package/dist/publisher/browser-sources.js.map +1 -0
  77. package/dist/publisher/flux-entrypoint.d.ts +7 -0
  78. package/dist/publisher/flux-entrypoint.d.ts.map +1 -0
  79. package/dist/publisher/flux-entrypoint.js +11 -0
  80. package/dist/publisher/flux-entrypoint.js.map +1 -0
  81. package/dist/publisher/flux.d.ts +121 -0
  82. package/dist/publisher/flux.d.ts.map +1 -0
  83. package/dist/publisher/flux.js +697 -0
  84. package/dist/publisher/flux.js.map +1 -0
  85. package/dist/publisher/helpers.d.ts +19 -0
  86. package/dist/publisher/helpers.d.ts.map +1 -0
  87. package/dist/publisher/helpers.js +84 -0
  88. package/dist/publisher/helpers.js.map +1 -0
  89. package/dist/publisher/mono-entrypoint.d.ts +7 -0
  90. package/dist/publisher/mono-entrypoint.d.ts.map +1 -0
  91. package/dist/publisher/mono-entrypoint.js +11 -0
  92. package/dist/publisher/mono-entrypoint.js.map +1 -0
  93. package/dist/publisher/mono.d.ts +72 -0
  94. package/dist/publisher/mono.d.ts.map +1 -0
  95. package/dist/publisher/mono.js +373 -0
  96. package/dist/publisher/mono.js.map +1 -0
  97. package/dist/publisher/operators/aggregate.d.ts +35 -0
  98. package/dist/publisher/operators/aggregate.d.ts.map +1 -0
  99. package/dist/publisher/operators/aggregate.js +217 -0
  100. package/dist/publisher/operators/aggregate.js.map +1 -0
  101. package/dist/publisher/operators/compat/flux.d.ts +56 -0
  102. package/dist/publisher/operators/compat/flux.d.ts.map +1 -0
  103. package/dist/publisher/operators/compat/flux.js +156 -0
  104. package/dist/publisher/operators/compat/flux.js.map +1 -0
  105. package/dist/publisher/operators/compat/mono.d.ts +82 -0
  106. package/dist/publisher/operators/compat/mono.d.ts.map +1 -0
  107. package/dist/publisher/operators/compat/mono.js +125 -0
  108. package/dist/publisher/operators/compat/mono.js.map +1 -0
  109. package/dist/publisher/operators/coordination.d.ts +82 -0
  110. package/dist/publisher/operators/coordination.d.ts.map +1 -0
  111. package/dist/publisher/operators/coordination.js +345 -0
  112. package/dist/publisher/operators/coordination.js.map +1 -0
  113. package/dist/publisher/operators/error.d.ts +17 -0
  114. package/dist/publisher/operators/error.d.ts.map +1 -0
  115. package/dist/publisher/operators/error.js +48 -0
  116. package/dist/publisher/operators/error.js.map +1 -0
  117. package/dist/publisher/operators/flatten.d.ts +19 -0
  118. package/dist/publisher/operators/flatten.d.ts.map +1 -0
  119. package/dist/publisher/operators/flatten.js +127 -0
  120. package/dist/publisher/operators/flatten.js.map +1 -0
  121. package/dist/publisher/operators/lifecycle.d.ts +103 -0
  122. package/dist/publisher/operators/lifecycle.d.ts.map +1 -0
  123. package/dist/publisher/operators/lifecycle.js +237 -0
  124. package/dist/publisher/operators/lifecycle.js.map +1 -0
  125. package/dist/publisher/operators/mono-parity.d.ts +109 -0
  126. package/dist/publisher/operators/mono-parity.d.ts.map +1 -0
  127. package/dist/publisher/operators/mono-parity.js +144 -0
  128. package/dist/publisher/operators/mono-parity.js.map +1 -0
  129. package/dist/publisher/operators/mono.d.ts +30 -0
  130. package/dist/publisher/operators/mono.d.ts.map +1 -0
  131. package/dist/publisher/operators/mono.js +37 -0
  132. package/dist/publisher/operators/mono.js.map +1 -0
  133. package/dist/publisher/operators/register.d.ts +20 -0
  134. package/dist/publisher/operators/register.d.ts.map +1 -0
  135. package/dist/publisher/operators/register.js +23 -0
  136. package/dist/publisher/operators/register.js.map +1 -0
  137. package/dist/publisher/operators/selection.d.ts +25 -0
  138. package/dist/publisher/operators/selection.d.ts.map +1 -0
  139. package/dist/publisher/operators/selection.js +108 -0
  140. package/dist/publisher/operators/selection.js.map +1 -0
  141. package/dist/publisher/operators/side-effect.d.ts +15 -0
  142. package/dist/publisher/operators/side-effect.d.ts.map +1 -0
  143. package/dist/publisher/operators/side-effect.js +56 -0
  144. package/dist/publisher/operators/side-effect.js.map +1 -0
  145. package/dist/publisher/operators/terminal.d.ts +16 -0
  146. package/dist/publisher/operators/terminal.d.ts.map +1 -0
  147. package/dist/publisher/operators/terminal.js +47 -0
  148. package/dist/publisher/operators/terminal.js.map +1 -0
  149. package/dist/publisher/operators/time.d.ts +20 -0
  150. package/dist/publisher/operators/time.d.ts.map +1 -0
  151. package/dist/publisher/operators/time.js +92 -0
  152. package/dist/publisher/operators/time.js.map +1 -0
  153. package/dist/publisher/operators/transform.d.ts +31 -0
  154. package/dist/publisher/operators/transform.d.ts.map +1 -0
  155. package/dist/publisher/operators/transform.js +74 -0
  156. package/dist/publisher/operators/transform.js.map +1 -0
  157. package/dist/publisher/operators/windowing.d.ts +28 -0
  158. package/dist/publisher/operators/windowing.d.ts.map +1 -0
  159. package/dist/publisher/operators/windowing.js +148 -0
  160. package/dist/publisher/operators/windowing.js.map +1 -0
  161. package/dist/publisher/publisher.d.ts +11 -0
  162. package/dist/publisher/publisher.d.ts.map +1 -0
  163. package/dist/publisher/types.d.ts +51 -0
  164. package/dist/publisher/types.d.ts.map +1 -0
  165. package/dist/schedulers/animation-frame-scheduler.d.ts +11 -0
  166. package/dist/schedulers/animation-frame-scheduler.d.ts.map +1 -0
  167. package/dist/schedulers/animation-frame-scheduler.js +30 -0
  168. package/dist/schedulers/animation-frame-scheduler.js.map +1 -0
  169. package/dist/schedulers/base-scheduler.d.ts +28 -0
  170. package/dist/schedulers/base-scheduler.d.ts.map +1 -0
  171. package/dist/schedulers/base-scheduler.js +77 -0
  172. package/dist/schedulers/base-scheduler.js.map +1 -0
  173. package/dist/schedulers/basic-worker.d.ts +22 -0
  174. package/dist/schedulers/basic-worker.d.ts.map +1 -0
  175. package/dist/schedulers/basic-worker.js +71 -0
  176. package/dist/schedulers/basic-worker.js.map +1 -0
  177. package/dist/schedulers/delay-scheduler.d.ts +12 -0
  178. package/dist/schedulers/delay-scheduler.d.ts.map +1 -0
  179. package/dist/schedulers/delay-scheduler.js +17 -0
  180. package/dist/schedulers/delay-scheduler.js.map +1 -0
  181. package/dist/schedulers/disposed.d.ts +4 -0
  182. package/dist/schedulers/disposed.d.ts.map +1 -0
  183. package/dist/schedulers/disposed.js +14 -0
  184. package/dist/schedulers/disposed.js.map +1 -0
  185. package/dist/schedulers/duration.d.ts +8 -0
  186. package/dist/schedulers/duration.d.ts.map +1 -0
  187. package/dist/schedulers/duration.js +11 -0
  188. package/dist/schedulers/duration.js.map +1 -0
  189. package/dist/schedulers/executor-scheduler.d.ts +13 -0
  190. package/dist/schedulers/executor-scheduler.d.ts.map +1 -0
  191. package/dist/schedulers/executor-scheduler.js +43 -0
  192. package/dist/schedulers/executor-scheduler.js.map +1 -0
  193. package/dist/schedulers/immediate-scheduler.d.ts +15 -0
  194. package/dist/schedulers/immediate-scheduler.d.ts.map +1 -0
  195. package/dist/schedulers/immediate-scheduler.js +24 -0
  196. package/dist/schedulers/immediate-scheduler.js.map +1 -0
  197. package/dist/schedulers/index.d.ts +7 -0
  198. package/dist/schedulers/index.d.ts.map +1 -0
  199. package/dist/schedulers/index.js +2 -0
  200. package/dist/schedulers/interval-scheduler.d.ts +12 -0
  201. package/dist/schedulers/interval-scheduler.d.ts.map +1 -0
  202. package/dist/schedulers/interval-scheduler.js +17 -0
  203. package/dist/schedulers/interval-scheduler.js.map +1 -0
  204. package/dist/schedulers/macro-scheduler.d.ts +19 -0
  205. package/dist/schedulers/macro-scheduler.d.ts.map +1 -0
  206. package/dist/schedulers/macro-scheduler.js +30 -0
  207. package/dist/schedulers/macro-scheduler.js.map +1 -0
  208. package/dist/schedulers/microtask-scheduler.d.ts +11 -0
  209. package/dist/schedulers/microtask-scheduler.d.ts.map +1 -0
  210. package/dist/schedulers/microtask-scheduler.js +34 -0
  211. package/dist/schedulers/microtask-scheduler.js.map +1 -0
  212. package/dist/schedulers/schedulers.d.ts +39 -0
  213. package/dist/schedulers/schedulers.d.ts.map +1 -0
  214. package/dist/schedulers/schedulers.js +83 -0
  215. package/dist/schedulers/schedulers.js.map +1 -0
  216. package/dist/schedulers/timeout-scheduler.d.ts +11 -0
  217. package/dist/schedulers/timeout-scheduler.d.ts.map +1 -0
  218. package/dist/schedulers/types.d.ts +35 -0
  219. package/dist/schedulers/types.d.ts.map +1 -0
  220. package/dist/signal/signal-type.d.ts +26 -0
  221. package/dist/signal/signal-type.d.ts.map +1 -0
  222. package/dist/signal/signal-type.js +31 -0
  223. package/dist/signal/signal-type.js.map +1 -0
  224. package/dist/signal/signal.d.ts +33 -0
  225. package/dist/signal/signal.d.ts.map +1 -0
  226. package/dist/signal/signal.js +70 -0
  227. package/dist/signal/signal.js.map +1 -0
  228. package/dist/sinks/index.d.ts +7 -0
  229. package/dist/sinks/index.d.ts.map +1 -0
  230. package/dist/sinks/index.js +2 -0
  231. package/dist/sinks/many.d.ts +66 -0
  232. package/dist/sinks/many.d.ts.map +1 -0
  233. package/dist/sinks/many.js +193 -0
  234. package/dist/sinks/many.js.map +1 -0
  235. package/dist/sinks/one.d.ts +41 -0
  236. package/dist/sinks/one.d.ts.map +1 -0
  237. package/dist/sinks/one.js +144 -0
  238. package/dist/sinks/one.js.map +1 -0
  239. package/dist/sinks/specs.d.ts +25 -0
  240. package/dist/sinks/specs.d.ts.map +1 -0
  241. package/dist/sinks/specs.js +126 -0
  242. package/dist/sinks/specs.js.map +1 -0
  243. package/dist/sinks/subscribers.d.ts +16 -0
  244. package/dist/sinks/subscribers.d.ts.map +1 -0
  245. package/dist/sinks/subscribers.js +32 -0
  246. package/dist/sinks/subscribers.js.map +1 -0
  247. package/dist/sinks/types.d.ts +137 -0
  248. package/dist/sinks/types.d.ts.map +1 -0
  249. package/dist/sinks/types.js +58 -0
  250. package/dist/sinks/types.js.map +1 -0
  251. package/dist/subscription/core-subscriber.d.ts +12 -0
  252. package/dist/subscription/core-subscriber.d.ts.map +1 -0
  253. package/dist/subscription/iterable-subscription.d.ts +64 -0
  254. package/dist/subscription/iterable-subscription.d.ts.map +1 -0
  255. package/dist/subscription/iterable-subscription.js +281 -0
  256. package/dist/subscription/iterable-subscription.js.map +1 -0
  257. package/dist/subscription/reactive-streams.d.ts +8 -0
  258. package/dist/subscription/reactive-streams.d.ts.map +1 -0
  259. package/dist/subscription/reactive-streams.js +0 -0
  260. package/dist/subscription/subscriber.d.ts +17 -0
  261. package/dist/subscription/subscriber.d.ts.map +1 -0
  262. package/dist/subscription/subscription.d.ts +12 -0
  263. package/dist/subscription/subscription.d.ts.map +1 -0
  264. package/package.json +99 -33
  265. package/LICENSE.md +0 -56
  266. package/dist/index.d.mts +0 -2535
  267. package/dist/index.js.map +0 -1
  268. package/dist/index.mjs +0 -2
  269. package/dist/index.mjs.map +0 -1
package/README.md CHANGED
@@ -1,760 +1,1036 @@
1
1
  # reactor-core-ts
2
2
 
3
- A TypeScript implementation of [Reactive Streams](https://www.reactive-streams.org/), inspired by [Project Reactor](https://projectreactor.io/). Provides `Flux` and `Mono` publishers with full backpressure support, a suite of composable operators, programmable Sinks, and pluggable Schedulers.
4
-
5
- ## Table of Contents
6
-
7
- - [Installation](#installation)
8
- - [Core Concepts](#core-concepts)
9
- - [Flux](#flux)
10
- - [Factories](#flux-factories)
11
- - [Transformation](#flux-transformation)
12
- - [Filtering](#flux-filtering)
13
- - [Aggregation](#flux-aggregation)
14
- - [Combining](#flux-combining)
15
- - [Backpressure](#flux-backpressure)
16
- - [Side effects](#flux-side-effects)
17
- - [Error handling](#flux-error-handling)
18
- - [Scheduling](#flux-scheduling)
19
- - [Utilities](#flux-utilities)
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
- ```bash
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
- ### Flux Transformation
117
-
118
- | Method | Description |
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
- ### Flux Filtering
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
- ### Flux Aggregation
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
- ### Flux Combining
238
-
239
- | Method | Description |
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
- ### Flux Backpressure
267
-
268
- | Method | Description |
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
- ### Flux Scheduling
52
+ ```ts
53
+ import { Flux, Mono, Sinks } from "reactor-core-ts";
315
54
 
316
- | Method | Description |
317
- |---|---|
318
- | `.publishOn(scheduler)` | Deliver `onNext`, `onError`, `onComplete` signals through the given `Scheduler`. |
319
- | `.subscribeOn(scheduler)` | Perform the upstream subscription (and therefore the source work) on the given `Scheduler`. |
320
- | `.delayElements(ms)` | Delay each item by `ms` milliseconds using `Schedulers.delay(ms)`. |
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
- ```typescript
323
- const asyncScheduler = Schedulers.macro(); // setTimeout-based
61
+ console.log(numbers); // [20, 40, 60]
324
62
 
325
- Flux.just(1, 2, 3)
326
- .publishOn(asyncScheduler)
327
- .subscribe(v => console.log('received asynchronously:', v));
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
- Flux.just('a', 'b', 'c')
330
- .delayElements(500)
331
- .subscribe(v => console.log(v)); // each item arrives 500 ms after the previous
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
- ### Flux Utilities
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
- | Method | Description |
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
- ```typescript
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
- hot.subscribe(v => console.log('A:', v));
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
- ### FluxSink
354
-
355
- `Flux.create(emitter => …)` hands the `emitter` callback a `FluxSink<T>` with:
356
-
357
- | Member | Description |
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
- ### Flux Subscribe
91
+ The four basic signals are:
377
92
 
378
- ```typescript
379
- // Full Subscriber full backpressure control
380
- const sub = flux.subscribe({
381
- onSubscribe(s) { s.request(10); },
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
- // Callback convenience auto-requests Number.MAX_SAFE_INTEGER
388
- flux.subscribe(
389
- v => console.log(v), // onNext (optional)
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
- // Minimal — just consume items
395
- flux.subscribe(v => console.log(v));
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
- // No callbacks drain silently
398
- flux.subscribe();
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
- // All overloads return a Subscription
401
- sub.unsubscribe(); // cancel at any time
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
- ## Mono
407
-
408
- `Mono<T>` is a cold publisher of at most 1 item. Each subscriber gets an independent run.
409
-
410
- ### Mono Factories
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
- ### Mono Transformation
452
-
453
- | Method | Description |
454
- |---|---|
455
- | `.map(fn)` | Transform the value `T → R`. |
456
- | `.mapNotNull(fn)` | Transform `T → R | null | undefined`; if result is null/undefined, complete empty. |
457
- | `.flatMap(fn)` | Map the value to a `Mono<R>`, then subscribe to that inner Mono. |
458
- | `.flatMapMany(fn)` | Map the value to a `Flux<R>` or `Mono<R>`, returning a `Flux<R>`. |
459
- | `.then()` | `Mono<void>` — ignore the emitted value; complete when source completes. |
460
- | `.then(other)` | Ignore the emitted value; subscribe to `other` after source completes and return `other`'s result. |
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
- ### Mono Filtering
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
- | Method | Description |
492
- |---|---|
493
- | `.filter(predicate)` | Pass the value only if `predicate` returns `true`; otherwise complete empty. |
494
- | `.filterWhen(predicate)` | Async filter subscribe to `predicate(value)` and pass the value only if it emits `true`. |
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
- ### Mono Combining
634
+ `Context` is immutable. `put`, `putAll`, `putNonNull` and `delete` return a new
635
+ context.
497
636
 
498
- | Method | Description |
499
- |---|---|
500
- | `.zipWith(other)` | Concurrently subscribe to this and `other`; combine their values into `[T, R]`. |
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
- ```typescript
504
- Mono.just(1).zipWith(Mono.just('a'))
505
- .subscribe(([n, s]) => console.log(n, s));
506
- // 1 'a'
641
+ console.log(base.hasKey("userId")); // false
642
+ console.log(next.get("userId")); // 42
643
+ ```
507
644
 
508
- Mono.just(42)
509
- .zipWhen(n => Mono.just(n.toString()))
510
- .subscribe(([n, s]) => console.log(n, s));
511
- // 42 '42'
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
- ### Mono Side Effects
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
- // Callback convenience — auto-requests 1
567
- mono.subscribe(
568
- v => console.log(v), // onNext (optional)
569
- e => console.error(e), // onError (optional)
570
- () => console.log('done'), // onComplete (optional)
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
- // Promise bridge
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
- Sinks are imperative bridges that let external code push values into a reactive stream. The `Sinks` factory returns a `SinkPublisher<T>` — an object that implements both `Sink<T>` (push API) and `Publisher<T>` (subscribe API), plus a convenience `asFlux()` method that wraps the sink as a full-featured `Flux<T>` with all operators available.
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
- ```typescript
584
- import { Sinks, Flux } from 'reactor-core-ts';
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
- // asFlux() gives access to all Flux operators
587
- const sink = Sinks.many().replay().all<number>();
588
- sink.next(1);
589
- sink.next(2);
590
- sink.asFlux().map(n => n * 10).subscribe(v => console.log(v)); // 10 20
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
- ### `Sinks.empty<T>()`
727
+ Common results:
594
728
 
595
- Completes immediately on subscribe. Useful as a sentinel or no-op source.
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
- ### `Sinks.one<T>()`
738
+ ### Empty And One Sinks
598
739
 
599
- Accepts a single `next(v)` call. Further calls are ignored. Suitable for request/response patterns.
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
- ```typescript
745
+ `Sinks.one` methods:
746
+
747
+ ```ts
602
748
  const sink = Sinks.one<number>();
603
749
 
604
- Flux.from(sink).subscribe(v => console.log('got:', v));
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.next(42); // got: 42
758
+ const mono = sink.asMono();
759
+ const count = sink.currentSubscriberCount();
607
760
  ```
608
761
 
609
- ### `Sinks.many().unicast()`
762
+ `Sinks.empty` has the same terminal methods except value emission:
610
763
 
611
- Only one subscriber allowed. Queues items until the subscriber requests them.
764
+ ```ts
765
+ const sink = Sinks.empty<void>();
612
766
 
613
- | Variant | Description |
614
- |---|---|
615
- | `.onBackpressureBuffer<T>()` | Buffer all items until subscriber requests them. |
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
- ```typescript
619
- const sink = Sinks.many().unicast().onBackpressureBuffer<number>();
771
+ ### Many Sink Variations
620
772
 
621
- Flux.from(sink).subscribe(v => console.log(v));
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
- sink.next(1);
624
- sink.next(2);
625
- sink.next(3);
626
- sink.complete();
627
- // 1 2 3
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
- ### `Sinks.many().multicast()`
805
+ ### Sinks.one Example
806
+
807
+ Use `Sinks.one` when a callback completes once.
631
808
 
632
- Multiple subscribers share the same stream. Items are dispatched to all current subscribers simultaneously.
809
+ ```ts
810
+ function geolocation(): Mono<GeolocationPosition> {
811
+ const sink = Sinks.one<GeolocationPosition>();
633
812
 
634
- | Variant | Description |
635
- |---|---|
636
- | `.directAllOrNothing<T>()` | Deliver to all subscribers if all have demand; otherwise signal an error. |
637
- | `.directBestEffort<T>()` | Deliver to subscribers that have demand; silently skip those that don't. |
638
- | `.onBackpressureBuffer<T>(bufferSize?, autoCancel?)` | Buffer items per-subscriber (up to `bufferSize`, default 256). When `autoCancel` is `true` (default), upstream is cancelled when all subscribers unsubscribe. |
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
- ```typescript
641
- const sink = Sinks.many().multicast().onBackpressureBuffer<string>();
822
+ ### Unicast Example
642
823
 
643
- Flux.from(sink).subscribe(v => console.log('A:', v));
644
- Flux.from(sink).subscribe(v => console.log('B:', v));
824
+ Use unicast when exactly one consumer should read the stream.
645
825
 
646
- sink.next('hello');
647
- // A: hello
648
- // B: hello
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
- ### `Sinks.many().replay()`
839
+ ### Multicast Example
652
840
 
653
- Cache emitted items and replay them to new subscribers.
841
+ Use multicast when multiple current subscribers should receive live values.
654
842
 
655
- | Variant | Description |
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
- ```typescript
662
- const sink = Sinks.many().replay().all<number>();
846
+ const first = sink.asFlux().take(2).toArray();
847
+ const second = sink.asFlux().take(2).toArray();
663
848
 
664
- sink.next(1);
665
- sink.next(2);
666
- sink.next(3);
849
+ sink.emitNext(1);
850
+ sink.emitNext(2);
851
+ sink.emitComplete();
667
852
 
668
- // New subscriber receives all previously emitted items
669
- Flux.from(sink).subscribe(v => console.log(v));
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
- ## Schedulers
859
+ Use replay when late subscribers must see previous values.
676
860
 
677
- Schedulers control the thread/task context in which work runs.
861
+ ```ts
862
+ const all = Sinks.many().replay().all<number>();
863
+ all.emitNext(1);
864
+ all.emitNext(2);
865
+ all.emitComplete();
678
866
 
679
- ```typescript
680
- import { Schedulers } from 'reactor-core-ts';
867
+ console.log(await all.asFlux().toArray()); // [1, 2]
681
868
  ```
682
869
 
683
- | Factory | Backed by | Use case |
684
- |---|---|---|
685
- | `Schedulers.immediate()` | Synchronous call | Inline execution — no scheduling overhead. |
686
- | `Schedulers.micro()` | `queueMicrotask` | After the current task but before any macro-tasks. |
687
- | `Schedulers.macro()` | `setTimeout(fn, 0)` | Next event loop iteration. |
688
- | `Schedulers.delay(ms)` | `setTimeout(fn, ms)` | One-shot delayed execution. Returns `{ cancel() }`. |
689
- | `Schedulers.interval(ms)` | `setInterval(fn, ms)` | Repeating execution. Returns `{ cancel() }`. |
690
-
691
- All schedulers implement `Scheduler` (`schedule(fn)`). The `delay` and `interval` schedulers additionally implement `CancellableScheduler` (return `{ cancel() }` from `schedule`).
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
- ## Backpressure
903
+ Durations can be a number of milliseconds or an object:
716
904
 
717
- Every subscription starts with zero demand. Items flow only after `request(n)` is called.
905
+ ```ts
906
+ Mono.delay(250);
907
+ Mono.delay({ seconds: 1 });
908
+ Mono.delay({ minutes: 1, seconds: 30 });
909
+ ```
718
910
 
719
- ```typescript
720
- const sub = Flux.range(0, 100).subscribe({
721
- onSubscribe(s) {
722
- // request 10 items to start
723
- s.request(10);
724
- },
725
- onNext(v) {
726
- console.log(v);
727
- // request the next batch when processing is done
728
- },
729
- onError(e) { console.error(e); },
730
- onComplete() { console.log('done'); },
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
- // cancel any time
734
- sub.unsubscribe();
940
+ // Cancel before it runs.
941
+ delayed.dispose();
735
942
  ```
736
943
 
737
- The convenience callback overloads issue `request(Number.MAX_SAFE_INTEGER)` for `Flux` and `request(1)` for `Mono`, effectively making them "unbounded" without any ceremony.
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
- ## Contributing
950
+ // Disposes every task scheduled by this worker.
951
+ worker.dispose();
952
+ ```
742
953
 
743
- ```bash
744
- git clone https://github.com/CKATEPTb/reactor-core-ts.git
745
- cd reactor-core-ts
746
- npm install
954
+ ### Scheduler Usage In Publishers
955
+
956
+ Use scheduler-aware factories and operators when timing matters.
747
957
 
748
- npm run build # compile
749
- npm run test # run all tests (Jest + TCK)
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
- Feel free to open issues and submit pull requests.
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
- ## License
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
- LGPL-3.0-only. See [LICENSE.md](LICENSE.md) for details.
988
+ ```ts
989
+ Flux.interval(16, Schedulers.animationFrame())
990
+ .take(10)
991
+ .subscribe(frame => draw(frame));
992
+ ```
759
993
 
760
- **Author**: [CKATEPTb](https://github.com/CKATEPTb)
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.