synstate 3.3.0 → 4.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 +14 -9
- package/dist/core/authoring.d.mts +7 -0
- package/dist/core/authoring.d.mts.map +1 -0
- package/dist/core/authoring.mjs +7 -0
- package/dist/core/authoring.mjs.map +1 -0
- package/dist/core/class/child-observable-class.d.mts +93 -0
- package/dist/core/class/child-observable-class.d.mts.map +1 -0
- package/dist/core/class/child-observable-class.mjs +248 -0
- package/dist/core/class/child-observable-class.mjs.map +1 -0
- package/dist/core/class/create-child-observable.d.mts +159 -0
- package/dist/core/class/create-child-observable.d.mts.map +1 -0
- package/dist/core/class/create-child-observable.mjs +183 -0
- package/dist/core/class/create-child-observable.mjs.map +1 -0
- package/dist/core/class/create-root-observable.d.mts +37 -0
- package/dist/core/class/create-root-observable.d.mts.map +1 -0
- package/dist/core/class/create-root-observable.mjs +52 -0
- package/dist/core/class/create-root-observable.mjs.map +1 -0
- package/dist/core/class/index.d.mts +9 -0
- package/dist/core/class/index.d.mts.map +1 -0
- package/dist/core/class/index.mjs +9 -0
- package/dist/core/class/index.mjs.map +1 -0
- package/dist/core/class/manager-observable-class.d.mts +26 -0
- package/dist/core/class/manager-observable-class.d.mts.map +1 -0
- package/dist/core/class/manager-observable-class.mjs +116 -0
- package/dist/core/class/manager-observable-class.mjs.map +1 -0
- package/dist/core/class/observable-base-class.d.mts +97 -0
- package/dist/core/class/observable-base-class.d.mts.map +1 -0
- package/dist/core/class/observable-base-class.mjs +219 -0
- package/dist/core/class/observable-base-class.mjs.map +1 -0
- package/dist/core/class/observable-node.d.mts +40 -0
- package/dist/core/class/observable-node.d.mts.map +1 -0
- package/dist/core/class/observable-node.mjs +10 -0
- package/dist/core/class/observable-node.mjs.map +1 -0
- package/dist/core/class/propagation-state.d.mts +24 -0
- package/dist/core/class/propagation-state.d.mts.map +1 -0
- package/dist/core/class/propagation-state.mjs +66 -0
- package/dist/core/class/propagation-state.mjs.map +1 -0
- package/dist/core/class/root-observable-class.d.mts +23 -0
- package/dist/core/class/root-observable-class.d.mts.map +1 -0
- package/dist/core/class/root-observable-class.mjs +58 -0
- package/dist/core/class/root-observable-class.mjs.map +1 -0
- package/dist/core/combine/combine.d.mts.map +1 -1
- package/dist/core/combine/combine.mjs +25 -14
- package/dist/core/combine/combine.mjs.map +1 -1
- package/dist/core/combine/merge.d.mts +0 -3
- package/dist/core/combine/merge.d.mts.map +1 -1
- package/dist/core/combine/merge.mjs +24 -15
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +4 -2
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +54 -18
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.d.mts +9 -3
- package/dist/core/create/counter.d.mts.map +1 -1
- package/dist/core/create/counter.mjs +69 -35
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
- package/dist/core/create/from-abortable-promise.mjs +26 -18
- package/dist/core/create/from-abortable-promise.mjs.map +1 -1
- package/dist/core/create/from-promise.d.mts.map +1 -1
- package/dist/core/create/from-promise.mjs +30 -21
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/from-subscribable.d.mts.map +1 -1
- package/dist/core/create/from-subscribable.mjs +48 -12
- package/dist/core/create/from-subscribable.mjs.map +1 -1
- package/dist/core/create/just.d.mts.map +1 -1
- package/dist/core/create/just.mjs +4 -2
- package/dist/core/create/just.mjs.map +1 -1
- package/dist/core/create/source.d.mts +7 -0
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +27 -13
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/create/timer.d.mts.map +1 -1
- package/dist/core/create/timer.mjs +40 -25
- package/dist/core/create/timer.mjs.map +1 -1
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.d.mts.map +1 -1
- package/dist/core/index.mjs +2 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/operators/audit.d.mts +3 -0
- package/dist/core/operators/audit.d.mts.map +1 -1
- package/dist/core/operators/audit.mjs +56 -28
- package/dist/core/operators/audit.mjs.map +1 -1
- package/dist/core/operators/debounce.d.mts +3 -0
- package/dist/core/operators/debounce.d.mts.map +1 -1
- package/dist/core/operators/debounce.mjs +57 -26
- package/dist/core/operators/debounce.mjs.map +1 -1
- package/dist/core/operators/filter.d.mts.map +1 -1
- package/dist/core/operators/filter.mjs +35 -21
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/index.d.mts +1 -0
- package/dist/core/operators/index.d.mts.map +1 -1
- package/dist/core/operators/index.mjs +1 -0
- package/dist/core/operators/index.mjs.map +1 -1
- package/dist/core/operators/map.d.mts.map +1 -1
- package/dist/core/operators/map.mjs +31 -17
- package/dist/core/operators/map.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +5 -3
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +49 -26
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/pairwise.d.mts.map +1 -1
- package/dist/core/operators/pairwise.mjs +31 -20
- package/dist/core/operators/pairwise.mjs.map +1 -1
- package/dist/core/operators/scan.d.mts.map +1 -1
- package/dist/core/operators/scan.mjs +27 -14
- package/dist/core/operators/scan.mjs.map +1 -1
- package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
- package/dist/core/operators/skip-if-no-change.mjs +33 -19
- package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
- package/dist/core/operators/skip-until.d.mts.map +1 -1
- package/dist/core/operators/skip-until.mjs +43 -20
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/skip-while.d.mts.map +1 -1
- package/dist/core/operators/skip-while.mjs +35 -20
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +5 -3
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +53 -25
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-until.d.mts.map +1 -1
- package/dist/core/operators/take-until.mjs +38 -19
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/take-while.d.mts.map +1 -1
- package/dist/core/operators/take-while.mjs +36 -22
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
- package/dist/core/operators/take.d.mts.map +1 -0
- package/dist/core/operators/take.mjs +102 -0
- package/dist/core/operators/take.mjs.map +1 -0
- package/dist/core/operators/throttle.d.mts.map +1 -1
- package/dist/core/operators/throttle.mjs +41 -26
- package/dist/core/operators/throttle.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs +44 -31
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
- package/dist/core/operators/with-current-value-from.mjs +36 -21
- package/dist/core/operators/with-current-value-from.mjs.map +1 -1
- package/dist/core/operators/with-initial-value.d.mts.map +1 -1
- package/dist/core/operators/with-initial-value.mjs +24 -12
- package/dist/core/operators/with-initial-value.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/scope/index.d.mts +2 -0
- package/dist/core/scope/index.d.mts.map +1 -0
- package/dist/core/scope/index.mjs +2 -0
- package/dist/core/scope/index.mjs.map +1 -0
- package/dist/core/scope/scope.d.mts +51 -0
- package/dist/core/scope/scope.d.mts.map +1 -0
- package/dist/core/scope/scope.mjs +194 -0
- package/dist/core/scope/scope.mjs.map +1 -0
- package/dist/core/types/id.d.mts +0 -2
- 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/index.mjs +1 -0
- package/dist/core/types/index.mjs.map +1 -1
- package/dist/core/types/observable-family.d.mts +6 -1
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable-family.mjs.map +1 -1
- package/dist/core/types/observable.d.mts +40 -28
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/observable.mjs +24 -0
- package/dist/core/types/observable.mjs.map +1 -1
- package/dist/core/types/scope.d.mts +27 -0
- package/dist/core/types/scope.d.mts.map +1 -0
- package/dist/core/types/scope.mjs +2 -0
- package/dist/core/types/scope.mjs.map +1 -0
- package/dist/core/utils/current-scope.d.mts +30 -0
- package/dist/core/utils/current-scope.d.mts.map +1 -0
- package/dist/core/utils/current-scope.mjs +26 -0
- package/dist/core/utils/current-scope.mjs.map +1 -0
- package/dist/core/utils/id-maker.d.mts +14 -3
- package/dist/core/utils/id-maker.d.mts.map +1 -1
- package/dist/core/utils/id-maker.mjs +18 -14
- package/dist/core/utils/id-maker.mjs.map +1 -1
- package/dist/core/utils/index.d.mts +2 -1
- package/dist/core/utils/index.d.mts.map +1 -1
- package/dist/core/utils/index.mjs +2 -1
- package/dist/core/utils/index.mjs.map +1 -1
- package/dist/core/utils/max-depth.d.mts +1 -0
- package/dist/core/utils/max-depth.d.mts.map +1 -1
- package/dist/core/utils/max-depth.mjs +9 -7
- package/dist/core/utils/max-depth.mjs.map +1 -1
- package/dist/core/utils/observable-brand.d.mts +13 -0
- package/dist/core/utils/observable-brand.d.mts.map +1 -0
- package/dist/core/utils/observable-brand.mjs +13 -0
- package/dist/core/utils/observable-brand.mjs.map +1 -0
- package/dist/utils/create-boolean-state.d.mts +2 -2
- package/dist/utils/create-event-emitter.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +17 -8
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +14 -6
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +9 -2
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +7 -0
- package/dist/utils/create-state.mjs.map +1 -1
- package/package.json +16 -16
- package/src/core/authoring.mts +19 -0
- package/src/core/class/child-observable-class.mts +374 -0
- package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
- package/src/core/{base → class}/circular-dependency.test.mts +38 -2
- package/src/core/class/complete-propagation.test.mts +264 -0
- package/src/core/class/create-child-observable.mts +370 -0
- package/src/core/class/create-root-observable.mts +107 -0
- package/src/core/class/index.mts +8 -0
- package/src/core/class/manager-observable-class.mts +166 -0
- package/src/core/class/observable-base-class.mts +315 -0
- package/src/core/{base → class}/observable-base.test.mts +1 -1
- package/src/core/class/observable-node.mts +54 -0
- package/src/core/class/propagation-order.test.mts +92 -0
- package/src/core/class/propagation-state.mts +80 -0
- package/src/core/class/remove-completed.test.mts +369 -0
- package/src/core/class/root-observable-class.mts +85 -0
- package/src/core/class/write-during-propagation.test.mts +234 -0
- package/src/core/combine/combine.mts +67 -55
- package/src/core/combine/merge.mts +33 -26
- package/src/core/combine/zip.mts +103 -58
- package/src/core/create/counter.mts +105 -54
- package/src/core/create/counter.test.mts +82 -0
- package/src/core/create/from-abortable-promise.mts +54 -42
- package/src/core/create/from-promise.mts +38 -29
- package/src/core/create/from-subscribable.mts +68 -23
- package/src/core/create/from-subscribable.test.mts +134 -0
- package/src/core/create/just.mts +4 -5
- package/src/core/create/source.mts +37 -13
- package/src/core/create/source.test.mts +41 -0
- package/src/core/create/timer.mts +51 -32
- package/src/core/index.mts +2 -0
- package/src/core/operators/audit.mts +79 -45
- package/src/core/operators/debounce-audit-complete.test.mts +121 -0
- package/src/core/operators/debounce.mts +81 -40
- package/src/core/operators/filter.mts +48 -31
- package/src/core/operators/index.mts +1 -0
- package/src/core/operators/inner-ownership.test.mts +280 -0
- package/src/core/operators/map.mts +49 -32
- package/src/core/operators/merge-map.mts +81 -46
- package/src/core/operators/pairwise.mts +40 -30
- package/src/core/operators/scan.mts +41 -24
- package/src/core/operators/skip-if-no-change.mts +45 -31
- package/src/core/operators/skip-until.mts +59 -33
- package/src/core/operators/skip-while.mts +49 -31
- package/src/core/operators/switch-map.mts +74 -38
- package/src/core/operators/take-until-skip-until-teardown.test.mts +133 -0
- package/src/core/operators/take-until.mts +49 -31
- package/src/core/operators/take-while.mts +50 -33
- package/src/core/operators/take.mts +138 -0
- package/src/core/operators/throttle.mts +55 -38
- package/src/core/operators/with-buffered-from.mts +45 -35
- package/src/core/operators/with-buffered-from.test.mts +57 -0
- package/src/core/operators/with-current-value-from.mts +39 -25
- package/src/core/operators/with-initial-value.mts +30 -21
- package/src/core/predefined/operators/index.mts +0 -1
- package/src/core/scope/index.mts +1 -0
- package/src/core/scope/scope.mts +260 -0
- package/src/core/scope/scope.test.mts +462 -0
- package/src/core/types/id.mts +0 -4
- package/src/core/types/index.mts +1 -0
- package/src/core/types/observable-family.mts +7 -1
- package/src/core/types/observable.mts +77 -42
- package/src/core/types/scope.mts +27 -0
- package/src/core/utils/current-scope.mts +61 -0
- package/src/core/utils/id-maker.mts +20 -30
- package/src/core/utils/index.mts +2 -1
- package/src/core/utils/max-depth.mts +12 -9
- package/src/core/utils/observable-brand.mts +12 -0
- package/src/utils/create-boolean-state.mts +2 -2
- package/src/utils/create-boolean-state.test.mts +18 -4
- package/src/utils/create-event-emitter.mts +2 -2
- package/src/utils/create-reducer.mts +22 -16
- package/src/utils/create-reducer.test.mts +43 -14
- package/src/utils/create-state.mts +9 -2
- package/src/utils/create-state.test.mts +32 -14
- package/dist/core/base/create-child-observable.d.mts +0 -65
- package/dist/core/base/create-child-observable.d.mts.map +0 -1
- package/dist/core/base/create-child-observable.mjs +0 -267
- package/dist/core/base/create-child-observable.mjs.map +0 -1
- package/dist/core/base/create-observable-base.d.mts +0 -147
- package/dist/core/base/create-observable-base.d.mts.map +0 -1
- package/dist/core/base/create-observable-base.mjs +0 -255
- package/dist/core/base/create-observable-base.mjs.map +0 -1
- package/dist/core/base/create-root-observable.d.mts +0 -37
- package/dist/core/base/create-root-observable.d.mts.map +0 -1
- package/dist/core/base/create-root-observable.mjs +0 -57
- package/dist/core/base/create-root-observable.mjs.map +0 -1
- package/dist/core/base/index.d.mts +0 -3
- package/dist/core/base/index.d.mts.map +0 -1
- package/dist/core/base/index.mjs +0 -3
- package/dist/core/base/index.mjs.map +0 -1
- package/dist/core/predefined/operators/take.d.mts.map +0 -1
- package/dist/core/predefined/operators/take.mjs +0 -44
- package/dist/core/predefined/operators/take.mjs.map +0 -1
- package/dist/core/utils/utils.d.mts +0 -6
- package/dist/core/utils/utils.d.mts.map +0 -1
- package/dist/core/utils/utils.mjs +0 -37
- package/dist/core/utils/utils.mjs.map +0 -1
- package/src/core/base/create-child-observable.mts +0 -457
- package/src/core/base/create-observable-base.mts +0 -548
- package/src/core/base/create-root-observable.mts +0 -113
- package/src/core/base/index.mts +0 -2
- package/src/core/predefined/operators/take.mts +0 -48
- package/src/core/utils/utils.mts +0 -56
- package/src/core/utils/utils.test.mts +0 -88
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBxE
|
|
1
|
+
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,gBACb,CAAC,YACL,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KACjC,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,KAAK,EAAE,QAAQ,CAAC;IACd,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CA0BH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-state.mjs","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;AAaxE,MAAM,OAAO,GAAG,CAAK,KAAQ,EAAE,MAAiB,EAAK,EAAE;IACrD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,SAAS,CAAC;QAE1B,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;AACH,CAAC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"create-state.mjs","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAqB,MAAM,sBAAsB,CAAC;AAaxE,MAAM,OAAO,GAAG,CAAK,KAAQ,EAAE,MAAiB,EAAK,EAAE;IACrD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,SAAS,CAAC;QAE1B,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,YAAe,EACf,OAAkC,EAUlC,EAAE;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,GAAG,aAAa,CACtD,OAAO,EACP,YAAY,EACZ,OAAO,CACR,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,QAAwB,EAAK,EAAE,CAClD,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEzC,MAAM,QAAQ,GAAG,CAAC,SAAY,EAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAE3E,MAAM,UAAU,GAAG,GAAM,EAAE,CACzB,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;IAErD,OAAO;QACL,KAAK;QACL,QAAQ;QACR;YACE,WAAW;YACX,UAAU;YACV,WAAW;YACX,YAAY;SACb;KACO,CAAC;AACb,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synstate",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Type-safe State Management Library for TypeScript/JavaScript",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
"LICENSE"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"ts-data-forge": "^14.
|
|
32
|
+
"ts-data-forge": "^14.8.0",
|
|
33
33
|
"ts-std-forge": "^0.5.1",
|
|
34
34
|
"ts-type-forge": "^9.2.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@reduxjs/toolkit": "2.13.0",
|
|
38
|
-
"@sumi-lang/cli": "0.0.
|
|
38
|
+
"@sumi-lang/cli": "0.0.6",
|
|
39
39
|
"@types/react": "19.3.0",
|
|
40
40
|
"@vitest/browser-playwright": "5.0.3",
|
|
41
41
|
"argparse": "3.0.2",
|
|
42
|
-
"eslint-config-typed": "5.
|
|
43
|
-
"eslint-plugin-ts-data-forge": "0.
|
|
44
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
45
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
42
|
+
"eslint-config-typed": "5.19.0",
|
|
43
|
+
"eslint-plugin-ts-data-forge": "0.9.0",
|
|
44
|
+
"eslint-plugin-ts-fortress": "1.3.4",
|
|
45
|
+
"eslint-plugin-ts-type-forge": "0.8.9",
|
|
46
46
|
"jotai": "3.0.1",
|
|
47
47
|
"mobx": "7.0.6",
|
|
48
48
|
"playwright": "1.64.0",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"prettier-plugin-packagejson": "3.0.2",
|
|
52
52
|
"react": "19.3.0",
|
|
53
53
|
"rxjs": "7.8.2",
|
|
54
|
-
"synstate-react-hooks": "
|
|
55
|
-
"synstate-react-hooks-compat": "
|
|
54
|
+
"synstate-react-hooks": "4.0.0",
|
|
55
|
+
"synstate-react-hooks-compat": "4.0.0",
|
|
56
56
|
"ts-repo-utils": "10.7.1",
|
|
57
57
|
"valtio": "2.3.2",
|
|
58
58
|
"zustand": "5.0.15"
|
|
@@ -68,22 +68,22 @@
|
|
|
68
68
|
"scripts": {
|
|
69
69
|
"build": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts",
|
|
70
70
|
"check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
|
|
71
|
-
"check:lint": "oxlint . && eslint .",
|
|
71
|
+
"check:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . && eslint .",
|
|
72
72
|
"check:sumi": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../languages/sumi/cli/src/cmd/sumi.mts check ./tsconfig.sumi.json",
|
|
73
|
-
"check:test": "pnpm run z:vitest:node run",
|
|
74
|
-
"check:test:browser": "pnpm run z:vitest --project='Browser' run",
|
|
75
|
-
"check:test:cov": "pnpm run z:vitest:node run --coverage",
|
|
76
|
-
"check:types": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
|
|
73
|
+
"check:test": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest:node run",
|
|
74
|
+
"check:test:browser": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest --project='Browser' run",
|
|
75
|
+
"check:test:cov": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest:node run --coverage",
|
|
76
|
+
"check:types": "node ../../tools/scripts/cmd/assert-deps-built.mts && node ../../node_modules/typescript-native/bin/tsc --noEmit",
|
|
77
77
|
"check-all": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-all.mts",
|
|
78
78
|
"doc": "run-s gen:readme gen:jsdoc gen:sample-diffs fix:fmt",
|
|
79
79
|
"fix:fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
|
|
80
80
|
"fix:fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
|
|
81
81
|
"fix:fmt:full": "pnpm run z:prettier .",
|
|
82
|
-
"fix:lint": "oxlint . --fix && eslint . --fix",
|
|
82
|
+
"fix:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . --fix && eslint . --fix",
|
|
83
83
|
"fmt": "pnpm run fix:fmt",
|
|
84
84
|
"gen": "run-s gen:index gen:readme gen:jsdoc gen:sample-diffs",
|
|
85
85
|
"gen:index": "run-s gen:index:src fix:fmt",
|
|
86
|
-
"gen:index:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/gen-index-ts.mts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude \"core/
|
|
86
|
+
"gen:index:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/gen-index-ts.mts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude \"core/class/**\" --exclude \"core/utils/**\"",
|
|
87
87
|
"gen:jsdoc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples-in-jsdoc.mts",
|
|
88
88
|
"gen:readme": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
|
|
89
89
|
"gen:sample-diffs": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/generate-sample-diffs.mts",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What custom creators and operators are written with: the factories every
|
|
3
|
+
* built-in observable is made by (#2117). The rest of `core/base` stays
|
|
4
|
+
* internal.
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
createAsyncChildObservable,
|
|
8
|
+
createInitializedSyncChildObservable,
|
|
9
|
+
createRootObservable,
|
|
10
|
+
createSyncChildObservable,
|
|
11
|
+
type AsyncChildObservableHooks,
|
|
12
|
+
type AsyncChildObservableTools,
|
|
13
|
+
type ChildObservableConfig,
|
|
14
|
+
type InitializedChildObservableConfig,
|
|
15
|
+
type RootObservableConfig,
|
|
16
|
+
type RootObservableTools,
|
|
17
|
+
type SyncChildObservableHooks,
|
|
18
|
+
type SyncChildObservableTools,
|
|
19
|
+
} from './class/index.mjs';
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// @sumi-expect-error-file classes/no-class
|
|
2
|
+
// @sumi-expect-error-file banned-syntax/no-this
|
|
3
|
+
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
4
|
+
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
5
|
+
import { Arr, hasKey, isRecord, type Optional, type Some } from 'ts-data-forge';
|
|
6
|
+
import { panic } from 'ts-std-forge';
|
|
7
|
+
import type { MutableSet } from 'ts-type-forge';
|
|
8
|
+
import type {
|
|
9
|
+
AsyncChildObservable,
|
|
10
|
+
DropInitialValueOperator,
|
|
11
|
+
InitializedObservable,
|
|
12
|
+
InitializedSyncChildObservable,
|
|
13
|
+
KeepInitialValueOperator,
|
|
14
|
+
NonEmptyUnknownList,
|
|
15
|
+
Observable,
|
|
16
|
+
Operator,
|
|
17
|
+
SyncChildObservable,
|
|
18
|
+
WithInitialValueOperator,
|
|
19
|
+
Wrap,
|
|
20
|
+
} from '../types/index.mjs';
|
|
21
|
+
import { maxDepth } from '../utils/index.mjs';
|
|
22
|
+
import { ManagerObservableClass } from './manager-observable-class.mjs';
|
|
23
|
+
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
24
|
+
import {
|
|
25
|
+
toNode,
|
|
26
|
+
type ChildNode,
|
|
27
|
+
type ManagerNode,
|
|
28
|
+
} from './observable-node.mjs';
|
|
29
|
+
import { runAfterPropagation } from './propagation-state.mjs';
|
|
30
|
+
|
|
31
|
+
type ChildObservableConfig<A, P extends NonEmptyUnknownList> = Readonly<{
|
|
32
|
+
parents: Wrap<P>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Defaults to one more than the deepest parent. An update reaches
|
|
36
|
+
* observables in ascending depth, so an observable `tryUpdate` reads with
|
|
37
|
+
* `getSnapshot` without being its child (`withCurrentValueFrom`'s second
|
|
38
|
+
* source) has to be shallower than this one too, or this one reads its value
|
|
39
|
+
* from before the update: pass `1 + Math.max(...)` over both then.
|
|
40
|
+
*/
|
|
41
|
+
depth?: number;
|
|
42
|
+
|
|
43
|
+
initialValue: Optional<A>;
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An observable derived from `parents` that emits, if at all, in the same
|
|
48
|
+
* update as they do (`map`, `filter`, `combine`, …).
|
|
49
|
+
*
|
|
50
|
+
* A subclass overrides `tryUpdate`, and, when it holds something to
|
|
51
|
+
* release, `teardown`; its constructor ends with `this.attach()`, once
|
|
52
|
+
* everything `tryUpdate` and `teardown` read is in place.
|
|
53
|
+
*/
|
|
54
|
+
export class SyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
55
|
+
extends ObservableBaseClass<A, 'sync child', number>
|
|
56
|
+
implements SyncChildObservable<A, P>
|
|
57
|
+
{
|
|
58
|
+
#mut_isAttached: boolean;
|
|
59
|
+
readonly parents: Wrap<P>;
|
|
60
|
+
readonly managers: readonly ManagerNode[];
|
|
61
|
+
|
|
62
|
+
constructor({
|
|
63
|
+
parents,
|
|
64
|
+
depth = 1 + maxDepth(parents),
|
|
65
|
+
initialValue,
|
|
66
|
+
}: ChildObservableConfig<A, P>) {
|
|
67
|
+
super({
|
|
68
|
+
kind: 'sync child',
|
|
69
|
+
depth,
|
|
70
|
+
initialValue,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.parents = parents;
|
|
74
|
+
|
|
75
|
+
this.managers = managersOf(parents);
|
|
76
|
+
|
|
77
|
+
this.#mut_isAttached = false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Joins the current scope and registers this observable to its parents
|
|
82
|
+
* and the managers it is reached from. Completed parents never update
|
|
83
|
+
* again, so a child created under them is complete from the start
|
|
84
|
+
* (#2248), as is one its constructor completed; registering it would only
|
|
85
|
+
* leave a reference in each parent.
|
|
86
|
+
*/
|
|
87
|
+
protected attach(): void {
|
|
88
|
+
this.adoptIntoCurrentScope();
|
|
89
|
+
|
|
90
|
+
if (this.isCompleted || this.parents.every((p) => p.isCompleted)) {
|
|
91
|
+
this.complete();
|
|
92
|
+
} else {
|
|
93
|
+
registerChild(this, this.managers);
|
|
94
|
+
|
|
95
|
+
this.#mut_isAttached = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected override detach(): void {
|
|
100
|
+
if (!this.#mut_isAttached) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.#mut_isAttached = false;
|
|
105
|
+
|
|
106
|
+
unregisterChild(this, this.managers);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override tryComplete(): void {
|
|
110
|
+
if (shouldComplete(this)) {
|
|
111
|
+
this.complete();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Whether this observable can emit nothing more although some parent is
|
|
117
|
+
* still live, asked whenever a parent completes. `true` completes it then,
|
|
118
|
+
* as all parents completing does (`zip` once a completed source's queue is
|
|
119
|
+
* empty).
|
|
120
|
+
*/
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
122
|
+
canEmitNoMore(): boolean {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Completes this observable once the update now propagating has reached
|
|
128
|
+
* every descendant, so that they see the value just passed on first.
|
|
129
|
+
*/
|
|
130
|
+
protected completeAfterPropagation(): void {
|
|
131
|
+
runAfterPropagation(() => {
|
|
132
|
+
this.complete();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* An observable derived from `parents` that may emit later, in an update of
|
|
139
|
+
* its own, through `startUpdate` (`debounce`, `switchMap`). It may also emit
|
|
140
|
+
* synchronously with `setNext`, as a sync child does. A subclass may also
|
|
141
|
+
* override `flush`, to pass on at completion what it still holds.
|
|
142
|
+
*/
|
|
143
|
+
export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
144
|
+
extends ManagerObservableClass<A, 'async child', number>
|
|
145
|
+
implements AsyncChildObservable<A, P>
|
|
146
|
+
{
|
|
147
|
+
#mut_isAttached: boolean;
|
|
148
|
+
readonly parents: Wrap<P>;
|
|
149
|
+
|
|
150
|
+
constructor({
|
|
151
|
+
parents,
|
|
152
|
+
depth = 1 + maxDepth(parents),
|
|
153
|
+
initialValue,
|
|
154
|
+
}: ChildObservableConfig<A, P>) {
|
|
155
|
+
super({
|
|
156
|
+
kind: 'async child',
|
|
157
|
+
depth,
|
|
158
|
+
initialValue,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.parents = parents;
|
|
162
|
+
|
|
163
|
+
this.#mut_isAttached = false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** See `SyncChildObservableClass.attach`. */
|
|
167
|
+
protected attach(): void {
|
|
168
|
+
this.adoptIntoCurrentScope();
|
|
169
|
+
|
|
170
|
+
if (this.isCompleted || this.parents.every((p) => p.isCompleted)) {
|
|
171
|
+
this.complete();
|
|
172
|
+
} else {
|
|
173
|
+
registerChild(this, managersOf(this.parents));
|
|
174
|
+
|
|
175
|
+
this.#mut_isAttached = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected override detach(): void {
|
|
180
|
+
if (!this.#mut_isAttached) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.#mut_isAttached = false;
|
|
185
|
+
|
|
186
|
+
unregisterChild(this, managersOf(this.parents));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
override tryComplete(): void {
|
|
190
|
+
if (shouldComplete(this)) {
|
|
191
|
+
this.complete();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** See `SyncChildObservableClass.canEmitNoMore`. */
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
197
|
+
canEmitNoMore(): boolean {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** See `SyncChildObservableClass.completeAfterPropagation`. */
|
|
202
|
+
protected completeAfterPropagation(): void {
|
|
203
|
+
runAfterPropagation(() => {
|
|
204
|
+
this.complete();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A sync child that always holds a value: a subclass passes a `Some` as its
|
|
211
|
+
* `initialValue`. It differs from its base only in the types of
|
|
212
|
+
* `getSnapshot` and `pipe`.
|
|
213
|
+
*/
|
|
214
|
+
export class InitializedSyncChildObservableClass<
|
|
215
|
+
A,
|
|
216
|
+
const P extends NonEmptyUnknownList,
|
|
217
|
+
>
|
|
218
|
+
extends SyncChildObservableClass<A, P>
|
|
219
|
+
implements InitializedSyncChildObservable<A, P>
|
|
220
|
+
{
|
|
221
|
+
override getSnapshot(): Some<A> {
|
|
222
|
+
// `initialValue` is a `Some`, and `setNext` only ever stores a `Some`.
|
|
223
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
224
|
+
return super.getSnapshot() as Some<A>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
override pipe<B>(
|
|
228
|
+
operator: KeepInitialValueOperator<A, B> | WithInitialValueOperator<A, B>,
|
|
229
|
+
): InitializedObservable<B>;
|
|
230
|
+
|
|
231
|
+
override pipe<B>(operator: DropInitialValueOperator<A, B>): Observable<B>;
|
|
232
|
+
|
|
233
|
+
override pipe<B>(operator: Operator<A, B>): Observable<B> {
|
|
234
|
+
return operator(this);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* helpers */
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Called by a parent that has completed: whether no live parent is left, or
|
|
242
|
+
* no value can come from the live ones.
|
|
243
|
+
*/
|
|
244
|
+
const shouldComplete = (
|
|
245
|
+
child: ChildNode & Readonly<{ canEmitNoMore: () => boolean }>,
|
|
246
|
+
): boolean =>
|
|
247
|
+
!child.isCompleted &&
|
|
248
|
+
(child.parents.every((p) => p.isCompleted) || child.canEmitNoMore());
|
|
249
|
+
|
|
250
|
+
/** Registers a child to its parents and to `managers`. */
|
|
251
|
+
const registerChild = (
|
|
252
|
+
child: ChildNode,
|
|
253
|
+
managers: readonly ManagerNode[],
|
|
254
|
+
): void => {
|
|
255
|
+
if (checksCircularDependency) {
|
|
256
|
+
detectCircularDependency(child);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const p of child.parents) {
|
|
260
|
+
toNode(p).addChild(child);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const m of managers) {
|
|
264
|
+
m.addDescendant(child);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Undoes {@link registerChild} for a completed child: its parents forget it,
|
|
270
|
+
* and every manager it registered to stops propagating updates to it.
|
|
271
|
+
* Without this a completed node stayed reachable for as long as its root
|
|
272
|
+
* lived, and ran its `tryUpdate` on every update (#2134).
|
|
273
|
+
*/
|
|
274
|
+
const unregisterChild = (
|
|
275
|
+
child: ChildNode,
|
|
276
|
+
managers: readonly ManagerNode[],
|
|
277
|
+
): void => {
|
|
278
|
+
for (const p of child.parents) {
|
|
279
|
+
toNode(p).deleteChild(child);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (const m of managers) {
|
|
283
|
+
m.deleteDescendant(child);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The managers the descendants of `parents` register to, shared with the
|
|
289
|
+
* parent when there is one.
|
|
290
|
+
*/
|
|
291
|
+
const managersOf = (
|
|
292
|
+
parents: readonly Observable<unknown>[],
|
|
293
|
+
): readonly ManagerNode[] =>
|
|
294
|
+
Arr.isFixedLengthArray(1, parents)
|
|
295
|
+
? toNode(parents[0]).managers
|
|
296
|
+
: Array.from(new Set(parents.flatMap((p) => toNode(p).managers)));
|
|
297
|
+
|
|
298
|
+
const isDevelopmentProcess = (): boolean => {
|
|
299
|
+
const proc: unknown = Reflect.get(globalThis, 'process');
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
isRecord(proc) &&
|
|
303
|
+
hasKey(proc, 'env') &&
|
|
304
|
+
isRecord(proc.env) &&
|
|
305
|
+
(!hasKey(proc.env, 'NODE_ENV') || proc.env.NODE_ENV !== 'production')
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Whether {@link registerChild} checks for a circular dependency: everywhere
|
|
311
|
+
* but in a process whose `NODE_ENV` is `production`. A child's parents exist
|
|
312
|
+
* before it and cannot be changed afterwards, so only code that rewrites an
|
|
313
|
+
* observable's readonly `parents` can make a cycle, while the walk over every
|
|
314
|
+
* ancestor is paid on every creation. Where there is no `process` (a browser
|
|
315
|
+
* bundle) the check is off.
|
|
316
|
+
*/
|
|
317
|
+
export const checksCircularDependency: boolean = isDevelopmentProcess();
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Detects circular dependencies by walking the full ancestor chain of the
|
|
321
|
+
* child's parents and checking whether the child already appears among them.
|
|
322
|
+
*
|
|
323
|
+
* @throws {Error} if a circular dependency is detected
|
|
324
|
+
*/
|
|
325
|
+
const detectCircularDependency = (child: ChildNode): void => {
|
|
326
|
+
const mut_visited = new Set<Observable<unknown>>();
|
|
327
|
+
|
|
328
|
+
const mut_inPath = new Set<Observable<unknown>>([
|
|
329
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
330
|
+
child as Observable<unknown>,
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
for (const parent of child.parents) {
|
|
334
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
335
|
+
// A programming error (invariant violation), not a recoverable
|
|
336
|
+
// failure. `panic` marks the error it throws, so the `Result` /
|
|
337
|
+
// `AsyncResult` boundaries rethrow it rather than turning an invariant
|
|
338
|
+
// violation into an `Err` a caller might handle (Sumi D-48 / D-53).
|
|
339
|
+
panic(
|
|
340
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const hasCircularDependencyFrom = (
|
|
347
|
+
node: Observable<unknown>,
|
|
348
|
+
mut_visited: MutableSet<Observable<unknown>>,
|
|
349
|
+
mut_inPath: MutableSet<Observable<unknown>>,
|
|
350
|
+
): boolean => {
|
|
351
|
+
if (mut_inPath.has(node)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (mut_visited.has(node)) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
mut_visited.add(node);
|
|
360
|
+
|
|
361
|
+
mut_inPath.add(node);
|
|
362
|
+
|
|
363
|
+
if (node.kind !== 'root') {
|
|
364
|
+
for (const parent of node.parents) {
|
|
365
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
mut_inPath.delete(node);
|
|
372
|
+
|
|
373
|
+
return false;
|
|
374
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Comparison tests: how SynState, RxJS, and Jotai handle circular dependencies.
|
|
3
3
|
*
|
|
4
|
-
* - SynState: detects cycles at construction time and throws a clear error
|
|
4
|
+
* - SynState: detects cycles at construction time and throws a clear error,
|
|
5
|
+
* outside production (`checksCircularDependency`).
|
|
5
6
|
* - RxJS: pipe() always returns a new Observable, so static cycles cannot be
|
|
6
7
|
* expressed through the public API. No detection is needed or provided.
|
|
7
8
|
* - Jotai: circular atom definitions are silently accepted. A cycle manifests
|
|
@@ -13,55 +14,62 @@ import { Optional } from 'ts-data-forge';
|
|
|
13
14
|
import { combine } from '../combine/index.mjs';
|
|
14
15
|
import { source } from '../create/index.mjs';
|
|
15
16
|
import { map } from '../operators/index.mjs';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
17
|
+
import { checksCircularDependency } from './child-observable-class.mjs';
|
|
18
|
+
import {
|
|
19
|
+
createSyncChildObservable,
|
|
20
|
+
createTryUpdateNotImplemented,
|
|
21
|
+
} from './create-child-observable.mjs';
|
|
18
22
|
import { createRootObservable } from './create-root-observable.mjs';
|
|
19
23
|
|
|
20
24
|
describe('circular dependency comparison', () => {
|
|
21
25
|
describe('SynState', () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
parents: [root],
|
|
31
|
-
initialValue: Optional.some(0),
|
|
32
|
-
},
|
|
33
|
-
createTryUpdateNotImplemented,
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const childB = createSyncChildObservable(
|
|
37
|
-
{
|
|
38
|
-
parents: [mut_childA],
|
|
39
|
-
initialValue: Optional.some(0),
|
|
40
|
-
},
|
|
41
|
-
createTryUpdateNotImplemented,
|
|
42
|
-
);
|
|
26
|
+
// Only where the check runs: see `checksCircularDependency`.
|
|
27
|
+
test.runIf(checksCircularDependency)(
|
|
28
|
+
'detects cycle at construction time with a clear error message',
|
|
29
|
+
() => {
|
|
30
|
+
const root = createRootObservable(
|
|
31
|
+
{ initialValue: Optional.some(0) },
|
|
32
|
+
() => ({}),
|
|
33
|
+
);
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
35
|
+
const mut_childA = createSyncChildObservable(
|
|
36
|
+
{
|
|
37
|
+
parents: [root],
|
|
38
|
+
initialValue: Optional.some(0),
|
|
39
|
+
},
|
|
40
|
+
createTryUpdateNotImplemented,
|
|
41
|
+
);
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
createSyncChildObservable(
|
|
43
|
+
const childB = createSyncChildObservable(
|
|
55
44
|
{
|
|
56
45
|
parents: [mut_childA],
|
|
57
46
|
initialValue: Optional.some(0),
|
|
58
47
|
},
|
|
59
48
|
createTryUpdateNotImplemented,
|
|
60
49
|
);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
|
|
51
|
+
// Simulate a cycle: mut_childA → childB → mut_childA
|
|
52
|
+
// In normal usage this is impossible — the check runs in every
|
|
53
|
+
// child constructor before the reference becomes available.
|
|
54
|
+
Object.defineProperty(mut_childA, 'parents', {
|
|
55
|
+
value: [childB],
|
|
56
|
+
writable: false,
|
|
57
|
+
configurable: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(() => {
|
|
61
|
+
createSyncChildObservable(
|
|
62
|
+
{
|
|
63
|
+
parents: [mut_childA],
|
|
64
|
+
initialValue: Optional.some(0),
|
|
65
|
+
},
|
|
66
|
+
createTryUpdateNotImplemented,
|
|
67
|
+
);
|
|
68
|
+
}).toThrow(
|
|
69
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
65
73
|
|
|
66
74
|
test('accepts valid DAG (diamond dependency)', () => {
|
|
67
75
|
const a$ = source(0);
|
|
@@ -2,15 +2,18 @@ import { Optional } from 'ts-data-forge';
|
|
|
2
2
|
import { combine, merge } from '../combine/index.mjs';
|
|
3
3
|
import { source } from '../create/index.mjs';
|
|
4
4
|
import { map } from '../operators/index.mjs';
|
|
5
|
+
import { checksCircularDependency } from './child-observable-class.mjs';
|
|
5
6
|
import {
|
|
6
7
|
createAsyncChildObservable,
|
|
7
8
|
createSyncChildObservable,
|
|
9
|
+
createTryUpdateNotImplemented,
|
|
8
10
|
} from './create-child-observable.mjs';
|
|
9
|
-
import { createTryUpdateNotImplemented } from './create-observable-base.mjs';
|
|
10
11
|
import { createRootObservable } from './create-root-observable.mjs';
|
|
11
12
|
|
|
12
13
|
describe('circular dependency detection', () => {
|
|
13
|
-
|
|
14
|
+
// The check runs only in a process whose `NODE_ENV` is not `production`;
|
|
15
|
+
// the browser run has no `process`, and there nothing is checked.
|
|
16
|
+
describe.runIf(checksCircularDependency)('cycle in ancestor graph', () => {
|
|
14
17
|
test('should throw when parent chain contains a cycle (A -> B -> A)', () => {
|
|
15
18
|
const root = createRootObservable(
|
|
16
19
|
{ initialValue: Optional.some(0) },
|
|
@@ -180,6 +183,39 @@ describe('circular dependency detection', () => {
|
|
|
180
183
|
});
|
|
181
184
|
});
|
|
182
185
|
|
|
186
|
+
describe.skipIf(checksCircularDependency)('with the check off', () => {
|
|
187
|
+
test('does not walk the ancestors of a new child', () => {
|
|
188
|
+
const root = createRootObservable(
|
|
189
|
+
{ initialValue: Optional.some(0) },
|
|
190
|
+
() => ({}),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const mut_childA = createSyncChildObservable(
|
|
194
|
+
{
|
|
195
|
+
parents: [root],
|
|
196
|
+
initialValue: Optional.some(0),
|
|
197
|
+
},
|
|
198
|
+
createTryUpdateNotImplemented,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
Object.defineProperty(mut_childA, 'parents', {
|
|
202
|
+
value: [mut_childA],
|
|
203
|
+
writable: false,
|
|
204
|
+
configurable: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(() => {
|
|
208
|
+
createSyncChildObservable(
|
|
209
|
+
{
|
|
210
|
+
parents: [mut_childA],
|
|
211
|
+
initialValue: Optional.some(0),
|
|
212
|
+
},
|
|
213
|
+
createTryUpdateNotImplemented,
|
|
214
|
+
);
|
|
215
|
+
}).not.toThrow();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
183
219
|
describe('valid DAG patterns should not throw', () => {
|
|
184
220
|
test('diamond dependency is not a cycle', () => {
|
|
185
221
|
const root = createRootObservable(
|