synstate 0.1.2 → 1.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 +199 -146
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +43 -10
- package/dist/core/class/child-observable-class.mjs.map +1 -1
- package/dist/core/class/observable-base-class.d.mts +4 -4
- package/dist/core/class/observable-base-class.d.mts.map +1 -1
- package/dist/core/class/observable-base-class.mjs +8 -8
- package/dist/core/class/observable-base-class.mjs.map +1 -1
- package/dist/core/class/root-observable-class.d.mts +1 -1
- package/dist/core/class/root-observable-class.d.mts.map +1 -1
- package/dist/core/class/root-observable-class.mjs +9 -9
- package/dist/core/class/root-observable-class.mjs.map +1 -1
- package/dist/core/combine/combine.d.mts +6 -6
- package/dist/core/combine/combine.mjs +11 -12
- package/dist/core/combine/combine.mjs.map +1 -1
- package/dist/core/combine/merge.d.mts +6 -6
- package/dist/core/combine/merge.mjs +9 -9
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +20 -19
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +22 -21
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/{interval.d.mts → counter.d.mts} +14 -12
- package/dist/core/create/counter.d.mts.map +1 -0
- package/dist/core/create/{interval.mjs → counter.mjs} +21 -23
- package/dist/core/create/counter.mjs.map +1 -0
- package/dist/core/create/from-abortable-promise.d.mts +29 -0
- package/dist/core/create/from-abortable-promise.d.mts.map +1 -0
- package/dist/core/create/from-abortable-promise.mjs +70 -0
- package/dist/core/create/from-abortable-promise.mjs.map +1 -0
- package/dist/core/create/from-promise.d.mts +9 -6
- package/dist/core/create/from-promise.d.mts.map +1 -1
- package/dist/core/create/from-promise.mjs +8 -5
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/from-subscribable.d.mts +4 -4
- package/dist/core/create/from-subscribable.mjs +4 -4
- package/dist/core/create/index.d.mts +3 -3
- package/dist/core/create/index.d.mts.map +1 -1
- package/dist/core/create/index.mjs +4 -4
- package/dist/core/create/just.d.mts +32 -0
- package/dist/core/create/just.d.mts.map +1 -0
- package/dist/core/create/just.mjs +44 -0
- package/dist/core/create/just.mjs.map +1 -0
- package/dist/core/create/source.d.mts +7 -12
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +1 -6
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/create/timer.d.mts +6 -4
- package/dist/core/create/timer.d.mts.map +1 -1
- package/dist/core/create/timer.mjs +6 -7
- package/dist/core/create/timer.mjs.map +1 -1
- package/dist/core/index.d.mts +0 -1
- package/dist/core/index.d.mts.map +1 -1
- package/dist/core/index.mjs +8 -13
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/operators/audit.d.mts +97 -0
- package/dist/core/operators/audit.d.mts.map +1 -0
- package/dist/core/operators/audit.mjs +144 -0
- package/dist/core/operators/audit.mjs.map +1 -0
- package/dist/core/operators/debounce.d.mts +88 -0
- package/dist/core/operators/debounce.d.mts.map +1 -0
- package/dist/core/operators/debounce.mjs +130 -0
- package/dist/core/operators/debounce.mjs.map +1 -0
- package/dist/core/operators/filter.d.mts +5 -5
- package/dist/core/operators/filter.mjs +3 -3
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/index.d.mts +4 -4
- package/dist/core/operators/index.d.mts.map +1 -1
- package/dist/core/operators/index.mjs +4 -4
- package/dist/core/operators/{map-with-index.d.mts → map.d.mts} +11 -11
- package/dist/core/operators/map.d.mts.map +1 -0
- package/dist/core/operators/{map-with-index.mjs → map.mjs} +17 -17
- package/dist/core/operators/map.mjs.map +1 -0
- package/dist/core/operators/merge-map.d.mts +56 -29
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +58 -31
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/pairwise.d.mts +6 -6
- package/dist/core/operators/pairwise.mjs +9 -9
- package/dist/core/operators/pairwise.mjs.map +1 -1
- package/dist/core/operators/scan.d.mts +6 -6
- package/dist/core/operators/scan.mjs +9 -9
- package/dist/core/operators/scan.mjs.map +1 -1
- package/dist/core/operators/skip-if-no-change.d.mts +20 -8
- package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
- package/dist/core/operators/skip-if-no-change.mjs +23 -11
- package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
- package/dist/core/operators/skip-until.d.mts +5 -5
- package/dist/core/operators/skip-until.mjs +8 -8
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/skip-while.d.mts +18 -8
- package/dist/core/operators/skip-while.d.mts.map +1 -1
- package/dist/core/operators/skip-while.mjs +26 -11
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +57 -26
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +59 -28
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-until.d.mts +5 -5
- package/dist/core/operators/take-until.mjs +8 -8
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/take-while.d.mts +15 -7
- package/dist/core/operators/take-while.d.mts.map +1 -1
- package/dist/core/operators/take-while.mjs +18 -10
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/operators/throttle.d.mts +81 -0
- package/dist/core/operators/throttle.d.mts.map +1 -0
- package/dist/core/operators/throttle.mjs +126 -0
- package/dist/core/operators/throttle.mjs.map +1 -0
- package/dist/core/operators/with-buffered-from.d.mts +9 -9
- package/dist/core/operators/with-buffered-from.mjs +12 -12
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/operators/with-current-value-from.d.mts +10 -9
- package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
- package/dist/core/operators/with-current-value-from.mjs +13 -12
- package/dist/core/operators/with-current-value-from.mjs.map +1 -1
- package/dist/core/operators/with-initial-value.d.mts +5 -5
- package/dist/core/operators/with-initial-value.mjs +8 -8
- package/dist/core/operators/with-initial-value.mjs.map +1 -1
- package/dist/core/predefined/index.mjs +0 -1
- package/dist/core/predefined/index.mjs.map +1 -1
- package/dist/core/predefined/operators/attach-index.d.mts +49 -0
- package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
- package/dist/core/predefined/operators/attach-index.mjs +51 -2
- package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
- package/dist/core/predefined/operators/index.d.mts +0 -1
- package/dist/core/predefined/operators/index.d.mts.map +1 -1
- package/dist/core/predefined/operators/index.mjs +0 -1
- package/dist/core/predefined/operators/index.mjs.map +1 -1
- package/dist/core/predefined/operators/map-optional.d.mts +47 -0
- package/dist/core/predefined/operators/map-optional.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-optional.mjs +49 -1
- package/dist/core/predefined/operators/map-optional.mjs.map +1 -1
- package/dist/core/predefined/operators/map-result-err.d.mts +47 -0
- package/dist/core/predefined/operators/map-result-err.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-result-err.mjs +49 -1
- package/dist/core/predefined/operators/map-result-err.mjs.map +1 -1
- package/dist/core/predefined/operators/map-result-ok.d.mts +47 -0
- package/dist/core/predefined/operators/map-result-ok.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-result-ok.mjs +49 -1
- package/dist/core/predefined/operators/map-result-ok.mjs.map +1 -1
- package/dist/core/predefined/operators/map-to.d.mts +40 -0
- package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-to.mjs +43 -1
- package/dist/core/predefined/operators/map-to.mjs.map +1 -1
- package/dist/core/predefined/operators/pluck.d.mts +39 -0
- package/dist/core/predefined/operators/pluck.d.mts.map +1 -1
- package/dist/core/predefined/operators/pluck.mjs +42 -1
- package/dist/core/predefined/operators/pluck.mjs.map +1 -1
- package/dist/core/predefined/operators/skip.d.mts +47 -0
- package/dist/core/predefined/operators/skip.d.mts.map +1 -1
- package/dist/core/predefined/operators/skip.mjs +47 -0
- package/dist/core/predefined/operators/skip.mjs.map +1 -1
- package/dist/core/predefined/operators/take.d.mts +41 -0
- package/dist/core/predefined/operators/take.d.mts.map +1 -1
- package/dist/core/predefined/operators/take.mjs +41 -0
- package/dist/core/predefined/operators/take.mjs.map +1 -1
- package/dist/core/predefined/operators/unwrap-optional.d.mts +40 -0
- package/dist/core/predefined/operators/unwrap-optional.d.mts.map +1 -1
- package/dist/core/predefined/operators/unwrap-optional.mjs +42 -1
- package/dist/core/predefined/operators/unwrap-optional.mjs.map +1 -1
- package/dist/core/predefined/operators/unwrap-result-err.d.mts +40 -0
- package/dist/core/predefined/operators/unwrap-result-err.d.mts.map +1 -1
- package/dist/core/predefined/operators/unwrap-result-err.mjs +42 -1
- package/dist/core/predefined/operators/unwrap-result-err.mjs.map +1 -1
- package/dist/core/predefined/operators/unwrap-result-ok.d.mts +40 -0
- package/dist/core/predefined/operators/unwrap-result-ok.d.mts.map +1 -1
- package/dist/core/predefined/operators/unwrap-result-ok.mjs +42 -1
- package/dist/core/predefined/operators/unwrap-result-ok.mjs.map +1 -1
- package/dist/core/types/id.d.mts +1 -1
- package/dist/core/types/id.d.mts.map +1 -1
- package/dist/core/types/index.d.mts +1 -0
- package/dist/core/types/index.d.mts.map +1 -1
- package/dist/core/types/observable-family.d.mts +8 -14
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable.d.mts +3 -3
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/timer.d.mts +2 -0
- package/dist/core/types/timer.d.mts.map +1 -0
- package/dist/core/types/timer.mjs +2 -0
- package/dist/core/types/timer.mjs.map +1 -0
- package/dist/core/utils/id-maker.d.mts +2 -2
- package/dist/core/utils/id-maker.d.mts.map +1 -1
- package/dist/core/utils/id-maker.mjs +3 -3
- package/dist/core/utils/id-maker.mjs.map +1 -1
- package/dist/core/utils/index.mjs +1 -1
- package/dist/entry-point.mjs +11 -14
- package/dist/entry-point.mjs.map +1 -1
- package/dist/globals.d.mts +0 -3
- package/dist/index.mjs +11 -14
- package/dist/index.mjs.map +1 -1
- package/dist/utils/collect-to-array.d.mts +3 -0
- package/dist/utils/collect-to-array.d.mts.map +1 -0
- package/dist/utils/collect-to-array.mjs +11 -0
- package/dist/utils/collect-to-array.mjs.map +1 -0
- package/dist/utils/create-boolean-state.d.mts +40 -0
- package/dist/utils/create-boolean-state.d.mts.map +1 -0
- package/dist/utils/create-boolean-state.mjs +53 -0
- package/dist/utils/create-boolean-state.mjs.map +1 -0
- package/dist/utils/create-event-emitter.d.mts +4 -4
- package/dist/utils/create-event-emitter.mjs +4 -4
- package/dist/utils/create-reducer.d.mts +10 -7
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +7 -7
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +8 -48
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +10 -60
- package/dist/utils/create-state.mjs.map +1 -1
- package/dist/utils/index.d.mts +2 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +3 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +17 -11
- package/src/core/class/child-observable-class.mts +65 -9
- package/src/core/class/circular-dependency-comparison.test.mts +142 -0
- package/src/core/class/circular-dependency.test.mts +251 -0
- package/src/core/class/observable-base-class.mts +9 -9
- package/src/core/class/root-observable-class.mts +14 -10
- package/src/core/combine/combine.mts +13 -13
- package/src/core/combine/merge.mts +13 -14
- package/src/core/combine/zip.mts +26 -25
- package/src/core/create/{interval.mts → counter.mts} +32 -30
- package/src/core/create/from-abortable-promise.mts +83 -0
- package/src/core/create/from-promise.mts +10 -7
- package/src/core/create/from-subscribable.mts +4 -4
- package/src/core/create/index.mts +3 -3
- package/src/core/create/just.mts +43 -0
- package/src/core/create/source.mts +10 -14
- package/src/core/create/timer.mts +12 -11
- package/src/core/index.mts +0 -1
- package/src/core/operators/audit.mts +172 -0
- package/src/core/operators/debounce.mts +154 -0
- package/src/core/operators/filter.mts +9 -9
- package/src/core/operators/index.mts +4 -4
- package/src/core/operators/{map-with-index.mts → map.mts} +20 -20
- package/src/core/operators/merge-map.mts +59 -32
- package/src/core/operators/pairwise.mts +10 -10
- package/src/core/operators/scan.mts +10 -10
- package/src/core/operators/skip-if-no-change.mts +24 -12
- package/src/core/operators/skip-until.mts +9 -9
- package/src/core/operators/skip-while.mts +29 -12
- package/src/core/operators/switch-map.mts +60 -29
- package/src/core/operators/take-until.mts +9 -9
- package/src/core/operators/take-while.mts +19 -11
- package/src/core/operators/{throttle-time.mts → throttle.mts} +58 -38
- package/src/core/operators/with-buffered-from.mts +13 -13
- package/src/core/operators/with-current-value-from.mts +14 -13
- package/src/core/operators/with-initial-value.mts +9 -9
- package/src/core/predefined/operators/attach-index.mts +51 -2
- package/src/core/predefined/operators/index.mts +0 -1
- package/src/core/predefined/operators/map-optional.mts +48 -1
- package/src/core/predefined/operators/map-result-err.mts +48 -1
- package/src/core/predefined/operators/map-result-ok.mts +48 -1
- package/src/core/predefined/operators/map-to.mts +41 -1
- package/src/core/predefined/operators/pluck.mts +40 -1
- package/src/core/predefined/operators/skip.mts +47 -0
- package/src/core/predefined/operators/take.mts +41 -0
- package/src/core/predefined/operators/unwrap-optional.mts +41 -1
- package/src/core/predefined/operators/unwrap-result-err.mts +41 -1
- package/src/core/predefined/operators/unwrap-result-ok.mts +41 -1
- package/src/core/types/id.mts +1 -1
- package/src/core/types/index.mts +1 -0
- package/src/core/types/observable-family.mts +8 -24
- package/src/core/types/observable.mts +3 -3
- package/src/core/types/timer.mts +2 -0
- package/src/core/utils/id-maker.mts +4 -4
- package/src/globals.d.mts +0 -3
- package/src/utils/collect-to-array.mts +17 -0
- package/src/utils/create-boolean-state.mts +68 -0
- package/src/utils/create-event-emitter.mts +4 -4
- package/src/utils/create-reducer.mts +11 -8
- package/src/utils/create-state.mts +10 -75
- package/src/utils/index.mts +2 -0
- package/dist/core/create/from-array.d.mts +0 -39
- package/dist/core/create/from-array.d.mts.map +0 -1
- package/dist/core/create/from-array.mjs +0 -65
- package/dist/core/create/from-array.mjs.map +0 -1
- package/dist/core/create/interval.d.mts.map +0 -1
- package/dist/core/create/interval.mjs.map +0 -1
- package/dist/core/create/of.d.mts +0 -39
- package/dist/core/create/of.d.mts.map +0 -1
- package/dist/core/create/of.mjs +0 -63
- package/dist/core/create/of.mjs.map +0 -1
- package/dist/core/operators/audit-time.d.mts +0 -62
- package/dist/core/operators/audit-time.d.mts.map +0 -1
- package/dist/core/operators/audit-time.mjs +0 -109
- package/dist/core/operators/audit-time.mjs.map +0 -1
- package/dist/core/operators/debounce-time.d.mts +0 -51
- package/dist/core/operators/debounce-time.d.mts.map +0 -1
- package/dist/core/operators/debounce-time.mjs +0 -93
- package/dist/core/operators/debounce-time.mjs.map +0 -1
- package/dist/core/operators/map-with-index.d.mts.map +0 -1
- package/dist/core/operators/map-with-index.mjs.map +0 -1
- package/dist/core/operators/throttle-time.d.mts +0 -62
- package/dist/core/operators/throttle-time.d.mts.map +0 -1
- package/dist/core/operators/throttle-time.mjs +0 -107
- package/dist/core/operators/throttle-time.mjs.map +0 -1
- package/dist/core/predefined/operators/map.d.mts +0 -3
- package/dist/core/predefined/operators/map.d.mts.map +0 -1
- package/dist/core/predefined/operators/map.mjs +0 -8
- package/dist/core/predefined/operators/map.mjs.map +0 -1
- package/src/core/create/from-array.mts +0 -76
- package/src/core/create/of.mts +0 -73
- package/src/core/operators/audit-time.mts +0 -136
- package/src/core/operators/debounce-time.mts +0 -116
- package/src/core/predefined/operators/map.mts +0 -5
package/README.md
CHANGED
|
@@ -9,26 +9,30 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/synstate)
|
|
10
10
|
[](https://www.npmjs.com/package/synstate)
|
|
11
11
|
[](./LICENSE)
|
|
12
|
-
[](https://codecov.io/gh/noshiro-pf/synstate)
|
|
13
13
|
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
**SynState** is a lightweight, high-performance, type-safe state management library for TypeScript/JavaScript applications. Perfect for building reactive global state and event-driven systems in React, Vue, and other frameworks.
|
|
17
17
|
|
|
18
|
+
"SynState" is named after "Synchronized + State." It represents a sound synchronized state through a **glitch-free**[^1] Observable implementation.
|
|
19
|
+
|
|
20
|
+
[^1]: See ["How SynState solved the glitch?"](./documents/how-synstate-solved-the-glitch.md).
|
|
21
|
+
|
|
18
22
|
## Features
|
|
19
23
|
|
|
20
|
-
- 🎯 **Simple State Management**: Easy-to-use `createState` and `createReducer` for global state
|
|
21
|
-
- 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
|
|
22
|
-
- 🔄 **Reactive Updates**: Automatic propagation of state changes to all subscribers
|
|
23
|
-
- 🎨 **Type-Safe**: Full TypeScript support with precise type inference
|
|
24
|
-
- 🚀 **Lightweight**: Minimal bundle size with only one external runtime dependency ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge))
|
|
24
|
+
- 🎯 **Simple State Management**: Easy-to-use `createState` and `createReducer` similar to React useState/useReducer for global state
|
|
25
25
|
- ⚡ **High Performance**: Optimized for fast state updates and minimal re-renders
|
|
26
|
+
- 🎨 **Type-Safe**: Full TypeScript support with precise type inference
|
|
27
|
+
- 🚀 **Lightweight**: <!-- bundle-size:synstate -->~4.5 kB min+gzip<!-- /bundle-size:synstate --> with only one external runtime dependency ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge))
|
|
26
28
|
- 🌐 **Framework Agnostic**: Works with React, Vue, Svelte, or vanilla JavaScript
|
|
27
|
-
-
|
|
29
|
+
- 🔄 **Reactive Updates**: Automatic propagation of state changes to all subscribers
|
|
30
|
+
- 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
|
|
31
|
+
- 🔧 **Observable-based**: Built on Observable pattern similar to RxJS, but with a completely independent implementation from scratch — not a wrapper. Offers optional advanced features like operators (`map`, `filter`, `scan`, `debounce`) and combinators (`merge`, `combine`)
|
|
28
32
|
|
|
29
33
|
## Documentation
|
|
30
34
|
|
|
31
|
-
-
|
|
35
|
+
- <https://noshiro-pf.github.io/synstate/>
|
|
32
36
|
|
|
33
37
|
## Installation
|
|
34
38
|
|
|
@@ -52,63 +56,46 @@ pnpm add synstate
|
|
|
52
56
|
|
|
53
57
|
```tsx
|
|
54
58
|
// Create a reactive state
|
|
55
|
-
const [state, setState
|
|
56
|
-
|
|
59
|
+
const [state, setState] = createState(0);
|
|
60
|
+
// type of state: InitializedObservable<number>
|
|
61
|
+
// type of setState: (v: number) => number
|
|
57
62
|
|
|
58
|
-
const
|
|
63
|
+
const stateHistory: number[] = [];
|
|
59
64
|
|
|
60
|
-
// Subscribe to changes
|
|
65
|
+
// Subscribe to changes
|
|
61
66
|
state.subscribe((count) => {
|
|
62
|
-
|
|
67
|
+
stateHistory.push(count);
|
|
63
68
|
});
|
|
64
69
|
|
|
65
|
-
assert.deepStrictEqual(
|
|
70
|
+
assert.deepStrictEqual(stateHistory, [0]);
|
|
66
71
|
|
|
67
72
|
// Update state
|
|
68
73
|
setState(1);
|
|
69
74
|
|
|
70
|
-
assert.deepStrictEqual(
|
|
71
|
-
|
|
72
|
-
updateState((prev) => prev + 2);
|
|
73
|
-
|
|
74
|
-
assert.deepStrictEqual(mut_history, [0, 1, 3]);
|
|
75
|
-
|
|
76
|
-
assert.isTrue(getSnapshot() === 3);
|
|
77
|
-
|
|
78
|
-
resetState();
|
|
79
|
-
|
|
80
|
-
assert.isTrue(getSnapshot() === 0);
|
|
75
|
+
assert.deepStrictEqual(stateHistory, [0, 1]);
|
|
81
76
|
```
|
|
82
77
|
|
|
83
78
|
### With React
|
|
84
79
|
|
|
80
|
+
```bash
|
|
81
|
+
npm add synstate-react-hooks
|
|
82
|
+
```
|
|
83
|
+
|
|
85
84
|
```tsx
|
|
86
|
-
import * as React from 'react';
|
|
87
|
-
import { createState } from 'synstate';
|
|
85
|
+
import type * as React from 'react';
|
|
86
|
+
import { createState } from 'synstate-react-hooks';
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
const [userState, setUserState, { getSnapshot }] = createState({
|
|
88
|
+
const [useUserState, setUserState] = createState({
|
|
91
89
|
name: '',
|
|
92
90
|
email: '',
|
|
93
91
|
});
|
|
94
92
|
|
|
95
93
|
const UserProfile = (): React.JSX.Element => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
React.useEffect(() => {
|
|
99
|
-
const subscription = userState.subscribe(setUser);
|
|
100
|
-
|
|
101
|
-
return () => {
|
|
102
|
-
subscription.unsubscribe();
|
|
103
|
-
};
|
|
104
|
-
}, []);
|
|
94
|
+
const user = useUserState();
|
|
105
95
|
|
|
106
96
|
return (
|
|
107
97
|
<div>
|
|
108
|
-
<p>
|
|
109
|
-
{'Name: '}
|
|
110
|
-
{user.name}
|
|
111
|
-
</p>
|
|
98
|
+
<p>{`Name: ${user.name}`}</p>
|
|
112
99
|
<button
|
|
113
100
|
onClick={() => {
|
|
114
101
|
setUserState({
|
|
@@ -124,7 +111,7 @@ const UserProfile = (): React.JSX.Element => {
|
|
|
124
111
|
};
|
|
125
112
|
```
|
|
126
113
|
|
|
127
|
-
|
|
114
|
+
This is equivalent to the following code without synstate-react-hook:
|
|
128
115
|
|
|
129
116
|
```tsx
|
|
130
117
|
import * as React from 'react';
|
|
@@ -147,10 +134,7 @@ const UserProfile = (): React.JSX.Element => {
|
|
|
147
134
|
|
|
148
135
|
return (
|
|
149
136
|
<div>
|
|
150
|
-
<p>
|
|
151
|
-
{'Name: '}
|
|
152
|
-
{user.name}
|
|
153
|
-
</p>
|
|
137
|
+
<p>{`Name: ${user.name}`}</p>
|
|
154
138
|
<button
|
|
155
139
|
onClick={() => {
|
|
156
140
|
setUserState({
|
|
@@ -166,30 +150,34 @@ const UserProfile = (): React.JSX.Element => {
|
|
|
166
150
|
};
|
|
167
151
|
```
|
|
168
152
|
|
|
169
|
-
|
|
153
|
+
See also the [synstate-react-hooks README](../synstate-react-hooks/README.md).
|
|
170
154
|
|
|
171
|
-
|
|
172
|
-
npm add synstate-react-hooks
|
|
173
|
-
```
|
|
155
|
+
If you're using React v17 or earlier:
|
|
174
156
|
|
|
175
157
|
```tsx
|
|
176
|
-
import
|
|
177
|
-
import { createState } from 'synstate
|
|
158
|
+
import * as React from 'react';
|
|
159
|
+
import { createState } from 'synstate';
|
|
178
160
|
|
|
179
|
-
|
|
161
|
+
// Global state (outside component)
|
|
162
|
+
const [userState, setUserState, { getSnapshot }] = createState({
|
|
180
163
|
name: '',
|
|
181
164
|
email: '',
|
|
182
165
|
});
|
|
183
166
|
|
|
184
167
|
const UserProfile = (): React.JSX.Element => {
|
|
185
|
-
const user =
|
|
168
|
+
const [user, setUser] = React.useState(getSnapshot());
|
|
169
|
+
|
|
170
|
+
React.useEffect(() => {
|
|
171
|
+
const subscription = userState.subscribe(setUser);
|
|
172
|
+
|
|
173
|
+
return () => {
|
|
174
|
+
subscription.unsubscribe();
|
|
175
|
+
};
|
|
176
|
+
}, []);
|
|
186
177
|
|
|
187
178
|
return (
|
|
188
179
|
<div>
|
|
189
|
-
<p>
|
|
190
|
-
{'Name: '}
|
|
191
|
-
{user.name}
|
|
192
|
-
</p>
|
|
180
|
+
<p>{`Name: ${user.name}`}</p>
|
|
193
181
|
<button
|
|
194
182
|
onClick={() => {
|
|
195
183
|
setUserState({
|
|
@@ -205,59 +193,92 @@ const UserProfile = (): React.JSX.Element => {
|
|
|
205
193
|
};
|
|
206
194
|
```
|
|
207
195
|
|
|
208
|
-
|
|
196
|
+
## Why SynState?
|
|
209
197
|
|
|
210
|
-
|
|
198
|
+
### Simple to Start, Powerful When You Need It
|
|
211
199
|
|
|
212
|
-
|
|
200
|
+
SynState is a state management library for web frontends. For most use cases, `createState`, `createReducer`, and simple combinators like `combine` and `map` are all you need — clean, minimal APIs that feel as intuitive as React's `useState` / `useReducer`, but for global state.
|
|
213
201
|
|
|
214
|
-
SynState
|
|
202
|
+
When your requirements grow more complex, SynState scales with you. Built on its own Observable implementation, it provides operators like `debounce`, `throttle`, `switchMap`, and `mergeMap` for sophisticated asynchronous state management — without requiring an additional library like RxJS. You can describe everything from a simple counter to a debounced search pipeline with auto-cancellation in a single, unified API.
|
|
215
203
|
|
|
216
|
-
|
|
217
|
-
- **`createReducer`**: Create state by reducer and initial value
|
|
218
|
-
- **`createBooleanState`**: Specialized state for boolean values
|
|
204
|
+
### Why Observable-Based?
|
|
219
205
|
|
|
220
|
-
|
|
206
|
+
A state management library that scales from simple global state to complex asynchronous workflows needs **reactive value propagation** at its core — when one piece of state changes, all derived values must update automatically and consistently. The Observable pattern is a natural fit for this: it models state as streams of values that can be composed, transformed, and combined declaratively.
|
|
221
207
|
|
|
222
|
-
|
|
208
|
+
RxJS is the most well-known Observable library, and it excels at modeling asynchronous event processing. However, RxJS has a fundamental issue known as **glitch**[^1] — a phenomenon where derived values can temporarily enter inconsistent intermediate states during synchronous propagation. For a state management library, where consistency of derived state is critical, this is unacceptable. SynState was built from scratch with a glitch-free Observable implementation to solve this problem.
|
|
223
209
|
|
|
224
|
-
|
|
225
|
-
- **`createEventEmitter`**: Create event emitters without payload
|
|
210
|
+
For a detailed explanation, see ["How SynState solved the glitch?"](./documents/how-synstate-solved-the-glitch.md).
|
|
226
211
|
|
|
227
|
-
###
|
|
212
|
+
### Key Differences from RxJS
|
|
228
213
|
|
|
229
|
-
|
|
214
|
+
- **Glitch free**: While RxJS Observables suffer from a troublesome phenomenon called glitch [^1], SynState Observables are glitch-free.
|
|
215
|
+
- **InitializedObservable**: Provides `InitializedObservable` which always holds an initial value, making it ideal for representing state
|
|
216
|
+
- **Focus on State Management**: Designed specifically for state management, not just asynchronous event processing. SynState provides utility functions `createState`, `createReducer`, and `createBooleanState`. However, this doesn't mean it's inadequate for asynchronous event processing — it can handle asynchronous operations as elegantly as RxJS.
|
|
230
217
|
|
|
231
|
-
|
|
218
|
+
### Use Cases
|
|
232
219
|
|
|
233
|
-
|
|
220
|
+
**Use SynState when you need:**
|
|
234
221
|
|
|
235
|
-
|
|
222
|
+
- ✅ A small piece of global state shared across components (e.g., dark mode toggle, user session)
|
|
223
|
+
- ✅ Complex asynchronous state management with operators like `debounce`, `throttle`, `switchMap`
|
|
224
|
+
- ✅ Redux-like state with reducers (`createReducer`)
|
|
225
|
+
- ✅ A project where the scale of state management is uncertain — SynState's unified API covers everything from a single shared counter to a full debounced search pipeline, so you never have to switch libraries as requirements grow
|
|
226
|
+
- ✅ Type-safe event emitters (`createEventEmitter`)
|
|
236
227
|
|
|
237
|
-
|
|
238
|
-
- `of(value)`: Create observable from a single value
|
|
239
|
-
- `fromArray(array)`: Create observable from array
|
|
240
|
-
- `fromPromise(promise)`: Create observable from promise
|
|
241
|
-
- `interval(ms)`: Emit values at intervals
|
|
242
|
-
- `timer(delay)`: Emit after delay
|
|
228
|
+
**Consider other solutions when:**
|
|
243
229
|
|
|
244
|
-
|
|
230
|
+
- You only need a React component (local) state (use React hooks `useState`, `useReducer`)
|
|
245
231
|
|
|
246
|
-
|
|
247
|
-
- `map(fn)`: Transform values
|
|
248
|
-
- `scan(reducer, seed)`: Accumulate values
|
|
249
|
-
- `debounceTime(ms)`: Debounce emissions
|
|
250
|
-
- `throttleTime(ms)`: Throttle emissions
|
|
251
|
-
- `skipIfNoChange()`: Skip duplicate values
|
|
252
|
-
- `takeUntil(notifier)`: Complete on notifier emission
|
|
232
|
+
## Examples
|
|
253
233
|
|
|
254
|
-
###
|
|
234
|
+
### Simple State with Additional APIs
|
|
255
235
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
236
|
+
```tsx
|
|
237
|
+
// Create a reactive state
|
|
238
|
+
const [
|
|
239
|
+
state,
|
|
240
|
+
setState,
|
|
241
|
+
{ updateState, resetState, getSnapshot, initialState },
|
|
242
|
+
] = createState(0);
|
|
243
|
+
// type of state: InitializedObservable<number>
|
|
244
|
+
// type of setState: (v: number) => number
|
|
245
|
+
// type of updateState: (updater: (prev: number) => number) => number
|
|
246
|
+
// type of resetState: () => void
|
|
247
|
+
// type of getSnapshot: () => number
|
|
248
|
+
// type of initialState: number
|
|
249
|
+
|
|
250
|
+
const stateHistory: number[] = [];
|
|
251
|
+
|
|
252
|
+
// Subscribe to changes
|
|
253
|
+
state.subscribe((count) => {
|
|
254
|
+
stateHistory.push(count);
|
|
255
|
+
});
|
|
259
256
|
|
|
260
|
-
|
|
257
|
+
assert.deepStrictEqual(stateHistory, [0]);
|
|
258
|
+
|
|
259
|
+
assert.strictEqual(getSnapshot(), 0);
|
|
260
|
+
|
|
261
|
+
// Update state
|
|
262
|
+
setState(1);
|
|
263
|
+
|
|
264
|
+
assert.strictEqual(getSnapshot(), 1);
|
|
265
|
+
|
|
266
|
+
assert.deepStrictEqual(stateHistory, [0, 1]);
|
|
267
|
+
|
|
268
|
+
updateState((prev) => prev + 2);
|
|
269
|
+
|
|
270
|
+
assert.strictEqual(getSnapshot(), 3);
|
|
271
|
+
|
|
272
|
+
assert.deepStrictEqual(stateHistory, [0, 1, 3]);
|
|
273
|
+
|
|
274
|
+
resetState();
|
|
275
|
+
|
|
276
|
+
assert.strictEqual(getSnapshot(), 0);
|
|
277
|
+
|
|
278
|
+
assert.strictEqual(initialState, 0);
|
|
279
|
+
|
|
280
|
+
assert.deepStrictEqual(stateHistory, [0, 1, 3, 0]);
|
|
281
|
+
```
|
|
261
282
|
|
|
262
283
|
### Global Counter State (React)
|
|
263
284
|
|
|
@@ -266,8 +287,11 @@ import type * as React from 'react';
|
|
|
266
287
|
import { createState } from 'synstate-react-hooks';
|
|
267
288
|
|
|
268
289
|
// Create global state
|
|
269
|
-
export const [useCounterState, , { updateState, resetState
|
|
270
|
-
|
|
290
|
+
export const [useCounterState, , { updateState, resetState }] = createState(0);
|
|
291
|
+
|
|
292
|
+
const increment = (): void => {
|
|
293
|
+
updateState((n) => n + 1);
|
|
294
|
+
};
|
|
271
295
|
|
|
272
296
|
// Component 1
|
|
273
297
|
const Counter = (): React.JSX.Element => {
|
|
@@ -275,30 +299,15 @@ const Counter = (): React.JSX.Element => {
|
|
|
275
299
|
|
|
276
300
|
return (
|
|
277
301
|
<div>
|
|
278
|
-
<p>
|
|
279
|
-
|
|
280
|
-
{count}
|
|
281
|
-
</p>
|
|
282
|
-
<button
|
|
283
|
-
onClick={() => {
|
|
284
|
-
updateState((n: number) => n + 1);
|
|
285
|
-
}}
|
|
286
|
-
>
|
|
287
|
-
{'Increment'}
|
|
288
|
-
</button>
|
|
302
|
+
<p>{`Count: ${count}`}</p>
|
|
303
|
+
<button onClick={increment}>{'Increment'}</button>
|
|
289
304
|
</div>
|
|
290
305
|
);
|
|
291
306
|
};
|
|
292
307
|
|
|
293
|
-
// Component 2
|
|
308
|
+
// Component 2
|
|
294
309
|
const ResetButton = (): React.JSX.Element => (
|
|
295
|
-
<button
|
|
296
|
-
onClick={() => {
|
|
297
|
-
resetState();
|
|
298
|
-
}}
|
|
299
|
-
>
|
|
300
|
-
{'Reset'}
|
|
301
|
-
</button>
|
|
310
|
+
<button onClick={resetState}>{'Reset'}</button>
|
|
302
311
|
);
|
|
303
312
|
```
|
|
304
313
|
|
|
@@ -320,7 +329,7 @@ type Action = Readonly<
|
|
|
320
329
|
| { type: 'remove'; id: number }
|
|
321
330
|
>;
|
|
322
331
|
|
|
323
|
-
const initialTodos: readonly Todo[] = [];
|
|
332
|
+
const initialTodos: readonly Todo[] = [] as const;
|
|
324
333
|
|
|
325
334
|
const reducer = (todos: readonly Todo[], action: Action): readonly Todo[] => {
|
|
326
335
|
switch (action.type) {
|
|
@@ -475,17 +484,15 @@ const ItemList = (): React.JSX.Element => {
|
|
|
475
484
|
};
|
|
476
485
|
```
|
|
477
486
|
|
|
478
|
-
// Events
|
|
479
|
-
|
|
480
487
|
### Advanced: Search with Debounce
|
|
481
488
|
|
|
482
489
|
```tsx
|
|
483
490
|
import type * as React from 'react';
|
|
484
491
|
import {
|
|
485
492
|
createState,
|
|
486
|
-
|
|
493
|
+
debounce,
|
|
487
494
|
filter,
|
|
488
|
-
|
|
495
|
+
fromAbortablePromise,
|
|
489
496
|
type InitializedObservable,
|
|
490
497
|
map,
|
|
491
498
|
switchMap,
|
|
@@ -500,12 +507,12 @@ const [searchState, setSearchState] = createState('');
|
|
|
500
507
|
const searchResults$: InitializedObservable<
|
|
501
508
|
readonly Readonly<{ id: string; name: string }>[]
|
|
502
509
|
> = searchState
|
|
503
|
-
.pipe(
|
|
510
|
+
.pipe(debounce(300))
|
|
504
511
|
.pipe(filter((query) => query.length > 2))
|
|
505
512
|
.pipe(
|
|
506
513
|
switchMap((query) =>
|
|
507
|
-
|
|
508
|
-
fetch(`/api/search?q=${query}
|
|
514
|
+
fromAbortablePromise((signal) =>
|
|
515
|
+
fetch(`/api/search?q=${query}`, { signal }).then(
|
|
509
516
|
(r) =>
|
|
510
517
|
r.json() as Promise<
|
|
511
518
|
readonly Readonly<{ id: string; name: string }>[]
|
|
@@ -542,10 +549,12 @@ const SearchBox = (): React.JSX.Element => {
|
|
|
542
549
|
### Advanced: Event Emitter with Throttle
|
|
543
550
|
|
|
544
551
|
```tsx
|
|
545
|
-
import { createEventEmitter,
|
|
552
|
+
import { createEventEmitter, throttle } from 'synstate';
|
|
546
553
|
|
|
547
554
|
// Create event emitter
|
|
548
555
|
const [refreshClicked, onRefreshClick] = createEventEmitter();
|
|
556
|
+
// refreshClicked: Observable<void>
|
|
557
|
+
// onRefreshClick: () => void
|
|
549
558
|
|
|
550
559
|
// Subscribe to events
|
|
551
560
|
refreshClicked.subscribe(() => {
|
|
@@ -553,7 +562,7 @@ refreshClicked.subscribe(() => {
|
|
|
553
562
|
});
|
|
554
563
|
|
|
555
564
|
// Throttle refresh clicks to prevent rapid successive executions
|
|
556
|
-
const throttledRefresh = refreshClicked.pipe(
|
|
565
|
+
const throttledRefresh = refreshClicked.pipe(throttle(2000));
|
|
557
566
|
|
|
558
567
|
throttledRefresh.subscribe(() => {
|
|
559
568
|
console.log('Executing refresh...');
|
|
@@ -572,38 +581,82 @@ const DataTable = (): React.JSX.Element => (
|
|
|
572
581
|
);
|
|
573
582
|
```
|
|
574
583
|
|
|
575
|
-
##
|
|
584
|
+
## API Reference
|
|
585
|
+
|
|
586
|
+
### State Management
|
|
587
|
+
|
|
588
|
+
SynState provides simple, intuitive APIs for managing application state:
|
|
576
589
|
|
|
577
|
-
|
|
590
|
+
- **`createState`**: Create state with InitializedObservable and setter
|
|
591
|
+
- **`createReducer`**: Create state by reducer and initial value
|
|
592
|
+
- **`createBooleanState`**: Specialized state for boolean values
|
|
578
593
|
|
|
579
|
-
|
|
594
|
+
### Event System
|
|
580
595
|
|
|
581
|
-
|
|
596
|
+
Built-in event emitter for event-driven patterns:
|
|
582
597
|
|
|
583
|
-
|
|
598
|
+
- **`createValueEmitter`**: Create type-safe event emitters
|
|
599
|
+
- **`createEventEmitter`**: Create event emitters without payload
|
|
584
600
|
|
|
585
|
-
###
|
|
601
|
+
### Observable APIs
|
|
586
602
|
|
|
587
|
-
|
|
588
|
-
- **InitializedObservable**: Provides `InitializedObservable` which always holds an initial value, making it ideal for representing state
|
|
589
|
-
- **Simpler API**: Most use cases are covered by `createState`, `createReducer`, and `createEventEmitter`
|
|
590
|
-
- **Better Readability**: No need for complex operator chains in everyday code
|
|
591
|
-
- **Optional Complexity**: Advanced features available to manipulate Observables when needed
|
|
603
|
+
For complex scenarios, SynState provides observable-based APIs:
|
|
592
604
|
|
|
593
|
-
|
|
605
|
+
#### Creation Functions
|
|
594
606
|
|
|
595
|
-
|
|
607
|
+
- `source<T>()`: Create a new observable source (Almost equivalent to RxJS `subject`)
|
|
608
|
+
- `fromPromise(promise)`: Create observable from promise
|
|
609
|
+
- `fromSubscribable()`: Create observable from any subscribable object
|
|
610
|
+
- `counter(ms)`: Emit values at intervals (Almost equivalent to RxJS `interval`)
|
|
611
|
+
- `timer(delay)`: Emit after delay
|
|
596
612
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
-
|
|
600
|
-
-
|
|
601
|
-
-
|
|
613
|
+
#### Operators
|
|
614
|
+
|
|
615
|
+
- `map` variants
|
|
616
|
+
- `map(fn)`: Transform values
|
|
617
|
+
- `mapTo(value)`: Map all values to a constant
|
|
618
|
+
- `getKey(key)`: Extract property value from objects (alias: `pluck`)
|
|
619
|
+
- `attachIndex()`: Attach index to each value (alias: `withIndex`)
|
|
620
|
+
- Result/Optional
|
|
621
|
+
- `mapOptional(fn)`: Map over Optional values
|
|
622
|
+
- `mapResultOk(fn)`: Map over Result ok values
|
|
623
|
+
- `mapResultErr(fn)`: Map over Result error values
|
|
624
|
+
- `unwrapOptional()`: Unwrap Optional values to undefined
|
|
625
|
+
- `unwrapResultOk()`: Unwrap Result ok values to undefined
|
|
626
|
+
- `unwrapResultErr()`: Unwrap Result error values to undefined
|
|
627
|
+
- `mergeMap(fn)`: Map to observables and merge all (runs in parallel) (alias: `flatMap`)
|
|
628
|
+
- `switchMap(fn)`: Map to observables and switch to latest (cancels previous)
|
|
629
|
+
- Filtering
|
|
630
|
+
- `filter(predicate)`: Filter values
|
|
631
|
+
- `skipIfNoChange()`: Skip duplicate values (alias: `distinctUntilChanged`)
|
|
632
|
+
- `skip(n)`: Skip first n emissions
|
|
633
|
+
- `take(n)`: Take first n emissions then complete
|
|
634
|
+
- `skipWhile(predicate)`: Skip values while predicate is true
|
|
635
|
+
- `takeWhile(predicate)`: Emit values while predicate is true, then complete
|
|
636
|
+
- `skipUntil(notifier)`: Skip values until notifier emits
|
|
637
|
+
- `takeUntil(notifier)`: Complete on notifier emission
|
|
638
|
+
- Time series processing
|
|
639
|
+
- `audit(ms)`: Emit the last value after specified time window (Almost equivalent to RxJS `auditTime`)
|
|
640
|
+
- `debounce(ms)`: Debounce emissions (Almost equivalent to RxJS `debounceTime`)
|
|
641
|
+
- `throttle(ms)`: Throttle emissions (Almost equivalent to RxJS `throttleTime`)
|
|
642
|
+
- Others
|
|
643
|
+
- `pairwise()`: Emit previous and current values as pairs
|
|
644
|
+
- `scan(reducer, seed)`: Accumulate values
|
|
645
|
+
- `withBuffered(observable)`: Buffer values from observable and emit with parent (alias: `withBufferedFrom`)
|
|
646
|
+
- `withCurrentValueFrom(observable)`: Sample current value from another observable (alias: `withLatestFrom`)
|
|
647
|
+
- `withInitialValue(value)`: Provide an initial value for uninitialized observable
|
|
648
|
+
|
|
649
|
+
#### Combination
|
|
650
|
+
|
|
651
|
+
- `combine(observables)`: Combine latest values from multiple sources (alias: `combineLatest`)
|
|
652
|
+
- `merge(observables)`: Merge multiple streams
|
|
653
|
+
- `zip(observables)`: Pair values by index
|
|
602
654
|
|
|
603
|
-
|
|
655
|
+
#### Utilities
|
|
604
656
|
|
|
605
|
-
-
|
|
606
|
-
-
|
|
657
|
+
- `isChildObservable(obs)`: Check if observable is a child observable
|
|
658
|
+
- `isManagerObservable(obs)`: Check if observable is a manager observable
|
|
659
|
+
- `isRootObservable(obs)`: Check if observable is a root observable
|
|
607
660
|
|
|
608
661
|
## Type Safety
|
|
609
662
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"child-observable-class.d.mts","sourceRoot":"","sources":["../../../src/core/class/child-observable-class.mts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"child-observable-class.d.mts","sourceRoot":"","sources":["../../../src/core/class/child-observable-class.mts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AA2GlE,qBAAa,yBAAyB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAC3E,SAAQ,mBAAmB,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CACpD,YAAW,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC;;IAErC,QAAQ,CAAC,OAAO,sDAAC;IAEjB,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;gBAElD,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KAClE,CAAC;IAiBF,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAiBjD,WAAW,CAAC,SAAS,EAAE,CAAC,GAAG,IAAI;IAUtB,QAAQ,IAAI,IAAI;IAShB,WAAW,IAAI,IAAI;CAU7B;AAED,qBAAa,wBAAwB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAC1E,SAAQ,mBAAmB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CACnD,YAAW,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpC,QAAQ,CAAC,OAAO,sDAAC;gBAEL,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KACjE,CAAC;IAYO,QAAQ,IAAI,IAAI;IAQhB,WAAW,IAAI,IAAI;CAU7B;AAED,qBAAa,mCAAmC,CAC9C,CAAC,EACD,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAEnC,SAAQ,wBAAwB,CAAC,CAAC,EAAE,CAAC,CACrC,YAAW,8BAA8B,CAAC,CAAC,EAAE,CAAC,CAAC;gBAEnC,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KAC5E,CAAC;IAIO,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC;IAKtB,IAAI,CAAC,CAAC,EACb,QAAQ,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACxE,qBAAqB,CAAC,CAAC,CAAC;IAElB,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;CAO1D"}
|
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
import { Arr } from 'ts-data-forge';
|
|
2
|
-
import { isManagerObservable } from '../types/observable.mjs';
|
|
3
|
-
import {
|
|
2
|
+
import { isManagerObservable, isChildObservable } from '../types/observable.mjs';
|
|
3
|
+
import { issueUpdateToken } from '../utils/id-maker.mjs';
|
|
4
4
|
import { maxDepth } from '../utils/max-depth.mjs';
|
|
5
5
|
import { binarySearch } from '../utils/utils.mjs';
|
|
6
6
|
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Detects circular dependencies by walking the full ancestor chain of the
|
|
10
|
+
* given parents and checking whether `child` already appears among them.
|
|
11
|
+
*
|
|
12
|
+
* @throws {Error} if a circular dependency is detected
|
|
13
|
+
*/
|
|
14
|
+
const hasCircularDependencyFrom = (node, mut_visited, mut_inPath) => {
|
|
15
|
+
if (mut_inPath.has(node.id))
|
|
16
|
+
return true;
|
|
17
|
+
if (mut_visited.has(node.id))
|
|
18
|
+
return false;
|
|
19
|
+
mut_visited.add(node.id);
|
|
20
|
+
mut_inPath.add(node.id);
|
|
21
|
+
if (isChildObservable(node)) {
|
|
22
|
+
for (const parent of node.parents) {
|
|
23
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
mut_inPath.delete(node.id);
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
const detectCircularDependency = (child, parents) => {
|
|
32
|
+
const mut_visited = new Set();
|
|
33
|
+
const mut_inPath = new Set([child.id]);
|
|
34
|
+
for (const parent of parents) {
|
|
35
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
36
|
+
throw new Error('Circular dependency detected in observable graph: a child observable cannot be its own ancestor.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
8
40
|
const registerChild = (child, parents) => {
|
|
41
|
+
detectCircularDependency(child, parents);
|
|
9
42
|
for (const p of parents) {
|
|
10
43
|
p.addChild(child);
|
|
11
44
|
}
|
|
@@ -41,7 +74,7 @@ const tryComplete = ({ hasSubscriber, hasActiveChild, parents, complete, }) => {
|
|
|
41
74
|
};
|
|
42
75
|
class AsyncChildObservableClass extends ObservableBaseClass {
|
|
43
76
|
parents;
|
|
44
|
-
#
|
|
77
|
+
#mut_propagationOrder;
|
|
45
78
|
descendantsIdSet;
|
|
46
79
|
constructor({ parents, depth = 1 + maxDepth(parents), initialValue, }) {
|
|
47
80
|
super({
|
|
@@ -50,7 +83,7 @@ class AsyncChildObservableClass extends ObservableBaseClass {
|
|
|
50
83
|
initialValue,
|
|
51
84
|
});
|
|
52
85
|
this.parents = parents;
|
|
53
|
-
this.#
|
|
86
|
+
this.#mut_propagationOrder = [];
|
|
54
87
|
this.descendantsIdSet = new Set();
|
|
55
88
|
registerChild(this, parents);
|
|
56
89
|
}
|
|
@@ -59,14 +92,14 @@ class AsyncChildObservableClass extends ObservableBaseClass {
|
|
|
59
92
|
if (this.descendantsIdSet.has(child.id))
|
|
60
93
|
return;
|
|
61
94
|
this.descendantsIdSet.add(child.id);
|
|
62
|
-
const insertPos = binarySearch(this.#
|
|
63
|
-
this.#
|
|
95
|
+
const insertPos = binarySearch(this.#mut_propagationOrder.map((a) => a.depth), child.depth);
|
|
96
|
+
this.#mut_propagationOrder = Arr.toInserted(this.#mut_propagationOrder, insertPos, child);
|
|
64
97
|
}
|
|
65
98
|
startUpdate(nextValue) {
|
|
66
|
-
const
|
|
67
|
-
this.setNext(nextValue,
|
|
68
|
-
for (const p of this.#
|
|
69
|
-
p.tryUpdate(
|
|
99
|
+
const updateToken = issueUpdateToken();
|
|
100
|
+
this.setNext(nextValue, updateToken);
|
|
101
|
+
for (const p of this.#mut_propagationOrder) {
|
|
102
|
+
p.tryUpdate(updateToken);
|
|
70
103
|
}
|
|
71
104
|
}
|
|
72
105
|
complete() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"child-observable-class.mjs","sources":["../../../src/core/class/child-observable-class.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"child-observable-class.mjs","sources":["../../../src/core/class/child-observable-class.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|