@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
@@ -1,1517 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { derivationAsync, effect, signal, stateAsync } from "#package";
3
-
4
- describe("FlowDerivationAsync", () => {
5
- describe("unit", () => {
6
- describe("initialization", () => {
7
- it("should not call compute function on creation", () => {
8
- const computeFn = vi.fn(async () => 42);
9
- derivationAsync(computeFn);
10
-
11
- expect(computeFn).not.toHaveBeenCalled();
12
- });
13
-
14
- it("should call compute function on first access", async () => {
15
- const computeFn = vi.fn(() => 100);
16
- const $state = stateAsync(Promise.resolve(1));
17
- const $derivation = derivationAsync(async (t) => {
18
- computeFn();
19
- return (await $state.get(t)) * 2;
20
- });
21
-
22
- expect(computeFn).not.toHaveBeenCalled();
23
- await $derivation.pick();
24
- expect(computeFn).toHaveBeenCalledTimes(1);
25
- });
26
-
27
- it("should call compute function again when dependency changes", async () => {
28
- const computeFn = vi.fn(async (t) => {
29
- const $s = stateAsync(Promise.resolve(1));
30
- return (await $s.get(t)) * 2;
31
- });
32
- const $state = stateAsync(Promise.resolve(1));
33
- const $derivation = derivationAsync(async (t) => {
34
- await computeFn(t);
35
- return (await $state.get(t)) * 2;
36
- });
37
-
38
- await $derivation.pick();
39
- expect(computeFn).toHaveBeenCalledTimes(1);
40
-
41
- $state.set(Promise.resolve(2));
42
- await $derivation.pick();
43
- expect(computeFn).toHaveBeenCalledTimes(2);
44
- });
45
-
46
- it("should handle number return values", async () => {
47
- const $state = stateAsync(Promise.resolve(5));
48
- const $derivation = derivationAsync(
49
- async (t) => (await $state.get(t)) * 2,
50
- );
51
- expect(await $derivation.pick()).toBe(10);
52
- });
53
-
54
- it("should handle string return values", async () => {
55
- const $state = stateAsync(Promise.resolve("hello"));
56
- const $derivation = derivationAsync(async (t) =>
57
- (await $state.get(t)).toUpperCase(),
58
- );
59
- expect(await $derivation.pick()).toBe("HELLO");
60
- });
61
-
62
- it("should handle object return values", async () => {
63
- const $state = stateAsync(Promise.resolve({ a: 1 }));
64
- const $derivation = derivationAsync(async (t) => ({
65
- ...(await $state.get(t)),
66
- b: 2,
67
- }));
68
- const value = await $derivation.pick();
69
- expect(value).toEqual({ a: 1, b: 2 });
70
- });
71
-
72
- it("should handle array return values", async () => {
73
- const $state = stateAsync(Promise.resolve([1, 2]));
74
- const $derivation = derivationAsync(async (t) => [
75
- ...(await $state.get(t)),
76
- 3,
77
- ]);
78
- const value = await $derivation.pick();
79
- expect(value).toEqual([1, 2, 3]);
80
- });
81
-
82
- it("should handle null return values", async () => {
83
- const $state = stateAsync(Promise.resolve(true));
84
- const $derivation = derivationAsync(async (t) =>
85
- (await $state.get(t)) ? null : "not null",
86
- );
87
- expect(await $derivation.pick()).toBeNull();
88
- });
89
-
90
- it("should handle undefined return values", async () => {
91
- const $state = stateAsync(Promise.resolve(true));
92
- const $derivation = derivationAsync(async (t) =>
93
- (await $state.get(t)) ? undefined : "defined",
94
- );
95
- expect(await $derivation.pick()).toBeUndefined();
96
- });
97
- });
98
-
99
- describe("get", () => {
100
- it("should return computed value with tracking context", async () => {
101
- const $state = stateAsync(Promise.resolve(5));
102
- const $derivation = derivationAsync(
103
- async (t) => (await $state.get(t)) * 2,
104
- );
105
- const $tracker = stateAsync(Promise.resolve(0));
106
- const value = await $derivation.get($tracker);
107
- expect(value).toBe(10);
108
- });
109
-
110
- it("should compute lazy value on first get call", async () => {
111
- const computeFn = vi.fn(async (t) => {
112
- const $s = stateAsync(Promise.resolve(10));
113
- return (await $s.get(t)) * 2;
114
- });
115
- const $state = stateAsync(Promise.resolve(10));
116
- const $derivation = derivationAsync(async (t) => {
117
- await computeFn(t);
118
- return (await $state.get(t)) * 2;
119
- });
120
- const $tracker = stateAsync(Promise.resolve(0));
121
-
122
- expect(computeFn).not.toHaveBeenCalled();
123
- const value = await $derivation.get($tracker);
124
- expect(value).toBe(20);
125
- expect(computeFn).toHaveBeenCalledTimes(1);
126
- });
127
-
128
- it("should recompute when dependency changes", async () => {
129
- const $state = stateAsync(Promise.resolve(1));
130
- const $derivation = derivationAsync(
131
- async (t) => (await $state.get(t)) * 2,
132
- );
133
- const $tracker = stateAsync(Promise.resolve(0));
134
-
135
- expect(await $derivation.get($tracker)).toBe(2);
136
-
137
- await $state.set(Promise.resolve(2));
138
- expect(await $derivation.get($tracker)).toBe(4);
139
- });
140
- });
141
-
142
- describe("pick", () => {
143
- it("should return computed value without tracking", async () => {
144
- const $state = stateAsync(Promise.resolve(15));
145
- const $derivation = derivationAsync(
146
- async (t) => (await $state.get(t)) * 2,
147
- );
148
- const value = await $derivation.pick();
149
- expect(value).toBe(30);
150
- });
151
-
152
- it("should compute lazy value on first pick call", async () => {
153
- const computeFn = vi.fn(async (t) => {
154
- const $s = stateAsync(Promise.resolve(25));
155
- return (await $s.get(t)) * 2;
156
- });
157
- const $state = stateAsync(Promise.resolve(25));
158
- const $derivation = derivationAsync(async (t) => {
159
- await computeFn(t);
160
- return (await $state.get(t)) * 2;
161
- });
162
-
163
- expect(computeFn).not.toHaveBeenCalled();
164
- const value = await $derivation.pick();
165
- expect(value).toBe(50);
166
- expect(computeFn).toHaveBeenCalledTimes(1);
167
- });
168
-
169
- it("should recompute when dependency changes", async () => {
170
- const $state = stateAsync(Promise.resolve(1));
171
- const $derivation = derivationAsync(
172
- async (t) => (await $state.get(t)) * 2,
173
- );
174
-
175
- expect(await $derivation.pick()).toBe(2);
176
-
177
- $state.set(Promise.resolve(2));
178
- expect(await $derivation.pick()).toBe(4);
179
- });
180
- });
181
-
182
- describe("refresh", () => {
183
- it("should force recomputation", async () => {
184
- const $state = stateAsync(Promise.resolve(1));
185
- const computeFn = vi.fn(async (t) => (await $state.get(t)) * 2);
186
- const $derivation = derivationAsync(computeFn);
187
-
188
- await $derivation.pick();
189
- expect(computeFn).toHaveBeenCalledTimes(1);
190
-
191
- await $derivation.refresh();
192
- expect(computeFn).toHaveBeenCalledTimes(2);
193
- });
194
-
195
- it("should return a Promise", () => {
196
- const $state = stateAsync(Promise.resolve(1));
197
- const $derivation = derivationAsync(
198
- async (t) => (await $state.get(t)) * 2,
199
- );
200
- const result = $derivation.refresh();
201
- expect(result).toBeInstanceOf(Promise);
202
- });
203
-
204
- it("should not notify when refresh returns same value", async () => {
205
- const $state = stateAsync(Promise.resolve(5));
206
- const $derivation = derivationAsync(
207
- async (t) => (await $state.get(t)) * 2,
208
- );
209
- const listener = vi.fn();
210
- $derivation.subscribe(listener);
211
-
212
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
213
-
214
- // Value is 10, refresh will compute same value
215
- await $derivation.refresh();
216
-
217
- // Wait to ensure no additional calls
218
- await new Promise((resolve) => setTimeout(resolve, 50));
219
- expect(listener).toHaveBeenCalledTimes(1);
220
- });
221
-
222
- it("should notify when refresh returns different value", async () => {
223
- let multiplier = 2;
224
- const $state = stateAsync(Promise.resolve(5));
225
- const $derivation = derivationAsync(
226
- async (t) => (await $state.get(t)) * multiplier,
227
- );
228
- const listener = vi.fn();
229
- $derivation.subscribe(async (promise) => listener(await promise));
230
-
231
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
232
- expect(listener).toHaveBeenLastCalledWith(10);
233
-
234
- multiplier = 3;
235
- await $derivation.refresh();
236
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
237
- });
238
-
239
- it("should recompute even if not dirty", async () => {
240
- const $state = stateAsync(Promise.resolve(1));
241
- const computeFn = vi.fn(async (t) => (await $state.get(t)) * 2);
242
- const $derivation = derivationAsync(computeFn);
243
-
244
- await $derivation.pick();
245
- expect(computeFn).toHaveBeenCalledTimes(1);
246
-
247
- // Not dirty, but refresh forces recomputation
248
- await $derivation.refresh();
249
- expect(computeFn).toHaveBeenCalledTimes(2);
250
- });
251
- });
252
-
253
- describe("watch", () => {
254
- it("should register dependency when watch is called", async () => {
255
- const $state = stateAsync(Promise.resolve(35));
256
- const $derivation = derivationAsync(
257
- async (t) => (await $state.get(t)) * 2,
258
- );
259
- const $tracker = stateAsync(Promise.resolve(0));
260
-
261
- expect(() => $derivation.watch($tracker)).not.toThrow();
262
- });
263
-
264
- it("should compute lazy value when watch is called", async () => {
265
- const computeFn = vi.fn(async (t) => {
266
- const $s = stateAsync(Promise.resolve(40));
267
- return (await $s.get(t)) * 2;
268
- });
269
- const $state = stateAsync(Promise.resolve(40));
270
- const $derivation = derivationAsync(async (t) => {
271
- await computeFn(t);
272
- return (await $state.get(t)) * 2;
273
- });
274
- const $tracker = stateAsync(Promise.resolve(0));
275
-
276
- expect(computeFn).not.toHaveBeenCalled();
277
- $derivation.watch($tracker);
278
- expect(computeFn).toHaveBeenCalledTimes(1);
279
- });
280
- });
281
-
282
- describe("subscribe", () => {
283
- it("should return a disposer function", () => {
284
- const $state = stateAsync(Promise.resolve(50));
285
- const $derivation = derivationAsync(
286
- async (t) => (await $state.get(t)) * 2,
287
- );
288
- const listener = vi.fn();
289
- const disposer = $derivation.subscribe(listener);
290
-
291
- expect(typeof disposer).toBe("function");
292
- });
293
-
294
- it("should call listener immediately with computed value", async () => {
295
- const $state = stateAsync(Promise.resolve(55));
296
- const $derivation = derivationAsync(
297
- async (t) => (await $state.get(t)) * 2,
298
- );
299
- const listener = vi.fn();
300
-
301
- $derivation.subscribe(listener);
302
-
303
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
304
- expect(await listener.mock.calls[0][0]).toBe(110);
305
- });
306
-
307
- it("should call listener when value changes", async () => {
308
- const $state = stateAsync(Promise.resolve(60));
309
- const $derivation = derivationAsync(
310
- async (t) => (await $state.get(t)) * 2,
311
- );
312
- const listener = vi.fn();
313
-
314
- $derivation.subscribe(async (promise) => listener(await promise));
315
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
316
-
317
- $state.set(Promise.resolve(70));
318
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
319
- expect(await listener.mock.calls[1][0]).toBe(140);
320
- });
321
-
322
- it("should not call listener when value does not change", async () => {
323
- const $state = stateAsync(Promise.resolve(75));
324
- const $derivation = derivationAsync(
325
- async (t) => (await $state.get(t)) * 2,
326
- );
327
- const listener = vi.fn();
328
-
329
- $derivation.subscribe(listener);
330
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
331
-
332
- $state.set(Promise.resolve(75));
333
- await new Promise((resolve) => setTimeout(resolve, 50));
334
- expect(listener).toHaveBeenCalledTimes(1);
335
- });
336
-
337
- it("should support multiple subscriptions", async () => {
338
- const $state = stateAsync(Promise.resolve(80));
339
- const $derivation = derivationAsync(
340
- async (t) => (await $state.get(t)) * 2,
341
- );
342
- const listener1 = vi.fn();
343
- const listener2 = vi.fn();
344
- const listener3 = vi.fn();
345
-
346
- $derivation.subscribe(listener1);
347
- $derivation.subscribe(listener2);
348
- $derivation.subscribe(listener3);
349
-
350
- await vi.waitFor(() => {
351
- expect(listener1).toHaveBeenCalledTimes(1);
352
- expect(listener2).toHaveBeenCalledTimes(1);
353
- expect(listener3).toHaveBeenCalledTimes(1);
354
- });
355
-
356
- expect(await listener1.mock.calls[0][0]).toBe(160);
357
- expect(await listener2.mock.calls[0][0]).toBe(160);
358
- expect(await listener3.mock.calls[0][0]).toBe(160);
359
- });
360
-
361
- it("should dispose effect when disposer is called", async () => {
362
- const $state = stateAsync(Promise.resolve(85));
363
- const $derivation = derivationAsync(
364
- async (t) => (await $state.get(t)) * 2,
365
- );
366
- const listener = vi.fn();
367
-
368
- const unsubscribe = $derivation.subscribe(async (promise) =>
369
- listener(await promise),
370
- );
371
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
372
-
373
- $state.set(Promise.resolve(90));
374
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
375
-
376
- unsubscribe();
377
-
378
- $state.set(Promise.resolve(95));
379
- await new Promise((resolve) => setTimeout(resolve, 50));
380
- expect(listener).toHaveBeenCalledTimes(2);
381
- });
382
- });
383
-
384
- describe("trigger", () => {
385
- it("should return a Promise", () => {
386
- const $state = stateAsync(Promise.resolve(1));
387
- const $derivation = derivationAsync(
388
- async (t) => (await $state.get(t)) * 2,
389
- );
390
- const result = $derivation.trigger();
391
- expect(result).toBeInstanceOf(Promise);
392
- });
393
-
394
- it("should allow multiple triggers", async () => {
395
- const $state = stateAsync(Promise.resolve(1));
396
- const $derivation = derivationAsync(
397
- async (t) => (await $state.get(t)) * 2,
398
- );
399
- const promise1 = $derivation.trigger();
400
- const promise2 = $derivation.trigger();
401
- const promise3 = $derivation.trigger();
402
-
403
- expect(promise1).toBeInstanceOf(Promise);
404
- expect(promise2).toBeInstanceOf(Promise);
405
- expect(promise3).toBeInstanceOf(Promise);
406
-
407
- await Promise.all([promise1, promise2, promise3]);
408
- });
409
- });
410
-
411
- describe("disposal", () => {
412
- it("should have disposed property set to false initially", () => {
413
- const $state = stateAsync(Promise.resolve(1));
414
- const $derivation = derivationAsync(
415
- async (t) => (await $state.get(t)) * 2,
416
- );
417
- expect($derivation.disposed).toBe(false);
418
- });
419
-
420
- it("should have disposed property set to true after disposal", () => {
421
- const $state = stateAsync(Promise.resolve(1));
422
- const $derivation = derivationAsync(
423
- async (t) => (await $state.get(t)) * 2,
424
- );
425
- $derivation.dispose();
426
- expect($derivation.disposed).toBe(true);
427
- });
428
-
429
- it("should throw error when disposed twice", () => {
430
- const $state = stateAsync(Promise.resolve(1));
431
- const $derivation = derivationAsync(
432
- async (t) => (await $state.get(t)) * 2,
433
- );
434
- $derivation.dispose();
435
- expect(() => $derivation.dispose()).toThrow(
436
- "[PicoFlow] Primitive is disposed",
437
- );
438
- });
439
-
440
- it("should accept self option without throwing", () => {
441
- const $state = stateAsync(Promise.resolve(1));
442
- const $derivation = derivationAsync(
443
- async (t) => (await $state.get(t)) * 2,
444
- );
445
- expect(() => $derivation.dispose({ self: true })).not.toThrow();
446
- expect($derivation.disposed).toBe(true);
447
- });
448
-
449
- it("should accept default options without throwing", () => {
450
- const $state = stateAsync(Promise.resolve(1));
451
- const $derivation = derivationAsync(
452
- async (t) => (await $state.get(t)) * 2,
453
- );
454
- expect(() => $derivation.dispose()).not.toThrow();
455
- expect($derivation.disposed).toBe(true);
456
- });
457
- });
458
-
459
- describe("special cases", () => {
460
- it("should handle dynamic dependencies - dependencies change between computations", async () => {
461
- const $state1 = stateAsync(Promise.resolve(1));
462
- const $state2 = stateAsync(Promise.resolve(10));
463
- const $cond = stateAsync(Promise.resolve(true));
464
- const $derivation = derivationAsync(async (t) => {
465
- if (await $cond.get(t)) {
466
- return (await $state1.get(t)) * 2;
467
- }
468
- return (await $state2.get(t)) * 2;
469
- });
470
-
471
- // Initially depends on state1
472
- expect(await $derivation.pick()).toBe(2);
473
-
474
- // Change state1 - should recompute
475
- $state1.set(Promise.resolve(2));
476
- expect(await $derivation.pick()).toBe(4);
477
-
478
- // Switch to state2 dependency
479
- $cond.set(Promise.resolve(false));
480
- expect(await $derivation.pick()).toBe(20);
481
-
482
- // Change state2 - should recompute
483
- $state2.set(Promise.resolve(20));
484
- expect(await $derivation.pick()).toBe(40);
485
-
486
- // Change state1 - should NOT recompute (no longer a dependency)
487
- $state1.set(Promise.resolve(100));
488
- expect(await $derivation.pick()).toBe(40);
489
- });
490
-
491
- it("should unregister dependencies that are no longer used", async () => {
492
- const $state1 = stateAsync(Promise.resolve(1));
493
- const $state2 = stateAsync(Promise.resolve(10));
494
- const $cond = stateAsync(Promise.resolve(true));
495
- const $derivation = derivationAsync(async (t) => {
496
- if (await $cond.get(t)) {
497
- return await $state1.get(t);
498
- }
499
- return await $state2.get(t);
500
- });
501
-
502
- await $derivation.pick();
503
- // Initially depends on state1
504
-
505
- $cond.set(Promise.resolve(false));
506
- await $derivation.pick();
507
- // Now depends on state2, state1 should be unregistered
508
- });
509
-
510
- it("should mark as dirty when dependency changes", async () => {
511
- const $state = stateAsync(Promise.resolve(1));
512
- const computeFn = vi.fn(async (t) => (await $state.get(t)) * 2);
513
- const $derivation = derivationAsync(computeFn);
514
-
515
- await $derivation.pick();
516
- expect(computeFn).toHaveBeenCalledTimes(1);
517
-
518
- // Change dependency - marks as dirty but doesn't recompute yet
519
- $state.set(Promise.resolve(2));
520
-
521
- // Next access triggers recomputation
522
- await $derivation.pick();
523
- expect(computeFn).toHaveBeenCalledTimes(2);
524
- });
525
-
526
- it("should recompute only on next access when dirty", async () => {
527
- const $state = stateAsync(Promise.resolve(1));
528
- const computeFn = vi.fn(async (t) => (await $state.get(t)) * 2);
529
- const $derivation = derivationAsync(computeFn);
530
-
531
- await $derivation.pick();
532
- expect(computeFn).toHaveBeenCalledTimes(1);
533
-
534
- // Multiple dependency changes
535
- $state.set(Promise.resolve(2));
536
- $state.set(Promise.resolve(3));
537
- $state.set(Promise.resolve(4));
538
-
539
- // Still only called once more (lazy recomputation)
540
- await $derivation.pick();
541
- expect(computeFn).toHaveBeenCalledTimes(2);
542
- });
543
-
544
- it("should handle nested derivations", async () => {
545
- const $state = stateAsync(Promise.resolve(1));
546
- const $derivation1 = derivationAsync(
547
- async (t) => (await $state.get(t)) * 2,
548
- );
549
- const $derivation2 = derivationAsync(
550
- async (t) => (await $derivation1.get(t)) * 2,
551
- );
552
-
553
- expect(await $derivation2.pick()).toBe(4);
554
-
555
- $state.set(Promise.resolve(2));
556
- expect(await $derivation2.pick()).toBe(8);
557
- });
558
-
559
- it("should handle compute function that returns undefined", async () => {
560
- const $state = stateAsync(Promise.resolve(true));
561
- const $derivation = derivationAsync(async (t) =>
562
- (await $state.get(t)) ? undefined : "defined",
563
- );
564
- expect(await $derivation.pick()).toBeUndefined();
565
- });
566
-
567
- it("should handle compute function that throws error", async () => {
568
- const error = new Error("Compute error");
569
- const $state = stateAsync(Promise.resolve(1));
570
- const $derivation = derivationAsync(async (t) => {
571
- if ((await $state.get(t)) === 1) {
572
- throw error;
573
- }
574
- return (await $state.get(t)) * 2;
575
- });
576
-
577
- await expect($derivation.pick()).rejects.toThrow("Compute error");
578
- });
579
- });
580
-
581
- describe("error handling", () => {
582
- describe("compute function errors", () => {
583
- it("should propagate error when pick is called with throwing compute", async () => {
584
- const error = new Error("Compute pick error");
585
- const $derivation = derivationAsync(async () => {
586
- throw error;
587
- });
588
-
589
- await expect($derivation.pick()).rejects.toThrow(
590
- "Compute pick error",
591
- );
592
- });
593
-
594
- it("should propagate error when pick throws on subsequent compute", async () => {
595
- let throwNow = false;
596
- const $state = stateAsync(Promise.resolve(1));
597
- const $derivation = derivationAsync(async (t) => {
598
- if (throwNow) {
599
- throw new Error("Compute pick error");
600
- }
601
- throwNow = true;
602
- return await $state.get(t);
603
- });
604
-
605
- expect(await $derivation.pick()).toBe(1);
606
-
607
- $state.set(Promise.resolve(2));
608
- await expect($derivation.pick()).rejects.toThrow(
609
- "Compute pick error",
610
- );
611
- });
612
-
613
- it("should propagate error when get is called with throwing compute", async () => {
614
- const error = new Error("Compute get error");
615
- const $derivation = derivationAsync(async () => {
616
- throw error;
617
- });
618
-
619
- const $tracker = stateAsync(Promise.resolve(0));
620
- await expect($derivation.get($tracker)).rejects.toThrow(
621
- "Compute get error",
622
- );
623
- });
624
-
625
- it("should propagate error when get throws on subsequent compute", async () => {
626
- let throwNow = false;
627
- const $state = stateAsync(Promise.resolve(1));
628
- const $derivation = derivationAsync(async (t) => {
629
- if (throwNow) {
630
- throw new Error("Compute get error");
631
- }
632
- throwNow = true;
633
- return await $state.get(t);
634
- });
635
- const $tracker = stateAsync(Promise.resolve(0));
636
-
637
- expect(await $derivation.get($tracker)).toBe(1);
638
- await $state.set(Promise.resolve(2));
639
- await expect($derivation.get($tracker)).rejects.toThrow(
640
- "Compute get error",
641
- );
642
- });
643
- });
644
-
645
- describe("disposed derivation", () => {
646
- it("should throw error when pick is called after disposal", async () => {
647
- const $derivation = derivationAsync(
648
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
649
- );
650
-
651
- await $derivation.pick();
652
- $derivation.dispose();
653
-
654
- await expect($derivation.pick()).rejects.toThrow(
655
- "[PicoFlow] Primitive is disposed",
656
- );
657
- });
658
-
659
- it("should throw error when get is called after disposal", async () => {
660
- const $derivation = derivationAsync(
661
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
662
- );
663
- const $tracker = stateAsync(Promise.resolve(0));
664
-
665
- await $derivation.get($tracker);
666
- $derivation.dispose();
667
-
668
- await expect($derivation.get($tracker)).rejects.toThrow(
669
- "[PicoFlow] Primitive is disposed",
670
- );
671
- });
672
-
673
- it("should throw error when refresh is called after disposal", async () => {
674
- const $derivation = derivationAsync(
675
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
676
- );
677
-
678
- $derivation.dispose();
679
-
680
- await expect($derivation.refresh()).rejects.toThrow(
681
- "[PicoFlow] Primitive is disposed",
682
- );
683
- });
684
-
685
- it("should throw error when watch is called after disposal", async () => {
686
- const $derivation = derivationAsync(
687
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
688
- );
689
- const $tracker = stateAsync(Promise.resolve(0));
690
-
691
- await $derivation.watch($tracker);
692
- $derivation.dispose();
693
-
694
- await expect($derivation.watch($tracker)).rejects.toThrow(
695
- "[PicoFlow] Primitive is disposed",
696
- );
697
- });
698
-
699
- it("should throw error when subscribe is called after disposal", () => {
700
- const $derivation = derivationAsync(
701
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
702
- );
703
- const listener = vi.fn();
704
-
705
- $derivation.dispose();
706
-
707
- expect(() => $derivation.subscribe(listener)).toThrow(
708
- "[PicoFlow] Primitive is disposed",
709
- );
710
- });
711
-
712
- it("should throw error when trigger is called after disposal", async () => {
713
- const $derivation = derivationAsync(
714
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
715
- );
716
- $derivation.dispose();
717
- await expect($derivation.trigger()).rejects.toThrow(
718
- "[PicoFlow] Primitive is disposed",
719
- );
720
- });
721
- });
722
- });
723
- });
724
-
725
- describe("with effect", () => {
726
- describe("get", () => {
727
- it("should create reactive dependency when get is used in effect", async () => {
728
- const $state = stateAsync(Promise.resolve(100));
729
- const $derivation = derivationAsync(
730
- async (t) => (await $state.get(t)) * 2,
731
- );
732
- const effectFn = vi.fn();
733
-
734
- effect(async (t) => {
735
- await $derivation.get(t);
736
- effectFn();
737
- });
738
-
739
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
740
- });
741
-
742
- it("should call effect with initial computed value", async () => {
743
- const $state = stateAsync(Promise.resolve(1));
744
- const $derivation = derivationAsync(
745
- async (t) => (await $state.get(t)) * 2,
746
- );
747
- const effectFn = vi.fn();
748
- effect(async (t) => effectFn(await $derivation.get(t)));
749
-
750
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
751
- expect(effectFn).toHaveBeenLastCalledWith(2);
752
- });
753
-
754
- it("should call effect when dependency changes", async () => {
755
- const $state = stateAsync(Promise.resolve(1));
756
- const $derivation = derivationAsync(
757
- async (t) => (await $state.get(t)) * 2,
758
- );
759
- const effectFn = vi.fn();
760
- effect(async (t) => effectFn(await $derivation.get(t)));
761
-
762
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
763
- expect(effectFn).toHaveBeenLastCalledWith(2);
764
-
765
- $state.set(Promise.resolve(2));
766
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
767
- expect(effectFn).toHaveBeenLastCalledWith(4);
768
- });
769
-
770
- it("should not call effect when value does not change", async () => {
771
- const $state = stateAsync(Promise.resolve(1));
772
- const $derivation = derivationAsync(
773
- async (t) => (await $state.get(t)) * 2,
774
- );
775
- const effectFn = vi.fn();
776
- effect(async (t) => effectFn(await $derivation.get(t)));
777
-
778
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
779
-
780
- $state.set(Promise.resolve(1));
781
- await new Promise((resolve) => setTimeout(resolve, 50));
782
- expect(effectFn).toHaveBeenCalledTimes(1);
783
- });
784
-
785
- it("should not call effect after effect is disposed", async () => {
786
- const $state = stateAsync(Promise.resolve(1));
787
- const $derivation = derivationAsync(
788
- async (t) => (await $state.get(t)) * 2,
789
- );
790
- const effectFn = vi.fn();
791
- const $effect = effect(async (t) => effectFn(await $derivation.get(t)));
792
-
793
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
794
- expect(effectFn).toHaveBeenLastCalledWith(2);
795
-
796
- $state.set(Promise.resolve(2));
797
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
798
- expect(effectFn).toHaveBeenLastCalledWith(4);
799
-
800
- $effect.dispose();
801
-
802
- $state.set(Promise.resolve(3));
803
- await new Promise((resolve) => setTimeout(resolve, 50));
804
- expect(effectFn).toHaveBeenCalledTimes(2);
805
- });
806
-
807
- it("should support multiple effects depending on same derivation", async () => {
808
- const $state = stateAsync(Promise.resolve(200));
809
- const $derivation = derivationAsync(
810
- async (t) => (await $state.get(t)) * 2,
811
- );
812
- const effectFn1 = vi.fn();
813
- const effectFn2 = vi.fn();
814
- const effectFn3 = vi.fn();
815
-
816
- effect(async (t) => {
817
- await $derivation.get(t);
818
- effectFn1();
819
- });
820
-
821
- effect(async (t) => {
822
- await $derivation.get(t);
823
- effectFn2();
824
- });
825
-
826
- effect(async (t) => {
827
- await $derivation.get(t);
828
- effectFn3();
829
- });
830
-
831
- await vi.waitFor(() => {
832
- expect(effectFn1).toHaveBeenCalledTimes(1);
833
- expect(effectFn2).toHaveBeenCalledTimes(1);
834
- expect(effectFn3).toHaveBeenCalledTimes(1);
835
- });
836
-
837
- $state.set(Promise.resolve(300));
838
- await vi.waitFor(() => {
839
- expect(effectFn1).toHaveBeenCalledTimes(2);
840
- expect(effectFn2).toHaveBeenCalledTimes(2);
841
- expect(effectFn3).toHaveBeenCalledTimes(2);
842
- });
843
- });
844
-
845
- it("should support effect with get and untracked get mixed", async () => {
846
- const $state1 = stateAsync(Promise.resolve(10));
847
- const $state2 = stateAsync(Promise.resolve(20));
848
- const $derivation1 = derivationAsync(
849
- async (t) => (await $state1.get(t)) * 2,
850
- );
851
- const $derivation2 = derivationAsync(
852
- async (t) => (await $state2.get(t)) * 2,
853
- );
854
- const effectFn = vi.fn();
855
-
856
- effect(async (t) => {
857
- const tracked = await $derivation1.get(t);
858
- const untracked = await $derivation2.get(null);
859
- effectFn(tracked, untracked);
860
- });
861
-
862
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
863
- expect(effectFn).toHaveBeenLastCalledWith(20, 40);
864
-
865
- // Changing derivation1 should trigger effect
866
- $state1.set(Promise.resolve(11));
867
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
868
- expect(effectFn).toHaveBeenLastCalledWith(22, 40);
869
-
870
- // Changing derivation2 should not trigger effect (untracked)
871
- $state2.set(Promise.resolve(21));
872
- await new Promise((resolve) => setTimeout(resolve, 50));
873
- expect(effectFn).toHaveBeenCalledTimes(2);
874
- });
875
-
876
- it("should support effect depending on multiple derivations", async () => {
877
- const $stateA = stateAsync(Promise.resolve(5));
878
- const $stateB = stateAsync(Promise.resolve(10));
879
- const $derivationA = derivationAsync(
880
- async (t) => (await $stateA.get(t)) * 2,
881
- );
882
- const $derivationB = derivationAsync(
883
- async (t) => (await $stateB.get(t)) * 2,
884
- );
885
- const effectFn = vi.fn();
886
-
887
- effect(async (t) => {
888
- const a = await $derivationA.get(t);
889
- const b = await $derivationB.get(t);
890
- effectFn(a, b);
891
- });
892
-
893
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
894
- expect(effectFn).toHaveBeenLastCalledWith(10, 20);
895
-
896
- $stateA.set(Promise.resolve(6));
897
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
898
- expect(effectFn).toHaveBeenLastCalledWith(12, 20);
899
-
900
- $stateB.set(Promise.resolve(11));
901
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
902
- expect(effectFn).toHaveBeenLastCalledWith(12, 22);
903
- });
904
- });
905
-
906
- describe("watch", () => {
907
- it("should register dependency when watch is used in effect", async () => {
908
- const $state = stateAsync(Promise.resolve(400));
909
- const $derivation = derivationAsync(
910
- async (t) => (await $state.get(t)) * 2,
911
- );
912
- const effectFn = vi.fn();
913
-
914
- effect(async (t) => {
915
- await $derivation.watch(t);
916
- effectFn();
917
- });
918
-
919
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
920
- });
921
-
922
- it("should trigger re-runs when derivation changes after watch", async () => {
923
- const $state = stateAsync(Promise.resolve(500));
924
- const $derivation = derivationAsync(
925
- async (t) => (await $state.get(t)) * 2,
926
- );
927
- const effectFn = vi.fn();
928
-
929
- effect(async (t) => {
930
- await $derivation.watch(t);
931
- effectFn();
932
- });
933
-
934
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
935
-
936
- $state.set(Promise.resolve(600));
937
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
938
- });
939
- });
940
-
941
- describe("subscribe", () => {
942
- it("should create effect internally when subscribe is used", async () => {
943
- const $state = stateAsync(Promise.resolve(700));
944
- const $derivation = derivationAsync(
945
- async (t) => (await $state.get(t)) * 2,
946
- );
947
- const listener = vi.fn();
948
-
949
- $derivation.subscribe(listener);
950
-
951
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
952
- expect(await listener.mock.calls[0][0]).toBe(1400);
953
- });
954
-
955
- it("should support multiple subscriptions with effects", async () => {
956
- const $state = stateAsync(Promise.resolve(800));
957
- const $derivation = derivationAsync(
958
- async (t) => (await $state.get(t)) * 2,
959
- );
960
- const listener1 = vi.fn();
961
- const listener2 = vi.fn();
962
-
963
- $derivation.subscribe(listener1);
964
- $derivation.subscribe(listener2);
965
-
966
- await vi.waitFor(() => {
967
- expect(listener1).toHaveBeenCalledTimes(1);
968
- expect(listener2).toHaveBeenCalledTimes(1);
969
- });
970
-
971
- $state.set(Promise.resolve(900));
972
- await vi.waitFor(() => {
973
- expect(listener1).toHaveBeenCalledTimes(2);
974
- expect(listener2).toHaveBeenCalledTimes(2);
975
- });
976
- });
977
-
978
- it("should dispose effect when disposer is called", async () => {
979
- const $state = stateAsync(Promise.resolve(1000));
980
- const $derivation = derivationAsync(
981
- async (t) => (await $state.get(t)) * 2,
982
- );
983
- const listener = vi.fn();
984
-
985
- const unsubscribe = $derivation.subscribe(async (promise) =>
986
- listener(await promise),
987
- );
988
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(1));
989
-
990
- $state.set(Promise.resolve(1100));
991
- await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
992
-
993
- unsubscribe();
994
-
995
- $state.set(Promise.resolve(1200));
996
- await new Promise((resolve) => setTimeout(resolve, 50));
997
- expect(listener).toHaveBeenCalledTimes(2);
998
- });
999
- });
1000
-
1001
- describe("error handling", () => {
1002
- describe("compute function errors", () => {
1003
- it("should propagate error when lazy compute throws in effect", async () => {
1004
- const error = new Error("Effect compute error");
1005
- const $derivation = derivationAsync(async () => {
1006
- throw error;
1007
- });
1008
-
1009
- const $effect = effect(async (t) => {
1010
- await $derivation.get(t);
1011
- });
1012
-
1013
- await expect($effect.settled).rejects.toThrow("Effect compute error");
1014
- });
1015
-
1016
- it("should propagate error when lazy compute throws on subsequent effect run", async () => {
1017
- const $state = stateAsync(Promise.resolve(1));
1018
- const $derivation = derivationAsync(async (t) => {
1019
- const value = await $state.get(t);
1020
- if (value > 1) {
1021
- throw new Error("Effect compute error");
1022
- }
1023
- return value;
1024
- });
1025
-
1026
- const $effect = effect(async (t) => {
1027
- await $derivation.get(t);
1028
- });
1029
-
1030
- await expect($effect.settled).resolves.toBeUndefined();
1031
-
1032
- await expect($state.set(Promise.resolve(2))).rejects.toThrow(
1033
- "Effect compute error",
1034
- );
1035
- await expect($effect.settled).rejects.toThrow("Effect compute error");
1036
- });
1037
- });
1038
-
1039
- describe("disposed derivation", () => {
1040
- it("should throw error when creating an effect with a disposed derivation", async () => {
1041
- const $derivation = derivationAsync(
1042
- async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2,
1043
- );
1044
-
1045
- $derivation.dispose();
1046
-
1047
- const $effect = effect(async (t) => {
1048
- await $derivation.get(t);
1049
- });
1050
-
1051
- await expect($effect.settled).rejects.toThrow(
1052
- "[PicoFlow] Primitive is disposed",
1053
- );
1054
- });
1055
- });
1056
- });
1057
-
1058
- describe("disposal", () => {
1059
- it("should dispose effects when derivation is disposed", async () => {
1060
- const $state = stateAsync(Promise.resolve(1300));
1061
- const $derivation = derivationAsync(
1062
- async (t) => (await $state.get(t)) * 2,
1063
- );
1064
- const effectFn = vi.fn();
1065
- const $effect = effect(async (t) => {
1066
- await $derivation.get(t);
1067
- effectFn();
1068
- });
1069
-
1070
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1071
- expect($effect.disposed).toBe(false);
1072
-
1073
- $derivation.dispose();
1074
-
1075
- expect($effect.disposed).toBe(true);
1076
- });
1077
-
1078
- it("should not dispose effects when derivation is disposed with self option", async () => {
1079
- const $state = stateAsync(Promise.resolve(1400));
1080
- const $derivation = derivationAsync(
1081
- async (t) => (await $state.get(t)) * 2,
1082
- );
1083
- const effectFn = vi.fn();
1084
- const $effect = effect(async (t) => {
1085
- await $derivation.get(t);
1086
- effectFn();
1087
- });
1088
-
1089
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1090
- expect($effect.disposed).toBe(false);
1091
-
1092
- $derivation.dispose({ self: true });
1093
-
1094
- expect($effect.disposed).toBe(false);
1095
- expect($derivation.disposed).toBe(true);
1096
- });
1097
-
1098
- it("should unregister effects when derivation is disposed with self option", async () => {
1099
- const $state = stateAsync(Promise.resolve(1500));
1100
- const $derivation = derivationAsync(
1101
- async (t) => (await $state.get(t)) * 2,
1102
- );
1103
- const effectFn = vi.fn();
1104
- const $effect = effect(async (t) => {
1105
- await $derivation.get(t);
1106
- effectFn();
1107
- });
1108
-
1109
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1110
-
1111
- $derivation.dispose({ self: true });
1112
-
1113
- // Effect should still be active but not notified
1114
- expect($effect.disposed).toBe(false);
1115
-
1116
- // But derivation is disposed so operations should fail
1117
- const $tracker = stateAsync(Promise.resolve(0));
1118
- await expect($derivation.get($tracker)).rejects.toThrow(
1119
- "[PicoFlow] Primitive is disposed",
1120
- );
1121
- });
1122
- });
1123
-
1124
- describe("dependencies", () => {
1125
- it("should handle chained dependencies", async () => {
1126
- const $state = stateAsync(Promise.resolve(1));
1127
- const $derivation1 = derivationAsync(
1128
- async (t) => (await $state.get(t)) * 2,
1129
- );
1130
- const $derivation2 = derivationAsync(
1131
- async (t) => (await $derivation1.get(t)) * 2,
1132
- );
1133
- const effectFn = vi.fn();
1134
- effect(async (t) => effectFn(await $derivation2.get(t)));
1135
-
1136
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1137
- expect(effectFn).toHaveBeenLastCalledWith(4);
1138
-
1139
- $state.set(Promise.resolve(2));
1140
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1141
- expect(effectFn).toHaveBeenLastCalledWith(8);
1142
- });
1143
-
1144
- it("should handle multiple dependencies", async () => {
1145
- const $state1 = stateAsync(Promise.resolve(1));
1146
- const $state2 = stateAsync(Promise.resolve(2));
1147
- const $derivation = derivationAsync(
1148
- async (t) => (await $state1.get(t)) + (await $state2.get(t)),
1149
- );
1150
- const effectFn = vi.fn();
1151
- effect(async (t) => effectFn(await $derivation.get(t)));
1152
-
1153
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1154
- expect(effectFn).toHaveBeenLastCalledWith(3);
1155
-
1156
- $state1.set(Promise.resolve(2));
1157
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1158
- expect(effectFn).toHaveBeenLastCalledWith(4);
1159
-
1160
- $state2.set(Promise.resolve(3));
1161
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1162
- expect(effectFn).toHaveBeenLastCalledWith(5);
1163
- });
1164
-
1165
- it("should handle multiple dependants", async () => {
1166
- const $state = stateAsync(Promise.resolve(1));
1167
- const $derivation1 = derivationAsync(
1168
- async (t) => (await $state.get(t)) * 2,
1169
- );
1170
- const $derivation2 = derivationAsync(
1171
- async (t) => (await $state.get(t)) * 3,
1172
- );
1173
- const effect1Fn = vi.fn();
1174
- const effect2Fn = vi.fn();
1175
-
1176
- effect(async (t) => effect1Fn(await $derivation1.get(t)));
1177
- effect(async (t) => effect2Fn(await $derivation2.get(t)));
1178
-
1179
- await vi.waitFor(() => {
1180
- expect(effect1Fn).toHaveBeenCalledTimes(1);
1181
- expect(effect2Fn).toHaveBeenCalledTimes(1);
1182
- });
1183
- expect(effect1Fn).toHaveBeenLastCalledWith(2);
1184
- expect(effect2Fn).toHaveBeenLastCalledWith(3);
1185
-
1186
- $state.set(Promise.resolve(2));
1187
- await vi.waitFor(() => {
1188
- expect(effect1Fn).toHaveBeenCalledTimes(2);
1189
- expect(effect2Fn).toHaveBeenCalledTimes(2);
1190
- });
1191
- expect(effect1Fn).toHaveBeenLastCalledWith(4);
1192
- expect(effect2Fn).toHaveBeenLastCalledWith(6);
1193
- });
1194
-
1195
- it("should handle derivation depending on state and signal", async () => {
1196
- const $signal = signal();
1197
- const $state = stateAsync(Promise.resolve(1));
1198
- const $derivation = derivationAsync(async (t) => {
1199
- $signal.watch(t);
1200
- return (await $state.get(t)) * 2;
1201
- });
1202
- const effectFn = vi.fn();
1203
- effect(async (t) => effectFn(await $derivation.get(t)));
1204
-
1205
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1206
- expect(effectFn).toHaveBeenLastCalledWith(2);
1207
-
1208
- $signal.trigger();
1209
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1210
- expect(effectFn).toHaveBeenLastCalledWith(2);
1211
-
1212
- $state.set(Promise.resolve(2));
1213
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1214
- expect(effectFn).toHaveBeenLastCalledWith(4);
1215
- });
1216
-
1217
- it("should handle derivation depending on multiple states", async () => {
1218
- const $state1 = stateAsync(Promise.resolve(5));
1219
- const $state2 = stateAsync(Promise.resolve(10));
1220
- const $state3 = stateAsync(Promise.resolve(15));
1221
- const $derivation = derivationAsync(
1222
- async (t) =>
1223
- (await $state1.get(t)) +
1224
- (await $state2.get(t)) +
1225
- (await $state3.get(t)),
1226
- );
1227
- const effectFn = vi.fn();
1228
- effect(async (t) => effectFn(await $derivation.get(t)));
1229
-
1230
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1231
- expect(effectFn).toHaveBeenLastCalledWith(30);
1232
-
1233
- $state1.set(Promise.resolve(6));
1234
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1235
- expect(effectFn).toHaveBeenLastCalledWith(31);
1236
- });
1237
-
1238
- it("should handle derivation depending on multiple signals", async () => {
1239
- const $signal1 = signal();
1240
- const $signal2 = signal();
1241
- const $derivation = derivationAsync(async (t) => {
1242
- $signal1.watch(t);
1243
- $signal2.watch(t);
1244
- });
1245
- const effectFn = vi.fn();
1246
- effect(async (t) => {
1247
- await $derivation.watch(t);
1248
- effectFn();
1249
- });
1250
-
1251
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1252
-
1253
- $signal1.trigger();
1254
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1255
-
1256
- $signal2.trigger();
1257
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1258
- });
1259
-
1260
- it("should handle derivation depending on state and other derivation", async () => {
1261
- const $state = stateAsync(Promise.resolve(1));
1262
- const $derivation1 = derivationAsync(
1263
- async (t) => (await $state.get(t)) * 2,
1264
- );
1265
- const $derivation2 = derivationAsync(
1266
- async (t) => (await $state.get(t)) + (await $derivation1.get(t)),
1267
- );
1268
- const effectFn = vi.fn();
1269
- effect(async (t) => effectFn(await $derivation2.get(t)));
1270
-
1271
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1272
- expect(effectFn).toHaveBeenLastCalledWith(3);
1273
-
1274
- $state.set(Promise.resolve(2));
1275
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1276
- expect(effectFn).toHaveBeenLastCalledWith(6);
1277
- });
1278
- });
1279
-
1280
- describe("patterns", () => {
1281
- it("should handle diamond pattern", async () => {
1282
- const $stateA = stateAsync(Promise.resolve(1));
1283
- const $aMult3 = derivationAsync(
1284
- async (t) => (await $stateA.get(t)) * 3,
1285
- );
1286
- const $aMult2 = derivationAsync(
1287
- async (t) => (await $stateA.get(t)) * 2,
1288
- );
1289
- const $addAmult3Amult2 = derivationAsync(
1290
- async (t) => (await $aMult3.get(t)) + (await $aMult2.get(t)),
1291
- );
1292
-
1293
- const effectFn = vi.fn();
1294
- effect(async (t) => effectFn(await $addAmult3Amult2.get(t)));
1295
-
1296
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1297
- expect(effectFn).toHaveBeenLastCalledWith(5);
1298
-
1299
- $stateA.set(Promise.resolve(2));
1300
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1301
- expect(effectFn).toHaveBeenLastCalledWith(10);
1302
-
1303
- $stateA.set(Promise.resolve(3));
1304
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1305
- expect(effectFn).toHaveBeenLastCalledWith(15);
1306
- });
1307
-
1308
- it("should handle multi diamond pattern", async () => {
1309
- const $stateA = stateAsync(Promise.resolve(1));
1310
- const $stateB = stateAsync(Promise.resolve(2));
1311
- const $addAB = derivationAsync(async (t) => {
1312
- const a = await $stateA.get(t);
1313
- const b = await $stateB.get(t);
1314
- return a + b;
1315
- });
1316
-
1317
- const $multiplyAB = derivationAsync(async (t) => {
1318
- const a = await $stateA.get(t);
1319
- const b = await $stateB.get(t);
1320
- return a * b;
1321
- });
1322
-
1323
- const $addAndMultiply = derivationAsync(async (t) => {
1324
- const add = await $addAB.get(t);
1325
- const multiply = await $multiplyAB.get(t);
1326
- return add * multiply;
1327
- });
1328
-
1329
- const effectFn = vi.fn();
1330
- effect(async (t) => effectFn(await $addAndMultiply.get(t)));
1331
-
1332
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1333
- expect(effectFn).toHaveBeenLastCalledWith(6);
1334
-
1335
- $stateA.set(Promise.resolve(2));
1336
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1337
- expect(effectFn).toHaveBeenLastCalledWith(16);
1338
-
1339
- $stateB.set(Promise.resolve(3));
1340
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1341
- expect(effectFn).toHaveBeenLastCalledWith(30);
1342
- });
1343
-
1344
- it("should handle derivation with conditional dependencies", async () => {
1345
- const obj = {
1346
- cond: stateAsync(Promise.resolve(false)),
1347
- b: stateAsync(Promise.resolve(2)),
1348
- };
1349
-
1350
- const $state = stateAsync(Promise.resolve(obj));
1351
- const $derivation = derivationAsync(async (t) => {
1352
- const cond = await (await $state.get(t)).cond.get(t);
1353
- if (cond) {
1354
- return (await (await $state.get(t)).b.get(t)) * 2;
1355
- }
1356
- return 0;
1357
- });
1358
-
1359
- const effectFn = vi.fn();
1360
- effect(async (t) => effectFn(await $derivation.get(t)));
1361
-
1362
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1363
- expect(effectFn).toHaveBeenLastCalledWith(0);
1364
-
1365
- (await $state.pick()).cond.set(Promise.resolve(true));
1366
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1367
- expect(effectFn).toHaveBeenLastCalledWith(4);
1368
-
1369
- await $state.set(
1370
- Promise.resolve({
1371
- cond: stateAsync(Promise.resolve(false)),
1372
- b: stateAsync(Promise.resolve(3)),
1373
- }),
1374
- );
1375
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1376
- expect(effectFn).toHaveBeenLastCalledWith(0);
1377
-
1378
- (await $state.pick()).cond.set(Promise.resolve(true));
1379
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
1380
- expect(effectFn).toHaveBeenLastCalledWith(6);
1381
- });
1382
-
1383
- it("should handle derivation with dynamic dependencies", async () => {
1384
- const $state1 = stateAsync(Promise.resolve(1));
1385
- const $state2 = stateAsync(Promise.resolve(10));
1386
- const $cond = stateAsync(Promise.resolve(true));
1387
- const $derivation = derivationAsync(async (t) => {
1388
- if (await $cond.get(t)) {
1389
- return (await $state1.get(t)) * 2;
1390
- }
1391
- return (await $state2.get(t)) * 2;
1392
- });
1393
-
1394
- const effectFn = vi.fn();
1395
- effect(async (t) => effectFn(await $derivation.get(t)));
1396
-
1397
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1398
- expect(effectFn).toHaveBeenLastCalledWith(2);
1399
-
1400
- $state1.set(Promise.resolve(2));
1401
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1402
- expect(effectFn).toHaveBeenLastCalledWith(4);
1403
-
1404
- $cond.set(Promise.resolve(false));
1405
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
1406
- expect(effectFn).toHaveBeenLastCalledWith(20);
1407
-
1408
- $state2.set(Promise.resolve(20));
1409
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
1410
- expect(effectFn).toHaveBeenLastCalledWith(40);
1411
- });
1412
-
1413
- it("should handle nested states in derivation", async () => {
1414
- const obj1 = {
1415
- cond: stateAsync(Promise.resolve(false)),
1416
- num: stateAsync(Promise.resolve(2)),
1417
- dispose: (options: { self: boolean }) => {
1418
- obj1.cond.dispose(options);
1419
- obj1.num.dispose(options);
1420
- },
1421
- };
1422
- const obj2 = {
1423
- cond: stateAsync(Promise.resolve(false)),
1424
- num: stateAsync(Promise.resolve(4)),
1425
- dispose: (options: { self: boolean }) => {
1426
- obj2.cond.dispose(options);
1427
- obj2.num.dispose(options);
1428
- },
1429
- };
1430
- const $state = stateAsync(Promise.resolve(obj1));
1431
- const $derivationCond = derivationAsync(async (t) =>
1432
- (await $state.get(t)).cond.get(t),
1433
- );
1434
- const $derivationNum = derivationAsync(
1435
- async (t) => (await (await $state.get(t)).num.get(t)) * 2,
1436
- );
1437
- const effectCondFn = vi.fn();
1438
- const effectNumFn = vi.fn();
1439
- effect(async (t) => effectCondFn(await $derivationCond.get(t)));
1440
- effect(async (t) => effectNumFn(await $derivationNum.get(t)));
1441
-
1442
- await vi.waitFor(() => expect(effectCondFn).toHaveBeenCalledTimes(1));
1443
- expect(effectCondFn).toHaveBeenLastCalledWith(false);
1444
- await vi.waitFor(() => expect(effectNumFn).toHaveBeenCalledTimes(1));
1445
- expect(effectNumFn).toHaveBeenLastCalledWith(4);
1446
-
1447
- (await $state.pick()).num.set(Promise.resolve(3));
1448
- await vi.waitFor(() => expect(effectCondFn).toHaveBeenCalledTimes(1));
1449
- expect(effectCondFn).toHaveBeenLastCalledWith(false);
1450
- await vi.waitFor(() => expect(effectNumFn).toHaveBeenCalledTimes(2));
1451
- expect(effectNumFn).toHaveBeenLastCalledWith(6);
1452
-
1453
- (await $state.pick()).cond.set(Promise.resolve(true));
1454
- await vi.waitFor(() => expect(effectCondFn).toHaveBeenCalledTimes(2));
1455
- expect(effectCondFn).toHaveBeenLastCalledWith(true);
1456
- await vi.waitFor(() => expect(effectNumFn).toHaveBeenCalledTimes(2));
1457
- expect(effectNumFn).toHaveBeenLastCalledWith(6);
1458
-
1459
- $state.set(Promise.resolve(obj2));
1460
- await vi.waitFor(() => expect(effectCondFn).toHaveBeenCalledTimes(3));
1461
- expect(effectCondFn).toHaveBeenLastCalledWith(false);
1462
- await vi.waitFor(() => expect(effectNumFn).toHaveBeenCalledTimes(3));
1463
- expect(effectNumFn).toHaveBeenLastCalledWith(8);
1464
-
1465
- (await $state.pick()).cond.set(Promise.resolve(true));
1466
- await vi.waitFor(() => expect(effectCondFn).toHaveBeenCalledTimes(4));
1467
- expect(effectCondFn).toHaveBeenLastCalledWith(true);
1468
- await vi.waitFor(() => expect(effectNumFn).toHaveBeenCalledTimes(3));
1469
- expect(effectNumFn).toHaveBeenLastCalledWith(8);
1470
-
1471
- (await $state.pick()).num.set(Promise.resolve(5));
1472
- await vi.waitFor(() => expect(effectCondFn).toHaveBeenCalledTimes(4));
1473
- expect(effectCondFn).toHaveBeenLastCalledWith(true);
1474
- await vi.waitFor(() => expect(effectNumFn).toHaveBeenCalledTimes(4));
1475
- expect(effectNumFn).toHaveBeenLastCalledWith(10);
1476
- });
1477
- });
1478
-
1479
- describe("with refresh", () => {
1480
- it("should force recomputation in effect", async () => {
1481
- let multiplier = 2;
1482
- const $state = stateAsync(Promise.resolve(5));
1483
- const $derivation = derivationAsync(
1484
- async (t) => (await $state.get(t)) * multiplier,
1485
- );
1486
- const effectFn = vi.fn();
1487
- effect(async (t) => effectFn(await $derivation.get(t)));
1488
-
1489
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1490
- expect(effectFn).toHaveBeenLastCalledWith(10);
1491
-
1492
- multiplier = 3;
1493
- await $derivation.refresh();
1494
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
1495
- expect(effectFn).toHaveBeenLastCalledWith(15);
1496
- });
1497
-
1498
- it("shouldn't trigger effects even if dependencies have not changed", async () => {
1499
- const $state = stateAsync(Promise.resolve(1));
1500
- const computeFn = vi.fn(async (t) => (await $state.get(t)) * 2);
1501
- const $derivation = derivationAsync(computeFn);
1502
- const effectFn = vi.fn();
1503
- effect(async (t) => effectFn(await $derivation.get(t)));
1504
-
1505
- await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
1506
- expect(computeFn).toHaveBeenCalledTimes(1);
1507
-
1508
- // Refresh forces recomputation even though state hasn't changed
1509
- await $derivation.refresh();
1510
- await vi.waitFor(() => expect(computeFn).toHaveBeenCalledTimes(2));
1511
- // Effect should not be called again because value hasn't changed
1512
- await new Promise((resolve) => setTimeout(resolve, 50));
1513
- expect(effectFn).toHaveBeenCalledTimes(1);
1514
- });
1515
- });
1516
- });
1517
- });