@ersbeth/picoflow 0.2.4 → 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.
- package/.cursor/plans/update-js-e795d61b.plan.md +567 -0
- package/.gitlab-ci.yml +24 -0
- package/.vscode/settings.json +3 -3
- package/CHANGELOG.md +51 -0
- package/IMPLEMENTATION_GUIDE.md +1578 -0
- package/README.md +62 -25
- package/biome.json +32 -32
- package/dist/picoflow.js +610 -436
- package/dist/types/advanced/array.d.ts +0 -6
- package/dist/types/advanced/array.d.ts.map +1 -1
- package/dist/types/advanced/index.d.ts +5 -5
- package/dist/types/advanced/index.d.ts.map +1 -1
- package/dist/types/advanced/map.d.ts +114 -23
- package/dist/types/advanced/map.d.ts.map +1 -1
- package/dist/types/advanced/resource.d.ts +51 -12
- package/dist/types/advanced/resource.d.ts.map +1 -1
- package/dist/types/advanced/resourceAsync.d.ts +28 -13
- package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
- package/dist/types/advanced/stream.d.ts +74 -16
- package/dist/types/advanced/stream.d.ts.map +1 -1
- package/dist/types/advanced/streamAsync.d.ts +69 -15
- package/dist/types/advanced/streamAsync.d.ts.map +1 -1
- package/dist/types/basic/constant.d.ts +44 -16
- package/dist/types/basic/constant.d.ts.map +1 -1
- package/dist/types/basic/derivation.d.ts +73 -24
- package/dist/types/basic/derivation.d.ts.map +1 -1
- package/dist/types/basic/disposable.d.ts +65 -6
- package/dist/types/basic/disposable.d.ts.map +1 -1
- package/dist/types/basic/effect.d.ts +27 -16
- package/dist/types/basic/effect.d.ts.map +1 -1
- package/dist/types/basic/index.d.ts +7 -8
- package/dist/types/basic/index.d.ts.map +1 -1
- package/dist/types/basic/observable.d.ts +62 -13
- package/dist/types/basic/observable.d.ts.map +1 -1
- package/dist/types/basic/signal.d.ts +35 -6
- package/dist/types/basic/signal.d.ts.map +1 -1
- package/dist/types/basic/state.d.ts +25 -4
- package/dist/types/basic/state.d.ts.map +1 -1
- package/dist/types/basic/trackingContext.d.ts +33 -0
- package/dist/types/basic/trackingContext.d.ts.map +1 -0
- package/dist/types/creators.d.ts +271 -26
- package/dist/types/creators.d.ts.map +1 -1
- package/dist/types/index.d.ts +60 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +5 -5
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/index.d.ts +2 -2
- package/dist/types/solid/index.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +96 -4
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +110 -0
- package/docs/api/classes/FlowArray.md +489 -0
- package/docs/api/classes/FlowConstant.md +350 -0
- package/docs/api/classes/FlowDerivation.md +334 -0
- package/docs/api/classes/FlowEffect.md +100 -0
- package/docs/api/classes/FlowMap.md +512 -0
- package/docs/api/classes/FlowObservable.md +306 -0
- package/docs/api/classes/FlowResource.md +380 -0
- package/docs/api/classes/FlowResourceAsync.md +362 -0
- package/docs/api/classes/FlowSignal.md +160 -0
- package/docs/api/classes/FlowState.md +368 -0
- package/docs/api/classes/FlowStream.md +367 -0
- package/docs/api/classes/FlowStreamAsync.md +364 -0
- package/docs/api/classes/SolidDerivation.md +75 -0
- package/docs/api/classes/SolidResource.md +91 -0
- package/docs/api/classes/SolidState.md +71 -0
- package/docs/api/classes/TrackingContext.md +33 -0
- package/docs/api/functions/array.md +58 -0
- package/docs/api/functions/constant.md +45 -0
- package/docs/api/functions/derivation.md +53 -0
- package/docs/api/functions/effect.md +49 -0
- package/docs/api/functions/from.md +220 -0
- package/docs/api/functions/isDisposable.md +49 -0
- package/docs/api/functions/map.md +57 -0
- package/docs/api/functions/resource.md +52 -0
- package/docs/api/functions/resourceAsync.md +50 -0
- package/docs/api/functions/signal.md +36 -0
- package/docs/api/functions/state.md +47 -0
- package/docs/api/functions/stream.md +53 -0
- package/docs/api/functions/streamAsync.md +50 -0
- package/docs/api/index.md +118 -0
- package/docs/api/interfaces/FlowDisposable.md +65 -0
- package/docs/api/interfaces/SolidObservable.md +19 -0
- package/docs/api/type-aliases/FlowArrayAction.md +49 -0
- package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
- package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
- package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
- package/docs/api/type-aliases/NotPromise.md +18 -0
- package/docs/api/type-aliases/SolidGetter.md +17 -0
- package/docs/api/typedoc-sidebar.json +1 -0
- package/docs/examples/examples.md +2313 -0
- package/docs/examples/patterns.md +649 -0
- package/docs/guide/advanced/disposal.md +426 -0
- package/docs/guide/advanced/solidjs.md +221 -0
- package/docs/guide/advanced/upgrading.md +464 -0
- package/docs/guide/introduction/concepts.md +56 -0
- package/docs/guide/introduction/conventions.md +61 -0
- package/docs/guide/introduction/getting-started.md +134 -0
- package/docs/guide/introduction/lifecycle.md +371 -0
- package/docs/guide/primitives/array.md +400 -0
- package/docs/guide/primitives/constant.md +380 -0
- package/docs/guide/primitives/derivations.md +348 -0
- package/docs/guide/primitives/effects.md +458 -0
- package/docs/guide/primitives/map.md +387 -0
- package/docs/guide/primitives/overview.md +175 -0
- package/docs/guide/primitives/resources.md +858 -0
- package/docs/guide/primitives/signal.md +259 -0
- package/docs/guide/primitives/state.md +368 -0
- package/docs/guide/primitives/streams.md +931 -0
- package/docs/index.md +47 -0
- package/docs/public/logo.svg +1 -0
- package/package.json +57 -41
- package/src/advanced/array.ts +208 -210
- package/src/advanced/index.ts +7 -7
- package/src/advanced/map.ts +178 -68
- package/src/advanced/resource.ts +87 -43
- package/src/advanced/resourceAsync.ts +62 -42
- package/src/advanced/stream.ts +113 -50
- package/src/advanced/streamAsync.ts +120 -61
- package/src/basic/constant.ts +82 -49
- package/src/basic/derivation.ts +128 -84
- package/src/basic/disposable.ts +74 -15
- package/src/basic/effect.ts +85 -77
- package/src/basic/index.ts +7 -8
- package/src/basic/observable.ts +94 -36
- package/src/basic/signal.ts +133 -105
- package/src/basic/state.ts +46 -25
- package/src/basic/trackingContext.ts +45 -0
- package/src/creators.ts +297 -54
- package/src/index.ts +96 -43
- package/src/solid/converters.ts +186 -67
- package/src/solid/index.ts +8 -2
- package/src/solid/primitives.ts +167 -65
- package/test/array.test.ts +592 -612
- package/test/constant.test.ts +31 -33
- package/test/derivation.test.ts +531 -536
- package/test/effect.test.ts +21 -21
- package/test/map.test.ts +233 -137
- package/test/resource.test.ts +119 -121
- package/test/resourceAsync.test.ts +98 -100
- package/test/signal.test.ts +51 -55
- package/test/state.test.ts +186 -168
- package/test/stream.test.ts +189 -189
- package/test/streamAsync.test.ts +186 -186
- package/tsconfig.json +19 -18
- package/typedoc.json +37 -0
- package/vite.config.ts +23 -23
- package/vitest.config.ts +7 -7
- package/api/doc/index.md +0 -31
- package/api/doc/picoflow.array.md +0 -55
- package/api/doc/picoflow.constant.md +0 -55
- package/api/doc/picoflow.derivation.md +0 -55
- package/api/doc/picoflow.effect.md +0 -55
- package/api/doc/picoflow.flowarray._constructor_.md +0 -49
- package/api/doc/picoflow.flowarray._lastaction.md +0 -13
- package/api/doc/picoflow.flowarray.clear.md +0 -17
- package/api/doc/picoflow.flowarray.dispose.md +0 -55
- package/api/doc/picoflow.flowarray.get.md +0 -19
- package/api/doc/picoflow.flowarray.length.md +0 -13
- package/api/doc/picoflow.flowarray.md +0 -273
- package/api/doc/picoflow.flowarray.pop.md +0 -17
- package/api/doc/picoflow.flowarray.push.md +0 -53
- package/api/doc/picoflow.flowarray.set.md +0 -53
- package/api/doc/picoflow.flowarray.setitem.md +0 -69
- package/api/doc/picoflow.flowarray.shift.md +0 -17
- package/api/doc/picoflow.flowarray.splice.md +0 -85
- package/api/doc/picoflow.flowarray.unshift.md +0 -53
- package/api/doc/picoflow.flowarrayaction.md +0 -37
- package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
- package/api/doc/picoflow.flowconstant.get.md +0 -25
- package/api/doc/picoflow.flowconstant.md +0 -88
- package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
- package/api/doc/picoflow.flowderivation.get.md +0 -23
- package/api/doc/picoflow.flowderivation.md +0 -86
- package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
- package/api/doc/picoflow.flowdisposable.md +0 -43
- package/api/doc/picoflow.floweffect._constructor_.md +0 -54
- package/api/doc/picoflow.floweffect.dispose.md +0 -21
- package/api/doc/picoflow.floweffect.disposed.md +0 -13
- package/api/doc/picoflow.floweffect.md +0 -131
- package/api/doc/picoflow.flowgetter.md +0 -15
- package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
- package/api/doc/picoflow.flowmap._lastset.md +0 -21
- package/api/doc/picoflow.flowmap.delete.md +0 -61
- package/api/doc/picoflow.flowmap.md +0 -133
- package/api/doc/picoflow.flowmap.setat.md +0 -77
- package/api/doc/picoflow.flowobservable.get.md +0 -19
- package/api/doc/picoflow.flowobservable.md +0 -68
- package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
- package/api/doc/picoflow.flowresource._constructor_.md +0 -49
- package/api/doc/picoflow.flowresource.fetch.md +0 -27
- package/api/doc/picoflow.flowresource.get.md +0 -23
- package/api/doc/picoflow.flowresource.md +0 -100
- package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
- package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
- package/api/doc/picoflow.flowresourceasync.get.md +0 -23
- package/api/doc/picoflow.flowresourceasync.md +0 -100
- package/api/doc/picoflow.flowsignal.dispose.md +0 -59
- package/api/doc/picoflow.flowsignal.disposed.md +0 -18
- package/api/doc/picoflow.flowsignal.md +0 -112
- package/api/doc/picoflow.flowsignal.trigger.md +0 -21
- package/api/doc/picoflow.flowstate.md +0 -52
- package/api/doc/picoflow.flowstate.set.md +0 -61
- package/api/doc/picoflow.flowstream._constructor_.md +0 -49
- package/api/doc/picoflow.flowstream.dispose.md +0 -21
- package/api/doc/picoflow.flowstream.get.md +0 -23
- package/api/doc/picoflow.flowstream.md +0 -100
- package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
- package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
- package/api/doc/picoflow.flowstreamasync.get.md +0 -23
- package/api/doc/picoflow.flowstreamasync.md +0 -100
- package/api/doc/picoflow.flowstreamdisposer.md +0 -13
- package/api/doc/picoflow.flowstreamsetter.md +0 -13
- package/api/doc/picoflow.flowstreamupdater.md +0 -19
- package/api/doc/picoflow.flowwatcher.md +0 -15
- package/api/doc/picoflow.from.md +0 -55
- package/api/doc/picoflow.from_1.md +0 -55
- package/api/doc/picoflow.from_2.md +0 -55
- package/api/doc/picoflow.from_3.md +0 -55
- package/api/doc/picoflow.from_4.md +0 -55
- package/api/doc/picoflow.from_5.md +0 -55
- package/api/doc/picoflow.isdisposable.md +0 -55
- package/api/doc/picoflow.map.md +0 -59
- package/api/doc/picoflow.md +0 -544
- package/api/doc/picoflow.resource.md +0 -55
- package/api/doc/picoflow.resourceasync.md +0 -55
- package/api/doc/picoflow.signal.md +0 -19
- package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
- package/api/doc/picoflow.solidderivation.get.md +0 -13
- package/api/doc/picoflow.solidderivation.md +0 -94
- package/api/doc/picoflow.solidgetter.md +0 -13
- package/api/doc/picoflow.solidobservable.get.md +0 -13
- package/api/doc/picoflow.solidobservable.md +0 -57
- package/api/doc/picoflow.solidresource._constructor_.md +0 -49
- package/api/doc/picoflow.solidresource.get.md +0 -13
- package/api/doc/picoflow.solidresource.latest.md +0 -13
- package/api/doc/picoflow.solidresource.md +0 -157
- package/api/doc/picoflow.solidresource.refetch.md +0 -13
- package/api/doc/picoflow.solidresource.state.md +0 -13
- package/api/doc/picoflow.solidstate._constructor_.md +0 -49
- package/api/doc/picoflow.solidstate.get.md +0 -13
- package/api/doc/picoflow.solidstate.md +0 -115
- package/api/doc/picoflow.solidstate.set.md +0 -13
- package/api/doc/picoflow.state.md +0 -55
- package/api/doc/picoflow.stream.md +0 -55
- package/api/doc/picoflow.streamasync.md +0 -55
- package/api/picoflow.public.api.md +0 -244
- 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
|