synstate 3.4.0 → 4.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 (305) hide show
  1. package/README.md +13 -8
  2. package/dist/core/authoring.d.mts +7 -0
  3. package/dist/core/authoring.d.mts.map +1 -0
  4. package/dist/core/authoring.mjs +7 -0
  5. package/dist/core/authoring.mjs.map +1 -0
  6. package/dist/core/class/child-observable-class.d.mts +93 -0
  7. package/dist/core/class/child-observable-class.d.mts.map +1 -0
  8. package/dist/core/class/child-observable-class.mjs +248 -0
  9. package/dist/core/class/child-observable-class.mjs.map +1 -0
  10. package/dist/core/class/create-child-observable.d.mts +159 -0
  11. package/dist/core/class/create-child-observable.d.mts.map +1 -0
  12. package/dist/core/class/create-child-observable.mjs +183 -0
  13. package/dist/core/class/create-child-observable.mjs.map +1 -0
  14. package/dist/core/class/create-root-observable.d.mts +37 -0
  15. package/dist/core/class/create-root-observable.d.mts.map +1 -0
  16. package/dist/core/class/create-root-observable.mjs +52 -0
  17. package/dist/core/class/create-root-observable.mjs.map +1 -0
  18. package/dist/core/class/index.d.mts +9 -0
  19. package/dist/core/class/index.d.mts.map +1 -0
  20. package/dist/core/class/index.mjs +9 -0
  21. package/dist/core/class/index.mjs.map +1 -0
  22. package/dist/core/class/manager-observable-class.d.mts +26 -0
  23. package/dist/core/class/manager-observable-class.d.mts.map +1 -0
  24. package/dist/core/class/manager-observable-class.mjs +116 -0
  25. package/dist/core/class/manager-observable-class.mjs.map +1 -0
  26. package/dist/core/class/observable-base-class.d.mts +97 -0
  27. package/dist/core/class/observable-base-class.d.mts.map +1 -0
  28. package/dist/core/class/observable-base-class.mjs +219 -0
  29. package/dist/core/class/observable-base-class.mjs.map +1 -0
  30. package/dist/core/class/observable-node.d.mts +40 -0
  31. package/dist/core/class/observable-node.d.mts.map +1 -0
  32. package/dist/core/class/observable-node.mjs +10 -0
  33. package/dist/core/class/observable-node.mjs.map +1 -0
  34. package/dist/core/class/propagation-state.d.mts +24 -0
  35. package/dist/core/class/propagation-state.d.mts.map +1 -0
  36. package/dist/core/class/propagation-state.mjs +66 -0
  37. package/dist/core/class/propagation-state.mjs.map +1 -0
  38. package/dist/core/class/root-observable-class.d.mts +23 -0
  39. package/dist/core/class/root-observable-class.d.mts.map +1 -0
  40. package/dist/core/class/root-observable-class.mjs +58 -0
  41. package/dist/core/class/root-observable-class.mjs.map +1 -0
  42. package/dist/core/combine/combine.d.mts.map +1 -1
  43. package/dist/core/combine/combine.mjs +25 -14
  44. package/dist/core/combine/combine.mjs.map +1 -1
  45. package/dist/core/combine/merge.d.mts.map +1 -1
  46. package/dist/core/combine/merge.mjs +26 -14
  47. package/dist/core/combine/merge.mjs.map +1 -1
  48. package/dist/core/combine/zip.d.mts.map +1 -1
  49. package/dist/core/combine/zip.mjs +43 -25
  50. package/dist/core/combine/zip.mjs.map +1 -1
  51. package/dist/core/create/counter.d.mts.map +1 -1
  52. package/dist/core/create/counter.mjs +65 -44
  53. package/dist/core/create/counter.mjs.map +1 -1
  54. package/dist/core/create/empty.d.mts +32 -0
  55. package/dist/core/create/empty.d.mts.map +1 -0
  56. package/dist/core/create/empty.mjs +39 -0
  57. package/dist/core/create/empty.mjs.map +1 -0
  58. package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
  59. package/dist/core/create/from-abortable-promise.mjs +26 -18
  60. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  61. package/dist/core/create/from-promise.d.mts.map +1 -1
  62. package/dist/core/create/from-promise.mjs +30 -21
  63. package/dist/core/create/from-promise.mjs.map +1 -1
  64. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  65. package/dist/core/create/from-subscribable.mjs +37 -24
  66. package/dist/core/create/from-subscribable.mjs.map +1 -1
  67. package/dist/core/create/index.d.mts +1 -0
  68. package/dist/core/create/index.d.mts.map +1 -1
  69. package/dist/core/create/index.mjs +1 -0
  70. package/dist/core/create/index.mjs.map +1 -1
  71. package/dist/core/create/just.d.mts.map +1 -1
  72. package/dist/core/create/just.mjs +4 -2
  73. package/dist/core/create/just.mjs.map +1 -1
  74. package/dist/core/create/source.d.mts.map +1 -1
  75. package/dist/core/create/source.mjs +27 -17
  76. package/dist/core/create/source.mjs.map +1 -1
  77. package/dist/core/create/timer.d.mts.map +1 -1
  78. package/dist/core/create/timer.mjs +40 -25
  79. package/dist/core/create/timer.mjs.map +1 -1
  80. package/dist/core/index.d.mts +2 -0
  81. package/dist/core/index.d.mts.map +1 -1
  82. package/dist/core/index.mjs +2 -0
  83. package/dist/core/index.mjs.map +1 -1
  84. package/dist/core/operators/audit.d.mts +3 -0
  85. package/dist/core/operators/audit.d.mts.map +1 -1
  86. package/dist/core/operators/audit.mjs +56 -28
  87. package/dist/core/operators/audit.mjs.map +1 -1
  88. package/dist/core/operators/debounce.d.mts +3 -0
  89. package/dist/core/operators/debounce.d.mts.map +1 -1
  90. package/dist/core/operators/debounce.mjs +57 -26
  91. package/dist/core/operators/debounce.mjs.map +1 -1
  92. package/dist/core/operators/filter.d.mts.map +1 -1
  93. package/dist/core/operators/filter.mjs +35 -21
  94. package/dist/core/operators/filter.mjs.map +1 -1
  95. package/dist/core/operators/index.d.mts +1 -0
  96. package/dist/core/operators/index.d.mts.map +1 -1
  97. package/dist/core/operators/index.mjs +1 -0
  98. package/dist/core/operators/index.mjs.map +1 -1
  99. package/dist/core/operators/map.d.mts.map +1 -1
  100. package/dist/core/operators/map.mjs +31 -17
  101. package/dist/core/operators/map.mjs.map +1 -1
  102. package/dist/core/operators/merge-map.d.mts +5 -0
  103. package/dist/core/operators/merge-map.d.mts.map +1 -1
  104. package/dist/core/operators/merge-map.mjs +52 -26
  105. package/dist/core/operators/merge-map.mjs.map +1 -1
  106. package/dist/core/operators/pairwise.d.mts.map +1 -1
  107. package/dist/core/operators/pairwise.mjs +31 -20
  108. package/dist/core/operators/pairwise.mjs.map +1 -1
  109. package/dist/core/operators/scan.d.mts.map +1 -1
  110. package/dist/core/operators/scan.mjs +27 -14
  111. package/dist/core/operators/scan.mjs.map +1 -1
  112. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  113. package/dist/core/operators/skip-if-no-change.mjs +33 -19
  114. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  115. package/dist/core/operators/skip-until.d.mts.map +1 -1
  116. package/dist/core/operators/skip-until.mjs +40 -26
  117. package/dist/core/operators/skip-until.mjs.map +1 -1
  118. package/dist/core/operators/skip-while.d.mts.map +1 -1
  119. package/dist/core/operators/skip-while.mjs +35 -20
  120. package/dist/core/operators/skip-while.mjs.map +1 -1
  121. package/dist/core/operators/switch-map.d.mts +5 -0
  122. package/dist/core/operators/switch-map.d.mts.map +1 -1
  123. package/dist/core/operators/switch-map.mjs +56 -25
  124. package/dist/core/operators/switch-map.mjs.map +1 -1
  125. package/dist/core/operators/take-until.d.mts.map +1 -1
  126. package/dist/core/operators/take-until.mjs +36 -29
  127. package/dist/core/operators/take-until.mjs.map +1 -1
  128. package/dist/core/operators/take-while.d.mts.map +1 -1
  129. package/dist/core/operators/take-while.mjs +36 -22
  130. package/dist/core/operators/take-while.mjs.map +1 -1
  131. package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
  132. package/dist/core/operators/take.d.mts.map +1 -0
  133. package/dist/core/operators/take.mjs +102 -0
  134. package/dist/core/operators/take.mjs.map +1 -0
  135. package/dist/core/operators/throttle.d.mts.map +1 -1
  136. package/dist/core/operators/throttle.mjs +41 -26
  137. package/dist/core/operators/throttle.mjs.map +1 -1
  138. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  139. package/dist/core/operators/with-buffered-from.mjs +43 -39
  140. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  141. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  142. package/dist/core/operators/with-current-value-from.mjs +36 -21
  143. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  144. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  145. package/dist/core/operators/with-initial-value.mjs +24 -12
  146. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  147. package/dist/core/predefined/operators/index.d.mts +0 -1
  148. package/dist/core/predefined/operators/index.d.mts.map +1 -1
  149. package/dist/core/predefined/operators/index.mjs +0 -1
  150. package/dist/core/predefined/operators/index.mjs.map +1 -1
  151. package/dist/core/scope/index.d.mts +2 -0
  152. package/dist/core/scope/index.d.mts.map +1 -0
  153. package/dist/core/scope/index.mjs +2 -0
  154. package/dist/core/scope/index.mjs.map +1 -0
  155. package/dist/core/scope/scope.d.mts +51 -0
  156. package/dist/core/scope/scope.d.mts.map +1 -0
  157. package/dist/core/scope/scope.mjs +194 -0
  158. package/dist/core/scope/scope.mjs.map +1 -0
  159. package/dist/core/types/id.d.mts +0 -2
  160. package/dist/core/types/id.d.mts.map +1 -1
  161. package/dist/core/types/index.d.mts +1 -0
  162. package/dist/core/types/index.d.mts.map +1 -1
  163. package/dist/core/types/index.mjs +1 -0
  164. package/dist/core/types/index.mjs.map +1 -1
  165. package/dist/core/types/observable-family.d.mts +1 -0
  166. package/dist/core/types/observable-family.d.mts.map +1 -1
  167. package/dist/core/types/observable.d.mts +40 -28
  168. package/dist/core/types/observable.d.mts.map +1 -1
  169. package/dist/core/types/observable.mjs +24 -0
  170. package/dist/core/types/observable.mjs.map +1 -1
  171. package/dist/core/types/scope.d.mts +27 -0
  172. package/dist/core/types/scope.d.mts.map +1 -0
  173. package/dist/core/types/scope.mjs +2 -0
  174. package/dist/core/types/scope.mjs.map +1 -0
  175. package/dist/core/utils/current-scope.d.mts +30 -0
  176. package/dist/core/utils/current-scope.d.mts.map +1 -0
  177. package/dist/core/utils/current-scope.mjs +26 -0
  178. package/dist/core/utils/current-scope.mjs.map +1 -0
  179. package/dist/core/utils/id-maker.d.mts +14 -3
  180. package/dist/core/utils/id-maker.d.mts.map +1 -1
  181. package/dist/core/utils/id-maker.mjs +18 -14
  182. package/dist/core/utils/id-maker.mjs.map +1 -1
  183. package/dist/core/utils/index.d.mts +2 -1
  184. package/dist/core/utils/index.d.mts.map +1 -1
  185. package/dist/core/utils/index.mjs +2 -1
  186. package/dist/core/utils/index.mjs.map +1 -1
  187. package/dist/core/utils/max-depth.d.mts +1 -0
  188. package/dist/core/utils/max-depth.d.mts.map +1 -1
  189. package/dist/core/utils/max-depth.mjs +9 -7
  190. package/dist/core/utils/max-depth.mjs.map +1 -1
  191. package/dist/core/utils/observable-brand.d.mts +13 -0
  192. package/dist/core/utils/observable-brand.d.mts.map +1 -0
  193. package/dist/core/utils/observable-brand.mjs +13 -0
  194. package/dist/core/utils/observable-brand.mjs.map +1 -0
  195. package/dist/utils/create-boolean-state.d.mts +2 -2
  196. package/dist/utils/create-reducer.d.mts +7 -7
  197. package/dist/utils/create-reducer.mjs +2 -2
  198. package/dist/utils/create-reducer.mjs.map +1 -1
  199. package/dist/utils/create-state.d.mts +2 -2
  200. package/package.json +17 -17
  201. package/src/core/authoring.mts +19 -0
  202. package/src/core/class/child-observable-class.mts +374 -0
  203. package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
  204. package/src/core/{base → class}/circular-dependency.test.mts +38 -2
  205. package/src/core/class/complete-propagation.test.mts +264 -0
  206. package/src/core/class/create-child-observable.mts +370 -0
  207. package/src/core/class/create-root-observable.mts +107 -0
  208. package/src/core/class/index.mts +8 -0
  209. package/src/core/class/manager-observable-class.mts +166 -0
  210. package/src/core/class/observable-base-class.mts +315 -0
  211. package/src/core/{base → class}/observable-base.test.mts +1 -1
  212. package/src/core/class/observable-node.mts +54 -0
  213. package/src/core/class/propagation-order.test.mts +92 -0
  214. package/src/core/class/propagation-state.mts +80 -0
  215. package/src/core/class/remove-completed.test.mts +369 -0
  216. package/src/core/class/root-observable-class.mts +85 -0
  217. package/src/core/{base → class}/write-during-propagation.test.mts +3 -3
  218. package/src/core/combine/combine.mts +67 -55
  219. package/src/core/combine/merge.mts +33 -23
  220. package/src/core/combine/zip.mts +95 -81
  221. package/src/core/create/counter.mts +95 -65
  222. package/src/core/create/empty.mts +42 -0
  223. package/src/core/create/empty.test.mts +65 -0
  224. package/src/core/create/from-abortable-promise.mts +54 -42
  225. package/src/core/create/from-promise.mts +38 -29
  226. package/src/core/create/from-subscribable.mts +62 -45
  227. package/src/core/create/from-subscribable.test.mts +8 -3
  228. package/src/core/create/index.mts +1 -0
  229. package/src/core/create/just.mts +4 -5
  230. package/src/core/create/source.mts +29 -20
  231. package/src/core/create/timer.mts +51 -32
  232. package/src/core/index.mts +2 -0
  233. package/src/core/operators/audit.mts +79 -45
  234. package/src/core/operators/debounce-audit-complete.test.mts +121 -0
  235. package/src/core/operators/debounce.mts +81 -40
  236. package/src/core/operators/filter.mts +48 -31
  237. package/src/core/operators/index.mts +1 -0
  238. package/src/core/operators/inner-ownership.test.mts +280 -0
  239. package/src/core/operators/map.mts +49 -32
  240. package/src/core/operators/merge-map.mts +81 -43
  241. package/src/core/operators/pairwise.mts +40 -30
  242. package/src/core/operators/scan.mts +41 -24
  243. package/src/core/operators/skip-if-no-change.mts +45 -31
  244. package/src/core/operators/skip-until.mts +55 -41
  245. package/src/core/operators/skip-while.mts +49 -31
  246. package/src/core/operators/switch-map.mts +74 -35
  247. package/src/core/operators/take-until-skip-until-teardown.test.mts +18 -12
  248. package/src/core/operators/take-until.mts +45 -41
  249. package/src/core/operators/take-while.mts +50 -33
  250. package/src/core/operators/take.mts +138 -0
  251. package/src/core/operators/throttle.mts +55 -38
  252. package/src/core/operators/with-buffered-from.mts +42 -44
  253. package/src/core/operators/with-buffered-from.test.mts +4 -3
  254. package/src/core/operators/with-current-value-from.mts +39 -25
  255. package/src/core/operators/with-initial-value.mts +30 -21
  256. package/src/core/predefined/operators/index.mts +0 -1
  257. package/src/core/scope/index.mts +1 -0
  258. package/src/core/scope/scope.mts +260 -0
  259. package/src/core/scope/scope.test.mts +462 -0
  260. package/src/core/types/id.mts +0 -4
  261. package/src/core/types/index.mts +1 -0
  262. package/src/core/types/observable-family.mts +2 -0
  263. package/src/core/types/observable.mts +77 -42
  264. package/src/core/types/scope.mts +27 -0
  265. package/src/core/utils/current-scope.mts +61 -0
  266. package/src/core/utils/id-maker.mts +20 -30
  267. package/src/core/utils/index.mts +2 -1
  268. package/src/core/utils/max-depth.mts +12 -9
  269. package/src/core/utils/observable-brand.mts +12 -0
  270. package/src/utils/create-boolean-state.mts +2 -2
  271. package/src/utils/create-boolean-state.test.mts +18 -4
  272. package/src/utils/create-reducer.mts +9 -9
  273. package/src/utils/create-reducer.test.mts +31 -14
  274. package/src/utils/create-state.mts +2 -2
  275. package/src/utils/create-state.test.mts +12 -14
  276. package/dist/core/base/create-child-observable.d.mts +0 -72
  277. package/dist/core/base/create-child-observable.d.mts.map +0 -1
  278. package/dist/core/base/create-child-observable.mjs +0 -285
  279. package/dist/core/base/create-child-observable.mjs.map +0 -1
  280. package/dist/core/base/create-observable-base.d.mts +0 -149
  281. package/dist/core/base/create-observable-base.d.mts.map +0 -1
  282. package/dist/core/base/create-observable-base.mjs +0 -273
  283. package/dist/core/base/create-observable-base.mjs.map +0 -1
  284. package/dist/core/base/create-root-observable.d.mts +0 -46
  285. package/dist/core/base/create-root-observable.d.mts.map +0 -1
  286. package/dist/core/base/create-root-observable.mjs +0 -80
  287. package/dist/core/base/create-root-observable.mjs.map +0 -1
  288. package/dist/core/base/index.d.mts +0 -3
  289. package/dist/core/base/index.d.mts.map +0 -1
  290. package/dist/core/base/index.mjs +0 -3
  291. package/dist/core/base/index.mjs.map +0 -1
  292. package/dist/core/predefined/operators/take.d.mts.map +0 -1
  293. package/dist/core/predefined/operators/take.mjs +0 -44
  294. package/dist/core/predefined/operators/take.mjs.map +0 -1
  295. package/dist/core/utils/utils.d.mts +0 -6
  296. package/dist/core/utils/utils.d.mts.map +0 -1
  297. package/dist/core/utils/utils.mjs +0 -37
  298. package/dist/core/utils/utils.mjs.map +0 -1
  299. package/src/core/base/create-child-observable.mts +0 -485
  300. package/src/core/base/create-observable-base.mts +0 -568
  301. package/src/core/base/create-root-observable.mts +0 -147
  302. package/src/core/base/index.mts +0 -2
  303. package/src/core/predefined/operators/take.mts +0 -48
  304. package/src/core/utils/utils.mts +0 -56
  305. package/src/core/utils/utils.test.mts +0 -88
@@ -1,10 +1,18 @@
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).
4
+ // @sumi-expect-error-file classes/no-class
5
+ // @sumi-expect-error-file banned-syntax/no-this
6
+ // @sumi-expect-error-file functions/prefer-arrow-function
7
+ // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
1
8
  import { Optional } from 'ts-data-forge';
2
- import { createAsyncChildObservable } from '../base/index.mjs';
9
+ import { AsyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  AuditOperatorObservable,
5
12
  KeepInitialValueOperator,
6
13
  Observable,
7
14
  TimerId,
15
+ UpdateToken,
8
16
  } from '../types/index.mjs';
9
17
 
10
18
  /**
@@ -12,6 +20,9 @@ import type {
12
20
  *
13
21
  * Unlike `throttle` which emits the first value, `audit` emits the last value.
14
22
  *
23
+ * Completing it, or its source completing, passes on the value its timer is
24
+ * still holding before the completion; `dispose()` drops that value.
25
+ *
15
26
  * @template A - The type of values from the source
16
27
  * @param milliSeconds - The audit time window in milliseconds
17
28
  * @returns An operator that audits emissions from the observable
@@ -106,55 +117,78 @@ export const audit = <A,>(
106
117
  ): KeepInitialValueOperator<A, A> =>
107
118
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
108
119
  ((parentObservable) =>
109
- createAuditObservable(
120
+ new AuditObservableClass(
110
121
  parentObservable,
111
122
  milliSeconds,
112
123
  )) as KeepInitialValueOperator<A, A>;
113
124
 
114
- const createAuditObservable = <A,>(
115
- parentObservable: Observable<A>,
116
- milliSeconds: number,
117
- ): AuditOperatorObservable<A> => {
118
- let mut_timerId: TimerId | undefined = undefined;
119
-
120
- let mut_isSkipping = false;
125
+ class AuditObservableClass<A>
126
+ extends AsyncChildObservableClass<A, readonly [A]>
127
+ implements AuditOperatorObservable<A>
128
+ {
129
+ readonly #milliSeconds: number;
121
130
 
122
- const resetTimer = (): void => {
123
- if (mut_timerId !== undefined) {
124
- clearTimeout(mut_timerId);
125
- }
126
- };
131
+ /** Set while a window is open; its end passes on the latest value. */
132
+ #mut_timerId: TimerId | undefined;
127
133
 
128
- return createAsyncChildObservable(
129
- {
134
+ constructor(parentObservable: Observable<A>, milliSeconds: number) {
135
+ super({
130
136
  parents: [parentObservable],
131
137
  initialValue: parentObservable.getSnapshot(),
132
- onComplete: resetTimer,
133
- },
134
- ({ startUpdate }) =>
135
- (updateToken) => {
136
- if (
137
- mut_isSkipping ||
138
- parentObservable.updateToken !== updateToken ||
139
- Optional.isNone(parentObservable.getSnapshot())
140
- ) {
141
- return; // skip update
142
- }
143
-
144
- // set timer
145
- mut_isSkipping = true;
146
-
147
- mut_timerId = setTimeout(() => {
148
- const sn = parentObservable.getSnapshot();
149
-
150
- if (Optional.isNone(sn)) {
151
- return;
152
- }
153
-
154
- startUpdate(sn.value);
155
-
156
- mut_isSkipping = false;
157
- }, milliSeconds);
158
- },
159
- );
160
- };
138
+ });
139
+
140
+ this.#mut_timerId = undefined;
141
+
142
+ this.#milliSeconds = milliSeconds;
143
+
144
+ this.attach();
145
+ }
146
+
147
+ /** Ends the open window, passing on the latest value of the source. */
148
+ #emitLatest(): void {
149
+ this.#mut_timerId = undefined;
150
+
151
+ const sn = this.parents[0].getSnapshot();
152
+
153
+ if (Optional.isSome(sn)) {
154
+ this.startUpdate(sn.value);
155
+ }
156
+ }
157
+
158
+ override tryUpdate(updateToken: UpdateToken): void {
159
+ const par = this.parents[0];
160
+
161
+ if (
162
+ this.#mut_timerId !== undefined ||
163
+ par.updateToken !== updateToken ||
164
+ Optional.isNone(par.getSnapshot())
165
+ ) {
166
+ return; // skip update
167
+ }
168
+
169
+ // set timer
170
+ this.#mut_timerId = setTimeout(() => {
171
+ this.#emitLatest();
172
+ }, this.#milliSeconds);
173
+ }
174
+
175
+ protected override flush(): void {
176
+ if (this.#mut_timerId === undefined) {
177
+ return;
178
+ }
179
+
180
+ clearTimeout(this.#mut_timerId);
181
+
182
+ this.#emitLatest();
183
+ }
184
+
185
+ protected override teardown(): void {
186
+ if (this.#mut_timerId === undefined) {
187
+ return;
188
+ }
189
+
190
+ clearTimeout(this.#mut_timerId);
191
+
192
+ this.#mut_timerId = undefined;
193
+ }
194
+ }
@@ -0,0 +1,121 @@
1
+ import { source } from '../create/index.mjs';
2
+ import type {
3
+ KeepInitialValueOperator,
4
+ Observable,
5
+ SourceObservable,
6
+ } from '../types/index.mjs';
7
+ import { audit } from './audit.mjs';
8
+ import { debounce } from './debounce.mjs';
9
+
10
+ const operators: readonly (readonly [
11
+ string,
12
+ (milliSeconds: number) => KeepInitialValueOperator<number, number>,
13
+ ])[] = [
14
+ ['debounce', debounce],
15
+ ['audit', audit],
16
+ ] as const;
17
+
18
+ describe.each(operators)('%s', (_name, operator) => {
19
+ const setup = (): Readonly<{
20
+ source$: SourceObservable<number>;
21
+ operated$: Observable<number>;
22
+ history: readonly string[];
23
+ }> => {
24
+ const source$ = source<number>();
25
+
26
+ const operated$ = source$.pipe(operator(100));
27
+
28
+ const mut_history: string[] = [];
29
+
30
+ operated$.subscribe(
31
+ (v) => {
32
+ mut_history.push(`next ${v}`);
33
+ },
34
+ () => {
35
+ mut_history.push('complete');
36
+ },
37
+ );
38
+
39
+ return { source$, operated$, history: mut_history };
40
+ };
41
+
42
+ test('passes on the pending value, then completes, when the source completes within the window', () => {
43
+ vi.useFakeTimers();
44
+
45
+ const { source$, operated$, history } = setup();
46
+
47
+ source$.next(1);
48
+
49
+ source$.next(2);
50
+
51
+ source$.complete();
52
+
53
+ assert.deepStrictEqual(history, ['next 2', 'complete']);
54
+
55
+ assert.isTrue(operated$.isCompleted);
56
+
57
+ vi.runAllTimers();
58
+
59
+ assert.deepStrictEqual(history, ['next 2', 'complete']);
60
+
61
+ vi.useRealTimers();
62
+ });
63
+
64
+ test('passes on the pending value, then completes, when it is completed itself', () => {
65
+ vi.useFakeTimers();
66
+
67
+ const { source$, operated$, history } = setup();
68
+
69
+ source$.next(1);
70
+
71
+ operated$.complete();
72
+
73
+ assert.deepStrictEqual(history, ['next 1', 'complete']);
74
+
75
+ vi.runAllTimers();
76
+
77
+ assert.deepStrictEqual(history, ['next 1', 'complete']);
78
+
79
+ vi.useRealTimers();
80
+ });
81
+
82
+ test('completes without emitting when nothing is pending', () => {
83
+ vi.useFakeTimers();
84
+
85
+ const { source$, operated$, history } = setup();
86
+
87
+ source$.next(1);
88
+
89
+ vi.runAllTimers();
90
+
91
+ assert.deepStrictEqual(history, ['next 1']);
92
+
93
+ operated$.complete();
94
+
95
+ assert.deepStrictEqual(history, ['next 1', 'complete']);
96
+
97
+ vi.useRealTimers();
98
+ });
99
+
100
+ test('drops the pending value when it is disposed', () => {
101
+ vi.useFakeTimers();
102
+
103
+ const { source$, operated$, history } = setup();
104
+
105
+ source$.next(1);
106
+
107
+ operated$.dispose();
108
+
109
+ assert.deepStrictEqual(history, ['complete']);
110
+
111
+ assert.isTrue(operated$.isCompleted);
112
+
113
+ vi.runAllTimers();
114
+
115
+ assert.deepStrictEqual(history, ['complete']);
116
+
117
+ assert.isFalse(source$.isCompleted);
118
+
119
+ vi.useRealTimers();
120
+ });
121
+ });
@@ -1,16 +1,27 @@
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).
4
+ // @sumi-expect-error-file classes/no-class
5
+ // @sumi-expect-error-file banned-syntax/no-this
6
+ // @sumi-expect-error-file functions/prefer-arrow-function
7
+ // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
1
8
  import { Optional } from 'ts-data-forge';
2
- import { createAsyncChildObservable } from '../base/index.mjs';
9
+ import { AsyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  DebounceOperatorObservable,
5
12
  KeepInitialValueOperator,
6
13
  Observable,
7
14
  TimerId,
15
+ UpdateToken,
8
16
  } from '../types/index.mjs';
9
17
 
10
18
  /**
11
19
  * Delays emissions from the source observable until a specified time has passed without another emission.
12
20
  * Useful for handling user input events like typing or scrolling.
13
21
  *
22
+ * Completing it, or its source completing, passes on the value its timer is
23
+ * still holding before the completion; `dispose()` drops that value.
24
+ *
14
25
  * @template A - The type of values from the source
15
26
  * @param milliSeconds - The debounce duration in milliseconds
16
27
  * @returns An operator that debounces the observable
@@ -97,50 +108,80 @@ export const debounce = <A,>(
97
108
  ): KeepInitialValueOperator<A, A> =>
98
109
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
99
110
  ((parentObservable) =>
100
- createDebounceObservable(
111
+ new DebounceObservableClass(
101
112
  parentObservable,
102
113
  milliSeconds,
103
114
  )) as KeepInitialValueOperator<A, A>;
104
115
 
105
- const createDebounceObservable = <A,>(
106
- parentObservable: Observable<A>,
107
- milliSeconds: number,
108
- ): DebounceOperatorObservable<A> => {
109
- let mut_timerId: TimerId | undefined = undefined;
116
+ class DebounceObservableClass<A>
117
+ extends AsyncChildObservableClass<A, readonly [A]>
118
+ implements DebounceOperatorObservable<A>
119
+ {
120
+ readonly #milliSeconds: number;
121
+ #mut_timerId: TimerId | undefined;
110
122
 
111
- const resetTimer = (): void => {
112
- if (mut_timerId !== undefined) {
113
- clearTimeout(mut_timerId);
114
- }
115
- };
123
+ /** The value the running timer will pass on. */
124
+ #mut_pending: Optional<A>;
116
125
 
117
- return createAsyncChildObservable(
118
- {
126
+ constructor(parentObservable: Observable<A>, milliSeconds: number) {
127
+ super({
119
128
  parents: [parentObservable],
120
129
  initialValue: parentObservable.getSnapshot(),
121
- onComplete: resetTimer,
122
- },
123
- ({ startUpdate }) =>
124
- (updateToken) => {
125
- const sn = parentObservable.getSnapshot();
126
-
127
- if (
128
- parentObservable.updateToken !== updateToken ||
129
- Optional.isNone(sn)
130
- ) {
131
- return; // skip update
132
- }
133
-
134
- resetTimer();
135
-
136
- // set timer
137
- mut_timerId = setTimeout(() => {
138
- if (Optional.isNone(sn)) {
139
- return;
140
- }
141
-
142
- startUpdate(sn.value);
143
- }, milliSeconds);
144
- },
145
- );
146
- };
130
+ });
131
+
132
+ this.#mut_timerId = undefined;
133
+
134
+ this.#mut_pending = Optional.none;
135
+
136
+ this.#milliSeconds = milliSeconds;
137
+
138
+ this.attach();
139
+ }
140
+
141
+ #resetTimer(): void {
142
+ if (this.#mut_timerId !== undefined) {
143
+ clearTimeout(this.#mut_timerId);
144
+
145
+ this.#mut_timerId = undefined;
146
+ }
147
+
148
+ this.#mut_pending = Optional.none;
149
+ }
150
+
151
+ override tryUpdate(updateToken: UpdateToken): void {
152
+ const par = this.parents[0];
153
+
154
+ const sn = par.getSnapshot();
155
+
156
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
157
+ return; // skip update
158
+ }
159
+
160
+ this.#resetTimer();
161
+
162
+ this.#mut_pending = sn;
163
+
164
+ // set timer
165
+ this.#mut_timerId = setTimeout(() => {
166
+ this.#mut_timerId = undefined;
167
+
168
+ this.#mut_pending = Optional.none;
169
+
170
+ this.startUpdate(sn.value);
171
+ }, this.#milliSeconds);
172
+ }
173
+
174
+ protected override flush(): void {
175
+ const pending = this.#mut_pending;
176
+
177
+ this.#resetTimer();
178
+
179
+ if (Optional.isSome(pending)) {
180
+ this.startUpdate(pending.value);
181
+ }
182
+ }
183
+
184
+ protected override teardown(): void {
185
+ this.#resetTimer();
186
+ }
187
+ }
@@ -1,3 +1,10 @@
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).
4
+ // @sumi-expect-error-file classes/no-class
5
+ // @sumi-expect-error-file banned-syntax/no-this
6
+ // @sumi-expect-error-file functions/prefer-arrow-function
7
+ // @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
1
8
  import {
2
9
  asSafeUint,
3
10
  expectType,
@@ -7,13 +14,14 @@ import {
7
14
  pipe,
8
15
  SafeUint,
9
16
  } from 'ts-data-forge';
10
- import { createSyncChildObservable } from '../base/index.mjs';
17
+ import { SyncChildObservableClass } from '../class/index.mjs';
11
18
  import { source } from '../create/index.mjs';
12
19
  import type {
13
20
  DropInitialValueOperator,
14
21
  FilterOperatorObservable,
15
22
  InitializedObservable,
16
23
  Observable,
24
+ UpdateToken,
17
25
  } from '../types/index.mjs';
18
26
  import { withInitialValue } from './with-initial-value.mjs';
19
27
 
@@ -83,15 +91,21 @@ export function filter<A>(
83
91
  predicate: (value: A, index: SafeUint | -1) => boolean,
84
92
  ): DropInitialValueOperator<A, A> {
85
93
  return (parentObservable) =>
86
- createFilterObservable(parentObservable, predicate);
94
+ new FilterObservableClass(parentObservable, predicate);
87
95
  }
88
96
 
89
- const createFilterObservable = <A,>(
90
- parentObservable: Observable<A>,
91
- predicate: (x: A, index: SafeUint | -1) => boolean,
92
- ): FilterOperatorObservable<A> =>
93
- createSyncChildObservable(
94
- {
97
+ class FilterObservableClass<A>
98
+ extends SyncChildObservableClass<A, readonly [A]>
99
+ implements FilterOperatorObservable<A>
100
+ {
101
+ readonly #predicate: (x: A, index: SafeUint | -1) => boolean;
102
+ #mut_index: SafeUint | -1;
103
+
104
+ constructor(
105
+ parentObservable: Observable<A>,
106
+ predicate: (x: A, index: SafeUint | -1) => boolean,
107
+ ) {
108
+ super({
95
109
  parents: [parentObservable],
96
110
  initialValue: pipe(parentObservable.getSnapshot()).map((sn) =>
97
111
  Optional.isNone(sn)
@@ -100,29 +114,32 @@ const createFilterObservable = <A,>(
100
114
  ? sn
101
115
  : Optional.none,
102
116
  ).value,
103
- },
104
- ({ setNext }) => {
105
- let mut_index: SafeUint | -1 = -1;
106
-
107
- return (updateToken) => {
108
- const sn = parentObservable.getSnapshot();
109
-
110
- if (
111
- parentObservable.updateToken !== updateToken ||
112
- Optional.isNone(sn)
113
- ) {
114
- return; // skip update
115
- }
116
-
117
- mut_index =
118
- mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, mut_index);
119
-
120
- if (predicate(sn.value, mut_index)) {
121
- setNext(sn.value, updateToken);
122
- }
123
- };
124
- },
125
- );
117
+ });
118
+
119
+ this.#mut_index = -1;
120
+
121
+ this.#predicate = predicate;
122
+
123
+ this.attach();
124
+ }
125
+
126
+ override tryUpdate(updateToken: UpdateToken): void {
127
+ const par = this.parents[0];
128
+
129
+ const sn = par.getSnapshot();
130
+
131
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
132
+ return; // skip update
133
+ }
134
+
135
+ this.#mut_index =
136
+ this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
137
+
138
+ if (this.#predicate(sn.value, this.#mut_index)) {
139
+ this.setNext(sn.value, updateToken);
140
+ }
141
+ }
142
+ }
126
143
 
127
144
  if (import.meta.vitest !== undefined) {
128
145
  test('type test', () => {
@@ -11,6 +11,7 @@ export * from './skip-while.mjs';
11
11
  export * from './switch-map.mjs';
12
12
  export * from './take-until.mjs';
13
13
  export * from './take-while.mjs';
14
+ export * from './take.mjs';
14
15
  export * from './throttle.mjs';
15
16
  export * from './with-buffered-from.mjs';
16
17
  export * from './with-current-value-from.mjs';