@ersbeth/picoflow 2.0.2 → 2.1.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/LICENSE +21 -0
- package/README.md +25 -0
- package/SKILL.md +106 -0
- package/dist/picoflow.js +204 -60
- package/dist/types/api/base/flowObservable.d.ts +3 -3
- package/dist/types/api/base/flowObservable.d.ts.map +1 -1
- package/dist/types/api/base/flowSubscribable.d.ts +2 -2
- package/dist/types/api/base/flowSubscribable.d.ts.map +1 -1
- package/dist/types/api/base/flowTracker.d.ts +1 -1
- package/dist/types/api/base/flowTracker.d.ts.map +1 -1
- package/dist/types/api/base/index.d.ts +4 -4
- package/dist/types/api/base/index.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +2 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts +1 -1
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts +3 -3
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowStateAsync.d.ts +3 -3
- package/dist/types/api/nodes/async/flowStateAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts +2 -2
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/index.d.ts +4 -4
- package/dist/types/api/nodes/async/index.d.ts.map +1 -1
- package/dist/types/api/nodes/collections/flowArray.d.ts +7 -4
- package/dist/types/api/nodes/collections/flowArray.d.ts.map +1 -1
- package/dist/types/api/nodes/collections/flowMap.d.ts +3 -2
- package/dist/types/api/nodes/collections/flowMap.d.ts.map +1 -1
- package/dist/types/api/nodes/collections/index.d.ts +2 -2
- package/dist/types/api/nodes/collections/index.d.ts.map +1 -1
- package/dist/types/api/nodes/flowEffect.d.ts +2 -2
- package/dist/types/api/nodes/flowEffect.d.ts.map +1 -1
- package/dist/types/api/nodes/flowSignal.d.ts +1 -1
- package/dist/types/api/nodes/flowSignal.d.ts.map +1 -1
- package/dist/types/api/nodes/flowValue.d.ts +2 -2
- package/dist/types/api/nodes/flowValue.d.ts.map +1 -1
- package/dist/types/api/nodes/index.d.ts +7 -7
- package/dist/types/api/nodes/index.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowConstant.d.ts +2 -2
- package/dist/types/api/nodes/sync/flowConstant.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowDerivation.d.ts +3 -3
- package/dist/types/api/nodes/sync/flowDerivation.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowState.d.ts +3 -3
- package/dist/types/api/nodes/sync/flowState.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts +2 -2
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/index.d.ts +4 -4
- package/dist/types/api/nodes/sync/index.d.ts.map +1 -1
- package/dist/types/api/nodes/utils.d.ts +1 -1
- package/dist/types/api/nodes/utils.d.ts.map +1 -1
- package/dist/types/base/disposable.d.ts +1 -1
- package/dist/types/base/disposable.d.ts.map +1 -1
- package/dist/types/base/executionStack.d.ts +22 -1
- package/dist/types/base/executionStack.d.ts.map +1 -1
- package/dist/types/base/index.d.ts +5 -5
- package/dist/types/base/index.d.ts.map +1 -1
- package/dist/types/base/node.d.ts +4 -4
- package/dist/types/base/node.d.ts.map +1 -1
- package/dist/types/base/observable.d.ts +3 -3
- package/dist/types/base/observable.d.ts.map +1 -1
- package/dist/types/base/observer.d.ts +2 -2
- package/dist/types/base/observer.d.ts.map +1 -1
- package/dist/types/converters/index.d.ts +1 -1
- package/dist/types/converters/index.d.ts.map +1 -1
- package/dist/types/converters/solid.d.ts +12 -8
- package/dist/types/converters/solid.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/nodes/actionNode.d.ts +23 -0
- package/dist/types/nodes/actionNode.d.ts.map +1 -0
- package/dist/types/nodes/arrayNode.d.ts +4 -3
- package/dist/types/nodes/arrayNode.d.ts.map +1 -1
- package/dist/types/nodes/effectNode.d.ts +18 -2
- package/dist/types/nodes/effectNode.d.ts.map +1 -1
- package/dist/types/nodes/index.d.ts +8 -7
- package/dist/types/nodes/index.d.ts.map +1 -1
- package/dist/types/nodes/mapNode.d.ts +4 -3
- package/dist/types/nodes/mapNode.d.ts.map +1 -1
- package/dist/types/nodes/signalNode.d.ts +2 -2
- package/dist/types/nodes/signalNode.d.ts.map +1 -1
- package/dist/types/nodes/valueAsyncNode.d.ts +3 -3
- package/dist/types/nodes/valueAsyncNode.d.ts.map +1 -1
- package/dist/types/nodes/valueNode.d.ts +3 -3
- package/dist/types/nodes/valueNode.d.ts.map +1 -1
- package/dist/types/nodes/valueSyncNode.d.ts +3 -3
- package/dist/types/nodes/valueSyncNode.d.ts.map +1 -1
- package/dist/types/schedulers/asyncScheduler.d.ts +2 -1
- package/dist/types/schedulers/asyncScheduler.d.ts.map +1 -1
- package/dist/types/schedulers/index.d.ts +4 -4
- package/dist/types/schedulers/index.d.ts.map +1 -1
- package/dist/types/schedulers/syncResolver.d.ts +1 -2
- package/dist/types/schedulers/syncResolver.d.ts.map +1 -1
- package/dist/types/schedulers/syncScheduler.d.ts +1 -1
- package/dist/types/schedulers/syncScheduler.d.ts.map +1 -1
- package/package.json +24 -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,207 +0,0 @@
|
|
|
1
|
-
# Constants
|
|
2
|
-
|
|
3
|
-
A **constant** is a reactive primitive that holds an immutable value computed lazily on first access. Constants integrate immutable values into the reactive system, allowing them to be tracked as dependencies while ensuring they never change.
|
|
4
|
-
|
|
5
|
-
The key benefit of constants is **lazy initialization** - the value isn't computed until it's first accessed, making them perfect for expensive one-time computations like parsing configuration files or building lookup tables.
|
|
6
|
-
|
|
7
|
-
## When to Use Constants
|
|
8
|
-
|
|
9
|
-
Use constants when you need to:
|
|
10
|
-
|
|
11
|
-
- ✅ Cache expensive computations that only run once
|
|
12
|
-
|
|
13
|
-
Don't use constants when:
|
|
14
|
-
|
|
15
|
-
- ❌ The value needs to change (use state instead)
|
|
16
|
-
- ❌ The value isn't reactive (use plain `const` instead)
|
|
17
|
-
|
|
18
|
-
## Creating Constants
|
|
19
|
-
|
|
20
|
-
Constants always take a function that returns the value. This function is called lazily on first access:
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { constant } from '@ersbeth/picoflow'
|
|
24
|
-
|
|
25
|
-
// Function is required - value computed on first access
|
|
26
|
-
const $apiUrl = constant(() => 'https://api.example.com')
|
|
27
|
-
const $config = constant(() => ({ apiUrl: 'https://api.example.com', timeout: 5000 }))
|
|
28
|
-
const $version = constant(() => '1.0.0')
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Expensive Computations
|
|
32
|
-
|
|
33
|
-
The lazy initialization makes constants perfect for expensive one-time computations:
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
const $expensiveValue = constant(() => {
|
|
37
|
-
console.log('Computing expensive value...')
|
|
38
|
-
return performExpensiveCalculation()
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// Nothing logged yet - function not called
|
|
42
|
-
|
|
43
|
-
$expensiveValue.subscribe((value) => {
|
|
44
|
-
// NOW it logs: "Computing expensive value..."
|
|
45
|
-
console.log(value)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// Subsequent accesses return the cached value (no recomputation)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Using Constants
|
|
52
|
-
|
|
53
|
-
Constants provide two ways to read values:
|
|
54
|
-
|
|
55
|
-
### Get
|
|
56
|
-
|
|
57
|
-
Inside a subscription or derivation, use `.get(t)` to read the value and create a dependency:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
import { constant } from '@ersbeth/picoflow'
|
|
61
|
-
|
|
62
|
-
const $config = constant(() => ({ apiUrl: 'https://api.example.com' }))
|
|
63
|
-
const $merged = derivation((t) => ({url: $config.get(t).apiUrl, data: someOtherData}))
|
|
64
|
-
|
|
65
|
-
$merged.subscribe((merged) => {
|
|
66
|
-
console.log('Merged data', merged)
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Pick
|
|
71
|
-
|
|
72
|
-
Use `.pick()` when you want the current value without creating a dependency:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
const $config = constant(() => ({ apiUrl: 'https://api.example.com' }))
|
|
76
|
-
|
|
77
|
-
// Read without tracking (e.g., outside a subscription or derivation)
|
|
78
|
-
const currentConfig = await $config.pick()
|
|
79
|
-
console.log(currentConfig.apiUrl)
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Dispose
|
|
84
|
-
|
|
85
|
-
Constants can be disposed to free resources:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
const $config = constant(() => ({ apiUrl: 'https://api.example.com' }))
|
|
89
|
-
|
|
90
|
-
// Later, clean up
|
|
91
|
-
$config.dispose()
|
|
92
|
-
|
|
93
|
-
// Subsequent operations will throw
|
|
94
|
-
$config.pick() // Error: Primitive is disposed
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Lifecycle
|
|
98
|
-
|
|
99
|
-
Constants use lazy initialization - the function you provide is executed only on first access:
|
|
100
|
-
|
|
101
|
-
```mermaid
|
|
102
|
-
sequenceDiagram
|
|
103
|
-
participant User
|
|
104
|
-
participant $config as $config (Constant)
|
|
105
|
-
participant InitFn as Initialization Function
|
|
106
|
-
|
|
107
|
-
Note over User,$config: 1. Creation Phase
|
|
108
|
-
User->>$config: constant(() => compute())
|
|
109
|
-
activate $config
|
|
110
|
-
Note over $config: Store function<br/>NOT called yet<br/>NOT initialized
|
|
111
|
-
deactivate $config
|
|
112
|
-
|
|
113
|
-
Note over User,InitFn: 2. First Access
|
|
114
|
-
User->>$config: get(t)
|
|
115
|
-
activate $config
|
|
116
|
-
Note over $config: Check: initialized?<br/>NO - call function
|
|
117
|
-
|
|
118
|
-
$config->>InitFn: Execute function
|
|
119
|
-
activate InitFn
|
|
120
|
-
Note over InitFn: Perform expensive<br/>calculation
|
|
121
|
-
InitFn-->>$config: Computed value
|
|
122
|
-
deactivate InitFn
|
|
123
|
-
|
|
124
|
-
Note over $config: Store value<br/>Mark as initialized
|
|
125
|
-
$config-->>User: Return value
|
|
126
|
-
deactivate $config
|
|
127
|
-
|
|
128
|
-
Note over User,$config: 3. Subsequent Access
|
|
129
|
-
User->>$config: get(t)
|
|
130
|
-
activate $config
|
|
131
|
-
Note over $config: Check: initialized?<br/>YES - return cached value
|
|
132
|
-
$config-->>User: Return cached value
|
|
133
|
-
deactivate $config
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
**How it works:**
|
|
137
|
-
|
|
138
|
-
1. **Creation**: Store the initializer function, mark as not initialized
|
|
139
|
-
|
|
140
|
-
2. **First access**: When `.get(t)` or `.pick()` is called
|
|
141
|
-
- Check if initialized
|
|
142
|
-
- If not, execute the function
|
|
143
|
-
- Cache result and mark as initialized
|
|
144
|
-
|
|
145
|
-
3. **Subsequent access**: Always return cached value (function never called again)
|
|
146
|
-
|
|
147
|
-
## Best Practices
|
|
148
|
-
|
|
149
|
-
### Use Constants for Expensive Computations
|
|
150
|
-
|
|
151
|
-
Constants defer expensive work until it's actually needed:
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
// ✅ Good - lazy evaluation with constant
|
|
155
|
-
const $parsedData = constant(() => JSON.parse(largeDataString))
|
|
156
|
-
|
|
157
|
-
// ❌ Eager - runs immediately (plain JavaScript)
|
|
158
|
-
const parsedData = JSON.parse(largeDataString)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Prefer Plain const for Non-Reactive Values
|
|
162
|
-
|
|
163
|
-
If no subscriptions or derivations will read it, use plain JavaScript:
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
// ❌ Unnecessary - no reactivity needed
|
|
167
|
-
const $localConfig = constant(() => ({ timeout: 5000 }))
|
|
168
|
-
function async fetch() {
|
|
169
|
-
const config = await $localConfig.pick() // Never tracked
|
|
170
|
-
return doFetch(config)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ✅ Better - plain const
|
|
174
|
-
const localConfig = { timeout: 5000 }
|
|
175
|
-
function fetch() {
|
|
176
|
-
return doFetch(localConfig)
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Common Pitfalls
|
|
181
|
-
|
|
182
|
-
### Lazy Init with Side Effects
|
|
183
|
-
|
|
184
|
-
**Problem**: Using lazy initialization with side effects.
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// ❌ Bad - side effects in lazy init
|
|
188
|
-
const $config = constant(() => {
|
|
189
|
-
console.log('Loading config...') // Side effect!
|
|
190
|
-
localStorage.setItem('loaded', 'true') // Side effect!
|
|
191
|
-
return loadConfig()
|
|
192
|
-
})
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
**Solution**: Keep lazy init pure, handle side effects elsewhere:
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
// ✅ Better - pure initialization
|
|
199
|
-
const $config = constant(() => loadConfig())
|
|
200
|
-
|
|
201
|
-
// Handle side effects separately
|
|
202
|
-
$config.subscribe((config) => {
|
|
203
|
-
console.log('Config loaded:', config)
|
|
204
|
-
localStorage.setItem('loaded', 'true')
|
|
205
|
-
})
|
|
206
|
-
```
|
|
207
|
-
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# Derivations
|
|
2
|
-
|
|
3
|
-
Derivations are **reactive formulas** that compute values based on other reactive primitives. They're pure functions that track their dependencies and efficiently recompute when needed.
|
|
4
|
-
|
|
5
|
-
### Key Characteristics
|
|
6
|
-
|
|
7
|
-
- **Lazy evaluation**: Computes only when accessed, not when created
|
|
8
|
-
- **Automatic caching**: Caches results until dependencies change
|
|
9
|
-
|
|
10
|
-
## When to Use Derivations
|
|
11
|
-
|
|
12
|
-
Use derivations when you need to:
|
|
13
|
-
|
|
14
|
-
- ✅ Compute values from other reactive primitives
|
|
15
|
-
- ✅ Transform or filter data reactively
|
|
16
|
-
- ✅ Build complex reactive data flows
|
|
17
|
-
|
|
18
|
-
Don't use derivations when:
|
|
19
|
-
|
|
20
|
-
- ❌ You need to perform side effects (use subscribe instead)
|
|
21
|
-
|
|
22
|
-
::: tip
|
|
23
|
-
Not sure whether to use a derivation or a side effect? See the [Derivations vs Side Effects](./overview.md#derivations-vs-side-effects) section for a detailed comparison and guidance.
|
|
24
|
-
:::
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## Creating Derivations
|
|
28
|
-
|
|
29
|
-
Creating a derivation is straightforward:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { derivation } from '@ersbeth/picoflow'
|
|
33
|
-
|
|
34
|
-
const $count = state(1);
|
|
35
|
-
|
|
36
|
-
const $doubled = derivation((t) => {
|
|
37
|
-
// Your computation here
|
|
38
|
-
return $count.get(t) * 2
|
|
39
|
-
})
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
TypeScript automatically infers the return type based on what your function returns.
|
|
43
|
-
|
|
44
|
-
## Using Derivations
|
|
45
|
-
|
|
46
|
-
Derivations have several methods for reading values and managing lifecycle.
|
|
47
|
-
|
|
48
|
-
### Get
|
|
49
|
-
|
|
50
|
-
Inside a subscription or another derivation, use `.get(t)` to read the value and track the dependency:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
const $count = state(10)
|
|
54
|
-
const $doubled = derivation((t) => $count.get(t) * 2)
|
|
55
|
-
const $result = derivation(t)=> $doubled.get(t) + 2)
|
|
56
|
-
|
|
57
|
-
$result.subscribe((value) => {
|
|
58
|
-
console.log('Result:', value)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
$count.set(20) // Logs: "Result: 42"
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Pick
|
|
65
|
-
|
|
66
|
-
Use `.pick()` to read the current value without tracking:
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
const $count = state(10)
|
|
70
|
-
const $doubled = derivation((t) => $count.get(t) * 2)
|
|
71
|
-
|
|
72
|
-
// Read once without tracking
|
|
73
|
-
const snapshot = $doubled.pick()
|
|
74
|
-
console.log(snapshot) // 20
|
|
75
|
-
|
|
76
|
-
$count.set(20)
|
|
77
|
-
// No reaction because we used .pick()
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Dispose
|
|
81
|
-
|
|
82
|
-
Derivations can be disposed to free resources and prevent memory leaks:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
const $doubled = derivation((t) => $count.get(t) * 2)
|
|
86
|
-
|
|
87
|
-
// Use the derivation
|
|
88
|
-
$doubled.subscribe((value) => {
|
|
89
|
-
console.log(value)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// Later, clean up
|
|
93
|
-
$doubled.dispose()
|
|
94
|
-
|
|
95
|
-
// Subsequent operations will throw
|
|
96
|
-
await $doubled.pick() // Error: Primitive is disposed
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Chaining Derivations
|
|
100
|
-
|
|
101
|
-
Derivations can depend on other derivations, creating chains of reactive computations:
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
const $items = state([10, 20, 30])
|
|
105
|
-
|
|
106
|
-
// First derivation: sum
|
|
107
|
-
const $sum = derivation((t) => {
|
|
108
|
-
return $items.get(t).reduce((a, b) => a + b, 0)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Second derivation: average (depends on $sum)
|
|
112
|
-
const $average = derivation((t) => {
|
|
113
|
-
const sum = $sum.get(t)
|
|
114
|
-
const count = $items.get(t).length
|
|
115
|
-
return sum / count
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// Third derivation: formatted (depends on $average)
|
|
119
|
-
const $formatted = derivation((t) => {
|
|
120
|
-
const avg = $average.get(t)
|
|
121
|
-
return `Average: ${avg.toFixed(2)}`
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
$formatted.subscribe((value) => {
|
|
125
|
-
console.log(value)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
$items.set([15, 25, 35]) // All derivations update in sequence
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
```mermaid
|
|
132
|
-
graph LR
|
|
133
|
-
A[$items] --> B[$sum]
|
|
134
|
-
B --> C[$average]
|
|
135
|
-
A -.-> C
|
|
136
|
-
C --> D[$formatted]
|
|
137
|
-
D --> E[Subscription]
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## Lifecycle
|
|
143
|
-
|
|
144
|
-
Derivations use a lazy, cached evaluation model with dirty checking. Understanding this lifecycle is key to writing efficient reactive code.
|
|
145
|
-
|
|
146
|
-
When you create a derivation, it doesn't execute immediately. It waits until someone needs its value. Once computed, the value is cached. When dependencies change, the derivation is marked as "dirty" but doesn't recompute until accessed again.
|
|
147
|
-
|
|
148
|
-
```mermaid
|
|
149
|
-
sequenceDiagram
|
|
150
|
-
participant User
|
|
151
|
-
participant $count as $count (State)
|
|
152
|
-
participant $doubled as $doubled (Derivation)
|
|
153
|
-
participant Subscription
|
|
154
|
-
|
|
155
|
-
Note over User,$doubled: 1. Creation Phase
|
|
156
|
-
User->>$doubled: Create derivation
|
|
157
|
-
Note over $doubled: Store function<br/>NOT executed yet
|
|
158
|
-
|
|
159
|
-
Note over User,Subscription: 2. First Access
|
|
160
|
-
User->>Subscription: Create subscription
|
|
161
|
-
activate Subscription
|
|
162
|
-
Subscription->>$doubled: get(t)
|
|
163
|
-
activate $doubled
|
|
164
|
-
Note over $doubled: Not computed yet<br/>Need to compute!
|
|
165
|
-
|
|
166
|
-
$doubled->>$count: get(t)
|
|
167
|
-
activate $count
|
|
168
|
-
Note over $count: Register $doubled as dependent
|
|
169
|
-
$count-->>$doubled: 0
|
|
170
|
-
deactivate $count
|
|
171
|
-
|
|
172
|
-
Note over $doubled: Compute: 0 * 2 = 0<br/>Cache result
|
|
173
|
-
$doubled-->>Subscription: 0
|
|
174
|
-
deactivate $doubled
|
|
175
|
-
Note over Subscription: Log: "Doubled: 0"
|
|
176
|
-
deactivate Subscription
|
|
177
|
-
|
|
178
|
-
Note over User,Subscription: 3. Dependency Change
|
|
179
|
-
User->>$count: set(5)
|
|
180
|
-
activate $count
|
|
181
|
-
Note over $count: Value changed
|
|
182
|
-
|
|
183
|
-
$count->>$doubled: Notify (mark dirty)
|
|
184
|
-
activate $doubled
|
|
185
|
-
Note over $doubled: Mark as dirty<br/>NO recompute yet!
|
|
186
|
-
deactivate $doubled
|
|
187
|
-
|
|
188
|
-
$count->>Subscription: Schedule execution
|
|
189
|
-
deactivate $count
|
|
190
|
-
|
|
191
|
-
Note over User,Subscription: 4. Recompute on Access
|
|
192
|
-
activate Subscription
|
|
193
|
-
Subscription->>$doubled: get(t)
|
|
194
|
-
activate $doubled
|
|
195
|
-
Note over $doubled: Dirty? YES<br/>Recompute now!
|
|
196
|
-
|
|
197
|
-
$doubled->>$count: get(t)
|
|
198
|
-
activate $count
|
|
199
|
-
$count-->>$doubled: 5
|
|
200
|
-
deactivate $count
|
|
201
|
-
|
|
202
|
-
Note over $doubled: Compute: 5 * 2 = 10<br/>Cache new result
|
|
203
|
-
$doubled-->>Subscription: 10
|
|
204
|
-
deactivate $doubled
|
|
205
|
-
Note over Subscription: Log: "Doubled: 10"
|
|
206
|
-
deactivate Subscription
|
|
207
|
-
|
|
208
|
-
Note over User,Subscription: 5. Cached Access
|
|
209
|
-
User->>$doubled: pick()
|
|
210
|
-
activate $doubled
|
|
211
|
-
Note over $doubled: Not dirty<br/>Return cache
|
|
212
|
-
$doubled-->>User: 10
|
|
213
|
-
deactivate $doubled
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Key Lifecycle Points
|
|
217
|
-
|
|
218
|
-
1. **Creation**: The derivation function is stored but not executed
|
|
219
|
-
2. **First Access**: Computes the value, tracks dependencies, and caches the result
|
|
220
|
-
3. **Dependency Change**: Marked as "dirty" but doesn't recompute immediately
|
|
221
|
-
4. **Dirty Access**: Recomputes because it's dirty, caches new result
|
|
222
|
-
5. **Clean Access**: Returns cached value without recomputation
|
|
223
|
-
|
|
224
|
-
This lazy + cached approach means:
|
|
225
|
-
- **Unused derivations never compute** (no wasted CPU)
|
|
226
|
-
- **Multiple reads use the cache** (efficient)
|
|
227
|
-
- **Multiple dependency changes = one recompute** (optimized)
|
|
228
|
-
|
|
229
|
-
## Common Pitfalls
|
|
230
|
-
|
|
231
|
-
### Side Effects in Derivations
|
|
232
|
-
|
|
233
|
-
**Problem**: Adding side effects (logging, API calls, DOM updates) in a derivation.
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
// ❌ Wrong - side effect in derivation
|
|
237
|
-
const $count = state(0)
|
|
238
|
-
const $logged = derivation((t) => {
|
|
239
|
-
const value = $count.get(t)
|
|
240
|
-
console.log('Count:', value) // Side effect!
|
|
241
|
-
return value * 2
|
|
242
|
-
})
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
**Solution**: Move side effects to a subscription:
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
// ✅ Correct - side effect in subscription
|
|
249
|
-
const $count = state(0)
|
|
250
|
-
|
|
251
|
-
$count.subscribe((value) => {
|
|
252
|
-
console.log('Count:', value) // Side effect here is fine
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
const $doubled = derivation((t) => {
|
|
256
|
-
return $count.get(t) * 2 // Pure computation
|
|
257
|
-
})
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Mutating Returned Values
|
|
261
|
-
|
|
262
|
-
**Problem**: Mutating arrays or objects returned from derivations doesn't trigger the reactivity system.
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
// ❌ Mutating the array
|
|
266
|
-
const $items = state([1, 2, 3])
|
|
267
|
-
const $doubled = derivation((t) => {
|
|
268
|
-
const items = $items.get(t)
|
|
269
|
-
items.forEach((v, i) => items[i] = v * 2) // Mutation!
|
|
270
|
-
return items
|
|
271
|
-
})
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Solution**: Always return new values:
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
// ✅ Return new array
|
|
278
|
-
const $doubled = derivation((t) => {
|
|
279
|
-
return $items.get(t).map(v => v * 2)
|
|
280
|
-
})
|
|
281
|
-
```
|