@ersbeth/picoflow 0.2.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +62 -25
  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,259 @@
1
+ # Signals
2
+
3
+ A **signal** is a reactive primitive that represents an event or notification. Unlike state or derivations, signals don't hold values - they simply notify watchers that something has happened.
4
+
5
+ Imagine a refresh button on a web page. When clicked, it doesn't need to pass data - it just needs to tell the system "hey, refresh now!" That's exactly what signals do.
6
+
7
+ ### Key Characteristics
8
+
9
+ - **No value**: Signals don't store or return data
10
+ - **Pure notification**: They exist solely to trigger reactions
11
+ - **Lightweight**: Perfect for events that don't need to carry information
12
+ - **Explicit tracking**: Use `.watch(t)` to track them in effects
13
+
14
+ ## When to Use Signals
15
+
16
+ Use signals when you need to:
17
+
18
+ - ✅ Trigger manual refreshes or updates
19
+ - ✅ Coordinate actions between independent parts of your app
20
+ - ✅ Signal that an event has occurred without sending data
21
+ - ✅ Create synchronization points in reactive flows
22
+
23
+ Don't use signals when:
24
+
25
+ - ❌ You need to pass data (use state instead)
26
+ - ❌ You're tracking continuous streams of events (use streams instead)
27
+ - ❌ The value itself matters (use state or derivation)
28
+
29
+ ## Creating Signals
30
+
31
+ Creating a signal is straightforward:
32
+
33
+ ```typescript
34
+ import { signal } from '@ersbeth/picoflow'
35
+
36
+ const $refreshTrigger = signal()
37
+ const $saveComplete = signal()
38
+ const $userLoggedOut = signal()
39
+ ```
40
+
41
+ Signals don't take any parameters since they don't hold values.
42
+
43
+ ## Using Signals
44
+
45
+ Signals have three main operations: watching, triggering, and disposing.
46
+
47
+ ### Watching Signals with `.watch(t)`
48
+
49
+ Inside an effect or derivation, use `.watch(t)` to track a signal:
50
+
51
+ ```typescript
52
+ import { signal, effect } from '@ersbeth/picoflow'
53
+
54
+ const $refresh = signal()
55
+
56
+ effect((t) => {
57
+ $refresh.watch(t)
58
+ console.log('Refresh triggered!')
59
+ })
60
+
61
+ $refresh.trigger() // Logs: "Refresh triggered!"
62
+ $refresh.trigger() // Logs: "Refresh triggered!" again
63
+ ```
64
+
65
+ Every time the signal is triggered, the effect re-executes.
66
+
67
+ ### Triggering Signals with `.trigger()`
68
+
69
+ Call `.trigger()` to notify all watchers:
70
+
71
+ ```typescript
72
+ const $notification = signal()
73
+
74
+ // Setup an effect that watches the signal
75
+ effect((t) => {
76
+ $notification.watch(t)
77
+ showNotification('New activity!')
78
+ })
79
+
80
+ // Trigger from anywhere
81
+ function onNewMessage() {
82
+ $notification.trigger()
83
+ }
84
+
85
+ function onNewLike() {
86
+ $notification.trigger()
87
+ }
88
+ ```
89
+
90
+ ### Disposing Signals with `.dispose()`
91
+
92
+ Signals can be disposed to free resources and prevent memory leaks:
93
+
94
+ ```typescript
95
+ const $mySignal = signal()
96
+
97
+ // Use the signal
98
+ effect((t) => {
99
+ $mySignal.watch(t)
100
+ console.log('Signal triggered!')
101
+ })
102
+
103
+ // Later, clean up
104
+ $mySignal.dispose()
105
+
106
+ // Subsequent operations will throw
107
+ $mySignal.trigger() // Error: Primitive is disposed
108
+ ```
109
+
110
+ ## Lifecyle
111
+
112
+ When you create an effect that watches a signal:
113
+
114
+ 1. **Registration**: The signal registers the effect as a watcher
115
+ 2. **Waiting**: The signal waits for `.trigger()` to be called
116
+ 3. **Notification**: When triggered, all watching effects are scheduled to run
117
+ 4. **Re-execution**: Each watching effect re-executes its function
118
+
119
+ ```mermaid
120
+ sequenceDiagram
121
+ participant User
122
+ participant Signal as $refresh (Signal)
123
+ participant Effect
124
+
125
+ Note over User,Effect: 1. Setup Phase
126
+ User->>Effect: Create effect
127
+ activate Effect
128
+ Effect->>Signal: watch(t)
129
+ Note over Signal: Register Effect as watcher
130
+ Signal-->>Effect: Registered
131
+ Effect->>Effect: Execute function
132
+ Note over Effect: Log: "Ready to refresh"
133
+ deactivate Effect
134
+
135
+ Note over User,Effect: 2. Trigger Phase
136
+ User->>Signal: trigger()
137
+ activate Signal
138
+ Note over Signal: Notify all watchers
139
+ Signal->>Effect: Schedule execution
140
+ deactivate Signal
141
+
142
+ activate Effect
143
+ Note over Effect: Re-execute function
144
+ Effect->>Effect: Perform refresh
145
+ Note over Effect: Log: "Refreshing..."
146
+ deactivate Effect
147
+ ```
148
+
149
+ ## Best Practices
150
+
151
+ ### Name Signals Clearly
152
+
153
+ Use names that describe the event, not the action:
154
+
155
+ ```typescript
156
+ // ✅ Good - describes the event
157
+ const $refreshRequested = signal()
158
+ const $saveComplete = signal()
159
+ const $validationFailed = signal()
160
+
161
+ // ❌ Bad - describes the handler
162
+ const $doRefresh = signal()
163
+ const $handleSave = signal()
164
+ ```
165
+
166
+ ## Common Pitfalls
167
+
168
+ ### Using Signals When State Would Be Better
169
+
170
+ **Problem**: Using a signal when you actually need to track a value.
171
+
172
+ ```typescript
173
+ // ❌ Bad - should use state
174
+ const $userUpdated = signal()
175
+ let currentUser: User | null = null
176
+
177
+ effect((t) => {
178
+ $userUpdated.watch(t)
179
+ if (currentUser) {
180
+ renderUser(currentUser)
181
+ }
182
+ })
183
+
184
+ function setUser(user: User) {
185
+ currentUser = user
186
+ $userUpdated.trigger()
187
+ }
188
+ ```
189
+
190
+ **Solution**: Use state to hold the value:
191
+
192
+ ```typescript
193
+ // ✅ Good - state holds the value
194
+ const $currentUser = state<User | null>(null)
195
+
196
+ effect((t) => {
197
+ const user = $currentUser.get(t)
198
+ if (user) {
199
+ renderUser(user)
200
+ }
201
+ })
202
+
203
+ function setUser(user: User) {
204
+ $currentUser.set(user)
205
+ }
206
+ ```
207
+
208
+ ### Forgetting to Watch the Signal
209
+
210
+ **Problem**: Calling `.trigger()` without any watchers.
211
+
212
+ ```typescript
213
+ // ❌ Bad - nothing watches the signal
214
+ const $refresh = signal()
215
+
216
+ effect((t) => {
217
+ // Forgot to watch!
218
+ performRefresh()
219
+ })
220
+
221
+ $refresh.trigger() // Effect won't run
222
+ ```
223
+
224
+ **Solution**: Make sure to call `.watch(t)`:
225
+
226
+ ```typescript
227
+ // ✅ Good - explicitly watch the signal
228
+ const $refresh = signal()
229
+
230
+ effect((t) => {
231
+ $refresh.watch(t) // Now it will react!
232
+ performRefresh()
233
+ })
234
+
235
+ $refresh.trigger() // Effect runs
236
+ ```
237
+
238
+ ### Over-Triggering
239
+
240
+ **Problem**: Triggering a signal in a loop or too frequently.
241
+
242
+ ```typescript
243
+ // ❌ Bad - triggers in a loop
244
+ for (let i = 0; i < 100; i++) {
245
+ processItem(i)
246
+ $itemProcessed.trigger() // 100 triggers!
247
+ }
248
+ ```
249
+
250
+ **Solution**: Trigger once after the batch:
251
+
252
+ ```typescript
253
+ // ✅ Good - single trigger after batch
254
+ for (let i = 0; i < 100; i++) {
255
+ processItem(i)
256
+ }
257
+ $batchProcessed.trigger() // 1 trigger
258
+ ```
259
+
@@ -0,0 +1,368 @@
1
+ # State
2
+
3
+ **State** is a reactive primitive that holds a mutable value. When you update the state, all effects and derivations watching it automatically re-execute.
4
+
5
+ Imagine a thermometer in your home. The temperature is state - it changes throughout the day, and your heating system watches it to decide when to turn on or off. That's exactly what state does in PicoFlow.
6
+
7
+ ### Key Characteristics
8
+
9
+ - **Mutable**: Can be updated with `.set()`
10
+ - **Reactive**: Automatically notifies watchers on changes
11
+ - **Equality checking**: Only notifies if the new value differs from the current value
12
+ - **Type-safe**: TypeScript ensures updates match the initial type
13
+
14
+ ## When to Use State
15
+
16
+ Use state when you need to:
17
+
18
+ - ✅ Track values that change over time
19
+ - ✅ Respond to user input or interactions
20
+ - ✅ Store data fetched from APIs
21
+ - ✅ Manage application state (UI state, form data, etc.)
22
+
23
+ Don't use state when:
24
+
25
+ - ❌ The value never changes (use constants instead)
26
+ - ❌ The value is derived from other reactive values (use derivations instead)
27
+ - ❌ You're tracking events without data (use signals instead)
28
+
29
+ ## Creating State
30
+
31
+ Creating state is straightforward:
32
+
33
+ ```typescript
34
+ import { state } from '@ersbeth/picoflow'
35
+
36
+ const $count = state(0)
37
+ const $name = state('Alice')
38
+ const $user = state({ id: 1, name: 'Bob', age: 25 })
39
+ const $items = state<string[]>([])
40
+ ```
41
+
42
+ The value you pass becomes the initial value. It can be any type: numbers, strings, objects, arrays, etc.
43
+
44
+ ## Using State
45
+
46
+ State provides methods for reading, updating, and disposing.
47
+
48
+ ### Reading with `.get(t)`
49
+
50
+ Inside an effect or derivation, use `.get(t)` to read the value and create a dependency:
51
+
52
+ ```typescript
53
+ import { state, effect } from '@ersbeth/picoflow'
54
+
55
+ const $count = state(0)
56
+
57
+ effect((t) => {
58
+ const value = $count.get(t) // Read and track
59
+ console.log('Count is:', value)
60
+ })
61
+
62
+ $count.set(1) // Console logs: "Count is: 1"
63
+ $count.set(2) // Console logs: "Count is: 2"
64
+ ```
65
+
66
+ ### Reading with `.pick()`
67
+
68
+ Use `.pick()` when you want the current value without creating a dependency:
69
+
70
+ ```typescript
71
+ const $count = state(0)
72
+
73
+ // Read without tracking (e.g., outside an effect)
74
+ const currentValue = $count.pick()
75
+ console.log(currentValue) // 0
76
+
77
+ // Or inside an effect, when you don't want to track changes
78
+ const $trigger = signal()
79
+ effect((t) => {
80
+ $trigger.watch(t) // Only track trigger
81
+ const snapshot = $count.pick() // Don't track count
82
+ console.log(snapshot)
83
+ })
84
+ ```
85
+
86
+ ### Updating with `.set(value)`
87
+
88
+ Pass the new value directly to `.set()`:
89
+
90
+ ```typescript
91
+ const $count = state(0)
92
+
93
+ $count.set(5) // Set to 5
94
+ $count.set(10) // Set to 10
95
+ ```
96
+
97
+ ### Updating with `.set(updater)`
98
+
99
+ Pass a function that receives the current value and returns the new value:
100
+
101
+ ```typescript
102
+ const $count = state(0)
103
+
104
+ $count.set(n => n + 1) // Increment by 1
105
+ $count.set(n => n * 2) // Double the value
106
+ $count.set(n => Math.max(0, n - 1)) // Decrement, min 0
107
+ ```
108
+
109
+ **When to use updater functions:**
110
+ - When the new value depends on the current value
111
+ - To guarantee you're working with the latest value
112
+ - For atomic updates (important in concurrent scenarios)
113
+
114
+ ### Disposing with `.dispose()`
115
+
116
+ State can be disposed to free resources and prevent memory leaks:
117
+
118
+ ```typescript
119
+ const $count = state(0)
120
+
121
+ // Use the state
122
+ effect((t) => {
123
+ console.log($count.get(t))
124
+ })
125
+
126
+ // Later, clean up
127
+ $count.dispose()
128
+
129
+ // Subsequent operations will throw
130
+ $count.set(5) // Error: Primitive is disposed
131
+ ```
132
+
133
+ ## Lifecycle
134
+
135
+ When you update state, a specific flow occurs to ensure efficiency and consistency.
136
+
137
+ ### Update Flow
138
+
139
+ Here's what happens when you call `.set()`:
140
+
141
+ ```mermaid
142
+ sequenceDiagram
143
+ participant User
144
+ participant $count as $count (State)
145
+ participant $double as $double (Derivation)
146
+ participant Effect
147
+
148
+ User->>$count: set(n => n + 1)
149
+ activate $count
150
+ Note over $count: Get current: 0<br/>Compute new: 0 + 1 = 1
151
+ Note over $count: Check: 1 !== 0 ✓
152
+ Note over $count: Update: 0 → 1
153
+
154
+ $count->>$double: Notify (mark dirty)
155
+ activate $double
156
+ Note over $double: Mark as dirty<br/>NO recompute yet
157
+ deactivate $double
158
+
159
+ $count->>Effect: Schedule execution
160
+ deactivate $count
161
+
162
+ activate Effect
163
+ Note over Effect: Execute function
164
+
165
+ Effect->>$count: get(t)
166
+ activate $count
167
+ $count-->>Effect: 1
168
+ deactivate $count
169
+
170
+ Effect->>$double: get(t)
171
+ activate $double
172
+ Note over $double: Dirty? YES<br/>Recompute now
173
+
174
+ $double->>$count: get(t)
175
+ activate $count
176
+ $count-->>$double: 1
177
+ deactivate $count
178
+
179
+ Note over $double: Compute: 1 * 2 = 2
180
+ $double-->>Effect: 2
181
+ deactivate $double
182
+
183
+ Note over Effect: Log: "Count: 1, Double: 2"
184
+ deactivate Effect
185
+ ```
186
+
187
+ **How it works:**
188
+
189
+ 1. **Compute new value**: If updater function, call it with current value
190
+ 2. **Equality check**: Compare new value with current using `===`
191
+ 3. **Early return**: If equal, skip notification entirely
192
+ 4. **Update value**: Store the new value
193
+ 5. **Notify dependents**:
194
+ - Derivations are marked dirty (not recomputed yet)
195
+ - Effects are scheduled for execution
196
+ 6. **Pull-based recompute**: When effects run and read derivations, they recompute
197
+
198
+ ### Equality Check Example
199
+
200
+ State only notifies when the value actually changes:
201
+
202
+ ```mermaid
203
+ sequenceDiagram
204
+ participant User
205
+ participant $count as $count (State)
206
+ participant Effect
207
+
208
+ Note over User,Effect: Initial state: $count = 5
209
+
210
+ User->>$count: set(5)
211
+ activate $count
212
+ Note over $count: Check: 5 === 5?<br/>YES - no change
213
+ Note over $count: Skip notification
214
+ deactivate $count
215
+ Note over Effect: Effect does NOT run
216
+
217
+ User->>$count: set(10)
218
+ activate $count
219
+ Note over $count: Check: 10 === 5?<br/>NO - value changed
220
+ Note over $count: Update: 5 → 10
221
+ $count->>Effect: Notify & schedule
222
+ deactivate $count
223
+
224
+ activate Effect
225
+ Note over Effect: Effect runs
226
+ deactivate Effect
227
+ ```
228
+
229
+ ## Best Practices
230
+
231
+ ### Use Updater Functions for Computed Updates
232
+
233
+ Always use updater functions when the new value depends on the current value:
234
+
235
+ ```typescript
236
+ // ❌ Bad - race condition risk
237
+ const current = $count.pick()
238
+ $count.set(current + 1)
239
+
240
+ // ✅ Good - atomic update
241
+ $count.set(n => n + 1)
242
+ ```
243
+
244
+ ### Immutable Updates for Objects
245
+
246
+ Create new objects instead of mutating:
247
+
248
+ ```typescript
249
+ // ❌ Bad - mutation
250
+ $user.set(user => {
251
+ user.name = 'Bob' // Mutates the object!
252
+ return user
253
+ })
254
+
255
+ // ✅ Good - immutable
256
+ $user.set(user => ({
257
+ ...user,
258
+ name: 'Bob'
259
+ }))
260
+ ```
261
+
262
+ ### Granular State Over Large Objects
263
+
264
+ Prefer multiple small state values when fields update independently:
265
+
266
+ ```typescript
267
+ // ❌ Less efficient - entire object tracked
268
+ const $form = state({
269
+ email: '',
270
+ password: '',
271
+ confirmPassword: ''
272
+ })
273
+
274
+ // ✅ More efficient - independent tracking
275
+ const form = {
276
+ $email: state(''),
277
+ $password = state(''),
278
+ $confirmPassword = state(''),
279
+ }
280
+ ```
281
+
282
+ ### Name State Clearly
283
+
284
+ Use the `$` prefix for all reactive values:
285
+
286
+ ```typescript
287
+ // ✅ Good - clear reactive naming
288
+ const $count = state(0)
289
+ const $user = state({...})
290
+ const $isLoading = state(false)
291
+
292
+ // ❌ Less clear
293
+ const count = state(0)
294
+ const user = state({...})
295
+ ```
296
+
297
+ ## Common Pitfalls
298
+
299
+ ### Using `.pick()` in Effects
300
+
301
+ **Problem**: Using `.pick()` inside effects prevents reactivity.
302
+
303
+ ```typescript
304
+ // ❌ Wrong - will never update
305
+ effect((t) => {
306
+ const count = $count.pick() // No dependency created!
307
+ console.log(count) // Only logs once
308
+ })
309
+ ```
310
+
311
+ **Solution**: Use `.get(t)` to create dependencies:
312
+
313
+ ```typescript
314
+ // ✅ Correct - creates dependency
315
+ effect((t) => {
316
+ const count = $count.get(t) // Creates dependency
317
+ console.log(count) // Logs on every change
318
+ })
319
+ ```
320
+
321
+ ### Forgetting Updater Functions
322
+
323
+ **Problem**: Reading and setting separately creates race conditions.
324
+
325
+ ```typescript
326
+ // ❌ Potential race condition
327
+ const current = $count.pick()
328
+ $count.set(current + 1)
329
+ // What if $count changes between pick() and set()?
330
+ ```
331
+
332
+ **Solution**: Use updater functions:
333
+
334
+ ```typescript
335
+ // ✅ Safe - uses latest value atomically
336
+ $count.set(n => n + 1)
337
+ ```
338
+
339
+ ### Mutating State Directly
340
+
341
+ **Problem**: Directly mutating arrays or objects doesn't trigger updates.
342
+
343
+ ```typescript
344
+ const $items = state([1, 2, 3])
345
+
346
+ // ❌ Wrong - mutates the array
347
+ const items = $items.pick()
348
+ items.push(4) // Doesn't trigger updates!
349
+
350
+ // ❌ Also wrong - same array reference
351
+ $items.set(items => {
352
+ items.push(4) // Mutation!
353
+ return items // Same reference, no notification!
354
+ })
355
+ ```
356
+
357
+ **Solution**: Create new arrays/objects (or use FlowArray/FlowMap):
358
+
359
+ ```typescript
360
+ // ✅ Correct - creates new array
361
+ $items.set(items => [...items, 4])
362
+
363
+ // ✅ Also correct - for objects
364
+ $user.set(user => ({
365
+ ...user,
366
+ age: user.age + 1
367
+ }))
368
+ ```