@ersbeth/picoflow 1.0.1 → 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 +17 -1
  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
@@ -1,63 +1,34 @@
1
1
  import { onCleanup, onMount } from "solid-js";
2
- import {
3
- FlowDerivation,
4
- FlowEffect,
5
- FlowObservable,
6
- type TrackingContext,
7
- } from "../basic";
8
- import { type SolidDerivation, SolidResource, SolidState } from "./primitives";
2
+ import { FlowNode, type FlowReadonly, type FlowReadonlyAsync } from "../flow";
3
+ import { FlowEffect } from "../flow/base";
4
+ import type { FlowTracker } from "../flow/base/flowTracker";
5
+ import { FlowNodeAsync } from "../flow/nodes/async/flowNodeAsync";
6
+ import { SolidResource } from "./primitives";
9
7
 
10
8
  /**
11
- * Converts a synchronous FlowObservable into a SolidJS signal.
9
+ * Converts an asynchronous FlowNodeAsync (Promise-based) into a SolidJS resource.
12
10
  *
13
- * @param state - The FlowObservable to convert.
14
- * @returns A SolidDerivation that mirrors the FlowObservable's value.
11
+ * @param node - The FlowNodeAsync to convert.
12
+ * @returns A SolidResource that wraps the FlowNodeAsync's value.
15
13
  *
16
14
  * @remarks
17
- * This internal helper bridges PicoFlow's reactive system with SolidJS by:
18
- * 1. Reading the initial value without tracking (using `pick()`)
19
- * 2. Creating a FlowEffect that tracks the observable and updates the Solid signal
20
- * 3. Properly disposing the effect when the Solid component unmounts
15
+ * This internal helper bridges PicoFlow's async reactive system with SolidJS by creating
16
+ * a SolidResource that automatically tracks the FlowNodeAsync and updates when its value changes.
17
+ * The resource is properly cleaned up when the Solid component unmounts.
21
18
  *
22
19
  * @internal
23
20
  */
24
- function fromSync<T>(state: FlowObservable<T>): SolidDerivation<T> {
25
- const solidState = new SolidState<T>(state.pick());
21
+ function fromNode<T>(node: FlowNodeAsync<T> | FlowNode<T>): SolidResource<T> {
22
+ let initialized = false;
26
23
 
27
- let fx: FlowEffect;
28
-
29
- onMount(() => {
30
- fx = new FlowEffect((t) => {
31
- const value = state.get(t);
32
- solidState.set(() => value);
33
- });
34
- });
35
-
36
- onCleanup(() => fx.dispose());
37
-
38
- return solidState;
39
- }
40
-
41
- /**
42
- * Converts an asynchronous FlowObservable (Promise-based) into a SolidJS resource.
43
- *
44
- * @param derivation - The FlowObservable that resolves to a Promise.
45
- * @returns A SolidResource that mirrors the FlowObservable's async value.
46
- *
47
- * @remarks
48
- * This internal helper bridges PicoFlow's async reactive system with SolidJS by:
49
- * 1. Creating a SolidResource with an initial fetch using `pick()` (untracked)
50
- * 2. Setting up a FlowEffect that tracks the Promise observable
51
- * 3. Refetching the SolidResource whenever the Promise observable changes
52
- * 4. Properly disposing the effect when the Solid component unmounts
53
- *
54
- * @internal
55
- */
56
- function fromAsync<T>(
57
- derivation: FlowObservable<Promise<T>>,
58
- ): SolidResource<T> {
59
24
  const solidResource = new SolidResource<T>(async () => {
60
- const value = await derivation.pick();
25
+ let value: T;
26
+ if (initialized) {
27
+ value = await node.get(null); // don't request graph read
28
+ } else {
29
+ value = await node.pick();
30
+ initialized = true;
31
+ }
61
32
  return value;
62
33
  });
63
34
 
@@ -65,7 +36,7 @@ function fromAsync<T>(
65
36
 
66
37
  onMount(() => {
67
38
  fx = new FlowEffect(async (t) => {
68
- await derivation.get(t);
39
+ node.watch(t);
69
40
  solidResource.refetch();
70
41
  });
71
42
  });
@@ -76,68 +47,27 @@ function fromAsync<T>(
76
47
  }
77
48
 
78
49
  /**
79
- * Converts a FlowObservable directly into a Solid primitive (signal or resource).
50
+ * Converts a getter function into a SolidResource.
80
51
  *
81
- * @param flow - The FlowObservable to convert (can be sync or async).
82
- * @returns A SolidDerivation for sync values or SolidResource for async values.
52
+ * @param getter - A function that computes a value using a FlowTracker.
53
+ * @returns A SolidResource that wraps the computed value.
83
54
  *
84
55
  * @remarks
85
- * This internal helper performs "shallow" conversion, meaning it converts the
86
- * observable itself without creating an intermediate FlowDerivation. It inspects
87
- * the initial value to determine if it's a Promise, then delegates to the
88
- * appropriate converter (fromSync or fromAsync).
56
+ * This internal helper performs "deep" conversion by creating a FlowNodeAsync from
57
+ * the getter function and then converting it to a SolidResource. This allows users
58
+ * to pass computation functions directly to `from()` without manually creating a
59
+ * FlowNodeAsync first.
89
60
  *
90
61
  * @internal
91
62
  */
92
- function shallowFrom<T>(flow: FlowObservable<Promise<T>>): SolidResource<T>;
93
- function shallowFrom<T>(flow: FlowObservable<T>): SolidDerivation<T>;
94
- function shallowFrom<T>(
95
- flow: FlowObservable<Promise<T>> | FlowObservable<T>,
96
- ): SolidDerivation<T> | SolidResource<T> {
97
- const initialValue = flow.pick();
98
- const isAsync = initialValue instanceof Promise;
99
- if (isAsync) {
100
- return fromAsync(flow as FlowObservable<Promise<T>>);
101
- }
102
- return fromSync(flow as FlowObservable<T>);
103
- }
104
-
105
- /**
106
- * Converts a getter function with TrackingContext into a Solid primitive (signal or resource).
107
- *
108
- * @param getter - A function that computes a value using a TrackingContext.
109
- * @returns A SolidDerivation for sync values or SolidResource for async values.
110
- *
111
- * @remarks
112
- * This internal helper performs "deep" conversion by:
113
- * 1. Creating a FlowDerivation from the getter function
114
- * 2. Inspecting the initial computed value to determine if it's a Promise
115
- * 3. Delegating to the appropriate converter (fromSync or fromAsync)
116
- *
117
- * This allows users to pass computation functions directly to `from()` without
118
- * manually creating a FlowDerivation first.
119
- *
120
- * @internal
121
- */
122
- function deepFrom<T>(getter: (t: TrackingContext) => T): SolidDerivation<T>;
123
- function deepFrom<T>(
124
- getter: (t: TrackingContext) => Promise<T>,
125
- ): SolidResource<T>;
126
- function deepFrom<T>(
127
- getter: (t: TrackingContext) => T | Promise<T>,
128
- ): SolidDerivation<T> | SolidResource<T> {
129
- const derivation = new FlowDerivation((t) => {
63
+ function fromGetter<T>(
64
+ getter: (t: FlowTracker) => T | Promise<T>,
65
+ ): SolidResource<T> {
66
+ const derivation = new FlowNodeAsync<T>(async (t) => {
130
67
  return getter(t);
131
68
  });
132
69
 
133
- const initialValue = derivation.pick();
134
- const isAsync = initialValue instanceof Promise;
135
-
136
- if (isAsync) {
137
- return fromAsync(derivation as FlowObservable<Promise<T>>);
138
- }
139
-
140
- return fromSync(derivation as FlowObservable<T>);
70
+ return fromNode(derivation);
141
71
  }
142
72
 
143
73
  /**
@@ -149,91 +79,23 @@ function deepFrom<T>(
149
79
  export type NotPromise<T> = T extends Promise<unknown> ? never : T;
150
80
 
151
81
  /**
152
- * Converts a FlowObservable of a Promise value into a SolidResource.
82
+ * Converts a FlowNode, FlowNodeAsync, or getter function into a SolidResource.
153
83
  *
154
- * @param flow - The FlowObservable to convert.
155
- * @returns A SolidResource wrapping the observable.
156
- *
157
- * @public
158
- */
159
- export function from<T>(
160
- flow: FlowObservable<Promise<NotPromise<T>>>,
161
- ): SolidResource<NotPromise<T>>;
162
- /**
163
- * Converts a FlowObservable of a non-Promise value into a SolidDerivation.
164
- *
165
- * @param flow - The FlowObservable to convert.
166
- * @returns A SolidDerivation wrapping the observable.
167
- *
168
- * @public
169
- */
170
- export function from<T>(
171
- flow: FlowObservable<NotPromise<T>>,
172
- ): SolidDerivation<NotPromise<T>>;
173
- /**
174
- * Converts a FlowObservable into a Solid derivation or resource, depending on whether the value is synchronous or asynchronous.
175
- *
176
- * @param flow - The FlowObservable to convert.
177
- * @returns A SolidDerivation or SolidResource, depending on the input type.
178
- *
179
- * @public
180
- */
181
- export function from<T>(
182
- flow: FlowObservable<Promise<NotPromise<T>>> | FlowObservable<NotPromise<T>>,
183
- ): SolidDerivation<NotPromise<T>> | SolidResource<NotPromise<T>>;
184
- /**
185
- * Converts a getter function returning a non-Promise value into a SolidDerivation.
186
- *
187
- * @param flow - The getter function to convert.
188
- * @returns A SolidDerivation wrapping the getter.
189
- *
190
- * @public
191
- */
192
- export function from<T>(
193
- flow: (t: TrackingContext) => NotPromise<T>,
194
- ): SolidDerivation<NotPromise<T>>;
195
- /**
196
- * Converts a getter function returning a Promise into a SolidResource.
197
- *
198
- * @param flow - The getter function to convert.
199
- * @returns A SolidResource wrapping the getter.
200
- *
201
- * @public
202
- */
203
- export function from<T>(
204
- flow: (t: TrackingContext) => Promise<NotPromise<T>>,
205
- ): SolidResource<NotPromise<T>>;
206
- /**
207
- * Converts a getter function into a Solid derivation or resource, depending on whether the returned value is synchronous or asynchronous.
208
- *
209
- * @param flow - The getter function to convert.
210
- * @returns A SolidDerivation or SolidResource, depending on the input type.
211
- *
212
- * @public
213
- */
214
- export function from<T>(
215
- flow:
216
- | ((t: TrackingContext) => NotPromise<T>)
217
- | ((t: TrackingContext) => Promise<NotPromise<T>>),
218
- ): SolidDerivation<T> | SolidResource<T>;
219
- /**
220
- * Converts a FlowObservable or getter function into a Solid derivation or resource, depending on whether the value is synchronous or asynchronous.
221
- *
222
- * @param flow - The FlowObservable or getter function to convert.
223
- * @returns A SolidDerivation or SolidResource, depending on the input type.
84
+ * @param flow - The FlowNode, FlowNodeAsync, or getter function to convert.
85
+ * @returns A SolidResource that wraps the value or computation.
224
86
  *
225
87
  * @remarks
226
88
  * This function bridges PicoFlow's reactive system with SolidJS, allowing you to use
227
- * PicoFlow observables within Solid components. The conversion is automatic based on
228
- * whether the value is a Promise or not:
89
+ * PicoFlow nodes and computations within Solid components. All conversions return a
90
+ * `SolidResource`, which can handle both synchronous and asynchronous values seamlessly.
229
91
  *
230
- * - **FlowObservable of non-Promise value** → SolidDerivation (reactive signal)
231
- * - **FlowObservable of Promise** SolidResource (async resource)
232
- * - **Getter function returning non-Promise** SolidDerivation (computed signal)
233
- * - **Getter function returning Promise** SolidResource (async computed resource)
92
+ * The conversion works with:
93
+ * - **FlowNode**: Synchronous reactive nodes (e.g., from `state()` or `derivation()`)
94
+ * - **FlowNodeAsync**: Asynchronous reactive nodes (e.g., from `resourceAsync()`)
95
+ * - **Getter functions**: Computation functions that use a FlowTracker to access reactive values
234
96
  *
235
- * The created Solid primitives automatically subscribe to the PicoFlow observables and
236
- * update when changes occur. The subscription is properly cleaned up when the Solid
97
+ * The created SolidResource automatically subscribes to the PicoFlow nodes and updates
98
+ * when their values change. The subscription is properly cleaned up when the Solid
237
99
  * component unmounts.
238
100
  *
239
101
  * @example
@@ -241,19 +103,18 @@ export function from<T>(
241
103
  * import { from } from 'picoflow/solid';
242
104
  * import { state } from 'picoflow';
243
105
  *
244
- * // Convert a PicoFlow state to a Solid signal
106
+ * // Convert a PicoFlow state to a Solid resource
245
107
  * const $count = state(0);
246
108
  * const solidCount = from($count);
247
109
  *
248
110
  * // Use in a Solid component
249
111
  * function Counter() {
250
- * const count = solidCount.get(); // Solid's reactive get
251
- * return <div>Count: {count}</div>;
112
+ * return <div>Count: {solidCount.get()}</div>;
252
113
  * }
253
114
  *
254
115
  * // Or convert a computation function
255
116
  * const solidDerived = from((t) => {
256
- * return $count.get(t) * 2;
117
+ * return solidCount.get(t) * 2;
257
118
  * });
258
119
  * ```
259
120
  *
@@ -261,18 +122,18 @@ export function from<T>(
261
122
  */
262
123
  export function from<T>(
263
124
  flow:
264
- | FlowObservable<Promise<T>>
265
- | FlowObservable<T>
266
- | ((t: TrackingContext) => T)
267
- | ((t: TrackingContext) => Promise<T>),
268
- ): SolidDerivation<T> | SolidResource<T> {
269
- if (flow instanceof FlowObservable) {
270
- return shallowFrom(flow as FlowObservable<T | Promise<T>>) as
271
- | SolidDerivation<T>
272
- | SolidResource<T>;
125
+ | FlowReadonlyAsync<T>
126
+ | FlowReadonly<T>
127
+ | ((t: FlowTracker) => T)
128
+ | ((t: FlowTracker) => Promise<T>),
129
+ ): SolidResource<T> {
130
+ if (flow instanceof FlowNodeAsync || flow instanceof FlowNode) {
131
+ return fromNode(flow);
132
+ }
133
+
134
+ if (typeof flow === "function") {
135
+ return fromGetter(flow);
273
136
  }
274
137
 
275
- return deepFrom(flow as (t: TrackingContext) => T | Promise<T>) as
276
- | SolidDerivation<T>
277
- | SolidResource<T>;
138
+ throw new Error("Invalid flow type");
278
139
  }
@@ -197,6 +197,8 @@ export class SolidResource<T> implements SolidObservable<T | undefined> {
197
197
  */
198
198
  readonly refetch: () => void;
199
199
 
200
+ readonly set: (param: SolidGetter<T>) => void;
201
+
200
202
  /**
201
203
  * Creates a new SolidResource from a fetcher function.
202
204
  * @param fetcher - The async fetcher function.
@@ -207,5 +209,7 @@ export class SolidResource<T> implements SolidObservable<T | undefined> {
207
209
  this.state = () => get.state;
208
210
  this.latest = () => get.latest;
209
211
  this.refetch = () => set.refetch();
212
+ //@ts-expect-error - SolidGetter is not assignable to parameter of type 'Exclude<T, Function> | ((prev: T | undefined) => T)'
213
+ this.set = (value: SolidGetter<T>) => set.mutate(value);
210
214
  }
211
215
  }
@@ -0,0 +1,108 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { effect } from "#package";
3
+
4
+ describe("FlowEffect", () => {
5
+ describe("disposal", () => {
6
+ it("should have disposed property set to false initially and true after disposal", () => {
7
+ const effectFn = vi.fn();
8
+ const $effect = effect(() => {
9
+ effectFn();
10
+ });
11
+
12
+ expect($effect.disposed).toBe(false);
13
+ $effect.dispose();
14
+ expect($effect.disposed).toBe(true);
15
+ });
16
+
17
+ it("should throw error when disposed twice", () => {
18
+ const effectFn = vi.fn();
19
+ const $effect = effect(() => {
20
+ effectFn();
21
+ });
22
+
23
+ $effect.dispose();
24
+ expect(() => $effect.dispose()).toThrow("Effect is disposed");
25
+ });
26
+ });
27
+
28
+ describe("execution", () => {
29
+ it("should execute immediately upon creation", async () => {
30
+ const effectFn = vi.fn();
31
+ effect(() => {
32
+ effectFn();
33
+ });
34
+
35
+ await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
36
+ });
37
+
38
+ it("should execute callback with tracking context as parameter", async () => {
39
+ const effectFn = vi.fn();
40
+ let receivedContext: unknown = null;
41
+
42
+ const $effect = effect((t) => {
43
+ receivedContext = t;
44
+ effectFn();
45
+ });
46
+
47
+ await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
48
+ expect(receivedContext).toBe($effect);
49
+ });
50
+ });
51
+
52
+ describe("async support", () => {
53
+ it("should support async callback functions", async () => {
54
+ const effectFn = vi.fn();
55
+ let asyncCompleted = false;
56
+
57
+ effect(async () => {
58
+ await new Promise((resolve) => setTimeout(resolve, 10));
59
+ asyncCompleted = true;
60
+ effectFn();
61
+ });
62
+
63
+ await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
64
+ expect(asyncCompleted).toBe(true);
65
+ });
66
+
67
+ it("should await async callback before completing", async () => {
68
+ const executionOrder: string[] = [];
69
+ const effectFn = vi.fn();
70
+
71
+ effect(async () => {
72
+ executionOrder.push("start");
73
+ await new Promise((resolve) => setTimeout(resolve, 10));
74
+ executionOrder.push("async-complete");
75
+ effectFn();
76
+ });
77
+
78
+ executionOrder.push("after-effect-creation");
79
+
80
+ await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
81
+ expect(executionOrder).toEqual([
82
+ "start",
83
+ "after-effect-creation",
84
+ "async-complete",
85
+ ]);
86
+ });
87
+ });
88
+
89
+ describe("error handling", () => {
90
+ it("should throw error when trying to execute after disposal", async () => {
91
+ const effectFn = vi.fn();
92
+ const $effect = effect(() => {
93
+ effectFn();
94
+ });
95
+
96
+ await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
97
+
98
+ $effect.dispose();
99
+
100
+ // The internal _exec method should throw when called after disposal
101
+ // This is tested indirectly - if we try to trigger execution after disposal,
102
+ // it should fail. However, since we're testing in isolation without dependencies,
103
+ // we verify that the disposed state prevents further execution
104
+ expect($effect.disposed).toBe(true);
105
+ expect(() => $effect.dispose()).toThrow("Effect is disposed");
106
+ });
107
+ });
108
+ });