@neurosell/reactivets 0.9.5 → 0.9.7

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,9 +1,11 @@
1
1
  # ReactiveTS
2
+ ![ReactiveTS - Simple and Powerful Reactive State Engine](img/cover.png)
3
+
2
4
  A **reactive state engine for TypeScript** with fields, events, objects, arrays, computed values, effects, batching, path subscriptions, async support, cancellation, and built-in undo/redo history.
3
5
 
4
6
  ---
5
7
 
6
- [Get Started]() | [Other Libraries](https://github.com/neurosell) | [Website](https://neurosell.top/)
8
+ [Get Started](#installation) | [Other Libraries](https://github.com/neurosell) | [Telegram](https://t.me/devsdaddy) | [Contacts](https://neurosell.top/)
7
9
 
8
10
  ---
9
11
 
@@ -24,84 +26,679 @@ A **reactive state engine for TypeScript** with fields, events, objects, arrays,
24
26
  * **Async listeners** with cancellation;
25
27
  * **Adapters:** ``toPromise``, ``fromEvent``, ``fromObservable``;
26
28
  * **WeakMap proxy cache** for stable nested references;
29
+ * **Transaction middleware and profiler**;
30
+ * **Snapshot API** for capture/restore;
31
+ * **Sync helpers** for one-way and two-way field synchronization;
32
+ * **Lens and Atom** primitives;
33
+ * **Inspect Dependencies** API for computed values;
34
+ * **Worker bridge** and **DevTools event bus**;
27
35
 
28
36
  ## Table of Contents
29
37
  * [Installation](#installation)
30
- * [Core Concepts]()
31
- * [ReactiveField]()
32
- * [ReactiveEvent]()
33
- * [ReactiveObject]()
34
- * [ReactiveArray]()
35
- * [Computed]()
36
- * [Selectors]()
37
- * [Effect]()
38
- * [Batching]()
39
- * [History & Transactions]()
40
- * [Path Subscriptions]()
41
- * [Async & Cancellation]()
42
- * [Views (``useFiltered``, ``useMapped``, ``useSorted``)]()
43
- * [Adapters (``toPromise``, ``fromEvent``, ``fromObservable``)]()
44
- * [Reactive Watcher (auto-unsubscribe)]()
45
- * [Performance Notes]()
46
- * [Comparison Philosophy]()
47
- * [License]()
38
+ * [Core Concepts](#core-concepts)
39
+ * [ReactiveField](#reactive-fields)
40
+ * [ReactiveEvent](#reactive-events)
41
+ * [ReactiveObject](#reactive-objects)
42
+ * [ReactiveArray](#reactive-arrays)
43
+ * [Computed](#computed)
44
+ * [Selectors](#selectors)
45
+ * [Effect](#effects)
46
+ * [Batching](#batching)
47
+ * [History & Transactions](#history-and-transactions)
48
+ * [Path Subscriptions](#path-subscriptions)
49
+ * [Async & Cancellation](#async-and-cancellation)
50
+ * [Views (``useFiltered``, ``useMapped``, ``useSorted``)](#views-filtering-mapping-sorting)
51
+ * [Adapters (``toPromise``, ``fromEvent``, ``fromObservable``)](#adapters-and-converters)
52
+ * [Transaction Middleware & Profiler](#transaction-middleware-and-profiler)
53
+ * [Snapshot API](#snapshot-api)
54
+ * [Sync API](#sync-api)
55
+ * [Lens and Atom](#lens-and-atom)
56
+ * [Inspect Dependencies](#inspect-dependencies)
57
+ * [Worker Bridge](#worker-bridge)
58
+ * [DevTools](#devtools)
59
+ * [Reactive Watcher (auto-unsubscribe)](#reactive-watcher)
60
+ * [Performance Notes](#performance-notes-and-benchmark)
61
+ * [Comparison Philosophy](#comparison-philosophy)
62
+ * [License](#license)
48
63
 
49
64
  ### Installation
50
-
65
+ To install the library, you can use NPM:
51
66
  ```bash
52
67
  npm install @neurosell/reactivets
53
68
  ```
54
69
 
70
+ **Or from CDN:**
71
+ ```html
72
+ <script src="https://cdn.jsdelivr.net/npm/@neurosell/reactivets@0.9.5/browser/reactivets.global.js"></script>
73
+ <script type="text/javascript">
74
+ // Will be connected as Global
75
+ const { ReactiveField } = window.ReactiveTS;
76
+ </script>
77
+ ```
78
+
79
+ **Manual GitHub Installation for developers:**
80
+ ```bash
81
+ git clone https://github.com/Neurosell/ReactiveTS.git
82
+ cd ./ReactiveTS/
83
+ npm install
84
+ npm run build
85
+ ```
86
+
55
87
  ### Core Concepts
56
- [WIP]
88
+ **ReactiveTS** Library is built around:
89
+ * **State-first** reactivity;
90
+ * **Automatic dependency** tracking;
91
+ * **Microtask** batching;
92
+ * **Deterministic** undo/redo with **transactions** support;
93
+ * **Minimal** boilerplate;
94
+
95
+ **You work with state naturally:**
96
+ ```javascript
97
+ state.value.user.name = "Elijah";
98
+ ```
99
+
100
+ And everything reacts. Simple.
57
101
 
58
102
  ### Reactive Fields
59
- [WIP]
103
+ **ReactiveField** is a reactive primitive (similar to a signal).
104
+ > By default, **ReactiveTS** coalesces (merges) reactions into a single microtask.
105
+ > The restart of effects and reactive fields is scheduled once and will be executed at the end of the tick, so it only sees the last value. This is due to the batching system for optimization, so you should take this into account in your work.
106
+
107
+ **Basic Usage:**
108
+ ```javascript
109
+ // Import
110
+ import { ReactiveField } from "@neurosell/reactivets";
111
+
112
+ // Create Reactive Field
113
+ const count = new ReactiveField(0);
114
+ count.addListener((v) => {
115
+ console.log("count:", v);
116
+ });
117
+
118
+ count.value = 1;
119
+ ```
120
+
121
+ **Batching and Unsubscribe:**
122
+ ```javascript
123
+ // Let's Create our Reactive Field
124
+ const count = new ReactiveField(0);
125
+
126
+ // Listener Returns Unsubscribe Method
127
+ const unsub = count.addListener((v) => {
128
+ console.log("count:", v);
129
+ });
130
+
131
+ count.value = 1;
132
+ await new Promise(resolve => {}) // If you don't wait before unsubscribe in single tick - batching does't run reactive listener
133
+ unsub();
134
+ ```
60
135
 
61
136
  ### Reactive Events
62
- [WIP]
137
+ **Reactive events** are generally similar in concept to reactive fields, but typically do not contain a current value (such as fields or objects), except when you use history.
138
+
139
+ **Let's look at basic usage:**
140
+ ```javascript
141
+ // Import Events Class
142
+ import { ReactiveEvent } from "@neurosell/reactivets";
143
+
144
+ // Create Event
145
+ const event = new ReactiveEvent<string>();
146
+
147
+ // Add Listener
148
+ event.addListener((msg, ctx) => {
149
+ console.log(msg);
150
+ });
151
+
152
+ // Invoke Event
153
+ event.invoke("hello");
154
+ ```
155
+
156
+ **You can also use async event listeners:**
157
+ ```javascript
158
+ event.addListener(async (msg, ctx) => {
159
+ await new Promise(r => setTimeout(r, 100));
160
+ if (ctx.signal.aborted) return;
161
+ console.log(msg);
162
+ });
163
+ ```
164
+
165
+ **Reactive Events Supports:**
166
+ * **batched** listeners;
167
+ * **AbortSignal** cancellation;
168
+ * ``invokeAsync()`` for async events;
63
169
 
64
170
  ### Reactive Objects
65
- [WIP]
171
+ **Reactive objects** are similar to fields, but they can contain any objects. This is useful when you need to track changes, for example, in user data. Reactive objects work through **Proxy**, can also use **Patch Tracking**, and support **change history** (stream).
172
+
173
+ **Let's look at basic usage:**
174
+ ```javascript
175
+ import { ReactiveObject } from "@neurosell/reactivets";
176
+
177
+ // Create our Object
178
+ const state = new ReactiveObject({
179
+ user: { name: "Ada" },
180
+ count: 0
181
+ });
182
+
183
+ // Add Listener
184
+ state.addListener((patch) => {
185
+ console.log("patch:", patch);
186
+ });
187
+
188
+ // Let's change object
189
+ state.value.count++;
190
+ state.value.user.name = "Grace";
191
+ ```
192
+
193
+ **Listener contains patch for our changes. For example:**
194
+ ```javascript
195
+ {
196
+ patch: {
197
+ op: "set", // Operation
198
+ path: ['count'], // Object Path
199
+ prev: 0, // Preview Value
200
+ next: 1 // Next Value
201
+ }
202
+ }
203
+ ```
66
204
 
67
205
  ### Reactive Arrays
68
- [WIP]
206
+ Reactive arrays work in a similar way to objects, but additional filters and other functions can be applied to them (which we will discuss later).
207
+
208
+ **Basic Usage with Patch Tracking:**
209
+ ```javascript
210
+ import { ReactiveArray } from "@neurosell/reactivets";
211
+
212
+ // Our Array
213
+ const list = new ReactiveArray<number>([1, 2]);
214
+
215
+ // Similar Add Listener
216
+ list.addListener((patch) => {
217
+ console.log("array patch:", patch);
218
+ });
219
+
220
+ // And Try to Change Array
221
+ list.value.push(3);
222
+ list.value.splice(0, 1);
223
+ ```
224
+
225
+ **Patch Example:**
226
+ ```javascript
227
+ {
228
+ op: "splice", path: [], index: 0, deleteCount: 1, items: [3], removed: [1]
229
+ }
230
+ ```
69
231
 
70
232
  ### Computed
71
- [WIP]
233
+ > Computed functions are needed to automatically track dependencies for calculations and recalculate the final value if one of the dependencies changes. An example of the logic behind such calculations can be found in linked cells in Excel — when you change one of the two, the sum changes.
234
+
235
+ **Computed are:**
236
+ * **tracks dependencies** automatically;
237
+ * **recomputes final value** when dependencies change;
238
+ * **batched**;
239
+ * **supports lazy mode**;
240
+ * supports **custom equality**;
241
+
242
+ **Let's look at simple example:**
243
+ ```javascript
244
+ import { ReactiveField, useComputed } from "@neurosell/reactivets";
245
+
246
+ // Let's create two Reactive Fields
247
+ const a = new ReactiveField(2);
248
+ const b = new ReactiveField(3);
249
+
250
+ // Create Computed Function
251
+ const sum = useComputed(() => a.value + b.value);
252
+
253
+ // Add Listener for Sum
254
+ sum.addListener((v) => console.log("sum:", v));
255
+
256
+ // Now let's change A
257
+ a.value = 10;
258
+
259
+ // And after 100ms change B, computed listener printed new value
260
+ await new Promise(resolve => setTimeout(resolve, 100));
261
+ a.value = 5;
262
+ ```
72
263
 
73
264
  ### Selectors
74
- [WIP]
265
+ **Selectors** are needed to respond to changes in only certain object fields without unnecessarily triggering listeners.
266
+
267
+ **Simple selector example:**
268
+ ```javascript
269
+ import { ReactiveField, useSelect } from "@neurosell/reactivets";
270
+
271
+ // Create our user
272
+ const user = new ReactiveField({ id: 1, name: "Ada" });
273
+
274
+ // Select only name
275
+ const name = useSelect(user, u => u.name);
276
+
277
+ // Add Listener for name chages
278
+ name.addListener(n => console.log(n));
279
+
280
+ // Update Value
281
+ user.value = { ...user.value, name: "Grace" };
282
+
283
+ // Try to change ID after 100ms, Name listener not called after this action :)
284
+ await new Promise(resolve => setTimeout(resolve, 100));
285
+ user.value.id = 2;
286
+ ```
75
287
 
76
288
  ### Effects
77
- [WIP]
289
+ Side effects with automatic dependency tracking and cleanup.
290
+
291
+ **Use Case:**
292
+ ```javascript
293
+ import { ReactiveField, useEffect } from "@neurosell/reactivets";
294
+
295
+ // Create our Reactive Field
296
+ const count = new ReactiveField(0);
297
+
298
+ const stop = useEffect(() => {
299
+ console.log("count is", count.value);
300
+
301
+ const timer = setInterval(() => {}, 1000);
302
+ return () => clearInterval(timer);
303
+ });
304
+
305
+ // Let's Change Value
306
+ count.value = 1;
307
+
308
+ // Wait 100ms and stop our effector
309
+ // The next value changes can't be called in useEffect listener
310
+ await new Promise(resolve => setTimeout(resolve, 100));
311
+ stop();
312
+ count.value = 2;
313
+ ```
78
314
 
79
315
  ### Batching
80
- [WIP]
316
+ Batch multiple mutations into one reactive wave. By defaults all mutations will be batched in first generation.
317
+
318
+ **Use Case:**
319
+ ```javascript
320
+ import { ReactiveField, useBatch } from "@neurosell/reactivets";
321
+
322
+ // Let's create our field
323
+ const f = new ReactiveField(0);
324
+ f.addListener(v => console.log(v));
325
+
326
+ // Batch our calculation
327
+ useBatch(() => {
328
+ f.value = 1;
329
+ f.value = 2;
330
+ f.value = 3;
331
+ });
332
+ ```
333
+
334
+ > Only one notification wave runs with ``useBatch`` helper.
81
335
 
82
336
  ### History and Transactions
83
- [WIP]
337
+ **ReactiveTS** supports powerful built-in undo/redo system with transactions support.
338
+
339
+ **Simple use case:**
340
+ ```javascript
341
+ import { ReactiveField, ReactiveHistoryStack } from "@neurosell/reactivets";
342
+
343
+ // Create our history stack
344
+ const history = new ReactiveHistoryStack();
345
+
346
+ // Create Reactive Field with History Stack
347
+ const count = new ReactiveField(0, { history });
348
+
349
+ // Fill our history
350
+ count.value = 1;
351
+ count.value = 2;
352
+
353
+ // Work with history
354
+ history.undo();
355
+ console.log(count.value); // 1
356
+ history.undo();
357
+ console.log(count.value); // 0
358
+ history.redo();
359
+ console.log(count.value); // 1
360
+ ```
361
+
362
+ You can also group multiple changes into one undo step with transactions.
363
+
364
+ **Transaction Example:**
365
+ ```javascript
366
+ import { useReactiveTransaction } from "@neurosell/reactivets";
367
+
368
+ console.log(count.value); // 1
369
+
370
+ // Will be applied as single step
371
+ useReactiveTransaction(history, () => {
372
+ count.value = 10;
373
+ count.value = 20;
374
+ count.value = 30;
375
+ });
376
+ console.log(count.value); // 30
377
+
378
+ // Back to history
379
+ history.undo();
380
+ console.log(count.value); // 1
381
+ ```
84
382
 
85
383
  ### Path Subscriptions
86
- [WIP]
384
+ With **ReactiveTS** you can listen to specific paths of objects.
385
+
386
+ **Usage sample:**
387
+ ```javascript
388
+ // Create our Reactive Object
389
+ const state = new ReactiveObject({
390
+ user: {
391
+ name: "Igor",
392
+ age: 15
393
+ }
394
+ });
395
+
396
+ // This Listener reacts only at user.name changes
397
+ state.addPathListener("user.name", (patch) => {
398
+ console.log("name changed");
399
+ }, { mode: "exact" });
400
+
401
+ // This Listener reacts at all user changes
402
+ state.addPathListener("user", (patch) => {
403
+ console.log("anything under user changed");
404
+ });
405
+
406
+ // Change our object
407
+ state.value.user.name = "Elijah"; // Calls both listeners
408
+ state.value.user.age = 10; // Calls only second listener
409
+ ```
410
+
411
+ **Path Subscription supports:**
412
+ * exact mode (``item.data.key``);
413
+ * prefix mode (``item``);
414
+ * wildcard mode (``items.*.id``)
87
415
 
88
416
  ### Async and Cancellation
89
- [WIP]
417
+ You can use cancellation tokens and async listeners for your reactive fields.
418
+
419
+ **For Example:**
420
+ ```javascript
421
+ const field = new ReactiveField(0);
422
+ const controller = new AbortController();
423
+
424
+ field.addListener(async (v, ctx) => {
425
+ await someAsyncTask();
426
+ if (ctx.signal.aborted) return;
427
+ }, { signal: controller.signal });
428
+
429
+ controller.abort();
430
+ ```
90
431
 
91
432
  ### Views (Filtering, Mapping, Sorting)
92
- [WIP]
433
+ To simplify working with **Reactive Arrays**, you can also use auxiliary functionality for filtering, mapping, and sorting data.
434
+
435
+ **Usage Example:**
436
+ ```javascript
437
+ import { ReactiveArray, useFiltered, useMapped, useSorted } from "@neurosell/reactivets";
438
+
439
+ // Create our Array
440
+ const list = new ReactiveArray([1, 2, 3, 4]);
441
+
442
+ // Filtered Array
443
+ const evens = useFiltered(list, x => x % 2 === 0);
444
+ evens.addListener(arr => console.log(arr));
445
+
446
+ // Push new value
447
+ list.value.push(6);
448
+ ```
93
449
 
94
450
  ### Adapters and Converters
95
- [WIP]
451
+ **Adapters** are **helper functions** for converting reactive events, fields, and other elements into **asynchronous methods**, **Observables**, etc.
452
+
453
+ **Conversion to Promise:**
454
+ ```javascript
455
+ import { toPromise } from "@neurosell/reactivets";
456
+
457
+ toPromise(event, {
458
+ predicate: v => v > 10
459
+ }).then(v => console.log(v));
460
+ ```
461
+
462
+ **Conversion to Promise Field:**
463
+ ```javascript
464
+ import { toPromiseField } from "@neurosell/reactivets";
465
+
466
+ toPromiseField(field, {
467
+ predicate: v => v === 5
468
+ });
469
+ ```
470
+
471
+ **Conversion from DOM Event:**
472
+ ```javascript
473
+ import { fromEvent } from "@neurosell/reactivets";
474
+
475
+ const { event, dispose } = fromEvent(document, "click");
476
+ event.addListener(e => console.log(e));
477
+ ```
478
+
479
+ **Conversion from Observable:**
480
+ ```javascript
481
+ import { fromObservable } from "@neurosell/reactivets";
482
+
483
+ // Observable Example
484
+ const obs = {
485
+ subscribe(next) {
486
+ const t = setInterval(() => next(Date.now()), 1000);
487
+ return () => clearInterval(t);
488
+ }
489
+ };
490
+
491
+ const { event } = fromObservable(obs);
492
+ ```
493
+
494
+ ### Transaction Middleware and Profiler
495
+ Use middleware function around transactions and collect profiling data.
496
+
497
+ ```javascript
498
+ import {
499
+ ReactiveHistoryStack,
500
+ ReactiveTransactionManager,
501
+ createTransactionProfiler,
502
+ ReactiveField
503
+ } from "@neurosell/reactivets";
504
+
505
+ const history = new ReactiveHistoryStack();
506
+ const tx = new ReactiveTransactionManager(history);
507
+ const timings = [];
508
+
509
+ tx.use(createTransactionProfiler(timings));
510
+
511
+ const count = new ReactiveField(0, { history });
512
+
513
+ tx.run(() => {
514
+ count.value = 1;
515
+ count.value = 2;
516
+ }, "update-count");
517
+
518
+ console.log(timings[0]?.durationMs);
519
+ ```
520
+
521
+ ### Snapshot API
522
+ Take a snapshot and restore it later.
523
+
524
+ ```javascript
525
+ import { ReactiveObject, createSnapshot, restoreSnapshot } from "@neurosell/reactivets";
526
+
527
+ const state = new ReactiveObject({ user: { name: "Ada" }, count: 1 });
528
+ const snap = createSnapshot(state);
529
+
530
+ state.value.user.name = "Grace";
531
+ state.value.count = 10;
532
+
533
+ restoreSnapshot(state, snap);
534
+ console.log(state.value.user.name); // Ada
535
+ ```
536
+
537
+ ### Sync API
538
+ Synchronize two reactive fields.
539
+
540
+ ```javascript
541
+ import { ReactiveField, useSync } from "@neurosell/reactivets";
542
+
543
+ const left = new ReactiveField("A");
544
+ const right = new ReactiveField("B");
545
+
546
+ const stop = useSync(left, right);
547
+ left.value = "Hello";
548
+ console.log(right.value); // Hello
549
+
550
+ stop();
551
+ ```
552
+
553
+ ### Lens and Atom features
554
+ `ReactiveAtom` is a thin alias over `ReactiveField`; `useLens` focuses into nested state.
555
+
556
+ ```javascript
557
+ import { ReactiveObject, useLens, useAtom } from "@neurosell/reactivets";
558
+
559
+ const state = new ReactiveObject({ profile: { name: "Ada" } });
560
+ const nameLens = useLens(state, ["profile", "name"]);
561
+ const localFlag = useAtom(false);
562
+
563
+ nameLens.value = "Grace";
564
+ console.log(state.value.profile.name); // Grace
565
+
566
+ localFlag.value = true;
567
+ ```
568
+
569
+ ### Inspect Dependencies
570
+ Inspect collected dependencies for computed values (useful for debugging).
571
+
572
+ ```javascript
573
+ import { ReactiveField, useComputed } from "@neurosell/reactivets";
574
+
575
+ const a = new ReactiveField(1);
576
+ const b = new ReactiveField(2);
577
+ const sum = useComputed(() => a.value + b.value);
578
+
579
+ console.log(sum.inspectDependencies().length); // 2
580
+ ```
581
+
582
+ ### Worker Bridge
583
+ Bridge browser `Worker` messages with ReactiveTS events.
584
+
585
+ ```javascript
586
+ import { createWorkerBridge } from "@neurosell/reactivets";
587
+
588
+ const worker = new Worker("./worker.js", { type: "module" });
589
+ const bridge = createWorkerBridge(worker);
590
+
591
+ bridge.onMessage.addListener((message) => {
592
+ console.log(message.type, message.payload);
593
+ });
594
+
595
+ bridge.post({ type: "PING", payload: { at: Date.now() } });
596
+ ```
597
+
598
+ ### DevTools
599
+ Use a minimal built-in event bus for state/debug records.
600
+
601
+ ```javascript
602
+ import { ReactiveDevTools } from "@neurosell/reactivets";
603
+
604
+ const devtools = new ReactiveDevTools();
605
+ devtools.addListener((record) => console.log(record.type, record.payload));
606
+
607
+ devtools.emit("state:update", { feature: "counter", next: 10 });
608
+ console.log(devtools.inspect().length); // 1
609
+ ```
96
610
 
97
611
  ### Reactive Watcher
98
- [WIP]
612
+ **Reactive Watcher** in **ReactiveTS** needed to track dependent listeners and further automatically unsubscribe all listeners from specific reactive fields, events, objects, and arrays.
613
+
614
+ **Use Case:**
615
+ ```javascript
616
+ import { ReactiveWatcher } from "@neurosell/reactivets";
617
+
618
+ // Create Watcher
619
+ const watcher = new ReactiveWatcher();
620
+ watcher.own(field.addListener(console.log));
621
+ watcher.dispose(); // removes all listeners
622
+ ```
99
623
 
100
624
  ### Performance Notes and Benchmark
101
- [WIP]
625
+ Now let's talk about **ReactiveTS** **performance and optimization** under the hood, and take a look at the **benchmarks**.
626
+
627
+ **ReactiveTS uses:**
628
+ * **Microtask batching**;
629
+ * **WeakMap proxy caching**;
630
+ * **Deduplicated scheduler** queue;
631
+ * **Version-based dependency tracking**;
632
+
633
+ **For extreme hot paths:**
634
+ 1. Prefer **ReactiveField** over deep Proxy objects;
635
+ 2. Use **batching**;
636
+ 3. Use **transactions** for grouped updates and history optimisation;
637
+
638
+ #### Benchmarks
639
+ **ReactiveTS** is optimized for typical UI/state scenarios (frequent changes to small fields + batching + effects). To fairly compare performance between versions/configurations, use reproducible microbenchmarks.
640
+
641
+ **The benchmarks below include the following scenarios:**
642
+ * **ReactiveField:** speed of ``set`` and listener notifications;
643
+ * **Computed:** recalculation of derived value chains;
644
+ * **Effect:** restarting effects when changes occur;
645
+ * **ReactiveObject / ReactiveArray (Proxy):** cost of ``set``/``splice`` and patch generation;
646
+ * **Path subscriptions:** filtering patches by path/mask;
647
+ * **Batching & Transactions:** how well the wave of updates coalesces;
648
+ * **History undo/redo:** cost of recording/rolling back changes;
649
+
650
+ > **Important:** Proxies and patches are inevitably more expensive than simple signals. For hot paths, use ``ReactiveField`` and computed/selectors.
651
+
652
+ #### Benchmark Results (NodeJS VPS 1vCPU, 4GB Ram), 200K Iterations
653
+ | Scenario (200K Iterations) | ops/s | Notes |
654
+ |-----------------------------|-------------:|-----------------------|
655
+ | Field.set (no listeners) | 9,1M (21ms) | baseline |
656
+ | Field.set (10 listeners) | 768K (260ms) | fan-out |
657
+ | Computed chain (3 nodes) | 79K (2500ms) | dep tracking cost |
658
+ | ReactiveArray push | 4,4m (22ms) | reactive array push |
659
+ | ReactiveObject set (deep) | 1,1m (176ms) | Proxy + patch |
660
+ | Batch(100 sets) => 1 wave | 58K (859ms) | coalescing |
661
+ | Transaction(100 sets)+undo | 114K (174ms) | grouped history |
662
+ | Event.invoke (10 listeners) | 864K (231ms) | reactive event invoke |
102
663
 
103
664
  ### Comparison Philosophy
104
- [WIP]
665
+ In this section, we have provided you with the main comparisons with other popular reactive extension libraries.
666
+
667
+ **ReactiveTS focuses on:**
668
+ 1. Reactive state management;
669
+ 2. Deterministic undo/redo and transactions;
670
+ 3. Path-level reactivity and simple API;
671
+ 4. TypeScript-first API;
672
+
673
+ > It is not a stream algebra engine like RxJS. It is your simple reactive state management engine!
674
+
675
+ #### ReactiveTS vs RxJS
676
+ | Feature | ReactiveTS | RxJS |
677
+ |-------------------------------------------------|---------------|--------------------------|
678
+ | ReactiveField | ✅ | ⚠️ using BehaviorSubject |
679
+ | ReactiveObject (Proxy) | ✅ | ❌ |
680
+ | Path subscriptions | ✅ | ❌ |
681
+ | Computed (auto deps) | ✅ | ⚠️ using combineLatest |
682
+ | useEffect-подобное | ✅ | ⚠️ subscribe |
683
+ | Undo/Redo history | ✅ | ❌ |
684
+ | Transaction history | ✅ | ❌ |
685
+ | Stream combinators (switchMap, retry, debounce) | ⚠️ partial | ✅ powerful |
686
+ | Cancellation | ✅ AbortSignal | ✅ |
687
+ | Async operators | ⚠️ basic | ✅ large ecosystem |
688
+
689
+ #### ReactiveTS vs MobX
690
+ | Feature | ReactiveTS | MobX |
691
+ |---------------------|-----------------|---------------------------------|
692
+ | Proxy-based | ✅ | ❌ (only using getters/observables) |
693
+ | Dependency tracking | ✅ | ✅ |
694
+ | History | ✅ | ❌ |
695
+ | Transaction | ✅ | ⚠️ runInAction |
696
+ | Devtools ecosystem | ⚠️ in development | ✅ |
697
+ | Battle-tested | ✅ | ✅ |
105
698
 
106
699
  ### License
107
- [WIP]
700
+ Our library is distributed under the **MIT license**. You can use it however you like. We would appreciate any feedback and suggestions for improvement.
701
+
702
+ ---
703
+
704
+ [Get Started](#installation) | [Other Libraries](https://github.com/neurosell) | [Telegram](https://t.me/devsdaddy) | [Contacts](https://neurosell.top/)