@ersbeth/picoflow 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
  2. package/README.md +25 -171
  3. package/biome.json +4 -1
  4. package/dist/picoflow.js +1129 -661
  5. package/dist/types/flow/base/flowDisposable.d.ts +67 -0
  6. package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
  7. package/dist/types/flow/base/flowEffect.d.ts +127 -0
  8. package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
  9. package/dist/types/flow/base/flowGraph.d.ts +97 -0
  10. package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
  11. package/dist/types/flow/base/flowSignal.d.ts +134 -0
  12. package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
  13. package/dist/types/flow/base/flowTracker.d.ts +15 -0
  14. package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
  15. package/dist/types/flow/base/index.d.ts +7 -0
  16. package/dist/types/flow/base/index.d.ts.map +1 -0
  17. package/dist/types/flow/base/utils.d.ts +20 -0
  18. package/dist/types/flow/base/utils.d.ts.map +1 -0
  19. package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
  20. package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
  21. package/dist/types/flow/collections/flowMap.d.ts +224 -0
  22. package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
  23. package/dist/types/flow/collections/index.d.ts +3 -0
  24. package/dist/types/flow/collections/index.d.ts.map +1 -0
  25. package/dist/types/flow/index.d.ts +4 -0
  26. package/dist/types/flow/index.d.ts.map +1 -0
  27. package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
  28. package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
  29. package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
  30. package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
  31. package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
  32. package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
  33. package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
  34. package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
  35. package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
  36. package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
  37. package/dist/types/flow/nodes/async/index.d.ts +6 -0
  38. package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
  39. package/dist/types/flow/nodes/index.d.ts +3 -0
  40. package/dist/types/flow/nodes/index.d.ts.map +1 -0
  41. package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
  42. package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
  43. package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
  44. package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
  45. package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
  46. package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
  47. package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
  48. package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
  49. package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
  50. package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
  51. package/dist/types/flow/nodes/sync/index.d.ts +6 -0
  52. package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
  53. package/dist/types/index.d.ts +1 -4
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/dist/types/solid/converters.d.ts +34 -44
  56. package/dist/types/solid/converters.d.ts.map +1 -1
  57. package/dist/types/solid/primitives.d.ts +1 -0
  58. package/dist/types/solid/primitives.d.ts.map +1 -1
  59. package/docs/.vitepress/config.mts +1 -1
  60. package/docs/api/typedoc-sidebar.json +81 -1
  61. package/package.json +60 -58
  62. package/src/flow/base/flowDisposable.ts +71 -0
  63. package/src/flow/base/flowEffect.ts +171 -0
  64. package/src/flow/base/flowGraph.ts +288 -0
  65. package/src/flow/base/flowSignal.ts +207 -0
  66. package/src/flow/base/flowTracker.ts +17 -0
  67. package/src/flow/base/index.ts +6 -0
  68. package/src/flow/base/utils.ts +19 -0
  69. package/src/flow/collections/flowArray.ts +409 -0
  70. package/src/flow/collections/flowMap.ts +398 -0
  71. package/src/flow/collections/index.ts +2 -0
  72. package/src/flow/index.ts +3 -0
  73. package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
  74. package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
  75. package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
  76. package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
  77. package/src/flow/nodes/async/flowStateAsync.ts +116 -0
  78. package/src/flow/nodes/async/index.ts +5 -0
  79. package/src/flow/nodes/await/advanced/index.ts +5 -0
  80. package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
  81. package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
  82. package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
  83. package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
  84. package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
  85. package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
  86. package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
  87. package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
  88. package/src/flow/nodes/await/flowStateAwait.ts +130 -0
  89. package/src/flow/nodes/await/index.ts +5 -0
  90. package/src/flow/nodes/index.ts +3 -0
  91. package/src/flow/nodes/sync/flowConstant.ts +111 -0
  92. package/src/flow/nodes/sync/flowDerivation.ts +105 -0
  93. package/src/flow/nodes/sync/flowNode.ts +439 -0
  94. package/src/flow/nodes/sync/flowReadonly.ts +57 -0
  95. package/src/flow/nodes/sync/flowState.ts +101 -0
  96. package/src/flow/nodes/sync/index.ts +5 -0
  97. package/src/index.ts +1 -47
  98. package/src/solid/converters.ts +59 -198
  99. package/src/solid/primitives.ts +4 -0
  100. package/test/base/flowEffect.test.ts +108 -0
  101. package/test/base/flowGraph.test.ts +485 -0
  102. package/test/base/flowSignal.test.ts +372 -0
  103. package/test/collections/flowArray.asyncStates.test.ts +1553 -0
  104. package/test/collections/flowArray.scalars.test.ts +1129 -0
  105. package/test/collections/flowArray.states.test.ts +1365 -0
  106. package/test/collections/flowMap.asyncStates.test.ts +1105 -0
  107. package/test/collections/flowMap.scalars.test.ts +877 -0
  108. package/test/collections/flowMap.states.test.ts +1097 -0
  109. package/test/nodes/async/flowConstantAsync.test.ts +860 -0
  110. package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
  111. package/test/nodes/async/flowStateAsync.test.ts +1387 -0
  112. package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
  113. package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
  114. package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
  115. package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
  116. package/test/nodes/await/flowConstantAwait.test.ts +643 -0
  117. package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
  118. package/test/nodes/await/flowStateAwait.test.ts +999 -0
  119. package/test/nodes/mixed/derivation.test.ts +1527 -0
  120. package/test/nodes/sync/flowConstant.test.ts +620 -0
  121. package/test/nodes/sync/flowDerivation.test.ts +1373 -0
  122. package/test/nodes/sync/flowState.test.ts +945 -0
  123. package/test/solid/converters.test.ts +721 -0
  124. package/test/solid/primitives.test.ts +1031 -0
  125. package/tsconfig.json +2 -1
  126. package/vitest.config.ts +7 -1
  127. package/IMPLEMENTATION_GUIDE.md +0 -1578
  128. package/dist/types/advanced/array.d.ts.map +0 -1
  129. package/dist/types/advanced/index.d.ts +0 -9
  130. package/dist/types/advanced/index.d.ts.map +0 -1
  131. package/dist/types/advanced/map.d.ts +0 -166
  132. package/dist/types/advanced/map.d.ts.map +0 -1
  133. package/dist/types/advanced/resource.d.ts +0 -78
  134. package/dist/types/advanced/resource.d.ts.map +0 -1
  135. package/dist/types/advanced/resourceAsync.d.ts +0 -56
  136. package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
  137. package/dist/types/advanced/stream.d.ts +0 -117
  138. package/dist/types/advanced/stream.d.ts.map +0 -1
  139. package/dist/types/advanced/streamAsync.d.ts +0 -97
  140. package/dist/types/advanced/streamAsync.d.ts.map +0 -1
  141. package/dist/types/basic/constant.d.ts +0 -60
  142. package/dist/types/basic/constant.d.ts.map +0 -1
  143. package/dist/types/basic/derivation.d.ts +0 -89
  144. package/dist/types/basic/derivation.d.ts.map +0 -1
  145. package/dist/types/basic/disposable.d.ts +0 -82
  146. package/dist/types/basic/disposable.d.ts.map +0 -1
  147. package/dist/types/basic/effect.d.ts +0 -67
  148. package/dist/types/basic/effect.d.ts.map +0 -1
  149. package/dist/types/basic/index.d.ts +0 -10
  150. package/dist/types/basic/index.d.ts.map +0 -1
  151. package/dist/types/basic/observable.d.ts +0 -83
  152. package/dist/types/basic/observable.d.ts.map +0 -1
  153. package/dist/types/basic/signal.d.ts +0 -69
  154. package/dist/types/basic/signal.d.ts.map +0 -1
  155. package/dist/types/basic/state.d.ts +0 -47
  156. package/dist/types/basic/state.d.ts.map +0 -1
  157. package/dist/types/basic/trackingContext.d.ts +0 -33
  158. package/dist/types/basic/trackingContext.d.ts.map +0 -1
  159. package/dist/types/creators.d.ts +0 -340
  160. package/dist/types/creators.d.ts.map +0 -1
  161. package/src/advanced/array.ts +0 -222
  162. package/src/advanced/index.ts +0 -12
  163. package/src/advanced/map.ts +0 -193
  164. package/src/basic/constant.ts +0 -97
  165. package/src/basic/derivation.ts +0 -147
  166. package/src/basic/disposable.ts +0 -86
  167. package/src/basic/effect.ts +0 -104
  168. package/src/basic/index.ts +0 -9
  169. package/src/basic/observable.ts +0 -109
  170. package/src/basic/signal.ts +0 -145
  171. package/src/basic/state.ts +0 -60
  172. package/src/basic/trackingContext.ts +0 -45
  173. package/src/creators.ts +0 -395
  174. package/test/array.test.ts +0 -600
  175. package/test/constant.test.ts +0 -44
  176. package/test/derivation.test.ts +0 -539
  177. package/test/effect.test.ts +0 -29
  178. package/test/map.test.ts +0 -240
  179. package/test/signal.test.ts +0 -72
  180. package/test/state.test.ts +0 -212
@@ -0,0 +1,288 @@
1
+ import type { FlowEffect } from "./flowEffect";
2
+
3
+ /**
4
+ * Represents a queued read operation in the reactive system.
5
+ *
6
+ * @remarks
7
+ * This interface is used internally to coordinate read operations. Most users
8
+ * will not interact with it directly.
9
+ *
10
+ * @internal
11
+ */
12
+ export interface ReadRequest<T> {
13
+ type: "read";
14
+ promise: Promise<T>;
15
+ resolve: (value: T) => void;
16
+ reject: (error: Error) => void;
17
+ read: () => T;
18
+ }
19
+
20
+ /**
21
+ * Represents a queued trigger notification in the reactive system.
22
+ *
23
+ * @remarks
24
+ * This interface is used internally to coordinate signal triggers. Most users
25
+ * will not interact with it directly.
26
+ *
27
+ * @internal
28
+ */
29
+ export interface TriggerRequest {
30
+ type: "trigger";
31
+ promise: Promise<void>;
32
+ resolve: () => void;
33
+ reject: (error: Error) => void;
34
+ notify: () => void;
35
+ }
36
+
37
+ /**
38
+ * Represents a queued write operation in the reactive system.
39
+ *
40
+ * @remarks
41
+ * This interface is used internally to coordinate write operations with update
42
+ * logic. Most users will not interact with it directly.
43
+ *
44
+ * @internal
45
+ */
46
+ export interface WriteRequest {
47
+ type: "write";
48
+ promise: Promise<void>;
49
+ resolve: () => void;
50
+ reject: (error: Error) => void;
51
+ notify: () => void;
52
+ update: () => boolean | Promise<boolean>;
53
+ }
54
+
55
+ /**
56
+ * Union type representing any queued operation in the reactive system.
57
+ *
58
+ * @remarks
59
+ * This type is used internally to coordinate different types of reactive
60
+ * operations (reads, writes, triggers). Most users will not interact with it
61
+ * directly.
62
+ *
63
+ * @internal
64
+ */
65
+ // biome-ignore lint/suspicious/noExplicitAny: we don't need to know the type of the value
66
+ export type ActionRequest = ReadRequest<any> | TriggerRequest | WriteRequest;
67
+
68
+ /**
69
+ * Coordinates reactive operations (reads, writes, triggers, effects).
70
+ *
71
+ * @remarks
72
+ * FlowGraph manages the execution order of reactive operations to ensure
73
+ * consistent state updates. It queues operations and processes them in a
74
+ * controlled sequence, executing effects after state changes complete.
75
+ *
76
+ * Most users will not interact with FlowGraph directly; reactive primitives
77
+ * use it internally. The `clear()` method may be useful for testing scenarios
78
+ * where you need to reset the internal state between tests.
79
+ *
80
+ * @public
81
+ */
82
+ export class FlowGraph {
83
+ private static _effectsQueue: FlowEffect[] = [];
84
+ private static _actionQueue: ActionRequest[] = [];
85
+ private static _processingActionQueue = false;
86
+
87
+ /**
88
+ * Resets all internal queues and processing state.
89
+ *
90
+ * @remarks
91
+ * Use this method primarily for testing scenarios where you need to ensure
92
+ * a clean state between test runs. It clears pending effects and queued
93
+ * operations.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * beforeEach(() => {
98
+ * FlowGraph.clear();
99
+ * });
100
+ * ```
101
+ *
102
+ * @public
103
+ */
104
+ static clear(): void {
105
+ FlowGraph._effectsQueue = [];
106
+ FlowGraph._actionQueue = [];
107
+ FlowGraph._processingActionQueue = false;
108
+ }
109
+
110
+ /**
111
+ * Queues a trigger notification for processing.
112
+ *
113
+ * @param notify - Function to call when the trigger is processed.
114
+ * @returns A promise that resolves after the trigger is processed.
115
+ *
116
+ * @remarks
117
+ * This method is used internally by reactive primitives to coordinate
118
+ * signal triggers. The promise resolves after all associated effects
119
+ * have been executed.
120
+ *
121
+ * @public
122
+ */
123
+ static requestTrigger(notify: () => void): Promise<void> {
124
+ const promiseWithResolvers = Promise.withResolvers<void>();
125
+ FlowGraph._actionQueue.push({
126
+ ...promiseWithResolvers,
127
+ type: "trigger",
128
+ notify,
129
+ });
130
+ FlowGraph._processActionQueue();
131
+ return promiseWithResolvers.promise;
132
+ }
133
+
134
+ /**
135
+ * Queues a read operation for processing.
136
+ *
137
+ * @param read - Function that performs the read operation.
138
+ * @returns A promise that resolves with the read value.
139
+ *
140
+ * @remarks
141
+ * This method is used internally by reactive primitives to coordinate
142
+ * read operations. The read function is executed and its result (or promise)
143
+ * is returned.
144
+ *
145
+ * @public
146
+ */
147
+ static requestRead<T>(read: () => T | Promise<T>): Promise<T> {
148
+ const promiseWithResolvers = Promise.withResolvers<T>();
149
+ FlowGraph._actionQueue.push({
150
+ ...promiseWithResolvers,
151
+ type: "read",
152
+ read,
153
+ });
154
+ FlowGraph._processActionQueue();
155
+ return promiseWithResolvers.promise;
156
+ }
157
+
158
+ /**
159
+ * Queues a write operation with update logic for processing.
160
+ *
161
+ * @param notify - Function to call if the update succeeds.
162
+ * @param update - Function that performs the update and returns whether
163
+ * it changed the value.
164
+ * @returns A promise that resolves after the write is processed.
165
+ *
166
+ * @remarks
167
+ * This method is used internally by reactive primitives to coordinate
168
+ * write operations. The update function is executed, and if it returns true
169
+ * (or a promise resolving to true), the notify function is called to
170
+ * trigger dependent effects.
171
+ *
172
+ * @public
173
+ */
174
+ static requestWrite(
175
+ notify: () => void,
176
+ update: () => boolean | Promise<boolean>,
177
+ ): Promise<void> {
178
+ const promiseWithResolvers = Promise.withResolvers<void>();
179
+ FlowGraph._actionQueue.push({
180
+ ...promiseWithResolvers,
181
+ type: "write",
182
+ notify,
183
+ update,
184
+ });
185
+ FlowGraph._processActionQueue();
186
+ return promiseWithResolvers.promise;
187
+ }
188
+
189
+ /**
190
+ * Queues effects for execution after the current operation completes.
191
+ *
192
+ * @param effects - Array of effects to queue for execution.
193
+ *
194
+ * @remarks
195
+ * This method is used internally by reactive primitives to schedule effect
196
+ * execution. Effects are executed after the current read/write/trigger
197
+ * operation completes, ensuring proper ordering of reactive updates.
198
+ *
199
+ * @public
200
+ */
201
+ static pushEffects(effects: FlowEffect[]): void {
202
+ FlowGraph._effectsQueue.push(...effects);
203
+ }
204
+
205
+ /** @internal */
206
+ private static _processActionQueue = async () => {
207
+ if (FlowGraph._processingActionQueue) return;
208
+ FlowGraph._processingActionQueue = true;
209
+
210
+ while (FlowGraph._actionQueue.length > 0) {
211
+ const actionRequest = FlowGraph._actionQueue.shift();
212
+ if (!actionRequest) break;
213
+
214
+ // Process read operation
215
+ if (actionRequest.type === "read") {
216
+ try {
217
+ const read = actionRequest.read();
218
+
219
+ let value: unknown;
220
+ if (read instanceof Promise) {
221
+ value = await read;
222
+ } else {
223
+ value = read;
224
+ }
225
+ actionRequest.resolve(value);
226
+ } catch (error: unknown) {
227
+ const safeError =
228
+ error instanceof Error ? error : new Error(String(error));
229
+ actionRequest.reject(safeError);
230
+ }
231
+ continue; // stop here
232
+ }
233
+
234
+ // Process write operation
235
+ if (actionRequest.type === "write") {
236
+ try {
237
+ const updateResult = actionRequest.update();
238
+
239
+ let updated = false;
240
+ if (updateResult instanceof Promise) {
241
+ updated = await updateResult;
242
+ } else {
243
+ updated = updateResult;
244
+ }
245
+
246
+ if (!updated) {
247
+ actionRequest.resolve();
248
+ continue;
249
+ }
250
+
251
+ actionRequest.notify();
252
+ } catch (error: unknown) {
253
+ const safeError =
254
+ error instanceof Error ? error : new Error(String(error));
255
+ actionRequest.reject(safeError);
256
+ continue;
257
+ }
258
+ }
259
+
260
+ // Process trigger operation
261
+ if (actionRequest.type === "trigger") {
262
+ actionRequest.notify();
263
+ }
264
+
265
+ // Process effects
266
+ let effectError: Error | null = null;
267
+ for (const effect of FlowGraph._effectsQueue) {
268
+ try {
269
+ await effect._requestExec();
270
+ } catch (error: unknown) {
271
+ effectError =
272
+ error instanceof Error ? error : new Error(String(error));
273
+ break;
274
+ }
275
+ }
276
+
277
+ FlowGraph._effectsQueue = [];
278
+
279
+ if (effectError) {
280
+ actionRequest.reject(effectError);
281
+ } else {
282
+ actionRequest.resolve();
283
+ }
284
+ }
285
+
286
+ FlowGraph._processingActionQueue = false;
287
+ };
288
+ }
@@ -0,0 +1,207 @@
1
+ import type { FlowDisposable } from "./flowDisposable";
2
+ import type { FlowEffect } from "./flowEffect";
3
+ import { FlowGraph } from "./flowGraph";
4
+ import type { FlowTracker } from "./flowTracker";
5
+
6
+ /**
7
+ * Event-like reactive primitive with no payload.
8
+ *
9
+ * @remarks
10
+ * A signal is used to broadcast that “something happened” (refresh, invalidate,
11
+ * notify). It does not carry data; it only notifies dependents that they should
12
+ * react. Track it with `watch(t)` inside an effect or derivation to re-run when
13
+ * the signal is triggered.
14
+ *
15
+ * Signals can be triggered and disposed. Once disposed, further interaction
16
+ * throws an error.
17
+ *
18
+ * @public
19
+ */
20
+ export class FlowSignal implements FlowDisposable, FlowTracker {
21
+ /**
22
+ * Triggers the signal and notifies all dependents.
23
+ *
24
+ * @remarks
25
+ * Any effect/derivation that called `watch(t)` on this signal will re-run.
26
+ * Returns a promise that settles after notifications complete.
27
+ *
28
+ * @throws Error if the signal is disposed.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const $tick = signal();
33
+ *
34
+ * effect((t) => {
35
+ * $tick.watch(t);
36
+ * console.log("tick");
37
+ * });
38
+ *
39
+ * $tick.trigger(); // logs "tick"
40
+ * ```
41
+ *
42
+ * @public
43
+ */
44
+ public async trigger() {
45
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
46
+ return FlowGraph.requestTrigger(() => this._notify());
47
+ }
48
+
49
+ /**
50
+ * Registers this signal as a dependency in the current tracking context.
51
+ *
52
+ * @param context - The tracking context (`t`) provided to effects/derivations.
53
+ *
54
+ * @remarks
55
+ * Signals have no value to read; calling `watch(t)` simply means “re-run me
56
+ * when this signal is triggered.” Call this inside an effect/derivation
57
+ * callback where a tracking context is available.
58
+ *
59
+ * @throws Error if the signal has been disposed.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const $refresh = signal();
64
+ *
65
+ * effect((t) => {
66
+ * $refresh.watch(t);
67
+ * console.log("refresh triggered");
68
+ * });
69
+ *
70
+ * $refresh.trigger(); // logs "refresh triggered"
71
+ * ```
72
+ *
73
+ * @public
74
+ */
75
+ public watch(caller: FlowTracker): void {
76
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
77
+ caller._registerDependency(this);
78
+ }
79
+
80
+ /**
81
+ * Disposes the signal and cleans up its dependencies/listeners.
82
+ *
83
+ * @remarks
84
+ * After disposal the signal must not be used; calling `trigger` or `watch`
85
+ * will throw. If `options?.self` is true, only this signal is disposed; when
86
+ * false or omitted, dependents may also be disposed depending on the
87
+ * implementation.
88
+ *
89
+ * @throws Error if the signal is already disposed.
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const $refresh = signal();
94
+ * // ... use it
95
+ * $refresh.dispose();
96
+ * ```
97
+ *
98
+ * @public
99
+ */
100
+ public dispose(options?: { self: boolean }): void {
101
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
102
+ if (options?.self) {
103
+ Array.from(this._effects).forEach((effect) => {
104
+ effect._unregisterDependency(this);
105
+ });
106
+ Array.from(this._listeners).forEach((listener) => {
107
+ listener._unregisterDependency(this);
108
+ });
109
+ } else {
110
+ Array.from(this._effects).forEach((effect) => {
111
+ effect.dispose();
112
+ });
113
+ Array.from(this._listeners).forEach((listener) => {
114
+ listener.dispose();
115
+ });
116
+ }
117
+ Array.from(this._dependencies).forEach((dependency) => {
118
+ this._unregisterDependency(dependency);
119
+ });
120
+ this._disposed = true;
121
+ }
122
+
123
+ /**
124
+ * Whether the signal has been disposed.
125
+ *
126
+ * @returns `true` if disposed; `false` while active.
127
+ *
128
+ * @remarks Use to guard operations or avoid double disposal.
129
+ *
130
+ * @public
131
+ */
132
+ public get disposed(): boolean {
133
+ return this._disposed;
134
+ }
135
+
136
+ /* INTERNAL ------------------------------------------------------------- */
137
+
138
+ protected _disposed = false;
139
+
140
+ protected _dependencies = new Set<FlowSignal>();
141
+
142
+ protected _listeners = new Set<FlowSignal>();
143
+
144
+ protected _effects = new Set<FlowEffect>();
145
+
146
+ protected _notify(): void {
147
+ FlowGraph.pushEffects(Array.from(this._effects));
148
+
149
+ this._listeners.forEach((listener) => {
150
+ listener._notify();
151
+ });
152
+ }
153
+
154
+ /** @internal */ _registerDependency(dependency: FlowSignal): void {
155
+ this._dependencies.add(dependency);
156
+ dependency._registerListener(this);
157
+ }
158
+
159
+ /** @internal */ _unregisterDependency(dependency: FlowSignal): void {
160
+ this._dependencies.delete(dependency);
161
+ dependency._unregisterListener(this);
162
+ }
163
+
164
+ /** @internal */ _registerListener(signal: FlowSignal): void {
165
+ this._listeners.add(signal);
166
+ }
167
+
168
+ /** @internal */ _unregisterListener(signal: FlowSignal): void {
169
+ this._listeners.delete(signal);
170
+ }
171
+
172
+ /** @internal */ _registerEffect(effect: FlowEffect): void {
173
+ this._effects.add(effect);
174
+ }
175
+
176
+ /** @internal */ _unregisterEffect(effect: FlowEffect): void {
177
+ this._effects.delete(effect);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Creates a new signal for event-like notifications.
183
+ *
184
+ * @returns A new instance of {@link FlowSignal}.
185
+ *
186
+ * @remarks
187
+ * Use signals to announce “something happened” when no payload is needed. Track
188
+ * them with `watch(t)` inside effects/derivations and trigger them to re-run
189
+ * dependents.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const $ready = signal();
194
+ *
195
+ * effect((t) => {
196
+ * $ready.watch(t);
197
+ * console.log("ready!");
198
+ * });
199
+ *
200
+ * $ready.trigger(); // logs "ready!"
201
+ * ```
202
+ *
203
+ * @public
204
+ */
205
+ export function signal(): FlowSignal {
206
+ return new FlowSignal();
207
+ }
@@ -0,0 +1,17 @@
1
+ import type { FlowSignal } from "./flowSignal";
2
+
3
+ /**
4
+ * Tracking context passed to reactive callbacks.
5
+ *
6
+ * @remarks
7
+ * Effects and derivations receive an instance of `FlowTracker` as the `t`
8
+ * parameter. Use it inside your callback to register dependencies (for example
9
+ * via APIs that accept `t`) so the callback can re-run when those dependencies
10
+ * change. Typical users do not implement this interface directly; it is
11
+ * implemented by the reactive primitives themselves.
12
+ *
13
+ * @public
14
+ */
15
+ export interface FlowTracker {
16
+ /** @internal */ _registerDependency(signal: FlowSignal): void;
17
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./flowDisposable";
2
+ export * from "./flowEffect";
3
+ export * from "./flowGraph";
4
+ export * from "./flowSignal";
5
+ export * from "./flowTracker";
6
+ export * from "./utils";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Excludes `Promise` types from `T`, returning `never` if `T` is a promise.
3
+ *
4
+ * @remarks
5
+ * Use `NotPromise<T>` to constrain generics to synchronous values. It is helpful
6
+ * when an API must reject promise inputs while accepting other types unchanged.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * function useSync<T>(value: NotPromise<T>) {
11
+ * // value cannot be a Promise
12
+ * return value;
13
+ * }
14
+ *
15
+ * useSync(123); // ok
16
+ * // useSync(Promise.resolve(1)); // type error
17
+ * ```
18
+ */
19
+ export type NotPromise<T> = T extends Promise<unknown> ? never : T;