@ersbeth/picoflow 2.0.2 → 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/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,92 +0,0 @@
|
|
|
1
|
-
# Primitives Overview
|
|
2
|
-
|
|
3
|
-
PicoFlow distinguishes between two main concepts:
|
|
4
|
-
- **Primitives**: Reactive values (state, derivation, signal, collections)
|
|
5
|
-
- **Side Effects**: Created by subscribing to primitives
|
|
6
|
-
|
|
7
|
-
::: tip
|
|
8
|
-
Looking for naming conventions? Check out the **[Conventions](/guide/introduction/conventions)** guide to learn about the `$` prefix.
|
|
9
|
-
:::
|
|
10
|
-
|
|
11
|
-
## Primitives
|
|
12
|
-
|
|
13
|
-
PicoFlow provides several reactive primitives, each with specific capabilities. Primitive may exist in synchrone and asynchrone flavours.
|
|
14
|
-
|
|
15
|
-
| Primitive | Sync | Async | Purpose |
|
|
16
|
-
|-----------|:----:|:-----:|---------|
|
|
17
|
-
| **Signal** | ✓ | - | Event notifications without values |
|
|
18
|
-
| **State** | ✓ | ✓ | Mutable reactive values |
|
|
19
|
-
| **Constant** | ✓ | ✓ | Immutable reactive values |
|
|
20
|
-
| **Derivation** | ✓ | ✓ | Computed values from dependencies |
|
|
21
|
-
| **Writable Derivation** | ✓ | ✓ | Derivations with custom setters |
|
|
22
|
-
| **Array** | ✓ | - | Reactive lists with fine-grained tracking |
|
|
23
|
-
| **Map** | ✓ | - | Reactive dictionaries with fine-grained tracking |
|
|
24
|
-
|
|
25
|
-
## Side Effects
|
|
26
|
-
|
|
27
|
-
You may subscribe to any primitive to perform side effects on change. When you subscribe, you get a `FlowEffect` that can be disposed.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
When reacting to a single primitive, use the `.subscribe()` method:
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
const effect = $count.subscribe((value) => {
|
|
34
|
-
console.log('Count is:', value)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
// Later: dispose the effect
|
|
38
|
-
effect.dispose()
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
When combining multiple primitives, use the `subscribe()` function:
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
const effect = subscribe(
|
|
46
|
-
(t) => ({ count: $count.get(t), name: $name.get(t) }),
|
|
47
|
-
(data) => {
|
|
48
|
-
console.log('Count:', data.count, 'Name:', data.name)
|
|
49
|
-
}
|
|
50
|
-
)
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Use cases
|
|
54
|
-
|
|
55
|
-
- Logging and debugging
|
|
56
|
-
- DOM manipulation
|
|
57
|
-
- API calls and data persistence
|
|
58
|
-
- Analytics and tracking
|
|
59
|
-
- Side effects that don't produce values
|
|
60
|
-
|
|
61
|
-
### Key features
|
|
62
|
-
|
|
63
|
-
- **Eager execution**: Runs immediately when created
|
|
64
|
-
- **Automatic re-execution**: Runs whenever dependencies change
|
|
65
|
-
- **Returns FlowEffect**: Can be disposed to stop the effect
|
|
66
|
-
- **Error handling**: Optional `onError` and `onPending` callbacks
|
|
67
|
-
|
|
68
|
-
## Derivations vs Side Effects
|
|
69
|
-
|
|
70
|
-
What's the difference between a derivation and a side effect? Both react to changes, but they serve different purposes:
|
|
71
|
-
|
|
72
|
-
| Feature | Derivation | Side Effect (subscribe) |
|
|
73
|
-
|---------|-----------|---------|
|
|
74
|
-
| Purpose | **Compute a value** | **Perform side effects** |
|
|
75
|
-
| Returns | A reactive value | FlowEffect (disposable) |
|
|
76
|
-
| Should be pure? | ✅ Yes | ❌ No |
|
|
77
|
-
| Side effects allowed? | ❌ No | ✅ Yes |
|
|
78
|
-
| When it runs | Lazily (when accessed) | Immediately and on changes |
|
|
79
|
-
| How to create | `derivation()` | `subscribe()` or `.subscribe()` |
|
|
80
|
-
| Use case | Calculate totals, filter lists | Update DOM, save data, log |
|
|
81
|
-
|
|
82
|
-
```mermaid
|
|
83
|
-
flowchart LR
|
|
84
|
-
A[Reactive Values] --> B[Derivation]
|
|
85
|
-
A --> C[Effect]
|
|
86
|
-
B --> D[New Computed Value]
|
|
87
|
-
C --> E[Side Effects<br/>DOM, API, Storage]
|
|
88
|
-
D -.-> F[Can be used by<br/>other derivations]
|
|
89
|
-
D -.-> G[Can be used by<br/>effects]
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
**Key insight:** Use derivations for **calculations**, use effects for **actions**.
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
# Signals
|
|
2
|
-
|
|
3
|
-
A **signal** is a reactive primitive that represents an event or notification. Unlike state or derivations, signals don't hold values - they simply notify watchers that something has happened.
|
|
4
|
-
|
|
5
|
-
## When to Use Signals
|
|
6
|
-
|
|
7
|
-
Use signals when you need to:
|
|
8
|
-
|
|
9
|
-
- ✅ Trigger manual refreshes or updates
|
|
10
|
-
- ✅ Coordinate actions between independent parts of your app
|
|
11
|
-
- ✅ Signal that an event has occurred without sending data
|
|
12
|
-
- ✅ Create synchronization points in reactive flows
|
|
13
|
-
|
|
14
|
-
Don't use signals when:
|
|
15
|
-
|
|
16
|
-
- ❌ You need to pass data (use state instead)
|
|
17
|
-
|
|
18
|
-
## Creating Signals
|
|
19
|
-
|
|
20
|
-
Creating a signal is straightforward:
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { signal } from '@ersbeth/picoflow'
|
|
24
|
-
|
|
25
|
-
const $refreshTrigger = signal()
|
|
26
|
-
const $saveComplete = signal()
|
|
27
|
-
const $userLoggedOut = signal()
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Signals don't take any parameters since they don't hold values.
|
|
31
|
-
|
|
32
|
-
## Using Signals
|
|
33
|
-
|
|
34
|
-
Signals have three main operations: watching, triggering, and disposing.
|
|
35
|
-
|
|
36
|
-
### Watch
|
|
37
|
-
|
|
38
|
-
Inside a subscription or derivation, use `.watch(t)` to track a signal:
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { signal, subscribe } from '@ersbeth/picoflow'
|
|
42
|
-
|
|
43
|
-
const $refresh = signal()
|
|
44
|
-
|
|
45
|
-
subscribe(
|
|
46
|
-
(t) => { $refresh.watch(t) },
|
|
47
|
-
() => { console.log('Refresh triggered!') }
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
$refresh.trigger() // Logs: "Refresh triggered!"
|
|
51
|
-
$refresh.trigger() // Logs: "Refresh triggered!" again
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Every time the signal is triggered, the subscription re-executes.
|
|
55
|
-
|
|
56
|
-
### Trigger
|
|
57
|
-
|
|
58
|
-
Call `.trigger()` to notify all watchers:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
const $notification = signal()
|
|
62
|
-
|
|
63
|
-
// Setup a subscription that watches the signal
|
|
64
|
-
subscribe(
|
|
65
|
-
(t) => { $notification.watch(t) },
|
|
66
|
-
() => { showNotification('New activity!') }
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
// Trigger from anywhere
|
|
70
|
-
function onNewMessage() {
|
|
71
|
-
$notification.trigger()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function onNewLike() {
|
|
75
|
-
$notification.trigger()
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Dispose
|
|
80
|
-
|
|
81
|
-
Signals can be disposed to free resources and prevent memory leaks:
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
const $mySignal = signal()
|
|
85
|
-
|
|
86
|
-
// Use the signal
|
|
87
|
-
subscribe(
|
|
88
|
-
(t) => { $mySignal.watch(t) },
|
|
89
|
-
() => { console.log('Signal triggered!') }
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
// Later, clean up
|
|
93
|
-
$mySignal.dispose()
|
|
94
|
-
|
|
95
|
-
// Subsequent operations will throw
|
|
96
|
-
$mySignal.trigger() // Error: Primitive is disposed
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Lifecyle
|
|
100
|
-
|
|
101
|
-
When you create a subscription that watches a signal:
|
|
102
|
-
|
|
103
|
-
1. **Registration**: The signal registers the subscription as a watcher
|
|
104
|
-
2. **Waiting**: The signal waits for `.trigger()` to be called
|
|
105
|
-
3. **Notification**: When triggered, all watching subscriptions are scheduled to run
|
|
106
|
-
4. **Re-execution**: Each watching subscription re-executes its function
|
|
107
|
-
|
|
108
|
-
```mermaid
|
|
109
|
-
sequenceDiagram
|
|
110
|
-
participant User
|
|
111
|
-
participant Signal as $refresh (Signal)
|
|
112
|
-
participant Subscription
|
|
113
|
-
|
|
114
|
-
Note over User,Subscription: 1. Setup Phase
|
|
115
|
-
User->>Subscription: Create subscription
|
|
116
|
-
activate Subscription
|
|
117
|
-
Subscription->>Signal: watch(t)
|
|
118
|
-
Note over Signal: Register Subscription as watcher
|
|
119
|
-
Signal-->>Subscription: Registered
|
|
120
|
-
Subscription->>Subscription: Execute function
|
|
121
|
-
Note over Subscription: Log: "Ready to refresh"
|
|
122
|
-
deactivate Subscription
|
|
123
|
-
|
|
124
|
-
Note over User,Subscription: 2. Trigger Phase
|
|
125
|
-
User->>Signal: trigger()
|
|
126
|
-
activate Signal
|
|
127
|
-
Note over Signal: Notify all watchers
|
|
128
|
-
Signal->>Subscription: Schedule execution
|
|
129
|
-
deactivate Signal
|
|
130
|
-
|
|
131
|
-
activate Subscription
|
|
132
|
-
Note over Subscription: Re-execute function
|
|
133
|
-
Subscription->>Subscription: Perform refresh
|
|
134
|
-
Note over Subscription: Log: "Refreshing..."
|
|
135
|
-
deactivate Subscription
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Best Practices
|
|
139
|
-
|
|
140
|
-
### Name Signals Clearly
|
|
141
|
-
|
|
142
|
-
Use names that describe the event, not the action:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
// ✅ Good - describes the event
|
|
146
|
-
const $refreshRequested = signal()
|
|
147
|
-
const $saveComplete = signal()
|
|
148
|
-
const $validationFailed = signal()
|
|
149
|
-
|
|
150
|
-
// ❌ Bad - describes the handler
|
|
151
|
-
const $doRefresh = signal()
|
|
152
|
-
const $handleSave = signal()
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Common Pitfalls
|
|
156
|
-
|
|
157
|
-
### Using Signals When State Would Be Better
|
|
158
|
-
|
|
159
|
-
**Problem**: Using a signal when you actually need to track a value.
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
// ❌ Bad - should use state
|
|
163
|
-
const $userUpdated = signal()
|
|
164
|
-
let currentUser: User | null = null
|
|
165
|
-
|
|
166
|
-
subscribe(
|
|
167
|
-
(t) => {
|
|
168
|
-
$userUpdated.watch(t)
|
|
169
|
-
return currentUser
|
|
170
|
-
},
|
|
171
|
-
(user) => {
|
|
172
|
-
if (user) {
|
|
173
|
-
renderUser(user)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
function setUser(user: User) {
|
|
179
|
-
currentUser = user
|
|
180
|
-
$userUpdated.trigger()
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
**Solution**: Use state to hold the value:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// ✅ Good - state holds the value
|
|
188
|
-
const $currentUser = state<User | null>(null)
|
|
189
|
-
|
|
190
|
-
$currentUser.subscribe((user) => {
|
|
191
|
-
if (user) {
|
|
192
|
-
renderUser(user)
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
function setUser(user: User) {
|
|
197
|
-
$currentUser.set(user)
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Over-Triggering
|
|
202
|
-
|
|
203
|
-
**Problem**: Triggering a signal in a loop or too frequently.
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
// ❌ Bad - triggers in a loop
|
|
207
|
-
for (let i = 0; i < 100; i++) {
|
|
208
|
-
processItem(i)
|
|
209
|
-
$itemProcessed.trigger() // 100 triggers!
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
**Solution**: Trigger once after the batch:
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
// ✅ Good - single trigger after batch
|
|
217
|
-
for (let i = 0; i < 100; i++) {
|
|
218
|
-
processItem(i)
|
|
219
|
-
}
|
|
220
|
-
$batchProcessed.trigger() // 1 trigger
|
|
221
|
-
```
|
|
222
|
-
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
# State
|
|
2
|
-
|
|
3
|
-
**State** is a reactive primitive that holds a mutable value.
|
|
4
|
-
|
|
5
|
-
### Key Characteristics
|
|
6
|
-
|
|
7
|
-
- **Mutable**: Can be updated with `.set()`
|
|
8
|
-
- **Reactive**: Automatically notifies watchers on changes
|
|
9
|
-
- **Equality checking**: Only notifies if the new value differs from the current value
|
|
10
|
-
|
|
11
|
-
## When to Use State
|
|
12
|
-
|
|
13
|
-
Use state when you need to:
|
|
14
|
-
|
|
15
|
-
- ✅ Track values that change over time
|
|
16
|
-
|
|
17
|
-
Don't use state when:
|
|
18
|
-
|
|
19
|
-
- ❌ The value never changes (use constants instead)
|
|
20
|
-
- ❌ The value is derived from other reactive values (use derivations instead)
|
|
21
|
-
- ❌ You're tracking events without data (use signals instead)
|
|
22
|
-
|
|
23
|
-
## Creating State
|
|
24
|
-
|
|
25
|
-
Creating state is straightforward:
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { state } from '@ersbeth/picoflow'
|
|
29
|
-
|
|
30
|
-
const $count = state(0)
|
|
31
|
-
const $name = state('Alice')
|
|
32
|
-
const $user = state({ id: 1, name: 'Bob', age: 25 })
|
|
33
|
-
const $items = state<string[]>([])
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The value you pass becomes the initial value. It can be any type: numbers, strings, objects, arrays, etc.
|
|
37
|
-
|
|
38
|
-
## Using State
|
|
39
|
-
|
|
40
|
-
State provides methods for reading, updating, and disposing.
|
|
41
|
-
|
|
42
|
-
### Get
|
|
43
|
-
|
|
44
|
-
Inside a subscription or derivation, use `.get(t)` to read the value and create a dependency:
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
const $count = state(0)
|
|
48
|
-
const $double = derivation((t) => $count.get(t) * 2)
|
|
49
|
-
|
|
50
|
-
$double.subscribe((value) => {
|
|
51
|
-
console.log('Double is:', value)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
$count.set(1) // Console logs: "Double is: 2"
|
|
55
|
-
$count.set(2) // Console logs: "Double is: 4"
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Pick
|
|
59
|
-
|
|
60
|
-
Use `.pick()` when you want the current value without creating a dependency:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
const $count = state(0)
|
|
64
|
-
|
|
65
|
-
// Read without tracking (e.g., outside a subscription)
|
|
66
|
-
const currentValue = await $count.pick()
|
|
67
|
-
console.log(currentValue) // 0
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Update
|
|
71
|
-
|
|
72
|
-
Pass the new value directly to `.set()`:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
const $count = state(0)
|
|
76
|
-
|
|
77
|
-
$count.set(5) // Set to 5
|
|
78
|
-
$count.set(10) // Set to 10
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Pass a function that receives the current value and returns the new value:
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
const $count = state(0)
|
|
85
|
-
|
|
86
|
-
$count.set(n => n + 1) // Increment by 1
|
|
87
|
-
$count.set(n => n * 2) // Double the value
|
|
88
|
-
$count.set(n => Math.max(0, n - 1)) // Decrement, min 0
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**When to use updater functions:**
|
|
92
|
-
- When the new value depends on the current value
|
|
93
|
-
- To guarantee you're working with the latest value
|
|
94
|
-
- For atomic updates (important in concurrent scenarios)
|
|
95
|
-
|
|
96
|
-
### Dispose
|
|
97
|
-
|
|
98
|
-
State can be disposed to free resources and prevent memory leaks:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
const $count = state(0)
|
|
102
|
-
|
|
103
|
-
// Use the state
|
|
104
|
-
$count.subscribe((value) => {
|
|
105
|
-
console.log(value)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
// Later, clean up
|
|
109
|
-
$count.dispose()
|
|
110
|
-
|
|
111
|
-
// Subsequent operations will throw
|
|
112
|
-
$count.set(5) // Error: Primitive is disposed
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Lifecycle
|
|
116
|
-
|
|
117
|
-
When you update state, a specific flow occurs to ensure efficiency and consistency.
|
|
118
|
-
|
|
119
|
-
```mermaid
|
|
120
|
-
sequenceDiagram
|
|
121
|
-
participant User
|
|
122
|
-
participant $count as $count (State)
|
|
123
|
-
participant $double as $double (Derivation)
|
|
124
|
-
participant Subscription
|
|
125
|
-
|
|
126
|
-
User->>$count: set(n => n + 1)
|
|
127
|
-
activate $count
|
|
128
|
-
Note over $count: Get current: 0<br/>Compute new: 0 + 1 = 1
|
|
129
|
-
Note over $count: Check: 1 !== 0 ✓
|
|
130
|
-
Note over $count: Update: 0 → 1
|
|
131
|
-
|
|
132
|
-
$count->>$double: Notify (mark dirty)
|
|
133
|
-
activate $double
|
|
134
|
-
Note over $double: Mark as dirty<br/>NO recompute yet
|
|
135
|
-
deactivate $double
|
|
136
|
-
|
|
137
|
-
$count->>Subscription: Schedule execution
|
|
138
|
-
deactivate $count
|
|
139
|
-
|
|
140
|
-
activate Subscription
|
|
141
|
-
Note over Subscription: Execute function
|
|
142
|
-
|
|
143
|
-
Subscription->>$count: get(t)
|
|
144
|
-
activate $count
|
|
145
|
-
$count-->>Subscription: 1
|
|
146
|
-
deactivate $count
|
|
147
|
-
|
|
148
|
-
Subscription->>$double: get(t)
|
|
149
|
-
activate $double
|
|
150
|
-
Note over $double: Dirty? YES<br/>Recompute now
|
|
151
|
-
|
|
152
|
-
$double->>$count: get(t)
|
|
153
|
-
activate $count
|
|
154
|
-
$count-->>$double: 1
|
|
155
|
-
deactivate $count
|
|
156
|
-
|
|
157
|
-
Note over $double: Compute: 1 * 2 = 2
|
|
158
|
-
$double-->>Subscription: 2
|
|
159
|
-
deactivate $double
|
|
160
|
-
|
|
161
|
-
Note over Subscription: Log: "Count: 1, Double: 2"
|
|
162
|
-
deactivate Subscription
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**How it works:**
|
|
166
|
-
|
|
167
|
-
1. **Compute new value**: If updater function, call it with current value
|
|
168
|
-
2. **Equality check**: Compare new value with current using `===`
|
|
169
|
-
3. **Early return**: If equal, skip notification entirely
|
|
170
|
-
4. **Update value**: Store the new value
|
|
171
|
-
5. **Notify dependents**:
|
|
172
|
-
- Derivations are marked dirty (not recomputed yet)
|
|
173
|
-
- Subscriptions are scheduled for execution
|
|
174
|
-
6. **Pull-based recompute**: When subscriptions run and read derivations, they recompute
|
|
175
|
-
|
|
176
|
-
### Equality Check Example
|
|
177
|
-
|
|
178
|
-
State only notifies when the value actually changes:
|
|
179
|
-
|
|
180
|
-
```mermaid
|
|
181
|
-
sequenceDiagram
|
|
182
|
-
participant User
|
|
183
|
-
participant $count as $count (State)
|
|
184
|
-
participant Subscription
|
|
185
|
-
|
|
186
|
-
Note over User,Subscription: Initial state: $count = 5
|
|
187
|
-
|
|
188
|
-
User->>$count: set(5)
|
|
189
|
-
activate $count
|
|
190
|
-
Note over $count: Check: 5 === 5?<br/>YES - no change
|
|
191
|
-
Note over $count: Skip notification
|
|
192
|
-
deactivate $count
|
|
193
|
-
Note over Subscription: Subscription does NOT run
|
|
194
|
-
|
|
195
|
-
User->>$count: set(10)
|
|
196
|
-
activate $count
|
|
197
|
-
Note over $count: Check: 10 === 5?<br/>NO - value changed
|
|
198
|
-
Note over $count: Update: 5 → 10
|
|
199
|
-
$count->>Subscription: Notify & schedule
|
|
200
|
-
deactivate $count
|
|
201
|
-
|
|
202
|
-
activate Subscription
|
|
203
|
-
Note over Subscription: Subscription runs
|
|
204
|
-
deactivate Subscription
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Best Practices
|
|
208
|
-
|
|
209
|
-
### Use Updater Functions for Computed Updates
|
|
210
|
-
|
|
211
|
-
Always use updater functions when the new value depends on the current value:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// ❌ Bad - race condition risk
|
|
215
|
-
const current = $count.pick()
|
|
216
|
-
$count.set(current + 1)
|
|
217
|
-
|
|
218
|
-
// ✅ Good - atomic update
|
|
219
|
-
$count.set(n => n + 1)
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Immutable Updates for Objects
|
|
223
|
-
|
|
224
|
-
Create new objects instead of mutating:
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
// ❌ Bad - mutation
|
|
228
|
-
$user.set(user => {
|
|
229
|
-
user.name = 'Bob' // Mutates the object!
|
|
230
|
-
return user
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
// ✅ Good - immutable
|
|
234
|
-
$user.set(user => ({
|
|
235
|
-
...user,
|
|
236
|
-
name: 'Bob'
|
|
237
|
-
}))
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Granular State Over Large Objects
|
|
241
|
-
|
|
242
|
-
Prefer multiple small state values when fields update independently:
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
// ❌ Less efficient - entire object tracked
|
|
246
|
-
const $form = state({
|
|
247
|
-
email: '',
|
|
248
|
-
password: '',
|
|
249
|
-
confirmPassword: ''
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
// ✅ More efficient - independent tracking
|
|
253
|
-
const form = {
|
|
254
|
-
$email: state(''),
|
|
255
|
-
$password = state(''),
|
|
256
|
-
$confirmPassword = state(''),
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
Use the `$` prefix for all reactive values:
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
// ✅ Good - clear reactive naming
|
|
265
|
-
const $count = state(0)
|
|
266
|
-
const $user = state({...})
|
|
267
|
-
const $isLoading = state(false)
|
|
268
|
-
|
|
269
|
-
// ❌ Less clear
|
|
270
|
-
const count = state(0)
|
|
271
|
-
const user = state({...})
|
|
272
|
-
```
|
package/docs/index.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
layout: home
|
|
3
|
-
|
|
4
|
-
hero:
|
|
5
|
-
name: PicoFlow
|
|
6
|
-
text: Lightweight Reactive Dataflow
|
|
7
|
-
tagline: Fine-grained reactive primitives for TypeScript with explicit dependency tracking
|
|
8
|
-
image:
|
|
9
|
-
src: /logo.svg
|
|
10
|
-
alt: PicoFlow
|
|
11
|
-
actions:
|
|
12
|
-
- theme: brand
|
|
13
|
-
text: Get Started
|
|
14
|
-
link: /guide/introduction/getting-started
|
|
15
|
-
- theme: alt
|
|
16
|
-
text: API Reference
|
|
17
|
-
link: /api/
|
|
18
|
-
- theme: alt
|
|
19
|
-
text: View on GitLab
|
|
20
|
-
link: https://gitlab.com/ersbeth-web/picoflow
|
|
21
|
-
|
|
22
|
-
features:
|
|
23
|
-
- icon: 🎯
|
|
24
|
-
title: Explicit Tracking
|
|
25
|
-
details: Full control over reactive dependencies with TrackingContext. Choose between reactive reads with get(t) and non-reactive reads with pick().
|
|
26
|
-
|
|
27
|
-
- icon: ⚡
|
|
28
|
-
title: Lazy Evaluation
|
|
29
|
-
details: Derivations compute only when accessed, not on construction. Smart caching ensures computations run only when dependencies change.
|
|
30
|
-
|
|
31
|
-
- icon: 🔧
|
|
32
|
-
title: Fine-grained Reactivity
|
|
33
|
-
details: Track specific operations on collections with reactive arrays and maps. Subscribe to additions, deletions, or entire collection changes.
|
|
34
|
-
|
|
35
|
-
- icon: 🧹
|
|
36
|
-
title: Resource Management
|
|
37
|
-
details: Automatic cleanup with dispose patterns. Manage async resources, streams, and effects with confidence.
|
|
38
|
-
|
|
39
|
-
- icon: 📦
|
|
40
|
-
title: Lightweight
|
|
41
|
-
details: Minimal bundle size with zero dependencies. Tree-shakeable exports ensure you only ship what you use.
|
|
42
|
-
|
|
43
|
-
- icon: 🔄
|
|
44
|
-
title: SolidJS Integration
|
|
45
|
-
details: Seamless integration with SolidJS through built-in converters. Use PicoFlow primitives directly in Solid components.
|
|
46
|
-
---
|
|
47
|
-
|
package/docs/public/logo.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M176 120C189.3 120 200 130.7 200 144C200 157.3 189.3 168 176 168C162.7 168 152 157.3 152 144C152 130.7 162.7 120 176 120zM208.4 217.2C236.4 204.8 256 176.7 256 144C256 99.8 220.2 64 176 64C131.8 64 96 99.8 96 144C96 176.8 115.7 205 144 217.3L144 422.6C115.7 435 96 463.2 96 496C96 540.2 131.8 576 176 576C220.2 576 256 540.2 256 496C256 463.2 236.3 435 208 422.7L208 336.1C234.7 356.2 268 368.1 304 368.1L390.7 368.1C403 396.4 431.2 416.1 464 416.1C508.2 416.1 544 380.3 544 336.1C544 291.9 508.2 256.1 464 256.1C431.2 256.1 403 275.8 390.7 304.1L304 304C254.1 304 213 265.9 208.4 217.2zM176 472C189.3 472 200 482.7 200 496C200 509.3 189.3 520 176 520C162.7 520 152 509.3 152 496C152 482.7 162.7 472 176 472zM440 336C440 322.7 450.7 312 464 312C477.3 312 488 322.7 488 336C488 349.3 477.3 360 464 360C450.7 360 440 349.3 440 336z" fill="#5672cd"/></svg>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Contract for resources that can be explicitly released to prevent memory leaks.
|
|
3
|
-
*
|
|
4
|
-
* All reactive primitives in PicoFlow implement this interface, allowing manual cleanup of subscriptions
|
|
5
|
-
* and dependencies. Once disposed, the resource cannot be reused and will throw errors on further access.
|
|
6
|
-
* Call `dispose()` when you no longer need a reactive primitive to free its resources.
|
|
7
|
-
*
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
export interface FlowDisposable {
|
|
11
|
-
/**
|
|
12
|
-
* Indicates whether this resource has been disposed.
|
|
13
|
-
*
|
|
14
|
-
* Returns `true` if `dispose()` has been called, `false` otherwise. Once disposed,
|
|
15
|
-
* the resource is no longer usable and any operations on it will throw errors.
|
|
16
|
-
*/
|
|
17
|
-
get disposed(): boolean;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Releases all resources held by this disposable object.
|
|
21
|
-
*
|
|
22
|
-
* Cleans up all subscriptions, dependencies, and internal state. After calling this method,
|
|
23
|
-
* the resource enters a disposed state and cannot be reused. Any subsequent operations will
|
|
24
|
-
* throw errors. This method should be called when you no longer need the reactive primitive
|
|
25
|
-
* to prevent memory leaks.
|
|
26
|
-
*/
|
|
27
|
-
dispose(): void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Type guard to check if an object implements the FlowDisposable interface.
|
|
32
|
-
*
|
|
33
|
-
* Useful for conditionally disposing objects that may or may not be disposable,
|
|
34
|
-
* such as when cleaning up mixed collections of values. Returns true if the object
|
|
35
|
-
* has a `dispose` method, providing type-safe access to disposal functionality.
|
|
36
|
-
*
|
|
37
|
-
* @param obj - The object to check
|
|
38
|
-
* @returns True if the object implements FlowDisposable, false otherwise
|
|
39
|
-
*
|
|
40
|
-
* @public
|
|
41
|
-
*/
|
|
42
|
-
export function isDisposable(obj: unknown): obj is FlowDisposable {
|
|
43
|
-
return obj !== null && obj !== undefined && typeof (obj as FlowDisposable).dispose === "function";
|
|
44
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { FlowDisposable } from "./flowDisposable";
|
|
2
|
-
import type { FlowSubscribable } from "./flowSubscribable";
|
|
3
|
-
import type { FlowTracker } from "./flowTracker";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Contract for reactive primitives that emit changes and notify their dependents in the reactivity graph.
|
|
7
|
-
* @public
|
|
8
|
-
*/
|
|
9
|
-
export interface FlowObservable<T> extends FlowDisposable, FlowSubscribable<T> {
|
|
10
|
-
/**
|
|
11
|
-
* Establishes a reactive dependency without reading the value.
|
|
12
|
-
*
|
|
13
|
-
* Registers this observable as a dependency of the given tracker, allowing it to be notified
|
|
14
|
-
* when changes occur. This enables dependency tracking without accessing the actual value.
|
|
15
|
-
*
|
|
16
|
-
* @param tracker - Tracker that records this observable as a dependency
|
|
17
|
-
*/
|
|
18
|
-
watch(tracker: FlowTracker): void;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Manually triggers notifications to all dependents.
|
|
22
|
-
*
|
|
23
|
-
* Forces all dependents of this observable to be notified and re-executed, even if the
|
|
24
|
-
* value hasn't actually changed. Useful for imperatively triggering updates when the
|
|
25
|
-
* reactive system cannot detect changes automatically.
|
|
26
|
-
*/
|
|
27
|
-
trigger(): void;
|
|
28
|
-
}
|