@ersbeth/picoflow 2.0.2 → 2.1.0

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