synstate 0.1.1 → 1.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 (309) hide show
  1. package/README.md +317 -298
  2. package/dist/core/class/child-observable-class.d.mts.map +1 -1
  3. package/dist/core/class/child-observable-class.mjs +43 -10
  4. package/dist/core/class/child-observable-class.mjs.map +1 -1
  5. package/dist/core/class/observable-base-class.d.mts +4 -4
  6. package/dist/core/class/observable-base-class.d.mts.map +1 -1
  7. package/dist/core/class/observable-base-class.mjs +8 -8
  8. package/dist/core/class/observable-base-class.mjs.map +1 -1
  9. package/dist/core/class/root-observable-class.d.mts +1 -1
  10. package/dist/core/class/root-observable-class.d.mts.map +1 -1
  11. package/dist/core/class/root-observable-class.mjs +9 -9
  12. package/dist/core/class/root-observable-class.mjs.map +1 -1
  13. package/dist/core/combine/combine.d.mts +7 -7
  14. package/dist/core/combine/combine.mjs +13 -14
  15. package/dist/core/combine/combine.mjs.map +1 -1
  16. package/dist/core/combine/merge.d.mts +6 -6
  17. package/dist/core/combine/merge.mjs +9 -9
  18. package/dist/core/combine/merge.mjs.map +1 -1
  19. package/dist/core/combine/zip.d.mts +20 -19
  20. package/dist/core/combine/zip.d.mts.map +1 -1
  21. package/dist/core/combine/zip.mjs +22 -21
  22. package/dist/core/combine/zip.mjs.map +1 -1
  23. package/dist/core/create/{interval.d.mts → counter.d.mts} +14 -12
  24. package/dist/core/create/counter.d.mts.map +1 -0
  25. package/dist/core/create/{interval.mjs → counter.mjs} +21 -23
  26. package/dist/core/create/counter.mjs.map +1 -0
  27. package/dist/core/create/from-abortable-promise.d.mts +29 -0
  28. package/dist/core/create/from-abortable-promise.d.mts.map +1 -0
  29. package/dist/core/create/from-abortable-promise.mjs +70 -0
  30. package/dist/core/create/from-abortable-promise.mjs.map +1 -0
  31. package/dist/core/create/from-promise.d.mts +9 -6
  32. package/dist/core/create/from-promise.d.mts.map +1 -1
  33. package/dist/core/create/from-promise.mjs +8 -5
  34. package/dist/core/create/from-promise.mjs.map +1 -1
  35. package/dist/core/create/from-subscribable.d.mts +4 -4
  36. package/dist/core/create/from-subscribable.mjs +4 -4
  37. package/dist/core/create/index.d.mts +3 -3
  38. package/dist/core/create/index.d.mts.map +1 -1
  39. package/dist/core/create/index.mjs +4 -4
  40. package/dist/core/create/just.d.mts +32 -0
  41. package/dist/core/create/just.d.mts.map +1 -0
  42. package/dist/core/create/just.mjs +44 -0
  43. package/dist/core/create/just.mjs.map +1 -0
  44. package/dist/core/create/source.d.mts +7 -12
  45. package/dist/core/create/source.d.mts.map +1 -1
  46. package/dist/core/create/source.mjs +1 -6
  47. package/dist/core/create/source.mjs.map +1 -1
  48. package/dist/core/create/timer.d.mts +6 -4
  49. package/dist/core/create/timer.d.mts.map +1 -1
  50. package/dist/core/create/timer.mjs +6 -7
  51. package/dist/core/create/timer.mjs.map +1 -1
  52. package/dist/core/index.d.mts +1 -1
  53. package/dist/core/index.d.mts.map +1 -1
  54. package/dist/core/index.mjs +21 -14
  55. package/dist/core/index.mjs.map +1 -1
  56. package/dist/core/operators/audit.d.mts +97 -0
  57. package/dist/core/operators/audit.d.mts.map +1 -0
  58. package/dist/core/operators/audit.mjs +144 -0
  59. package/dist/core/operators/audit.mjs.map +1 -0
  60. package/dist/core/operators/debounce.d.mts +88 -0
  61. package/dist/core/operators/debounce.d.mts.map +1 -0
  62. package/dist/core/operators/debounce.mjs +130 -0
  63. package/dist/core/operators/debounce.mjs.map +1 -0
  64. package/dist/core/operators/filter.d.mts +5 -5
  65. package/dist/core/operators/filter.mjs +3 -3
  66. package/dist/core/operators/filter.mjs.map +1 -1
  67. package/dist/core/operators/index.d.mts +4 -4
  68. package/dist/core/operators/index.d.mts.map +1 -1
  69. package/dist/core/operators/index.mjs +6 -6
  70. package/dist/core/operators/map.d.mts +41 -0
  71. package/dist/core/operators/map.d.mts.map +1 -0
  72. package/dist/core/operators/map.mjs +71 -0
  73. package/dist/core/operators/map.mjs.map +1 -0
  74. package/dist/core/operators/merge-map.d.mts +57 -30
  75. package/dist/core/operators/merge-map.d.mts.map +1 -1
  76. package/dist/core/operators/merge-map.mjs +59 -32
  77. package/dist/core/operators/merge-map.mjs.map +1 -1
  78. package/dist/core/operators/pairwise.d.mts +6 -6
  79. package/dist/core/operators/pairwise.mjs +9 -9
  80. package/dist/core/operators/pairwise.mjs.map +1 -1
  81. package/dist/core/operators/scan.d.mts +6 -6
  82. package/dist/core/operators/scan.mjs +9 -9
  83. package/dist/core/operators/scan.mjs.map +1 -1
  84. package/dist/core/operators/skip-if-no-change.d.mts +21 -9
  85. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  86. package/dist/core/operators/skip-if-no-change.mjs +25 -13
  87. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  88. package/dist/core/operators/skip-until.d.mts +5 -5
  89. package/dist/core/operators/skip-until.mjs +8 -8
  90. package/dist/core/operators/skip-until.mjs.map +1 -1
  91. package/dist/core/operators/skip-while.d.mts +18 -9
  92. package/dist/core/operators/skip-while.d.mts.map +1 -1
  93. package/dist/core/operators/skip-while.mjs +28 -16
  94. package/dist/core/operators/skip-while.mjs.map +1 -1
  95. package/dist/core/operators/switch-map.d.mts +57 -26
  96. package/dist/core/operators/switch-map.d.mts.map +1 -1
  97. package/dist/core/operators/switch-map.mjs +59 -28
  98. package/dist/core/operators/switch-map.mjs.map +1 -1
  99. package/dist/core/operators/take-until.d.mts +5 -5
  100. package/dist/core/operators/take-until.mjs +8 -8
  101. package/dist/core/operators/take-until.mjs.map +1 -1
  102. package/dist/core/operators/take-while.d.mts +15 -8
  103. package/dist/core/operators/take-while.d.mts.map +1 -1
  104. package/dist/core/operators/take-while.mjs +19 -13
  105. package/dist/core/operators/take-while.mjs.map +1 -1
  106. package/dist/core/operators/throttle.d.mts +81 -0
  107. package/dist/core/operators/throttle.d.mts.map +1 -0
  108. package/dist/core/operators/throttle.mjs +126 -0
  109. package/dist/core/operators/throttle.mjs.map +1 -0
  110. package/dist/core/operators/with-buffered-from.d.mts +13 -9
  111. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  112. package/dist/core/operators/with-buffered-from.mjs +17 -13
  113. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  114. package/dist/core/operators/with-current-value-from.d.mts +14 -9
  115. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  116. package/dist/core/operators/with-current-value-from.mjs +18 -13
  117. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  118. package/dist/core/operators/with-initial-value.d.mts +5 -5
  119. package/dist/core/operators/with-initial-value.mjs +8 -8
  120. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  121. package/dist/core/predefined/index.d.mts +2 -0
  122. package/dist/core/predefined/index.d.mts.map +1 -0
  123. package/dist/core/predefined/index.mjs +12 -0
  124. package/dist/core/predefined/index.mjs.map +1 -0
  125. package/dist/core/predefined/operators/attach-index.d.mts +57 -0
  126. package/dist/core/predefined/operators/attach-index.d.mts.map +1 -0
  127. package/dist/core/predefined/operators/attach-index.mjs +62 -0
  128. package/dist/core/predefined/operators/attach-index.mjs.map +1 -0
  129. package/dist/core/predefined/operators/index.d.mts +12 -0
  130. package/dist/core/predefined/operators/index.d.mts.map +1 -0
  131. package/dist/core/predefined/operators/index.mjs +12 -0
  132. package/dist/core/predefined/operators/index.mjs.map +1 -0
  133. package/dist/core/predefined/operators/map-optional.d.mts +51 -0
  134. package/dist/core/predefined/operators/map-optional.d.mts.map +1 -0
  135. package/dist/core/predefined/operators/map-optional.mjs +55 -0
  136. package/dist/core/predefined/operators/map-optional.mjs.map +1 -0
  137. package/dist/core/predefined/operators/map-result-err.d.mts +51 -0
  138. package/dist/core/predefined/operators/map-result-err.d.mts.map +1 -0
  139. package/dist/core/predefined/operators/map-result-err.mjs +55 -0
  140. package/dist/core/predefined/operators/map-result-err.mjs.map +1 -0
  141. package/dist/core/predefined/operators/map-result-ok.d.mts +51 -0
  142. package/dist/core/predefined/operators/map-result-ok.d.mts.map +1 -0
  143. package/dist/core/predefined/operators/map-result-ok.mjs +55 -0
  144. package/dist/core/predefined/operators/map-result-ok.mjs.map +1 -0
  145. package/dist/core/predefined/operators/map-to.d.mts +43 -0
  146. package/dist/core/predefined/operators/map-to.d.mts.map +1 -0
  147. package/dist/core/predefined/operators/map-to.mjs +48 -0
  148. package/dist/core/predefined/operators/map-to.mjs.map +1 -0
  149. package/dist/core/predefined/operators/pluck.d.mts +47 -0
  150. package/dist/core/predefined/operators/pluck.d.mts.map +1 -0
  151. package/dist/core/predefined/operators/pluck.mjs +52 -0
  152. package/dist/core/predefined/operators/pluck.mjs.map +1 -0
  153. package/dist/core/predefined/operators/skip.d.mts +50 -0
  154. package/dist/core/predefined/operators/skip.d.mts.map +1 -0
  155. package/dist/core/predefined/operators/skip.mjs +56 -0
  156. package/dist/core/predefined/operators/skip.mjs.map +1 -0
  157. package/dist/core/predefined/operators/take.d.mts +44 -0
  158. package/dist/core/predefined/operators/take.d.mts.map +1 -0
  159. package/dist/core/predefined/operators/take.mjs +49 -0
  160. package/dist/core/predefined/operators/take.mjs.map +1 -0
  161. package/dist/core/predefined/operators/unwrap-optional.d.mts +44 -0
  162. package/dist/core/predefined/operators/unwrap-optional.d.mts.map +1 -0
  163. package/dist/core/predefined/operators/unwrap-optional.mjs +50 -0
  164. package/dist/core/predefined/operators/unwrap-optional.mjs.map +1 -0
  165. package/dist/core/predefined/operators/unwrap-result-err.d.mts +44 -0
  166. package/dist/core/predefined/operators/unwrap-result-err.d.mts.map +1 -0
  167. package/dist/core/predefined/operators/unwrap-result-err.mjs +48 -0
  168. package/dist/core/predefined/operators/unwrap-result-err.mjs.map +1 -0
  169. package/dist/core/predefined/operators/unwrap-result-ok.d.mts +44 -0
  170. package/dist/core/predefined/operators/unwrap-result-ok.d.mts.map +1 -0
  171. package/dist/core/predefined/operators/unwrap-result-ok.mjs +50 -0
  172. package/dist/core/predefined/operators/unwrap-result-ok.mjs.map +1 -0
  173. package/dist/core/types/id.d.mts +1 -1
  174. package/dist/core/types/id.d.mts.map +1 -1
  175. package/dist/core/types/index.d.mts +1 -0
  176. package/dist/core/types/index.d.mts.map +1 -1
  177. package/dist/core/types/observable-family.d.mts +8 -14
  178. package/dist/core/types/observable-family.d.mts.map +1 -1
  179. package/dist/core/types/observable.d.mts +3 -3
  180. package/dist/core/types/observable.d.mts.map +1 -1
  181. package/dist/core/types/timer.d.mts +2 -0
  182. package/dist/core/types/timer.d.mts.map +1 -0
  183. package/dist/core/types/timer.mjs +2 -0
  184. package/dist/core/types/timer.mjs.map +1 -0
  185. package/dist/core/utils/id-maker.d.mts +2 -2
  186. package/dist/core/utils/id-maker.d.mts.map +1 -1
  187. package/dist/core/utils/id-maker.mjs +3 -3
  188. package/dist/core/utils/id-maker.mjs.map +1 -1
  189. package/dist/core/utils/index.mjs +1 -1
  190. package/dist/entry-point.mjs +24 -15
  191. package/dist/entry-point.mjs.map +1 -1
  192. package/dist/globals.d.mts +0 -3
  193. package/dist/index.mjs +24 -15
  194. package/dist/index.mjs.map +1 -1
  195. package/dist/utils/collect-to-array.d.mts +3 -0
  196. package/dist/utils/collect-to-array.d.mts.map +1 -0
  197. package/dist/utils/collect-to-array.mjs +11 -0
  198. package/dist/utils/collect-to-array.mjs.map +1 -0
  199. package/dist/utils/create-boolean-state.d.mts +40 -0
  200. package/dist/utils/create-boolean-state.d.mts.map +1 -0
  201. package/dist/utils/create-boolean-state.mjs +53 -0
  202. package/dist/utils/create-boolean-state.mjs.map +1 -0
  203. package/dist/utils/create-event-emitter.d.mts +4 -4
  204. package/dist/utils/create-event-emitter.mjs +4 -4
  205. package/dist/utils/create-reducer.d.mts +10 -7
  206. package/dist/utils/create-reducer.d.mts.map +1 -1
  207. package/dist/utils/create-reducer.mjs +7 -7
  208. package/dist/utils/create-reducer.mjs.map +1 -1
  209. package/dist/utils/create-state.d.mts +8 -48
  210. package/dist/utils/create-state.d.mts.map +1 -1
  211. package/dist/utils/create-state.mjs +10 -60
  212. package/dist/utils/create-state.mjs.map +1 -1
  213. package/dist/utils/index.d.mts +2 -0
  214. package/dist/utils/index.d.mts.map +1 -1
  215. package/dist/utils/index.mjs +3 -1
  216. package/dist/utils/index.mjs.map +1 -1
  217. package/package.json +17 -11
  218. package/src/core/class/child-observable-class.mts +65 -9
  219. package/src/core/class/circular-dependency-comparison.test.mts +142 -0
  220. package/src/core/class/circular-dependency.test.mts +251 -0
  221. package/src/core/class/observable-base-class.mts +9 -9
  222. package/src/core/class/root-observable-class.mts +14 -10
  223. package/src/core/combine/combine.mts +15 -15
  224. package/src/core/combine/merge.mts +13 -14
  225. package/src/core/combine/zip.mts +26 -25
  226. package/src/core/create/{interval.mts → counter.mts} +32 -30
  227. package/src/core/create/from-abortable-promise.mts +83 -0
  228. package/src/core/create/from-promise.mts +10 -7
  229. package/src/core/create/from-subscribable.mts +4 -4
  230. package/src/core/create/index.mts +3 -3
  231. package/src/core/create/just.mts +43 -0
  232. package/src/core/create/source.mts +10 -14
  233. package/src/core/create/timer.mts +12 -11
  234. package/src/core/index.mts +1 -1
  235. package/src/core/operators/audit.mts +172 -0
  236. package/src/core/operators/debounce.mts +154 -0
  237. package/src/core/operators/filter.mts +9 -9
  238. package/src/core/operators/index.mts +4 -4
  239. package/src/core/operators/map.mts +124 -0
  240. package/src/core/operators/merge-map.mts +60 -33
  241. package/src/core/operators/pairwise.mts +10 -10
  242. package/src/core/operators/scan.mts +10 -10
  243. package/src/core/operators/skip-if-no-change.mts +26 -14
  244. package/src/core/operators/skip-until.mts +9 -9
  245. package/src/core/operators/skip-while.mts +30 -28
  246. package/src/core/operators/switch-map.mts +60 -29
  247. package/src/core/operators/take-until.mts +9 -9
  248. package/src/core/operators/take-while.mts +21 -19
  249. package/src/core/operators/{throttle-time.mts → throttle.mts} +58 -38
  250. package/src/core/operators/with-buffered-from.mts +18 -14
  251. package/src/core/operators/with-current-value-from.mts +19 -14
  252. package/src/core/operators/with-initial-value.mts +9 -9
  253. package/src/core/predefined/index.mts +1 -0
  254. package/src/core/predefined/operators/attach-index.mts +62 -0
  255. package/src/core/predefined/operators/index.mts +11 -0
  256. package/src/core/predefined/operators/map-optional.mts +55 -0
  257. package/src/core/predefined/operators/map-result-err.mts +55 -0
  258. package/src/core/predefined/operators/map-result-ok.mts +55 -0
  259. package/src/core/predefined/operators/map-to.mts +45 -0
  260. package/src/core/predefined/operators/pluck.mts +51 -0
  261. package/src/core/predefined/operators/skip.mts +57 -0
  262. package/src/core/predefined/operators/take.mts +47 -0
  263. package/src/core/predefined/operators/unwrap-optional.mts +49 -0
  264. package/src/core/predefined/operators/unwrap-result-err.mts +48 -0
  265. package/src/core/predefined/operators/unwrap-result-ok.mts +49 -0
  266. package/src/core/types/id.mts +1 -1
  267. package/src/core/types/index.mts +1 -0
  268. package/src/core/types/observable-family.mts +8 -24
  269. package/src/core/types/observable.mts +3 -3
  270. package/src/core/types/timer.mts +2 -0
  271. package/src/core/utils/id-maker.mts +4 -4
  272. package/src/globals.d.mts +0 -3
  273. package/src/utils/collect-to-array.mts +17 -0
  274. package/src/utils/create-boolean-state.mts +68 -0
  275. package/src/utils/create-event-emitter.mts +4 -4
  276. package/src/utils/create-reducer.mts +11 -8
  277. package/src/utils/create-state.mts +10 -75
  278. package/src/utils/index.mts +2 -0
  279. package/dist/core/create/from-array.d.mts +0 -39
  280. package/dist/core/create/from-array.d.mts.map +0 -1
  281. package/dist/core/create/from-array.mjs +0 -65
  282. package/dist/core/create/from-array.mjs.map +0 -1
  283. package/dist/core/create/interval.d.mts.map +0 -1
  284. package/dist/core/create/interval.mjs.map +0 -1
  285. package/dist/core/create/of.d.mts +0 -39
  286. package/dist/core/create/of.d.mts.map +0 -1
  287. package/dist/core/create/of.mjs +0 -63
  288. package/dist/core/create/of.mjs.map +0 -1
  289. package/dist/core/operators/audit-time.d.mts +0 -62
  290. package/dist/core/operators/audit-time.d.mts.map +0 -1
  291. package/dist/core/operators/audit-time.mjs +0 -109
  292. package/dist/core/operators/audit-time.mjs.map +0 -1
  293. package/dist/core/operators/debounce-time.d.mts +0 -51
  294. package/dist/core/operators/debounce-time.d.mts.map +0 -1
  295. package/dist/core/operators/debounce-time.mjs +0 -93
  296. package/dist/core/operators/debounce-time.mjs.map +0 -1
  297. package/dist/core/operators/map-with-index.d.mts +0 -54
  298. package/dist/core/operators/map-with-index.d.mts.map +0 -1
  299. package/dist/core/operators/map-with-index.mjs +0 -88
  300. package/dist/core/operators/map-with-index.mjs.map +0 -1
  301. package/dist/core/operators/throttle-time.d.mts +0 -62
  302. package/dist/core/operators/throttle-time.d.mts.map +0 -1
  303. package/dist/core/operators/throttle-time.mjs +0 -107
  304. package/dist/core/operators/throttle-time.mjs.map +0 -1
  305. package/src/core/create/from-array.mts +0 -76
  306. package/src/core/create/of.mts +0 -73
  307. package/src/core/operators/audit-time.mts +0 -136
  308. package/src/core/operators/debounce-time.mts +0 -116
  309. package/src/core/operators/map-with-index.mts +0 -183
@@ -0,0 +1,251 @@
1
+ /* eslint-disable functional/immutable-data */
2
+ /* eslint-disable no-new */
3
+ import { Optional } from 'ts-data-forge';
4
+ import { combine, merge } from '../combine/index.mjs';
5
+ import { source } from '../create/index.mjs';
6
+ import { map } from '../operators/index.mjs';
7
+ import {
8
+ AsyncChildObservableClass,
9
+ SyncChildObservableClass,
10
+ } from './child-observable-class.mjs';
11
+ import { RootObservableClass } from './root-observable-class.mjs';
12
+
13
+ describe('circular dependency detection', () => {
14
+ describe('cycle in ancestor graph', () => {
15
+ test('should throw when parent chain contains a cycle (A -> B -> A)', () => {
16
+ const root = new RootObservableClass({
17
+ initialValue: Optional.some(0),
18
+ });
19
+
20
+ const childA = new SyncChildObservableClass({
21
+ parents: [root],
22
+ initialValue: Optional.some(0),
23
+ });
24
+
25
+ const childB = new SyncChildObservableClass({
26
+ parents: [childA],
27
+ initialValue: Optional.some(0),
28
+ });
29
+
30
+ // Mutate childA.parents to create a cycle: childA -> childB -> childA
31
+ Object.defineProperty(childA, 'parents', {
32
+ value: [childB],
33
+ writable: false,
34
+ configurable: true,
35
+ });
36
+
37
+ expect(() => {
38
+ new SyncChildObservableClass({
39
+ parents: [childA],
40
+ initialValue: Optional.some(0),
41
+ });
42
+ }).toThrow(
43
+ 'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
44
+ );
45
+ });
46
+
47
+ test('should throw when AsyncChildObservable parent chain contains a cycle', () => {
48
+ const root = new RootObservableClass({
49
+ initialValue: Optional.some(0),
50
+ });
51
+
52
+ const childA = new AsyncChildObservableClass({
53
+ parents: [root],
54
+ initialValue: Optional.some(0),
55
+ });
56
+
57
+ const childB = new SyncChildObservableClass({
58
+ parents: [childA],
59
+ initialValue: Optional.some(0),
60
+ });
61
+
62
+ // Create cycle: childA -> childB -> childA
63
+ Object.defineProperty(childA, 'parents', {
64
+ value: [childB],
65
+ writable: false,
66
+ configurable: true,
67
+ });
68
+
69
+ expect(() => {
70
+ new AsyncChildObservableClass({
71
+ parents: [childA],
72
+ initialValue: Optional.some(0),
73
+ });
74
+ }).toThrow(
75
+ 'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
76
+ );
77
+ });
78
+
79
+ test('should throw when a cycle exists through a chain of 3 observables', () => {
80
+ const root = new RootObservableClass({
81
+ initialValue: Optional.some(0),
82
+ });
83
+
84
+ const childA = new SyncChildObservableClass({
85
+ parents: [root],
86
+ initialValue: Optional.some(0),
87
+ });
88
+
89
+ const childB = new SyncChildObservableClass({
90
+ parents: [childA],
91
+ initialValue: Optional.some(0),
92
+ });
93
+
94
+ const childC = new SyncChildObservableClass({
95
+ parents: [childB],
96
+ initialValue: Optional.some(0),
97
+ });
98
+
99
+ // Create cycle: childA -> childC -> childB -> childA
100
+ Object.defineProperty(childA, 'parents', {
101
+ value: [childC],
102
+ writable: false,
103
+ configurable: true,
104
+ });
105
+
106
+ expect(() => {
107
+ new SyncChildObservableClass({
108
+ parents: [childA],
109
+ initialValue: Optional.some(0),
110
+ });
111
+ }).toThrow(
112
+ 'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
113
+ );
114
+ });
115
+
116
+ test('should throw when the new child itself appears as an ancestor', () => {
117
+ const root = new RootObservableClass({
118
+ initialValue: Optional.some(0),
119
+ });
120
+
121
+ const childA = new SyncChildObservableClass({
122
+ parents: [root],
123
+ initialValue: Optional.some(0),
124
+ });
125
+
126
+ // Make childA's parents reference childA itself (self-loop)
127
+ Object.defineProperty(childA, 'parents', {
128
+ value: [childA],
129
+ writable: false,
130
+ configurable: true,
131
+ });
132
+
133
+ expect(() => {
134
+ new SyncChildObservableClass({
135
+ parents: [childA],
136
+ initialValue: Optional.some(0),
137
+ });
138
+ }).toThrow(
139
+ 'Circular dependency detected in observable graph: a child observable cannot be its own ancestor.',
140
+ );
141
+ });
142
+ });
143
+
144
+ describe('valid DAG patterns should not throw', () => {
145
+ test('diamond dependency is not a cycle', () => {
146
+ const root = new RootObservableClass({
147
+ initialValue: Optional.some(0),
148
+ });
149
+
150
+ const left = new SyncChildObservableClass({
151
+ parents: [root],
152
+ initialValue: Optional.some(0),
153
+ });
154
+
155
+ const right = new SyncChildObservableClass({
156
+ parents: [root],
157
+ initialValue: Optional.some(0),
158
+ });
159
+
160
+ // Diamond: root -> left -> combined, root -> right -> combined
161
+ expect(() => {
162
+ new SyncChildObservableClass({
163
+ parents: [left, right],
164
+ initialValue: Optional.some(0),
165
+ });
166
+ }).not.toThrow();
167
+ });
168
+
169
+ test('linear chain is not a cycle', () => {
170
+ const root = new RootObservableClass({
171
+ initialValue: Optional.some(0),
172
+ });
173
+
174
+ const a = new SyncChildObservableClass({
175
+ parents: [root],
176
+ initialValue: Optional.some(0),
177
+ });
178
+
179
+ const b = new SyncChildObservableClass({
180
+ parents: [a],
181
+ initialValue: Optional.some(0),
182
+ });
183
+
184
+ expect(() => {
185
+ new SyncChildObservableClass({
186
+ parents: [b],
187
+ initialValue: Optional.some(0),
188
+ });
189
+ }).not.toThrow();
190
+ });
191
+
192
+ test('multiple roots converging is not a cycle', () => {
193
+ const root1 = new RootObservableClass({
194
+ initialValue: Optional.some(1),
195
+ });
196
+
197
+ const root2 = new RootObservableClass({
198
+ initialValue: Optional.some(2),
199
+ });
200
+
201
+ const child1 = new SyncChildObservableClass({
202
+ parents: [root1],
203
+ initialValue: Optional.some(0),
204
+ });
205
+
206
+ const child2 = new SyncChildObservableClass({
207
+ parents: [root2],
208
+ initialValue: Optional.some(0),
209
+ });
210
+
211
+ expect(() => {
212
+ new SyncChildObservableClass({
213
+ parents: [child1, child2],
214
+ initialValue: Optional.some(0),
215
+ });
216
+ }).not.toThrow();
217
+ });
218
+
219
+ test('combine with source observables works', () => {
220
+ const a$ = source<number>();
221
+
222
+ const b$ = source<string>();
223
+
224
+ expect(() => {
225
+ combine([a$, b$]);
226
+ }).not.toThrow();
227
+ });
228
+
229
+ test('combine with derived observables works', () => {
230
+ const a$ = source<number>();
231
+
232
+ const b$ = source<number>();
233
+
234
+ const mapped$ = a$.pipe(map((x) => x * 2));
235
+
236
+ expect(() => {
237
+ combine([mapped$, b$]);
238
+ }).not.toThrow();
239
+ });
240
+
241
+ test('merge with source observables works', () => {
242
+ const a$ = source<number>();
243
+
244
+ const b$ = source<number>();
245
+
246
+ expect(() => {
247
+ merge([a$, b$]);
248
+ }).not.toThrow();
249
+ });
250
+ });
251
+ });
@@ -8,13 +8,13 @@ import {
8
8
  type Subscriber,
9
9
  type SubscriberId,
10
10
  type Subscription,
11
- type UpdaterSymbol,
11
+ type UpdateToken,
12
12
  type WithInitialValueOperator,
13
13
  } from '../types/index.mjs';
14
14
  import {
15
15
  issueObservableId,
16
16
  issueSubscriberId,
17
- issueUpdaterSymbol,
17
+ issueUpdateToken,
18
18
  toSubscriber,
19
19
  } from '../utils/index.mjs';
20
20
 
@@ -30,7 +30,7 @@ export class ObservableBaseClass<
30
30
  readonly #subscribers: MutableMap<SubscriberId, Subscriber<A>>;
31
31
  #mut_currentValue: ReturnType<ObservableBase<A>['getSnapshot']>;
32
32
  #mut_isCompleted: ObservableBase<A>['isCompleted'];
33
- #mut_updaterSymbol: ObservableBase<A>['updaterSymbol'];
33
+ #mut_updateToken: ObservableBase<A>['updateToken'];
34
34
 
35
35
  constructor({
36
36
  kind,
@@ -55,7 +55,7 @@ export class ObservableBaseClass<
55
55
 
56
56
  this.#mut_isCompleted = false;
57
57
 
58
- this.#mut_updaterSymbol = issueUpdaterSymbol();
58
+ this.#mut_updateToken = issueUpdateToken();
59
59
  }
60
60
 
61
61
  addChild<B>(child: ChildObservable<B>): void {
@@ -78,8 +78,8 @@ export class ObservableBaseClass<
78
78
  return this.#mut_isCompleted;
79
79
  }
80
80
 
81
- get updaterSymbol(): UpdaterSymbol {
82
- return this.#mut_updaterSymbol;
81
+ get updateToken(): UpdateToken {
82
+ return this.#mut_updateToken;
83
83
  }
84
84
 
85
85
  get hasSubscriber(): boolean {
@@ -94,8 +94,8 @@ export class ObservableBaseClass<
94
94
  return this.#mut_children.some((c) => !c.isCompleted);
95
95
  }
96
96
 
97
- protected setNext(nextValue: A, updaterSymbol: UpdaterSymbol): void {
98
- this.#mut_updaterSymbol = updaterSymbol;
97
+ protected setNext(nextValue: A, updateToken: UpdateToken): void {
98
+ this.#mut_updateToken = updateToken;
99
99
 
100
100
  this.#mut_currentValue = Optional.some(nextValue);
101
101
 
@@ -105,7 +105,7 @@ export class ObservableBaseClass<
105
105
  }
106
106
 
107
107
  // eslint-disable-next-line @typescript-eslint/class-methods-use-this
108
- tryUpdate(_updaterSymbol: UpdaterSymbol): void {
108
+ tryUpdate(_updateToken: UpdateToken): void {
109
109
  throw new Error('not implemented');
110
110
  }
111
111
 
@@ -5,20 +5,20 @@ import {
5
5
  type ObservableId,
6
6
  type RootObservable,
7
7
  } from '../types/index.mjs';
8
- import { binarySearch, issueUpdaterSymbol } from '../utils/index.mjs';
8
+ import { binarySearch, issueUpdateToken } from '../utils/index.mjs';
9
9
  import { ObservableBaseClass } from './observable-base-class.mjs';
10
10
 
11
11
  export class RootObservableClass<A>
12
12
  extends ObservableBaseClass<A, 'root', 0>
13
13
  implements RootObservable<A>
14
14
  {
15
- #mut_procedure: readonly ChildObservable<unknown>[];
15
+ #mut_propagationOrder: readonly ChildObservable<unknown>[];
16
16
  protected readonly _descendantsIdSet: MutableSet<ObservableId>;
17
17
 
18
18
  constructor({
19
19
  initialValue,
20
20
  }: Readonly<{
21
- initialValue: ReturnType<RootObservable<A>['getSnapshot']>;
21
+ initialValue: Optional<A>;
22
22
  }>) {
23
23
  super({
24
24
  kind: 'root',
@@ -26,7 +26,7 @@ export class RootObservableClass<A>
26
26
  initialValue,
27
27
  });
28
28
 
29
- this.#mut_procedure = [];
29
+ this.#mut_propagationOrder = [];
30
30
 
31
31
  this._descendantsIdSet = new Set<ObservableId>();
32
32
  }
@@ -37,20 +37,24 @@ export class RootObservableClass<A>
37
37
  this._descendantsIdSet.add(child.id);
38
38
 
39
39
  const insertPos = binarySearch(
40
- this.#mut_procedure.map((a) => a.depth),
40
+ this.#mut_propagationOrder.map((a) => a.depth),
41
41
  child.depth,
42
42
  );
43
43
 
44
- this.#mut_procedure = Arr.toInserted(this.#mut_procedure, insertPos, child);
44
+ this.#mut_propagationOrder = Arr.toInserted(
45
+ this.#mut_propagationOrder,
46
+ insertPos,
47
+ child,
48
+ );
45
49
  }
46
50
 
47
51
  startUpdate(nextValue: A): void {
48
- const updaterSymbol = issueUpdaterSymbol();
52
+ const updateToken = issueUpdateToken();
49
53
 
50
- this.setNext(nextValue, updaterSymbol);
54
+ this.setNext(nextValue, updateToken);
51
55
 
52
- for (const p of this.#mut_procedure) {
53
- p.tryUpdate(updaterSymbol);
56
+ for (const p of this.#mut_propagationOrder) {
57
+ p.tryUpdate(updateToken);
54
58
  }
55
59
  }
56
60
  }
@@ -1,6 +1,6 @@
1
1
  import { Arr, Optional, expectType } from 'ts-data-forge';
2
2
  import { SyncChildObservableClass } from '../class/index.mjs';
3
- import { fromArray, source } from '../create/index.mjs';
3
+ import { source } from '../create/index.mjs';
4
4
  import { withInitialValue } from '../operators/index.mjs';
5
5
  import {
6
6
  type CombineObservable,
@@ -10,7 +10,7 @@ import {
10
10
  type NonEmptyUnknownList,
11
11
  type Observable,
12
12
  type SyncChildObservable,
13
- type UpdaterSymbol,
13
+ type UpdateToken,
14
14
  type Wrap,
15
15
  } from '../types/index.mjs';
16
16
 
@@ -41,30 +41,30 @@ import {
41
41
  *
42
42
  * const user$ = combine([name$, age$]);
43
43
  *
44
- * const mut_history: (readonly [string, number])[] = [];
44
+ * const userHistory: (readonly [string, number])[] = [];
45
45
  *
46
46
  * user$.subscribe(([name_, age]) => {
47
- * mut_history.push([name_, age]);
47
+ * userHistory.push([name_, age]);
48
48
  * });
49
49
  *
50
50
  * name$.next('Alice'); // nothing logged (age$ hasn't emitted yet)
51
51
  *
52
- * assert.deepStrictEqual(mut_history, []);
52
+ * assert.deepStrictEqual(userHistory, []);
53
53
  *
54
54
  * age$.next(25); // logs: { name: 'Alice', age: 25 }
55
55
  *
56
- * assert.deepStrictEqual(mut_history, [['Alice', 25]]);
56
+ * assert.deepStrictEqual(userHistory, [['Alice', 25]]);
57
57
  *
58
58
  * name$.next('Bob'); // logs: { name: 'Bob', age: 25 }
59
59
  *
60
- * assert.deepStrictEqual(mut_history, [
60
+ * assert.deepStrictEqual(userHistory, [
61
61
  * ['Alice', 25],
62
62
  * ['Bob', 25],
63
63
  * ]);
64
64
  *
65
65
  * age$.next(30); // logs: { name: 'Bob', age: 30 }
66
66
  *
67
- * assert.deepStrictEqual(mut_history, [
67
+ * assert.deepStrictEqual(userHistory, [
68
68
  * ['Alice', 25],
69
69
  * ['Bob', 25],
70
70
  * ['Bob', 30],
@@ -80,10 +80,10 @@ export const combine = <const OS extends NonEmptyArray<Observable<unknown>>>(
80
80
  ) as unknown as CombineObservableRefined<OS>;
81
81
 
82
82
  /**
83
- * Alias for `combine()`.
83
+ * Alias for `combine`.
84
84
  * @see combine
85
85
  */
86
- export const combineLatest = combine; // alias
86
+ export const combineLatest = combine;
87
87
 
88
88
  class CombineObservableClass<const A extends NonEmptyUnknownList>
89
89
  extends SyncChildObservableClass<A, A>
@@ -103,8 +103,8 @@ class CombineObservableClass<const A extends NonEmptyUnknownList>
103
103
  });
104
104
  }
105
105
 
106
- override tryUpdate(updaterSymbol: UpdaterSymbol): void {
107
- if (this.parents.every((o) => o.updaterSymbol !== updaterSymbol)) return; // all parents are skipped
106
+ override tryUpdate(updateToken: UpdateToken): void {
107
+ if (this.parents.every((o) => o.updateToken !== updateToken)) return; // all parents are skipped
108
108
 
109
109
  const parentValues = this.parents.map((a) => a.getSnapshot());
110
110
 
@@ -113,7 +113,7 @@ class CombineObservableClass<const A extends NonEmptyUnknownList>
113
113
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
114
114
  Arr.map(parentValues, (a) => a.value) as A;
115
115
 
116
- this.setNext(nextValue, updaterSymbol);
116
+ this.setNext(nextValue, updateToken);
117
117
  }
118
118
  }
119
119
  }
@@ -154,9 +154,9 @@ class CombineObservableClass<const A extends NonEmptyUnknownList>
154
154
  expectType<typeof _d, InitializedObservable<readonly [1, 2]>>('<=');
155
155
  }
156
156
 
157
- const r1 = fromArray([1, 2, 3]);
157
+ const r1 = source(1);
158
158
 
159
- const r2 = fromArray(['a', 'b', 'c']);
159
+ const r2 = source('a');
160
160
 
161
161
  const _c = combine([r1, r2]);
162
162
 
@@ -1,13 +1,13 @@
1
1
  import { Optional, expectType } from 'ts-data-forge';
2
2
  import { SyncChildObservableClass } from '../class/index.mjs';
3
- import { fromArray } from '../create/index.mjs';
3
+ import { source } from '../create/index.mjs';
4
4
  import {
5
5
  type MergeObservable,
6
6
  type MergeObservableRefined,
7
7
  type NonEmptyUnknownList,
8
8
  type Observable,
9
9
  type SyncChildObservable,
10
- type UpdaterSymbol,
10
+ type UpdateToken,
11
11
  type Wrap,
12
12
  } from '../types/index.mjs';
13
13
 
@@ -23,7 +23,7 @@ import {
23
23
  * ```ts
24
24
  * // Timeline:
25
25
  * //
26
- * // clicks$ c1 c2 c3
26
+ * // clicks$ c1 c2 c3
27
27
  * // keys$ k1 k2 k3
28
28
  * // events$ c1 k1 c2 k2 c3 k3
29
29
  * //
@@ -38,25 +38,25 @@ import {
38
38
  *
39
39
  * const events$ = merge([clicks$, keys$]);
40
40
  *
41
- * const mut_history: string[] = [];
41
+ * const valueHistory: string[] = [];
42
42
  *
43
43
  * events$.subscribe((event_) => {
44
- * mut_history.push(event_);
44
+ * valueHistory.push(event_);
45
45
  * });
46
46
  *
47
47
  * clicks$.next('c1');
48
48
  *
49
- * assert.deepStrictEqual(mut_history, ['c1']);
49
+ * assert.deepStrictEqual(valueHistory, ['c1']);
50
50
  *
51
51
  * keys$.next('k1');
52
52
  *
53
- * assert.deepStrictEqual(mut_history, ['c1', 'k1']);
53
+ * assert.deepStrictEqual(valueHistory, ['c1', 'k1']);
54
54
  *
55
55
  * clicks$.next('c2');
56
56
  *
57
57
  * keys$.next('k2');
58
58
  *
59
- * assert.deepStrictEqual(mut_history, ['c1', 'k1', 'c2', 'k2']);
59
+ * assert.deepStrictEqual(valueHistory, ['c1', 'k1', 'c2', 'k2']);
60
60
  * ```
61
61
  *
62
62
  * @note To improve code readability, consider using `createState` instead of `merge`,
@@ -79,10 +79,9 @@ class MergeObservableClass<const P extends NonEmptyUnknownList>
79
79
  });
80
80
  }
81
81
 
82
- override tryUpdate(updaterSymbol: UpdaterSymbol): void {
82
+ override tryUpdate(updateToken: UpdateToken): void {
83
83
  const parentToUse = this.parents.find(
84
- (o) =>
85
- o.updaterSymbol === updaterSymbol && Optional.isSome(o.getSnapshot()),
84
+ (o) => o.updateToken === updateToken && Optional.isSome(o.getSnapshot()),
86
85
  );
87
86
 
88
87
  if (parentToUse === undefined) return;
@@ -91,7 +90,7 @@ class MergeObservableClass<const P extends NonEmptyUnknownList>
91
90
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
92
91
  Optional.unwrap(parentToUse.getSnapshot()) as ArrayElement<P>;
93
92
 
94
- this.setNext(nextValue, updaterSymbol);
93
+ this.setNext(nextValue, updateToken);
95
94
  }
96
95
  }
97
96
 
@@ -100,9 +99,9 @@ if (import.meta.vitest !== undefined) {
100
99
  expect(1).toBe(1); // dummy
101
100
  });
102
101
 
103
- const r1 = fromArray([1, 2, 3]);
102
+ const r1 = source(1);
104
103
 
105
- const r2 = fromArray(['a', 'b', 'c']);
104
+ const r2 = source('a');
106
105
 
107
106
  const _m = merge([r1, r2] as const);
108
107
 
@@ -1,6 +1,6 @@
1
1
  import { Arr, Optional, createQueue, expectType } from 'ts-data-forge';
2
2
  import { SyncChildObservableClass } from '../class/index.mjs';
3
- import { fromArray, source } from '../create/index.mjs';
3
+ import { source } from '../create/index.mjs';
4
4
  import { withInitialValue } from '../operators/index.mjs';
5
5
  import {
6
6
  type InitializedObservable,
@@ -9,7 +9,7 @@ import {
9
9
  type Observable,
10
10
  type SyncChildObservable,
11
11
  type TupleToQueueTuple,
12
- type UpdaterSymbol,
12
+ type UpdateToken,
13
13
  type Wrap,
14
14
  type ZipObservable,
15
15
  type ZipObservableRefined,
@@ -28,38 +28,39 @@ import {
28
28
  * ```ts
29
29
  * // Timeline:
30
30
  * //
31
- * // letters$ 'a' 'b' 'c'
31
+ * // letters$ 'A' 'B' 'C'
32
32
  * // numbers$ 1 2 3
33
- * // zipped$ ['a',1] ['b',2] ['c',3]
33
+ * // zipped$ ['A',1] ['B',2] ['C',3]
34
34
  * //
35
35
  * // Explanation:
36
36
  * // - zip pairs values by their index from multiple sources
37
37
  * // - Waits for all sources to emit at the same index
38
38
  * // - Completes when any source completes
39
39
  *
40
- * const letters$ = fromArray(['a', 'b', 'c']);
40
+ * const [letters$, setLetter] = createState<string>('A');
41
41
  *
42
- * const numbers$ = fromArray([1, 2, 3]);
42
+ * const [numbers$, setNumber] = createState<number>(1);
43
43
  *
44
44
  * const zipped$ = zip([letters$, numbers$]);
45
45
  *
46
- * const mut_history: (readonly [string, number])[] = [];
46
+ * const valueHistory: (readonly [string, number])[] = [];
47
47
  *
48
- * await new Promise<void>((resolve) => {
49
- * zipped$.subscribe(
50
- * ([letter, num]) => {
51
- * mut_history.push([letter, num]);
52
- * },
53
- * () => {
54
- * resolve();
55
- * },
56
- * );
48
+ * zipped$.subscribe(([letter, num]) => {
49
+ * valueHistory.push([letter, num]);
57
50
  * });
58
51
  *
59
- * assert.deepStrictEqual(mut_history, [
60
- * ['a', 1],
61
- * ['b', 2],
62
- * ['c', 3],
52
+ * for (const letter of ['B', 'C']) {
53
+ * setLetter(letter);
54
+ * }
55
+ *
56
+ * for (const num of [2, 3]) {
57
+ * setNumber(num);
58
+ * }
59
+ *
60
+ * assert.deepStrictEqual(valueHistory, [
61
+ * ['A', 1],
62
+ * ['B', 2],
63
+ * ['C', 3],
63
64
  * ]);
64
65
  * ```
65
66
  */
@@ -93,13 +94,13 @@ class ZipObservableClass<const A extends NonEmptyUnknownList>
93
94
  ) satisfies TupleToQueueTuple<A>;
94
95
  }
95
96
 
96
- override tryUpdate(updaterSymbol: UpdaterSymbol): void {
97
+ override tryUpdate(updateToken: UpdateToken): void {
97
98
  const queues = this.#queues;
98
99
 
99
100
  for (const [index, par] of this.parents.entries()) {
100
101
  const sn = par.getSnapshot();
101
102
 
102
- if (par.updaterSymbol === updaterSymbol && Optional.isSome(sn)) {
103
+ if (par.updateToken === updateToken && Optional.isSome(sn)) {
103
104
  queues[index]?.enqueue(sn.value);
104
105
  }
105
106
  }
@@ -109,7 +110,7 @@ class ZipObservableClass<const A extends NonEmptyUnknownList>
109
110
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
110
111
  Arr.map(queues, (q) => Optional.unwrap(q.dequeue())) as A;
111
112
 
112
- this.setNext(nextValue, updaterSymbol);
113
+ this.setNext(nextValue, updateToken);
113
114
  }
114
115
  }
115
116
  }
@@ -154,9 +155,9 @@ if (import.meta.vitest !== undefined) {
154
155
  expectType<typeof _d, InitializedObservable<readonly [1, 2]>>('<=');
155
156
  }
156
157
 
157
- const r1 = fromArray([1, 2, 3]);
158
+ const r1 = source(1);
158
159
 
159
- const r2 = fromArray(['a', 'b', 'c']);
160
+ const r2 = source('a');
160
161
 
161
162
  const _z = zip([r1, r2] as const);
162
163