@ersbeth/picoflow 2.0.1 → 2.0.3
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/README.md +10 -0
- package/SKILL.md +106 -0
- package/dist/types/api/base/flowSubscribable.d.ts +3 -3
- package/dist/types/api/base/flowSubscribable.d.ts.map +1 -1
- package/package.json +5 -1
- package/.gitlab-ci.yml +0 -24
- package/.vscode/settings.json +0 -5
- package/CHANGELOG.md +0 -94
- package/biome.json +0 -47
- package/docs/.vitepress/config.mts +0 -145
- package/docs/api/functions/array.md +0 -35
- package/docs/api/functions/constant.md +0 -33
- package/docs/api/functions/constantAsync.md +0 -69
- package/docs/api/functions/derivation.md +0 -34
- package/docs/api/functions/derivationAsync.md +0 -34
- package/docs/api/functions/from.md +0 -129
- package/docs/api/functions/isDisposable.md +0 -27
- package/docs/api/functions/map.md +0 -36
- package/docs/api/functions/signal.md +0 -21
- package/docs/api/functions/state.md +0 -67
- package/docs/api/functions/stateAsync.md +0 -69
- package/docs/api/functions/subscribe.md +0 -40
- package/docs/api/functions/writableDerivation.md +0 -33
- package/docs/api/functions/writableDerivationAsync.md +0 -34
- package/docs/api/index.md +0 -61
- package/docs/api/interfaces/FlowArray.md +0 -439
- package/docs/api/interfaces/FlowConstant.md +0 -220
- package/docs/api/interfaces/FlowConstantAsync.md +0 -221
- package/docs/api/interfaces/FlowDerivation.md +0 -241
- package/docs/api/interfaces/FlowDerivationAsync.md +0 -242
- package/docs/api/interfaces/FlowDisposable.md +0 -59
- package/docs/api/interfaces/FlowEffect.md +0 -64
- package/docs/api/interfaces/FlowMap.md +0 -374
- package/docs/api/interfaces/FlowObservable.md +0 -155
- package/docs/api/interfaces/FlowSignal.md +0 -156
- package/docs/api/interfaces/FlowState.md +0 -269
- package/docs/api/interfaces/FlowStateAsync.md +0 -268
- package/docs/api/interfaces/FlowSubscribable.md +0 -55
- package/docs/api/interfaces/FlowTracker.md +0 -61
- package/docs/api/interfaces/FlowValue.md +0 -222
- package/docs/api/interfaces/FlowWritableDerivation.md +0 -292
- package/docs/api/interfaces/FlowWritableDerivationAsync.md +0 -293
- package/docs/api/type-aliases/DerivationFunction.md +0 -28
- package/docs/api/type-aliases/DerivationFunctionAsync.md +0 -28
- package/docs/api/type-aliases/FlowArrayAction.md +0 -60
- package/docs/api/type-aliases/FlowDataTracker.md +0 -33
- package/docs/api/type-aliases/FlowMapAction.md +0 -48
- package/docs/api/type-aliases/FlowOnDataListener.md +0 -33
- package/docs/api/type-aliases/FlowOnErrorListener.md +0 -27
- package/docs/api/type-aliases/FlowOnPendingListener.md +0 -21
- package/docs/api/type-aliases/FlowReadonly.md +0 -22
- package/docs/api/type-aliases/InitFunction.md +0 -21
- package/docs/api/type-aliases/InitFunctionAsync.md +0 -21
- package/docs/api/type-aliases/NotPromise.md +0 -21
- package/docs/api/type-aliases/UpdateFunction.md +0 -27
- package/docs/api/type-aliases/UpdateFunctionAsync.md +0 -27
- package/docs/api/typedoc-sidebar.json +0 -65
- package/docs/examples/examples.md +0 -2311
- package/docs/examples/patterns.md +0 -649
- package/docs/guide/advanced/architecture.md +0 -1234
- package/docs/guide/advanced/disposal.md +0 -426
- package/docs/guide/advanced/migration-v1.md +0 -464
- package/docs/guide/advanced/migration-v2.md +0 -204
- package/docs/guide/advanced/solidjs.md +0 -135
- package/docs/guide/introduction/concepts.md +0 -57
- package/docs/guide/introduction/conventions.md +0 -30
- package/docs/guide/introduction/getting-started.md +0 -139
- package/docs/guide/introduction/lifecycle.md +0 -368
- package/docs/guide/primitives/array.md +0 -286
- package/docs/guide/primitives/constant.md +0 -207
- package/docs/guide/primitives/derivations.md +0 -281
- package/docs/guide/primitives/effects.md +0 -372
- package/docs/guide/primitives/map.md +0 -265
- package/docs/guide/primitives/overview.md +0 -92
- package/docs/guide/primitives/signal.md +0 -222
- package/docs/guide/primitives/state.md +0 -272
- package/docs/index.md +0 -47
- package/docs/public/logo.svg +0 -1
- package/src/api/base/flowDisposable.ts +0 -44
- package/src/api/base/flowObservable.ts +0 -28
- package/src/api/base/flowSubscribable.ts +0 -87
- package/src/api/base/flowTracker.ts +0 -7
- package/src/api/base/index.ts +0 -4
- package/src/api/index.ts +0 -2
- package/src/api/nodes/async/flowConstantAsync.ts +0 -36
- package/src/api/nodes/async/flowDerivationAsync.ts +0 -42
- package/src/api/nodes/async/flowStateAsync.ts +0 -47
- package/src/api/nodes/async/flowWritableDerivationAsync.ts +0 -33
- package/src/api/nodes/async/index.ts +0 -4
- package/src/api/nodes/collections/flowArray.ts +0 -155
- package/src/api/nodes/collections/flowMap.ts +0 -115
- package/src/api/nodes/collections/index.ts +0 -2
- package/src/api/nodes/flowEffect.ts +0 -42
- package/src/api/nodes/flowSignal.ts +0 -28
- package/src/api/nodes/flowValue.ts +0 -37
- package/src/api/nodes/index.ts +0 -7
- package/src/api/nodes/sync/flowConstant.ts +0 -33
- package/src/api/nodes/sync/flowDerivation.ts +0 -41
- package/src/api/nodes/sync/flowState.ts +0 -45
- package/src/api/nodes/sync/flowWritableDerivation.ts +0 -31
- package/src/api/nodes/sync/index.ts +0 -4
- package/src/api/nodes/utils.ts +0 -24
- package/src/base/disposable.ts +0 -18
- package/src/base/executionStack.ts +0 -42
- package/src/base/index.ts +0 -5
- package/src/base/node.ts +0 -98
- package/src/base/observable.ts +0 -92
- package/src/base/observer.ts +0 -51
- package/src/converters/index.ts +0 -1
- package/src/converters/solid.ts +0 -109
- package/src/index.ts +0 -2
- package/src/nodes/arrayNode.ts +0 -180
- package/src/nodes/effectNode.ts +0 -58
- package/src/nodes/index.ts +0 -7
- package/src/nodes/mapNode.ts +0 -125
- package/src/nodes/signalNode.ts +0 -19
- package/src/nodes/valueAsyncNode.ts +0 -85
- package/src/nodes/valueNode.ts +0 -148
- package/src/nodes/valueSyncNode.ts +0 -125
- package/src/schedulers/asyncResolver.ts +0 -78
- package/src/schedulers/asyncScheduler.ts +0 -66
- package/src/schedulers/index.ts +0 -4
- package/src/schedulers/pendingError.ts +0 -13
- package/src/schedulers/scheduler.ts +0 -9
- package/src/schedulers/syncResolver.ts +0 -69
- package/src/schedulers/syncScheduler.ts +0 -55
- package/test/base/pendingError.test.ts +0 -67
- package/test/converters/solid.derivation.browser.test.tsx +0 -69
- package/test/converters/solid.node.test.ts +0 -654
- package/test/converters/solid.state.browser.test.tsx +0 -1592
- package/test/reactivity/flowSignal.test.ts +0 -226
- package/test/reactivity/nodes/async/asyncScheduler/asyncResolver.test.ts +0 -593
- package/test/reactivity/nodes/async/asyncScheduler/asyncScheduler.test.ts +0 -317
- package/test/reactivity/nodes/async/flowConstantAsync.test.ts +0 -652
- package/test/reactivity/nodes/async/flowDerivation.test.ts +0 -898
- package/test/reactivity/nodes/async/flowDerivationAsync.test.ts +0 -1716
- package/test/reactivity/nodes/async/flowStateAsync.test.ts +0 -708
- package/test/reactivity/nodes/async/flowWritableDerivationAsync.test.ts +0 -614
- package/test/reactivity/nodes/collections/flowArray.asyncStates.test.ts +0 -1289
- package/test/reactivity/nodes/collections/flowArray.scalars.test.ts +0 -961
- package/test/reactivity/nodes/collections/flowArray.states.test.ts +0 -1035
- package/test/reactivity/nodes/collections/flowMap.asyncStates.test.ts +0 -960
- package/test/reactivity/nodes/collections/flowMap.scalars.test.ts +0 -775
- package/test/reactivity/nodes/collections/flowMap.states.test.ts +0 -958
- package/test/reactivity/nodes/sync/flowConstant.test.ts +0 -377
- package/test/reactivity/nodes/sync/flowDerivation.test.ts +0 -896
- package/test/reactivity/nodes/sync/flowState.test.ts +0 -341
- package/test/reactivity/nodes/sync/flowWritableDerivation.test.ts +0 -603
- package/test/vitest.d.ts +0 -10
- package/tsconfig.json +0 -37
- package/typedoc.json +0 -37
- package/vite.config.ts +0 -31
- package/vitest.browser.config.ts +0 -21
- package/vitest.config.ts +0 -17
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
# Lifecycle
|
|
2
|
-
|
|
3
|
-
Understanding PicoFlow's internal execution model is key to mastering reactive programming. This guide walks through exactly what happens during creation, updates, and disposal using a concrete example.
|
|
4
|
-
|
|
5
|
-
## The Example
|
|
6
|
-
|
|
7
|
-
Throughout this guide, we'll use this simple but complete example:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
const $a = state(1)
|
|
11
|
-
const $b = state(2)
|
|
12
|
-
const $c = derivation((t) => $a.get(t) + $b.get(t))
|
|
13
|
-
$c.subscribe((c) => console.log('C =', c))
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
This example demonstrates:
|
|
17
|
-
- **Two states**: `$a` and `$b`
|
|
18
|
-
- **One derivation**: `$c` that computes the sum
|
|
19
|
-
- **One effect**: that logs the result
|
|
20
|
-
|
|
21
|
-
Let's see what happens at each stage of the lifecycle.
|
|
22
|
-
|
|
23
|
-
## Creation Flow
|
|
24
|
-
|
|
25
|
-
When you run the example code, here's the exact sequence of events:
|
|
26
|
-
|
|
27
|
-
### Step-by-Step
|
|
28
|
-
|
|
29
|
-
1. **`state(1)` creates `$a`**
|
|
30
|
-
- A new `FlowState` instance is created
|
|
31
|
-
- Internal value is set to `1`
|
|
32
|
-
- No dependencies yet
|
|
33
|
-
|
|
34
|
-
2. **`state(2)` creates `$b`**
|
|
35
|
-
- Another `FlowState` instance is created
|
|
36
|
-
- Internal value is set to `2`
|
|
37
|
-
- No dependencies yet
|
|
38
|
-
|
|
39
|
-
3. **`derivation(...)` creates `$c`**
|
|
40
|
-
- A new `FlowDerivation` instance is created
|
|
41
|
-
- The compute function is stored but **NOT executed** (lazy evaluation)
|
|
42
|
-
- A `FlowTracker` will be created when the derivation is first accessed
|
|
43
|
-
- Still no computed value yet
|
|
44
|
-
|
|
45
|
-
4. **`$c.subscribe(...)` creates the effect**
|
|
46
|
-
- A new `FlowEffect` instance is created
|
|
47
|
-
- **The effect executes immediately** (not lazy!)
|
|
48
|
-
- During execution:
|
|
49
|
-
- The subscribe method internally calls `$c.get(t)` with a `FlowTracker`
|
|
50
|
-
- This triggers `$c` to initialize (first access)
|
|
51
|
-
- `$c` creates its own `FlowTracker` and runs its compute function
|
|
52
|
-
- Compute calls `$a.get(t)` → returns `1`, registers `$a` as dependency of `$c`
|
|
53
|
-
- Compute calls `$b.get(t)` → returns `2`, registers `$b` as dependency of `$c`
|
|
54
|
-
- Compute returns `3`
|
|
55
|
-
- The value `3` is registered as a dependency of the effect
|
|
56
|
-
- Callback is called with `3` and logs: `"C = 3"`
|
|
57
|
-
|
|
58
|
-
### Creation Sequence Diagram
|
|
59
|
-
|
|
60
|
-
```mermaid
|
|
61
|
-
sequenceDiagram
|
|
62
|
-
participant User
|
|
63
|
-
participant $a as $a (State)
|
|
64
|
-
participant $b as $b (State)
|
|
65
|
-
participant $c as $c (Derivation)
|
|
66
|
-
participant Effect
|
|
67
|
-
participant Context
|
|
68
|
-
|
|
69
|
-
User->>$a: state(1)
|
|
70
|
-
activate $a
|
|
71
|
-
Note over $a: Store value: 1
|
|
72
|
-
deactivate $a
|
|
73
|
-
|
|
74
|
-
User->>$b: state(2)
|
|
75
|
-
activate $b
|
|
76
|
-
Note over $b: Store value: 2
|
|
77
|
-
deactivate $b
|
|
78
|
-
|
|
79
|
-
User->>$c: derivation((t) => ...)
|
|
80
|
-
activate $c
|
|
81
|
-
Note over $c: Store compute fn<br/>NOT executed yet<br/>(lazy)
|
|
82
|
-
deactivate $c
|
|
83
|
-
|
|
84
|
-
User->>Effect: $c.subscribe(...)
|
|
85
|
-
activate Effect
|
|
86
|
-
Note over Effect: Create & execute<br/>immediately
|
|
87
|
-
|
|
88
|
-
Effect->>$c: get(t)
|
|
89
|
-
activate $c
|
|
90
|
-
Note over $c: First access!<br/>Initialize & compute
|
|
91
|
-
|
|
92
|
-
$c->>$a: get(t)
|
|
93
|
-
activate $a
|
|
94
|
-
$a-->>$c: 1
|
|
95
|
-
$a->>$c: Register as listener
|
|
96
|
-
Note over $a,$c: $c now depends on $a
|
|
97
|
-
deactivate $a
|
|
98
|
-
|
|
99
|
-
$c->>$b: get(t)
|
|
100
|
-
activate $b
|
|
101
|
-
$b-->>$c: 2
|
|
102
|
-
$b->>$c: Register as listener
|
|
103
|
-
Note over $b,$c: $c now depends on $b
|
|
104
|
-
deactivate $b
|
|
105
|
-
|
|
106
|
-
Note over $c: Compute: 1 + 2 = 3
|
|
107
|
-
$c-->>Effect: 3
|
|
108
|
-
$c->>Effect: Register as effect
|
|
109
|
-
Note over $c,Effect: Effect depends on $c
|
|
110
|
-
deactivate $c
|
|
111
|
-
|
|
112
|
-
Note over Effect: Log: "C = 3"
|
|
113
|
-
deactivate Effect
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**Key Insights:**
|
|
117
|
-
- States are simple: just store the value
|
|
118
|
-
- Derivations are **lazy**: they don't compute until accessed
|
|
119
|
-
- Effects are **eager**: they execute immediately on creation
|
|
120
|
-
- Dependencies are registered **during execution** through `.get(t)`
|
|
121
|
-
|
|
122
|
-
## Update Flow
|
|
123
|
-
|
|
124
|
-
Now let's see what happens when we update `$a`:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
$a.set(5)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### The Push-Pull Model
|
|
131
|
-
|
|
132
|
-
PicoFlow uses a "**push notification, pull computation**" model:
|
|
133
|
-
|
|
134
|
-
- **Push**: When a value changes, notifications are pushed to all dependents immediately
|
|
135
|
-
- **Pull**: Actual recomputation only happens when a value is accessed (pulled)
|
|
136
|
-
|
|
137
|
-
This is efficient because:
|
|
138
|
-
- Derivations that aren't currently needed don't recompute
|
|
139
|
-
- Multiple state changes can batch before triggering computation
|
|
140
|
-
- Only the necessary chain of dependencies is recomputed
|
|
141
|
-
|
|
142
|
-
### Step-by-Step
|
|
143
|
-
|
|
144
|
-
1. **`$a.set(5)` is called**
|
|
145
|
-
- `$a` checks if the value changed: `5 !== 1` ✓
|
|
146
|
-
- `$a` updates its internal value: `1 → 5`
|
|
147
|
-
- `$a` notifies all its dependents
|
|
148
|
-
|
|
149
|
-
2. **`$a` notifies its dependents**
|
|
150
|
-
- `$a` sends notifications to all listeners (includes `$c`)
|
|
151
|
-
- `$c` receives the notification
|
|
152
|
-
|
|
153
|
-
3. **`$c` receives notification**
|
|
154
|
-
- `$c` marks itself as needing recomputation
|
|
155
|
-
- **Does NOT recompute yet!** (lazy evaluation)
|
|
156
|
-
- `$c` propagates the notification to its dependents
|
|
157
|
-
|
|
158
|
-
4. **`$c` notifies its dependents**
|
|
159
|
-
- `$c` sends notifications to all effects (includes our effect)
|
|
160
|
-
- The effect is scheduled to execute
|
|
161
|
-
|
|
162
|
-
5. **Effect executes**
|
|
163
|
-
- Effect runs its function with tracking context
|
|
164
|
-
- Calls `$c.get(t)`
|
|
165
|
-
|
|
166
|
-
6. **`$c` recomputes (pull)**
|
|
167
|
-
- `$c.get(t)` detects that recomputation is needed
|
|
168
|
-
- Clears old dependencies
|
|
169
|
-
- Runs compute function again
|
|
170
|
-
- Compute calls `$a.get(t)` → returns `5`
|
|
171
|
-
- Compute calls `$b.get(t)` → returns `2`
|
|
172
|
-
- Computes new value: `5 + 2 = 7`
|
|
173
|
-
- Re-registers dependencies
|
|
174
|
-
- Returns `7`
|
|
175
|
-
|
|
176
|
-
7. **Effect logs the result**
|
|
177
|
-
- Effect receives `7` from `$c.get(t)`
|
|
178
|
-
- Logs: `"C = 7"`
|
|
179
|
-
|
|
180
|
-
### Update Sequence Diagram
|
|
181
|
-
|
|
182
|
-
```mermaid
|
|
183
|
-
sequenceDiagram
|
|
184
|
-
participant User
|
|
185
|
-
participant $a as $a (State)
|
|
186
|
-
participant $c as $c (Derivation)
|
|
187
|
-
participant Effect
|
|
188
|
-
|
|
189
|
-
User->>$a: set(5)
|
|
190
|
-
activate $a
|
|
191
|
-
Note over $a: Check: 5 !== 1 ✓<br/>Update: 1 → 5
|
|
192
|
-
|
|
193
|
-
$a->>$c: notify
|
|
194
|
-
activate $c
|
|
195
|
-
Note over $c: Mark as needs<br/>recomputation<br/>NO recompute yet!
|
|
196
|
-
|
|
197
|
-
$c->>Effect: notify
|
|
198
|
-
deactivate $c
|
|
199
|
-
deactivate $a
|
|
200
|
-
|
|
201
|
-
activate Effect
|
|
202
|
-
Note over Effect: Execute function
|
|
203
|
-
|
|
204
|
-
Effect->>$c: get(t)
|
|
205
|
-
activate $c
|
|
206
|
-
Note over $c: Recomputation<br/>needed? YES
|
|
207
|
-
|
|
208
|
-
Note over $c: Recompute
|
|
209
|
-
activate $c
|
|
210
|
-
Note over $c: Clear old deps
|
|
211
|
-
|
|
212
|
-
$c->>$a: get(t)
|
|
213
|
-
activate $a
|
|
214
|
-
$a-->>$c: 5
|
|
215
|
-
deactivate $a
|
|
216
|
-
|
|
217
|
-
$c->>$b: get(t)
|
|
218
|
-
activate $b
|
|
219
|
-
$b-->>$c: 2
|
|
220
|
-
deactivate $b
|
|
221
|
-
|
|
222
|
-
Note over $c: Compute: 5 + 2 = 7<br/>Re-register deps
|
|
223
|
-
deactivate $c
|
|
224
|
-
|
|
225
|
-
$c-->>Effect: 7
|
|
226
|
-
deactivate $c
|
|
227
|
-
|
|
228
|
-
Note over Effect: Log: "C = 7"
|
|
229
|
-
deactivate Effect
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
**Key Insights:**
|
|
233
|
-
- **Notifications are pushed** immediately when state changes
|
|
234
|
-
- **Derivations mark as dirty** but don't recompute until needed
|
|
235
|
-
- **Effects execute immediately** when notified
|
|
236
|
-
- **Recomputation is pulled** when effects access derivations
|
|
237
|
-
- **Dependencies are re-registered** on each execution (dynamic tracking)
|
|
238
|
-
|
|
239
|
-
## Disposal Flow
|
|
240
|
-
|
|
241
|
-
Finally, let's see what happens when we dispose a primitive:
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
$a.dispose()
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Step-by-Step
|
|
248
|
-
|
|
249
|
-
1. **`$a.dispose()` is called**
|
|
250
|
-
- `$a` checks if already disposed (throws if yes)
|
|
251
|
-
- Iterates through all listeners (dependents like `$c`)
|
|
252
|
-
- Iterates through all effects (none in this case)
|
|
253
|
-
|
|
254
|
-
2. **Cleanup listeners**
|
|
255
|
-
- For each listener, calls `listener.dispose()`
|
|
256
|
-
- This triggers `$c.dispose()`
|
|
257
|
-
|
|
258
|
-
3. **`$c.dispose()` cascades**
|
|
259
|
-
- `$c` iterates through its listeners (none)
|
|
260
|
-
- `$c` iterates through its effects (includes our effect)
|
|
261
|
-
- Calls `effect.dispose()`
|
|
262
|
-
|
|
263
|
-
4. **Effect cleanup**
|
|
264
|
-
- Effect unregisters from all its dependencies (`$c`)
|
|
265
|
-
- Effect marks itself as disposed
|
|
266
|
-
- Future notifications won't trigger this effect
|
|
267
|
-
|
|
268
|
-
5. **State cleanup**
|
|
269
|
-
- `$a` unregisters from all its listeners
|
|
270
|
-
- `$a` marks itself as disposed
|
|
271
|
-
- Future attempts to use `$a` will throw errors
|
|
272
|
-
|
|
273
|
-
6. **Result**
|
|
274
|
-
- The entire dependency graph is cleaned up
|
|
275
|
-
- Memory is freed
|
|
276
|
-
- No more reactions will occur
|
|
277
|
-
|
|
278
|
-
### Disposal Sequence Diagram
|
|
279
|
-
|
|
280
|
-
```mermaid
|
|
281
|
-
sequenceDiagram
|
|
282
|
-
participant User
|
|
283
|
-
participant $a as $a (State)
|
|
284
|
-
participant $c as $c (Derivation)
|
|
285
|
-
participant Effect
|
|
286
|
-
|
|
287
|
-
User->>$a: dispose()
|
|
288
|
-
activate $a
|
|
289
|
-
Note over $a: Check if disposed<br/>Iterate listeners
|
|
290
|
-
|
|
291
|
-
$a->>$c: dispose()
|
|
292
|
-
activate $c
|
|
293
|
-
Note over $c: Iterate effects
|
|
294
|
-
|
|
295
|
-
$c->>Effect: dispose()
|
|
296
|
-
activate Effect
|
|
297
|
-
|
|
298
|
-
Effect->>$c: Unregister
|
|
299
|
-
Note over Effect,$c: Remove dependency
|
|
300
|
-
|
|
301
|
-
Note over Effect: Mark disposed<br/>Stop running
|
|
302
|
-
deactivate Effect
|
|
303
|
-
|
|
304
|
-
$c->>$a: Unregister
|
|
305
|
-
Note over $c,$a: Remove dependency
|
|
306
|
-
|
|
307
|
-
Note over $c: Mark disposed
|
|
308
|
-
deactivate $c
|
|
309
|
-
|
|
310
|
-
Note over $a: Mark disposed<br/>Clean up listeners
|
|
311
|
-
deactivate $a
|
|
312
|
-
|
|
313
|
-
Note over User,Effect: All cleaned up!<br/>Memory freed
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
**Key Insights:**
|
|
317
|
-
- **Disposal cascades** through the dependency graph
|
|
318
|
-
- **Dependents are disposed first** (effects before derivations before states)
|
|
319
|
-
- **All registrations are cleaned up** to prevent memory leaks
|
|
320
|
-
- **Disposed primitives throw errors** if you try to use them
|
|
321
|
-
- **Order matters**: dispose dependents before dependencies when doing manual cleanup
|
|
322
|
-
|
|
323
|
-
## Practical Implications
|
|
324
|
-
|
|
325
|
-
Understanding these flows helps you:
|
|
326
|
-
|
|
327
|
-
### 1. Optimize Performance
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
// Derivation won't compute if effect is disposed
|
|
331
|
-
const $expensive = derivation((t) => {
|
|
332
|
-
return expensiveCalculation($data.get(t))
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
const effect = $expensive.subscribe((value) => {
|
|
336
|
-
if (shouldDisplay) display(value)
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
// When not needed, just dispose the effect
|
|
340
|
-
effect.dispose() // $expensive stops computing
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### 2. Debug Dependency Issues
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
// If logs don't appear, check:
|
|
347
|
-
// 1. Is the effect created? (runs immediately)
|
|
348
|
-
// 2. Are you using .subscribe()? (not .pick())
|
|
349
|
-
// 3. Is anything disposed?
|
|
350
|
-
|
|
351
|
-
$state.subscribe((value) => {
|
|
352
|
-
console.log('Effect created') // Should log immediately
|
|
353
|
-
console.log('Value:', value)
|
|
354
|
-
})
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### 3. Avoid Memory Leaks
|
|
358
|
-
|
|
359
|
-
```typescript
|
|
360
|
-
// Always dispose effects when done
|
|
361
|
-
const disposables: FlowDisposable[] = []
|
|
362
|
-
|
|
363
|
-
disposables.push(subscribe((t) => { /* ... */ }, () => { /* ... */ }))
|
|
364
|
-
disposables.push(subscribe((t) => { /* ... */ }, () => { /* ... */ }))
|
|
365
|
-
|
|
366
|
-
// Clean up
|
|
367
|
-
disposables.forEach(d => d.dispose())
|
|
368
|
-
```
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
# Arrays
|
|
2
|
-
|
|
3
|
-
PicoFlow provides reactive arrays with **fine-grained tracking**. Instead of reacting to the entire array changing, you can react to specific operations like pushing, popping, or splicing individual items.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
## When to Use Arrays
|
|
7
|
-
|
|
8
|
-
Use arrays when you need to:
|
|
9
|
-
|
|
10
|
-
- ✅ Track large collections with fine-grained updates
|
|
11
|
-
- ✅ React to specific operations (additions, removals, updates) separately
|
|
12
|
-
- ✅ Optimize performance by avoiding full array re-processing
|
|
13
|
-
|
|
14
|
-
Don't use arrays when:
|
|
15
|
-
|
|
16
|
-
- ❌ You don't need fine-grained tracking (use `state` with an array)
|
|
17
|
-
- ❌ You need key-value operations (use `map` instead)
|
|
18
|
-
|
|
19
|
-
## Creating Arrays
|
|
20
|
-
|
|
21
|
-
Creating a reactive array is straightforward:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { array } from '@ersbeth/picoflow'
|
|
25
|
-
|
|
26
|
-
// Create with initial values
|
|
27
|
-
const $todos = array([
|
|
28
|
-
{ id: 1, text: 'Learn PicoFlow', done: false },
|
|
29
|
-
{ id: 2, text: 'Build app', done: false }
|
|
30
|
-
])
|
|
31
|
-
|
|
32
|
-
// Create empty
|
|
33
|
-
const $items = array<string>()
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Using Arrays
|
|
37
|
-
|
|
38
|
-
Arrays provide multiple ways to track changes and perform operations.
|
|
39
|
-
|
|
40
|
-
### Array Operations
|
|
41
|
-
|
|
42
|
-
Arrays provide several operations: `set()`, `update()`, `push()`, `pop()`, `unshift()`, `shift()`, `splice()`, and `clear()`.
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
const $list = array<number>([])
|
|
46
|
-
|
|
47
|
-
// Replace entire array
|
|
48
|
-
$list.set([1, 2, 3])
|
|
49
|
-
|
|
50
|
-
// Replace item at index
|
|
51
|
-
$list.update(0, 10)
|
|
52
|
-
|
|
53
|
-
// Add to end
|
|
54
|
-
$list.push(4)
|
|
55
|
-
|
|
56
|
-
// Add to start
|
|
57
|
-
$list.unshift(0)
|
|
58
|
-
|
|
59
|
-
// Remove from end
|
|
60
|
-
$list.pop()
|
|
61
|
-
|
|
62
|
-
// Remove from start
|
|
63
|
-
$list.shift()
|
|
64
|
-
|
|
65
|
-
// Splice (remove/add at index)
|
|
66
|
-
$list.splice(1, 1, 99) // Remove 1 item at index 1, add 99
|
|
67
|
-
|
|
68
|
-
// Clear all
|
|
69
|
-
$list.clear()
|
|
70
|
-
|
|
71
|
-
// Read (reactive)
|
|
72
|
-
$list.subscribe((items) => {
|
|
73
|
-
// Track entire array
|
|
74
|
-
console.log(items)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Read (non-reactive)
|
|
78
|
-
const snapshot = $list.pick()
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**Important**:
|
|
82
|
-
- `update(index, item)` throws an error if the index is out of bounds
|
|
83
|
-
- `push(item)` only accepts a single item
|
|
84
|
-
- `splice(start, deleteCount, ...items)` accepts multiple items to add
|
|
85
|
-
|
|
86
|
-
### Whole Array Tracking
|
|
87
|
-
|
|
88
|
-
Track when the entire array changes (any operation):
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const $items = array([1, 2, 3])
|
|
92
|
-
|
|
93
|
-
$items.subscribe((items) => {
|
|
94
|
-
console.log('Array changed:', items)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
$items.push(4) // Logs: "Array changed: [1,2,3,4]"
|
|
98
|
-
$items.pop() // Logs: "Array changed: [1,2,3]"
|
|
99
|
-
$items.splice(1, 1) // Logs: "Array changed: [1,3]"
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Use `.get(t)` when you need the entire array's current state. It returns a copy of the array.
|
|
103
|
-
|
|
104
|
-
### Fine-Grained Tracking
|
|
105
|
-
|
|
106
|
-
Track specific operations with `$lastAction`:
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
const $items = array<number>([])
|
|
110
|
-
|
|
111
|
-
$items.$lastAction.subscribe(
|
|
112
|
-
(action) => {
|
|
113
|
-
if (!action) return
|
|
114
|
-
|
|
115
|
-
switch (action.type) {
|
|
116
|
-
case 'push':
|
|
117
|
-
console.log('Pushed:', action.item)
|
|
118
|
-
break
|
|
119
|
-
case 'pop':
|
|
120
|
-
console.log('Popped')
|
|
121
|
-
break
|
|
122
|
-
case 'splice':
|
|
123
|
-
console.log('Splice at', action.start, ':', action.deleteCount, 'deleted,', action.items.length, 'added')
|
|
124
|
-
break
|
|
125
|
-
case 'update':
|
|
126
|
-
console.log('Set item at', action.index, 'to', action.item)
|
|
127
|
-
break
|
|
128
|
-
// ... other operations
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
$items.push(1) // Logs: "Pushed: 1"
|
|
134
|
-
$items.push(2) // Logs: "Pushed: 2"
|
|
135
|
-
$items.pop() // Logs: "Popped"
|
|
136
|
-
$items.splice(0, 1) // Logs: "Splice at 0: 1 deleted, 0 added"
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Lifecycle
|
|
140
|
-
|
|
141
|
-
When you create a subscription that tracks an array:
|
|
142
|
-
|
|
143
|
-
1. **Registration**: The array registers the subscription as a watcher
|
|
144
|
-
2. **Operation**: When you call `push()`, `pop()`, `splice()`, etc., the array updates internally
|
|
145
|
-
3. **Signal Update**: The `$lastAction` signal is updated with the operation details
|
|
146
|
-
4. **Notification**: All watching subscriptions are scheduled to run
|
|
147
|
-
5. **Re-execution**: Each watching subscription re-executes its function
|
|
148
|
-
|
|
149
|
-
```mermaid
|
|
150
|
-
sequenceDiagram
|
|
151
|
-
participant User
|
|
152
|
-
participant Array as $todos (Array)
|
|
153
|
-
participant Signal as $lastAction
|
|
154
|
-
participant Subscription
|
|
155
|
-
|
|
156
|
-
Note over User,Subscription: 1. Setup Phase
|
|
157
|
-
User->>Subscription: Create subscription
|
|
158
|
-
activate Subscription
|
|
159
|
-
Subscription->>Array: get(t)
|
|
160
|
-
Note over Array: Register Subscription as watcher
|
|
161
|
-
Subscription->>Signal: get(t)
|
|
162
|
-
Note over Signal: Register Subscription as watcher
|
|
163
|
-
Subscription->>Subscription: Execute function
|
|
164
|
-
Note over Subscription: Initial render
|
|
165
|
-
deactivate Subscription
|
|
166
|
-
|
|
167
|
-
Note over User,Subscription: 2. Operation Phase
|
|
168
|
-
User->>Array: push(todo)
|
|
169
|
-
activate Array
|
|
170
|
-
Note over Array: Update internal array
|
|
171
|
-
Array->>Signal: set({ type: 'push', item: todo })
|
|
172
|
-
activate Signal
|
|
173
|
-
Note over Signal: Notify watchers
|
|
174
|
-
Signal->>Subscription: Schedule execution
|
|
175
|
-
deactivate Signal
|
|
176
|
-
Array->>Array: Notify whole array watchers
|
|
177
|
-
Array->>Subscription: Schedule execution
|
|
178
|
-
deactivate Array
|
|
179
|
-
|
|
180
|
-
activate Subscription
|
|
181
|
-
Note over Subscription: Re-execute function
|
|
182
|
-
Subscription->>Signal: get(t)
|
|
183
|
-
Signal-->>Subscription: { type: 'push', item: todo }
|
|
184
|
-
Subscription->>Subscription: Handle new todo
|
|
185
|
-
Note over Subscription: Update UI
|
|
186
|
-
deactivate Subscription
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Best Practices
|
|
190
|
-
|
|
191
|
-
### Handle All Action Types
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
subscribe(
|
|
195
|
-
(t) => $items.$lastAction.get(t),
|
|
196
|
-
(action) => {
|
|
197
|
-
if (!action) return
|
|
198
|
-
|
|
199
|
-
switch (action.type) {
|
|
200
|
-
case 'push':
|
|
201
|
-
// Handle push
|
|
202
|
-
break
|
|
203
|
-
case 'pop':
|
|
204
|
-
// Handle pop
|
|
205
|
-
break
|
|
206
|
-
case 'splice':
|
|
207
|
-
// Handle splice
|
|
208
|
-
break
|
|
209
|
-
case 'update':
|
|
210
|
-
// Handle update
|
|
211
|
-
break
|
|
212
|
-
case 'clear':
|
|
213
|
-
// Handle clear
|
|
214
|
-
break
|
|
215
|
-
case 'unshift':
|
|
216
|
-
// Handle unshift
|
|
217
|
-
break
|
|
218
|
-
case 'shift':
|
|
219
|
-
// Handle shift
|
|
220
|
-
break
|
|
221
|
-
case 'set':
|
|
222
|
-
// Handle set (replace entire array)
|
|
223
|
-
break
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
)
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Dispose Items Properly
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
// When removing disposable items
|
|
233
|
-
subscribe(
|
|
234
|
-
(t) => $resources.$lastAction.get(t),
|
|
235
|
-
(action) => {
|
|
236
|
-
if (action && action.type === 'splice') {
|
|
237
|
-
// Dispose removed items
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
)
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## Common Pitfalls
|
|
244
|
-
|
|
245
|
-
### Using Wrong Tracking
|
|
246
|
-
|
|
247
|
-
**Problem**: Tracking the entire array when you only need specific operations.
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
// ❌ Tracks entire array when you only need additions
|
|
251
|
-
$items.subscribe((items) => {
|
|
252
|
-
const lastItem = items[items.length - 1]
|
|
253
|
-
animateNewItem(lastItem) // Runs on ALL changes
|
|
254
|
-
})
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
**Solution**: Track only what you need:
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
// ✅ Track only pushes
|
|
261
|
-
subscribe(
|
|
262
|
-
(t) => $items.$lastAction.get(t),
|
|
263
|
-
(action) => {
|
|
264
|
-
if (action && action.type === 'push') {
|
|
265
|
-
animateNewItem(action.item)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
)
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Mutating Retrieved Arrays
|
|
272
|
-
|
|
273
|
-
**Problem**: Mutating the array directly instead of using reactive methods.
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
// ❌ Mutating the array
|
|
277
|
-
const items = $items.pick()
|
|
278
|
-
items.push(newItem) // Doesn't update reactive array!
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
**Solution**: Use reactive methods:
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
// ✅ Use reactive methods
|
|
285
|
-
$items.push(newItem)
|
|
286
|
-
```
|