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
@@ -0,0 +1,280 @@
1
+ import { source } from '../create/index.mjs';
2
+ import { createScope } from '../scope/index.mjs';
3
+ import { map } from './map.mjs';
4
+ import { mergeMap } from './merge-map.mjs';
5
+ import { switchMap } from './switch-map.mjs';
6
+
7
+ describe('switchMap releases only the inner observables its projection created (#2335)', () => {
8
+ test('a shared inner survives a switch', () => {
9
+ const user$ = source<'a' | 'b'>();
10
+
11
+ const a$ = source<number>();
12
+
13
+ const b$ = source<number>();
14
+
15
+ const current$ = user$.pipe(switchMap((u) => (u === 'a' ? a$ : b$)));
16
+
17
+ const mut_current: number[] = [];
18
+
19
+ current$.subscribe((v) => {
20
+ mut_current.push(v);
21
+ });
22
+
23
+ const mut_others: number[] = [];
24
+
25
+ a$.subscribe((v) => {
26
+ mut_others.push(v);
27
+ });
28
+
29
+ user$.next('a');
30
+
31
+ a$.next(1);
32
+
33
+ user$.next('b');
34
+
35
+ assert.isFalse(a$.isCompleted);
36
+
37
+ a$.next(2);
38
+
39
+ b$.next(3);
40
+
41
+ assert.deepStrictEqual(mut_others, [1, 2]);
42
+
43
+ assert.deepStrictEqual(mut_current, [1, 3]);
44
+ });
45
+
46
+ test('a shared inner survives the completion of switchMap', () => {
47
+ const user$ = source<'a'>();
48
+
49
+ const a$ = source<number>();
50
+
51
+ const current$ = user$.pipe(switchMap(() => a$));
52
+
53
+ current$.subscribe(() => {});
54
+
55
+ user$.next('a');
56
+
57
+ current$.complete();
58
+
59
+ assert.isFalse(a$.isCompleted);
60
+
61
+ const mut_others: number[] = [];
62
+
63
+ a$.subscribe((v) => {
64
+ mut_others.push(v);
65
+ });
66
+
67
+ a$.next(1);
68
+
69
+ assert.deepStrictEqual(mut_others, [1]);
70
+ });
71
+
72
+ test('a node derived in the projection from a shared observable is released, and the shared one survives', () => {
73
+ const user$ = source<number>();
74
+
75
+ const shared$ = source<number>();
76
+
77
+ const mut_derived: { isCompleted: boolean }[] = [];
78
+
79
+ const current$ = user$.pipe(
80
+ switchMap((u) => {
81
+ const derived$ = shared$.pipe(map((x) => x + u));
82
+
83
+ mut_derived.push(derived$);
84
+
85
+ return derived$;
86
+ }),
87
+ );
88
+
89
+ current$.subscribe(() => {});
90
+
91
+ user$.next(1);
92
+
93
+ user$.next(2);
94
+
95
+ assert.deepStrictEqual(
96
+ mut_derived.map((o) => o.isCompleted),
97
+ [true, false],
98
+ );
99
+
100
+ assert.isFalse(shared$.isCompleted);
101
+
102
+ current$.complete();
103
+
104
+ assert.deepStrictEqual(
105
+ mut_derived.map((o) => o.isCompleted),
106
+ [true, true],
107
+ );
108
+
109
+ assert.isFalse(shared$.isCompleted);
110
+ });
111
+
112
+ test('every node the projection created is released on a switch, not only the returned one', () => {
113
+ const user$ = source<number>();
114
+
115
+ const mut_created: { isCompleted: boolean }[] = [];
116
+
117
+ const current$ = user$.pipe(
118
+ switchMap((u) => {
119
+ const inner$ = source<number>();
120
+
121
+ const mapped$ = inner$.pipe(map((x) => x + u));
122
+
123
+ mut_created.push(inner$, mapped$);
124
+
125
+ return mapped$;
126
+ }),
127
+ );
128
+
129
+ current$.subscribe(() => {});
130
+
131
+ user$.next(1);
132
+
133
+ user$.next(2);
134
+
135
+ assert.deepStrictEqual(
136
+ mut_created.map((o) => o.isCompleted),
137
+ [true, true, false, false],
138
+ );
139
+
140
+ current$.complete();
141
+
142
+ assert.deepStrictEqual(
143
+ mut_created.map((o) => o.isCompleted),
144
+ [true, true, true, true],
145
+ );
146
+ });
147
+ });
148
+
149
+ describe('the scope switchMap and mergeMap were created in (#2335)', () => {
150
+ test('disposing it releases what their projections created, and the shared inners survive', () => {
151
+ const user$ = source<number>();
152
+
153
+ const shared$ = source<number>();
154
+
155
+ const mut_created: { isCompleted: boolean }[] = [];
156
+
157
+ const scope = createScope();
158
+
159
+ scope.run(() => {
160
+ user$
161
+ .pipe(
162
+ switchMap(() => {
163
+ const inner$ = source<number>();
164
+
165
+ mut_created.push(inner$);
166
+
167
+ return inner$;
168
+ }),
169
+ )
170
+ .subscribe(() => {});
171
+
172
+ user$.pipe(mergeMap(() => shared$)).subscribe(() => {});
173
+ });
174
+
175
+ user$.next(1);
176
+
177
+ assert.deepStrictEqual(
178
+ mut_created.map((o) => o.isCompleted),
179
+ [false],
180
+ );
181
+
182
+ scope.dispose();
183
+
184
+ assert.deepStrictEqual(
185
+ mut_created.map((o) => o.isCompleted),
186
+ [true],
187
+ );
188
+
189
+ assert.isFalse(shared$.isCompleted);
190
+
191
+ assert.isFalse(user$.isCompleted);
192
+ });
193
+ });
194
+
195
+ describe('mergeMap releases only the inner observables its projection created (#2335)', () => {
196
+ test('a shared inner survives the completion of mergeMap', () => {
197
+ const user$ = source<'a' | 'b'>();
198
+
199
+ const a$ = source<number>();
200
+
201
+ const b$ = source<number>();
202
+
203
+ const merged$ = user$.pipe(mergeMap((u) => (u === 'a' ? a$ : b$)));
204
+
205
+ const mut_merged: number[] = [];
206
+
207
+ merged$.subscribe((v) => {
208
+ mut_merged.push(v);
209
+ });
210
+
211
+ user$.next('a');
212
+
213
+ user$.next('b');
214
+
215
+ a$.next(1);
216
+
217
+ b$.next(2);
218
+
219
+ assert.deepStrictEqual(mut_merged, [1, 2]);
220
+
221
+ merged$.complete();
222
+
223
+ assert.isFalse(a$.isCompleted);
224
+
225
+ assert.isFalse(b$.isCompleted);
226
+
227
+ const mut_others: number[] = [];
228
+
229
+ a$.subscribe((v) => {
230
+ mut_others.push(v);
231
+ });
232
+
233
+ a$.next(3);
234
+
235
+ // A new subscriber receives the current value first.
236
+ assert.deepStrictEqual(mut_others, [1, 3]);
237
+
238
+ assert.deepStrictEqual(mut_merged, [1, 2]);
239
+ });
240
+
241
+ test('every node the projection created is released on completion', () => {
242
+ const user$ = source<number>();
243
+
244
+ const shared$ = source<number>();
245
+
246
+ const mut_created: { isCompleted: boolean }[] = [];
247
+
248
+ const merged$ = user$.pipe(
249
+ mergeMap((u) => {
250
+ const inner$ = source<number>();
251
+
252
+ const derived$ = shared$.pipe(map((x) => x + u));
253
+
254
+ mut_created.push(inner$, derived$);
255
+
256
+ return inner$;
257
+ }),
258
+ );
259
+
260
+ merged$.subscribe(() => {});
261
+
262
+ user$.next(1);
263
+
264
+ user$.next(2);
265
+
266
+ assert.deepStrictEqual(
267
+ mut_created.map((o) => o.isCompleted),
268
+ [false, false, false, false],
269
+ );
270
+
271
+ merged$.complete();
272
+
273
+ assert.deepStrictEqual(
274
+ mut_created.map((o) => o.isCompleted),
275
+ [true, true, true, true],
276
+ );
277
+
278
+ assert.isFalse(shared$.isCompleted);
279
+ });
280
+ });
@@ -1,11 +1,19 @@
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, expectType } from 'ts-data-forge';
2
- import { createSyncChildObservable } from '../base/index.mjs';
9
+ import { SyncChildObservableClass } from '../class/index.mjs';
3
10
  import { source } from '../create/index.mjs';
4
11
  import type {
5
12
  InitializedObservable,
6
13
  KeepInitialValueOperator,
7
14
  MapOperatorObservable,
8
15
  Observable,
16
+ UpdateToken,
9
17
  } from '../types/index.mjs';
10
18
  import { withInitialValue } from './with-initial-value.mjs';
11
19
 
@@ -52,44 +60,53 @@ export const map = <A, B>(
52
60
  ): KeepInitialValueOperator<A, B> =>
53
61
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
54
62
  ((parentObservable) =>
55
- createMapObservable(parentObservable, mapFn)) as KeepInitialValueOperator<
56
- A,
57
- B
58
- >;
63
+ new MapObservableClass(
64
+ parentObservable,
65
+ mapFn,
66
+ )) as KeepInitialValueOperator<A, B>;
59
67
 
60
68
  /* implementation */
61
69
 
62
- const createMapObservable = <A, B>(
63
- parentObservable: Observable<A>,
64
- mapFn: (x: A, index: SafeUint | -1) => B,
65
- ): MapOperatorObservable<A, B> =>
66
- createSyncChildObservable(
67
- {
70
+ class MapObservableClass<A, B>
71
+ extends SyncChildObservableClass<B, readonly [A]>
72
+ implements MapOperatorObservable<A, B>
73
+ {
74
+ readonly #mapFn: (x: A, index: SafeUint | -1) => B;
75
+ #mut_index: SafeUint | -1;
76
+
77
+ constructor(
78
+ parentObservable: Observable<A>,
79
+ mapFn: (x: A, index: SafeUint | -1) => B,
80
+ ) {
81
+ super({
68
82
  parents: [parentObservable],
69
83
  initialValue: Optional.map(parentObservable.getSnapshot(), (x) =>
70
84
  mapFn(x, -1),
71
85
  ),
72
- },
73
- ({ setNext }) => {
74
- let mut_index: SafeUint | -1 = -1;
75
-
76
- return (updateToken) => {
77
- const sn = parentObservable.getSnapshot();
78
-
79
- if (
80
- parentObservable.updateToken !== updateToken ||
81
- Optional.isNone(sn)
82
- ) {
83
- return; // skip update
84
- }
85
-
86
- mut_index =
87
- mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, mut_index);
88
-
89
- setNext(mapFn(sn.value, mut_index), updateToken);
90
- };
91
- },
92
- );
86
+ });
87
+
88
+ this.#mut_index = -1;
89
+
90
+ this.#mapFn = mapFn;
91
+
92
+ this.attach();
93
+ }
94
+
95
+ override tryUpdate(updateToken: UpdateToken): void {
96
+ const par = this.parents[0];
97
+
98
+ const sn = par.getSnapshot();
99
+
100
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
101
+ return; // skip update
102
+ }
103
+
104
+ this.#mut_index =
105
+ this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
106
+
107
+ this.setNext(this.#mapFn(sn.value, this.#mut_index), updateToken);
108
+ }
109
+ }
93
110
 
94
111
  if (import.meta.vitest !== undefined) {
95
112
  test('type test', () => {
@@ -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 { Arr, 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
  MergeMapOperatorObservable,
6
14
  Observable,
15
+ Scope,
7
16
  Subscription,
17
+ UpdateToken,
8
18
  } from '../types/index.mjs';
9
19
 
10
20
  /**
11
21
  * Projects each source value to an observable and merges all inner observables.
12
22
  * Unlike `switchMap`, does not cancel previous inner observables.
13
23
  *
24
+ * Each projection runs in a scope of its own (see `createScope`): what it
25
+ * creates is disposed when `mergeMap` completes. An observable it only
26
+ * returns — one created outside the projection and shared with others — is
27
+ * 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
@@ -104,7 +119,7 @@ export const mergeMap =
104
119
  mapToObservable: (curr: A) => Observable<B>,
105
120
  ): DropInitialValueOperator<A, B> =>
106
121
  (parentObservable) =>
107
- createMergeMapObservable(parentObservable, mapToObservable);
122
+ new MergeMapObservableClass(parentObservable, mapToObservable);
108
123
 
109
124
  /**
110
125
  * Alias for `mergeMap`.
@@ -112,48 +127,71 @@ export const mergeMap =
112
127
  */
113
128
  export const flatMap = mergeMap;
114
129
 
115
- const createMergeMapObservable = <A, B>(
116
- parentObservable: Observable<A>,
117
- mapToObservable: (curr: A) => Observable<B>,
118
- ): MergeMapOperatorObservable<A, B> => {
119
- let mut_observables: readonly Observable<B>[] = [];
130
+ class MergeMapObservableClass<A, B>
131
+ extends AsyncChildObservableClass<B, readonly [A]>
132
+ implements MergeMapOperatorObservable<A, B>
133
+ {
134
+ readonly #mapToObservable: (curr: A) => Observable<B>;
135
+
136
+ /** See `SwitchMapObservableClass`. */
137
+ readonly #ownerScope: Scope | undefined;
120
138
 
121
- let mut_subscriptions: readonly Subscription[] = [];
139
+ #mut_innerScopes: readonly Scope[];
140
+ #mut_subscriptions: readonly Subscription[];
122
141
 
123
- return createAsyncChildObservable(
124
- {
142
+ constructor(
143
+ parentObservable: Observable<A>,
144
+ mapToObservable: (curr: A) => Observable<B>,
145
+ ) {
146
+ super({
125
147
  parents: [parentObservable],
126
148
  initialValue: Optional.none,
127
- onComplete: () => {
128
- for (const s of mut_subscriptions) {
129
- s.unsubscribe();
130
- }
131
-
132
- for (const o of mut_observables) {
133
- o.complete();
134
- }
135
- },
136
- },
137
- ({ startUpdate }) =>
138
- (updateToken) => {
139
- const sn = parentObservable.getSnapshot();
140
-
141
- if (
142
- parentObservable.updateToken !== updateToken ||
143
- Optional.isNone(sn)
144
- ) {
145
- return; // skip update
146
- }
147
-
148
- const observable = mapToObservable(sn.value);
149
-
150
- mut_observables = Arr.toPushed(mut_observables, observable);
151
-
152
- const subscription = observable.subscribe((curr) => {
153
- startUpdate(curr);
154
- });
155
-
156
- mut_subscriptions = Arr.toPushed(mut_subscriptions, subscription);
157
- },
158
- );
159
- };
149
+ });
150
+
151
+ this.#mapToObservable = mapToObservable;
152
+
153
+ this.#ownerScope = getCurrentScope();
154
+
155
+ this.#mut_innerScopes = [];
156
+
157
+ this.#mut_subscriptions = [];
158
+
159
+ this.attach();
160
+ }
161
+
162
+ override tryUpdate(updateToken: UpdateToken): void {
163
+ const par = this.parents[0];
164
+
165
+ const sn = par.getSnapshot();
166
+
167
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
168
+ return; // skip update
169
+ }
170
+
171
+ const innerScope = createScope(this.#ownerScope);
172
+
173
+ this.#mut_innerScopes = Arr.toPushed(this.#mut_innerScopes, innerScope);
174
+
175
+ const observable = innerScope.run(() => this.#mapToObservable(sn.value));
176
+
177
+ const subscription = observable.subscribe((curr) => {
178
+ this.startUpdate(curr);
179
+ });
180
+
181
+ this.#mut_subscriptions = Arr.toPushed(
182
+ this.#mut_subscriptions,
183
+ subscription,
184
+ );
185
+ }
186
+
187
+ /** Releases the inner observables, and only those this operator made. */
188
+ protected override teardown(): void {
189
+ for (const s of this.#mut_subscriptions) {
190
+ s.unsubscribe();
191
+ }
192
+
193
+ for (const scope of this.#mut_innerScopes) {
194
+ scope.dispose();
195
+ }
196
+ }
197
+ }
@@ -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
9
  import type { FixedLengthTuple } from 'ts-type-forge';
3
- import { createSyncChildObservable } from '../base/index.mjs';
10
+ import { SyncChildObservableClass } from '../class/index.mjs';
4
11
  import type {
5
12
  DropInitialValueOperator,
6
13
  Observable,
7
14
  PairwiseOperatorObservable,
15
+ UpdateToken,
8
16
  } from '../types/index.mjs';
9
17
 
10
18
  /**
@@ -68,41 +76,43 @@ export const pairwise = <A,>(): DropInitialValueOperator<
68
76
  const f = <A,>(
69
77
  parentObservable: Observable<A>,
70
78
  ): Observable<FixedLengthTuple<2, A>> =>
71
- createPairwiseObservable(parentObservable);
79
+ new PairwiseObservableClass(parentObservable);
72
80
 
73
- const createPairwiseObservable = <A,>(
74
- parentObservable: Observable<A>,
75
- ): PairwiseOperatorObservable<A> =>
76
- createSyncChildObservable<FixedLengthTuple<2, A>, readonly [A]>(
77
- {
81
+ class PairwiseObservableClass<A>
82
+ extends SyncChildObservableClass<FixedLengthTuple<2, A>, readonly [A]>
83
+ implements PairwiseOperatorObservable<A>
84
+ {
85
+ #mut_previousValue: Optional<A>;
86
+
87
+ constructor(parentObservable: Observable<A>) {
88
+ super({
78
89
  parents: [parentObservable],
79
90
  initialValue: Optional.none,
80
- },
81
- ({ setNext }) => {
82
- // parentObservable.snapshot has value
83
- // if parentObservable is InitializedObservable
84
- let mut_previousValue: Optional<A> = parentObservable.getSnapshot();
91
+ });
92
+
93
+ // parentObservable.snapshot has value
94
+ // if parentObservable is InitializedObservable
95
+ this.#mut_previousValue = parentObservable.getSnapshot();
96
+
97
+ this.attach();
98
+ }
85
99
 
86
- return (updateToken) => {
87
- const sn = parentObservable.getSnapshot();
100
+ override tryUpdate(updateToken: UpdateToken): void {
101
+ const par = this.parents[0];
88
102
 
89
- if (
90
- parentObservable.updateToken !== updateToken ||
91
- Optional.isNone(sn)
92
- ) {
93
- return; // skip update
94
- }
103
+ const sn = par.getSnapshot();
95
104
 
96
- const prev = mut_previousValue;
105
+ if (par.updateToken !== updateToken || Optional.isNone(sn)) {
106
+ return; // skip update
107
+ }
97
108
 
98
- const cond = !Optional.isNone(prev);
109
+ const prev = this.#mut_previousValue;
99
110
 
100
- // NOTE: Must update before setNext, otherwise Optional.isNone(prev) remains true when tryUpdate is called consecutively
101
- mut_previousValue = parentObservable.getSnapshot();
111
+ // NOTE: Must update before setNext, otherwise Optional.isNone(prev) remains true when tryUpdate is called consecutively
112
+ this.#mut_previousValue = sn;
102
113
 
103
- if (cond) {
104
- setNext([prev.value, sn.value], updateToken);
105
- }
106
- };
107
- },
108
- );
114
+ if (Optional.isSome(prev)) {
115
+ this.setNext([prev.value, sn.value], updateToken);
116
+ }
117
+ }
118
+ }