@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.
- 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 +557 -1099
- 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 -20
- 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,649 @@
|
|
|
1
|
+
# Common Patterns
|
|
2
|
+
|
|
3
|
+
This guide presents common patterns and best practices for working with PicoFlow's reactive primitives. These patterns will help you write more efficient and maintainable reactive code.
|
|
4
|
+
|
|
5
|
+
## Pattern 1: Computed Display Values
|
|
6
|
+
|
|
7
|
+
Use derivations to create formatted or computed values for display:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const $price = state(99.99)
|
|
11
|
+
const $quantity = state(2)
|
|
12
|
+
|
|
13
|
+
const $formatted = derivation((t) => {
|
|
14
|
+
const price = $price.get(t)
|
|
15
|
+
const quantity = $quantity.get(t)
|
|
16
|
+
const total = price * quantity
|
|
17
|
+
|
|
18
|
+
return `${quantity} × $${price.toFixed(2)} = $${total.toFixed(2)}`
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
effect((t) => {
|
|
22
|
+
console.log($formatted.get(t))
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
$quantity.set(3) // Logs: "3 × $99.99 = $299.97"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Use cases:** Formatted strings, currency display, computed totals, user-friendly messages
|
|
29
|
+
|
|
30
|
+
## Pattern 2: Conditional Tracking
|
|
31
|
+
|
|
32
|
+
Only track a dependency when certain conditions are met:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const $enabled = state(true)
|
|
36
|
+
const $data = state('some data')
|
|
37
|
+
|
|
38
|
+
effect((t) => {
|
|
39
|
+
if ($enabled.get(t)) {
|
|
40
|
+
// Only tracks $data when enabled
|
|
41
|
+
console.log('Data:', $data.get(t))
|
|
42
|
+
} else {
|
|
43
|
+
console.log('Disabled')
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
$data.set('new data') // Logs if enabled
|
|
48
|
+
$enabled.set(false) // Now data changes won't log
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Use cases:** Feature flags, conditional data fetching, dynamic subscriptions
|
|
52
|
+
|
|
53
|
+
## Pattern 3: Non-Reactive Snapshot
|
|
54
|
+
|
|
55
|
+
Take a snapshot of a reactive value without creating a dependency:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const $livePrice = state(100)
|
|
59
|
+
const $refreshTrigger = signal()
|
|
60
|
+
|
|
61
|
+
effect((t) => {
|
|
62
|
+
// React to trigger only
|
|
63
|
+
$refreshTrigger.watch(t)
|
|
64
|
+
|
|
65
|
+
// Take snapshot of current price (no dependency)
|
|
66
|
+
const snapshot = $livePrice.pick()
|
|
67
|
+
console.log('Snapshot taken:', snapshot)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
$livePrice.set(200) // Does NOT trigger effect
|
|
71
|
+
$refreshTrigger.trigger() // DOES trigger, logs current price
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Use cases:** Manual refresh with current values, avoiding circular dependencies, comparison snapshots
|
|
75
|
+
|
|
76
|
+
## Pattern 4: Snapshot + Reactive
|
|
77
|
+
|
|
78
|
+
Mix reactive and non-reactive reads:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
effect((t) => {
|
|
82
|
+
const currentId = $userId.get(t) // Reactive - triggers re-run
|
|
83
|
+
const config = $appConfig.pick() // Non-reactive snapshot
|
|
84
|
+
|
|
85
|
+
fetchUserData(currentId, config)
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Use Cases
|
|
90
|
+
|
|
91
|
+
- Using configuration that shouldn't trigger updates
|
|
92
|
+
- Taking snapshots for comparison
|
|
93
|
+
- Avoiding circular dependencies
|
|
94
|
+
|
|
95
|
+
### Example: API Call with Static Config
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const $searchQuery = state('')
|
|
99
|
+
const $apiConfig = state({ baseUrl: 'https://api.example.com', timeout: 5000 })
|
|
100
|
+
|
|
101
|
+
effect((t) => {
|
|
102
|
+
const query = $searchQuery.get(t) // React to query changes
|
|
103
|
+
const config = $apiConfig.pick() // Use current config without tracking
|
|
104
|
+
|
|
105
|
+
if (query.length > 2) {
|
|
106
|
+
fetch(`${config.baseUrl}/search?q=${query}`, {
|
|
107
|
+
signal: AbortSignal.timeout(config.timeout)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Example: Previous Value Comparison
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const $count = state(0)
|
|
117
|
+
|
|
118
|
+
effect((t) => {
|
|
119
|
+
const previous = $count.pick() // Snapshot before reactive read
|
|
120
|
+
const current = $count.get(t) // Reactive read
|
|
121
|
+
|
|
122
|
+
if (current > previous) {
|
|
123
|
+
console.log('Count increased')
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Pattern 5: Batch Updates
|
|
129
|
+
|
|
130
|
+
Multiple effects can depend on the same state:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const $data = state({ count: 0, name: 'test' })
|
|
134
|
+
|
|
135
|
+
effect((t) => console.log('Count:', $data.get(t).count))
|
|
136
|
+
effect((t) => console.log('Name:', $data.get(t).name))
|
|
137
|
+
|
|
138
|
+
$data.set({ count: 1, name: 'updated' }) // Both effects run once
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Use Cases
|
|
142
|
+
|
|
143
|
+
- Multiple UI components reacting to the same state
|
|
144
|
+
- Coordinated updates across different parts of the app
|
|
145
|
+
- Efficient bulk operations
|
|
146
|
+
|
|
147
|
+
### Example: Form State Management
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
interface FormState {
|
|
151
|
+
username: string
|
|
152
|
+
email: string
|
|
153
|
+
isValid: boolean
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const $form = state<FormState>({
|
|
157
|
+
username: '',
|
|
158
|
+
email: '',
|
|
159
|
+
isValid: false
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Multiple effects react to different parts
|
|
163
|
+
effect((t) => {
|
|
164
|
+
const username = $form.get(t).username
|
|
165
|
+
validateUsername(username)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
effect((t) => {
|
|
169
|
+
const email = $form.get(t).email
|
|
170
|
+
validateEmail(email)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
effect((t) => {
|
|
174
|
+
const form = $form.get(t)
|
|
175
|
+
updateSubmitButton(form.isValid)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Single update triggers all effects once
|
|
179
|
+
function updateForm(updates: Partial<FormState>) {
|
|
180
|
+
$form.set(current => ({ ...current, ...updates }))
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Pattern 6: Derived State Chain
|
|
185
|
+
|
|
186
|
+
Create chains of computed values:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const $items = state<Item[]>([])
|
|
190
|
+
|
|
191
|
+
const $activeItems = derivation((t) =>
|
|
192
|
+
$items.get(t).filter(item => item.active)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const $itemCount = derivation((t) =>
|
|
196
|
+
$activeItems.get(t).length
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
const $statusMessage = derivation((t) => {
|
|
200
|
+
const count = $itemCount.get(t)
|
|
201
|
+
return count === 0 ? 'No items' : `${count} items`
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
effect((t) => {
|
|
205
|
+
console.log($statusMessage.get(t))
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Benefits
|
|
210
|
+
|
|
211
|
+
- Clear separation of concerns
|
|
212
|
+
- Efficient caching at each level
|
|
213
|
+
- Easy to test individual derivations
|
|
214
|
+
|
|
215
|
+
## Pattern 7: Signal for Manual Refresh
|
|
216
|
+
|
|
217
|
+
Use signals to trigger manual updates:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const $refreshTrigger = signal()
|
|
221
|
+
const $data = state<Data>({ cached: true })
|
|
222
|
+
|
|
223
|
+
effect((t) => {
|
|
224
|
+
$refreshTrigger.watch(t) // React to manual trigger
|
|
225
|
+
|
|
226
|
+
// Fetch fresh data
|
|
227
|
+
fetchData().then(data => $data.set(data))
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Trigger refresh from anywhere
|
|
231
|
+
$refreshTrigger.trigger()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Use Cases
|
|
235
|
+
|
|
236
|
+
- Manual data refresh
|
|
237
|
+
- Force recalculation
|
|
238
|
+
- Synchronization points
|
|
239
|
+
|
|
240
|
+
## Pattern 8: Cleanup with Effect Disposer
|
|
241
|
+
|
|
242
|
+
Effects can return cleanup functions:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const $url = state('https://api.example.com/data')
|
|
246
|
+
|
|
247
|
+
effect((t) => {
|
|
248
|
+
const url = $url.get(t)
|
|
249
|
+
const controller = new AbortController()
|
|
250
|
+
|
|
251
|
+
fetch(url, { signal: controller.signal })
|
|
252
|
+
.then(/* handle response */)
|
|
253
|
+
|
|
254
|
+
// Cleanup when effect re-runs or disposes
|
|
255
|
+
return () => controller.abort()
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Use Cases
|
|
260
|
+
|
|
261
|
+
- Canceling pending requests
|
|
262
|
+
- Clearing timers
|
|
263
|
+
- Unsubscribing from events
|
|
264
|
+
|
|
265
|
+
## Pattern 9: Lazy Loading with Resources
|
|
266
|
+
|
|
267
|
+
Use resources for async data that depends on other reactive values:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const $userId = state(1)
|
|
271
|
+
|
|
272
|
+
const $user = resource(
|
|
273
|
+
(t) => $userId.get(t), // Reactive source
|
|
274
|
+
(id) => fetchUser(id) // Async fetcher
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
effect((t) => {
|
|
278
|
+
const user = $user.get(t)
|
|
279
|
+
if (user.state === 'ready') {
|
|
280
|
+
console.log('User:', user.value)
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Benefits
|
|
286
|
+
|
|
287
|
+
- Built-in loading/error states
|
|
288
|
+
- Automatic refetching when dependencies change
|
|
289
|
+
- Cancellation of stale requests
|
|
290
|
+
|
|
291
|
+
## Pattern 10: Conditional Computation (Derivations)
|
|
292
|
+
|
|
293
|
+
Skip expensive derivation computation when not needed:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const $enabled = state(false)
|
|
297
|
+
const $data = state([1, 2, 3, 4, 5])
|
|
298
|
+
|
|
299
|
+
const $processedData = derivation((t) => {
|
|
300
|
+
if (!$enabled.get(t)) {
|
|
301
|
+
return [] // Skip computation when disabled
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Only compute when enabled
|
|
305
|
+
return $data.get(t).map(n => n * 2).filter(n => n > 5)
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Use Cases
|
|
310
|
+
|
|
311
|
+
- Feature flags that control expensive computations
|
|
312
|
+
- Conditional data processing
|
|
313
|
+
- Performance optimization for optional features
|
|
314
|
+
|
|
315
|
+
## Pattern 11: Memoization Within Derivation
|
|
316
|
+
|
|
317
|
+
Use derivations to cache expensive theme or configuration calculations:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const $config = state({ theme: 'dark', lang: 'en' })
|
|
321
|
+
|
|
322
|
+
const $themeColors = derivation((t) => {
|
|
323
|
+
const theme = $config.get(t).theme
|
|
324
|
+
|
|
325
|
+
// Expensive computation
|
|
326
|
+
return theme === 'dark'
|
|
327
|
+
? generateDarkPalette()
|
|
328
|
+
: generateLightPalette()
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Use Cases
|
|
333
|
+
|
|
334
|
+
- Theme color palette generation
|
|
335
|
+
- Expensive configuration parsing
|
|
336
|
+
- Complex UI calculations
|
|
337
|
+
|
|
338
|
+
## Pattern 12: Combining Multiple Derivations
|
|
339
|
+
|
|
340
|
+
Chain derivations to create a computation pipeline:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const $sales = state([100, 200, 150])
|
|
344
|
+
const $expenses = state([50, 80, 60])
|
|
345
|
+
|
|
346
|
+
const $totalSales = derivation((t) => {
|
|
347
|
+
return $sales.get(t).reduce((a, b) => a + b, 0)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
const $totalExpenses = derivation((t) => {
|
|
351
|
+
return $expenses.get(t).reduce((a, b) => a + b, 0)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const $profit = derivation((t) => {
|
|
355
|
+
return $totalSales.get(t) - $totalExpenses.get(t)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
const $profitMargin = derivation((t) => {
|
|
359
|
+
const profit = $profit.get(t)
|
|
360
|
+
const sales = $totalSales.get(t)
|
|
361
|
+
return sales > 0 ? (profit / sales) * 100 : 0
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Dependency graph:
|
|
366
|
+
|
|
367
|
+
```mermaid
|
|
368
|
+
graph TD
|
|
369
|
+
A[$sales] --> C[$totalSales]
|
|
370
|
+
B[$expenses] --> D[$totalExpenses]
|
|
371
|
+
C --> E[$profit]
|
|
372
|
+
D --> E
|
|
373
|
+
E --> F[$profitMargin]
|
|
374
|
+
C --> F
|
|
375
|
+
|
|
376
|
+
style A fill:#FFE6E6
|
|
377
|
+
style B fill:#FFE6E6
|
|
378
|
+
style C fill:#E6F3FF
|
|
379
|
+
style D fill:#E6F3FF
|
|
380
|
+
style E fill:#E6F3FF
|
|
381
|
+
style F fill:#E6F3FF
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Benefits
|
|
385
|
+
|
|
386
|
+
- Each derivation is simple and focused
|
|
387
|
+
- Efficient caching at each level
|
|
388
|
+
- Easy to test and debug
|
|
389
|
+
- Clear dependency relationships
|
|
390
|
+
|
|
391
|
+
## Pattern 13: Logging and Debugging with Effects
|
|
392
|
+
|
|
393
|
+
Use effects for reactive logging and debugging:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const $count = state(0)
|
|
397
|
+
|
|
398
|
+
// Debug logger
|
|
399
|
+
effect((t) => {
|
|
400
|
+
const value = $count.get(t)
|
|
401
|
+
console.log('[DEBUG] count changed:', value, 'at', new Date())
|
|
402
|
+
})
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Use Cases
|
|
406
|
+
|
|
407
|
+
- Development debugging
|
|
408
|
+
- Tracking state changes
|
|
409
|
+
- Performance monitoring
|
|
410
|
+
- Audit logs
|
|
411
|
+
|
|
412
|
+
## Pattern 14: DOM Updates with Effects
|
|
413
|
+
|
|
414
|
+
Effects are perfect for updating the DOM based on reactive state:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
const $username = state('')
|
|
418
|
+
|
|
419
|
+
effect((t) => {
|
|
420
|
+
const name = $username.get(t)
|
|
421
|
+
document.getElementById('username').textContent = name
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Use Cases
|
|
426
|
+
|
|
427
|
+
- Updating text content
|
|
428
|
+
- Toggling classes
|
|
429
|
+
- Managing visibility
|
|
430
|
+
- Synchronizing UI with state
|
|
431
|
+
|
|
432
|
+
## Pattern 15: LocalStorage Sync with Effects
|
|
433
|
+
|
|
434
|
+
Automatically persist state to localStorage:
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
const $preferences = state({ theme: 'light', lang: 'en' })
|
|
438
|
+
|
|
439
|
+
effect((t) => {
|
|
440
|
+
const prefs = $preferences.get(t)
|
|
441
|
+
localStorage.setItem('preferences', JSON.stringify(prefs))
|
|
442
|
+
})
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Use Cases
|
|
446
|
+
|
|
447
|
+
- User preferences
|
|
448
|
+
- Draft autosave
|
|
449
|
+
- Cache management
|
|
450
|
+
- Offline-first applications
|
|
451
|
+
|
|
452
|
+
## Pattern 16: Derived Side Effects
|
|
453
|
+
|
|
454
|
+
Combine derivations with effects for complex reactive behaviors:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
const $items = state([1, 2, 3])
|
|
458
|
+
const $total = derivation((t) => {
|
|
459
|
+
return $items.get(t).reduce((sum, n) => sum + n, 0)
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
effect((t) => {
|
|
463
|
+
const total = $total.get(t)
|
|
464
|
+
if (total > 100) {
|
|
465
|
+
alert('Total exceeded limit!')
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Use Cases
|
|
471
|
+
|
|
472
|
+
- Threshold alerts
|
|
473
|
+
- Conditional notifications
|
|
474
|
+
- Validation side effects
|
|
475
|
+
- Derived triggers
|
|
476
|
+
|
|
477
|
+
## Pattern 17: Batch Updates with Maps
|
|
478
|
+
|
|
479
|
+
When updating multiple map entries, consider the notification pattern:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// ❌ Multiple updates = multiple notifications
|
|
483
|
+
$map.add('a', 1)
|
|
484
|
+
$map.add('b', 2)
|
|
485
|
+
$map.add('c', 3)
|
|
486
|
+
// Each operation triggers notifications
|
|
487
|
+
|
|
488
|
+
// ✅ Consider if batching is possible at a higher level
|
|
489
|
+
// Note: PicoFlow doesn't have built-in batching, but you can
|
|
490
|
+
// minimize unnecessary work by grouping related operations
|
|
491
|
+
const updates = [
|
|
492
|
+
{ key: 'a', value: 1 },
|
|
493
|
+
{ key: 'b', value: 2 },
|
|
494
|
+
{ key: 'c', value: 3 }
|
|
495
|
+
]
|
|
496
|
+
for (const { key, value } of updates) {
|
|
497
|
+
const existing = $map.pick().has(key)
|
|
498
|
+
if (existing) {
|
|
499
|
+
$map.update(key, value)
|
|
500
|
+
} else {
|
|
501
|
+
$map.add(key, value)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Use cases:** Bulk operations, data synchronization, minimizing UI updates during batch changes
|
|
507
|
+
|
|
508
|
+
## Pattern 18: Selective Tracking with Maps
|
|
509
|
+
|
|
510
|
+
Track only the operations you need, not all operations:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
// Track only additions, not updates or deletions
|
|
514
|
+
effect((t) => {
|
|
515
|
+
const lastAdded = $users.$lastAdded.get(t)
|
|
516
|
+
if (lastAdded) {
|
|
517
|
+
handleNewUser(lastAdded.value)
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
// Separate effect for deletions
|
|
522
|
+
effect((t) => {
|
|
523
|
+
const lastDeleted = $users.$lastDeleted.get(t)
|
|
524
|
+
if (lastDeleted) {
|
|
525
|
+
handleRemovedUser(lastDeleted.key)
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
// Separate effect for updates
|
|
530
|
+
effect((t) => {
|
|
531
|
+
const lastUpdated = $users.$lastUpdated.get(t)
|
|
532
|
+
if (lastUpdated) {
|
|
533
|
+
handleUpdatedUser(lastUpdated.value)
|
|
534
|
+
}
|
|
535
|
+
})
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Use cases:** Different UI animations for different operations, selective logging, operation-specific side effects
|
|
539
|
+
|
|
540
|
+
## Pattern 19: Combine Coarse and Fine-Grained Tracking
|
|
541
|
+
|
|
542
|
+
Use both whole-map tracking and fine-grained tracking together:
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
// Coarse: Update count (reacts to any change)
|
|
546
|
+
effect((t) => {
|
|
547
|
+
const count = $items.get(t).size
|
|
548
|
+
updateCountDisplay(count)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
// Fine: Animate specific changes
|
|
552
|
+
effect((t) => {
|
|
553
|
+
const lastAdded = $items.$lastAdded.get(t)
|
|
554
|
+
if (lastAdded) {
|
|
555
|
+
animateNewItem(lastAdded.key, lastAdded.value)
|
|
556
|
+
}
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
effect((t) => {
|
|
560
|
+
const lastDeleted = $items.$lastDeleted.get(t)
|
|
561
|
+
if (lastDeleted) {
|
|
562
|
+
animateRemovedItem(lastDeleted.key)
|
|
563
|
+
}
|
|
564
|
+
})
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Use cases:** Combining aggregate statistics with individual item animations, efficient UI updates that need both totals and specific changes
|
|
568
|
+
|
|
569
|
+
## Pattern 20: Batch Updates with Arrays
|
|
570
|
+
|
|
571
|
+
Minimize notifications by batching multiple operations:
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// ❌ Multiple updates = multiple notifications
|
|
575
|
+
$items.push(1)
|
|
576
|
+
$items.push(2)
|
|
577
|
+
$items.push(3)
|
|
578
|
+
|
|
579
|
+
// ✅ Use splice for batch additions
|
|
580
|
+
$items.splice($items.length, 0, 1, 2, 3)
|
|
581
|
+
|
|
582
|
+
// Or use set() to replace entire array if appropriate
|
|
583
|
+
const newItems = [...$items.pick(), 1, 2, 3]
|
|
584
|
+
$items.set(newItems)
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Use cases:** Bulk operations, data synchronization, minimizing UI updates during batch changes
|
|
588
|
+
|
|
589
|
+
## Pattern 21: Selective Tracking with Arrays
|
|
590
|
+
|
|
591
|
+
Track only the operations you need, not all operations:
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// Track only additions, not deletions
|
|
595
|
+
effect((t) => {
|
|
596
|
+
const action = $items.$lastAction.get(t)
|
|
597
|
+
if (action && action.type === 'push') {
|
|
598
|
+
handleNewItem(action.item)
|
|
599
|
+
}
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
// Separate effect for deletions
|
|
603
|
+
effect((t) => {
|
|
604
|
+
const action = $items.$lastAction.get(t)
|
|
605
|
+
if (action && (action.type === 'pop' || action.type === 'splice')) {
|
|
606
|
+
handleRemovedItems(action)
|
|
607
|
+
}
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
// Separate effect for updates
|
|
611
|
+
effect((t) => {
|
|
612
|
+
const action = $items.$lastAction.get(t)
|
|
613
|
+
if (action && action.type === 'setItem') {
|
|
614
|
+
handleUpdatedItem(action.index, action.item)
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**Use cases:** Different UI animations for different operations, selective logging, operation-specific side effects
|
|
620
|
+
|
|
621
|
+
## Pattern 22: Combine Coarse and Fine-Grained Array Tracking
|
|
622
|
+
|
|
623
|
+
Use both whole-array tracking and fine-grained tracking together:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
// Coarse: Update count (reacts to any change)
|
|
627
|
+
effect((t) => {
|
|
628
|
+
const count = $items.get(t).length
|
|
629
|
+
updateCountDisplay(count)
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
// Fine: Animate specific changes
|
|
633
|
+
effect((t) => {
|
|
634
|
+
const action = $items.$lastAction.get(t)
|
|
635
|
+
if (action && action.type === 'push') {
|
|
636
|
+
animateNewItem(action.item)
|
|
637
|
+
}
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
effect((t) => {
|
|
641
|
+
const action = $items.$lastAction.get(t)
|
|
642
|
+
if (action && action.type === 'splice') {
|
|
643
|
+
animateRemovedItems(action.start, action.deleteCount)
|
|
644
|
+
}
|
|
645
|
+
})
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Use cases:** Combining aggregate statistics with individual item animations, efficient UI updates that need both totals and specific changes
|
|
649
|
+
|