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
package/README.md CHANGED
@@ -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
- **SynState** is a lightweight, high-performance, type-safe state management library for TypeScript/JavaScript. Perfect for building reactive global state and event-driven systems in React, Vue, and other frameworks.
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
+
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).
17
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 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
- - 🔧 **Flexible**: Simple state management with optional advanced Observable-based features (operators like `map`, `filter`, `debounceTime`, `throttleTime`, and combinators like `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,28 +56,103 @@ pnpm add synstate
52
56
 
53
57
  ```tsx
54
58
  // Create a reactive state
55
- const [state, setState, { updateState }] = createState(0);
59
+ const [state, setState] = createState(0);
60
+ // type of state: InitializedObservable<number>
61
+ // type of setState: (v: number) => number
56
62
 
57
- const mut_history: number[] = [];
63
+ const stateHistory: number[] = [];
58
64
 
59
- // Subscribe to changes (in React components, Vue watchers, etc.)
60
- state.subscribe((count: number) => {
61
- mut_history.push(count);
65
+ // Subscribe to changes
66
+ state.subscribe((count) => {
67
+ stateHistory.push(count);
62
68
  });
63
69
 
64
- assert.deepStrictEqual(mut_history, [0]);
70
+ assert.deepStrictEqual(stateHistory, [0]);
65
71
 
66
72
  // Update state
67
73
  setState(1);
68
74
 
69
- assert.deepStrictEqual(mut_history, [0, 1]);
75
+ assert.deepStrictEqual(stateHistory, [0, 1]);
76
+ ```
70
77
 
71
- updateState((prev: number) => prev + 1);
78
+ ### With React
72
79
 
73
- assert.deepStrictEqual(mut_history, [0, 1, 2]);
80
+ ```bash
81
+ npm add synstate-react-hooks
74
82
  ```
75
83
 
76
- ### With React
84
+ ```tsx
85
+ import type * as React from 'react';
86
+ import { createState } from 'synstate-react-hooks';
87
+
88
+ const [useUserState, setUserState] = createState({
89
+ name: '',
90
+ email: '',
91
+ });
92
+
93
+ const UserProfile = (): React.JSX.Element => {
94
+ const user = useUserState();
95
+
96
+ return (
97
+ <div>
98
+ <p>{`Name: ${user.name}`}</p>
99
+ <button
100
+ onClick={() => {
101
+ setUserState({
102
+ name: 'Alice',
103
+ email: 'alice@example.com',
104
+ });
105
+ }}
106
+ >
107
+ {'Set User'}
108
+ </button>
109
+ </div>
110
+ );
111
+ };
112
+ ```
113
+
114
+ This is equivalent to the following code without synstate-react-hook:
115
+
116
+ ```tsx
117
+ import * as React from 'react';
118
+ import { createState } from 'synstate';
119
+
120
+ const [userState, setUserState] = createState({
121
+ name: '',
122
+ email: '',
123
+ });
124
+
125
+ const UserProfile = (): React.JSX.Element => {
126
+ const user = React.useSyncExternalStore(
127
+ (onStoreChange: () => void) => {
128
+ const { unsubscribe } = userState.subscribe(onStoreChange);
129
+
130
+ return unsubscribe;
131
+ },
132
+ () => userState.getSnapshot().value,
133
+ );
134
+
135
+ return (
136
+ <div>
137
+ <p>{`Name: ${user.name}`}</p>
138
+ <button
139
+ onClick={() => {
140
+ setUserState({
141
+ name: 'Alice',
142
+ email: 'alice@example.com',
143
+ });
144
+ }}
145
+ >
146
+ {'Set User'}
147
+ </button>
148
+ </div>
149
+ );
150
+ };
151
+ ```
152
+
153
+ See also the [synstate-react-hooks README](../synstate-react-hooks/README.md).
154
+
155
+ If you're using React v17 or earlier:
77
156
 
78
157
  ```tsx
79
158
  import * as React from 'react';
@@ -98,10 +177,7 @@ const UserProfile = (): React.JSX.Element => {
98
177
 
99
178
  return (
100
179
  <div>
101
- <p>
102
- {'Name: '}
103
- {user.name}
104
- </p>
180
+ <p>{`Name: ${user.name}`}</p>
105
181
  <button
106
182
  onClick={() => {
107
183
  setUserState({
@@ -117,207 +193,135 @@ const UserProfile = (): React.JSX.Element => {
117
193
  };
118
194
  ```
119
195
 
120
- ## Core Concepts
121
-
122
- ### State Management
196
+ ## Why SynState?
123
197
 
124
- SynState provides simple, intuitive APIs for managing application state:
198
+ ### Simple to Start, Powerful When You Need It
125
199
 
126
- - **`createState`**: Create mutable state with getter/setter
127
- - **`createReducer`**: Redux-style state management
128
- - **`createBooleanState`**: Specialized state for boolean values
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.
129
201
 
130
- ### Event System
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.
131
203
 
132
- Built-in event emitter for event-driven patterns:
204
+ ### Why Observable-Based?
133
205
 
134
- - **`createValueEmitter`**: Create type-safe event emitters
135
- - **`createEventEmitter`**: Create event emitters without payload
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.
136
207
 
137
- ### Observable (Optional Advanced Feature)
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.
138
209
 
139
- For advanced use cases, you can use observables to build complex reactive data flows. However, most applications will only need `createState`, `createReducer`, and `createValueEmitter`.
210
+ For a detailed explanation, see ["How SynState solved the glitch?"](./documents/how-synstate-solved-the-glitch.md).
140
211
 
141
- ## API Reference
212
+ ### Key Differences from RxJS
142
213
 
143
- <!-- ### State Management (Recommended)
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.
144
217
 
145
- #### createState
218
+ ### Use Cases
146
219
 
147
- Create reactive state with getter and setter.
220
+ **Use SynState when you need:**
148
221
 
149
- #### createBooleanState
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`)
150
227
 
151
- Specialized state for boolean values.
228
+ **Consider other solutions when:**
152
229
 
153
- #### createReducer
230
+ - You only need a React component (local) state (use React hooks `useState`, `useReducer`)
154
231
 
155
- Create state with reducer pattern (like Redux).
232
+ ## Examples
156
233
 
157
- ### Event System
234
+ ### Simple State with Additional APIs
158
235
 
159
- #### createValueEmitter
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
+ });
160
256
 
161
- Create type-safe event emitter with payload.
257
+ assert.deepStrictEqual(stateHistory, [0]);
162
258
 
163
- #### createEventEmitter
259
+ assert.strictEqual(getSnapshot(), 0);
164
260
 
165
- Create event emitter without payload.
166
- -->
261
+ // Update state
262
+ setState(1);
167
263
 
168
- ### Advanced Features (Optional)
264
+ assert.strictEqual(getSnapshot(), 1);
169
265
 
170
- For complex scenarios, SynState provides observable-based APIs:
266
+ assert.deepStrictEqual(stateHistory, [0, 1]);
171
267
 
172
- #### Creation Functions
268
+ updateState((prev) => prev + 2);
173
269
 
174
- - `source<T>()`: Create a new observable source
175
- - `of(value)`: Create observable from a single value
176
- - `fromArray(array)`: Create observable from array
177
- - `fromPromise(promise)`: Create observable from promise
178
- - `interval(ms)`: Emit values at intervals
179
- - `timer(delay)`: Emit after delay
270
+ assert.strictEqual(getSnapshot(), 3);
180
271
 
181
- #### Operators
272
+ assert.deepStrictEqual(stateHistory, [0, 1, 3]);
182
273
 
183
- - `filter(predicate)`: Filter values
184
- - `map(fn)`: Transform values
185
- - `scan(reducer, seed)`: Accumulate values
186
- - `debounceTime(ms)`: Debounce emissions
187
- - `throttleTime(ms)`: Throttle emissions
188
- - `skipIfNoChange()`: Skip duplicate values
189
- - `takeUntil(notifier)`: Complete on notifier emission
274
+ resetState();
190
275
 
191
- #### Combination
276
+ assert.strictEqual(getSnapshot(), 0);
192
277
 
193
- - `combine(observables)`: Combine latest values from multiple sources
194
- - `merge(observables)`: Merge multiple streams
195
- - `zip(observables)`: Pair values by index
278
+ assert.strictEqual(initialState, 0);
196
279
 
197
- ## Examples
280
+ assert.deepStrictEqual(stateHistory, [0, 1, 3, 0]);
281
+ ```
198
282
 
199
283
  ### Global Counter State (React)
200
284
 
201
285
  ```tsx
202
- import * as React from 'react';
203
- import { createState } from 'synstate';
286
+ import type * as React from 'react';
287
+ import { createState } from 'synstate-react-hooks';
204
288
 
205
289
  // Create global state
206
- export const [counterState, , { updateState, resetState, getSnapshot }] =
207
- createState(0);
290
+ export const [useCounterState, , { updateState, resetState }] = createState(0);
291
+
292
+ const increment = (): void => {
293
+ updateState((n) => n + 1);
294
+ };
208
295
 
209
296
  // Component 1
210
297
  const Counter = (): React.JSX.Element => {
211
- const [count, setCount] = React.useState(getSnapshot());
212
-
213
- React.useEffect(() => {
214
- const sub = counterState.subscribe(setCount);
215
-
216
- return () => {
217
- sub.unsubscribe();
218
- };
219
- }, []);
298
+ const count = useCounterState();
220
299
 
221
300
  return (
222
301
  <div>
223
- <p>
224
- {'Count: '}
225
- {count}
226
- </p>
227
- <button
228
- onClick={() => {
229
- updateState((n: number) => n + 1);
230
- }}
231
- >
232
- {'Increment'}
233
- </button>
302
+ <p>{`Count: ${count}`}</p>
303
+ <button onClick={increment}>{'Increment'}</button>
234
304
  </div>
235
305
  );
236
306
  };
237
307
 
238
- // Component 2 (synced automatically)
308
+ // Component 2
239
309
  const ResetButton = (): React.JSX.Element => (
240
- <button
241
- onClick={() => {
242
- resetState();
243
- }}
244
- >
245
- {'Reset'}
246
- </button>
310
+ <button onClick={resetState}>{'Reset'}</button>
247
311
  );
248
312
  ```
249
313
 
250
- ### Event-Driven Architecture (React)
251
-
252
- ```tsx
253
- import * as React from 'react';
254
- import { createEventEmitter, createValueEmitter } from 'synstate';
255
-
256
- // Global events
257
- export const [userLoggedIn$, emitUserLoggedIn] = createValueEmitter<
258
- Readonly<{
259
- id: number;
260
- name: string;
261
- }>
262
- >();
263
-
264
- export const [userLoggedOut$, emitUserLoggedOut] = createEventEmitter();
265
-
266
- // Component that emits events
267
- const LoginButton = (): React.JSX.Element => {
268
- const handleLogin = React.useCallback(() => {
269
- (async () => {
270
- const user = await loginUser();
271
-
272
- emitUserLoggedIn(user);
273
- })().catch(() => {});
274
- }, []);
275
-
276
- return <button onClick={handleLogin}>{'Login'}</button>;
277
- };
278
-
279
- // Component that listens to events
280
- const NotificationPage = (): React.JSX.Element => {
281
- const [message, setMessage] = React.useState('');
282
-
283
- React.useEffect(() => {
284
- const sub1 = userLoggedIn$.subscribe((user) => {
285
- setMessage(`Welcome, ${user.name}!`);
286
- });
287
-
288
- const sub2 = userLoggedOut$.subscribe(() => {
289
- setMessage('Logged out');
290
- });
291
-
292
- return () => {
293
- sub1.unsubscribe();
294
-
295
- sub2.unsubscribe();
296
- };
297
- }, []);
298
-
299
- return message !== '' ? (
300
- <div className={'notification'}>{message}</div>
301
- ) : (
302
- <>{null}</>
303
- );
304
- };
305
-
306
- const loginUser = async (): Promise<
307
- Readonly<{
308
- id: number;
309
- name: string;
310
- }>
311
- > => ({ id: 1, name: 'Alice' });
312
- ```
313
-
314
314
  ### Todo List with Reducer (React)
315
315
 
316
316
  ```tsx
317
317
  import * as React from 'react';
318
- import { createReducer } from 'synstate';
318
+ import { createReducer } from 'synstate-react-hooks';
319
319
 
320
- type Todo = Readonly<{ id: number; text: string; done: boolean }>;
320
+ type Todo = Readonly<{
321
+ id: number;
322
+ text: string;
323
+ done: boolean;
324
+ }>;
321
325
 
322
326
  type Action = Readonly<
323
327
  | { type: 'add'; text: string }
@@ -325,10 +329,9 @@ type Action = Readonly<
325
329
  | { type: 'remove'; id: number }
326
330
  >;
327
331
 
328
- const [todoState, dispatch, getSnapshot] = createReducer<
329
- readonly Todo[],
330
- Action
331
- >((todos, action) => {
332
+ const initialTodos: readonly Todo[] = [] as const;
333
+
334
+ const reducer = (todos: readonly Todo[], action: Action): readonly Todo[] => {
332
335
  switch (action.type) {
333
336
  case 'add':
334
337
  return [
@@ -339,53 +342,66 @@ const [todoState, dispatch, getSnapshot] = createReducer<
339
342
  done: false,
340
343
  },
341
344
  ];
345
+
342
346
  case 'toggle':
343
347
  return todos.map((t) =>
344
348
  t.id === action.id ? { ...t, done: !t.done } : t,
345
349
  );
350
+
346
351
  case 'remove':
347
352
  return todos.filter((t) => t.id !== action.id);
348
353
  }
349
- }, []);
354
+ };
350
355
 
351
- const TodoList = (): React.JSX.Element => {
352
- const [todos, setTodos] = React.useState(getSnapshot());
356
+ const [useTodoState, dispatch] = createReducer<readonly Todo[], Action>(
357
+ reducer,
358
+ initialTodos,
359
+ );
353
360
 
354
- React.useEffect(() => {
355
- const sub = todoState.subscribe(setTodos);
361
+ const addTodo = (): void => {
362
+ dispatch({
363
+ type: 'add',
364
+ text: 'New Todo',
365
+ });
366
+ };
356
367
 
357
- return () => {
358
- sub.unsubscribe();
359
- };
360
- }, []);
368
+ const TodoList = (): React.JSX.Element => {
369
+ const todos = useTodoState();
370
+
371
+ const todosWithHandler = React.useMemo(
372
+ () =>
373
+ todos.map((todo) => ({
374
+ ...todo,
375
+ onToggle: () => {
376
+ dispatch({
377
+ type: 'toggle',
378
+ id: todo.id,
379
+ });
380
+ },
381
+ onRemove: () => {
382
+ dispatch({
383
+ type: 'remove',
384
+ id: todo.id,
385
+ });
386
+ },
387
+ })),
388
+ [todos],
389
+ );
361
390
 
362
391
  return (
363
392
  <div>
364
- {todos.map((todo) => (
393
+ {todosWithHandler.map((todo) => (
365
394
  <div key={todo.id}>
366
395
  <input
367
396
  checked={todo.done}
368
397
  type={'checkbox'}
369
- onChange={() => {
370
- dispatch({
371
- type: 'toggle',
372
- id: todo.id,
373
- });
374
- }}
398
+ onChange={todo.onToggle}
375
399
  />
376
400
  <span>{todo.text}</span>
401
+ <button onClick={todo.onRemove}>{'Remove'}</button>
377
402
  </div>
378
403
  ))}
379
- <button
380
- onClick={() => {
381
- dispatch({
382
- type: 'add',
383
- text: 'New Todo',
384
- });
385
- }}
386
- >
387
- {'Add Todo'}
388
- </button>
404
+ <button onClick={addTodo}>{'Add Todo'}</button>
389
405
  </div>
390
406
  );
391
407
  };
@@ -395,35 +411,19 @@ const TodoList = (): React.JSX.Element => {
395
411
 
396
412
  ```tsx
397
413
  import * as React from 'react';
398
- import { createBooleanState } from 'synstate';
414
+ import { createBooleanState } from 'synstate-react-hooks';
399
415
 
400
- export const [darkModeState, { toggle, getSnapshot }] =
416
+ export const [useDarkModeState, { toggle: toggleDarkMode }] =
401
417
  createBooleanState(false);
402
418
 
403
419
  const ThemeToggle = (): React.JSX.Element => {
404
- const [isDark, setIsDark] = React.useState(getSnapshot());
405
-
406
- React.useEffect(() => {
407
- const sub = darkModeState.subscribe(setIsDark);
408
-
409
- return () => {
410
- sub.unsubscribe();
411
- };
412
- }, []);
420
+ const isDark = useDarkModeState();
413
421
 
414
422
  React.useEffect(() => {
415
423
  document.body.className = isDark ? 'dark' : 'light';
416
424
  }, [isDark]);
417
425
 
418
- return (
419
- <button
420
- onClick={() => {
421
- toggle();
422
- }}
423
- >
424
- {isDark ? '🌙' : '☀️'}
425
- </button>
426
- );
426
+ return <button onClick={toggleDarkMode}>{isDark ? '🌙' : '☀️'}</button>;
427
427
  };
428
428
  ```
429
429
 
@@ -431,30 +431,20 @@ const ThemeToggle = (): React.JSX.Element => {
431
431
 
432
432
  ```tsx
433
433
  import * as React from 'react';
434
- import { createEventEmitter, createState, createValueEmitter } from 'synstate';
435
-
436
- // Events
437
- const [onItemAdded$, emitItemAdded] = createValueEmitter<string>();
438
-
439
- const [onClearAll$, emitClearAll] = createEventEmitter();
434
+ import { createState } from 'synstate-react-hooks';
440
435
 
441
436
  // State
442
- const [itemsState, setItemsState, { updateState, getSnapshot }] = createState<
443
- readonly string[]
444
- >([]);
437
+ const [useItemsState, _, { updateState, resetState: resetItemsState }] =
438
+ createState<readonly string[]>([]);
445
439
 
446
440
  // Setup event handlers
447
- onItemAdded$.subscribe((item) => {
441
+ const addItem = (item: string): void => {
448
442
  updateState((items: readonly string[]) => [...items, item]);
449
- });
450
-
451
- onClearAll$.subscribe(() => {
452
- setItemsState([]);
453
- });
443
+ };
454
444
 
455
445
  // Component 1: Add items
456
446
  const ItemInput = (): React.JSX.Element => {
457
- const [input, setInput] = React.useState('');
447
+ const [input, setInput] = React.useState<string>('');
458
448
 
459
449
  return (
460
450
  <div>
@@ -466,7 +456,7 @@ const ItemInput = (): React.JSX.Element => {
466
456
  />
467
457
  <button
468
458
  onClick={() => {
469
- emitItemAdded(input);
459
+ addItem(input);
470
460
 
471
461
  setInput('');
472
462
  }}
@@ -479,15 +469,7 @@ const ItemInput = (): React.JSX.Element => {
479
469
 
480
470
  // Component 2: Display items
481
471
  const ItemList = (): React.JSX.Element => {
482
- const [items, setItems] = React.useState(getSnapshot());
483
-
484
- React.useEffect(() => {
485
- const sub = itemsState.subscribe(setItems);
486
-
487
- return () => {
488
- sub.unsubscribe();
489
- };
490
- }, []);
472
+ const items = useItemsState();
491
473
 
492
474
  return (
493
475
  <div>
@@ -496,40 +478,41 @@ const ItemList = (): React.JSX.Element => {
496
478
  <li key={i}>{item}</li>
497
479
  ))}
498
480
  </ul>
499
- <button onClick={emitClearAll}>{'Clear All'}</button>
481
+ <button onClick={resetItemsState}>{'Clear All'}</button>
500
482
  </div>
501
483
  );
502
484
  };
503
485
  ```
504
486
 
505
- // Events
506
-
507
487
  ### Advanced: Search with Debounce
508
488
 
509
489
  ```tsx
510
- import * as React from 'react';
490
+ import type * as React from 'react';
511
491
  import {
512
492
  createState,
513
- debounceTime,
493
+ debounce,
514
494
  filter,
515
- fromPromise,
516
- type Observable,
495
+ fromAbortablePromise,
496
+ type InitializedObservable,
497
+ map,
517
498
  switchMap,
499
+ withInitialValue,
518
500
  } from 'synstate';
501
+ import { useObservableValue } from 'synstate-react-hooks';
519
502
  import { Result } from 'ts-data-forge';
520
503
 
521
504
  const [searchState, setSearchState] = createState('');
522
505
 
523
- // Advanced reactive pipeline (optional feature)
524
- const searchResults$: Observable<
525
- Result<readonly Readonly<{ id: string; name: string }>[], unknown>
506
+ // Advanced reactive pipeline with debounce and filtering
507
+ const searchResults$: InitializedObservable<
508
+ readonly Readonly<{ id: string; name: string }>[]
526
509
  > = searchState
527
- .pipe(debounceTime(300))
510
+ .pipe(debounce(300))
528
511
  .pipe(filter((query) => query.length > 2))
529
512
  .pipe(
530
513
  switchMap((query) =>
531
- fromPromise(
532
- fetch(`/api/search?q=${query}`).then(
514
+ fromAbortablePromise((signal) =>
515
+ fetch(`/api/search?q=${query}`, { signal }).then(
533
516
  (r) =>
534
517
  r.json() as Promise<
535
518
  readonly Readonly<{ id: string; name: string }>[]
@@ -537,24 +520,13 @@ const searchResults$: Observable<
537
520
  ),
538
521
  ),
539
522
  ),
540
- );
523
+ )
524
+ .pipe(filter((res) => Result.isOk(res)))
525
+ .pipe(map((res) => Result.unwrapOk(res)))
526
+ .pipe(withInitialValue([]));
541
527
 
542
528
  const SearchBox = (): React.JSX.Element => {
543
- const [results, setResults] = React.useState<
544
- readonly Readonly<{ id: string; name: string }>[]
545
- >([]);
546
-
547
- React.useEffect(() => {
548
- const sub = searchResults$.subscribe((result) => {
549
- if (Result.isOk(result)) {
550
- setResults(result.value);
551
- }
552
- });
553
-
554
- return () => {
555
- sub.unsubscribe();
556
- };
557
- }, []);
529
+ const searchResults = useObservableValue(searchResults$);
558
530
 
559
531
  return (
560
532
  <div>
@@ -565,7 +537,7 @@ const SearchBox = (): React.JSX.Element => {
565
537
  }}
566
538
  />
567
539
  <ul>
568
- {results.map((item) => (
540
+ {searchResults.map((item) => (
569
541
  <li key={item.id}>{item.name}</li>
570
542
  ))}
571
543
  </ul>
@@ -577,10 +549,12 @@ const SearchBox = (): React.JSX.Element => {
577
549
  ### Advanced: Event Emitter with Throttle
578
550
 
579
551
  ```tsx
580
- import { createEventEmitter, throttleTime } from 'synstate';
552
+ import { createEventEmitter, throttle } from 'synstate';
581
553
 
582
554
  // Create event emitter
583
555
  const [refreshClicked, onRefreshClick] = createEventEmitter();
556
+ // refreshClicked: Observable<void>
557
+ // onRefreshClick: () => void
584
558
 
585
559
  // Subscribe to events
586
560
  refreshClicked.subscribe(() => {
@@ -588,7 +562,7 @@ refreshClicked.subscribe(() => {
588
562
  });
589
563
 
590
564
  // Throttle refresh clicks to prevent rapid successive executions
591
- const throttledRefresh = refreshClicked.pipe(throttleTime(2000));
565
+ const throttledRefresh = refreshClicked.pipe(throttle(2000));
592
566
 
593
567
  throttledRefresh.subscribe(() => {
594
568
  console.log('Executing refresh...');
@@ -607,37 +581,82 @@ const DataTable = (): React.JSX.Element => (
607
581
  );
608
582
  ```
609
583
 
610
- ## Why SynState?
584
+ ## API Reference
611
585
 
612
- ### Simple State Management, Not Complex Reactive Programming
586
+ ### State Management
613
587
 
614
- 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.
588
+ SynState provides simple, intuitive APIs for managing application state:
615
589
 
616
- 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 `createValueEmitter` - clean, straightforward APIs that developers understand immediately.
590
+ - **`createState`**: Create state with InitializedObservable and setter
591
+ - **`createReducer`**: Create state by reducer and initial value
592
+ - **`createBooleanState`**: Specialized state for boolean values
617
593
 
618
- **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.
594
+ ### Event System
619
595
 
620
- ### Key Differences from RxJS
596
+ Built-in event emitter for event-driven patterns:
621
597
 
622
- - **Focus on State & Events**: Designed for state management and event-driven architecture
623
- - **Simpler API**: Most use cases covered by `createState`, `createReducer`, and `createValueEmitter`
624
- - **Better Readability**: No need for complex operator chains in everyday code
625
- - **Optional Complexity**: Advanced features available when needed
598
+ - **`createValueEmitter`**: Create type-safe event emitters
599
+ - **`createEventEmitter`**: Create event emitters without payload
626
600
 
627
- ### Use Cases
601
+ ### Observable APIs
628
602
 
629
- **Use SynState when you need:**
603
+ For complex scenarios, SynState provides observable-based APIs:
630
604
 
631
- - Global state management across components
632
- - ✅ Event-driven communication between components
633
- - ✅ Type-safe event emitters
634
- - ✅ Redux-like state with reducers
635
- - ✅ Simple reactive patterns (debounce, throttle, etc.)
605
+ #### Creation Functions
636
606
 
637
- **Consider other solutions when:**
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
612
+
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
654
+
655
+ #### Utilities
638
656
 
639
- - You need complex stream processing (use RxJS)
640
- - 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
641
660
 
642
661
  ## Type Safety
643
662