@ersbeth/picoflow 0.2.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/.cursor/plans/update-js-e795d61b.plan.md +567 -0
  2. package/.gitlab-ci.yml +24 -0
  3. package/.vscode/settings.json +3 -3
  4. package/CHANGELOG.md +51 -0
  5. package/IMPLEMENTATION_GUIDE.md +1578 -0
  6. package/README.md +62 -25
  7. package/biome.json +32 -32
  8. package/dist/picoflow.js +557 -1099
  9. package/dist/types/advanced/array.d.ts +0 -6
  10. package/dist/types/advanced/array.d.ts.map +1 -1
  11. package/dist/types/advanced/index.d.ts +5 -5
  12. package/dist/types/advanced/index.d.ts.map +1 -1
  13. package/dist/types/advanced/map.d.ts +114 -23
  14. package/dist/types/advanced/map.d.ts.map +1 -1
  15. package/dist/types/advanced/resource.d.ts +51 -12
  16. package/dist/types/advanced/resource.d.ts.map +1 -1
  17. package/dist/types/advanced/resourceAsync.d.ts +28 -13
  18. package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
  19. package/dist/types/advanced/stream.d.ts +74 -16
  20. package/dist/types/advanced/stream.d.ts.map +1 -1
  21. package/dist/types/advanced/streamAsync.d.ts +69 -15
  22. package/dist/types/advanced/streamAsync.d.ts.map +1 -1
  23. package/dist/types/basic/constant.d.ts +44 -16
  24. package/dist/types/basic/constant.d.ts.map +1 -1
  25. package/dist/types/basic/derivation.d.ts +73 -24
  26. package/dist/types/basic/derivation.d.ts.map +1 -1
  27. package/dist/types/basic/disposable.d.ts +65 -6
  28. package/dist/types/basic/disposable.d.ts.map +1 -1
  29. package/dist/types/basic/effect.d.ts +27 -16
  30. package/dist/types/basic/effect.d.ts.map +1 -1
  31. package/dist/types/basic/index.d.ts +7 -8
  32. package/dist/types/basic/index.d.ts.map +1 -1
  33. package/dist/types/basic/observable.d.ts +62 -13
  34. package/dist/types/basic/observable.d.ts.map +1 -1
  35. package/dist/types/basic/signal.d.ts +35 -6
  36. package/dist/types/basic/signal.d.ts.map +1 -1
  37. package/dist/types/basic/state.d.ts +25 -4
  38. package/dist/types/basic/state.d.ts.map +1 -1
  39. package/dist/types/basic/trackingContext.d.ts +33 -0
  40. package/dist/types/basic/trackingContext.d.ts.map +1 -0
  41. package/dist/types/creators.d.ts +271 -26
  42. package/dist/types/creators.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +60 -7
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/dist/types/solid/converters.d.ts +5 -5
  46. package/dist/types/solid/converters.d.ts.map +1 -1
  47. package/dist/types/solid/index.d.ts +2 -2
  48. package/dist/types/solid/index.d.ts.map +1 -1
  49. package/dist/types/solid/primitives.d.ts +96 -4
  50. package/dist/types/solid/primitives.d.ts.map +1 -1
  51. package/docs/.vitepress/config.mts +110 -0
  52. package/docs/api/classes/FlowArray.md +489 -0
  53. package/docs/api/classes/FlowConstant.md +350 -0
  54. package/docs/api/classes/FlowDerivation.md +334 -0
  55. package/docs/api/classes/FlowEffect.md +100 -0
  56. package/docs/api/classes/FlowMap.md +512 -0
  57. package/docs/api/classes/FlowObservable.md +306 -0
  58. package/docs/api/classes/FlowResource.md +380 -0
  59. package/docs/api/classes/FlowResourceAsync.md +362 -0
  60. package/docs/api/classes/FlowSignal.md +160 -0
  61. package/docs/api/classes/FlowState.md +368 -0
  62. package/docs/api/classes/FlowStream.md +367 -0
  63. package/docs/api/classes/FlowStreamAsync.md +364 -0
  64. package/docs/api/classes/SolidDerivation.md +75 -0
  65. package/docs/api/classes/SolidResource.md +91 -0
  66. package/docs/api/classes/SolidState.md +71 -0
  67. package/docs/api/classes/TrackingContext.md +33 -0
  68. package/docs/api/functions/array.md +58 -0
  69. package/docs/api/functions/constant.md +45 -0
  70. package/docs/api/functions/derivation.md +53 -0
  71. package/docs/api/functions/effect.md +49 -0
  72. package/docs/api/functions/from.md +220 -0
  73. package/docs/api/functions/isDisposable.md +49 -0
  74. package/docs/api/functions/map.md +57 -0
  75. package/docs/api/functions/resource.md +52 -0
  76. package/docs/api/functions/resourceAsync.md +50 -0
  77. package/docs/api/functions/signal.md +36 -0
  78. package/docs/api/functions/state.md +47 -0
  79. package/docs/api/functions/stream.md +53 -0
  80. package/docs/api/functions/streamAsync.md +50 -0
  81. package/docs/api/index.md +118 -0
  82. package/docs/api/interfaces/FlowDisposable.md +65 -0
  83. package/docs/api/interfaces/SolidObservable.md +19 -0
  84. package/docs/api/type-aliases/FlowArrayAction.md +49 -0
  85. package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
  86. package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
  87. package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
  88. package/docs/api/type-aliases/NotPromise.md +18 -0
  89. package/docs/api/type-aliases/SolidGetter.md +17 -0
  90. package/docs/api/typedoc-sidebar.json +1 -0
  91. package/docs/examples/examples.md +2313 -0
  92. package/docs/examples/patterns.md +649 -0
  93. package/docs/guide/advanced/disposal.md +426 -0
  94. package/docs/guide/advanced/solidjs.md +221 -0
  95. package/docs/guide/advanced/upgrading.md +464 -0
  96. package/docs/guide/introduction/concepts.md +56 -0
  97. package/docs/guide/introduction/conventions.md +61 -0
  98. package/docs/guide/introduction/getting-started.md +134 -0
  99. package/docs/guide/introduction/lifecycle.md +371 -0
  100. package/docs/guide/primitives/array.md +400 -0
  101. package/docs/guide/primitives/constant.md +380 -0
  102. package/docs/guide/primitives/derivations.md +348 -0
  103. package/docs/guide/primitives/effects.md +458 -0
  104. package/docs/guide/primitives/map.md +387 -0
  105. package/docs/guide/primitives/overview.md +175 -0
  106. package/docs/guide/primitives/resources.md +858 -0
  107. package/docs/guide/primitives/signal.md +259 -0
  108. package/docs/guide/primitives/state.md +368 -0
  109. package/docs/guide/primitives/streams.md +931 -0
  110. package/docs/index.md +47 -0
  111. package/docs/public/logo.svg +1 -0
  112. package/package.json +57 -41
  113. package/src/advanced/array.ts +208 -210
  114. package/src/advanced/index.ts +7 -7
  115. package/src/advanced/map.ts +178 -68
  116. package/src/advanced/resource.ts +87 -43
  117. package/src/advanced/resourceAsync.ts +62 -42
  118. package/src/advanced/stream.ts +113 -50
  119. package/src/advanced/streamAsync.ts +120 -61
  120. package/src/basic/constant.ts +82 -49
  121. package/src/basic/derivation.ts +128 -84
  122. package/src/basic/disposable.ts +74 -15
  123. package/src/basic/effect.ts +85 -77
  124. package/src/basic/index.ts +7 -8
  125. package/src/basic/observable.ts +94 -36
  126. package/src/basic/signal.ts +133 -105
  127. package/src/basic/state.ts +46 -25
  128. package/src/basic/trackingContext.ts +45 -0
  129. package/src/creators.ts +297 -54
  130. package/src/index.ts +96 -43
  131. package/src/solid/converters.ts +186 -67
  132. package/src/solid/index.ts +8 -2
  133. package/src/solid/primitives.ts +167 -65
  134. package/test/array.test.ts +592 -612
  135. package/test/constant.test.ts +31 -33
  136. package/test/derivation.test.ts +531 -536
  137. package/test/effect.test.ts +21 -21
  138. package/test/map.test.ts +233 -137
  139. package/test/resource.test.ts +119 -121
  140. package/test/resourceAsync.test.ts +98 -100
  141. package/test/signal.test.ts +51 -55
  142. package/test/state.test.ts +186 -168
  143. package/test/stream.test.ts +189 -189
  144. package/test/streamAsync.test.ts +186 -186
  145. package/tsconfig.json +19 -18
  146. package/typedoc.json +37 -0
  147. package/vite.config.ts +23 -20
  148. package/vitest.config.ts +7 -7
  149. package/api/doc/index.md +0 -31
  150. package/api/doc/picoflow.array.md +0 -55
  151. package/api/doc/picoflow.constant.md +0 -55
  152. package/api/doc/picoflow.derivation.md +0 -55
  153. package/api/doc/picoflow.effect.md +0 -55
  154. package/api/doc/picoflow.flowarray._constructor_.md +0 -49
  155. package/api/doc/picoflow.flowarray._lastaction.md +0 -13
  156. package/api/doc/picoflow.flowarray.clear.md +0 -17
  157. package/api/doc/picoflow.flowarray.dispose.md +0 -55
  158. package/api/doc/picoflow.flowarray.get.md +0 -19
  159. package/api/doc/picoflow.flowarray.length.md +0 -13
  160. package/api/doc/picoflow.flowarray.md +0 -273
  161. package/api/doc/picoflow.flowarray.pop.md +0 -17
  162. package/api/doc/picoflow.flowarray.push.md +0 -53
  163. package/api/doc/picoflow.flowarray.set.md +0 -53
  164. package/api/doc/picoflow.flowarray.setitem.md +0 -69
  165. package/api/doc/picoflow.flowarray.shift.md +0 -17
  166. package/api/doc/picoflow.flowarray.splice.md +0 -85
  167. package/api/doc/picoflow.flowarray.unshift.md +0 -53
  168. package/api/doc/picoflow.flowarrayaction.md +0 -37
  169. package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
  170. package/api/doc/picoflow.flowconstant.get.md +0 -25
  171. package/api/doc/picoflow.flowconstant.md +0 -88
  172. package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
  173. package/api/doc/picoflow.flowderivation.get.md +0 -23
  174. package/api/doc/picoflow.flowderivation.md +0 -86
  175. package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
  176. package/api/doc/picoflow.flowdisposable.md +0 -43
  177. package/api/doc/picoflow.floweffect._constructor_.md +0 -54
  178. package/api/doc/picoflow.floweffect.dispose.md +0 -21
  179. package/api/doc/picoflow.floweffect.disposed.md +0 -13
  180. package/api/doc/picoflow.floweffect.md +0 -131
  181. package/api/doc/picoflow.flowgetter.md +0 -15
  182. package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
  183. package/api/doc/picoflow.flowmap._lastset.md +0 -21
  184. package/api/doc/picoflow.flowmap.delete.md +0 -61
  185. package/api/doc/picoflow.flowmap.md +0 -133
  186. package/api/doc/picoflow.flowmap.setat.md +0 -77
  187. package/api/doc/picoflow.flowobservable.get.md +0 -19
  188. package/api/doc/picoflow.flowobservable.md +0 -68
  189. package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
  190. package/api/doc/picoflow.flowresource._constructor_.md +0 -49
  191. package/api/doc/picoflow.flowresource.fetch.md +0 -27
  192. package/api/doc/picoflow.flowresource.get.md +0 -23
  193. package/api/doc/picoflow.flowresource.md +0 -100
  194. package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
  195. package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
  196. package/api/doc/picoflow.flowresourceasync.get.md +0 -23
  197. package/api/doc/picoflow.flowresourceasync.md +0 -100
  198. package/api/doc/picoflow.flowsignal.dispose.md +0 -59
  199. package/api/doc/picoflow.flowsignal.disposed.md +0 -18
  200. package/api/doc/picoflow.flowsignal.md +0 -112
  201. package/api/doc/picoflow.flowsignal.trigger.md +0 -21
  202. package/api/doc/picoflow.flowstate.md +0 -52
  203. package/api/doc/picoflow.flowstate.set.md +0 -61
  204. package/api/doc/picoflow.flowstream._constructor_.md +0 -49
  205. package/api/doc/picoflow.flowstream.dispose.md +0 -21
  206. package/api/doc/picoflow.flowstream.get.md +0 -23
  207. package/api/doc/picoflow.flowstream.md +0 -100
  208. package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
  209. package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
  210. package/api/doc/picoflow.flowstreamasync.get.md +0 -23
  211. package/api/doc/picoflow.flowstreamasync.md +0 -100
  212. package/api/doc/picoflow.flowstreamdisposer.md +0 -13
  213. package/api/doc/picoflow.flowstreamsetter.md +0 -13
  214. package/api/doc/picoflow.flowstreamupdater.md +0 -19
  215. package/api/doc/picoflow.flowwatcher.md +0 -15
  216. package/api/doc/picoflow.from.md +0 -55
  217. package/api/doc/picoflow.from_1.md +0 -55
  218. package/api/doc/picoflow.from_2.md +0 -55
  219. package/api/doc/picoflow.from_3.md +0 -55
  220. package/api/doc/picoflow.from_4.md +0 -55
  221. package/api/doc/picoflow.from_5.md +0 -55
  222. package/api/doc/picoflow.isdisposable.md +0 -55
  223. package/api/doc/picoflow.map.md +0 -59
  224. package/api/doc/picoflow.md +0 -544
  225. package/api/doc/picoflow.resource.md +0 -55
  226. package/api/doc/picoflow.resourceasync.md +0 -55
  227. package/api/doc/picoflow.signal.md +0 -19
  228. package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
  229. package/api/doc/picoflow.solidderivation.get.md +0 -13
  230. package/api/doc/picoflow.solidderivation.md +0 -94
  231. package/api/doc/picoflow.solidgetter.md +0 -13
  232. package/api/doc/picoflow.solidobservable.get.md +0 -13
  233. package/api/doc/picoflow.solidobservable.md +0 -57
  234. package/api/doc/picoflow.solidresource._constructor_.md +0 -49
  235. package/api/doc/picoflow.solidresource.get.md +0 -13
  236. package/api/doc/picoflow.solidresource.latest.md +0 -13
  237. package/api/doc/picoflow.solidresource.md +0 -157
  238. package/api/doc/picoflow.solidresource.refetch.md +0 -13
  239. package/api/doc/picoflow.solidresource.state.md +0 -13
  240. package/api/doc/picoflow.solidstate._constructor_.md +0 -49
  241. package/api/doc/picoflow.solidstate.get.md +0 -13
  242. package/api/doc/picoflow.solidstate.md +0 -115
  243. package/api/doc/picoflow.solidstate.set.md +0 -13
  244. package/api/doc/picoflow.state.md +0 -55
  245. package/api/doc/picoflow.stream.md +0 -55
  246. package/api/doc/picoflow.streamasync.md +0 -55
  247. package/api/picoflow.public.api.md +0 -244
  248. package/api-extractor.json +0 -61
@@ -0,0 +1,426 @@
1
+ # Disposal
2
+
3
+ Memory management is crucial in long-running applications. PicoFlow provides explicit disposal mechanisms to prevent memory leaks and ensure resources are properly cleaned up.
4
+
5
+ ## Why Disposal Matters
6
+
7
+ All PicoFlow primitives are disposable. When you're done with them, you should clean them up:
8
+
9
+ ```typescript
10
+ const $count = state(0)
11
+ const fx = effect((t) => {
12
+ console.log($count.get(t))
13
+ })
14
+
15
+ // Later... clean up
16
+ fx.dispose() // Effect stops running
17
+ $count.dispose() // State is cleaned up
18
+ ```
19
+
20
+ **Why disposal matters:**
21
+ - **Prevents memory leaks** - Unreferenced but undisposed primitives stay in memory
22
+ - **Stops effects from running** - Effects continue executing until explicitly disposed
23
+ - **Cleans up resources** - WebSocket connections, intervals, event listeners, etc.
24
+ - **Important in long-running applications** - SPAs, dashboards, real-time apps
25
+
26
+ ```mermaid
27
+ flowchart LR
28
+ A[Create primitive] --> B[Use in effects]
29
+ B --> C[No longer needed]
30
+ C --> D{Disposed?}
31
+ D -->|Yes| E[Memory freed]
32
+ D -->|No| F[Memory leak!]
33
+
34
+ style E fill:#90EE90
35
+ style F fill:#FFB6C6
36
+ ```
37
+
38
+ ## When to Dispose
39
+
40
+ ### Component Cleanup
41
+
42
+ In component-based architectures, dispose primitives when components unmount:
43
+
44
+ ```typescript
45
+ class TodoList {
46
+ private $todos = state<Todo[]>([])
47
+ private disposables: FlowDisposable[] = []
48
+
49
+ constructor() {
50
+ // Track all disposables
51
+ this.disposables.push(
52
+ effect((t) => {
53
+ this.render($todos.get(t))
54
+ })
55
+ )
56
+ }
57
+
58
+ destroy() {
59
+ // Clean up when component is destroyed
60
+ this.disposables.forEach(d => d.dispose())
61
+ this.$todos.dispose()
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### Conditional Effects
67
+
68
+ Dispose and recreate effects based on application state:
69
+
70
+ ```typescript
71
+ let currentEffect: FlowEffect | null = null
72
+
73
+ function enableLogging(enable: boolean) {
74
+ // Dispose previous effect
75
+ currentEffect?.dispose()
76
+
77
+ if (enable) {
78
+ currentEffect = effect((t) => {
79
+ console.log('Value:', $data.get(t))
80
+ })
81
+ }
82
+ }
83
+
84
+ // Toggle logging on/off
85
+ enableLogging(true) // Effect starts
86
+ enableLogging(false) // Effect disposed
87
+ ```
88
+
89
+ ### Resource Management
90
+
91
+ Dispose primitives that manage external resources:
92
+
93
+ ```typescript
94
+ const $wsData = stream<string>((set) => {
95
+ const ws = new WebSocket('ws://example.com')
96
+ ws.onmessage = (e) => set(e.data)
97
+
98
+ // Cleanup function called on disposal
99
+ return () => ws.close()
100
+ })
101
+
102
+ // Use the stream
103
+ effect((t) => {
104
+ console.log('Message:', $wsData.get(t))
105
+ })
106
+
107
+ // Later... close the connection
108
+ $wsData.dispose()
109
+ ```
110
+
111
+ ### Interval Cleanup
112
+
113
+ ```typescript
114
+ const $tick = stream<number>((set) => {
115
+ let count = 0
116
+ const interval = setInterval(() => {
117
+ set(count++)
118
+ }, 1000)
119
+
120
+ // Cleanup: clear interval on disposal
121
+ return () => clearInterval(interval)
122
+ })
123
+
124
+ // Later...
125
+ $tick.dispose() // Interval cleared
126
+ ```
127
+
128
+ ## Automatic Cleanup
129
+
130
+ Effects automatically clean up their dependencies when disposed:
131
+
132
+ ```typescript
133
+ const $a = state(1)
134
+ const $b = state(2)
135
+
136
+ const fx = effect((t) => {
137
+ console.log($a.get(t) + $b.get(t))
138
+ })
139
+
140
+ fx.dispose()
141
+
142
+ // These no longer trigger the effect
143
+ $a.set(10) // No console log
144
+ $b.set(20) // No console log
145
+ ```
146
+
147
+ When an effect is disposed:
148
+ 1. It unregisters from all its dependencies
149
+ 2. Future changes to those dependencies won't trigger it
150
+ 3. The effect's memory is freed
151
+
152
+ ## Best Practices
153
+
154
+ ### 1. Always Dispose in Cleanup Hooks
155
+
156
+ In any framework with lifecycle hooks, dispose in the cleanup phase:
157
+
158
+ ```typescript
159
+ // React
160
+ useEffect(() => {
161
+ const fx = effect((t) => {
162
+ // ... effect code
163
+ })
164
+
165
+ return () => fx.dispose() // Cleanup
166
+ }, [])
167
+
168
+ // Vue
169
+ onUnmounted(() => {
170
+ fx.dispose()
171
+ })
172
+
173
+ // Svelte
174
+ onDestroy(() => {
175
+ fx.dispose()
176
+ })
177
+ ```
178
+
179
+ ### 2. Group Related Disposables
180
+
181
+ Use collections to manage multiple disposables:
182
+
183
+ ```typescript
184
+ class DataManager {
185
+ private disposables = new Set<FlowDisposable>()
186
+
187
+ track(disposable: FlowDisposable) {
188
+ this.disposables.add(disposable)
189
+ }
190
+
191
+ cleanup() {
192
+ this.disposables.forEach(d => d.dispose())
193
+ this.disposables.clear()
194
+ }
195
+ }
196
+
197
+ // Usage
198
+ const manager = new DataManager()
199
+ manager.track(effect((t) => { /* ... */ }))
200
+ manager.track(effect((t) => { /* ... */ }))
201
+ manager.track($someState)
202
+
203
+ // Clean up everything
204
+ manager.cleanup()
205
+ ```
206
+
207
+ ### 3. Dispose in Reverse Order of Creation
208
+
209
+ Dispose dependents before dependencies:
210
+
211
+ ```typescript
212
+ const $data = state(0)
213
+ const $derived = derivation((t) => $data.get(t) * 2)
214
+ const fx = effect((t) => console.log($derived.get(t)))
215
+
216
+ // Cleanup: dispose in reverse order
217
+ fx.dispose() // Effect first (depends on $derived)
218
+ $derived.dispose() // Derivation second (depends on $data)
219
+ $data.dispose() // State last (no dependencies)
220
+ ```
221
+
222
+ **Why?** Disposing in reverse order prevents:
223
+ - Accessing disposed dependencies
224
+ - Unnecessary recomputations during cleanup
225
+ - Race conditions during shutdown
226
+
227
+ ### 4. Check Disposal State
228
+
229
+ Use the `disposed` property to check if a primitive has been disposed:
230
+
231
+ ```typescript
232
+ const fx = effect((t) => {
233
+ console.log($count.get(t))
234
+ })
235
+
236
+ console.log(fx.disposed) // false
237
+
238
+ fx.dispose()
239
+
240
+ console.log(fx.disposed) // true
241
+ ```
242
+
243
+ ### 5. Handle Disposal Errors
244
+
245
+ Wrap disposal in try-catch for robust cleanup:
246
+
247
+ ```typescript
248
+ function cleanupResources(disposables: FlowDisposable[]) {
249
+ const errors: Error[] = []
250
+
251
+ for (const disposable of disposables) {
252
+ try {
253
+ disposable.dispose()
254
+ } catch (error) {
255
+ errors.push(error as Error)
256
+ }
257
+ }
258
+
259
+ if (errors.length > 0) {
260
+ console.error('Disposal errors:', errors)
261
+ }
262
+ }
263
+ ```
264
+
265
+ ## Common Pitfalls
266
+
267
+ ### Pitfall 1: Not Disposing Effects
268
+
269
+ ```typescript
270
+ // ❌ Memory leak - effect never disposed
271
+ function updateCounter() {
272
+ effect((t) => {
273
+ document.getElementById('count').textContent = $count.get(t)
274
+ })
275
+ }
276
+
277
+ // Called 100 times = 100 effects running!
278
+ for (let i = 0; i < 100; i++) {
279
+ updateCounter()
280
+ }
281
+
282
+ // ✅ Proper cleanup
283
+ function updateCounter(): () => void {
284
+ const fx = effect((t) => {
285
+ document.getElementById('count').textContent = $count.get(t)
286
+ })
287
+
288
+ return () => fx.dispose()
289
+ }
290
+
291
+ const cleanup = updateCounter()
292
+ // Later...
293
+ cleanup()
294
+ ```
295
+
296
+ ### Pitfall 2: Disposing Too Early
297
+
298
+ ```typescript
299
+ // ❌ Disposed too early
300
+ const fx = effect((t) => {
301
+ console.log($count.get(t))
302
+ })
303
+
304
+ fx.dispose() // Effect disposed immediately!
305
+
306
+ $count.set(1) // No effect runs
307
+
308
+ // ✅ Dispose at the right time
309
+ const fx = effect((t) => {
310
+ console.log($count.get(t))
311
+ })
312
+
313
+ // Use the effect...
314
+ $count.set(1) // Effect runs
315
+
316
+ // Now dispose when truly done
317
+ fx.dispose()
318
+ ```
319
+
320
+ ### Pitfall 3: Forgetting Stream Cleanup
321
+
322
+ ```typescript
323
+ // ❌ WebSocket never closed
324
+ function connectWebSocket() {
325
+ const $ws = stream<string>((set) => {
326
+ const socket = new WebSocket('ws://...')
327
+ socket.onmessage = (e) => set(e.data)
328
+ // Missing: return () => socket.close()
329
+ })
330
+ return $ws
331
+ }
332
+
333
+ // ✅ Proper cleanup
334
+ function connectWebSocket() {
335
+ const $ws = stream<string>((set) => {
336
+ const socket = new WebSocket('ws://...')
337
+ socket.onmessage = (e) => set(e.data)
338
+
339
+ return () => socket.close() // Cleanup function
340
+ })
341
+ return $ws
342
+ }
343
+ ```
344
+
345
+ ## Disposal Patterns
346
+
347
+ ### Pattern 1: Disposable Builder
348
+
349
+ ```typescript
350
+ class DisposableBuilder {
351
+ private disposables: FlowDisposable[] = []
352
+
353
+ add<T extends FlowDisposable>(disposable: T): T {
354
+ this.disposables.push(disposable)
355
+ return disposable
356
+ }
357
+
358
+ disposeAll(): void {
359
+ // Dispose in reverse order
360
+ for (let i = this.disposables.length - 1; i >= 0; i--) {
361
+ this.disposables[i].dispose()
362
+ }
363
+ this.disposables = []
364
+ }
365
+ }
366
+
367
+ // Usage
368
+ const builder = new DisposableBuilder()
369
+ builder.add(effect((t) => { /* ... */ }))
370
+ builder.add(effect((t) => { /* ... */ }))
371
+ builder.disposeAll()
372
+ ```
373
+
374
+ ### Pattern 2: Scoped Disposables
375
+
376
+ ```typescript
377
+ function withDisposables<T>(
378
+ fn: (track: <D extends FlowDisposable>(d: D) => D) => T
379
+ ): T & { cleanup: () => void } {
380
+ const disposables: FlowDisposable[] = []
381
+
382
+ const track = <D extends FlowDisposable>(d: D): D => {
383
+ disposables.push(d)
384
+ return d
385
+ }
386
+
387
+ const result = fn(track) as T & { cleanup: () => void }
388
+ result.cleanup = () => disposables.forEach(d => d.dispose())
389
+
390
+ return result
391
+ }
392
+
393
+ // Usage
394
+ const { cleanup } = withDisposables((track) => {
395
+ const $count = track(state(0))
396
+ const fx = track(effect((t) => console.log($count.get(t))))
397
+ })
398
+
399
+ // Clean up everything
400
+ cleanup()
401
+ ```
402
+
403
+ ### Pattern 3: Auto-disposal on Condition
404
+
405
+ ```typescript
406
+ function createAutoDisposeEffect(
407
+ condition: () => boolean,
408
+ apply: (t: TrackingContext) => void
409
+ ): FlowEffect {
410
+ const fx = effect((t) => {
411
+ if (!condition()) {
412
+ fx.dispose()
413
+ return
414
+ }
415
+ apply(t)
416
+ })
417
+ return fx
418
+ }
419
+
420
+ // Usage: effect auto-disposes when count > 10
421
+ createAutoDisposeEffect(
422
+ () => $count.pick() <= 10,
423
+ (t) => console.log('Count:', $count.get(t))
424
+ )
425
+ ```
426
+
@@ -0,0 +1,221 @@
1
+ # Use with SolidJS
2
+
3
+ PicoFlow provides seamless integration with SolidJS through the `@ersbeth/picoflow/solid` module. This integration allows you to use PicoFlow's reactive primitives within SolidJS components, combining PicoFlow's explicit tracking model with SolidJS's automatic dependency tracking.
4
+
5
+ ## Why Use PicoFlow with SolidJS?
6
+
7
+ PicoFlow and SolidJS complement each other well:
8
+
9
+ - **PicoFlow** provides explicit control over reactivity with fine-grained tracking
10
+ - **SolidJS** offers automatic dependency tracking within components
11
+ - **Together** you get the best of both worlds: explicit control in your business logic and automatic reactivity in your UI
12
+
13
+ ### When to Use This Integration
14
+
15
+ Use PicoFlow with SolidJS when:
16
+
17
+ - ✅ You want explicit control over reactivity in shared business logic
18
+ - ✅ You need fine-grained tracking (e.g., specific array operations)
19
+ - ✅ You're building a library that should work with multiple frameworks
20
+ - ✅ You want to share reactive logic between different UI frameworks
21
+ - ✅ You prefer PicoFlow's explicit tracking model for complex state management
22
+
23
+ Use pure SolidJS when:
24
+
25
+ - ✅ Your reactive logic is tightly coupled to components
26
+ - ✅ You prefer automatic dependency tracking everywhere
27
+ - ✅ You don't need fine-grained control over reactivity
28
+
29
+ ## Installation
30
+
31
+ First, ensure you have both PicoFlow and SolidJS installed:
32
+
33
+ ::: code-group
34
+
35
+ ```bash [pnpm]
36
+ pnpm add @ersbeth/picoflow solid-js
37
+ ```
38
+
39
+ ```bash [npm]
40
+ npm install @ersbeth/picoflow solid-js
41
+ ```
42
+
43
+ ```bash [yarn]
44
+ yarn add @ersbeth/picoflow solid-js
45
+ ```
46
+
47
+ :::
48
+
49
+ Then import the integration utilities:
50
+
51
+ ```typescript
52
+ import { from } from '@ersbeth/picoflow/solid'
53
+ import { state, derivation, resource } from '@ersbeth/picoflow'
54
+ ```
55
+
56
+ ## PicoFlow to SolidJS
57
+
58
+ The `from()` function converts PicoFlow observables into SolidJS primitives that work seamlessly in Solid components.
59
+
60
+ ### Basic Conversion
61
+
62
+ Convert any PicoFlow observable to a Solid primitive:
63
+
64
+ ```typescript
65
+ import { from } from '@ersbeth/picoflow/solid'
66
+ import { state } from '@ersbeth/picoflow'
67
+
68
+ // Create PicoFlow state
69
+ const $count = state(0)
70
+
71
+
72
+ // Use in Solid component
73
+ function Counter() {
74
+ // Convert to Solid primitive
75
+ const count = from($count)
76
+ return <div>Count: {count.get()}</div>
77
+ }
78
+ ```
79
+
80
+ The `from()` function uses SolidJS's `onMount` and `onCleanup` to properly dispose of the internal PicoFlow effects when the component unmounts. You don't need to manually dispose of converted primitives.
81
+
82
+ ### Conversion Rules
83
+
84
+ The `from()` function automatically determines the right Solid primitive based on the value type:
85
+
86
+ | PicoFlow Input | Solid Output | When |
87
+ |----------------|--------------|------|
88
+ | `FlowObservable<T>` (non-Promise) | `SolidDerivation<T>` | Synchronous values |
89
+ | `FlowObservable<Promise<T>>` | `SolidResource<T>` | Asynchronous values |
90
+ | `(t) => T` (getter function) | `SolidDerivation<T>` | Synchronous computation |
91
+ | `(t) => Promise<T>` (getter function) | `SolidResource<T>` | Asynchronous computation |
92
+
93
+ ## SolidJS Primitives
94
+
95
+ PicoFlow also provides Solid-style primitives that you can use directly in Solid components. These are thin wrappers around SolidJS's built-in primitives with a class-based API consistent with PicoFlow's style.
96
+
97
+ ### SolidState
98
+
99
+ `SolidState` wraps SolidJS's `createSignal`:
100
+
101
+ ```typescript
102
+ import { SolidState } from '@ersbeth/picoflow/solid'
103
+
104
+ // Use in component
105
+ function Counter() {
106
+
107
+ // Create Solid state
108
+ const count = new SolidState(0)
109
+
110
+ return (
111
+ <div>
112
+ <p>Count: {count.get()}</p>
113
+ <button onClick={() => count.set(count.get() + 1)}>Increment</button>
114
+ <button onClick={() => count.set(prev => prev + 1)}>Increment (updater)</button>
115
+ </div>
116
+ )
117
+ }
118
+ ```
119
+
120
+ **When to use:** When you want a simple reactive state that's only used in Solid components.
121
+
122
+ ### SolidDerivation
123
+
124
+ `SolidDerivation` wraps SolidJS's `createMemo`:
125
+
126
+ ```typescript
127
+ import { SolidState, SolidDerivation } from '@ersbeth/picoflow/solid'
128
+
129
+ // Use in component
130
+ function NameDisplay() {
131
+ const firstName = new SolidState('John')
132
+ const lastName = new SolidState('Doe')
133
+
134
+ // Create derived value
135
+ const fullName = new SolidDerivation(() => {
136
+ return `${firstName.get()} ${lastName.get()}`
137
+ })
138
+ return <div>{fullName.get()}</div> // Automatically updates
139
+ }
140
+ ```
141
+
142
+ **When to use:** When you need computed values that are only used in Solid components.
143
+
144
+ ### SolidResource
145
+
146
+ `SolidResource` wraps SolidJS's `createResource`:
147
+
148
+ ```typescript
149
+ import { SolidResource } from '@ersbeth/picoflow/solid'
150
+
151
+ // Use in component
152
+ function UserProfile() {
153
+
154
+ // Create resource
155
+ const user = new SolidResource(async () => {
156
+ const res = await fetch('/api/user')
157
+ return res.json()
158
+ })
159
+
160
+ const userData = user.get()
161
+
162
+ return (
163
+ <div>
164
+ {user.state() === 'pending' && <p>Loading...</p>}
165
+ {state.state() === 'ready' && user.latest() && (
166
+ <>
167
+ <h1>{user.latest().name}</h1>
168
+ <p>{user.latest().email}</p>
169
+ </>
170
+ )}
171
+ {user.state() === 'errored' && <p>Error loading user</p>}
172
+ <button onClick={() => user.refetch()}>Refresh</button>
173
+ </div>
174
+ )
175
+ }
176
+ ```
177
+
178
+ **When to use:** When you need async data loading that integrates with SolidJS's Suspense and ErrorBoundary.
179
+
180
+ ## Complete Examples
181
+
182
+ ```typescript
183
+ import { from } from '@ersbeth/picoflow/solid'
184
+ import { state, derivation } from '@ersbeth/picoflow'
185
+
186
+ // Create PicoFlow global state and derivation
187
+ const $count = state(0)
188
+ const $isEven = derivation((t) => $count.get(t) % 2 === 0)
189
+
190
+ // Use in component
191
+ function Counter() {
192
+
193
+ // Convert to Solid primitives
194
+ const count = from($count)
195
+ const isEven = from($isEven)
196
+
197
+ return (
198
+ <div>
199
+ <p>Count: {count.get()}</p>
200
+ <p>{isEven.get() ? 'Even' : 'Odd'}</p>
201
+ <button onClick={() => $count.set(prev => prev + 1)}>Increment</button>
202
+ <button onClick={() => $count.set(prev => prev - 1)}>Decrement</button>
203
+ </div>
204
+ )
205
+ }
206
+ ```
207
+
208
+ ## Best Practices
209
+
210
+ ### When to Convert vs Use Directly
211
+
212
+ **Convert PicoFlow observables (`from()`) when:**
213
+ - ✅ You have existing PicoFlow state/logic to reuse
214
+ - ✅ You want to share reactive logic between frameworks
215
+ - ✅ You need fine-grained PicoFlow features (arrays, maps, streams)
216
+ - ✅ Your business logic is framework-agnostic
217
+
218
+ **Use Solid primitives directly when:**
219
+ - ✅ The state is only used in Solid components
220
+ - ✅ You don't need PicoFlow's advanced features
221
+ - ✅ You prefer SolidJS's automatic tracking for simple cases