@ersbeth/picoflow 1.0.0 → 1.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 (180) hide show
  1. package/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
  2. package/README.md +25 -171
  3. package/biome.json +4 -1
  4. package/dist/picoflow.js +1129 -661
  5. package/dist/types/flow/base/flowDisposable.d.ts +67 -0
  6. package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
  7. package/dist/types/flow/base/flowEffect.d.ts +127 -0
  8. package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
  9. package/dist/types/flow/base/flowGraph.d.ts +97 -0
  10. package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
  11. package/dist/types/flow/base/flowSignal.d.ts +134 -0
  12. package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
  13. package/dist/types/flow/base/flowTracker.d.ts +15 -0
  14. package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
  15. package/dist/types/flow/base/index.d.ts +7 -0
  16. package/dist/types/flow/base/index.d.ts.map +1 -0
  17. package/dist/types/flow/base/utils.d.ts +20 -0
  18. package/dist/types/flow/base/utils.d.ts.map +1 -0
  19. package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
  20. package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
  21. package/dist/types/flow/collections/flowMap.d.ts +224 -0
  22. package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
  23. package/dist/types/flow/collections/index.d.ts +3 -0
  24. package/dist/types/flow/collections/index.d.ts.map +1 -0
  25. package/dist/types/flow/index.d.ts +4 -0
  26. package/dist/types/flow/index.d.ts.map +1 -0
  27. package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
  28. package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
  29. package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
  30. package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
  31. package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
  32. package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
  33. package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
  34. package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
  35. package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
  36. package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
  37. package/dist/types/flow/nodes/async/index.d.ts +6 -0
  38. package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
  39. package/dist/types/flow/nodes/index.d.ts +3 -0
  40. package/dist/types/flow/nodes/index.d.ts.map +1 -0
  41. package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
  42. package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
  43. package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
  44. package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
  45. package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
  46. package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
  47. package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
  48. package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
  49. package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
  50. package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
  51. package/dist/types/flow/nodes/sync/index.d.ts +6 -0
  52. package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
  53. package/dist/types/index.d.ts +1 -4
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/dist/types/solid/converters.d.ts +34 -44
  56. package/dist/types/solid/converters.d.ts.map +1 -1
  57. package/dist/types/solid/primitives.d.ts +1 -0
  58. package/dist/types/solid/primitives.d.ts.map +1 -1
  59. package/docs/.vitepress/config.mts +1 -1
  60. package/docs/api/typedoc-sidebar.json +81 -1
  61. package/package.json +60 -58
  62. package/src/flow/base/flowDisposable.ts +71 -0
  63. package/src/flow/base/flowEffect.ts +171 -0
  64. package/src/flow/base/flowGraph.ts +288 -0
  65. package/src/flow/base/flowSignal.ts +207 -0
  66. package/src/flow/base/flowTracker.ts +17 -0
  67. package/src/flow/base/index.ts +6 -0
  68. package/src/flow/base/utils.ts +19 -0
  69. package/src/flow/collections/flowArray.ts +409 -0
  70. package/src/flow/collections/flowMap.ts +398 -0
  71. package/src/flow/collections/index.ts +2 -0
  72. package/src/flow/index.ts +3 -0
  73. package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
  74. package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
  75. package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
  76. package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
  77. package/src/flow/nodes/async/flowStateAsync.ts +116 -0
  78. package/src/flow/nodes/async/index.ts +5 -0
  79. package/src/flow/nodes/await/advanced/index.ts +5 -0
  80. package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
  81. package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
  82. package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
  83. package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
  84. package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
  85. package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
  86. package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
  87. package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
  88. package/src/flow/nodes/await/flowStateAwait.ts +130 -0
  89. package/src/flow/nodes/await/index.ts +5 -0
  90. package/src/flow/nodes/index.ts +3 -0
  91. package/src/flow/nodes/sync/flowConstant.ts +111 -0
  92. package/src/flow/nodes/sync/flowDerivation.ts +105 -0
  93. package/src/flow/nodes/sync/flowNode.ts +439 -0
  94. package/src/flow/nodes/sync/flowReadonly.ts +57 -0
  95. package/src/flow/nodes/sync/flowState.ts +101 -0
  96. package/src/flow/nodes/sync/index.ts +5 -0
  97. package/src/index.ts +1 -47
  98. package/src/solid/converters.ts +59 -198
  99. package/src/solid/primitives.ts +4 -0
  100. package/test/base/flowEffect.test.ts +108 -0
  101. package/test/base/flowGraph.test.ts +485 -0
  102. package/test/base/flowSignal.test.ts +372 -0
  103. package/test/collections/flowArray.asyncStates.test.ts +1553 -0
  104. package/test/collections/flowArray.scalars.test.ts +1129 -0
  105. package/test/collections/flowArray.states.test.ts +1365 -0
  106. package/test/collections/flowMap.asyncStates.test.ts +1105 -0
  107. package/test/collections/flowMap.scalars.test.ts +877 -0
  108. package/test/collections/flowMap.states.test.ts +1097 -0
  109. package/test/nodes/async/flowConstantAsync.test.ts +860 -0
  110. package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
  111. package/test/nodes/async/flowStateAsync.test.ts +1387 -0
  112. package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
  113. package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
  114. package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
  115. package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
  116. package/test/nodes/await/flowConstantAwait.test.ts +643 -0
  117. package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
  118. package/test/nodes/await/flowStateAwait.test.ts +999 -0
  119. package/test/nodes/mixed/derivation.test.ts +1527 -0
  120. package/test/nodes/sync/flowConstant.test.ts +620 -0
  121. package/test/nodes/sync/flowDerivation.test.ts +1373 -0
  122. package/test/nodes/sync/flowState.test.ts +945 -0
  123. package/test/solid/converters.test.ts +721 -0
  124. package/test/solid/primitives.test.ts +1031 -0
  125. package/tsconfig.json +2 -1
  126. package/vitest.config.ts +7 -1
  127. package/IMPLEMENTATION_GUIDE.md +0 -1578
  128. package/dist/types/advanced/array.d.ts.map +0 -1
  129. package/dist/types/advanced/index.d.ts +0 -9
  130. package/dist/types/advanced/index.d.ts.map +0 -1
  131. package/dist/types/advanced/map.d.ts +0 -166
  132. package/dist/types/advanced/map.d.ts.map +0 -1
  133. package/dist/types/advanced/resource.d.ts +0 -78
  134. package/dist/types/advanced/resource.d.ts.map +0 -1
  135. package/dist/types/advanced/resourceAsync.d.ts +0 -56
  136. package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
  137. package/dist/types/advanced/stream.d.ts +0 -117
  138. package/dist/types/advanced/stream.d.ts.map +0 -1
  139. package/dist/types/advanced/streamAsync.d.ts +0 -97
  140. package/dist/types/advanced/streamAsync.d.ts.map +0 -1
  141. package/dist/types/basic/constant.d.ts +0 -60
  142. package/dist/types/basic/constant.d.ts.map +0 -1
  143. package/dist/types/basic/derivation.d.ts +0 -89
  144. package/dist/types/basic/derivation.d.ts.map +0 -1
  145. package/dist/types/basic/disposable.d.ts +0 -82
  146. package/dist/types/basic/disposable.d.ts.map +0 -1
  147. package/dist/types/basic/effect.d.ts +0 -67
  148. package/dist/types/basic/effect.d.ts.map +0 -1
  149. package/dist/types/basic/index.d.ts +0 -10
  150. package/dist/types/basic/index.d.ts.map +0 -1
  151. package/dist/types/basic/observable.d.ts +0 -83
  152. package/dist/types/basic/observable.d.ts.map +0 -1
  153. package/dist/types/basic/signal.d.ts +0 -69
  154. package/dist/types/basic/signal.d.ts.map +0 -1
  155. package/dist/types/basic/state.d.ts +0 -47
  156. package/dist/types/basic/state.d.ts.map +0 -1
  157. package/dist/types/basic/trackingContext.d.ts +0 -33
  158. package/dist/types/basic/trackingContext.d.ts.map +0 -1
  159. package/dist/types/creators.d.ts +0 -340
  160. package/dist/types/creators.d.ts.map +0 -1
  161. package/src/advanced/array.ts +0 -222
  162. package/src/advanced/index.ts +0 -12
  163. package/src/advanced/map.ts +0 -193
  164. package/src/basic/constant.ts +0 -97
  165. package/src/basic/derivation.ts +0 -147
  166. package/src/basic/disposable.ts +0 -86
  167. package/src/basic/effect.ts +0 -104
  168. package/src/basic/index.ts +0 -9
  169. package/src/basic/observable.ts +0 -109
  170. package/src/basic/signal.ts +0 -145
  171. package/src/basic/state.ts +0 -60
  172. package/src/basic/trackingContext.ts +0 -45
  173. package/src/creators.ts +0 -395
  174. package/test/array.test.ts +0 -600
  175. package/test/constant.test.ts +0 -44
  176. package/test/derivation.test.ts +0 -539
  177. package/test/effect.test.ts +0 -29
  178. package/test/map.test.ts +0 -240
  179. package/test/signal.test.ts +0 -72
  180. package/test/state.test.ts +0 -212
@@ -0,0 +1,398 @@
1
+ import { FlowGraph, isDisposable } from "../base";
2
+ import { FlowNode, type FlowState, state } from "../nodes";
3
+
4
+ /**
5
+ * Represents the actions that can be performed on a FlowMap.
6
+ * @public
7
+ */
8
+ export type FlowMapAction<K, V> =
9
+ | {
10
+ type: "set";
11
+ map: Map<K, V>;
12
+ }
13
+ | {
14
+ type: "add";
15
+ key: K;
16
+ value: V;
17
+ }
18
+ | {
19
+ type: "update";
20
+ key: K;
21
+ value: V;
22
+ }
23
+ | {
24
+ type: "delete";
25
+ key: K;
26
+ value: V;
27
+ }
28
+ | {
29
+ type: "clear";
30
+ };
31
+
32
+ /**
33
+ * Represents a reactive map that extends {@link FlowState} for tracking key-value pairs.
34
+ *
35
+ * @remarks
36
+ * FlowMap wraps a native JavaScript Map and provides reactive tracking at multiple granularity levels.
37
+ * Unlike plain reactive state, FlowMap offers fine-grained reactivity that lets you track:
38
+ *
39
+ * 1. **Whole map changes**: Via `get(t)` or `pick()` on the FlowMap itself
40
+ * 2. **Last action**: Via the `$lastAction` signal, track the most recent operation performed on the map
41
+ *
42
+ * **Reactive Signals:**
43
+ * - **$lastAction**: A FlowState containing a {@link FlowMapAction} that describes the most recent operation
44
+ * (set, add, update, delete, or clear) performed on the map
45
+ *
46
+ * These signals enable fine-grained reactivity patterns where effects can respond to specific
47
+ * map operations without re-processing the entire map.
48
+ *
49
+ * **Use Cases:**
50
+ * - Entity stores where you want to track additions/removals separately
51
+ * - Cache implementations with granular invalidation
52
+ * - Collections where operations on individual keys matter
53
+ * - UI state where you want to animate specific additions or removals
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const $users = map<string, User>();
58
+ *
59
+ * // Track the whole map
60
+ * effect((t) => {
61
+ * const users = $users.get(t);
62
+ * console.log(`Total users: ${users.size}`);
63
+ * });
64
+ *
65
+ * // Track last action
66
+ * effect((t) => {
67
+ * const action = $users.$lastAction.get(t);
68
+ * switch (action.type) {
69
+ * case 'add':
70
+ * console.log(`User ${action.key} was added:`, action.value);
71
+ * break;
72
+ * case 'update':
73
+ * console.log(`User ${action.key} was updated:`, action.value);
74
+ * break;
75
+ * case 'delete':
76
+ * console.log(`User ${action.key} was deleted:`, action.value);
77
+ * break;
78
+ * case 'set':
79
+ * console.log('Map was replaced');
80
+ * break;
81
+ * case 'clear':
82
+ * console.log('Map was cleared');
83
+ * break;
84
+ * }
85
+ * });
86
+ *
87
+ * // Modify the map
88
+ * $users.add('user1', { name: 'John', age: 30 });
89
+ * $users.add('user2', { name: 'Jane', age: 25 });
90
+ * $users.update('user1', { name: 'John', age: 31 });
91
+ * $users.delete('user1');
92
+ * $users.clear();
93
+ * ```
94
+ *
95
+ * @typeParam K - The type of the map keys.
96
+ * @typeParam V - The type of the map values.
97
+ *
98
+ * @public
99
+ */
100
+ export class FlowMap<K, V> extends FlowNode<Map<K, V>> {
101
+ /**
102
+ * Last action performed on the FlowMap.
103
+ * @public
104
+ */
105
+ public $lastAction: FlowState<FlowMapAction<K, V>>;
106
+ protected declare _value: Map<K, V>;
107
+
108
+ /**
109
+ * Creates an instance of FlowMap.
110
+ * @param value - Initial map value.
111
+ * @public
112
+ */
113
+ constructor(value: Map<K, V> = new Map()) {
114
+ super(value);
115
+ this.$lastAction = state<FlowMapAction<K, V>>({
116
+ type: "set",
117
+ map: value,
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Adds a new key-value pair to the map.
123
+ *
124
+ * @param key - The key to add.
125
+ * @param value - The value to associate with the key.
126
+ * @throws If the FlowMap instance is disposed.
127
+ * @throws If the key already exists in the map.
128
+ *
129
+ * @remarks
130
+ * Adds a new entry to the internal map, emits the key-value pair via {@link FlowMap.$lastAction},
131
+ * and notifies all subscribers of the change.
132
+ *
133
+ * @public
134
+ */
135
+ public async add(key: K, value: V): Promise<void> {
136
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
137
+
138
+ const update = () => {
139
+ // get value
140
+ const currentValue = this._value.get(key);
141
+ if (currentValue) {
142
+ throw new Error("[PicoFlow] Key already exists");
143
+ }
144
+
145
+ // add key
146
+ this._value.set(key, value);
147
+
148
+ // emit last action
149
+ this.$lastAction.set({ type: "add", key, value });
150
+
151
+ // return value changed check
152
+ return true;
153
+ };
154
+
155
+ const notify = () => {
156
+ // call super method to avoid setting dirty flag back to true
157
+ super._notify();
158
+ };
159
+
160
+ return FlowGraph.requestWrite(notify, update);
161
+ }
162
+
163
+ /**
164
+ * Updates an existing key-value pair in the map.
165
+ *
166
+ * @param key - The key to update.
167
+ * @param value - The new value to associate with the key.
168
+ * @throws If the FlowMap instance is disposed.
169
+ * @throws If the key does not exist in the map.
170
+ *
171
+ * @remarks
172
+ * Updates an existing entry in the internal map, emits the key-value pair via {@link FlowMap.$lastAction},
173
+ * and notifies all subscribers of the change.
174
+ *
175
+ * @public
176
+ */
177
+ public async update(key: K, value: V): Promise<void> {
178
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
179
+
180
+ const update = () => {
181
+ // get value
182
+ const currentValue = this._value.get(key);
183
+ if (!currentValue) throw new Error("[PicoFlow] Key does not exist");
184
+
185
+ if (currentValue !== value) {
186
+ // dispose value
187
+ if (isDisposable(currentValue)) currentValue.dispose({ self: true });
188
+
189
+ // update key
190
+ this._value.set(key, value);
191
+
192
+ // emit last action
193
+ this.$lastAction.set({ type: "update", key, value });
194
+
195
+ // return value changed check
196
+ return true;
197
+ }
198
+ return false;
199
+ };
200
+
201
+ const notify = () => {
202
+ // call super method to avoid setting dirty flag back to true
203
+ super._notify();
204
+ };
205
+
206
+ return FlowGraph.requestWrite(notify, update);
207
+ }
208
+
209
+ /**
210
+ * Deletes the value at the specified key from the underlying map.
211
+ *
212
+ * @param key - The key to delete.
213
+ * @throws If the FlowMap instance is disposed.
214
+ *
215
+ * @remarks
216
+ * Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$lastAction},
217
+ * and notifies all subscribers of the change.
218
+ *
219
+ * @public
220
+ */
221
+ public async delete(key: K): Promise<void> {
222
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
223
+
224
+ const update = () => {
225
+ // get value
226
+ const value = this._value.get(key);
227
+ if (value === undefined) throw new Error("[PicoFlow] Key does not exist");
228
+
229
+ // dispose value
230
+ if (isDisposable(value)) value.dispose({ self: true });
231
+
232
+ // delete key
233
+ this._value.delete(key);
234
+
235
+ // emit last action
236
+ this.$lastAction.set({ type: "delete", key, value });
237
+
238
+ // return value changed check
239
+ return true;
240
+ };
241
+
242
+ const notify = () => {
243
+ // call super method to avoid setting dirty flag back to true
244
+ super._notify();
245
+ };
246
+
247
+ return FlowGraph.requestWrite(notify, update);
248
+ }
249
+
250
+ /**
251
+ * Replaces the entire map with new entries.
252
+ *
253
+ * @param map - The new map of entries.
254
+ * @throws If the FlowMap instance is disposed.
255
+ *
256
+ * @remarks
257
+ * Replaces all entries in the internal map, disposes the old values (if they are disposable),
258
+ * emits the new map via {@link FlowMap.$lastAction}, and notifies all subscribers of the change.
259
+ *
260
+ * @public
261
+ */
262
+ override async set(map: Map<K, V>): Promise<void> {
263
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
264
+ const update = () => {
265
+ // compute current value
266
+ const currentValue = this._value;
267
+
268
+ // dispose old values
269
+ if (currentValue !== map) {
270
+ currentValue.forEach((item) => {
271
+ if (isDisposable(item)) item.dispose({ self: true });
272
+ });
273
+ }
274
+
275
+ // assign new map
276
+ this._value = map;
277
+
278
+ // emit last action
279
+ if (currentValue !== map) {
280
+ this.$lastAction.set({ type: "set", map: map });
281
+ }
282
+
283
+ // return value changed check
284
+ return currentValue !== map;
285
+ };
286
+
287
+ const notify = () => {
288
+ // call super method to avoid setting dirty flag back to true
289
+ super._notify();
290
+ };
291
+
292
+ return FlowGraph.requestWrite(notify, update);
293
+ }
294
+
295
+ /**
296
+ * Clears all entries from the map.
297
+ *
298
+ * @throws If the FlowMap instance is disposed.
299
+ *
300
+ * @remarks
301
+ * Removes all entries from the internal map, disposes the removed values (if they are disposable),
302
+ * emits a clear action via {@link FlowMap.$lastAction}, and notifies all subscribers of the change.
303
+ *
304
+ * @public
305
+ */
306
+ public async clear(): Promise<void> {
307
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
308
+
309
+ const update = () => {
310
+ // dispose old values
311
+ const items = [...this._value.values()];
312
+ items.forEach((item) => {
313
+ if (isDisposable(item)) item.dispose({ self: true });
314
+ });
315
+
316
+ // clear map
317
+ this._value.clear();
318
+
319
+ // emit last action
320
+ this.$lastAction.set({ type: "clear" });
321
+
322
+ // return value changed check
323
+ return true;
324
+ };
325
+
326
+ const notify = () => {
327
+ // call super method to avoid setting dirty flag back to true
328
+ super._notify();
329
+ };
330
+
331
+ return FlowGraph.requestWrite(notify, update);
332
+ }
333
+
334
+ /**
335
+ * Disposes the FlowMap and its values.
336
+ * @param options - Disposal options.
337
+ * @public
338
+ */
339
+ override dispose(options?: { self: boolean }): void {
340
+ super.dispose(options);
341
+ this._value.forEach((item) => {
342
+ if (isDisposable(item)) item.dispose(options);
343
+ });
344
+ this._value.clear();
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Creates a new reactive map state with fine-grained tracking of operations.
350
+ *
351
+ * @typeParam K - The type of the keys.
352
+ * @typeParam V - The type of the values.
353
+ * @param initial - An optional record of key-value pairs or a Map to initialize the map.
354
+ * @returns A new instance of {@link FlowMap}.
355
+ *
356
+ * @remarks
357
+ * A reactive map wraps a native JavaScript Map and provides multiple levels of reactivity:
358
+ * tracking the entire map, tracking individual set operations, and tracking individual
359
+ * delete operations. The initial record (if provided) is converted to a native Map.
360
+ * If a Map is provided, it is used directly.
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * const $users = map<string, User>({
365
+ * 'user1': { name: 'John', age: 30 }
366
+ * });
367
+ *
368
+ * // Or with a Map
369
+ * const $users2 = map<string, User>(new Map([['user1', { name: 'John', age: 30 }]]));
370
+ *
371
+ * // Track the whole map
372
+ * effect((t) => {
373
+ * console.log('Users:', $users.get(t).size);
374
+ * });
375
+ *
376
+ * // Track last action
377
+ * effect((t) => {
378
+ * const action = $users.$lastAction.get(t);
379
+ * if (action.type === 'add') {
380
+ * console.log(`Added: ${action.key}`);
381
+ * }
382
+ * });
383
+ *
384
+ * $users.add('user2', { name: 'Jane', age: 25 });
385
+ * ```
386
+ *
387
+ * @public
388
+ */
389
+ export function map<K extends string | number | symbol, V>(
390
+ initial?: Record<K, V> | Map<K, V>,
391
+ ): FlowMap<K, V> {
392
+ if (initial instanceof Map) {
393
+ return new FlowMap<K, V>(initial);
394
+ }
395
+ return new FlowMap<K, V>(
396
+ new Map<K, V>(initial ? (Object.entries(initial) as [K, V][]) : []),
397
+ );
398
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./flowArray";
2
+ export * from "./flowMap";
@@ -0,0 +1,3 @@
1
+ export * from "./base";
2
+ export * from "./collections";
3
+ export * from "./nodes";
@@ -0,0 +1,142 @@
1
+ import { FlowNodeAsync } from "./flowNodeAsync";
2
+
3
+ /**
4
+ * Represents a constant value that can be computed lazily upon first access, with asynchronous values.
5
+ *
6
+ * @typeParam T - The type of the constant value (not the Promise itself).
7
+ *
8
+ * @remarks
9
+ * `FlowConstantAsync` is a type alias based on {@link FlowNodeAsync} with the `set()` and `refresh()`
10
+ * methods removed, making it immutable. It provides all the reactive capabilities of `FlowNodeAsync`
11
+ * (lazy computation, caching, dependency tracking) but prevents value mutation after initialization.
12
+ *
13
+ * Unlike {@link FlowStateAsync}, which is mutable via the `set()` method, a constant's value never
14
+ * changes after it's computed. This makes it ideal for values that should remain stable throughout
15
+ * the application lifecycle.
16
+ *
17
+ * **Asynchronous Nature:**
18
+ * All values in FlowConstantAsync are Promises. When you call `get()` or `pick()`, you receive a
19
+ * `Promise<T>` that you must await to get the actual value. This allows for seamless integration
20
+ * with async/await patterns and asynchronous data sources.
21
+ *
22
+ * **Lazy Computation:**
23
+ * Constants are created using the `constantAsync()` factory function, which accepts either a
24
+ * `Promise<T>` or a function returning `Promise<T>`. The computation doesn't run immediately
25
+ * upon creation when using a function. It executes only when:
26
+ * - The value is first read via `get()` or `pick()`
27
+ * - The constant is watched via `watch()`
28
+ * This allows you to defer expensive async computations until they're actually needed.
29
+ *
30
+ * **Initialization Patterns:**
31
+ * - **Constant Promise**: Pass a `Promise<T>` directly; it's stored immediately and can be accessed
32
+ * right away. The Promise resolves to the value of type T.
33
+ * - **Lazy initialization**: Pass a function `() => Promise<T>`; it's called only when the value
34
+ * is first accessed via `get()` or `pick()`. This is useful for expensive async operations that
35
+ * may not be needed immediately.
36
+ *
37
+ * **Caching:**
38
+ * Once computed (either immediately or lazily), the Promise is cached permanently. All subsequent
39
+ * accesses return the cached Promise without re-computation, ensuring the value remains constant
40
+ * and the computation runs only once.
41
+ *
42
+ * **Use Cases:**
43
+ * - Configuration values loaded asynchronously that don't change
44
+ * - Expensive async computations that should only run once
45
+ * - Initialization values for other reactive primitives
46
+ * - Values derived from external async sources that shouldn't be modified
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // Constant Promise - initialized immediately
51
+ * const $config = constantAsync(Promise.resolve({ apiUrl: 'https://api.example.com' }));
52
+ * const config = await $config.pick(); // Resolves immediately
53
+ *
54
+ * // Lazy initialization - computed on first access
55
+ * const $expensiveValue = constantAsync(async () => {
56
+ * console.log('Computing...');
57
+ * return await performExpensiveAsyncCalculation();
58
+ * });
59
+ *
60
+ * // First access triggers computation
61
+ * const value1 = await $expensiveValue.pick(); // Logs: "Computing..."
62
+ *
63
+ * // Subsequent accesses return cached Promise
64
+ * const value2 = await $expensiveValue.pick(); // No log - returns cached value
65
+ *
66
+ * // Value cannot be changed (set() method is not available)
67
+ * // $expensiveValue.set(Promise.resolve(100)); // TypeScript error: Property 'set' does not exist
68
+ * ```
69
+ *
70
+ * @public
71
+ */
72
+ export type FlowConstantAsync<T> = Omit<FlowNodeAsync<T>, "set" | "refresh">;
73
+
74
+ /**
75
+ * Creates a new reactive constant with lazy initialization support.
76
+ *
77
+ * @typeParam T - The type of the constant value (not the Promise itself).
78
+ *
79
+ * @param value - Either a `Promise<T>` for immediate initialization, or a function `() => Promise<T>`
80
+ * for lazy initialization. The function will be called only when the value is first accessed.
81
+ *
82
+ * @returns A new instance of {@link FlowConstantAsync} that provides reactive access to the computed value.
83
+ *
84
+ * @remarks
85
+ * Constants are immutable reactive values that are computed once and cached permanently. Unlike
86
+ * states (created with `stateAsync()`), constants cannot be modified after initialization - they don't
87
+ * have a `set()` method.
88
+ *
89
+ * **Asynchronous Nature:**
90
+ * All values are Promises. When you access the constant via `get(t)` or `pick()`, you receive a
91
+ * `Promise<T>` that you must await to get the actual value. This allows for seamless integration
92
+ * with async/await patterns and asynchronous data sources.
93
+ *
94
+ * **Lazy Computation:**
95
+ * When you provide a function, the computation is not executed immediately upon creation. It runs only when:
96
+ * - The value is first read via `get(t)` or `pick()`
97
+ * - The constant is watched via `watch()`
98
+ *
99
+ * This lazy computation is useful for:
100
+ * - Expensive async computations that may not be needed immediately
101
+ * - Values that depend on resources not available at construction time
102
+ * - Deferring async operations until the value is actually needed
103
+ *
104
+ * **When to Use Constants:**
105
+ * - Use `constantAsync()` for values that should never change after initialization
106
+ * - Use `stateAsync()` for mutable values that can be updated
107
+ * - Use `derivationAsync()` for values that recompute when dependencies change
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // Eager initialization with Promise
112
+ * const $config = constantAsync(Promise.resolve({
113
+ * apiUrl: 'https://api.example.com'
114
+ * }));
115
+ * const config = await $config.pick();
116
+ *
117
+ * // Lazy initialization with async function
118
+ * const $expensiveValue = constantAsync(async () => {
119
+ * return await loadConfigurationFromFile();
120
+ * });
121
+ *
122
+ * // Computation hasn't run yet
123
+ * // First access triggers it
124
+ * const value = await $expensiveValue.pick();
125
+ *
126
+ * // Subsequent accesses return cached Promise
127
+ * const value2 = await $expensiveValue.pick();
128
+ *
129
+ * // Use in reactive context
130
+ * effect(async (t) => {
131
+ * const cfg = await $config.get(t);
132
+ * console.log(`API URL: ${cfg.apiUrl}`);
133
+ * });
134
+ * ```
135
+ *
136
+ * @public
137
+ */
138
+ export function constantAsync<T>(
139
+ value: Promise<T> | (() => Promise<T>),
140
+ ): FlowConstantAsync<T> {
141
+ return new FlowNodeAsync(value);
142
+ }
@@ -0,0 +1,143 @@
1
+ import type { FlowTracker } from "../../base";
2
+ import { FlowNodeAsync } from "./flowNodeAsync";
3
+
4
+ /**
5
+ * Represents a reactive derivation whose value is computed based on other reactive signals, with asynchronous values.
6
+ *
7
+ * @typeParam T - The type of the computed value (not the Promise itself).
8
+ *
9
+ * @remarks
10
+ * `FlowDerivationAsync` is a type alias based on {@link FlowNodeAsync} with the `set()` method removed,
11
+ * making it immutable. It provides all the reactive capabilities of `FlowNodeAsync` (dependency tracking,
12
+ * lazy computation, caching) but prevents value mutation after initialization.
13
+ *
14
+ * Unlike {@link FlowConstantAsync}, which computes its value once and never changes, derivations
15
+ * automatically recompute when any tracked dependency changes. This makes them ideal for
16
+ * creating derived state that stays in sync with its sources.
17
+ *
18
+ * **Asynchronous Nature:**
19
+ * All values in FlowDerivationAsync are Promises. When you call `get()` or `pick()`, you receive a
20
+ * `Promise<T>` that you must await to get the actual value. The compute function must return a
21
+ * `Promise<T>`. This allows for seamless integration with async/await patterns and asynchronous
22
+ * data sources.
23
+ *
24
+ * **Lazy Computation:**
25
+ * The compute function doesn't run immediately upon creation. It executes only when:
26
+ * - The value is first read via `get()` or `pick()`
27
+ * - The derivation is watched via `watch()`
28
+ * This allows you to defer expensive async computations until they're actually needed.
29
+ *
30
+ * **Automatic Recomputation:**
31
+ * When a tracked dependency changes, the derivation is marked as "dirty" but doesn't recompute
32
+ * immediately. Recomputation happens lazily on the next value access. This prevents unnecessary
33
+ * computations when multiple dependencies change in quick succession. The Promise is cached until
34
+ * dependencies change, ensuring efficient access patterns. You can also force recomputation using
35
+ * the `refresh()` method.
36
+ *
37
+ * **Dynamic Dependencies:**
38
+ * Dependencies are tracked dynamically during each computation. If the compute function
39
+ * conditionally tracks different observables, the dependency graph updates automatically.
40
+ * When accessing async dependencies via `.get(t)`, you must await the returned Promise.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const $firstName = stateAsync(Promise.resolve('John'));
45
+ * const $lastName = stateAsync(Promise.resolve('Doe'));
46
+ *
47
+ * const $fullName = derivationAsync(async (t) => {
48
+ * const first = await $firstName.get(t);
49
+ * const last = await $lastName.get(t);
50
+ * return `${first} ${last}`;
51
+ * });
52
+ *
53
+ * // Compute function hasn't run yet (lazy)
54
+ * const name = await $fullName.pick(); // Now it computes: "John Doe"
55
+ *
56
+ * // When dependencies change, derivation recomputes automatically
57
+ * await $firstName.set(Promise.resolve('Jane'));
58
+ * const newName = await $fullName.pick(); // "Jane Doe" (recomputed)
59
+ * ```
60
+ *
61
+ * @public
62
+ */
63
+
64
+ export type FlowDerivationAsync<T> = Omit<FlowNodeAsync<T>, "set">;
65
+
66
+ /**
67
+ * Creates a new reactive derivation whose value is computed based on other reactive signals.
68
+ *
69
+ * @typeParam T - The type of the derived value (not the Promise itself).
70
+ *
71
+ * @param fn - A function that computes the derived value using a tracking context. The function
72
+ * receives a tracking context (`t`) that should be used to access dependencies via `.get(t)`.
73
+ * The function must return a `Promise<T>`. The function is not executed immediately; it runs
74
+ * lazily on first access.
75
+ *
76
+ * @returns A new instance of {@link FlowDerivationAsync} that provides reactive access to the computed value.
77
+ *
78
+ * @remarks
79
+ * A derivation is a computed reactive value that automatically tracks its dependencies and
80
+ * recomputes when they change. The computation is lazy - it runs only when the value is
81
+ * accessed, not on construction. Use derivations to create derived state without manual
82
+ * dependency management.
83
+ *
84
+ * **Asynchronous Nature:**
85
+ * All values are Promises. When you access the derivation via `get(t)` or `pick()`, you receive a
86
+ * `Promise<T>` that you must await to get the actual value. The compute function must return a
87
+ * `Promise<T>`. When accessing async dependencies within the compute function, you must await
88
+ * the Promises returned by `.get(t)`.
89
+ *
90
+ * **Lazy Computation:**
91
+ * The compute function is not executed immediately upon creation. It runs only when:
92
+ * - The value is first read via `get(t)` or `pick()`
93
+ * - The derivation is watched via `watch()`
94
+ *
95
+ * This lazy computation is useful for:
96
+ * - Expensive async computations that may not be needed immediately
97
+ * - Values that depend on async resources
98
+ * - Deferring async operations until the value is actually needed
99
+ *
100
+ * **Automatic Recomputation:**
101
+ * When any tracked dependency changes, the derivation automatically recomputes on the next access.
102
+ * This ensures the derived value always stays in sync with its sources.
103
+ *
104
+ * **When to Use Derivations:**
105
+ * - Use `derivationAsync()` for values that should recompute when dependencies change
106
+ * - Use `constantAsync()` for values that compute once and never change
107
+ * - Use `stateAsync()` for mutable values that can be updated directly
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const $firstName = stateAsync(Promise.resolve('John'));
112
+ * const $lastName = stateAsync(Promise.resolve('Doe'));
113
+ *
114
+ * const $fullName = derivationAsync(async (t) => {
115
+ * const first = await $firstName.get(t);
116
+ * const last = await $lastName.get(t);
117
+ * return `${first} ${last}`;
118
+ * });
119
+ *
120
+ * // Use in reactive context
121
+ * effect(async (t) => {
122
+ * const name = await $fullName.get(t);
123
+ * console.log(name); // Logs: "John Doe"
124
+ * });
125
+ *
126
+ * // When dependencies change, derivation recomputes automatically
127
+ * await $firstName.set(Promise.resolve('Jane')); // Logs: "Jane Doe"
128
+ *
129
+ * // Async data fetching example
130
+ * const $userId = stateAsync(Promise.resolve(1));
131
+ * const $user = derivationAsync(async (t) => {
132
+ * const id = await $userId.get(t);
133
+ * return await fetchUser(id);
134
+ * });
135
+ * ```
136
+ *
137
+ * @public
138
+ */
139
+ export function derivationAsync<T>(
140
+ fn: (t: FlowTracker) => Promise<T>,
141
+ ): FlowDerivationAsync<T> {
142
+ return new FlowNodeAsync(fn);
143
+ }