synstate 0.1.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +200 -147
- package/assets/old/synstate-icon-square.png +0 -0
- package/assets/synstate-logo.png +0 -0
- package/dist/core/class/child-observable-class.d.mts +2 -0
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +44 -13
- 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 +3 -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 +7 -6
- package/dist/core/combine/combine.d.mts.map +1 -1
- package/dist/core/combine/combine.mjs +11 -12
- package/dist/core/combine/combine.mjs.map +1 -1
- package/dist/core/combine/merge.d.mts +7 -6
- package/dist/core/combine/merge.d.mts.map +1 -1
- package/dist/core/combine/merge.mjs +9 -9
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +21 -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 +6 -5
- package/dist/core/operators/filter.d.mts.map +1 -1
- 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} +12 -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 +19 -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 +16 -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 +50 -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 +48 -1
- 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 +48 -1
- 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 +48 -1
- 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 +48 -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 +42 -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 +41 -1
- 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 +41 -1
- 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 +41 -1
- 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 +2 -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 +10 -14
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable-kind.d.mts +1 -0
- package/dist/core/types/observable-kind.d.mts.map +1 -1
- package/dist/core/types/observable.d.mts +5 -3
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/observable.mjs.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/core/utils/utils.d.mts +2 -0
- package/dist/core/utils/utils.d.mts.map +1 -1
- package/dist/core/utils/utils.mjs.map +1 -1
- package/dist/entry-point.mjs +11 -14
- package/dist/entry-point.mjs.map +1 -1
- package/dist/index.mjs +11 -14
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.mts +1 -2
- 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 +11 -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 +21 -14
- package/src/core/class/child-observable-class.mts +68 -14
- 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 +10 -9
- package/src/core/class/root-observable-class.mts +15 -10
- package/src/core/combine/combine.mts +14 -13
- package/src/core/combine/merge.mts +14 -14
- package/src/core/combine/zip.mts +27 -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 +52 -2
- package/src/core/predefined/operators/index.mts +0 -1
- package/src/core/predefined/operators/map-optional.mts +49 -2
- package/src/core/predefined/operators/map-result-err.mts +49 -2
- package/src/core/predefined/operators/map-result-ok.mts +49 -2
- 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 +48 -0
- package/src/core/predefined/operators/take.mts +42 -0
- package/src/core/predefined/operators/unwrap-optional.mts +43 -2
- package/src/core/predefined/operators/unwrap-result-err.mts +43 -2
- package/src/core/predefined/operators/unwrap-result-ok.mts +43 -2
- package/src/core/types/id.mts +3 -1
- package/src/core/types/index.mts +1 -0
- package/src/core/types/observable-family.mts +13 -24
- package/src/core/types/observable-kind.mts +2 -0
- package/src/core/types/observable.mts +5 -4
- package/src/core/types/timer.mts +2 -0
- package/src/core/utils/id-maker.mts +4 -4
- package/src/core/utils/utils.mts +1 -0
- 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 +12 -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/dist/globals.d.mts +0 -4
- 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/src/globals.d.mts +0 -4
- /package/assets/{synstate-icon.png → old/synstate-icon.png} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Arr } from 'ts-data-forge';
|
|
1
|
+
import { Arr, type Some } from 'ts-data-forge';
|
|
2
|
+
import { type MutableSet } from 'ts-type-forge';
|
|
2
3
|
import {
|
|
4
|
+
isChildObservable,
|
|
3
5
|
isManagerObservable,
|
|
4
6
|
type AsyncChildObservable,
|
|
5
7
|
type ChildObservable,
|
|
@@ -14,13 +16,64 @@ import {
|
|
|
14
16
|
type WithInitialValueOperator,
|
|
15
17
|
type Wrap,
|
|
16
18
|
} from '../types/index.mjs';
|
|
17
|
-
import { binarySearch,
|
|
19
|
+
import { binarySearch, issueUpdateToken, maxDepth } from '../utils/index.mjs';
|
|
18
20
|
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Detects circular dependencies by walking the full ancestor chain of the
|
|
24
|
+
* given parents and checking whether `child` already appears among them.
|
|
25
|
+
*
|
|
26
|
+
* @throws {Error} if a circular dependency is detected
|
|
27
|
+
*/
|
|
28
|
+
const hasCircularDependencyFrom = (
|
|
29
|
+
node: Observable<unknown>,
|
|
30
|
+
mut_visited: MutableSet<ObservableId>,
|
|
31
|
+
mut_inPath: MutableSet<ObservableId>,
|
|
32
|
+
): boolean => {
|
|
33
|
+
if (mut_inPath.has(node.id)) return true;
|
|
34
|
+
|
|
35
|
+
if (mut_visited.has(node.id)) return false;
|
|
36
|
+
|
|
37
|
+
mut_visited.add(node.id);
|
|
38
|
+
|
|
39
|
+
mut_inPath.add(node.id);
|
|
40
|
+
|
|
41
|
+
if (isChildObservable(node)) {
|
|
42
|
+
for (const parent of node.parents) {
|
|
43
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mut_inPath.delete(node.id);
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const detectCircularDependency = (
|
|
55
|
+
child: ChildObservable<unknown>,
|
|
56
|
+
parents: readonly Observable<unknown>[],
|
|
57
|
+
): void => {
|
|
58
|
+
const mut_visited = new Set<ObservableId>();
|
|
59
|
+
|
|
60
|
+
const mut_inPath = new Set<ObservableId>([child.id]);
|
|
61
|
+
|
|
62
|
+
for (const parent of parents) {
|
|
63
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
20
71
|
const registerChild = <A,>(
|
|
21
72
|
child: ChildObservable<A>,
|
|
22
73
|
parents: ChildObservable<A>['parents'],
|
|
23
74
|
): void => {
|
|
75
|
+
detectCircularDependency(child, parents);
|
|
76
|
+
|
|
24
77
|
for (const p of parents) {
|
|
25
78
|
p.addChild(child);
|
|
26
79
|
}
|
|
@@ -76,7 +129,7 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
76
129
|
implements AsyncChildObservable<A, P>
|
|
77
130
|
{
|
|
78
131
|
readonly parents;
|
|
79
|
-
#
|
|
132
|
+
#mut_propagationOrder: readonly ChildObservable<unknown>[];
|
|
80
133
|
protected readonly descendantsIdSet: MutableSet<ObservableId>;
|
|
81
134
|
|
|
82
135
|
constructor({
|
|
@@ -96,7 +149,7 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
96
149
|
|
|
97
150
|
this.parents = parents;
|
|
98
151
|
|
|
99
|
-
this.#
|
|
152
|
+
this.#mut_propagationOrder = [];
|
|
100
153
|
|
|
101
154
|
this.descendantsIdSet = new Set<ObservableId>();
|
|
102
155
|
|
|
@@ -110,20 +163,24 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
110
163
|
this.descendantsIdSet.add(child.id);
|
|
111
164
|
|
|
112
165
|
const insertPos = binarySearch(
|
|
113
|
-
this.#
|
|
166
|
+
this.#mut_propagationOrder.map((a) => a.depth),
|
|
114
167
|
child.depth,
|
|
115
168
|
);
|
|
116
169
|
|
|
117
|
-
this.#
|
|
170
|
+
this.#mut_propagationOrder = Arr.toInserted(
|
|
171
|
+
this.#mut_propagationOrder,
|
|
172
|
+
insertPos,
|
|
173
|
+
child,
|
|
174
|
+
);
|
|
118
175
|
}
|
|
119
176
|
|
|
120
177
|
startUpdate(nextValue: A): void {
|
|
121
|
-
const
|
|
178
|
+
const updateToken = issueUpdateToken();
|
|
122
179
|
|
|
123
|
-
this.setNext(nextValue,
|
|
180
|
+
this.setNext(nextValue, updateToken);
|
|
124
181
|
|
|
125
|
-
for (const p of this.#
|
|
126
|
-
p.tryUpdate(
|
|
182
|
+
for (const p of this.#mut_propagationOrder) {
|
|
183
|
+
p.tryUpdate(updateToken);
|
|
127
184
|
}
|
|
128
185
|
}
|
|
129
186
|
|
|
@@ -224,9 +281,6 @@ export class InitializedSyncChildObservableClass<
|
|
|
224
281
|
|
|
225
282
|
override pipe<B>(operator: Operator<A, B>): Observable<B>;
|
|
226
283
|
override pipe<B>(operator: Operator<A, B>): Observable<B> {
|
|
227
|
-
return operator(
|
|
228
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
229
|
-
this as unknown as InitializedObservable<A>,
|
|
230
|
-
);
|
|
284
|
+
return operator(this);
|
|
231
285
|
}
|
|
232
286
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison tests: how SynState, RxJS, and Jotai handle circular dependencies.
|
|
3
|
+
*
|
|
4
|
+
* - SynState: detects cycles at construction time and throws a clear error.
|
|
5
|
+
* - RxJS: pipe() always returns a new Observable, so static cycles cannot be
|
|
6
|
+
* expressed through the public API. No detection is needed or provided.
|
|
7
|
+
* - Jotai: circular atom definitions are silently accepted. A cycle manifests
|
|
8
|
+
* only at read time as a `Maximum call stack size exceeded` error.
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-disable functional/immutable-data */
|
|
11
|
+
/* eslint-disable no-new */
|
|
12
|
+
import { type Atom, atom, createStore } from 'jotai';
|
|
13
|
+
import { BehaviorSubject, combineLatest, map as rxMap } from 'rxjs';
|
|
14
|
+
import { Optional } from 'ts-data-forge';
|
|
15
|
+
import { combine } from '../combine/index.mjs';
|
|
16
|
+
import { source } from '../create/index.mjs';
|
|
17
|
+
import { map } from '../operators/index.mjs';
|
|
18
|
+
import { SyncChildObservableClass } from './child-observable-class.mjs';
|
|
19
|
+
import { RootObservableClass } from './root-observable-class.mjs';
|
|
20
|
+
|
|
21
|
+
describe('circular dependency comparison', () => {
|
|
22
|
+
describe('SynState', () => {
|
|
23
|
+
test('detects cycle at construction time with a clear error message', () => {
|
|
24
|
+
const root = new RootObservableClass({
|
|
25
|
+
initialValue: Optional.some(0),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const childA = new SyncChildObservableClass({
|
|
29
|
+
parents: [root],
|
|
30
|
+
initialValue: Optional.some(0),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const childB = new SyncChildObservableClass({
|
|
34
|
+
parents: [childA],
|
|
35
|
+
initialValue: Optional.some(0),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Simulate a cycle: childA → childB → childA
|
|
39
|
+
// In normal usage this is impossible — the check runs in every
|
|
40
|
+
// child constructor before the reference becomes available.
|
|
41
|
+
Object.defineProperty(childA, 'parents', {
|
|
42
|
+
value: [childB],
|
|
43
|
+
writable: false,
|
|
44
|
+
configurable: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(() => {
|
|
48
|
+
new SyncChildObservableClass({
|
|
49
|
+
parents: [childA],
|
|
50
|
+
initialValue: Optional.some(0),
|
|
51
|
+
});
|
|
52
|
+
}).toThrow(
|
|
53
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('accepts valid DAG (diamond dependency)', () => {
|
|
58
|
+
const a$ = source(0);
|
|
59
|
+
|
|
60
|
+
const b$ = a$.pipe(map((x) => x * 10));
|
|
61
|
+
|
|
62
|
+
const c$ = a$.pipe(map((x) => x * 1000));
|
|
63
|
+
|
|
64
|
+
expect(() => {
|
|
65
|
+
combine([b$, c$]);
|
|
66
|
+
}).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('RxJS', () => {
|
|
71
|
+
test('cannot express static graph cycles — pipe() always creates new instances', () => {
|
|
72
|
+
// RxJS pipe() returns a brand-new Observable each time, so there is no
|
|
73
|
+
// way to construct a cycle through the public API. This test documents
|
|
74
|
+
// that behavior rather than asserting error detection.
|
|
75
|
+
const a$ = new BehaviorSubject(0);
|
|
76
|
+
|
|
77
|
+
const b$ = a$.pipe(rxMap((x) => x + 1));
|
|
78
|
+
|
|
79
|
+
// b$ is a wholly separate object; there is no "parents" link back to a$.
|
|
80
|
+
expect(a$).not.toBe(b$);
|
|
81
|
+
|
|
82
|
+
// A diamond dependency works but produces glitches (intermediate states).
|
|
83
|
+
const left$ = a$.pipe(rxMap((x) => x * 10));
|
|
84
|
+
|
|
85
|
+
const right$ = a$.pipe(rxMap((x) => x * 1000));
|
|
86
|
+
|
|
87
|
+
const combined$ = combineLatest([left$, right$]).pipe(
|
|
88
|
+
rxMap(([l, r]) => l + r),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const mut_values: number[] = [];
|
|
92
|
+
|
|
93
|
+
combined$.subscribe((v) => {
|
|
94
|
+
mut_values.push(v);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
a$.next(1);
|
|
98
|
+
|
|
99
|
+
a$.next(2);
|
|
100
|
+
|
|
101
|
+
// Glitch values (10, 1020) appear between the correct values.
|
|
102
|
+
assert.deepStrictEqual(mut_values, [0, 10, 1010, 1020, 2020]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('Jotai', () => {
|
|
107
|
+
test('does not detect circular atom definitions — crashes on read', () => {
|
|
108
|
+
const store = createStore();
|
|
109
|
+
|
|
110
|
+
// Two atoms that depend on each other — an obvious cycle.
|
|
111
|
+
// Explicit type annotations are required because TypeScript cannot infer
|
|
112
|
+
// the type of mutually-recursive initializers.
|
|
113
|
+
const atomA: Atom<number> = atom((get) => get(atomB) + 1);
|
|
114
|
+
|
|
115
|
+
const atomB: Atom<number> = atom((get) => get(atomA) + 1);
|
|
116
|
+
|
|
117
|
+
// Definition succeeds without any error.
|
|
118
|
+
// Reading triggers infinite recursion.
|
|
119
|
+
expect(() => {
|
|
120
|
+
store.get(atomA);
|
|
121
|
+
}).toThrow('Maximum call stack size exceeded');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('handles valid DAG (diamond dependency) correctly', () => {
|
|
125
|
+
const store = createStore();
|
|
126
|
+
|
|
127
|
+
const base = atom(1);
|
|
128
|
+
|
|
129
|
+
const left = atom((get) => get(base) * 10);
|
|
130
|
+
|
|
131
|
+
const right = atom((get) => get(base) * 1000);
|
|
132
|
+
|
|
133
|
+
const combined = atom((get) => get(left) + get(right));
|
|
134
|
+
|
|
135
|
+
expect(store.get(combined)).toBe(1010);
|
|
136
|
+
|
|
137
|
+
store.set(base, 2);
|
|
138
|
+
|
|
139
|
+
expect(store.get(combined)).toBe(2020);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/* eslint-disable functional/immutable-data */
|
|
2
|
+
/* eslint-disable no-new */
|
|
3
|
+
import { Optional } from 'ts-data-forge';
|
|
4
|
+
import { combine, merge } from '../combine/index.mjs';
|
|
5
|
+
import { source } from '../create/index.mjs';
|
|
6
|
+
import { map } from '../operators/index.mjs';
|
|
7
|
+
import {
|
|
8
|
+
AsyncChildObservableClass,
|
|
9
|
+
SyncChildObservableClass,
|
|
10
|
+
} from './child-observable-class.mjs';
|
|
11
|
+
import { RootObservableClass } from './root-observable-class.mjs';
|
|
12
|
+
|
|
13
|
+
describe('circular dependency detection', () => {
|
|
14
|
+
describe('cycle in ancestor graph', () => {
|
|
15
|
+
test('should throw when parent chain contains a cycle (A -> B -> A)', () => {
|
|
16
|
+
const root = new RootObservableClass({
|
|
17
|
+
initialValue: Optional.some(0),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const childA = new SyncChildObservableClass({
|
|
21
|
+
parents: [root],
|
|
22
|
+
initialValue: Optional.some(0),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const childB = new SyncChildObservableClass({
|
|
26
|
+
parents: [childA],
|
|
27
|
+
initialValue: Optional.some(0),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Mutate childA.parents to create a cycle: childA -> childB -> childA
|
|
31
|
+
Object.defineProperty(childA, 'parents', {
|
|
32
|
+
value: [childB],
|
|
33
|
+
writable: false,
|
|
34
|
+
configurable: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(() => {
|
|
38
|
+
new SyncChildObservableClass({
|
|
39
|
+
parents: [childA],
|
|
40
|
+
initialValue: Optional.some(0),
|
|
41
|
+
});
|
|
42
|
+
}).toThrow(
|
|
43
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should throw when AsyncChildObservable parent chain contains a cycle', () => {
|
|
48
|
+
const root = new RootObservableClass({
|
|
49
|
+
initialValue: Optional.some(0),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const childA = new AsyncChildObservableClass({
|
|
53
|
+
parents: [root],
|
|
54
|
+
initialValue: Optional.some(0),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const childB = new SyncChildObservableClass({
|
|
58
|
+
parents: [childA],
|
|
59
|
+
initialValue: Optional.some(0),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Create cycle: childA -> childB -> childA
|
|
63
|
+
Object.defineProperty(childA, 'parents', {
|
|
64
|
+
value: [childB],
|
|
65
|
+
writable: false,
|
|
66
|
+
configurable: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(() => {
|
|
70
|
+
new AsyncChildObservableClass({
|
|
71
|
+
parents: [childA],
|
|
72
|
+
initialValue: Optional.some(0),
|
|
73
|
+
});
|
|
74
|
+
}).toThrow(
|
|
75
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should throw when a cycle exists through a chain of 3 observables', () => {
|
|
80
|
+
const root = new RootObservableClass({
|
|
81
|
+
initialValue: Optional.some(0),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const childA = new SyncChildObservableClass({
|
|
85
|
+
parents: [root],
|
|
86
|
+
initialValue: Optional.some(0),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const childB = new SyncChildObservableClass({
|
|
90
|
+
parents: [childA],
|
|
91
|
+
initialValue: Optional.some(0),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const childC = new SyncChildObservableClass({
|
|
95
|
+
parents: [childB],
|
|
96
|
+
initialValue: Optional.some(0),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Create cycle: childA -> childC -> childB -> childA
|
|
100
|
+
Object.defineProperty(childA, 'parents', {
|
|
101
|
+
value: [childC],
|
|
102
|
+
writable: false,
|
|
103
|
+
configurable: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(() => {
|
|
107
|
+
new SyncChildObservableClass({
|
|
108
|
+
parents: [childA],
|
|
109
|
+
initialValue: Optional.some(0),
|
|
110
|
+
});
|
|
111
|
+
}).toThrow(
|
|
112
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('should throw when the new child itself appears as an ancestor', () => {
|
|
117
|
+
const root = new RootObservableClass({
|
|
118
|
+
initialValue: Optional.some(0),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const childA = new SyncChildObservableClass({
|
|
122
|
+
parents: [root],
|
|
123
|
+
initialValue: Optional.some(0),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Make childA's parents reference childA itself (self-loop)
|
|
127
|
+
Object.defineProperty(childA, 'parents', {
|
|
128
|
+
value: [childA],
|
|
129
|
+
writable: false,
|
|
130
|
+
configurable: true,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(() => {
|
|
134
|
+
new SyncChildObservableClass({
|
|
135
|
+
parents: [childA],
|
|
136
|
+
initialValue: Optional.some(0),
|
|
137
|
+
});
|
|
138
|
+
}).toThrow(
|
|
139
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('valid DAG patterns should not throw', () => {
|
|
145
|
+
test('diamond dependency is not a cycle', () => {
|
|
146
|
+
const root = new RootObservableClass({
|
|
147
|
+
initialValue: Optional.some(0),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const left = new SyncChildObservableClass({
|
|
151
|
+
parents: [root],
|
|
152
|
+
initialValue: Optional.some(0),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const right = new SyncChildObservableClass({
|
|
156
|
+
parents: [root],
|
|
157
|
+
initialValue: Optional.some(0),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Diamond: root -> left -> combined, root -> right -> combined
|
|
161
|
+
expect(() => {
|
|
162
|
+
new SyncChildObservableClass({
|
|
163
|
+
parents: [left, right],
|
|
164
|
+
initialValue: Optional.some(0),
|
|
165
|
+
});
|
|
166
|
+
}).not.toThrow();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('linear chain is not a cycle', () => {
|
|
170
|
+
const root = new RootObservableClass({
|
|
171
|
+
initialValue: Optional.some(0),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const a = new SyncChildObservableClass({
|
|
175
|
+
parents: [root],
|
|
176
|
+
initialValue: Optional.some(0),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const b = new SyncChildObservableClass({
|
|
180
|
+
parents: [a],
|
|
181
|
+
initialValue: Optional.some(0),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(() => {
|
|
185
|
+
new SyncChildObservableClass({
|
|
186
|
+
parents: [b],
|
|
187
|
+
initialValue: Optional.some(0),
|
|
188
|
+
});
|
|
189
|
+
}).not.toThrow();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('multiple roots converging is not a cycle', () => {
|
|
193
|
+
const root1 = new RootObservableClass({
|
|
194
|
+
initialValue: Optional.some(1),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const root2 = new RootObservableClass({
|
|
198
|
+
initialValue: Optional.some(2),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const child1 = new SyncChildObservableClass({
|
|
202
|
+
parents: [root1],
|
|
203
|
+
initialValue: Optional.some(0),
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const child2 = new SyncChildObservableClass({
|
|
207
|
+
parents: [root2],
|
|
208
|
+
initialValue: Optional.some(0),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(() => {
|
|
212
|
+
new SyncChildObservableClass({
|
|
213
|
+
parents: [child1, child2],
|
|
214
|
+
initialValue: Optional.some(0),
|
|
215
|
+
});
|
|
216
|
+
}).not.toThrow();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('combine with source observables works', () => {
|
|
220
|
+
const a$ = source<number>();
|
|
221
|
+
|
|
222
|
+
const b$ = source<string>();
|
|
223
|
+
|
|
224
|
+
expect(() => {
|
|
225
|
+
combine([a$, b$]);
|
|
226
|
+
}).not.toThrow();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('combine with derived observables works', () => {
|
|
230
|
+
const a$ = source<number>();
|
|
231
|
+
|
|
232
|
+
const b$ = source<number>();
|
|
233
|
+
|
|
234
|
+
const mapped$ = a$.pipe(map((x) => x * 2));
|
|
235
|
+
|
|
236
|
+
expect(() => {
|
|
237
|
+
combine([mapped$, b$]);
|
|
238
|
+
}).not.toThrow();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('merge with source observables works', () => {
|
|
242
|
+
const a$ = source<number>();
|
|
243
|
+
|
|
244
|
+
const b$ = source<number>();
|
|
245
|
+
|
|
246
|
+
expect(() => {
|
|
247
|
+
merge([a$, b$]);
|
|
248
|
+
}).not.toThrow();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Arr, Optional } from 'ts-data-forge';
|
|
2
|
+
import { type MutableMap } from 'ts-type-forge';
|
|
2
3
|
import {
|
|
3
4
|
type ChildObservable,
|
|
4
5
|
type InitializedObservable,
|
|
@@ -8,13 +9,13 @@ import {
|
|
|
8
9
|
type Subscriber,
|
|
9
10
|
type SubscriberId,
|
|
10
11
|
type Subscription,
|
|
11
|
-
type
|
|
12
|
+
type UpdateToken,
|
|
12
13
|
type WithInitialValueOperator,
|
|
13
14
|
} from '../types/index.mjs';
|
|
14
15
|
import {
|
|
15
16
|
issueObservableId,
|
|
16
17
|
issueSubscriberId,
|
|
17
|
-
|
|
18
|
+
issueUpdateToken,
|
|
18
19
|
toSubscriber,
|
|
19
20
|
} from '../utils/index.mjs';
|
|
20
21
|
|
|
@@ -30,7 +31,7 @@ export class ObservableBaseClass<
|
|
|
30
31
|
readonly #subscribers: MutableMap<SubscriberId, Subscriber<A>>;
|
|
31
32
|
#mut_currentValue: ReturnType<ObservableBase<A>['getSnapshot']>;
|
|
32
33
|
#mut_isCompleted: ObservableBase<A>['isCompleted'];
|
|
33
|
-
#
|
|
34
|
+
#mut_updateToken: ObservableBase<A>['updateToken'];
|
|
34
35
|
|
|
35
36
|
constructor({
|
|
36
37
|
kind,
|
|
@@ -55,7 +56,7 @@ export class ObservableBaseClass<
|
|
|
55
56
|
|
|
56
57
|
this.#mut_isCompleted = false;
|
|
57
58
|
|
|
58
|
-
this.#
|
|
59
|
+
this.#mut_updateToken = issueUpdateToken();
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
addChild<B>(child: ChildObservable<B>): void {
|
|
@@ -78,8 +79,8 @@ export class ObservableBaseClass<
|
|
|
78
79
|
return this.#mut_isCompleted;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
get
|
|
82
|
-
return this.#
|
|
82
|
+
get updateToken(): UpdateToken {
|
|
83
|
+
return this.#mut_updateToken;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
get hasSubscriber(): boolean {
|
|
@@ -94,8 +95,8 @@ export class ObservableBaseClass<
|
|
|
94
95
|
return this.#mut_children.some((c) => !c.isCompleted);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
protected setNext(nextValue: A,
|
|
98
|
-
this.#
|
|
98
|
+
protected setNext(nextValue: A, updateToken: UpdateToken): void {
|
|
99
|
+
this.#mut_updateToken = updateToken;
|
|
99
100
|
|
|
100
101
|
this.#mut_currentValue = Optional.some(nextValue);
|
|
101
102
|
|
|
@@ -105,7 +106,7 @@ export class ObservableBaseClass<
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
108
|
-
tryUpdate(
|
|
109
|
+
tryUpdate(_updateToken: UpdateToken): void {
|
|
109
110
|
throw new Error('not implemented');
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { Arr, Optional } from 'ts-data-forge';
|
|
2
|
+
import { type MutableSet } from 'ts-type-forge';
|
|
2
3
|
import {
|
|
3
4
|
isRootObservable,
|
|
4
5
|
type ChildObservable,
|
|
5
6
|
type ObservableId,
|
|
6
7
|
type RootObservable,
|
|
7
8
|
} from '../types/index.mjs';
|
|
8
|
-
import { binarySearch,
|
|
9
|
+
import { binarySearch, issueUpdateToken } from '../utils/index.mjs';
|
|
9
10
|
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
10
11
|
|
|
11
12
|
export class RootObservableClass<A>
|
|
12
13
|
extends ObservableBaseClass<A, 'root', 0>
|
|
13
14
|
implements RootObservable<A>
|
|
14
15
|
{
|
|
15
|
-
#
|
|
16
|
+
#mut_propagationOrder: readonly ChildObservable<unknown>[];
|
|
16
17
|
protected readonly _descendantsIdSet: MutableSet<ObservableId>;
|
|
17
18
|
|
|
18
19
|
constructor({
|
|
19
20
|
initialValue,
|
|
20
21
|
}: Readonly<{
|
|
21
|
-
initialValue:
|
|
22
|
+
initialValue: Optional<A>;
|
|
22
23
|
}>) {
|
|
23
24
|
super({
|
|
24
25
|
kind: 'root',
|
|
@@ -26,7 +27,7 @@ export class RootObservableClass<A>
|
|
|
26
27
|
initialValue,
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
this.#
|
|
30
|
+
this.#mut_propagationOrder = [];
|
|
30
31
|
|
|
31
32
|
this._descendantsIdSet = new Set<ObservableId>();
|
|
32
33
|
}
|
|
@@ -37,20 +38,24 @@ export class RootObservableClass<A>
|
|
|
37
38
|
this._descendantsIdSet.add(child.id);
|
|
38
39
|
|
|
39
40
|
const insertPos = binarySearch(
|
|
40
|
-
this.#
|
|
41
|
+
this.#mut_propagationOrder.map((a) => a.depth),
|
|
41
42
|
child.depth,
|
|
42
43
|
);
|
|
43
44
|
|
|
44
|
-
this.#
|
|
45
|
+
this.#mut_propagationOrder = Arr.toInserted(
|
|
46
|
+
this.#mut_propagationOrder,
|
|
47
|
+
insertPos,
|
|
48
|
+
child,
|
|
49
|
+
);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
startUpdate(nextValue: A): void {
|
|
48
|
-
const
|
|
53
|
+
const updateToken = issueUpdateToken();
|
|
49
54
|
|
|
50
|
-
this.setNext(nextValue,
|
|
55
|
+
this.setNext(nextValue, updateToken);
|
|
51
56
|
|
|
52
|
-
for (const p of this.#
|
|
53
|
-
p.tryUpdate(
|
|
57
|
+
for (const p of this.#mut_propagationOrder) {
|
|
58
|
+
p.tryUpdate(updateToken);
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
}
|