@ersbeth/picoflow 0.2.3 → 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 +557 -1099
  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 -20
  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,2313 @@
1
+ # Examples
2
+
3
+ This page contains complete, real-world examples demonstrating PicoFlow's features working together. Each example is runnable and shows best practices.
4
+
5
+ ## Example 1: Todo List Application
6
+
7
+ A complete todo list with filtering, persistence, and statistics.
8
+
9
+ ```typescript
10
+ import { state, array, derivation, effect } from '@ersbeth/picoflow'
11
+
12
+ // Types
13
+ interface Todo {
14
+ id: number
15
+ text: string
16
+ completed: boolean
17
+ createdAt: number
18
+ }
19
+
20
+ type Filter = 'all' | 'active' | 'completed'
21
+
22
+ // State
23
+ const $todos = array<Todo>([])
24
+ const $filter = state<Filter>('all')
25
+ const $newTodoText = state('')
26
+
27
+ // Derived values
28
+ const $filteredTodos = derivation((t) => {
29
+ const todos = $todos.get(t)
30
+ const filter = $filter.get(t)
31
+
32
+ switch (filter) {
33
+ case 'active':
34
+ return todos.filter(todo => !todo.completed)
35
+ case 'completed':
36
+ return todos.filter(todo => todo.completed)
37
+ default:
38
+ return todos
39
+ }
40
+ })
41
+
42
+ const $activeCount = derivation((t) => {
43
+ return $todos.get(t).filter(todo => !todo.completed).length
44
+ })
45
+
46
+ const $completedCount = derivation((t) => {
47
+ return $todos.get(t).filter(todo => todo.completed).length
48
+ })
49
+
50
+ const $totalCount = derivation((t) => {
51
+ return $todos.get(t).length
52
+ })
53
+
54
+ // Persistence
55
+ effect((t) => {
56
+ const todos = $todos.get(t)
57
+ localStorage.setItem('todos', JSON.stringify(todos))
58
+ })
59
+
60
+ // Load from storage on init
61
+ function loadTodos() {
62
+ const stored = localStorage.getItem('todos')
63
+ if (stored) {
64
+ const todos = JSON.parse(stored)
65
+ todos.forEach((todo: Todo) => $todos.push(todo))
66
+ }
67
+ }
68
+
69
+ // Operations
70
+ function addTodo() {
71
+ const text = $newTodoText.pick().trim()
72
+ if (!text) return
73
+
74
+ $todos.push({
75
+ id: Date.now(),
76
+ text,
77
+ completed: false,
78
+ createdAt: Date.now()
79
+ })
80
+
81
+ $newTodoText.set('')
82
+ }
83
+
84
+ function removeTodo(id: number) {
85
+ const todos = $todos.pick()
86
+ const index = todos.findIndex(t => t.id === id)
87
+ if (index !== -1) {
88
+ $todos.splice(index, 1)
89
+ }
90
+ }
91
+
92
+ function toggleTodo(id: number) {
93
+ const todos = $todos.pick()
94
+ const index = todos.findIndex(t => t.id === id)
95
+ if (index !== -1) {
96
+ const todo = todos[index]
97
+ $todos.splice(index, 1, {
98
+ ...todo,
99
+ completed: !todo.completed
100
+ })
101
+ }
102
+ }
103
+
104
+ function clearCompleted() {
105
+ const todos = $todos.pick()
106
+ const activeTodos = todos.filter(t => !t.completed)
107
+ $todos.clear()
108
+ activeTodos.forEach(todo => $todos.push(todo))
109
+ }
110
+
111
+ // UI Updates
112
+ effect((t) => {
113
+ const filtered = $filteredTodos.get(t)
114
+ renderTodoList(filtered)
115
+ })
116
+
117
+ effect((t) => {
118
+ const active = $activeCount.get(t)
119
+ const completed = $completedCount.get(t)
120
+ const total = $totalCount.get(t)
121
+
122
+ renderStats({
123
+ active,
124
+ completed,
125
+ total
126
+ })
127
+ })
128
+
129
+ // Initialize
130
+ loadTodos()
131
+ ```
132
+
133
+ ### Data Flow Diagram
134
+
135
+ ```mermaid
136
+ flowchart TD
137
+ A[User Input] --> B[$newTodoText]
138
+ B --> C[addTodo]
139
+ C --> D[$todos array]
140
+ D --> E[$filteredTodos]
141
+ D --> F[$activeCount]
142
+ D --> G[$completedCount]
143
+ E --> H[Render Todo List]
144
+ F --> I[Render Stats]
145
+ G --> I
146
+ D --> J[localStorage Effect]
147
+
148
+ K[$filter state] --> E
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Example 2: Form with Validation
154
+
155
+ A registration form with real-time validation and submission handling.
156
+
157
+ ```typescript
158
+ import { state, derivation, effect } from '@ersbeth/picoflow'
159
+
160
+ // Form state
161
+ const $email = state('')
162
+ const $password = state('')
163
+ const $confirmPassword = state('')
164
+ const $agreedToTerms = state(false)
165
+
166
+ // Validation derivations
167
+ const $emailError = derivation((t) => {
168
+ const email = $email.get(t)
169
+ if (!email) return 'Email is required'
170
+ if (!email.includes('@')) return 'Email must contain @'
171
+ if (email.length < 5) return 'Email is too short'
172
+ return null
173
+ })
174
+
175
+ const $passwordError = derivation((t) => {
176
+ const password = $password.get(t)
177
+ if (!password) return 'Password is required'
178
+ if (password.length < 8) return 'Password must be at least 8 characters'
179
+ if (!/[A-Z]/.test(password)) return 'Password must contain uppercase letter'
180
+ if (!/[0-9]/.test(password)) return 'Password must contain a number'
181
+ return null
182
+ })
183
+
184
+ const $confirmError = derivation((t) => {
185
+ const password = $password.get(t)
186
+ const confirm = $confirmPassword.get(t)
187
+ if (!confirm) return 'Please confirm your password'
188
+ if (password !== confirm) return 'Passwords must match'
189
+ return null
190
+ })
191
+
192
+ const $termsError = derivation((t) => {
193
+ const agreed = $agreedToTerms.get(t)
194
+ return agreed ? null : 'You must agree to the terms'
195
+ })
196
+
197
+ // Form validity
198
+ const $isValid = derivation((t) => {
199
+ return !$emailError.get(t) &&
200
+ !$passwordError.get(t) &&
201
+ !$confirmError.get(t) &&
202
+ !$termsError.get(t)
203
+ })
204
+
205
+ // Submission state
206
+ const $submitting = state(false)
207
+ const $submitError = state<string | null>(null)
208
+ const $submitted = state(false)
209
+
210
+ // Update UI with validation errors
211
+ effect((t) => {
212
+ const emailError = $emailError.get(t)
213
+ const passwordError = $passwordError.get(t)
214
+ const confirmError = $confirmError.get(t)
215
+ const termsError = $termsError.get(t)
216
+
217
+ updateValidationUI({
218
+ email: emailError,
219
+ password: passwordError,
220
+ confirm: confirmError,
221
+ terms: termsError
222
+ })
223
+ })
224
+
225
+ // Enable/disable submit button
226
+ effect((t) => {
227
+ const isValid = $isValid.get(t)
228
+ const submitting = $submitting.get(t)
229
+
230
+ const submitButton = document.getElementById('submit') as HTMLButtonElement
231
+ submitButton.disabled = !isValid || submitting
232
+ submitButton.textContent = submitting ? 'Submitting...' : 'Submit'
233
+ })
234
+
235
+ // Handle submission
236
+ async function handleSubmit() {
237
+ if (!$isValid.pick()) return
238
+
239
+ $submitting.set(true)
240
+ $submitError.set(null)
241
+
242
+ try {
243
+ const response = await fetch('/api/register', {
244
+ method: 'POST',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ body: JSON.stringify({
247
+ email: $email.pick(),
248
+ password: $password.pick()
249
+ })
250
+ })
251
+
252
+ if (!response.ok) {
253
+ throw new Error('Registration failed')
254
+ }
255
+
256
+ $submitted.set(true)
257
+ } catch (error) {
258
+ $submitError.set(error.message)
259
+ } finally {
260
+ $submitting.set(false)
261
+ }
262
+ }
263
+
264
+ // Show success message
265
+ effect((t) => {
266
+ const submitted = $submitted.get(t)
267
+ if (submitted) {
268
+ showSuccessMessage('Registration successful!')
269
+ }
270
+ })
271
+
272
+ // Show submission errors
273
+ effect((t) => {
274
+ const error = $submitError.get(t)
275
+ if (error) {
276
+ showErrorMessage(error)
277
+ }
278
+ })
279
+ ```
280
+
281
+ ### Validation Flow
282
+
283
+ ```mermaid
284
+ flowchart TD
285
+ A[$email] --> B[$emailError]
286
+ C[$password] --> D[$passwordError]
287
+ E[$confirmPassword] --> F[$confirmError]
288
+ G[$agreedToTerms] --> H[$termsError]
289
+
290
+ B --> I[$isValid]
291
+ D --> I
292
+ F --> I
293
+ H --> I
294
+
295
+ I --> J[Submit Button State]
296
+ J --> K{Valid?}
297
+ K -->|Yes| L[Enable Submit]
298
+ K -->|No| M[Disable Submit]
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Example 3: Real-time Data Dashboard
304
+
305
+ A dashboard displaying live data from a WebSocket connection with statistics.
306
+
307
+ ```typescript
308
+ import { stream, state, derivation, effect } from '@ersbeth/picoflow'
309
+
310
+ // Types
311
+ interface DataPoint {
312
+ timestamp: number
313
+ value: number
314
+ source: string
315
+ }
316
+
317
+ interface Stats {
318
+ min: number
319
+ max: number
320
+ avg: number
321
+ count: number
322
+ }
323
+
324
+ // WebSocket stream
325
+ const $liveData = stream<DataPoint>((set) => {
326
+ const ws = new WebSocket('wss://data.example.com')
327
+
328
+ ws.onopen = () => {
329
+ console.log('Connected to data stream')
330
+ $connectionStatus.set('connected')
331
+ }
332
+
333
+ ws.onmessage = (event) => {
334
+ const data = JSON.parse(event.data)
335
+ set(data)
336
+ }
337
+
338
+ ws.onerror = (error) => {
339
+ console.error('WebSocket error:', error)
340
+ $connectionStatus.set('error')
341
+ }
342
+
343
+ ws.onclose = () => {
344
+ console.log('Disconnected from data stream')
345
+ $connectionStatus.set('disconnected')
346
+ }
347
+
348
+ return () => ws.close()
349
+ })
350
+
351
+ // State
352
+ const $connectionStatus = state<'connected' | 'disconnected' | 'error'>('disconnected')
353
+ const $dataHistory = state<DataPoint[]>([])
354
+ const $maxHistorySize = state(100)
355
+
356
+ // Add new data to history
357
+ effect((t) => {
358
+ const newData = $liveData.get(t)
359
+ if (!newData) return
360
+
361
+ const history = $dataHistory.pick()
362
+ const maxSize = $maxHistorySize.pick()
363
+
364
+ const updated = [...history, newData]
365
+ if (updated.length > maxSize) {
366
+ updated.shift() // Remove oldest
367
+ }
368
+
369
+ $dataHistory.set(updated)
370
+ })
371
+
372
+ // Derived statistics
373
+ const $stats = derivation<Stats>((t) => {
374
+ const history = $dataHistory.get(t)
375
+ if (history.length === 0) {
376
+ return { min: 0, max: 0, avg: 0, count: 0 }
377
+ }
378
+
379
+ const values = history.map(d => d.value)
380
+ const sum = values.reduce((a, b) => a + b, 0)
381
+
382
+ return {
383
+ min: Math.min(...values),
384
+ max: Math.max(...values),
385
+ avg: sum / values.length,
386
+ count: history.length
387
+ }
388
+ })
389
+
390
+ // Recent data (last 10 points)
391
+ const $recentData = derivation((t) => {
392
+ const history = $dataHistory.get(t)
393
+ return history.slice(-10)
394
+ })
395
+
396
+ // Data by source
397
+ const $dataBySource = derivation((t) => {
398
+ const history = $dataHistory.get(t)
399
+ const bySource: Record<string, DataPoint[]> = {}
400
+
401
+ for (const point of history) {
402
+ if (!bySource[point.source]) {
403
+ bySource[point.source] = []
404
+ }
405
+ bySource[point.source].push(point)
406
+ }
407
+
408
+ return bySource
409
+ })
410
+
411
+ // UI Updates
412
+ effect((t) => {
413
+ const status = $connectionStatus.get(t)
414
+ updateConnectionIndicator(status)
415
+ })
416
+
417
+ effect((t) => {
418
+ const stats = $stats.get(t)
419
+ renderStatsDisplay(stats)
420
+ })
421
+
422
+ effect((t) => {
423
+ const recent = $recentData.get(t)
424
+ renderRecentDataTable(recent)
425
+ })
426
+
427
+ effect((t) => {
428
+ const history = $dataHistory.get(t)
429
+ renderChart(history)
430
+ })
431
+
432
+ // Alert on high values
433
+ effect((t) => {
434
+ const newData = $liveData.get(t)
435
+ if (newData && newData.value > 100) {
436
+ showAlert(`High value detected: ${newData.value}`)
437
+ }
438
+ })
439
+ ```
440
+
441
+ ### Dashboard Data Flow
442
+
443
+ ```mermaid
444
+ flowchart TD
445
+ A[WebSocket] -->|pushes| B[$liveData stream]
446
+ B --> C[Effect: Add to history]
447
+ C --> D[$dataHistory]
448
+ D --> E[$stats derivation]
449
+ D --> F[$recentData derivation]
450
+ D --> G[$dataBySource derivation]
451
+
452
+ E --> H[Stats Display]
453
+ F --> I[Recent Data Table]
454
+ D --> J[Chart]
455
+ G --> K[Source Breakdown]
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Example 4: Shopping Cart
461
+
462
+ A shopping cart with items, quantities, prices, and calculated totals.
463
+
464
+ ```typescript
465
+ import { map, derivation, effect, state } from '@ersbeth/picoflow'
466
+
467
+ // Types
468
+ interface Product {
469
+ id: string
470
+ name: string
471
+ price: number
472
+ image: string
473
+ }
474
+
475
+ interface CartItem {
476
+ product: Product
477
+ quantity: number
478
+ }
479
+
480
+ // Cart state
481
+ const $cart = map<string, CartItem>()
482
+ const $taxRate = state(0.08) // 8%
483
+ const $shippingThreshold = state(50) // Free shipping over $50
484
+
485
+ // Derived values
486
+ const $subtotal = derivation((t) => {
487
+ const cart = $cart.get(t)
488
+ return Object.values(cart).reduce(
489
+ (sum, item) => sum + item.product.price * item.quantity,
490
+ 0
491
+ )
492
+ })
493
+
494
+ const $tax = derivation((t) => {
495
+ const subtotal = $subtotal.get(t)
496
+ const taxRate = $taxRate.get(t)
497
+ return subtotal * taxRate
498
+ })
499
+
500
+ const $shipping = derivation((t) => {
501
+ const subtotal = $subtotal.get(t)
502
+ const threshold = $shippingThreshold.get(t)
503
+ return subtotal >= threshold ? 0 : 5.99
504
+ })
505
+
506
+ const $total = derivation((t) => {
507
+ return $subtotal.get(t) + $tax.get(t) + $shipping.get(t)
508
+ })
509
+
510
+ const $itemCount = derivation((t) => {
511
+ const cart = $cart.get(t)
512
+ return Object.values(cart).reduce(
513
+ (sum, item) => sum + item.quantity,
514
+ 0
515
+ )
516
+ })
517
+
518
+ // Operations
519
+ function addToCart(product: Product) {
520
+ const existing = $cart.pick().get(product.id)
521
+
522
+ if (existing) {
523
+ $cart.update(product.id, {
524
+ product,
525
+ quantity: existing.quantity + 1
526
+ })
527
+ } else {
528
+ $cart.add(product.id, {
529
+ product,
530
+ quantity: 1
531
+ })
532
+ }
533
+ }
534
+
535
+ function removeFromCart(productId: string) {
536
+ $cart.delete(productId)
537
+ }
538
+
539
+ function updateQuantity(productId: string, quantity: number) {
540
+ const item = $cart.pick().get(productId)
541
+ if (!item) return
542
+
543
+ if (quantity <= 0) {
544
+ $cart.delete(productId)
545
+ } else {
546
+ $cart.update(productId, {
547
+ ...item,
548
+ quantity
549
+ })
550
+ }
551
+ }
552
+
553
+ function clearCart() {
554
+ const items = Array.from($cart.pick().keys())
555
+ items.forEach(id => $cart.delete(id))
556
+ }
557
+
558
+ // UI Updates - Add items
559
+ effect((t) => {
560
+ const lastAdded = $cart.$lastAdded.get(t)
561
+ if (lastAdded) {
562
+ addCartItemToUI(lastAdded.value)
563
+ }
564
+ })
565
+
566
+ // UI Updates - Update items
567
+ effect((t) => {
568
+ const lastUpdated = $cart.$lastUpdated.get(t)
569
+ if (lastUpdated) {
570
+ updateCartItemInUI(lastUpdated.value)
571
+ }
572
+ })
573
+
574
+ // UI Updates - Remove items
575
+ effect((t) => {
576
+ const lastDeleted = $cart.$lastDeleted.get(t)
577
+ if (lastDeleted) {
578
+ removeCartItemFromUI(lastDeleted.key)
579
+ }
580
+ })
581
+
582
+ // UI Updates - Totals
583
+ effect((t) => {
584
+ const subtotal = $subtotal.get(t)
585
+ const tax = $tax.get(t)
586
+ const shipping = $shipping.get(t)
587
+ const total = $total.get(t)
588
+
589
+ renderCartTotals({
590
+ subtotal,
591
+ tax,
592
+ shipping,
593
+ total
594
+ })
595
+ })
596
+
597
+ // UI Updates - Item count badge
598
+ effect((t) => {
599
+ const count = $itemCount.get(t)
600
+ updateCartBadge(count)
601
+ })
602
+
603
+ // Persistence
604
+ effect((t) => {
605
+ const cart = $cart.get(t)
606
+ const cartObj = Object.fromEntries(cart)
607
+ localStorage.setItem('cart', JSON.stringify(cartObj))
608
+ })
609
+
610
+ // Load cart on init
611
+ function loadCart() {
612
+ const stored = localStorage.getItem('cart')
613
+ if (stored) {
614
+ const items = JSON.parse(stored)
615
+ for (const [key, item] of Object.entries(items)) {
616
+ const existing = $cart.pick().has(key)
617
+ if (existing) {
618
+ $cart.update(key, item as CartItem)
619
+ } else {
620
+ $cart.add(key, item as CartItem)
621
+ }
622
+ }
623
+ }
624
+ }
625
+
626
+ loadCart()
627
+ ```
628
+
629
+ ### Shopping Cart Flow
630
+
631
+ ```mermaid
632
+ flowchart TD
633
+ A[User Actions] --> B[addToCart/updateQuantity]
634
+ B --> C[$cart map]
635
+ C --> D[$subtotal]
636
+ D --> E[$tax]
637
+ D --> F[$shipping]
638
+ E --> G[$total]
639
+ F --> G
640
+ C --> H[$itemCount]
641
+
642
+ C --> I[$lastAdded effect]
643
+ C --> J[$lastUpdated effect]
644
+ C --> K[$lastDeleted effect]
645
+ I --> L[Add UI Item]
646
+ J --> M[Update UI Item]
647
+ K --> N[Remove UI Item]
648
+
649
+ D --> O[Render Totals]
650
+ E --> O
651
+ F --> O
652
+ G --> O
653
+ H --> P[Update Badge]
654
+ ```
655
+
656
+ ---
657
+
658
+ ## Example 5: Auto-save Feature
659
+
660
+ An auto-save system that debounces changes and handles errors.
661
+
662
+ ```typescript
663
+ import { state, effect, signal } from '@ersbeth/picoflow'
664
+
665
+ // Types
666
+ interface Document {
667
+ id: string
668
+ title: string
669
+ content: string
670
+ lastModified: number
671
+ }
672
+
673
+ type SaveStatus = 'saved' | 'saving' | 'unsaved' | 'error'
674
+
675
+ // State
676
+ const $document = state<Document>({
677
+ id: '123',
678
+ title: 'Untitled',
679
+ content: '',
680
+ lastModified: Date.now()
681
+ })
682
+
683
+ const $saveStatus = state<SaveStatus>('saved')
684
+ const $lastError = state<string | null>(null)
685
+ const $manualSave = signal()
686
+
687
+ // Debounced auto-save
688
+ let saveTimeout: ReturnType<typeof setTimeout> | null = null
689
+ const SAVE_DELAY = 2000 // 2 seconds
690
+
691
+ effect((t) => {
692
+ const doc = $document.get(t)
693
+
694
+ // Clear previous timeout
695
+ if (saveTimeout) {
696
+ clearTimeout(saveTimeout)
697
+ }
698
+
699
+ // Mark as unsaved
700
+ $saveStatus.set('unsaved')
701
+
702
+ // Debounce save
703
+ saveTimeout = setTimeout(() => {
704
+ saveDocument(doc)
705
+ }, SAVE_DELAY)
706
+ })
707
+
708
+ // Manual save trigger
709
+ effect((t) => {
710
+ $manualSave.watch(t)
711
+ const doc = $document.pick()
712
+
713
+ // Clear debounce timeout
714
+ if (saveTimeout) {
715
+ clearTimeout(saveTimeout)
716
+ saveTimeout = null
717
+ }
718
+
719
+ saveDocument(doc)
720
+ })
721
+
722
+ // Save function
723
+ async function saveDocument(doc: Document) {
724
+ $saveStatus.set('saving')
725
+ $lastError.set(null)
726
+
727
+ try {
728
+ const response = await fetch(`/api/documents/${doc.id}`, {
729
+ method: 'PUT',
730
+ headers: { 'Content-Type': 'application/json' },
731
+ body: JSON.stringify(doc)
732
+ })
733
+
734
+ if (!response.ok) {
735
+ throw new Error('Save failed')
736
+ }
737
+
738
+ $saveStatus.set('saved')
739
+ } catch (error) {
740
+ $saveStatus.set('error')
741
+ $lastError.set(error.message)
742
+
743
+ // Retry after 5 seconds
744
+ setTimeout(() => {
745
+ if ($saveStatus.pick() === 'error') {
746
+ saveDocument($document.pick())
747
+ }
748
+ }, 5000)
749
+ }
750
+ }
751
+
752
+ // UI Updates
753
+ effect((t) => {
754
+ const status = $saveStatus.get(t)
755
+ const error = $lastError.get(t)
756
+
757
+ updateSaveIndicator(status, error)
758
+ })
759
+
760
+ // Document operations
761
+ function updateTitle(title: string) {
762
+ $document.set(doc => ({
763
+ ...doc,
764
+ title,
765
+ lastModified: Date.now()
766
+ }))
767
+ }
768
+
769
+ function updateContent(content: string) {
770
+ $document.set(doc => ({
771
+ ...doc,
772
+ content,
773
+ lastModified: Date.now()
774
+ }))
775
+ }
776
+
777
+ function triggerManualSave() {
778
+ $manualSave.trigger()
779
+ }
780
+
781
+ // Warn before leaving with unsaved changes
782
+ window.addEventListener('beforeunload', (e) => {
783
+ if ($saveStatus.pick() === 'unsaved' || $saveStatus.pick() === 'saving') {
784
+ e.preventDefault()
785
+ e.returnValue = ''
786
+ }
787
+ })
788
+ ```
789
+
790
+ ### Auto-save Flow
791
+
792
+ ```mermaid
793
+ sequenceDiagram
794
+ participant User
795
+ participant Document
796
+ participant Effect
797
+ participant API
798
+
799
+ User->>Document: Edit content
800
+ Document->>Effect: Change detected
801
+ Effect->>Effect: Start 2s timer
802
+ Note over Effect: Status: unsaved
803
+
804
+ User->>Document: Edit more
805
+ Effect->>Effect: Reset timer
806
+
807
+ Note over Effect: 2s pass...
808
+ Effect->>API: Save document
809
+ Note over Effect: Status: saving
810
+ API->>Effect: Success
811
+ Note over Effect: Status: saved
812
+
813
+ User->>Effect: Manual save
814
+ Effect->>Effect: Cancel timer
815
+ Effect->>API: Save immediately
816
+ ```
817
+
818
+ ---
819
+
820
+ ## Example 6: Event Bus with Signals
821
+
822
+ A global event bus using signals to coordinate communication between independent components.
823
+
824
+ ```typescript
825
+ import { signal, effect, state } from '@ersbeth/picoflow'
826
+
827
+ // Event signals
828
+ const $userLoggedIn = signal()
829
+ const $userLoggedOut = signal()
830
+ const $dataRefreshRequested = signal()
831
+ const $notificationRequested = signal()
832
+ const $themeChanged = signal()
833
+
834
+ // Shared state
835
+ const $currentUser = state<User | null>(null)
836
+ const $theme = state<'light' | 'dark'>('light')
837
+ const $notifications = state<string[]>([])
838
+
839
+ // Component 1: Authentication Manager
840
+ effect((t) => {
841
+ $userLoggedIn.watch(t)
842
+ const user = $currentUser.pick()
843
+
844
+ if (user) {
845
+ console.log(`User ${user.name} logged in`)
846
+
847
+ // Initialize user-specific features
848
+ loadUserPreferences(user.id)
849
+ startActivityTracking(user.id)
850
+
851
+ // Request data refresh
852
+ $dataRefreshRequested.trigger()
853
+ }
854
+ })
855
+
856
+ effect((t) => {
857
+ $userLoggedOut.watch(t)
858
+
859
+ console.log('User logged out')
860
+
861
+ // Cleanup user-specific features
862
+ clearUserPreferences()
863
+ stopActivityTracking()
864
+
865
+ // Reset state
866
+ $currentUser.set(null)
867
+ })
868
+
869
+ // Component 2: Data Manager
870
+ const $userData = state<UserData | null>(null)
871
+ const $dashboardData = state<DashboardData | null>(null)
872
+
873
+ effect((t) => {
874
+ $dataRefreshRequested.watch(t)
875
+ const user = $currentUser.pick()
876
+
877
+ if (!user) return
878
+
879
+ console.log('Refreshing all data...')
880
+
881
+ // Fetch multiple data sources
882
+ Promise.all([
883
+ fetchUserData(user.id),
884
+ fetchDashboardData(user.id)
885
+ ]).then(([userData, dashboardData]) => {
886
+ $userData.set(userData)
887
+ $dashboardData.set(dashboardData)
888
+ $notificationRequested.trigger()
889
+ })
890
+ })
891
+
892
+ // Component 3: Notification Manager
893
+ const $lastNotification = state<string>('')
894
+
895
+ effect((t) => {
896
+ $notificationRequested.watch(t)
897
+ const notification = $lastNotification.pick()
898
+
899
+ if (notification) {
900
+ showToast(notification)
901
+ }
902
+ })
903
+
904
+ effect((t) => {
905
+ $userLoggedIn.watch(t)
906
+ const user = $currentUser.pick()
907
+
908
+ if (user) {
909
+ $lastNotification.set(`Welcome back, ${user.name}!`)
910
+ $notificationRequested.trigger()
911
+ }
912
+ })
913
+
914
+ // Component 4: Theme Manager
915
+ effect((t) => {
916
+ $themeChanged.watch(t)
917
+ const theme = $theme.pick()
918
+
919
+ document.body.classList.remove('light', 'dark')
920
+ document.body.classList.add(theme)
921
+
922
+ localStorage.setItem('theme', theme)
923
+
924
+ $lastNotification.set(`Theme changed to ${theme}`)
925
+ $notificationRequested.trigger()
926
+ })
927
+
928
+ // Component 5: Analytics Tracker
929
+ effect((t) => {
930
+ $userLoggedIn.watch(t)
931
+ const user = $currentUser.pick()
932
+
933
+ if (user) {
934
+ analytics.track('user_logged_in', {
935
+ userId: user.id,
936
+ timestamp: Date.now()
937
+ })
938
+ }
939
+ })
940
+
941
+ effect((t) => {
942
+ $dataRefreshRequested.watch(t)
943
+
944
+ analytics.track('data_refresh_requested', {
945
+ timestamp: Date.now()
946
+ })
947
+ })
948
+
949
+ // Public API
950
+ export const auth = {
951
+ login: (user: User) => {
952
+ $currentUser.set(user)
953
+ $userLoggedIn.trigger()
954
+ },
955
+
956
+ logout: () => {
957
+ $userLoggedOut.trigger()
958
+ }
959
+ }
960
+
961
+ export const data = {
962
+ refresh: () => {
963
+ $dataRefreshRequested.trigger()
964
+ }
965
+ }
966
+
967
+ export const theme = {
968
+ set: (newTheme: 'light' | 'dark') => {
969
+ $theme.set(newTheme)
970
+ $themeChanged.trigger()
971
+ },
972
+
973
+ toggle: () => {
974
+ const current = $theme.pick()
975
+ const newTheme = current === 'light' ? 'dark' : 'light'
976
+ $theme.set(newTheme)
977
+ $themeChanged.trigger()
978
+ }
979
+ }
980
+
981
+ export const notifications = {
982
+ show: (message: string) => {
983
+ $lastNotification.set(message)
984
+ $notificationRequested.trigger()
985
+ }
986
+ }
987
+ ```
988
+
989
+ ### Event Bus Flow
990
+
991
+ ```mermaid
992
+ sequenceDiagram
993
+ participant User
994
+ participant Auth as Auth Manager
995
+ participant Data as Data Manager
996
+ participant Notif as Notification Manager
997
+ participant Analytics
998
+
999
+ User->>Auth: login(user)
1000
+ activate Auth
1001
+ Auth->>Auth: $currentUser.set(user)
1002
+ Auth->>Auth: $userLoggedIn.trigger()
1003
+
1004
+ Note over Auth,Analytics: Signal broadcasts to all watchers
1005
+
1006
+ Auth->>Data: Watch $userLoggedIn
1007
+ activate Data
1008
+ Note over Data: Load user preferences<br/>Start tracking
1009
+ Data->>Data: $dataRefreshRequested.trigger()
1010
+ deactivate Data
1011
+
1012
+ Auth->>Notif: Watch $userLoggedIn
1013
+ activate Notif
1014
+ Note over Notif: Show welcome message
1015
+ Notif->>Notif: $notificationRequested.trigger()
1016
+ deactivate Notif
1017
+
1018
+ Auth->>Analytics: Watch $userLoggedIn
1019
+ activate Analytics
1020
+ Note over Analytics: Track login event
1021
+ deactivate Analytics
1022
+
1023
+ deactivate Auth
1024
+
1025
+ Data->>Data: Watch $dataRefreshRequested
1026
+ activate Data
1027
+ Note over Data: Fetch user data
1028
+ Data->>Notif: $notificationRequested.trigger()
1029
+ deactivate Data
1030
+ ```
1031
+
1032
+ **Key Benefits:**
1033
+
1034
+ - **Decoupled**: Components don't know about each other
1035
+ - **Scalable**: Easy to add new listeners to any signal
1036
+ - **Testable**: Each component can be tested independently
1037
+ - **Maintainable**: Clear separation of concerns
1038
+
1039
+ ---
1040
+
1041
+ ## Example 7: Manual Refresh with Signals
1042
+
1043
+ Use a signal to trigger data refresh on demand:
1044
+
1045
+ ```typescript
1046
+ import { signal, effect, state, resource } from '@ersbeth/picoflow'
1047
+
1048
+ const $manualRefresh = signal()
1049
+ const $userId = state(1)
1050
+
1051
+ // Resource that refetches when signal triggers
1052
+ const $userData = resource(
1053
+ (t) => {
1054
+ $manualRefresh.watch(t) // Watch for manual refresh
1055
+ return $userId.get(t)
1056
+ },
1057
+ (id) => fetchUser(id)
1058
+ )
1059
+
1060
+ // Display the data
1061
+ effect((t) => {
1062
+ const data = $userData.get(t)
1063
+ if (data.state === 'ready') {
1064
+ renderUserProfile(data.value)
1065
+ }
1066
+ })
1067
+
1068
+ // Trigger refresh from a button
1069
+ function onRefreshClick() {
1070
+ $manualRefresh.trigger()
1071
+ }
1072
+ ```
1073
+
1074
+ ### Manual Refresh Flow
1075
+
1076
+ ```mermaid
1077
+ sequenceDiagram
1078
+ participant User
1079
+ participant Button
1080
+ participant $manualRefresh as $manualRefresh (Signal)
1081
+ participant Resource as $userData (Resource)
1082
+ participant API
1083
+ participant Effect
1084
+
1085
+ User->>Button: Click refresh
1086
+ Button->>$manualRefresh: trigger()
1087
+
1088
+ activate $manualRefresh
1089
+ Note over $manualRefresh: Notify all watchers
1090
+ $manualRefresh->>Resource: Re-execute source function
1091
+ deactivate $manualRefresh
1092
+
1093
+ activate Resource
1094
+ Resource->>API: fetchUser(1)
1095
+ Note over Resource: State: pending
1096
+
1097
+ API-->>Resource: User data
1098
+ Note over Resource: State: ready
1099
+ Resource->>Effect: Notify
1100
+ deactivate Resource
1101
+
1102
+ activate Effect
1103
+ Effect->>Effect: renderUserProfile(data)
1104
+ deactivate Effect
1105
+ ```
1106
+
1107
+ **Use Case:** Perfect for implementing refresh buttons in dashboards, data tables, or any UI that needs manual data updates.
1108
+
1109
+ ---
1110
+
1111
+ ## Example 8: Coordinating Multiple Operations with Signals
1112
+
1113
+ Use signals to synchronize independent reactive flows:
1114
+
1115
+ ```typescript
1116
+ import { signal, effect, state } from '@ersbeth/picoflow'
1117
+
1118
+ const $syncPoint = signal()
1119
+
1120
+ const $dataA = state<Data | null>(null)
1121
+ const $dataB = state<Data | null>(null)
1122
+
1123
+ // First operation: fetch A
1124
+ effect((t) => {
1125
+ $syncPoint.watch(t)
1126
+ fetchDataA().then(data => {
1127
+ $dataA.set(data)
1128
+ })
1129
+ })
1130
+
1131
+ // Second operation: fetch B
1132
+ effect((t) => {
1133
+ $syncPoint.watch(t)
1134
+ fetchDataB().then(data => {
1135
+ $dataB.set(data)
1136
+ })
1137
+ })
1138
+
1139
+ // Combine results when both are ready
1140
+ effect((t) => {
1141
+ const a = $dataA.get(t)
1142
+ const b = $dataB.get(t)
1143
+
1144
+ if (a && b) {
1145
+ renderCombinedView(a, b)
1146
+ }
1147
+ })
1148
+
1149
+ // Trigger both fetches at once
1150
+ function refreshAll() {
1151
+ $syncPoint.trigger()
1152
+ }
1153
+ ```
1154
+
1155
+ ### Synchronization Flow
1156
+
1157
+ ```mermaid
1158
+ sequenceDiagram
1159
+ participant User
1160
+ participant Signal as $syncPoint
1161
+ participant EffectA as Effect A
1162
+ participant EffectB as Effect B
1163
+ participant CombineEffect as Combine Effect
1164
+
1165
+ User->>Signal: refreshAll()
1166
+
1167
+ activate Signal
1168
+ Note over Signal: Notify all watchers
1169
+
1170
+ par Parallel Fetches
1171
+ Signal->>EffectA: Execute
1172
+ activate EffectA
1173
+ EffectA->>EffectA: fetchDataA()
1174
+ Note over EffectA: Async operation...
1175
+
1176
+ Signal->>EffectB: Execute
1177
+ activate EffectB
1178
+ EffectB->>EffectB: fetchDataB()
1179
+ Note over EffectB: Async operation...
1180
+ end
1181
+ deactivate Signal
1182
+
1183
+ EffectA->>EffectA: $dataA.set(data)
1184
+ EffectA->>CombineEffect: Notify
1185
+ deactivate EffectA
1186
+
1187
+ EffectB->>EffectB: $dataB.set(data)
1188
+ EffectB->>CombineEffect: Notify
1189
+ deactivate EffectB
1190
+
1191
+ activate CombineEffect
1192
+ Note over CombineEffect: Both ready!
1193
+ CombineEffect->>CombineEffect: renderCombinedView(a, b)
1194
+ deactivate CombineEffect
1195
+ ```
1196
+
1197
+ **Use Case:** Ideal for coordinating multiple data fetches, synchronizing app initialization, or triggering multiple related operations from a single action.
1198
+
1199
+ ---
1200
+
1201
+ ## Example 9: Save Confirmation with Signals
1202
+
1203
+ Use signals to notify when async operations complete:
1204
+
1205
+ ```typescript
1206
+ import { signal, effect, state } from '@ersbeth/picoflow'
1207
+
1208
+ const $saveComplete = signal()
1209
+ const $saveError = signal()
1210
+ const $document = state({ title: '', content: '' })
1211
+
1212
+ // Show success message when save completes
1213
+ effect((t) => {
1214
+ $saveComplete.watch(t)
1215
+ showNotification('Document saved!', 'success')
1216
+ })
1217
+
1218
+ // Show error message on failure
1219
+ effect((t) => {
1220
+ $saveError.watch(t)
1221
+ showNotification('Save failed', 'error')
1222
+ })
1223
+
1224
+ // Save function
1225
+ async function saveDocument() {
1226
+ try {
1227
+ const doc = $document.pick()
1228
+ await api.saveDocument(doc)
1229
+ $saveComplete.trigger()
1230
+ } catch (error) {
1231
+ $saveError.trigger()
1232
+ }
1233
+ }
1234
+ ```
1235
+
1236
+ ### Save Confirmation Flow
1237
+
1238
+ ```mermaid
1239
+ sequenceDiagram
1240
+ participant User
1241
+ participant SaveFn as saveDocument()
1242
+ participant API
1243
+ participant $saveComplete as $saveComplete Signal
1244
+ participant $saveError as $saveError Signal
1245
+ participant SuccessEffect
1246
+ participant ErrorEffect
1247
+
1248
+ User->>SaveFn: Call saveDocument()
1249
+ activate SaveFn
1250
+
1251
+ SaveFn->>API: POST document
1252
+
1253
+ alt Save Success
1254
+ API-->>SaveFn: 200 OK
1255
+ SaveFn->>$saveComplete: trigger()
1256
+ activate $saveComplete
1257
+ $saveComplete->>SuccessEffect: Execute
1258
+ activate SuccessEffect
1259
+ Note over SuccessEffect: Show success notification
1260
+ deactivate SuccessEffect
1261
+ deactivate $saveComplete
1262
+ else Save Failure
1263
+ API-->>SaveFn: 500 Error
1264
+ SaveFn->>$saveError: trigger()
1265
+ activate $saveError
1266
+ $saveError->>ErrorEffect: Execute
1267
+ activate ErrorEffect
1268
+ Note over ErrorEffect: Show error notification
1269
+ deactivate ErrorEffect
1270
+ deactivate $saveError
1271
+ end
1272
+
1273
+ deactivate SaveFn
1274
+ ```
1275
+
1276
+ **Use Case:** Great for handling async operation outcomes, showing notifications, or triggering cleanup after operations complete.
1277
+
1278
+ ---
1279
+
1280
+ ## Example 10: Mathematical Calculations with Derivations
1281
+
1282
+ Use derivations to compute statistics from reactive data:
1283
+
1284
+ ```typescript
1285
+ import { state, derivation, effect } from '@ersbeth/picoflow'
1286
+
1287
+ const $numbers = state([1, 2, 3, 4, 5])
1288
+
1289
+ const $sum = derivation((t) => {
1290
+ return $numbers.get(t).reduce((a, b) => a + b, 0)
1291
+ })
1292
+
1293
+ const $average = derivation((t) => {
1294
+ const sum = $sum.get(t)
1295
+ const count = $numbers.pick().length
1296
+ return sum / count
1297
+ })
1298
+
1299
+ const $max = derivation((t) => {
1300
+ return Math.max(...$numbers.get(t))
1301
+ })
1302
+
1303
+ const $min = derivation((t) => {
1304
+ return Math.min(...$numbers.get(t))
1305
+ })
1306
+
1307
+ // Display statistics
1308
+ effect((t) => {
1309
+ console.log('Sum:', $sum.get(t))
1310
+ console.log('Average:', $average.get(t))
1311
+ console.log('Max:', $max.get(t))
1312
+ console.log('Min:', $min.get(t))
1313
+ })
1314
+
1315
+ // Update numbers
1316
+ $numbers.set([10, 20, 30, 40, 50])
1317
+ // All statistics update automatically
1318
+ ```
1319
+
1320
+ **Use Case:** Real-time statistics dashboards, data analysis tools, financial calculations.
1321
+
1322
+ ---
1323
+
1324
+ ## Example 11: Filtered Lists with Derivations
1325
+
1326
+ Implement dynamic filtering with chained derivations:
1327
+
1328
+ ```typescript
1329
+ interface Todo {
1330
+ id: number
1331
+ text: string
1332
+ completed: boolean
1333
+ }
1334
+
1335
+ const $todos = state<Todo[]>([
1336
+ { id: 1, text: 'Learn PicoFlow', completed: true },
1337
+ { id: 2, text: 'Build app', completed: false },
1338
+ { id: 3, text: 'Deploy', completed: false }
1339
+ ])
1340
+
1341
+ const $filter = state<'all' | 'active' | 'completed'>('all')
1342
+
1343
+ const $filteredTodos = derivation((t) => {
1344
+ const todos = $todos.get(t)
1345
+ const filter = $filter.get(t)
1346
+
1347
+ switch (filter) {
1348
+ case 'active':
1349
+ return todos.filter(todo => !todo.completed)
1350
+ case 'completed':
1351
+ return todos.filter(todo => todo.completed)
1352
+ default:
1353
+ return todos
1354
+ }
1355
+ })
1356
+
1357
+ const $activeCount = derivation((t) => {
1358
+ return $todos.get(t).filter(todo => !todo.completed).length
1359
+ })
1360
+
1361
+ const $completedCount = derivation((t) => {
1362
+ return $todos.get(t).filter(todo => todo.completed).length
1363
+ })
1364
+
1365
+ // Display filtered todos
1366
+ effect((t) => {
1367
+ const filtered = $filteredTodos.get(t)
1368
+ const active = $activeCount.get(t)
1369
+ const completed = $completedCount.get(t)
1370
+
1371
+ console.log(`Showing ${filtered.length} todos`)
1372
+ console.log(`${active} active, ${completed} completed`)
1373
+ renderTodoList(filtered)
1374
+ })
1375
+ ```
1376
+
1377
+ **Use Case:** Todo lists, task managers, filtered data grids with multiple view modes.
1378
+
1379
+ ---
1380
+
1381
+ ## Example 12: Formatted Values with Derivations
1382
+
1383
+ Use derivations for internationalization and formatting:
1384
+
1385
+ ```typescript
1386
+ const $amount = state(1234.56)
1387
+ const $currency = state('USD')
1388
+
1389
+ const $formatted = derivation((t) => {
1390
+ const amount = $amount.get(t)
1391
+ const currency = $currency.get(t)
1392
+
1393
+ return new Intl.NumberFormat('en-US', {
1394
+ style: 'currency',
1395
+ currency: currency
1396
+ }).format(amount)
1397
+ })
1398
+
1399
+ effect((t) => {
1400
+ console.log($formatted.get(t))
1401
+ })
1402
+
1403
+ $amount.set(9999.99) // "$9,999.99"
1404
+ $currency.set('EUR') // "€9,999.99"
1405
+ $currency.set('JPY') // "¥9,999"
1406
+ ```
1407
+
1408
+ **Use Case:** Multi-currency applications, internationalized interfaces, dynamic formatting based on user locale.
1409
+
1410
+ ---
1411
+
1412
+ ## Example 13: Derived UI State
1413
+
1414
+ Create a unified UI state from multiple sources:
1415
+
1416
+ ```typescript
1417
+ const $user = state<User | null>(null)
1418
+ const $loading = state(false)
1419
+ const $error = state<string | null>(null)
1420
+
1421
+ const $uiState = derivation((t) => {
1422
+ const loading = $loading.get(t)
1423
+ const error = $error.get(t)
1424
+ const user = $user.get(t)
1425
+
1426
+ if (loading) return { type: 'loading' as const }
1427
+ if (error) return { type: 'error' as const, message: error }
1428
+ if (user) return { type: 'success' as const, user }
1429
+ return { type: 'idle' as const }
1430
+ })
1431
+
1432
+ effect((t) => {
1433
+ const state = $uiState.get(t)
1434
+
1435
+ switch (state.type) {
1436
+ case 'loading':
1437
+ showSpinner()
1438
+ break
1439
+ case 'error':
1440
+ showError(state.message)
1441
+ break
1442
+ case 'success':
1443
+ showUser(state.user)
1444
+ break
1445
+ case 'idle':
1446
+ showLogin()
1447
+ break
1448
+ }
1449
+ })
1450
+
1451
+ // Simulate async operation
1452
+ async function loadUser() {
1453
+ $loading.set(true)
1454
+ $error.set(null)
1455
+
1456
+ try {
1457
+ const user = await fetchUser()
1458
+ $user.set(user)
1459
+ } catch (err) {
1460
+ $error.set(err.message)
1461
+ } finally {
1462
+ $loading.set(false)
1463
+ }
1464
+ }
1465
+ ```
1466
+
1467
+ **Use Case:** State machines for async operations, loading screens, error boundaries.
1468
+
1469
+ ---
1470
+
1471
+ ## Example 14: Shopping Cart with Derived Totals
1472
+
1473
+ Calculate cart totals reactively with multiple derivations:
1474
+
1475
+ ```typescript
1476
+ interface CartItem {
1477
+ id: string
1478
+ name: string
1479
+ price: number
1480
+ quantity: number
1481
+ }
1482
+
1483
+ const $cartItems = state<CartItem[]>([])
1484
+
1485
+ const $subtotal = derivation((t) => {
1486
+ return $cartItems.get(t).reduce((sum, item) => {
1487
+ return sum + (item.price * item.quantity)
1488
+ }, 0)
1489
+ })
1490
+
1491
+ const $tax = derivation((t) => {
1492
+ return $subtotal.get(t) * 0.08 // 8% tax
1493
+ })
1494
+
1495
+ const $shipping = derivation((t) => {
1496
+ const subtotal = $subtotal.get(t)
1497
+ return subtotal > 50 ? 0 : 5.99 // Free shipping over $50
1498
+ })
1499
+
1500
+ const $total = derivation((t) => {
1501
+ return $subtotal.get(t) + $tax.get(t) + $shipping.get(t)
1502
+ })
1503
+
1504
+ // Display in UI
1505
+ effect((t) => {
1506
+ const subtotal = $subtotal.get(t)
1507
+ const tax = $tax.get(t)
1508
+ const shipping = $shipping.get(t)
1509
+ const total = $total.get(t)
1510
+
1511
+ document.getElementById('subtotal').textContent = `$${subtotal.toFixed(2)}`
1512
+ document.getElementById('tax').textContent = `$${tax.toFixed(2)}`
1513
+ document.getElementById('shipping').textContent = `$${shipping.toFixed(2)}`
1514
+ document.getElementById('total').textContent = `$${total.toFixed(2)}`
1515
+ })
1516
+
1517
+ // Add items to cart
1518
+ function addItem(item: CartItem) {
1519
+ $cartItems.set(items => [...items, item])
1520
+ }
1521
+ ```
1522
+
1523
+ **Use Case:** E-commerce carts, order summaries, price calculators with multiple components.
1524
+
1525
+ ---
1526
+
1527
+ ## Example 15: Product Search and Filter
1528
+
1529
+ Multi-criteria filtering with derivations:
1530
+
1531
+ ```typescript
1532
+ interface Product {
1533
+ id: number
1534
+ name: string
1535
+ category: string
1536
+ price: number
1537
+ inStock: boolean
1538
+ }
1539
+
1540
+ const $products = state<Product[]>([
1541
+ { id: 1, name: 'Laptop', category: 'Electronics', price: 999, inStock: true },
1542
+ { id: 2, name: 'Mouse', category: 'Electronics', price: 25, inStock: true },
1543
+ { id: 3, name: 'Desk', category: 'Furniture', price: 300, inStock: false },
1544
+ // ... more products
1545
+ ])
1546
+
1547
+ const $searchQuery = state('')
1548
+ const $selectedCategory = state<string | null>(null)
1549
+ const $showOutOfStock = state(false)
1550
+
1551
+ const $filteredProducts = derivation((t) => {
1552
+ let products = $products.get(t)
1553
+ const query = $searchQuery.get(t).toLowerCase()
1554
+ const category = $selectedCategory.get(t)
1555
+ const showOutOfStock = $showOutOfStock.get(t)
1556
+
1557
+ // Apply filters
1558
+ if (query) {
1559
+ products = products.filter(p =>
1560
+ p.name.toLowerCase().includes(query)
1561
+ )
1562
+ }
1563
+
1564
+ if (category) {
1565
+ products = products.filter(p => p.category === category)
1566
+ }
1567
+
1568
+ if (!showOutOfStock) {
1569
+ products = products.filter(p => p.inStock)
1570
+ }
1571
+
1572
+ return products
1573
+ })
1574
+
1575
+ const $resultCount = derivation((t) => {
1576
+ return $filteredProducts.get(t).length
1577
+ })
1578
+
1579
+ // Display results
1580
+ effect((t) => {
1581
+ const products = $filteredProducts.get(t)
1582
+ const count = $resultCount.get(t)
1583
+
1584
+ console.log(`Found ${count} products`)
1585
+ renderProducts(products)
1586
+ })
1587
+
1588
+ // Update filters
1589
+ function updateSearch(query: string) {
1590
+ $searchQuery.set(query)
1591
+ }
1592
+
1593
+ function selectCategory(category: string | null) {
1594
+ $selectedCategory.set(category)
1595
+ }
1596
+
1597
+ function toggleOutOfStock() {
1598
+ $showOutOfStock.set(value => !value)
1599
+ }
1600
+ ```
1601
+
1602
+ **Use Case:** Product catalogs, data tables with filters, search interfaces with multiple criteria.
1603
+
1604
+ ---
1605
+
1606
+ ## Example 16: Form Validation with Derivations
1607
+
1608
+ Real-time form validation using derivations:
1609
+
1610
+ ```typescript
1611
+ const $email = state('')
1612
+ const $password = state('')
1613
+ const $confirmPassword = state('')
1614
+
1615
+ const $emailError = derivation((t) => {
1616
+ const email = $email.get(t)
1617
+ if (!email) return null
1618
+ if (!email.includes('@')) return 'Email must contain @'
1619
+ if (email.length < 5) return 'Email too short'
1620
+ return null
1621
+ })
1622
+
1623
+ const $passwordError = derivation((t) => {
1624
+ const password = $password.get(t)
1625
+ if (!password) return null
1626
+ if (password.length < 8) return 'Password must be at least 8 characters'
1627
+ return null
1628
+ })
1629
+
1630
+ const $confirmError = derivation((t) => {
1631
+ const password = $password.get(t)
1632
+ const confirm = $confirmPassword.get(t)
1633
+ if (!confirm) return null
1634
+ if (password !== confirm) return 'Passwords must match'
1635
+ return null
1636
+ })
1637
+
1638
+ const $isValid = derivation((t) => {
1639
+ return !$emailError.get(t) &&
1640
+ !$passwordError.get(t) &&
1641
+ !$confirmError.get(t)
1642
+ })
1643
+
1644
+ // Update submit button
1645
+ effect((t) => {
1646
+ const valid = $isValid.get(t)
1647
+ const submitButton = document.getElementById('submit') as HTMLButtonElement
1648
+ submitButton.disabled = !valid
1649
+ })
1650
+
1651
+ // Show email error
1652
+ effect((t) => {
1653
+ const error = $emailError.get(t)
1654
+ const errorEl = document.getElementById('email-error')
1655
+ errorEl.textContent = error || ''
1656
+ errorEl.style.display = error ? 'block' : 'none'
1657
+ })
1658
+
1659
+ // Show password error
1660
+ effect((t) => {
1661
+ const error = $passwordError.get(t)
1662
+ const errorEl = document.getElementById('password-error')
1663
+ errorEl.textContent = error || ''
1664
+ errorEl.style.display = error ? 'block' : 'none'
1665
+ })
1666
+
1667
+ // Show confirm error
1668
+ effect((t) => {
1669
+ const error = $confirmError.get(t)
1670
+ const errorEl = document.getElementById('confirm-error')
1671
+ errorEl.textContent = error || ''
1672
+ errorEl.style.display = error ? 'block' : 'none'
1673
+ })
1674
+
1675
+ // Form submission
1676
+ async function handleSubmit() {
1677
+ if (!$isValid.pick()) return
1678
+
1679
+ const email = $email.pick()
1680
+ const password = $password.pick()
1681
+
1682
+ await registerUser(email, password)
1683
+ }
1684
+ ```
1685
+
1686
+ **Use Case:** Registration forms, settings panels, any form requiring real-time validation feedback.
1687
+
1688
+ ---
1689
+
1690
+ ## Example 17: Console Logging with Effects
1691
+
1692
+ Simple reactive console logging:
1693
+
1694
+ ```typescript
1695
+ import { state, effect } from '@ersbeth/picoflow'
1696
+
1697
+ const $temperature = state(20)
1698
+
1699
+ effect((t) => {
1700
+ const temp = $temperature.get(t)
1701
+ console.log(`Temperature: ${temp}°C`)
1702
+ })
1703
+
1704
+ $temperature.set(25) // Logs: "Temperature: 25°C"
1705
+ ```
1706
+
1707
+ **Use Case:** Development debugging, tracking state changes, monitoring application behavior.
1708
+
1709
+ ---
1710
+
1711
+ ## Example 18: Document Title Updates
1712
+
1713
+ Update the browser document title reactively:
1714
+
1715
+ ```typescript
1716
+ import { state, derivation, effect } from '@ersbeth/picoflow'
1717
+
1718
+ const $pageName = state('Home')
1719
+ const $unreadCount = state(0)
1720
+
1721
+ const $title = derivation((t) => {
1722
+ const page = $pageName.get(t)
1723
+ const unread = $unreadCount.get(t)
1724
+
1725
+ return unread > 0
1726
+ ? `(${unread}) ${page}`
1727
+ : page
1728
+ })
1729
+
1730
+ effect((t) => {
1731
+ document.title = $title.get(t)
1732
+ })
1733
+
1734
+ $pageName.set('Messages') // Title: "Messages"
1735
+ $unreadCount.set(5) // Title: "(5) Messages"
1736
+ ```
1737
+
1738
+ **Use Case:** Dynamic page titles, notification badges in browser tabs, user feedback.
1739
+
1740
+ ---
1741
+
1742
+ ## Example 19: Form Validation Feedback
1743
+
1744
+ Real-time form validation with visual feedback:
1745
+
1746
+ ```typescript
1747
+ import { state, derivation, effect } from '@ersbeth/picoflow'
1748
+
1749
+ const $email = state('')
1750
+ const $password = state('')
1751
+
1752
+ const $errors = derivation((t) => {
1753
+ const email = $email.get(t)
1754
+ const password = $password.get(t)
1755
+
1756
+ const errors = []
1757
+ if (email && !email.includes('@')) {
1758
+ errors.push('Invalid email')
1759
+ }
1760
+ if (password && password.length < 8) {
1761
+ errors.push('Password too short')
1762
+ }
1763
+ return errors
1764
+ })
1765
+
1766
+ // Update error display
1767
+ effect((t) => {
1768
+ const errors = $errors.get(t)
1769
+ const errorDiv = document.getElementById('errors')
1770
+ errorDiv.innerHTML = errors.map(e => `<div>${e}</div>`).join('')
1771
+ })
1772
+ ```
1773
+
1774
+ **Use Case:** Real-time form validation, user input feedback, accessibility improvements.
1775
+
1776
+ ---
1777
+
1778
+ ## Example 20: Auto-save with Debouncing
1779
+
1780
+ Automatically save document changes with debouncing:
1781
+
1782
+ ```typescript
1783
+ import { state, signal, effect } from '@ersbeth/picoflow'
1784
+
1785
+ const $document = state({ title: '', content: '' })
1786
+ const $saveStatus = state<'saved' | 'saving' | 'unsaved'>('saved')
1787
+
1788
+ // Debounced save effect (simplified)
1789
+ let saveTimeout: number | null = null
1790
+
1791
+ effect((t) => {
1792
+ const doc = $document.get(t)
1793
+
1794
+ // Clear previous timeout
1795
+ if (saveTimeout) clearTimeout(saveTimeout)
1796
+
1797
+ $saveStatus.set('unsaved')
1798
+
1799
+ // Debounce save
1800
+ saveTimeout = setTimeout(() => {
1801
+ $saveStatus.set('saving')
1802
+ saveToServer(doc).then(() => {
1803
+ $saveStatus.set('saved')
1804
+ })
1805
+ }, 1000)
1806
+ })
1807
+
1808
+ // Show save status
1809
+ effect((t) => {
1810
+ const status = $saveStatus.get(t)
1811
+ document.getElementById('status').textContent = status
1812
+ })
1813
+ ```
1814
+
1815
+ **Use Case:** Text editors, draft saving, preventing data loss, user experience improvements.
1816
+
1817
+ ---
1818
+
1819
+ ## Example 21: User Profile Display
1820
+
1821
+ Complete user profile management with multiple effects:
1822
+
1823
+ ```typescript
1824
+ import { state, derivation, effect } from '@ersbeth/picoflow'
1825
+
1826
+ // State
1827
+ const $user = state({
1828
+ firstName: 'Alice',
1829
+ lastName: 'Smith',
1830
+ email: 'alice@example.com'
1831
+ })
1832
+
1833
+ const $editMode = state(false)
1834
+
1835
+ // Derived values
1836
+ const $fullName = derivation((t) => {
1837
+ const user = $user.get(t)
1838
+ return `${user.firstName} ${user.lastName}`
1839
+ })
1840
+
1841
+ // Effects
1842
+ effect((t) => {
1843
+ const name = $fullName.get(t)
1844
+ document.getElementById('display-name').textContent = name
1845
+ })
1846
+
1847
+ effect((t) => {
1848
+ const user = $user.get(t)
1849
+ document.getElementById('email').textContent = user.email
1850
+ })
1851
+
1852
+ effect((t) => {
1853
+ const editMode = $editMode.get(t)
1854
+ document.body.classList.toggle('edit-mode', editMode)
1855
+ })
1856
+
1857
+ effect((t) => {
1858
+ const user = $user.get(t)
1859
+ localStorage.setItem('user', JSON.stringify(user))
1860
+ })
1861
+
1862
+ // Update functions
1863
+ function updateFirstName(name: string) {
1864
+ $user.set(user => ({ ...user, firstName: name }))
1865
+ }
1866
+
1867
+ function toggleEditMode() {
1868
+ $editMode.set(mode => !mode)
1869
+ }
1870
+ ```
1871
+
1872
+ **Use Case:** User profiles, settings panels, account management interfaces.
1873
+
1874
+ ---
1875
+
1876
+ ## Example 22: User List Management with Reactive Maps
1877
+
1878
+ A user management system using fine-grained map tracking to efficiently handle additions, updates, and deletions.
1879
+
1880
+ ```typescript
1881
+ import { map, effect, derivation } from '@ersbeth/picoflow'
1882
+
1883
+ interface User {
1884
+ id: number
1885
+ name: string
1886
+ status: 'online' | 'offline'
1887
+ }
1888
+
1889
+ const $users = map<number, User>()
1890
+
1891
+ // Add user to UI when added
1892
+ effect((t) => {
1893
+ const lastAdded = $users.$lastAdded.get(t)
1894
+ if (lastAdded) {
1895
+ addUserToDOM(lastAdded.value)
1896
+ }
1897
+ })
1898
+
1899
+ // Update user in UI when updated
1900
+ effect((t) => {
1901
+ const lastUpdated = $users.$lastUpdated.get(t)
1902
+ if (lastUpdated) {
1903
+ updateUserInDOM(lastUpdated.value)
1904
+ }
1905
+ })
1906
+
1907
+ // Remove user from UI when deleted
1908
+ effect((t) => {
1909
+ const lastDeleted = $users.$lastDeleted.get(t)
1910
+ if (lastDeleted) {
1911
+ removeUserFromDOM(lastDeleted.key)
1912
+ }
1913
+ })
1914
+
1915
+ // Update count
1916
+ const $userCount = derivation((t) => {
1917
+ const users = $users.get(t)
1918
+ return users.size
1919
+ })
1920
+
1921
+ // API calls
1922
+ function addUser(user: User) {
1923
+ $users.add(user.id, user)
1924
+ }
1925
+
1926
+ function updateUser(user: User) {
1927
+ $users.update(user.id, user)
1928
+ }
1929
+
1930
+ function removeUser(userId: number) {
1931
+ $users.delete(userId)
1932
+ }
1933
+ ```
1934
+
1935
+ **Use Case:** User management interfaces, contact lists, real-time collaboration tools where individual user changes need to be tracked separately.
1936
+
1937
+ ---
1938
+
1939
+ ## Example 23: Shopping Cart with Reactive Maps
1940
+
1941
+ A shopping cart implementation using fine-grained map tracking for efficient UI updates.
1942
+
1943
+ ```typescript
1944
+ import { map, derivation, effect } from '@ersbeth/picoflow'
1945
+
1946
+ interface CartItem {
1947
+ id: string
1948
+ name: string
1949
+ price: number
1950
+ quantity: number
1951
+ }
1952
+
1953
+ const $cart = map<string, CartItem>()
1954
+
1955
+ // Update UI only for added items
1956
+ effect((t) => {
1957
+ const lastAdded = $cart.$lastAdded.get(t)
1958
+ if (lastAdded) {
1959
+ addCartItemToUI(lastAdded.value)
1960
+ }
1961
+ })
1962
+
1963
+ // Update UI only for updated items
1964
+ effect((t) => {
1965
+ const lastUpdated = $cart.$lastUpdated.get(t)
1966
+ if (lastUpdated) {
1967
+ updateCartItemInUI(lastUpdated.value)
1968
+ }
1969
+ })
1970
+
1971
+ // Remove item from UI
1972
+ effect((t) => {
1973
+ const lastDeleted = $cart.$lastDeleted.get(t)
1974
+ if (lastDeleted) {
1975
+ removeCartItemFromUI(lastDeleted.key)
1976
+ }
1977
+ })
1978
+
1979
+ // Computed totals
1980
+ const $subtotal = derivation((t) => {
1981
+ const cart = $cart.get(t)
1982
+ let sum = 0
1983
+ for (const item of cart.values()) {
1984
+ sum += item.price * item.quantity
1985
+ }
1986
+ return sum
1987
+ })
1988
+
1989
+ const $itemCount = derivation((t) => {
1990
+ const cart = $cart.get(t)
1991
+ let count = 0
1992
+ for (const item of cart.values()) {
1993
+ count += item.quantity
1994
+ }
1995
+ return count
1996
+ })
1997
+
1998
+ // Cart operations
1999
+ function addToCart(item: CartItem) {
2000
+ const existing = $cart.pick().get(item.id)
2001
+ if (existing) {
2002
+ $cart.update(item.id, {
2003
+ ...existing,
2004
+ quantity: existing.quantity + 1
2005
+ })
2006
+ } else {
2007
+ $cart.add(item.id, item)
2008
+ }
2009
+ }
2010
+
2011
+ function removeFromCart(itemId: string) {
2012
+ $cart.delete(itemId)
2013
+ }
2014
+
2015
+ function updateQuantity(itemId: string, quantity: number) {
2016
+ const item = $cart.pick().get(itemId)
2017
+ if (item) {
2018
+ if (quantity <= 0) {
2019
+ $cart.delete(itemId)
2020
+ } else {
2021
+ $cart.update(itemId, { ...item, quantity })
2022
+ }
2023
+ }
2024
+ }
2025
+ ```
2026
+
2027
+ **Use Case:** E-commerce shopping carts, order management, inventory tracking with granular updates.
2028
+
2029
+ ---
2030
+
2031
+ ## Example 24: Entity Cache with LRU
2032
+
2033
+ An LRU (Least Recently Used) cache implementation using reactive maps and arrays.
2034
+
2035
+ ```typescript
2036
+ import { map, array, effect } from '@ersbeth/picoflow'
2037
+
2038
+ const MAX_CACHE_SIZE = 100
2039
+
2040
+ const $entityCache = map<string, any>()
2041
+ const $accessOrder = array<string>([])
2042
+
2043
+ // Track cache additions
2044
+ effect((t) => {
2045
+ const lastAdded = $entityCache.$lastAdded.get(t)
2046
+ if (!lastAdded) return
2047
+
2048
+ // Update access order
2049
+ const order = $accessOrder.pick()
2050
+ const existingIndex = order.indexOf(lastAdded.key)
2051
+
2052
+ if (existingIndex !== -1) {
2053
+ $accessOrder.splice(existingIndex, 1)
2054
+ }
2055
+
2056
+ $accessOrder.push(lastAdded.key)
2057
+
2058
+ // Evict oldest if over limit
2059
+ if ($accessOrder.pick().length > MAX_CACHE_SIZE) {
2060
+ const oldest = $accessOrder.shift()
2061
+ if (oldest) {
2062
+ $entityCache.delete(oldest)
2063
+ }
2064
+ }
2065
+ })
2066
+
2067
+ // Track cache updates (move to end of access order)
2068
+ effect((t) => {
2069
+ const lastUpdated = $entityCache.$lastUpdated.get(t)
2070
+ if (!lastUpdated) return
2071
+
2072
+ const order = $accessOrder.pick()
2073
+ const index = order.indexOf(lastUpdated.key)
2074
+ if (index !== -1) {
2075
+ $accessOrder.splice(index, 1)
2076
+ $accessOrder.push(lastUpdated.key)
2077
+ }
2078
+ })
2079
+
2080
+ function getEntity(id: string) {
2081
+ const cached = $entityCache.pick().get(id)
2082
+ if (cached) {
2083
+ // Update access order
2084
+ const order = $accessOrder.pick()
2085
+ const index = order.indexOf(id)
2086
+ if (index !== -1) {
2087
+ $accessOrder.splice(index, 1)
2088
+ $accessOrder.push(id)
2089
+ }
2090
+ return cached
2091
+ }
2092
+
2093
+ // Fetch and cache
2094
+ return fetchEntity(id).then(entity => {
2095
+ const existing = $entityCache.pick().has(id)
2096
+ if (existing) {
2097
+ $entityCache.update(id, entity)
2098
+ } else {
2099
+ $entityCache.add(id, entity)
2100
+ }
2101
+ return entity
2102
+ })
2103
+ }
2104
+ ```
2105
+
2106
+ **Use Case:** API response caching, resource management, memory-efficient data stores with automatic eviction.
2107
+
2108
+ ---
2109
+
2110
+ ## Example 25: Todo List with Granular Array Updates
2111
+
2112
+ A todo list implementation using reactive arrays with fine-grained tracking for animations and efficient updates.
2113
+
2114
+ ```typescript
2115
+ import { array, derivation, effect } from '@ersbeth/picoflow'
2116
+
2117
+ interface Todo {
2118
+ id: number
2119
+ text: string
2120
+ completed: boolean
2121
+ }
2122
+
2123
+ const $todos = array<Todo>([])
2124
+
2125
+ // Animate additions
2126
+ effect((t) => {
2127
+ const action = $todos.$lastAction.get(t)
2128
+ if (action && action.type === 'push') {
2129
+ animateNewTodo(action.item)
2130
+ }
2131
+ })
2132
+
2133
+ // Animate removals
2134
+ effect((t) => {
2135
+ const action = $todos.$lastAction.get(t)
2136
+ if (action && action.type === 'splice') {
2137
+ animateRemovedTodos(action.start, action.deleteCount)
2138
+ }
2139
+ })
2140
+
2141
+ // Computed counts
2142
+ const $totalCount = derivation((t) => {
2143
+ return $todos.get(t).length
2144
+ })
2145
+
2146
+ const $completedCount = derivation((t) => {
2147
+ return $todos.get(t).filter(t => t.completed).length
2148
+ })
2149
+
2150
+ const $activeCount = derivation((t) => {
2151
+ return $todos.get(t).filter(t => !t.completed).length
2152
+ })
2153
+
2154
+ // Todo operations
2155
+ function addTodo(text: string) {
2156
+ $todos.push({
2157
+ id: Date.now(),
2158
+ text,
2159
+ completed: false
2160
+ })
2161
+ }
2162
+
2163
+ function removeTodo(id: number) {
2164
+ const todos = $todos.pick()
2165
+ const index = todos.findIndex(t => t.id === id)
2166
+ if (index !== -1) {
2167
+ $todos.splice(index, 1)
2168
+ }
2169
+ }
2170
+
2171
+ function toggleTodo(id: number) {
2172
+ const todos = $todos.pick()
2173
+ const index = todos.findIndex(t => t.id === id)
2174
+ if (index !== -1) {
2175
+ const todo = todos[index]
2176
+ $todos.splice(index, 1, {
2177
+ ...todo,
2178
+ completed: !todo.completed
2179
+ })
2180
+ }
2181
+ }
2182
+ ```
2183
+
2184
+ **Use Case:** Todo applications, task management systems, lists with animations and granular UI updates.
2185
+
2186
+ ---
2187
+
2188
+ ## Example 26: Collaborative Task Board with Arrays
2189
+
2190
+ A collaborative task board using multiple reactive arrays for different status columns with fine-grained tracking.
2191
+
2192
+ ```typescript
2193
+ import { array, derivation, effect } from '@ersbeth/picoflow'
2194
+
2195
+ interface Task {
2196
+ id: string
2197
+ title: string
2198
+ assignee: string
2199
+ status: 'todo' | 'doing' | 'done'
2200
+ }
2201
+
2202
+ // Tasks by status column
2203
+ const $todoTasks = array<Task>([])
2204
+ const $doingTasks = array<Task>([])
2205
+ const $doneTasks = array<Task>([])
2206
+
2207
+ // Track task additions for animations
2208
+ effect((t) => {
2209
+ const action = $todoTasks.$lastAction.get(t)
2210
+ if (action && action.type === 'push') {
2211
+ animateTaskAddition('todo', action.item)
2212
+ }
2213
+ })
2214
+
2215
+ effect((t) => {
2216
+ const action = $doingTasks.$lastAction.get(t)
2217
+ if (action && action.type === 'push') {
2218
+ animateTaskAddition('doing', action.item)
2219
+ }
2220
+ })
2221
+
2222
+ effect((t) => {
2223
+ const action = $doneTasks.$lastAction.get(t)
2224
+ if (action && action.type === 'push') {
2225
+ animateTaskAddition('done', action.item)
2226
+ }
2227
+ })
2228
+
2229
+ // Track removals for cleanup
2230
+ effect((t) => {
2231
+ const action = $todoTasks.$lastAction.get(t)
2232
+ if (action && action.type === 'splice') {
2233
+ animateTaskRemoval('todo', action.start)
2234
+ }
2235
+ })
2236
+
2237
+ // Computed statistics
2238
+ const $totalTasks = derivation((t) => {
2239
+ return $todoTasks.get(t).length +
2240
+ $doingTasks.get(t).length +
2241
+ $doneTasks.get(t).length
2242
+ })
2243
+
2244
+ const $completionRate = derivation((t) => {
2245
+ const total = $totalTasks.get(t)
2246
+ const done = $doneTasks.get(t).length
2247
+ return total > 0 ? (done / total) * 100 : 0
2248
+ })
2249
+
2250
+ // Operations
2251
+ function addTask(task: Task) {
2252
+ switch (task.status) {
2253
+ case 'todo':
2254
+ $todoTasks.push(task)
2255
+ break
2256
+ case 'doing':
2257
+ $doingTasks.push(task)
2258
+ break
2259
+ case 'done':
2260
+ $doneTasks.push(task)
2261
+ break
2262
+ }
2263
+ }
2264
+
2265
+ function moveTask(taskId: string, from: string, to: string) {
2266
+ const sourceArray = getArrayForStatus(from)
2267
+ const destArray = getArrayForStatus(to)
2268
+
2269
+ const tasks = sourceArray.pick()
2270
+ const index = tasks.findIndex(t => t.id === taskId)
2271
+
2272
+ if (index !== -1) {
2273
+ const task = tasks[index]
2274
+ sourceArray.splice(index, 1)
2275
+ destArray.push({ ...task, status: to as Task['status'] })
2276
+ }
2277
+ }
2278
+
2279
+ function getArrayForStatus(status: string) {
2280
+ switch (status) {
2281
+ case 'todo': return $todoTasks
2282
+ case 'doing': return $doingTasks
2283
+ case 'done': return $doneTasks
2284
+ default: throw new Error('Invalid status')
2285
+ }
2286
+ }
2287
+
2288
+ // Display stats
2289
+ effect((t) => {
2290
+ const total = $totalTasks.get(t)
2291
+ const rate = $completionRate.get(t)
2292
+
2293
+ updateStatsDisplay(total, rate)
2294
+ })
2295
+ ```
2296
+
2297
+ **Use Case:** Kanban boards, project management tools, collaborative task tracking with real-time updates and animations.
2298
+
2299
+ ---
2300
+
2301
+ ## Next Steps
2302
+
2303
+ These examples demonstrate how PicoFlow primitives work together in real applications. For more information:
2304
+
2305
+ - [State](/guide/primitives/state) - Managing reactive values
2306
+ - [Signals](/guide/primitives/signal) - Event notifications
2307
+ - [Effects](/guide/primitives/effects) - Handling side effects
2308
+ - [Derivations](/guide/primitives/derivations) - Computing derived values
2309
+ - [Resources](/guide/primitives/resources) - Fetching data
2310
+ - [Streams](/guide/primitives/streams) - Handling events
2311
+ - [Reactive Maps](/guide/primitives/map) - Fine-grained map tracking
2312
+ - [Reactive Arrays](/guide/primitives/array) - Fine-grained array tracking
2313
+ - [API Reference](/api/) - Complete API documentation