@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.
Files changed (248) hide show
  1. package/.cursor/plans/update-js-e795d61b.plan.md +567 -0
  2. package/.gitlab-ci.yml +24 -0
  3. package/.vscode/settings.json +3 -3
  4. package/CHANGELOG.md +51 -0
  5. package/IMPLEMENTATION_GUIDE.md +1578 -0
  6. package/README.md +9 -134
  7. package/biome.json +32 -32
  8. package/dist/picoflow.js +610 -436
  9. package/dist/types/advanced/array.d.ts +0 -6
  10. package/dist/types/advanced/array.d.ts.map +1 -1
  11. package/dist/types/advanced/index.d.ts +5 -5
  12. package/dist/types/advanced/index.d.ts.map +1 -1
  13. package/dist/types/advanced/map.d.ts +114 -23
  14. package/dist/types/advanced/map.d.ts.map +1 -1
  15. package/dist/types/advanced/resource.d.ts +51 -12
  16. package/dist/types/advanced/resource.d.ts.map +1 -1
  17. package/dist/types/advanced/resourceAsync.d.ts +28 -13
  18. package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
  19. package/dist/types/advanced/stream.d.ts +74 -16
  20. package/dist/types/advanced/stream.d.ts.map +1 -1
  21. package/dist/types/advanced/streamAsync.d.ts +69 -15
  22. package/dist/types/advanced/streamAsync.d.ts.map +1 -1
  23. package/dist/types/basic/constant.d.ts +44 -16
  24. package/dist/types/basic/constant.d.ts.map +1 -1
  25. package/dist/types/basic/derivation.d.ts +73 -24
  26. package/dist/types/basic/derivation.d.ts.map +1 -1
  27. package/dist/types/basic/disposable.d.ts +65 -6
  28. package/dist/types/basic/disposable.d.ts.map +1 -1
  29. package/dist/types/basic/effect.d.ts +27 -16
  30. package/dist/types/basic/effect.d.ts.map +1 -1
  31. package/dist/types/basic/index.d.ts +7 -8
  32. package/dist/types/basic/index.d.ts.map +1 -1
  33. package/dist/types/basic/observable.d.ts +62 -13
  34. package/dist/types/basic/observable.d.ts.map +1 -1
  35. package/dist/types/basic/signal.d.ts +35 -6
  36. package/dist/types/basic/signal.d.ts.map +1 -1
  37. package/dist/types/basic/state.d.ts +25 -4
  38. package/dist/types/basic/state.d.ts.map +1 -1
  39. package/dist/types/basic/trackingContext.d.ts +33 -0
  40. package/dist/types/basic/trackingContext.d.ts.map +1 -0
  41. package/dist/types/creators.d.ts +271 -26
  42. package/dist/types/creators.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +60 -7
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/dist/types/solid/converters.d.ts +5 -5
  46. package/dist/types/solid/converters.d.ts.map +1 -1
  47. package/dist/types/solid/index.d.ts +2 -2
  48. package/dist/types/solid/index.d.ts.map +1 -1
  49. package/dist/types/solid/primitives.d.ts +96 -4
  50. package/dist/types/solid/primitives.d.ts.map +1 -1
  51. package/docs/.vitepress/config.mts +110 -0
  52. package/docs/api/classes/FlowArray.md +489 -0
  53. package/docs/api/classes/FlowConstant.md +350 -0
  54. package/docs/api/classes/FlowDerivation.md +334 -0
  55. package/docs/api/classes/FlowEffect.md +100 -0
  56. package/docs/api/classes/FlowMap.md +512 -0
  57. package/docs/api/classes/FlowObservable.md +306 -0
  58. package/docs/api/classes/FlowResource.md +380 -0
  59. package/docs/api/classes/FlowResourceAsync.md +362 -0
  60. package/docs/api/classes/FlowSignal.md +160 -0
  61. package/docs/api/classes/FlowState.md +368 -0
  62. package/docs/api/classes/FlowStream.md +367 -0
  63. package/docs/api/classes/FlowStreamAsync.md +364 -0
  64. package/docs/api/classes/SolidDerivation.md +75 -0
  65. package/docs/api/classes/SolidResource.md +91 -0
  66. package/docs/api/classes/SolidState.md +71 -0
  67. package/docs/api/classes/TrackingContext.md +33 -0
  68. package/docs/api/functions/array.md +58 -0
  69. package/docs/api/functions/constant.md +45 -0
  70. package/docs/api/functions/derivation.md +53 -0
  71. package/docs/api/functions/effect.md +49 -0
  72. package/docs/api/functions/from.md +220 -0
  73. package/docs/api/functions/isDisposable.md +49 -0
  74. package/docs/api/functions/map.md +57 -0
  75. package/docs/api/functions/resource.md +52 -0
  76. package/docs/api/functions/resourceAsync.md +50 -0
  77. package/docs/api/functions/signal.md +36 -0
  78. package/docs/api/functions/state.md +47 -0
  79. package/docs/api/functions/stream.md +53 -0
  80. package/docs/api/functions/streamAsync.md +50 -0
  81. package/docs/api/index.md +118 -0
  82. package/docs/api/interfaces/FlowDisposable.md +65 -0
  83. package/docs/api/interfaces/SolidObservable.md +19 -0
  84. package/docs/api/type-aliases/FlowArrayAction.md +49 -0
  85. package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
  86. package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
  87. package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
  88. package/docs/api/type-aliases/NotPromise.md +18 -0
  89. package/docs/api/type-aliases/SolidGetter.md +17 -0
  90. package/docs/api/typedoc-sidebar.json +1 -0
  91. package/docs/examples/examples.md +2313 -0
  92. package/docs/examples/patterns.md +649 -0
  93. package/docs/guide/advanced/disposal.md +426 -0
  94. package/docs/guide/advanced/solidjs.md +221 -0
  95. package/docs/guide/advanced/upgrading.md +464 -0
  96. package/docs/guide/introduction/concepts.md +56 -0
  97. package/docs/guide/introduction/conventions.md +61 -0
  98. package/docs/guide/introduction/getting-started.md +134 -0
  99. package/docs/guide/introduction/lifecycle.md +371 -0
  100. package/docs/guide/primitives/array.md +400 -0
  101. package/docs/guide/primitives/constant.md +380 -0
  102. package/docs/guide/primitives/derivations.md +348 -0
  103. package/docs/guide/primitives/effects.md +458 -0
  104. package/docs/guide/primitives/map.md +387 -0
  105. package/docs/guide/primitives/overview.md +175 -0
  106. package/docs/guide/primitives/resources.md +858 -0
  107. package/docs/guide/primitives/signal.md +259 -0
  108. package/docs/guide/primitives/state.md +368 -0
  109. package/docs/guide/primitives/streams.md +931 -0
  110. package/docs/index.md +47 -0
  111. package/docs/public/logo.svg +1 -0
  112. package/package.json +57 -41
  113. package/src/advanced/array.ts +208 -210
  114. package/src/advanced/index.ts +7 -7
  115. package/src/advanced/map.ts +178 -68
  116. package/src/advanced/resource.ts +87 -43
  117. package/src/advanced/resourceAsync.ts +62 -42
  118. package/src/advanced/stream.ts +113 -50
  119. package/src/advanced/streamAsync.ts +120 -61
  120. package/src/basic/constant.ts +82 -49
  121. package/src/basic/derivation.ts +128 -84
  122. package/src/basic/disposable.ts +74 -15
  123. package/src/basic/effect.ts +85 -77
  124. package/src/basic/index.ts +7 -8
  125. package/src/basic/observable.ts +94 -36
  126. package/src/basic/signal.ts +133 -105
  127. package/src/basic/state.ts +46 -25
  128. package/src/basic/trackingContext.ts +45 -0
  129. package/src/creators.ts +297 -54
  130. package/src/index.ts +96 -43
  131. package/src/solid/converters.ts +186 -67
  132. package/src/solid/index.ts +8 -2
  133. package/src/solid/primitives.ts +167 -65
  134. package/test/array.test.ts +592 -612
  135. package/test/constant.test.ts +31 -33
  136. package/test/derivation.test.ts +531 -536
  137. package/test/effect.test.ts +21 -21
  138. package/test/map.test.ts +233 -137
  139. package/test/resource.test.ts +119 -121
  140. package/test/resourceAsync.test.ts +98 -100
  141. package/test/signal.test.ts +51 -55
  142. package/test/state.test.ts +186 -168
  143. package/test/stream.test.ts +189 -189
  144. package/test/streamAsync.test.ts +186 -186
  145. package/tsconfig.json +19 -18
  146. package/typedoc.json +37 -0
  147. package/vite.config.ts +23 -23
  148. package/vitest.config.ts +7 -7
  149. package/api/doc/index.md +0 -31
  150. package/api/doc/picoflow.array.md +0 -55
  151. package/api/doc/picoflow.constant.md +0 -55
  152. package/api/doc/picoflow.derivation.md +0 -55
  153. package/api/doc/picoflow.effect.md +0 -55
  154. package/api/doc/picoflow.flowarray._constructor_.md +0 -49
  155. package/api/doc/picoflow.flowarray._lastaction.md +0 -13
  156. package/api/doc/picoflow.flowarray.clear.md +0 -17
  157. package/api/doc/picoflow.flowarray.dispose.md +0 -55
  158. package/api/doc/picoflow.flowarray.get.md +0 -19
  159. package/api/doc/picoflow.flowarray.length.md +0 -13
  160. package/api/doc/picoflow.flowarray.md +0 -273
  161. package/api/doc/picoflow.flowarray.pop.md +0 -17
  162. package/api/doc/picoflow.flowarray.push.md +0 -53
  163. package/api/doc/picoflow.flowarray.set.md +0 -53
  164. package/api/doc/picoflow.flowarray.setitem.md +0 -69
  165. package/api/doc/picoflow.flowarray.shift.md +0 -17
  166. package/api/doc/picoflow.flowarray.splice.md +0 -85
  167. package/api/doc/picoflow.flowarray.unshift.md +0 -53
  168. package/api/doc/picoflow.flowarrayaction.md +0 -37
  169. package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
  170. package/api/doc/picoflow.flowconstant.get.md +0 -25
  171. package/api/doc/picoflow.flowconstant.md +0 -88
  172. package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
  173. package/api/doc/picoflow.flowderivation.get.md +0 -23
  174. package/api/doc/picoflow.flowderivation.md +0 -86
  175. package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
  176. package/api/doc/picoflow.flowdisposable.md +0 -43
  177. package/api/doc/picoflow.floweffect._constructor_.md +0 -54
  178. package/api/doc/picoflow.floweffect.dispose.md +0 -21
  179. package/api/doc/picoflow.floweffect.disposed.md +0 -13
  180. package/api/doc/picoflow.floweffect.md +0 -131
  181. package/api/doc/picoflow.flowgetter.md +0 -15
  182. package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
  183. package/api/doc/picoflow.flowmap._lastset.md +0 -21
  184. package/api/doc/picoflow.flowmap.delete.md +0 -61
  185. package/api/doc/picoflow.flowmap.md +0 -133
  186. package/api/doc/picoflow.flowmap.setat.md +0 -77
  187. package/api/doc/picoflow.flowobservable.get.md +0 -19
  188. package/api/doc/picoflow.flowobservable.md +0 -68
  189. package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
  190. package/api/doc/picoflow.flowresource._constructor_.md +0 -49
  191. package/api/doc/picoflow.flowresource.fetch.md +0 -27
  192. package/api/doc/picoflow.flowresource.get.md +0 -23
  193. package/api/doc/picoflow.flowresource.md +0 -100
  194. package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
  195. package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
  196. package/api/doc/picoflow.flowresourceasync.get.md +0 -23
  197. package/api/doc/picoflow.flowresourceasync.md +0 -100
  198. package/api/doc/picoflow.flowsignal.dispose.md +0 -59
  199. package/api/doc/picoflow.flowsignal.disposed.md +0 -18
  200. package/api/doc/picoflow.flowsignal.md +0 -112
  201. package/api/doc/picoflow.flowsignal.trigger.md +0 -21
  202. package/api/doc/picoflow.flowstate.md +0 -52
  203. package/api/doc/picoflow.flowstate.set.md +0 -61
  204. package/api/doc/picoflow.flowstream._constructor_.md +0 -49
  205. package/api/doc/picoflow.flowstream.dispose.md +0 -21
  206. package/api/doc/picoflow.flowstream.get.md +0 -23
  207. package/api/doc/picoflow.flowstream.md +0 -100
  208. package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
  209. package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
  210. package/api/doc/picoflow.flowstreamasync.get.md +0 -23
  211. package/api/doc/picoflow.flowstreamasync.md +0 -100
  212. package/api/doc/picoflow.flowstreamdisposer.md +0 -13
  213. package/api/doc/picoflow.flowstreamsetter.md +0 -13
  214. package/api/doc/picoflow.flowstreamupdater.md +0 -19
  215. package/api/doc/picoflow.flowwatcher.md +0 -15
  216. package/api/doc/picoflow.from.md +0 -55
  217. package/api/doc/picoflow.from_1.md +0 -55
  218. package/api/doc/picoflow.from_2.md +0 -55
  219. package/api/doc/picoflow.from_3.md +0 -55
  220. package/api/doc/picoflow.from_4.md +0 -55
  221. package/api/doc/picoflow.from_5.md +0 -55
  222. package/api/doc/picoflow.isdisposable.md +0 -55
  223. package/api/doc/picoflow.map.md +0 -59
  224. package/api/doc/picoflow.md +0 -544
  225. package/api/doc/picoflow.resource.md +0 -55
  226. package/api/doc/picoflow.resourceasync.md +0 -55
  227. package/api/doc/picoflow.signal.md +0 -19
  228. package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
  229. package/api/doc/picoflow.solidderivation.get.md +0 -13
  230. package/api/doc/picoflow.solidderivation.md +0 -94
  231. package/api/doc/picoflow.solidgetter.md +0 -13
  232. package/api/doc/picoflow.solidobservable.get.md +0 -13
  233. package/api/doc/picoflow.solidobservable.md +0 -57
  234. package/api/doc/picoflow.solidresource._constructor_.md +0 -49
  235. package/api/doc/picoflow.solidresource.get.md +0 -13
  236. package/api/doc/picoflow.solidresource.latest.md +0 -13
  237. package/api/doc/picoflow.solidresource.md +0 -157
  238. package/api/doc/picoflow.solidresource.refetch.md +0 -13
  239. package/api/doc/picoflow.solidresource.state.md +0 -13
  240. package/api/doc/picoflow.solidstate._constructor_.md +0 -49
  241. package/api/doc/picoflow.solidstate.get.md +0 -13
  242. package/api/doc/picoflow.solidstate.md +0 -115
  243. package/api/doc/picoflow.solidstate.set.md +0 -13
  244. package/api/doc/picoflow.state.md +0 -55
  245. package/api/doc/picoflow.stream.md +0 -55
  246. package/api/doc/picoflow.streamasync.md +0 -55
  247. package/api/picoflow.public.api.md +0 -244
  248. 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
+ ```