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,5 +1,12 @@
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, Result } from 'ts-data-forge';
2
- import { createRootObservable } from '../base/index.mjs';
9
+ import { RootObservableClass } from '../class/index.mjs';
3
10
  import type { FromPromiseObservable } from '../types/index.mjs';
4
11
 
5
12
  /**
@@ -72,50 +79,55 @@ import type { FromPromiseObservable } from '../types/index.mjs';
72
79
  export const fromAbortablePromise = <A, E = unknown>(
73
80
  // @sumi-expect-error readonly/require-readonly-parameter
74
81
  factory: (signal: AbortSignal) => Promise<A>,
75
- ): FromPromiseObservable<A, E> => {
76
- const abortController = new AbortController();
82
+ ): FromPromiseObservable<A, E> =>
83
+ new FromAbortablePromiseObservableClass(factory);
77
84
 
78
- return createRootObservable<Result<A, E>>(
79
- {
80
- initialValue: Optional.none,
81
- onComplete: () => {
82
- abortController.abort();
83
- },
84
- },
85
- ({ startUpdate, isCompleted, complete }) => {
86
- const promise = factory(abortController.signal);
85
+ class FromAbortablePromiseObservableClass<A, E = unknown>
86
+ extends RootObservableClass<Result<A, E>>
87
+ implements FromPromiseObservable<A, E>
88
+ {
89
+ readonly #abortController: AbortController;
87
90
 
88
- promise
89
- .then((value) => {
90
- if (isCompleted()) {
91
- return;
92
- }
91
+ // @sumi-expect-error readonly/require-readonly-parameter
92
+ constructor(factory: (signal: AbortSignal) => Promise<A>) {
93
+ super({ initialValue: Optional.none });
94
+
95
+ this.#abortController = new AbortController();
96
+
97
+ const promise = factory(this.#abortController.signal);
98
+
99
+ promise
100
+ .then((value) => {
101
+ if (this.isCompleted) {
102
+ return;
103
+ }
93
104
 
94
- startUpdate(Result.ok(value));
95
- })
96
- .catch((error: unknown) => {
97
- if (isCompleted()) {
98
- return;
99
- }
105
+ this.startUpdate(Result.ok(value));
106
+ })
107
+ .catch((error: unknown) => {
108
+ if (this.isCompleted) {
109
+ return;
110
+ }
100
111
 
101
- // Silently ignore AbortError — it means the observable was
102
- // intentionally completed (e.g., by switchMap).
103
- if (error instanceof DOMException && error.name === 'AbortError') {
104
- return;
105
- }
112
+ // Silently ignore AbortError — it means the observable was
113
+ // intentionally completed (e.g., by switchMap).
114
+ if (error instanceof DOMException && error.name === 'AbortError') {
115
+ return;
116
+ }
106
117
 
107
- startUpdate(
108
- Result.err(
109
- // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
110
- error as E,
111
- ),
112
- );
113
- })
114
- .finally(() => {
115
- complete();
116
- });
118
+ this.startUpdate(
119
+ Result.err(
120
+ // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
121
+ error as E,
122
+ ),
123
+ );
124
+ })
125
+ .finally(() => {
126
+ this.complete();
127
+ });
128
+ }
117
129
 
118
- return {};
119
- },
120
- );
121
- };
130
+ protected override teardown(): void {
131
+ this.#abortController.abort();
132
+ }
133
+ }
@@ -1,5 +1,11 @@
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
1
7
  import { Optional, Result } from 'ts-data-forge';
2
- import { createRootObservable } from '../base/index.mjs';
8
+ import { RootObservableClass } from '../class/index.mjs';
3
9
  import type { FromPromiseObservable } from '../types/index.mjs';
4
10
 
5
11
  /**
@@ -51,34 +57,37 @@ import type { FromPromiseObservable } from '../types/index.mjs';
51
57
  */
52
58
  export const fromPromise = <A, E = unknown>(
53
59
  promise: Promise<A>,
54
- ): FromPromiseObservable<A, E> =>
55
- createRootObservable<Result<A, E>>(
56
- { initialValue: Optional.none },
57
- ({ startUpdate, isCompleted, complete }) => {
58
- promise
59
- .then((value) => {
60
- if (isCompleted()) {
61
- return;
62
- }
60
+ ): FromPromiseObservable<A, E> => new FromPromiseObservableClass(promise);
63
61
 
64
- startUpdate(Result.ok(value));
65
- })
66
- .catch((error: unknown) => {
67
- if (isCompleted()) {
68
- return;
69
- }
62
+ class FromPromiseObservableClass<A, E = unknown>
63
+ extends RootObservableClass<Result<A, E>>
64
+ implements FromPromiseObservable<A, E>
65
+ {
66
+ constructor(promise: Promise<A>) {
67
+ super({ initialValue: Optional.none });
70
68
 
71
- startUpdate(
72
- Result.err(
73
- // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
74
- error as E,
75
- ),
76
- );
77
- })
78
- .finally(() => {
79
- complete();
80
- });
69
+ promise
70
+ .then((value) => {
71
+ if (this.isCompleted) {
72
+ return;
73
+ }
81
74
 
82
- return {};
83
- },
84
- );
75
+ this.startUpdate(Result.ok(value));
76
+ })
77
+ .catch((error: unknown) => {
78
+ if (this.isCompleted) {
79
+ return;
80
+ }
81
+
82
+ this.startUpdate(
83
+ Result.err(
84
+ // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
85
+ error as E,
86
+ ),
87
+ );
88
+ })
89
+ .finally(() => {
90
+ this.complete();
91
+ });
92
+ }
93
+ }
@@ -1,5 +1,12 @@
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, Result } from 'ts-data-forge';
2
- import { createRootObservable } from '../base/index.mjs';
9
+ import { RootObservableClass } from '../class/index.mjs';
3
10
  import type {
4
11
  FromSubscribableObservable,
5
12
  Subscribable,
@@ -66,54 +73,64 @@ import type {
66
73
  */
67
74
  export const fromSubscribable = <A, E = unknown>(
68
75
  subscribable: Subscribable<A>,
69
- ): FromSubscribableObservable<A, E> => {
70
- let mut_subscription: Subscription | undefined = undefined;
76
+ ): FromSubscribableObservable<A, E> =>
77
+ new FromSubscribableObservableClass(subscribable);
71
78
 
72
- // Set once the subscribable has completed: its subscription is over, so
73
- // completing this observable must not unsubscribe from it again.
74
- let mut_isSourceCompleted = false;
79
+ class FromSubscribableObservableClass<A, E = unknown>
80
+ extends RootObservableClass<Result<A, E>>
81
+ implements FromSubscribableObservable<A, E>
82
+ {
83
+ readonly #mut_subscription: Subscription | undefined;
75
84
 
76
- const unsubscribe = (): void => {
77
- if (!mut_isSourceCompleted) {
78
- mut_subscription?.unsubscribe();
79
- }
85
+ /**
86
+ * Set once the subscribable has completed: its subscription is over, so
87
+ * completing this observable must not unsubscribe from it again.
88
+ */
89
+ #mut_isSourceCompleted: boolean;
90
+
91
+ constructor(subscribable: Subscribable<A>) {
92
+ super({ initialValue: Optional.none });
80
93
 
81
- mut_subscription = undefined;
82
- };
94
+ this.#mut_subscription = undefined;
83
95
 
84
- return createRootObservable<Result<A, E>>(
85
- { initialValue: Optional.none, onComplete: unsubscribe },
86
- ({ startUpdate, isCompleted, complete }) => {
87
- const subscription = subscribable.subscribe(
88
- (nextValue) => {
89
- startUpdate(Result.ok(nextValue));
90
- },
91
- (error?: unknown) => {
92
- startUpdate(
93
- Result.err(
94
- // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
95
- error as E,
96
- ),
97
- );
98
- },
99
- () => {
100
- mut_isSourceCompleted = true;
96
+ this.#mut_isSourceCompleted = false;
101
97
 
102
- complete();
103
- },
104
- );
98
+ const subscription = subscribable.subscribe(
99
+ (nextValue) => {
100
+ this.startUpdate(Result.ok(nextValue));
101
+ },
102
+ (error?: unknown) => {
103
+ this.startUpdate(
104
+ Result.err(
105
+ // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
106
+ error as E,
107
+ ),
108
+ );
109
+ },
110
+ () => {
111
+ this.#mut_isSourceCompleted = true;
105
112
 
106
- // `subscribe` may have completed this observable before returning
107
- // the subscription, so `unsubscribe` above had nothing to release.
108
- if (isCompleted()) {
109
- if (!mut_isSourceCompleted) {
110
- subscription.unsubscribe();
111
- }
112
- } else {
113
- mut_subscription = subscription;
114
- }
113
+ this.complete();
114
+ },
115
+ );
116
+
117
+ // `subscribe` may have completed this observable already.
118
+ if (this.isCompleted) {
119
+ this.#unsubscribe(subscription);
120
+ } else {
121
+ this.#mut_subscription = subscription;
122
+ }
123
+ }
115
124
 
116
- return {};
117
- },
118
- );
119
- };
125
+ #unsubscribe(subscription: Subscription): void {
126
+ if (!this.#mut_isSourceCompleted) {
127
+ subscription.unsubscribe();
128
+ }
129
+ }
130
+
131
+ protected override teardown(): void {
132
+ if (this.#mut_subscription !== undefined) {
133
+ this.#unsubscribe(this.#mut_subscription);
134
+ }
135
+ }
136
+ }
@@ -1,5 +1,5 @@
1
1
  import { Result } from 'ts-data-forge';
2
- import { take } from '../predefined/index.mjs';
2
+ import { take } from '../operators/index.mjs';
3
3
  import type { Subscribable } from '../types/index.mjs';
4
4
  import { fromSubscribable } from './from-subscribable.mjs';
5
5
 
@@ -71,7 +71,7 @@ describe(fromSubscribable, () => {
71
71
  expect(unsubscribe).not.toHaveBeenCalled();
72
72
  });
73
73
 
74
- test('a child that completes with nothing else using it unsubscribes from the subscribable', () => {
74
+ test('a child that completes leaves the subscribable subscribed until the observable is completed', () => {
75
75
  const { subscribable, emit, unsubscribe } = createManualSubscribable();
76
76
 
77
77
  const observable$ = fromSubscribable(subscribable);
@@ -86,7 +86,12 @@ describe(fromSubscribable, () => {
86
86
 
87
87
  assert.isTrue(taken$.isCompleted);
88
88
 
89
- assert.isTrue(observable$.isCompleted);
89
+ // Completion flows only downstream.
90
+ assert.isFalse(observable$.isCompleted);
91
+
92
+ expect(unsubscribe).not.toHaveBeenCalled();
93
+
94
+ observable$.complete();
90
95
 
91
96
  expect(unsubscribe).toHaveBeenCalledExactlyOnceWith();
92
97
  });
@@ -1,5 +1,5 @@
1
1
  import { Optional } from 'ts-data-forge';
2
- import { createRootObservable } from '../base/index.mjs';
2
+ import { RootObservableClass } from '../class/index.mjs';
3
3
  import type { InitializedRootObservable } from '../types/index.mjs';
4
4
 
5
5
  /**
@@ -32,10 +32,9 @@ import type { InitializedRootObservable } from '../types/index.mjs';
32
32
  * ```
33
33
  */
34
34
  export const just = <const A,>(value: A): InitializedRootObservable<A> => {
35
- const obs = createRootObservable<A>(
36
- { initialValue: Optional.some(value) },
37
- () => ({}),
38
- );
35
+ const obs = new RootObservableClass<A>({
36
+ initialValue: Optional.some(value),
37
+ });
39
38
 
40
39
  obs.complete();
41
40
 
@@ -1,5 +1,11 @@
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
1
7
  import { Arr, Optional } from 'ts-data-forge';
2
- import { createRootObservable } from '../base/index.mjs';
8
+ import { RootObservableClass } from '../class/index.mjs';
3
9
  import type {
4
10
  InitializedSourceObservable,
5
11
  SourceObservable,
@@ -58,28 +64,31 @@ export function source<const A>(
58
64
  export function source<const A>(): SourceObservable<A>;
59
65
 
60
66
  export function source<const A>(...args: readonly A[]): SourceObservable<A> {
61
- return createRootObservable<
62
- A,
63
- Readonly<{ next: (nextValue: A) => A | undefined }>
64
- >(
65
- {
67
+ return new SourceObservableClass<A>(...args);
68
+ }
69
+
70
+ class SourceObservableClass<A>
71
+ extends RootObservableClass<A>
72
+ implements SourceObservable<A>
73
+ {
74
+ constructor(...args: readonly A[]) {
75
+ super({
66
76
  initialValue: Arr.isNonEmpty(args)
67
77
  ? Optional.some(args[0])
68
78
  : Optional.none,
69
- },
70
- ({ startUpdate, getSnapshot, isCompleted }) => ({
71
- // Returns the value held after the call: `nextValue`, or, once
72
- // completed, the value already there — `undefined` only on a source
73
- // that never had one, whose `next` is typed `void`.
74
- next: (nextValue: A) => {
75
- if (isCompleted()) {
76
- return Optional.unwrap(getSnapshot());
77
- }
79
+ });
80
+ }
81
+
82
+ // Returns the value held after the call: `nextValue`, or, once completed,
83
+ // the value already there — `undefined` only on a source that never had
84
+ // one, whose `next` is typed `void`.
85
+ next(nextValue: A): A | undefined {
86
+ if (this.isCompleted) {
87
+ return Optional.unwrap(this.getSnapshot());
88
+ }
78
89
 
79
- startUpdate(nextValue);
90
+ this.startUpdate(nextValue);
80
91
 
81
- return nextValue;
82
- },
83
- }),
84
- );
92
+ return nextValue;
93
+ }
85
94
  }
@@ -1,5 +1,12 @@
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 { createRootObservable } from '../base/index.mjs';
9
+ import { RootObservableClass } from '../class/index.mjs';
3
10
  import type { TimerId, TimerObservable } from '../types/index.mjs';
4
11
 
5
12
  /**
@@ -44,46 +51,58 @@ export const timer = (
44
51
  startManually?: boolean;
45
52
  }>,
46
53
  ): TimerObservable => {
47
- let mut_timerId: TimerId | undefined = undefined;
54
+ const observable = new TimerObservableClass(milliSeconds);
48
55
 
49
- let mut_isStarted = false;
56
+ if (!(options?.startManually ?? false)) {
57
+ observable.start();
58
+ }
50
59
 
51
- const resetTimer = (): void => {
52
- if (mut_timerId !== undefined) {
53
- clearTimeout(mut_timerId);
54
- }
55
- };
60
+ return observable;
61
+ };
56
62
 
57
- const observable = createRootObservable<0, Readonly<{ start: () => void }>>(
58
- { initialValue: Optional.none, onComplete: resetTimer },
59
- ({ startUpdate, isCompleted, complete }) => ({
60
- start: () => {
61
- if (mut_isStarted) {
62
- console.warn('cannot start twice');
63
+ class TimerObservableClass
64
+ extends RootObservableClass<0>
65
+ implements TimerObservable
66
+ {
67
+ readonly #milliSeconds: number;
68
+ #mut_timerId: TimerId | undefined;
69
+ #mut_isStarted: boolean;
63
70
 
64
- return;
65
- }
71
+ constructor(milliSeconds: number) {
72
+ super({ initialValue: Optional.none });
66
73
 
67
- mut_isStarted = true;
74
+ this.#milliSeconds = milliSeconds;
68
75
 
69
- if (isCompleted()) {
70
- console.warn('cannot restart stopped TimerObservable');
76
+ this.#mut_timerId = undefined;
71
77
 
72
- return;
73
- }
78
+ this.#mut_isStarted = false;
79
+ }
74
80
 
75
- mut_timerId = setTimeout(() => {
76
- startUpdate(0);
81
+ start(): void {
82
+ if (this.#mut_isStarted) {
83
+ console.warn('cannot start twice');
77
84
 
78
- complete();
79
- }, milliSeconds);
80
- },
81
- }),
82
- );
85
+ return;
86
+ }
83
87
 
84
- if (!(options?.startManually ?? false)) {
85
- observable.start();
88
+ this.#mut_isStarted = true;
89
+
90
+ if (this.isCompleted) {
91
+ console.warn('cannot restart stopped TimerObservable');
92
+
93
+ return;
94
+ }
95
+
96
+ this.#mut_timerId = setTimeout(() => {
97
+ this.startUpdate(0);
98
+
99
+ this.complete();
100
+ }, this.#milliSeconds);
86
101
  }
87
102
 
88
- return observable;
89
- };
103
+ protected override teardown(): void {
104
+ if (this.#mut_timerId !== undefined) {
105
+ clearTimeout(this.#mut_timerId);
106
+ }
107
+ }
108
+ }
@@ -1,5 +1,7 @@
1
+ export * from './authoring.mjs';
1
2
  export * from './combine/index.mjs';
2
3
  export * from './create/index.mjs';
3
4
  export * from './operators/index.mjs';
4
5
  export * from './predefined/index.mjs';
6
+ export * from './scope/index.mjs';
5
7
  export * from './types/index.mjs';