@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,372 +0,0 @@
1
- # Side Effects & Subscribe
2
-
3
- Side effects are how you perform **actions** in response to reactive changes. They're the bridge between your reactive data and the outside world - updating the DOM, making API calls, saving to localStorage, and more.
4
-
5
- Side effects are created by subscribing to reactive primitives using `subscribe()` or `.subscribe()`. When you subscribe, you get a `FlowEffect` object that can be disposed to stop the side effect.
6
-
7
- ### Key Characteristics
8
-
9
- - **Two syntaxes**: `.subscribe()` for single primitive, `subscribe()` for multiple
10
- - **Immediate execution**: Runs synchronously when created
11
- - **Automatic re-execution**: Re-runs when dependencies change
12
- - **Dynamic dependency tracking**: Dependencies adapt at runtime
13
- - **Returns FlowEffect**: Disposable handle to stop the subscription
14
-
15
- ## When to Use Subscribe
16
-
17
- Use subscribe when you need to:
18
-
19
- - ✅ Perform side effects (DOM updates, API calls, localStorage)
20
- - ✅ React to changes in reactive values
21
- - ✅ Coordinate with external systems
22
- - ✅ Log or debug reactive changes
23
-
24
- Don't use subscribe when:
25
-
26
- - ❌ Computing derived values (use derivations instead)
27
- - ❌ You only need to run code once (use regular functions)
28
-
29
- ## Creating Side Effects
30
-
31
- There are two ways to create side effects, depending on your needs:
32
-
33
- ### Single Primitive - `.subscribe()`
34
-
35
- When reacting to a single primitive, use the `.subscribe()` method:
36
-
37
- ```typescript
38
- import { state } from '@ersbeth/picoflow'
39
-
40
- const $count = state(0)
41
-
42
- // Subscribe directly to the primitive
43
- const effect = $count.subscribe((value) => {
44
- console.log('Count is:', value)
45
- })
46
-
47
- // Later: dispose the effect
48
- effect.dispose()
49
- ```
50
-
51
- ### Multiple Primitives - `subscribe()`
52
-
53
- When combining multiple primitives, use the global `subscribe()` function:
54
-
55
- ```typescript
56
- import { state, subscribe } from '@ersbeth/picoflow'
57
-
58
- const $firstName = state('Alice')
59
- const $lastName = state('Smith')
60
-
61
- // Combine multiple values
62
- const effect = subscribe(
63
- (t) => ({ first: $firstName.get(t), last: $lastName.get(t) }),
64
- (data) => {
65
- console.log('Full name:', data.first, data.last)
66
- }
67
- )
68
- ```
69
-
70
- The parameter `t` is the **FlowTracker** - your tool for creating dependencies.
71
-
72
- ## Using Side-Effects
73
-
74
- ### Conditional Dependencies
75
-
76
- Dependencies can be conditional - they're re-evaluated on each execution:
77
-
78
- ```typescript
79
- const $showDetails = state(false)
80
- const $details = state({ info: 'secret' })
81
-
82
- subscribe(
83
- (t) => {
84
- const show = $showDetails.get(t)
85
- if (show) {
86
- return { show: true, details: $details.get(t) }
87
- }
88
- return { show: false, details: null }
89
- },
90
- (data) => {
91
- if (data.show) {
92
- console.log('Details:', data.details)
93
- } else {
94
- console.log('Details hidden')
95
- }
96
- }
97
- )
98
-
99
- // When $showDetails is false, changes to $details don't trigger the subscription
100
- $details.set({ info: 'new secret' }) // Subscription doesn't run
101
-
102
- // But once $showDetails is true...
103
- $showDetails.set(true) // Subscription runs and now tracks $details
104
- $details.set({ info: 'updated' }) // Now this triggers the subscription!
105
- ```
106
-
107
- ### Dispose
108
-
109
- Subscriptions run indefinitely unless disposed. Always clean them up when no longer needed:
110
-
111
- ```typescript
112
- const $count = state(0)
113
-
114
- // Create subscription
115
- const effect = $count.subscribe((value) => {
116
- console.log(value)
117
- })
118
-
119
- // Later... stop the subscription
120
- effect.dispose()
121
-
122
- // After disposal, the subscription won't run anymore
123
- $count.set(10) // No console log
124
- ```
125
-
126
- **Always dispose subscriptions when:**
127
- - Component unmounts (in UI frameworks)
128
- - Feature is disabled
129
- - User navigates away
130
- - App shuts down
131
-
132
- **Memory leak example:**
133
-
134
- ```typescript
135
- // ❌ Memory leak
136
- function createUserPanel(userId: number) {
137
- const $user = state(getUserData(userId))
138
-
139
- $user.subscribe((user) => {
140
- updateUI(user)
141
- })
142
- // Subscription never disposed! ⚠️
143
- }
144
-
145
- // Called 100 times = 100 subscriptions still running!
146
-
147
- // ✅ Proper cleanup
148
- function createUserPanel(userId: number) {
149
- const $user = state(getUserData(userId))
150
-
151
- const effect = $user.subscribe((user) => {
152
- updateUI(user)
153
- })
154
-
155
- return {
156
- dispose: () => {
157
- effect.dispose()
158
- $user.dispose()
159
- }
160
- }
161
- }
162
- ```
163
-
164
- ## Lifecycle
165
-
166
- Subscriptions execute immediately when created, track their dependencies, and re-execute when any dependency changes. Understanding this lifecycle is key to writing efficient reactive code.
167
-
168
- When you create a subscription, it runs **synchronously** - not on the next tick, but right away. This allows subscriptions to establish initial dependencies and perform initial side effects. When a dependency changes, the subscription re-executes synchronously.
169
-
170
- ```mermaid
171
- sequenceDiagram
172
- participant User
173
- participant $count as $count (State)
174
- participant Subscription
175
-
176
- Note over User,Subscription: 1. Creation Phase
177
- User->>Subscription: "Create $count.subscribe(...)"
178
- activate Subscription
179
- Note over Subscription: Execute immediately
180
-
181
- Subscription->>$count: Read value
182
- activate $count
183
- Note over $count: Register Subscription as dependent
184
- $count-->>Subscription: 0
185
- deactivate $count
186
-
187
- Note over Subscription: "console.log(\"Count: 0\")"
188
- Note over Subscription: Track: depends on $count
189
- deactivate Subscription
190
-
191
- Note over User,Subscription: 2. Change Phase
192
- User->>$count: set(5)
193
- activate $count
194
- Note over $count: Value changed: 0 → 5
195
-
196
- $count->>Subscription: Notify & schedule
197
- deactivate $count
198
-
199
- activate Subscription
200
- Note over Subscription: Clear old dependencies<br/>Execute callback
201
-
202
- Subscription->>$count: Read value
203
- activate $count
204
- Note over $count: Register Subscription as dependent
205
- $count-->>Subscription: 5
206
- deactivate $count
207
-
208
- Note over Subscription: "console.log(\"Count: 5\")"
209
- Note over Subscription: Track: depends on $count
210
- deactivate Subscription
211
-
212
- Note over User,Subscription: 3. Disposal Phase
213
- User->>Subscription: dispose()
214
- activate Subscription
215
- Note over Subscription: Clear dependencies<br/>Unregister from $count
216
- Note over Subscription: Mark as disposed
217
- deactivate Subscription
218
-
219
- User->>$count: set(10)
220
- activate $count
221
- Note over $count: Value changed<br/>No dependents to notify
222
- deactivate $count
223
- Note over Subscription: (Does not run - disposed)
224
- ```
225
-
226
- ### Key Lifecycle Points
227
-
228
- 1. **Creation & Immediate Execution**: The subscription callback runs synchronously during creation
229
- 2. **Dependency Registration**: Reactive reads register the subscription as a dependent
230
- 3. **Change Notification**: When dependencies change, the subscription is scheduled to re-run
231
- 4. **Re-execution**: Subscription clears old dependencies, runs again, re-registers new dependencies
232
- 5. **Disposal**: Clears all dependencies, unregisters from all reactive values, prevents future execution
233
-
234
- This immediate, synchronous execution model means:
235
- - **Subscriptions establish dependencies on first run** (no waiting)
236
- - **Changes trigger re-execution immediately** (synchronous)
237
- - **Dynamic dependencies adapt automatically** (conditional tracking)
238
-
239
- ## Best Practices
240
-
241
- ### Keep Subscriptions Focused
242
-
243
- Each subscription should do one thing:
244
-
245
- ```typescript
246
- // ❌ Doing too much
247
- $count.subscribe((value) => {
248
- updateDOM(value)
249
- saveToStorage(value)
250
- sendAnalytics(value)
251
- updateTitle(value)
252
- })
253
-
254
- // ✅ Separate concerns
255
- $count.subscribe((value) => updateDOM(value))
256
- $count.subscribe((value) => saveToStorage(value))
257
- $count.subscribe((value) => sendAnalytics(value))
258
- $count.subscribe((value) => updateTitle(value))
259
- ```
260
-
261
- **Why?** Easier to debug, test, and disable individual subscriptions.
262
-
263
- ### Avoid Creating Subscriptions Inside Subscriptions
264
-
265
- ```typescript
266
- // ❌ Bad - creates new subscriptions on every run
267
- $count.subscribe((count) => {
268
- $other.subscribe((other) => {
269
- console.log('Nested:', count, other) // Memory leak!
270
- })
271
- })
272
-
273
- // ✅ Good - create subscriptions at the top level
274
- subscribe(
275
- (t) => ({ count: $count.get(t), other: $other.get(t) }),
276
- (data) => {
277
- console.log('Values:', data.count, data.other)
278
- }
279
- )
280
- ```
281
-
282
- ### Handle Errors
283
-
284
- Use the `onError` callback or wrap risky operations in try-catch:
285
-
286
- ```typescript
287
- // ✅ Using onError callback
288
- subscribe(
289
- (t) => $data.get(t),
290
- (data) => {
291
- updateDOM(data)
292
- },
293
- (error) => {
294
- console.error('Failed to update DOM:', error)
295
- }
296
- )
297
-
298
- // ✅ Or use try-catch
299
- $data.subscribe((data) => {
300
- try {
301
- updateDOM(data)
302
- } catch (error) {
303
- console.error('Failed to update DOM:', error)
304
- }
305
- })
306
- ```
307
-
308
- ### Be Careful with Infinite Loops
309
-
310
- Don't update dependencies inside the same subscription:
311
-
312
- ```typescript
313
- const $count = state(0)
314
-
315
- // ❌ Infinite loop!
316
- $count.subscribe((count) => {
317
- $count.set(count + 1) // Triggers itself!
318
- })
319
-
320
- // ✅ Use a different state
321
- $count.subscribe((count) => {
322
- $doubled.set(count * 2) // Updates different state
323
- })
324
- ```
325
-
326
- ### Use Signals for Event-Only Tracking
327
-
328
- When you don't need a value, just an event:
329
-
330
- ```typescript
331
- const $refresh = signal()
332
-
333
- $refresh.subscribe(() => {
334
- fetchData()
335
- })
336
-
337
- // Trigger refresh
338
- $refresh.trigger()
339
- ```
340
-
341
- ## Common Pitfalls
342
-
343
- ### Not Disposing Subscriptions
344
-
345
- **Problem**: Subscriptions continue running even when no longer needed.
346
-
347
- ```typescript
348
- // ❌ Memory leak in component lifecycle
349
- function MyComponent() {
350
- $data.subscribe((data) => {
351
- updateView(data)
352
- })
353
- // Never disposed!
354
- }
355
-
356
- // Each component creation adds another subscription that never stops
357
- ```
358
-
359
- **Solution**: Always dispose subscriptions when done:
360
-
361
- ```typescript
362
- // ✅ Proper cleanup
363
- function MyComponent() {
364
- const effect = $data.subscribe((data) => {
365
- updateView(data)
366
- })
367
-
368
- return {
369
- cleanup: () => effect.dispose()
370
- }
371
- }
372
- ```
@@ -1,265 +0,0 @@
1
- # Maps
2
-
3
- PicoFlow provides reactive maps with **fine-grained tracking**. Instead of reacting to the entire map changing, you can react to specific operations like adding, updating, or deleting individual entries.
4
-
5
- ## When to Use Maps
6
-
7
- Use maps when you need to:
8
-
9
- - ✅ Track large collections with fine-grained updates
10
- - ✅ React to specific operations (additions, updates, deletions) separately
11
- - ✅ Optimize performance by avoiding full map re-processing
12
-
13
- Don't use maps when:
14
-
15
- - ❌ You don't need fine-grained tracking (use `state` with a Record/Map)
16
- - ❌ You need array-like operations (use `array` instead)
17
-
18
- ## Creating Maps
19
-
20
- Creating a reactive map is straightforward:
21
-
22
- ```typescript
23
- import { map } from '@ersbeth/picoflow'
24
-
25
- // Create with initial values (key is user id, value is User object)
26
- const $usersByID = map<string, User>({
27
- aliceID: { name: 'Alice', email: 'alice@example.com', role: 'admin' },
28
- bobID: { name: 'Bob', email: 'bob@example.com', role: 'user' }
29
- })
30
-
31
- // Create empty
32
- const $cache = map<string, Data>()
33
- ```
34
-
35
- The initial values (if provided) are converted to a native JavaScript `Map`.
36
-
37
- ## Using Maps
38
-
39
- Maps provide multiple ways to track changes and perform operations.
40
-
41
- ### Map Operations
42
-
43
- Maps provide three main operations: `add()`, `update()`, and `delete()`.
44
-
45
- ```typescript
46
- const $cache = map<string, Data>()
47
-
48
- // Add a new key-value pair
49
- $cache.add('user:1', userData)
50
-
51
- // Update an existing key-value pair
52
- $cache.update('user:1', updatedUserData)
53
-
54
- // Delete a key-value pair
55
- $cache.delete('user:1')
56
-
57
- // Read (reactive)
58
- $cache.subscribe((data) => {
59
- // Track entire map - returns Map<string, Data>
60
- const hasUser = data.has('user:1')
61
- if (hasUser) {
62
- const user = data.get('user:1')
63
- // Use the user data
64
- }
65
- })
66
-
67
- // Read (non-reactive)
68
- const snapshot = $cache.pick() // Returns Map<string, Data>
69
- const user = snapshot.get('user:1')
70
- ```
71
-
72
- **Important**:
73
- - `add(key, value)` throws an error if the key already exists
74
- - `update(key, value)` throws an error if the key doesn't exist
75
- - `delete(key)` throws an error if the key doesn't exist
76
-
77
-
78
- ### Whole Map Tracking
79
-
80
- Track when the entire map changes (any operation):
81
-
82
- ```typescript
83
- const $items = map<string, number>()
84
-
85
- // Initialize with values
86
- $items.add('a', 1)
87
- $items.add('b', 2)
88
-
89
- $items.subscribe((items) => {
90
- console.log('Map size:', items.size)
91
- })
92
-
93
- $items.add('c', 3) // Logs: "Map size: 3"
94
- $items.delete('a') // Logs: "Map size: 2"
95
- ```
96
-
97
- Use `.get(t)` when you need the entire map's current state. It returns a native `Map<K, V>`.
98
-
99
- ### Fine-Grained Tracking
100
-
101
- Track specific operations with `$lastAction`:
102
-
103
- ```typescript
104
- const $items = map<string, number>()
105
-
106
- $items.$lastAction.subscribe((action) => {
107
- if (!action) return
108
-
109
- switch (action.type) {
110
- case 'add':
111
- console.log(`Added ${action.key} = ${action.addedValue}`)
112
- break
113
- case 'update':
114
- console.log(`Updated ${action.key}: ${action.clearedValue} → ${action.setValue}`)
115
- break
116
- case 'delete':
117
- console.log(`Deleted ${action.key}, was: ${action.removedValue}`)
118
- break
119
- case 'clear':
120
- console.log(`Cleared ${action.clearedMap.size} entries`)
121
- break
122
- case 'set':
123
- console.log(`Set to ${action.setMap.size} entries`)
124
- break
125
- }
126
- })
127
-
128
- $items.add('a', 1) // Logs: "Added a = 1"
129
- $items.add('b', 2) // Logs: "Added b = 2"
130
- $items.update('a', 10) // Logs: "Updated a: 1 → 10"
131
- $items.delete('a') // Logs: "Deleted a, was: 10"
132
- $items.clear() // Logs: "Cleared 1 entries"
133
- ```
134
-
135
- ## Lifecycle
136
-
137
- When you create a subscription that tracks a map:
138
-
139
- 1. **Registration**: The map registers the subscription as a watcher
140
- 2. **Operation**: When you call `add()`, `update()`, `delete()`, `set()`, or `clear()`, the map updates internally
141
- 3. **Signal Update**: The `$lastAction` signal is updated with the operation details
142
- 4. **Notification**: All watching subscriptions are scheduled to run
143
- 5. **Re-execution**: Each watching subscription re-executes its function
144
-
145
- ```mermaid
146
- sequenceDiagram
147
- participant User
148
- participant Map as $users (Map)
149
- participant Signal as $lastAction
150
- participant Subscription
151
-
152
- Note over User,Subscription: 1. Setup Phase
153
- User->>Subscription: Create subscription
154
- activate Subscription
155
- Subscription->>Map: get(t)
156
- Note over Map: Register Subscription as watcher
157
- Subscription->>Signal: get(t)
158
- Note over Signal: Register Subscription as watcher
159
- Subscription->>Subscription: Execute function
160
- Note over Subscription: Initial render
161
- deactivate Subscription
162
-
163
- Note over User,Subscription: 2. Operation Phase
164
- User->>Map: add('user1', userData)
165
- activate Map
166
- Note over Map: Update internal Map
167
- Map->>Signal: "set({ type: 'add', key, addedValue })"
168
- activate Signal
169
- Note over Signal: Notify watchers
170
- Signal->>Subscription: Schedule execution
171
- deactivate Signal
172
- Map->>Map: Notify whole map watchers
173
- Map->>Subscription: Schedule execution
174
- deactivate Map
175
-
176
- activate Subscription
177
- Note over Subscription: Re-execute function
178
- Subscription->>Signal: get(t)
179
- Signal-->>Subscription: "{ type: 'add', key: 'user1', addedValue: userData }"
180
- Subscription->>Subscription: Handle new user
181
- Note over Subscription: Update UI
182
- deactivate Subscription
183
- ```
184
-
185
- ## Common Pitfalls
186
-
187
- ### Using Wrong Tracking
188
-
189
- **Problem**: Tracking the entire map when you only need specific operations.
190
-
191
- ```typescript
192
- // ❌ Tracks entire map when you only need additions
193
- $items.subscribe((items) => {
194
- const keys = Array.from(items.keys())
195
- const lastKey = keys[keys.length - 1]
196
- animateNewItem(lastKey) // Runs on ALL changes
197
- })
198
- ```
199
-
200
- **Solution**: Track only what you need:
201
-
202
- ```typescript
203
- // ✅ Track only additions
204
- $items.$lastAction.subscribe((action) => {
205
- if (action && action.type === 'add') {
206
- animateNewItem(action.key)
207
- }
208
- })
209
- ```
210
-
211
- ### Confusing Add and Update
212
-
213
- **Problem**: Using `add()` when the key might already exist, or `update()` when it might not.
214
-
215
- ```typescript
216
- // ❌ Will throw if key exists
217
- function setUser(id: number, user: User) {
218
- $users.add(id, user) // Error if user already exists!
219
- }
220
-
221
- // ❌ Will throw if key doesn't exist
222
- function changeUser(id: number, user: User) {
223
- $users.update(id, user) // Error if user doesn't exist!
224
- }
225
- ```
226
-
227
- **Solution**: Check first or handle errors:
228
-
229
- ```typescript
230
- // ✅ Check before operation
231
- function setUser(id: number, user: User) {
232
- const users = $users.pick()
233
- if (users.has(id)) {
234
- $users.update(id, user)
235
- } else {
236
- $users.add(id, user)
237
- }
238
- }
239
- ```
240
-
241
- ### Mutating Retrieved Maps
242
-
243
- **Problem**: Mutating the map directly instead of using reactive methods.
244
-
245
- ```typescript
246
- // ❌ Mutating the map directly
247
- const items = $items.pick()
248
- items.set('newKey', newValue) // Doesn't trigger reactivity!
249
-
250
- // ❌ Mutating the map directly
251
- const items = $items.get(t)
252
- items.set('newKey', newValue) // Changes internal state but doesn't notify properly!
253
- ```
254
-
255
- **Solution**: Use reactive methods:
256
-
257
- ```typescript
258
- // ✅ Use reactive methods
259
- const hasItem = $items.pick().has('newKey');
260
- if (hasItem) {
261
- $items.update('newKey', newValue)
262
- } else {
263
- $items.add('newKey', newValue)
264
- }
265
- ```