synstate 4.0.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/create/empty.d.mts +32 -0
- package/dist/core/create/empty.d.mts.map +1 -0
- package/dist/core/create/empty.mjs +39 -0
- package/dist/core/create/empty.mjs.map +1 -0
- package/dist/core/create/index.d.mts +1 -0
- package/dist/core/create/index.d.mts.map +1 -1
- package/dist/core/create/index.mjs +1 -0
- package/dist/core/create/index.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 +10 -10
- 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/create/empty.mts +42 -0
- package/src/core/create/empty.test.mts +65 -0
- package/src/core/create/index.mts +1 -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
|
@@ -29,7 +29,8 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
|
|
|
29
29
|
* @template S - The type of the state
|
|
30
30
|
* @param initialState - The initial value of the state
|
|
31
31
|
* @param options - {@link StateOptions}: `equals` (default `Object.is`),
|
|
32
|
-
* to pass on only the updates that change the state
|
|
32
|
+
* to pass on only the updates that change the state, and `keepEqualParts`,
|
|
33
|
+
* to keep the parts of the state an update did not change
|
|
33
34
|
* @returns A 3-element tuple: `[state, setState, { updateState, resetState, getSnapshot, initialState }]`
|
|
34
35
|
*
|
|
35
36
|
* @example
|
|
@@ -89,6 +90,57 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
|
|
|
89
90
|
* ]);
|
|
90
91
|
* ```
|
|
91
92
|
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* type PullReport = DeepReadonly<{
|
|
96
|
+
* summary: { open: number };
|
|
97
|
+
* pulls: { number: number; title: string }[];
|
|
98
|
+
* }>;
|
|
99
|
+
*
|
|
100
|
+
* const [report, setReport] = createState<PullReport>(
|
|
101
|
+
* { summary: { open: 0 }, pulls: [] },
|
|
102
|
+
* { keepEqualParts: true },
|
|
103
|
+
* );
|
|
104
|
+
*
|
|
105
|
+
* const summary = report.pipe(map((r) => r.summary)).pipe(skipIfNoChange());
|
|
106
|
+
*
|
|
107
|
+
* const summaryHistory: unknown[] = [];
|
|
108
|
+
*
|
|
109
|
+
* summary.subscribe((value) => {
|
|
110
|
+
* summaryHistory.push(value);
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* // A server answers each poll with the whole report, a new object every
|
|
114
|
+
* // time. What did not change keeps the object already held.
|
|
115
|
+
* setReport({
|
|
116
|
+
* summary: { open: 1 },
|
|
117
|
+
* pulls: [{ number: 1, title: 'one' }],
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* const held = report.getSnapshot().value;
|
|
121
|
+
*
|
|
122
|
+
* // An answer that changes nothing is not passed on at all.
|
|
123
|
+
* setReport({
|
|
124
|
+
* summary: { open: 1 },
|
|
125
|
+
* pulls: [{ number: 1, title: 'one' }],
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* assert.strictEqual(report.getSnapshot().value, held);
|
|
129
|
+
*
|
|
130
|
+
* // An edited title: a new report and a new array, while the summary is
|
|
131
|
+
* // the one held, so `summary` is not told of it.
|
|
132
|
+
* setReport({
|
|
133
|
+
* summary: { open: 1 },
|
|
134
|
+
* pulls: [{ number: 1, title: 'one (edited)' }],
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* assert.notStrictEqual(report.getSnapshot().value, held);
|
|
138
|
+
*
|
|
139
|
+
* assert.strictEqual(report.getSnapshot().value.summary, held.summary);
|
|
140
|
+
*
|
|
141
|
+
* assert.deepStrictEqual(summaryHistory, [{ open: 0 }, { open: 1 }]);
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
92
144
|
* @note Do not call `setState`, `updateState` or `resetState` from a subscriber or a projection while another
|
|
93
145
|
* update is propagating: the write propagates immediately, so subscribers can
|
|
94
146
|
* see an intermediate state, and it logs a `console.warn` (once per
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasKey, isRecord } from 'ts-data-forge';
|
|
1
2
|
import { createState } from './create-state.mjs';
|
|
2
3
|
|
|
3
4
|
describe(createState, () => {
|
|
@@ -75,6 +76,93 @@ describe(createState, () => {
|
|
|
75
76
|
|
|
76
77
|
assert.deepStrictEqual(seen, [held]);
|
|
77
78
|
});
|
|
79
|
+
|
|
80
|
+
describe('keepEqualParts', () => {
|
|
81
|
+
type Report = Readonly<{
|
|
82
|
+
summary: Readonly<{ open: number }>;
|
|
83
|
+
entries: readonly Readonly<{ number: number; title: string }>[];
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
const initial: Report = {
|
|
87
|
+
summary: { open: 1 },
|
|
88
|
+
entries: [{ number: 1, title: 'a' }],
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
test('passes nothing on, and keeps the current object, when the next state is deeply equal', () => {
|
|
92
|
+
const [state, setState, { getSnapshot }] = createState(initial, {
|
|
93
|
+
keepEqualParts: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const seen = record(state);
|
|
97
|
+
|
|
98
|
+
assert.strictEqual(
|
|
99
|
+
setState({
|
|
100
|
+
summary: { open: 1 },
|
|
101
|
+
entries: [{ number: 1, title: 'a' }],
|
|
102
|
+
}),
|
|
103
|
+
initial,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
assert.strictEqual(getSnapshot(), initial);
|
|
107
|
+
|
|
108
|
+
assert.deepStrictEqual(seen, [initial]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('writes a new state whose unchanged branches are the current ones', () => {
|
|
112
|
+
const [state, setState] = createState(initial, { keepEqualParts: true });
|
|
113
|
+
|
|
114
|
+
const seen = record(state);
|
|
115
|
+
|
|
116
|
+
const written = setState({
|
|
117
|
+
summary: { open: 1 },
|
|
118
|
+
entries: [
|
|
119
|
+
{ number: 1, title: 'a' },
|
|
120
|
+
{ number: 2, title: 'b' },
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
assert.deepStrictEqual(seen, [initial, written]);
|
|
125
|
+
|
|
126
|
+
assert.strictEqual(written.summary, initial.summary);
|
|
127
|
+
|
|
128
|
+
assert.strictEqual(written.entries[0], initial.entries[0]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('applies to `updateState` and matches items by `key`', () => {
|
|
132
|
+
const [state, , { updateState }] = createState(initial, {
|
|
133
|
+
keepEqualParts: {
|
|
134
|
+
key: (item) =>
|
|
135
|
+
isRecord(item) && hasKey(item, 'number') ? item.number : undefined,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// A whole new object, as a server answer would be.
|
|
140
|
+
const written = updateState(() => ({
|
|
141
|
+
summary: { open: 1 },
|
|
142
|
+
entries: [
|
|
143
|
+
{ number: 0, title: 'new' },
|
|
144
|
+
{ number: 1, title: 'a' },
|
|
145
|
+
],
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
assert.strictEqual(written.summary, initial.summary);
|
|
149
|
+
|
|
150
|
+
assert.strictEqual(written.entries[1], initial.entries[0]);
|
|
151
|
+
|
|
152
|
+
assert.strictEqual(state.getSnapshot().value, written);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('is off by default', () => {
|
|
156
|
+
const [, setState] = createState(initial);
|
|
157
|
+
|
|
158
|
+
const next: Report = {
|
|
159
|
+
summary: { open: 1 },
|
|
160
|
+
entries: [{ number: 1, title: 'a' }],
|
|
161
|
+
} as const;
|
|
162
|
+
|
|
163
|
+
assert.strictEqual(setState(next), next);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
78
166
|
});
|
|
79
167
|
|
|
80
168
|
const record = <A,>(
|