@ersbeth/picoflow 0.2.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/plans/update-js-e795d61b.plan.md +567 -0
- package/.gitlab-ci.yml +24 -0
- package/.vscode/settings.json +3 -3
- package/CHANGELOG.md +51 -0
- package/IMPLEMENTATION_GUIDE.md +1578 -0
- package/README.md +62 -25
- package/biome.json +32 -32
- package/dist/picoflow.js +610 -436
- package/dist/types/advanced/array.d.ts +0 -6
- package/dist/types/advanced/array.d.ts.map +1 -1
- package/dist/types/advanced/index.d.ts +5 -5
- package/dist/types/advanced/index.d.ts.map +1 -1
- package/dist/types/advanced/map.d.ts +114 -23
- package/dist/types/advanced/map.d.ts.map +1 -1
- package/dist/types/advanced/resource.d.ts +51 -12
- package/dist/types/advanced/resource.d.ts.map +1 -1
- package/dist/types/advanced/resourceAsync.d.ts +28 -13
- package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
- package/dist/types/advanced/stream.d.ts +74 -16
- package/dist/types/advanced/stream.d.ts.map +1 -1
- package/dist/types/advanced/streamAsync.d.ts +69 -15
- package/dist/types/advanced/streamAsync.d.ts.map +1 -1
- package/dist/types/basic/constant.d.ts +44 -16
- package/dist/types/basic/constant.d.ts.map +1 -1
- package/dist/types/basic/derivation.d.ts +73 -24
- package/dist/types/basic/derivation.d.ts.map +1 -1
- package/dist/types/basic/disposable.d.ts +65 -6
- package/dist/types/basic/disposable.d.ts.map +1 -1
- package/dist/types/basic/effect.d.ts +27 -16
- package/dist/types/basic/effect.d.ts.map +1 -1
- package/dist/types/basic/index.d.ts +7 -8
- package/dist/types/basic/index.d.ts.map +1 -1
- package/dist/types/basic/observable.d.ts +62 -13
- package/dist/types/basic/observable.d.ts.map +1 -1
- package/dist/types/basic/signal.d.ts +35 -6
- package/dist/types/basic/signal.d.ts.map +1 -1
- package/dist/types/basic/state.d.ts +25 -4
- package/dist/types/basic/state.d.ts.map +1 -1
- package/dist/types/basic/trackingContext.d.ts +33 -0
- package/dist/types/basic/trackingContext.d.ts.map +1 -0
- package/dist/types/creators.d.ts +271 -26
- package/dist/types/creators.d.ts.map +1 -1
- package/dist/types/index.d.ts +60 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +5 -5
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/index.d.ts +2 -2
- package/dist/types/solid/index.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +96 -4
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +110 -0
- package/docs/api/classes/FlowArray.md +489 -0
- package/docs/api/classes/FlowConstant.md +350 -0
- package/docs/api/classes/FlowDerivation.md +334 -0
- package/docs/api/classes/FlowEffect.md +100 -0
- package/docs/api/classes/FlowMap.md +512 -0
- package/docs/api/classes/FlowObservable.md +306 -0
- package/docs/api/classes/FlowResource.md +380 -0
- package/docs/api/classes/FlowResourceAsync.md +362 -0
- package/docs/api/classes/FlowSignal.md +160 -0
- package/docs/api/classes/FlowState.md +368 -0
- package/docs/api/classes/FlowStream.md +367 -0
- package/docs/api/classes/FlowStreamAsync.md +364 -0
- package/docs/api/classes/SolidDerivation.md +75 -0
- package/docs/api/classes/SolidResource.md +91 -0
- package/docs/api/classes/SolidState.md +71 -0
- package/docs/api/classes/TrackingContext.md +33 -0
- package/docs/api/functions/array.md +58 -0
- package/docs/api/functions/constant.md +45 -0
- package/docs/api/functions/derivation.md +53 -0
- package/docs/api/functions/effect.md +49 -0
- package/docs/api/functions/from.md +220 -0
- package/docs/api/functions/isDisposable.md +49 -0
- package/docs/api/functions/map.md +57 -0
- package/docs/api/functions/resource.md +52 -0
- package/docs/api/functions/resourceAsync.md +50 -0
- package/docs/api/functions/signal.md +36 -0
- package/docs/api/functions/state.md +47 -0
- package/docs/api/functions/stream.md +53 -0
- package/docs/api/functions/streamAsync.md +50 -0
- package/docs/api/index.md +118 -0
- package/docs/api/interfaces/FlowDisposable.md +65 -0
- package/docs/api/interfaces/SolidObservable.md +19 -0
- package/docs/api/type-aliases/FlowArrayAction.md +49 -0
- package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
- package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
- package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
- package/docs/api/type-aliases/NotPromise.md +18 -0
- package/docs/api/type-aliases/SolidGetter.md +17 -0
- package/docs/api/typedoc-sidebar.json +1 -0
- package/docs/examples/examples.md +2313 -0
- package/docs/examples/patterns.md +649 -0
- package/docs/guide/advanced/disposal.md +426 -0
- package/docs/guide/advanced/solidjs.md +221 -0
- package/docs/guide/advanced/upgrading.md +464 -0
- package/docs/guide/introduction/concepts.md +56 -0
- package/docs/guide/introduction/conventions.md +61 -0
- package/docs/guide/introduction/getting-started.md +134 -0
- package/docs/guide/introduction/lifecycle.md +371 -0
- package/docs/guide/primitives/array.md +400 -0
- package/docs/guide/primitives/constant.md +380 -0
- package/docs/guide/primitives/derivations.md +348 -0
- package/docs/guide/primitives/effects.md +458 -0
- package/docs/guide/primitives/map.md +387 -0
- package/docs/guide/primitives/overview.md +175 -0
- package/docs/guide/primitives/resources.md +858 -0
- package/docs/guide/primitives/signal.md +259 -0
- package/docs/guide/primitives/state.md +368 -0
- package/docs/guide/primitives/streams.md +931 -0
- package/docs/index.md +47 -0
- package/docs/public/logo.svg +1 -0
- package/package.json +57 -41
- package/src/advanced/array.ts +208 -210
- package/src/advanced/index.ts +7 -7
- package/src/advanced/map.ts +178 -68
- package/src/advanced/resource.ts +87 -43
- package/src/advanced/resourceAsync.ts +62 -42
- package/src/advanced/stream.ts +113 -50
- package/src/advanced/streamAsync.ts +120 -61
- package/src/basic/constant.ts +82 -49
- package/src/basic/derivation.ts +128 -84
- package/src/basic/disposable.ts +74 -15
- package/src/basic/effect.ts +85 -77
- package/src/basic/index.ts +7 -8
- package/src/basic/observable.ts +94 -36
- package/src/basic/signal.ts +133 -105
- package/src/basic/state.ts +46 -25
- package/src/basic/trackingContext.ts +45 -0
- package/src/creators.ts +297 -54
- package/src/index.ts +96 -43
- package/src/solid/converters.ts +186 -67
- package/src/solid/index.ts +8 -2
- package/src/solid/primitives.ts +167 -65
- package/test/array.test.ts +592 -612
- package/test/constant.test.ts +31 -33
- package/test/derivation.test.ts +531 -536
- package/test/effect.test.ts +21 -21
- package/test/map.test.ts +233 -137
- package/test/resource.test.ts +119 -121
- package/test/resourceAsync.test.ts +98 -100
- package/test/signal.test.ts +51 -55
- package/test/state.test.ts +186 -168
- package/test/stream.test.ts +189 -189
- package/test/streamAsync.test.ts +186 -186
- package/tsconfig.json +19 -18
- package/typedoc.json +37 -0
- package/vite.config.ts +23 -23
- package/vitest.config.ts +7 -7
- package/api/doc/index.md +0 -31
- package/api/doc/picoflow.array.md +0 -55
- package/api/doc/picoflow.constant.md +0 -55
- package/api/doc/picoflow.derivation.md +0 -55
- package/api/doc/picoflow.effect.md +0 -55
- package/api/doc/picoflow.flowarray._constructor_.md +0 -49
- package/api/doc/picoflow.flowarray._lastaction.md +0 -13
- package/api/doc/picoflow.flowarray.clear.md +0 -17
- package/api/doc/picoflow.flowarray.dispose.md +0 -55
- package/api/doc/picoflow.flowarray.get.md +0 -19
- package/api/doc/picoflow.flowarray.length.md +0 -13
- package/api/doc/picoflow.flowarray.md +0 -273
- package/api/doc/picoflow.flowarray.pop.md +0 -17
- package/api/doc/picoflow.flowarray.push.md +0 -53
- package/api/doc/picoflow.flowarray.set.md +0 -53
- package/api/doc/picoflow.flowarray.setitem.md +0 -69
- package/api/doc/picoflow.flowarray.shift.md +0 -17
- package/api/doc/picoflow.flowarray.splice.md +0 -85
- package/api/doc/picoflow.flowarray.unshift.md +0 -53
- package/api/doc/picoflow.flowarrayaction.md +0 -37
- package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
- package/api/doc/picoflow.flowconstant.get.md +0 -25
- package/api/doc/picoflow.flowconstant.md +0 -88
- package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
- package/api/doc/picoflow.flowderivation.get.md +0 -23
- package/api/doc/picoflow.flowderivation.md +0 -86
- package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
- package/api/doc/picoflow.flowdisposable.md +0 -43
- package/api/doc/picoflow.floweffect._constructor_.md +0 -54
- package/api/doc/picoflow.floweffect.dispose.md +0 -21
- package/api/doc/picoflow.floweffect.disposed.md +0 -13
- package/api/doc/picoflow.floweffect.md +0 -131
- package/api/doc/picoflow.flowgetter.md +0 -15
- package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
- package/api/doc/picoflow.flowmap._lastset.md +0 -21
- package/api/doc/picoflow.flowmap.delete.md +0 -61
- package/api/doc/picoflow.flowmap.md +0 -133
- package/api/doc/picoflow.flowmap.setat.md +0 -77
- package/api/doc/picoflow.flowobservable.get.md +0 -19
- package/api/doc/picoflow.flowobservable.md +0 -68
- package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
- package/api/doc/picoflow.flowresource._constructor_.md +0 -49
- package/api/doc/picoflow.flowresource.fetch.md +0 -27
- package/api/doc/picoflow.flowresource.get.md +0 -23
- package/api/doc/picoflow.flowresource.md +0 -100
- package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
- package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
- package/api/doc/picoflow.flowresourceasync.get.md +0 -23
- package/api/doc/picoflow.flowresourceasync.md +0 -100
- package/api/doc/picoflow.flowsignal.dispose.md +0 -59
- package/api/doc/picoflow.flowsignal.disposed.md +0 -18
- package/api/doc/picoflow.flowsignal.md +0 -112
- package/api/doc/picoflow.flowsignal.trigger.md +0 -21
- package/api/doc/picoflow.flowstate.md +0 -52
- package/api/doc/picoflow.flowstate.set.md +0 -61
- package/api/doc/picoflow.flowstream._constructor_.md +0 -49
- package/api/doc/picoflow.flowstream.dispose.md +0 -21
- package/api/doc/picoflow.flowstream.get.md +0 -23
- package/api/doc/picoflow.flowstream.md +0 -100
- package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
- package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
- package/api/doc/picoflow.flowstreamasync.get.md +0 -23
- package/api/doc/picoflow.flowstreamasync.md +0 -100
- package/api/doc/picoflow.flowstreamdisposer.md +0 -13
- package/api/doc/picoflow.flowstreamsetter.md +0 -13
- package/api/doc/picoflow.flowstreamupdater.md +0 -19
- package/api/doc/picoflow.flowwatcher.md +0 -15
- package/api/doc/picoflow.from.md +0 -55
- package/api/doc/picoflow.from_1.md +0 -55
- package/api/doc/picoflow.from_2.md +0 -55
- package/api/doc/picoflow.from_3.md +0 -55
- package/api/doc/picoflow.from_4.md +0 -55
- package/api/doc/picoflow.from_5.md +0 -55
- package/api/doc/picoflow.isdisposable.md +0 -55
- package/api/doc/picoflow.map.md +0 -59
- package/api/doc/picoflow.md +0 -544
- package/api/doc/picoflow.resource.md +0 -55
- package/api/doc/picoflow.resourceasync.md +0 -55
- package/api/doc/picoflow.signal.md +0 -19
- package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
- package/api/doc/picoflow.solidderivation.get.md +0 -13
- package/api/doc/picoflow.solidderivation.md +0 -94
- package/api/doc/picoflow.solidgetter.md +0 -13
- package/api/doc/picoflow.solidobservable.get.md +0 -13
- package/api/doc/picoflow.solidobservable.md +0 -57
- package/api/doc/picoflow.solidresource._constructor_.md +0 -49
- package/api/doc/picoflow.solidresource.get.md +0 -13
- package/api/doc/picoflow.solidresource.latest.md +0 -13
- package/api/doc/picoflow.solidresource.md +0 -157
- package/api/doc/picoflow.solidresource.refetch.md +0 -13
- package/api/doc/picoflow.solidresource.state.md +0 -13
- package/api/doc/picoflow.solidstate._constructor_.md +0 -49
- package/api/doc/picoflow.solidstate.get.md +0 -13
- package/api/doc/picoflow.solidstate.md +0 -115
- package/api/doc/picoflow.solidstate.set.md +0 -13
- package/api/doc/picoflow.state.md +0 -55
- package/api/doc/picoflow.stream.md +0 -55
- package/api/doc/picoflow.streamasync.md +0 -55
- package/api/picoflow.public.api.md +0 -244
- package/api-extractor.json +0 -61
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# Maps
|
|
2
|
+
|
|
3
|
+
PicoFlow provides reactive maps with **fine-grained tracking**. Instead of reacting to the entire map changing, you can react to specific operations like adding, updating, or deleting individual entries.
|
|
4
|
+
|
|
5
|
+
Imagine you have a map of 1000 users. In a naive reactive system, every time you add, remove, or update a single user, the entire map re-renders. **Inefficient!** With PicoFlow's reactive maps, you can track specific operations and only update what changed.
|
|
6
|
+
|
|
7
|
+
### Key Characteristics
|
|
8
|
+
|
|
9
|
+
- **Fine-grained reactivity**: Track individual operations (add, update, delete) separately
|
|
10
|
+
- **Multiple tracking levels**: Track the whole map or specific operations
|
|
11
|
+
- **Native Map**: Uses JavaScript's native `Map<K, V>` internally
|
|
12
|
+
- **Operation-specific signals**: `$lastAdded`, `$lastUpdated`, `$lastDeleted` for granular tracking
|
|
13
|
+
- **Disposable**: Can be disposed to clean up resources
|
|
14
|
+
|
|
15
|
+
## When to Use Maps
|
|
16
|
+
|
|
17
|
+
Use maps when you need to:
|
|
18
|
+
|
|
19
|
+
- ✅ Track large collections with fine-grained updates
|
|
20
|
+
- ✅ React to specific operations (additions, updates, deletions) separately
|
|
21
|
+
- ✅ Manage key-value pairs where individual key operations matter
|
|
22
|
+
- ✅ Optimize performance by avoiding full map re-processing
|
|
23
|
+
- ✅ Animate or handle UI updates for specific changes
|
|
24
|
+
|
|
25
|
+
Don't use maps when:
|
|
26
|
+
|
|
27
|
+
- ❌ You have a small, simple object (use `state` instead)
|
|
28
|
+
- ❌ You don't need fine-grained tracking (use `state` with a Record)
|
|
29
|
+
- ❌ You need array-like operations (use `array` instead)
|
|
30
|
+
- ❌ The collection is rarely modified (regular state might be simpler)
|
|
31
|
+
|
|
32
|
+
## Creating Maps
|
|
33
|
+
|
|
34
|
+
Creating a reactive map is straightforward:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { map } from '@ersbeth/picoflow'
|
|
38
|
+
|
|
39
|
+
// Create with initial values (key is user id, value is User object)
|
|
40
|
+
const $usersByID = map<string, User>({
|
|
41
|
+
aliceID: { name: 'Alice', email: 'alice@example.com', role: 'admin' },
|
|
42
|
+
bobID: { name: 'Bob', email: 'bob@example.com', role: 'user' }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Create empty
|
|
46
|
+
const $cache = map<string, Data>()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The initial values (if provided) are converted to a native JavaScript `Map`.
|
|
50
|
+
|
|
51
|
+
## Using Maps
|
|
52
|
+
|
|
53
|
+
Maps provide multiple ways to track changes and perform operations.
|
|
54
|
+
|
|
55
|
+
### Map Operations
|
|
56
|
+
|
|
57
|
+
Maps provide three main operations: `add()`, `update()`, and `delete()`.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const $cache = map<string, Data>()
|
|
61
|
+
|
|
62
|
+
// Add a new key-value pair
|
|
63
|
+
$cache.add('user:1', userData)
|
|
64
|
+
|
|
65
|
+
// Update an existing key-value pair
|
|
66
|
+
$cache.update('user:1', updatedUserData)
|
|
67
|
+
|
|
68
|
+
// Delete a key-value pair
|
|
69
|
+
$cache.delete('user:1')
|
|
70
|
+
|
|
71
|
+
// Read (reactive)
|
|
72
|
+
effect((t) => {
|
|
73
|
+
const data = $cache.get(t)
|
|
74
|
+
// Track entire map - returns Map<string, Data>
|
|
75
|
+
const hasUser = data.has('user:1')
|
|
76
|
+
if (hasUser) {
|
|
77
|
+
const user = data.get('user:1')
|
|
78
|
+
// Use the user data
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Read (non-reactive)
|
|
83
|
+
const snapshot = $cache.pick() // Returns Map<string, Data>
|
|
84
|
+
const user = snapshot.get('user:1')
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Important**:
|
|
88
|
+
- `add(key, value)` throws an error if the key already exists
|
|
89
|
+
- `update(key, value)` throws an error if the key doesn't exist
|
|
90
|
+
- `delete(key)` throws an error if the key doesn't exist
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
### Whole Map Tracking with `.get(t)`
|
|
94
|
+
|
|
95
|
+
Track when the entire map changes (any operation):
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { map, effect } from '@ersbeth/picoflow'
|
|
99
|
+
|
|
100
|
+
const $items = map<string, number>()
|
|
101
|
+
|
|
102
|
+
// Initialize with values
|
|
103
|
+
$items.add('a', 1)
|
|
104
|
+
$items.add('b', 2)
|
|
105
|
+
|
|
106
|
+
effect((t) => {
|
|
107
|
+
const items = $items.get(t) // Track all changes
|
|
108
|
+
console.log('Map size:', items.size)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
$items.add('c', 3) // Logs: "Map size: 3"
|
|
112
|
+
$items.delete('a') // Logs: "Map size: 2"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use `.get(t)` when you need the entire map's current state. It returns a native `Map<K, V>`.
|
|
116
|
+
|
|
117
|
+
### Fine-Grained: Track Additions
|
|
118
|
+
|
|
119
|
+
Track only when items are added:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const $items = map<string, number>()
|
|
123
|
+
|
|
124
|
+
effect((t) => {
|
|
125
|
+
const lastAdded = $items.$lastAdded.get(t)
|
|
126
|
+
if (lastAdded) {
|
|
127
|
+
console.log(`Added ${lastAdded.key} = ${lastAdded.value}`)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
$items.add('a', 1) // Logs: "Added a = 1"
|
|
132
|
+
$items.add('b', 2) // Logs: "Added b = 2"
|
|
133
|
+
$items.update('a', 10) // No log - not an addition!
|
|
134
|
+
$items.delete('a') // No log - not an addition!
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Fine-Grained: Track Updates
|
|
138
|
+
|
|
139
|
+
Track only when existing items are updated:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const $items = map<string, number>()
|
|
143
|
+
|
|
144
|
+
// Add some initial values
|
|
145
|
+
$items.add('a', 1)
|
|
146
|
+
$items.add('b', 2)
|
|
147
|
+
|
|
148
|
+
effect((t) => {
|
|
149
|
+
const lastUpdated = $items.$lastUpdated.get(t)
|
|
150
|
+
if (lastUpdated) {
|
|
151
|
+
console.log(`Updated ${lastUpdated.key} = ${lastUpdated.value}`)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
$items.update('a', 10) // Logs: "Updated a = 10"
|
|
156
|
+
$items.add('c', 3) // No log - not an update!
|
|
157
|
+
$items.delete('a') // No log - not an update!
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Fine-Grained: Track Deletions
|
|
161
|
+
|
|
162
|
+
Track only when items are deleted:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const $items = map<string, number>()
|
|
166
|
+
|
|
167
|
+
// Add some initial values
|
|
168
|
+
$items.add('a', 1)
|
|
169
|
+
$items.add('b', 2)
|
|
170
|
+
$items.add('c', 3)
|
|
171
|
+
|
|
172
|
+
effect((t) => {
|
|
173
|
+
const lastDeleted = $items.$lastDeleted.get(t)
|
|
174
|
+
if (lastDeleted) {
|
|
175
|
+
console.log(`Deleted key: ${lastDeleted.key}, value was: ${lastDeleted.value}`)
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
$items.delete('a') // Logs: "Deleted key: a, value was: 1"
|
|
180
|
+
$items.add('d', 4) // No log - not a deletion!
|
|
181
|
+
$items.update('b', 20) // No log - not a deletion!
|
|
182
|
+
$items.delete('b') // Logs: "Deleted key: b, value was: 20"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Lifecycle
|
|
186
|
+
|
|
187
|
+
When you create an effect that tracks a map:
|
|
188
|
+
|
|
189
|
+
1. **Registration**: The map registers the effect as a watcher
|
|
190
|
+
2. **Operation**: When you call `add()`, `update()`, or `delete()`, the map updates internally
|
|
191
|
+
3. **Signal Update**: The appropriate signal (`$lastAdded`, `$lastUpdated`, or `$lastDeleted`) is updated
|
|
192
|
+
4. **Notification**: All watching effects are scheduled to run
|
|
193
|
+
5. **Re-execution**: Each watching effect re-executes its function
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
sequenceDiagram
|
|
197
|
+
participant User
|
|
198
|
+
participant Map as $users (Map)
|
|
199
|
+
participant Signal as $lastAdded
|
|
200
|
+
participant Effect
|
|
201
|
+
|
|
202
|
+
Note over User,Effect: 1. Setup Phase
|
|
203
|
+
User->>Effect: Create effect
|
|
204
|
+
activate Effect
|
|
205
|
+
Effect->>Map: get(t)
|
|
206
|
+
Note over Map: Register Effect as watcher
|
|
207
|
+
Effect->>Signal: get(t)
|
|
208
|
+
Note over Signal: Register Effect as watcher
|
|
209
|
+
Effect->>Effect: Execute function
|
|
210
|
+
Note over Effect: Initial render
|
|
211
|
+
deactivate Effect
|
|
212
|
+
|
|
213
|
+
Note over User,Effect: 2. Operation Phase
|
|
214
|
+
User->>Map: add('user1', userData)
|
|
215
|
+
activate Map
|
|
216
|
+
Note over Map: Update internal Map
|
|
217
|
+
Map->>Signal: set({ key, value })
|
|
218
|
+
activate Signal
|
|
219
|
+
Note over Signal: Notify watchers
|
|
220
|
+
Signal->>Effect: Schedule execution
|
|
221
|
+
deactivate Signal
|
|
222
|
+
Map->>Map: Notify whole map watchers
|
|
223
|
+
Map->>Effect: Schedule execution
|
|
224
|
+
deactivate Map
|
|
225
|
+
|
|
226
|
+
activate Effect
|
|
227
|
+
Note over Effect: Re-execute function
|
|
228
|
+
Effect->>Signal: get(t)
|
|
229
|
+
Signal-->>Effect: { key: 'user1', value: userData }
|
|
230
|
+
Effect->>Effect: Handle new user
|
|
231
|
+
Note over Effect: Update UI
|
|
232
|
+
deactivate Effect
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Best Practices
|
|
236
|
+
|
|
237
|
+
### Choose the Right Granularity
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// ✅ Use fine-grained for large maps
|
|
241
|
+
const $users = map<number, User>()
|
|
242
|
+
effect((t) => {
|
|
243
|
+
const lastAdded = $users.$lastAdded.get(t)
|
|
244
|
+
if (lastAdded) {
|
|
245
|
+
// Handle specific change
|
|
246
|
+
renderNewUser(lastAdded.value)
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// ✅ Use coarse-grained for small maps
|
|
251
|
+
const $settings = state({ theme: 'dark', lang: 'en', size: 'large' })
|
|
252
|
+
effect((t) => {
|
|
253
|
+
const settings = $settings.get(t)
|
|
254
|
+
// Re-apply all settings (cheap for 3 items)
|
|
255
|
+
applySettings(settings)
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Combine for Best Results
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Fine-grained for UI updates
|
|
263
|
+
effect((t) => {
|
|
264
|
+
const lastAdded = $items.$lastAdded.get(t)
|
|
265
|
+
if (lastAdded) {
|
|
266
|
+
updateUIIncremental(lastAdded)
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// Coarse-grained for totals
|
|
271
|
+
const $total = derivation((t) => {
|
|
272
|
+
const items = $items.get(t)
|
|
273
|
+
let sum = 0
|
|
274
|
+
for (const value of items.values()) {
|
|
275
|
+
sum += value.price
|
|
276
|
+
}
|
|
277
|
+
return sum
|
|
278
|
+
})
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Common Pitfalls
|
|
282
|
+
|
|
283
|
+
### Forgetting to Handle Nulls
|
|
284
|
+
|
|
285
|
+
**Problem**: The operation signals can be `null` initially.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// ❌ Can be null initially
|
|
289
|
+
effect((t) => {
|
|
290
|
+
const lastAdded = $items.$lastAdded.get(t)
|
|
291
|
+
console.log(lastAdded.key) // Error if null!
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Solution**: Always check for null:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// ✅ Check for null
|
|
299
|
+
effect((t) => {
|
|
300
|
+
const lastAdded = $items.$lastAdded.get(t)
|
|
301
|
+
if (lastAdded) {
|
|
302
|
+
console.log(lastAdded.key)
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Using Wrong Tracking
|
|
308
|
+
|
|
309
|
+
**Problem**: Tracking the entire map when you only need specific operations.
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// ❌ Tracks entire map when you only need additions
|
|
313
|
+
effect((t) => {
|
|
314
|
+
const items = $items.get(t) // Runs on ALL changes
|
|
315
|
+
const keys = Array.from(items.keys())
|
|
316
|
+
const lastKey = keys[keys.length - 1]
|
|
317
|
+
animateNewItem(lastKey)
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Solution**: Track only what you need:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// ✅ Track only additions
|
|
325
|
+
effect((t) => {
|
|
326
|
+
const lastAdded = $items.$lastAdded.get(t)
|
|
327
|
+
if (lastAdded) {
|
|
328
|
+
animateNewItem(lastAdded.key)
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Confusing Add and Update
|
|
334
|
+
|
|
335
|
+
**Problem**: Using `add()` when the key might already exist, or `update()` when it might not.
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// ❌ Will throw if key exists
|
|
339
|
+
function setUser(id: number, user: User) {
|
|
340
|
+
$users.add(id, user) // Error if user already exists!
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ❌ Will throw if key doesn't exist
|
|
344
|
+
function changeUser(id: number, user: User) {
|
|
345
|
+
$users.update(id, user) // Error if user doesn't exist!
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Solution**: Check first or handle errors:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// ✅ Check before operation
|
|
353
|
+
function setUser(id: number, user: User) {
|
|
354
|
+
const users = $users.pick()
|
|
355
|
+
if (users.has(id)) {
|
|
356
|
+
$users.update(id, user)
|
|
357
|
+
} else {
|
|
358
|
+
$users.add(id, user)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Mutating Retrieved Maps
|
|
364
|
+
|
|
365
|
+
**Problem**: Mutating the map directly instead of using reactive methods.
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// ❌ Mutating the map directly
|
|
369
|
+
const items = $items.pick()
|
|
370
|
+
items.set('newKey', newValue) // Doesn't trigger reactivity!
|
|
371
|
+
|
|
372
|
+
// ❌ Mutating the map directly
|
|
373
|
+
const items = $items.get(t)
|
|
374
|
+
items.set('newKey', newValue) // Changes internal state but doesn't notify properly!
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Solution**: Use reactive methods:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// ✅ Use reactive methods
|
|
381
|
+
const hasItem = $items.pick().has('newKey');
|
|
382
|
+
if (hasItem) {
|
|
383
|
+
$items.update('newKey', newValue)
|
|
384
|
+
} else {
|
|
385
|
+
$items.add('newKey', newValue)
|
|
386
|
+
}
|
|
387
|
+
```
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Primitives Overview
|
|
2
|
+
|
|
3
|
+
PicoFlow provides several reactive primitives, each designed for specific use cases in reactive programming. This guide introduces all the primitives and shows how they work together.
|
|
4
|
+
|
|
5
|
+
::: tip
|
|
6
|
+
Looking for naming conventions? Check out the **[Conventions](/guide/introduction/conventions)** guide to learn about the `$` prefix.
|
|
7
|
+
:::
|
|
8
|
+
|
|
9
|
+
## Listing
|
|
10
|
+
|
|
11
|
+
PicoFlow provides several reactive primitives for different use cases:
|
|
12
|
+
|
|
13
|
+
| Primitive | Purpose | Example |
|
|
14
|
+
|-----------|---------|---------|
|
|
15
|
+
| **Signal** | Event notifications | Manual refresh, synchronization |
|
|
16
|
+
| **State** | Mutable values | Form inputs, toggles, counters |
|
|
17
|
+
| **Constant** | Immutable values | Configuration, initial values |
|
|
18
|
+
| **Derivation** | Computed values | Totals, filtered lists, formatted strings |
|
|
19
|
+
| **Effect** | Side effects | DOM updates, logging, API calls |
|
|
20
|
+
| **Resource** | Async data fetching | API calls, file loading |
|
|
21
|
+
| **Stream** | Event-driven data | WebSockets, timers, DOM events |
|
|
22
|
+
| **Map** | Reactive dictionaries | Key-value stores, caches |
|
|
23
|
+
| **Array** | Reactive lists | Todo lists, dynamic collections |
|
|
24
|
+
|
|
25
|
+
## Details
|
|
26
|
+
|
|
27
|
+
### Signal
|
|
28
|
+
|
|
29
|
+
**Event-like notifications without values** - use when you want to trigger effects without carrying data.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const $refresh = signal()
|
|
33
|
+
$refresh.trigger() // Tell everyone: "Refresh now!"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Use cases:** Manual refresh triggers, event notifications, synchronization points
|
|
37
|
+
|
|
38
|
+
### State
|
|
39
|
+
|
|
40
|
+
**Mutable reactive values** - use for data that changes over time.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const $count = state(0)
|
|
44
|
+
$count.set(1)
|
|
45
|
+
$count.set(n => n + 1) // Updater function
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Use cases:** Form inputs, counters, toggles, user data
|
|
49
|
+
|
|
50
|
+
### Constant
|
|
51
|
+
|
|
52
|
+
**Immutable reactive values** - use for values that never change but need to be part of the reactive graph.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const $config = constant({ apiUrl: 'https://api.example.com', timeout: 5000 })
|
|
56
|
+
const $version = constant('1.0.0')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Use cases:** Configuration values, application constants, initial data
|
|
60
|
+
|
|
61
|
+
**Key feature:** Unlike regular JavaScript constants, these can be tracked in the reactive graph, making them useful for dependencies that shouldn't change.
|
|
62
|
+
|
|
63
|
+
### Derivation
|
|
64
|
+
|
|
65
|
+
**Computed values** - use when you want to calculate something based on other reactive values.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const $double = derivation((t) => $count.get(t) * 2)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Use cases:** Totals, filtered lists, formatted values, derived UI state
|
|
72
|
+
|
|
73
|
+
**Key feature:** Lazy evaluation and caching - only recomputes when dependencies change AND when the value is accessed.
|
|
74
|
+
|
|
75
|
+
### Effect
|
|
76
|
+
|
|
77
|
+
**Side effects** - use when you want to do something in response to changes (DOM updates, logging, etc.).
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
effect((t) => {
|
|
81
|
+
console.log('Count is:', $count.get(t))
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Use cases:** Logging, DOM manipulation, API calls, persistence
|
|
86
|
+
|
|
87
|
+
**Key feature:** Eager execution - runs immediately when created and whenever dependencies change.
|
|
88
|
+
|
|
89
|
+
### Resource
|
|
90
|
+
|
|
91
|
+
**Async data fetching** - use when you need to load data asynchronously and track its loading state.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const $userId = state(1)
|
|
95
|
+
const $user = resource(
|
|
96
|
+
(t) => $userId.get(t),
|
|
97
|
+
(id) => fetchUser(id)
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Use cases:** API calls, file loading, async data fetching
|
|
102
|
+
|
|
103
|
+
**Key feature:** Provides loading, error, and ready states automatically, with built-in cancellation of stale requests.
|
|
104
|
+
|
|
105
|
+
### Stream
|
|
106
|
+
|
|
107
|
+
**Event-driven data** - use when you want to handle continuous streams of events or data.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const $clicks = stream<MouseEvent>((emit) => {
|
|
111
|
+
document.addEventListener('click', emit)
|
|
112
|
+
return () => document.removeEventListener('click', emit)
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Use cases:** WebSocket connections, timers, DOM events, real-time data
|
|
117
|
+
|
|
118
|
+
**Key feature:** Push-based model for handling continuous event streams with automatic cleanup.
|
|
119
|
+
|
|
120
|
+
### Map
|
|
121
|
+
|
|
122
|
+
**Reactive dictionaries** - use when you need fine-grained tracking of key-value pairs.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const $users = map<number, User>({
|
|
126
|
+
1: { id: 1, name: 'Alice' },
|
|
127
|
+
2: { id: 2, name: 'Bob' }
|
|
128
|
+
})
|
|
129
|
+
$users.setAt(3, { id: 3, name: 'Charlie' })
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Use cases:** Caches, entity stores, key-value collections
|
|
133
|
+
|
|
134
|
+
**Key feature:** Fine-grained reactivity - track additions, deletions, or changes to the entire map separately.
|
|
135
|
+
|
|
136
|
+
### Array
|
|
137
|
+
|
|
138
|
+
**Reactive lists** - use when you need fine-grained tracking of array operations.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const $todos = array<Todo>([
|
|
142
|
+
{ id: 1, text: 'Learn PicoFlow', done: false },
|
|
143
|
+
{ id: 2, text: 'Build app', done: false }
|
|
144
|
+
])
|
|
145
|
+
$todos.push({ id: 3, text: 'Deploy', done: false })
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Use cases:** Todo lists, dynamic collections, ordered data
|
|
149
|
+
|
|
150
|
+
**Key feature:** Fine-grained reactivity - track specific operations (push, pop, splice) instead of full array changes.
|
|
151
|
+
|
|
152
|
+
## Derivations vs Effects
|
|
153
|
+
|
|
154
|
+
What's the difference between a derivation and an effect? Both react to changes, but they serve different purposes:
|
|
155
|
+
|
|
156
|
+
| Feature | Derivation | Effect |
|
|
157
|
+
|---------|-----------|--------|
|
|
158
|
+
| Purpose | **Compute a value** | **Perform side effects** |
|
|
159
|
+
| Returns | A value | Nothing (void) |
|
|
160
|
+
| Should be pure? | ✅ Yes | ❌ No |
|
|
161
|
+
| Side effects allowed? | ❌ No | ✅ Yes |
|
|
162
|
+
| When it runs | Lazily (when accessed) | Immediately and on changes |
|
|
163
|
+
| Use case | Calculate totals, filter lists | Update DOM, save data, log |
|
|
164
|
+
|
|
165
|
+
```mermaid
|
|
166
|
+
flowchart LR
|
|
167
|
+
A[Reactive Values] --> B[Derivation]
|
|
168
|
+
A --> C[Effect]
|
|
169
|
+
B --> D[New Computed Value]
|
|
170
|
+
C --> E[Side Effects<br/>DOM, API, Storage]
|
|
171
|
+
D -.-> F[Can be used by<br/>other derivations]
|
|
172
|
+
D -.-> G[Can be used by<br/>effects]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Key insight:** Use derivations for **calculations**, use effects for **actions**.
|