synstate 0.1.2 → 1.0.1

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 (323) hide show
  1. package/README.md +200 -147
  2. package/assets/old/synstate-icon-square.png +0 -0
  3. package/assets/synstate-logo.png +0 -0
  4. package/dist/core/class/child-observable-class.d.mts +2 -0
  5. package/dist/core/class/child-observable-class.d.mts.map +1 -1
  6. package/dist/core/class/child-observable-class.mjs +44 -13
  7. package/dist/core/class/child-observable-class.mjs.map +1 -1
  8. package/dist/core/class/observable-base-class.d.mts +4 -4
  9. package/dist/core/class/observable-base-class.d.mts.map +1 -1
  10. package/dist/core/class/observable-base-class.mjs +8 -8
  11. package/dist/core/class/observable-base-class.mjs.map +1 -1
  12. package/dist/core/class/root-observable-class.d.mts +3 -1
  13. package/dist/core/class/root-observable-class.d.mts.map +1 -1
  14. package/dist/core/class/root-observable-class.mjs +9 -9
  15. package/dist/core/class/root-observable-class.mjs.map +1 -1
  16. package/dist/core/combine/combine.d.mts +7 -6
  17. package/dist/core/combine/combine.d.mts.map +1 -1
  18. package/dist/core/combine/combine.mjs +11 -12
  19. package/dist/core/combine/combine.mjs.map +1 -1
  20. package/dist/core/combine/merge.d.mts +7 -6
  21. package/dist/core/combine/merge.d.mts.map +1 -1
  22. package/dist/core/combine/merge.mjs +9 -9
  23. package/dist/core/combine/merge.mjs.map +1 -1
  24. package/dist/core/combine/zip.d.mts +21 -19
  25. package/dist/core/combine/zip.d.mts.map +1 -1
  26. package/dist/core/combine/zip.mjs +22 -21
  27. package/dist/core/combine/zip.mjs.map +1 -1
  28. package/dist/core/create/{interval.d.mts → counter.d.mts} +14 -12
  29. package/dist/core/create/counter.d.mts.map +1 -0
  30. package/dist/core/create/{interval.mjs → counter.mjs} +21 -23
  31. package/dist/core/create/counter.mjs.map +1 -0
  32. package/dist/core/create/from-abortable-promise.d.mts +29 -0
  33. package/dist/core/create/from-abortable-promise.d.mts.map +1 -0
  34. package/dist/core/create/from-abortable-promise.mjs +70 -0
  35. package/dist/core/create/from-abortable-promise.mjs.map +1 -0
  36. package/dist/core/create/from-promise.d.mts +9 -6
  37. package/dist/core/create/from-promise.d.mts.map +1 -1
  38. package/dist/core/create/from-promise.mjs +8 -5
  39. package/dist/core/create/from-promise.mjs.map +1 -1
  40. package/dist/core/create/from-subscribable.d.mts +4 -4
  41. package/dist/core/create/from-subscribable.mjs +4 -4
  42. package/dist/core/create/index.d.mts +3 -3
  43. package/dist/core/create/index.d.mts.map +1 -1
  44. package/dist/core/create/index.mjs +4 -4
  45. package/dist/core/create/just.d.mts +32 -0
  46. package/dist/core/create/just.d.mts.map +1 -0
  47. package/dist/core/create/just.mjs +44 -0
  48. package/dist/core/create/just.mjs.map +1 -0
  49. package/dist/core/create/source.d.mts +7 -12
  50. package/dist/core/create/source.d.mts.map +1 -1
  51. package/dist/core/create/source.mjs +1 -6
  52. package/dist/core/create/source.mjs.map +1 -1
  53. package/dist/core/create/timer.d.mts +6 -4
  54. package/dist/core/create/timer.d.mts.map +1 -1
  55. package/dist/core/create/timer.mjs +6 -7
  56. package/dist/core/create/timer.mjs.map +1 -1
  57. package/dist/core/index.d.mts +0 -1
  58. package/dist/core/index.d.mts.map +1 -1
  59. package/dist/core/index.mjs +8 -13
  60. package/dist/core/index.mjs.map +1 -1
  61. package/dist/core/operators/audit.d.mts +97 -0
  62. package/dist/core/operators/audit.d.mts.map +1 -0
  63. package/dist/core/operators/audit.mjs +144 -0
  64. package/dist/core/operators/audit.mjs.map +1 -0
  65. package/dist/core/operators/debounce.d.mts +88 -0
  66. package/dist/core/operators/debounce.d.mts.map +1 -0
  67. package/dist/core/operators/debounce.mjs +130 -0
  68. package/dist/core/operators/debounce.mjs.map +1 -0
  69. package/dist/core/operators/filter.d.mts +6 -5
  70. package/dist/core/operators/filter.d.mts.map +1 -1
  71. package/dist/core/operators/filter.mjs +3 -3
  72. package/dist/core/operators/filter.mjs.map +1 -1
  73. package/dist/core/operators/index.d.mts +4 -4
  74. package/dist/core/operators/index.d.mts.map +1 -1
  75. package/dist/core/operators/index.mjs +4 -4
  76. package/dist/core/operators/{map-with-index.d.mts → map.d.mts} +12 -11
  77. package/dist/core/operators/map.d.mts.map +1 -0
  78. package/dist/core/operators/{map-with-index.mjs → map.mjs} +17 -17
  79. package/dist/core/operators/map.mjs.map +1 -0
  80. package/dist/core/operators/merge-map.d.mts +56 -29
  81. package/dist/core/operators/merge-map.d.mts.map +1 -1
  82. package/dist/core/operators/merge-map.mjs +58 -31
  83. package/dist/core/operators/merge-map.mjs.map +1 -1
  84. package/dist/core/operators/pairwise.d.mts +6 -6
  85. package/dist/core/operators/pairwise.mjs +9 -9
  86. package/dist/core/operators/pairwise.mjs.map +1 -1
  87. package/dist/core/operators/scan.d.mts +6 -6
  88. package/dist/core/operators/scan.mjs +9 -9
  89. package/dist/core/operators/scan.mjs.map +1 -1
  90. package/dist/core/operators/skip-if-no-change.d.mts +20 -8
  91. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  92. package/dist/core/operators/skip-if-no-change.mjs +23 -11
  93. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  94. package/dist/core/operators/skip-until.d.mts +5 -5
  95. package/dist/core/operators/skip-until.mjs +8 -8
  96. package/dist/core/operators/skip-until.mjs.map +1 -1
  97. package/dist/core/operators/skip-while.d.mts +19 -8
  98. package/dist/core/operators/skip-while.d.mts.map +1 -1
  99. package/dist/core/operators/skip-while.mjs +26 -11
  100. package/dist/core/operators/skip-while.mjs.map +1 -1
  101. package/dist/core/operators/switch-map.d.mts +57 -26
  102. package/dist/core/operators/switch-map.d.mts.map +1 -1
  103. package/dist/core/operators/switch-map.mjs +59 -28
  104. package/dist/core/operators/switch-map.mjs.map +1 -1
  105. package/dist/core/operators/take-until.d.mts +5 -5
  106. package/dist/core/operators/take-until.mjs +8 -8
  107. package/dist/core/operators/take-until.mjs.map +1 -1
  108. package/dist/core/operators/take-while.d.mts +16 -7
  109. package/dist/core/operators/take-while.d.mts.map +1 -1
  110. package/dist/core/operators/take-while.mjs +18 -10
  111. package/dist/core/operators/take-while.mjs.map +1 -1
  112. package/dist/core/operators/throttle.d.mts +81 -0
  113. package/dist/core/operators/throttle.d.mts.map +1 -0
  114. package/dist/core/operators/throttle.mjs +126 -0
  115. package/dist/core/operators/throttle.mjs.map +1 -0
  116. package/dist/core/operators/with-buffered-from.d.mts +9 -9
  117. package/dist/core/operators/with-buffered-from.mjs +12 -12
  118. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  119. package/dist/core/operators/with-current-value-from.d.mts +10 -9
  120. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  121. package/dist/core/operators/with-current-value-from.mjs +13 -12
  122. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  123. package/dist/core/operators/with-initial-value.d.mts +5 -5
  124. package/dist/core/operators/with-initial-value.mjs +8 -8
  125. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  126. package/dist/core/predefined/index.mjs +0 -1
  127. package/dist/core/predefined/index.mjs.map +1 -1
  128. package/dist/core/predefined/operators/attach-index.d.mts +50 -0
  129. package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
  130. package/dist/core/predefined/operators/attach-index.mjs +51 -2
  131. package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
  132. package/dist/core/predefined/operators/index.d.mts +0 -1
  133. package/dist/core/predefined/operators/index.d.mts.map +1 -1
  134. package/dist/core/predefined/operators/index.mjs +0 -1
  135. package/dist/core/predefined/operators/index.mjs.map +1 -1
  136. package/dist/core/predefined/operators/map-optional.d.mts +48 -1
  137. package/dist/core/predefined/operators/map-optional.d.mts.map +1 -1
  138. package/dist/core/predefined/operators/map-optional.mjs +49 -1
  139. package/dist/core/predefined/operators/map-optional.mjs.map +1 -1
  140. package/dist/core/predefined/operators/map-result-err.d.mts +48 -1
  141. package/dist/core/predefined/operators/map-result-err.d.mts.map +1 -1
  142. package/dist/core/predefined/operators/map-result-err.mjs +49 -1
  143. package/dist/core/predefined/operators/map-result-err.mjs.map +1 -1
  144. package/dist/core/predefined/operators/map-result-ok.d.mts +48 -1
  145. package/dist/core/predefined/operators/map-result-ok.d.mts.map +1 -1
  146. package/dist/core/predefined/operators/map-result-ok.mjs +49 -1
  147. package/dist/core/predefined/operators/map-result-ok.mjs.map +1 -1
  148. package/dist/core/predefined/operators/map-to.d.mts +40 -0
  149. package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
  150. package/dist/core/predefined/operators/map-to.mjs +43 -1
  151. package/dist/core/predefined/operators/map-to.mjs.map +1 -1
  152. package/dist/core/predefined/operators/pluck.d.mts +39 -0
  153. package/dist/core/predefined/operators/pluck.d.mts.map +1 -1
  154. package/dist/core/predefined/operators/pluck.mjs +42 -1
  155. package/dist/core/predefined/operators/pluck.mjs.map +1 -1
  156. package/dist/core/predefined/operators/skip.d.mts +48 -0
  157. package/dist/core/predefined/operators/skip.d.mts.map +1 -1
  158. package/dist/core/predefined/operators/skip.mjs +47 -0
  159. package/dist/core/predefined/operators/skip.mjs.map +1 -1
  160. package/dist/core/predefined/operators/take.d.mts +42 -0
  161. package/dist/core/predefined/operators/take.d.mts.map +1 -1
  162. package/dist/core/predefined/operators/take.mjs +41 -0
  163. package/dist/core/predefined/operators/take.mjs.map +1 -1
  164. package/dist/core/predefined/operators/unwrap-optional.d.mts +41 -1
  165. package/dist/core/predefined/operators/unwrap-optional.d.mts.map +1 -1
  166. package/dist/core/predefined/operators/unwrap-optional.mjs +42 -1
  167. package/dist/core/predefined/operators/unwrap-optional.mjs.map +1 -1
  168. package/dist/core/predefined/operators/unwrap-result-err.d.mts +41 -1
  169. package/dist/core/predefined/operators/unwrap-result-err.d.mts.map +1 -1
  170. package/dist/core/predefined/operators/unwrap-result-err.mjs +42 -1
  171. package/dist/core/predefined/operators/unwrap-result-err.mjs.map +1 -1
  172. package/dist/core/predefined/operators/unwrap-result-ok.d.mts +41 -1
  173. package/dist/core/predefined/operators/unwrap-result-ok.d.mts.map +1 -1
  174. package/dist/core/predefined/operators/unwrap-result-ok.mjs +42 -1
  175. package/dist/core/predefined/operators/unwrap-result-ok.mjs.map +1 -1
  176. package/dist/core/types/id.d.mts +2 -1
  177. package/dist/core/types/id.d.mts.map +1 -1
  178. package/dist/core/types/index.d.mts +1 -0
  179. package/dist/core/types/index.d.mts.map +1 -1
  180. package/dist/core/types/observable-family.d.mts +10 -14
  181. package/dist/core/types/observable-family.d.mts.map +1 -1
  182. package/dist/core/types/observable-kind.d.mts +1 -0
  183. package/dist/core/types/observable-kind.d.mts.map +1 -1
  184. package/dist/core/types/observable.d.mts +5 -3
  185. package/dist/core/types/observable.d.mts.map +1 -1
  186. package/dist/core/types/observable.mjs.map +1 -1
  187. package/dist/core/types/timer.d.mts +2 -0
  188. package/dist/core/types/timer.d.mts.map +1 -0
  189. package/dist/core/types/timer.mjs +2 -0
  190. package/dist/core/types/timer.mjs.map +1 -0
  191. package/dist/core/utils/id-maker.d.mts +2 -2
  192. package/dist/core/utils/id-maker.d.mts.map +1 -1
  193. package/dist/core/utils/id-maker.mjs +3 -3
  194. package/dist/core/utils/id-maker.mjs.map +1 -1
  195. package/dist/core/utils/index.mjs +1 -1
  196. package/dist/core/utils/utils.d.mts +2 -0
  197. package/dist/core/utils/utils.d.mts.map +1 -1
  198. package/dist/core/utils/utils.mjs.map +1 -1
  199. package/dist/entry-point.mjs +11 -14
  200. package/dist/entry-point.mjs.map +1 -1
  201. package/dist/index.mjs +11 -14
  202. package/dist/index.mjs.map +1 -1
  203. package/dist/types.d.mts +1 -2
  204. package/dist/utils/collect-to-array.d.mts +3 -0
  205. package/dist/utils/collect-to-array.d.mts.map +1 -0
  206. package/dist/utils/collect-to-array.mjs +11 -0
  207. package/dist/utils/collect-to-array.mjs.map +1 -0
  208. package/dist/utils/create-boolean-state.d.mts +40 -0
  209. package/dist/utils/create-boolean-state.d.mts.map +1 -0
  210. package/dist/utils/create-boolean-state.mjs +53 -0
  211. package/dist/utils/create-boolean-state.mjs.map +1 -0
  212. package/dist/utils/create-event-emitter.d.mts +4 -4
  213. package/dist/utils/create-event-emitter.mjs +4 -4
  214. package/dist/utils/create-reducer.d.mts +11 -7
  215. package/dist/utils/create-reducer.d.mts.map +1 -1
  216. package/dist/utils/create-reducer.mjs +7 -7
  217. package/dist/utils/create-reducer.mjs.map +1 -1
  218. package/dist/utils/create-state.d.mts +8 -48
  219. package/dist/utils/create-state.d.mts.map +1 -1
  220. package/dist/utils/create-state.mjs +10 -60
  221. package/dist/utils/create-state.mjs.map +1 -1
  222. package/dist/utils/index.d.mts +2 -0
  223. package/dist/utils/index.d.mts.map +1 -1
  224. package/dist/utils/index.mjs +3 -1
  225. package/dist/utils/index.mjs.map +1 -1
  226. package/package.json +21 -14
  227. package/src/core/class/child-observable-class.mts +68 -14
  228. package/src/core/class/circular-dependency-comparison.test.mts +142 -0
  229. package/src/core/class/circular-dependency.test.mts +251 -0
  230. package/src/core/class/observable-base-class.mts +10 -9
  231. package/src/core/class/root-observable-class.mts +15 -10
  232. package/src/core/combine/combine.mts +14 -13
  233. package/src/core/combine/merge.mts +14 -14
  234. package/src/core/combine/zip.mts +27 -25
  235. package/src/core/create/{interval.mts → counter.mts} +32 -30
  236. package/src/core/create/from-abortable-promise.mts +83 -0
  237. package/src/core/create/from-promise.mts +10 -7
  238. package/src/core/create/from-subscribable.mts +4 -4
  239. package/src/core/create/index.mts +3 -3
  240. package/src/core/create/just.mts +43 -0
  241. package/src/core/create/source.mts +10 -14
  242. package/src/core/create/timer.mts +12 -11
  243. package/src/core/index.mts +0 -1
  244. package/src/core/operators/audit.mts +172 -0
  245. package/src/core/operators/debounce.mts +154 -0
  246. package/src/core/operators/filter.mts +9 -9
  247. package/src/core/operators/index.mts +4 -4
  248. package/src/core/operators/{map-with-index.mts → map.mts} +20 -20
  249. package/src/core/operators/merge-map.mts +59 -32
  250. package/src/core/operators/pairwise.mts +10 -10
  251. package/src/core/operators/scan.mts +10 -10
  252. package/src/core/operators/skip-if-no-change.mts +24 -12
  253. package/src/core/operators/skip-until.mts +9 -9
  254. package/src/core/operators/skip-while.mts +29 -12
  255. package/src/core/operators/switch-map.mts +60 -29
  256. package/src/core/operators/take-until.mts +9 -9
  257. package/src/core/operators/take-while.mts +19 -11
  258. package/src/core/operators/{throttle-time.mts → throttle.mts} +58 -38
  259. package/src/core/operators/with-buffered-from.mts +13 -13
  260. package/src/core/operators/with-current-value-from.mts +14 -13
  261. package/src/core/operators/with-initial-value.mts +9 -9
  262. package/src/core/predefined/operators/attach-index.mts +52 -2
  263. package/src/core/predefined/operators/index.mts +0 -1
  264. package/src/core/predefined/operators/map-optional.mts +49 -2
  265. package/src/core/predefined/operators/map-result-err.mts +49 -2
  266. package/src/core/predefined/operators/map-result-ok.mts +49 -2
  267. package/src/core/predefined/operators/map-to.mts +41 -1
  268. package/src/core/predefined/operators/pluck.mts +40 -1
  269. package/src/core/predefined/operators/skip.mts +48 -0
  270. package/src/core/predefined/operators/take.mts +42 -0
  271. package/src/core/predefined/operators/unwrap-optional.mts +43 -2
  272. package/src/core/predefined/operators/unwrap-result-err.mts +43 -2
  273. package/src/core/predefined/operators/unwrap-result-ok.mts +43 -2
  274. package/src/core/types/id.mts +3 -1
  275. package/src/core/types/index.mts +1 -0
  276. package/src/core/types/observable-family.mts +13 -24
  277. package/src/core/types/observable-kind.mts +2 -0
  278. package/src/core/types/observable.mts +5 -4
  279. package/src/core/types/timer.mts +2 -0
  280. package/src/core/utils/id-maker.mts +4 -4
  281. package/src/core/utils/utils.mts +1 -0
  282. package/src/utils/collect-to-array.mts +17 -0
  283. package/src/utils/create-boolean-state.mts +68 -0
  284. package/src/utils/create-event-emitter.mts +4 -4
  285. package/src/utils/create-reducer.mts +12 -8
  286. package/src/utils/create-state.mts +10 -75
  287. package/src/utils/index.mts +2 -0
  288. package/dist/core/create/from-array.d.mts +0 -39
  289. package/dist/core/create/from-array.d.mts.map +0 -1
  290. package/dist/core/create/from-array.mjs +0 -65
  291. package/dist/core/create/from-array.mjs.map +0 -1
  292. package/dist/core/create/interval.d.mts.map +0 -1
  293. package/dist/core/create/interval.mjs.map +0 -1
  294. package/dist/core/create/of.d.mts +0 -39
  295. package/dist/core/create/of.d.mts.map +0 -1
  296. package/dist/core/create/of.mjs +0 -63
  297. package/dist/core/create/of.mjs.map +0 -1
  298. package/dist/core/operators/audit-time.d.mts +0 -62
  299. package/dist/core/operators/audit-time.d.mts.map +0 -1
  300. package/dist/core/operators/audit-time.mjs +0 -109
  301. package/dist/core/operators/audit-time.mjs.map +0 -1
  302. package/dist/core/operators/debounce-time.d.mts +0 -51
  303. package/dist/core/operators/debounce-time.d.mts.map +0 -1
  304. package/dist/core/operators/debounce-time.mjs +0 -93
  305. package/dist/core/operators/debounce-time.mjs.map +0 -1
  306. package/dist/core/operators/map-with-index.d.mts.map +0 -1
  307. package/dist/core/operators/map-with-index.mjs.map +0 -1
  308. package/dist/core/operators/throttle-time.d.mts +0 -62
  309. package/dist/core/operators/throttle-time.d.mts.map +0 -1
  310. package/dist/core/operators/throttle-time.mjs +0 -107
  311. package/dist/core/operators/throttle-time.mjs.map +0 -1
  312. package/dist/core/predefined/operators/map.d.mts +0 -3
  313. package/dist/core/predefined/operators/map.d.mts.map +0 -1
  314. package/dist/core/predefined/operators/map.mjs +0 -8
  315. package/dist/core/predefined/operators/map.mjs.map +0 -1
  316. package/dist/globals.d.mts +0 -4
  317. package/src/core/create/from-array.mts +0 -76
  318. package/src/core/create/of.mts +0 -73
  319. package/src/core/operators/audit-time.mts +0 -136
  320. package/src/core/operators/debounce-time.mts +0 -116
  321. package/src/core/predefined/operators/map.mts +0 -5
  322. package/src/globals.d.mts +0 -4
  323. /package/assets/{synstate-icon.png → old/synstate-icon.png} +0 -0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # SynState
2
2
 
3
3
  <p align="center">
4
- <img src="./assets/synstate-icon.png" alt="SynState Logo" width="400" />
4
+ <img src="./assets/synstate-logo.png" alt="SynState Logo" width="400" />
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -9,26 +9,30 @@
9
9
  [![npm version](https://img.shields.io/npm/v/synstate.svg)](https://www.npmjs.com/package/synstate)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/synstate.svg)](https://www.npmjs.com/package/synstate)
11
11
  [![License](https://img.shields.io/npm/l/synstate.svg)](./LICENSE)
12
- [![codecov](https://codecov.io/gh/noshiro-pf/synstate/graph/badge.svg?token=xrJgTVxMpr)](https://codecov.io/gh/noshiro-pf/synstate)
12
+ [![codecov](https://codecov.io/gh/noshiro-pf/synstate/graph/badge.svg)](https://codecov.io/gh/noshiro-pf/synstate)
13
13
 
14
14
  </p>
15
15
 
16
16
  **SynState** is a lightweight, high-performance, type-safe state management library for TypeScript/JavaScript applications. Perfect for building reactive global state and event-driven systems in React, Vue, and other frameworks.
17
17
 
18
+ "SynState" is named after "Synchronized + State." It represents a sound synchronized state through a **glitch-free**[^1] Observable implementation.
19
+
20
+ [^1]: See ["How SynState solved the glitch?"](./documents/how-synstate-solved-the-glitch.md).
21
+
18
22
  ## Features
19
23
 
20
- - 🎯 **Simple State Management**: Easy-to-use `createState` and `createReducer` for global state
21
- - 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
22
- - 🔄 **Reactive Updates**: Automatic propagation of state changes to all subscribers
23
- - 🎨 **Type-Safe**: Full TypeScript support with precise type inference
24
- - 🚀 **Lightweight**: Minimal bundle size with only one external runtime dependency ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge))
24
+ - 🎯 **Simple State Management**: Easy-to-use `createState` and `createReducer` similar to React useState/useReducer for global state
25
25
  - ⚡ **High Performance**: Optimized for fast state updates and minimal re-renders
26
+ - 🎨 **Type-Safe**: Full TypeScript support with precise type inference
27
+ - 🚀 **Lightweight**: <!-- bundle-size:synstate -->~4.5 kB min+gzip<!-- /bundle-size:synstate --> with only one external runtime dependency ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge))
26
28
  - 🌐 **Framework Agnostic**: Works with React, Vue, Svelte, or vanilla JavaScript
27
- - 🔧 **Observable-based**: Built on Observable pattern similar to RxJS, but with a completely independent implementation from scratch — not a wrapper. Offers optional advanced features like operators (`map`, `filter`, `scan`, `debounceTime`) and combinators (`merge`, `combine`)
29
+ - 🔄 **Reactive Updates**: Automatic propagation of state changes to all subscribers
30
+ - 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
31
+ - 🔧 **Observable-based**: Built on Observable pattern similar to RxJS, but with a completely independent implementation from scratch — not a wrapper. Offers optional advanced features like operators (`map`, `filter`, `scan`, `debounce`) and combinators (`merge`, `combine`)
28
32
 
29
33
  ## Documentation
30
34
 
31
- - API reference: TBD <!-- <https://noshiro-pf.github.io/synstate/> -->
35
+ - <https://noshiro-pf.github.io/synstate/>
32
36
 
33
37
  ## Installation
34
38
 
@@ -52,63 +56,46 @@ pnpm add synstate
52
56
 
53
57
  ```tsx
54
58
  // Create a reactive state
55
- const [state, setState, { updateState, resetState, getSnapshot }] =
56
- createState(0);
59
+ const [state, setState] = createState(0);
60
+ // type of state: InitializedObservable<number>
61
+ // type of setState: (v: number) => number
57
62
 
58
- const mut_history: number[] = [];
63
+ const stateHistory: number[] = [];
59
64
 
60
- // Subscribe to changes (in React components, Vue watchers, etc.)
65
+ // Subscribe to changes
61
66
  state.subscribe((count) => {
62
- mut_history.push(count);
67
+ stateHistory.push(count);
63
68
  });
64
69
 
65
- assert.deepStrictEqual(mut_history, [0]);
70
+ assert.deepStrictEqual(stateHistory, [0]);
66
71
 
67
72
  // Update state
68
73
  setState(1);
69
74
 
70
- assert.deepStrictEqual(mut_history, [0, 1]);
71
-
72
- updateState((prev) => prev + 2);
73
-
74
- assert.deepStrictEqual(mut_history, [0, 1, 3]);
75
-
76
- assert.isTrue(getSnapshot() === 3);
77
-
78
- resetState();
79
-
80
- assert.isTrue(getSnapshot() === 0);
75
+ assert.deepStrictEqual(stateHistory, [0, 1]);
81
76
  ```
82
77
 
83
78
  ### With React
84
79
 
80
+ ```bash
81
+ npm add synstate-react-hooks
82
+ ```
83
+
85
84
  ```tsx
86
- import * as React from 'react';
87
- import { createState } from 'synstate';
85
+ import type * as React from 'react';
86
+ import { createState } from 'synstate-react-hooks';
88
87
 
89
- // Global state (outside component)
90
- const [userState, setUserState, { getSnapshot }] = createState({
88
+ const [useUserState, setUserState] = createState({
91
89
  name: '',
92
90
  email: '',
93
91
  });
94
92
 
95
93
  const UserProfile = (): React.JSX.Element => {
96
- const [user, setUser] = React.useState(getSnapshot());
97
-
98
- React.useEffect(() => {
99
- const subscription = userState.subscribe(setUser);
100
-
101
- return () => {
102
- subscription.unsubscribe();
103
- };
104
- }, []);
94
+ const user = useUserState();
105
95
 
106
96
  return (
107
97
  <div>
108
- <p>
109
- {'Name: '}
110
- {user.name}
111
- </p>
98
+ <p>{`Name: ${user.name}`}</p>
112
99
  <button
113
100
  onClick={() => {
114
101
  setUserState({
@@ -124,7 +111,7 @@ const UserProfile = (): React.JSX.Element => {
124
111
  };
125
112
  ```
126
113
 
127
- If you're using React v18 or later:
114
+ This is equivalent to the following code without synstate-react-hook:
128
115
 
129
116
  ```tsx
130
117
  import * as React from 'react';
@@ -147,10 +134,7 @@ const UserProfile = (): React.JSX.Element => {
147
134
 
148
135
  return (
149
136
  <div>
150
- <p>
151
- {'Name: '}
152
- {user.name}
153
- </p>
137
+ <p>{`Name: ${user.name}`}</p>
154
138
  <button
155
139
  onClick={() => {
156
140
  setUserState({
@@ -166,30 +150,34 @@ const UserProfile = (): React.JSX.Element => {
166
150
  };
167
151
  ```
168
152
 
169
- You can write the equivalent code more concisely using synstate-react-hooks:
153
+ See also the [synstate-react-hooks README](../synstate-react-hooks/README.md).
170
154
 
171
- ```bash
172
- npm add synstate-react-hooks
173
- ```
155
+ If you're using React v17 or earlier:
174
156
 
175
157
  ```tsx
176
- import type * as React from 'react';
177
- import { createState } from 'synstate-react-hooks';
158
+ import * as React from 'react';
159
+ import { createState } from 'synstate';
178
160
 
179
- const [useUserState, setUserState] = createState({
161
+ // Global state (outside component)
162
+ const [userState, setUserState, { getSnapshot }] = createState({
180
163
  name: '',
181
164
  email: '',
182
165
  });
183
166
 
184
167
  const UserProfile = (): React.JSX.Element => {
185
- const user = useUserState();
168
+ const [user, setUser] = React.useState(getSnapshot());
169
+
170
+ React.useEffect(() => {
171
+ const subscription = userState.subscribe(setUser);
172
+
173
+ return () => {
174
+ subscription.unsubscribe();
175
+ };
176
+ }, []);
186
177
 
187
178
  return (
188
179
  <div>
189
- <p>
190
- {'Name: '}
191
- {user.name}
192
- </p>
180
+ <p>{`Name: ${user.name}`}</p>
193
181
  <button
194
182
  onClick={() => {
195
183
  setUserState({
@@ -205,59 +193,92 @@ const UserProfile = (): React.JSX.Element => {
205
193
  };
206
194
  ```
207
195
 
208
- See also the [synstate-react-hooks README](../synstate-react-hooks/README.md).
196
+ ## Why SynState?
209
197
 
210
- ## Core Concepts
198
+ ### Simple to Start, Powerful When You Need It
211
199
 
212
- ### State Management
200
+ SynState is a state management library for web frontends. For most use cases, `createState`, `createReducer`, and simple combinators like `combine` and `map` are all you need — clean, minimal APIs that feel as intuitive as React's `useState` / `useReducer`, but for global state.
213
201
 
214
- SynState provides simple, intuitive APIs for managing application state:
202
+ When your requirements grow more complex, SynState scales with you. Built on its own Observable implementation, it provides operators like `debounce`, `throttle`, `switchMap`, and `mergeMap` for sophisticated asynchronous state management — without requiring an additional library like RxJS. You can describe everything from a simple counter to a debounced search pipeline with auto-cancellation in a single, unified API.
215
203
 
216
- - **`createState`**: Create state with getter/setter
217
- - **`createReducer`**: Create state by reducer and initial value
218
- - **`createBooleanState`**: Specialized state for boolean values
204
+ ### Why Observable-Based?
219
205
 
220
- ### Event System
206
+ A state management library that scales from simple global state to complex asynchronous workflows needs **reactive value propagation** at its core — when one piece of state changes, all derived values must update automatically and consistently. The Observable pattern is a natural fit for this: it models state as streams of values that can be composed, transformed, and combined declaratively.
221
207
 
222
- Built-in event emitter for event-driven patterns:
208
+ RxJS is the most well-known Observable library, and it excels at modeling asynchronous event processing. However, RxJS has a fundamental issue known as **glitch**[^1] — a phenomenon where derived values can temporarily enter inconsistent intermediate states during synchronous propagation. For a state management library, where consistency of derived state is critical, this is unacceptable. SynState was built from scratch with a glitch-free Observable implementation to solve this problem.
223
209
 
224
- - **`createValueEmitter`**: Create type-safe event emitters
225
- - **`createEventEmitter`**: Create event emitters without payload
210
+ For a detailed explanation, see ["How SynState solved the glitch?"](./documents/how-synstate-solved-the-glitch.md).
226
211
 
227
- ### Observable (Optional Advanced Feature)
212
+ ### Key Differences from RxJS
228
213
 
229
- For advanced use cases, you can use observables to build complex reactive data flows. However, most applications will only need `createState`, `createReducer`, and `createValueEmitter`.
214
+ - **Glitch free**: While RxJS Observables suffer from a troublesome phenomenon called glitch [^1], SynState Observables are glitch-free.
215
+ - **InitializedObservable**: Provides `InitializedObservable` which always holds an initial value, making it ideal for representing state
216
+ - **Focus on State Management**: Designed specifically for state management, not just asynchronous event processing. SynState provides utility functions `createState`, `createReducer`, and `createBooleanState`. However, this doesn't mean it's inadequate for asynchronous event processing — it can handle asynchronous operations as elegantly as RxJS.
230
217
 
231
- ## API Reference
218
+ ### Use Cases
232
219
 
233
- For complex scenarios, SynState provides observable-based APIs:
220
+ **Use SynState when you need:**
234
221
 
235
- ### Creation Functions
222
+ - A small piece of global state shared across components (e.g., dark mode toggle, user session)
223
+ - ✅ Complex asynchronous state management with operators like `debounce`, `throttle`, `switchMap`
224
+ - ✅ Redux-like state with reducers (`createReducer`)
225
+ - ✅ A project where the scale of state management is uncertain — SynState's unified API covers everything from a single shared counter to a full debounced search pipeline, so you never have to switch libraries as requirements grow
226
+ - ✅ Type-safe event emitters (`createEventEmitter`)
236
227
 
237
- - `source<T>()`: Create a new observable source
238
- - `of(value)`: Create observable from a single value
239
- - `fromArray(array)`: Create observable from array
240
- - `fromPromise(promise)`: Create observable from promise
241
- - `interval(ms)`: Emit values at intervals
242
- - `timer(delay)`: Emit after delay
228
+ **Consider other solutions when:**
243
229
 
244
- ### Operators
230
+ - You only need a React component (local) state (use React hooks `useState`, `useReducer`)
245
231
 
246
- - `filter(predicate)`: Filter values
247
- - `map(fn)`: Transform values
248
- - `scan(reducer, seed)`: Accumulate values
249
- - `debounceTime(ms)`: Debounce emissions
250
- - `throttleTime(ms)`: Throttle emissions
251
- - `skipIfNoChange()`: Skip duplicate values
252
- - `takeUntil(notifier)`: Complete on notifier emission
232
+ ## Examples
253
233
 
254
- ### Combination
234
+ ### Simple State with Additional APIs
255
235
 
256
- - `combine(observables)`: Combine latest values from multiple sources
257
- - `merge(observables)`: Merge multiple streams
258
- - `zip(observables)`: Pair values by index
236
+ ```tsx
237
+ // Create a reactive state
238
+ const [
239
+ state,
240
+ setState,
241
+ { updateState, resetState, getSnapshot, initialState },
242
+ ] = createState(0);
243
+ // type of state: InitializedObservable<number>
244
+ // type of setState: (v: number) => number
245
+ // type of updateState: (updater: (prev: number) => number) => number
246
+ // type of resetState: () => void
247
+ // type of getSnapshot: () => number
248
+ // type of initialState: number
249
+
250
+ const stateHistory: number[] = [];
251
+
252
+ // Subscribe to changes
253
+ state.subscribe((count) => {
254
+ stateHistory.push(count);
255
+ });
259
256
 
260
- ## Examples
257
+ assert.deepStrictEqual(stateHistory, [0]);
258
+
259
+ assert.strictEqual(getSnapshot(), 0);
260
+
261
+ // Update state
262
+ setState(1);
263
+
264
+ assert.strictEqual(getSnapshot(), 1);
265
+
266
+ assert.deepStrictEqual(stateHistory, [0, 1]);
267
+
268
+ updateState((prev) => prev + 2);
269
+
270
+ assert.strictEqual(getSnapshot(), 3);
271
+
272
+ assert.deepStrictEqual(stateHistory, [0, 1, 3]);
273
+
274
+ resetState();
275
+
276
+ assert.strictEqual(getSnapshot(), 0);
277
+
278
+ assert.strictEqual(initialState, 0);
279
+
280
+ assert.deepStrictEqual(stateHistory, [0, 1, 3, 0]);
281
+ ```
261
282
 
262
283
  ### Global Counter State (React)
263
284
 
@@ -266,8 +287,11 @@ import type * as React from 'react';
266
287
  import { createState } from 'synstate-react-hooks';
267
288
 
268
289
  // Create global state
269
- export const [useCounterState, , { updateState, resetState, getSnapshot }] =
270
- createState(0);
290
+ export const [useCounterState, , { updateState, resetState }] = createState(0);
291
+
292
+ const increment = (): void => {
293
+ updateState((n) => n + 1);
294
+ };
271
295
 
272
296
  // Component 1
273
297
  const Counter = (): React.JSX.Element => {
@@ -275,30 +299,15 @@ const Counter = (): React.JSX.Element => {
275
299
 
276
300
  return (
277
301
  <div>
278
- <p>
279
- {'Count: '}
280
- {count}
281
- </p>
282
- <button
283
- onClick={() => {
284
- updateState((n: number) => n + 1);
285
- }}
286
- >
287
- {'Increment'}
288
- </button>
302
+ <p>{`Count: ${count}`}</p>
303
+ <button onClick={increment}>{'Increment'}</button>
289
304
  </div>
290
305
  );
291
306
  };
292
307
 
293
- // Component 2 (synced automatically)
308
+ // Component 2
294
309
  const ResetButton = (): React.JSX.Element => (
295
- <button
296
- onClick={() => {
297
- resetState();
298
- }}
299
- >
300
- {'Reset'}
301
- </button>
310
+ <button onClick={resetState}>{'Reset'}</button>
302
311
  );
303
312
  ```
304
313
 
@@ -320,7 +329,7 @@ type Action = Readonly<
320
329
  | { type: 'remove'; id: number }
321
330
  >;
322
331
 
323
- const initialTodos: readonly Todo[] = [];
332
+ const initialTodos: readonly Todo[] = [] as const;
324
333
 
325
334
  const reducer = (todos: readonly Todo[], action: Action): readonly Todo[] => {
326
335
  switch (action.type) {
@@ -475,17 +484,15 @@ const ItemList = (): React.JSX.Element => {
475
484
  };
476
485
  ```
477
486
 
478
- // Events
479
-
480
487
  ### Advanced: Search with Debounce
481
488
 
482
489
  ```tsx
483
490
  import type * as React from 'react';
484
491
  import {
485
492
  createState,
486
- debounceTime,
493
+ debounce,
487
494
  filter,
488
- fromPromise,
495
+ fromAbortablePromise,
489
496
  type InitializedObservable,
490
497
  map,
491
498
  switchMap,
@@ -500,12 +507,12 @@ const [searchState, setSearchState] = createState('');
500
507
  const searchResults$: InitializedObservable<
501
508
  readonly Readonly<{ id: string; name: string }>[]
502
509
  > = searchState
503
- .pipe(debounceTime(300))
510
+ .pipe(debounce(300))
504
511
  .pipe(filter((query) => query.length > 2))
505
512
  .pipe(
506
513
  switchMap((query) =>
507
- fromPromise(
508
- fetch(`/api/search?q=${query}`).then(
514
+ fromAbortablePromise((signal) =>
515
+ fetch(`/api/search?q=${query}`, { signal }).then(
509
516
  (r) =>
510
517
  r.json() as Promise<
511
518
  readonly Readonly<{ id: string; name: string }>[]
@@ -542,10 +549,12 @@ const SearchBox = (): React.JSX.Element => {
542
549
  ### Advanced: Event Emitter with Throttle
543
550
 
544
551
  ```tsx
545
- import { createEventEmitter, throttleTime } from 'synstate';
552
+ import { createEventEmitter, throttle } from 'synstate';
546
553
 
547
554
  // Create event emitter
548
555
  const [refreshClicked, onRefreshClick] = createEventEmitter();
556
+ // refreshClicked: Observable<void>
557
+ // onRefreshClick: () => void
549
558
 
550
559
  // Subscribe to events
551
560
  refreshClicked.subscribe(() => {
@@ -553,7 +562,7 @@ refreshClicked.subscribe(() => {
553
562
  });
554
563
 
555
564
  // Throttle refresh clicks to prevent rapid successive executions
556
- const throttledRefresh = refreshClicked.pipe(throttleTime(2000));
565
+ const throttledRefresh = refreshClicked.pipe(throttle(2000));
557
566
 
558
567
  throttledRefresh.subscribe(() => {
559
568
  console.log('Executing refresh...');
@@ -572,38 +581,82 @@ const DataTable = (): React.JSX.Element => (
572
581
  );
573
582
  ```
574
583
 
575
- ## Why SynState?
584
+ ## API Reference
585
+
586
+ ### State Management
587
+
588
+ SynState provides simple, intuitive APIs for managing application state:
576
589
 
577
- ### Simple State Management, Not Complex Reactive Programming
590
+ - **`createState`**: Create state with InitializedObservable and setter
591
+ - **`createReducer`**: Create state by reducer and initial value
592
+ - **`createBooleanState`**: Specialized state for boolean values
578
593
 
579
- SynState is a state management library for web frontends, similar to Redux, Jotai, Zustand, and MobX. It provides APIs for creating and managing global state across your application.
594
+ ### Event System
580
595
 
581
- Under the hood, SynState is built on Observable patterns similar to those provided by RxJS. However, unlike RxJS, which can make code harder to read with many operators and complex streams, SynState focuses on **simple, readable state management and event handling**. Most applications only need `createState`, `createReducer`, and simple operators/combinators like `combine` and `map` — clean, straightforward APIs that developers understand immediately.
596
+ Built-in event emitter for event-driven patterns:
582
597
 
583
- **Advanced reactive features are optional** and only used when you actually need them (like debouncing search input). The library doesn't force you into a reactive programming mindset.
598
+ - **`createValueEmitter`**: Create type-safe event emitters
599
+ - **`createEventEmitter`**: Create event emitters without payload
584
600
 
585
- ### Key Differences from RxJS
601
+ ### Observable APIs
586
602
 
587
- - **Focus on State Management**: Designed specifically for state management, not just asynchronous event processing
588
- - **InitializedObservable**: Provides `InitializedObservable` which always holds an initial value, making it ideal for representing state
589
- - **Simpler API**: Most use cases are covered by `createState`, `createReducer`, and `createEventEmitter`
590
- - **Better Readability**: No need for complex operator chains in everyday code
591
- - **Optional Complexity**: Advanced features available to manipulate Observables when needed
603
+ For complex scenarios, SynState provides observable-based APIs:
592
604
 
593
- ### Use Cases
605
+ #### Creation Functions
594
606
 
595
- **Use SynState when you need:**
607
+ - `source<T>()`: Create a new observable source (Almost equivalent to RxJS `subject`)
608
+ - `fromPromise(promise)`: Create observable from promise
609
+ - `fromSubscribable()`: Create observable from any subscribable object
610
+ - `counter(ms)`: Emit values at intervals (Almost equivalent to RxJS `interval`)
611
+ - `timer(delay)`: Emit after delay
596
612
 
597
- - ✅ Global state management across components
598
- - ✅ Event-driven communication between components
599
- - Type-safe event emitters
600
- - Redux-like state with reducers
601
- - Simple reactive patterns (debounce, throttle, etc.)
613
+ #### Operators
614
+
615
+ - `map` variants
616
+ - `map(fn)`: Transform values
617
+ - `mapTo(value)`: Map all values to a constant
618
+ - `getKey(key)`: Extract property value from objects (alias: `pluck`)
619
+ - `attachIndex()`: Attach index to each value (alias: `withIndex`)
620
+ - Result/Optional
621
+ - `mapOptional(fn)`: Map over Optional values
622
+ - `mapResultOk(fn)`: Map over Result ok values
623
+ - `mapResultErr(fn)`: Map over Result error values
624
+ - `unwrapOptional()`: Unwrap Optional values to undefined
625
+ - `unwrapResultOk()`: Unwrap Result ok values to undefined
626
+ - `unwrapResultErr()`: Unwrap Result error values to undefined
627
+ - `mergeMap(fn)`: Map to observables and merge all (runs in parallel) (alias: `flatMap`)
628
+ - `switchMap(fn)`: Map to observables and switch to latest (cancels previous)
629
+ - Filtering
630
+ - `filter(predicate)`: Filter values
631
+ - `skipIfNoChange()`: Skip duplicate values (alias: `distinctUntilChanged`)
632
+ - `skip(n)`: Skip first n emissions
633
+ - `take(n)`: Take first n emissions then complete
634
+ - `skipWhile(predicate)`: Skip values while predicate is true
635
+ - `takeWhile(predicate)`: Emit values while predicate is true, then complete
636
+ - `skipUntil(notifier)`: Skip values until notifier emits
637
+ - `takeUntil(notifier)`: Complete on notifier emission
638
+ - Time series processing
639
+ - `audit(ms)`: Emit the last value after specified time window (Almost equivalent to RxJS `auditTime`)
640
+ - `debounce(ms)`: Debounce emissions (Almost equivalent to RxJS `debounceTime`)
641
+ - `throttle(ms)`: Throttle emissions (Almost equivalent to RxJS `throttleTime`)
642
+ - Others
643
+ - `pairwise()`: Emit previous and current values as pairs
644
+ - `scan(reducer, seed)`: Accumulate values
645
+ - `withBuffered(observable)`: Buffer values from observable and emit with parent (alias: `withBufferedFrom`)
646
+ - `withCurrentValueFrom(observable)`: Sample current value from another observable (alias: `withLatestFrom`)
647
+ - `withInitialValue(value)`: Provide an initial value for uninitialized observable
648
+
649
+ #### Combination
650
+
651
+ - `combine(observables)`: Combine latest values from multiple sources (alias: `combineLatest`)
652
+ - `merge(observables)`: Merge multiple streams
653
+ - `zip(observables)`: Pair values by index
602
654
 
603
- **Consider other solutions when:**
655
+ #### Utilities
604
656
 
605
- - You need state in a React component (use React hooks `useState`, `useReducer`)
606
- - Your app is simple enough for React Context alone
657
+ - `isChildObservable(obs)`: Check if observable is a child observable
658
+ - `isManagerObservable(obs)`: Check if observable is a manager observable
659
+ - `isRootObservable(obs)`: Check if observable is a root observable
607
660
 
608
661
  ## Type Safety
609
662
 
Binary file
@@ -1,3 +1,5 @@
1
+ import { type Some } from 'ts-data-forge';
2
+ import { type MutableSet } from 'ts-type-forge';
1
3
  import { type AsyncChildObservable, type ChildObservable, type InitializedObservable, type InitializedSyncChildObservable, type KeepInitialValueOperator, type NonEmptyUnknownList, type Observable, type ObservableId, type Operator, type SyncChildObservable, type WithInitialValueOperator, type Wrap } from '../types/index.mjs';
2
4
  import { ObservableBaseClass } from './observable-base-class.mjs';
3
5
  export declare class AsyncChildObservableClass<A, const P extends NonEmptyUnknownList> extends ObservableBaseClass<A, 'async child', number> implements AsyncChildObservable<A, P> {
@@ -1 +1 @@
1
- {"version":3,"file":"child-observable-class.d.mts","sourceRoot":"","sources":["../../../src/core/class/child-observable-class.mts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAwDlE,qBAAa,yBAAyB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAC3E,SAAQ,mBAAmB,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CACpD,YAAW,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC;;IAErC,QAAQ,CAAC,OAAO,sDAAC;IAEjB,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;gBAElD,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KAClE,CAAC;IAiBF,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAajD,WAAW,CAAC,SAAS,EAAE,CAAC,GAAG,IAAI;IAUtB,QAAQ,IAAI,IAAI;IAShB,WAAW,IAAI,IAAI;CAU7B;AAED,qBAAa,wBAAwB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAC1E,SAAQ,mBAAmB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CACnD,YAAW,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpC,QAAQ,CAAC,OAAO,sDAAC;gBAEL,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KACjE,CAAC;IAYO,QAAQ,IAAI,IAAI;IAQhB,WAAW,IAAI,IAAI;CAU7B;AAED,qBAAa,mCAAmC,CAC9C,CAAC,EACD,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAEnC,SAAQ,wBAAwB,CAAC,CAAC,EAAE,CAAC,CACrC,YAAW,8BAA8B,CAAC,CAAC,EAAE,CAAC,CAAC;gBAEnC,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KAC5E,CAAC;IAIO,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC;IAKtB,IAAI,CAAC,CAAC,EACb,QAAQ,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACxE,qBAAqB,CAAC,CAAC,CAAC;IAElB,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;CAO1D"}
1
+ {"version":3,"file":"child-observable-class.d.mts","sourceRoot":"","sources":["../../../src/core/class/child-observable-class.mts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AA2GlE,qBAAa,yBAAyB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAC3E,SAAQ,mBAAmB,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CACpD,YAAW,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC;;IAErC,QAAQ,CAAC,OAAO,sDAAC;IAEjB,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;gBAElD,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KAClE,CAAC;IAiBF,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAiBjD,WAAW,CAAC,SAAS,EAAE,CAAC,GAAG,IAAI;IAUtB,QAAQ,IAAI,IAAI;IAShB,WAAW,IAAI,IAAI;CAU7B;AAED,qBAAa,wBAAwB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAC1E,SAAQ,mBAAmB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CACnD,YAAW,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpC,QAAQ,CAAC,OAAO,sDAAC;gBAEL,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KACjE,CAAC;IAYO,QAAQ,IAAI,IAAI;IAQhB,WAAW,IAAI,IAAI;CAU7B;AAED,qBAAa,mCAAmC,CAC9C,CAAC,EACD,KAAK,CAAC,CAAC,SAAS,mBAAmB,CAEnC,SAAQ,wBAAwB,CAAC,CAAC,EAAE,CAAC,CACrC,YAAW,8BAA8B,CAAC,CAAC,EAAE,CAAC,CAAC;gBAEnC,EACV,OAAO,EACP,KAA6B,EAC7B,YAAY,GACb,EAAE,QAAQ,CAAC;QACV,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;KAC5E,CAAC;IAIO,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC;IAKtB,IAAI,CAAC,CAAC,EACb,QAAQ,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACxE,qBAAqB,CAAC,CAAC,CAAC;IAElB,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;CAI1D"}
@@ -1,11 +1,44 @@
1
1
  import { Arr } from 'ts-data-forge';
2
- import { isManagerObservable } from '../types/observable.mjs';
3
- import { issueUpdaterSymbol } from '../utils/id-maker.mjs';
2
+ import { isManagerObservable, isChildObservable } from '../types/observable.mjs';
3
+ import { issueUpdateToken } from '../utils/id-maker.mjs';
4
4
  import { maxDepth } from '../utils/max-depth.mjs';
5
5
  import { binarySearch } from '../utils/utils.mjs';
6
6
  import { ObservableBaseClass } from './observable-base-class.mjs';
7
7
 
8
+ /**
9
+ * Detects circular dependencies by walking the full ancestor chain of the
10
+ * given parents and checking whether `child` already appears among them.
11
+ *
12
+ * @throws {Error} if a circular dependency is detected
13
+ */
14
+ const hasCircularDependencyFrom = (node, mut_visited, mut_inPath) => {
15
+ if (mut_inPath.has(node.id))
16
+ return true;
17
+ if (mut_visited.has(node.id))
18
+ return false;
19
+ mut_visited.add(node.id);
20
+ mut_inPath.add(node.id);
21
+ if (isChildObservable(node)) {
22
+ for (const parent of node.parents) {
23
+ if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
24
+ return true;
25
+ }
26
+ }
27
+ }
28
+ mut_inPath.delete(node.id);
29
+ return false;
30
+ };
31
+ const detectCircularDependency = (child, parents) => {
32
+ const mut_visited = new Set();
33
+ const mut_inPath = new Set([child.id]);
34
+ for (const parent of parents) {
35
+ if (hasCircularDependencyFrom(parent, mut_visited, mut_inPath)) {
36
+ throw new Error('Circular dependency detected in observable graph: a child observable cannot be its own ancestor.');
37
+ }
38
+ }
39
+ };
8
40
  const registerChild = (child, parents) => {
41
+ detectCircularDependency(child, parents);
9
42
  for (const p of parents) {
10
43
  p.addChild(child);
11
44
  }
@@ -41,7 +74,7 @@ const tryComplete = ({ hasSubscriber, hasActiveChild, parents, complete, }) => {
41
74
  };
42
75
  class AsyncChildObservableClass extends ObservableBaseClass {
43
76
  parents;
44
- #mut_procedure;
77
+ #mut_propagationOrder;
45
78
  descendantsIdSet;
46
79
  constructor({ parents, depth = 1 + maxDepth(parents), initialValue, }) {
47
80
  super({
@@ -50,7 +83,7 @@ class AsyncChildObservableClass extends ObservableBaseClass {
50
83
  initialValue,
51
84
  });
52
85
  this.parents = parents;
53
- this.#mut_procedure = [];
86
+ this.#mut_propagationOrder = [];
54
87
  this.descendantsIdSet = new Set();
55
88
  registerChild(this, parents);
56
89
  }
@@ -59,14 +92,14 @@ class AsyncChildObservableClass extends ObservableBaseClass {
59
92
  if (this.descendantsIdSet.has(child.id))
60
93
  return;
61
94
  this.descendantsIdSet.add(child.id);
62
- const insertPos = binarySearch(this.#mut_procedure.map((a) => a.depth), child.depth);
63
- this.#mut_procedure = Arr.toInserted(this.#mut_procedure, insertPos, child);
95
+ const insertPos = binarySearch(this.#mut_propagationOrder.map((a) => a.depth), child.depth);
96
+ this.#mut_propagationOrder = Arr.toInserted(this.#mut_propagationOrder, insertPos, child);
64
97
  }
65
98
  startUpdate(nextValue) {
66
- const updaterSymbol = issueUpdaterSymbol();
67
- this.setNext(nextValue, updaterSymbol);
68
- for (const p of this.#mut_procedure) {
69
- p.tryUpdate(updaterSymbol);
99
+ const updateToken = issueUpdateToken();
100
+ this.setNext(nextValue, updateToken);
101
+ for (const p of this.#mut_propagationOrder) {
102
+ p.tryUpdate(updateToken);
70
103
  }
71
104
  }
72
105
  complete() {
@@ -124,9 +157,7 @@ class InitializedSyncChildObservableClass extends SyncChildObservableClass {
124
157
  return super.getCurrentValue();
125
158
  }
126
159
  pipe(operator) {
127
- return operator(
128
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
129
- this);
160
+ return operator(this);
130
161
  }
131
162
  }
132
163