synstate 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/README.md +14 -9
  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 +0 -3
  46. package/dist/core/combine/merge.d.mts.map +1 -1
  47. package/dist/core/combine/merge.mjs +24 -15
  48. package/dist/core/combine/merge.mjs.map +1 -1
  49. package/dist/core/combine/zip.d.mts +4 -2
  50. package/dist/core/combine/zip.d.mts.map +1 -1
  51. package/dist/core/combine/zip.mjs +54 -18
  52. package/dist/core/combine/zip.mjs.map +1 -1
  53. package/dist/core/create/counter.d.mts +9 -3
  54. package/dist/core/create/counter.d.mts.map +1 -1
  55. package/dist/core/create/counter.mjs +69 -35
  56. package/dist/core/create/counter.mjs.map +1 -1
  57. package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
  58. package/dist/core/create/from-abortable-promise.mjs +26 -18
  59. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  60. package/dist/core/create/from-promise.d.mts.map +1 -1
  61. package/dist/core/create/from-promise.mjs +30 -21
  62. package/dist/core/create/from-promise.mjs.map +1 -1
  63. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  64. package/dist/core/create/from-subscribable.mjs +48 -12
  65. package/dist/core/create/from-subscribable.mjs.map +1 -1
  66. package/dist/core/create/just.d.mts.map +1 -1
  67. package/dist/core/create/just.mjs +4 -2
  68. package/dist/core/create/just.mjs.map +1 -1
  69. package/dist/core/create/source.d.mts +7 -0
  70. package/dist/core/create/source.d.mts.map +1 -1
  71. package/dist/core/create/source.mjs +27 -13
  72. package/dist/core/create/source.mjs.map +1 -1
  73. package/dist/core/create/timer.d.mts.map +1 -1
  74. package/dist/core/create/timer.mjs +40 -25
  75. package/dist/core/create/timer.mjs.map +1 -1
  76. package/dist/core/index.d.mts +2 -0
  77. package/dist/core/index.d.mts.map +1 -1
  78. package/dist/core/index.mjs +2 -0
  79. package/dist/core/index.mjs.map +1 -1
  80. package/dist/core/operators/audit.d.mts +3 -0
  81. package/dist/core/operators/audit.d.mts.map +1 -1
  82. package/dist/core/operators/audit.mjs +56 -28
  83. package/dist/core/operators/audit.mjs.map +1 -1
  84. package/dist/core/operators/debounce.d.mts +3 -0
  85. package/dist/core/operators/debounce.d.mts.map +1 -1
  86. package/dist/core/operators/debounce.mjs +57 -26
  87. package/dist/core/operators/debounce.mjs.map +1 -1
  88. package/dist/core/operators/filter.d.mts.map +1 -1
  89. package/dist/core/operators/filter.mjs +35 -21
  90. package/dist/core/operators/filter.mjs.map +1 -1
  91. package/dist/core/operators/index.d.mts +1 -0
  92. package/dist/core/operators/index.d.mts.map +1 -1
  93. package/dist/core/operators/index.mjs +1 -0
  94. package/dist/core/operators/index.mjs.map +1 -1
  95. package/dist/core/operators/map.d.mts.map +1 -1
  96. package/dist/core/operators/map.mjs +31 -17
  97. package/dist/core/operators/map.mjs.map +1 -1
  98. package/dist/core/operators/merge-map.d.mts +5 -3
  99. package/dist/core/operators/merge-map.d.mts.map +1 -1
  100. package/dist/core/operators/merge-map.mjs +49 -26
  101. package/dist/core/operators/merge-map.mjs.map +1 -1
  102. package/dist/core/operators/pairwise.d.mts.map +1 -1
  103. package/dist/core/operators/pairwise.mjs +31 -20
  104. package/dist/core/operators/pairwise.mjs.map +1 -1
  105. package/dist/core/operators/scan.d.mts.map +1 -1
  106. package/dist/core/operators/scan.mjs +27 -14
  107. package/dist/core/operators/scan.mjs.map +1 -1
  108. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  109. package/dist/core/operators/skip-if-no-change.mjs +33 -19
  110. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  111. package/dist/core/operators/skip-until.d.mts.map +1 -1
  112. package/dist/core/operators/skip-until.mjs +43 -20
  113. package/dist/core/operators/skip-until.mjs.map +1 -1
  114. package/dist/core/operators/skip-while.d.mts.map +1 -1
  115. package/dist/core/operators/skip-while.mjs +35 -20
  116. package/dist/core/operators/skip-while.mjs.map +1 -1
  117. package/dist/core/operators/switch-map.d.mts +5 -3
  118. package/dist/core/operators/switch-map.d.mts.map +1 -1
  119. package/dist/core/operators/switch-map.mjs +53 -25
  120. package/dist/core/operators/switch-map.mjs.map +1 -1
  121. package/dist/core/operators/take-until.d.mts.map +1 -1
  122. package/dist/core/operators/take-until.mjs +38 -19
  123. package/dist/core/operators/take-until.mjs.map +1 -1
  124. package/dist/core/operators/take-while.d.mts.map +1 -1
  125. package/dist/core/operators/take-while.mjs +36 -22
  126. package/dist/core/operators/take-while.mjs.map +1 -1
  127. package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
  128. package/dist/core/operators/take.d.mts.map +1 -0
  129. package/dist/core/operators/take.mjs +102 -0
  130. package/dist/core/operators/take.mjs.map +1 -0
  131. package/dist/core/operators/throttle.d.mts.map +1 -1
  132. package/dist/core/operators/throttle.mjs +41 -26
  133. package/dist/core/operators/throttle.mjs.map +1 -1
  134. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  135. package/dist/core/operators/with-buffered-from.mjs +44 -31
  136. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  137. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  138. package/dist/core/operators/with-current-value-from.mjs +36 -21
  139. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  140. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  141. package/dist/core/operators/with-initial-value.mjs +24 -12
  142. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  143. package/dist/core/predefined/operators/index.d.mts +0 -1
  144. package/dist/core/predefined/operators/index.d.mts.map +1 -1
  145. package/dist/core/predefined/operators/index.mjs +0 -1
  146. package/dist/core/predefined/operators/index.mjs.map +1 -1
  147. package/dist/core/scope/index.d.mts +2 -0
  148. package/dist/core/scope/index.d.mts.map +1 -0
  149. package/dist/core/scope/index.mjs +2 -0
  150. package/dist/core/scope/index.mjs.map +1 -0
  151. package/dist/core/scope/scope.d.mts +51 -0
  152. package/dist/core/scope/scope.d.mts.map +1 -0
  153. package/dist/core/scope/scope.mjs +194 -0
  154. package/dist/core/scope/scope.mjs.map +1 -0
  155. package/dist/core/types/id.d.mts +0 -2
  156. package/dist/core/types/id.d.mts.map +1 -1
  157. package/dist/core/types/index.d.mts +1 -0
  158. package/dist/core/types/index.d.mts.map +1 -1
  159. package/dist/core/types/index.mjs +1 -0
  160. package/dist/core/types/index.mjs.map +1 -1
  161. package/dist/core/types/observable-family.d.mts +6 -1
  162. package/dist/core/types/observable-family.d.mts.map +1 -1
  163. package/dist/core/types/observable-family.mjs.map +1 -1
  164. package/dist/core/types/observable.d.mts +40 -28
  165. package/dist/core/types/observable.d.mts.map +1 -1
  166. package/dist/core/types/observable.mjs +24 -0
  167. package/dist/core/types/observable.mjs.map +1 -1
  168. package/dist/core/types/scope.d.mts +27 -0
  169. package/dist/core/types/scope.d.mts.map +1 -0
  170. package/dist/core/types/scope.mjs +2 -0
  171. package/dist/core/types/scope.mjs.map +1 -0
  172. package/dist/core/utils/current-scope.d.mts +30 -0
  173. package/dist/core/utils/current-scope.d.mts.map +1 -0
  174. package/dist/core/utils/current-scope.mjs +26 -0
  175. package/dist/core/utils/current-scope.mjs.map +1 -0
  176. package/dist/core/utils/id-maker.d.mts +14 -3
  177. package/dist/core/utils/id-maker.d.mts.map +1 -1
  178. package/dist/core/utils/id-maker.mjs +18 -14
  179. package/dist/core/utils/id-maker.mjs.map +1 -1
  180. package/dist/core/utils/index.d.mts +2 -1
  181. package/dist/core/utils/index.d.mts.map +1 -1
  182. package/dist/core/utils/index.mjs +2 -1
  183. package/dist/core/utils/index.mjs.map +1 -1
  184. package/dist/core/utils/max-depth.d.mts +1 -0
  185. package/dist/core/utils/max-depth.d.mts.map +1 -1
  186. package/dist/core/utils/max-depth.mjs +9 -7
  187. package/dist/core/utils/max-depth.mjs.map +1 -1
  188. package/dist/core/utils/observable-brand.d.mts +13 -0
  189. package/dist/core/utils/observable-brand.d.mts.map +1 -0
  190. package/dist/core/utils/observable-brand.mjs +13 -0
  191. package/dist/core/utils/observable-brand.mjs.map +1 -0
  192. package/dist/utils/create-boolean-state.d.mts +2 -2
  193. package/dist/utils/create-event-emitter.d.mts +2 -2
  194. package/dist/utils/create-reducer.d.mts +17 -8
  195. package/dist/utils/create-reducer.d.mts.map +1 -1
  196. package/dist/utils/create-reducer.mjs +14 -6
  197. package/dist/utils/create-reducer.mjs.map +1 -1
  198. package/dist/utils/create-state.d.mts +9 -2
  199. package/dist/utils/create-state.d.mts.map +1 -1
  200. package/dist/utils/create-state.mjs +7 -0
  201. package/dist/utils/create-state.mjs.map +1 -1
  202. package/package.json +16 -16
  203. package/src/core/authoring.mts +19 -0
  204. package/src/core/class/child-observable-class.mts +374 -0
  205. package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
  206. package/src/core/{base → class}/circular-dependency.test.mts +38 -2
  207. package/src/core/class/complete-propagation.test.mts +264 -0
  208. package/src/core/class/create-child-observable.mts +370 -0
  209. package/src/core/class/create-root-observable.mts +107 -0
  210. package/src/core/class/index.mts +8 -0
  211. package/src/core/class/manager-observable-class.mts +166 -0
  212. package/src/core/class/observable-base-class.mts +315 -0
  213. package/src/core/{base → class}/observable-base.test.mts +1 -1
  214. package/src/core/class/observable-node.mts +54 -0
  215. package/src/core/class/propagation-order.test.mts +92 -0
  216. package/src/core/class/propagation-state.mts +80 -0
  217. package/src/core/class/remove-completed.test.mts +369 -0
  218. package/src/core/class/root-observable-class.mts +85 -0
  219. package/src/core/class/write-during-propagation.test.mts +234 -0
  220. package/src/core/combine/combine.mts +67 -55
  221. package/src/core/combine/merge.mts +33 -26
  222. package/src/core/combine/zip.mts +103 -58
  223. package/src/core/create/counter.mts +105 -54
  224. package/src/core/create/counter.test.mts +82 -0
  225. package/src/core/create/from-abortable-promise.mts +54 -42
  226. package/src/core/create/from-promise.mts +38 -29
  227. package/src/core/create/from-subscribable.mts +68 -23
  228. package/src/core/create/from-subscribable.test.mts +134 -0
  229. package/src/core/create/just.mts +4 -5
  230. package/src/core/create/source.mts +37 -13
  231. package/src/core/create/source.test.mts +41 -0
  232. package/src/core/create/timer.mts +51 -32
  233. package/src/core/index.mts +2 -0
  234. package/src/core/operators/audit.mts +79 -45
  235. package/src/core/operators/debounce-audit-complete.test.mts +121 -0
  236. package/src/core/operators/debounce.mts +81 -40
  237. package/src/core/operators/filter.mts +48 -31
  238. package/src/core/operators/index.mts +1 -0
  239. package/src/core/operators/inner-ownership.test.mts +280 -0
  240. package/src/core/operators/map.mts +49 -32
  241. package/src/core/operators/merge-map.mts +81 -46
  242. package/src/core/operators/pairwise.mts +40 -30
  243. package/src/core/operators/scan.mts +41 -24
  244. package/src/core/operators/skip-if-no-change.mts +45 -31
  245. package/src/core/operators/skip-until.mts +59 -33
  246. package/src/core/operators/skip-while.mts +49 -31
  247. package/src/core/operators/switch-map.mts +74 -38
  248. package/src/core/operators/take-until-skip-until-teardown.test.mts +133 -0
  249. package/src/core/operators/take-until.mts +49 -31
  250. package/src/core/operators/take-while.mts +50 -33
  251. package/src/core/operators/take.mts +138 -0
  252. package/src/core/operators/throttle.mts +55 -38
  253. package/src/core/operators/with-buffered-from.mts +45 -35
  254. package/src/core/operators/with-buffered-from.test.mts +57 -0
  255. package/src/core/operators/with-current-value-from.mts +39 -25
  256. package/src/core/operators/with-initial-value.mts +30 -21
  257. package/src/core/predefined/operators/index.mts +0 -1
  258. package/src/core/scope/index.mts +1 -0
  259. package/src/core/scope/scope.mts +260 -0
  260. package/src/core/scope/scope.test.mts +462 -0
  261. package/src/core/types/id.mts +0 -4
  262. package/src/core/types/index.mts +1 -0
  263. package/src/core/types/observable-family.mts +7 -1
  264. package/src/core/types/observable.mts +77 -42
  265. package/src/core/types/scope.mts +27 -0
  266. package/src/core/utils/current-scope.mts +61 -0
  267. package/src/core/utils/id-maker.mts +20 -30
  268. package/src/core/utils/index.mts +2 -1
  269. package/src/core/utils/max-depth.mts +12 -9
  270. package/src/core/utils/observable-brand.mts +12 -0
  271. package/src/utils/create-boolean-state.mts +2 -2
  272. package/src/utils/create-boolean-state.test.mts +18 -4
  273. package/src/utils/create-event-emitter.mts +2 -2
  274. package/src/utils/create-reducer.mts +22 -16
  275. package/src/utils/create-reducer.test.mts +43 -14
  276. package/src/utils/create-state.mts +9 -2
  277. package/src/utils/create-state.test.mts +32 -14
  278. package/dist/core/base/create-child-observable.d.mts +0 -65
  279. package/dist/core/base/create-child-observable.d.mts.map +0 -1
  280. package/dist/core/base/create-child-observable.mjs +0 -267
  281. package/dist/core/base/create-child-observable.mjs.map +0 -1
  282. package/dist/core/base/create-observable-base.d.mts +0 -147
  283. package/dist/core/base/create-observable-base.d.mts.map +0 -1
  284. package/dist/core/base/create-observable-base.mjs +0 -255
  285. package/dist/core/base/create-observable-base.mjs.map +0 -1
  286. package/dist/core/base/create-root-observable.d.mts +0 -37
  287. package/dist/core/base/create-root-observable.d.mts.map +0 -1
  288. package/dist/core/base/create-root-observable.mjs +0 -57
  289. package/dist/core/base/create-root-observable.mjs.map +0 -1
  290. package/dist/core/base/index.d.mts +0 -3
  291. package/dist/core/base/index.d.mts.map +0 -1
  292. package/dist/core/base/index.mjs +0 -3
  293. package/dist/core/base/index.mjs.map +0 -1
  294. package/dist/core/predefined/operators/take.d.mts.map +0 -1
  295. package/dist/core/predefined/operators/take.mjs +0 -44
  296. package/dist/core/predefined/operators/take.mjs.map +0 -1
  297. package/dist/core/utils/utils.d.mts +0 -6
  298. package/dist/core/utils/utils.d.mts.map +0 -1
  299. package/dist/core/utils/utils.mjs +0 -37
  300. package/dist/core/utils/utils.mjs.map +0 -1
  301. package/src/core/base/create-child-observable.mts +0 -457
  302. package/src/core/base/create-observable-base.mts +0 -548
  303. package/src/core/base/create-root-observable.mts +0 -113
  304. package/src/core/base/index.mts +0 -2
  305. package/src/core/predefined/operators/take.mts +0 -48
  306. package/src/core/utils/utils.mts +0 -56
  307. package/src/core/utils/utils.test.mts +0 -88
@@ -1,16 +1,31 @@
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';
10
+ import { createScope, getCurrentScope } from '../scope/index.mjs';
3
11
  import type {
4
12
  DropInitialValueOperator,
5
13
  Observable,
14
+ Scope,
6
15
  Subscription,
7
16
  SwitchMapOperatorObservable,
17
+ UpdateToken,
8
18
  } from '../types/index.mjs';
9
19
 
10
20
  /**
11
21
  * Projects each source value to an observable, subscribes to it, and emits its values.
12
22
  * When a new value arrives from the source, the previous inner observable is unsubscribed.
13
23
  *
24
+ * The projection runs in a scope of its own (see `createScope`): what it
25
+ * creates is disposed when the next value switches away from it and when
26
+ * `switchMap` completes. An observable it only returns — one created outside
27
+ * the projection and shared with others — is unsubscribed and left running.
28
+ *
14
29
  * @template A - The type of values from the source
15
30
  * @template B - The type of values from the projected observable
16
31
  * @param mapToObservable - A function that maps each source value to an observable
@@ -96,59 +111,80 @@ import type {
96
111
  * 'C3',
97
112
  * ]);
98
113
  * ```
99
- *
100
- * @note To improve code readability, consider using `createState` instead of `switchMap`,
101
- * subscribe to `parentObservable` and call `setState` within it.
102
114
  */
103
115
  export const switchMap =
104
116
  <A, B>(
105
117
  mapToObservable: (curr: A) => Observable<B>,
106
118
  ): DropInitialValueOperator<A, B> =>
107
119
  (parentObservable) =>
108
- createSwitchMapObservable(parentObservable, mapToObservable);
120
+ new SwitchMapObservableClass(parentObservable, mapToObservable);
121
+
122
+ class SwitchMapObservableClass<A, B>
123
+ extends AsyncChildObservableClass<B, readonly [A]>
124
+ implements SwitchMapOperatorObservable<A, B>
125
+ {
126
+ readonly #mapToObservable: (curr: A) => Observable<B>;
109
127
 
110
- const createSwitchMapObservable = <A, B>(
111
- parentObservable: Observable<A>,
112
- mapToObservable: (curr: A) => Observable<B>,
113
- ): SwitchMapOperatorObservable<A, B> => {
114
- let mut_observable: Observable<B> | undefined = undefined;
128
+ /**
129
+ * The scope the operator was created in, so that each inner scope is
130
+ * disposed with it; the projection itself runs during propagation, when the
131
+ * current scope is whatever happens to be running.
132
+ */
133
+ readonly #ownerScope: Scope | undefined;
115
134
 
116
- let mut_subscription: Subscription | undefined = undefined;
135
+ #mut_innerScope: Scope | undefined;
136
+ #mut_subscription: Subscription | undefined;
117
137
 
118
- return createAsyncChildObservable(
119
- {
138
+ constructor(
139
+ parentObservable: Observable<A>,
140
+ mapToObservable: (curr: A) => Observable<B>,
141
+ ) {
142
+ super({
120
143
  parents: [parentObservable],
121
144
  initialValue: Optional.none,
122
- onComplete: () => {
123
- mut_subscription?.unsubscribe();
145
+ });
146
+
147
+ this.#mapToObservable = mapToObservable;
148
+
149
+ this.#ownerScope = getCurrentScope();
150
+
151
+ this.#mut_innerScope = undefined;
152
+
153
+ this.#mut_subscription = undefined;
154
+
155
+ this.attach();
156
+ }
157
+
158
+ /** Releases the inner observable, and only the one this operator made. */
159
+ #releaseInner(): void {
160
+ this.#mut_subscription?.unsubscribe();
161
+
162
+ this.#mut_innerScope?.dispose();
163
+ }
164
+
165
+ override tryUpdate(updateToken: UpdateToken): void {
166
+ const par = this.parents[0];
124
167
 
125
- mut_observable?.complete();
126
- },
127
- },
128
- ({ startUpdate }) =>
129
- (updateToken) => {
130
- const sn = parentObservable.getSnapshot();
168
+ const sn = par.getSnapshot();
131
169
 
132
- if (
133
- parentObservable.updateToken !== updateToken ||
134
- Optional.isNone(sn)
135
- ) {
136
- return; // skip update
137
- }
170
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
171
+ return; // skip update
172
+ }
138
173
 
139
- mut_observable?.complete();
174
+ this.#releaseInner();
140
175
 
141
- mut_subscription?.unsubscribe();
176
+ const innerScope = createScope(this.#ownerScope);
142
177
 
143
- const observable = mapToObservable(sn.value);
178
+ this.#mut_innerScope = innerScope;
144
179
 
145
- mut_observable = observable;
180
+ const observable = innerScope.run(() => this.#mapToObservable(sn.value));
146
181
 
147
- const subscription = observable.subscribe((curr) => {
148
- startUpdate(curr);
149
- });
182
+ this.#mut_subscription = observable.subscribe((curr) => {
183
+ this.startUpdate(curr);
184
+ });
185
+ }
150
186
 
151
- mut_subscription = subscription;
152
- },
153
- );
154
- };
187
+ protected override teardown(): void {
188
+ this.#releaseInner();
189
+ }
190
+ }
@@ -0,0 +1,133 @@
1
+ import { toNode } from '../class/index.mjs';
2
+ import { source } from '../create/index.mjs';
3
+ import { skipUntil } from './skip-until.mjs';
4
+ import { takeUntil } from './take-until.mjs';
5
+ import { take } from './take.mjs';
6
+
7
+ describe('takeUntil releases the notifier', () => {
8
+ test('when the result is completed before the notifier emits', () => {
9
+ const notifier$ = source<undefined>();
10
+
11
+ const result$ = source<number>().pipe(takeUntil(notifier$));
12
+
13
+ assert.isTrue(toNode(notifier$).hasSubscriber);
14
+
15
+ result$.complete();
16
+
17
+ assert.isFalse(toNode(notifier$).hasSubscriber);
18
+ });
19
+
20
+ test('when the parent completes', () => {
21
+ const notifier$ = source<undefined>();
22
+
23
+ const num$ = source<number>();
24
+
25
+ num$.pipe(takeUntil(notifier$));
26
+
27
+ num$.complete();
28
+
29
+ assert.isFalse(toNode(notifier$).hasSubscriber);
30
+ });
31
+
32
+ test('not when the downstream completes', () => {
33
+ const notifier$ = source<undefined>();
34
+
35
+ const num$ = source<number>();
36
+
37
+ const result$ = num$.pipe(takeUntil(notifier$));
38
+
39
+ const taken$ = result$.pipe(take(1));
40
+
41
+ taken$.subscribe(() => {});
42
+
43
+ // `take(n)` completes on the emission after its `n`th.
44
+ num$.next(1);
45
+
46
+ num$.next(2);
47
+
48
+ assert.isTrue(taken$.isCompleted);
49
+
50
+ // Completion flows only downstream, so `takeUntil` is still running.
51
+ assert.isFalse(result$.isCompleted);
52
+
53
+ assert.isTrue(toNode(notifier$).hasSubscriber);
54
+ });
55
+
56
+ test('when the notifier emits', () => {
57
+ const notifier$ = source<undefined>();
58
+
59
+ const result$ = source<number>().pipe(takeUntil(notifier$));
60
+
61
+ notifier$.next(undefined);
62
+
63
+ assert.isTrue(result$.isCompleted);
64
+
65
+ assert.isFalse(toNode(notifier$).hasSubscriber);
66
+ });
67
+
68
+ test('when the notifier has already emitted', () => {
69
+ const notifier$ = source<number>(0);
70
+
71
+ const result$ = source<number>().pipe(takeUntil(notifier$));
72
+
73
+ assert.isTrue(result$.isCompleted);
74
+
75
+ assert.isFalse(toNode(notifier$).hasSubscriber);
76
+ });
77
+ });
78
+
79
+ describe('skipUntil releases the notifier', () => {
80
+ test('when the notifier emits', () => {
81
+ const notifier$ = source<undefined>();
82
+
83
+ const num$ = source<number>();
84
+
85
+ const result$ = num$.pipe(skipUntil(notifier$));
86
+
87
+ const mut_history: number[] = [];
88
+
89
+ result$.subscribe((x) => {
90
+ mut_history.push(x);
91
+ });
92
+
93
+ num$.next(1);
94
+
95
+ notifier$.next(undefined);
96
+
97
+ assert.isFalse(toNode(notifier$).hasSubscriber);
98
+
99
+ num$.next(2);
100
+
101
+ assert.deepStrictEqual(mut_history, [2]);
102
+ });
103
+
104
+ test('when the notifier has already emitted', () => {
105
+ const notifier$ = source<number>(0);
106
+
107
+ const num$ = source<number>();
108
+
109
+ const result$ = num$.pipe(skipUntil(notifier$));
110
+
111
+ const mut_history: number[] = [];
112
+
113
+ result$.subscribe((x) => {
114
+ mut_history.push(x);
115
+ });
116
+
117
+ assert.isFalse(toNode(notifier$).hasSubscriber);
118
+
119
+ num$.next(1);
120
+
121
+ assert.deepStrictEqual(mut_history, [1]);
122
+ });
123
+
124
+ test('when the result is completed before the notifier emits', () => {
125
+ const notifier$ = source<undefined>();
126
+
127
+ const result$ = source<number>().pipe(skipUntil(notifier$));
128
+
129
+ result$.complete();
130
+
131
+ assert.isFalse(toNode(notifier$).hasSubscriber);
132
+ });
133
+ });
@@ -1,9 +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 { createSyncChildObservable } from '../base/index.mjs';
9
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  KeepInitialValueOperator,
5
12
  Observable,
13
+ Subscription,
6
14
  TakeUntilOperatorObservable,
15
+ UpdateToken,
7
16
  } from '../types/index.mjs';
8
17
 
9
18
  /**
@@ -59,43 +68,52 @@ export const takeUntil = <A,>(
59
68
  ): KeepInitialValueOperator<A, A> =>
60
69
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
61
70
  ((parentObservable) =>
62
- createTakeUntilObservable(
71
+ new TakeUntilObservableClass(
63
72
  parentObservable,
64
73
  notifier,
65
74
  )) as KeepInitialValueOperator<A, A>;
66
75
 
67
- const createTakeUntilObservable = <A,>(
68
- parentObservable: Observable<A>,
69
- notifier: Observable<unknown>,
70
- ): TakeUntilOperatorObservable<A> => {
71
- const observable = createSyncChildObservable<A, readonly [A]>(
72
- {
76
+ class TakeUntilObservableClass<A>
77
+ extends SyncChildObservableClass<A, readonly [A]>
78
+ implements TakeUntilOperatorObservable<A>
79
+ {
80
+ /** Unset only while the `subscribe` in the constructor runs. */
81
+ readonly #subscription: Subscription | undefined;
82
+
83
+ constructor(parentObservable: Observable<A>, notifier: Observable<unknown>) {
84
+ super({
73
85
  parents: [parentObservable],
74
86
  initialValue: parentObservable.getSnapshot(),
75
- },
76
- ({ setNext }) =>
77
- (updateToken) => {
78
- const sn = parentObservable.getSnapshot();
87
+ });
88
+
89
+ const complete = (): void => {
90
+ this.complete();
91
+ };
92
+
93
+ // A notifier that already holds a value completes this observable inside
94
+ // `subscribe`, before the subscription is stored.
95
+ this.#subscription = notifier.subscribe(complete, complete);
96
+
97
+ if (this.isCompleted) {
98
+ this.#subscription.unsubscribe();
99
+ }
100
+
101
+ this.attach();
102
+ }
103
+
104
+ override tryUpdate(updateToken: UpdateToken): void {
105
+ const par = this.parents[0];
79
106
 
80
- if (
81
- parentObservable.updateToken !== updateToken ||
82
- Optional.isNone(sn)
83
- ) {
84
- return; // skip update
85
- }
107
+ const sn = par.getSnapshot();
86
108
 
87
- setNext(sn.value, updateToken);
88
- },
89
- );
109
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
110
+ return; // skip update
111
+ }
90
112
 
91
- notifier.subscribe(
92
- () => {
93
- observable.complete();
94
- },
95
- () => {
96
- observable.complete();
97
- },
98
- );
113
+ this.setNext(sn.value, updateToken);
114
+ }
99
115
 
100
- return observable;
101
- };
116
+ protected override teardown(): void {
117
+ this.#subscription?.unsubscribe();
118
+ }
119
+ }
@@ -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
  Optional,
3
10
  SafeUint,
@@ -5,13 +12,14 @@ import {
5
12
  expectType,
6
13
  pipe,
7
14
  } from 'ts-data-forge';
8
- import { createSyncChildObservable } from '../base/index.mjs';
15
+ import { SyncChildObservableClass } from '../class/index.mjs';
9
16
  import { source } from '../create/index.mjs';
10
17
  import type {
11
18
  DropInitialValueOperator,
12
19
  InitializedObservable,
13
20
  Observable,
14
21
  TakeWhileOperatorObservable,
22
+ UpdateToken,
15
23
  } from '../types/index.mjs';
16
24
  import { withInitialValue } from './with-initial-value.mjs';
17
25
 
@@ -75,16 +83,22 @@ export const takeWhile =
75
83
  predicate: (value: A, index: SafeUint | -1) => boolean,
76
84
  ): DropInitialValueOperator<A, A> =>
77
85
  (parentObservable) =>
78
- createTakeWhileObservable(parentObservable, predicate);
86
+ new TakeWhileObservableClass(parentObservable, predicate);
79
87
 
80
88
  /* implementation */
81
89
 
82
- const createTakeWhileObservable = <A,>(
83
- parentObservable: Observable<A>,
84
- predicate: (value: A, index: SafeUint | -1) => boolean,
85
- ): TakeWhileOperatorObservable<A> =>
86
- createSyncChildObservable(
87
- {
90
+ class TakeWhileObservableClass<A>
91
+ extends SyncChildObservableClass<A, readonly [A]>
92
+ implements TakeWhileOperatorObservable<A>
93
+ {
94
+ readonly #predicate: (value: A, index: SafeUint | -1) => boolean;
95
+ #mut_index: SafeUint | -1;
96
+
97
+ constructor(
98
+ parentObservable: Observable<A>,
99
+ predicate: (value: A, index: SafeUint | -1) => boolean,
100
+ ) {
101
+ super({
88
102
  parents: [parentObservable],
89
103
  initialValue: pipe(parentObservable.getSnapshot()).map((par) =>
90
104
  Optional.isNone(par)
@@ -93,31 +107,34 @@ const createTakeWhileObservable = <A,>(
93
107
  ? par
94
108
  : Optional.none,
95
109
  ).value,
96
- },
97
- ({ setNext, complete }) => {
98
- let mut_index: SafeUint | -1 = -1;
99
-
100
- return (updateToken) => {
101
- const sn = parentObservable.getSnapshot();
102
-
103
- if (
104
- parentObservable.updateToken !== updateToken ||
105
- Optional.isNone(sn)
106
- ) {
107
- return; // skip update
108
- }
109
-
110
- mut_index =
111
- mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, mut_index);
112
-
113
- if (predicate(sn.value, mut_index)) {
114
- setNext(sn.value, updateToken);
115
- } else {
116
- complete();
117
- }
118
- };
119
- },
120
- );
110
+ });
111
+
112
+ this.#mut_index = -1;
113
+
114
+ this.#predicate = predicate;
115
+
116
+ this.attach();
117
+ }
118
+
119
+ override tryUpdate(updateToken: UpdateToken): void {
120
+ const par = this.parents[0];
121
+
122
+ const sn = par.getSnapshot();
123
+
124
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
125
+ return; // skip update
126
+ }
127
+
128
+ this.#mut_index =
129
+ this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
130
+
131
+ if (this.#predicate(sn.value, this.#mut_index)) {
132
+ this.setNext(sn.value, updateToken);
133
+ } else {
134
+ this.complete();
135
+ }
136
+ }
137
+ }
121
138
 
122
139
  if (import.meta.vitest !== undefined) {
123
140
  test('type test', () => {
@@ -0,0 +1,138 @@
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
8
+ import { Optional, expectType } from 'ts-data-forge';
9
+ import type { PositiveSafeIntWithSmallInt } from 'ts-type-forge';
10
+ import { SyncChildObservableClass } from '../class/index.mjs';
11
+ import { source } from '../create/index.mjs';
12
+ import type {
13
+ DropInitialValueOperator,
14
+ InitializedObservable,
15
+ Observable,
16
+ TakeOperatorObservable,
17
+ UpdateToken,
18
+ } from '../types/index.mjs';
19
+ import { withInitialValue } from './with-initial-value.mjs';
20
+
21
+ /**
22
+ * Takes only the first `n` emissions from the source observable, then completes.
23
+ * It completes in the same update as the `n`th emission, after passing that
24
+ * value on. The initial value is passed on and not counted.
25
+ *
26
+ * @template A - The type of values from the source
27
+ * @param n - The number of values to take
28
+ * @returns An operator that takes the first n emissions
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Timeline:
33
+ * //
34
+ * // num$ 1 2 3 4 (ignored)
35
+ * // taken$ 1 2 3|(completes)
36
+ * //
37
+ * // Explanation:
38
+ * // - take emits only the first n values, completing with the nth
39
+ * // - Subsequent emissions from the source are ignored
40
+ *
41
+ * const num$ = source<number>();
42
+ *
43
+ * const taken$ = num$.pipe(take(3));
44
+ *
45
+ * const valueHistory: number[] = [];
46
+ *
47
+ * taken$.subscribe((x) => {
48
+ * valueHistory.push(x);
49
+ * });
50
+ *
51
+ * num$.next(1);
52
+ *
53
+ * num$.next(2);
54
+ *
55
+ * num$.next(3);
56
+ *
57
+ * assert.deepStrictEqual(valueHistory, [1, 2, 3]);
58
+ *
59
+ * assert.isTrue(taken$.isCompleted);
60
+ *
61
+ * num$.next(4); // ignored (already completed)
62
+ *
63
+ * assert.deepStrictEqual(valueHistory, [1, 2, 3]);
64
+ * ```
65
+ */
66
+ export const take =
67
+ <A,>(n: PositiveSafeIntWithSmallInt): DropInitialValueOperator<A, A> =>
68
+ (parentObservable) =>
69
+ new TakeObservableClass(parentObservable, n);
70
+
71
+ /* implementation */
72
+
73
+ class TakeObservableClass<A>
74
+ extends SyncChildObservableClass<A, readonly [A]>
75
+ implements TakeOperatorObservable<A>
76
+ {
77
+ readonly #n: PositiveSafeIntWithSmallInt;
78
+ #mut_count: number;
79
+
80
+ constructor(parentObservable: Observable<A>, n: PositiveSafeIntWithSmallInt) {
81
+ super({
82
+ parents: [parentObservable],
83
+ initialValue: parentObservable.getSnapshot(),
84
+ });
85
+
86
+ this.#mut_count = 0;
87
+
88
+ this.#n = n;
89
+
90
+ this.attach();
91
+ }
92
+
93
+ override tryUpdate(updateToken: UpdateToken): void {
94
+ const par = this.parents[0];
95
+
96
+ const sn = par.getSnapshot();
97
+
98
+ if (
99
+ this.#mut_count >= this.#n ||
100
+ par.updateToken !== updateToken ||
101
+ Optional.isNone(sn)
102
+ ) {
103
+ return; // skip update
104
+ }
105
+
106
+ this.#mut_count += 1;
107
+
108
+ this.setNext(sn.value, updateToken);
109
+
110
+ if (this.#mut_count >= this.#n) {
111
+ this.completeAfterPropagation();
112
+ }
113
+ }
114
+ }
115
+
116
+ if (import.meta.vitest !== undefined) {
117
+ test('type test', () => {
118
+ expect(1).toBe(1); // dummy
119
+ });
120
+
121
+ {
122
+ const s: Observable<number> = source<number>();
123
+
124
+ const _d1 = s.pipe(take(3));
125
+
126
+ expectType<typeof _d1, Observable<number>>('=');
127
+ }
128
+
129
+ {
130
+ const s = source<number>();
131
+
132
+ const m: InitializedObservable<number> = s.pipe(withInitialValue(0));
133
+
134
+ const _d = m.pipe(take(3));
135
+
136
+ expectType<typeof _d, Observable<number>>('=');
137
+ }
138
+ }