synstate 4.1.0 → 5.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 +58 -1
- package/dist/core/class/child-observable-class.d.mts +4 -7
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +8 -25
- package/dist/core/class/child-observable-class.mjs.map +1 -1
- package/dist/core/class/create-child-observable.d.mts +8 -5
- package/dist/core/class/create-child-observable.d.mts.map +1 -1
- package/dist/core/class/create-child-observable.mjs.map +1 -1
- package/dist/core/class/manager-observable-class.d.mts +4 -15
- package/dist/core/class/manager-observable-class.d.mts.map +1 -1
- package/dist/core/class/manager-observable-class.mjs +5 -91
- package/dist/core/class/manager-observable-class.mjs.map +1 -1
- package/dist/core/class/observable-base-class.d.mts +9 -2
- package/dist/core/class/observable-base-class.d.mts.map +1 -1
- package/dist/core/class/observable-base-class.mjs +17 -0
- package/dist/core/class/observable-base-class.mjs.map +1 -1
- package/dist/core/class/observable-node.d.mts +4 -8
- package/dist/core/class/observable-node.d.mts.map +1 -1
- package/dist/core/class/observable-node.mjs.map +1 -1
- package/dist/core/class/propagation-state.d.mts +26 -0
- package/dist/core/class/propagation-state.d.mts.map +1 -1
- package/dist/core/class/propagation-state.mjs +190 -0
- package/dist/core/class/propagation-state.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/keep-equal-parts.d.mts +85 -0
- package/dist/core/operators/keep-equal-parts.d.mts.map +1 -0
- package/dist/core/operators/keep-equal-parts.mjs +129 -0
- package/dist/core/operators/keep-equal-parts.mjs.map +1 -0
- package/dist/core/types/id.d.mts +6 -2
- package/dist/core/types/id.d.mts.map +1 -1
- package/dist/core/types/observable-family.d.mts +1 -0
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/utils/id-maker.d.mts +0 -8
- package/dist/core/utils/id-maker.d.mts.map +1 -1
- package/dist/core/utils/id-maker.mjs +1 -11
- package/dist/core/utils/id-maker.mjs.map +1 -1
- package/dist/utils/create-reducer.d.mts +30 -1
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +8 -1
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +53 -1
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +52 -0
- package/dist/utils/create-state.mjs.map +1 -1
- package/package.json +9 -9
- package/src/core/class/child-observable-class.mts +15 -49
- package/src/core/class/create-child-observable.mts +8 -5
- package/src/core/class/manager-observable-class.mts +15 -134
- package/src/core/class/observable-base-class.mts +23 -2
- package/src/core/class/observable-node.mts +5 -11
- package/src/core/class/propagation-order.test.mts +107 -0
- package/src/core/class/propagation-state.mts +262 -0
- package/src/core/class/remove-completed.test.mts +2 -2
- package/src/core/class/update-loop.test.mts +104 -0
- package/src/core/operators/index.mts +1 -0
- package/src/core/operators/keep-equal-parts.mts +167 -0
- package/src/core/operators/keep-equal-parts.test.mts +132 -0
- package/src/core/types/id.mts +6 -2
- package/src/core/types/observable-family.mts +5 -0
- package/src/core/utils/id-maker.mts +1 -12
- package/src/utils/create-reducer.mts +42 -2
- package/src/utils/create-state.mts +53 -1
- package/src/utils/create-state.test.mts +88 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReplaceEqualDeepOptions } from 'ts-data-forge';
|
|
1
2
|
import type { Reducer } from 'ts-type-forge';
|
|
2
3
|
import { type InitializedObservable } from '../core/index.mjs';
|
|
3
4
|
/**
|
|
@@ -9,7 +10,8 @@ import { type InitializedObservable } from '../core/index.mjs';
|
|
|
9
10
|
* @param reducer - A pure function that takes current state and action, returns new state
|
|
10
11
|
* @param initialState - The initial value of the state
|
|
11
12
|
* @param options - {@link StateOptions}: `equals` (default `Object.is`),
|
|
12
|
-
* to pass on only the dispatches that change the state
|
|
13
|
+
* to pass on only the dispatches that change the state, and
|
|
14
|
+
* `keepEqualParts`, to keep the parts of the state a dispatch did not change
|
|
13
15
|
* @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`.
|
|
14
16
|
* `dispatch` returns the state it leaves; once `state` is completed, that
|
|
15
17
|
* is the state held.
|
|
@@ -102,5 +104,32 @@ export type StateOptions<S> = Readonly<{
|
|
|
102
104
|
* @param next - The state the update would set
|
|
103
105
|
*/
|
|
104
106
|
equals?: (current: S, next: S) => boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Whether an update keeps the parts of the state it did not change. With
|
|
109
|
+
* `true`, the next state is compared with the current one before it is
|
|
110
|
+
* written, and every part of it deeply equal to the same part of the
|
|
111
|
+
* current state is replaced by that part (`replaceEqualDeep` of
|
|
112
|
+
* ts-data-forge): a reader of an unchanged part keeps the object it has, so
|
|
113
|
+
* `skipIfNoChange()` with `Object.is` stops it. When the whole next state is
|
|
114
|
+
* deeply equal, the current state is kept and nothing is passed on, so
|
|
115
|
+
* `equals` can stay `Object.is`.
|
|
116
|
+
*
|
|
117
|
+
* This is for a state written whole from a source that answers with a new
|
|
118
|
+
* object every time, a server polled for a report, say. Array items are
|
|
119
|
+
* matched by index; pass `{ key }` to match them by key, an item for which
|
|
120
|
+
* `key` returns `undefined` being matched by index. Only plain objects and
|
|
121
|
+
* arrays are walked into; a `Map`, a `Set`, a `Date` or a class instance is
|
|
122
|
+
* compared and kept whole.
|
|
123
|
+
*
|
|
124
|
+
* Prefer this over `keepEqualParts()` downstream: keeping the parts where
|
|
125
|
+
* the state is written means no update with nothing in it is passed on at
|
|
126
|
+
* all, and nothing downstream has anything to undo.
|
|
127
|
+
*
|
|
128
|
+
* Defaults to `false`: the next state is written as given. With it on,
|
|
129
|
+
* each update compares deeply every part of the next state that is not
|
|
130
|
+
* the very object of the current one; a state whose updates already keep
|
|
131
|
+
* what they do not change, as `{ ...prev, x }` does, gains nothing from it.
|
|
132
|
+
*/
|
|
133
|
+
keepEqualParts?: boolean | ReplaceEqualDeepOptions;
|
|
105
134
|
}>;
|
|
106
135
|
//# 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,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAU,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE
|
|
1
|
+
{"version":3,"file":"create-reducer.d.mts","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAU,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,WACvB,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,gBACR,CAAC,YACL,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KACjC,SAAS,CACV,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAC1B,KAAK,EAAE,QAAQ,CAAC;IACd,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CA8BH,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,QAAQ,CAAC;IACrC;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IAE1C;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,uBAAuB,CAAC;CACpD,CAAC,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { replaceEqualDeep } from 'ts-data-forge';
|
|
1
2
|
import { source } from '../core/index.mjs';
|
|
2
3
|
|
|
3
4
|
|
|
@@ -73,14 +74,20 @@ import { source } from '../core/index.mjs';
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
export const createReducer = (reducer, initialState, options) => {
|
|
79
81
|
const state = source(initialState);
|
|
80
82
|
const equals = options?.equals ?? Object.is;
|
|
83
|
+
const keepEqualParts = options?.keepEqualParts ?? false;
|
|
84
|
+
const keepOptions = typeof keepEqualParts === 'boolean' ? undefined : keepEqualParts;
|
|
81
85
|
const dispatch = (action) => {
|
|
82
86
|
const currentState = state.getSnapshot().value;
|
|
83
|
-
const
|
|
87
|
+
const reduced = reducer(currentState, action);
|
|
88
|
+
const nextState = keepEqualParts === false
|
|
89
|
+
? reduced
|
|
90
|
+
: replaceEqualDeep(currentState, reduced, keepOptions);
|
|
84
91
|
|
|
85
92
|
return equals(currentState, nextState)
|
|
86
93
|
? currentState
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-reducer.mjs","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-reducer.mjs","sourceRoot":"","sources":["../../src/utils/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAgC,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAE,MAAM,EAA8B,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,OAAsB,EACtB,YAAe,EACf,OAAkC,EAQlC,EAAE;IACF,MAAM,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC,CAAC;IAEtC,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC;IAE5C,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,KAAK,CAAC;IAExD,MAAM,WAAW,GACf,OAAO,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC;IAEnE,MAAM,QAAQ,GAAG,CAAC,MAAS,EAAK,EAAE;QAChC,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;QAE/C,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAE9C,MAAM,SAAS,GACb,cAAc,KAAK,KAAK;YACtB,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QAE3D,8DAA8D;QAC9D,OAAO,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC;YACpC,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAM,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;IAEvD,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,CAAU,CAAC;AACnE,CAAC,CAAC"}
|
|
@@ -7,7 +7,8 @@ import { type StateOptions } from './create-reducer.mjs';
|
|
|
7
7
|
* @template S - The type of the state
|
|
8
8
|
* @param initialState - The initial value of the state
|
|
9
9
|
* @param options - {@link StateOptions}: `equals` (default `Object.is`),
|
|
10
|
-
* to pass on only the updates that change the state
|
|
10
|
+
* to pass on only the updates that change the state, and `keepEqualParts`,
|
|
11
|
+
* to keep the parts of the state an update did not change
|
|
11
12
|
* @returns A 3-element tuple: `[state, setState, { updateState, resetState, getSnapshot, initialState }]`
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
@@ -67,6 +68,57 @@ import { type StateOptions } from './create-reducer.mjs';
|
|
|
67
68
|
* ]);
|
|
68
69
|
* ```
|
|
69
70
|
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* type PullReport = DeepReadonly<{
|
|
74
|
+
* summary: { open: number };
|
|
75
|
+
* pulls: { number: number; title: string }[];
|
|
76
|
+
* }>;
|
|
77
|
+
*
|
|
78
|
+
* const [report, setReport] = createState<PullReport>(
|
|
79
|
+
* { summary: { open: 0 }, pulls: [] },
|
|
80
|
+
* { keepEqualParts: true },
|
|
81
|
+
* );
|
|
82
|
+
*
|
|
83
|
+
* const summary = report.pipe(map((r) => r.summary)).pipe(skipIfNoChange());
|
|
84
|
+
*
|
|
85
|
+
* const summaryHistory: unknown[] = [];
|
|
86
|
+
*
|
|
87
|
+
* summary.subscribe((value) => {
|
|
88
|
+
* summaryHistory.push(value);
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* // A server answers each poll with the whole report, a new object every
|
|
92
|
+
* // time. What did not change keeps the object already held.
|
|
93
|
+
* setReport({
|
|
94
|
+
* summary: { open: 1 },
|
|
95
|
+
* pulls: [{ number: 1, title: 'one' }],
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* const held = report.getSnapshot().value;
|
|
99
|
+
*
|
|
100
|
+
* // An answer that changes nothing is not passed on at all.
|
|
101
|
+
* setReport({
|
|
102
|
+
* summary: { open: 1 },
|
|
103
|
+
* pulls: [{ number: 1, title: 'one' }],
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* assert.strictEqual(report.getSnapshot().value, held);
|
|
107
|
+
*
|
|
108
|
+
* // An edited title: a new report and a new array, while the summary is
|
|
109
|
+
* // the one held, so `summary` is not told of it.
|
|
110
|
+
* setReport({
|
|
111
|
+
* summary: { open: 1 },
|
|
112
|
+
* pulls: [{ number: 1, title: 'one (edited)' }],
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* assert.notStrictEqual(report.getSnapshot().value, held);
|
|
116
|
+
*
|
|
117
|
+
* assert.strictEqual(report.getSnapshot().value.summary, held.summary);
|
|
118
|
+
*
|
|
119
|
+
* assert.deepStrictEqual(summaryHistory, [{ open: 0 }, { open: 1 }]);
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
70
122
|
* @note Do not call `setState`, `updateState` or `resetState` from a subscriber or a projection while another
|
|
71
123
|
* update is propagating: the write propagates immediately, so subscribers can
|
|
72
124
|
* see an intermediate state, and it logs a `console.warn` (once per
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6HG;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"}
|
|
@@ -60,6 +60,58 @@ const reducer = (state, action) => {
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
63
115
|
|
|
64
116
|
|
|
65
117
|
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6HG;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": "5.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.10.0",
|
|
33
33
|
"ts-std-forge": "^0.5.2",
|
|
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.1.0",
|
|
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.19.
|
|
43
|
-
"eslint-plugin-ts-data-forge": "0.10.
|
|
44
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
45
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
42
|
+
"eslint-config-typed": "5.19.1",
|
|
43
|
+
"eslint-plugin-ts-data-forge": "0.10.2",
|
|
44
|
+
"eslint-plugin-ts-fortress": "1.3.7",
|
|
45
|
+
"eslint-plugin-ts-type-forge": "0.8.12",
|
|
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": "5.0.0",
|
|
55
|
+
"synstate-react-hooks-compat": "5.0.0",
|
|
56
56
|
"ts-repo-utils": "10.7.1",
|
|
57
57
|
"valtio": "2.3.2",
|
|
58
58
|
"zustand": "5.0.15"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
3
3
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
4
4
|
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
5
|
-
import {
|
|
5
|
+
import { hasKey, isRecord, type Optional, type Some } from 'ts-data-forge';
|
|
6
6
|
import { panic } from 'ts-std-forge';
|
|
7
7
|
import type { MutableSet } from 'ts-type-forge';
|
|
8
8
|
import type {
|
|
@@ -21,11 +21,7 @@ import type {
|
|
|
21
21
|
import { maxDepth } from '../utils/index.mjs';
|
|
22
22
|
import { ManagerObservableClass } from './manager-observable-class.mjs';
|
|
23
23
|
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
24
|
-
import {
|
|
25
|
-
toNode,
|
|
26
|
-
type ChildNode,
|
|
27
|
-
type ManagerNode,
|
|
28
|
-
} from './observable-node.mjs';
|
|
24
|
+
import { toNode, type ChildNode } from './observable-node.mjs';
|
|
29
25
|
import { runAfterPropagation } from './propagation-state.mjs';
|
|
30
26
|
|
|
31
27
|
type ChildObservableConfig<A, P extends NonEmptyUnknownList> = Readonly<{
|
|
@@ -57,7 +53,6 @@ export class SyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
57
53
|
{
|
|
58
54
|
#mut_isAttached: boolean;
|
|
59
55
|
readonly parents: Wrap<P>;
|
|
60
|
-
readonly managers: readonly ManagerNode[];
|
|
61
56
|
|
|
62
57
|
constructor({
|
|
63
58
|
parents,
|
|
@@ -72,17 +67,14 @@ export class SyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
72
67
|
|
|
73
68
|
this.parents = parents;
|
|
74
69
|
|
|
75
|
-
this.managers = managersOf(parents);
|
|
76
|
-
|
|
77
70
|
this.#mut_isAttached = false;
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
/**
|
|
81
|
-
* Joins the current scope and registers this observable to its parents
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* leave a reference in each parent.
|
|
74
|
+
* Joins the current scope and registers this observable to its parents.
|
|
75
|
+
* Completed parents never update again, so a child created under them is
|
|
76
|
+
* complete from the start (#2248), as is one its constructor completed;
|
|
77
|
+
* registering it would only leave a reference in each parent.
|
|
86
78
|
*/
|
|
87
79
|
protected attach(): void {
|
|
88
80
|
this.adoptIntoCurrentScope();
|
|
@@ -90,7 +82,7 @@ export class SyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
90
82
|
if (this.isCompleted || this.parents.every((p) => p.isCompleted)) {
|
|
91
83
|
this.complete();
|
|
92
84
|
} else {
|
|
93
|
-
registerChild(this
|
|
85
|
+
registerChild(this);
|
|
94
86
|
|
|
95
87
|
this.#mut_isAttached = true;
|
|
96
88
|
}
|
|
@@ -103,7 +95,7 @@ export class SyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
103
95
|
|
|
104
96
|
this.#mut_isAttached = false;
|
|
105
97
|
|
|
106
|
-
unregisterChild(this
|
|
98
|
+
unregisterChild(this);
|
|
107
99
|
}
|
|
108
100
|
|
|
109
101
|
override tryComplete(): void {
|
|
@@ -170,7 +162,7 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
170
162
|
if (this.isCompleted || this.parents.every((p) => p.isCompleted)) {
|
|
171
163
|
this.complete();
|
|
172
164
|
} else {
|
|
173
|
-
registerChild(this
|
|
165
|
+
registerChild(this);
|
|
174
166
|
|
|
175
167
|
this.#mut_isAttached = true;
|
|
176
168
|
}
|
|
@@ -183,7 +175,7 @@ export class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList>
|
|
|
183
175
|
|
|
184
176
|
this.#mut_isAttached = false;
|
|
185
177
|
|
|
186
|
-
unregisterChild(this
|
|
178
|
+
unregisterChild(this);
|
|
187
179
|
}
|
|
188
180
|
|
|
189
181
|
override tryComplete(): void {
|
|
@@ -247,11 +239,8 @@ const shouldComplete = (
|
|
|
247
239
|
!child.isCompleted &&
|
|
248
240
|
(child.parents.every((p) => p.isCompleted) || child.canEmitNoMore());
|
|
249
241
|
|
|
250
|
-
/** Registers a child to its parents
|
|
251
|
-
const registerChild = (
|
|
252
|
-
child: ChildNode,
|
|
253
|
-
managers: readonly ManagerNode[],
|
|
254
|
-
): void => {
|
|
242
|
+
/** Registers a child to its parents. */
|
|
243
|
+
const registerChild = (child: ChildNode): void => {
|
|
255
244
|
if (checksCircularDependency) {
|
|
256
245
|
detectCircularDependency(child);
|
|
257
246
|
}
|
|
@@ -259,42 +248,19 @@ const registerChild = (
|
|
|
259
248
|
for (const p of child.parents) {
|
|
260
249
|
toNode(p).addChild(child);
|
|
261
250
|
}
|
|
262
|
-
|
|
263
|
-
for (const m of managers) {
|
|
264
|
-
m.addDescendant(child);
|
|
265
|
-
}
|
|
266
251
|
};
|
|
267
252
|
|
|
268
253
|
/**
|
|
269
254
|
* Undoes {@link registerChild} for a completed child: its parents forget it,
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* lived, and ran its `tryUpdate` on every update (#2134).
|
|
255
|
+
* so that no update puts it in its queue again. Without this a completed
|
|
256
|
+
* node stayed reachable for as long as its root lived (#2134).
|
|
273
257
|
*/
|
|
274
|
-
const unregisterChild = (
|
|
275
|
-
child: ChildNode,
|
|
276
|
-
managers: readonly ManagerNode[],
|
|
277
|
-
): void => {
|
|
258
|
+
const unregisterChild = (child: ChildNode): void => {
|
|
278
259
|
for (const p of child.parents) {
|
|
279
260
|
toNode(p).deleteChild(child);
|
|
280
261
|
}
|
|
281
|
-
|
|
282
|
-
for (const m of managers) {
|
|
283
|
-
m.deleteDescendant(child);
|
|
284
|
-
}
|
|
285
262
|
};
|
|
286
263
|
|
|
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
264
|
const isDevelopmentProcess = (): boolean => {
|
|
299
265
|
const proc: unknown = Reflect.get(globalThis, 'process');
|
|
300
266
|
|
|
@@ -61,7 +61,10 @@ export type AsyncChildObservableTools<A> = Readonly<{
|
|
|
61
61
|
* hooks the factory calls, which read the state `init` creates.
|
|
62
62
|
*/
|
|
63
63
|
export type SyncChildObservableHooks = Readonly<{
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Called with the token of every update in which a parent of this
|
|
66
|
+
* observable emitted.
|
|
67
|
+
*/
|
|
65
68
|
tryUpdate: (updateToken: UpdateToken) => void;
|
|
66
69
|
|
|
67
70
|
/**
|
|
@@ -124,10 +127,10 @@ export type InitializedChildObservableConfig<
|
|
|
124
127
|
* other synchronous operators are built with it.
|
|
125
128
|
*
|
|
126
129
|
* `init` runs during construction and receives the tools. It returns
|
|
127
|
-
* `tryUpdate`, which is called with the token of every update
|
|
128
|
-
* this observable — a parent that did not emit in that
|
|
129
|
-
* `updateToken`; to emit, call `setNext` with the
|
|
130
|
-
* object holding `tryUpdate` and the other hooks
|
|
130
|
+
* `tryUpdate`, which is called with the token of every update in which some
|
|
131
|
+
* parent of this observable emitted — a parent that did not emit in that
|
|
132
|
+
* update has a different `updateToken`; to emit, call `setNext` with the
|
|
133
|
+
* token received — or an object holding `tryUpdate` and the other hooks
|
|
131
134
|
* ({@link SyncChildObservableHooks}). State the hooks share can live inside
|
|
132
135
|
* `init`, next to the teardown it registers with the `onComplete` tool.
|
|
133
136
|
* `init` may complete the observable; it is then never registered to its
|
|
@@ -1,137 +1,32 @@
|
|
|
1
1
|
// @sumi-expect-error-file classes/no-class
|
|
2
2
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
3
3
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
4
|
-
|
|
5
|
-
import type { Optional } from 'ts-data-forge';
|
|
6
|
-
import type { MutableSet } from 'ts-type-forge';
|
|
7
|
-
import type { ManagerObservableKind, UpdateToken } from '../types/index.mjs';
|
|
8
|
-
import { issueUpdateToken } from '../utils/index.mjs';
|
|
4
|
+
import type { ManagerObservableKind } from '../types/index.mjs';
|
|
9
5
|
import { ObservableBaseClass } from './observable-base-class.mjs';
|
|
10
|
-
import
|
|
11
|
-
|
|
6
|
+
import {
|
|
7
|
+
beginPropagation,
|
|
8
|
+
closeUpdate,
|
|
9
|
+
drainUpdate,
|
|
10
|
+
endPropagation,
|
|
11
|
+
openUpdate,
|
|
12
|
+
} from './propagation-state.mjs';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The base of the observables that start updates of their own: a root, and
|
|
15
16
|
* an async child (`debounce`, `switchMap`), which emits later than its
|
|
16
|
-
* parents do.
|
|
17
|
-
*
|
|
18
|
-
* parents' values of that
|
|
17
|
+
* parents do. Its update visits, shallowest first, the nodes its value
|
|
18
|
+
* reaches: each node that emits puts its children in the update's queue
|
|
19
|
+
* (`propagation-state.mts`), so that each sees its parents' values of that
|
|
20
|
+
* update (no glitch) and a node no value reached is not visited.
|
|
19
21
|
*/
|
|
20
22
|
export class ManagerObservableClass<
|
|
21
23
|
A,
|
|
22
24
|
Kind extends ManagerObservableKind,
|
|
23
25
|
Depth extends number,
|
|
24
26
|
> extends ObservableBaseClass<A, Kind, Depth> {
|
|
25
|
-
/**
|
|
26
|
-
* The descendants, by depth: the set at index `i` holds those at
|
|
27
|
-
* `depth + 1 + i`, in the order they registered. Depths below a live node
|
|
28
|
-
* never skip one (every live node has a live parent one level up), so the
|
|
29
|
-
* array has few holes; a hole is `undefined`, never a gap left for V8 to
|
|
30
|
-
* treat as a holey array.
|
|
31
|
-
*
|
|
32
|
-
* An update walks this array and its sets in place. Adding or removing a
|
|
33
|
-
* node is O(1) and there is no flat order to rebuild; only the first node
|
|
34
|
-
* of a depth replaces the array with a longer copy. A node deleted during
|
|
35
|
-
* the walk is skipped by the set's iterator; a node registered during it is
|
|
36
|
-
* also put in `#mut_joinedDuringUpdate` and skipped, so an update visits the
|
|
37
|
-
* nodes that existed when it began.
|
|
38
|
-
*/
|
|
39
|
-
#mut_propagationOrder: readonly (MutableSet<ChildNode> | undefined)[];
|
|
40
|
-
|
|
41
|
-
/** Whether this node is propagating an update of its own. */
|
|
42
|
-
#mut_isPropagating: boolean;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The descendants that registered while this node propagated the update
|
|
46
|
-
* under way, which that update does not visit; made when the first one
|
|
47
|
-
* arrives. Kept on the manager rather than in a module, so that it also
|
|
48
|
-
* holds for a node another copy of synstate registered.
|
|
49
|
-
*/
|
|
50
|
-
#mut_joinedDuringUpdate: MutableSet<ChildNode> | undefined;
|
|
51
|
-
|
|
52
|
-
/** The managers this node's descendants register to: itself. */
|
|
53
|
-
readonly managers: readonly ManagerNode[];
|
|
54
|
-
|
|
55
|
-
constructor(
|
|
56
|
-
config: Readonly<{
|
|
57
|
-
kind: Kind;
|
|
58
|
-
depth: Depth;
|
|
59
|
-
initialValue: Optional<A>;
|
|
60
|
-
}>,
|
|
61
|
-
) {
|
|
62
|
-
super(config);
|
|
63
|
-
|
|
64
|
-
this.managers = [this];
|
|
65
|
-
|
|
66
|
-
this.#mut_propagationOrder = [];
|
|
67
|
-
|
|
68
|
-
this.#mut_isPropagating = false;
|
|
69
|
-
|
|
70
|
-
this.#mut_joinedDuringUpdate = undefined;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Runs `tryUpdate` on the descendants, shallowest first, skipping those
|
|
75
|
-
* registered after this update began.
|
|
76
|
-
*/
|
|
77
|
-
#propagate(updateToken: UpdateToken): void {
|
|
78
|
-
for (const bucket of this.#mut_propagationOrder) {
|
|
79
|
-
if (bucket !== undefined) {
|
|
80
|
-
for (const c of bucket) {
|
|
81
|
-
// Read for each node, since the set is made when the first joins.
|
|
82
|
-
if (this.#mut_joinedDuringUpdate?.has(c) !== true) {
|
|
83
|
-
c.tryUpdate(updateToken);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
addDescendant(child: ChildNode): void {
|
|
91
|
-
const descendants = this.#mut_propagationOrder;
|
|
92
|
-
|
|
93
|
-
const index = child.depth - this.depth - 1;
|
|
94
|
-
|
|
95
|
-
const mut_bucket = descendants[index];
|
|
96
|
-
|
|
97
|
-
if (mut_bucket === undefined) {
|
|
98
|
-
// The first node of its depth: rare, so the array is copied rather than
|
|
99
|
-
// written, which also leaves a walk already under way on the old one.
|
|
100
|
-
this.#mut_propagationOrder = Array.from(
|
|
101
|
-
{ length: Math.max(descendants.length, index + 1) },
|
|
102
|
-
(_, i) => (i === index ? new Set([child]) : descendants[i]),
|
|
103
|
-
);
|
|
104
|
-
} else {
|
|
105
|
-
mut_bucket.add(child);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!this.#mut_isPropagating) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this.#mut_joinedDuringUpdate ??= new Set();
|
|
113
|
-
|
|
114
|
-
this.#mut_joinedDuringUpdate.add(child);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
deleteDescendant(child: ChildNode): void {
|
|
118
|
-
// An emptied set stays, to be reused by the next node of its depth.
|
|
119
|
-
this.#mut_propagationOrder[child.depth - this.depth - 1]?.delete(child);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
27
|
/** Emits `nextValue` and propagates it to the descendants. */
|
|
123
28
|
protected startUpdate(nextValue: A): void {
|
|
124
|
-
const updateToken =
|
|
125
|
-
|
|
126
|
-
// A write to this same manager from inside its own update starts an inner
|
|
127
|
-
// one; what joins during it joined during the outer one too.
|
|
128
|
-
const wasPropagating = this.#mut_isPropagating;
|
|
129
|
-
|
|
130
|
-
const joinedBefore = this.#mut_joinedDuringUpdate;
|
|
131
|
-
|
|
132
|
-
this.#mut_isPropagating = true;
|
|
133
|
-
|
|
134
|
-
this.#mut_joinedDuringUpdate = undefined;
|
|
29
|
+
const updateToken = openUpdate(this.depth);
|
|
135
30
|
|
|
136
31
|
beginPropagation(updateToken);
|
|
137
32
|
|
|
@@ -142,23 +37,9 @@ export class ManagerObservableClass<
|
|
|
142
37
|
try {
|
|
143
38
|
this.setNext(nextValue, updateToken);
|
|
144
39
|
|
|
145
|
-
|
|
40
|
+
drainUpdate(updateToken);
|
|
146
41
|
} finally {
|
|
147
|
-
|
|
148
|
-
const joinedNow = this.#mut_joinedDuringUpdate as
|
|
149
|
-
MutableSet<ChildNode> | undefined;
|
|
150
|
-
|
|
151
|
-
this.#mut_isPropagating = wasPropagating;
|
|
152
|
-
|
|
153
|
-
this.#mut_joinedDuringUpdate = joinedBefore;
|
|
154
|
-
|
|
155
|
-
if (wasPropagating && joinedNow !== undefined) {
|
|
156
|
-
this.#mut_joinedDuringUpdate ??= new Set();
|
|
157
|
-
|
|
158
|
-
for (const c of joinedNow) {
|
|
159
|
-
this.#mut_joinedDuringUpdate.add(c);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
42
|
+
closeUpdate(updateToken);
|
|
162
43
|
|
|
163
44
|
endPropagation();
|
|
164
45
|
}
|