@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,193 +0,0 @@
1
- import { FlowState, isDisposable } from "../basic/";
2
-
3
- /**
4
- * Represents a reactive map that extends {@link FlowState} for tracking key-value pairs.
5
- *
6
- * @remarks
7
- * FlowMap wraps a native JavaScript Map and provides reactive tracking at multiple granularity levels.
8
- * Unlike plain reactive state, FlowMap offers fine-grained reactivity that lets you track:
9
- *
10
- * 1. **Whole map changes**: Via `get(t)` or `pick()` on the FlowMap itself
11
- * 2. **Last add operation**: Via the `$lastAdded` signal, track which key-value pair was most recently added
12
- * 3. **Last update operation**: Via the `$lastUpdated` signal, track which key-value pair was most recently updated
13
- * 4. **Last delete operation**: Via the `$lastDeleted` signal, track which key-value pair was most recently removed
14
- *
15
- * **Reactive Signals:**
16
- * - **$lastAdded**: A FlowState containing `{ key?: K, value?: V }` updated on each `add()` call
17
- * - **$lastUpdated**: A FlowState containing `{ key?: K, value?: V }` updated on each `update()` call
18
- * - **$lastDeleted**: A FlowState containing `{ key?: K, value?: V }` updated on each `delete()` call
19
- *
20
- * These signals enable fine-grained reactivity patterns where effects can respond to specific
21
- * map operations without re-processing the entire map.
22
- *
23
- * **Use Cases:**
24
- * - Entity stores where you want to track additions/removals separately
25
- * - Cache implementations with granular invalidation
26
- * - Collections where operations on individual keys matter
27
- * - UI state where you want to animate specific additions or removals
28
- *
29
- * @example
30
- * ```typescript
31
- * const $users = map<string, User>();
32
- *
33
- * // Track the whole map
34
- * effect((t) => {
35
- * const users = $users.get(t);
36
- * console.log(`Total users: ${users.size}`);
37
- * });
38
- *
39
- * // Track only additions
40
- * effect((t) => {
41
- * const { key, value } = $users.$lastAdded.get(t);
42
- * if (key && value) {
43
- * console.log(`User ${key} was added:`, value);
44
- * }
45
- * });
46
- *
47
- * // Track only updates
48
- * effect((t) => {
49
- * const { key, value } = $users.$lastUpdated.get(t);
50
- * if (key && value) {
51
- * console.log(`User ${key} was updated:`, value);
52
- * }
53
- * });
54
- *
55
- * // Track only deletions
56
- * effect((t) => {
57
- * const { key, value } = $users.$lastDeleted.get(t);
58
- * if (key && value) {
59
- * console.log(`User ${key} was deleted:`, value);
60
- * }
61
- * });
62
- *
63
- * // Modify the map
64
- * $users.add('user1', { name: 'John', age: 30 });
65
- * $users.add('user2', { name: 'Jane', age: 25 });
66
- * $users.update('user1', { name: 'John', age: 31 });
67
- * $users.delete('user1');
68
- * ```
69
- *
70
- * @typeParam K - The type of the map keys.
71
- * @typeParam V - The type of the map values.
72
- *
73
- * @public
74
- */
75
- export class FlowMap<K, V> extends FlowState<Map<K, V>> {
76
- /**
77
- * A reactive state that holds the most recent key and value that were added.
78
- *
79
- * @remarks
80
- * When a key is added via {@link FlowMap.add}, this state is updated with
81
- * the corresponding key and value.
82
- *
83
- * @public
84
- */
85
- public $lastAdded = new FlowState<{ key: K; value: V } | null>(null);
86
-
87
- /**
88
- * A reactive state that holds the most recent key and value that were updated.
89
- *
90
- * @remarks
91
- * When a key is updated via {@link FlowMap.update}, this state is updated with
92
- * the corresponding key and value.
93
- *
94
- * @public
95
- */
96
- public $lastUpdated = new FlowState<{ key: K; value: V } | null>(null);
97
-
98
- /**
99
- * A reactive state that holds the most recent key and value that were deleted.
100
- *
101
- * @remarks
102
- * When a key is deleted via {@link FlowMap.delete}, this state is updated with
103
- * the corresponding key and its last known value.
104
- *
105
- * @public
106
- */
107
- public $lastDeleted = new FlowState<{ key: K; value: V } | null>(null);
108
-
109
- /**
110
- * Adds a new key-value pair to the map.
111
- *
112
- * @param key - The key to add.
113
- * @param value - The value to associate with the key.
114
- * @throws If the FlowMap instance is disposed.
115
- * @throws If the key already exists in the map.
116
- *
117
- * @remarks
118
- * Adds a new entry to the internal map, emits the key-value pair via {@link FlowMap.$lastAdded},
119
- * and notifies all subscribers of the change.
120
- *
121
- * @public
122
- */
123
- public add(key: K, value: V): void {
124
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
125
- if (this._value.has(key)) {
126
- throw new Error("[PicoFlow] Key already exists");
127
- }
128
- this._value.set(key, value);
129
- this.$lastAdded.set({ key, value });
130
- this._notify();
131
- }
132
-
133
- /**
134
- * Updates an existing key-value pair in the map.
135
- *
136
- * @param key - The key to update.
137
- * @param value - The new value to associate with the key.
138
- * @throws If the FlowMap instance is disposed.
139
- * @throws If the key does not exist in the map.
140
- *
141
- * @remarks
142
- * Updates an existing entry in the internal map, emits the key-value pair via {@link FlowMap.$lastUpdated},
143
- * and notifies all subscribers of the change.
144
- *
145
- * @public
146
- */
147
- public update(key: K, value: V): void {
148
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
149
- if (!this._value.has(key)) {
150
- throw new Error("[PicoFlow] Key does not exist");
151
- }
152
- this._value.set(key, value);
153
- this.$lastUpdated.set({ key, value });
154
- this._notify();
155
- }
156
-
157
- /**
158
- * Deletes the value at the specified key from the underlying map.
159
- *
160
- * @param key - The key to delete.
161
- * @throws If the FlowMap instance is disposed.
162
- *
163
- * @remarks
164
- * Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$lastDeleted},
165
- * and notifies all subscribers of the change.
166
- *
167
- * @public
168
- */
169
- public delete(key: K): void {
170
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
171
- const value = this._value.get(key);
172
- if (!value) throw new Error("[PicoFlow] Key does not exist");
173
- this._value.delete(key);
174
- this.$lastDeleted.set({ key, value });
175
- this._notify();
176
- }
177
-
178
- /**
179
- * Disposes the FlowMap and its values.
180
- * @param options - Disposal options.
181
- * @public
182
- */
183
- override dispose(options?: { self: boolean }): void {
184
- super.dispose(options);
185
- this._value.forEach((item) => {
186
- if (isDisposable(item)) item.dispose(options);
187
- });
188
- this._value.clear();
189
- this.$lastAdded.dispose(options);
190
- this.$lastUpdated.dispose(options);
191
- this.$lastDeleted.dispose(options);
192
- }
193
- }
@@ -1,97 +0,0 @@
1
- import { FlowObservable } from "./observable";
2
-
3
- /**
4
- * Represents a reactive and immutable constant value that can be computed lazily upon first access.
5
- *
6
- * @remarks
7
- * FlowConstant extends FlowObservable to provide an immutable reactive value. Unlike {@link FlowState},
8
- * which is mutable via the `set()` method, a constant's value never changes after initialization.
9
- *
10
- * **Initialization Patterns:**
11
- * - **Direct value**: Pass a value directly; it's stored immediately.
12
- * - **Lazy initialization**: Pass a function; it's called only when the value is first accessed
13
- * via `get()` or `pick()`.
14
- *
15
- * **Lazy Evaluation Benefits:**
16
- * Lazy initialization is useful when:
17
- * - The computation is expensive and may not be needed immediately
18
- * - The value depends on resources that aren't available at construction time
19
- * - You want to defer computation until the value is actually needed
20
- *
21
- * **Caching:**
22
- * Once computed (either immediately or lazily), the value is cached permanently. All subsequent
23
- * accesses return the cached value without re-computation.
24
- *
25
- * **Use Cases:**
26
- * - Configuration values that don't change
27
- * - Expensive computations that should only run once
28
- * - Initialization values for other reactive primitives
29
- *
30
- * @example
31
- * ```typescript
32
- * // Direct value - initialized immediately
33
- * const $config = constant({ apiUrl: 'https://api.example.com' });
34
- *
35
- * // Lazy initialization - computed on first access
36
- * const $expensiveValue = constant(() => {
37
- * console.log('Computing...');
38
- * return performExpensiveCalculation();
39
- * });
40
- *
41
- * $expensiveValue.pick(); // Logs: "Computing..."
42
- * $expensiveValue.pick(); // No log - returns cached value
43
- * ```
44
- *
45
- * @typeParam T - The type of the constant value.
46
- *
47
- * @public
48
- */
49
- export class FlowConstant<T> extends FlowObservable<T> {
50
- /**
51
- * Creates a new FlowConstant instance.
52
- *
53
- * @param value - Either a direct value of type T or a function returning a value of type T.
54
- * If a function is provided, it will be invoked lazily on the first value access.
55
- * If a direct value is provided, it is stored immediately.
56
- *
57
- * @public
58
- */
59
- constructor(value: T | (() => T)) {
60
- super();
61
- this._initEager(value);
62
- }
63
-
64
- /**
65
- * Internal method to get the raw value.
66
- * @internal
67
- */
68
- protected _getRaw(): T {
69
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
70
- this._initLazy();
71
- return this._value;
72
- }
73
-
74
- /* INTERNAL --------------------------------------------------------- */
75
-
76
- /** @internal */ protected _initialized = false;
77
- /** @internal */ protected _init?: () => T;
78
-
79
- /** @internal */ protected _initEager(value: T | (() => T)): void {
80
- if (typeof value === "function") {
81
- this._init = value as () => T;
82
- } else {
83
- this._value = value;
84
- this._initialized = true;
85
- }
86
- }
87
-
88
- /** @internal */ protected _initLazy(): void {
89
- if (!this._initialized && this._init) {
90
- this._value = this._init();
91
- this._initialized = true;
92
- }
93
- /* v8 ignore next 2 */
94
- if (!this._initialized)
95
- throw new Error("[PicoFlow] Primitive can't be initialized");
96
- }
97
- }
@@ -1,147 +0,0 @@
1
- import { FlowObservable } from "./observable";
2
- import { TrackingContext } from "./trackingContext";
3
-
4
- /**
5
- * Represents a reactive derivation whose value is computed based on other reactive signals.
6
- *
7
- * @remarks
8
- * FlowDerivation creates a computed value that automatically tracks its dependencies and
9
- * recomputes when any dependency changes. The computation is lazy: it doesn't execute until
10
- * the value is first accessed (via `get()` or `pick()`), and subsequent accesses return the
11
- * cached value until a dependency changes.
12
- *
13
- * **Lazy Initialization:**
14
- * The compute function doesn't run immediately upon creation. It runs only when:
15
- * - The value is first read via `get()` or `pick()`
16
- * - The derivation is watched via `watch()`
17
- *
18
- * **Dirty Checking and Recomputation:**
19
- * When a tracked dependency changes, the derivation is marked as "dirty" but doesn't recompute
20
- * immediately. Recomputation happens lazily on the next value access. This prevents unnecessary
21
- * computations when multiple dependencies change in quick succession.
22
- *
23
- * **Dynamic Dependencies:**
24
- * Dependencies are tracked dynamically during each computation. If the compute function
25
- * conditionally tracks different observables, the dependency graph updates automatically.
26
- *
27
- * @example
28
- * ```typescript
29
- * const $firstName = state('John');
30
- * const $lastName = state('Doe');
31
- *
32
- * const $fullName = derivation((t) => {
33
- * return `${$firstName.get(t)} ${$lastName.get(t)}`;
34
- * });
35
- *
36
- * // Compute function hasn't run yet (lazy)
37
- * const name = $fullName.pick(); // Now it computes
38
- * ```
39
- *
40
- * @typeParam T - The type of the computed value.
41
- * @public
42
- */
43
- export class FlowDerivation<T> extends FlowObservable<T> {
44
- /**
45
- * Creates a new FlowDerivation.
46
- *
47
- * @param compute - A function that computes the derived value using a tracking context.
48
- * The function receives a TrackingContext and should use it to access dependencies via
49
- * `.get(t)`. The function is not executed immediately; it runs lazily on first access.
50
- *
51
- * @public
52
- */
53
- constructor(compute: (t: TrackingContext) => T) {
54
- super();
55
- this._compute = compute;
56
- this._trackedContext = new TrackingContext(this);
57
- }
58
-
59
- /**
60
- * Internal method to get the raw value.
61
- * @internal
62
- */
63
- protected _getRaw(): T {
64
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
65
- this._initLazy();
66
- this._update();
67
- return this._value;
68
- }
69
-
70
- /* INTERNAL --------------------------------------------------------- */
71
-
72
- private _initialized = false;
73
- private _dirty = false;
74
- private _compute: (t: TrackingContext) => T;
75
- private _trackedContext: TrackingContext;
76
-
77
- private _initLazy(): void {
78
- if (!this._initialized) {
79
- this._value = this._compute(this._trackedContext);
80
- this._initialized = true;
81
- }
82
- }
83
-
84
- /* @internal */ private _update(): void {
85
- if (this._dirty) {
86
- // Store current dependencies
87
- const dependencies = [...this._dependencies];
88
-
89
- // Clear current dependencies, compute and retrack dependencies
90
- this._dependencies.clear();
91
- this._value = this._compute(this._trackedContext);
92
-
93
- // Unsubscribe from dependencies that are no longer needed
94
- const dependenciesToRemove = dependencies.filter(
95
- (dependency) => !this._dependencies.has(dependency),
96
- );
97
- dependenciesToRemove.forEach((dependency) => {
98
- dependency._unregisterDependency(this);
99
- });
100
-
101
- this._dirty = false;
102
- }
103
- }
104
-
105
- /* @internal */ override _notify(): void {
106
- this._dirty = true;
107
- super._notify();
108
- }
109
-
110
- /**
111
- * Watches the derivation, registering it as a dependency in the given context.
112
- *
113
- * @param context - The tracking context in which to register this derivation.
114
- *
115
- * @remarks
116
- * This method overrides the base `watch()` to handle a special case: when a derivation
117
- * is watched without having its value read (e.g., `$derivation.watch(t)` instead of
118
- * `$derivation.get(t)`), the derivation still needs to compute its value to establish
119
- * its own dependencies. Otherwise, the derivation would be tracked but wouldn't track
120
- * its own dependencies, breaking the reactive chain.
121
- *
122
- * This override ensures that:
123
- * 1. The derivation is registered as a dependency in the provided context
124
- * 2. The derivation computes its value (if not already computed)
125
- * 3. The derivation tracks its own dependencies during computation
126
- *
127
- * @example
128
- * ```typescript
129
- * const $derived = derivation((t) => $state.get(t) * 2);
130
- *
131
- * effect((t) => {
132
- * $derived.watch(t); // Derivation computes even without reading value
133
- * doSomething(); // Effect re-runs when $derived's dependencies change
134
- * });
135
- * ```
136
- *
137
- * @public
138
- */
139
- public override watch(context: TrackingContext): void {
140
- super.watch(context); // Register this derivation as a dependency
141
-
142
- // Ensure the derivation is initialized and up-to-date
143
- // This is needed when watching a derivation without reading its value
144
- this._initLazy();
145
- this._update();
146
- }
147
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * Represents an object with a disposable lifecycle that manages resources requiring cleanup.
3
- *
4
- * @remarks
5
- * FlowDisposable is the interface for PicoFlow primitives that hold resources needing
6
- * explicit cleanup. Implementing this interface ensures that objects can properly release
7
- * resources such as subscriptions, event listeners, and dependent effects.
8
- *
9
- * **Disposal Behavior:**
10
- * All PicoFlow reactive primitives (signals, states, effects, derivations, etc.) implement
11
- * this interface. When disposed:
12
- * - The primitive becomes unusable and throws errors on further access
13
- * - All subscriptions and dependencies are cleaned up
14
- * - Memory is freed for garbage collection
15
- *
16
- * **Options:**
17
- * The optional `options` parameter controls disposal behavior:
18
- * - `{ self: true }`: Dispose only this object, leaving dependent effects/listeners active
19
- * - `{ self: false }` or omitted: Dispose this object AND all dependent effects/listeners (cascade)
20
- *
21
- * @example
22
- * ```typescript
23
- * const $state = state(0);
24
- * const fx = effect((t) => console.log($state.get(t)));
25
- *
26
- * // Cascade disposal - disposes $state and all its effects
27
- * $state.dispose();
28
- *
29
- * // Self-only disposal - disposes $state but leaves effects intact
30
- * $state.dispose({ self: true });
31
- * ```
32
- *
33
- * @public
34
- */
35
- export interface FlowDisposable {
36
- /**
37
- * Disposes resources held by this object.
38
- *
39
- * @param options - Optional configuration for disposal behavior.
40
- * @param options.self - When true, disposes only this object without cascading to dependents.
41
- * When false or omitted, disposes this object and all its dependent effects and listeners.
42
- *
43
- * @throws Error if the object has already been disposed (behavior may vary by implementation).
44
- */
45
- dispose(options?: { self: boolean }): void;
46
- }
47
-
48
- /**
49
- * Type guard that checks whether an object implements the FlowDisposable interface.
50
- *
51
- * @param obj - The object to test for disposability.
52
- * @returns True if the object has a `dispose` method and is therefore disposable, false otherwise.
53
- *
54
- * @remarks
55
- * This utility function is useful for safely checking if an object needs disposal before
56
- * attempting cleanup operations. It performs a runtime check for the presence of a `dispose`
57
- * method, making it safe to use with unknown types.
58
- *
59
- * **Common Use Cases:**
60
- * - Conditionally disposing items in collections (arrays, maps)
61
- * - Generic cleanup functions that handle both disposable and non-disposable objects
62
- * - Defensive programming when working with mixed types
63
- *
64
- * @example
65
- * ```typescript
66
- * function cleanupArray<T>(items: T[]) {
67
- * items.forEach(item => {
68
- * if (isDisposable(item)) {
69
- * item.dispose();
70
- * }
71
- * });
72
- * }
73
- *
74
- * const mixed = [state(1), "string", signal(), 42];
75
- * cleanupArray(mixed); // Only disposes the state and signal
76
- * ```
77
- *
78
- * @public
79
- */
80
- export function isDisposable(obj: unknown): obj is FlowDisposable {
81
- return (
82
- obj !== null &&
83
- obj !== undefined &&
84
- typeof (obj as FlowDisposable).dispose === "function"
85
- );
86
- }
@@ -1,104 +0,0 @@
1
- import type { FlowSignal } from "./signal";
2
- import { TrackingContext } from "./trackingContext";
3
-
4
- /**
5
- * Represents a reactive effect that executes side-effect functions based
6
- * on its tracked dependencies.
7
- *
8
- * @remarks
9
- * FlowEffect executes an apply function that performs side effects. The effect always runs
10
- * with a tracking context, allowing you to explicitly control which observables and signals
11
- * become dependencies using `.get(t)` for tracked reads or `.pick()` for untracked reads.
12
- *
13
- * When any tracked dependency changes, the effect automatically re-executes. The effect
14
- * runs immediately upon creation and whenever its dependencies trigger updates.
15
- *
16
- * Unlike the old API, effects in the new TrackingContext-based system always execute with
17
- * a tracking context available. You control reactivity by choosing which observables to track
18
- * within the effect body.
19
- *
20
- * @example
21
- * ```typescript
22
- * const fx = effect((t) => {
23
- * const reactive = $stateA.get(t); // Tracked - effect re-runs when $stateA changes
24
- * const snapshot = $stateB.pick(); // Not tracked - changes don't trigger re-runs
25
- * console.log(reactive, snapshot);
26
- * });
27
- * ```
28
- *
29
- * @public
30
- */
31
- export class FlowEffect {
32
- /**
33
- * Creates a new FlowEffect.
34
- *
35
- * @param apply - A side-effect function that receives a tracking context to
36
- * access and register dependencies on reactive observables and signals.
37
- *
38
- * @remarks
39
- * The provided function is executed immediately upon construction with a tracking context.
40
- * Use the context parameter to call `.get(t)` on observables you want to track, or `.pick()`
41
- * on observables you want to read without creating dependencies.
42
- *
43
- * @public
44
- */
45
- constructor(apply: (t: TrackingContext) => void) {
46
- this._trackedContext = new TrackingContext(this);
47
- this._apply = apply;
48
- this._exec();
49
- }
50
-
51
- /**
52
- * Disposes the effect, unregistering all its tracked dependencies.
53
- *
54
- * @remarks
55
- * Once disposed, the effect must no longer be used. Trying to dispose an effect
56
- * that is already disposed will throw an error.
57
- *
58
- * @public
59
- */
60
- public dispose(): void {
61
- if (this._disposed) throw new Error("[PicoFlow] Effect is disposed");
62
- Array.from(this._dependencies).forEach((dependency) => {
63
- this._unregisterDependency(dependency);
64
- });
65
- this._disposed = true;
66
- }
67
-
68
- /**
69
- * Indicates whether this effect has been disposed.
70
- *
71
- * @returns A boolean value that is true if the effect is disposed, false otherwise.
72
- *
73
- * @public
74
- */
75
- public get disposed(): boolean {
76
- return this._disposed;
77
- }
78
-
79
- /* INTERNAL ------------------------------------------------------------ */
80
-
81
- private _disposed = false;
82
- private _dependencies = new Set<FlowSignal>();
83
- private _trackedContext: TrackingContext;
84
- private _apply: (t: TrackingContext) => void;
85
-
86
- /** @internal */ _exec(): void {
87
- if (this._disposed)
88
- /* v8 ignore next 1 */
89
- throw new Error("[PicoFlow] Effect is disposed");
90
-
91
- // Always execute with tracking context
92
- this._apply(this._trackedContext);
93
- }
94
-
95
- /** @internal */ _registerDependency(dependency: FlowSignal): void {
96
- this._dependencies.add(dependency);
97
- dependency._registerEffect(this);
98
- }
99
-
100
- /** @internal */ _unregisterDependency(dependency: FlowSignal): void {
101
- this._dependencies.delete(dependency);
102
- dependency._unregisterEffect(this);
103
- }
104
- }
@@ -1,9 +0,0 @@
1
- export { FlowConstant } from "./constant";
2
- export { FlowDerivation } from "./derivation";
3
- export type { FlowDisposable } from "./disposable";
4
- export { isDisposable } from "./disposable";
5
- export { FlowEffect } from "./effect";
6
- export { FlowObservable } from "./observable";
7
- export { FlowSignal } from "./signal";
8
- export { FlowState } from "./state";
9
- export { TrackingContext } from "./trackingContext";