@ersbeth/picoflow 0.2.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/.cursor/plans/update-js-e795d61b.plan.md +567 -0
  2. package/.gitlab-ci.yml +24 -0
  3. package/.vscode/settings.json +3 -3
  4. package/CHANGELOG.md +51 -0
  5. package/IMPLEMENTATION_GUIDE.md +1578 -0
  6. package/README.md +62 -25
  7. package/biome.json +32 -32
  8. package/dist/picoflow.js +610 -436
  9. package/dist/types/advanced/array.d.ts +0 -6
  10. package/dist/types/advanced/array.d.ts.map +1 -1
  11. package/dist/types/advanced/index.d.ts +5 -5
  12. package/dist/types/advanced/index.d.ts.map +1 -1
  13. package/dist/types/advanced/map.d.ts +114 -23
  14. package/dist/types/advanced/map.d.ts.map +1 -1
  15. package/dist/types/advanced/resource.d.ts +51 -12
  16. package/dist/types/advanced/resource.d.ts.map +1 -1
  17. package/dist/types/advanced/resourceAsync.d.ts +28 -13
  18. package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
  19. package/dist/types/advanced/stream.d.ts +74 -16
  20. package/dist/types/advanced/stream.d.ts.map +1 -1
  21. package/dist/types/advanced/streamAsync.d.ts +69 -15
  22. package/dist/types/advanced/streamAsync.d.ts.map +1 -1
  23. package/dist/types/basic/constant.d.ts +44 -16
  24. package/dist/types/basic/constant.d.ts.map +1 -1
  25. package/dist/types/basic/derivation.d.ts +73 -24
  26. package/dist/types/basic/derivation.d.ts.map +1 -1
  27. package/dist/types/basic/disposable.d.ts +65 -6
  28. package/dist/types/basic/disposable.d.ts.map +1 -1
  29. package/dist/types/basic/effect.d.ts +27 -16
  30. package/dist/types/basic/effect.d.ts.map +1 -1
  31. package/dist/types/basic/index.d.ts +7 -8
  32. package/dist/types/basic/index.d.ts.map +1 -1
  33. package/dist/types/basic/observable.d.ts +62 -13
  34. package/dist/types/basic/observable.d.ts.map +1 -1
  35. package/dist/types/basic/signal.d.ts +35 -6
  36. package/dist/types/basic/signal.d.ts.map +1 -1
  37. package/dist/types/basic/state.d.ts +25 -4
  38. package/dist/types/basic/state.d.ts.map +1 -1
  39. package/dist/types/basic/trackingContext.d.ts +33 -0
  40. package/dist/types/basic/trackingContext.d.ts.map +1 -0
  41. package/dist/types/creators.d.ts +271 -26
  42. package/dist/types/creators.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +60 -7
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/dist/types/solid/converters.d.ts +5 -5
  46. package/dist/types/solid/converters.d.ts.map +1 -1
  47. package/dist/types/solid/index.d.ts +2 -2
  48. package/dist/types/solid/index.d.ts.map +1 -1
  49. package/dist/types/solid/primitives.d.ts +96 -4
  50. package/dist/types/solid/primitives.d.ts.map +1 -1
  51. package/docs/.vitepress/config.mts +110 -0
  52. package/docs/api/classes/FlowArray.md +489 -0
  53. package/docs/api/classes/FlowConstant.md +350 -0
  54. package/docs/api/classes/FlowDerivation.md +334 -0
  55. package/docs/api/classes/FlowEffect.md +100 -0
  56. package/docs/api/classes/FlowMap.md +512 -0
  57. package/docs/api/classes/FlowObservable.md +306 -0
  58. package/docs/api/classes/FlowResource.md +380 -0
  59. package/docs/api/classes/FlowResourceAsync.md +362 -0
  60. package/docs/api/classes/FlowSignal.md +160 -0
  61. package/docs/api/classes/FlowState.md +368 -0
  62. package/docs/api/classes/FlowStream.md +367 -0
  63. package/docs/api/classes/FlowStreamAsync.md +364 -0
  64. package/docs/api/classes/SolidDerivation.md +75 -0
  65. package/docs/api/classes/SolidResource.md +91 -0
  66. package/docs/api/classes/SolidState.md +71 -0
  67. package/docs/api/classes/TrackingContext.md +33 -0
  68. package/docs/api/functions/array.md +58 -0
  69. package/docs/api/functions/constant.md +45 -0
  70. package/docs/api/functions/derivation.md +53 -0
  71. package/docs/api/functions/effect.md +49 -0
  72. package/docs/api/functions/from.md +220 -0
  73. package/docs/api/functions/isDisposable.md +49 -0
  74. package/docs/api/functions/map.md +57 -0
  75. package/docs/api/functions/resource.md +52 -0
  76. package/docs/api/functions/resourceAsync.md +50 -0
  77. package/docs/api/functions/signal.md +36 -0
  78. package/docs/api/functions/state.md +47 -0
  79. package/docs/api/functions/stream.md +53 -0
  80. package/docs/api/functions/streamAsync.md +50 -0
  81. package/docs/api/index.md +118 -0
  82. package/docs/api/interfaces/FlowDisposable.md +65 -0
  83. package/docs/api/interfaces/SolidObservable.md +19 -0
  84. package/docs/api/type-aliases/FlowArrayAction.md +49 -0
  85. package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
  86. package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
  87. package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
  88. package/docs/api/type-aliases/NotPromise.md +18 -0
  89. package/docs/api/type-aliases/SolidGetter.md +17 -0
  90. package/docs/api/typedoc-sidebar.json +1 -0
  91. package/docs/examples/examples.md +2313 -0
  92. package/docs/examples/patterns.md +649 -0
  93. package/docs/guide/advanced/disposal.md +426 -0
  94. package/docs/guide/advanced/solidjs.md +221 -0
  95. package/docs/guide/advanced/upgrading.md +464 -0
  96. package/docs/guide/introduction/concepts.md +56 -0
  97. package/docs/guide/introduction/conventions.md +61 -0
  98. package/docs/guide/introduction/getting-started.md +134 -0
  99. package/docs/guide/introduction/lifecycle.md +371 -0
  100. package/docs/guide/primitives/array.md +400 -0
  101. package/docs/guide/primitives/constant.md +380 -0
  102. package/docs/guide/primitives/derivations.md +348 -0
  103. package/docs/guide/primitives/effects.md +458 -0
  104. package/docs/guide/primitives/map.md +387 -0
  105. package/docs/guide/primitives/overview.md +175 -0
  106. package/docs/guide/primitives/resources.md +858 -0
  107. package/docs/guide/primitives/signal.md +259 -0
  108. package/docs/guide/primitives/state.md +368 -0
  109. package/docs/guide/primitives/streams.md +931 -0
  110. package/docs/index.md +47 -0
  111. package/docs/public/logo.svg +1 -0
  112. package/package.json +57 -41
  113. package/src/advanced/array.ts +208 -210
  114. package/src/advanced/index.ts +7 -7
  115. package/src/advanced/map.ts +178 -68
  116. package/src/advanced/resource.ts +87 -43
  117. package/src/advanced/resourceAsync.ts +62 -42
  118. package/src/advanced/stream.ts +113 -50
  119. package/src/advanced/streamAsync.ts +120 -61
  120. package/src/basic/constant.ts +82 -49
  121. package/src/basic/derivation.ts +128 -84
  122. package/src/basic/disposable.ts +74 -15
  123. package/src/basic/effect.ts +85 -77
  124. package/src/basic/index.ts +7 -8
  125. package/src/basic/observable.ts +94 -36
  126. package/src/basic/signal.ts +133 -105
  127. package/src/basic/state.ts +46 -25
  128. package/src/basic/trackingContext.ts +45 -0
  129. package/src/creators.ts +297 -54
  130. package/src/index.ts +96 -43
  131. package/src/solid/converters.ts +186 -67
  132. package/src/solid/index.ts +8 -2
  133. package/src/solid/primitives.ts +167 -65
  134. package/test/array.test.ts +592 -612
  135. package/test/constant.test.ts +31 -33
  136. package/test/derivation.test.ts +531 -536
  137. package/test/effect.test.ts +21 -21
  138. package/test/map.test.ts +233 -137
  139. package/test/resource.test.ts +119 -121
  140. package/test/resourceAsync.test.ts +98 -100
  141. package/test/signal.test.ts +51 -55
  142. package/test/state.test.ts +186 -168
  143. package/test/stream.test.ts +189 -189
  144. package/test/streamAsync.test.ts +186 -186
  145. package/tsconfig.json +19 -18
  146. package/typedoc.json +37 -0
  147. package/vite.config.ts +23 -23
  148. package/vitest.config.ts +7 -7
  149. package/api/doc/index.md +0 -31
  150. package/api/doc/picoflow.array.md +0 -55
  151. package/api/doc/picoflow.constant.md +0 -55
  152. package/api/doc/picoflow.derivation.md +0 -55
  153. package/api/doc/picoflow.effect.md +0 -55
  154. package/api/doc/picoflow.flowarray._constructor_.md +0 -49
  155. package/api/doc/picoflow.flowarray._lastaction.md +0 -13
  156. package/api/doc/picoflow.flowarray.clear.md +0 -17
  157. package/api/doc/picoflow.flowarray.dispose.md +0 -55
  158. package/api/doc/picoflow.flowarray.get.md +0 -19
  159. package/api/doc/picoflow.flowarray.length.md +0 -13
  160. package/api/doc/picoflow.flowarray.md +0 -273
  161. package/api/doc/picoflow.flowarray.pop.md +0 -17
  162. package/api/doc/picoflow.flowarray.push.md +0 -53
  163. package/api/doc/picoflow.flowarray.set.md +0 -53
  164. package/api/doc/picoflow.flowarray.setitem.md +0 -69
  165. package/api/doc/picoflow.flowarray.shift.md +0 -17
  166. package/api/doc/picoflow.flowarray.splice.md +0 -85
  167. package/api/doc/picoflow.flowarray.unshift.md +0 -53
  168. package/api/doc/picoflow.flowarrayaction.md +0 -37
  169. package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
  170. package/api/doc/picoflow.flowconstant.get.md +0 -25
  171. package/api/doc/picoflow.flowconstant.md +0 -88
  172. package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
  173. package/api/doc/picoflow.flowderivation.get.md +0 -23
  174. package/api/doc/picoflow.flowderivation.md +0 -86
  175. package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
  176. package/api/doc/picoflow.flowdisposable.md +0 -43
  177. package/api/doc/picoflow.floweffect._constructor_.md +0 -54
  178. package/api/doc/picoflow.floweffect.dispose.md +0 -21
  179. package/api/doc/picoflow.floweffect.disposed.md +0 -13
  180. package/api/doc/picoflow.floweffect.md +0 -131
  181. package/api/doc/picoflow.flowgetter.md +0 -15
  182. package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
  183. package/api/doc/picoflow.flowmap._lastset.md +0 -21
  184. package/api/doc/picoflow.flowmap.delete.md +0 -61
  185. package/api/doc/picoflow.flowmap.md +0 -133
  186. package/api/doc/picoflow.flowmap.setat.md +0 -77
  187. package/api/doc/picoflow.flowobservable.get.md +0 -19
  188. package/api/doc/picoflow.flowobservable.md +0 -68
  189. package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
  190. package/api/doc/picoflow.flowresource._constructor_.md +0 -49
  191. package/api/doc/picoflow.flowresource.fetch.md +0 -27
  192. package/api/doc/picoflow.flowresource.get.md +0 -23
  193. package/api/doc/picoflow.flowresource.md +0 -100
  194. package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
  195. package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
  196. package/api/doc/picoflow.flowresourceasync.get.md +0 -23
  197. package/api/doc/picoflow.flowresourceasync.md +0 -100
  198. package/api/doc/picoflow.flowsignal.dispose.md +0 -59
  199. package/api/doc/picoflow.flowsignal.disposed.md +0 -18
  200. package/api/doc/picoflow.flowsignal.md +0 -112
  201. package/api/doc/picoflow.flowsignal.trigger.md +0 -21
  202. package/api/doc/picoflow.flowstate.md +0 -52
  203. package/api/doc/picoflow.flowstate.set.md +0 -61
  204. package/api/doc/picoflow.flowstream._constructor_.md +0 -49
  205. package/api/doc/picoflow.flowstream.dispose.md +0 -21
  206. package/api/doc/picoflow.flowstream.get.md +0 -23
  207. package/api/doc/picoflow.flowstream.md +0 -100
  208. package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
  209. package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
  210. package/api/doc/picoflow.flowstreamasync.get.md +0 -23
  211. package/api/doc/picoflow.flowstreamasync.md +0 -100
  212. package/api/doc/picoflow.flowstreamdisposer.md +0 -13
  213. package/api/doc/picoflow.flowstreamsetter.md +0 -13
  214. package/api/doc/picoflow.flowstreamupdater.md +0 -19
  215. package/api/doc/picoflow.flowwatcher.md +0 -15
  216. package/api/doc/picoflow.from.md +0 -55
  217. package/api/doc/picoflow.from_1.md +0 -55
  218. package/api/doc/picoflow.from_2.md +0 -55
  219. package/api/doc/picoflow.from_3.md +0 -55
  220. package/api/doc/picoflow.from_4.md +0 -55
  221. package/api/doc/picoflow.from_5.md +0 -55
  222. package/api/doc/picoflow.isdisposable.md +0 -55
  223. package/api/doc/picoflow.map.md +0 -59
  224. package/api/doc/picoflow.md +0 -544
  225. package/api/doc/picoflow.resource.md +0 -55
  226. package/api/doc/picoflow.resourceasync.md +0 -55
  227. package/api/doc/picoflow.signal.md +0 -19
  228. package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
  229. package/api/doc/picoflow.solidderivation.get.md +0 -13
  230. package/api/doc/picoflow.solidderivation.md +0 -94
  231. package/api/doc/picoflow.solidgetter.md +0 -13
  232. package/api/doc/picoflow.solidobservable.get.md +0 -13
  233. package/api/doc/picoflow.solidobservable.md +0 -57
  234. package/api/doc/picoflow.solidresource._constructor_.md +0 -49
  235. package/api/doc/picoflow.solidresource.get.md +0 -13
  236. package/api/doc/picoflow.solidresource.latest.md +0 -13
  237. package/api/doc/picoflow.solidresource.md +0 -157
  238. package/api/doc/picoflow.solidresource.refetch.md +0 -13
  239. package/api/doc/picoflow.solidresource.state.md +0 -13
  240. package/api/doc/picoflow.solidstate._constructor_.md +0 -49
  241. package/api/doc/picoflow.solidstate.get.md +0 -13
  242. package/api/doc/picoflow.solidstate.md +0 -115
  243. package/api/doc/picoflow.solidstate.set.md +0 -13
  244. package/api/doc/picoflow.state.md +0 -55
  245. package/api/doc/picoflow.stream.md +0 -55
  246. package/api/doc/picoflow.streamasync.md +0 -55
  247. package/api/picoflow.public.api.md +0 -244
  248. package/api-extractor.json +0 -61
@@ -1,51 +1,109 @@
1
1
  import { FlowEffect } from "./effect";
2
2
  import { FlowSignal } from "./signal";
3
-
4
- /**
5
- * A function that retrieves the current value from a FlowObservable.
6
- * @typeparam T - The type of the value held by the observable.
7
- * @param observable - The FlowObservable instance to retrieve the value from.
8
- * @returns The current value of the observable.
9
- * @public
10
- */
11
- export type FlowGetter = <T>(observable: FlowObservable<T>) => T;
3
+ import type { TrackingContext } from "./trackingContext";
12
4
 
13
5
  /**
14
6
  * Represents a reactive observable that holds and tracks a value.
15
7
  *
8
+ * @remarks
9
+ * FlowObservable is the base class for all reactive values in PicoFlow. It provides two ways
10
+ * to access the current value:
16
11
  *
17
- * @remarks Subclasses must implement the {@link FlowObservable.get} method to return the current value.
18
- * @typeparam T - The type of the value held by the observable.
12
+ * 1. **Tracked access** via `get(context)`: Registers the observable as a dependency in the
13
+ * tracking context, so changes trigger re-execution of the effect or derivation.
14
+ *
15
+ * 2. **Untracked access** via `pick()` or `get(null)`: Reads the current value without registering
16
+ * a dependency, useful for reading values within effects that shouldn't trigger re-runs.
17
+ *
18
+ * Subclasses must implement the {@link FlowObservable._getRaw} method to provide the actual value.
19
+ *
20
+ * @typeParam T - The type of the value held by the observable.
19
21
  * @public
20
22
  */
21
23
  export abstract class FlowObservable<T> extends FlowSignal {
22
- /**
23
- * Retrieves the current value stored in the observable.
24
- * Subclasses must override this method to provide the current value.
25
- * @returns The current value of type T.
26
- * @public
27
- */
28
- abstract get(): T;
24
+ /**
25
+ * Gets the current value with optional dependency tracking.
26
+ *
27
+ * @param context - The tracking context for reactive tracking, or null for untracked access.
28
+ * When a context is provided, this observable is registered as a dependency. When null,
29
+ * the value is read without any tracking.
30
+ *
31
+ * @returns The current value of type T.
32
+ *
33
+ * @remarks
34
+ * Use `get(t)` within effects and derivations to create reactive dependencies.
35
+ * Use `get(null)` when you need to read a value without tracking (though `pick()` is more idiomatic).
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * effect((t) => {
40
+ * const tracked = $state.get(t); // Dependency registered
41
+ * const untracked = $other.get(null); // No dependency
42
+ * });
43
+ * ```
44
+ *
45
+ * @public
46
+ */
47
+ get(context: TrackingContext | null): T {
48
+ if (context) {
49
+ this.watch(context);
50
+ }
51
+ return this._getRaw();
52
+ }
53
+
54
+ /**
55
+ * Gets the current value without any dependency tracking.
56
+ *
57
+ * @returns The current value of type T.
58
+ *
59
+ * @remarks
60
+ * This method is equivalent to calling `get(null)` but provides a more semantic and readable API.
61
+ * Use `pick()` when you want to read a snapshot of the current value without creating a reactive
62
+ * dependency. This is useful for:
63
+ * - Reading initial values
64
+ * - Accessing configuration that shouldn't trigger updates
65
+ * - Mixing tracked and untracked reads in the same effect
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Read a snapshot outside reactive context
70
+ * const currentValue = $state.pick();
71
+ *
72
+ * // Mix tracked and untracked reads
73
+ * effect((t) => {
74
+ * const tracked = $reactive.get(t); // Triggers re-runs
75
+ * const snapshot = $config.pick(); // Doesn't trigger re-runs
76
+ * processData(tracked, snapshot);
77
+ * });
78
+ * ```
79
+ *
80
+ * @public
81
+ */
82
+ pick(): T {
83
+ return this._getRaw();
84
+ }
29
85
 
30
- /* INTERNAL -------------------------------------------*/
86
+ /**
87
+ * Internal method to retrieve the raw value.
88
+ * Subclasses must override this method to provide the current value.
89
+ * @internal
90
+ */
91
+ protected abstract _getRaw(): T;
31
92
 
32
- /*@internal*/ protected _value!: T;
93
+ /* INTERNAL -------------------------------------------*/
33
94
 
34
- /*@internal*/ _getFrom(listener: FlowObservable<unknown> | FlowEffect): T {
35
- listener._registerDependency(this);
36
- return this.get();
37
- }
95
+ /** @internal */ protected _value!: T;
38
96
 
39
- /**
40
- * Subscribes a listener function to changes of the observable.
41
- * The listener is executed immediately with the current value and on subsequent updates.
42
- * @param listener - A callback function that receives the new value.
43
- * @returns A disposer function to cancel the subscription.
44
- */
45
- subscribe(listener: (value: T) => void): () => void {
46
- const effect = new FlowEffect((get) => {
47
- listener(get(this));
48
- });
49
- return () => effect.dispose();
50
- }
97
+ /**
98
+ * Subscribes a listener function to changes of the observable.
99
+ * The listener is executed immediately with the current value and on subsequent updates.
100
+ * @param listener - A callback function that receives the new value.
101
+ * @returns A disposer function to cancel the subscription.
102
+ */
103
+ subscribe(listener: (value: T) => void): () => void {
104
+ const effect = new FlowEffect((t) => {
105
+ listener(this.get(t));
106
+ });
107
+ return () => effect.dispose();
108
+ }
51
109
  }
@@ -1,12 +1,6 @@
1
1
  import type { FlowDisposable } from "./disposable";
2
2
  import type { FlowEffect } from "./effect";
3
-
4
- /**
5
- * A function for watching a FlowSignal.
6
- * @param signal - The FlowSignal that is being observed.
7
- * @public
8
- */
9
- export type FlowWatcher = (signal: FlowSignal) => void;
3
+ import type { TrackingContext } from "./trackingContext";
10
4
 
11
5
  /**
12
6
  * Represents a reactive signal.
@@ -16,102 +10,136 @@ export type FlowWatcher = (signal: FlowSignal) => void;
16
10
  * @public
17
11
  */
18
12
  export class FlowSignal implements FlowDisposable {
19
- /**
20
- * Triggers the FlowSignal.
21
- * Notifies all registered listeners and schedules execution of associated effects.
22
- * @throws If the FlowSignal has already been disposed.
23
- * @public
24
- */
25
- public trigger(): void {
26
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
27
- this._notify();
28
- }
29
-
30
- /**
31
- * Disposes the FlowSignal.
32
- * Cleans up all registered effects, listeners, and dependencies.
33
- * Once disposed, further usage of the signal will throw an error.
34
- * @throws If the FlowSignal is already disposed.
35
- * @public
36
- */
37
- public dispose(options?: { self: boolean }): void {
38
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
39
- if (options?.self) {
40
- Array.from(this._effects).forEach((effect) =>
41
- effect._unregisterDependency(this),
42
- );
43
- Array.from(this._listeners).forEach((listener) =>
44
- listener._unregisterDependency(this),
45
- );
46
- } else {
47
- Array.from(this._effects).forEach((effect) => effect.dispose());
48
- Array.from(this._listeners).forEach((listener) =>
49
- listener.dispose(),
50
- );
51
- }
52
- Array.from(this._dependencies).forEach((dependency) => {
53
- this._unregisterDependency(dependency);
54
- });
55
- this._disposed = true;
56
- }
57
-
58
- /**
59
- * Indicates whether the FlowSignal has been disposed.
60
- * @remarks Once disposed, the signal should not be used.
61
- * @public
62
- */
63
- public get disposed(): boolean {
64
- return this._disposed;
65
- }
66
-
67
- /* INTERNAL ------------------------------------------------------------- */
68
-
69
- /*@internal*/ protected _disposed = false;
70
-
71
- /*@internal*/ protected _dependencies = new Set<FlowSignal>();
72
-
73
- /*@internal*/ protected _listeners = new Set<FlowSignal>();
74
-
75
- /*@internal*/ protected _effects = new Set<FlowEffect>();
76
-
77
- /*@internal*/ _watch(): void {
78
- /* v8 ignore next 1 */
79
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
80
- }
81
-
82
- /*@internal*/ _notify(): void {
83
- this._listeners.forEach((listener) => listener._notify());
84
- this._effects.forEach((effect) => effect._exec());
85
- }
86
-
87
- /*@internal*/ _watchFrom(listener: FlowSignal | FlowEffect): void {
88
- listener._registerDependency(this);
89
- this._watch();
90
- }
91
-
92
- /*@internal*/ _registerDependency(dependency: FlowSignal): void {
93
- this._dependencies.add(dependency);
94
- dependency._registerListener(this);
95
- }
96
-
97
- /*@internal*/ _unregisterDependency(dependency: FlowSignal): void {
98
- this._dependencies.delete(dependency);
99
- dependency._unregisterListener(this);
100
- }
101
-
102
- /*@internal*/ _registerListener(signal: FlowSignal): void {
103
- this._listeners.add(signal);
104
- }
105
-
106
- /*@internal*/ _unregisterListener(signal: FlowSignal): void {
107
- this._listeners.delete(signal);
108
- }
109
-
110
- /*@internal*/ _registerEffect(effect: FlowEffect): void {
111
- this._effects.add(effect);
112
- }
113
-
114
- /*@internal*/ _unregisterEffect(effect: FlowEffect): void {
115
- this._effects.delete(effect);
116
- }
13
+ /**
14
+ * Triggers the FlowSignal.
15
+ * Notifies all registered listeners and schedules execution of associated effects.
16
+ * @throws If the FlowSignal has already been disposed.
17
+ * @public
18
+ */
19
+ public trigger(): void {
20
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
21
+ this._notify();
22
+ }
23
+
24
+ /**
25
+ * Watches the signal, registering it as a dependency in the tracking context.
26
+ *
27
+ * @param context - The tracking context in which to register this signal as a dependency.
28
+ *
29
+ * @remarks
30
+ * Use `watch()` when you want to track a signal without reading its value (signals don't
31
+ * have values to read). This is useful for triggering effects based on signal events
32
+ * without needing associated data.
33
+ *
34
+ * When the signal is triggered via `trigger()`, any effects or derivations that have
35
+ * watched this signal will automatically re-execute.
36
+ *
37
+ * This method must be called within an effect or derivation context where a TrackingContext
38
+ * is available. For observables (which hold values), use `.get(t)` instead, which both
39
+ * reads the value and watches for changes.
40
+ *
41
+ * @throws Error if the signal has been disposed.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const $signal = signal();
46
+ *
47
+ * effect((t) => {
48
+ * $signal.watch(t); // Track the signal
49
+ * console.log('Signal triggered!');
50
+ * });
51
+ *
52
+ * $signal.trigger(); // Logs: "Signal triggered!"
53
+ * ```
54
+ *
55
+ * @public
56
+ */
57
+ public watch(context: TrackingContext): void {
58
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
59
+ context._registerDependency(this);
60
+ }
61
+
62
+ /**
63
+ * Disposes the FlowSignal.
64
+ * Cleans up all registered effects, listeners, and dependencies.
65
+ * Once disposed, further usage of the signal will throw an error.
66
+ * @throws If the FlowSignal is already disposed.
67
+ * @public
68
+ */
69
+ public dispose(options?: { self: boolean }): void {
70
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
71
+ if (options?.self) {
72
+ Array.from(this._effects).forEach((effect) => {
73
+ effect._unregisterDependency(this);
74
+ });
75
+ Array.from(this._listeners).forEach((listener) => {
76
+ listener._unregisterDependency(this);
77
+ });
78
+ } else {
79
+ Array.from(this._effects).forEach((effect) => {
80
+ effect.dispose();
81
+ });
82
+ Array.from(this._listeners).forEach((listener) => {
83
+ listener.dispose();
84
+ });
85
+ }
86
+ Array.from(this._dependencies).forEach((dependency) => {
87
+ this._unregisterDependency(dependency);
88
+ });
89
+ this._disposed = true;
90
+ }
91
+
92
+ /**
93
+ * Indicates whether the FlowSignal has been disposed.
94
+ * @remarks Once disposed, the signal should not be used.
95
+ * @public
96
+ */
97
+ public get disposed(): boolean {
98
+ return this._disposed;
99
+ }
100
+
101
+ /* INTERNAL ------------------------------------------------------------- */
102
+
103
+ /** @internal */ protected _disposed = false;
104
+
105
+ /** @internal */ protected _dependencies = new Set<FlowSignal>();
106
+
107
+ /** @internal */ protected _listeners = new Set<FlowSignal>();
108
+
109
+ /** @internal */ protected _effects = new Set<FlowEffect>();
110
+
111
+ /** @internal */ _notify(): void {
112
+ this._listeners.forEach((listener) => {
113
+ listener._notify();
114
+ });
115
+ this._effects.forEach((effect) => {
116
+ effect._exec();
117
+ });
118
+ }
119
+
120
+ /** @internal */ _registerDependency(dependency: FlowSignal): void {
121
+ this._dependencies.add(dependency);
122
+ dependency._registerListener(this);
123
+ }
124
+
125
+ /** @internal */ _unregisterDependency(dependency: FlowSignal): void {
126
+ this._dependencies.delete(dependency);
127
+ dependency._unregisterListener(this);
128
+ }
129
+
130
+ /** @internal */ _registerListener(signal: FlowSignal): void {
131
+ this._listeners.add(signal);
132
+ }
133
+
134
+ /** @internal */ _unregisterListener(signal: FlowSignal): void {
135
+ this._listeners.delete(signal);
136
+ }
137
+
138
+ /** @internal */ _registerEffect(effect: FlowEffect): void {
139
+ this._effects.add(effect);
140
+ }
141
+
142
+ /** @internal */ _unregisterEffect(effect: FlowEffect): void {
143
+ this._effects.delete(effect);
144
+ }
117
145
  }
@@ -3,37 +3,58 @@ import { FlowConstant } from "./constant";
3
3
  /**
4
4
  * Represents a reactive state that holds a mutable value.
5
5
  *
6
- * @typeparam T - The type of the state value.
6
+ * @typeParam T - The type of the state value.
7
7
  *
8
8
  * @remarks
9
- * FlowState extends FlowConstant, which provides the {@link FlowConstant.get} method to read
10
- * the current state. Use the {@link FlowState.set} method to update the state. When the state is updated,
11
- * subscribers are notified automatically. This class notifies subscribers only when the value changes.
9
+ * FlowState extends FlowConstant and inherits reactive value access methods from FlowObservable.
10
+ * You can read the state value using `get(context)` for tracked access or `pick()` for untracked access.
11
+ * Use the {@link FlowState.set} method to update the state value.
12
+ *
13
+ * When the state is updated with a new value that differs from the current value, all dependent
14
+ * effects and derivations are automatically notified and re-executed. If the new value is strictly
15
+ * equal to the current value, no notification occurs.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const $count = state(0);
20
+ *
21
+ * // Read with tracking
22
+ * effect((t) => {
23
+ * console.log($count.get(t)); // Effect re-runs when $count changes
24
+ * });
25
+ *
26
+ * // Read without tracking
27
+ * const snapshot = $count.pick();
28
+ *
29
+ * // Update the value
30
+ * $count.set(1);
31
+ * $count.set(current => current + 1);
32
+ * ```
12
33
  *
13
34
  * @public
14
35
  */
15
36
  export class FlowState<T> extends FlowConstant<T> {
16
- /**
17
- * Updates the state with a new value.
18
- * @param value - A new value or a callback function that computes a new value based on the current state.
19
- * @remarks
20
- * If the computed new value is strictly equal to the current state value, no change is made and subscribers
21
- * will not be notified. Otherwise, the state is updated and all subscribers are informed of the change.
22
- * @throws Error if the state has been disposed.
23
- * @public
24
- */
25
- set(value: T | ((current: T) => T)): void {
26
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
37
+ /**
38
+ * Updates the state with a new value.
39
+ * @param value - A new value or a callback function that computes a new value based on the current state.
40
+ * @remarks
41
+ * If the computed new value is strictly equal to the current state value, no change is made and subscribers
42
+ * will not be notified. Otherwise, the state is updated and all subscribers are informed of the change.
43
+ * @throws Error if the state has been disposed.
44
+ * @public
45
+ */
46
+ set(value: T | ((current: T) => T)): void {
47
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
27
48
 
28
- // compute new value
29
- const next =
30
- typeof value === "function"
31
- ? (value as (current: T) => T)(this._value)
32
- : value;
49
+ // compute new value
50
+ const next =
51
+ typeof value === "function"
52
+ ? (value as (current: T) => T)(this._value)
53
+ : value;
33
54
 
34
- // apply new value
35
- if (next === this._value) return;
36
- this._value = next;
37
- this._notify();
38
- }
55
+ // apply new value
56
+ if (next === this._value) return;
57
+ this._value = next;
58
+ this._notify();
59
+ }
39
60
  }
@@ -0,0 +1,45 @@
1
+ import type { FlowDerivation } from "./derivation";
2
+ import type { FlowEffect } from "./effect";
3
+ import type { FlowSignal } from "./signal";
4
+
5
+ /**
6
+ * Represents a tracking context used to register dependencies during reactive computations.
7
+ *
8
+ * @remarks
9
+ * TrackingContext is the core mechanism that enables automatic dependency tracking in PicoFlow's
10
+ * reactive system. When you create an effect or derivation, PicoFlow automatically creates and
11
+ * manages a TrackingContext instance for you. This context is passed as a parameter (typically
12
+ * named `t`) to your computation functions.
13
+ *
14
+ * You use the tracking context to explicitly mark which observables should be tracked as dependencies:
15
+ * - Call `observable.get(t)` to read a value AND register it as a dependency
16
+ * - Call `observable.pick()` to read a value WITHOUT registering it as a dependency
17
+ * - Call `signal.watch(t)` to register a signal as a dependency without reading a value
18
+ *
19
+ * End-users typically don't instantiate TrackingContext directly; instead, they receive it as
20
+ * a parameter in effect and derivation callbacks.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // TrackingContext passed as parameter 't'
25
+ * effect((t) => {
26
+ * const value = $state.get(t); // Tracked dependency
27
+ * const snapshot = $other.pick(); // Not tracked
28
+ * console.log(value, snapshot);
29
+ * });
30
+ * ```
31
+ *
32
+ * @public
33
+ */
34
+ export class TrackingContext {
35
+ /** @internal */
36
+ constructor(private _owner: FlowEffect | FlowDerivation<unknown>) {}
37
+
38
+ /**
39
+ * Registers a dependency on the given signal.
40
+ * @internal
41
+ */
42
+ /** @internal */ _registerDependency(signal: FlowSignal): void {
43
+ this._owner._registerDependency(signal);
44
+ }
45
+ }