synstate 6.1.0 → 6.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/core/authoring.d.mts +3 -3
- package/dist/core/class/child-observable-class.d.mts +24 -25
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +41 -42
- package/dist/core/class/child-observable-class.mjs.map +1 -1
- package/dist/core/class/create-child-observable.d.mts +27 -29
- 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/create-root-observable.d.mts +11 -11
- package/dist/core/class/manager-observable-class.d.mts +6 -6
- package/dist/core/class/manager-observable-class.d.mts.map +1 -1
- package/dist/core/class/manager-observable-class.mjs +6 -0
- package/dist/core/class/manager-observable-class.mjs.map +1 -1
- package/dist/core/class/observable-base-class.d.mts +32 -26
- package/dist/core/class/observable-base-class.d.mts.map +1 -1
- package/dist/core/class/observable-base-class.mjs +16 -3
- package/dist/core/class/observable-base-class.mjs.map +1 -1
- package/dist/core/class/observable-node.d.mts +3 -4
- package/dist/core/class/observable-node.d.mts.map +1 -1
- package/dist/core/class/observable-node.mjs +0 -1
- package/dist/core/class/observable-node.mjs.map +1 -1
- package/dist/core/class/propagation-state.d.mts +10 -9
- package/dist/core/class/propagation-state.d.mts.map +1 -1
- package/dist/core/class/propagation-state.mjs +4 -2
- package/dist/core/class/propagation-state.mjs.map +1 -1
- package/dist/core/class/root-observable-class.d.mts +4 -4
- package/dist/core/combine/derive.d.mts +7 -8
- package/dist/core/combine/derive.d.mts.map +1 -1
- package/dist/core/combine/derive.mjs +0 -1
- package/dist/core/combine/derive.mjs.map +1 -1
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +3 -3
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.d.mts +3 -3
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-abortable-promise.mjs.map +1 -1
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/from-subscribable.mjs.map +1 -1
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/create/timer.mjs.map +1 -1
- package/dist/core/operators/audit.mjs.map +1 -1
- package/dist/core/operators/debounce.mjs.map +1 -1
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/keep-equal-parts.d.mts +14 -15
- package/dist/core/operators/keep-equal-parts.d.mts.map +1 -1
- package/dist/core/operators/keep-equal-parts.mjs +0 -1
- package/dist/core/operators/keep-equal-parts.mjs.map +1 -1
- package/dist/core/operators/map.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +3 -3
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/pairwise.mjs.map +1 -1
- package/dist/core/operators/scan.mjs.map +1 -1
- package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/operators/take.mjs.map +1 -1
- package/dist/core/operators/throttle.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/operators/with-current-value-from.mjs.map +1 -1
- package/dist/core/operators/with-initial-value.mjs.map +1 -1
- package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
- package/dist/core/predefined/operators/map-to.d.mts +3 -4
- package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-to.mjs +0 -1
- package/dist/core/predefined/operators/map-to.mjs.map +1 -1
- package/dist/core/scope/scope.d.mts +4 -4
- package/dist/core/types/id.d.mts +2 -1
- package/dist/core/types/id.d.mts.map +1 -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 +7 -6
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/observable.mjs.map +1 -1
- package/dist/core/utils/current-scope.d.mts +6 -6
- package/dist/core/utils/id-maker.d.mts +7 -0
- package/dist/core/utils/id-maker.d.mts.map +1 -1
- package/dist/core/utils/id-maker.mjs +9 -0
- package/dist/core/utils/id-maker.mjs.map +1 -1
- package/dist/core/utils/observable-brand.d.mts +5 -5
- package/dist/utils/create-boolean-state.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +20 -20
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +4 -16
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +3 -3
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +10 -13
- package/dist/utils/create-state.mjs.map +1 -1
- package/dist/utils/state-source.d.mts +19 -0
- package/dist/utils/state-source.d.mts.map +1 -0
- package/dist/utils/state-source.mjs +31 -0
- package/dist/utils/state-source.mjs.map +1 -0
- package/package.json +10 -10
- package/src/core/authoring.mts +3 -3
- package/src/core/class/child-observable-class.mts +86 -87
- package/src/core/class/circular-dependency-comparison.test.mts +2 -2
- package/src/core/class/create-child-observable.mts +27 -29
- package/src/core/class/create-root-observable.mts +11 -11
- package/src/core/class/manager-observable-class.mts +16 -9
- package/src/core/class/observable-base-class.mts +48 -30
- package/src/core/class/observable-node.mts +3 -4
- package/src/core/class/propagation-state.mts +31 -29
- package/src/core/class/remove-completed.test.mts +2 -2
- package/src/core/class/root-observable-class.mts +9 -9
- package/src/core/class/unobserved-write.test.mts +194 -0
- package/src/core/combine/derive.mts +10 -11
- package/src/core/combine/merge.mts +2 -2
- package/src/core/combine/zip.mts +7 -7
- package/src/core/create/counter.mts +5 -5
- package/src/core/create/from-abortable-promise.mts +2 -2
- package/src/core/create/from-promise.mts +2 -2
- package/src/core/create/from-subscribable.mts +2 -2
- package/src/core/create/source.mts +5 -5
- package/src/core/create/timer.mts +2 -2
- package/src/core/operators/audit.mts +2 -2
- package/src/core/operators/debounce.mts +2 -2
- package/src/core/operators/filter.mts +4 -4
- package/src/core/operators/keep-equal-parts.mts +16 -17
- package/src/core/operators/map.mts +2 -2
- package/src/core/operators/merge-map.mts +5 -5
- package/src/core/operators/pairwise.mts +4 -4
- package/src/core/operators/scan.mts +2 -2
- package/src/core/operators/skip-if-no-change.mts +4 -4
- package/src/core/operators/skip-until.mts +4 -4
- package/src/core/operators/skip-while.mts +2 -2
- package/src/core/operators/switch-map.mts +5 -5
- package/src/core/operators/take-until.mts +2 -2
- package/src/core/operators/take-while.mts +2 -2
- package/src/core/operators/take.mts +2 -2
- package/src/core/operators/throttle.mts +2 -2
- package/src/core/operators/with-buffered-from.mts +2 -2
- package/src/core/operators/with-current-value-from.mts +2 -2
- package/src/core/operators/with-initial-value.mts +2 -2
- package/src/core/predefined/operators/attach-index.mts +2 -2
- package/src/core/predefined/operators/map-to.mts +3 -4
- package/src/core/scope/scope.mts +6 -6
- package/src/core/types/id.mts +2 -1
- package/src/core/types/observable-family.mts +4 -5
- package/src/core/types/observable.mts +9 -8
- package/src/core/utils/current-scope.mts +8 -8
- package/src/core/utils/id-maker.mts +10 -0
- package/src/core/utils/observable-brand.mts +5 -5
- package/src/utils/create-boolean-state.mts +2 -2
- package/src/utils/create-reducer.mts +27 -42
- package/src/utils/create-state.mts +14 -31
- package/src/utils/create-state.test.mts +20 -2
- package/src/utils/state-source.mts +48 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReplaceEqualDeepOptions } from 'ts-data-forge';
|
|
2
2
|
import type { Reducer } from 'ts-type-forge';
|
|
3
|
-
import {
|
|
3
|
+
import type { InitializedObservable } from '../core/index.mjs';
|
|
4
|
+
import { createStateSource } from './state-source.mjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Creates a reducer-based state management container following the Redux pattern.
|
|
@@ -91,33 +92,17 @@ export const createReducer = <S, A>(
|
|
|
91
92
|
initialState: S;
|
|
92
93
|
}>,
|
|
93
94
|
] => {
|
|
94
|
-
const state =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const keepEqualParts = options?.keepEqualParts ?? false;
|
|
99
|
-
|
|
100
|
-
const keepOptions =
|
|
101
|
-
typeof keepEqualParts === 'boolean' ? undefined : keepEqualParts;
|
|
95
|
+
const { state, getSnapshot, commit } = createStateSource(
|
|
96
|
+
initialState,
|
|
97
|
+
options,
|
|
98
|
+
);
|
|
102
99
|
|
|
103
100
|
const dispatch = (action: A): S => {
|
|
104
|
-
const currentState =
|
|
101
|
+
const currentState = getSnapshot();
|
|
105
102
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const nextState =
|
|
109
|
-
keepEqualParts === false
|
|
110
|
-
? reduced
|
|
111
|
-
: replaceEqualDeep(currentState, reduced, keepOptions);
|
|
112
|
-
|
|
113
|
-
// Once completed, `next` keeps the state held and returns it.
|
|
114
|
-
return equals(currentState, nextState)
|
|
115
|
-
? currentState
|
|
116
|
-
: state.next(nextState);
|
|
103
|
+
return commit(currentState, reducer(currentState, action));
|
|
117
104
|
};
|
|
118
105
|
|
|
119
|
-
const getSnapshot = (): S => state.getSnapshot().value;
|
|
120
|
-
|
|
121
106
|
return [state, dispatch, { getSnapshot, initialState }] as const;
|
|
122
107
|
};
|
|
123
108
|
|
|
@@ -126,18 +111,18 @@ export const createReducer = <S, A>(
|
|
|
126
111
|
*/
|
|
127
112
|
export type StateOptions<S> = Readonly<{
|
|
128
113
|
/**
|
|
129
|
-
* Whether the next state is the same as the current one. When it says so,
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* identity: `setState` and the like return the current state rather than
|
|
133
|
-
*
|
|
114
|
+
* Whether the next state is the same as the current one. When it says so, the
|
|
115
|
+
* update is not passed on — no subscriber, and nothing derived from the
|
|
116
|
+
* state, is told of it — and the current state is kept, along with its
|
|
117
|
+
* identity: `setState` and the like return the current state rather than the
|
|
118
|
+
* next.
|
|
134
119
|
*
|
|
135
120
|
* Decide here, where the state is held, rather than downstream with
|
|
136
|
-
* `skipIfNoChange`: an update passed on here reaches whatever reads the
|
|
137
|
-
*
|
|
121
|
+
* `skipIfNoChange`: an update passed on here reaches whatever reads the state
|
|
122
|
+
* directly before any operator can thin it out.
|
|
138
123
|
*
|
|
139
|
-
* Defaults to `Object.is`, so writing the value already held passes
|
|
140
|
-
*
|
|
124
|
+
* Defaults to `Object.is`, so writing the value already held passes nothing
|
|
125
|
+
* on. Pass `() => false` to pass on every update, equal or not.
|
|
141
126
|
*
|
|
142
127
|
* @param current - The state held now
|
|
143
128
|
* @param next - The state the update would set
|
|
@@ -147,8 +132,8 @@ export type StateOptions<S> = Readonly<{
|
|
|
147
132
|
/**
|
|
148
133
|
* Whether an update keeps the parts of the state it did not change. With
|
|
149
134
|
* `true`, the next state is compared with the current one before it is
|
|
150
|
-
* written, and every part of it deeply equal to the same part of the
|
|
151
|
-
*
|
|
135
|
+
* written, and every part of it deeply equal to the same part of the current
|
|
136
|
+
* state is replaced by that part (`replaceEqualDeep` of
|
|
152
137
|
* ts-data-forge): a reader of an unchanged part keeps the object it has, so
|
|
153
138
|
* `map`, comparing with `Object.is`, stops there. When the whole next state is
|
|
154
139
|
* deeply equal, the current state is kept and nothing is passed on, so
|
|
@@ -161,14 +146,14 @@ export type StateOptions<S> = Readonly<{
|
|
|
161
146
|
* arrays are walked into; a `Map`, a `Set`, a `Date` or a class instance is
|
|
162
147
|
* compared and kept whole.
|
|
163
148
|
*
|
|
164
|
-
* Prefer this over `keepEqualParts()` downstream: keeping the parts where
|
|
165
|
-
*
|
|
166
|
-
*
|
|
149
|
+
* Prefer this over `keepEqualParts()` downstream: keeping the parts where the
|
|
150
|
+
* state is written means no update with nothing in it is passed on at all,
|
|
151
|
+
* and nothing downstream has anything to undo.
|
|
167
152
|
*
|
|
168
|
-
* Defaults to `false`: the next state is written as given. With it on,
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
153
|
+
* Defaults to `false`: the next state is written as given. With it on, each
|
|
154
|
+
* update compares deeply every part of the next state that is not the very
|
|
155
|
+
* object of the current one; a state whose updates already keep what they do
|
|
156
|
+
* not change, as `{ ...prev, x }` does, gains nothing from it.
|
|
172
157
|
*/
|
|
173
158
|
keepEqualParts?: boolean | ReplaceEqualDeepOptions;
|
|
174
159
|
}>;
|
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
import type { InitializedObservable } from '../core/index.mjs';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
type Action<S> = Readonly<
|
|
5
|
-
| {
|
|
6
|
-
type: 'set';
|
|
7
|
-
nextState: S;
|
|
8
|
-
}
|
|
9
|
-
| {
|
|
10
|
-
type: 'update';
|
|
11
|
-
updateFn: (a: S) => S;
|
|
12
|
-
}
|
|
13
|
-
>;
|
|
14
|
-
|
|
15
|
-
const reducer = <S,>(state: S, action: Action<S>): S => {
|
|
16
|
-
switch (action.type) {
|
|
17
|
-
case 'set':
|
|
18
|
-
return action.nextState;
|
|
19
|
-
|
|
20
|
-
case 'update':
|
|
21
|
-
return action.updateFn(state);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
2
|
+
import type { StateOptions } from './create-reducer.mjs';
|
|
3
|
+
import { createStateSource } from './state-source.mjs';
|
|
24
4
|
|
|
25
5
|
/**
|
|
26
6
|
* Creates a reactive state container with getter and setter methods.
|
|
@@ -127,8 +107,8 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
|
|
|
127
107
|
*
|
|
128
108
|
* assert.strictEqual(report.getSnapshot().value, held);
|
|
129
109
|
*
|
|
130
|
-
* // An edited title: a new report and a new array, while the summary is
|
|
131
|
-
* //
|
|
110
|
+
* // An edited title: a new report and a new array, while the summary is the
|
|
111
|
+
* // one held, so `summary` is not told of it.
|
|
132
112
|
* setReport({
|
|
133
113
|
* summary: { open: 1 },
|
|
134
114
|
* pulls: [{ number: 1, title: 'one (edited)' }],
|
|
@@ -161,19 +141,22 @@ export const createState = <S,>(
|
|
|
161
141
|
initialState: S;
|
|
162
142
|
}>,
|
|
163
143
|
] => {
|
|
164
|
-
const
|
|
165
|
-
reducer,
|
|
144
|
+
const { state, getSnapshot, commit } = createStateSource(
|
|
166
145
|
initialState,
|
|
167
146
|
options,
|
|
168
147
|
);
|
|
169
148
|
|
|
170
|
-
|
|
171
|
-
|
|
149
|
+
// Each compares with the state the write started from, as `dispatch` does,
|
|
150
|
+
// and builds nothing on the way.
|
|
151
|
+
const updateState = (updateFn: (prev: S) => S): S => {
|
|
152
|
+
const currentState = getSnapshot();
|
|
153
|
+
|
|
154
|
+
return commit(currentState, updateFn(currentState));
|
|
155
|
+
};
|
|
172
156
|
|
|
173
|
-
const setState = (nextState: S): S =>
|
|
157
|
+
const setState = (nextState: S): S => commit(getSnapshot(), nextState);
|
|
174
158
|
|
|
175
|
-
const resetState = (): S =>
|
|
176
|
-
dispatch({ type: 'set', nextState: initialState });
|
|
159
|
+
const resetState = (): S => commit(getSnapshot(), initialState);
|
|
177
160
|
|
|
178
161
|
return [
|
|
179
162
|
state,
|
|
@@ -54,8 +54,8 @@ describe(createState, () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// What `equals` is for: whatever reads the state, directly or through
|
|
57
|
-
// anything derived from it, is told only of a real change, and an equal
|
|
58
|
-
//
|
|
57
|
+
// anything derived from it, is told only of a real change, and an equal value
|
|
58
|
+
// written over the current one leaves the object that is there.
|
|
59
59
|
test('keeps the current object when `equals` finds the next one equal to it', () => {
|
|
60
60
|
const [state, setState, { getSnapshot }] = createState<readonly number[]>(
|
|
61
61
|
[1, 2],
|
|
@@ -77,6 +77,24 @@ describe(createState, () => {
|
|
|
77
77
|
assert.deepStrictEqual(seen, [held]);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
test('updateState compares the result with the state updateFn was given', () => {
|
|
81
|
+
const [state, setState, { updateState }] = createState(0);
|
|
82
|
+
|
|
83
|
+
const seen = record(state);
|
|
84
|
+
|
|
85
|
+
// updateFn writes the state itself, then returns what it was given:
|
|
86
|
+
// equal to that, so nothing more is written and 5 stays.
|
|
87
|
+
updateState((prev) => {
|
|
88
|
+
setState(5);
|
|
89
|
+
|
|
90
|
+
return prev;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
assert.deepStrictEqual(seen, [0, 5]);
|
|
94
|
+
|
|
95
|
+
assert.strictEqual(state.getSnapshot().value, 5);
|
|
96
|
+
});
|
|
97
|
+
|
|
80
98
|
describe('keepEqualParts', () => {
|
|
81
99
|
type Report = Readonly<{
|
|
82
100
|
summary: Readonly<{ open: number }>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { replaceEqualDeep } from 'ts-data-forge';
|
|
2
|
+
import { source, type InitializedSourceObservable } from '../core/index.mjs';
|
|
3
|
+
import type { StateOptions } from './create-reducer.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What `createState` and `createReducer` are both built on: the source holding
|
|
7
|
+
* the state, its current value, and `commit`, the one way either writes it.
|
|
8
|
+
* `commit` writes `reduced` unless `equals` finds it the same as
|
|
9
|
+
* `currentState`, after keeping the parts of `currentState` it did not change
|
|
10
|
+
* when `keepEqualParts` asks for that, and returns the state written, or
|
|
11
|
+
* `currentState` when nothing was.
|
|
12
|
+
*
|
|
13
|
+
* Not part of the package's API: `gen:index:src` leaves this file out of the
|
|
14
|
+
* barrel.
|
|
15
|
+
*/
|
|
16
|
+
export const createStateSource = <S,>(
|
|
17
|
+
initialState: S,
|
|
18
|
+
options: StateOptions<S> | undefined,
|
|
19
|
+
): Readonly<{
|
|
20
|
+
state: InitializedSourceObservable<S>;
|
|
21
|
+
getSnapshot: () => S;
|
|
22
|
+
commit: (currentState: S, reduced: S) => S;
|
|
23
|
+
}> => {
|
|
24
|
+
const state = source<S>(initialState);
|
|
25
|
+
|
|
26
|
+
const getSnapshot = (): S => state.getSnapshot().value;
|
|
27
|
+
|
|
28
|
+
const equals = options?.equals ?? Object.is;
|
|
29
|
+
|
|
30
|
+
const keepEqualParts = options?.keepEqualParts ?? false;
|
|
31
|
+
|
|
32
|
+
const keepOptions =
|
|
33
|
+
typeof keepEqualParts === 'boolean' ? undefined : keepEqualParts;
|
|
34
|
+
|
|
35
|
+
const commit = (currentState: S, reduced: S): S => {
|
|
36
|
+
const nextState =
|
|
37
|
+
keepEqualParts === false
|
|
38
|
+
? reduced
|
|
39
|
+
: replaceEqualDeep(currentState, reduced, keepOptions);
|
|
40
|
+
|
|
41
|
+
// Once completed, `next` keeps the state held and returns it.
|
|
42
|
+
return equals(currentState, nextState)
|
|
43
|
+
? currentState
|
|
44
|
+
: state.next(nextState);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return { state, getSnapshot, commit };
|
|
48
|
+
};
|