@ersbeth/picoflow 1.1.2 → 2.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 (383) hide show
  1. package/.vscode/settings.json +3 -3
  2. package/CHANGELOG.md +43 -0
  3. package/README.md +2 -18
  4. package/biome.json +45 -35
  5. package/dist/picoflow.js +856 -1530
  6. package/dist/types/api/base/flowDisposable.d.ts +41 -0
  7. package/dist/types/api/base/flowDisposable.d.ts.map +1 -0
  8. package/dist/types/api/base/flowObservable.d.ts +27 -0
  9. package/dist/types/api/base/flowObservable.d.ts.map +1 -0
  10. package/dist/types/api/base/flowSubscribable.d.ts +79 -0
  11. package/dist/types/api/base/flowSubscribable.d.ts.map +1 -0
  12. package/dist/types/api/base/flowTracker.d.ts +8 -0
  13. package/dist/types/api/base/flowTracker.d.ts.map +1 -0
  14. package/dist/types/api/base/index.d.ts +5 -0
  15. package/dist/types/api/base/index.d.ts.map +1 -0
  16. package/dist/types/api/index.d.ts +3 -0
  17. package/dist/types/api/index.d.ts.map +1 -0
  18. package/dist/types/api/nodes/async/flowConstantAsync.d.ts +31 -0
  19. package/dist/types/api/nodes/async/flowConstantAsync.d.ts.map +1 -0
  20. package/dist/types/api/nodes/async/flowDerivationAsync.d.ts +37 -0
  21. package/dist/types/api/nodes/async/flowDerivationAsync.d.ts.map +1 -0
  22. package/dist/types/api/nodes/async/flowStateAsync.d.ts +41 -0
  23. package/dist/types/api/nodes/async/flowStateAsync.d.ts.map +1 -0
  24. package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts +30 -0
  25. package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts.map +1 -0
  26. package/dist/types/{flow → api}/nodes/async/index.d.ts +1 -2
  27. package/dist/types/api/nodes/async/index.d.ts.map +1 -0
  28. package/dist/types/api/nodes/collections/flowArray.d.ts +134 -0
  29. package/dist/types/api/nodes/collections/flowArray.d.ts.map +1 -0
  30. package/dist/types/api/nodes/collections/flowMap.d.ts +98 -0
  31. package/dist/types/api/nodes/collections/flowMap.d.ts.map +1 -0
  32. package/dist/types/api/nodes/collections/index.d.ts.map +1 -0
  33. package/dist/types/api/nodes/flowEffect.d.ts +28 -0
  34. package/dist/types/api/nodes/flowEffect.d.ts.map +1 -0
  35. package/dist/types/api/nodes/flowSignal.d.ts +25 -0
  36. package/dist/types/api/nodes/flowSignal.d.ts.map +1 -0
  37. package/dist/types/api/nodes/flowValue.d.ts +35 -0
  38. package/dist/types/api/nodes/flowValue.d.ts.map +1 -0
  39. package/dist/types/api/nodes/index.d.ts +8 -0
  40. package/dist/types/api/nodes/index.d.ts.map +1 -0
  41. package/dist/types/api/nodes/sync/flowConstant.d.ts +29 -0
  42. package/dist/types/api/nodes/sync/flowConstant.d.ts.map +1 -0
  43. package/dist/types/api/nodes/sync/flowDerivation.d.ts +36 -0
  44. package/dist/types/api/nodes/sync/flowDerivation.d.ts.map +1 -0
  45. package/dist/types/api/nodes/sync/flowState.d.ts +39 -0
  46. package/dist/types/api/nodes/sync/flowState.d.ts.map +1 -0
  47. package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts +28 -0
  48. package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts.map +1 -0
  49. package/dist/types/{flow → api}/nodes/sync/index.d.ts +1 -2
  50. package/dist/types/api/nodes/sync/index.d.ts.map +1 -0
  51. package/dist/types/api/nodes/utils.d.ts +22 -0
  52. package/dist/types/api/nodes/utils.d.ts.map +1 -0
  53. package/dist/types/base/disposable.d.ts +11 -0
  54. package/dist/types/base/disposable.d.ts.map +1 -0
  55. package/dist/types/base/executionStack.d.ts +14 -0
  56. package/dist/types/base/executionStack.d.ts.map +1 -0
  57. package/dist/types/base/index.d.ts +6 -0
  58. package/dist/types/base/index.d.ts.map +1 -0
  59. package/dist/types/base/node.d.ts +27 -0
  60. package/dist/types/base/node.d.ts.map +1 -0
  61. package/dist/types/base/observable.d.ts +37 -0
  62. package/dist/types/base/observable.d.ts.map +1 -0
  63. package/dist/types/base/observer.d.ts +25 -0
  64. package/dist/types/base/observer.d.ts.map +1 -0
  65. package/dist/types/converters/index.d.ts +2 -0
  66. package/dist/types/converters/index.d.ts.map +1 -0
  67. package/dist/types/converters/solid.d.ts +46 -0
  68. package/dist/types/converters/solid.d.ts.map +1 -0
  69. package/dist/types/index.d.ts +2 -63
  70. package/dist/types/index.d.ts.map +1 -1
  71. package/dist/types/nodes/arrayNode.d.ts +2 -0
  72. package/dist/types/nodes/arrayNode.d.ts.map +1 -0
  73. package/dist/types/nodes/effectNode.d.ts +2 -0
  74. package/dist/types/nodes/effectNode.d.ts.map +1 -0
  75. package/dist/types/nodes/index.d.ts +9 -0
  76. package/dist/types/nodes/index.d.ts.map +1 -0
  77. package/dist/types/nodes/mapNode.d.ts +2 -0
  78. package/dist/types/nodes/mapNode.d.ts.map +1 -0
  79. package/dist/types/nodes/signalNode.d.ts +2 -0
  80. package/dist/types/nodes/signalNode.d.ts.map +1 -0
  81. package/dist/types/nodes/valueAsyncNode.d.ts +2 -0
  82. package/dist/types/nodes/valueAsyncNode.d.ts.map +1 -0
  83. package/dist/types/nodes/valueNode.d.ts +2 -0
  84. package/dist/types/nodes/valueNode.d.ts.map +1 -0
  85. package/dist/types/nodes/valueSyncNode.d.ts +2 -0
  86. package/dist/types/nodes/valueSyncNode.d.ts.map +1 -0
  87. package/dist/types/schedulers/asyncResolver.d.ts +2 -0
  88. package/dist/types/schedulers/asyncResolver.d.ts.map +1 -0
  89. package/dist/types/schedulers/asyncScheduler.d.ts +2 -0
  90. package/dist/types/schedulers/asyncScheduler.d.ts.map +1 -0
  91. package/dist/types/schedulers/index.d.ts +5 -0
  92. package/dist/types/schedulers/index.d.ts.map +1 -0
  93. package/dist/types/schedulers/pendingError.d.ts +2 -0
  94. package/dist/types/schedulers/pendingError.d.ts.map +1 -0
  95. package/dist/types/schedulers/scheduler.d.ts +2 -0
  96. package/dist/types/schedulers/scheduler.d.ts.map +1 -0
  97. package/dist/types/schedulers/syncResolver.d.ts +2 -0
  98. package/dist/types/schedulers/syncResolver.d.ts.map +1 -0
  99. package/dist/types/schedulers/syncScheduler.d.ts +2 -0
  100. package/dist/types/schedulers/syncScheduler.d.ts.map +1 -0
  101. package/docs/.vitepress/config.mts +128 -93
  102. package/docs/api/functions/array.md +14 -37
  103. package/docs/api/functions/constant.md +13 -25
  104. package/docs/api/functions/constantAsync.md +69 -0
  105. package/docs/api/functions/derivation.md +14 -33
  106. package/docs/api/functions/derivationAsync.md +34 -0
  107. package/docs/api/functions/from.md +62 -153
  108. package/docs/api/functions/isDisposable.md +8 -30
  109. package/docs/api/functions/map.md +15 -36
  110. package/docs/api/functions/signal.md +8 -23
  111. package/docs/api/functions/state.md +43 -23
  112. package/docs/api/functions/stateAsync.md +69 -0
  113. package/docs/api/functions/subscribe.md +40 -0
  114. package/docs/api/functions/writableDerivation.md +33 -0
  115. package/docs/api/functions/writableDerivationAsync.md +34 -0
  116. package/docs/api/index.md +45 -102
  117. package/docs/api/interfaces/FlowArray.md +439 -0
  118. package/docs/api/interfaces/FlowConstant.md +220 -0
  119. package/docs/api/interfaces/FlowConstantAsync.md +221 -0
  120. package/docs/api/interfaces/FlowDerivation.md +241 -0
  121. package/docs/api/interfaces/FlowDerivationAsync.md +242 -0
  122. package/docs/api/interfaces/FlowDisposable.md +32 -38
  123. package/docs/api/interfaces/FlowEffect.md +64 -0
  124. package/docs/api/interfaces/FlowMap.md +374 -0
  125. package/docs/api/interfaces/FlowObservable.md +155 -0
  126. package/docs/api/interfaces/FlowSignal.md +156 -0
  127. package/docs/api/interfaces/FlowState.md +269 -0
  128. package/docs/api/interfaces/FlowStateAsync.md +268 -0
  129. package/docs/api/interfaces/FlowSubscribable.md +55 -0
  130. package/docs/api/interfaces/FlowTracker.md +61 -0
  131. package/docs/api/interfaces/FlowValue.md +222 -0
  132. package/docs/api/interfaces/FlowWritableDerivation.md +292 -0
  133. package/docs/api/interfaces/FlowWritableDerivationAsync.md +293 -0
  134. package/docs/api/type-aliases/DerivationFunction.md +28 -0
  135. package/docs/api/type-aliases/DerivationFunctionAsync.md +28 -0
  136. package/docs/api/type-aliases/FlowArrayAction.md +19 -8
  137. package/docs/api/type-aliases/FlowDataTracker.md +33 -0
  138. package/docs/api/type-aliases/FlowMapAction.md +48 -0
  139. package/docs/api/type-aliases/FlowOnDataListener.md +33 -0
  140. package/docs/api/type-aliases/FlowOnErrorListener.md +27 -0
  141. package/docs/api/type-aliases/FlowOnPendingListener.md +21 -0
  142. package/docs/api/type-aliases/FlowReadonly.md +22 -0
  143. package/docs/api/type-aliases/InitFunction.md +21 -0
  144. package/docs/api/type-aliases/InitFunctionAsync.md +21 -0
  145. package/docs/api/type-aliases/NotPromise.md +6 -3
  146. package/docs/api/type-aliases/UpdateFunction.md +27 -0
  147. package/docs/api/type-aliases/UpdateFunctionAsync.md +27 -0
  148. package/docs/api/typedoc-sidebar.json +1 -81
  149. package/docs/examples/examples.md +0 -2
  150. package/docs/guide/advanced/architecture.md +1234 -0
  151. package/docs/guide/advanced/migration-v2.md +204 -0
  152. package/docs/guide/advanced/solidjs.md +2 -88
  153. package/docs/guide/introduction/concepts.md +4 -3
  154. package/docs/guide/introduction/conventions.md +2 -33
  155. package/docs/guide/introduction/getting-started.md +28 -23
  156. package/docs/guide/introduction/lifecycle.md +16 -19
  157. package/docs/guide/primitives/array.md +102 -216
  158. package/docs/guide/primitives/constant.md +39 -212
  159. package/docs/guide/primitives/derivations.md +55 -122
  160. package/docs/guide/primitives/effects.md +155 -241
  161. package/docs/guide/primitives/map.md +64 -186
  162. package/docs/guide/primitives/overview.md +45 -128
  163. package/docs/guide/primitives/signal.md +51 -88
  164. package/docs/guide/primitives/state.md +34 -130
  165. package/package.json +56 -60
  166. package/src/api/base/flowDisposable.ts +44 -0
  167. package/src/api/base/flowObservable.ts +28 -0
  168. package/src/api/base/flowSubscribable.ts +87 -0
  169. package/src/api/base/flowTracker.ts +7 -0
  170. package/src/api/base/index.ts +4 -0
  171. package/src/{flow → api}/index.ts +0 -1
  172. package/src/api/nodes/async/flowConstantAsync.ts +36 -0
  173. package/src/api/nodes/async/flowDerivationAsync.ts +42 -0
  174. package/src/api/nodes/async/flowStateAsync.ts +47 -0
  175. package/src/api/nodes/async/flowWritableDerivationAsync.ts +33 -0
  176. package/src/{flow → api}/nodes/async/index.ts +1 -2
  177. package/src/api/nodes/collections/flowArray.ts +155 -0
  178. package/src/api/nodes/collections/flowMap.ts +115 -0
  179. package/src/api/nodes/flowEffect.ts +42 -0
  180. package/src/api/nodes/flowSignal.ts +28 -0
  181. package/src/api/nodes/flowValue.ts +36 -0
  182. package/src/api/nodes/index.ts +7 -0
  183. package/src/api/nodes/sync/flowConstant.ts +33 -0
  184. package/src/api/nodes/sync/flowDerivation.ts +41 -0
  185. package/src/api/nodes/sync/flowState.ts +45 -0
  186. package/src/api/nodes/sync/flowWritableDerivation.ts +31 -0
  187. package/src/{flow → api}/nodes/sync/index.ts +1 -2
  188. package/src/api/nodes/utils.ts +22 -0
  189. package/src/base/disposable.ts +18 -0
  190. package/src/base/executionStack.ts +42 -0
  191. package/src/base/index.ts +5 -0
  192. package/src/base/node.ts +98 -0
  193. package/src/base/observable.ts +87 -0
  194. package/src/base/observer.ts +51 -0
  195. package/src/converters/index.ts +1 -0
  196. package/src/converters/solid.ts +109 -0
  197. package/src/index.ts +2 -64
  198. package/src/nodes/arrayNode.ts +172 -0
  199. package/src/nodes/effectNode.ts +59 -0
  200. package/src/nodes/index.ts +8 -0
  201. package/src/nodes/mapNode.ts +127 -0
  202. package/src/nodes/signalNode.ts +21 -0
  203. package/src/nodes/valueAsyncNode.ts +88 -0
  204. package/src/nodes/valueNode.ts +144 -0
  205. package/src/nodes/valueSyncNode.ts +128 -0
  206. package/src/schedulers/asyncResolver.ts +78 -0
  207. package/src/schedulers/asyncScheduler.ts +66 -0
  208. package/src/schedulers/index.ts +4 -0
  209. package/src/schedulers/pendingError.ts +13 -0
  210. package/src/schedulers/scheduler.ts +9 -0
  211. package/src/schedulers/syncResolver.ts +69 -0
  212. package/src/schedulers/syncScheduler.ts +55 -0
  213. package/test/base/pendingError.test.ts +67 -0
  214. package/test/converters/solid.derivation.browser.test.tsx +69 -0
  215. package/test/converters/solid.node.test.ts +654 -0
  216. package/test/converters/solid.state.browser.test.tsx +1592 -0
  217. package/test/reactivity/flowSignal.test.ts +226 -0
  218. package/test/reactivity/nodes/async/asyncScheduler/asyncResolver.test.ts +593 -0
  219. package/test/reactivity/nodes/async/asyncScheduler/asyncScheduler.test.ts +317 -0
  220. package/test/reactivity/nodes/async/flowConstantAsync.test.ts +652 -0
  221. package/test/reactivity/nodes/async/flowDerivation.test.ts +898 -0
  222. package/test/reactivity/nodes/async/flowDerivationAsync.test.ts +1716 -0
  223. package/test/reactivity/nodes/async/flowStateAsync.test.ts +708 -0
  224. package/test/reactivity/nodes/async/flowWritableDerivationAsync.test.ts +614 -0
  225. package/test/reactivity/nodes/collections/flowArray.asyncStates.test.ts +1289 -0
  226. package/test/reactivity/nodes/collections/flowArray.scalars.test.ts +961 -0
  227. package/test/reactivity/nodes/collections/flowArray.states.test.ts +1035 -0
  228. package/test/reactivity/nodes/collections/flowMap.asyncStates.test.ts +960 -0
  229. package/test/reactivity/nodes/collections/flowMap.scalars.test.ts +775 -0
  230. package/test/reactivity/nodes/collections/flowMap.states.test.ts +958 -0
  231. package/test/reactivity/nodes/sync/flowConstant.test.ts +377 -0
  232. package/test/reactivity/nodes/sync/flowDerivation.test.ts +896 -0
  233. package/test/reactivity/nodes/sync/flowState.test.ts +341 -0
  234. package/test/reactivity/nodes/sync/flowWritableDerivation.test.ts +603 -0
  235. package/test/vitest.d.ts +10 -0
  236. package/tsconfig.json +31 -20
  237. package/typedoc.json +35 -35
  238. package/vite.config.ts +25 -23
  239. package/vitest.browser.config.ts +21 -0
  240. package/vitest.config.ts +12 -12
  241. package/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +0 -372
  242. package/.cursor/plans/update-js-e795d61b.plan.md +0 -567
  243. package/dist/types/flow/base/flowDisposable.d.ts +0 -67
  244. package/dist/types/flow/base/flowDisposable.d.ts.map +0 -1
  245. package/dist/types/flow/base/flowEffect.d.ts +0 -127
  246. package/dist/types/flow/base/flowEffect.d.ts.map +0 -1
  247. package/dist/types/flow/base/flowGraph.d.ts +0 -97
  248. package/dist/types/flow/base/flowGraph.d.ts.map +0 -1
  249. package/dist/types/flow/base/flowSignal.d.ts +0 -134
  250. package/dist/types/flow/base/flowSignal.d.ts.map +0 -1
  251. package/dist/types/flow/base/flowTracker.d.ts +0 -15
  252. package/dist/types/flow/base/flowTracker.d.ts.map +0 -1
  253. package/dist/types/flow/base/index.d.ts +0 -7
  254. package/dist/types/flow/base/index.d.ts.map +0 -1
  255. package/dist/types/flow/base/utils.d.ts +0 -20
  256. package/dist/types/flow/base/utils.d.ts.map +0 -1
  257. package/dist/types/flow/collections/flowArray.d.ts +0 -148
  258. package/dist/types/flow/collections/flowArray.d.ts.map +0 -1
  259. package/dist/types/flow/collections/flowMap.d.ts +0 -224
  260. package/dist/types/flow/collections/flowMap.d.ts.map +0 -1
  261. package/dist/types/flow/collections/index.d.ts.map +0 -1
  262. package/dist/types/flow/index.d.ts +0 -4
  263. package/dist/types/flow/index.d.ts.map +0 -1
  264. package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +0 -137
  265. package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +0 -1
  266. package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +0 -137
  267. package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +0 -1
  268. package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +0 -343
  269. package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +0 -1
  270. package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +0 -81
  271. package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +0 -1
  272. package/dist/types/flow/nodes/async/flowStateAsync.d.ts +0 -111
  273. package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +0 -1
  274. package/dist/types/flow/nodes/async/index.d.ts.map +0 -1
  275. package/dist/types/flow/nodes/index.d.ts +0 -3
  276. package/dist/types/flow/nodes/index.d.ts.map +0 -1
  277. package/dist/types/flow/nodes/sync/flowConstant.d.ts +0 -108
  278. package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +0 -1
  279. package/dist/types/flow/nodes/sync/flowDerivation.d.ts +0 -100
  280. package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +0 -1
  281. package/dist/types/flow/nodes/sync/flowNode.d.ts +0 -314
  282. package/dist/types/flow/nodes/sync/flowNode.d.ts.map +0 -1
  283. package/dist/types/flow/nodes/sync/flowReadonly.d.ts +0 -57
  284. package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +0 -1
  285. package/dist/types/flow/nodes/sync/flowState.d.ts +0 -96
  286. package/dist/types/flow/nodes/sync/flowState.d.ts.map +0 -1
  287. package/dist/types/flow/nodes/sync/index.d.ts.map +0 -1
  288. package/dist/types/solid/converters.d.ts +0 -57
  289. package/dist/types/solid/converters.d.ts.map +0 -1
  290. package/dist/types/solid/index.d.ts +0 -3
  291. package/dist/types/solid/index.d.ts.map +0 -1
  292. package/dist/types/solid/primitives.d.ts +0 -181
  293. package/dist/types/solid/primitives.d.ts.map +0 -1
  294. package/docs/api/classes/FlowArray.md +0 -489
  295. package/docs/api/classes/FlowConstant.md +0 -350
  296. package/docs/api/classes/FlowDerivation.md +0 -334
  297. package/docs/api/classes/FlowEffect.md +0 -100
  298. package/docs/api/classes/FlowMap.md +0 -512
  299. package/docs/api/classes/FlowObservable.md +0 -306
  300. package/docs/api/classes/FlowResource.md +0 -380
  301. package/docs/api/classes/FlowResourceAsync.md +0 -362
  302. package/docs/api/classes/FlowSignal.md +0 -160
  303. package/docs/api/classes/FlowState.md +0 -368
  304. package/docs/api/classes/FlowStream.md +0 -367
  305. package/docs/api/classes/FlowStreamAsync.md +0 -364
  306. package/docs/api/classes/SolidDerivation.md +0 -75
  307. package/docs/api/classes/SolidResource.md +0 -91
  308. package/docs/api/classes/SolidState.md +0 -71
  309. package/docs/api/classes/TrackingContext.md +0 -33
  310. package/docs/api/functions/effect.md +0 -49
  311. package/docs/api/functions/resource.md +0 -52
  312. package/docs/api/functions/resourceAsync.md +0 -50
  313. package/docs/api/functions/stream.md +0 -53
  314. package/docs/api/functions/streamAsync.md +0 -50
  315. package/docs/api/interfaces/SolidObservable.md +0 -19
  316. package/docs/api/type-aliases/FlowStreamDisposer.md +0 -15
  317. package/docs/api/type-aliases/FlowStreamSetter.md +0 -27
  318. package/docs/api/type-aliases/FlowStreamUpdater.md +0 -32
  319. package/docs/api/type-aliases/SolidGetter.md +0 -17
  320. package/docs/guide/primitives/resources.md +0 -858
  321. package/docs/guide/primitives/streams.md +0 -931
  322. package/src/flow/base/flowDisposable.ts +0 -71
  323. package/src/flow/base/flowEffect.ts +0 -171
  324. package/src/flow/base/flowGraph.ts +0 -288
  325. package/src/flow/base/flowSignal.ts +0 -207
  326. package/src/flow/base/flowTracker.ts +0 -17
  327. package/src/flow/base/index.ts +0 -6
  328. package/src/flow/base/utils.ts +0 -19
  329. package/src/flow/collections/flowArray.ts +0 -409
  330. package/src/flow/collections/flowMap.ts +0 -398
  331. package/src/flow/nodes/async/flowConstantAsync.ts +0 -142
  332. package/src/flow/nodes/async/flowDerivationAsync.ts +0 -143
  333. package/src/flow/nodes/async/flowNodeAsync.ts +0 -474
  334. package/src/flow/nodes/async/flowReadonlyAsync.ts +0 -81
  335. package/src/flow/nodes/async/flowStateAsync.ts +0 -116
  336. package/src/flow/nodes/await/advanced/index.ts +0 -5
  337. package/src/flow/nodes/await/advanced/resource.ts +0 -134
  338. package/src/flow/nodes/await/advanced/resourceAsync.ts +0 -109
  339. package/src/flow/nodes/await/advanced/stream.ts +0 -188
  340. package/src/flow/nodes/await/advanced/streamAsync.ts +0 -176
  341. package/src/flow/nodes/await/flowConstantAwait.ts +0 -154
  342. package/src/flow/nodes/await/flowDerivationAwait.ts +0 -154
  343. package/src/flow/nodes/await/flowNodeAwait.ts +0 -508
  344. package/src/flow/nodes/await/flowReadonlyAwait.ts +0 -89
  345. package/src/flow/nodes/await/flowStateAwait.ts +0 -130
  346. package/src/flow/nodes/await/index.ts +0 -5
  347. package/src/flow/nodes/index.ts +0 -3
  348. package/src/flow/nodes/sync/flowConstant.ts +0 -111
  349. package/src/flow/nodes/sync/flowDerivation.ts +0 -105
  350. package/src/flow/nodes/sync/flowNode.ts +0 -439
  351. package/src/flow/nodes/sync/flowReadonly.ts +0 -57
  352. package/src/flow/nodes/sync/flowState.ts +0 -101
  353. package/src/solid/converters.ts +0 -148
  354. package/src/solid/index.ts +0 -2
  355. package/src/solid/primitives.ts +0 -215
  356. package/test/base/flowEffect.test.ts +0 -108
  357. package/test/base/flowGraph.test.ts +0 -485
  358. package/test/base/flowSignal.test.ts +0 -372
  359. package/test/collections/flowArray.asyncStates.test.ts +0 -1553
  360. package/test/collections/flowArray.scalars.test.ts +0 -1129
  361. package/test/collections/flowArray.states.test.ts +0 -1365
  362. package/test/collections/flowMap.asyncStates.test.ts +0 -1105
  363. package/test/collections/flowMap.scalars.test.ts +0 -877
  364. package/test/collections/flowMap.states.test.ts +0 -1097
  365. package/test/nodes/async/flowConstantAsync.test.ts +0 -860
  366. package/test/nodes/async/flowDerivationAsync.test.ts +0 -1517
  367. package/test/nodes/async/flowStateAsync.test.ts +0 -1387
  368. package/test/nodes/await/advanced/resource.test.ts +0 -129
  369. package/test/nodes/await/advanced/resourceAsync.test.ts +0 -108
  370. package/test/nodes/await/advanced/stream.test.ts +0 -198
  371. package/test/nodes/await/advanced/streamAsync.test.ts +0 -196
  372. package/test/nodes/await/flowConstantAwait.test.ts +0 -643
  373. package/test/nodes/await/flowDerivationAwait.test.ts +0 -1583
  374. package/test/nodes/await/flowStateAwait.test.ts +0 -999
  375. package/test/nodes/mixed/derivation.test.ts +0 -1527
  376. package/test/nodes/sync/flowConstant.test.ts +0 -620
  377. package/test/nodes/sync/flowDerivation.test.ts +0 -1373
  378. package/test/nodes/sync/flowState.test.ts +0 -945
  379. package/test/solid/converters.test.ts +0 -721
  380. package/test/solid/primitives.test.ts +0 -1031
  381. /package/dist/types/{flow → api/nodes}/collections/index.d.ts +0 -0
  382. /package/docs/guide/advanced/{upgrading.md → migration-v1.md} +0 -0
  383. /package/src/{flow → api/nodes}/collections/index.ts +0 -0
@@ -0,0 +1,1234 @@
1
+ # Architecture
2
+
3
+ This document provides a deep dive into PicoFlow's internal architecture. Whether you're a curious user wanting to understand how things work under the hood, or a potential contributor looking to get oriented before diving into the source code, this guide will help you understand the core concepts, design patterns, and implementation details that make PicoFlow tick.
4
+
5
+ ## Why Understanding Architecture Matters
6
+
7
+ Understanding PicoFlow's architecture helps you:
8
+
9
+ - **Debug effectively** - Know where to look when things go wrong
10
+ - **Optimize performance** - Understand the performance implications of different patterns
11
+ - **Contribute confidently** - Navigate the codebase with a mental model
12
+ - **Make better design decisions** - Use PicoFlow in ways that align with its design
13
+
14
+ ## Document Organization
15
+
16
+ This guide progresses from high-level concepts to implementation details:
17
+
18
+ 1. **Architecture Overview** - The layered structure
19
+ 2. **Core Concepts** - Fundamental patterns and ideas
20
+ 3. **Layer Deep Dives** - Detailed exploration of each layer
21
+ 4. **Design Patterns** - Key patterns used throughout
22
+ 5. **Data Flows** - How data moves through the system
23
+ 6. **For Contributors** - Practical guidance for contributing
24
+
25
+ ## Architecture Overview
26
+
27
+ PicoFlow follows a **layered architecture** with clear separation of concerns. Each layer has a specific responsibility and builds upon the layer below it.
28
+
29
+ ```mermaid
30
+ graph TB
31
+ API["Public API Layer<br/>(src/api/)<br/>────────────────<br/>Factory functions & Interfaces<br/>state(), derivation(), etc."]
32
+ Nodes["Nodes Layer<br/>(src/nodes/)<br/>────────────────<br/>Concrete implementations<br/>ValueSyncNode, EffectNode, etc."]
33
+ Base["Base Layer<br/>(src/base/)<br/>────────────────<br/>Abstract classes & patterns<br/>Observable, Observer, Node"]
34
+ Schedulers["Schedulers Layer<br/>(src/schedulers/)<br/>────────────────<br/>Computation lifecycle<br/>SyncScheduler, AsyncScheduler"]
35
+
36
+ API --> Nodes
37
+ Nodes --> Base
38
+ Nodes --> Schedulers
39
+ Base --> Schedulers
40
+
41
+ style API fill:#e1f5ff
42
+ style Nodes fill:#fff4e1
43
+ style Base fill:#ffe1f5
44
+ style Schedulers fill:#e1ffe1
45
+ ```
46
+
47
+ ### Layer Responsibilities
48
+
49
+ **Public API Layer** (`src/api/`)
50
+ - Factory functions that users call (`state()`, `derivation()`, etc.)
51
+ - TypeScript interfaces that define contracts (`FlowState`, `FlowObservable`)
52
+ - Hides implementation details from users
53
+ - Provides a stable, semantic API
54
+
55
+ **Nodes Layer** (`src/nodes/`)
56
+ - Concrete implementations of reactive primitives
57
+ - Each primitive type has its own class (`ValueSyncNode`, `EffectNode`, etc.)
58
+ - Manages the reactive value lifecycle
59
+ - Coordinates between Base and Schedulers
60
+
61
+ **Base Layer** (`src/base/`)
62
+ - Abstract classes implementing core patterns
63
+ - Observable/Observer pattern implementation
64
+ - Dependency graph management
65
+ - Execution batching and ordering
66
+
67
+ **Schedulers Layer** (`src/schedulers/`)
68
+ - Manages computation execution lifecycle
69
+ - Handles synchronous vs asynchronous execution
70
+ - Prevents race conditions with iteration tracking
71
+ - Provides consistent promise settling behavior
72
+
73
+ ## Core Concepts
74
+
75
+ ### The Observer Pattern
76
+
77
+ PicoFlow is built on the **Observer Pattern**, where objects (observables) can notify other objects (observers) when their state changes.
78
+
79
+ ```mermaid
80
+ graph LR
81
+ Observable["Observable<br/>(can be watched)"]
82
+ Observer1["Observer 1<br/>(watches changes)"]
83
+ Observer2["Observer 2<br/>(watches changes)"]
84
+ Observer3["Observer 3<br/>(watches changes)"]
85
+
86
+ Observable -->|notifies| Observer1
87
+ Observable -->|notifies| Observer2
88
+ Observable -->|notifies| Observer3
89
+
90
+ style Observable fill:#ffeb99
91
+ style Observer1 fill:#99e1ff
92
+ style Observer2 fill:#99e1ff
93
+ style Observer3 fill:#99e1ff
94
+ ```
95
+
96
+ **Key insight**: In PicoFlow, most primitives are *both* observable AND observer. A derivation observes a state (it's an observer), but can itself be observed by an effect (it's an observable).
97
+
98
+ ```typescript
99
+ // $count is an Observable
100
+ const $count = state(0)
101
+
102
+ // $doubled is both:
103
+ // - An Observer (observes $count)
104
+ // - An Observable (can be observed by others)
105
+ const $doubled = derivation((t) => $count.get(t) * 2)
106
+
107
+ // effect is an Observer
108
+ const fx = subscribe(
109
+ (t) => $doubled.get(t),
110
+ (value) => console.log(value)
111
+ )
112
+ ```
113
+
114
+ ### Dependency Graph
115
+
116
+ Primitives form a **directed acyclic graph (DAG)** of dependencies:
117
+
118
+ ```mermaid
119
+ graph TB
120
+ State1["state(0)<br/>Observable"]
121
+ State2["state(1)<br/>Observable"]
122
+ Deriv["derivation()<br/>Observer + Observable"]
123
+ Effect["effect()<br/>Observer"]
124
+
125
+ State1 --> Deriv
126
+ State2 --> Deriv
127
+ Deriv --> Effect
128
+
129
+ style State1 fill:#ffe1e1
130
+ style State2 fill:#ffe1e1
131
+ style Deriv fill:#e1ffe1
132
+ style Effect fill:#e1e1ff
133
+ ```
134
+
135
+ When a state changes:
136
+ 1. It notifies its dependents (the derivation)
137
+ 2. The derivation recomputes and notifies its dependents (the effect)
138
+ 3. The effect re-executes
139
+
140
+ This creates a **push-based** reactive system where changes automatically propagate through the graph.
141
+
142
+ ### Reactive Tracking
143
+
144
+ How does PicoFlow know which primitives depend on which? Through **automatic dependency tracking**.
145
+
146
+ When you call `get(tracker)` on a primitive, it registers itself as a dependency:
147
+
148
+ ```typescript
149
+ const $count = state(0)
150
+
151
+ // When this derivation executes...
152
+ const $doubled = derivation((tracker) => {
153
+ // ...this get() call automatically registers $count as a dependency
154
+ return $count.get(tracker) * 2
155
+ })
156
+ ```
157
+
158
+ **The FlowTracker** is the key. It's passed to all computation functions and used to record dependencies:
159
+
160
+ ```mermaid
161
+ sequenceDiagram
162
+ participant D as Derivation
163
+ participant T as Tracker
164
+ participant S as State
165
+
166
+ D->>D: execute()
167
+ D->>S: get(tracker)
168
+ S->>T: registerDependency(this)
169
+ T->>T: Add state to dependencies
170
+ S->>D: Return value
171
+ ```
172
+
173
+ This automatic tracking means you don't manually wire up subscriptions - just read values and PicoFlow handles the rest.
174
+
175
+ ## Base Layer (src/base/)
176
+
177
+ The base layer provides the fundamental abstractions that all reactive primitives build upon.
178
+
179
+ ### Disposable
180
+
181
+ The simplest building block - explicit resource management:
182
+
183
+ ```typescript
184
+ abstract class Disposable {
185
+ protected _disposed = false
186
+
187
+ get disposed(): boolean {
188
+ return this._disposed
189
+ }
190
+
191
+ dispose(): void {
192
+ if (this._disposed) throw new Error("Already disposed")
193
+ this._disposed = true
194
+ }
195
+ }
196
+ ```
197
+
198
+ **Why this matters**: Every PicoFlow primitive is disposable. This prevents memory leaks by allowing explicit cleanup of subscriptions and resources.
199
+
200
+ ### Observable
201
+
202
+ Manages a list of dependents and notifies them of changes:
203
+
204
+ ```typescript
205
+ abstract class Observable<T> extends Disposable {
206
+ private _dependents = new Set<IObserver>()
207
+
208
+ registerDependent(dependent: IObserver): void {
209
+ this._dependents.add(dependent)
210
+ }
211
+
212
+ notifyDependents(): void {
213
+ this._dependents.forEach(dep => dep.notify())
214
+ }
215
+
216
+ watch(tracker: FlowTracker): void {
217
+ (tracker as IObserver).registerDependency(this)
218
+ }
219
+ }
220
+ ```
221
+
222
+ **Key methods**:
223
+ - `watch()` - Register this observable as a dependency of a tracker
224
+ - `trigger()` - Manually notify all dependents
225
+ - `notifyDependents()` - Internal method to propagate changes
226
+
227
+ ### Observer
228
+
229
+ Manages a list of dependencies and reacts to changes:
230
+
231
+ ```typescript
232
+ abstract class Observer extends Disposable {
233
+ private _dependencies = new Set<IObservable<unknown>>()
234
+
235
+ registerDependency(dependency: IObservable<unknown>): void {
236
+ this._dependencies.add(dependency)
237
+ dependency.registerDependent(this)
238
+ }
239
+
240
+ clearDependencies(): void {
241
+ this._dependencies.forEach(dep =>
242
+ dep.unregisterDependent(this)
243
+ )
244
+ this._dependencies.clear()
245
+ }
246
+
247
+ abstract notify(): void
248
+ abstract execute(): void
249
+ }
250
+ ```
251
+
252
+ **Key methods**:
253
+ - `registerDependency()` - Add a new dependency
254
+ - `clearDependencies()` - Clear all dependencies (before recomputing)
255
+ - `notify()` - Called when a dependency changes
256
+ - `execute()` - Run the observer's computation
257
+
258
+ ### Node
259
+
260
+ The `Node` class combines Observable and Observer, creating primitives that can both watch others and be watched:
261
+
262
+ ```typescript
263
+ abstract class Node<T> extends Disposable
264
+ implements IObservable<T>, IObserver {
265
+
266
+ private _dependencies = new Set<IObservable<unknown>>()
267
+ private _dependents = new Set<IObserver>()
268
+
269
+ // Observable behavior
270
+ registerDependent(dependent: IObserver): void { /*...*/ }
271
+ notifyDependents(): void { /*...*/ }
272
+
273
+ // Observer behavior
274
+ registerDependency(dependency: IObservable<unknown>): void { /*...*/ }
275
+ notify(): void {
276
+ if (this.status === "dirty") return
277
+ this.status = "dirty"
278
+ this.notifyDependents()
279
+ }
280
+
281
+ abstract execute(): void
282
+ abstract subscribe(...): () => void
283
+ }
284
+ ```
285
+
286
+ This dual nature is what enables chaining: state → derivation → derivation → effect.
287
+
288
+ ### ExecutionStack
289
+
290
+ The `ExecutionStack` is a global singleton that batches reactive updates to prevent unnecessary cascading executions:
291
+
292
+ ```typescript
293
+ class ExecutionStack {
294
+ private static _pendingQueue: IObserver[] = []
295
+ private static _effectQueue: IObserver[] = []
296
+ private static _executionScheduled?: Promise<void>
297
+
298
+ static pushPending(node: IObserver): void {
299
+ this._scheduleExecution()
300
+ this._pendingQueue.push(node)
301
+ }
302
+
303
+ static pushEffect(effect: IObserver): void {
304
+ this._scheduleExecution()
305
+ this._effectQueue.push(effect)
306
+ }
307
+
308
+ private static _execute(): void {
309
+ // First, resolve all pending computations
310
+ this._pendingQueue.forEach(node => node.execute())
311
+ this._pendingQueue.length = 0
312
+
313
+ // Then, run effects
314
+ this._effectQueue.forEach(effect => effect.execute())
315
+ this._effectQueue.length = 0
316
+ }
317
+ }
318
+ ```
319
+
320
+ **Why two queues?**
321
+ - **Pending queue**: For value computations (derivations) that other things depend on
322
+ - **Effect queue**: For side effects that don't produce values
323
+
324
+ This ordering ensures that all values are computed before any effects run, preventing effects from running with stale data.
325
+
326
+ **Async scheduling**: Executions are batched with `setTimeout(..., 0)` to:
327
+ - Allow the current call stack to complete
328
+ - Batch multiple synchronous changes into one execution
329
+ - Prevent stack overflow from deep reactive chains
330
+
331
+ ## Nodes Layer (src/nodes/)
332
+
333
+ The nodes layer contains concrete implementations of reactive primitives.
334
+
335
+ ### ValueNode
336
+
337
+ The abstract `ValueNode` is the foundation for all value-holding primitives (state, derivation, constant, etc.):
338
+
339
+ ```typescript
340
+ abstract class ValueNode<T> extends Node<T> {
341
+ protected abstract _scheduler: Scheduler
342
+ protected _value?: NotPromise<T>
343
+ protected _error?: unknown
344
+ protected _status: ObservableStatus = "resolved"
345
+
346
+ get(tracker: FlowTracker): T {
347
+ this.watch(tracker)
348
+
349
+ switch (this.status) {
350
+ case "resolved": return this._value as T
351
+ case "error": throw this._error
352
+ case "pending": throw new PendingError(this._scheduler.settled)
353
+ case "dirty":
354
+ this.execute()
355
+ // Check status again after execution
356
+ // ...
357
+ }
358
+ }
359
+
360
+ async pick(): Promise<T> {
361
+ switch (this.status) {
362
+ case "resolved": return this._value as T
363
+ case "pending":
364
+ await this._scheduler.settled
365
+ return this._value as T
366
+ case "dirty":
367
+ this.execute()
368
+ // ...
369
+ }
370
+ }
371
+ }
372
+ ```
373
+
374
+ **Value states**:
375
+ - `resolved` - Has a computed value, ready to use
376
+ - `pending` - Async computation in progress
377
+ - `error` - Computation failed
378
+ - `dirty` - Needs recomputation (dependency changed)
379
+
380
+ **Two access methods**:
381
+ - `get(tracker)` - Reactive read that registers a dependency
382
+ - `pick()` - Non-reactive async read
383
+
384
+ ### ValueSyncNode
385
+
386
+ Handles synchronous computations:
387
+
388
+ ```typescript
389
+ class ValueSyncNode<T> extends ValueNode<T> {
390
+ protected _scheduler: SyncScheduler<T>
391
+ private _compute: () => T
392
+
393
+ constructor(valueOrCompute: T | ComputeFunction<T>) {
394
+ super()
395
+
396
+ if (typeof valueOrCompute === "function") {
397
+ this._compute = () => valueOrCompute(this, this._value)
398
+ } else {
399
+ this._compute = () => valueOrCompute
400
+ }
401
+
402
+ this._scheduler = new SyncScheduler(
403
+ () => this._compute(),
404
+ (value) => this._onResolve(value),
405
+ (error) => this._onReject(error)
406
+ )
407
+ }
408
+
409
+ set(value: T): void {
410
+ this._scheduler.overwrite(value)
411
+ this.notifyDependents()
412
+ }
413
+
414
+ refresh(): void {
415
+ this.execute()
416
+ this.notifyDependents()
417
+ }
418
+ }
419
+ ```
420
+
421
+ Used by: `state()`, `constant()`, `derivation()`, `writableDerivation()`
422
+
423
+ ### ValueAsyncNode
424
+
425
+ Handles asynchronous computations with promises:
426
+
427
+ ```typescript
428
+ class ValueAsyncNode<T> extends ValueNode<T> {
429
+ protected _scheduler: AsyncScheduler<T>
430
+ private _compute: () => Promise<T>
431
+
432
+ constructor(promiseOrCompute: Promise<T> | ComputeFunctionAsync<T>) {
433
+ super()
434
+
435
+ if (typeof promiseOrCompute === "function") {
436
+ this._compute = () => promiseOrCompute(this, this._value)
437
+ } else {
438
+ this._compute = () => promiseOrCompute
439
+ }
440
+
441
+ this._scheduler = new AsyncScheduler(
442
+ () => this._compute(),
443
+ (value) => {
444
+ this._onResolve(value)
445
+ this.notifyDependents() // Async: notify after resolution
446
+ },
447
+ (error) => {
448
+ this._onReject(error)
449
+ this.notifyDependents()
450
+ }
451
+ )
452
+ }
453
+
454
+ set(promise: Promise<T>): void {
455
+ this._scheduler.overwrite(promise)
456
+ this.notifyDependents() // Immediately notify (status becomes pending)
457
+ }
458
+ }
459
+ ```
460
+
461
+ **Key difference from sync**: Async nodes notify dependents twice:
462
+ 1. Immediately when computation starts (status becomes "pending")
463
+ 2. Again when computation completes (status becomes "resolved" or "error")
464
+
465
+ Used by: `stateAsync()`, `constantAsync()`, `derivationAsync()`, `writableDerivationAsync()`
466
+
467
+ ### EffectNode
468
+
469
+ Effects are observers that execute side effects but don't produce values:
470
+
471
+ ```typescript
472
+ class EffectNode<T> extends Observer {
473
+ private _data: FlowDataTracker<T>
474
+ private _onData: FlowOnDataListener<T>
475
+ private _onError?: FlowOnErrorListener
476
+ private _onPending?: FlowOnPendingListener
477
+
478
+ constructor(
479
+ data: FlowDataTracker<T>,
480
+ onData: FlowOnDataListener<T>,
481
+ onError?: FlowOnErrorListener,
482
+ onPending?: FlowOnPendingListener
483
+ ) {
484
+ super()
485
+ this._data = data
486
+ this._onData = onData
487
+ this._onError = onError
488
+ this._onPending = onPending
489
+ this.execute() // Run immediately
490
+ }
491
+
492
+ execute(): void {
493
+ try {
494
+ this.clearDependencies() // Clear old dependencies
495
+ const data = this._data(this) // Track new dependencies
496
+ this._onData(data)
497
+ } catch (error) {
498
+ if (error instanceof PendingError) {
499
+ this._onPending?.()
500
+ } else {
501
+ if (this._onError) {
502
+ this._onError(error)
503
+ } else {
504
+ throw error // Re-throw if no error handler
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ notify(): void {
511
+ ExecutionStack.pushEffect(this)
512
+ }
513
+ }
514
+ ```
515
+
516
+ Used by: `subscribe()` (the public API for effects)
517
+
518
+ ### SignalNode
519
+
520
+ Signals are events without values:
521
+
522
+ ```typescript
523
+ class SignalNode extends Observable<void> {
524
+ subscribe(
525
+ onTrigger: FlowOnDataListener<void>,
526
+ onError?: FlowOnErrorListener,
527
+ onPending?: FlowOnPendingListener
528
+ ): () => void {
529
+ const effect = new EffectNode(
530
+ (t) => this.watch(t),
531
+ onTrigger,
532
+ onError,
533
+ onPending
534
+ )
535
+ return () => effect.dispose()
536
+ }
537
+ }
538
+ ```
539
+
540
+ Signals are useful for:
541
+ - User actions (button clicks, form submissions)
542
+ - Timer events
543
+ - Custom events that don't carry data
544
+
545
+ ### Collections (ArrayNode, MapNode)
546
+
547
+ Collections provide fine-grained reactivity with mutation tracking:
548
+
549
+ ```typescript
550
+ class ArrayNode<T> extends ValueSyncNode<T[]> {
551
+ $lastAction: ValueSyncNode<FlowArrayAction<T>>
552
+
553
+ push(item: T): void {
554
+ this._value.push(item)
555
+ this.notifyDependents()
556
+ this.$lastAction.set({
557
+ type: "push",
558
+ addedItem: item
559
+ })
560
+ }
561
+
562
+ // Similar for pop, shift, unshift, splice, etc.
563
+ }
564
+ ```
565
+
566
+ The `$lastAction` primitive allows you to react specifically to certain mutation types:
567
+
568
+ ```typescript
569
+ const $todos = array<Todo>([])
570
+
571
+ // React to any change
572
+ subscribe(
573
+ (t) => $todos.get(t),
574
+ (todos) => console.log("Todos changed", todos)
575
+ )
576
+
577
+ // React only to additions
578
+ subscribe(
579
+ (t) => {
580
+ const action = $todos.$lastAction.get(t)
581
+ if (action.type === "push") return action.addedItem
582
+ throw new PendingError(Promise.resolve())
583
+ },
584
+ (item) => console.log("Todo added", item)
585
+ )
586
+ ```
587
+
588
+ ## Schedulers Layer (src/schedulers/)
589
+
590
+ Schedulers manage the lifecycle of computations, handling both synchronous and asynchronous execution.
591
+
592
+ ### Why Schedulers?
593
+
594
+ Schedulers solve several problems:
595
+
596
+ 1. **Lifecycle management** - Track whether a computation is pending, resolved, or errored
597
+ 2. **Async/sync separation** - Different execution strategies for sync vs async
598
+ 3. **Race conditions** - Prevent stale results from async operations
599
+ 4. **Promise settling** - Provide consistent `settled` promise for awaiting completion
600
+
601
+ ### Scheduler Interface
602
+
603
+ All schedulers implement this contract:
604
+
605
+ ```typescript
606
+ interface Scheduler {
607
+ compute(): void // Start/restart computation
608
+ dispose(): void // Clean up resources
609
+ settled: Promise<void> // Promise that resolves when computation settles
610
+ }
611
+ ```
612
+
613
+ ### SyncScheduler & SyncResolver
614
+
615
+ For synchronous computations:
616
+
617
+ ```mermaid
618
+ sequenceDiagram
619
+ participant N as Node
620
+ participant SS as SyncScheduler
621
+ participant SR as SyncResolver
622
+
623
+ N->>SS: compute()
624
+ SS->>SR: compute()
625
+ SR->>SR: _compute()
626
+ alt Success
627
+ SR->>SS: onResolve(value)
628
+ SS->>N: callback with value
629
+ else Error
630
+ SR->>SS: onReject(error)
631
+ SS->>N: callback with error
632
+ end
633
+ SS->>SS: settled.resolve()
634
+ ```
635
+
636
+ **SyncResolver iteration tracking**:
637
+
638
+ ```typescript
639
+ class SyncResolver<T> {
640
+ private _iteration = 0
641
+
642
+ compute(): void {
643
+ this._iteration++
644
+ const currentIteration = this._iteration
645
+
646
+ try {
647
+ const value = this._compute()
648
+ // Only use result if no newer computation started
649
+ if (this._iteration === currentIteration) {
650
+ this._onValue(value)
651
+ }
652
+ } catch (error) {
653
+ if (this._iteration === currentIteration) {
654
+ this._onError(error)
655
+ }
656
+ }
657
+ }
658
+ }
659
+ ```
660
+
661
+ This prevents race conditions if `compute()` is called multiple times rapidly.
662
+
663
+ ### AsyncScheduler & AsyncResolver
664
+
665
+ For asynchronous computations:
666
+
667
+ ```mermaid
668
+ sequenceDiagram
669
+ participant N as Node
670
+ participant AS as AsyncScheduler
671
+ participant AR as AsyncResolver
672
+
673
+ N->>AS: compute()
674
+ AS->>AR: compute()
675
+ AR->>AR: _compute() (returns Promise)
676
+ AR-->>AS: Promise pending
677
+
678
+ Note over AR: Async work happening...
679
+
680
+ AR->>AS: Promise resolves
681
+ AS->>N: onResolve(value)
682
+ AS->>AS: settled.resolve()
683
+ ```
684
+
685
+ **AsyncResolver race condition prevention**:
686
+
687
+ ```typescript
688
+ class AsyncResolver<T> {
689
+ private _iteration = 0
690
+
691
+ compute(): void {
692
+ this._iteration++
693
+ const currentIteration = this._iteration
694
+
695
+ this._compute()
696
+ .then(value => {
697
+ // Only use result if no newer computation started
698
+ if (this._iteration === currentIteration && !this._aborted) {
699
+ this._computed.resolve(value)
700
+ }
701
+ })
702
+ .catch(error => {
703
+ if (this._iteration === currentIteration && !this._aborted) {
704
+ this._computed.reject(error)
705
+ }
706
+ })
707
+ }
708
+ }
709
+ ```
710
+
711
+ If a new computation starts while an async operation is in flight, the old result is discarded.
712
+
713
+ ### PendingError
714
+
715
+ `PendingError` is a special error type used to propagate pending states:
716
+
717
+ ```typescript
718
+ class PendingError extends Error {
719
+ readonly pendingPromise: Promise<unknown>
720
+
721
+ constructor(promise: Promise<unknown>) {
722
+ super("[PicoFlow] PendingResolver pending")
723
+ this.pendingPromise = promise
724
+ }
725
+ }
726
+ ```
727
+
728
+ **How it's used**:
729
+
730
+ ```typescript
731
+ const $user = stateAsync(fetchUser()) // Async state
732
+
733
+ const $userName = derivation((t) => {
734
+ const user = $user.get(t) // Throws PendingError while loading
735
+ return user.name
736
+ })
737
+
738
+ subscribe(
739
+ (t) => $userName.get(t),
740
+ (name) => console.log("Name:", name),
741
+ (error) => console.error("Error:", error),
742
+ () => console.log("Loading...") // Called when PendingError is caught
743
+ )
744
+ ```
745
+
746
+ When `$user` is pending, `$user.get()` throws a `PendingError`. This propagates up, the effect catches it, and calls the `onPending` callback.
747
+
748
+ ## Public API Layer (src/api/)
749
+
750
+ The API layer provides the user-facing interface to PicoFlow.
751
+
752
+ ### Factory Pattern
753
+
754
+ Users never instantiate node classes directly. Instead, they use factory functions:
755
+
756
+ ```typescript
757
+ // In src/api/nodes/sync/flowState.ts
758
+ export function state<T>(value: T): FlowState<T> {
759
+ return new ValueSyncNode(value)
760
+ }
761
+
762
+ // In src/api/nodes/sync/flowDerivation.ts
763
+ export function derivation<T>(
764
+ compute: DerivationFunction<T>
765
+ ): FlowDerivation<T> {
766
+ return new ValueSyncNode(compute)
767
+ }
768
+ ```
769
+
770
+ **Benefits**:
771
+ - **Encapsulation** - Users don't need to know about implementation classes
772
+ - **Flexibility** - We can change implementations without breaking the API
773
+ - **Semantic naming** - `state()` is clearer than `new ValueSyncNode()`
774
+ - **Type safety** - Return types are specific interfaces, not implementation classes
775
+
776
+ ### Interface Design
777
+
778
+ Public interfaces define contracts, not implementations:
779
+
780
+ ```typescript
781
+ // Public interface
782
+ export interface FlowState<T> extends FlowValue<T> {
783
+ set(value: T): void
784
+ set(updater: UpdateFunction<T>): void
785
+ }
786
+
787
+ // Users interact with FlowState interface
788
+ // Implementation (ValueSyncNode) is hidden
789
+ const $count: FlowState<number> = state(0)
790
+ ```
791
+
792
+ This separation allows:
793
+ - Documentation to focus on contracts, not implementations
794
+ - Implementation changes without API changes
795
+ - Clear separation of public vs internal
796
+
797
+ ### Type Hierarchy
798
+
799
+ ```mermaid
800
+ graph TB
801
+ FlowDisposable["FlowDisposable<br/>disposed, dispose()"]
802
+ FlowSubscribable["FlowSubscribable<br/>subscribe()"]
803
+ FlowObservable["FlowObservable<br/>watch(), trigger()"]
804
+ FlowTracker["FlowTracker<br/>(for dependency tracking)"]
805
+ FlowValue["FlowValue<br/>get(), pick()"]
806
+
807
+ FlowState["FlowState<br/>set()"]
808
+ FlowDerivation["FlowDerivation<br/>refresh()"]
809
+ FlowConstant["FlowConstant<br/>(no mutations)"]
810
+
811
+ FlowDisposable --> FlowObservable
812
+ FlowSubscribable --> FlowObservable
813
+ FlowTracker --> FlowDisposable
814
+ FlowObservable --> FlowValue
815
+
816
+ FlowValue --> FlowState
817
+ FlowValue --> FlowDerivation
818
+ FlowValue --> FlowConstant
819
+
820
+ style FlowDisposable fill:#ffe1e1
821
+ style FlowObservable fill:#e1ffe1
822
+ style FlowValue fill:#e1e1ff
823
+ style FlowState fill:#fff4e1
824
+ ```
825
+
826
+ This hierarchy expresses capabilities:
827
+ - All primitives are **Disposable**
828
+ - All primitives are **Subscribable** (can create effects)
829
+ - All primitives are **Observable** (can be watched)
830
+ - Value primitives add **get/pick** methods
831
+ - Specific types add their own methods (set, refresh, etc.)
832
+
833
+ ## Key Design Patterns
834
+
835
+ ### Factory Pattern
836
+
837
+ **Intent**: Provide a simple creation interface while hiding complex construction logic.
838
+
839
+ **Implementation**:
840
+ ```typescript
841
+ // Factory function (public API)
842
+ export function state<T>(value: T): FlowState<T> {
843
+ return new ValueSyncNode(value)
844
+ }
845
+
846
+ // Complex internal class (hidden from users)
847
+ class ValueSyncNode<T> extends ValueNode<T> {
848
+ // Complex internal logic...
849
+ }
850
+ ```
851
+
852
+ **Benefits**: Semantic naming, encapsulation, flexibility to change implementations.
853
+
854
+ ### Strategy Pattern
855
+
856
+ **Intent**: Define a family of algorithms (sync vs async execution) and make them interchangeable.
857
+
858
+ **Implementation**:
859
+ ```typescript
860
+ // Strategy interface
861
+ interface Scheduler {
862
+ compute(): void
863
+ dispose(): void
864
+ settled: Promise<void>
865
+ }
866
+
867
+ // Concrete strategies
868
+ class SyncScheduler implements Scheduler { /* sync logic */ }
869
+ class AsyncScheduler implements Scheduler { /* async logic */ }
870
+
871
+ // Context uses the strategy
872
+ class ValueNode {
873
+ protected abstract _scheduler: Scheduler // Can be either strategy
874
+ }
875
+ ```
876
+
877
+ **Benefits**: Separate sync/async concerns, easy to add new execution strategies.
878
+
879
+ ### Observer Pattern
880
+
881
+ **Intent**: Define a one-to-many dependency where changes in one object notify all dependents.
882
+
883
+ **Implementation**:
884
+ ```typescript
885
+ class Observable {
886
+ private _dependents = new Set<IObserver>()
887
+
888
+ notifyDependents(): void {
889
+ this._dependents.forEach(dep => dep.notify())
890
+ }
891
+ }
892
+
893
+ class Observer {
894
+ notify(): void {
895
+ // React to change
896
+ }
897
+ }
898
+ ```
899
+
900
+ **Benefits**: Decoupling, automatic propagation, dynamic subscription.
901
+
902
+ ### Disposable Pattern
903
+
904
+ **Intent**: Explicit resource management to prevent memory leaks.
905
+
906
+ **Implementation**:
907
+ ```typescript
908
+ class Disposable {
909
+ protected _disposed = false
910
+
911
+ dispose(): void {
912
+ if (this._disposed) throw new Error("Already disposed")
913
+ // Clean up resources
914
+ this._disposed = true
915
+ }
916
+ }
917
+ ```
918
+
919
+ **Benefits**: Explicit cleanup, prevents memory leaks, clear resource ownership.
920
+
921
+ ## Data Flow Examples
922
+
923
+ ### Creating a State
924
+
925
+ ```mermaid
926
+ sequenceDiagram
927
+ participant U as User Code
928
+ participant API as state()
929
+ participant VSN as ValueSyncNode
930
+ participant SS as SyncScheduler
931
+ participant SR as SyncResolver
932
+
933
+ U->>API: state(0)
934
+ API->>VSN: new ValueSyncNode(0)
935
+ VSN->>SS: new SyncScheduler(compute, onResolve, onReject)
936
+ SS->>SR: new SyncResolver(compute, onResolve, onReject)
937
+ VSN->>VSN: status = "dirty"
938
+ Note over VSN: Lazy - won't compute until accessed
939
+ API->>U: return VSN (as FlowState)
940
+ ```
941
+
942
+ ### Reactive Read with Tracking
943
+
944
+ ```mermaid
945
+ sequenceDiagram
946
+ participant D as Derivation
947
+ participant S as State
948
+
949
+ Note over D: Creating derivation
950
+ D->>D: new ValueSyncNode(compute)
951
+
952
+ Note over D: First access triggers execution
953
+ D->>D: get(tracker)
954
+ D->>D: execute()
955
+ D->>S: get(tracker)
956
+ S->>D: registerDependency(this)
957
+ Note over D,S: Dependency registered!
958
+ S->>D: return value
959
+ D->>D: compute result
960
+ D->>D: status = "resolved"
961
+ ```
962
+
963
+ ### Update Propagation
964
+
965
+ ```mermaid
966
+ sequenceDiagram
967
+ participant U as User Code
968
+ participant S as State
969
+ participant D as Derivation
970
+ participant E as Effect
971
+ participant ES as ExecutionStack
972
+
973
+ U->>S: set(1)
974
+ S->>S: _value = 1
975
+ S->>D: notifyDependents()
976
+ D->>D: notify()
977
+ D->>D: status = "dirty"
978
+ D->>E: notifyDependents()
979
+ E->>E: notify()
980
+ E->>ES: pushEffect(this)
981
+ ES->>ES: scheduleExecution()
982
+
983
+ Note over ES: setTimeout completes
984
+
985
+ ES->>E: execute()
986
+ E->>E: clearDependencies()
987
+ E->>D: get(tracker)
988
+ D->>D: status is "dirty", recompute
989
+ D->>S: get(tracker)
990
+ S->>D: return new value
991
+ D->>E: return computed value
992
+ E->>E: call onData callback
993
+ ```
994
+
995
+ ### Async Computation Flow
996
+
997
+ ```mermaid
998
+ sequenceDiagram
999
+ participant U as User Code
1000
+ participant VAN as ValueAsyncNode
1001
+ participant AS as AsyncScheduler
1002
+ participant AR as AsyncResolver
1003
+
1004
+ U->>VAN: stateAsync(fetchUser())
1005
+ VAN->>AS: new AsyncScheduler()
1006
+ AS->>AR: new AsyncResolver()
1007
+
1008
+ Note over VAN: First access
1009
+ U->>VAN: get(tracker)
1010
+ VAN->>VAN: status is "dirty"
1011
+ VAN->>VAN: execute()
1012
+ VAN->>AS: compute()
1013
+ AS->>AR: compute()
1014
+ AR->>AR: _compute() returns Promise
1015
+ VAN->>VAN: status = "pending"
1016
+ VAN->>U: throw PendingError
1017
+
1018
+ Note over AR: Async operation completes
1019
+
1020
+ AR->>AS: Promise resolves
1021
+ AS->>VAN: onResolve(value)
1022
+ VAN->>VAN: status = "resolved"
1023
+ VAN->>VAN: notifyDependents()
1024
+
1025
+ Note over VAN: Dependents re-execute
1026
+ Note over VAN: Next get() returns value immediately
1027
+ ```
1028
+
1029
+ ## Performance Considerations
1030
+
1031
+ ### Batching with ExecutionStack
1032
+
1033
+ Multiple synchronous state changes are batched into a single update cycle:
1034
+
1035
+ ```typescript
1036
+ const $a = state(0)
1037
+ const $b = state(0)
1038
+
1039
+ const $sum = derivation((t) => $a.get(t) + $b.get(t))
1040
+
1041
+ subscribe((t) => $sum.get(t), (sum) => console.log(sum))
1042
+
1043
+ // These changes are batched
1044
+ $a.set(1) // Doesn't immediately trigger effect
1045
+ $b.set(2) // Doesn't immediately trigger effect
1046
+ // Effect runs once after current call stack completes
1047
+ // Output: 3 (not 1, then 3)
1048
+ ```
1049
+
1050
+ **Performance benefit**: O(1) updates regardless of number of changes, prevents redundant computations.
1051
+
1052
+ ### Lazy Evaluation
1053
+
1054
+ Primitives don't compute until accessed:
1055
+
1056
+ ```typescript
1057
+ const $expensive = derivation((t) => {
1058
+ // This only runs when something actually uses $expensive
1059
+ return expensiveComputation()
1060
+ })
1061
+
1062
+ // No computation yet...
1063
+ // Still no computation...
1064
+
1065
+ // Now it computes
1066
+ subscribe((t) => $expensive.get(t), console.log)
1067
+ ```
1068
+
1069
+ **Performance benefit**: Unused primitives have zero overhead.
1070
+
1071
+ ### Memoization
1072
+
1073
+ Computed values are cached until dependencies change:
1074
+
1075
+ ```typescript
1076
+ const $count = state(0)
1077
+ const $squared = derivation((t) => {
1078
+ console.log("Computing...")
1079
+ return $count.get(t) ** 2
1080
+ })
1081
+
1082
+ $squared.get(tracker) // "Computing..." → returns 0
1083
+ $squared.get(tracker) // Uses cached value → returns 0 (no log)
1084
+ $squared.get(tracker) // Uses cached value → returns 0 (no log)
1085
+
1086
+ $count.set(1)
1087
+ $squared.get(tracker) // "Computing..." → returns 1 (recomputed)
1088
+ ```
1089
+
1090
+ **Performance benefit**: Expensive computations only run when necessary.
1091
+
1092
+ ### Dirty Checking
1093
+
1094
+ Changes short-circuit if the value hasn't actually changed:
1095
+
1096
+ ```typescript
1097
+ const $count = state(0)
1098
+ const $effect = subscribe(
1099
+ (t) => $count.get(t),
1100
+ (value) => console.log(value)
1101
+ )
1102
+
1103
+ $count.set(0) // Same value - effect doesn't run
1104
+ $count.set(1) // Different value - effect runs
1105
+ ```
1106
+
1107
+ **Performance benefit**: Prevents unnecessary downstream updates.
1108
+
1109
+ ## For Contributors
1110
+
1111
+ ### Adding a New Primitive
1112
+
1113
+ To add a new reactive primitive:
1114
+
1115
+ 1. **Decide on the base class**:
1116
+ - Extends `ValueNode` if it holds a value
1117
+ - Extends `Observer` if it's an effect-like primitive
1118
+ - Extends `Node` if it needs custom observable/observer behavior
1119
+
1120
+ 2. **Implement the node class** in `src/nodes/`:
1121
+ ```typescript
1122
+ class MyNewNode extends ValueNode<T> {
1123
+ protected _scheduler: SyncScheduler<T>
1124
+
1125
+ constructor(/* params */) {
1126
+ super()
1127
+ // Initialize scheduler
1128
+ this._scheduler = new SyncScheduler(/* ... */)
1129
+ }
1130
+
1131
+ execute(): void {
1132
+ // Computation logic
1133
+ }
1134
+ }
1135
+ ```
1136
+
1137
+ 3. **Create public API** in `src/api/`:
1138
+ ```typescript
1139
+ export interface FlowMyNew<T> extends FlowValue<T> {
1140
+ // Add any specific methods
1141
+ }
1142
+
1143
+ export function myNew<T>(/* params */): FlowMyNew<T> {
1144
+ return new MyNewNode(/* params */)
1145
+ }
1146
+ ```
1147
+
1148
+ 4. **Write tests** in `test/`:
1149
+ ```typescript
1150
+ describe("myNew", () => {
1151
+ it("should work correctly", () => {
1152
+ const $val = myNew(/* ... */)
1153
+ // Test behavior
1154
+ })
1155
+ })
1156
+ ```
1157
+
1158
+ ### Code Conventions
1159
+
1160
+ **@internal vs @public tags**:
1161
+ - Use `@public` on API elements (in `src/api/`)
1162
+ - Use `@internal` on implementation elements (in `src/base/`, `src/nodes/`, `src/schedulers/`)
1163
+
1164
+ **Naming conventions**:
1165
+ - Classes: PascalCase (`ValueSyncNode`, `AsyncScheduler`)
1166
+ - Functions: camelCase (`state`, `derivation`)
1167
+ - Private fields: underscore prefix (`_value`, `_dependencies`)
1168
+ - Public interfaces: Flow prefix (`FlowState`, `FlowObservable`)
1169
+
1170
+ **Error handling**:
1171
+ - Throw errors for programmer mistakes (wrong usage)
1172
+ - Use PendingError for async pending states
1173
+ - Provide onError callbacks for runtime errors
1174
+
1175
+ ### Testing Structure
1176
+
1177
+ Tests are organized by concern:
1178
+
1179
+ ```
1180
+ test/
1181
+ ├── base/ # Tests for base classes
1182
+ ├── converters/ # Tests for integrations
1183
+ ├── reactivity/ # Tests for reactive behavior
1184
+ │ └── nodes/ # Tests for specific node types
1185
+ └── vitest.d.ts # Type definitions
1186
+ ```
1187
+
1188
+ Run tests:
1189
+ ```bash
1190
+ pnpm test # Run all tests
1191
+ pnpm test:watch # Watch mode
1192
+ pnpm test:coverage # With coverage
1193
+ ```
1194
+
1195
+ ## Glossary
1196
+
1197
+ **Disposable** - An object that can be explicitly cleaned up to free resources and prevent memory leaks. All PicoFlow primitives are disposable.
1198
+
1199
+ **Observable** - An object that can be watched and notifies its dependents when it changes. Can have multiple observers.
1200
+
1201
+ **Observer** - An object that watches one or more observables and reacts to their changes. Can have multiple dependencies.
1202
+
1203
+ **Tracker** - An object (typically an Observer) passed to computation functions to automatically record dependencies when values are accessed.
1204
+
1205
+ **Primitive** - A fundamental reactive building block in PicoFlow (state, derivation, effect, signal, etc.).
1206
+
1207
+ **Node** - An internal implementation class that combines Observable and Observer behavior. Users never interact with nodes directly.
1208
+
1209
+ **Scheduler** - Manages the execution lifecycle of computations, handling both synchronous and asynchronous execution strategies.
1210
+
1211
+ **Resolver** - Manages iteration tracking within a scheduler to prevent race conditions from rapid recomputations.
1212
+
1213
+ **Dependency Graph** - The directed acyclic graph formed by primitives observing each other. Changes propagate from sources (states) through intermediaries (derivations) to sinks (effects).
1214
+
1215
+ **Batching** - Combining multiple synchronous changes into a single update cycle to optimize performance.
1216
+
1217
+ **Lazy Evaluation** - Deferring computation until a value is actually needed, avoiding work for unused primitives.
1218
+
1219
+ **PendingError** - A special error type thrown when an async computation is in progress, used to propagate pending states through the dependency graph.
1220
+
1221
+ **ExecutionStack** - A global singleton that batches and orders reactive executions to prevent cascading updates and stack overflow.
1222
+
1223
+ ---
1224
+
1225
+ ## Further Reading
1226
+
1227
+ - **[Concepts](/guide/introduction/concepts)** - High-level reactive concepts
1228
+ - **[Lifecycle](/guide/introduction/lifecycle)** - Primitive lifecycle and states
1229
+ - **[Disposal](/guide/advanced/disposal)** - Memory management best practices
1230
+ - **[API Reference](/api/)** - Complete API documentation
1231
+
1232
+ ---
1233
+
1234
+ This architecture enables PicoFlow to provide fine-grained, explicit, and performant reactivity with a clean separation between public API and internal implementation. Whether you're debugging a tricky reactive chain or contributing a new feature, understanding these architectural patterns will help you work effectively with PicoFlow.