synstate 1.0.2 → 2.0.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 (194) hide show
  1. package/README.md +16 -17
  2. package/dist/core/class/child-observable-class.d.mts.map +1 -1
  3. package/dist/core/class/child-observable-class.mjs +148 -140
  4. package/dist/core/class/child-observable-class.mjs.map +1 -1
  5. package/dist/core/class/observable-base-class.d.mts.map +1 -1
  6. package/dist/core/class/observable-base-class.mjs +111 -107
  7. package/dist/core/class/observable-base-class.mjs.map +1 -1
  8. package/dist/core/class/root-observable-class.d.mts.map +1 -1
  9. package/dist/core/class/root-observable-class.mjs +32 -24
  10. package/dist/core/class/root-observable-class.mjs.map +1 -1
  11. package/dist/core/combine/combine.d.mts +3 -3
  12. package/dist/core/combine/combine.d.mts.map +1 -1
  13. package/dist/core/combine/combine.mjs +31 -113
  14. package/dist/core/combine/combine.mjs.map +1 -1
  15. package/dist/core/combine/merge.d.mts +2 -2
  16. package/dist/core/combine/merge.d.mts.map +1 -1
  17. package/dist/core/combine/merge.mjs +21 -69
  18. package/dist/core/combine/merge.mjs.map +1 -1
  19. package/dist/core/combine/zip.d.mts +2 -2
  20. package/dist/core/combine/zip.d.mts.map +1 -1
  21. package/dist/core/combine/zip.mjs +34 -79
  22. package/dist/core/combine/zip.mjs.map +1 -1
  23. package/dist/core/create/counter.d.mts +1 -1
  24. package/dist/core/create/counter.d.mts.map +1 -1
  25. package/dist/core/create/counter.mjs +45 -84
  26. package/dist/core/create/counter.mjs.map +1 -1
  27. package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
  28. package/dist/core/create/from-abortable-promise.mjs +29 -61
  29. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  30. package/dist/core/create/from-promise.d.mts.map +1 -1
  31. package/dist/core/create/from-promise.mjs +17 -66
  32. package/dist/core/create/from-promise.mjs.map +1 -1
  33. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  34. package/dist/core/create/from-subscribable.mjs +18 -69
  35. package/dist/core/create/from-subscribable.mjs.map +1 -1
  36. package/dist/core/create/just.d.mts.map +1 -1
  37. package/dist/core/create/just.mjs +5 -35
  38. package/dist/core/create/just.mjs.map +1 -1
  39. package/dist/core/create/source.mjs +10 -13
  40. package/dist/core/create/source.mjs.map +1 -1
  41. package/dist/core/create/timer.d.mts.map +1 -1
  42. package/dist/core/create/timer.mjs +32 -68
  43. package/dist/core/create/timer.mjs.map +1 -1
  44. package/dist/core/operators/audit.d.mts.map +1 -1
  45. package/dist/core/operators/audit.mjs +39 -133
  46. package/dist/core/operators/audit.mjs.map +1 -1
  47. package/dist/core/operators/debounce.d.mts.map +1 -1
  48. package/dist/core/operators/debounce.mjs +36 -119
  49. package/dist/core/operators/debounce.mjs.map +1 -1
  50. package/dist/core/operators/filter.mjs +22 -25
  51. package/dist/core/operators/filter.mjs.map +1 -1
  52. package/dist/core/operators/map.d.mts.map +1 -1
  53. package/dist/core/operators/map.mjs +28 -61
  54. package/dist/core/operators/map.mjs.map +1 -1
  55. package/dist/core/operators/merge-map.d.mts +1 -1
  56. package/dist/core/operators/merge-map.d.mts.map +1 -1
  57. package/dist/core/operators/merge-map.mjs +34 -130
  58. package/dist/core/operators/merge-map.mjs.map +1 -1
  59. package/dist/core/operators/pairwise.d.mts +3 -2
  60. package/dist/core/operators/pairwise.d.mts.map +1 -1
  61. package/dist/core/operators/pairwise.mjs +19 -75
  62. package/dist/core/operators/pairwise.mjs.map +1 -1
  63. package/dist/core/operators/scan.d.mts.map +1 -1
  64. package/dist/core/operators/scan.mjs +16 -67
  65. package/dist/core/operators/scan.mjs.map +1 -1
  66. package/dist/core/operators/skip-if-no-change.d.mts +1 -1
  67. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  68. package/dist/core/operators/skip-if-no-change.mjs +28 -92
  69. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  70. package/dist/core/operators/skip-until.d.mts.map +1 -1
  71. package/dist/core/operators/skip-until.mjs +23 -72
  72. package/dist/core/operators/skip-until.mjs.map +1 -1
  73. package/dist/core/operators/skip-while.d.mts.map +1 -1
  74. package/dist/core/operators/skip-while.mjs +26 -88
  75. package/dist/core/operators/skip-while.mjs.map +1 -1
  76. package/dist/core/operators/switch-map.d.mts.map +1 -1
  77. package/dist/core/operators/switch-map.mjs +31 -124
  78. package/dist/core/operators/switch-map.mjs.map +1 -1
  79. package/dist/core/operators/take-until.d.mts.map +1 -1
  80. package/dist/core/operators/take-until.mjs +28 -69
  81. package/dist/core/operators/take-until.mjs.map +1 -1
  82. package/dist/core/operators/take-while.d.mts.map +1 -1
  83. package/dist/core/operators/take-while.mjs +23 -83
  84. package/dist/core/operators/take-while.mjs.map +1 -1
  85. package/dist/core/operators/throttle.d.mts.map +1 -1
  86. package/dist/core/operators/throttle.mjs +38 -115
  87. package/dist/core/operators/throttle.mjs.map +1 -1
  88. package/dist/core/operators/with-buffered-from.d.mts +1 -1
  89. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  90. package/dist/core/operators/with-buffered-from.mjs +37 -90
  91. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  92. package/dist/core/operators/with-current-value-from.d.mts +1 -1
  93. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  94. package/dist/core/operators/with-current-value-from.mjs +23 -84
  95. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  96. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  97. package/dist/core/operators/with-initial-value.mjs +13 -58
  98. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  99. package/dist/core/predefined/operators/attach-index.d.mts +1 -1
  100. package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
  101. package/dist/core/predefined/operators/attach-index.mjs +0 -53
  102. package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
  103. package/dist/core/predefined/operators/map-optional.d.mts.map +1 -1
  104. package/dist/core/predefined/operators/map-optional.mjs +1 -48
  105. package/dist/core/predefined/operators/map-optional.mjs.map +1 -1
  106. package/dist/core/predefined/operators/map-result-err.d.mts.map +1 -1
  107. package/dist/core/predefined/operators/map-result-err.mjs +1 -48
  108. package/dist/core/predefined/operators/map-result-err.mjs.map +1 -1
  109. package/dist/core/predefined/operators/map-result-ok.d.mts.map +1 -1
  110. package/dist/core/predefined/operators/map-result-ok.mjs +1 -48
  111. package/dist/core/predefined/operators/map-result-ok.mjs.map +1 -1
  112. package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
  113. package/dist/core/predefined/operators/map-to.mjs +0 -40
  114. package/dist/core/predefined/operators/map-to.mjs.map +1 -1
  115. package/dist/core/predefined/operators/pluck.d.mts +1 -1
  116. package/dist/core/predefined/operators/pluck.d.mts.map +1 -1
  117. package/dist/core/predefined/operators/pluck.mjs +0 -43
  118. package/dist/core/predefined/operators/pluck.mjs.map +1 -1
  119. package/dist/core/predefined/operators/skip.d.mts.map +1 -1
  120. package/dist/core/predefined/operators/skip.mjs +0 -47
  121. package/dist/core/predefined/operators/skip.mjs.map +1 -1
  122. package/dist/core/predefined/operators/take.d.mts.map +1 -1
  123. package/dist/core/predefined/operators/take.mjs +0 -41
  124. package/dist/core/predefined/operators/take.mjs.map +1 -1
  125. package/dist/core/predefined/operators/unwrap-optional.mjs +4 -43
  126. package/dist/core/predefined/operators/unwrap-optional.mjs.map +1 -1
  127. package/dist/core/predefined/operators/unwrap-result-err.mjs +0 -40
  128. package/dist/core/predefined/operators/unwrap-result-err.mjs.map +1 -1
  129. package/dist/core/predefined/operators/unwrap-result-ok.mjs +4 -43
  130. package/dist/core/predefined/operators/unwrap-result-ok.mjs.map +1 -1
  131. package/dist/core/types/observable-family.d.mts +10 -10
  132. package/dist/core/types/observable-family.d.mts.map +1 -1
  133. package/dist/core/types/observable.d.mts.map +1 -1
  134. package/dist/core/types/observable.mjs +3 -5
  135. package/dist/core/types/observable.mjs.map +1 -1
  136. package/dist/core/types/types.d.mts +2 -1
  137. package/dist/core/types/types.d.mts.map +1 -1
  138. package/dist/core/utils/id-maker.mjs +5 -6
  139. package/dist/core/utils/id-maker.mjs.map +1 -1
  140. package/dist/core/utils/max-depth.d.mts.map +1 -1
  141. package/dist/core/utils/max-depth.mjs +1 -3
  142. package/dist/core/utils/max-depth.mjs.map +1 -1
  143. package/dist/core/utils/observable-utils.d.mts.map +1 -1
  144. package/dist/core/utils/observable-utils.mjs +3 -2
  145. package/dist/core/utils/observable-utils.mjs.map +1 -1
  146. package/dist/core/utils/utils.d.mts.map +1 -1
  147. package/dist/core/utils/utils.mjs +23 -30
  148. package/dist/core/utils/utils.mjs.map +1 -1
  149. package/dist/utils/collect-to-array.d.mts.map +1 -1
  150. package/dist/utils/collect-to-array.mjs +9 -6
  151. package/dist/utils/collect-to-array.mjs.map +1 -1
  152. package/dist/utils/create-boolean-state.d.mts +1 -1
  153. package/dist/utils/create-boolean-state.d.mts.map +1 -1
  154. package/dist/utils/create-boolean-state.mjs +18 -46
  155. package/dist/utils/create-boolean-state.mjs.map +1 -1
  156. package/dist/utils/create-event-emitter.mjs +10 -64
  157. package/dist/utils/create-event-emitter.mjs.map +1 -1
  158. package/dist/utils/create-reducer.d.mts +1 -1
  159. package/dist/utils/create-reducer.d.mts.map +1 -1
  160. package/dist/utils/create-reducer.mjs +8 -52
  161. package/dist/utils/create-reducer.mjs.map +1 -1
  162. package/dist/utils/create-state.d.mts +1 -1
  163. package/dist/utils/create-state.d.mts.map +1 -1
  164. package/dist/utils/create-state.mjs +23 -53
  165. package/dist/utils/create-state.mjs.map +1 -1
  166. package/package.json +42 -29
  167. package/src/core/class/child-observable-class.mts +1 -1
  168. package/src/core/class/observable-base-class.mts +16 -16
  169. package/src/core/combine/combine.mts +38 -16
  170. package/src/core/combine/merge.mts +3 -3
  171. package/src/core/combine/zip.mts +24 -13
  172. package/src/core/create/counter.mts +11 -9
  173. package/src/core/create/counter.test.mts +11 -0
  174. package/src/core/create/timer.mts +6 -6
  175. package/src/core/create/timer.test.mts +11 -0
  176. package/src/core/operators/audit-throttle-teardown.test.mts +21 -0
  177. package/src/core/operators/audit.mts +6 -6
  178. package/src/core/operators/debounce.mts +6 -6
  179. package/src/core/operators/pairwise.mts +10 -5
  180. package/src/core/operators/skip-while.mts +1 -5
  181. package/src/core/operators/skip-while.test.mts +20 -0
  182. package/src/core/operators/throttle.mts +6 -6
  183. package/src/core/operators/with-buffered-from.mts +4 -4
  184. package/src/core/predefined/operators/map-optional.mts +1 -2
  185. package/src/core/predefined/operators/map-result-err.mts +1 -1
  186. package/src/core/predefined/operators/map-result-ok.mts +1 -1
  187. package/src/core/types/observable-family.mts +12 -11
  188. package/src/core/types/observable.mts +5 -7
  189. package/src/core/types/types.mts +2 -1
  190. package/src/core/utils/max-depth.mts +5 -1
  191. package/src/core/utils/utils.mts +1 -1
  192. package/src/utils/create-boolean-state.mts +1 -1
  193. package/src/utils/create-reducer.mts +1 -1
  194. package/src/utils/create-state.mts +1 -1
@@ -1,5 +1,5 @@
1
- import { Arr, Optional, expectType } from 'ts-data-forge';
2
- import { type NonEmptyArray } from 'ts-type-forge';
1
+ import { Arr, expectType, Optional } from 'ts-data-forge';
2
+ import { type NonEmptyTuple, type Tuple } from 'ts-type-forge';
3
3
  import { SyncChildObservableClass } from '../class/index.mjs';
4
4
  import { source } from '../create/index.mjs';
5
5
  import { withInitialValue } from '../operators/index.mjs';
@@ -72,7 +72,7 @@ import {
72
72
  * ]);
73
73
  * ```
74
74
  */
75
- export const combine = <const OS extends NonEmptyArray<Observable<unknown>>>(
75
+ export const combine = <const OS extends NonEmptyTuple<Observable<unknown>>>(
76
76
  parents: OS,
77
77
  ): CombineObservableRefined<OS> =>
78
78
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
@@ -91,35 +91,57 @@ class CombineObservableClass<const A extends NonEmptyUnknownList>
91
91
  implements CombineObservable<A>
92
92
  {
93
93
  constructor(parents: Wrap<A>) {
94
- const parentsValues = parents.map((p) => p.getSnapshot());
94
+ const parentsValues: Tuple.MapTo<Optional<A[number]>, A> = Arr.map(
95
+ parents,
96
+ (p) => p.getSnapshot(),
97
+ );
98
+
99
+ // The annotation on `initialValue` is what replaces the type assertion.
100
+ // `Arr.map` carries a single result element type, so it reports `A[number]`
101
+ // at every position, and `Tuple.MapTo<A[number], A>` — the uniform mapping
102
+ // — is *not* assignable back to `A`. The positional
103
+ // `Readonly<{ [K in keyof A]: A[K] }>` is, and TypeScript accepts the
104
+ // uniform result into it because the two mapped types share `keyof A`. So
105
+ // it is the annotation, not an assertion, that records the position-wise
106
+ // fact the checker cannot derive on its own.
107
+ const initialValue: Optional<Readonly<{ [K in keyof A]: A[K] }>> =
108
+ Arr.every(parentsValues, Optional.isSome)
109
+ ? Optional.some(Arr.map(parentsValues, (c) => c.value))
110
+ : Optional.none;
95
111
 
96
112
  super({
97
113
  parents,
98
- initialValue: parentsValues.every(Optional.isSome)
99
- ? Optional.some(
100
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
101
- Arr.map(parentsValues, (c) => c.value) as A,
102
- )
103
- : Optional.none,
114
+ initialValue,
104
115
  });
105
116
  }
106
117
 
107
118
  override tryUpdate(updateToken: UpdateToken): void {
108
119
  if (this.parents.every((o) => o.updateToken !== updateToken)) return; // all parents are skipped
109
120
 
110
- const parentValues = this.parents.map((a) => a.getSnapshot());
121
+ // Same shape-preserving chain as the constructor: `Arr.map` (not the
122
+ // native one, which drops the tuple shape) into the uniform
123
+ // `Tuple.MapTo`, then a positional annotation to get back to `A`.
124
+ const parentValues: Tuple.MapTo<Optional<A[number]>, A> = Arr.map(
125
+ this.parents,
126
+ (a) => a.getSnapshot(),
127
+ );
111
128
 
112
- if (parentValues.every(Optional.isSome)) {
113
- const nextValue =
114
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
115
- Arr.map(parentValues, (a) => a.value) as A;
129
+ if (Arr.every(parentValues, Optional.isSome)) {
130
+ const nextValue: Readonly<{ [K in keyof A]: A[K] }> = Arr.map(
131
+ parentValues,
132
+ (a) => a.value,
133
+ );
116
134
 
117
135
  this.setNext(nextValue, updateToken);
118
136
  }
119
137
  }
120
138
  }
121
139
 
122
- {
140
+ if (import.meta.vitest !== undefined) {
141
+ test('type test', () => {
142
+ expect(1).toBe(1); // dummy
143
+ });
144
+
123
145
  {
124
146
  const s1: Observable<1> = source<1>();
125
147
 
@@ -1,5 +1,5 @@
1
1
  import { Optional, expectType } from 'ts-data-forge';
2
- import { type ArrayElement, type NonEmptyArray } from 'ts-type-forge';
2
+ import { type ArrayElement, type NonEmptyTuple } from 'ts-type-forge';
3
3
  import { SyncChildObservableClass } from '../class/index.mjs';
4
4
  import { source } from '../create/index.mjs';
5
5
  import {
@@ -63,7 +63,7 @@ import {
63
63
  * @note To improve code readability, consider using `createState` instead of `merge`,
64
64
  * subscribing to `parents` and calling `setState` within it.
65
65
  */
66
- export const merge = <const OS extends NonEmptyArray<Observable<unknown>>>(
66
+ export const merge = <const OS extends NonEmptyTuple<Observable<unknown>>>(
67
67
  parents: OS,
68
68
  ): MergeObservableRefined<OS> =>
69
69
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
@@ -104,7 +104,7 @@ if (import.meta.vitest !== undefined) {
104
104
 
105
105
  const r2 = source('a');
106
106
 
107
- const _m = merge([r1, r2] as const);
107
+ const _m = merge([r1, r2]);
108
108
 
109
109
  expectType<typeof _m, SyncChildObservable<number | string>>('<=');
110
110
  }
@@ -1,5 +1,5 @@
1
1
  import { Arr, Optional, createQueue, expectType } from 'ts-data-forge';
2
- import { type NonEmptyArray } from 'ts-type-forge';
2
+ import { type NonEmptyTuple, type Tuple } from 'ts-type-forge';
3
3
  import { SyncChildObservableClass } from '../class/index.mjs';
4
4
  import { source } from '../create/index.mjs';
5
5
  import { withInitialValue } from '../operators/index.mjs';
@@ -65,7 +65,7 @@ import {
65
65
  * ]);
66
66
  * ```
67
67
  */
68
- export const zip = <const OS extends NonEmptyArray<Observable<unknown>>>(
68
+ export const zip = <const OS extends NonEmptyTuple<Observable<unknown>>>(
69
69
  parents: OS,
70
70
  ): ZipObservableRefined<OS> =>
71
71
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
@@ -78,21 +78,30 @@ class ZipObservableClass<const A extends NonEmptyUnknownList>
78
78
  readonly #queues: TupleToQueueTuple<A>;
79
79
 
80
80
  constructor(parents: Wrap<A>) {
81
- const parentsValues = parents.map((p) => p.getSnapshot());
81
+ const parentsValues: Tuple.MapTo<Optional<A[number]>, A> = Arr.map(
82
+ parents,
83
+ (p) => p.getSnapshot(),
84
+ );
85
+
86
+ // The annotation on `initialValue` is what replaces the type assertion.
87
+ // `Arr.map` carries a single result element type, so it reports `A[number]`
88
+ // at every position, and `Tuple.MapTo<A[number], A>` — the uniform mapping
89
+ // — is *not* assignable back to `A`. The positional
90
+ // `Readonly<{ [K in keyof A]: A[K] }>` is, and TypeScript accepts the
91
+ // uniform result into it because the two mapped types share `keyof A`. So
92
+ // it is the annotation, not an assertion, that records the position-wise
93
+ // fact the checker cannot derive on its own.
94
+ const initialValue: Optional<Readonly<{ [K in keyof A]: A[K] }>> =
95
+ Arr.every(parentsValues, Optional.isSome)
96
+ ? Optional.some(Arr.map(parentsValues, (c) => c.value))
97
+ : Optional.none;
82
98
 
83
99
  super({
84
100
  parents,
85
- initialValue: parentsValues.every(Optional.isSome)
86
- ? Optional.some(
87
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
88
- Arr.map(parentsValues, (c) => c.value) as A,
89
- )
90
- : Optional.none,
101
+ initialValue,
91
102
  });
92
103
 
93
- this.#queues = Arr.map(parents, () =>
94
- createQueue(),
95
- ) satisfies TupleToQueueTuple<A>;
104
+ this.#queues = Arr.map(parents, () => createQueue());
96
105
  }
97
106
 
98
107
  override tryUpdate(updateToken: UpdateToken): void {
@@ -108,6 +117,8 @@ class ZipObservableClass<const A extends NonEmptyUnknownList>
108
117
 
109
118
  if (queues.every((list) => !list.isEmpty)) {
110
119
  const nextValue =
120
+ // `Arr.map` has a single result element type, so it reports
121
+ // `A[number] | undefined` per position rather than `A[P]`.
111
122
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
112
123
  Arr.map(queues, (q) => Optional.unwrap(q.dequeue())) as A;
113
124
 
@@ -160,7 +171,7 @@ if (import.meta.vitest !== undefined) {
160
171
 
161
172
  const r2 = source('a');
162
173
 
163
- const _z = zip([r1, r2] as const);
174
+ const _z = zip([r1, r2]);
164
175
 
165
176
  const _zi = zip([
166
177
  r1.pipe(withInitialValue(0)),
@@ -36,7 +36,7 @@ import { type CounterObservable, type TimerId } from '../types/index.mjs';
36
36
  *
37
37
  * subscription.unsubscribe();
38
38
  *
39
- * assert.isTrue(Arr.isArrayAtLeastLength(valueHistory, 3));
39
+ * assert.isTrue(Arr.isMinLengthArray(3, valueHistory));
40
40
  *
41
41
  * assert.deepStrictEqual(valueHistory[0], 0);
42
42
  *
@@ -84,6 +84,16 @@ class CounterObservableClass
84
84
  }
85
85
  }
86
86
 
87
+ #resetTimer(): void {
88
+ if (this.#mut_timerId0 === undefined || this.#mut_timerId === undefined) {
89
+ return;
90
+ }
91
+
92
+ clearInterval(this.#mut_timerId0);
93
+
94
+ clearInterval(this.#mut_timerId);
95
+ }
96
+
87
97
  start(): void {
88
98
  if (this.#mut_isStarted) {
89
99
  console.warn('cannot start twice');
@@ -110,14 +120,6 @@ class CounterObservableClass
110
120
  }, this.#intervalMilliSeconds);
111
121
  }
112
122
 
113
- #resetTimer(): void {
114
- if (this.#mut_timerId0 !== undefined && this.#mut_timerId !== undefined) {
115
- clearInterval(this.#mut_timerId0);
116
-
117
- clearInterval(this.#mut_timerId);
118
- }
119
- }
120
-
121
123
  override complete(): void {
122
124
  this.#resetTimer();
123
125
 
@@ -0,0 +1,11 @@
1
+ import { counter } from './counter.mjs';
2
+
3
+ describe(counter, () => {
4
+ test('completing before start() tears down without touching the timers', () => {
5
+ const counter$ = counter(10, { startManually: true });
6
+
7
+ counter$.complete();
8
+
9
+ assert.isTrue(counter$.isCompleted);
10
+ });
11
+ });
@@ -68,6 +68,12 @@ class TimerObservableClass
68
68
  }
69
69
  }
70
70
 
71
+ #resetTimer(): void {
72
+ if (this.#mut_timerId !== undefined) {
73
+ clearTimeout(this.#mut_timerId);
74
+ }
75
+ }
76
+
71
77
  start(): void {
72
78
  if (this.#mut_isStarted) {
73
79
  console.warn('cannot start twice');
@@ -90,12 +96,6 @@ class TimerObservableClass
90
96
  }, this.#milliSeconds);
91
97
  }
92
98
 
93
- #resetTimer(): void {
94
- if (this.#mut_timerId !== undefined) {
95
- clearTimeout(this.#mut_timerId);
96
- }
97
- }
98
-
99
99
  override complete(): void {
100
100
  this.#resetTimer();
101
101
 
@@ -0,0 +1,11 @@
1
+ import { timer } from './timer.mjs';
2
+
3
+ describe(timer, () => {
4
+ test('completing before start() tears down without touching the timer', () => {
5
+ const timer$ = timer(10, { startManually: true });
6
+
7
+ timer$.complete();
8
+
9
+ assert.isTrue(timer$.isCompleted);
10
+ });
11
+ });
@@ -0,0 +1,21 @@
1
+ import { source } from '../create/index.mjs';
2
+ import { audit } from './audit.mjs';
3
+ import { throttle } from './throttle.mjs';
4
+
5
+ describe('completing before any emission', () => {
6
+ test('audit tears down without a pending timer', () => {
7
+ const audited$ = source<number>().pipe(audit(10));
8
+
9
+ audited$.complete();
10
+
11
+ assert.isTrue(audited$.isCompleted);
12
+ });
13
+
14
+ test('throttle tears down without a pending timer', () => {
15
+ const throttled$ = source<number>().pipe(throttle(10));
16
+
17
+ throttled$.complete();
18
+
19
+ assert.isTrue(throttled$.isCompleted);
20
+ });
21
+ });
@@ -133,6 +133,12 @@ class AuditObservableClass<A>
133
133
  this.#milliSeconds = milliSeconds;
134
134
  }
135
135
 
136
+ #resetTimer(): void {
137
+ if (this.#mut_timerId !== undefined) {
138
+ clearTimeout(this.#mut_timerId);
139
+ }
140
+ }
141
+
136
142
  override tryUpdate(updateToken: UpdateToken): void {
137
143
  const par = this.parents[0];
138
144
 
@@ -158,12 +164,6 @@ class AuditObservableClass<A>
158
164
  }, this.#milliSeconds);
159
165
  }
160
166
 
161
- #resetTimer(): void {
162
- if (this.#mut_timerId !== undefined) {
163
- clearTimeout(this.#mut_timerId);
164
- }
165
- }
166
-
167
167
  override complete(): void {
168
168
  this.#resetTimer();
169
169
 
@@ -121,6 +121,12 @@ class DebounceObservableClass<A>
121
121
  this.#milliSeconds = milliSeconds;
122
122
  }
123
123
 
124
+ #resetTimer(): void {
125
+ if (this.#mut_timerId !== undefined) {
126
+ clearTimeout(this.#mut_timerId);
127
+ }
128
+ }
129
+
124
130
  override tryUpdate(updateToken: UpdateToken): void {
125
131
  const par = this.parents[0];
126
132
 
@@ -140,12 +146,6 @@ class DebounceObservableClass<A>
140
146
  }, this.#milliSeconds);
141
147
  }
142
148
 
143
- #resetTimer(): void {
144
- if (this.#mut_timerId !== undefined) {
145
- clearTimeout(this.#mut_timerId);
146
- }
147
- }
148
-
149
149
  override complete(): void {
150
150
  this.#resetTimer();
151
151
 
@@ -1,4 +1,5 @@
1
1
  import { Optional } from 'ts-data-forge';
2
+ import { type FixedLengthTuple } from 'ts-type-forge';
2
3
  import { SyncChildObservableClass } from '../class/index.mjs';
3
4
  import {
4
5
  type DropInitialValueOperator,
@@ -30,7 +31,7 @@ import {
30
31
  *
31
32
  * const pairs$ = num$.pipe(pairwise());
32
33
  *
33
- * const valueHistory: (readonly [number, number])[] = [];
34
+ * const valueHistory: FixedLengthTuple<2, number>[] = [];
34
35
  *
35
36
  * pairs$.subscribe(([prev, curr]) => {
36
37
  * valueHistory.push([prev, curr]);
@@ -60,14 +61,18 @@ import {
60
61
  * ]);
61
62
  * ```
62
63
  */
63
- export const pairwise = <A,>(): DropInitialValueOperator<A, readonly [A, A]> =>
64
- f;
64
+ export const pairwise = <A,>(): DropInitialValueOperator<
65
+ A,
66
+ FixedLengthTuple<2, A>
67
+ > => f;
65
68
 
66
- const f = <A,>(parentObservable: Observable<A>): Observable<readonly [A, A]> =>
69
+ const f = <A,>(
70
+ parentObservable: Observable<A>,
71
+ ): Observable<FixedLengthTuple<2, A>> =>
67
72
  new PairwiseObservableClass(parentObservable);
68
73
 
69
74
  class PairwiseObservableClass<A>
70
- extends SyncChildObservableClass<readonly [A, A], readonly [A]>
75
+ extends SyncChildObservableClass<FixedLengthTuple<2, A>, readonly [A]>
71
76
  implements PairwiseOperatorObservable<A>
72
77
  {
73
78
  #mut_previousValue: Optional<A>;
@@ -89,11 +89,7 @@ class SkipWhileObservableClass<A>
89
89
  super({
90
90
  parents: [parentObservable],
91
91
  initialValue: pipe(parentObservable.getSnapshot()).map((sn) =>
92
- Optional.isNone(sn)
93
- ? Optional.none
94
- : predicate(sn.value, -1)
95
- ? Optional.none
96
- : sn,
92
+ Optional.isNone(sn) || predicate(sn.value, -1) ? Optional.none : sn,
97
93
  ).value,
98
94
  });
99
95
 
@@ -0,0 +1,20 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { just } from '../create/index.mjs';
3
+ import { skipWhile } from './skip-while.mjs';
4
+
5
+ describe(skipWhile, () => {
6
+ // Widened so the predicate is not statically decidable.
7
+ const seed: number = 1;
8
+
9
+ test('keeps an initialized parent value the predicate does not skip', () => {
10
+ const kept$ = just(seed).pipe(skipWhile((x) => x > 10));
11
+
12
+ expect(Optional.unwrapOr(kept$.getSnapshot(), -1)).toBe(1);
13
+ });
14
+
15
+ test('drops an initialized parent value the predicate skips', () => {
16
+ const dropped$ = just(seed).pipe(skipWhile((x) => x < 10));
17
+
18
+ assert.isTrue(Optional.isNone(dropped$.getSnapshot()));
19
+ });
20
+ });
@@ -117,6 +117,12 @@ class ThrottleObservableClass<A>
117
117
  this.#milliSeconds = milliSeconds;
118
118
  }
119
119
 
120
+ #resetTimer(): void {
121
+ if (this.#mut_timerId !== undefined) {
122
+ clearTimeout(this.#mut_timerId);
123
+ }
124
+ }
125
+
120
126
  override tryUpdate(updateToken: UpdateToken): void {
121
127
  const par = this.parents[0];
122
128
 
@@ -140,12 +146,6 @@ class ThrottleObservableClass<A>
140
146
  }, this.#milliSeconds);
141
147
  }
142
148
 
143
- #resetTimer(): void {
144
- if (this.#mut_timerId !== undefined) {
145
- clearTimeout(this.#mut_timerId);
146
- }
147
- }
148
-
149
149
  override complete(): void {
150
150
  this.#resetTimer();
151
151
 
@@ -105,6 +105,10 @@ class WithBufferedFromObservableClass<A, B>
105
105
  });
106
106
  }
107
107
 
108
+ #clearBuffer(): void {
109
+ this.#mut_bufferedValues = [];
110
+ }
111
+
108
112
  override tryUpdate(updateToken: UpdateToken): void {
109
113
  const par = this.parents[0];
110
114
 
@@ -118,8 +122,4 @@ class WithBufferedFromObservableClass<A, B>
118
122
 
119
123
  this.#clearBuffer();
120
124
  }
121
-
122
- #clearBuffer(): void {
123
- this.#mut_bufferedValues = [];
124
- }
125
125
  }
@@ -51,5 +51,4 @@ import { type KeepInitialValueOperator } from '../../types/index.mjs';
51
51
  */
52
52
  export const mapOptional = <O extends UnknownOptional, B>(
53
53
  mapFn: (x: Optional.Unwrap<O>) => B,
54
- ): KeepInitialValueOperator<O, Optional<B>> =>
55
- map((a) => Optional.map(a, mapFn));
54
+ ): KeepInitialValueOperator<O, Optional<B>> => map(Optional.map(mapFn));
@@ -52,4 +52,4 @@ import { type KeepInitialValueOperator } from '../../types/index.mjs';
52
52
  export const mapResultErr = <R extends UnknownResult, E2>(
53
53
  mapFn: (x: Result.UnwrapErr<R>) => E2,
54
54
  ): KeepInitialValueOperator<R, Result<Result.UnwrapOk<R>, E2>> =>
55
- map((a) => Result.mapErr(a, mapFn));
55
+ map(Result.mapErr(mapFn));
@@ -52,4 +52,4 @@ import { type KeepInitialValueOperator } from '../../types/index.mjs';
52
52
  export const mapResultOk = <R extends UnknownResult, S2>(
53
53
  mapFn: (x: Result.UnwrapOk<R>) => S2,
54
54
  ): KeepInitialValueOperator<R, Result<S2, Result.UnwrapErr<R>>> =>
55
- map((a) => Result.map(a, mapFn));
55
+ map(Result.map(mapFn));
@@ -1,7 +1,8 @@
1
1
  import { expectType, type Result } from 'ts-data-forge';
2
2
  import {
3
3
  type ArrayElement,
4
- type NonEmptyArray,
4
+ type FixedLengthTuple,
5
+ type NonEmptyTuple,
5
6
  type SafeUint,
6
7
  } from 'ts-type-forge';
7
8
  import {
@@ -63,8 +64,8 @@ export type ScanOperatorObservable<A, B> = InitializedSyncChildObservable<
63
64
  namespace SynStateInternals {
64
65
  type Cast<A> = A extends NonEmptyUnknownList ? A : never;
65
66
 
66
- type EveryInitialized<OS extends NonEmptyArray<Observable<unknown>>> =
67
- OS extends NonEmptyArray<InitializedObservable<unknown>> ? true : false;
67
+ type EveryInitialized<OS extends NonEmptyTuple<Observable<unknown>>> =
68
+ OS extends NonEmptyTuple<InitializedObservable<unknown>> ? true : false;
68
69
 
69
70
  type IsInitialized<O> = readonly [O] extends readonly [
70
71
  InitializedObservable<unknown>,
@@ -83,7 +84,7 @@ namespace SynStateInternals {
83
84
  // union distribution
84
85
  LogicalValue<OS extends OS ? IsInitialized<OS> : never>;
85
86
 
86
- type SomeInitialized<OS extends NonEmptyArray<Observable<unknown>>> =
87
+ type SomeInitialized<OS extends NonEmptyTuple<Observable<unknown>>> =
87
88
  SomeInitializedImpl<OS[number]>;
88
89
 
89
90
  expectType<EveryInitialized<readonly [Observable<1>]>, false>('=');
@@ -125,7 +126,7 @@ namespace SynStateInternals {
125
126
  SyncChildObservable<A, A>;
126
127
 
127
128
  export type CombineObservableRefinedImpl<
128
- OS extends NonEmptyArray<Observable<unknown>>,
129
+ OS extends NonEmptyTuple<Observable<unknown>>,
129
130
  > =
130
131
  EveryInitialized<OS> extends true
131
132
  ? InitializedCombineObservableImpl<Cast<Unwrap<OS>>>
@@ -138,7 +139,7 @@ namespace SynStateInternals {
138
139
  SyncChildObservable<A, A>;
139
140
 
140
141
  export type ZipObservableRefinedImpl<
141
- OS extends NonEmptyArray<Observable<unknown>>,
142
+ OS extends NonEmptyTuple<Observable<unknown>>,
142
143
  > =
143
144
  EveryInitialized<OS> extends true
144
145
  ? InitializedZipObservableImpl<Cast<Unwrap<OS>>>
@@ -151,7 +152,7 @@ namespace SynStateInternals {
151
152
  SyncChildObservable<ArrayElement<P>, P>;
152
153
 
153
154
  export type MergeObservableRefinedImpl<
154
- OS extends NonEmptyArray<Observable<unknown>>,
155
+ OS extends NonEmptyTuple<Observable<unknown>>,
155
156
  > =
156
157
  SomeInitialized<OS> extends true
157
158
  ? InitializedMergeObservableImpl<Cast<Unwrap<OS>>>
@@ -162,27 +163,27 @@ export type CombineObservable<A extends NonEmptyUnknownList> =
162
163
  SynStateInternals.CombineObservableImpl<A>;
163
164
 
164
165
  export type CombineObservableRefined<
165
- OS extends NonEmptyArray<Observable<unknown>>,
166
+ OS extends NonEmptyTuple<Observable<unknown>>,
166
167
  > = SynStateInternals.CombineObservableRefinedImpl<OS>;
167
168
 
168
169
  export type ZipObservable<A extends NonEmptyUnknownList> =
169
170
  SynStateInternals.ZipObservableImpl<A>;
170
171
 
171
172
  export type ZipObservableRefined<
172
- OS extends NonEmptyArray<Observable<unknown>>,
173
+ OS extends NonEmptyTuple<Observable<unknown>>,
173
174
  > = SynStateInternals.ZipObservableRefinedImpl<OS>;
174
175
 
175
176
  export type MergeObservable<A extends NonEmptyUnknownList> =
176
177
  SynStateInternals.MergeObservableImpl<A>;
177
178
 
178
179
  export type MergeObservableRefined<
179
- OS extends NonEmptyArray<Observable<unknown>>,
180
+ OS extends NonEmptyTuple<Observable<unknown>>,
180
181
  > = SynStateInternals.MergeObservableRefinedImpl<OS>;
181
182
 
182
183
  export type MapOperatorObservable<A, B> = SyncChildObservable<B, readonly [A]>;
183
184
 
184
185
  export type PairwiseOperatorObservable<A> = SyncChildObservable<
185
- readonly [A, A],
186
+ FixedLengthTuple<2, A>,
186
187
  readonly [A]
187
188
  >;
188
189
 
@@ -1,5 +1,5 @@
1
1
  import { expectType, type Optional, type Some } from 'ts-data-forge';
2
- import { type StrictOmit } from 'ts-type-forge';
2
+ import { type FixedLengthTuple, type StrictOmit } from 'ts-type-forge';
3
3
  import { type ObservableId, type UpdateToken } from './id.mjs';
4
4
  import { type ObservableKind } from './observable-kind.mjs';
5
5
  import { type NonEmptyUnknownList, type Subscription } from './types.mjs';
@@ -128,9 +128,7 @@ export type InitializedRootObservable<A> =
128
128
  ObservableTypeConverter.ToInitialized<A, RootObservable<A>>;
129
129
 
130
130
  export type Observable<A> =
131
- | AsyncChildObservable<A>
132
- | RootObservable<A>
133
- | SyncChildObservable<A>;
131
+ AsyncChildObservable<A> | RootObservable<A> | SyncChildObservable<A>;
134
132
 
135
133
  export type InitializedObservable<A> =
136
134
  | InitializedAsyncChildObservable<A>
@@ -209,11 +207,11 @@ expectType<
209
207
  expectType<number, ObservableValue<Observable<string>>>('!<=');
210
208
 
211
209
  expectType<
212
- readonly [Observable<number>, Observable<number>],
213
- Wrap<readonly [number, number]>
210
+ FixedLengthTuple<2, Observable<number>>,
211
+ Wrap<FixedLengthTuple<2, number>>
214
212
  >('<=');
215
213
 
216
214
  expectType<
217
215
  readonly [Observable<number>, Observable<string>],
218
- Wrap<readonly [number, number]>
216
+ Wrap<FixedLengthTuple<2, number>>
219
217
  >('!<=');
@@ -1,10 +1,11 @@
1
1
  import { expectType, type Queue } from 'ts-data-forge';
2
+ import { type NonEmptyTuple } from 'ts-type-forge';
2
3
 
3
4
  export type TupleToQueueTuple<T extends readonly unknown[]> = Readonly<{
4
5
  [P in keyof T]: Queue<T[P]>;
5
6
  }>;
6
7
 
7
- export type NonEmptyUnknownList = readonly [unknown, ...unknown[]];
8
+ export type NonEmptyUnknownList = NonEmptyTuple<unknown>;
8
9
 
9
10
  export type Subscriber<A> = Readonly<{
10
11
  onNext: (v: A) => void;
@@ -3,5 +3,9 @@ import { type Observable } from '../types/index.mjs';
3
3
 
4
4
  export const maxDepth = (parents: readonly Observable<unknown>[]): number =>
5
5
  pipe(Arr.maxBy(parents, (p) => p.depth))
6
- .mapOptional((o) => o.depth)
6
+ // Explicit return type + non-curried `Optional.unwrapOr` call: TypeScript
7
+ // 7 (native)'s inference for this chained generic call resolves to
8
+ // `unknown` without them, even though TypeScript <= 6.0's checker
9
+ // resolves this fine.
10
+ .mapOptional((o): number => o.depth)
7
11
  .map(Optional.unwrapOr(0)).value;
@@ -15,7 +15,7 @@ export const binarySearch = <N extends number>(
15
15
  ): NegativeInt32 | Uint32 => {
16
16
  const uint32Result = asUint32(0);
17
17
 
18
- if (Arr.isArrayOfLength(sortedArray, 0)) {
18
+ if (Arr.isEmpty(sortedArray)) {
19
19
  return uint32Result;
20
20
  }
21
21
 
@@ -34,7 +34,7 @@ export const createBooleanState = (
34
34
  initialState: boolean,
35
35
  ): readonly [
36
36
  state: InitializedObservable<boolean>,
37
- Readonly<{
37
+ utils: Readonly<{
38
38
  setTrue: () => void;
39
39
  setFalse: () => void;
40
40
  setState: (next: boolean) => boolean;