@ersbeth/picoflow 1.1.1 → 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 +857 -1528
  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 -53
  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 -139
  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
package/dist/picoflow.js CHANGED
@@ -1,1647 +1,976 @@
1
- import { createSignal, createMemo, createResource, onMount, onCleanup } from 'solid-js';
1
+ import { createResource, onMount, createEffect, resetErrorBoundaries, onCleanup } from 'solid-js';
2
2
 
3
3
  function isDisposable(obj) {
4
4
  return obj !== null && obj !== void 0 && typeof obj.dispose === "function";
5
5
  }
6
6
 
7
- class FlowGraph {
8
- static _effectsQueue = [];
9
- static _actionQueue = [];
10
- static _processingActionQueue = false;
11
- /**
12
- * Resets all internal queues and processing state.
13
- *
14
- * @remarks
15
- * Use this method primarily for testing scenarios where you need to ensure
16
- * a clean state between test runs. It clears pending effects and queued
17
- * operations.
18
- *
19
- * @example
20
- * ```typescript
21
- * beforeEach(() => {
22
- * FlowGraph.clear();
23
- * });
24
- * ```
25
- *
26
- * @public
27
- */
28
- static clear() {
29
- FlowGraph._effectsQueue = [];
30
- FlowGraph._actionQueue = [];
31
- FlowGraph._processingActionQueue = false;
32
- }
33
- /**
34
- * Queues a trigger notification for processing.
35
- *
36
- * @param notify - Function to call when the trigger is processed.
37
- * @returns A promise that resolves after the trigger is processed.
38
- *
39
- * @remarks
40
- * This method is used internally by reactive primitives to coordinate
41
- * signal triggers. The promise resolves after all associated effects
42
- * have been executed.
43
- *
44
- * @public
45
- */
46
- static requestTrigger(notify) {
47
- const promiseWithResolvers = Promise.withResolvers();
48
- FlowGraph._actionQueue.push({
49
- ...promiseWithResolvers,
50
- type: "trigger",
51
- notify
7
+ class ExecutionStack {
8
+ static _pendingQueue = [];
9
+ static _effectQueue = [];
10
+ static _executionScheduled;
11
+ static pushPending(node) {
12
+ ExecutionStack._scheduleExecution();
13
+ ExecutionStack._pendingQueue.push(node);
14
+ }
15
+ static pushEffect(effect) {
16
+ ExecutionStack._scheduleExecution();
17
+ ExecutionStack._effectQueue.push(effect);
18
+ }
19
+ static _scheduleExecution() {
20
+ if (ExecutionStack._executionScheduled) return;
21
+ ExecutionStack._executionScheduled = new Promise((resolve) => {
22
+ setTimeout(() => {
23
+ ExecutionStack._executionScheduled = void 0;
24
+ ExecutionStack._execute();
25
+ resolve();
26
+ }, 0);
52
27
  });
53
- FlowGraph._processActionQueue();
54
- return promiseWithResolvers.promise;
55
- }
56
- /**
57
- * Queues a read operation for processing.
58
- *
59
- * @param read - Function that performs the read operation.
60
- * @returns A promise that resolves with the read value.
61
- *
62
- * @remarks
63
- * This method is used internally by reactive primitives to coordinate
64
- * read operations. The read function is executed and its result (or promise)
65
- * is returned.
66
- *
67
- * @public
68
- */
69
- static requestRead(read) {
70
- const promiseWithResolvers = Promise.withResolvers();
71
- FlowGraph._actionQueue.push({
72
- ...promiseWithResolvers,
73
- type: "read",
74
- read
28
+ }
29
+ static _execute() {
30
+ ExecutionStack._pendingQueue.forEach((node) => {
31
+ node.execute();
75
32
  });
76
- FlowGraph._processActionQueue();
77
- return promiseWithResolvers.promise;
78
- }
79
- /**
80
- * Queues a write operation with update logic for processing.
81
- *
82
- * @param notify - Function to call if the update succeeds.
83
- * @param update - Function that performs the update and returns whether
84
- * it changed the value.
85
- * @returns A promise that resolves after the write is processed.
86
- *
87
- * @remarks
88
- * This method is used internally by reactive primitives to coordinate
89
- * write operations. The update function is executed, and if it returns true
90
- * (or a promise resolving to true), the notify function is called to
91
- * trigger dependent effects.
92
- *
93
- * @public
94
- */
95
- static requestWrite(notify, update) {
96
- const promiseWithResolvers = Promise.withResolvers();
97
- FlowGraph._actionQueue.push({
98
- ...promiseWithResolvers,
99
- type: "write",
100
- notify,
101
- update
33
+ ExecutionStack._pendingQueue.length = 0;
34
+ ExecutionStack._effectQueue.forEach((effect) => {
35
+ effect.execute();
102
36
  });
103
- FlowGraph._processActionQueue();
104
- return promiseWithResolvers.promise;
105
- }
106
- /**
107
- * Queues effects for execution after the current operation completes.
108
- *
109
- * @param effects - Array of effects to queue for execution.
110
- *
111
- * @remarks
112
- * This method is used internally by reactive primitives to schedule effect
113
- * execution. Effects are executed after the current read/write/trigger
114
- * operation completes, ensuring proper ordering of reactive updates.
115
- *
116
- * @public
117
- */
118
- static pushEffects(effects) {
119
- FlowGraph._effectsQueue.push(...effects);
120
- }
121
- /** @internal */
122
- static _processActionQueue = async () => {
123
- if (FlowGraph._processingActionQueue) return;
124
- FlowGraph._processingActionQueue = true;
125
- while (FlowGraph._actionQueue.length > 0) {
126
- const actionRequest = FlowGraph._actionQueue.shift();
127
- if (!actionRequest) break;
128
- if (actionRequest.type === "read") {
129
- try {
130
- const read = actionRequest.read();
131
- let value;
132
- if (read instanceof Promise) {
133
- value = await read;
134
- } else {
135
- value = read;
136
- }
137
- actionRequest.resolve(value);
138
- } catch (error) {
139
- const safeError = error instanceof Error ? error : new Error(String(error));
140
- actionRequest.reject(safeError);
141
- }
142
- continue;
143
- }
144
- if (actionRequest.type === "write") {
145
- try {
146
- const updateResult = actionRequest.update();
147
- let updated = false;
148
- if (updateResult instanceof Promise) {
149
- updated = await updateResult;
150
- } else {
151
- updated = updateResult;
152
- }
153
- if (!updated) {
154
- actionRequest.resolve();
155
- continue;
156
- }
157
- actionRequest.notify();
158
- } catch (error) {
159
- const safeError = error instanceof Error ? error : new Error(String(error));
160
- actionRequest.reject(safeError);
161
- continue;
162
- }
163
- }
164
- if (actionRequest.type === "trigger") {
165
- actionRequest.notify();
166
- }
167
- let effectError = null;
168
- for (const effect of FlowGraph._effectsQueue) {
169
- try {
170
- await effect._requestExec();
171
- } catch (error) {
172
- effectError = error instanceof Error ? error : new Error(String(error));
173
- break;
174
- }
175
- }
176
- FlowGraph._effectsQueue = [];
177
- if (effectError) {
178
- actionRequest.reject(effectError);
179
- } else {
180
- actionRequest.resolve();
181
- }
182
- }
183
- FlowGraph._processingActionQueue = false;
184
- };
37
+ ExecutionStack._effectQueue.length = 0;
38
+ }
185
39
  }
186
40
 
187
- class FlowEffect {
41
+ class Disposable {
188
42
  _disposed = false;
189
- _dependencies = /* @__PURE__ */ new Set();
190
- _apply;
191
- settled;
192
- /**
193
- * Creates a new effect and runs it once immediately.
194
- *
195
- * @param apply - Side-effect function receiving the tracking context (`t`).
196
- * It can be sync or async (returning `Promise<void>`).
197
- *
198
- * @remarks
199
- * Use `t` to opt-in to reactive tracking:
200
- * - `observable.get(t)` / `signal.watch(t)` to re-run when they change
201
- * - `observable.pick()` for reads that should not re-run the effect
202
- *
203
- * The effect schedules its first run during construction. Each subsequent
204
- * change to a tracked dependency queues another run.
205
- *
206
- * @public
207
- */
208
- constructor(apply) {
209
- this._apply = apply;
210
- this.settled = FlowGraph.requestRead(() => this._exec());
211
- }
212
- /**
213
- * Stops the effect and detaches it from all tracked dependencies.
214
- *
215
- * @remarks
216
- * Call this when the effect's work is no longer needed (e.g., component
217
- * unmount, teardown of a feature). After disposal:
218
- * - The effect will not re-run.
219
- * - All dependency links are removed.
220
- * - Calling `dispose()` again throws an error.
221
- *
222
- * @example
223
- * ```typescript
224
- * const fx = effect((t) => $signal.watch(t));
225
- * // ... later
226
- * fx.dispose();
227
- * ```
228
- *
229
- * @public
230
- */
43
+ get disposed() {
44
+ return this._disposed;
45
+ }
231
46
  dispose() {
232
- if (this._disposed) throw new Error("[PicoFlow] Effect is disposed");
233
- Array.from(this._dependencies).forEach((dependency) => {
234
- this._unregisterDependency(dependency);
235
- });
47
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
236
48
  this._disposed = true;
237
49
  }
238
- /**
239
- * Whether the effect has been disposed.
240
- *
241
- * @returns `true` once disposal has run; `false` while the effect is active.
242
- *
243
- * @public
244
- */
245
- get disposed() {
246
- return this._disposed;
50
+ }
51
+
52
+ class Node extends Disposable {
53
+ _dependencies = /* @__PURE__ */ new Set();
54
+ _dependents = /* @__PURE__ */ new Set();
55
+ _status = "resolved";
56
+ get status() {
57
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
58
+ return this._status;
247
59
  }
248
- /** @internal */
249
- async _requestExec() {
250
- this.settled = this._exec();
251
- return this.settled;
60
+ set status(status) {
61
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
62
+ this._status = status;
252
63
  }
253
- /** @internal */
254
- _registerDependency(dependency) {
64
+ registerDependency(dependency) {
65
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
255
66
  this._dependencies.add(dependency);
256
- dependency._registerEffect(this);
67
+ dependency.registerDependent(this);
257
68
  }
258
- /** @internal */
259
- _unregisterDependency(dependency) {
69
+ unregisterDependency(dependency) {
70
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
260
71
  this._dependencies.delete(dependency);
261
- dependency._unregisterEffect(this);
262
- }
263
- async _exec() {
264
- if (this._disposed)
265
- throw new Error("[PicoFlow] Effect is disposed");
266
- const result = this._apply(this);
267
- if (result instanceof Promise) {
268
- await result;
269
- }
72
+ dependency.unregisterDependent(this);
73
+ }
74
+ clearDependencies() {
75
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
76
+ this._dependencies.forEach((dependency) => {
77
+ dependency.unregisterDependent(this);
78
+ });
79
+ this._dependencies.clear();
80
+ }
81
+ registerDependent(dependent) {
82
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
83
+ this._dependents.add(dependent);
84
+ }
85
+ unregisterDependent(dependent) {
86
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
87
+ this._dependents.delete(dependent);
88
+ }
89
+ notifyDependents() {
90
+ if (this.disposed) return;
91
+ this._dependents.forEach((dependent) => {
92
+ dependent.notify();
93
+ });
94
+ }
95
+ watch(tracker) {
96
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
97
+ tracker.registerDependency(this);
98
+ }
99
+ trigger() {
100
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
101
+ this.notifyDependents();
102
+ }
103
+ notify() {
104
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
105
+ if (this.status === "dirty") return;
106
+ this.status = "dirty";
107
+ this.notifyDependents();
108
+ }
109
+ dispose() {
110
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
111
+ Array.from(this._dependents).forEach((dependant) => {
112
+ dependant.unregisterDependency(this);
113
+ this.unregisterDependent(dependant);
114
+ });
115
+ Array.from(this._dependencies).forEach((dependency) => {
116
+ this.unregisterDependency(dependency);
117
+ });
118
+ this._disposed = true;
270
119
  }
271
120
  }
272
- function effect(fn) {
273
- return new FlowEffect(fn);
121
+
122
+ class Observable extends Disposable {
123
+ _dependents = /* @__PURE__ */ new Set();
124
+ _status = "resolved";
125
+ get status() {
126
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
127
+ return this._status;
128
+ }
129
+ set status(status) {
130
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
131
+ this._status = status;
132
+ }
133
+ registerDependent(dependent) {
134
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
135
+ this._dependents.add(dependent);
136
+ }
137
+ unregisterDependent(dependent) {
138
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
139
+ this._dependents.delete(dependent);
140
+ }
141
+ notifyDependents() {
142
+ if (this.disposed) return;
143
+ this._dependents.forEach((dependent) => {
144
+ dependent.notify();
145
+ });
146
+ }
147
+ watch(tracker) {
148
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
149
+ tracker.registerDependency(this);
150
+ }
151
+ trigger() {
152
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
153
+ this.notifyDependents();
154
+ }
155
+ dispose() {
156
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
157
+ Array.from(this._dependents).forEach((dependant) => {
158
+ dependant.unregisterDependency(this);
159
+ this.unregisterDependent(dependant);
160
+ });
161
+ this._disposed = true;
162
+ }
274
163
  }
275
164
 
276
- class FlowSignal {
277
- /**
278
- * Triggers the signal and notifies all dependents.
279
- *
280
- * @remarks
281
- * Any effect/derivation that called `watch(t)` on this signal will re-run.
282
- * Returns a promise that settles after notifications complete.
283
- *
284
- * @throws Error if the signal is disposed.
285
- *
286
- * @example
287
- * ```typescript
288
- * const $tick = signal();
289
- *
290
- * effect((t) => {
291
- * $tick.watch(t);
292
- * console.log("tick");
293
- * });
294
- *
295
- * $tick.trigger(); // logs "tick"
296
- * ```
297
- *
298
- * @public
299
- */
300
- async trigger() {
165
+ class Observer extends Disposable {
166
+ _dependencies = /* @__PURE__ */ new Set();
167
+ registerDependency(dependency) {
301
168
  if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
302
- return FlowGraph.requestTrigger(() => this._notify());
303
- }
304
- /**
305
- * Registers this signal as a dependency in the current tracking context.
306
- *
307
- * @param context - The tracking context (`t`) provided to effects/derivations.
308
- *
309
- * @remarks
310
- * Signals have no value to read; calling `watch(t)` simply means “re-run me
311
- * when this signal is triggered.” Call this inside an effect/derivation
312
- * callback where a tracking context is available.
313
- *
314
- * @throws Error if the signal has been disposed.
315
- *
316
- * @example
317
- * ```typescript
318
- * const $refresh = signal();
319
- *
320
- * effect((t) => {
321
- * $refresh.watch(t);
322
- * console.log("refresh triggered");
323
- * });
324
- *
325
- * $refresh.trigger(); // logs "refresh triggered"
326
- * ```
327
- *
328
- * @public
329
- */
330
- watch(caller) {
169
+ this._dependencies.add(dependency);
170
+ dependency.registerDependent(this);
171
+ }
172
+ unregisterDependency(dependency) {
331
173
  if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
332
- caller._registerDependency(this);
333
- }
334
- /**
335
- * Disposes the signal and cleans up its dependencies/listeners.
336
- *
337
- * @remarks
338
- * After disposal the signal must not be used; calling `trigger` or `watch`
339
- * will throw. If `options?.self` is true, only this signal is disposed; when
340
- * false or omitted, dependents may also be disposed depending on the
341
- * implementation.
342
- *
343
- * @throws Error if the signal is already disposed.
344
- *
345
- * @example
346
- * ```typescript
347
- * const $refresh = signal();
348
- * // ... use it
349
- * $refresh.dispose();
350
- * ```
351
- *
352
- * @public
353
- */
354
- dispose(options) {
174
+ this._dependencies.delete(dependency);
175
+ dependency.unregisterDependent(this);
176
+ }
177
+ clearDependencies() {
178
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
179
+ this._dependencies.forEach((dependency) => {
180
+ dependency.unregisterDependent(this);
181
+ });
182
+ this._dependencies.clear();
183
+ }
184
+ dispose() {
355
185
  if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
356
- if (options?.self) {
357
- Array.from(this._effects).forEach((effect) => {
358
- effect._unregisterDependency(this);
359
- });
360
- Array.from(this._listeners).forEach((listener) => {
361
- listener._unregisterDependency(this);
362
- });
363
- } else {
364
- Array.from(this._effects).forEach((effect) => {
365
- effect.dispose();
366
- });
367
- Array.from(this._listeners).forEach((listener) => {
368
- listener.dispose();
369
- });
370
- }
371
186
  Array.from(this._dependencies).forEach((dependency) => {
372
- this._unregisterDependency(dependency);
187
+ this.unregisterDependency(dependency);
373
188
  });
374
189
  this._disposed = true;
375
190
  }
376
- /**
377
- * Whether the signal has been disposed.
378
- *
379
- * @returns `true` if disposed; `false` while active.
380
- *
381
- * @remarks Use to guard operations or avoid double disposal.
382
- *
383
- * @public
384
- */
385
- get disposed() {
386
- return this._disposed;
191
+ }
192
+
193
+ class PendingError extends Error {
194
+ pendingPromise;
195
+ constructor(promise) {
196
+ super("[PicoFlow] PendingResolver pending");
197
+ this.name = "PendingError";
198
+ this.pendingPromise = promise;
387
199
  }
388
- /* INTERNAL ------------------------------------------------------------- */
389
- _disposed = false;
390
- _dependencies = /* @__PURE__ */ new Set();
391
- _listeners = /* @__PURE__ */ new Set();
392
- _effects = /* @__PURE__ */ new Set();
393
- _notify() {
394
- FlowGraph.pushEffects(Array.from(this._effects));
395
- this._listeners.forEach((listener) => {
396
- listener._notify();
200
+ }
201
+
202
+ class AsyncResolver {
203
+ _compute;
204
+ _computed = Promise.withResolvers();
205
+ _iteration = 0;
206
+ _aborted = false;
207
+ _finished = false;
208
+ constructor(compute) {
209
+ this._compute = compute;
210
+ this._computed.promise.catch(() => {
211
+ }).finally(() => {
212
+ this._finished = true;
397
213
  });
398
214
  }
399
- /** @internal */
400
- _registerDependency(dependency) {
401
- this._dependencies.add(dependency);
402
- dependency._registerListener(this);
215
+ get computed() {
216
+ return this._computed.promise;
217
+ }
218
+ get aborted() {
219
+ return this._aborted;
220
+ }
221
+ get finished() {
222
+ return this._finished;
223
+ }
224
+ compute() {
225
+ if (this._finished) throw new Error("[Picoflow] PendingResolver: Can't restart a settled resolver");
226
+ if (this._aborted) throw new Error("[Picoflow] PendingResolver: Can't restart an aborted resolver");
227
+ this._iteration++;
228
+ const currentIteration = this._iteration;
229
+ this._compute().then((value) => {
230
+ if (this._iteration === currentIteration && !this._aborted) this._computed.resolve(value);
231
+ }).catch((error) => {
232
+ if (this._iteration === currentIteration && !this._aborted) {
233
+ if (!(error instanceof PendingError)) {
234
+ this._computed.reject(error);
235
+ }
236
+ }
237
+ });
403
238
  }
404
- /** @internal */
405
- _unregisterDependency(dependency) {
406
- this._dependencies.delete(dependency);
407
- dependency._unregisterListener(this);
239
+ overwrite(promise) {
240
+ if (this._finished) throw new Error("[Picoflow] PendingResolver: Can't overwrite a settled resolver");
241
+ if (this._aborted) throw new Error("[Picoflow] PendingResolver: Can't overwrite an aborted resolver");
242
+ this._iteration++;
243
+ const currentIteration = this._iteration;
244
+ promise.then((value) => {
245
+ if (this._iteration === currentIteration && !this._aborted) this._computed.resolve(value);
246
+ }).catch((error) => {
247
+ if (this._iteration === currentIteration && !this._aborted) {
248
+ this._computed.reject(error);
249
+ }
250
+ });
408
251
  }
409
- /** @internal */
410
- _registerListener(signal2) {
411
- this._listeners.add(signal2);
252
+ abort() {
253
+ this._iteration++;
254
+ this._aborted = true;
412
255
  }
413
- /** @internal */
414
- _unregisterListener(signal2) {
415
- this._listeners.delete(signal2);
256
+ }
257
+
258
+ class AsyncScheduler {
259
+ _compute;
260
+ _resolver;
261
+ _onResolve;
262
+ _onReject;
263
+ _settled = Promise.withResolvers();
264
+ _disposed = false;
265
+ constructor(compute, onResolve, onReject) {
266
+ this._compute = compute;
267
+ this._onResolve = onResolve;
268
+ this._onReject = onReject;
269
+ this._resolver = new AsyncResolver(compute);
270
+ this._resolver.computed.then(this._onResolve).catch(this._onReject).finally(() => this._settled.resolve());
416
271
  }
417
- /** @internal */
418
- _registerEffect(effect) {
419
- this._effects.add(effect);
272
+ get settled() {
273
+ if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
274
+ return this._settled.promise;
420
275
  }
421
- /** @internal */
422
- _unregisterEffect(effect) {
423
- this._effects.delete(effect);
276
+ get disposed() {
277
+ return this._disposed;
278
+ }
279
+ overwrite(promise) {
280
+ if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
281
+ if (this._resolver.finished) {
282
+ this._resolver = new AsyncResolver(this._compute);
283
+ this._settled = Promise.withResolvers();
284
+ this._resolver.computed.then(this._onResolve).catch(this._onReject).finally(() => this._settled.resolve());
285
+ }
286
+ this._resolver.overwrite(promise);
287
+ }
288
+ compute() {
289
+ if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
290
+ if (this._resolver.finished) {
291
+ this._resolver = new AsyncResolver(this._compute);
292
+ this._settled = Promise.withResolvers();
293
+ this._resolver.computed.then(this._onResolve).catch(this._onReject).finally(() => this._settled.resolve());
294
+ }
295
+ this._resolver.compute();
296
+ }
297
+ dispose() {
298
+ this._resolver.abort();
299
+ this._disposed = true;
424
300
  }
425
- }
426
- function signal() {
427
- return new FlowSignal();
428
301
  }
429
302
 
430
- class FlowNodeAsync extends FlowSignal {
431
- _promise;
432
- _dirty = true;
303
+ class SyncResolver {
433
304
  _compute;
434
- /**
435
- * Creates a new FlowNodeAsync.
436
- *
437
- * @param compute - Either a constant Promise or a compute function that derives the value.
438
- *
439
- * @remarks
440
- * The constructor accepts two different initialization modes:
441
- *
442
- * - **Constant Promise**: Creates a mutable reactive node that can be updated via `set()`.
443
- * The Promise is stored immediately and can be changed at any time. The Promise resolves
444
- * to the value of type T.
445
- *
446
- * - **Compute function**: Creates a computed reactive node that automatically tracks dependencies
447
- * and recomputes when they change. The compute function receives a tracking context (`t`)
448
- * that should be used to access dependencies via `.get(t)`. The function must return a
449
- * `Promise<T>`. The function is not executed immediately; it runs lazily on first access.
450
- * The computed value can be temporarily overridden using `set()`, but the override is cleared
451
- * on the next recomputation (triggered by dependency changes).
452
- *
453
- * @example
454
- * ```typescript
455
- * // Mutable state with constant Promise
456
- * const $count = new FlowNodeAsync(Promise.resolve(0));
457
- * await $count.set(Promise.resolve(5));
458
- *
459
- * // Computed derivation with async function
460
- * const $a = new FlowNodeAsync(Promise.resolve(10));
461
- * const $b = new FlowNodeAsync(Promise.resolve(20));
462
- * const $sum = new FlowNodeAsync(async (t) => {
463
- * const a = await $a.get(t);
464
- * const b = await $b.get(t);
465
- * return a + b;
466
- * });
467
- *
468
- * // Lazy evaluation - compute function hasn't run yet
469
- * console.log(await $sum.pick()); // Now it computes: 30
470
- * ```
471
- *
472
- * @public
473
- */
474
- constructor(compute) {
475
- super();
476
- if (typeof compute === "function") {
477
- this._compute = compute;
478
- } else {
479
- this._promise = compute;
480
- this._dirty = false;
305
+ _iteration = 0;
306
+ _aborted = false;
307
+ _finished = false;
308
+ _onValue;
309
+ _onError;
310
+ constructor(compute, onValue, onError) {
311
+ this._compute = compute;
312
+ this._onValue = onValue;
313
+ this._onError = onError;
314
+ }
315
+ get aborted() {
316
+ return this._aborted;
317
+ }
318
+ get finished() {
319
+ return this._finished;
320
+ }
321
+ compute() {
322
+ if (this._finished) throw new Error("[Picoflow] PendingResolver: Can't restart a settled resolver");
323
+ if (this._aborted) throw new Error("[Picoflow] PendingResolver: Can't restart an aborted resolver");
324
+ this._iteration++;
325
+ const currentIteration = this._iteration;
326
+ try {
327
+ const value = this._compute();
328
+ if (this._iteration === currentIteration && !this._aborted) {
329
+ this._finished = true;
330
+ this._onValue(value);
331
+ }
332
+ } catch (error) {
333
+ if (this._iteration === currentIteration && !this._aborted) {
334
+ if (!(error instanceof PendingError)) {
335
+ this._finished = true;
336
+ this._onError(error);
337
+ }
338
+ }
481
339
  }
482
340
  }
483
- get settled() {
484
- return this._promise;
341
+ overwrite(value) {
342
+ if (this._finished) throw new Error("[Picoflow] PendingResolver: Can't overwrite a settled resolver");
343
+ if (this._aborted) throw new Error("[Picoflow] PendingResolver: Can't overwrite an aborted resolver");
344
+ this._iteration++;
345
+ const currentIteration = this._iteration;
346
+ if (this._iteration === currentIteration && !this._aborted) {
347
+ this._finished = true;
348
+ this._onValue(value);
349
+ }
485
350
  }
486
- async watch(tracker) {
487
- if (this._disposed)
488
- return Promise.reject(new Error("[PicoFlow] Primitive is disposed"));
489
- super.watch(tracker);
490
- return this._computeValue();
491
- }
492
- /**
493
- * Gets the current value with dependency tracking.
494
- *
495
- * @param tracker - The tracking context for reactive tracking. This observable is registered
496
- * as a dependency when accessed through this method.
497
- *
498
- * @returns A Promise that resolves to the current value of type T.
499
- *
500
- * @remarks
501
- * Use `get(t)` within effects and derivations to create reactive dependencies. The tracker
502
- * parameter must be provided from the reactive context (typically the `t` parameter in effect
503
- * or derivation callbacks).
504
- *
505
- * **Important:** This method returns a `Promise<T>`, not `T`. You must await the Promise to
506
- * get the actual value. The Promise is cached until dependencies change, ensuring efficient
507
- * access patterns.
508
- *
509
- * To read a value without creating a dependency, use `pick()` instead.
510
- *
511
- * @example
512
- * ```typescript
513
- * effect(async (t) => {
514
- * const tracked = await $state.get(t); // Dependency registered, await the Promise
515
- * const untracked = await $other.pick(); // No dependency
516
- * });
517
- * ```
518
- *
519
- * @throws Error if the node has been disposed.
520
- *
521
- * @public
522
- */
523
- async get(tracker) {
524
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
525
- await this._computeValue();
526
- if (tracker) super.watch(tracker);
527
- return this._promise;
528
- }
529
- /**
530
- * Updates the node with a new value.
531
- *
532
- * @param value - A new Promise or a callback function that computes a new Promise based on the current value.
533
- *
534
- * @returns A promise that resolves after the update is processed and all dependent effects have been notified.
535
- *
536
- * @remarks
537
- * This method can be used in two ways:
538
- *
539
- * **For mutable state nodes** (constructed with a constant Promise):
540
- * - Updates the stored Promise directly
541
- * - All dependents are notified of the change
542
- *
543
- * **For computed nodes** (constructed with a compute function):
544
- * - Temporarily overrides the computed value
545
- * - The override persists until the next recomputation, which occurs when:
546
- * - A tracked dependency changes, or
547
- * - The node is refreshed
548
- * - This allows temporarily overriding computed values for testing or manual control
549
- *
550
- * **Value Comparison:**
551
- * The Promises are resolved and their values are compared. If the resolved new value is strictly
552
- * equal (`===`) to the current resolved value, no update occurs and subscribers are not notified.
553
- * This prevents unnecessary re-renders and effect executions.
554
- *
555
- * **Asynchronous Processing:**
556
- * The update is processed asynchronously through the reactive graph, ensuring proper
557
- * ordering of updates and effect execution. The method returns a Promise that resolves
558
- * after the update is complete.
559
- *
560
- * @throws Error if the node has been disposed.
561
- *
562
- * @example
563
- * ```typescript
564
- * // Mutable state usage
565
- * const $count = new FlowNodeAsync(Promise.resolve(0));
566
- * await $count.set(Promise.resolve(5));
567
- * await $count.set(async (current) => Promise.resolve(current + 1)); // 6
568
- *
569
- * // Temporary override of computed value
570
- * const $source = new FlowNodeAsync(Promise.resolve(10));
571
- * const $doubled = new FlowNodeAsync(async (t) => {
572
- * const val = await $source.get(t);
573
- * return val * 2;
574
- * });
575
- *
576
- * console.log(await $doubled.pick()); // 20
577
- *
578
- * // Temporarily override
579
- * await $doubled.set(Promise.resolve(50));
580
- * console.log(await $doubled.pick()); // 50 (override active)
581
- *
582
- * // Dependency change clears override
583
- * await $source.set(Promise.resolve(15));
584
- * console.log(await $doubled.pick()); // 30 (recomputed, override cleared)
585
- * ```
586
- *
587
- * @public
588
- */
589
- async set(value) {
590
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
591
- const update = async () => {
592
- await this._computeValue();
593
- const currentValue = await this._promise;
594
- const nextPromise = typeof value === "function" ? value(currentValue) : value;
595
- const nextValue = await nextPromise;
596
- this._promise = nextPromise;
597
- return nextValue !== currentValue;
598
- };
599
- const notify = () => {
600
- super._notify();
601
- };
602
- return FlowGraph.requestWrite(notify, update);
603
- }
604
- /**
605
- * Forces recomputation of the value, even if it's not marked as dirty.
606
- *
607
- * @returns A promise that resolves after the recomputation is complete and all
608
- * dependent effects have been notified.
609
- *
610
- * @remarks
611
- * This method is useful when you need to force a recomputation of a computed value,
612
- * for example when the computation depends on external data that has changed outside
613
- * the reactive system.
614
- *
615
- * **Behavior:**
616
- * - For nodes with a compute function: Forces the compute function to run again,
617
- * even if no dependencies have changed. This effectively clears any temporary
618
- * override that was set using `set()`.
619
- * - For nodes without a compute function (mutable state): Recomputes the current
620
- * value (which is just the stored value), useful for consistency but typically
621
- * not necessary.
622
- *
623
- * The recomputation happens asynchronously through the reactive graph, ensuring
624
- * proper ordering of updates and effect execution.
625
- *
626
- * @throws Error if the node has been disposed.
627
- *
628
- * @example
629
- * ```typescript
630
- * const $externalData = new FlowNode(() => fetchExternalData());
631
- *
632
- * // Some external event occurs that changes the data source
633
- * externalDataChanged();
634
- *
635
- * // Force recomputation to get the new value
636
- * await $externalData.refresh();
637
- *
638
- * // For computed nodes with temporary overrides
639
- * const $computed = new FlowNode((t) => $source.get(t) * 2);
640
- * $computed.set(100); // Temporary override
641
- * await $computed.refresh(); // Clears override, recomputes from source
642
- * ```
643
- *
644
- * @public
645
- */
646
- async refresh() {
647
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
648
- const update = async () => {
649
- await this._computeValue();
650
- const currentValue = await this._promise;
651
- await this._computeValue({ force: true });
652
- const nextValue = await this._promise;
653
- return nextValue !== currentValue;
351
+ abort() {
352
+ this._iteration++;
353
+ this._aborted = true;
354
+ }
355
+ }
356
+
357
+ class SyncScheduler {
358
+ _compute;
359
+ _resolver;
360
+ _onResolve;
361
+ _onReject;
362
+ _settled = Promise.withResolvers();
363
+ _disposed = false;
364
+ constructor(compute, onResolve, onReject) {
365
+ this._compute = compute;
366
+ this._onResolve = (value) => {
367
+ onResolve(value);
368
+ this._settled.resolve();
654
369
  };
655
- const notify = () => {
656
- super._notify();
370
+ this._onReject = (error) => {
371
+ onReject(error);
372
+ this._settled.resolve();
657
373
  };
658
- return await FlowGraph.requestWrite(notify, update);
659
- }
660
- /**
661
- * Gets the current value without any dependency tracking.
662
- *
663
- * @returns A promise that resolves with the current value of type T.
664
- *
665
- * @remarks
666
- * This method reads the value asynchronously through the reactive graph, ensuring proper ordering of
667
- * read operations. Unlike `get(t)`, this method does not create a reactive dependency.
668
- *
669
- * **Important:** This is an async method that returns `Promise<T>`. Always use `await` when calling it.
670
- *
671
- * Use `pick()` when you want to read a snapshot of the current value without creating a reactive
672
- * dependency. This is useful for:
673
- * - Reading initial values outside reactive contexts
674
- * - Accessing configuration that shouldn't trigger updates
675
- * - Mixing tracked and untracked reads in the same effect
676
- *
677
- * @example
678
- * ```typescript
679
- * // Read a snapshot outside reactive context
680
- * const currentValue = await $state.pick();
681
- *
682
- * // Mix tracked and untracked reads
683
- * effect(async (t) => {
684
- * const tracked = await $reactive.get(t); // Triggers re-runs
685
- * const snapshot = await $config.pick(); // Doesn't trigger re-runs
686
- * processData(tracked, snapshot);
687
- * });
688
- * ```
689
- *
690
- * @throws Error if the node has been disposed.
691
- *
692
- * @public
693
- */
694
- async pick() {
695
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
696
- await FlowGraph.requestRead(() => this._computeValue());
697
- return this._promise;
698
- }
699
- /**
700
- * Subscribes a listener function to changes of this node.
701
- *
702
- * @param listener - A callback function that receives the new value whenever it changes.
703
- *
704
- * @returns A disposer function that cancels the subscription when called.
705
- *
706
- * @remarks
707
- * This method creates a reactive subscription that automatically tracks this node as a dependency.
708
- * The listener is executed:
709
- * - Immediately with the current value when the subscription is created
710
- * - Automatically whenever the value changes
711
- *
712
- * **Important:** The listener receives a `Promise<T>`, not `T`. You must await the Promise
713
- * within the listener to access the actual value. The Promise is the same one returned by `get()`,
714
- * so it's cached until dependencies change.
715
- *
716
- * The subscription uses a {@link FlowEffect} internally to manage the reactive tracking and
717
- * automatic re-execution. When the value changes, the listener is called with the new Promise
718
- * after the update is processed through the reactive graph.
719
- *
720
- * **Cleanup:**
721
- * Always call the returned disposer function when you no longer need the subscription to
722
- * prevent memory leaks and unnecessary computations.
723
- *
724
- * @example
725
- * ```typescript
726
- * const $count = new FlowNodeAsync(Promise.resolve(0));
727
- *
728
- * // Subscribe to changes
729
- * const unsubscribe = $count.subscribe(async (valuePromise) => {
730
- * const value = await valuePromise;
731
- * console.log(`Count is now: ${value}`);
732
- * });
733
- * // Logs immediately: "Count is now: 0"
734
- *
735
- * await $count.set(Promise.resolve(5));
736
- * // Logs: "Count is now: 5"
737
- *
738
- * // Clean up when done
739
- * unsubscribe();
740
- * ```
741
- *
742
- * @throws Error if the node has been disposed.
743
- *
744
- * @public
745
- */
746
- subscribe(listener) {
747
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
748
- const effect = new FlowEffect((t) => {
749
- listener(this.get(t));
750
- });
751
- return () => effect.dispose();
374
+ this._resolver = new SyncResolver(compute, this._onResolve, this._onReject);
375
+ }
376
+ get settled() {
377
+ return this._settled.promise;
752
378
  }
753
- /* INTERNAL --------------------------------------------------------- */
754
- /* @internal */
755
- _notify() {
756
- if (this._dirty) {
757
- return;
379
+ overwrite(value) {
380
+ if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
381
+ if (this._resolver.finished) {
382
+ this._settled = Promise.withResolvers();
383
+ this._resolver = new SyncResolver(this._compute, this._onResolve, this._onReject);
758
384
  }
759
- this._dirty = true;
760
- super._notify();
761
- }
762
- async _computeValue(options) {
763
- if (!this._dirty && !options?.force) return;
764
- if (!this._compute) {
765
- this._dirty = false;
766
- return;
385
+ this._resolver.overwrite(value);
386
+ }
387
+ compute() {
388
+ if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
389
+ if (this._resolver.finished) {
390
+ this._settled = Promise.withResolvers();
391
+ this._resolver = new SyncResolver(this._compute, this._onResolve, this._onReject);
767
392
  }
768
- this._dirty = false;
769
- const dependencies = [...this._dependencies];
770
- this._dependencies.clear();
771
- this._promise = this._compute(this);
772
- await this._promise;
773
- const dependenciesToRemove = dependencies.filter(
774
- (dependency) => !this._dependencies.has(dependency)
775
- );
776
- dependenciesToRemove.forEach((dependency) => {
777
- dependency._unregisterDependency(this);
778
- });
393
+ this._resolver.compute();
394
+ }
395
+ dispose() {
396
+ this._resolver.abort();
397
+ this._disposed = true;
779
398
  }
780
399
  }
781
400
 
782
- function constantAsync(value) {
783
- return new FlowNodeAsync(value);
784
- }
785
-
786
- function derivationAsync(fn) {
787
- return new FlowNodeAsync(fn);
788
- }
789
-
790
- function stateAsync(value) {
791
- return new FlowNodeAsync(value);
792
- }
793
-
794
- class FlowNode extends FlowSignal {
795
- _value;
796
- _dirty = true;
797
- _compute;
798
- /**
799
- * Creates a new FlowNode.
800
- *
801
- * @param compute - Either a constant value or a compute function that derives the value.
802
- *
803
- * @remarks
804
- * The constructor accepts two different initialization modes:
805
- *
806
- * - **Constant value**: Creates a mutable reactive node that can be updated via `set()`.
807
- * The value is stored immediately and can be changed at any time.
808
- *
809
- * - **Compute function**: Creates a computed reactive node that automatically tracks dependencies
810
- * and recomputes when they change. The compute function receives a tracking context (`t`)
811
- * that should be used to access dependencies via `.get(t)`. The function is not executed
812
- * immediately; it runs lazily on first access. The computed value can be temporarily
813
- * overridden using `set()`, but the override is cleared on the next recomputation
814
- * (triggered by dependency changes or `refresh()`).
815
- *
816
- * @example
817
- * ```typescript
818
- * // Mutable state with constant value
819
- * const $count = new FlowNode(0);
820
- * $count.set(5);
821
- *
822
- * // Computed derivation with function
823
- * const $a = new FlowNode(10);
824
- * const $b = new FlowNode(20);
825
- * const $sum = new FlowNode((t) => $a.get(t) + $b.get(t));
826
- *
827
- * // Lazy evaluation - compute function hasn't run yet
828
- * console.log(await $sum.pick()); // Now it computes: 30
829
- * ```
830
- *
831
- * @public
832
- */
833
- constructor(compute) {
401
+ class EffectNode extends Observer {
402
+ _data;
403
+ _onData;
404
+ _onError;
405
+ _onPending;
406
+ constructor(data, onData, onError, onPending) {
834
407
  super();
835
- if (typeof compute === "function") {
836
- this._compute = compute;
837
- } else {
838
- this._value = compute;
839
- this._dirty = false;
408
+ this._data = data;
409
+ this._onData = onData;
410
+ this._onError = onError;
411
+ this._onPending = onPending;
412
+ this.execute();
413
+ }
414
+ notify() {
415
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
416
+ ExecutionStack.pushEffect(this);
417
+ }
418
+ execute() {
419
+ try {
420
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
421
+ this.clearDependencies();
422
+ const data = this._data(this);
423
+ this._onData(data);
424
+ } catch (error) {
425
+ if (error instanceof PendingError) {
426
+ this._onPending?.();
427
+ } else {
428
+ if (this._onError) {
429
+ this._onError(error instanceof Error ? error : new Error(String(error)));
430
+ } else {
431
+ throw error;
432
+ }
433
+ }
840
434
  }
841
435
  }
436
+ }
437
+
438
+ class ValueNode extends Node {
439
+ _value;
440
+ _error;
842
441
  watch(tracker) {
843
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
844
- this._computeValue();
442
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
845
443
  super.watch(tracker);
444
+ switch (this.status) {
445
+ case "resolved":
446
+ return;
447
+ case "error":
448
+ throw this._error;
449
+ case "pending": {
450
+ throw new PendingError(this._scheduler.settled);
451
+ }
452
+ case "dirty": {
453
+ this.execute();
454
+ switch (this.status) {
455
+ case "pending":
456
+ throw new PendingError(this._scheduler.settled);
457
+ case "resolved":
458
+ return;
459
+ case "error":
460
+ throw this._error;
461
+ case "dirty": {
462
+ throw new Error("[PicoFlow] Internal error");
463
+ }
464
+ }
465
+ }
466
+ }
846
467
  }
847
- /**
848
- * Gets the current value with dependency tracking.
849
- *
850
- * @param tracker - The tracking context for reactive tracking. This observable is registered
851
- * as a dependency when accessed through this method.
852
- *
853
- * @returns The current value of type T.
854
- *
855
- * @remarks
856
- * Use `get(t)` within effects and derivations to create reactive dependencies. The tracker
857
- * parameter must be provided from the reactive context (typically the `t` parameter in effect
858
- * or derivation callbacks).
859
- *
860
- * To read a value without creating a dependency, use `pick()` instead.
861
- *
862
- * @example
863
- * ```typescript
864
- * effect((t) => {
865
- * const tracked = $state.get(t); // Dependency registered
866
- * const untracked = await $other.pick(); // No dependency
867
- * });
868
- * ```
869
- *
870
- * @throws Error if the node has been disposed.
871
- *
872
- * @public
873
- */
874
- get(tracker) {
875
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
876
- this._computeValue();
877
- if (tracker) super.watch(tracker);
878
- return this._value;
879
- }
880
- /**
881
- * Updates the node with a new value.
882
- *
883
- * @param value - A new value or a callback function that computes a new value based on the current value.
884
- *
885
- * @returns A promise that resolves after the update is processed and all dependent effects have been notified.
886
- *
887
- * @remarks
888
- * This method can be used in two ways:
889
- *
890
- * **For mutable state nodes** (constructed with a constant value):
891
- * - Updates the stored value directly
892
- * - All dependents are notified of the change
893
- *
894
- * **For computed nodes** (constructed with a compute function):
895
- * - Temporarily overrides the computed value
896
- * - The override persists until the next recomputation, which occurs when:
897
- * - A tracked dependency changes, or
898
- * - `refresh()` is called
899
- * - This allows temporarily overriding computed values for testing or manual control
900
- *
901
- * **Value Comparison:**
902
- * If the new value is strictly equal (`===`) to the current value, no update occurs
903
- * and subscribers are not notified. This prevents unnecessary re-renders and effect
904
- * executions.
905
- *
906
- * **Asynchronous Processing:**
907
- * The update is processed asynchronously through the reactive graph, ensuring proper
908
- * ordering of updates and effect execution.
909
- *
910
- * @throws Error if the node has been disposed.
911
- *
912
- * @example
913
- * ```typescript
914
- * // Mutable state usage
915
- * const $count = new FlowNode(0);
916
- * await $count.set(5);
917
- * await $count.set(current => current + 1); // 6
918
- *
919
- * // Temporary override of computed value
920
- * const $source = new FlowNode(10);
921
- * const $doubled = new FlowNode((t) => $source.get(t) * 2);
922
- *
923
- * console.log(await $doubled.pick()); // 20
924
- *
925
- * // Temporarily override
926
- * await $doubled.set(50);
927
- * console.log(await $doubled.pick()); // 50 (override active)
928
- *
929
- * // Dependency change clears override
930
- * await $source.set(15);
931
- * console.log(await $doubled.pick()); // 30 (recomputed, override cleared)
932
- * ```
933
- *
934
- * @public
935
- */
936
- set(value) {
937
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
938
- const update = () => {
939
- this._computeValue();
940
- const currentValue = this._value;
941
- const next = typeof value === "function" ? value(currentValue) : value;
942
- this._value = next;
943
- return next !== currentValue;
944
- };
945
- const notify = () => {
946
- super._notify();
947
- };
948
- return FlowGraph.requestWrite(notify, update);
949
- }
950
- /**
951
- * Forces recomputation of the value, even if it's not marked as dirty.
952
- *
953
- * @returns A promise that resolves after the recomputation is complete and all
954
- * dependent effects have been notified.
955
- *
956
- * @remarks
957
- * This method is useful when you need to force a recomputation of a computed value,
958
- * for example when the computation depends on external data that has changed outside
959
- * the reactive system.
960
- *
961
- * **Behavior:**
962
- * - For nodes with a compute function: Forces the compute function to run again,
963
- * even if no dependencies have changed. This effectively clears any temporary
964
- * override that was set using `set()`.
965
- * - For nodes without a compute function (mutable state): Recomputes the current
966
- * value (which is just the stored value), useful for consistency but typically
967
- * not necessary.
968
- *
969
- * The recomputation happens asynchronously through the reactive graph, ensuring
970
- * proper ordering of updates and effect execution.
971
- *
972
- * @throws Error if the node has been disposed.
973
- *
974
- * @example
975
- * ```typescript
976
- * const $externalData = new FlowNode(() => fetchExternalData());
977
- *
978
- * // Some external event occurs that changes the data source
979
- * externalDataChanged();
980
- *
981
- * // Force recomputation to get the new value
982
- * await $externalData.refresh();
983
- *
984
- * // For computed nodes with temporary overrides
985
- * const $computed = new FlowNode((t) => $source.get(t) * 2);
986
- * $computed.set(100); // Temporary override
987
- * await $computed.refresh(); // Clears override, recomputes from source
988
- * ```
989
- *
990
- * @public
991
- */
992
- async refresh() {
993
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
994
- const update = () => {
995
- this._computeValue();
996
- const currentValue = this._value;
997
- this._computeValue({ force: true });
998
- const nextValue = this._value;
999
- return nextValue !== currentValue;
1000
- };
1001
- const notify = () => {
1002
- super._notify();
1003
- };
1004
- return await FlowGraph.requestWrite(notify, update);
1005
- }
1006
- /**
1007
- * Gets the current value without any dependency tracking.
1008
- *
1009
- * @returns A promise that resolves with the current value of type T.
1010
- *
1011
- * @remarks
1012
- * This method reads the value asynchronously through the reactive graph, ensuring proper ordering of
1013
- * read operations. Unlike `get(t)`, this method does not create a reactive dependency.
1014
- *
1015
- * Use `pick()` when you want to read a snapshot of the current value without creating a reactive
1016
- * dependency. This is useful for:
1017
- * - Reading initial values outside reactive contexts
1018
- * - Accessing configuration that shouldn't trigger updates
1019
- * - Mixing tracked and untracked reads in the same effect
1020
- *
1021
- * **Note:** This is an async method. Always use `await` when calling it.
1022
- *
1023
- * @example
1024
- * ```typescript
1025
- * // Read a snapshot outside reactive context
1026
- * const currentValue = await $state.pick();
1027
- *
1028
- * // Mix tracked and untracked reads
1029
- * effect(async (t) => {
1030
- * const tracked = $reactive.get(t); // Triggers re-runs
1031
- * const snapshot = await $config.pick(); // Doesn't trigger re-runs
1032
- * processData(tracked, snapshot);
1033
- * });
1034
- * ```
1035
- *
1036
- * @throws Error if the node has been disposed.
1037
- *
1038
- * @public
1039
- */
1040
- async pick() {
1041
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1042
- await FlowGraph.requestRead(() => this._computeValue());
1043
- return this._value;
1044
- }
1045
- /**
1046
- * Subscribes a listener function to changes of this node.
1047
- *
1048
- * @param listener - A callback function that receives the new value whenever it changes.
1049
- *
1050
- * @returns A disposer function that cancels the subscription when called.
1051
- *
1052
- * @remarks
1053
- * This method creates a reactive subscription that automatically tracks this node as a dependency.
1054
- * The listener is executed:
1055
- * - Immediately with the current value when the subscription is created
1056
- * - Automatically whenever the value changes
1057
- *
1058
- * The subscription uses a {@link FlowEffect} internally to manage the reactive tracking and
1059
- * automatic re-execution. When the value changes, the listener is called with the new value
1060
- * after the update is processed through the reactive graph.
1061
- *
1062
- * **Cleanup:**
1063
- * Always call the returned disposer function when you no longer need the subscription to
1064
- * prevent memory leaks and unnecessary computations.
1065
- *
1066
- * @example
1067
- * ```typescript
1068
- * const $count = new FlowNode(0);
1069
- *
1070
- * // Subscribe to changes
1071
- * const unsubscribe = $count.subscribe((value) => {
1072
- * console.log(`Count is now: ${value}`);
1073
- * });
1074
- * // Logs immediately: "Count is now: 0"
1075
- *
1076
- * await $count.set(5);
1077
- * // Logs: "Count is now: 5"
1078
- *
1079
- * // Clean up when done
1080
- * unsubscribe();
1081
- * ```
1082
- *
1083
- * @throws Error if the node has been disposed.
1084
- *
1085
- * @public
1086
- */
1087
- subscribe(listener) {
1088
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1089
- const effect = new FlowEffect((t) => {
1090
- listener(this.get(t));
1091
- });
1092
- return () => effect.dispose();
468
+ notify() {
469
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
470
+ if (this.status === "dirty") return;
471
+ if (this.status === "pending") {
472
+ ExecutionStack.pushPending(this);
473
+ }
474
+ this.status = "dirty";
475
+ this.notifyDependents();
476
+ }
477
+ dispose() {
478
+ super.dispose();
479
+ this._scheduler.dispose();
1093
480
  }
1094
- /* INTERNAL --------------------------------------------------------- */
1095
- /* @internal */
1096
- _notify() {
1097
- if (this._dirty) {
1098
- return;
481
+ get(tracker) {
482
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
483
+ super.watch(tracker);
484
+ switch (this.status) {
485
+ case "resolved":
486
+ return this._value;
487
+ case "error":
488
+ throw this._error;
489
+ case "pending": {
490
+ throw new PendingError(this._scheduler.settled);
491
+ }
492
+ case "dirty": {
493
+ this.execute();
494
+ switch (this.status) {
495
+ case "resolved":
496
+ return this._value;
497
+ case "error":
498
+ throw this._error;
499
+ case "pending": {
500
+ throw new PendingError(this._scheduler.settled);
501
+ }
502
+ case "dirty": {
503
+ throw new Error("[PicoFlow] Internal error");
504
+ }
505
+ }
506
+ }
1099
507
  }
1100
- this._dirty = true;
1101
- super._notify();
1102
- }
1103
- _computeValue(options) {
1104
- if (!this._dirty && !options?.force) return;
1105
- if (!this._compute) {
1106
- this._dirty = false;
1107
- return;
508
+ }
509
+ async pick() {
510
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
511
+ switch (this.status) {
512
+ case "resolved":
513
+ return this._value;
514
+ case "pending": {
515
+ await this._scheduler.settled;
516
+ if (this.status === "resolved") return this._value;
517
+ if (this.status === "error") throw this._error;
518
+ throw new Error("[PicoFlow] Internal error");
519
+ }
520
+ case "error":
521
+ throw this._error;
522
+ case "dirty": {
523
+ this.execute();
524
+ switch (this.status) {
525
+ case "resolved":
526
+ return this._value;
527
+ case "pending": {
528
+ await this._scheduler.settled;
529
+ if (this.status === "resolved") return this._value;
530
+ if (this.status === "error") throw this._error;
531
+ throw new Error("[PicoFlow] Internal error");
532
+ }
533
+ case "error":
534
+ throw this._error;
535
+ case "dirty": {
536
+ throw new Error("[PicoFlow] Internal error");
537
+ }
538
+ }
539
+ }
1108
540
  }
1109
- this._dirty = false;
1110
- const dependencies = [...this._dependencies];
1111
- this._dependencies.clear();
1112
- this._value = this._compute(this);
1113
- const dependenciesToRemove = dependencies.filter(
1114
- (dependency) => !this._dependencies.has(dependency)
1115
- );
1116
- dependenciesToRemove.forEach((dependency) => {
1117
- dependency._unregisterDependency(this);
1118
- });
541
+ }
542
+ execute() {
543
+ if (this.disposed) return;
544
+ this.clearDependencies();
545
+ this.status = "pending";
546
+ this._scheduler.compute();
547
+ }
548
+ subscribe(onValue, onError, onPending) {
549
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
550
+ const effect = new EffectNode((t) => this.get(t), onValue, onError, onPending);
551
+ return effect;
1119
552
  }
1120
553
  }
1121
554
 
1122
- function constant(value) {
1123
- return new FlowNode(value);
1124
- }
1125
-
1126
- function derivation(fn) {
1127
- return new FlowNode(fn);
1128
- }
1129
-
1130
- function state(value) {
1131
- return new FlowNode(value);
555
+ class ValueSyncNode extends ValueNode {
556
+ _scheduler;
557
+ _compute;
558
+ constructor(valueOrCompute) {
559
+ super();
560
+ if (typeof valueOrCompute === "function") {
561
+ this._compute = () => valueOrCompute(this, this._value);
562
+ } else {
563
+ this._compute = () => valueOrCompute;
564
+ }
565
+ this.status = "dirty";
566
+ this._scheduler = new SyncScheduler(
567
+ () => this._compute(),
568
+ (value) => this._onResolve(value),
569
+ (error) => this._onReject(error)
570
+ );
571
+ if (typeof valueOrCompute !== "function") {
572
+ this._scheduler.compute();
573
+ }
574
+ }
575
+ _onResolve(value) {
576
+ this.status = "resolved";
577
+ this._value = value;
578
+ this._error = void 0;
579
+ }
580
+ _onReject(error) {
581
+ this.status = "error";
582
+ this._error = error;
583
+ }
584
+ set(valueOrUpdater) {
585
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
586
+ if (typeof valueOrUpdater === "function") {
587
+ const updater = valueOrUpdater;
588
+ switch (this.status) {
589
+ case "resolved": {
590
+ const nextValue = updater(this._value);
591
+ if (nextValue === this._value) return;
592
+ this.status = "pending";
593
+ this._scheduler.overwrite(nextValue);
594
+ this.notifyDependents();
595
+ return;
596
+ }
597
+ case "pending":
598
+ case "error":
599
+ case "dirty": {
600
+ const nextValue = updater(this._value);
601
+ this.status = "pending";
602
+ this._scheduler.overwrite(nextValue);
603
+ this.notifyDependents();
604
+ return;
605
+ }
606
+ }
607
+ } else {
608
+ const nextValue = valueOrUpdater;
609
+ switch (this.status) {
610
+ case "resolved": {
611
+ if (nextValue === this._value) return;
612
+ this.status = "pending";
613
+ this._scheduler.overwrite(nextValue);
614
+ this.notifyDependents();
615
+ return;
616
+ }
617
+ case "pending":
618
+ case "error":
619
+ case "dirty": {
620
+ this.status = "pending";
621
+ this._scheduler.overwrite(nextValue);
622
+ this.notifyDependents();
623
+ return;
624
+ }
625
+ }
626
+ }
627
+ }
628
+ refresh() {
629
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
630
+ const currentValue = this._value;
631
+ const currentStatus = this.status;
632
+ this.execute();
633
+ switch (this.status) {
634
+ case "resolved": {
635
+ const nextValue = this._value;
636
+ if (nextValue === currentValue && currentStatus === this.status) return;
637
+ this.notifyDependents();
638
+ return;
639
+ }
640
+ case "error":
641
+ case "pending":
642
+ case "dirty": {
643
+ this.notifyDependents();
644
+ return;
645
+ }
646
+ }
647
+ }
1132
648
  }
1133
649
 
1134
- class FlowArray extends FlowNode {
1135
- /**
1136
- * Last action performed on the FlowArray.
1137
- * @public
1138
- */
650
+ class ArrayNode extends ValueSyncNode {
1139
651
  $lastAction;
1140
- /**
1141
- * Creates an instance of FlowArray.
1142
- * @param value - Initial array value.
1143
- * @public
1144
- */
1145
652
  constructor(value = []) {
1146
653
  super(value);
1147
- this.$lastAction = state({
654
+ this.$lastAction = new ValueSyncNode({
1148
655
  type: "set",
1149
- items: value
656
+ setItems: value,
657
+ clearedItems: []
1150
658
  });
1151
659
  }
1152
- /**
1153
- * Gets the current length of the array.
1154
- * @returns The length of the array.
1155
- * @public
1156
- */
1157
660
  get length() {
1158
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
661
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1159
662
  return this._value.length;
1160
663
  }
1161
- /**
1162
- * Internal method to get the raw value.
1163
- * @internal
1164
- */
1165
- _getRaw() {
1166
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1167
- return [...this._value];
1168
- }
1169
- /**
1170
- * Replaces the entire array with new items.
1171
- * @param items - The new array of items.
1172
- * @public
1173
- */
1174
- async set(items) {
1175
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1176
- const update = () => {
1177
- const currentValue = this._value;
1178
- if (currentValue !== items) {
1179
- currentValue.forEach((item) => {
1180
- if (isDisposable(item)) item.dispose({ self: true });
1181
- });
1182
- this._value = items;
1183
- this.$lastAction.set({ type: "set", items });
1184
- }
1185
- return currentValue !== items;
1186
- };
1187
- const notify = () => {
1188
- super._notify();
1189
- };
1190
- return FlowGraph.requestWrite(notify, update);
1191
- }
1192
- /**
1193
- * Replaces an item at a specific index.
1194
- * @param index - The index of the item to replace.
1195
- * @param item - The new item.
1196
- * @public
1197
- */
1198
- async update(index, item) {
1199
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1200
- const update = () => {
1201
- if (index < 0 || index >= this._value.length) {
1202
- throw new Error("[PicoFlow] Index out of bounds");
1203
- }
1204
- const currentValue = this._value[index];
1205
- if (currentValue !== item) {
1206
- if (isDisposable(currentValue)) {
1207
- currentValue.dispose({ self: true });
1208
- }
1209
- this._value[index] = item;
1210
- this.$lastAction.set({ type: "update", index, item });
1211
- }
1212
- return currentValue !== item;
1213
- };
1214
- const notify = () => {
1215
- super._notify();
1216
- };
1217
- return FlowGraph.requestWrite(notify, update);
1218
- }
1219
- /**
1220
- * Appends an item to the end of the array.
1221
- * @param item - The item to append.
1222
- * @public
1223
- */
1224
- async push(item) {
1225
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1226
- const update = () => {
1227
- this._value.push(item);
1228
- this.$lastAction.set({ type: "push", item });
1229
- return true;
1230
- };
1231
- const notify = () => {
1232
- super._notify();
1233
- };
1234
- return FlowGraph.requestWrite(notify, update);
664
+ set(itemsOrUpdater) {
665
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
666
+ const previousValue = this._value;
667
+ super.set(itemsOrUpdater);
668
+ this.$lastAction.set({
669
+ type: "set",
670
+ setItems: this._value,
671
+ clearedItems: previousValue
672
+ });
673
+ return previousValue;
1235
674
  }
1236
- /**
1237
- * Removes the last item from the array.
1238
- * @public
1239
- */
1240
- async pop() {
1241
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1242
- const update = () => {
1243
- const item = this._value.pop();
1244
- if (item !== void 0) {
1245
- if (isDisposable(item)) {
1246
- item.dispose({ self: true });
1247
- }
1248
- this.$lastAction.set({ type: "pop" });
1249
- return true;
1250
- }
1251
- return false;
1252
- };
1253
- const notify = () => {
1254
- super._notify();
1255
- };
1256
- return FlowGraph.requestWrite(notify, update);
1257
- }
1258
- /**
1259
- * Inserts an item at the beginning of the array.
1260
- * @param item - The item to insert.
1261
- * @public
1262
- */
1263
- async unshift(item) {
1264
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1265
- const update = () => {
1266
- this._value.unshift(item);
1267
- this.$lastAction.set({ type: "unshift", item });
1268
- return true;
1269
- };
1270
- const notify = () => {
1271
- super._notify();
1272
- };
1273
- return FlowGraph.requestWrite(notify, update);
675
+ update(index, item) {
676
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
677
+ if (index < 0 || index >= this._value.length) {
678
+ throw new Error("[PicoFlow] Index out of bounds");
679
+ }
680
+ const previousValue = this._value[index];
681
+ this._value[index] = item;
682
+ this.notifyDependents();
683
+ this.$lastAction.set({
684
+ type: "update",
685
+ index,
686
+ setItem: item,
687
+ clearedItem: previousValue
688
+ });
689
+ return previousValue;
690
+ }
691
+ push(item) {
692
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
693
+ this._value.push(item);
694
+ this.notifyDependents();
695
+ this.$lastAction.set({ type: "push", addedItem: item });
696
+ }
697
+ pop() {
698
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
699
+ const item = this._value.pop();
700
+ this.notifyDependents();
701
+ this.$lastAction.set({ type: "pop", removedItem: item });
702
+ return item;
703
+ }
704
+ unshift(item) {
705
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
706
+ this._value.unshift(item);
707
+ this.notifyDependents();
708
+ this.$lastAction.set({ type: "unshift", addedItem: item });
709
+ }
710
+ shift() {
711
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
712
+ const item = this._value.shift();
713
+ this.notifyDependents();
714
+ this.$lastAction.set({ type: "shift", removedItem: item });
715
+ return item;
716
+ }
717
+ splice(start, deleteCount, ...newItems) {
718
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
719
+ const items = this._value.splice(start, deleteCount, ...newItems);
720
+ this.notifyDependents();
721
+ this.$lastAction.set({
722
+ type: "splice",
723
+ start,
724
+ deleteCount,
725
+ addedItems: newItems,
726
+ removedItems: items
727
+ });
728
+ return items;
1274
729
  }
1275
- /**
1276
- * Removes the first item from the array.
1277
- * @public
1278
- */
1279
- async shift() {
1280
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1281
- const update = () => {
1282
- const item = this._value.shift();
1283
- if (item !== void 0) {
1284
- if (isDisposable(item)) {
1285
- item.dispose({ self: true });
1286
- }
1287
- this.$lastAction.set({ type: "shift" });
1288
- return true;
1289
- }
1290
- return false;
1291
- };
1292
- const notify = () => {
1293
- super._notify();
1294
- };
1295
- return FlowGraph.requestWrite(notify, update);
1296
- }
1297
- /**
1298
- * Changes the content of the array.
1299
- * @param start - The starting index.
1300
- * @param deleteCount - Number of items to remove.
1301
- * @param newItems - New items to add.
1302
- * @public
1303
- */
1304
- async splice(start, deleteCount, ...newItems) {
1305
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1306
- const update = () => {
1307
- const items = this._value.splice(start, deleteCount, ...newItems);
1308
- items.forEach((item) => {
1309
- if (isDisposable(item)) item.dispose({ self: true });
1310
- });
1311
- this.$lastAction.set({
1312
- type: "splice",
1313
- start,
1314
- deleteCount,
1315
- items: newItems
1316
- });
1317
- return true;
1318
- };
1319
- const notify = () => {
1320
- super._notify();
1321
- };
1322
- return FlowGraph.requestWrite(notify, update);
730
+ clear() {
731
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
732
+ const previousValue = [...this._value];
733
+ this._value = [];
734
+ this.notifyDependents();
735
+ this.$lastAction.set({ type: "clear", clearedItems: previousValue });
736
+ return previousValue;
1323
737
  }
1324
- /**
1325
- * Clears all items from the array.
1326
- * @public
1327
- */
1328
- async clear() {
1329
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1330
- const update = () => {
1331
- const items = [...this._value];
1332
- items.forEach((item) => {
1333
- if (isDisposable(item)) item.dispose({ self: true });
1334
- });
1335
- this._value = [];
1336
- this.$lastAction.set({ type: "clear" });
1337
- return true;
1338
- };
1339
- const notify = () => {
1340
- super._notify();
1341
- };
1342
- return FlowGraph.requestWrite(notify, update);
1343
- }
1344
- /**
1345
- * Disposes the FlowArray and its items.
1346
- * @param options - Disposal options.
1347
- * @public
1348
- */
1349
- dispose(options) {
1350
- super.dispose(options);
738
+ dispose() {
739
+ super.dispose();
1351
740
  this._value.forEach((item) => {
1352
- if (isDisposable(item)) item.dispose(options);
741
+ if (item instanceof Disposable) item.dispose();
1353
742
  });
1354
743
  this._value = [];
1355
744
  }
1356
- /* INTERNAL */
1357
- }
1358
- function array(initial) {
1359
- return new FlowArray(initial);
1360
745
  }
1361
746
 
1362
- class FlowMap extends FlowNode {
1363
- /**
1364
- * Last action performed on the FlowMap.
1365
- * @public
1366
- */
747
+ class MapNode extends ValueSyncNode {
1367
748
  $lastAction;
1368
- /**
1369
- * Creates an instance of FlowMap.
1370
- * @param value - Initial map value.
1371
- * @public
1372
- */
1373
749
  constructor(value = /* @__PURE__ */ new Map()) {
1374
750
  super(value);
1375
- this.$lastAction = state({
751
+ this.$lastAction = new ValueSyncNode({
1376
752
  type: "set",
1377
- map: value
753
+ setMap: value,
754
+ clearedMap: /* @__PURE__ */ new Map()
1378
755
  });
1379
756
  }
1380
- /**
1381
- * Adds a new key-value pair to the map.
1382
- *
1383
- * @param key - The key to add.
1384
- * @param value - The value to associate with the key.
1385
- * @throws If the FlowMap instance is disposed.
1386
- * @throws If the key already exists in the map.
1387
- *
1388
- * @remarks
1389
- * Adds a new entry to the internal map, emits the key-value pair via {@link FlowMap.$lastAction},
1390
- * and notifies all subscribers of the change.
1391
- *
1392
- * @public
1393
- */
1394
- async add(key, value) {
1395
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1396
- const update = () => {
1397
- const currentValue = this._value.get(key);
1398
- if (currentValue) {
1399
- throw new Error("[PicoFlow] Key already exists");
1400
- }
1401
- this._value.set(key, value);
1402
- this.$lastAction.set({ type: "add", key, value });
1403
- return true;
1404
- };
1405
- const notify = () => {
1406
- super._notify();
1407
- };
1408
- return FlowGraph.requestWrite(notify, update);
1409
- }
1410
- /**
1411
- * Updates an existing key-value pair in the map.
1412
- *
1413
- * @param key - The key to update.
1414
- * @param value - The new value to associate with the key.
1415
- * @throws If the FlowMap instance is disposed.
1416
- * @throws If the key does not exist in the map.
1417
- *
1418
- * @remarks
1419
- * Updates an existing entry in the internal map, emits the key-value pair via {@link FlowMap.$lastAction},
1420
- * and notifies all subscribers of the change.
1421
- *
1422
- * @public
1423
- */
1424
- async update(key, value) {
1425
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1426
- const update = () => {
1427
- const currentValue = this._value.get(key);
1428
- if (!currentValue) throw new Error("[PicoFlow] Key does not exist");
1429
- if (currentValue !== value) {
1430
- if (isDisposable(currentValue)) currentValue.dispose({ self: true });
1431
- this._value.set(key, value);
1432
- this.$lastAction.set({ type: "update", key, value });
1433
- return true;
1434
- }
1435
- return false;
1436
- };
1437
- const notify = () => {
1438
- super._notify();
1439
- };
1440
- return FlowGraph.requestWrite(notify, update);
1441
- }
1442
- /**
1443
- * Deletes the value at the specified key from the underlying map.
1444
- *
1445
- * @param key - The key to delete.
1446
- * @throws If the FlowMap instance is disposed.
1447
- *
1448
- * @remarks
1449
- * Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$lastAction},
1450
- * and notifies all subscribers of the change.
1451
- *
1452
- * @public
1453
- */
1454
- async delete(key) {
1455
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1456
- const update = () => {
1457
- const value = this._value.get(key);
1458
- if (value === void 0) throw new Error("[PicoFlow] Key does not exist");
1459
- if (isDisposable(value)) value.dispose({ self: true });
1460
- this._value.delete(key);
1461
- this.$lastAction.set({ type: "delete", key, value });
1462
- return true;
1463
- };
1464
- const notify = () => {
1465
- super._notify();
1466
- };
1467
- return FlowGraph.requestWrite(notify, update);
1468
- }
1469
- /**
1470
- * Replaces the entire map with new entries.
1471
- *
1472
- * @param map - The new map of entries.
1473
- * @throws If the FlowMap instance is disposed.
1474
- *
1475
- * @remarks
1476
- * Replaces all entries in the internal map, disposes the old values (if they are disposable),
1477
- * emits the new map via {@link FlowMap.$lastAction}, and notifies all subscribers of the change.
1478
- *
1479
- * @public
1480
- */
1481
- async set(map2) {
1482
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1483
- const update = () => {
1484
- const currentValue = this._value;
1485
- if (currentValue !== map2) {
1486
- currentValue.forEach((item) => {
1487
- if (isDisposable(item)) item.dispose({ self: true });
1488
- });
1489
- }
1490
- this._value = map2;
1491
- if (currentValue !== map2) {
1492
- this.$lastAction.set({ type: "set", map: map2 });
1493
- }
1494
- return currentValue !== map2;
1495
- };
1496
- const notify = () => {
1497
- super._notify();
1498
- };
1499
- return FlowGraph.requestWrite(notify, update);
1500
- }
1501
- /**
1502
- * Clears all entries from the map.
1503
- *
1504
- * @throws If the FlowMap instance is disposed.
1505
- *
1506
- * @remarks
1507
- * Removes all entries from the internal map, disposes the removed values (if they are disposable),
1508
- * emits a clear action via {@link FlowMap.$lastAction}, and notifies all subscribers of the change.
1509
- *
1510
- * @public
1511
- */
1512
- async clear() {
1513
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
1514
- const update = () => {
1515
- const items = [...this._value.values()];
1516
- items.forEach((item) => {
1517
- if (isDisposable(item)) item.dispose({ self: true });
1518
- });
1519
- this._value.clear();
1520
- this.$lastAction.set({ type: "clear" });
1521
- return true;
1522
- };
1523
- const notify = () => {
1524
- super._notify();
1525
- };
1526
- return FlowGraph.requestWrite(notify, update);
1527
- }
1528
- /**
1529
- * Disposes the FlowMap and its values.
1530
- * @param options - Disposal options.
1531
- * @public
1532
- */
1533
- dispose(options) {
1534
- super.dispose(options);
757
+ add(key, value) {
758
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
759
+ const previousValue = this._value.get(key);
760
+ if (previousValue) {
761
+ throw new Error("[PicoFlow] Key already exists");
762
+ }
763
+ this._value.set(key, value);
764
+ this.notifyDependents();
765
+ this.$lastAction.set({ type: "add", key, addedValue: value });
766
+ }
767
+ update(key, value) {
768
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
769
+ const previousValue = this._value.get(key);
770
+ if (!previousValue) throw new Error("[PicoFlow] Key does not exist");
771
+ this._value.set(key, value);
772
+ this.notifyDependents();
773
+ this.$lastAction.set({
774
+ type: "update",
775
+ key,
776
+ setValue: value,
777
+ clearedValue: previousValue
778
+ });
779
+ return previousValue;
780
+ }
781
+ delete(key) {
782
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
783
+ const value = this._value.get(key);
784
+ if (value === void 0) throw new Error("[PicoFlow] Key does not exist");
785
+ this._value.delete(key);
786
+ this.notifyDependents();
787
+ this.$lastAction.set({ type: "delete", key, removedValue: value });
788
+ return value;
789
+ }
790
+ set(mapOrUpdater) {
791
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
792
+ const previousValue = this._value;
793
+ super.set(mapOrUpdater);
794
+ this.$lastAction.set({
795
+ type: "set",
796
+ setMap: this._value,
797
+ clearedMap: previousValue
798
+ });
799
+ return previousValue;
800
+ }
801
+ clear() {
802
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
803
+ const previousValue = this._value;
804
+ this._value = /* @__PURE__ */ new Map();
805
+ this.notifyDependents();
806
+ this.$lastAction.set({ type: "clear", clearedMap: previousValue });
807
+ return previousValue;
808
+ }
809
+ dispose() {
810
+ super.dispose();
1535
811
  this._value.forEach((item) => {
1536
- if (isDisposable(item)) item.dispose(options);
812
+ if (item instanceof Disposable) item.dispose();
1537
813
  });
1538
814
  this._value.clear();
1539
815
  }
1540
816
  }
1541
- function map(initial) {
1542
- if (initial instanceof Map) {
1543
- return new FlowMap(initial);
817
+
818
+ class SignalNode extends Observable {
819
+ subscribe(onTrigger, onError, onPending) {
820
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
821
+ const effect = new EffectNode((t) => this.watch(t), onTrigger, onError, onPending);
822
+ return effect;
1544
823
  }
1545
- return new FlowMap(
1546
- new Map(initial ? Object.entries(initial) : [])
1547
- );
1548
824
  }
1549
825
 
1550
- class SolidState {
1551
- /**
1552
- * Returns the current value.
1553
- */
1554
- get;
1555
- /**
1556
- * Sets the value or updates it using a getter function.
1557
- */
1558
- set;
1559
- /**
1560
- * Creates a new SolidState with the given initial value.
1561
- * @param initialValue - The initial value.
1562
- */
1563
- constructor(initialValue) {
1564
- const [get, set] = createSignal(initialValue);
1565
- this.get = get;
1566
- this.set = set;
826
+ class ValueAsyncNode extends ValueNode {
827
+ _scheduler;
828
+ _compute;
829
+ constructor(promiseOrCompute) {
830
+ super();
831
+ if (typeof promiseOrCompute === "function") {
832
+ this._compute = () => promiseOrCompute(this, this._value);
833
+ } else {
834
+ this._compute = () => promiseOrCompute;
835
+ }
836
+ this.status = "dirty";
837
+ this._scheduler = new AsyncScheduler(
838
+ () => this._compute(),
839
+ (value) => this._onResolve(value),
840
+ (error) => this._onReject(error)
841
+ );
1567
842
  }
1568
- }
1569
- class SolidDerivation {
1570
- /**
1571
- * Returns the current derived value.
1572
- */
1573
- get;
1574
- /**
1575
- * Creates a new SolidDerivation from a getter function or value.
1576
- * @param calculator - The getter function or value.
1577
- */
1578
- constructor(calculator) {
1579
- const get = createMemo(calculator);
1580
- this.get = get;
843
+ _onResolve(value) {
844
+ this.status = "resolved";
845
+ this._value = value;
846
+ this._error = void 0;
847
+ this.notifyDependents();
848
+ }
849
+ _onReject(error) {
850
+ this.status = "error";
851
+ this._error = error;
852
+ this.notifyDependents();
853
+ }
854
+ set(promiseOrUpdater) {
855
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
856
+ if (typeof promiseOrUpdater === "function") {
857
+ const updater = promiseOrUpdater;
858
+ switch (this.status) {
859
+ case "resolved": {
860
+ this.status = "pending";
861
+ this._scheduler.overwrite(updater(this._value));
862
+ this.notifyDependents();
863
+ return;
864
+ }
865
+ case "pending":
866
+ case "error":
867
+ case "dirty": {
868
+ this.status = "pending";
869
+ this._scheduler.overwrite(updater(this._value));
870
+ this.notifyDependents();
871
+ return;
872
+ }
873
+ }
874
+ } else {
875
+ this.status = "pending";
876
+ const promise = promiseOrUpdater;
877
+ this._scheduler.overwrite(promise);
878
+ this.notifyDependents();
879
+ }
1581
880
  }
881
+ refresh() {
882
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
883
+ this.execute();
884
+ }
885
+ }
886
+
887
+ function constantAsync(valueOrInitializer) {
888
+ return new ValueAsyncNode(valueOrInitializer);
889
+ }
890
+
891
+ function derivationAsync(compute) {
892
+ return new ValueAsyncNode(compute);
1582
893
  }
1583
- class SolidResource {
1584
- /**
1585
- * Returns the current value (or undefined if not yet loaded).
1586
- */
1587
- get;
1588
- /**
1589
- * Returns the current resource state.
1590
- */
1591
- state;
1592
- /**
1593
- * Returns the latest successfully loaded value (or undefined).
1594
- */
1595
- latest;
1596
- /**
1597
- * Triggers a refetch of the resource.
1598
- */
1599
- refetch;
1600
- set;
1601
- /**
1602
- * Creates a new SolidResource from a fetcher function.
1603
- * @param fetcher - The async fetcher function.
1604
- */
1605
- constructor(fetcher) {
1606
- const [get, set] = createResource(fetcher);
1607
- this.get = get;
1608
- this.state = () => get.state;
1609
- this.latest = () => get.latest;
1610
- this.refetch = () => set.refetch();
1611
- this.set = (value) => set.mutate(value);
894
+
895
+ function stateAsync(valueOrInitializer) {
896
+ return new ValueAsyncNode(valueOrInitializer);
897
+ }
898
+
899
+ function writableDerivationAsync(compute) {
900
+ return new ValueAsyncNode(compute);
901
+ }
902
+
903
+ function array(initial) {
904
+ return new ArrayNode(initial);
905
+ }
906
+
907
+ function map(initial) {
908
+ if (initial instanceof Map) {
909
+ return new MapNode(initial);
1612
910
  }
911
+ return new MapNode(new Map(initial ? Object.entries(initial) : []));
912
+ }
913
+
914
+ function subscribe(data, onData, onError, onPending) {
915
+ return new EffectNode(data, onData, onError, onPending);
916
+ }
917
+
918
+ function signal() {
919
+ return new SignalNode();
920
+ }
921
+
922
+ function constant(initializer) {
923
+ return new ValueSyncNode(initializer);
924
+ }
925
+
926
+ function derivation(compute) {
927
+ return new ValueSyncNode(compute);
928
+ }
929
+
930
+ function state(valueOrInitializer) {
931
+ return new ValueSyncNode(valueOrInitializer);
932
+ }
933
+
934
+ function writableDerivation(compute) {
935
+ return new ValueSyncNode(compute);
1613
936
  }
1614
937
 
1615
938
  function fromNode(node) {
1616
- let initialized = false;
1617
- const solidResource = new SolidResource(async () => {
1618
- let value;
1619
- if (initialized) {
1620
- value = await node.get(null);
1621
- } else {
1622
- value = await node.pick();
1623
- initialized = true;
1624
- }
1625
- return value;
1626
- });
939
+ const [resource, { refetch }] = createResource(() => node.pick());
1627
940
  let fx;
1628
941
  onMount(() => {
1629
- fx = new FlowEffect(async (t) => {
1630
- node.watch(t);
1631
- solidResource.refetch();
1632
- });
942
+ fx = subscribe(
943
+ // data
944
+ (t) => node.get(t),
945
+ // onData
946
+ () => {
947
+ refetch();
948
+ },
949
+ // onError
950
+ () => {
951
+ refetch();
952
+ },
953
+ // onPending
954
+ () => {
955
+ refetch();
956
+ }
957
+ );
958
+ });
959
+ createEffect(() => {
960
+ resource.error ?? resource();
961
+ resetErrorBoundaries();
1633
962
  });
1634
963
  onCleanup(() => fx.dispose());
1635
- return solidResource;
964
+ return resource;
1636
965
  }
1637
966
  function fromGetter(getter) {
1638
- const derivation = new FlowNodeAsync(async (t) => {
967
+ const derivation = new ValueSyncNode((t) => {
1639
968
  return getter(t);
1640
969
  });
1641
970
  return fromNode(derivation);
1642
971
  }
1643
972
  function from(flow) {
1644
- if (flow instanceof FlowNodeAsync || flow instanceof FlowNode) {
973
+ if (flow instanceof ValueAsyncNode || flow instanceof ValueSyncNode) {
1645
974
  return fromNode(flow);
1646
975
  }
1647
976
  if (typeof flow === "function") {
@@ -1650,4 +979,4 @@ function from(flow) {
1650
979
  throw new Error("Invalid flow type");
1651
980
  }
1652
981
 
1653
- export { FlowArray, FlowEffect, FlowGraph, FlowMap, FlowNode, FlowNodeAsync, FlowSignal, SolidDerivation, SolidResource, SolidState, array, constant, constantAsync, derivation, derivationAsync, effect, from, isDisposable, map, signal, state, stateAsync };
982
+ export { array, constant, constantAsync, derivation, derivationAsync, from, isDisposable, map, signal, state, stateAsync, subscribe, writableDerivation, writableDerivationAsync };