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.
Files changed (151) hide show
  1. package/README.md +4 -4
  2. package/dist/core/authoring.d.mts +3 -3
  3. package/dist/core/class/child-observable-class.d.mts +24 -25
  4. package/dist/core/class/child-observable-class.d.mts.map +1 -1
  5. package/dist/core/class/child-observable-class.mjs +41 -42
  6. package/dist/core/class/child-observable-class.mjs.map +1 -1
  7. package/dist/core/class/create-child-observable.d.mts +27 -29
  8. package/dist/core/class/create-child-observable.d.mts.map +1 -1
  9. package/dist/core/class/create-child-observable.mjs.map +1 -1
  10. package/dist/core/class/create-root-observable.d.mts +11 -11
  11. package/dist/core/class/manager-observable-class.d.mts +6 -6
  12. package/dist/core/class/manager-observable-class.d.mts.map +1 -1
  13. package/dist/core/class/manager-observable-class.mjs +6 -0
  14. package/dist/core/class/manager-observable-class.mjs.map +1 -1
  15. package/dist/core/class/observable-base-class.d.mts +32 -26
  16. package/dist/core/class/observable-base-class.d.mts.map +1 -1
  17. package/dist/core/class/observable-base-class.mjs +16 -3
  18. package/dist/core/class/observable-base-class.mjs.map +1 -1
  19. package/dist/core/class/observable-node.d.mts +3 -4
  20. package/dist/core/class/observable-node.d.mts.map +1 -1
  21. package/dist/core/class/observable-node.mjs +0 -1
  22. package/dist/core/class/observable-node.mjs.map +1 -1
  23. package/dist/core/class/propagation-state.d.mts +10 -9
  24. package/dist/core/class/propagation-state.d.mts.map +1 -1
  25. package/dist/core/class/propagation-state.mjs +4 -2
  26. package/dist/core/class/propagation-state.mjs.map +1 -1
  27. package/dist/core/class/root-observable-class.d.mts +4 -4
  28. package/dist/core/combine/derive.d.mts +7 -8
  29. package/dist/core/combine/derive.d.mts.map +1 -1
  30. package/dist/core/combine/derive.mjs +0 -1
  31. package/dist/core/combine/derive.mjs.map +1 -1
  32. package/dist/core/combine/merge.mjs.map +1 -1
  33. package/dist/core/combine/zip.d.mts +3 -3
  34. package/dist/core/combine/zip.mjs.map +1 -1
  35. package/dist/core/create/counter.d.mts +3 -3
  36. package/dist/core/create/counter.mjs.map +1 -1
  37. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  38. package/dist/core/create/from-promise.mjs.map +1 -1
  39. package/dist/core/create/from-subscribable.mjs.map +1 -1
  40. package/dist/core/create/source.mjs.map +1 -1
  41. package/dist/core/create/timer.mjs.map +1 -1
  42. package/dist/core/operators/audit.mjs.map +1 -1
  43. package/dist/core/operators/debounce.mjs.map +1 -1
  44. package/dist/core/operators/filter.mjs.map +1 -1
  45. package/dist/core/operators/keep-equal-parts.d.mts +14 -15
  46. package/dist/core/operators/keep-equal-parts.d.mts.map +1 -1
  47. package/dist/core/operators/keep-equal-parts.mjs +0 -1
  48. package/dist/core/operators/keep-equal-parts.mjs.map +1 -1
  49. package/dist/core/operators/map.mjs.map +1 -1
  50. package/dist/core/operators/merge-map.d.mts +3 -3
  51. package/dist/core/operators/merge-map.mjs.map +1 -1
  52. package/dist/core/operators/pairwise.mjs.map +1 -1
  53. package/dist/core/operators/scan.mjs.map +1 -1
  54. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  55. package/dist/core/operators/skip-until.mjs.map +1 -1
  56. package/dist/core/operators/skip-while.mjs.map +1 -1
  57. package/dist/core/operators/switch-map.mjs.map +1 -1
  58. package/dist/core/operators/take-until.mjs.map +1 -1
  59. package/dist/core/operators/take-while.mjs.map +1 -1
  60. package/dist/core/operators/take.mjs.map +1 -1
  61. package/dist/core/operators/throttle.mjs.map +1 -1
  62. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  63. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  64. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  65. package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
  66. package/dist/core/predefined/operators/map-to.d.mts +3 -4
  67. package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
  68. package/dist/core/predefined/operators/map-to.mjs +0 -1
  69. package/dist/core/predefined/operators/map-to.mjs.map +1 -1
  70. package/dist/core/scope/scope.d.mts +4 -4
  71. package/dist/core/types/id.d.mts +2 -1
  72. package/dist/core/types/id.d.mts.map +1 -1
  73. package/dist/core/types/observable-family.d.mts.map +1 -1
  74. package/dist/core/types/observable-family.mjs.map +1 -1
  75. package/dist/core/types/observable.d.mts +7 -6
  76. package/dist/core/types/observable.d.mts.map +1 -1
  77. package/dist/core/types/observable.mjs.map +1 -1
  78. package/dist/core/utils/current-scope.d.mts +6 -6
  79. package/dist/core/utils/id-maker.d.mts +7 -0
  80. package/dist/core/utils/id-maker.d.mts.map +1 -1
  81. package/dist/core/utils/id-maker.mjs +9 -0
  82. package/dist/core/utils/id-maker.mjs.map +1 -1
  83. package/dist/core/utils/observable-brand.d.mts +5 -5
  84. package/dist/utils/create-boolean-state.d.mts +2 -2
  85. package/dist/utils/create-reducer.d.mts +20 -20
  86. package/dist/utils/create-reducer.d.mts.map +1 -1
  87. package/dist/utils/create-reducer.mjs +4 -16
  88. package/dist/utils/create-reducer.mjs.map +1 -1
  89. package/dist/utils/create-state.d.mts +3 -3
  90. package/dist/utils/create-state.d.mts.map +1 -1
  91. package/dist/utils/create-state.mjs +10 -13
  92. package/dist/utils/create-state.mjs.map +1 -1
  93. package/dist/utils/state-source.d.mts +19 -0
  94. package/dist/utils/state-source.d.mts.map +1 -0
  95. package/dist/utils/state-source.mjs +31 -0
  96. package/dist/utils/state-source.mjs.map +1 -0
  97. package/package.json +10 -10
  98. package/src/core/authoring.mts +3 -3
  99. package/src/core/class/child-observable-class.mts +86 -87
  100. package/src/core/class/circular-dependency-comparison.test.mts +2 -2
  101. package/src/core/class/create-child-observable.mts +27 -29
  102. package/src/core/class/create-root-observable.mts +11 -11
  103. package/src/core/class/manager-observable-class.mts +16 -9
  104. package/src/core/class/observable-base-class.mts +48 -30
  105. package/src/core/class/observable-node.mts +3 -4
  106. package/src/core/class/propagation-state.mts +31 -29
  107. package/src/core/class/remove-completed.test.mts +2 -2
  108. package/src/core/class/root-observable-class.mts +9 -9
  109. package/src/core/class/unobserved-write.test.mts +194 -0
  110. package/src/core/combine/derive.mts +10 -11
  111. package/src/core/combine/merge.mts +2 -2
  112. package/src/core/combine/zip.mts +7 -7
  113. package/src/core/create/counter.mts +5 -5
  114. package/src/core/create/from-abortable-promise.mts +2 -2
  115. package/src/core/create/from-promise.mts +2 -2
  116. package/src/core/create/from-subscribable.mts +2 -2
  117. package/src/core/create/source.mts +5 -5
  118. package/src/core/create/timer.mts +2 -2
  119. package/src/core/operators/audit.mts +2 -2
  120. package/src/core/operators/debounce.mts +2 -2
  121. package/src/core/operators/filter.mts +4 -4
  122. package/src/core/operators/keep-equal-parts.mts +16 -17
  123. package/src/core/operators/map.mts +2 -2
  124. package/src/core/operators/merge-map.mts +5 -5
  125. package/src/core/operators/pairwise.mts +4 -4
  126. package/src/core/operators/scan.mts +2 -2
  127. package/src/core/operators/skip-if-no-change.mts +4 -4
  128. package/src/core/operators/skip-until.mts +4 -4
  129. package/src/core/operators/skip-while.mts +2 -2
  130. package/src/core/operators/switch-map.mts +5 -5
  131. package/src/core/operators/take-until.mts +2 -2
  132. package/src/core/operators/take-while.mts +2 -2
  133. package/src/core/operators/take.mts +2 -2
  134. package/src/core/operators/throttle.mts +2 -2
  135. package/src/core/operators/with-buffered-from.mts +2 -2
  136. package/src/core/operators/with-current-value-from.mts +2 -2
  137. package/src/core/operators/with-initial-value.mts +2 -2
  138. package/src/core/predefined/operators/attach-index.mts +2 -2
  139. package/src/core/predefined/operators/map-to.mts +3 -4
  140. package/src/core/scope/scope.mts +6 -6
  141. package/src/core/types/id.mts +2 -1
  142. package/src/core/types/observable-family.mts +4 -5
  143. package/src/core/types/observable.mts +9 -8
  144. package/src/core/utils/current-scope.mts +8 -8
  145. package/src/core/utils/id-maker.mts +10 -0
  146. package/src/core/utils/observable-brand.mts +5 -5
  147. package/src/utils/create-boolean-state.mts +2 -2
  148. package/src/utils/create-reducer.mts +27 -42
  149. package/src/utils/create-state.mts +14 -31
  150. package/src/utils/create-state.test.mts +20 -2
  151. package/src/utils/state-source.mts +48 -0
@@ -1,6 +1,7 @@
1
- import { replaceEqualDeep, type ReplaceEqualDeepOptions } from 'ts-data-forge';
1
+ import type { ReplaceEqualDeepOptions } from 'ts-data-forge';
2
2
  import type { Reducer } from 'ts-type-forge';
3
- import { source, type InitializedObservable } from '../core/index.mjs';
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 = source<S>(initialState);
95
-
96
- const equals = options?.equals ?? Object.is;
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 = state.getSnapshot().value;
101
+ const currentState = getSnapshot();
105
102
 
106
- const reduced = reducer(currentState, action);
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
- * the update is not passed on — no subscriber, and nothing derived from
131
- * the state, is told of it — and the current state is kept, along with its
132
- * identity: `setState` and the like return the current state rather than
133
- * the next.
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
- * state directly before any operator can thin it out.
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
- * nothing on. Pass `() => false` to pass on every update, equal or not.
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
- * current state is replaced by that part (`replaceEqualDeep` of
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
- * the state is written means no update with nothing in it is passed on at
166
- * all, and nothing downstream has anything to undo.
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
- * each update compares deeply every part of the next state that is not
170
- * the very object of the current one; a state whose updates already keep
171
- * what they do not change, as `{ ...prev, x }` does, gains nothing from it.
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 { createReducer, type StateOptions } from './create-reducer.mjs';
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
- * // the one held, so `summary` is not told of it.
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 [state, dispatch, { getSnapshot }] = createReducer<S, Action<S>>(
165
- reducer,
144
+ const { state, getSnapshot, commit } = createStateSource(
166
145
  initialState,
167
146
  options,
168
147
  );
169
148
 
170
- const updateState = (updateFn: (prev: S) => S): S =>
171
- dispatch({ type: 'update', updateFn });
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 => dispatch({ type: 'set', nextState });
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
- // value written over the current one leaves the object that is there.
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
+ };