synstate 6.1.1 → 7.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.
Files changed (189) hide show
  1. package/README.md +3 -3
  2. package/dist/core/authoring.d.mts +3 -3
  3. package/dist/core/class/child-observable-class.d.mts +40 -27
  4. package/dist/core/class/child-observable-class.d.mts.map +1 -1
  5. package/dist/core/class/child-observable-class.mjs +78 -46
  6. package/dist/core/class/child-observable-class.mjs.map +1 -1
  7. package/dist/core/class/create-child-observable.d.mts +28 -30
  8. package/dist/core/class/create-child-observable.d.mts.map +1 -1
  9. package/dist/core/class/create-child-observable.mjs +6 -8
  10. package/dist/core/class/create-child-observable.mjs.map +1 -1
  11. package/dist/core/class/create-root-observable.d.mts +12 -12
  12. package/dist/core/class/create-root-observable.d.mts.map +1 -1
  13. package/dist/core/class/create-root-observable.mjs +1 -5
  14. package/dist/core/class/create-root-observable.mjs.map +1 -1
  15. package/dist/core/class/manager-observable-class.d.mts +6 -6
  16. package/dist/core/class/manager-observable-class.mjs.map +1 -1
  17. package/dist/core/class/observable-base-class.d.mts +29 -31
  18. package/dist/core/class/observable-base-class.d.mts.map +1 -1
  19. package/dist/core/class/observable-base-class.mjs +0 -2
  20. package/dist/core/class/observable-base-class.mjs.map +1 -1
  21. package/dist/core/class/observable-node.d.mts +3 -4
  22. package/dist/core/class/observable-node.d.mts.map +1 -1
  23. package/dist/core/class/observable-node.mjs +0 -1
  24. package/dist/core/class/observable-node.mjs.map +1 -1
  25. package/dist/core/class/propagation-state.d.mts +6 -8
  26. package/dist/core/class/propagation-state.d.mts.map +1 -1
  27. package/dist/core/class/propagation-state.mjs +1 -3
  28. package/dist/core/class/propagation-state.mjs.map +1 -1
  29. package/dist/core/class/root-observable-class.d.mts +4 -4
  30. package/dist/core/combine/derive.d.mts +7 -8
  31. package/dist/core/combine/derive.d.mts.map +1 -1
  32. package/dist/core/combine/derive.mjs +2 -2
  33. package/dist/core/combine/derive.mjs.map +1 -1
  34. package/dist/core/combine/merge.mjs.map +1 -1
  35. package/dist/core/combine/zip.d.mts +3 -3
  36. package/dist/core/combine/zip.d.mts.map +1 -1
  37. package/dist/core/combine/zip.mjs +5 -2
  38. package/dist/core/combine/zip.mjs.map +1 -1
  39. package/dist/core/create/counter.d.mts +3 -3
  40. package/dist/core/create/counter.mjs +2 -2
  41. package/dist/core/create/counter.mjs.map +1 -1
  42. package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
  43. package/dist/core/create/from-abortable-promise.mjs +10 -3
  44. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  45. package/dist/core/create/from-promise.d.mts.map +1 -1
  46. package/dist/core/create/from-promise.mjs +10 -3
  47. package/dist/core/create/from-promise.mjs.map +1 -1
  48. package/dist/core/create/from-subscribable.mjs.map +1 -1
  49. package/dist/core/create/source.d.mts.map +1 -1
  50. package/dist/core/create/source.mjs +2 -1
  51. package/dist/core/create/source.mjs.map +1 -1
  52. package/dist/core/create/timer.mjs.map +1 -1
  53. package/dist/core/operators/audit.mjs.map +1 -1
  54. package/dist/core/operators/debounce.mjs.map +1 -1
  55. package/dist/core/operators/filter.d.mts +1 -1
  56. package/dist/core/operators/filter.d.mts.map +1 -1
  57. package/dist/core/operators/filter.mjs +2 -3
  58. package/dist/core/operators/filter.mjs.map +1 -1
  59. package/dist/core/operators/keep-equal-parts.d.mts +14 -15
  60. package/dist/core/operators/keep-equal-parts.d.mts.map +1 -1
  61. package/dist/core/operators/keep-equal-parts.mjs +0 -1
  62. package/dist/core/operators/keep-equal-parts.mjs.map +1 -1
  63. package/dist/core/operators/map.mjs.map +1 -1
  64. package/dist/core/operators/merge-map.d.mts +7 -3
  65. package/dist/core/operators/merge-map.d.mts.map +1 -1
  66. package/dist/core/operators/merge-map.mjs +47 -16
  67. package/dist/core/operators/merge-map.mjs.map +1 -1
  68. package/dist/core/operators/pairwise.mjs.map +1 -1
  69. package/dist/core/operators/scan.mjs.map +1 -1
  70. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  71. package/dist/core/operators/skip-until.mjs.map +1 -1
  72. package/dist/core/operators/skip-while.d.mts +1 -1
  73. package/dist/core/operators/skip-while.d.mts.map +1 -1
  74. package/dist/core/operators/skip-while.mjs +9 -6
  75. package/dist/core/operators/skip-while.mjs.map +1 -1
  76. package/dist/core/operators/switch-map.d.mts +4 -0
  77. package/dist/core/operators/switch-map.d.mts.map +1 -1
  78. package/dist/core/operators/switch-map.mjs +16 -6
  79. package/dist/core/operators/switch-map.mjs.map +1 -1
  80. package/dist/core/operators/take-until.mjs.map +1 -1
  81. package/dist/core/operators/take-while.d.mts +2 -2
  82. package/dist/core/operators/take-while.d.mts.map +1 -1
  83. package/dist/core/operators/take-while.mjs +10 -8
  84. package/dist/core/operators/take-while.mjs.map +1 -1
  85. package/dist/core/operators/take.mjs.map +1 -1
  86. package/dist/core/operators/throttle.mjs +3 -2
  87. package/dist/core/operators/throttle.mjs.map +1 -1
  88. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  89. package/dist/core/operators/with-buffered-from.mjs +13 -3
  90. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  91. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  92. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  93. package/dist/core/predefined/operators/attach-index.d.mts +1 -1
  94. package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
  95. package/dist/core/predefined/operators/attach-index.mjs +2 -3
  96. package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
  97. package/dist/core/predefined/operators/map-to.d.mts +3 -4
  98. package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
  99. package/dist/core/predefined/operators/map-to.mjs +0 -1
  100. package/dist/core/predefined/operators/map-to.mjs.map +1 -1
  101. package/dist/core/predefined/operators/skip.d.mts.map +1 -1
  102. package/dist/core/predefined/operators/skip.mjs +2 -2
  103. package/dist/core/predefined/operators/skip.mjs.map +1 -1
  104. package/dist/core/scope/scope.d.mts +4 -4
  105. package/dist/core/types/observable-family.d.mts.map +1 -1
  106. package/dist/core/types/observable-family.mjs.map +1 -1
  107. package/dist/core/types/observable.d.mts +5 -5
  108. package/dist/core/types/observable.mjs.map +1 -1
  109. package/dist/core/utils/current-scope.d.mts +6 -6
  110. package/dist/core/utils/id-maker.d.mts +2 -2
  111. package/dist/core/utils/index.d.mts +1 -0
  112. package/dist/core/utils/index.d.mts.map +1 -1
  113. package/dist/core/utils/index.mjs +1 -0
  114. package/dist/core/utils/index.mjs.map +1 -1
  115. package/dist/core/utils/observable-brand.d.mts +5 -5
  116. package/dist/core/utils/rethrow-out-of-promise.d.mts +10 -0
  117. package/dist/core/utils/rethrow-out-of-promise.d.mts.map +1 -0
  118. package/dist/core/utils/rethrow-out-of-promise.mjs +17 -0
  119. package/dist/core/utils/rethrow-out-of-promise.mjs.map +1 -0
  120. package/dist/utils/create-boolean-state.d.mts +2 -2
  121. package/dist/utils/create-reducer.d.mts +18 -18
  122. package/dist/utils/create-state.d.mts +2 -2
  123. package/dist/utils/create-state.mjs.map +1 -1
  124. package/dist/utils/state-source.d.mts +3 -3
  125. package/package.json +12 -11
  126. package/src/core/authoring.mts +3 -3
  127. package/src/core/class/child-observable-class.mts +131 -90
  128. package/src/core/class/circular-dependency-comparison.test.mts +2 -2
  129. package/src/core/class/create-child-observable.mts +35 -34
  130. package/src/core/class/create-root-observable.mts +13 -12
  131. package/src/core/class/manager-observable-class.mts +12 -12
  132. package/src/core/class/observable-base-class.mts +33 -35
  133. package/src/core/class/observable-node.mts +3 -4
  134. package/src/core/class/propagation-state.mts +27 -29
  135. package/src/core/class/remove-completed.test.mts +2 -2
  136. package/src/core/class/root-observable-class.mts +9 -9
  137. package/src/core/class/unobserved-write.test.mts +4 -4
  138. package/src/core/class/write-during-propagation.test.mts +75 -4
  139. package/src/core/combine/derive.mts +12 -12
  140. package/src/core/combine/merge.mts +2 -2
  141. package/src/core/combine/zip.mts +12 -9
  142. package/src/core/create/counter.mts +7 -7
  143. package/src/core/create/from-abortable-promise.mts +35 -25
  144. package/src/core/create/from-promise.mts +30 -20
  145. package/src/core/create/from-promise.test.mts +97 -0
  146. package/src/core/create/from-subscribable.mts +2 -2
  147. package/src/core/create/source.mts +7 -6
  148. package/src/core/create/timer.mts +2 -2
  149. package/src/core/operators/audit.mts +2 -2
  150. package/src/core/operators/debounce.mts +2 -2
  151. package/src/core/operators/filter.mts +6 -7
  152. package/src/core/operators/flat-map-initial-value.test.mts +269 -0
  153. package/src/core/operators/flat-map-sync-inner.test.mts +4 -2
  154. package/src/core/operators/inner-ownership.test.mts +145 -2
  155. package/src/core/operators/keep-equal-parts.mts +16 -17
  156. package/src/core/operators/map.mts +2 -2
  157. package/src/core/operators/merge-map.mts +59 -29
  158. package/src/core/operators/pairwise.mts +4 -4
  159. package/src/core/operators/scan.mts +2 -2
  160. package/src/core/operators/skip-if-no-change.mts +4 -4
  161. package/src/core/operators/skip-until.mts +4 -4
  162. package/src/core/operators/skip-while.mts +14 -10
  163. package/src/core/operators/skip-while.test.mts +59 -0
  164. package/src/core/operators/switch-map.mts +26 -15
  165. package/src/core/operators/take-until.mts +2 -2
  166. package/src/core/operators/take-while.mts +17 -19
  167. package/src/core/operators/take-while.test.mts +51 -0
  168. package/src/core/operators/take.mts +2 -2
  169. package/src/core/operators/throttle.mts +6 -5
  170. package/src/core/operators/with-buffered-from.mts +18 -5
  171. package/src/core/operators/with-buffered-from.test.mts +27 -0
  172. package/src/core/operators/with-current-value-from.mts +2 -2
  173. package/src/core/operators/with-initial-value.mts +2 -2
  174. package/src/core/predefined/operators/attach-index.mts +4 -5
  175. package/src/core/predefined/operators/map-to.mts +3 -4
  176. package/src/core/predefined/operators/skip.mts +2 -2
  177. package/src/core/scope/scope.mts +6 -6
  178. package/src/core/types/observable-family.mts +4 -5
  179. package/src/core/types/observable.mts +7 -7
  180. package/src/core/utils/current-scope.mts +8 -8
  181. package/src/core/utils/id-maker.mts +2 -2
  182. package/src/core/utils/index.mts +1 -0
  183. package/src/core/utils/observable-brand.mts +5 -5
  184. package/src/core/utils/rethrow-out-of-promise.mts +16 -0
  185. package/src/utils/create-boolean-state.mts +2 -2
  186. package/src/utils/create-reducer.mts +18 -18
  187. package/src/utils/create-state.mts +4 -4
  188. package/src/utils/create-state.test.mts +2 -2
  189. package/src/utils/state-source.mts +3 -3
@@ -1,17 +1,11 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
7
7
  // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
8
- import {
9
- Optional,
10
- SafeUint,
11
- asSafeUint,
12
- expectType,
13
- pipe,
14
- } from 'ts-data-forge';
8
+ import { Optional, asSafeUint, expectType, type SafeUint } from 'ts-data-forge';
15
9
  import { SyncChildObservableClass } from '../class/index.mjs';
16
10
  import { source } from '../create/index.mjs';
17
11
  import type {
@@ -73,7 +67,7 @@ import { withInitialValue } from './with-initial-value.mjs';
73
67
  *
74
68
  * assert.deepStrictEqual(valueHistory, [1, 2, 3, 4]);
75
69
  *
76
- * num$.next(1); // logs: 1
70
+ * num$.next(1); // nothing logged (already completed)
77
71
  *
78
72
  * assert.deepStrictEqual(valueHistory, [1, 2, 3, 4]);
79
73
  * ```
@@ -98,21 +92,26 @@ class TakeWhileObservableClass<A>
98
92
  parentObservable: Observable<A>,
99
93
  predicate: (value: A, index: SafeUint | -1) => boolean,
100
94
  ) {
95
+ const parentSnapshot = parentObservable.getSnapshot();
96
+
97
+ const isTaking =
98
+ Optional.isNone(parentSnapshot) || predicate(parentSnapshot.value, -1);
99
+
101
100
  super({
102
101
  parents: [parentObservable],
103
- initialValue: pipe(parentObservable.getSnapshot()).map((par) =>
104
- Optional.isNone(par)
105
- ? Optional.none
106
- : predicate(par.value, -1)
107
- ? par
108
- : Optional.none,
109
- ).value,
102
+ initialValue: isTaking ? parentSnapshot : Optional.none,
110
103
  });
111
104
 
112
105
  this.#mut_index = -1;
113
106
 
114
107
  this.#predicate = predicate;
115
108
 
109
+ // A parent value the predicate rejects ends it, as any later one would;
110
+ // `attach` then leaves it out of the graph.
111
+ if (!isTaking) {
112
+ this.complete();
113
+ }
114
+
116
115
  this.attach();
117
116
  }
118
117
 
@@ -125,8 +124,7 @@ class TakeWhileObservableClass<A>
125
124
  return; // skip update
126
125
  }
127
126
 
128
- this.#mut_index =
129
- this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
127
+ this.#mut_index = asSafeUint(this.#mut_index + 1);
130
128
 
131
129
  if (this.#predicate(sn.value, this.#mut_index)) {
132
130
  this.setNext(sn.value, updateToken);
@@ -0,0 +1,51 @@
1
+ import { createState } from '../../utils/index.mjs';
2
+ import { takeWhile } from './take-while.mjs';
3
+
4
+ describe(takeWhile, () => {
5
+ test('completes at once when the initial value fails the predicate', () => {
6
+ const [s$, setS] = createState(5);
7
+
8
+ const taken$ = s$.pipe(takeWhile((x) => x < 3));
9
+
10
+ const mut_events: string[] = [];
11
+
12
+ taken$.subscribe(
13
+ (x) => {
14
+ mut_events.push(`next ${x}`);
15
+ },
16
+ () => {
17
+ mut_events.push('complete');
18
+ },
19
+ );
20
+
21
+ assert.isTrue(taken$.isCompleted);
22
+
23
+ setS(1);
24
+
25
+ assert.deepStrictEqual(mut_events, ['complete']);
26
+ });
27
+
28
+ test('keeps taking after an initial value that passes the predicate', () => {
29
+ const [s$, setS] = createState(1);
30
+
31
+ const taken$ = s$.pipe(takeWhile((x) => x < 3));
32
+
33
+ const mut_history: number[] = [];
34
+
35
+ taken$.subscribe((x) => {
36
+ mut_history.push(x);
37
+ });
38
+
39
+ setS(2);
40
+
41
+ assert.isFalse(taken$.isCompleted);
42
+
43
+ setS(5);
44
+
45
+ assert.isTrue(taken$.isCompleted);
46
+
47
+ setS(1);
48
+
49
+ assert.deepStrictEqual(mut_history, [1, 2]);
50
+ });
51
+ });
@@ -1,6 +1,6 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
@@ -1,6 +1,6 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
@@ -139,14 +139,15 @@ class ThrottleObservableClass<A>
139
139
  return; // skip update
140
140
  }
141
141
 
142
- this.setNext(sn.value, updateToken);
143
-
142
+ // The window opens before `setNext`, which can lead back here: a value a
143
+ // subscriber writes to the parent falls inside it.
144
144
  this.#mut_isSkipping = true;
145
145
 
146
- // set timer
147
146
  this.#mut_timerId = setTimeout(() => {
148
147
  this.#mut_isSkipping = false;
149
148
  }, this.#milliSeconds);
149
+
150
+ this.setNext(sn.value, updateToken);
150
151
  }
151
152
 
152
153
  protected override teardown(): void {
@@ -1,11 +1,12 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
7
7
  // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
8
- import { Arr, Optional, pipe } from 'ts-data-forge';
8
+ import { Optional, pipe } from 'ts-data-forge';
9
+ import * as Arr from 'ts-data-forge/array';
9
10
  import { SyncChildObservableClass } from '../class/index.mjs';
10
11
  import type {
11
12
  KeepInitialValueOperator,
@@ -93,11 +94,13 @@ class WithBufferedFromObservableClass<A, B>
93
94
  #mut_bufferedValues: readonly B[];
94
95
 
95
96
  constructor(parentObservable: Observable<A>, observable: Observable<B>) {
97
+ const parentSnapshot = parentObservable.getSnapshot();
98
+
96
99
  super({
97
100
  parents: [parentObservable],
98
101
  depth: 1 + maxDepth([parentObservable, observable]),
99
102
  initialValue: pipe({
100
- par: parentObservable.getSnapshot(),
103
+ par: parentSnapshot,
101
104
  me: observable.getSnapshot(),
102
105
  }).map(({ par, me }) =>
103
106
  Optional.isNone(par)
@@ -115,6 +118,12 @@ class WithBufferedFromObservableClass<A, B>
115
118
  this.#mut_bufferedValues = Arr.toPushed(this.#mut_bufferedValues, value);
116
119
  });
117
120
 
121
+ // `subscribe` has just pushed the buffered observable's current value,
122
+ // which the initial tuple already carries.
123
+ if (Optional.isSome(parentSnapshot)) {
124
+ this.#mut_bufferedValues = [];
125
+ }
126
+
118
127
  this.attach();
119
128
  }
120
129
 
@@ -127,9 +136,13 @@ class WithBufferedFromObservableClass<A, B>
127
136
  return; // skip update
128
137
  }
129
138
 
130
- this.setNext([sn.value, this.#mut_bufferedValues], updateToken);
139
+ const buffered = this.#mut_bufferedValues;
131
140
 
141
+ // Emptied before `setNext`, which can lead back here: a value a subscriber
142
+ // writes to the buffered observable belongs to the next batch.
132
143
  this.#mut_bufferedValues = [];
144
+
145
+ this.setNext([sn.value, buffered], updateToken);
133
146
  }
134
147
 
135
148
  protected override teardown(): void {
@@ -1,3 +1,4 @@
1
+ import { createState } from '../../utils/index.mjs';
1
2
  import { toNode } from '../class/index.mjs';
2
3
  import { source } from '../create/index.mjs';
3
4
  import { withBufferedFrom } from './with-buffered-from.mjs';
@@ -55,3 +56,29 @@ describe('withBufferedFrom teardown', () => {
55
56
  assert.deepStrictEqual(mut_history, [[1, ['A', 'B']]]);
56
57
  });
57
58
  });
59
+
60
+ describe('withBufferedFrom initial value', () => {
61
+ test('reports the current value of the buffered observable once when both hold one (#2767)', () => {
62
+ const [data$] = createState('x');
63
+
64
+ const [trigger$, setTrigger] = createState(0);
65
+
66
+ const result$ = trigger$.pipe(withBufferedFrom(data$));
67
+
68
+ const mut_history: (readonly [number, readonly string[]])[] = [];
69
+
70
+ result$.subscribe((v) => {
71
+ mut_history.push(v);
72
+ });
73
+
74
+ setTrigger(1);
75
+
76
+ setTrigger(2);
77
+
78
+ assert.deepStrictEqual(mut_history, [
79
+ [0, ['x']],
80
+ [1, []],
81
+ [2, []],
82
+ ]);
83
+ });
84
+ });
@@ -1,6 +1,6 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
@@ -1,6 +1,6 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
@@ -1,11 +1,11 @@
1
1
  // Sumi bans classes, `this`, method syntax and writes to `this` outside a
2
- // `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
3
- // so those rules are waived here (D-63).
2
+ // `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
3
+ // those rules are waived here (D-63).
4
4
  // @sumi-expect-error-file classes/no-class
5
5
  // @sumi-expect-error-file banned-syntax/no-this
6
6
  // @sumi-expect-error-file functions/prefer-arrow-function
7
7
  // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
8
- import { Optional, SafeUint, asSafeUint } from 'ts-data-forge';
8
+ import { Optional, asSafeUint, type SafeUint } from 'ts-data-forge';
9
9
  import { SyncChildObservableClass } from '../../class/index.mjs';
10
10
  import type {
11
11
  KeepInitialValueOperator,
@@ -112,8 +112,7 @@ class WithIndexObservableClass<A> extends SyncChildObservableClass<
112
112
  return; // skip update
113
113
  }
114
114
 
115
- this.#mut_index =
116
- this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
115
+ this.#mut_index = asSafeUint(this.#mut_index + 1);
117
116
 
118
117
  this.setNext([this.#mut_index, sn.value], updateToken);
119
118
  }
@@ -4,10 +4,9 @@ import type { KeepInitialValueOperator } from '../../types/index.mjs';
4
4
  /**
5
5
  * Maps all emitted values to a constant value, ignoring the source values.
6
6
  * Emits on every source value although the value never changes — where
7
- * `map(() => value)` would stop every result after the first as equal — so
8
- * it is the one for turning events into a value. The value's type is
9
- * inferred as written, as with `as const`: `mapTo('reset')` gives
10
- * `'reset'`, not `string`.
7
+ * `map(() => value)` would stop every result after the first as equal — so it
8
+ * is the one for turning events into a value. The value's type is inferred as
9
+ * written, as with `as const`: `mapTo('reset')` gives `'reset'`, not `string`.
11
10
  *
12
11
  * @template A - The type of values from the source
13
12
  * @template B - The type of the constant value
@@ -1,4 +1,4 @@
1
- import { PositiveSafeInt } from 'ts-data-forge';
1
+ import { isPositiveSafeInt } from 'ts-data-forge';
2
2
  import type { PositiveSafeIntWithSmallInt } from 'ts-type-forge';
3
3
  import { skipWhile } from '../../operators/index.mjs';
4
4
  import type { DropInitialValueOperator } from '../../types/index.mjs';
@@ -53,6 +53,6 @@ import type { DropInitialValueOperator } from '../../types/index.mjs';
53
53
  export const skip = <A,>(
54
54
  n: PositiveSafeIntWithSmallInt,
55
55
  ): DropInitialValueOperator<A, A> =>
56
- !PositiveSafeInt.is(n) ? idFn : skipWhile((_, index) => index + 1 <= n);
56
+ !isPositiveSafeInt(n) ? idFn : skipWhile((_, index) => index + 1 <= n);
57
57
 
58
58
  const idFn = <T,>(value: T): T => value;
@@ -23,8 +23,8 @@ import {
23
23
  * disposed gives a scope that is born disposed, with a warning.
24
24
  *
25
25
  * Intended for authors of libraries built on synstate rather than for
26
- * application code; state defined at module level belongs to no scope and
27
- * lives as long as the page.
26
+ * application code; state defined at module level belongs to no scope and lives
27
+ * as long as the page.
28
28
  *
29
29
  * `run` and `dispose` read the scope they are called on, so call them on it
30
30
  * (`scope.dispose()`) rather than taking them off it.
@@ -50,8 +50,8 @@ export const getCurrentScope = (): Scope | undefined =>
50
50
 
51
51
  /**
52
52
  * Registers `cleanup` to be called when the current scope is disposed. Cleanups
53
- * run after the child scopes are disposed and before the owned observables
54
- * are, newest first.
53
+ * run after the child scopes are disposed and before the owned observables are,
54
+ * newest first.
55
55
  *
56
56
  * Outside every scope it warns and registers nothing.
57
57
  *
@@ -85,8 +85,8 @@ export const untracked = <R,>(body: () => R): R =>
85
85
  * A scope is created for every row and branch of a UI, so creating one has to
86
86
  * be cheap. It used to be a set of closures with a `Set` of children, a `Set`
87
87
  * of owned observables and a record in a `WeakMap`; it is now one object whose
88
- * methods live on the prototype and whose lists are arrays made when the
89
- * first entry arrives.
88
+ * methods live on the prototype and whose lists are arrays made when the first
89
+ * entry arrives.
90
90
  *
91
91
  * Whatever leaves a list on its own (a child scope disposed by itself, an
92
92
  * observable that completed) is not looked up and spliced out: it only counts
@@ -65,11 +65,10 @@ export type ScanOperatorObservable<A, B> = InitializedSyncChildObservable<
65
65
 
66
66
  // SyncChildObservable
67
67
 
68
- // The helpers stay in a namespace so their names do not crowd the module
69
- // scope. A namespace holding only types is erasable, so it passes
70
- // `erasableSyntaxOnly` (TS1294); the `expectType` assertions are call
71
- // statements, which is why they sit below it rather than beside the types
72
- // they check.
68
+ // The helpers stay in a namespace so their names do not crowd the module scope.
69
+ // A namespace holding only types is erasable, so it passes `erasableSyntaxOnly`
70
+ // (TS1294); the `expectType` assertions are call statements, which is why they
71
+ // sit below it rather than beside the types they check.
73
72
  namespace SynStateInternals {
74
73
  type Cast<A> = A extends NonEmptyUnknownList ? A : never;
75
74
 
@@ -32,8 +32,8 @@ type CreateObservableType<A, Kind extends ObservableKind> = Readonly<{
32
32
 
33
33
  /**
34
34
  * One more than the deepest observable this one is derived from; 0 for a
35
- * root. An update reaches observables in ascending depth, so every parent
36
- * has emitted before its child reads it.
35
+ * root. An update reaches observables in ascending depth, so every parent has
36
+ * emitted before its child reads it.
37
37
  */
38
38
  depth: Kind extends 'root' ? 0 : number;
39
39
 
@@ -173,9 +173,9 @@ export type Operator<A, B> =
173
173
  | KeepInitialValueOperator<A, B>;
174
174
 
175
175
  /**
176
- * Tells an observable made by synstate from any other value, by the brand
177
- * every observable carries. Checking for a `subscribe` method instead would
178
- * also accept plain values that happen to have one.
176
+ * Tells an observable made by synstate from any other value, by the brand every
177
+ * observable carries. Checking for a `subscribe` method instead would also
178
+ * accept plain values that happen to have one.
179
179
  *
180
180
  * @example
181
181
  *
@@ -223,8 +223,8 @@ export type WrapInitialized<A extends readonly unknown[]> = Readonly<{
223
223
  // type GetDestTypeOfOperator<Op extends Operator<unknown, unknown>> =
224
224
  // Op extends Operator<unknown, infer B> ? B : never;
225
225
 
226
- // Only the members meant for users are public; the rest live on the
227
- // internal node types in `core/class` (#2117).
226
+ // Only the members meant for users are public; the rest live on the internal
227
+ // node types in `core/class` (#2117).
228
228
  expectType<
229
229
  keyof Observable<unknown>,
230
230
  | typeof observableBrand
@@ -7,10 +7,10 @@ export type ScopeMember = Readonly<{
7
7
  }>;
8
8
 
9
9
  /**
10
- * The side of a scope the rest of the core talks to, on top of its public
11
- * face {@link Scope}. Kept out of the public API (`core/utils/**` is excluded
12
- * from index generation), so that only `createScope` and its siblings decide
13
- * what a scope owns.
10
+ * The side of a scope the rest of the core talks to, on top of its public face
11
+ * {@link Scope}. Kept out of the public API (`core/utils/**` is excluded from
12
+ * index generation), so that only `createScope` and its siblings decide what a
13
+ * scope owns.
14
14
  */
15
15
  export type ScopeInternals = Scope &
16
16
  Readonly<{
@@ -18,8 +18,8 @@ export type ScopeInternals = Scope &
18
18
  adopt: (node: ScopeMember) => void;
19
19
 
20
20
  /**
21
- * Tells the scope that one of the nodes it adopted completed on its own,
22
- * so that the scope can drop it and keep no reference to it. The node is
21
+ * Tells the scope that one of the nodes it adopted completed on its own, so
22
+ * that the scope can drop it and keep no reference to it. The node is
23
23
  * completed (`isCompleted`) by the time it calls this.
24
24
  */
25
25
  release: () => void;
@@ -28,8 +28,8 @@ export type ScopeInternals = Scope &
28
28
  }>;
29
29
 
30
30
  /**
31
- * The scope whose `run` is executing. A field rather than a module-level
32
- * `let`, so that the one place writing it is visibly this object.
31
+ * The scope whose `run` is executing. A field rather than a module-level `let`,
32
+ * so that the one place writing it is visibly this object.
33
33
  */
34
34
  const mut_slot: { current: ScopeInternals | undefined } = {
35
35
  current: undefined,
@@ -12,8 +12,8 @@ export const notUpdatedYet: UpdateToken =
12
12
  /**
13
13
  * Issues the token of a write that opens no update, because nothing observes
14
14
  * the observable written to (`setNextIfUnobserved`). Nothing is ever queued
15
- * with it, so it is a token and not a queue, unlike the tokens
16
- * `openUpdate` issues.
15
+ * with it, so it is a token and not a queue, unlike the tokens `openUpdate`
16
+ * issues.
17
17
  */
18
18
  export const issueUnqueuedToken = (): UpdateToken =>
19
19
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
@@ -3,3 +3,4 @@ export * from './id-maker.mjs';
3
3
  export * from './max-depth.mjs';
4
4
  export * from './observable-brand.mjs';
5
5
  export * from './observable-utils.mjs';
6
+ export * from './rethrow-out-of-promise.mjs';
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The key every observable carries, set to `true` by `assembleObservable`. It
3
- * lets `isObservable` tell an observable from any other value at run time —
4
- * a `subscribe` method is not enough, since plain values may have one — and,
5
- * as a type, makes the public observable types impossible to satisfy with an
6
- * object literal: an imitation passed to an operator would lack the internal
7
- * members propagation reads and fail at run time instead of at compile time.
3
+ * lets `isObservable` tell an observable from any other value at run time — a
4
+ * `subscribe` method is not enough, since plain values may have one — and, as a
5
+ * type, makes the public observable types impossible to satisfy with an object
6
+ * literal: an imitation passed to an operator would lack the internal members
7
+ * propagation reads and fail at run time instead of at compile time.
8
8
  *
9
9
  * `Symbol.for`, so that an observable made by another copy of synstate in the
10
10
  * same program is still recognized.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Throws `error` again from a task of its own, for a `.catch` after each step
3
+ * of a promise chain that runs subscribers (`fromPromise`): after the step that
4
+ * emits and after the one that completes, so that neither exception replaces
5
+ * the other. A subscriber's exception then surfaces as an uncaught error, as
6
+ * one thrown during an update a timer started does (`debounce`, `timer`),
7
+ * rather than as a rejection or not at all.
8
+ */
9
+ export const rethrowOutOfPromise = (error: unknown): void => {
10
+ setTimeout(() => {
11
+ // The subscriber's own exception, passed on unchanged.
12
+ // @sumi-expect-error exceptions/no-throw
13
+ // eslint-disable-next-line @typescript-eslint/only-throw-error -- the subscriber's own exception, passed on unchanged
14
+ throw error;
15
+ }, 0);
16
+ };
@@ -18,8 +18,8 @@ import { createState } from './create-state.mjs';
18
18
  * import { useObservableValue } from 'synstate-react-hooks';
19
19
  *
20
20
  * // Menu drawer open/close state.
21
- * // setTrue and setFalse can be passed directly as callbacks
22
- * // — no need to create wrapper functions like `() => setState(true)`.
21
+ * // setTrue and setFalse can be passed directly as callbacks — no need to
22
+ * // create wrapper functions like `() => setState(true)`.
23
23
  * const [menuOpen$, { setTrue: openMenu, setFalse: closeMenu }] =
24
24
  * createBooleanState(false);
25
25
  *
@@ -111,18 +111,18 @@ export const createReducer = <S, A>(
111
111
  */
112
112
  export type StateOptions<S> = Readonly<{
113
113
  /**
114
- * Whether the next state is the same as the current one. When it says so,
115
- * the update is not passed on — no subscriber, and nothing derived from
116
- * the 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
118
- * 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.
119
119
  *
120
120
  * Decide here, where the state is held, rather than downstream with
121
- * `skipIfNoChange`: an update passed on here reaches whatever reads the
122
- * 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.
123
123
  *
124
- * Defaults to `Object.is`, so writing the value already held passes
125
- * 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.
126
126
  *
127
127
  * @param current - The state held now
128
128
  * @param next - The state the update would set
@@ -132,8 +132,8 @@ export type StateOptions<S> = Readonly<{
132
132
  /**
133
133
  * Whether an update keeps the parts of the state it did not change. With
134
134
  * `true`, the next state is compared with the current one before it is
135
- * written, and every part of it deeply equal to the same part of the
136
- * 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
137
137
  * ts-data-forge): a reader of an unchanged part keeps the object it has, so
138
138
  * `map`, comparing with `Object.is`, stops there. When the whole next state is
139
139
  * deeply equal, the current state is kept and nothing is passed on, so
@@ -146,14 +146,14 @@ export type StateOptions<S> = Readonly<{
146
146
  * arrays are walked into; a `Map`, a `Set`, a `Date` or a class instance is
147
147
  * compared and kept whole.
148
148
  *
149
- * Prefer this over `keepEqualParts()` downstream: keeping the parts where
150
- * the state is written means no update with nothing in it is passed on at
151
- * 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.
152
152
  *
153
- * Defaults to `false`: the next state is written as given. With it on,
154
- * each update compares deeply every part of the next state that is not
155
- * the very object of the current one; a state whose updates already keep
156
- * 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.
157
157
  */
158
158
  keepEqualParts?: boolean | ReplaceEqualDeepOptions;
159
159
  }>;
@@ -107,8 +107,8 @@ import { createStateSource } from './state-source.mjs';
107
107
  *
108
108
  * assert.strictEqual(report.getSnapshot().value, held);
109
109
  *
110
- * // An edited title: a new report and a new array, while the summary is
111
- * // 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.
112
112
  * setReport({
113
113
  * summary: { open: 1 },
114
114
  * pulls: [{ number: 1, title: 'one (edited)' }],
@@ -146,8 +146,8 @@ export const createState = <S,>(
146
146
  options,
147
147
  );
148
148
 
149
- // Each compares with the state the write started from, as `dispatch`
150
- // does, and builds nothing on the way.
149
+ // Each compares with the state the write started from, as `dispatch` does,
150
+ // and builds nothing on the way.
151
151
  const updateState = (updateFn: (prev: S) => S): S => {
152
152
  const currentState = getSnapshot();
153
153
 
@@ -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],
@@ -3,9 +3,9 @@ import { source, type InitializedSourceObservable } from '../core/index.mjs';
3
3
  import type { StateOptions } from './create-reducer.mjs';
4
4
 
5
5
  /**
6
- * What `createState` and `createReducer` are both built on: the source
7
- * holding the state, its current value, and `commit`, the one way either
8
- * writes it. `commit` writes `reduced` unless `equals` finds it the same as
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
9
  * `currentState`, after keeping the parts of `currentState` it did not change
10
10
  * when `keepEqualParts` asks for that, and returns the state written, or
11
11
  * `currentState` when nothing was.