@ersbeth/picoflow 0.2.4 → 1.0.1
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 +9 -134
- 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,380 @@
|
|
|
1
|
+
# Constants
|
|
2
|
+
|
|
3
|
+
A **constant** is a reactive primitive that holds an immutable value. Unlike state, constants cannot be updated after initialization - they exist to provide stable, reactive references to values that don't change.
|
|
4
|
+
|
|
5
|
+
Imagine a configuration file in your application. It's loaded once at startup and never changes, but many parts of your app need to read it. That's what constants do in PicoFlow.
|
|
6
|
+
|
|
7
|
+
### Key Characteristics
|
|
8
|
+
|
|
9
|
+
- **Immutable**: No `.set()` method - value never changes
|
|
10
|
+
- **Reactive**: Can be tracked with `.get(t)` just like state
|
|
11
|
+
- **Lazy evaluation**: Optional - compute value on first access
|
|
12
|
+
- **Cached forever**: Once computed, the value is permanently stored
|
|
13
|
+
|
|
14
|
+
## When to Use Constants
|
|
15
|
+
|
|
16
|
+
Use constants when you need to:
|
|
17
|
+
|
|
18
|
+
- ✅ Store configuration values that don't change
|
|
19
|
+
- ✅ Cache expensive computations that only run once
|
|
20
|
+
- ✅ Create reactive feature flags
|
|
21
|
+
- ✅ Provide stable reactive references to initialization values
|
|
22
|
+
|
|
23
|
+
Don't use constants when:
|
|
24
|
+
|
|
25
|
+
- ❌ The value needs to change (use state instead)
|
|
26
|
+
- ❌ You need a `.set()` method
|
|
27
|
+
- ❌ The value isn't reactive (use plain `const` instead)
|
|
28
|
+
|
|
29
|
+
## Creating Constants
|
|
30
|
+
|
|
31
|
+
Creating a constant is simple:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { constant } from '@ersbeth/picoflow'
|
|
35
|
+
|
|
36
|
+
// Direct value - initialized immediately
|
|
37
|
+
const $apiUrl = constant('https://api.example.com')
|
|
38
|
+
const $config = constant({ apiUrl: 'https://api.example.com', timeout: 5000 })
|
|
39
|
+
const $version = constant('1.0.0')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Lazy Initialization
|
|
43
|
+
|
|
44
|
+
Pass a function to defer computation until first access:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Lazy initialization - computed on first access
|
|
48
|
+
const $expensiveValue = constant(() => {
|
|
49
|
+
console.log('Computing expensive value...')
|
|
50
|
+
return performExpensiveCalculation()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Nothing logged yet - function not called
|
|
54
|
+
|
|
55
|
+
effect((t) => {
|
|
56
|
+
const value = $expensiveValue.get(t)
|
|
57
|
+
// NOW it logs: "Computing expensive value..."
|
|
58
|
+
console.log(value)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Subsequent accesses return the cached value (no recomputation)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Using Constants
|
|
65
|
+
|
|
66
|
+
Constants provide two ways to read values:
|
|
67
|
+
|
|
68
|
+
### Reading with `.get(t)`
|
|
69
|
+
|
|
70
|
+
Inside an effect or derivation, use `.get(t)` to read the value and create a dependency:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { constant, effect } from '@ersbeth/picoflow'
|
|
74
|
+
|
|
75
|
+
const $config = constant({ apiUrl: 'https://api.example.com' })
|
|
76
|
+
|
|
77
|
+
effect((t) => {
|
|
78
|
+
const config = $config.get(t) // Read and track
|
|
79
|
+
console.log('API URL:', config.apiUrl)
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Reading with `.pick()`
|
|
84
|
+
|
|
85
|
+
Use `.pick()` when you want the current value without creating a dependency:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const $config = constant({ apiUrl: 'https://api.example.com' })
|
|
89
|
+
|
|
90
|
+
// Read without tracking (e.g., outside an effect)
|
|
91
|
+
const currentConfig = $config.pick()
|
|
92
|
+
console.log(currentConfig.apiUrl)
|
|
93
|
+
|
|
94
|
+
// Or inside an effect, when you don't want to track changes
|
|
95
|
+
effect((t) => {
|
|
96
|
+
$trigger.watch(t) // Only track trigger
|
|
97
|
+
const config = $config.pick() // Don't track config
|
|
98
|
+
console.log(config)
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### No `.set()` Method
|
|
103
|
+
|
|
104
|
+
Constants are immutable - attempting to call `.set()` will result in a TypeScript error:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const $config = constant({ apiUrl: 'https://api.example.com' })
|
|
108
|
+
|
|
109
|
+
$config.set({ apiUrl: 'new-url' }) // Type error! Constants have no .set() method
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Disposing with `.dispose()`
|
|
113
|
+
|
|
114
|
+
Constants can be disposed to free resources:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const $config = constant({ apiUrl: 'https://api.example.com' })
|
|
118
|
+
|
|
119
|
+
// Later, clean up
|
|
120
|
+
$config.dispose()
|
|
121
|
+
|
|
122
|
+
// Subsequent operations will throw
|
|
123
|
+
$config.get(t) // Error: Primitive is disposed
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Lifecycle
|
|
127
|
+
|
|
128
|
+
Constants have two initialization patterns depending on whether you pass a value or a function.
|
|
129
|
+
|
|
130
|
+
### Eager Initialization Flow
|
|
131
|
+
|
|
132
|
+
When you pass a direct value, it's stored immediately:
|
|
133
|
+
|
|
134
|
+
```mermaid
|
|
135
|
+
sequenceDiagram
|
|
136
|
+
participant User
|
|
137
|
+
participant $config as $config (Constant)
|
|
138
|
+
|
|
139
|
+
User->>$config: constant({ value: 'data' })
|
|
140
|
+
activate $config
|
|
141
|
+
Note over $config: Store value immediately<br/>Mark as initialized
|
|
142
|
+
deactivate $config
|
|
143
|
+
|
|
144
|
+
User->>$config: get(t)
|
|
145
|
+
activate $config
|
|
146
|
+
Note over $config: Already initialized<br/>Return stored value
|
|
147
|
+
$config-->>User: { value: 'data' }
|
|
148
|
+
deactivate $config
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Lazy Initialization Flow
|
|
152
|
+
|
|
153
|
+
When you pass a function, it's executed on first access:
|
|
154
|
+
|
|
155
|
+
```mermaid
|
|
156
|
+
sequenceDiagram
|
|
157
|
+
participant User
|
|
158
|
+
participant $config as $config (Constant)
|
|
159
|
+
participant InitFn as Initialization Function
|
|
160
|
+
|
|
161
|
+
Note over User,$config: 1. Creation Phase
|
|
162
|
+
User->>$config: constant(() => compute())
|
|
163
|
+
activate $config
|
|
164
|
+
Note over $config: Store function<br/>NOT called yet<br/>NOT initialized
|
|
165
|
+
deactivate $config
|
|
166
|
+
|
|
167
|
+
Note over User,InitFn: 2. First Access
|
|
168
|
+
User->>$config: get(t)
|
|
169
|
+
activate $config
|
|
170
|
+
Note over $config: Check: initialized?<br/>NO - call function
|
|
171
|
+
|
|
172
|
+
$config->>InitFn: Execute function
|
|
173
|
+
activate InitFn
|
|
174
|
+
Note over InitFn: Perform expensive<br/>calculation
|
|
175
|
+
InitFn-->>$config: Computed value
|
|
176
|
+
deactivate InitFn
|
|
177
|
+
|
|
178
|
+
Note over $config: Store value<br/>Mark as initialized
|
|
179
|
+
$config-->>User: Return value
|
|
180
|
+
deactivate $config
|
|
181
|
+
|
|
182
|
+
Note over User,$config: 3. Subsequent Access
|
|
183
|
+
User->>$config: get(t)
|
|
184
|
+
activate $config
|
|
185
|
+
Note over $config: Check: initialized?<br/>YES - return cached value
|
|
186
|
+
$config-->>User: Return cached value
|
|
187
|
+
deactivate $config
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**How it works:**
|
|
191
|
+
|
|
192
|
+
1. **Creation**: Constructor checks if value is a function
|
|
193
|
+
- If function: store it, mark as not initialized
|
|
194
|
+
- If value: store it directly, mark as initialized
|
|
195
|
+
|
|
196
|
+
2. **First access**: When `.get(t)` or `.pick()` is called
|
|
197
|
+
- Check if initialized
|
|
198
|
+
- If not, execute stored function
|
|
199
|
+
- Cache result and mark as initialized
|
|
200
|
+
|
|
201
|
+
3. **Subsequent access**: Always return cached value
|
|
202
|
+
|
|
203
|
+
## Best Practices
|
|
204
|
+
|
|
205
|
+
### Use Lazy Init for Expensive Computations
|
|
206
|
+
|
|
207
|
+
Defer expensive work until it's actually needed:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// ✅ Good - lazy evaluation
|
|
211
|
+
const $parsedData = constant(() => {
|
|
212
|
+
return JSON.parse(largeDataString) // Only runs when accessed
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// ❌ Less efficient - eager evaluation
|
|
216
|
+
const $parsedData = constant(JSON.parse(largeDataString)) // Runs immediately
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Use Constants for Configuration
|
|
220
|
+
|
|
221
|
+
Perfect for application configuration:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const $appConfig = constant({
|
|
225
|
+
apiUrl: process.env.API_URL || 'https://api.example.com',
|
|
226
|
+
environment: process.env.NODE_ENV || 'development',
|
|
227
|
+
features: {
|
|
228
|
+
darkMode: true,
|
|
229
|
+
betaFeatures: false
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
effect((t) => {
|
|
234
|
+
const config = $appConfig.get(t)
|
|
235
|
+
setupApp(config)
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Name Constants Clearly
|
|
240
|
+
|
|
241
|
+
Use the `$` prefix like other reactive primitives:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// ✅ Good - clear constant names
|
|
245
|
+
const $apiEndpoint = constant('https://api.example.com')
|
|
246
|
+
const $appVersion = constant('1.0.0')
|
|
247
|
+
const $maxRetries = constant(3)
|
|
248
|
+
|
|
249
|
+
// ❌ Less clear
|
|
250
|
+
const apiEndpoint = constant('https://api.example.com')
|
|
251
|
+
const APP_VERSION = constant('1.0.0')
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Prefer Plain const for Non-Reactive Values
|
|
255
|
+
|
|
256
|
+
If no effects or derivations will read it, use plain JavaScript:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// ❌ Unnecessary - no reactivity needed
|
|
260
|
+
const $localConfig = constant({ timeout: 5000 })
|
|
261
|
+
function fetch() {
|
|
262
|
+
const config = $localConfig.pick() // Never tracked
|
|
263
|
+
return doFetch(config)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ✅ Better - plain const
|
|
267
|
+
const localConfig = { timeout: 5000 }
|
|
268
|
+
function fetch() {
|
|
269
|
+
return doFetch(localConfig)
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Common Pitfalls
|
|
274
|
+
|
|
275
|
+
### Using Constants When State Would Be Better
|
|
276
|
+
|
|
277
|
+
**Problem**: Using a constant for a value that actually needs to change.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// ❌ Bad - theme should be changeable
|
|
281
|
+
const $theme = constant('light')
|
|
282
|
+
|
|
283
|
+
// Can't update it later!
|
|
284
|
+
// $theme.set('dark') // Type error!
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Solution**: Use state for mutable values:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// ✅ Good - theme is mutable
|
|
291
|
+
const $theme = state('light')
|
|
292
|
+
|
|
293
|
+
// Can update it
|
|
294
|
+
$theme.set('dark')
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Creating Too Many Constants
|
|
298
|
+
|
|
299
|
+
**Problem**: Making every value a constant even when plain const would work.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// ❌ Overkill - not used reactively
|
|
303
|
+
const $pi = constant(3.14159)
|
|
304
|
+
const $maxLength = constant(100)
|
|
305
|
+
|
|
306
|
+
function calculate() {
|
|
307
|
+
return $pi.pick() * radius // Just use plain const
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Solution**: Use plain const for non-reactive values:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// ✅ Better - plain JavaScript
|
|
315
|
+
const PI = 3.14159
|
|
316
|
+
const MAX_LENGTH = 100
|
|
317
|
+
|
|
318
|
+
function calculate() {
|
|
319
|
+
return PI * radius
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Expecting Constants to Update
|
|
324
|
+
|
|
325
|
+
**Problem**: Thinking constants will somehow update like state.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// ❌ Wrong - constant won't update
|
|
329
|
+
const $userName = constant('Alice')
|
|
330
|
+
|
|
331
|
+
effect((t) => {
|
|
332
|
+
const name = $userName.get(t)
|
|
333
|
+
console.log(name) // Always "Alice"
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// This won't work - no .set() method!
|
|
337
|
+
// $userName.set('Bob')
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Solution**: Use state if the value needs to change:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// ✅ Correct - use state
|
|
344
|
+
const $userName = state('Alice')
|
|
345
|
+
|
|
346
|
+
effect((t) => {
|
|
347
|
+
const name = $userName.get(t)
|
|
348
|
+
console.log(name) // Updates when changed
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
$userName.set('Bob') // Now this works!
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Lazy Init with Side Effects
|
|
355
|
+
|
|
356
|
+
**Problem**: Using lazy initialization with side effects.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// ❌ Bad - side effects in lazy init
|
|
360
|
+
const $config = constant(() => {
|
|
361
|
+
console.log('Loading config...') // Side effect!
|
|
362
|
+
localStorage.setItem('loaded', 'true') // Side effect!
|
|
363
|
+
return loadConfig()
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Solution**: Keep lazy init pure, handle side effects elsewhere:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// ✅ Better - pure initialization
|
|
371
|
+
const $config = constant(() => loadConfig())
|
|
372
|
+
|
|
373
|
+
// Handle side effects separately
|
|
374
|
+
effect((t) => {
|
|
375
|
+
const config = $config.get(t)
|
|
376
|
+
console.log('Config loaded:', config)
|
|
377
|
+
localStorage.setItem('loaded', 'true')
|
|
378
|
+
})
|
|
379
|
+
```
|
|
380
|
+
|
|
@@ -0,0 +1,348 @@
|
|
|
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
|
+
- **Dirty tracking**: Marked as "dirty" on changes, recomputes on next access
|
|
10
|
+
- **Pure computation**: Should have no side effects
|
|
11
|
+
- **Efficient**: Avoids unnecessary recomputation through smart caching
|
|
12
|
+
|
|
13
|
+
## When to Use Derivations
|
|
14
|
+
|
|
15
|
+
Use derivations when you need to:
|
|
16
|
+
|
|
17
|
+
- ✅ Compute values from other reactive primitives
|
|
18
|
+
- ✅ Transform or filter data reactively
|
|
19
|
+
- ✅ Create calculated fields (totals, averages, formatted values)
|
|
20
|
+
- ✅ Chain computations together
|
|
21
|
+
- ✅ Build complex reactive data flows
|
|
22
|
+
|
|
23
|
+
Don't use derivations when:
|
|
24
|
+
|
|
25
|
+
- ❌ You need to perform side effects (use effects instead)
|
|
26
|
+
- ❌ The computation is not pure or deterministic
|
|
27
|
+
|
|
28
|
+
::: tip
|
|
29
|
+
Not sure whether to use a derivation or an effect? See the [Derivations vs Effects](./overview.md#derivations-vs-effects) section for a detailed comparison and guidance.
|
|
30
|
+
:::
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Creating Derivations
|
|
34
|
+
|
|
35
|
+
Creating a derivation is straightforward:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { derivation } from '@ersbeth/picoflow'
|
|
39
|
+
|
|
40
|
+
const $doubled = derivation((t) => {
|
|
41
|
+
// Your computation here
|
|
42
|
+
return $count.get(t) * 2
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
TypeScript automatically infers the return type based on what your function returns.
|
|
47
|
+
|
|
48
|
+
## Using Derivations
|
|
49
|
+
|
|
50
|
+
Derivations have several methods for reading values and managing lifecycle.
|
|
51
|
+
|
|
52
|
+
### Reading with `.get(t)` (Reactive)
|
|
53
|
+
|
|
54
|
+
Inside an effect or another derivation, use `.get(t)` to read the value and track the dependency:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const $count = state(10)
|
|
58
|
+
const $doubled = derivation((t) => $count.get(t) * 2)
|
|
59
|
+
|
|
60
|
+
effect((t) => {
|
|
61
|
+
const value = $doubled.get(t) // Tracks dependency
|
|
62
|
+
console.log('Doubled:', value)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
$count.set(20) // Logs: "Doubled: 40"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Reading with `.pick()` (Non-Reactive)
|
|
69
|
+
|
|
70
|
+
Use `.pick()` to read the current value without tracking:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const $count = state(10)
|
|
74
|
+
const $doubled = derivation((t) => $count.get(t) * 2)
|
|
75
|
+
|
|
76
|
+
// Read once without tracking
|
|
77
|
+
const snapshot = $doubled.pick()
|
|
78
|
+
console.log(snapshot) // 20
|
|
79
|
+
|
|
80
|
+
$count.set(20)
|
|
81
|
+
// No reaction because we used .pick()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Chaining Derivations
|
|
85
|
+
|
|
86
|
+
Derivations can depend on other derivations, creating chains of reactive computations:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const $items = state([10, 20, 30])
|
|
90
|
+
|
|
91
|
+
// First derivation: sum
|
|
92
|
+
const $sum = derivation((t) => {
|
|
93
|
+
return $items.get(t).reduce((a, b) => a + b, 0)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Second derivation: average (depends on $sum)
|
|
97
|
+
const $average = derivation((t) => {
|
|
98
|
+
const sum = $sum.get(t)
|
|
99
|
+
const count = $items.pick().length
|
|
100
|
+
return sum / count
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Third derivation: formatted (depends on $average)
|
|
104
|
+
const $formatted = derivation((t) => {
|
|
105
|
+
const avg = $average.get(t)
|
|
106
|
+
return `Average: ${avg.toFixed(2)}`
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
effect((t) => {
|
|
110
|
+
console.log($formatted.get(t))
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
$items.set([15, 25, 35]) // All derivations update in sequence
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```mermaid
|
|
117
|
+
graph LR
|
|
118
|
+
A[$items] --> B[$sum]
|
|
119
|
+
B --> C[$average]
|
|
120
|
+
A -.-> C
|
|
121
|
+
C --> D[$formatted]
|
|
122
|
+
D --> E[Effect]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Disposing with `.dispose()`
|
|
126
|
+
|
|
127
|
+
Derivations can be disposed to free resources and prevent memory leaks:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const $doubled = derivation((t) => $count.get(t) * 2)
|
|
131
|
+
|
|
132
|
+
// Use the derivation
|
|
133
|
+
effect((t) => {
|
|
134
|
+
console.log($doubled.get(t))
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Later, clean up
|
|
138
|
+
$doubled.dispose()
|
|
139
|
+
|
|
140
|
+
// Subsequent operations will throw
|
|
141
|
+
$doubled.get(someContext) // Error: Primitive is disposed
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Lifecycle
|
|
145
|
+
|
|
146
|
+
Derivations use a lazy, cached evaluation model with dirty checking. Understanding this lifecycle is key to writing efficient reactive code.
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
```mermaid
|
|
151
|
+
sequenceDiagram
|
|
152
|
+
participant User
|
|
153
|
+
participant $count as $count (State)
|
|
154
|
+
participant $doubled as $doubled (Derivation)
|
|
155
|
+
participant Effect
|
|
156
|
+
|
|
157
|
+
Note over User,$doubled: 1. Creation Phase
|
|
158
|
+
User->>$doubled: Create derivation
|
|
159
|
+
Note over $doubled: Store function<br/>NOT executed yet
|
|
160
|
+
|
|
161
|
+
Note over User,Effect: 2. First Access
|
|
162
|
+
User->>Effect: Create effect
|
|
163
|
+
activate Effect
|
|
164
|
+
Effect->>$doubled: get(t)
|
|
165
|
+
activate $doubled
|
|
166
|
+
Note over $doubled: Not computed yet<br/>Need to compute!
|
|
167
|
+
|
|
168
|
+
$doubled->>$count: get(t)
|
|
169
|
+
activate $count
|
|
170
|
+
Note over $count: Register $doubled as dependent
|
|
171
|
+
$count-->>$doubled: 0
|
|
172
|
+
deactivate $count
|
|
173
|
+
|
|
174
|
+
Note over $doubled: Compute: 0 * 2 = 0<br/>Cache result
|
|
175
|
+
$doubled-->>Effect: 0
|
|
176
|
+
deactivate $doubled
|
|
177
|
+
Note over Effect: Log: "Doubled: 0"
|
|
178
|
+
deactivate Effect
|
|
179
|
+
|
|
180
|
+
Note over User,Effect: 3. Dependency Change
|
|
181
|
+
User->>$count: set(5)
|
|
182
|
+
activate $count
|
|
183
|
+
Note over $count: Value changed
|
|
184
|
+
|
|
185
|
+
$count->>$doubled: Notify (mark dirty)
|
|
186
|
+
activate $doubled
|
|
187
|
+
Note over $doubled: Mark as dirty<br/>NO recompute yet!
|
|
188
|
+
deactivate $doubled
|
|
189
|
+
|
|
190
|
+
$count->>Effect: Schedule execution
|
|
191
|
+
deactivate $count
|
|
192
|
+
|
|
193
|
+
Note over User,Effect: 4. Recompute on Access
|
|
194
|
+
activate Effect
|
|
195
|
+
Effect->>$doubled: get(t)
|
|
196
|
+
activate $doubled
|
|
197
|
+
Note over $doubled: Dirty? YES<br/>Recompute now!
|
|
198
|
+
|
|
199
|
+
$doubled->>$count: get(t)
|
|
200
|
+
activate $count
|
|
201
|
+
$count-->>$doubled: 5
|
|
202
|
+
deactivate $count
|
|
203
|
+
|
|
204
|
+
Note over $doubled: Compute: 5 * 2 = 10<br/>Cache new result
|
|
205
|
+
$doubled-->>Effect: 10
|
|
206
|
+
deactivate $doubled
|
|
207
|
+
Note over Effect: Log: "Doubled: 10"
|
|
208
|
+
deactivate Effect
|
|
209
|
+
|
|
210
|
+
Note over User,Effect: 5. Cached Access
|
|
211
|
+
User->>$doubled: pick()
|
|
212
|
+
activate $doubled
|
|
213
|
+
Note over $doubled: Not dirty<br/>Return cache
|
|
214
|
+
$doubled-->>User: 10
|
|
215
|
+
deactivate $doubled
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Key Lifecycle Points
|
|
219
|
+
|
|
220
|
+
1. **Creation**: The derivation function is stored but not executed
|
|
221
|
+
2. **First Access**: Computes the value, tracks dependencies, and caches the result
|
|
222
|
+
3. **Dependency Change**: Marked as "dirty" but doesn't recompute immediately
|
|
223
|
+
4. **Dirty Access**: Recomputes because it's dirty, caches new result
|
|
224
|
+
5. **Clean Access**: Returns cached value without recomputation
|
|
225
|
+
|
|
226
|
+
This lazy + cached approach means:
|
|
227
|
+
- **Unused derivations never compute** (no wasted CPU)
|
|
228
|
+
- **Multiple reads use the cache** (efficient)
|
|
229
|
+
- **Multiple dependency changes = one recompute** (optimized)
|
|
230
|
+
|
|
231
|
+
## Best Practices
|
|
232
|
+
|
|
233
|
+
### Keep Derivations Pure
|
|
234
|
+
|
|
235
|
+
Derivations should be pure functions with no side effects:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// ✅ Good - pure function
|
|
239
|
+
const $total = derivation((t) => {
|
|
240
|
+
return $items.get(t).reduce((sum, item) => sum + item.price, 0)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// ❌ Bad - side effect
|
|
244
|
+
const $total = derivation((t) => {
|
|
245
|
+
const total = $items.get(t).reduce((sum, item) => sum + item.price, 0)
|
|
246
|
+
console.log('Total:', total) // Side effect!
|
|
247
|
+
return total
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Why pure?** PicoFlow may skip recomputation if it thinks the result hasn't changed. Side effects would be unpredictable!
|
|
252
|
+
|
|
253
|
+
### Name Derivations Clearly
|
|
254
|
+
|
|
255
|
+
Use names that describe the computed value:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// ✅ Good - clear what it computes
|
|
259
|
+
const $totalPrice = derivation((t) => ...)
|
|
260
|
+
const $filteredUsers = derivation((t) => ...)
|
|
261
|
+
const $formattedDate = derivation((t) => ...)
|
|
262
|
+
|
|
263
|
+
// ❌ Bad - unclear purpose
|
|
264
|
+
const $result = derivation((t) => ...)
|
|
265
|
+
const $temp = derivation((t) => ...)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Common Pitfalls
|
|
269
|
+
|
|
270
|
+
### Side Effects in Derivations
|
|
271
|
+
|
|
272
|
+
**Problem**: Adding side effects (logging, API calls, DOM updates) in a derivation.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// ❌ Wrong - side effect in derivation
|
|
276
|
+
const $count = state(0)
|
|
277
|
+
const $logged = derivation((t) => {
|
|
278
|
+
const value = $count.get(t)
|
|
279
|
+
console.log('Count:', value) // Side effect!
|
|
280
|
+
return value * 2
|
|
281
|
+
})
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Solution**: Move side effects to an effect:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// ✅ Correct - side effect in effect
|
|
288
|
+
const $count = state(0)
|
|
289
|
+
|
|
290
|
+
effect((t) => {
|
|
291
|
+
const value = $count.get(t)
|
|
292
|
+
console.log('Count:', value) // Side effect here is fine
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const $doubled = derivation((t) => {
|
|
296
|
+
return $count.get(t) * 2 // Pure computation
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Using `.pick()` Instead of `.get(t)`
|
|
301
|
+
|
|
302
|
+
**Problem**: Using `.pick()` inside a derivation breaks doesn't trigger the reactivity system.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// ❌ Derivation doesn't track $count
|
|
306
|
+
const $count = state(10)
|
|
307
|
+
const $doubled = derivation((t) => {
|
|
308
|
+
return $count.pick() * 2 // No dependency tracking!
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
effect((t) => {
|
|
312
|
+
console.log($doubled.get(t))
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
$count.set(20) // Effect won't run - no dependency!
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Solution**: Always use `.get(t)` to track dependencies:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// ✅ Correct - tracks $count
|
|
322
|
+
const $doubled = derivation((t) => {
|
|
323
|
+
return $count.get(t) * 2
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Mutating Returned Values
|
|
328
|
+
|
|
329
|
+
**Problem**: Mutating arrays or objects returned from derivations doesn't trigger the reactivity system.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// ❌ Mutating the array
|
|
333
|
+
const $items = state([1, 2, 3])
|
|
334
|
+
const $doubled = derivation((t) => {
|
|
335
|
+
const items = $items.get(t)
|
|
336
|
+
items.forEach((v, i) => items[i] = v * 2) // Mutation!
|
|
337
|
+
return items
|
|
338
|
+
})
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Solution**: Always return new values:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// ✅ Return new array
|
|
345
|
+
const $doubled = derivation((t) => {
|
|
346
|
+
return $items.get(t).map(v => v * 2)
|
|
347
|
+
})
|
|
348
|
+
```
|