synstate 3.3.0 → 3.4.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.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/dist/core/base/create-child-observable.d.mts +9 -2
  3. package/dist/core/base/create-child-observable.d.mts.map +1 -1
  4. package/dist/core/base/create-child-observable.mjs +26 -8
  5. package/dist/core/base/create-child-observable.mjs.map +1 -1
  6. package/dist/core/base/create-observable-base.d.mts +2 -0
  7. package/dist/core/base/create-observable-base.d.mts.map +1 -1
  8. package/dist/core/base/create-observable-base.mjs +21 -3
  9. package/dist/core/base/create-observable-base.mjs.map +1 -1
  10. package/dist/core/base/create-root-observable.d.mts +9 -0
  11. package/dist/core/base/create-root-observable.d.mts.map +1 -1
  12. package/dist/core/base/create-root-observable.mjs +25 -2
  13. package/dist/core/base/create-root-observable.mjs.map +1 -1
  14. package/dist/core/combine/merge.d.mts +0 -3
  15. package/dist/core/combine/merge.d.mts.map +1 -1
  16. package/dist/core/combine/merge.mjs +0 -3
  17. package/dist/core/combine/merge.mjs.map +1 -1
  18. package/dist/core/combine/zip.d.mts +4 -2
  19. package/dist/core/combine/zip.d.mts.map +1 -1
  20. package/dist/core/combine/zip.mjs +21 -3
  21. package/dist/core/combine/zip.mjs.map +1 -1
  22. package/dist/core/create/counter.d.mts +9 -3
  23. package/dist/core/create/counter.d.mts.map +1 -1
  24. package/dist/core/create/counter.mjs +16 -3
  25. package/dist/core/create/counter.mjs.map +1 -1
  26. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  27. package/dist/core/create/from-subscribable.mjs +34 -11
  28. package/dist/core/create/from-subscribable.mjs.map +1 -1
  29. package/dist/core/create/source.d.mts +7 -0
  30. package/dist/core/create/source.d.mts.map +1 -1
  31. package/dist/core/create/source.mjs +6 -2
  32. package/dist/core/create/source.mjs.map +1 -1
  33. package/dist/core/operators/merge-map.d.mts +0 -3
  34. package/dist/core/operators/merge-map.d.mts.map +1 -1
  35. package/dist/core/operators/merge-map.mjs +0 -3
  36. package/dist/core/operators/merge-map.mjs.map +1 -1
  37. package/dist/core/operators/skip-until.d.mts.map +1 -1
  38. package/dist/core/operators/skip-until.mjs +15 -6
  39. package/dist/core/operators/skip-until.mjs.map +1 -1
  40. package/dist/core/operators/switch-map.d.mts +0 -3
  41. package/dist/core/operators/switch-map.d.mts.map +1 -1
  42. package/dist/core/operators/switch-map.mjs +0 -3
  43. package/dist/core/operators/switch-map.mjs.map +1 -1
  44. package/dist/core/operators/take-until.d.mts.map +1 -1
  45. package/dist/core/operators/take-until.mjs +13 -1
  46. package/dist/core/operators/take-until.mjs.map +1 -1
  47. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  48. package/dist/core/operators/with-buffered-from.mjs +12 -3
  49. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  50. package/dist/core/types/observable-family.d.mts +5 -1
  51. package/dist/core/types/observable-family.d.mts.map +1 -1
  52. package/dist/core/types/observable-family.mjs.map +1 -1
  53. package/dist/utils/create-event-emitter.d.mts +2 -2
  54. package/dist/utils/create-reducer.d.mts +10 -1
  55. package/dist/utils/create-reducer.d.mts.map +1 -1
  56. package/dist/utils/create-reducer.mjs +13 -5
  57. package/dist/utils/create-reducer.mjs.map +1 -1
  58. package/dist/utils/create-state.d.mts +7 -0
  59. package/dist/utils/create-state.d.mts.map +1 -1
  60. package/dist/utils/create-state.mjs +7 -0
  61. package/dist/utils/create-state.mjs.map +1 -1
  62. package/package.json +4 -4
  63. package/src/core/base/create-child-observable.mts +34 -6
  64. package/src/core/base/create-observable-base.mts +23 -3
  65. package/src/core/base/create-root-observable.mts +35 -1
  66. package/src/core/base/write-during-propagation.test.mts +234 -0
  67. package/src/core/combine/merge.mts +0 -3
  68. package/src/core/combine/zip.mts +36 -5
  69. package/src/core/create/counter.mts +27 -6
  70. package/src/core/create/counter.test.mts +82 -0
  71. package/src/core/create/from-subscribable.mts +33 -5
  72. package/src/core/create/from-subscribable.test.mts +129 -0
  73. package/src/core/create/source.mts +18 -3
  74. package/src/core/create/source.test.mts +41 -0
  75. package/src/core/operators/merge-map.mts +0 -3
  76. package/src/core/operators/skip-until.mts +21 -9
  77. package/src/core/operators/switch-map.mts +0 -3
  78. package/src/core/operators/take-until-skip-until-teardown.test.mts +127 -0
  79. package/src/core/operators/take-until.mts +15 -1
  80. package/src/core/operators/with-buffered-from.mts +15 -3
  81. package/src/core/operators/with-buffered-from.test.mts +56 -0
  82. package/src/core/types/observable-family.mts +5 -1
  83. package/src/utils/create-event-emitter.mts +2 -2
  84. package/src/utils/create-reducer.mts +14 -8
  85. package/src/utils/create-reducer.test.mts +12 -0
  86. package/src/utils/create-state.mts +7 -0
  87. package/src/utils/create-state.test.mts +20 -0
@@ -0,0 +1,56 @@
1
+ import { source } from '../create/index.mjs';
2
+ import { withBufferedFrom } from './with-buffered-from.mjs';
3
+
4
+ describe('withBufferedFrom teardown', () => {
5
+ test('unsubscribes from the buffered observable on complete', () => {
6
+ const data$ = source<string>();
7
+
8
+ const trigger$ = source<number>();
9
+
10
+ const result$ = trigger$.pipe(withBufferedFrom(data$));
11
+
12
+ assert.isTrue(data$.hasSubscriber);
13
+
14
+ result$.complete();
15
+
16
+ assert.isTrue(result$.isCompleted);
17
+
18
+ assert.isFalse(data$.hasSubscriber);
19
+ });
20
+
21
+ test('unsubscribes from the buffered observable when the parent completes', () => {
22
+ const data$ = source<string>();
23
+
24
+ const trigger$ = source<number>();
25
+
26
+ const result$ = trigger$.pipe(withBufferedFrom(data$));
27
+
28
+ trigger$.complete();
29
+
30
+ assert.isTrue(result$.isCompleted);
31
+
32
+ assert.isFalse(data$.hasSubscriber);
33
+ });
34
+
35
+ test('keeps buffering while subscribed', () => {
36
+ const data$ = source<string>();
37
+
38
+ const trigger$ = source<number>();
39
+
40
+ const result$ = trigger$.pipe(withBufferedFrom(data$));
41
+
42
+ const mut_history: (readonly [number, readonly string[]])[] = [];
43
+
44
+ result$.subscribe((v) => {
45
+ mut_history.push(v);
46
+ });
47
+
48
+ data$.next('A');
49
+
50
+ data$.next('B');
51
+
52
+ trigger$.next(1);
53
+
54
+ assert.deepStrictEqual(mut_history, [[1, ['A', 'B']]]);
55
+ });
56
+ });
@@ -20,7 +20,11 @@ import type { NonEmptyUnknownList } from './types.mjs';
20
20
  // RootObservable
21
21
 
22
22
  export type InitializedSourceObservable<A> = Readonly<{
23
- next: (value: A) => void;
23
+ /**
24
+ * Passes `value` on and returns it. Once completed, passes nothing on and
25
+ * returns the value held.
26
+ */
27
+ next: (value: A) => A;
24
28
  }> &
25
29
  InitializedRootObservable<A>;
26
30
 
@@ -18,13 +18,13 @@ import { source, type Observable } from '../core/index.mjs';
18
18
  *
19
19
  * emitClick(); // logs: Clicked!
20
20
  *
21
- * assert.deepStrictEqual(mut_clickCount.value, 1);
21
+ * assert.strictEqual(mut_clickCount.value, 1);
22
22
  *
23
23
  * emitClick();
24
24
  *
25
25
  * emitClick();
26
26
  *
27
- * assert.deepStrictEqual(mut_clickCount.value, 3);
27
+ * assert.strictEqual(mut_clickCount.value, 3);
28
28
  * ```
29
29
  */
30
30
  export const createEventEmitter = (): readonly [
@@ -11,7 +11,9 @@ import { source, type InitializedObservable } from '../core/index.mjs';
11
11
  * @param initialState - The initial value of the state
12
12
  * @param options - {@link StateOptions}: `equals`, to pass on only the
13
13
  * dispatches that change the state
14
- * @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`
14
+ * @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`.
15
+ * `dispatch` returns the state it leaves; once `state` is completed, that
16
+ * is the state held.
15
17
  *
16
18
  * @example
17
19
  * ```ts
@@ -67,6 +69,13 @@ import { source, type InitializedObservable } from '../core/index.mjs';
67
69
  *
68
70
  * assert.deepStrictEqual(countHistory, [0, 2]);
69
71
  * ```
72
+ *
73
+ * @note Do not call `dispatch` from a subscriber or a projection while another
74
+ * update is propagating: the write propagates immediately, so subscribers can
75
+ * see an intermediate state, and it logs a `console.warn` (once per
76
+ * observable). Derive the value from its source instead —
77
+ * `const b$ = a$.pipe(map((a) => a * 2))` rather than
78
+ * `a$.subscribe((a) => { setB(a * 2); })`.
70
79
  */
71
80
  export const createReducer = <S, A>(
72
81
  reducer: Reducer<S, A>,
@@ -89,13 +98,10 @@ export const createReducer = <S, A>(
89
98
 
90
99
  const nextState = reducer(currentState, action);
91
100
 
92
- if (equals?.(currentState, nextState) === true) {
93
- return currentState;
94
- }
95
-
96
- state.next(nextState);
97
-
98
- return nextState;
101
+ // Once completed, `next` keeps the state held and returns it.
102
+ return equals?.(currentState, nextState) === true
103
+ ? currentState
104
+ : state.next(nextState);
99
105
  };
100
106
 
101
107
  const getSnapshot = (): S => state.getSnapshot().value;
@@ -65,6 +65,18 @@ describe(createReducer, () => {
65
65
 
66
66
  assert.deepStrictEqual(mut_asked, [[0, 5]]);
67
67
  });
68
+
69
+ test('once the state is completed, a dispatch returns the current state', () => {
70
+ const [state, dispatch, { getSnapshot }] = createReducer(keepOrAdd, 0);
71
+
72
+ dispatch({ type: 'add', amount: 1 });
73
+
74
+ state.complete();
75
+
76
+ assert.strictEqual(dispatch({ type: 'add', amount: 2 }), 1);
77
+
78
+ assert.strictEqual(getSnapshot(), 1);
79
+ });
68
80
  });
69
81
 
70
82
  type Count = Readonly<{ count: number }>;
@@ -88,6 +88,13 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
88
88
  * 'label:bug page:2',
89
89
  * ]);
90
90
  * ```
91
+ *
92
+ * @note Do not call `setState`, `updateState` or `resetState` from a subscriber or a projection while another
93
+ * update is propagating: the write propagates immediately, so subscribers can
94
+ * see an intermediate state, and it logs a `console.warn` (once per
95
+ * observable). Derive the value from its source instead —
96
+ * `const b$ = a$.pipe(map((a) => a * 2))` rather than
97
+ * `a$.subscribe((a) => { setB(a * 2); })`.
91
98
  */
92
99
  export const createState = <S,>(
93
100
  initialState: S,
@@ -34,6 +34,26 @@ describe(createState, () => {
34
34
  assert.deepStrictEqual(seen, [0, 1]);
35
35
  });
36
36
 
37
+ test('once the state is completed, setState, updateState and resetState return the current value', () => {
38
+ const [state, setState, { updateState, resetState, getSnapshot }] =
39
+ createState(0);
40
+
41
+ setState(1);
42
+
43
+ state.complete();
44
+
45
+ assert.strictEqual(setState(2), 1);
46
+
47
+ assert.strictEqual(
48
+ updateState((current) => current + 1),
49
+ 1,
50
+ );
51
+
52
+ assert.strictEqual(resetState(), 1);
53
+
54
+ assert.strictEqual(getSnapshot(), 1);
55
+ });
56
+
37
57
  // What `equals` is for: whatever reads the state, directly or through
38
58
  // anything derived from it, is told only of a real change, and an equal
39
59
  // value written over the current one leaves the object that is there.