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
|
@@ -7,7 +7,7 @@ import { type InitializedObservable } from '../core/index.mjs';
|
|
|
7
7
|
* @template A - The type of actions
|
|
8
8
|
* @param reducer - A pure function that takes current state and action, returns new state
|
|
9
9
|
* @param initialState - The initial value of the state
|
|
10
|
-
* @returns
|
|
10
|
+
* @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```ts
|
|
@@ -23,24 +23,27 @@ import { type InitializedObservable } from '../core/index.mjs';
|
|
|
23
23
|
* 0,
|
|
24
24
|
* );
|
|
25
25
|
*
|
|
26
|
-
* const
|
|
26
|
+
* const stateHistory: number[] = [];
|
|
27
27
|
*
|
|
28
28
|
* state.subscribe((value: number) => {
|
|
29
|
-
*
|
|
29
|
+
* stateHistory.push(value);
|
|
30
30
|
* });
|
|
31
31
|
*
|
|
32
|
-
* assert.deepStrictEqual(
|
|
32
|
+
* assert.deepStrictEqual(stateHistory, [0]);
|
|
33
33
|
*
|
|
34
34
|
* dispatch({ type: 'increment' }); // logs: 1
|
|
35
35
|
*
|
|
36
|
-
* assert.deepStrictEqual(
|
|
36
|
+
* assert.deepStrictEqual(stateHistory, [0, 1]);
|
|
37
37
|
*
|
|
38
38
|
* dispatch({ type: 'increment' });
|
|
39
39
|
*
|
|
40
40
|
* dispatch({ type: 'decrement' });
|
|
41
41
|
*
|
|
42
|
-
* assert.deepStrictEqual(
|
|
42
|
+
* assert.deepStrictEqual(stateHistory, [0, 1, 2, 1]);
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
|
-
export declare const createReducer: <S, A>(reducer: Reducer<S, A>, initialState: S) => readonly [state: InitializedObservable<S>, dispatch: (action: A) => S,
|
|
45
|
+
export declare const createReducer: <S, A>(reducer: Reducer<S, A>, initialState: S) => readonly [state: InitializedObservable<S>, dispatch: (action: A) => S, Readonly<{
|
|
46
|
+
getSnapshot: () => S;
|
|
47
|
+
initialState: S;
|
|
48
|
+
}>];
|
|
46
49
|
//# sourceMappingURL=create-reducer.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-reducer.d.mts","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,EAChC,SAAS,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,cAAc,CAAC,KACd,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAC1B,WAAW,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"create-reducer.d.mts","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,EAChC,SAAS,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,cAAc,CAAC,KACd,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAC1B,QAAQ,CAAC;IACP,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CAeH,CAAC"}
|
|
@@ -11,7 +11,7 @@ import { source } from '../core/create/source.mjs';
|
|
|
11
11
|
* @template A - The type of actions
|
|
12
12
|
* @param reducer - A pure function that takes current state and action, returns new state
|
|
13
13
|
* @param initialState - The initial value of the state
|
|
14
|
-
* @returns
|
|
14
|
+
* @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* ```ts
|
|
@@ -27,23 +27,23 @@ import { source } from '../core/create/source.mjs';
|
|
|
27
27
|
* 0,
|
|
28
28
|
* );
|
|
29
29
|
*
|
|
30
|
-
* const
|
|
30
|
+
* const stateHistory: number[] = [];
|
|
31
31
|
*
|
|
32
32
|
* state.subscribe((value: number) => {
|
|
33
|
-
*
|
|
33
|
+
* stateHistory.push(value);
|
|
34
34
|
* });
|
|
35
35
|
*
|
|
36
|
-
* assert.deepStrictEqual(
|
|
36
|
+
* assert.deepStrictEqual(stateHistory, [0]);
|
|
37
37
|
*
|
|
38
38
|
* dispatch({ type: 'increment' }); // logs: 1
|
|
39
39
|
*
|
|
40
|
-
* assert.deepStrictEqual(
|
|
40
|
+
* assert.deepStrictEqual(stateHistory, [0, 1]);
|
|
41
41
|
*
|
|
42
42
|
* dispatch({ type: 'increment' });
|
|
43
43
|
*
|
|
44
44
|
* dispatch({ type: 'decrement' });
|
|
45
45
|
*
|
|
46
|
-
* assert.deepStrictEqual(
|
|
46
|
+
* assert.deepStrictEqual(stateHistory, [0, 1, 2, 1]);
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
49
|
const createReducer = (reducer, initialState) => {
|
|
@@ -54,7 +54,7 @@ const createReducer = (reducer, initialState) => {
|
|
|
54
54
|
return nextState;
|
|
55
55
|
};
|
|
56
56
|
const getSnapshot = () => state.getSnapshot().value;
|
|
57
|
-
return [state, dispatch, getSnapshot];
|
|
57
|
+
return [state, dispatch, { getSnapshot, initialState }];
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export { createReducer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-reducer.mjs","sources":["../../src/utils/create-reducer.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CG;MACU,aAAa,GAAG,CAC3B,OAAsB,EACtB,YAAe,
|
|
1
|
+
{"version":3,"file":"create-reducer.mjs","sources":["../../src/utils/create-reducer.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CG;MACU,aAAa,GAAG,CAC3B,OAAsB,EACtB,YAAe,KAQb;AACF,IAAA,MAAM,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC;AAErC,IAAA,MAAM,QAAQ,GAAG,CAAC,MAAS,KAAO;AAChC,QAAA,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;AAE5D,QAAA,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;AAErB,QAAA,OAAO,SAAS;AAClB,IAAA,CAAC;IAED,MAAM,WAAW,GAAG,MAAS,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK;IAEtD,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,CAAU;AAClE;;;;"}
|
|
@@ -5,77 +5,37 @@ import { type InitializedObservable } from '../core/index.mjs';
|
|
|
5
5
|
*
|
|
6
6
|
* @template S - The type of the state
|
|
7
7
|
* @param initialState - The initial value of the state
|
|
8
|
-
* @returns
|
|
8
|
+
* @returns A 3-element tuple: `[state, setState, { updateState, resetState, getSnapshot, initialState }]`
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```ts
|
|
12
12
|
* const [state, setState, { updateState, resetState }] = createState(0);
|
|
13
13
|
*
|
|
14
|
-
* const
|
|
14
|
+
* const stateHistory: number[] = [];
|
|
15
15
|
*
|
|
16
16
|
* state.subscribe((value: number) => {
|
|
17
|
-
*
|
|
17
|
+
* stateHistory.push(value);
|
|
18
18
|
* });
|
|
19
19
|
*
|
|
20
|
-
* assert.deepStrictEqual(
|
|
20
|
+
* assert.deepStrictEqual(stateHistory, [0]);
|
|
21
21
|
*
|
|
22
22
|
* setState(10); // logs: 10
|
|
23
23
|
*
|
|
24
|
-
* assert.deepStrictEqual(
|
|
24
|
+
* assert.deepStrictEqual(stateHistory, [0, 10]);
|
|
25
25
|
*
|
|
26
26
|
* updateState((prev: number) => prev + 1); // logs: 11
|
|
27
27
|
*
|
|
28
|
-
* assert.deepStrictEqual(
|
|
28
|
+
* assert.deepStrictEqual(stateHistory, [0, 10, 11]);
|
|
29
29
|
*
|
|
30
30
|
* resetState(); // logs: 0
|
|
31
31
|
*
|
|
32
|
-
* assert.deepStrictEqual(
|
|
32
|
+
* assert.deepStrictEqual(stateHistory, [0, 10, 11, 0]);
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
export declare const createState: <S>(initialState: S) => readonly [state: InitializedObservable<S>, setState: (v: S) => S, Readonly<{
|
|
36
36
|
updateState: (updateFn: (prev: S) => S) => S;
|
|
37
37
|
resetState: () => S;
|
|
38
38
|
getSnapshot: () => S;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Creates a reactive boolean state with convenient methods for boolean operations.
|
|
42
|
-
* Extends `createState` with boolean-specific helpers like `toggle`, `setTrue`, and `setFalse`.
|
|
43
|
-
*
|
|
44
|
-
* @param initialState - The initial boolean value
|
|
45
|
-
* @returns An object with the state observable and boolean-specific methods
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* const [state, { setTrue, toggle }] = createBooleanState(false);
|
|
50
|
-
*
|
|
51
|
-
* const mut_history: boolean[] = [];
|
|
52
|
-
*
|
|
53
|
-
* state.subscribe((value: boolean) => {
|
|
54
|
-
* mut_history.push(value);
|
|
55
|
-
* });
|
|
56
|
-
*
|
|
57
|
-
* assert.deepStrictEqual(mut_history, [false]);
|
|
58
|
-
*
|
|
59
|
-
* setTrue(); // logs: true
|
|
60
|
-
*
|
|
61
|
-
* assert.deepStrictEqual(mut_history, [false, true]);
|
|
62
|
-
*
|
|
63
|
-
* toggle(); // logs: false
|
|
64
|
-
*
|
|
65
|
-
* assert.deepStrictEqual(mut_history, [false, true, false]);
|
|
66
|
-
*
|
|
67
|
-
* toggle(); // logs: true
|
|
68
|
-
*
|
|
69
|
-
* assert.deepStrictEqual(mut_history, [false, true, false, true]);
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
export declare const createBooleanState: (initialState: boolean) => readonly [state: InitializedObservable<boolean>, Readonly<{
|
|
73
|
-
setTrue: () => void;
|
|
74
|
-
setFalse: () => void;
|
|
75
|
-
setState: (next: boolean) => boolean;
|
|
76
|
-
toggle: () => boolean;
|
|
77
|
-
updateState: (updateFn: (prev: boolean) => boolean) => boolean;
|
|
78
|
-
resetState: () => boolean;
|
|
79
|
-
getSnapshot: () => boolean;
|
|
39
|
+
initialState: S;
|
|
80
40
|
}>];
|
|
81
41
|
//# sourceMappingURL=create-state.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAwB/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAC3B,cAAc,CAAC,KACd,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,QAAQ,CAAC;IACP,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;
|
|
1
|
+
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../../src/utils/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAwB/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAC3B,cAAc,CAAC,KACd,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,QAAQ,CAAC;IACP,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,CAyBH,CAAC"}
|
|
@@ -14,35 +14,35 @@ const reducer = (state, action) => {
|
|
|
14
14
|
*
|
|
15
15
|
* @template S - The type of the state
|
|
16
16
|
* @param initialState - The initial value of the state
|
|
17
|
-
* @returns
|
|
17
|
+
* @returns A 3-element tuple: `[state, setState, { updateState, resetState, getSnapshot, initialState }]`
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
21
|
* const [state, setState, { updateState, resetState }] = createState(0);
|
|
22
22
|
*
|
|
23
|
-
* const
|
|
23
|
+
* const stateHistory: number[] = [];
|
|
24
24
|
*
|
|
25
25
|
* state.subscribe((value: number) => {
|
|
26
|
-
*
|
|
26
|
+
* stateHistory.push(value);
|
|
27
27
|
* });
|
|
28
28
|
*
|
|
29
|
-
* assert.deepStrictEqual(
|
|
29
|
+
* assert.deepStrictEqual(stateHistory, [0]);
|
|
30
30
|
*
|
|
31
31
|
* setState(10); // logs: 10
|
|
32
32
|
*
|
|
33
|
-
* assert.deepStrictEqual(
|
|
33
|
+
* assert.deepStrictEqual(stateHistory, [0, 10]);
|
|
34
34
|
*
|
|
35
35
|
* updateState((prev: number) => prev + 1); // logs: 11
|
|
36
36
|
*
|
|
37
|
-
* assert.deepStrictEqual(
|
|
37
|
+
* assert.deepStrictEqual(stateHistory, [0, 10, 11]);
|
|
38
38
|
*
|
|
39
39
|
* resetState(); // logs: 0
|
|
40
40
|
*
|
|
41
|
-
* assert.deepStrictEqual(
|
|
41
|
+
* assert.deepStrictEqual(stateHistory, [0, 10, 11, 0]);
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
const createState = (initialState) => {
|
|
45
|
-
const [state, dispatch, getSnapshot] = createReducer(reducer, initialState);
|
|
45
|
+
const [state, dispatch, { getSnapshot }] = createReducer(reducer, initialState);
|
|
46
46
|
const updateState = (updateFn) => dispatch({ type: 'update', updateFn });
|
|
47
47
|
const setState = (nextState) => dispatch({ type: 'set', nextState });
|
|
48
48
|
const resetState = () => dispatch({ type: 'set', nextState: initialState });
|
|
@@ -53,60 +53,10 @@ const createState = (initialState) => {
|
|
|
53
53
|
updateState,
|
|
54
54
|
resetState,
|
|
55
55
|
getSnapshot,
|
|
56
|
-
|
|
57
|
-
];
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Creates a reactive boolean state with convenient methods for boolean operations.
|
|
61
|
-
* Extends `createState` with boolean-specific helpers like `toggle`, `setTrue`, and `setFalse`.
|
|
62
|
-
*
|
|
63
|
-
* @param initialState - The initial boolean value
|
|
64
|
-
* @returns An object with the state observable and boolean-specific methods
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```ts
|
|
68
|
-
* const [state, { setTrue, toggle }] = createBooleanState(false);
|
|
69
|
-
*
|
|
70
|
-
* const mut_history: boolean[] = [];
|
|
71
|
-
*
|
|
72
|
-
* state.subscribe((value: boolean) => {
|
|
73
|
-
* mut_history.push(value);
|
|
74
|
-
* });
|
|
75
|
-
*
|
|
76
|
-
* assert.deepStrictEqual(mut_history, [false]);
|
|
77
|
-
*
|
|
78
|
-
* setTrue(); // logs: true
|
|
79
|
-
*
|
|
80
|
-
* assert.deepStrictEqual(mut_history, [false, true]);
|
|
81
|
-
*
|
|
82
|
-
* toggle(); // logs: false
|
|
83
|
-
*
|
|
84
|
-
* assert.deepStrictEqual(mut_history, [false, true, false]);
|
|
85
|
-
*
|
|
86
|
-
* toggle(); // logs: true
|
|
87
|
-
*
|
|
88
|
-
* assert.deepStrictEqual(mut_history, [false, true, false, true]);
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
const createBooleanState = (initialState) => {
|
|
92
|
-
const [state, setState, { updateState, resetState, getSnapshot }] = createState(initialState);
|
|
93
|
-
return [
|
|
94
|
-
state,
|
|
95
|
-
{
|
|
96
|
-
setTrue: () => {
|
|
97
|
-
setState(true);
|
|
98
|
-
},
|
|
99
|
-
setFalse: () => {
|
|
100
|
-
setState(false);
|
|
101
|
-
},
|
|
102
|
-
toggle: () => updateState((s) => !s),
|
|
103
|
-
setState,
|
|
104
|
-
updateState,
|
|
105
|
-
resetState,
|
|
106
|
-
getSnapshot,
|
|
56
|
+
initialState,
|
|
107
57
|
},
|
|
108
58
|
];
|
|
109
59
|
};
|
|
110
60
|
|
|
111
|
-
export {
|
|
61
|
+
export { createState };
|
|
112
62
|
//# sourceMappingURL=create-state.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-state.mjs","sources":["../../src/utils/create-state.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAcA,MAAM,OAAO,GAAG,CAAK,KAAQ,EAAE,MAAiB,KAAO;AACrD,IAAA,QAAQ,MAAM,CAAC,IAAI;AACjB,QAAA,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,SAAS;AAEzB,QAAA,KAAK,QAAQ;AACX,YAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;;AAEnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;AACI,MAAM,WAAW,GAAG,CACzB,YAAe,
|
|
1
|
+
{"version":3,"file":"create-state.mjs","sources":["../../src/utils/create-state.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAcA,MAAM,OAAO,GAAG,CAAK,KAAQ,EAAE,MAAiB,KAAO;AACrD,IAAA,QAAQ,MAAM,CAAC,IAAI;AACjB,QAAA,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,SAAS;AAEzB,QAAA,KAAK,QAAQ;AACX,YAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;;AAEnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;AACI,MAAM,WAAW,GAAG,CACzB,YAAe,KAUb;AACF,IAAA,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,GAAG,aAAa,CACtD,OAAO,EACP,YAAY,CACb;AAED,IAAA,MAAM,WAAW,GAAG,CAAC,QAAwB,KAC3C,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAExC,IAAA,MAAM,QAAQ,GAAG,CAAC,SAAY,KAAQ,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAE1E,IAAA,MAAM,UAAU,GAAG,MACjB,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAEpD,OAAO;QACL,KAAK;QACL,QAAQ;AACR,QAAA;YACE,WAAW;YACX,UAAU;YACV,WAAW;YACX,YAAY;AACb,SAAA;KACO;AACZ;;;;"}
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC"}
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { collectToArray } from './collect-to-array.mjs';
|
|
2
|
+
export { createBooleanState } from './create-boolean-state.mjs';
|
|
1
3
|
export { createEventEmitter, createValueEmitter } from './create-event-emitter.mjs';
|
|
2
4
|
export { createReducer } from './create-reducer.mjs';
|
|
3
|
-
export {
|
|
5
|
+
export { createState } from './create-state.mjs';
|
|
4
6
|
//# sourceMappingURL=index.mjs.map
|
package/dist/utils/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synstate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Type-safe State Management Library for TypeScript/JavaScript",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,36 +28,42 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"ts-data-forge": "^6.
|
|
31
|
+
"ts-data-forge": "^6.8.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@reduxjs/toolkit": "^2.11.2",
|
|
34
35
|
"@types/argparse": "^2.0.17",
|
|
35
36
|
"@types/react": "^19.2.14",
|
|
36
37
|
"argparse": "^2.0.1",
|
|
37
|
-
"
|
|
38
|
+
"jotai": "^2.18.1",
|
|
39
|
+
"mobx": "^6.15.0",
|
|
40
|
+
"react": "^19.2.4",
|
|
41
|
+
"rxjs": "^7.8.2",
|
|
42
|
+
"valtio": "^2.3.1",
|
|
43
|
+
"zustand": "^5.0.12"
|
|
38
44
|
},
|
|
39
45
|
"scripts": {
|
|
46
|
+
"benchmark": "tsx ./scripts/cmd/run-benchmark.mts",
|
|
47
|
+
"benchmark:cascaded-diamond": "tsx ./scripts/cmd/run-benchmark-cascaded-diamond.mts",
|
|
48
|
+
"benchmark:deep-chain": "tsx ./scripts/cmd/run-benchmark-deep-chain.mts",
|
|
40
49
|
"build": "tsx ./scripts/cmd/build.mts",
|
|
41
50
|
"build:min": "tsx ./scripts/cmd/build.mts --skip-check",
|
|
42
51
|
"check:ext": "tsx ./scripts/cmd/check-ext.mts",
|
|
43
52
|
"check-all": "tsx ./scripts/cmd/check-all.mts",
|
|
44
|
-
"
|
|
45
|
-
"doc": "tsx ./scripts/cmd/gen-docs.mts",
|
|
53
|
+
"doc": "run-s doc:embed doc:embed:jsdoc doc:gen-sample-diffs fmt",
|
|
46
54
|
"doc:embed": "tsx ./scripts/cmd/embed-examples.mts",
|
|
47
55
|
"doc:embed:jsdoc": "tsx ./scripts/cmd/embed-examples-in-jsdoc.mts",
|
|
48
|
-
"doc:
|
|
49
|
-
"
|
|
50
|
-
"fmt": "pnpm run z:prettier .",
|
|
56
|
+
"doc:gen-sample-diffs": "tsx ./scripts/cmd/generate-sample-diffs.mts",
|
|
57
|
+
"fmt": "format-uncommitted",
|
|
58
|
+
"fmt:full": "pnpm run z:prettier .",
|
|
51
59
|
"gi": "run-s gi:src fmt",
|
|
52
|
-
"gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts",
|
|
60
|
+
"gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude \"core/utils/**\"",
|
|
53
61
|
"lint": "eslint .",
|
|
54
62
|
"lint:fix": "eslint . --fix",
|
|
55
63
|
"test": "pnpm run z:vitest:node run",
|
|
56
|
-
"test:all": "run-p test test:stream",
|
|
57
64
|
"test:browser": "pnpm run z:vitest --project='Browser' run",
|
|
58
65
|
"test:cov": "pnpm run z:vitest:node run --coverage",
|
|
59
66
|
"test:cov:ui": "vite preview --outDir ./coverage",
|
|
60
|
-
"test:stream": "pnpm run z:vitest:stream run",
|
|
61
67
|
"test:ui": "pnpm run z:vitest:node --ui",
|
|
62
68
|
"testw": "pnpm run z:vitest:node watch",
|
|
63
69
|
"tsc": "tsc --noEmit",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Arr } from 'ts-data-forge';
|
|
2
2
|
import {
|
|
3
|
+
isChildObservable,
|
|
3
4
|
isManagerObservable,
|
|
4
5
|
type AsyncChildObservable,
|
|
5
6
|
type ChildObservable,
|
|
@@ -14,13 +15,64 @@ import {
|
|
|
14
15
|
type WithInitialValueOperator,
|
|
15
16
|
type Wrap,
|
|
16
17
|
} from '../types/index.mjs';
|
|
17
|
-
import { binarySearch,
|
|
18
|
+
import { binarySearch, issueUpdateToken, maxDepth } from '../utils/index.mjs';
|
|
18
19
|
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Detects circular dependencies by walking the full ancestor chain of the
|
|
23
|
+
* given parents and checking whether `child` already appears among them.
|
|
24
|
+
*
|
|
25
|
+
* @throws {Error} if a circular dependency is detected
|
|
26
|
+
*/
|
|
27
|
+
const hasCircularDependencyFrom = (
|
|
28
|
+
node: Observable<unknown>,
|
|
29
|
+
mut_visited: MutableSet<ObservableId>,
|
|
30
|
+
mut_inPath: MutableSet<ObservableId>,
|
|
31
|
+
): boolean => {
|
|
32
|
+
if (mut_inPath.has(node.id)) return true;
|
|
33
|
+
|
|
34
|
+
if (mut_visited.has(node.id)) return false;
|
|
35
|
+
|
|
36
|
+
mut_visited.add(node.id);
|
|
37
|
+
|
|
38
|
+
mut_inPath.add(node.id);
|
|
39
|
+
|
|
40
|
+
if (isChildObservable(node)) {
|
|
41
|
+
for (const parent of node.parents) {
|
|
42
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
mut_inPath.delete(node.id);
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const detectCircularDependency = (
|
|
54
|
+
child: ChildObservable<unknown>,
|
|
55
|
+
parents: readonly Observable<unknown>[],
|
|
56
|
+
): void => {
|
|
57
|
+
const mut_visited = new Set<ObservableId>();
|
|
58
|
+
|
|
59
|
+
const mut_inPath = new Set<ObservableId>([child.id]);
|
|
60
|
+
|
|
61
|
+
for (const parent of parents) {
|
|
62
|
+
if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
20
70
|
const registerChild = <A,>(
|
|
21
71
|
child: ChildObservable<A>,
|
|
22
72
|
parents: ChildObservable<A>['parents'],
|
|
23
73
|
): void => {
|
|
74
|
+
detectCircularDependency(child, parents);
|
|
75
|
+
|
|
24
76
|
for (const p of parents) {
|
|
25
77
|
p.addChild(child);
|
|
26
78
|
}
|
|
@@ -76,7 +128,7 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
76
128
|
implements AsyncChildObservable<A, P>
|
|
77
129
|
{
|
|
78
130
|
readonly parents;
|
|
79
|
-
#
|
|
131
|
+
#mut_propagationOrder: readonly ChildObservable<unknown>[];
|
|
80
132
|
protected readonly descendantsIdSet: MutableSet<ObservableId>;
|
|
81
133
|
|
|
82
134
|
constructor({
|
|
@@ -96,7 +148,7 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
96
148
|
|
|
97
149
|
this.parents = parents;
|
|
98
150
|
|
|
99
|
-
this.#
|
|
151
|
+
this.#mut_propagationOrder = [];
|
|
100
152
|
|
|
101
153
|
this.descendantsIdSet = new Set<ObservableId>();
|
|
102
154
|
|
|
@@ -110,20 +162,24 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
110
162
|
this.descendantsIdSet.add(child.id);
|
|
111
163
|
|
|
112
164
|
const insertPos = binarySearch(
|
|
113
|
-
this.#
|
|
165
|
+
this.#mut_propagationOrder.map((a) => a.depth),
|
|
114
166
|
child.depth,
|
|
115
167
|
);
|
|
116
168
|
|
|
117
|
-
this.#
|
|
169
|
+
this.#mut_propagationOrder = Arr.toInserted(
|
|
170
|
+
this.#mut_propagationOrder,
|
|
171
|
+
insertPos,
|
|
172
|
+
child,
|
|
173
|
+
);
|
|
118
174
|
}
|
|
119
175
|
|
|
120
176
|
startUpdate(nextValue: A): void {
|
|
121
|
-
const
|
|
177
|
+
const updateToken = issueUpdateToken();
|
|
122
178
|
|
|
123
|
-
this.setNext(nextValue,
|
|
179
|
+
this.setNext(nextValue, updateToken);
|
|
124
180
|
|
|
125
|
-
for (const p of this.#
|
|
126
|
-
p.tryUpdate(
|
|
181
|
+
for (const p of this.#mut_propagationOrder) {
|
|
182
|
+
p.tryUpdate(updateToken);
|
|
127
183
|
}
|
|
128
184
|
}
|
|
129
185
|
|
|
@@ -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
|
+
});
|