synstate 0.1.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 (268) hide show
  1. package/README.md +878 -0
  2. package/dist/core/class/child-observable-class.d.mts +37 -0
  3. package/dist/core/class/child-observable-class.d.mts.map +1 -0
  4. package/dist/core/class/child-observable-class.mjs +134 -0
  5. package/dist/core/class/child-observable-class.mjs.map +1 -0
  6. package/dist/core/class/index.d.mts +4 -0
  7. package/dist/core/class/index.d.mts.map +1 -0
  8. package/dist/core/class/index.mjs +4 -0
  9. package/dist/core/class/index.mjs.map +1 -0
  10. package/dist/core/class/observable-base-class.d.mts +28 -0
  11. package/dist/core/class/observable-base-class.d.mts.map +1 -0
  12. package/dist/core/class/observable-base-class.mjs +116 -0
  13. package/dist/core/class/observable-base-class.mjs.map +1 -0
  14. package/dist/core/class/root-observable-class.d.mts +12 -0
  15. package/dist/core/class/root-observable-class.d.mts.map +1 -0
  16. package/dist/core/class/root-observable-class.mjs +35 -0
  17. package/dist/core/class/root-observable-class.mjs.map +1 -0
  18. package/dist/core/combine/combine.d.mts +35 -0
  19. package/dist/core/combine/combine.d.mts.map +1 -0
  20. package/dist/core/combine/combine.mjs +94 -0
  21. package/dist/core/combine/combine.mjs.map +1 -0
  22. package/dist/core/combine/index.d.mts +4 -0
  23. package/dist/core/combine/index.d.mts.map +1 -0
  24. package/dist/core/combine/index.mjs +4 -0
  25. package/dist/core/combine/index.mjs.map +1 -0
  26. package/dist/core/combine/merge.d.mts +28 -0
  27. package/dist/core/combine/merge.d.mts.map +1 -0
  28. package/dist/core/combine/merge.mjs +52 -0
  29. package/dist/core/combine/merge.mjs.map +1 -0
  30. package/dist/core/combine/zip.d.mts +26 -0
  31. package/dist/core/combine/zip.d.mts.map +1 -0
  32. package/dist/core/combine/zip.mjs +63 -0
  33. package/dist/core/combine/zip.mjs.map +1 -0
  34. package/dist/core/create/from-array.d.mts +21 -0
  35. package/dist/core/create/from-array.d.mts.map +1 -0
  36. package/dist/core/create/from-array.mjs +47 -0
  37. package/dist/core/create/from-array.mjs.map +1 -0
  38. package/dist/core/create/from-promise.d.mts +25 -0
  39. package/dist/core/create/from-promise.d.mts.map +1 -0
  40. package/dist/core/create/from-promise.mjs +51 -0
  41. package/dist/core/create/from-promise.mjs.map +1 -0
  42. package/dist/core/create/from-subscribable.d.mts +3 -0
  43. package/dist/core/create/from-subscribable.d.mts.map +1 -0
  44. package/dist/core/create/from-subscribable.mjs +22 -0
  45. package/dist/core/create/from-subscribable.mjs.map +1 -0
  46. package/dist/core/create/index.d.mts +8 -0
  47. package/dist/core/create/index.d.mts.map +1 -0
  48. package/dist/core/create/index.mjs +8 -0
  49. package/dist/core/create/index.mjs.map +1 -0
  50. package/dist/core/create/interval.d.mts +21 -0
  51. package/dist/core/create/interval.d.mts.map +1 -0
  52. package/dist/core/create/interval.mjs +74 -0
  53. package/dist/core/create/interval.mjs.map +1 -0
  54. package/dist/core/create/of.d.mts +20 -0
  55. package/dist/core/create/of.d.mts.map +1 -0
  56. package/dist/core/create/of.mjs +44 -0
  57. package/dist/core/create/of.mjs.map +1 -0
  58. package/dist/core/create/source.d.mts +29 -0
  59. package/dist/core/create/source.d.mts.map +1 -0
  60. package/dist/core/create/source.mjs +29 -0
  61. package/dist/core/create/source.mjs.map +1 -0
  62. package/dist/core/create/timer.d.mts +20 -0
  63. package/dist/core/create/timer.d.mts.map +1 -0
  64. package/dist/core/create/timer.mjs +64 -0
  65. package/dist/core/create/timer.mjs.map +1 -0
  66. package/dist/core/index.d.mts +7 -0
  67. package/dist/core/index.d.mts.map +1 -0
  68. package/dist/core/index.mjs +37 -0
  69. package/dist/core/index.mjs.map +1 -0
  70. package/dist/core/operators/audit-time.d.mts +3 -0
  71. package/dist/core/operators/audit-time.d.mts.map +1 -0
  72. package/dist/core/operators/audit-time.mjs +50 -0
  73. package/dist/core/operators/audit-time.mjs.map +1 -0
  74. package/dist/core/operators/debounce-time.d.mts +31 -0
  75. package/dist/core/operators/debounce-time.d.mts.map +1 -0
  76. package/dist/core/operators/debounce-time.mjs +73 -0
  77. package/dist/core/operators/debounce-time.mjs.map +1 -0
  78. package/dist/core/operators/filter.d.mts +28 -0
  79. package/dist/core/operators/filter.d.mts.map +1 -0
  80. package/dist/core/operators/filter.mjs +38 -0
  81. package/dist/core/operators/filter.mjs.map +1 -0
  82. package/dist/core/operators/index.d.mts +18 -0
  83. package/dist/core/operators/index.d.mts.map +1 -0
  84. package/dist/core/operators/index.mjs +18 -0
  85. package/dist/core/operators/index.mjs.map +1 -0
  86. package/dist/core/operators/map-with-index.d.mts +39 -0
  87. package/dist/core/operators/map-with-index.d.mts.map +1 -0
  88. package/dist/core/operators/map-with-index.mjs +73 -0
  89. package/dist/core/operators/map-with-index.mjs.map +1 -0
  90. package/dist/core/operators/merge-map.d.mts +34 -0
  91. package/dist/core/operators/merge-map.d.mts.map +1 -0
  92. package/dist/core/operators/merge-map.mjs +75 -0
  93. package/dist/core/operators/merge-map.mjs.map +1 -0
  94. package/dist/core/operators/pairwise.d.mts +27 -0
  95. package/dist/core/operators/pairwise.d.mts.map +1 -0
  96. package/dist/core/operators/pairwise.mjs +59 -0
  97. package/dist/core/operators/pairwise.mjs.map +1 -0
  98. package/dist/core/operators/scan.d.mts +30 -0
  99. package/dist/core/operators/scan.d.mts.map +1 -0
  100. package/dist/core/operators/scan.mjs +56 -0
  101. package/dist/core/operators/scan.mjs.map +1 -0
  102. package/dist/core/operators/skip-if-no-change.d.mts +33 -0
  103. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -0
  104. package/dist/core/operators/skip-if-no-change.mjs +68 -0
  105. package/dist/core/operators/skip-if-no-change.mjs.map +1 -0
  106. package/dist/core/operators/skip-until.d.mts +3 -0
  107. package/dist/core/operators/skip-until.d.mts.map +1 -0
  108. package/dist/core/operators/skip-until.mjs +33 -0
  109. package/dist/core/operators/skip-until.mjs.map +1 -0
  110. package/dist/core/operators/skip-while.d.mts +4 -0
  111. package/dist/core/operators/skip-while.d.mts.map +1 -0
  112. package/dist/core/operators/skip-while.mjs +40 -0
  113. package/dist/core/operators/skip-while.mjs.map +1 -0
  114. package/dist/core/operators/switch-map.d.mts +31 -0
  115. package/dist/core/operators/switch-map.d.mts.map +1 -0
  116. package/dist/core/operators/switch-map.mjs +70 -0
  117. package/dist/core/operators/switch-map.mjs.map +1 -0
  118. package/dist/core/operators/take-until.d.mts +32 -0
  119. package/dist/core/operators/take-until.d.mts.map +1 -0
  120. package/dist/core/operators/take-until.mjs +60 -0
  121. package/dist/core/operators/take-until.mjs.map +1 -0
  122. package/dist/core/operators/take-while.d.mts +4 -0
  123. package/dist/core/operators/take-while.d.mts.map +1 -0
  124. package/dist/core/operators/take-while.mjs +42 -0
  125. package/dist/core/operators/take-while.mjs.map +1 -0
  126. package/dist/core/operators/throttle-time.d.mts +23 -0
  127. package/dist/core/operators/throttle-time.d.mts.map +1 -0
  128. package/dist/core/operators/throttle-time.mjs +68 -0
  129. package/dist/core/operators/throttle-time.mjs.map +1 -0
  130. package/dist/core/operators/with-buffered-from.d.mts +4 -0
  131. package/dist/core/operators/with-buffered-from.d.mts.map +1 -0
  132. package/dist/core/operators/with-buffered-from.mjs +45 -0
  133. package/dist/core/operators/with-buffered-from.mjs.map +1 -0
  134. package/dist/core/operators/with-current-value-from.d.mts +4 -0
  135. package/dist/core/operators/with-current-value-from.d.mts.map +1 -0
  136. package/dist/core/operators/with-current-value-from.mjs +37 -0
  137. package/dist/core/operators/with-current-value-from.mjs.map +1 -0
  138. package/dist/core/operators/with-initial-value.d.mts +26 -0
  139. package/dist/core/operators/with-initial-value.d.mts.map +1 -0
  140. package/dist/core/operators/with-initial-value.mjs +47 -0
  141. package/dist/core/operators/with-initial-value.mjs.map +1 -0
  142. package/dist/core/types/id.d.mts +4 -0
  143. package/dist/core/types/id.d.mts.map +1 -0
  144. package/dist/core/types/id.mjs +2 -0
  145. package/dist/core/types/id.mjs.map +1 -0
  146. package/dist/core/types/index.d.mts +6 -0
  147. package/dist/core/types/index.d.mts.map +1 -0
  148. package/dist/core/types/index.mjs +3 -0
  149. package/dist/core/types/index.mjs.map +1 -0
  150. package/dist/core/types/observable-family.d.mts +68 -0
  151. package/dist/core/types/observable-family.d.mts.map +1 -0
  152. package/dist/core/types/observable-family.mjs +2 -0
  153. package/dist/core/types/observable-family.mjs.map +1 -0
  154. package/dist/core/types/observable-kind.d.mts +4 -0
  155. package/dist/core/types/observable-kind.d.mts.map +1 -0
  156. package/dist/core/types/observable-kind.mjs +2 -0
  157. package/dist/core/types/observable-kind.mjs.map +1 -0
  158. package/dist/core/types/observable.d.mts +83 -0
  159. package/dist/core/types/observable.d.mts.map +1 -0
  160. package/dist/core/types/observable.mjs +10 -0
  161. package/dist/core/types/observable.mjs.map +1 -0
  162. package/dist/core/types/types.d.mts +16 -0
  163. package/dist/core/types/types.d.mts.map +1 -0
  164. package/dist/core/types/types.mjs +2 -0
  165. package/dist/core/types/types.mjs.map +1 -0
  166. package/dist/core/utils/id-maker.d.mts +5 -0
  167. package/dist/core/utils/id-maker.d.mts.map +1 -0
  168. package/dist/core/utils/id-maker.mjs +17 -0
  169. package/dist/core/utils/id-maker.mjs.map +1 -0
  170. package/dist/core/utils/index.d.mts +5 -0
  171. package/dist/core/utils/index.d.mts.map +1 -0
  172. package/dist/core/utils/index.mjs +5 -0
  173. package/dist/core/utils/index.mjs.map +1 -0
  174. package/dist/core/utils/max-depth.d.mts +3 -0
  175. package/dist/core/utils/max-depth.d.mts.map +1 -0
  176. package/dist/core/utils/max-depth.mjs +8 -0
  177. package/dist/core/utils/max-depth.mjs.map +1 -0
  178. package/dist/core/utils/observable-utils.d.mts +3 -0
  179. package/dist/core/utils/observable-utils.d.mts.map +1 -0
  180. package/dist/core/utils/observable-utils.mjs +7 -0
  181. package/dist/core/utils/observable-utils.mjs.map +1 -0
  182. package/dist/core/utils/utils.d.mts +4 -0
  183. package/dist/core/utils/utils.d.mts.map +1 -0
  184. package/dist/core/utils/utils.mjs +38 -0
  185. package/dist/core/utils/utils.mjs.map +1 -0
  186. package/dist/entry-point.d.mts +2 -0
  187. package/dist/entry-point.d.mts.map +1 -0
  188. package/dist/entry-point.mjs +40 -0
  189. package/dist/entry-point.mjs.map +1 -0
  190. package/dist/globals.d.mts +4 -0
  191. package/dist/index.d.mts +3 -0
  192. package/dist/index.d.mts.map +1 -0
  193. package/dist/index.mjs +40 -0
  194. package/dist/index.mjs.map +1 -0
  195. package/dist/tsconfig.json +1 -0
  196. package/dist/types.d.mts +2 -0
  197. package/dist/utils/create-event-emitter.d.mts +39 -0
  198. package/dist/utils/create-event-emitter.d.mts.map +1 -0
  199. package/dist/utils/create-event-emitter.mjs +57 -0
  200. package/dist/utils/create-event-emitter.mjs.map +1 -0
  201. package/dist/utils/create-reducer.d.mts +34 -0
  202. package/dist/utils/create-reducer.d.mts.map +1 -0
  203. package/dist/utils/create-reducer.mjs +49 -0
  204. package/dist/utils/create-reducer.mjs.map +1 -0
  205. package/dist/utils/create-state.d.mts +61 -0
  206. package/dist/utils/create-state.d.mts.map +1 -0
  207. package/dist/utils/create-state.mjs +92 -0
  208. package/dist/utils/create-state.mjs.map +1 -0
  209. package/dist/utils/index.d.mts +4 -0
  210. package/dist/utils/index.d.mts.map +1 -0
  211. package/dist/utils/index.mjs +4 -0
  212. package/dist/utils/index.mjs.map +1 -0
  213. package/package.json +71 -0
  214. package/src/core/class/child-observable-class.mts +232 -0
  215. package/src/core/class/index.mts +3 -0
  216. package/src/core/class/observable-base-class.mts +186 -0
  217. package/src/core/class/observable.class.test.mts +89 -0
  218. package/src/core/class/root-observable-class.mts +68 -0
  219. package/src/core/combine/combine.mts +144 -0
  220. package/src/core/combine/index.mts +3 -0
  221. package/src/core/combine/merge.mts +84 -0
  222. package/src/core/combine/zip.mts +149 -0
  223. package/src/core/create/from-array.mts +58 -0
  224. package/src/core/create/from-promise.mts +58 -0
  225. package/src/core/create/from-subscribable.mts +37 -0
  226. package/src/core/create/index.mts +7 -0
  227. package/src/core/create/interval.mts +99 -0
  228. package/src/core/create/of.mts +54 -0
  229. package/src/core/create/source.mts +59 -0
  230. package/src/core/create/timer.mts +84 -0
  231. package/src/core/index.mts +6 -0
  232. package/src/core/operators/audit-time.mts +77 -0
  233. package/src/core/operators/debounce-time.mts +96 -0
  234. package/src/core/operators/filter.mts +125 -0
  235. package/src/core/operators/index.mts +17 -0
  236. package/src/core/operators/map-with-index.mts +168 -0
  237. package/src/core/operators/merge-map.mts +108 -0
  238. package/src/core/operators/pairwise.mts +77 -0
  239. package/src/core/operators/scan.mts +81 -0
  240. package/src/core/operators/skip-if-no-change.mts +91 -0
  241. package/src/core/operators/skip-until.mts +54 -0
  242. package/src/core/operators/skip-while.mts +77 -0
  243. package/src/core/operators/switch-map.mts +101 -0
  244. package/src/core/operators/take-until.mts +80 -0
  245. package/src/core/operators/take-while.mts +103 -0
  246. package/src/core/operators/throttle-time.mts +95 -0
  247. package/src/core/operators/with-buffered-from.mts +68 -0
  248. package/src/core/operators/with-current-value-from.mts +58 -0
  249. package/src/core/operators/with-initial-value.mts +76 -0
  250. package/src/core/types/id.mts +5 -0
  251. package/src/core/types/index.mts +5 -0
  252. package/src/core/types/observable-family.mts +259 -0
  253. package/src/core/types/observable-kind.mts +5 -0
  254. package/src/core/types/observable.mts +218 -0
  255. package/src/core/types/types.mts +40 -0
  256. package/src/core/utils/id-maker.mts +31 -0
  257. package/src/core/utils/index.mts +4 -0
  258. package/src/core/utils/max-depth.mts +7 -0
  259. package/src/core/utils/observable-utils.mts +10 -0
  260. package/src/core/utils/utils.mts +51 -0
  261. package/src/core/utils/utils.test.mts +88 -0
  262. package/src/entry-point.mts +1 -0
  263. package/src/globals.d.mts +4 -0
  264. package/src/index.mts +2 -0
  265. package/src/utils/create-event-emitter.mts +62 -0
  266. package/src/utils/create-reducer.mts +55 -0
  267. package/src/utils/create-state.mts +138 -0
  268. package/src/utils/index.mts +3 -0
@@ -0,0 +1,168 @@
1
+ import {
2
+ Optional,
3
+ Result,
4
+ SafeUint,
5
+ asSafeUint,
6
+ expectType,
7
+ } from 'ts-data-forge';
8
+ import { SyncChildObservableClass } from '../class/index.mjs';
9
+ import { source } from '../create/index.mjs';
10
+ import {
11
+ type InitializedObservable,
12
+ type KeepInitialValueOperator,
13
+ type MapWithIndexOperatorObservable,
14
+ type Observable,
15
+ type UpdaterSymbol,
16
+ } from '../types/index.mjs';
17
+ import { withInitialValue } from './with-initial-value.mjs';
18
+
19
+ /**
20
+ * Transforms each value emitted by the source using a mapping function that also receives the emission index.
21
+ *
22
+ * @template A - The type of values from the source
23
+ * @template B - The type of mapped values
24
+ * @param mapFn - A function that maps each value (receives value and index)
25
+ * @returns An operator that maps values with index
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const num$ = source<number>();
30
+ *
31
+ * const indexed$ = num$.pipe(mapWithIndex((x, i) => `${i}: ${x}`));
32
+ *
33
+ * indexed$.subscribe((s) => {
34
+ * console.log(s);
35
+ * });
36
+ *
37
+ * num$.next(10); // logs: 0: 10
38
+ *
39
+ * num$.next(20); // logs: 1: 20
40
+ * ```
41
+ */
42
+ export const mapWithIndex = <A, B>(
43
+ mapFn: (x: A, index: SafeUint | -1) => B,
44
+ ): KeepInitialValueOperator<A, B> =>
45
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
46
+ ((parentObservable) =>
47
+ new MapWithIndexObservableClass(
48
+ parentObservable,
49
+ mapFn,
50
+ )) as KeepInitialValueOperator<A, B>;
51
+
52
+ /* Specialized operators */
53
+
54
+ export const map = <A, B>(mapFn: (x: A) => B): KeepInitialValueOperator<A, B> =>
55
+ mapWithIndex(mapFn);
56
+
57
+ export const mapTo = <A, B>(value: B): KeepInitialValueOperator<A, B> =>
58
+ map(() => value);
59
+
60
+ export const pluck = <A, K extends keyof A>(
61
+ key: K,
62
+ ): KeepInitialValueOperator<A, A[K]> => map((a) => a[key]);
63
+
64
+ export const getKey = pluck; // alias
65
+
66
+ export const attachIndex = <A,>(): KeepInitialValueOperator<
67
+ A,
68
+ readonly [SafeUint | -1, A]
69
+ > => mapWithIndex((a, i) => [i, a] as const);
70
+
71
+ export const withIndex = attachIndex; // alias
72
+
73
+ export const unwrapOptional = <
74
+ O extends UnknownOptional,
75
+ >(): KeepInitialValueOperator<O, Optional.Unwrap<O> | undefined> =>
76
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
77
+ map(Optional.unwrap as Fn<O, Optional.Unwrap<O> | undefined>);
78
+
79
+ export const unwrapResultOk = <
80
+ R extends UnknownResult,
81
+ >(): KeepInitialValueOperator<R, Result.UnwrapOk<R> | undefined> =>
82
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
83
+ map(Result.unwrapOk as Fn<R, Result.UnwrapOk<R> | undefined>);
84
+
85
+ export const unwrapResultErr = <
86
+ R extends UnknownResult,
87
+ >(): KeepInitialValueOperator<R, Result.UnwrapErr<R> | undefined> =>
88
+ map(Result.unwrapErr as Fn<R, Result.UnwrapErr<R> | undefined>);
89
+
90
+ export const mapOptional = <O extends UnknownOptional, B>(
91
+ mapFn: (x: Optional.Unwrap<O>) => B,
92
+ ): KeepInitialValueOperator<O, Optional<B>> =>
93
+ map((a) => Optional.map(a, mapFn));
94
+
95
+ export const mapResultOk = <R extends UnknownResult, S2>(
96
+ mapFn: (x: Result.UnwrapOk<R>) => S2,
97
+ ): KeepInitialValueOperator<R, Result<S2, Result.UnwrapErr<R>>> =>
98
+ map((a) => Result.map(a, mapFn));
99
+
100
+ export const mapResultErr = <R extends UnknownResult, E2>(
101
+ mapFn: (x: Result.UnwrapErr<R>) => E2,
102
+ ): KeepInitialValueOperator<R, Result<Result.UnwrapOk<R>, E2>> =>
103
+ map((a) => Result.mapErr(a, mapFn));
104
+
105
+ /* implementation */
106
+
107
+ class MapWithIndexObservableClass<A, B>
108
+ extends SyncChildObservableClass<B, readonly [A]>
109
+ implements MapWithIndexOperatorObservable<A, B>
110
+ {
111
+ readonly #mapFn: (x: A, index: SafeUint | -1) => B;
112
+ #mut_index: SafeUint | -1;
113
+
114
+ constructor(
115
+ parentObservable: Observable<A>,
116
+ mapFn: (x: A, index: SafeUint | -1) => B,
117
+ ) {
118
+ super({
119
+ parents: [parentObservable],
120
+ initialValue: Optional.map(parentObservable.getSnapshot(), (x) =>
121
+ mapFn(x, -1),
122
+ ),
123
+ });
124
+
125
+ this.#mut_index = -1;
126
+
127
+ this.#mapFn = mapFn;
128
+ }
129
+
130
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
131
+ const par = this.parents[0];
132
+
133
+ const sn = par.getSnapshot();
134
+
135
+ if (par.updaterSymbol !== updaterSymbol || Optional.isNone(sn)) {
136
+ return; // skip update
137
+ }
138
+
139
+ this.#mut_index =
140
+ this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
141
+
142
+ this.setNext(this.#mapFn(sn.value, this.#mut_index), updaterSymbol);
143
+ }
144
+ }
145
+
146
+ if (import.meta.vitest !== undefined) {
147
+ test('type test', () => {
148
+ expect(1).toBe(1); // dummy
149
+ });
150
+
151
+ {
152
+ const s: Observable<number> = source<number>();
153
+
154
+ const _d1 = s.pipe(map((x) => x + 1));
155
+
156
+ expectType<typeof _d1, Observable<number>>('=');
157
+ }
158
+
159
+ {
160
+ const s = source<number>();
161
+
162
+ const m: InitializedObservable<number> = s.pipe(withInitialValue(0));
163
+
164
+ const _d = m.pipe(map((x) => x + 1));
165
+
166
+ expectType<typeof _d, InitializedObservable<number>>('=');
167
+ }
168
+ }
@@ -0,0 +1,108 @@
1
+ import { Arr, Optional } from 'ts-data-forge';
2
+ import { AsyncChildObservableClass } from '../class/index.mjs';
3
+ import {
4
+ type DropInitialValueOperator,
5
+ type MergeMapOperatorObservable,
6
+ type Observable,
7
+ type Subscription,
8
+ type UpdaterSymbol,
9
+ } from '../types/index.mjs';
10
+
11
+ /**
12
+ * Projects each source value to an observable and merges all inner observables.
13
+ * Unlike `switchMap`, does not cancel previous inner observables.
14
+ *
15
+ * @template A - The type of values from the source
16
+ * @template B - The type of values from the projected observable
17
+ * @param mapToObservable - A function that maps each source value to an observable
18
+ * @returns An operator that merges mapped observables
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const ids$ = source<number>();
23
+ *
24
+ * const users$ = ids$.pipe(mergeMap((id) => fromPromise(fetchUser(id))));
25
+ *
26
+ * users$.subscribe((user) => {
27
+ * console.log(user);
28
+ * });
29
+ * // All requests run in parallel, results merged as they arrive
30
+ *
31
+ * const fetchUser = async (id: number): Promise<unknown> => ({ id });
32
+ * ```
33
+ *
34
+ * @note To improve code readability, consider using `createState` instead of `mergeMap`,
35
+ * subscribing to `parentObservable` and calling `setState` within it.
36
+ */
37
+ export const mergeMap =
38
+ <A, B>(
39
+ mapToObservable: (curr: A) => Observable<B>,
40
+ ): DropInitialValueOperator<A, B> =>
41
+ (parentObservable) =>
42
+ new MergeMapObservableClass(parentObservable, mapToObservable);
43
+
44
+ /**
45
+ * Alias for `mergeMap()`.
46
+ * @see mergeMap
47
+ */
48
+ export const flatMap = mergeMap;
49
+
50
+ class MergeMapObservableClass<A, B>
51
+ extends AsyncChildObservableClass<B, readonly [A]>
52
+ implements MergeMapOperatorObservable<A, B>
53
+ {
54
+ readonly #mapToObservable: (curr: A) => Observable<B>;
55
+ #mut_observables: readonly Observable<B>[];
56
+ #mut_subscriptions: readonly Subscription[];
57
+
58
+ constructor(
59
+ parentObservable: Observable<A>,
60
+ mapToObservable: (curr: A) => Observable<B>,
61
+ ) {
62
+ super({
63
+ parents: [parentObservable],
64
+ initialValue: Optional.none,
65
+ });
66
+
67
+ this.#mapToObservable = mapToObservable;
68
+
69
+ this.#mut_observables = [];
70
+
71
+ this.#mut_subscriptions = [];
72
+ }
73
+
74
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
75
+ const par = this.parents[0];
76
+
77
+ const sn = par.getSnapshot();
78
+
79
+ if (par.updaterSymbol !== updaterSymbol || Optional.isNone(sn)) {
80
+ return; // skip update
81
+ }
82
+
83
+ const observable = this.#mapToObservable(sn.value);
84
+
85
+ this.#mut_observables = Arr.toPushed(this.#mut_observables, observable);
86
+
87
+ const subscription = observable.subscribe((curr) => {
88
+ this.startUpdate(curr);
89
+ });
90
+
91
+ this.#mut_subscriptions = Arr.toPushed(
92
+ this.#mut_subscriptions,
93
+ subscription,
94
+ );
95
+ }
96
+
97
+ override complete(): void {
98
+ for (const s of this.#mut_subscriptions) {
99
+ s.unsubscribe();
100
+ }
101
+
102
+ for (const o of this.#mut_observables) {
103
+ o.complete();
104
+ }
105
+
106
+ super.complete();
107
+ }
108
+ }
@@ -0,0 +1,77 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
+ import {
4
+ type DropInitialValueOperator,
5
+ type Observable,
6
+ type PairwiseOperatorObservable,
7
+ type UpdaterSymbol,
8
+ } from '../types/index.mjs';
9
+
10
+ /**
11
+ * Emits the previous and current values as a pair.
12
+ * Does not emit until the source has emitted at least twice.
13
+ *
14
+ * @template A - The type of values from the source
15
+ * @returns An operator that pairs consecutive values
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const num$ = source<number>();
20
+ *
21
+ * const pairs$ = num$.pipe(pairwise());
22
+ *
23
+ * pairs$.subscribe(([prev, curr]) => {
24
+ * console.log(prev, curr);
25
+ * });
26
+ *
27
+ * num$.next(1); // nothing logged
28
+ *
29
+ * num$.next(2); // logs: 1, 2
30
+ *
31
+ * num$.next(3); // logs: 2, 3
32
+ * ```
33
+ */
34
+ export const pairwise = <A,>(): DropInitialValueOperator<A, readonly [A, A]> =>
35
+ f;
36
+
37
+ const f = <A,>(parentObservable: Observable<A>): Observable<readonly [A, A]> =>
38
+ new PairwiseObservableClass(parentObservable);
39
+
40
+ class PairwiseObservableClass<A>
41
+ extends SyncChildObservableClass<readonly [A, A], readonly [A]>
42
+ implements PairwiseOperatorObservable<A>
43
+ {
44
+ #mut_previousValue: Optional<A>;
45
+
46
+ constructor(parentObservable: Observable<A>) {
47
+ super({
48
+ parents: [parentObservable],
49
+ initialValue: Optional.none,
50
+ });
51
+
52
+ // parentObservable.snapshot has value
53
+ // if parentObservable is InitializedObservable
54
+ this.#mut_previousValue = parentObservable.getSnapshot();
55
+ }
56
+
57
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
58
+ const par = this.parents[0];
59
+
60
+ const sn = par.getSnapshot();
61
+
62
+ if (par.updaterSymbol !== updaterSymbol || Optional.isNone(sn)) {
63
+ return; // skip update
64
+ }
65
+
66
+ const prev = this.#mut_previousValue;
67
+
68
+ const cond = !Optional.isNone(prev);
69
+
70
+ // NOTE: Must update before setNext, otherwise Optional.isNone(prev) remains true when tryUpdate is called consecutively
71
+ this.#mut_previousValue = par.getSnapshot();
72
+
73
+ if (cond) {
74
+ this.setNext([prev.value, sn.value], updaterSymbol);
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,81 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { InitializedSyncChildObservableClass } from '../class/index.mjs';
3
+ import {
4
+ type Observable,
5
+ type ScanOperatorObservable,
6
+ type UpdaterSymbol,
7
+ type WithInitialValueOperator,
8
+ } from '../types/index.mjs';
9
+
10
+ /**
11
+ * Applies an accumulator function over the source observable and emits each intermediate result.
12
+ * Similar to Array.reduce but emits accumulated value after each source emission.
13
+ *
14
+ * @template A - The type of values from the source
15
+ * @template B - The type of the accumulated value
16
+ * @param reducer - The accumulator function
17
+ * @param initialValue - The initial accumulated value (seed)
18
+ * @returns An operator that accumulates values
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const num$ = source<number>();
23
+ *
24
+ * const sum$ = num$.pipe(scan((acc, curr) => acc + curr, 0));
25
+ *
26
+ * sum$.subscribe((x) => {
27
+ * console.log(x);
28
+ * });
29
+ *
30
+ * num$.next(1); // logs: 1
31
+ *
32
+ * num$.next(2); // logs: 3
33
+ *
34
+ * num$.next(3); // logs: 6
35
+ * ```
36
+ */
37
+ export const scan =
38
+ <A, B>(
39
+ reducer: (acc: B, curr: A) => B,
40
+ initialValue: B,
41
+ ): WithInitialValueOperator<A, B> =>
42
+ (parentObservable) =>
43
+ new ScanObservableClass(parentObservable, reducer, initialValue);
44
+
45
+ class ScanObservableClass<A, B>
46
+ extends InitializedSyncChildObservableClass<B, readonly [A]>
47
+ implements ScanOperatorObservable<A, B>
48
+ {
49
+ readonly #reducer: (acc: B, curr: A) => B;
50
+
51
+ constructor(
52
+ parentObservable: Observable<A>,
53
+ reducer: (acc: B, curr: A) => B,
54
+ initialValue: B,
55
+ ) {
56
+ super({
57
+ parents: [parentObservable],
58
+ initialValue: Optional.some(initialValue),
59
+ });
60
+
61
+ this.#reducer = reducer;
62
+ }
63
+
64
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
65
+ const par = this.parents[0];
66
+
67
+ const psn = par.getSnapshot();
68
+
69
+ const sn = this.getSnapshot();
70
+
71
+ if (
72
+ par.updaterSymbol !== updaterSymbol ||
73
+ Optional.isNone(psn) ||
74
+ Optional.isNone(sn)
75
+ ) {
76
+ return; // skip update
77
+ }
78
+
79
+ this.setNext(this.#reducer(sn.value, psn.value), updaterSymbol);
80
+ }
81
+ }
@@ -0,0 +1,91 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
+ import {
4
+ type KeepInitialValueOperator,
5
+ type Observable,
6
+ type SkipIfNoChangeOperatorObservable,
7
+ type UpdaterSymbol,
8
+ } from '../types/index.mjs';
9
+
10
+ /**
11
+ * Skips emissions if the value hasn't changed from the previous emission.
12
+ * Uses a custom equality function or Object.is by default.
13
+ *
14
+ * @template A - The type of values from the source
15
+ * @param eq - Equality comparison function (default: Object.is)
16
+ * @returns An operator that skips duplicate consecutive values
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const num$ = source<number>();
21
+ *
22
+ * const distinct$ = num$.pipe(skipIfNoChange());
23
+ *
24
+ * distinct$.subscribe((x) => {
25
+ * console.log(x);
26
+ * });
27
+ *
28
+ * num$.next(1); // logs: 1
29
+ *
30
+ * num$.next(1); // nothing logged
31
+ *
32
+ * num$.next(2); // logs: 2
33
+ * ```
34
+ */
35
+ export const skipIfNoChange = <A,>(
36
+ eq: (x: A, y: A) => boolean = (x, y) => Object.is(x, y),
37
+ ): KeepInitialValueOperator<A, A> =>
38
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
39
+ ((parentObservable) =>
40
+ new SkipIfNoChangeObservableClass(
41
+ parentObservable,
42
+ eq,
43
+ )) as KeepInitialValueOperator<A, A>;
44
+
45
+ /**
46
+ * Alias for `skipIfNoChange()`.
47
+ * @see skipIfNoChange
48
+ */
49
+ export const distinctUntilChanged = skipIfNoChange; // alias
50
+
51
+ class SkipIfNoChangeObservableClass<A>
52
+ extends SyncChildObservableClass<A, readonly [A]>
53
+ implements SkipIfNoChangeOperatorObservable<A>
54
+ {
55
+ readonly #eq: (x: A, y: A) => boolean;
56
+ #mut_previousValue: Optional<A>;
57
+
58
+ constructor(parentObservable: Observable<A>, eq: (x: A, y: A) => boolean) {
59
+ super({
60
+ parents: [parentObservable],
61
+ initialValue: parentObservable.getSnapshot(),
62
+ });
63
+
64
+ // parentObservable.snapshot has value
65
+ // if parentObservable is InitializedObservable
66
+ this.#mut_previousValue = parentObservable.getSnapshot();
67
+
68
+ this.#eq = eq;
69
+ }
70
+
71
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
72
+ const par = this.parents[0];
73
+
74
+ const sn = par.getSnapshot();
75
+
76
+ if (par.updaterSymbol !== updaterSymbol || Optional.isNone(sn)) {
77
+ return; // skip update
78
+ }
79
+
80
+ const prev = this.#mut_previousValue;
81
+
82
+ const cond = Optional.isNone(prev) || !this.#eq(prev.value, sn.value);
83
+
84
+ // NOTE: Must update before setNext, otherwise Optional.isNone(prev) remains true when tryUpdate is called consecutively
85
+ this.#mut_previousValue = sn;
86
+
87
+ if (cond) {
88
+ this.setNext(sn.value, updaterSymbol);
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,54 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
+ import {
4
+ type DropInitialValueOperator,
5
+ type Observable,
6
+ type SkipUntilOperatorObservable,
7
+ type UpdaterSymbol,
8
+ } from '../types/index.mjs';
9
+
10
+ export const skipUntil =
11
+ <A,>(notifier: Observable<unknown>): DropInitialValueOperator<A, A> =>
12
+ (parentObservable) =>
13
+ new SkipUntilObservableClass(parentObservable, notifier);
14
+
15
+ class SkipUntilObservableClass<A>
16
+ extends SyncChildObservableClass<A, readonly [A]>
17
+ implements SkipUntilOperatorObservable<A>
18
+ {
19
+ #mut_isSkipping: boolean;
20
+
21
+ constructor(parentObservable: Observable<A>, notifier: Observable<unknown>) {
22
+ super({
23
+ parents: [parentObservable],
24
+ initialValue: Optional.none,
25
+ });
26
+
27
+ this.#mut_isSkipping = true;
28
+
29
+ notifier.subscribe(
30
+ () => {
31
+ this.#mut_isSkipping = false;
32
+ },
33
+ () => {
34
+ this.#mut_isSkipping = false;
35
+ },
36
+ );
37
+ }
38
+
39
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
40
+ const par = this.parents[0];
41
+
42
+ const sn = par.getSnapshot();
43
+
44
+ if (
45
+ par.updaterSymbol !== updaterSymbol ||
46
+ Optional.isNone(sn) ||
47
+ this.#mut_isSkipping
48
+ ) {
49
+ return; // skip update
50
+ }
51
+
52
+ this.setNext(sn.value, updaterSymbol);
53
+ }
54
+ }
@@ -0,0 +1,77 @@
1
+ import {
2
+ Optional,
3
+ PositiveSafeInt,
4
+ SafeUint,
5
+ asSafeUint,
6
+ pipe,
7
+ } from 'ts-data-forge';
8
+ import { SyncChildObservableClass } from '../class/index.mjs';
9
+ import {
10
+ type DropInitialValueOperator,
11
+ type Observable,
12
+ type SkipWhileOperatorObservable,
13
+ type UpdaterSymbol,
14
+ } from '../types/index.mjs';
15
+
16
+ export const skipWhile =
17
+ <A,>(
18
+ predicate: (value: A, index: SafeUint | -1) => boolean,
19
+ ): DropInitialValueOperator<A, A> =>
20
+ (parentObservable) =>
21
+ new SkipWhileObservableClass(parentObservable, predicate);
22
+
23
+ /* Specialized operators */
24
+
25
+ export const skip = <A,>(
26
+ n: PositiveSafeIntWithSmallInt,
27
+ ): DropInitialValueOperator<A, A> =>
28
+ !PositiveSafeInt.is(n) ? idFn : skipWhile((_, index) => index + 1 <= n);
29
+
30
+ const idFn = <T,>(value: T): T => value;
31
+
32
+ /* implementation */
33
+
34
+ class SkipWhileObservableClass<A>
35
+ extends SyncChildObservableClass<A, readonly [A]>
36
+ implements SkipWhileOperatorObservable<A>
37
+ {
38
+ readonly #predicate: (value: A, index: SafeUint | -1) => boolean;
39
+ #mut_index: SafeUint | -1;
40
+
41
+ constructor(
42
+ parentObservable: Observable<A>,
43
+ predicate: (value: A, index: SafeUint | -1) => boolean,
44
+ ) {
45
+ super({
46
+ parents: [parentObservable],
47
+ initialValue: pipe(parentObservable.getSnapshot()).map((sn) =>
48
+ Optional.isNone(sn)
49
+ ? Optional.none
50
+ : predicate(sn.value, -1)
51
+ ? Optional.none
52
+ : sn,
53
+ ).value,
54
+ });
55
+
56
+ this.#mut_index = -1;
57
+
58
+ this.#predicate = predicate;
59
+ }
60
+
61
+ override tryUpdate(updaterSymbol: UpdaterSymbol): void {
62
+ const par = this.parents[0];
63
+
64
+ const sn = par.getSnapshot();
65
+
66
+ if (par.updaterSymbol !== updaterSymbol || Optional.isNone(sn)) {
67
+ return; // skip update
68
+ }
69
+
70
+ this.#mut_index =
71
+ this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
72
+
73
+ if (!this.#predicate(sn.value, this.#mut_index)) {
74
+ this.setNext(sn.value, updaterSymbol);
75
+ }
76
+ }
77
+ }