reactor-core-ts 1.0.8-beta → 2.0.0

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