synstate 3.4.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 (294) 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/from-abortable-promise.d.mts.map +1 -1
  55. package/dist/core/create/from-abortable-promise.mjs +26 -18
  56. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  57. package/dist/core/create/from-promise.d.mts.map +1 -1
  58. package/dist/core/create/from-promise.mjs +30 -21
  59. package/dist/core/create/from-promise.mjs.map +1 -1
  60. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  61. package/dist/core/create/from-subscribable.mjs +37 -24
  62. package/dist/core/create/from-subscribable.mjs.map +1 -1
  63. package/dist/core/create/just.d.mts.map +1 -1
  64. package/dist/core/create/just.mjs +4 -2
  65. package/dist/core/create/just.mjs.map +1 -1
  66. package/dist/core/create/source.d.mts.map +1 -1
  67. package/dist/core/create/source.mjs +27 -17
  68. package/dist/core/create/source.mjs.map +1 -1
  69. package/dist/core/create/timer.d.mts.map +1 -1
  70. package/dist/core/create/timer.mjs +40 -25
  71. package/dist/core/create/timer.mjs.map +1 -1
  72. package/dist/core/index.d.mts +2 -0
  73. package/dist/core/index.d.mts.map +1 -1
  74. package/dist/core/index.mjs +2 -0
  75. package/dist/core/index.mjs.map +1 -1
  76. package/dist/core/operators/audit.d.mts +3 -0
  77. package/dist/core/operators/audit.d.mts.map +1 -1
  78. package/dist/core/operators/audit.mjs +56 -28
  79. package/dist/core/operators/audit.mjs.map +1 -1
  80. package/dist/core/operators/debounce.d.mts +3 -0
  81. package/dist/core/operators/debounce.d.mts.map +1 -1
  82. package/dist/core/operators/debounce.mjs +57 -26
  83. package/dist/core/operators/debounce.mjs.map +1 -1
  84. package/dist/core/operators/filter.d.mts.map +1 -1
  85. package/dist/core/operators/filter.mjs +35 -21
  86. package/dist/core/operators/filter.mjs.map +1 -1
  87. package/dist/core/operators/index.d.mts +1 -0
  88. package/dist/core/operators/index.d.mts.map +1 -1
  89. package/dist/core/operators/index.mjs +1 -0
  90. package/dist/core/operators/index.mjs.map +1 -1
  91. package/dist/core/operators/map.d.mts.map +1 -1
  92. package/dist/core/operators/map.mjs +31 -17
  93. package/dist/core/operators/map.mjs.map +1 -1
  94. package/dist/core/operators/merge-map.d.mts +5 -0
  95. package/dist/core/operators/merge-map.d.mts.map +1 -1
  96. package/dist/core/operators/merge-map.mjs +52 -26
  97. package/dist/core/operators/merge-map.mjs.map +1 -1
  98. package/dist/core/operators/pairwise.d.mts.map +1 -1
  99. package/dist/core/operators/pairwise.mjs +31 -20
  100. package/dist/core/operators/pairwise.mjs.map +1 -1
  101. package/dist/core/operators/scan.d.mts.map +1 -1
  102. package/dist/core/operators/scan.mjs +27 -14
  103. package/dist/core/operators/scan.mjs.map +1 -1
  104. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  105. package/dist/core/operators/skip-if-no-change.mjs +33 -19
  106. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  107. package/dist/core/operators/skip-until.d.mts.map +1 -1
  108. package/dist/core/operators/skip-until.mjs +40 -26
  109. package/dist/core/operators/skip-until.mjs.map +1 -1
  110. package/dist/core/operators/skip-while.d.mts.map +1 -1
  111. package/dist/core/operators/skip-while.mjs +35 -20
  112. package/dist/core/operators/skip-while.mjs.map +1 -1
  113. package/dist/core/operators/switch-map.d.mts +5 -0
  114. package/dist/core/operators/switch-map.d.mts.map +1 -1
  115. package/dist/core/operators/switch-map.mjs +56 -25
  116. package/dist/core/operators/switch-map.mjs.map +1 -1
  117. package/dist/core/operators/take-until.d.mts.map +1 -1
  118. package/dist/core/operators/take-until.mjs +36 -29
  119. package/dist/core/operators/take-until.mjs.map +1 -1
  120. package/dist/core/operators/take-while.d.mts.map +1 -1
  121. package/dist/core/operators/take-while.mjs +36 -22
  122. package/dist/core/operators/take-while.mjs.map +1 -1
  123. package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
  124. package/dist/core/operators/take.d.mts.map +1 -0
  125. package/dist/core/operators/take.mjs +102 -0
  126. package/dist/core/operators/take.mjs.map +1 -0
  127. package/dist/core/operators/throttle.d.mts.map +1 -1
  128. package/dist/core/operators/throttle.mjs +41 -26
  129. package/dist/core/operators/throttle.mjs.map +1 -1
  130. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  131. package/dist/core/operators/with-buffered-from.mjs +43 -39
  132. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  133. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  134. package/dist/core/operators/with-current-value-from.mjs +36 -21
  135. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  136. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  137. package/dist/core/operators/with-initial-value.mjs +24 -12
  138. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  139. package/dist/core/predefined/operators/index.d.mts +0 -1
  140. package/dist/core/predefined/operators/index.d.mts.map +1 -1
  141. package/dist/core/predefined/operators/index.mjs +0 -1
  142. package/dist/core/predefined/operators/index.mjs.map +1 -1
  143. package/dist/core/scope/index.d.mts +2 -0
  144. package/dist/core/scope/index.d.mts.map +1 -0
  145. package/dist/core/scope/index.mjs +2 -0
  146. package/dist/core/scope/index.mjs.map +1 -0
  147. package/dist/core/scope/scope.d.mts +51 -0
  148. package/dist/core/scope/scope.d.mts.map +1 -0
  149. package/dist/core/scope/scope.mjs +194 -0
  150. package/dist/core/scope/scope.mjs.map +1 -0
  151. package/dist/core/types/id.d.mts +0 -2
  152. package/dist/core/types/id.d.mts.map +1 -1
  153. package/dist/core/types/index.d.mts +1 -0
  154. package/dist/core/types/index.d.mts.map +1 -1
  155. package/dist/core/types/index.mjs +1 -0
  156. package/dist/core/types/index.mjs.map +1 -1
  157. package/dist/core/types/observable-family.d.mts +1 -0
  158. package/dist/core/types/observable-family.d.mts.map +1 -1
  159. package/dist/core/types/observable.d.mts +40 -28
  160. package/dist/core/types/observable.d.mts.map +1 -1
  161. package/dist/core/types/observable.mjs +24 -0
  162. package/dist/core/types/observable.mjs.map +1 -1
  163. package/dist/core/types/scope.d.mts +27 -0
  164. package/dist/core/types/scope.d.mts.map +1 -0
  165. package/dist/core/types/scope.mjs +2 -0
  166. package/dist/core/types/scope.mjs.map +1 -0
  167. package/dist/core/utils/current-scope.d.mts +30 -0
  168. package/dist/core/utils/current-scope.d.mts.map +1 -0
  169. package/dist/core/utils/current-scope.mjs +26 -0
  170. package/dist/core/utils/current-scope.mjs.map +1 -0
  171. package/dist/core/utils/id-maker.d.mts +14 -3
  172. package/dist/core/utils/id-maker.d.mts.map +1 -1
  173. package/dist/core/utils/id-maker.mjs +18 -14
  174. package/dist/core/utils/id-maker.mjs.map +1 -1
  175. package/dist/core/utils/index.d.mts +2 -1
  176. package/dist/core/utils/index.d.mts.map +1 -1
  177. package/dist/core/utils/index.mjs +2 -1
  178. package/dist/core/utils/index.mjs.map +1 -1
  179. package/dist/core/utils/max-depth.d.mts +1 -0
  180. package/dist/core/utils/max-depth.d.mts.map +1 -1
  181. package/dist/core/utils/max-depth.mjs +9 -7
  182. package/dist/core/utils/max-depth.mjs.map +1 -1
  183. package/dist/core/utils/observable-brand.d.mts +13 -0
  184. package/dist/core/utils/observable-brand.d.mts.map +1 -0
  185. package/dist/core/utils/observable-brand.mjs +13 -0
  186. package/dist/core/utils/observable-brand.mjs.map +1 -0
  187. package/dist/utils/create-boolean-state.d.mts +2 -2
  188. package/dist/utils/create-reducer.d.mts +7 -7
  189. package/dist/utils/create-reducer.mjs +2 -2
  190. package/dist/utils/create-reducer.mjs.map +1 -1
  191. package/dist/utils/create-state.d.mts +2 -2
  192. package/package.json +16 -16
  193. package/src/core/authoring.mts +19 -0
  194. package/src/core/class/child-observable-class.mts +374 -0
  195. package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
  196. package/src/core/{base → class}/circular-dependency.test.mts +38 -2
  197. package/src/core/class/complete-propagation.test.mts +264 -0
  198. package/src/core/class/create-child-observable.mts +370 -0
  199. package/src/core/class/create-root-observable.mts +107 -0
  200. package/src/core/class/index.mts +8 -0
  201. package/src/core/class/manager-observable-class.mts +166 -0
  202. package/src/core/class/observable-base-class.mts +315 -0
  203. package/src/core/{base → class}/observable-base.test.mts +1 -1
  204. package/src/core/class/observable-node.mts +54 -0
  205. package/src/core/class/propagation-order.test.mts +92 -0
  206. package/src/core/class/propagation-state.mts +80 -0
  207. package/src/core/class/remove-completed.test.mts +369 -0
  208. package/src/core/class/root-observable-class.mts +85 -0
  209. package/src/core/{base → class}/write-during-propagation.test.mts +3 -3
  210. package/src/core/combine/combine.mts +67 -55
  211. package/src/core/combine/merge.mts +33 -23
  212. package/src/core/combine/zip.mts +95 -81
  213. package/src/core/create/counter.mts +95 -65
  214. package/src/core/create/from-abortable-promise.mts +54 -42
  215. package/src/core/create/from-promise.mts +38 -29
  216. package/src/core/create/from-subscribable.mts +62 -45
  217. package/src/core/create/from-subscribable.test.mts +8 -3
  218. package/src/core/create/just.mts +4 -5
  219. package/src/core/create/source.mts +29 -20
  220. package/src/core/create/timer.mts +51 -32
  221. package/src/core/index.mts +2 -0
  222. package/src/core/operators/audit.mts +79 -45
  223. package/src/core/operators/debounce-audit-complete.test.mts +121 -0
  224. package/src/core/operators/debounce.mts +81 -40
  225. package/src/core/operators/filter.mts +48 -31
  226. package/src/core/operators/index.mts +1 -0
  227. package/src/core/operators/inner-ownership.test.mts +280 -0
  228. package/src/core/operators/map.mts +49 -32
  229. package/src/core/operators/merge-map.mts +81 -43
  230. package/src/core/operators/pairwise.mts +40 -30
  231. package/src/core/operators/scan.mts +41 -24
  232. package/src/core/operators/skip-if-no-change.mts +45 -31
  233. package/src/core/operators/skip-until.mts +55 -41
  234. package/src/core/operators/skip-while.mts +49 -31
  235. package/src/core/operators/switch-map.mts +74 -35
  236. package/src/core/operators/take-until-skip-until-teardown.test.mts +18 -12
  237. package/src/core/operators/take-until.mts +45 -41
  238. package/src/core/operators/take-while.mts +50 -33
  239. package/src/core/operators/take.mts +138 -0
  240. package/src/core/operators/throttle.mts +55 -38
  241. package/src/core/operators/with-buffered-from.mts +42 -44
  242. package/src/core/operators/with-buffered-from.test.mts +4 -3
  243. package/src/core/operators/with-current-value-from.mts +39 -25
  244. package/src/core/operators/with-initial-value.mts +30 -21
  245. package/src/core/predefined/operators/index.mts +0 -1
  246. package/src/core/scope/index.mts +1 -0
  247. package/src/core/scope/scope.mts +260 -0
  248. package/src/core/scope/scope.test.mts +462 -0
  249. package/src/core/types/id.mts +0 -4
  250. package/src/core/types/index.mts +1 -0
  251. package/src/core/types/observable-family.mts +2 -0
  252. package/src/core/types/observable.mts +77 -42
  253. package/src/core/types/scope.mts +27 -0
  254. package/src/core/utils/current-scope.mts +61 -0
  255. package/src/core/utils/id-maker.mts +20 -30
  256. package/src/core/utils/index.mts +2 -1
  257. package/src/core/utils/max-depth.mts +12 -9
  258. package/src/core/utils/observable-brand.mts +12 -0
  259. package/src/utils/create-boolean-state.mts +2 -2
  260. package/src/utils/create-boolean-state.test.mts +18 -4
  261. package/src/utils/create-reducer.mts +9 -9
  262. package/src/utils/create-reducer.test.mts +31 -14
  263. package/src/utils/create-state.mts +2 -2
  264. package/src/utils/create-state.test.mts +12 -14
  265. package/dist/core/base/create-child-observable.d.mts +0 -72
  266. package/dist/core/base/create-child-observable.d.mts.map +0 -1
  267. package/dist/core/base/create-child-observable.mjs +0 -285
  268. package/dist/core/base/create-child-observable.mjs.map +0 -1
  269. package/dist/core/base/create-observable-base.d.mts +0 -149
  270. package/dist/core/base/create-observable-base.d.mts.map +0 -1
  271. package/dist/core/base/create-observable-base.mjs +0 -273
  272. package/dist/core/base/create-observable-base.mjs.map +0 -1
  273. package/dist/core/base/create-root-observable.d.mts +0 -46
  274. package/dist/core/base/create-root-observable.d.mts.map +0 -1
  275. package/dist/core/base/create-root-observable.mjs +0 -80
  276. package/dist/core/base/create-root-observable.mjs.map +0 -1
  277. package/dist/core/base/index.d.mts +0 -3
  278. package/dist/core/base/index.d.mts.map +0 -1
  279. package/dist/core/base/index.mjs +0 -3
  280. package/dist/core/base/index.mjs.map +0 -1
  281. package/dist/core/predefined/operators/take.d.mts.map +0 -1
  282. package/dist/core/predefined/operators/take.mjs +0 -44
  283. package/dist/core/predefined/operators/take.mjs.map +0 -1
  284. package/dist/core/utils/utils.d.mts +0 -6
  285. package/dist/core/utils/utils.d.mts.map +0 -1
  286. package/dist/core/utils/utils.mjs +0 -37
  287. package/dist/core/utils/utils.mjs.map +0 -1
  288. package/src/core/base/create-child-observable.mts +0 -485
  289. package/src/core/base/create-observable-base.mts +0 -568
  290. package/src/core/base/create-root-observable.mts +0 -147
  291. package/src/core/base/index.mts +0 -2
  292. package/src/core/predefined/operators/take.mts +0 -48
  293. package/src/core/utils/utils.mts +0 -56
  294. package/src/core/utils/utils.test.mts +0 -88
@@ -1,8 +1,16 @@
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 { createInitializedSyncChildObservable } from '../base/index.mjs';
9
+ import { InitializedSyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  Observable,
5
12
  ScanOperatorObservable,
13
+ UpdateToken,
6
14
  WithInitialValueOperator,
7
15
  } from '../types/index.mjs';
8
16
 
@@ -61,32 +69,41 @@ export const scan =
61
69
  initialValue: B,
62
70
  ): WithInitialValueOperator<A, B> =>
63
71
  (parentObservable) =>
64
- createScanObservable(parentObservable, reducer, initialValue);
72
+ new ScanObservableClass(parentObservable, reducer, initialValue);
65
73
 
66
- const createScanObservable = <A, B>(
67
- parentObservable: Observable<A>,
68
- reducer: (acc: B, curr: A) => B,
69
- initialValue: B,
70
- ): ScanOperatorObservable<A, B> =>
71
- createInitializedSyncChildObservable(
72
- {
74
+ class ScanObservableClass<A, B>
75
+ extends InitializedSyncChildObservableClass<B, readonly [A]>
76
+ implements ScanOperatorObservable<A, B>
77
+ {
78
+ readonly #reducer: (acc: B, curr: A) => B;
79
+
80
+ constructor(
81
+ parentObservable: Observable<A>,
82
+ reducer: (acc: B, curr: A) => B,
83
+ initialValue: B,
84
+ ) {
85
+ super({
73
86
  parents: [parentObservable],
74
87
  initialValue: Optional.some(initialValue),
75
- },
76
- ({ setNext, getSnapshot }) =>
77
- (updateToken) => {
78
- const psn = parentObservable.getSnapshot();
88
+ });
89
+
90
+ this.#reducer = reducer;
91
+
92
+ this.attach();
93
+ }
94
+
95
+ override tryUpdate(updateToken: UpdateToken): void {
96
+ const par = this.parents[0];
79
97
 
80
- const sn = getSnapshot();
98
+ const psn = par.getSnapshot();
81
99
 
82
- if (
83
- parentObservable.updateToken !== updateToken ||
84
- Optional.isNone(psn) ||
85
- Optional.isNone(sn)
86
- ) {
87
- return; // skip update
88
- }
100
+ if (par.updateToken !== updateToken || Optional.isNone(psn)) {
101
+ return; // skip update
102
+ }
89
103
 
90
- setNext(reducer(sn.value, psn.value), updateToken);
91
- },
92
- );
104
+ this.setNext(
105
+ this.#reducer(this.getSnapshot().value, psn.value),
106
+ updateToken,
107
+ );
108
+ }
109
+ }
@@ -1,9 +1,17 @@
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,
6
13
  SkipIfNoChangeOperatorObservable,
14
+ UpdateToken,
7
15
  } from '../types/index.mjs';
8
16
 
9
17
  /**
@@ -72,7 +80,7 @@ export const skipIfNoChange = <A,>(
72
80
  ): KeepInitialValueOperator<A, A> =>
73
81
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
74
82
  ((parentObservable) =>
75
- createSkipIfNoChangeObservable(
83
+ new SkipIfNoChangeObservableClass(
76
84
  parentObservable,
77
85
  eq,
78
86
  )) as KeepInitialValueOperator<A, A>;
@@ -83,40 +91,46 @@ export const skipIfNoChange = <A,>(
83
91
  */
84
92
  export const distinctUntilChanged = skipIfNoChange;
85
93
 
86
- const createSkipIfNoChangeObservable = <A,>(
87
- parentObservable: Observable<A>,
88
- eq: (x: A, y: A) => boolean,
89
- ): SkipIfNoChangeOperatorObservable<A> =>
90
- createSyncChildObservable(
91
- {
94
+ class SkipIfNoChangeObservableClass<A>
95
+ extends SyncChildObservableClass<A, readonly [A]>
96
+ implements SkipIfNoChangeOperatorObservable<A>
97
+ {
98
+ readonly #eq: (x: A, y: A) => boolean;
99
+ #mut_previousValue: Optional<A>;
100
+
101
+ constructor(parentObservable: Observable<A>, eq: (x: A, y: A) => boolean) {
102
+ super({
92
103
  parents: [parentObservable],
93
104
  initialValue: parentObservable.getSnapshot(),
94
- },
95
- ({ setNext }) => {
96
- // parentObservable.snapshot has value
97
- // if parentObservable is InitializedObservable
98
- let mut_previousValue: Optional<A> = parentObservable.getSnapshot();
105
+ });
106
+
107
+ // parentObservable.snapshot has value
108
+ // if parentObservable is InitializedObservable
109
+ this.#mut_previousValue = parentObservable.getSnapshot();
110
+
111
+ this.#eq = eq;
112
+
113
+ this.attach();
114
+ }
115
+
116
+ override tryUpdate(updateToken: UpdateToken): void {
117
+ const par = this.parents[0];
99
118
 
100
- return (updateToken) => {
101
- const sn = parentObservable.getSnapshot();
119
+ const sn = par.getSnapshot();
102
120
 
103
- if (
104
- parentObservable.updateToken !== updateToken ||
105
- Optional.isNone(sn)
106
- ) {
107
- return; // skip update
108
- }
121
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
122
+ return; // skip update
123
+ }
109
124
 
110
- const prev = mut_previousValue;
125
+ const prev = this.#mut_previousValue;
111
126
 
112
- const cond = Optional.isNone(prev) || !eq(prev.value, sn.value);
127
+ const cond = Optional.isNone(prev) || !this.#eq(prev.value, sn.value);
113
128
 
114
- // NOTE: Must update before setNext, otherwise Optional.isNone(prev) remains true when tryUpdate is called consecutively
115
- mut_previousValue = sn;
129
+ // NOTE: Must update before setNext, otherwise Optional.isNone(prev) remains true when tryUpdate is called consecutively
130
+ this.#mut_previousValue = sn;
116
131
 
117
- if (cond) {
118
- setNext(sn.value, updateToken);
119
- }
120
- };
121
- },
122
- );
132
+ if (cond) {
133
+ this.setNext(sn.value, updateToken);
134
+ }
135
+ }
136
+ }
@@ -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 { createSyncChildObservable } from '../base/index.mjs';
9
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  DropInitialValueOperator,
5
12
  Observable,
6
13
  SkipUntilOperatorObservable,
7
14
  Subscription,
15
+ UpdateToken,
8
16
  } from '../types/index.mjs';
9
17
 
10
18
  /**
@@ -60,54 +68,60 @@ import type {
60
68
  export const skipUntil =
61
69
  <A,>(notifier: Observable<unknown>): DropInitialValueOperator<A, A> =>
62
70
  (parentObservable) =>
63
- createSkipUntilObservable(parentObservable, notifier);
71
+ new SkipUntilObservableClass(parentObservable, notifier);
64
72
 
65
- const createSkipUntilObservable = <A,>(
66
- parentObservable: Observable<A>,
67
- notifier: Observable<unknown>,
68
- ): SkipUntilOperatorObservable<A> => {
69
- // A notifier that already holds a value, or has completed, would call
70
- // `stopSkipping` inside `subscribe`, before the subscription is stored.
71
- let mut_isSkipping =
72
- Optional.isNone(notifier.getSnapshot()) && !notifier.isCompleted;
73
+ class SkipUntilObservableClass<A>
74
+ extends SyncChildObservableClass<A, readonly [A]>
75
+ implements SkipUntilOperatorObservable<A>
76
+ {
77
+ #mut_isSkipping: boolean;
78
+ #mut_subscription: Subscription | undefined;
73
79
 
74
- let mut_subscription: Subscription | undefined = undefined;
80
+ constructor(parentObservable: Observable<A>, notifier: Observable<unknown>) {
81
+ super({
82
+ parents: [parentObservable],
83
+ initialValue: Optional.none,
84
+ });
75
85
 
76
- const stopSkipping = (): void => {
77
- mut_isSkipping = false;
86
+ // A notifier that already holds a value, or has completed, would stop
87
+ // the skipping inside `subscribe`, before the subscription is stored.
88
+ this.#mut_isSkipping =
89
+ Optional.isNone(notifier.getSnapshot()) && !notifier.isCompleted;
78
90
 
79
- mut_subscription?.unsubscribe();
91
+ this.#mut_subscription = undefined;
80
92
 
81
- mut_subscription = undefined;
82
- };
93
+ if (this.#mut_isSkipping) {
94
+ const stopSkipping = (): void => {
95
+ this.#mut_isSkipping = false;
83
96
 
84
- const observable = createSyncChildObservable<A, readonly [A]>(
85
- {
86
- parents: [parentObservable],
87
- initialValue: Optional.none,
88
- onComplete: () => {
89
- mut_subscription?.unsubscribe();
90
- },
91
- },
92
- ({ setNext }) =>
93
- (updateToken) => {
94
- const sn = parentObservable.getSnapshot();
97
+ this.#mut_subscription?.unsubscribe();
98
+
99
+ this.#mut_subscription = undefined;
100
+ };
95
101
 
96
- if (
97
- mut_isSkipping ||
98
- parentObservable.updateToken !== updateToken ||
99
- Optional.isNone(sn)
100
- ) {
101
- return; // skip update
102
- }
102
+ this.#mut_subscription = notifier.subscribe(stopSkipping, stopSkipping);
103
+ }
103
104
 
104
- setNext(sn.value, updateToken);
105
- },
106
- );
105
+ this.attach();
106
+ }
107
+
108
+ override tryUpdate(updateToken: UpdateToken): void {
109
+ const par = this.parents[0];
110
+
111
+ const sn = par.getSnapshot();
107
112
 
108
- if (mut_isSkipping) {
109
- mut_subscription = notifier.subscribe(stopSkipping, stopSkipping);
113
+ if (
114
+ this.#mut_isSkipping ||
115
+ par.updateToken !== updateToken ||
116
+ Optional.isNone(sn)
117
+ ) {
118
+ return; // skip update
119
+ }
120
+
121
+ this.setNext(sn.value, updateToken);
110
122
  }
111
123
 
112
- return observable;
113
- };
124
+ protected override teardown(): void {
125
+ this.#mut_subscription?.unsubscribe();
126
+ }
127
+ }
@@ -1,9 +1,17 @@
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, SafeUint, asSafeUint, pipe } from 'ts-data-forge';
2
- import { createSyncChildObservable } from '../base/index.mjs';
9
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  DropInitialValueOperator,
5
12
  Observable,
6
13
  SkipWhileOperatorObservable,
14
+ UpdateToken,
7
15
  } from '../types/index.mjs';
8
16
 
9
17
  /**
@@ -69,46 +77,56 @@ export const skipWhile =
69
77
  predicate: (value: A, index: SafeUint | -1) => boolean,
70
78
  ): DropInitialValueOperator<A, A> =>
71
79
  (parentObservable) =>
72
- createSkipWhileObservable(parentObservable, predicate);
80
+ new SkipWhileObservableClass(parentObservable, predicate);
73
81
 
74
82
  /* implementation */
75
83
 
76
- const createSkipWhileObservable = <A,>(
77
- parentObservable: Observable<A>,
78
- predicate: (value: A, index: SafeUint | -1) => boolean,
79
- ): SkipWhileOperatorObservable<A> =>
80
- createSyncChildObservable(
81
- {
84
+ class SkipWhileObservableClass<A>
85
+ extends SyncChildObservableClass<A, readonly [A]>
86
+ implements SkipWhileOperatorObservable<A>
87
+ {
88
+ readonly #predicate: (value: A, index: SafeUint | -1) => boolean;
89
+ #mut_index: SafeUint | -1;
90
+ #mut_isSkipping: boolean;
91
+
92
+ constructor(
93
+ parentObservable: Observable<A>,
94
+ predicate: (value: A, index: SafeUint | -1) => boolean,
95
+ ) {
96
+ super({
82
97
  parents: [parentObservable],
83
98
  initialValue: pipe(parentObservable.getSnapshot()).map((sn) =>
84
99
  Optional.isNone(sn) || predicate(sn.value, -1) ? Optional.none : sn,
85
100
  ).value,
86
- },
87
- ({ setNext }) => {
88
- let mut_index: SafeUint | -1 = -1;
101
+ });
102
+
103
+ this.#mut_index = -1;
104
+
105
+ this.#mut_isSkipping = true;
106
+
107
+ this.#predicate = predicate;
108
+
109
+ this.attach();
110
+ }
89
111
 
90
- let mut_skipping = true;
112
+ override tryUpdate(updateToken: UpdateToken): void {
113
+ const par = this.parents[0];
91
114
 
92
- return (updateToken) => {
93
- const sn = parentObservable.getSnapshot();
115
+ const sn = par.getSnapshot();
94
116
 
95
- if (
96
- parentObservable.updateToken !== updateToken ||
97
- Optional.isNone(sn)
98
- ) {
99
- return; // skip update
100
- }
117
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
118
+ return; // skip update
119
+ }
101
120
 
102
- mut_index =
103
- mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, mut_index);
121
+ this.#mut_index =
122
+ this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
104
123
 
105
- if (!predicate(sn.value, mut_index)) {
106
- mut_skipping = false;
107
- }
124
+ if (!this.#predicate(sn.value, this.#mut_index)) {
125
+ this.#mut_isSkipping = false;
126
+ }
108
127
 
109
- if (!mut_skipping) {
110
- setNext(sn.value, updateToken);
111
- }
112
- };
113
- },
114
- );
128
+ if (!this.#mut_isSkipping) {
129
+ this.setNext(sn.value, updateToken);
130
+ }
131
+ }
132
+ }
@@ -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
@@ -102,50 +117,74 @@ export const switchMap =
102
117
  mapToObservable: (curr: A) => Observable<B>,
103
118
  ): DropInitialValueOperator<A, B> =>
104
119
  (parentObservable) =>
105
- 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>;
106
127
 
107
- const createSwitchMapObservable = <A, B>(
108
- parentObservable: Observable<A>,
109
- mapToObservable: (curr: A) => Observable<B>,
110
- ): SwitchMapOperatorObservable<A, B> => {
111
- 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;
112
134
 
113
- let mut_subscription: Subscription | undefined = undefined;
135
+ #mut_innerScope: Scope | undefined;
136
+ #mut_subscription: Subscription | undefined;
114
137
 
115
- return createAsyncChildObservable(
116
- {
138
+ constructor(
139
+ parentObservable: Observable<A>,
140
+ mapToObservable: (curr: A) => Observable<B>,
141
+ ) {
142
+ super({
117
143
  parents: [parentObservable],
118
144
  initialValue: Optional.none,
119
- onComplete: () => {
120
- 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];
121
167
 
122
- mut_observable?.complete();
123
- },
124
- },
125
- ({ startUpdate }) =>
126
- (updateToken) => {
127
- const sn = parentObservable.getSnapshot();
168
+ const sn = par.getSnapshot();
128
169
 
129
- if (
130
- parentObservable.updateToken !== updateToken ||
131
- Optional.isNone(sn)
132
- ) {
133
- return; // skip update
134
- }
170
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
171
+ return; // skip update
172
+ }
135
173
 
136
- mut_observable?.complete();
174
+ this.#releaseInner();
137
175
 
138
- mut_subscription?.unsubscribe();
176
+ const innerScope = createScope(this.#ownerScope);
139
177
 
140
- const observable = mapToObservable(sn.value);
178
+ this.#mut_innerScope = innerScope;
141
179
 
142
- mut_observable = observable;
180
+ const observable = innerScope.run(() => this.#mapToObservable(sn.value));
143
181
 
144
- const subscription = observable.subscribe((curr) => {
145
- startUpdate(curr);
146
- });
182
+ this.#mut_subscription = observable.subscribe((curr) => {
183
+ this.startUpdate(curr);
184
+ });
185
+ }
147
186
 
148
- mut_subscription = subscription;
149
- },
150
- );
151
- };
187
+ protected override teardown(): void {
188
+ this.#releaseInner();
189
+ }
190
+ }
@@ -1,7 +1,8 @@
1
+ import { toNode } from '../class/index.mjs';
1
2
  import { source } from '../create/index.mjs';
2
- import { take } from '../predefined/index.mjs';
3
3
  import { skipUntil } from './skip-until.mjs';
4
4
  import { takeUntil } from './take-until.mjs';
5
+ import { take } from './take.mjs';
5
6
 
6
7
  describe('takeUntil releases the notifier', () => {
7
8
  test('when the result is completed before the notifier emits', () => {
@@ -9,11 +10,11 @@ describe('takeUntil releases the notifier', () => {
9
10
 
10
11
  const result$ = source<number>().pipe(takeUntil(notifier$));
11
12
 
12
- assert.isTrue(notifier$.hasSubscriber);
13
+ assert.isTrue(toNode(notifier$).hasSubscriber);
13
14
 
14
15
  result$.complete();
15
16
 
16
- assert.isFalse(notifier$.hasSubscriber);
17
+ assert.isFalse(toNode(notifier$).hasSubscriber);
17
18
  });
18
19
 
19
20
  test('when the parent completes', () => {
@@ -25,15 +26,17 @@ describe('takeUntil releases the notifier', () => {
25
26
 
26
27
  num$.complete();
27
28
 
28
- assert.isFalse(notifier$.hasSubscriber);
29
+ assert.isFalse(toNode(notifier$).hasSubscriber);
29
30
  });
30
31
 
31
- test('when the downstream completes', () => {
32
+ test('not when the downstream completes', () => {
32
33
  const notifier$ = source<undefined>();
33
34
 
34
35
  const num$ = source<number>();
35
36
 
36
- const taken$ = num$.pipe(takeUntil(notifier$)).pipe(take(1));
37
+ const result$ = num$.pipe(takeUntil(notifier$));
38
+
39
+ const taken$ = result$.pipe(take(1));
37
40
 
38
41
  taken$.subscribe(() => {});
39
42
 
@@ -44,7 +47,10 @@ describe('takeUntil releases the notifier', () => {
44
47
 
45
48
  assert.isTrue(taken$.isCompleted);
46
49
 
47
- assert.isFalse(notifier$.hasSubscriber);
50
+ // Completion flows only downstream, so `takeUntil` is still running.
51
+ assert.isFalse(result$.isCompleted);
52
+
53
+ assert.isTrue(toNode(notifier$).hasSubscriber);
48
54
  });
49
55
 
50
56
  test('when the notifier emits', () => {
@@ -56,7 +62,7 @@ describe('takeUntil releases the notifier', () => {
56
62
 
57
63
  assert.isTrue(result$.isCompleted);
58
64
 
59
- assert.isFalse(notifier$.hasSubscriber);
65
+ assert.isFalse(toNode(notifier$).hasSubscriber);
60
66
  });
61
67
 
62
68
  test('when the notifier has already emitted', () => {
@@ -66,7 +72,7 @@ describe('takeUntil releases the notifier', () => {
66
72
 
67
73
  assert.isTrue(result$.isCompleted);
68
74
 
69
- assert.isFalse(notifier$.hasSubscriber);
75
+ assert.isFalse(toNode(notifier$).hasSubscriber);
70
76
  });
71
77
  });
72
78
 
@@ -88,7 +94,7 @@ describe('skipUntil releases the notifier', () => {
88
94
 
89
95
  notifier$.next(undefined);
90
96
 
91
- assert.isFalse(notifier$.hasSubscriber);
97
+ assert.isFalse(toNode(notifier$).hasSubscriber);
92
98
 
93
99
  num$.next(2);
94
100
 
@@ -108,7 +114,7 @@ describe('skipUntil releases the notifier', () => {
108
114
  mut_history.push(x);
109
115
  });
110
116
 
111
- assert.isFalse(notifier$.hasSubscriber);
117
+ assert.isFalse(toNode(notifier$).hasSubscriber);
112
118
 
113
119
  num$.next(1);
114
120
 
@@ -122,6 +128,6 @@ describe('skipUntil releases the notifier', () => {
122
128
 
123
129
  result$.complete();
124
130
 
125
- assert.isFalse(notifier$.hasSubscriber);
131
+ assert.isFalse(toNode(notifier$).hasSubscriber);
126
132
  });
127
133
  });