@ersbeth/picoflow 1.0.1 → 1.1.1
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.
- package/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
- package/README.md +17 -1
- package/biome.json +4 -1
- package/dist/picoflow.js +1155 -582
- package/dist/types/flow/base/flowDisposable.d.ts +67 -0
- package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/flow/base/flowEffect.d.ts +127 -0
- package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
- package/dist/types/flow/base/flowGraph.d.ts +97 -0
- package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
- package/dist/types/flow/base/flowSignal.d.ts +134 -0
- package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
- package/dist/types/flow/base/flowTracker.d.ts +15 -0
- package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
- package/dist/types/flow/base/index.d.ts +7 -0
- package/dist/types/flow/base/index.d.ts.map +1 -0
- package/dist/types/flow/base/utils.d.ts +20 -0
- package/dist/types/flow/base/utils.d.ts.map +1 -0
- package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
- package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
- package/dist/types/flow/collections/flowMap.d.ts +224 -0
- package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
- package/dist/types/flow/collections/index.d.ts +3 -0
- package/dist/types/flow/collections/index.d.ts.map +1 -0
- package/dist/types/flow/index.d.ts +4 -0
- package/dist/types/flow/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/index.d.ts +6 -0
- package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/index.d.ts +3 -0
- package/dist/types/flow/nodes/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/index.d.ts +6 -0
- package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +34 -45
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/index.d.ts +2 -2
- package/dist/types/solid/index.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +1 -0
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +1 -1
- package/docs/api/typedoc-sidebar.json +81 -1
- package/package.json +60 -58
- package/src/flow/base/flowDisposable.ts +71 -0
- package/src/flow/base/flowEffect.ts +171 -0
- package/src/flow/base/flowGraph.ts +288 -0
- package/src/flow/base/flowSignal.ts +207 -0
- package/src/flow/base/flowTracker.ts +17 -0
- package/src/flow/base/index.ts +6 -0
- package/src/flow/base/utils.ts +19 -0
- package/src/flow/collections/flowArray.ts +409 -0
- package/src/flow/collections/flowMap.ts +398 -0
- package/src/flow/collections/index.ts +2 -0
- package/src/flow/index.ts +3 -0
- package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
- package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
- package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
- package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
- package/src/flow/nodes/async/flowStateAsync.ts +116 -0
- package/src/flow/nodes/async/index.ts +5 -0
- package/src/flow/nodes/await/advanced/index.ts +5 -0
- package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
- package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
- package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
- package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
- package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
- package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
- package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
- package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
- package/src/flow/nodes/await/flowStateAwait.ts +130 -0
- package/src/flow/nodes/await/index.ts +5 -0
- package/src/flow/nodes/index.ts +3 -0
- package/src/flow/nodes/sync/flowConstant.ts +111 -0
- package/src/flow/nodes/sync/flowDerivation.ts +105 -0
- package/src/flow/nodes/sync/flowNode.ts +439 -0
- package/src/flow/nodes/sync/flowReadonly.ts +57 -0
- package/src/flow/nodes/sync/flowState.ts +101 -0
- package/src/flow/nodes/sync/index.ts +5 -0
- package/src/index.ts +2 -47
- package/src/solid/converters.ts +60 -199
- package/src/solid/index.ts +2 -8
- package/src/solid/primitives.ts +4 -0
- package/test/base/flowEffect.test.ts +108 -0
- package/test/base/flowGraph.test.ts +485 -0
- package/test/base/flowSignal.test.ts +372 -0
- package/test/collections/flowArray.asyncStates.test.ts +1553 -0
- package/test/collections/flowArray.scalars.test.ts +1129 -0
- package/test/collections/flowArray.states.test.ts +1365 -0
- package/test/collections/flowMap.asyncStates.test.ts +1105 -0
- package/test/collections/flowMap.scalars.test.ts +877 -0
- package/test/collections/flowMap.states.test.ts +1097 -0
- package/test/nodes/async/flowConstantAsync.test.ts +860 -0
- package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
- package/test/nodes/async/flowStateAsync.test.ts +1387 -0
- package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
- package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
- package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
- package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
- package/test/nodes/await/flowConstantAwait.test.ts +643 -0
- package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
- package/test/nodes/await/flowStateAwait.test.ts +999 -0
- package/test/nodes/mixed/derivation.test.ts +1527 -0
- package/test/nodes/sync/flowConstant.test.ts +620 -0
- package/test/nodes/sync/flowDerivation.test.ts +1373 -0
- package/test/nodes/sync/flowState.test.ts +945 -0
- package/test/solid/converters.test.ts +721 -0
- package/test/solid/primitives.test.ts +1031 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +7 -1
- package/IMPLEMENTATION_GUIDE.md +0 -1578
- package/dist/types/advanced/array.d.ts.map +0 -1
- package/dist/types/advanced/index.d.ts +0 -9
- package/dist/types/advanced/index.d.ts.map +0 -1
- package/dist/types/advanced/map.d.ts +0 -166
- package/dist/types/advanced/map.d.ts.map +0 -1
- package/dist/types/advanced/resource.d.ts +0 -78
- package/dist/types/advanced/resource.d.ts.map +0 -1
- package/dist/types/advanced/resourceAsync.d.ts +0 -56
- package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
- package/dist/types/advanced/stream.d.ts +0 -117
- package/dist/types/advanced/stream.d.ts.map +0 -1
- package/dist/types/advanced/streamAsync.d.ts +0 -97
- package/dist/types/advanced/streamAsync.d.ts.map +0 -1
- package/dist/types/basic/constant.d.ts +0 -60
- package/dist/types/basic/constant.d.ts.map +0 -1
- package/dist/types/basic/derivation.d.ts +0 -89
- package/dist/types/basic/derivation.d.ts.map +0 -1
- package/dist/types/basic/disposable.d.ts +0 -82
- package/dist/types/basic/disposable.d.ts.map +0 -1
- package/dist/types/basic/effect.d.ts +0 -67
- package/dist/types/basic/effect.d.ts.map +0 -1
- package/dist/types/basic/index.d.ts +0 -10
- package/dist/types/basic/index.d.ts.map +0 -1
- package/dist/types/basic/observable.d.ts +0 -83
- package/dist/types/basic/observable.d.ts.map +0 -1
- package/dist/types/basic/signal.d.ts +0 -69
- package/dist/types/basic/signal.d.ts.map +0 -1
- package/dist/types/basic/state.d.ts +0 -47
- package/dist/types/basic/state.d.ts.map +0 -1
- package/dist/types/basic/trackingContext.d.ts +0 -33
- package/dist/types/basic/trackingContext.d.ts.map +0 -1
- package/dist/types/creators.d.ts +0 -340
- package/dist/types/creators.d.ts.map +0 -1
- package/src/advanced/array.ts +0 -222
- package/src/advanced/index.ts +0 -12
- package/src/advanced/map.ts +0 -193
- package/src/basic/constant.ts +0 -97
- package/src/basic/derivation.ts +0 -147
- package/src/basic/disposable.ts +0 -86
- package/src/basic/effect.ts +0 -104
- package/src/basic/index.ts +0 -9
- package/src/basic/observable.ts +0 -109
- package/src/basic/signal.ts +0 -145
- package/src/basic/state.ts +0 -60
- package/src/basic/trackingContext.ts +0 -45
- package/src/creators.ts +0 -395
- package/test/array.test.ts +0 -600
- package/test/constant.test.ts +0 -44
- package/test/derivation.test.ts +0 -539
- package/test/effect.test.ts +0 -29
- package/test/map.test.ts +0 -240
- package/test/signal.test.ts +0 -72
- package/test/state.test.ts +0 -212
package/dist/picoflow.js
CHANGED
|
@@ -1,45 +1,230 @@
|
|
|
1
|
-
import { createMemo, createResource,
|
|
1
|
+
import { createSignal, createMemo, createResource, onMount, onCleanup } from 'solid-js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
function isDisposable(obj) {
|
|
4
|
+
return obj !== null && obj !== void 0 && typeof obj.dispose === "function";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class FlowGraph {
|
|
8
|
+
static _effectsQueue = [];
|
|
9
|
+
static _actionQueue = [];
|
|
10
|
+
static _processingActionQueue = false;
|
|
11
|
+
/**
|
|
12
|
+
* Resets all internal queues and processing state.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Use this method primarily for testing scenarios where you need to ensure
|
|
16
|
+
* a clean state between test runs. It clears pending effects and queued
|
|
17
|
+
* operations.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* beforeEach(() => {
|
|
22
|
+
* FlowGraph.clear();
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
static clear() {
|
|
29
|
+
FlowGraph._effectsQueue = [];
|
|
30
|
+
FlowGraph._actionQueue = [];
|
|
31
|
+
FlowGraph._processingActionQueue = false;
|
|
7
32
|
}
|
|
8
33
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
34
|
+
* Queues a trigger notification for processing.
|
|
35
|
+
*
|
|
36
|
+
* @param notify - Function to call when the trigger is processed.
|
|
37
|
+
* @returns A promise that resolves after the trigger is processed.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* This method is used internally by reactive primitives to coordinate
|
|
41
|
+
* signal triggers. The promise resolves after all associated effects
|
|
42
|
+
* have been executed.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
11
45
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
46
|
+
static requestTrigger(notify) {
|
|
47
|
+
const promiseWithResolvers = Promise.withResolvers();
|
|
48
|
+
FlowGraph._actionQueue.push({
|
|
49
|
+
...promiseWithResolvers,
|
|
50
|
+
type: "trigger",
|
|
51
|
+
notify
|
|
52
|
+
});
|
|
53
|
+
FlowGraph._processActionQueue();
|
|
54
|
+
return promiseWithResolvers.promise;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Queues a read operation for processing.
|
|
58
|
+
*
|
|
59
|
+
* @param read - Function that performs the read operation.
|
|
60
|
+
* @returns A promise that resolves with the read value.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* This method is used internally by reactive primitives to coordinate
|
|
64
|
+
* read operations. The read function is executed and its result (or promise)
|
|
65
|
+
* is returned.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
static requestRead(read) {
|
|
70
|
+
const promiseWithResolvers = Promise.withResolvers();
|
|
71
|
+
FlowGraph._actionQueue.push({
|
|
72
|
+
...promiseWithResolvers,
|
|
73
|
+
type: "read",
|
|
74
|
+
read
|
|
75
|
+
});
|
|
76
|
+
FlowGraph._processActionQueue();
|
|
77
|
+
return promiseWithResolvers.promise;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Queues a write operation with update logic for processing.
|
|
81
|
+
*
|
|
82
|
+
* @param notify - Function to call if the update succeeds.
|
|
83
|
+
* @param update - Function that performs the update and returns whether
|
|
84
|
+
* it changed the value.
|
|
85
|
+
* @returns A promise that resolves after the write is processed.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* This method is used internally by reactive primitives to coordinate
|
|
89
|
+
* write operations. The update function is executed, and if it returns true
|
|
90
|
+
* (or a promise resolving to true), the notify function is called to
|
|
91
|
+
* trigger dependent effects.
|
|
92
|
+
*
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
static requestWrite(notify, update) {
|
|
96
|
+
const promiseWithResolvers = Promise.withResolvers();
|
|
97
|
+
FlowGraph._actionQueue.push({
|
|
98
|
+
...promiseWithResolvers,
|
|
99
|
+
type: "write",
|
|
100
|
+
notify,
|
|
101
|
+
update
|
|
102
|
+
});
|
|
103
|
+
FlowGraph._processActionQueue();
|
|
104
|
+
return promiseWithResolvers.promise;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Queues effects for execution after the current operation completes.
|
|
108
|
+
*
|
|
109
|
+
* @param effects - Array of effects to queue for execution.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* This method is used internally by reactive primitives to schedule effect
|
|
113
|
+
* execution. Effects are executed after the current read/write/trigger
|
|
114
|
+
* operation completes, ensuring proper ordering of reactive updates.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
static pushEffects(effects) {
|
|
119
|
+
FlowGraph._effectsQueue.push(...effects);
|
|
15
120
|
}
|
|
121
|
+
/** @internal */
|
|
122
|
+
static _processActionQueue = async () => {
|
|
123
|
+
if (FlowGraph._processingActionQueue) return;
|
|
124
|
+
FlowGraph._processingActionQueue = true;
|
|
125
|
+
while (FlowGraph._actionQueue.length > 0) {
|
|
126
|
+
const actionRequest = FlowGraph._actionQueue.shift();
|
|
127
|
+
if (!actionRequest) break;
|
|
128
|
+
if (actionRequest.type === "read") {
|
|
129
|
+
try {
|
|
130
|
+
const read = actionRequest.read();
|
|
131
|
+
let value;
|
|
132
|
+
if (read instanceof Promise) {
|
|
133
|
+
value = await read;
|
|
134
|
+
} else {
|
|
135
|
+
value = read;
|
|
136
|
+
}
|
|
137
|
+
actionRequest.resolve(value);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const safeError = error instanceof Error ? error : new Error(String(error));
|
|
140
|
+
actionRequest.reject(safeError);
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (actionRequest.type === "write") {
|
|
145
|
+
try {
|
|
146
|
+
const updateResult = actionRequest.update();
|
|
147
|
+
let updated = false;
|
|
148
|
+
if (updateResult instanceof Promise) {
|
|
149
|
+
updated = await updateResult;
|
|
150
|
+
} else {
|
|
151
|
+
updated = updateResult;
|
|
152
|
+
}
|
|
153
|
+
if (!updated) {
|
|
154
|
+
actionRequest.resolve();
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
actionRequest.notify();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const safeError = error instanceof Error ? error : new Error(String(error));
|
|
160
|
+
actionRequest.reject(safeError);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (actionRequest.type === "trigger") {
|
|
165
|
+
actionRequest.notify();
|
|
166
|
+
}
|
|
167
|
+
let effectError = null;
|
|
168
|
+
for (const effect of FlowGraph._effectsQueue) {
|
|
169
|
+
try {
|
|
170
|
+
await effect._requestExec();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
effectError = error instanceof Error ? error : new Error(String(error));
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
FlowGraph._effectsQueue = [];
|
|
177
|
+
if (effectError) {
|
|
178
|
+
actionRequest.reject(effectError);
|
|
179
|
+
} else {
|
|
180
|
+
actionRequest.resolve();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
FlowGraph._processingActionQueue = false;
|
|
184
|
+
};
|
|
16
185
|
}
|
|
17
186
|
|
|
18
187
|
class FlowEffect {
|
|
188
|
+
_disposed = false;
|
|
189
|
+
_dependencies = /* @__PURE__ */ new Set();
|
|
190
|
+
_apply;
|
|
191
|
+
settled;
|
|
19
192
|
/**
|
|
20
|
-
* Creates a new
|
|
193
|
+
* Creates a new effect and runs it once immediately.
|
|
21
194
|
*
|
|
22
|
-
* @param apply -
|
|
23
|
-
*
|
|
195
|
+
* @param apply - Side-effect function receiving the tracking context (`t`).
|
|
196
|
+
* It can be sync or async (returning `Promise<void>`).
|
|
24
197
|
*
|
|
25
198
|
* @remarks
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
199
|
+
* Use `t` to opt-in to reactive tracking:
|
|
200
|
+
* - `observable.get(t)` / `signal.watch(t)` to re-run when they change
|
|
201
|
+
* - `observable.pick()` for reads that should not re-run the effect
|
|
202
|
+
*
|
|
203
|
+
* The effect schedules its first run during construction. Each subsequent
|
|
204
|
+
* change to a tracked dependency queues another run.
|
|
29
205
|
*
|
|
30
206
|
* @public
|
|
31
207
|
*/
|
|
32
208
|
constructor(apply) {
|
|
33
|
-
this._trackedContext = new TrackingContext(this);
|
|
34
209
|
this._apply = apply;
|
|
35
|
-
this._exec();
|
|
210
|
+
this.settled = FlowGraph.requestRead(() => this._exec());
|
|
36
211
|
}
|
|
37
212
|
/**
|
|
38
|
-
*
|
|
213
|
+
* Stops the effect and detaches it from all tracked dependencies.
|
|
39
214
|
*
|
|
40
215
|
* @remarks
|
|
41
|
-
*
|
|
42
|
-
*
|
|
216
|
+
* Call this when the effect's work is no longer needed (e.g., component
|
|
217
|
+
* unmount, teardown of a feature). After disposal:
|
|
218
|
+
* - The effect will not re-run.
|
|
219
|
+
* - All dependency links are removed.
|
|
220
|
+
* - Calling `dispose()` again throws an error.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```typescript
|
|
224
|
+
* const fx = effect((t) => $signal.watch(t));
|
|
225
|
+
* // ... later
|
|
226
|
+
* fx.dispose();
|
|
227
|
+
* ```
|
|
43
228
|
*
|
|
44
229
|
* @public
|
|
45
230
|
*/
|
|
@@ -51,25 +236,19 @@ class FlowEffect {
|
|
|
51
236
|
this._disposed = true;
|
|
52
237
|
}
|
|
53
238
|
/**
|
|
54
|
-
*
|
|
239
|
+
* Whether the effect has been disposed.
|
|
55
240
|
*
|
|
56
|
-
* @returns
|
|
241
|
+
* @returns `true` once disposal has run; `false` while the effect is active.
|
|
57
242
|
*
|
|
58
243
|
* @public
|
|
59
244
|
*/
|
|
60
245
|
get disposed() {
|
|
61
246
|
return this._disposed;
|
|
62
247
|
}
|
|
63
|
-
/* INTERNAL ------------------------------------------------------------ */
|
|
64
|
-
_disposed = false;
|
|
65
|
-
_dependencies = /* @__PURE__ */ new Set();
|
|
66
|
-
_trackedContext;
|
|
67
|
-
_apply;
|
|
68
248
|
/** @internal */
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this._apply(this._trackedContext);
|
|
249
|
+
async _requestExec() {
|
|
250
|
+
this.settled = this._exec();
|
|
251
|
+
return this.settled;
|
|
73
252
|
}
|
|
74
253
|
/** @internal */
|
|
75
254
|
_registerDependency(dependency) {
|
|
@@ -81,61 +260,95 @@ class FlowEffect {
|
|
|
81
260
|
this._dependencies.delete(dependency);
|
|
82
261
|
dependency._unregisterEffect(this);
|
|
83
262
|
}
|
|
263
|
+
async _exec() {
|
|
264
|
+
if (this._disposed)
|
|
265
|
+
throw new Error("[PicoFlow] Effect is disposed");
|
|
266
|
+
const result = this._apply(this);
|
|
267
|
+
if (result instanceof Promise) {
|
|
268
|
+
await result;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function effect(fn) {
|
|
273
|
+
return new FlowEffect(fn);
|
|
84
274
|
}
|
|
85
275
|
|
|
86
276
|
class FlowSignal {
|
|
87
277
|
/**
|
|
88
|
-
* Triggers the
|
|
89
|
-
*
|
|
90
|
-
* @
|
|
278
|
+
* Triggers the signal and notifies all dependents.
|
|
279
|
+
*
|
|
280
|
+
* @remarks
|
|
281
|
+
* Any effect/derivation that called `watch(t)` on this signal will re-run.
|
|
282
|
+
* Returns a promise that settles after notifications complete.
|
|
283
|
+
*
|
|
284
|
+
* @throws Error if the signal is disposed.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* const $tick = signal();
|
|
289
|
+
*
|
|
290
|
+
* effect((t) => {
|
|
291
|
+
* $tick.watch(t);
|
|
292
|
+
* console.log("tick");
|
|
293
|
+
* });
|
|
294
|
+
*
|
|
295
|
+
* $tick.trigger(); // logs "tick"
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
91
298
|
* @public
|
|
92
299
|
*/
|
|
93
|
-
trigger() {
|
|
300
|
+
async trigger() {
|
|
94
301
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
95
|
-
this._notify();
|
|
302
|
+
return FlowGraph.requestTrigger(() => this._notify());
|
|
96
303
|
}
|
|
97
304
|
/**
|
|
98
|
-
*
|
|
305
|
+
* Registers this signal as a dependency in the current tracking context.
|
|
99
306
|
*
|
|
100
|
-
* @param context - The tracking context
|
|
307
|
+
* @param context - The tracking context (`t`) provided to effects/derivations.
|
|
101
308
|
*
|
|
102
309
|
* @remarks
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* When the signal is triggered via `trigger()`, any effects or derivations that have
|
|
108
|
-
* watched this signal will automatically re-execute.
|
|
109
|
-
*
|
|
110
|
-
* This method must be called within an effect or derivation context where a TrackingContext
|
|
111
|
-
* is available. For observables (which hold values), use `.get(t)` instead, which both
|
|
112
|
-
* reads the value and watches for changes.
|
|
310
|
+
* Signals have no value to read; calling `watch(t)` simply means “re-run me
|
|
311
|
+
* when this signal is triggered.” Call this inside an effect/derivation
|
|
312
|
+
* callback where a tracking context is available.
|
|
113
313
|
*
|
|
114
314
|
* @throws Error if the signal has been disposed.
|
|
115
315
|
*
|
|
116
316
|
* @example
|
|
117
317
|
* ```typescript
|
|
118
|
-
* const $
|
|
318
|
+
* const $refresh = signal();
|
|
119
319
|
*
|
|
120
320
|
* effect((t) => {
|
|
121
|
-
* $
|
|
122
|
-
* console.log(
|
|
321
|
+
* $refresh.watch(t);
|
|
322
|
+
* console.log("refresh triggered");
|
|
123
323
|
* });
|
|
124
324
|
*
|
|
125
|
-
* $
|
|
325
|
+
* $refresh.trigger(); // logs "refresh triggered"
|
|
126
326
|
* ```
|
|
127
327
|
*
|
|
128
328
|
* @public
|
|
129
329
|
*/
|
|
130
|
-
watch(
|
|
330
|
+
watch(caller) {
|
|
131
331
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
132
|
-
|
|
332
|
+
caller._registerDependency(this);
|
|
133
333
|
}
|
|
134
334
|
/**
|
|
135
|
-
* Disposes the
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
335
|
+
* Disposes the signal and cleans up its dependencies/listeners.
|
|
336
|
+
*
|
|
337
|
+
* @remarks
|
|
338
|
+
* After disposal the signal must not be used; calling `trigger` or `watch`
|
|
339
|
+
* will throw. If `options?.self` is true, only this signal is disposed; when
|
|
340
|
+
* false or omitted, dependents may also be disposed depending on the
|
|
341
|
+
* implementation.
|
|
342
|
+
*
|
|
343
|
+
* @throws Error if the signal is already disposed.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```typescript
|
|
347
|
+
* const $refresh = signal();
|
|
348
|
+
* // ... use it
|
|
349
|
+
* $refresh.dispose();
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
139
352
|
* @public
|
|
140
353
|
*/
|
|
141
354
|
dispose(options) {
|
|
@@ -161,30 +374,27 @@ class FlowSignal {
|
|
|
161
374
|
this._disposed = true;
|
|
162
375
|
}
|
|
163
376
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
377
|
+
* Whether the signal has been disposed.
|
|
378
|
+
*
|
|
379
|
+
* @returns `true` if disposed; `false` while active.
|
|
380
|
+
*
|
|
381
|
+
* @remarks Use to guard operations or avoid double disposal.
|
|
382
|
+
*
|
|
166
383
|
* @public
|
|
167
384
|
*/
|
|
168
385
|
get disposed() {
|
|
169
386
|
return this._disposed;
|
|
170
387
|
}
|
|
171
388
|
/* INTERNAL ------------------------------------------------------------- */
|
|
172
|
-
/** @internal */
|
|
173
389
|
_disposed = false;
|
|
174
|
-
/** @internal */
|
|
175
390
|
_dependencies = /* @__PURE__ */ new Set();
|
|
176
|
-
/** @internal */
|
|
177
391
|
_listeners = /* @__PURE__ */ new Set();
|
|
178
|
-
/** @internal */
|
|
179
392
|
_effects = /* @__PURE__ */ new Set();
|
|
180
|
-
/** @internal */
|
|
181
393
|
_notify() {
|
|
394
|
+
FlowGraph.pushEffects(Array.from(this._effects));
|
|
182
395
|
this._listeners.forEach((listener) => {
|
|
183
396
|
listener._notify();
|
|
184
397
|
});
|
|
185
|
-
this._effects.forEach((effect) => {
|
|
186
|
-
effect._exec();
|
|
187
|
-
});
|
|
188
398
|
}
|
|
189
399
|
/** @internal */
|
|
190
400
|
_registerDependency(dependency) {
|
|
@@ -197,12 +407,12 @@ class FlowSignal {
|
|
|
197
407
|
dependency._unregisterListener(this);
|
|
198
408
|
}
|
|
199
409
|
/** @internal */
|
|
200
|
-
_registerListener(
|
|
201
|
-
this._listeners.add(
|
|
410
|
+
_registerListener(signal2) {
|
|
411
|
+
this._listeners.add(signal2);
|
|
202
412
|
}
|
|
203
413
|
/** @internal */
|
|
204
|
-
_unregisterListener(
|
|
205
|
-
this._listeners.delete(
|
|
414
|
+
_unregisterListener(signal2) {
|
|
415
|
+
this._listeners.delete(signal2);
|
|
206
416
|
}
|
|
207
417
|
/** @internal */
|
|
208
418
|
_registerEffect(effect) {
|
|
@@ -213,285 +423,715 @@ class FlowSignal {
|
|
|
213
423
|
this._effects.delete(effect);
|
|
214
424
|
}
|
|
215
425
|
}
|
|
426
|
+
function signal() {
|
|
427
|
+
return new FlowSignal();
|
|
428
|
+
}
|
|
216
429
|
|
|
217
|
-
class
|
|
430
|
+
class FlowNodeAsync extends FlowSignal {
|
|
431
|
+
_promise;
|
|
432
|
+
_dirty = true;
|
|
433
|
+
_compute;
|
|
218
434
|
/**
|
|
219
|
-
*
|
|
435
|
+
* Creates a new FlowNodeAsync.
|
|
220
436
|
*
|
|
221
|
-
* @param
|
|
222
|
-
* When a context is provided, this observable is registered as a dependency. When null,
|
|
223
|
-
* the value is read without any tracking.
|
|
224
|
-
*
|
|
225
|
-
* @returns The current value of type T.
|
|
437
|
+
* @param compute - Either a constant Promise or a compute function that derives the value.
|
|
226
438
|
*
|
|
227
439
|
* @remarks
|
|
228
|
-
*
|
|
229
|
-
*
|
|
440
|
+
* The constructor accepts two different initialization modes:
|
|
441
|
+
*
|
|
442
|
+
* - **Constant Promise**: Creates a mutable reactive node that can be updated via `set()`.
|
|
443
|
+
* The Promise is stored immediately and can be changed at any time. The Promise resolves
|
|
444
|
+
* to the value of type T.
|
|
445
|
+
*
|
|
446
|
+
* - **Compute function**: Creates a computed reactive node that automatically tracks dependencies
|
|
447
|
+
* and recomputes when they change. The compute function receives a tracking context (`t`)
|
|
448
|
+
* that should be used to access dependencies via `.get(t)`. The function must return a
|
|
449
|
+
* `Promise<T>`. The function is not executed immediately; it runs lazily on first access.
|
|
450
|
+
* The computed value can be temporarily overridden using `set()`, but the override is cleared
|
|
451
|
+
* on the next recomputation (triggered by dependency changes).
|
|
230
452
|
*
|
|
231
453
|
* @example
|
|
232
454
|
* ```typescript
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
455
|
+
* // Mutable state with constant Promise
|
|
456
|
+
* const $count = new FlowNodeAsync(Promise.resolve(0));
|
|
457
|
+
* await $count.set(Promise.resolve(5));
|
|
458
|
+
*
|
|
459
|
+
* // Computed derivation with async function
|
|
460
|
+
* const $a = new FlowNodeAsync(Promise.resolve(10));
|
|
461
|
+
* const $b = new FlowNodeAsync(Promise.resolve(20));
|
|
462
|
+
* const $sum = new FlowNodeAsync(async (t) => {
|
|
463
|
+
* const a = await $a.get(t);
|
|
464
|
+
* const b = await $b.get(t);
|
|
465
|
+
* return a + b;
|
|
236
466
|
* });
|
|
467
|
+
*
|
|
468
|
+
* // Lazy evaluation - compute function hasn't run yet
|
|
469
|
+
* console.log(await $sum.pick()); // Now it computes: 30
|
|
237
470
|
* ```
|
|
238
471
|
*
|
|
239
472
|
* @public
|
|
240
473
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
474
|
+
constructor(compute) {
|
|
475
|
+
super();
|
|
476
|
+
if (typeof compute === "function") {
|
|
477
|
+
this._compute = compute;
|
|
478
|
+
} else {
|
|
479
|
+
this._promise = compute;
|
|
480
|
+
this._dirty = false;
|
|
244
481
|
}
|
|
245
|
-
|
|
482
|
+
}
|
|
483
|
+
get settled() {
|
|
484
|
+
return this._promise;
|
|
485
|
+
}
|
|
486
|
+
async watch(tracker) {
|
|
487
|
+
if (this._disposed)
|
|
488
|
+
return Promise.reject(new Error("[PicoFlow] Primitive is disposed"));
|
|
489
|
+
super.watch(tracker);
|
|
490
|
+
return this._computeValue();
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Gets the current value with dependency tracking.
|
|
494
|
+
*
|
|
495
|
+
* @param tracker - The tracking context for reactive tracking. This observable is registered
|
|
496
|
+
* as a dependency when accessed through this method.
|
|
497
|
+
*
|
|
498
|
+
* @returns A Promise that resolves to the current value of type T.
|
|
499
|
+
*
|
|
500
|
+
* @remarks
|
|
501
|
+
* Use `get(t)` within effects and derivations to create reactive dependencies. The tracker
|
|
502
|
+
* parameter must be provided from the reactive context (typically the `t` parameter in effect
|
|
503
|
+
* or derivation callbacks).
|
|
504
|
+
*
|
|
505
|
+
* **Important:** This method returns a `Promise<T>`, not `T`. You must await the Promise to
|
|
506
|
+
* get the actual value. The Promise is cached until dependencies change, ensuring efficient
|
|
507
|
+
* access patterns.
|
|
508
|
+
*
|
|
509
|
+
* To read a value without creating a dependency, use `pick()` instead.
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* effect(async (t) => {
|
|
514
|
+
* const tracked = await $state.get(t); // Dependency registered, await the Promise
|
|
515
|
+
* const untracked = await $other.pick(); // No dependency
|
|
516
|
+
* });
|
|
517
|
+
* ```
|
|
518
|
+
*
|
|
519
|
+
* @throws Error if the node has been disposed.
|
|
520
|
+
*
|
|
521
|
+
* @public
|
|
522
|
+
*/
|
|
523
|
+
async get(tracker) {
|
|
524
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
525
|
+
await this._computeValue();
|
|
526
|
+
if (tracker) super.watch(tracker);
|
|
527
|
+
return this._promise;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Updates the node with a new value.
|
|
531
|
+
*
|
|
532
|
+
* @param value - A new Promise or a callback function that computes a new Promise based on the current value.
|
|
533
|
+
*
|
|
534
|
+
* @returns A promise that resolves after the update is processed and all dependent effects have been notified.
|
|
535
|
+
*
|
|
536
|
+
* @remarks
|
|
537
|
+
* This method can be used in two ways:
|
|
538
|
+
*
|
|
539
|
+
* **For mutable state nodes** (constructed with a constant Promise):
|
|
540
|
+
* - Updates the stored Promise directly
|
|
541
|
+
* - All dependents are notified of the change
|
|
542
|
+
*
|
|
543
|
+
* **For computed nodes** (constructed with a compute function):
|
|
544
|
+
* - Temporarily overrides the computed value
|
|
545
|
+
* - The override persists until the next recomputation, which occurs when:
|
|
546
|
+
* - A tracked dependency changes, or
|
|
547
|
+
* - The node is refreshed
|
|
548
|
+
* - This allows temporarily overriding computed values for testing or manual control
|
|
549
|
+
*
|
|
550
|
+
* **Value Comparison:**
|
|
551
|
+
* The Promises are resolved and their values are compared. If the resolved new value is strictly
|
|
552
|
+
* equal (`===`) to the current resolved value, no update occurs and subscribers are not notified.
|
|
553
|
+
* This prevents unnecessary re-renders and effect executions.
|
|
554
|
+
*
|
|
555
|
+
* **Asynchronous Processing:**
|
|
556
|
+
* The update is processed asynchronously through the reactive graph, ensuring proper
|
|
557
|
+
* ordering of updates and effect execution. The method returns a Promise that resolves
|
|
558
|
+
* after the update is complete.
|
|
559
|
+
*
|
|
560
|
+
* @throws Error if the node has been disposed.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* // Mutable state usage
|
|
565
|
+
* const $count = new FlowNodeAsync(Promise.resolve(0));
|
|
566
|
+
* await $count.set(Promise.resolve(5));
|
|
567
|
+
* await $count.set(async (current) => Promise.resolve(current + 1)); // 6
|
|
568
|
+
*
|
|
569
|
+
* // Temporary override of computed value
|
|
570
|
+
* const $source = new FlowNodeAsync(Promise.resolve(10));
|
|
571
|
+
* const $doubled = new FlowNodeAsync(async (t) => {
|
|
572
|
+
* const val = await $source.get(t);
|
|
573
|
+
* return val * 2;
|
|
574
|
+
* });
|
|
575
|
+
*
|
|
576
|
+
* console.log(await $doubled.pick()); // 20
|
|
577
|
+
*
|
|
578
|
+
* // Temporarily override
|
|
579
|
+
* await $doubled.set(Promise.resolve(50));
|
|
580
|
+
* console.log(await $doubled.pick()); // 50 (override active)
|
|
581
|
+
*
|
|
582
|
+
* // Dependency change clears override
|
|
583
|
+
* await $source.set(Promise.resolve(15));
|
|
584
|
+
* console.log(await $doubled.pick()); // 30 (recomputed, override cleared)
|
|
585
|
+
* ```
|
|
586
|
+
*
|
|
587
|
+
* @public
|
|
588
|
+
*/
|
|
589
|
+
async set(value) {
|
|
590
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
591
|
+
const update = async () => {
|
|
592
|
+
await this._computeValue();
|
|
593
|
+
const currentValue = await this._promise;
|
|
594
|
+
const nextPromise = typeof value === "function" ? value(currentValue) : value;
|
|
595
|
+
const nextValue = await nextPromise;
|
|
596
|
+
this._promise = nextPromise;
|
|
597
|
+
return nextValue !== currentValue;
|
|
598
|
+
};
|
|
599
|
+
const notify = () => {
|
|
600
|
+
super._notify();
|
|
601
|
+
};
|
|
602
|
+
return FlowGraph.requestWrite(notify, update);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Forces recomputation of the value, even if it's not marked as dirty.
|
|
606
|
+
*
|
|
607
|
+
* @returns A promise that resolves after the recomputation is complete and all
|
|
608
|
+
* dependent effects have been notified.
|
|
609
|
+
*
|
|
610
|
+
* @remarks
|
|
611
|
+
* This method is useful when you need to force a recomputation of a computed value,
|
|
612
|
+
* for example when the computation depends on external data that has changed outside
|
|
613
|
+
* the reactive system.
|
|
614
|
+
*
|
|
615
|
+
* **Behavior:**
|
|
616
|
+
* - For nodes with a compute function: Forces the compute function to run again,
|
|
617
|
+
* even if no dependencies have changed. This effectively clears any temporary
|
|
618
|
+
* override that was set using `set()`.
|
|
619
|
+
* - For nodes without a compute function (mutable state): Recomputes the current
|
|
620
|
+
* value (which is just the stored value), useful for consistency but typically
|
|
621
|
+
* not necessary.
|
|
622
|
+
*
|
|
623
|
+
* The recomputation happens asynchronously through the reactive graph, ensuring
|
|
624
|
+
* proper ordering of updates and effect execution.
|
|
625
|
+
*
|
|
626
|
+
* @throws Error if the node has been disposed.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```typescript
|
|
630
|
+
* const $externalData = new FlowNode(() => fetchExternalData());
|
|
631
|
+
*
|
|
632
|
+
* // Some external event occurs that changes the data source
|
|
633
|
+
* externalDataChanged();
|
|
634
|
+
*
|
|
635
|
+
* // Force recomputation to get the new value
|
|
636
|
+
* await $externalData.refresh();
|
|
637
|
+
*
|
|
638
|
+
* // For computed nodes with temporary overrides
|
|
639
|
+
* const $computed = new FlowNode((t) => $source.get(t) * 2);
|
|
640
|
+
* $computed.set(100); // Temporary override
|
|
641
|
+
* await $computed.refresh(); // Clears override, recomputes from source
|
|
642
|
+
* ```
|
|
643
|
+
*
|
|
644
|
+
* @public
|
|
645
|
+
*/
|
|
646
|
+
async refresh() {
|
|
647
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
648
|
+
const update = async () => {
|
|
649
|
+
await this._computeValue();
|
|
650
|
+
const currentValue = await this._promise;
|
|
651
|
+
await this._computeValue({ force: true });
|
|
652
|
+
const nextValue = await this._promise;
|
|
653
|
+
return nextValue !== currentValue;
|
|
654
|
+
};
|
|
655
|
+
const notify = () => {
|
|
656
|
+
super._notify();
|
|
657
|
+
};
|
|
658
|
+
return await FlowGraph.requestWrite(notify, update);
|
|
246
659
|
}
|
|
247
660
|
/**
|
|
248
661
|
* Gets the current value without any dependency tracking.
|
|
249
662
|
*
|
|
250
|
-
* @returns
|
|
663
|
+
* @returns A promise that resolves with the current value of type T.
|
|
251
664
|
*
|
|
252
665
|
* @remarks
|
|
253
|
-
* This method
|
|
666
|
+
* This method reads the value asynchronously through the reactive graph, ensuring proper ordering of
|
|
667
|
+
* read operations. Unlike `get(t)`, this method does not create a reactive dependency.
|
|
668
|
+
*
|
|
669
|
+
* **Important:** This is an async method that returns `Promise<T>`. Always use `await` when calling it.
|
|
670
|
+
*
|
|
254
671
|
* Use `pick()` when you want to read a snapshot of the current value without creating a reactive
|
|
255
672
|
* dependency. This is useful for:
|
|
256
|
-
* - Reading initial values
|
|
673
|
+
* - Reading initial values outside reactive contexts
|
|
257
674
|
* - Accessing configuration that shouldn't trigger updates
|
|
258
675
|
* - Mixing tracked and untracked reads in the same effect
|
|
259
676
|
*
|
|
260
677
|
* @example
|
|
261
678
|
* ```typescript
|
|
262
679
|
* // Read a snapshot outside reactive context
|
|
263
|
-
* const currentValue = $state.pick();
|
|
680
|
+
* const currentValue = await $state.pick();
|
|
264
681
|
*
|
|
265
682
|
* // Mix tracked and untracked reads
|
|
266
|
-
* effect((t) => {
|
|
267
|
-
* const tracked = $reactive.get(t); // Triggers re-runs
|
|
268
|
-
* const snapshot = $config.pick();
|
|
683
|
+
* effect(async (t) => {
|
|
684
|
+
* const tracked = await $reactive.get(t); // Triggers re-runs
|
|
685
|
+
* const snapshot = await $config.pick(); // Doesn't trigger re-runs
|
|
269
686
|
* processData(tracked, snapshot);
|
|
270
687
|
* });
|
|
271
688
|
* ```
|
|
272
689
|
*
|
|
690
|
+
* @throws Error if the node has been disposed.
|
|
691
|
+
*
|
|
273
692
|
* @public
|
|
274
693
|
*/
|
|
275
|
-
pick() {
|
|
276
|
-
|
|
694
|
+
async pick() {
|
|
695
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
696
|
+
await FlowGraph.requestRead(() => this._computeValue());
|
|
697
|
+
return this._promise;
|
|
277
698
|
}
|
|
278
|
-
/* INTERNAL -------------------------------------------*/
|
|
279
|
-
/** @internal */
|
|
280
|
-
_value;
|
|
281
699
|
/**
|
|
282
|
-
* Subscribes a listener function to changes of
|
|
283
|
-
*
|
|
284
|
-
* @param listener - A callback function that receives the new value.
|
|
285
|
-
*
|
|
700
|
+
* Subscribes a listener function to changes of this node.
|
|
701
|
+
*
|
|
702
|
+
* @param listener - A callback function that receives the new value whenever it changes.
|
|
703
|
+
*
|
|
704
|
+
* @returns A disposer function that cancels the subscription when called.
|
|
705
|
+
*
|
|
706
|
+
* @remarks
|
|
707
|
+
* This method creates a reactive subscription that automatically tracks this node as a dependency.
|
|
708
|
+
* The listener is executed:
|
|
709
|
+
* - Immediately with the current value when the subscription is created
|
|
710
|
+
* - Automatically whenever the value changes
|
|
711
|
+
*
|
|
712
|
+
* **Important:** The listener receives a `Promise<T>`, not `T`. You must await the Promise
|
|
713
|
+
* within the listener to access the actual value. The Promise is the same one returned by `get()`,
|
|
714
|
+
* so it's cached until dependencies change.
|
|
715
|
+
*
|
|
716
|
+
* The subscription uses a {@link FlowEffect} internally to manage the reactive tracking and
|
|
717
|
+
* automatic re-execution. When the value changes, the listener is called with the new Promise
|
|
718
|
+
* after the update is processed through the reactive graph.
|
|
719
|
+
*
|
|
720
|
+
* **Cleanup:**
|
|
721
|
+
* Always call the returned disposer function when you no longer need the subscription to
|
|
722
|
+
* prevent memory leaks and unnecessary computations.
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* ```typescript
|
|
726
|
+
* const $count = new FlowNodeAsync(Promise.resolve(0));
|
|
727
|
+
*
|
|
728
|
+
* // Subscribe to changes
|
|
729
|
+
* const unsubscribe = $count.subscribe(async (valuePromise) => {
|
|
730
|
+
* const value = await valuePromise;
|
|
731
|
+
* console.log(`Count is now: ${value}`);
|
|
732
|
+
* });
|
|
733
|
+
* // Logs immediately: "Count is now: 0"
|
|
734
|
+
*
|
|
735
|
+
* await $count.set(Promise.resolve(5));
|
|
736
|
+
* // Logs: "Count is now: 5"
|
|
737
|
+
*
|
|
738
|
+
* // Clean up when done
|
|
739
|
+
* unsubscribe();
|
|
740
|
+
* ```
|
|
741
|
+
*
|
|
742
|
+
* @throws Error if the node has been disposed.
|
|
743
|
+
*
|
|
744
|
+
* @public
|
|
286
745
|
*/
|
|
287
746
|
subscribe(listener) {
|
|
747
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
288
748
|
const effect = new FlowEffect((t) => {
|
|
289
749
|
listener(this.get(t));
|
|
290
750
|
});
|
|
291
751
|
return () => effect.dispose();
|
|
292
752
|
}
|
|
753
|
+
/* INTERNAL --------------------------------------------------------- */
|
|
754
|
+
/* @internal */
|
|
755
|
+
_notify() {
|
|
756
|
+
if (this._dirty) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
this._dirty = true;
|
|
760
|
+
super._notify();
|
|
761
|
+
}
|
|
762
|
+
async _computeValue(options) {
|
|
763
|
+
if (!this._dirty && !options?.force) return;
|
|
764
|
+
if (!this._compute) {
|
|
765
|
+
this._dirty = false;
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
this._dirty = false;
|
|
769
|
+
const dependencies = [...this._dependencies];
|
|
770
|
+
this._dependencies.clear();
|
|
771
|
+
this._promise = this._compute(this);
|
|
772
|
+
await this._promise;
|
|
773
|
+
const dependenciesToRemove = dependencies.filter(
|
|
774
|
+
(dependency) => !this._dependencies.has(dependency)
|
|
775
|
+
);
|
|
776
|
+
dependenciesToRemove.forEach((dependency) => {
|
|
777
|
+
dependency._unregisterDependency(this);
|
|
778
|
+
});
|
|
779
|
+
}
|
|
293
780
|
}
|
|
294
781
|
|
|
295
|
-
|
|
782
|
+
function constantAsync(value) {
|
|
783
|
+
return new FlowNodeAsync(value);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function derivationAsync(fn) {
|
|
787
|
+
return new FlowNodeAsync(fn);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function stateAsync(value) {
|
|
791
|
+
return new FlowNodeAsync(value);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
class FlowNode extends FlowSignal {
|
|
795
|
+
_value;
|
|
796
|
+
_dirty = true;
|
|
797
|
+
_compute;
|
|
296
798
|
/**
|
|
297
|
-
* Creates a new
|
|
799
|
+
* Creates a new FlowNode.
|
|
800
|
+
*
|
|
801
|
+
* @param compute - Either a constant value or a compute function that derives the value.
|
|
802
|
+
*
|
|
803
|
+
* @remarks
|
|
804
|
+
* The constructor accepts two different initialization modes:
|
|
805
|
+
*
|
|
806
|
+
* - **Constant value**: Creates a mutable reactive node that can be updated via `set()`.
|
|
807
|
+
* The value is stored immediately and can be changed at any time.
|
|
808
|
+
*
|
|
809
|
+
* - **Compute function**: Creates a computed reactive node that automatically tracks dependencies
|
|
810
|
+
* and recomputes when they change. The compute function receives a tracking context (`t`)
|
|
811
|
+
* that should be used to access dependencies via `.get(t)`. The function is not executed
|
|
812
|
+
* immediately; it runs lazily on first access. The computed value can be temporarily
|
|
813
|
+
* overridden using `set()`, but the override is cleared on the next recomputation
|
|
814
|
+
* (triggered by dependency changes or `refresh()`).
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* ```typescript
|
|
818
|
+
* // Mutable state with constant value
|
|
819
|
+
* const $count = new FlowNode(0);
|
|
820
|
+
* $count.set(5);
|
|
821
|
+
*
|
|
822
|
+
* // Computed derivation with function
|
|
823
|
+
* const $a = new FlowNode(10);
|
|
824
|
+
* const $b = new FlowNode(20);
|
|
825
|
+
* const $sum = new FlowNode((t) => $a.get(t) + $b.get(t));
|
|
298
826
|
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
827
|
+
* // Lazy evaluation - compute function hasn't run yet
|
|
828
|
+
* console.log(await $sum.pick()); // Now it computes: 30
|
|
829
|
+
* ```
|
|
302
830
|
*
|
|
303
831
|
* @public
|
|
304
832
|
*/
|
|
305
|
-
constructor(
|
|
833
|
+
constructor(compute) {
|
|
306
834
|
super();
|
|
307
|
-
|
|
835
|
+
if (typeof compute === "function") {
|
|
836
|
+
this._compute = compute;
|
|
837
|
+
} else {
|
|
838
|
+
this._value = compute;
|
|
839
|
+
this._dirty = false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
watch(tracker) {
|
|
843
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
844
|
+
this._computeValue();
|
|
845
|
+
super.watch(tracker);
|
|
308
846
|
}
|
|
309
847
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
848
|
+
* Gets the current value with dependency tracking.
|
|
849
|
+
*
|
|
850
|
+
* @param tracker - The tracking context for reactive tracking. This observable is registered
|
|
851
|
+
* as a dependency when accessed through this method.
|
|
852
|
+
*
|
|
853
|
+
* @returns The current value of type T.
|
|
854
|
+
*
|
|
855
|
+
* @remarks
|
|
856
|
+
* Use `get(t)` within effects and derivations to create reactive dependencies. The tracker
|
|
857
|
+
* parameter must be provided from the reactive context (typically the `t` parameter in effect
|
|
858
|
+
* or derivation callbacks).
|
|
859
|
+
*
|
|
860
|
+
* To read a value without creating a dependency, use `pick()` instead.
|
|
861
|
+
*
|
|
862
|
+
* @example
|
|
863
|
+
* ```typescript
|
|
864
|
+
* effect((t) => {
|
|
865
|
+
* const tracked = $state.get(t); // Dependency registered
|
|
866
|
+
* const untracked = await $other.pick(); // No dependency
|
|
867
|
+
* });
|
|
868
|
+
* ```
|
|
869
|
+
*
|
|
870
|
+
* @throws Error if the node has been disposed.
|
|
871
|
+
*
|
|
872
|
+
* @public
|
|
312
873
|
*/
|
|
313
|
-
|
|
874
|
+
get(tracker) {
|
|
314
875
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
315
|
-
this.
|
|
876
|
+
this._computeValue();
|
|
877
|
+
if (tracker) super.watch(tracker);
|
|
316
878
|
return this._value;
|
|
317
879
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
880
|
+
/**
|
|
881
|
+
* Updates the node with a new value.
|
|
882
|
+
*
|
|
883
|
+
* @param value - A new value or a callback function that computes a new value based on the current value.
|
|
884
|
+
*
|
|
885
|
+
* @returns A promise that resolves after the update is processed and all dependent effects have been notified.
|
|
886
|
+
*
|
|
887
|
+
* @remarks
|
|
888
|
+
* This method can be used in two ways:
|
|
889
|
+
*
|
|
890
|
+
* **For mutable state nodes** (constructed with a constant value):
|
|
891
|
+
* - Updates the stored value directly
|
|
892
|
+
* - All dependents are notified of the change
|
|
893
|
+
*
|
|
894
|
+
* **For computed nodes** (constructed with a compute function):
|
|
895
|
+
* - Temporarily overrides the computed value
|
|
896
|
+
* - The override persists until the next recomputation, which occurs when:
|
|
897
|
+
* - A tracked dependency changes, or
|
|
898
|
+
* - `refresh()` is called
|
|
899
|
+
* - This allows temporarily overriding computed values for testing or manual control
|
|
900
|
+
*
|
|
901
|
+
* **Value Comparison:**
|
|
902
|
+
* If the new value is strictly equal (`===`) to the current value, no update occurs
|
|
903
|
+
* and subscribers are not notified. This prevents unnecessary re-renders and effect
|
|
904
|
+
* executions.
|
|
905
|
+
*
|
|
906
|
+
* **Asynchronous Processing:**
|
|
907
|
+
* The update is processed asynchronously through the reactive graph, ensuring proper
|
|
908
|
+
* ordering of updates and effect execution.
|
|
909
|
+
*
|
|
910
|
+
* @throws Error if the node has been disposed.
|
|
911
|
+
*
|
|
912
|
+
* @example
|
|
913
|
+
* ```typescript
|
|
914
|
+
* // Mutable state usage
|
|
915
|
+
* const $count = new FlowNode(0);
|
|
916
|
+
* await $count.set(5);
|
|
917
|
+
* await $count.set(current => current + 1); // 6
|
|
918
|
+
*
|
|
919
|
+
* // Temporary override of computed value
|
|
920
|
+
* const $source = new FlowNode(10);
|
|
921
|
+
* const $doubled = new FlowNode((t) => $source.get(t) * 2);
|
|
922
|
+
*
|
|
923
|
+
* console.log(await $doubled.pick()); // 20
|
|
924
|
+
*
|
|
925
|
+
* // Temporarily override
|
|
926
|
+
* await $doubled.set(50);
|
|
927
|
+
* console.log(await $doubled.pick()); // 50 (override active)
|
|
928
|
+
*
|
|
929
|
+
* // Dependency change clears override
|
|
930
|
+
* await $source.set(15);
|
|
931
|
+
* console.log(await $doubled.pick()); // 30 (recomputed, override cleared)
|
|
932
|
+
* ```
|
|
933
|
+
*
|
|
934
|
+
* @public
|
|
935
|
+
*/
|
|
936
|
+
set(value) {
|
|
937
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
938
|
+
const update = () => {
|
|
939
|
+
this._computeValue();
|
|
940
|
+
const currentValue = this._value;
|
|
941
|
+
const next = typeof value === "function" ? value(currentValue) : value;
|
|
942
|
+
this._value = next;
|
|
943
|
+
return next !== currentValue;
|
|
944
|
+
};
|
|
945
|
+
const notify = () => {
|
|
946
|
+
super._notify();
|
|
947
|
+
};
|
|
948
|
+
return FlowGraph.requestWrite(notify, update);
|
|
340
949
|
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
class FlowDerivation extends FlowObservable {
|
|
344
950
|
/**
|
|
345
|
-
*
|
|
951
|
+
* Forces recomputation of the value, even if it's not marked as dirty.
|
|
952
|
+
*
|
|
953
|
+
* @returns A promise that resolves after the recomputation is complete and all
|
|
954
|
+
* dependent effects have been notified.
|
|
955
|
+
*
|
|
956
|
+
* @remarks
|
|
957
|
+
* This method is useful when you need to force a recomputation of a computed value,
|
|
958
|
+
* for example when the computation depends on external data that has changed outside
|
|
959
|
+
* the reactive system.
|
|
960
|
+
*
|
|
961
|
+
* **Behavior:**
|
|
962
|
+
* - For nodes with a compute function: Forces the compute function to run again,
|
|
963
|
+
* even if no dependencies have changed. This effectively clears any temporary
|
|
964
|
+
* override that was set using `set()`.
|
|
965
|
+
* - For nodes without a compute function (mutable state): Recomputes the current
|
|
966
|
+
* value (which is just the stored value), useful for consistency but typically
|
|
967
|
+
* not necessary.
|
|
968
|
+
*
|
|
969
|
+
* The recomputation happens asynchronously through the reactive graph, ensuring
|
|
970
|
+
* proper ordering of updates and effect execution.
|
|
971
|
+
*
|
|
972
|
+
* @throws Error if the node has been disposed.
|
|
973
|
+
*
|
|
974
|
+
* @example
|
|
975
|
+
* ```typescript
|
|
976
|
+
* const $externalData = new FlowNode(() => fetchExternalData());
|
|
977
|
+
*
|
|
978
|
+
* // Some external event occurs that changes the data source
|
|
979
|
+
* externalDataChanged();
|
|
980
|
+
*
|
|
981
|
+
* // Force recomputation to get the new value
|
|
982
|
+
* await $externalData.refresh();
|
|
346
983
|
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
984
|
+
* // For computed nodes with temporary overrides
|
|
985
|
+
* const $computed = new FlowNode((t) => $source.get(t) * 2);
|
|
986
|
+
* $computed.set(100); // Temporary override
|
|
987
|
+
* await $computed.refresh(); // Clears override, recomputes from source
|
|
988
|
+
* ```
|
|
350
989
|
*
|
|
351
990
|
* @public
|
|
352
991
|
*/
|
|
353
|
-
|
|
354
|
-
super();
|
|
355
|
-
this._compute = compute;
|
|
356
|
-
this._trackedContext = new TrackingContext(this);
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Internal method to get the raw value.
|
|
360
|
-
* @internal
|
|
361
|
-
*/
|
|
362
|
-
_getRaw() {
|
|
992
|
+
async refresh() {
|
|
363
993
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
this._value = this._compute(this._trackedContext);
|
|
376
|
-
this._initialized = true;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
/* @internal */
|
|
380
|
-
_update() {
|
|
381
|
-
if (this._dirty) {
|
|
382
|
-
const dependencies = [...this._dependencies];
|
|
383
|
-
this._dependencies.clear();
|
|
384
|
-
this._value = this._compute(this._trackedContext);
|
|
385
|
-
const dependenciesToRemove = dependencies.filter(
|
|
386
|
-
(dependency) => !this._dependencies.has(dependency)
|
|
387
|
-
);
|
|
388
|
-
dependenciesToRemove.forEach((dependency) => {
|
|
389
|
-
dependency._unregisterDependency(this);
|
|
390
|
-
});
|
|
391
|
-
this._dirty = false;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
/* @internal */
|
|
395
|
-
_notify() {
|
|
396
|
-
this._dirty = true;
|
|
397
|
-
super._notify();
|
|
994
|
+
const update = () => {
|
|
995
|
+
this._computeValue();
|
|
996
|
+
const currentValue = this._value;
|
|
997
|
+
this._computeValue({ force: true });
|
|
998
|
+
const nextValue = this._value;
|
|
999
|
+
return nextValue !== currentValue;
|
|
1000
|
+
};
|
|
1001
|
+
const notify = () => {
|
|
1002
|
+
super._notify();
|
|
1003
|
+
};
|
|
1004
|
+
return await FlowGraph.requestWrite(notify, update);
|
|
398
1005
|
}
|
|
399
1006
|
/**
|
|
400
|
-
*
|
|
1007
|
+
* Gets the current value without any dependency tracking.
|
|
401
1008
|
*
|
|
402
|
-
* @
|
|
1009
|
+
* @returns A promise that resolves with the current value of type T.
|
|
403
1010
|
*
|
|
404
1011
|
* @remarks
|
|
405
|
-
* This method
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
1012
|
+
* This method reads the value asynchronously through the reactive graph, ensuring proper ordering of
|
|
1013
|
+
* read operations. Unlike `get(t)`, this method does not create a reactive dependency.
|
|
1014
|
+
*
|
|
1015
|
+
* Use `pick()` when you want to read a snapshot of the current value without creating a reactive
|
|
1016
|
+
* dependency. This is useful for:
|
|
1017
|
+
* - Reading initial values outside reactive contexts
|
|
1018
|
+
* - Accessing configuration that shouldn't trigger updates
|
|
1019
|
+
* - Mixing tracked and untracked reads in the same effect
|
|
410
1020
|
*
|
|
411
|
-
* This
|
|
412
|
-
* 1. The derivation is registered as a dependency in the provided context
|
|
413
|
-
* 2. The derivation computes its value (if not already computed)
|
|
414
|
-
* 3. The derivation tracks its own dependencies during computation
|
|
1021
|
+
* **Note:** This is an async method. Always use `await` when calling it.
|
|
415
1022
|
*
|
|
416
1023
|
* @example
|
|
417
1024
|
* ```typescript
|
|
418
|
-
*
|
|
1025
|
+
* // Read a snapshot outside reactive context
|
|
1026
|
+
* const currentValue = await $state.pick();
|
|
419
1027
|
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
1028
|
+
* // Mix tracked and untracked reads
|
|
1029
|
+
* effect(async (t) => {
|
|
1030
|
+
* const tracked = $reactive.get(t); // Triggers re-runs
|
|
1031
|
+
* const snapshot = await $config.pick(); // Doesn't trigger re-runs
|
|
1032
|
+
* processData(tracked, snapshot);
|
|
423
1033
|
* });
|
|
424
1034
|
* ```
|
|
425
1035
|
*
|
|
1036
|
+
* @throws Error if the node has been disposed.
|
|
1037
|
+
*
|
|
426
1038
|
* @public
|
|
427
1039
|
*/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
this.
|
|
431
|
-
this.
|
|
1040
|
+
async pick() {
|
|
1041
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
1042
|
+
await FlowGraph.requestRead(() => this._computeValue());
|
|
1043
|
+
return this._value;
|
|
432
1044
|
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function isDisposable(obj) {
|
|
436
|
-
return obj !== null && obj !== void 0 && typeof obj.dispose === "function";
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
class FlowState extends FlowConstant {
|
|
440
1045
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
1046
|
+
* Subscribes a listener function to changes of this node.
|
|
1047
|
+
*
|
|
1048
|
+
* @param listener - A callback function that receives the new value whenever it changes.
|
|
1049
|
+
*
|
|
1050
|
+
* @returns A disposer function that cancels the subscription when called.
|
|
1051
|
+
*
|
|
443
1052
|
* @remarks
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
1053
|
+
* This method creates a reactive subscription that automatically tracks this node as a dependency.
|
|
1054
|
+
* The listener is executed:
|
|
1055
|
+
* - Immediately with the current value when the subscription is created
|
|
1056
|
+
* - Automatically whenever the value changes
|
|
1057
|
+
*
|
|
1058
|
+
* The subscription uses a {@link FlowEffect} internally to manage the reactive tracking and
|
|
1059
|
+
* automatic re-execution. When the value changes, the listener is called with the new value
|
|
1060
|
+
* after the update is processed through the reactive graph.
|
|
1061
|
+
*
|
|
1062
|
+
* **Cleanup:**
|
|
1063
|
+
* Always call the returned disposer function when you no longer need the subscription to
|
|
1064
|
+
* prevent memory leaks and unnecessary computations.
|
|
1065
|
+
*
|
|
1066
|
+
* @example
|
|
1067
|
+
* ```typescript
|
|
1068
|
+
* const $count = new FlowNode(0);
|
|
1069
|
+
*
|
|
1070
|
+
* // Subscribe to changes
|
|
1071
|
+
* const unsubscribe = $count.subscribe((value) => {
|
|
1072
|
+
* console.log(`Count is now: ${value}`);
|
|
1073
|
+
* });
|
|
1074
|
+
* // Logs immediately: "Count is now: 0"
|
|
1075
|
+
*
|
|
1076
|
+
* await $count.set(5);
|
|
1077
|
+
* // Logs: "Count is now: 5"
|
|
1078
|
+
*
|
|
1079
|
+
* // Clean up when done
|
|
1080
|
+
* unsubscribe();
|
|
1081
|
+
* ```
|
|
1082
|
+
*
|
|
1083
|
+
* @throws Error if the node has been disposed.
|
|
1084
|
+
*
|
|
447
1085
|
* @public
|
|
448
1086
|
*/
|
|
449
|
-
|
|
1087
|
+
subscribe(listener) {
|
|
450
1088
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1089
|
+
const effect = new FlowEffect((t) => {
|
|
1090
|
+
listener(this.get(t));
|
|
1091
|
+
});
|
|
1092
|
+
return () => effect.dispose();
|
|
1093
|
+
}
|
|
1094
|
+
/* INTERNAL --------------------------------------------------------- */
|
|
1095
|
+
/* @internal */
|
|
1096
|
+
_notify() {
|
|
1097
|
+
if (this._dirty) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
this._dirty = true;
|
|
1101
|
+
super._notify();
|
|
1102
|
+
}
|
|
1103
|
+
_computeValue(options) {
|
|
1104
|
+
if (!this._dirty && !options?.force) return;
|
|
1105
|
+
if (!this._compute) {
|
|
1106
|
+
this._dirty = false;
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
this._dirty = false;
|
|
1110
|
+
const dependencies = [...this._dependencies];
|
|
1111
|
+
this._dependencies.clear();
|
|
1112
|
+
this._value = this._compute(this);
|
|
1113
|
+
const dependenciesToRemove = dependencies.filter(
|
|
1114
|
+
(dependency) => !this._dependencies.has(dependency)
|
|
1115
|
+
);
|
|
1116
|
+
dependenciesToRemove.forEach((dependency) => {
|
|
1117
|
+
dependency._unregisterDependency(this);
|
|
1118
|
+
});
|
|
455
1119
|
}
|
|
456
1120
|
}
|
|
457
1121
|
|
|
458
|
-
function signal() {
|
|
459
|
-
return new FlowSignal();
|
|
460
|
-
}
|
|
461
1122
|
function constant(value) {
|
|
462
|
-
return new
|
|
463
|
-
}
|
|
464
|
-
function state(value) {
|
|
465
|
-
return new FlowState(value);
|
|
466
|
-
}
|
|
467
|
-
function resource(fn) {
|
|
468
|
-
return new FlowResource(fn);
|
|
469
|
-
}
|
|
470
|
-
function resourceAsync(fn) {
|
|
471
|
-
return new FlowResourceAsync(fn);
|
|
472
|
-
}
|
|
473
|
-
function stream(updater) {
|
|
474
|
-
return new FlowStream(updater);
|
|
475
|
-
}
|
|
476
|
-
function streamAsync(updater) {
|
|
477
|
-
return new FlowStreamAsync(updater);
|
|
1123
|
+
return new FlowNode(value);
|
|
478
1124
|
}
|
|
1125
|
+
|
|
479
1126
|
function derivation(fn) {
|
|
480
|
-
return new
|
|
481
|
-
}
|
|
482
|
-
function effect(fn) {
|
|
483
|
-
return new FlowEffect(fn);
|
|
484
|
-
}
|
|
485
|
-
function map(initial) {
|
|
486
|
-
return new FlowMap(
|
|
487
|
-
new Map(initial ? Object.entries(initial) : [])
|
|
488
|
-
);
|
|
1127
|
+
return new FlowNode(fn);
|
|
489
1128
|
}
|
|
490
|
-
|
|
491
|
-
|
|
1129
|
+
|
|
1130
|
+
function state(value) {
|
|
1131
|
+
return new FlowNode(value);
|
|
492
1132
|
}
|
|
493
1133
|
|
|
494
|
-
class FlowArray extends
|
|
1134
|
+
class FlowArray extends FlowNode {
|
|
495
1135
|
/**
|
|
496
1136
|
* Last action performed on the FlowArray.
|
|
497
1137
|
* @public
|
|
@@ -503,8 +1143,7 @@ class FlowArray extends FlowObservable {
|
|
|
503
1143
|
* @public
|
|
504
1144
|
*/
|
|
505
1145
|
constructor(value = []) {
|
|
506
|
-
super();
|
|
507
|
-
this._value = value;
|
|
1146
|
+
super(value);
|
|
508
1147
|
this.$lastAction = state({
|
|
509
1148
|
type: "set",
|
|
510
1149
|
items: value
|
|
@@ -532,14 +1171,23 @@ class FlowArray extends FlowObservable {
|
|
|
532
1171
|
* @param items - The new array of items.
|
|
533
1172
|
* @public
|
|
534
1173
|
*/
|
|
535
|
-
set(items) {
|
|
1174
|
+
async set(items) {
|
|
536
1175
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1176
|
+
const update = () => {
|
|
1177
|
+
const currentValue = this._value;
|
|
1178
|
+
if (currentValue !== items) {
|
|
1179
|
+
currentValue.forEach((item) => {
|
|
1180
|
+
if (isDisposable(item)) item.dispose({ self: true });
|
|
1181
|
+
});
|
|
1182
|
+
this._value = items;
|
|
1183
|
+
this.$lastAction.set({ type: "set", items });
|
|
1184
|
+
}
|
|
1185
|
+
return currentValue !== items;
|
|
1186
|
+
};
|
|
1187
|
+
const notify = () => {
|
|
1188
|
+
super._notify();
|
|
1189
|
+
};
|
|
1190
|
+
return FlowGraph.requestWrite(notify, update);
|
|
543
1191
|
}
|
|
544
1192
|
/**
|
|
545
1193
|
* Replaces an item at a specific index.
|
|
@@ -547,62 +1195,104 @@ class FlowArray extends FlowObservable {
|
|
|
547
1195
|
* @param item - The new item.
|
|
548
1196
|
* @public
|
|
549
1197
|
*/
|
|
550
|
-
|
|
1198
|
+
async update(index, item) {
|
|
551
1199
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1200
|
+
const update = () => {
|
|
1201
|
+
if (index < 0 || index >= this._value.length) {
|
|
1202
|
+
throw new Error("[PicoFlow] Index out of bounds");
|
|
1203
|
+
}
|
|
1204
|
+
const currentValue = this._value[index];
|
|
1205
|
+
if (currentValue !== item) {
|
|
1206
|
+
if (isDisposable(currentValue)) {
|
|
1207
|
+
currentValue.dispose({ self: true });
|
|
1208
|
+
}
|
|
1209
|
+
this._value[index] = item;
|
|
1210
|
+
this.$lastAction.set({ type: "update", index, item });
|
|
1211
|
+
}
|
|
1212
|
+
return currentValue !== item;
|
|
1213
|
+
};
|
|
1214
|
+
const notify = () => {
|
|
1215
|
+
super._notify();
|
|
1216
|
+
};
|
|
1217
|
+
return FlowGraph.requestWrite(notify, update);
|
|
558
1218
|
}
|
|
559
1219
|
/**
|
|
560
1220
|
* Appends an item to the end of the array.
|
|
561
1221
|
* @param item - The item to append.
|
|
562
1222
|
* @public
|
|
563
1223
|
*/
|
|
564
|
-
push(item) {
|
|
1224
|
+
async push(item) {
|
|
565
1225
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1226
|
+
const update = () => {
|
|
1227
|
+
this._value.push(item);
|
|
1228
|
+
this.$lastAction.set({ type: "push", item });
|
|
1229
|
+
return true;
|
|
1230
|
+
};
|
|
1231
|
+
const notify = () => {
|
|
1232
|
+
super._notify();
|
|
1233
|
+
};
|
|
1234
|
+
return FlowGraph.requestWrite(notify, update);
|
|
569
1235
|
}
|
|
570
1236
|
/**
|
|
571
1237
|
* Removes the last item from the array.
|
|
572
1238
|
* @public
|
|
573
1239
|
*/
|
|
574
|
-
pop() {
|
|
1240
|
+
async pop() {
|
|
575
1241
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
item
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
1242
|
+
const update = () => {
|
|
1243
|
+
const item = this._value.pop();
|
|
1244
|
+
if (item !== void 0) {
|
|
1245
|
+
if (isDisposable(item)) {
|
|
1246
|
+
item.dispose({ self: true });
|
|
1247
|
+
}
|
|
1248
|
+
this.$lastAction.set({ type: "pop" });
|
|
1249
|
+
return true;
|
|
1250
|
+
}
|
|
1251
|
+
return false;
|
|
1252
|
+
};
|
|
1253
|
+
const notify = () => {
|
|
1254
|
+
super._notify();
|
|
1255
|
+
};
|
|
1256
|
+
return FlowGraph.requestWrite(notify, update);
|
|
582
1257
|
}
|
|
583
1258
|
/**
|
|
584
1259
|
* Inserts an item at the beginning of the array.
|
|
585
1260
|
* @param item - The item to insert.
|
|
586
1261
|
* @public
|
|
587
1262
|
*/
|
|
588
|
-
unshift(item) {
|
|
1263
|
+
async unshift(item) {
|
|
589
1264
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1265
|
+
const update = () => {
|
|
1266
|
+
this._value.unshift(item);
|
|
1267
|
+
this.$lastAction.set({ type: "unshift", item });
|
|
1268
|
+
return true;
|
|
1269
|
+
};
|
|
1270
|
+
const notify = () => {
|
|
1271
|
+
super._notify();
|
|
1272
|
+
};
|
|
1273
|
+
return FlowGraph.requestWrite(notify, update);
|
|
593
1274
|
}
|
|
594
1275
|
/**
|
|
595
1276
|
* Removes the first item from the array.
|
|
596
1277
|
* @public
|
|
597
1278
|
*/
|
|
598
|
-
shift() {
|
|
1279
|
+
async shift() {
|
|
599
1280
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
item
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
1281
|
+
const update = () => {
|
|
1282
|
+
const item = this._value.shift();
|
|
1283
|
+
if (item !== void 0) {
|
|
1284
|
+
if (isDisposable(item)) {
|
|
1285
|
+
item.dispose({ self: true });
|
|
1286
|
+
}
|
|
1287
|
+
this.$lastAction.set({ type: "shift" });
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
};
|
|
1292
|
+
const notify = () => {
|
|
1293
|
+
super._notify();
|
|
1294
|
+
};
|
|
1295
|
+
return FlowGraph.requestWrite(notify, update);
|
|
606
1296
|
}
|
|
607
1297
|
/**
|
|
608
1298
|
* Changes the content of the array.
|
|
@@ -611,33 +1301,45 @@ class FlowArray extends FlowObservable {
|
|
|
611
1301
|
* @param newItems - New items to add.
|
|
612
1302
|
* @public
|
|
613
1303
|
*/
|
|
614
|
-
splice(start, deleteCount, ...newItems) {
|
|
1304
|
+
async splice(start, deleteCount, ...newItems) {
|
|
615
1305
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1306
|
+
const update = () => {
|
|
1307
|
+
const items = this._value.splice(start, deleteCount, ...newItems);
|
|
1308
|
+
items.forEach((item) => {
|
|
1309
|
+
if (isDisposable(item)) item.dispose({ self: true });
|
|
1310
|
+
});
|
|
1311
|
+
this.$lastAction.set({
|
|
1312
|
+
type: "splice",
|
|
1313
|
+
start,
|
|
1314
|
+
deleteCount,
|
|
1315
|
+
items: newItems
|
|
1316
|
+
});
|
|
1317
|
+
return true;
|
|
1318
|
+
};
|
|
1319
|
+
const notify = () => {
|
|
1320
|
+
super._notify();
|
|
1321
|
+
};
|
|
1322
|
+
return FlowGraph.requestWrite(notify, update);
|
|
627
1323
|
}
|
|
628
1324
|
/**
|
|
629
1325
|
* Clears all items from the array.
|
|
630
1326
|
* @public
|
|
631
1327
|
*/
|
|
632
|
-
clear() {
|
|
1328
|
+
async clear() {
|
|
633
1329
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1330
|
+
const update = () => {
|
|
1331
|
+
const items = [...this._value];
|
|
1332
|
+
items.forEach((item) => {
|
|
1333
|
+
if (isDisposable(item)) item.dispose({ self: true });
|
|
1334
|
+
});
|
|
1335
|
+
this._value = [];
|
|
1336
|
+
this.$lastAction.set({ type: "clear" });
|
|
1337
|
+
return true;
|
|
1338
|
+
};
|
|
1339
|
+
const notify = () => {
|
|
1340
|
+
super._notify();
|
|
1341
|
+
};
|
|
1342
|
+
return FlowGraph.requestWrite(notify, update);
|
|
641
1343
|
}
|
|
642
1344
|
/**
|
|
643
1345
|
* Disposes the FlowArray and its items.
|
|
@@ -652,41 +1354,29 @@ class FlowArray extends FlowObservable {
|
|
|
652
1354
|
this._value = [];
|
|
653
1355
|
}
|
|
654
1356
|
/* INTERNAL */
|
|
655
|
-
|
|
656
|
-
|
|
1357
|
+
}
|
|
1358
|
+
function array(initial) {
|
|
1359
|
+
return new FlowArray(initial);
|
|
657
1360
|
}
|
|
658
1361
|
|
|
659
|
-
class FlowMap extends
|
|
660
|
-
/**
|
|
661
|
-
* A reactive state that holds the most recent key and value that were added.
|
|
662
|
-
*
|
|
663
|
-
* @remarks
|
|
664
|
-
* When a key is added via {@link FlowMap.add}, this state is updated with
|
|
665
|
-
* the corresponding key and value.
|
|
666
|
-
*
|
|
667
|
-
* @public
|
|
668
|
-
*/
|
|
669
|
-
$lastAdded = new FlowState(null);
|
|
1362
|
+
class FlowMap extends FlowNode {
|
|
670
1363
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
* @remarks
|
|
674
|
-
* When a key is updated via {@link FlowMap.update}, this state is updated with
|
|
675
|
-
* the corresponding key and value.
|
|
676
|
-
*
|
|
1364
|
+
* Last action performed on the FlowMap.
|
|
677
1365
|
* @public
|
|
678
1366
|
*/
|
|
679
|
-
$
|
|
1367
|
+
$lastAction;
|
|
680
1368
|
/**
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
* @remarks
|
|
684
|
-
* When a key is deleted via {@link FlowMap.delete}, this state is updated with
|
|
685
|
-
* the corresponding key and its last known value.
|
|
686
|
-
*
|
|
1369
|
+
* Creates an instance of FlowMap.
|
|
1370
|
+
* @param value - Initial map value.
|
|
687
1371
|
* @public
|
|
688
1372
|
*/
|
|
689
|
-
|
|
1373
|
+
constructor(value = /* @__PURE__ */ new Map()) {
|
|
1374
|
+
super(value);
|
|
1375
|
+
this.$lastAction = state({
|
|
1376
|
+
type: "set",
|
|
1377
|
+
map: value
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
690
1380
|
/**
|
|
691
1381
|
* Adds a new key-value pair to the map.
|
|
692
1382
|
*
|
|
@@ -696,19 +1386,26 @@ class FlowMap extends FlowState {
|
|
|
696
1386
|
* @throws If the key already exists in the map.
|
|
697
1387
|
*
|
|
698
1388
|
* @remarks
|
|
699
|
-
* Adds a new entry to the internal map, emits the key-value pair via {@link FlowMap.$
|
|
1389
|
+
* Adds a new entry to the internal map, emits the key-value pair via {@link FlowMap.$lastAction},
|
|
700
1390
|
* and notifies all subscribers of the change.
|
|
701
1391
|
*
|
|
702
1392
|
* @public
|
|
703
1393
|
*/
|
|
704
|
-
add(key, value) {
|
|
1394
|
+
async add(key, value) {
|
|
705
1395
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1396
|
+
const update = () => {
|
|
1397
|
+
const currentValue = this._value.get(key);
|
|
1398
|
+
if (currentValue) {
|
|
1399
|
+
throw new Error("[PicoFlow] Key already exists");
|
|
1400
|
+
}
|
|
1401
|
+
this._value.set(key, value);
|
|
1402
|
+
this.$lastAction.set({ type: "add", key, value });
|
|
1403
|
+
return true;
|
|
1404
|
+
};
|
|
1405
|
+
const notify = () => {
|
|
1406
|
+
super._notify();
|
|
1407
|
+
};
|
|
1408
|
+
return FlowGraph.requestWrite(notify, update);
|
|
712
1409
|
}
|
|
713
1410
|
/**
|
|
714
1411
|
* Updates an existing key-value pair in the map.
|
|
@@ -719,19 +1416,28 @@ class FlowMap extends FlowState {
|
|
|
719
1416
|
* @throws If the key does not exist in the map.
|
|
720
1417
|
*
|
|
721
1418
|
* @remarks
|
|
722
|
-
* Updates an existing entry in the internal map, emits the key-value pair via {@link FlowMap.$
|
|
1419
|
+
* Updates an existing entry in the internal map, emits the key-value pair via {@link FlowMap.$lastAction},
|
|
723
1420
|
* and notifies all subscribers of the change.
|
|
724
1421
|
*
|
|
725
1422
|
* @public
|
|
726
1423
|
*/
|
|
727
|
-
update(key, value) {
|
|
1424
|
+
async update(key, value) {
|
|
728
1425
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1426
|
+
const update = () => {
|
|
1427
|
+
const currentValue = this._value.get(key);
|
|
1428
|
+
if (!currentValue) throw new Error("[PicoFlow] Key does not exist");
|
|
1429
|
+
if (currentValue !== value) {
|
|
1430
|
+
if (isDisposable(currentValue)) currentValue.dispose({ self: true });
|
|
1431
|
+
this._value.set(key, value);
|
|
1432
|
+
this.$lastAction.set({ type: "update", key, value });
|
|
1433
|
+
return true;
|
|
1434
|
+
}
|
|
1435
|
+
return false;
|
|
1436
|
+
};
|
|
1437
|
+
const notify = () => {
|
|
1438
|
+
super._notify();
|
|
1439
|
+
};
|
|
1440
|
+
return FlowGraph.requestWrite(notify, update);
|
|
735
1441
|
}
|
|
736
1442
|
/**
|
|
737
1443
|
* Deletes the value at the specified key from the underlying map.
|
|
@@ -740,225 +1446,105 @@ class FlowMap extends FlowState {
|
|
|
740
1446
|
* @throws If the FlowMap instance is disposed.
|
|
741
1447
|
*
|
|
742
1448
|
* @remarks
|
|
743
|
-
* Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$
|
|
1449
|
+
* Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$lastAction},
|
|
744
1450
|
* and notifies all subscribers of the change.
|
|
745
1451
|
*
|
|
746
1452
|
* @public
|
|
747
1453
|
*/
|
|
748
|
-
delete(key) {
|
|
1454
|
+
async delete(key) {
|
|
749
1455
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
super.dispose(options);
|
|
763
|
-
this._value.forEach((item) => {
|
|
764
|
-
if (isDisposable(item)) item.dispose(options);
|
|
765
|
-
});
|
|
766
|
-
this._value.clear();
|
|
767
|
-
this.$lastAdded.dispose(options);
|
|
768
|
-
this.$lastUpdated.dispose(options);
|
|
769
|
-
this.$lastDeleted.dispose(options);
|
|
1456
|
+
const update = () => {
|
|
1457
|
+
const value = this._value.get(key);
|
|
1458
|
+
if (value === void 0) throw new Error("[PicoFlow] Key does not exist");
|
|
1459
|
+
if (isDisposable(value)) value.dispose({ self: true });
|
|
1460
|
+
this._value.delete(key);
|
|
1461
|
+
this.$lastAction.set({ type: "delete", key, value });
|
|
1462
|
+
return true;
|
|
1463
|
+
};
|
|
1464
|
+
const notify = () => {
|
|
1465
|
+
super._notify();
|
|
1466
|
+
};
|
|
1467
|
+
return FlowGraph.requestWrite(notify, update);
|
|
770
1468
|
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
class FlowResource extends FlowObservable {
|
|
774
1469
|
/**
|
|
775
|
-
*
|
|
1470
|
+
* Replaces the entire map with new entries.
|
|
776
1471
|
*
|
|
777
|
-
* @param
|
|
778
|
-
*
|
|
779
|
-
* to execute it.
|
|
1472
|
+
* @param map - The new map of entries.
|
|
1473
|
+
* @throws If the FlowMap instance is disposed.
|
|
780
1474
|
*
|
|
781
|
-
* @public
|
|
782
|
-
*/
|
|
783
|
-
constructor(fetch) {
|
|
784
|
-
super();
|
|
785
|
-
this._fetch = fetch;
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Internal method to get the raw value.
|
|
789
|
-
* @internal
|
|
790
|
-
*/
|
|
791
|
-
_getRaw() {
|
|
792
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
793
|
-
return this._value;
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Asynchronously fetches a new value for the resource.
|
|
797
|
-
* @remarks
|
|
798
|
-
* Executes the internal fetch function. If the fetched value differs from the current one,
|
|
799
|
-
* updates the resource's value and notifies subscribers.
|
|
800
|
-
* @returns A Promise that resolves when the fetch operation is complete.
|
|
801
|
-
* @throws Error if the resource is disposed.
|
|
802
|
-
* @public
|
|
803
|
-
*/
|
|
804
|
-
async fetch() {
|
|
805
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
806
|
-
const value = await this._fetch();
|
|
807
|
-
if (value === this._value) return;
|
|
808
|
-
this._value = value;
|
|
809
|
-
this._notify();
|
|
810
|
-
}
|
|
811
|
-
/* INTERNAL ------------------------------------------------ */
|
|
812
|
-
_fetch;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
class FlowResourceAsync extends FlowObservable {
|
|
816
|
-
/**
|
|
817
|
-
* Creates a new FlowResource.
|
|
818
|
-
* @param fetch - An asynchronous function that retrieves the resource's value.
|
|
819
|
-
* @public
|
|
820
|
-
*/
|
|
821
|
-
constructor(fetch) {
|
|
822
|
-
super();
|
|
823
|
-
this._fetch = fetch;
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Internal method to get the raw value.
|
|
827
|
-
* @internal
|
|
828
|
-
*/
|
|
829
|
-
_getRaw() {
|
|
830
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
831
|
-
if (!this._value) this._value = this._fetch();
|
|
832
|
-
return this._value;
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* Asynchronously fetches a new value for the resource.
|
|
836
1475
|
* @remarks
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
* @throws Error if the resource is disposed.
|
|
1476
|
+
* Replaces all entries in the internal map, disposes the old values (if they are disposable),
|
|
1477
|
+
* emits the new map via {@link FlowMap.$lastAction}, and notifies all subscribers of the change.
|
|
1478
|
+
*
|
|
841
1479
|
* @public
|
|
842
1480
|
*/
|
|
843
|
-
async
|
|
1481
|
+
async set(map2) {
|
|
844
1482
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
845
|
-
|
|
846
|
-
|
|
1483
|
+
const update = () => {
|
|
1484
|
+
const currentValue = this._value;
|
|
1485
|
+
if (currentValue !== map2) {
|
|
1486
|
+
currentValue.forEach((item) => {
|
|
1487
|
+
if (isDisposable(item)) item.dispose({ self: true });
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
this._value = map2;
|
|
1491
|
+
if (currentValue !== map2) {
|
|
1492
|
+
this.$lastAction.set({ type: "set", map: map2 });
|
|
1493
|
+
}
|
|
1494
|
+
return currentValue !== map2;
|
|
1495
|
+
};
|
|
1496
|
+
const notify = () => {
|
|
1497
|
+
super._notify();
|
|
1498
|
+
};
|
|
1499
|
+
return FlowGraph.requestWrite(notify, update);
|
|
847
1500
|
}
|
|
848
|
-
/* INTERNAL ------------------------------------------------ */
|
|
849
|
-
_fetch;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
class FlowStream extends FlowObservable {
|
|
853
1501
|
/**
|
|
854
|
-
*
|
|
1502
|
+
* Clears all entries from the map.
|
|
855
1503
|
*
|
|
856
|
-
* @
|
|
857
|
-
* The setter should be called whenever new data is available. The disposer will be
|
|
858
|
-
* invoked when the stream is disposed to clean up resources.
|
|
1504
|
+
* @throws If the FlowMap instance is disposed.
|
|
859
1505
|
*
|
|
860
1506
|
* @remarks
|
|
861
|
-
*
|
|
862
|
-
*
|
|
1507
|
+
* Removes all entries from the internal map, disposes the removed values (if they are disposable),
|
|
1508
|
+
* emits a clear action via {@link FlowMap.$lastAction}, and notifies all subscribers of the change.
|
|
863
1509
|
*
|
|
864
1510
|
* @public
|
|
865
1511
|
*/
|
|
866
|
-
|
|
867
|
-
super();
|
|
868
|
-
this._disposer = updater((value) => {
|
|
869
|
-
this._set(value);
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Internal method to get the raw value.
|
|
874
|
-
* @internal
|
|
875
|
-
*/
|
|
876
|
-
_getRaw() {
|
|
877
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
878
|
-
return this._value;
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Disposes the stream, releasing all resources.
|
|
882
|
-
* @remarks
|
|
883
|
-
* In addition to disposing the underlying observable, this method calls the disposer
|
|
884
|
-
* returned by the updater.
|
|
885
|
-
* @public
|
|
886
|
-
*/
|
|
887
|
-
dispose() {
|
|
888
|
-
super.dispose();
|
|
889
|
-
this._disposer();
|
|
890
|
-
}
|
|
891
|
-
/* INTERNAL ------------------------------------------------------ */
|
|
892
|
-
_disposer;
|
|
893
|
-
_set(value) {
|
|
1512
|
+
async clear() {
|
|
894
1513
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1514
|
+
const update = () => {
|
|
1515
|
+
const items = [...this._value.values()];
|
|
1516
|
+
items.forEach((item) => {
|
|
1517
|
+
if (isDisposable(item)) item.dispose({ self: true });
|
|
1518
|
+
});
|
|
1519
|
+
this._value.clear();
|
|
1520
|
+
this.$lastAction.set({ type: "clear" });
|
|
1521
|
+
return true;
|
|
1522
|
+
};
|
|
1523
|
+
const notify = () => {
|
|
1524
|
+
super._notify();
|
|
1525
|
+
};
|
|
1526
|
+
return FlowGraph.requestWrite(notify, update);
|
|
898
1527
|
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
class FlowStreamAsync extends FlowObservable {
|
|
902
1528
|
/**
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
* @param updater - A function that receives a setter callback and returns a disposer.
|
|
906
|
-
* The setter should be called whenever new data is available (can be called asynchronously).
|
|
907
|
-
* The disposer will be invoked when the stream is disposed to clean up resources.
|
|
908
|
-
*
|
|
909
|
-
* @remarks
|
|
910
|
-
* The updater is invoked immediately during construction. An initial Promise is created
|
|
911
|
-
* that will resolve when the setter is first called. Make sure to return a proper cleanup
|
|
912
|
-
* function to avoid resource leaks.
|
|
913
|
-
*
|
|
1529
|
+
* Disposes the FlowMap and its values.
|
|
1530
|
+
* @param options - Disposal options.
|
|
914
1531
|
* @public
|
|
915
1532
|
*/
|
|
916
|
-
|
|
917
|
-
super();
|
|
918
|
-
this.
|
|
919
|
-
|
|
920
|
-
});
|
|
921
|
-
this._value = new Promise((resolve) => {
|
|
922
|
-
this._resolve = resolve;
|
|
1533
|
+
dispose(options) {
|
|
1534
|
+
super.dispose(options);
|
|
1535
|
+
this._value.forEach((item) => {
|
|
1536
|
+
if (isDisposable(item)) item.dispose(options);
|
|
923
1537
|
});
|
|
1538
|
+
this._value.clear();
|
|
924
1539
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
_getRaw() {
|
|
930
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
931
|
-
return this._value;
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Disposes the stream, releasing all resources.
|
|
935
|
-
* @remarks In addition to disposing the underlying observable, this method calls the disposer
|
|
936
|
-
* returned by the updater.
|
|
937
|
-
* @public
|
|
938
|
-
*/
|
|
939
|
-
dispose() {
|
|
940
|
-
super.dispose();
|
|
941
|
-
this._disposer();
|
|
942
|
-
}
|
|
943
|
-
/* INTERNAL ------------------------------------------------------ */
|
|
944
|
-
_initialized = false;
|
|
945
|
-
_awaitedValue;
|
|
946
|
-
_resolve;
|
|
947
|
-
_disposer;
|
|
948
|
-
_set(value) {
|
|
949
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
950
|
-
if (!this._initialized) {
|
|
951
|
-
this._resolve(value);
|
|
952
|
-
this._initialized = true;
|
|
953
|
-
this._awaitedValue = value;
|
|
954
|
-
this._notify();
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
if (value === this._awaitedValue) return;
|
|
958
|
-
this._value = Promise.resolve(value);
|
|
959
|
-
this._awaitedValue = value;
|
|
960
|
-
this._notify();
|
|
1540
|
+
}
|
|
1541
|
+
function map(initial) {
|
|
1542
|
+
if (initial instanceof Map) {
|
|
1543
|
+
return new FlowMap(initial);
|
|
961
1544
|
}
|
|
1545
|
+
return new FlowMap(
|
|
1546
|
+
new Map(initial ? Object.entries(initial) : [])
|
|
1547
|
+
);
|
|
962
1548
|
}
|
|
963
1549
|
|
|
964
1550
|
class SolidState {
|
|
@@ -1011,6 +1597,7 @@ class SolidResource {
|
|
|
1011
1597
|
* Triggers a refetch of the resource.
|
|
1012
1598
|
*/
|
|
1013
1599
|
refetch;
|
|
1600
|
+
set;
|
|
1014
1601
|
/**
|
|
1015
1602
|
* Creates a new SolidResource from a fetcher function.
|
|
1016
1603
|
* @param fetcher - The async fetcher function.
|
|
@@ -1021,60 +1608,46 @@ class SolidResource {
|
|
|
1021
1608
|
this.state = () => get.state;
|
|
1022
1609
|
this.latest = () => get.latest;
|
|
1023
1610
|
this.refetch = () => set.refetch();
|
|
1611
|
+
this.set = (value) => set.mutate(value);
|
|
1024
1612
|
}
|
|
1025
1613
|
}
|
|
1026
1614
|
|
|
1027
|
-
function
|
|
1028
|
-
|
|
1029
|
-
let fx;
|
|
1030
|
-
onMount(() => {
|
|
1031
|
-
fx = new FlowEffect((t) => {
|
|
1032
|
-
const value = state.get(t);
|
|
1033
|
-
solidState.set(() => value);
|
|
1034
|
-
});
|
|
1035
|
-
});
|
|
1036
|
-
onCleanup(() => fx.dispose());
|
|
1037
|
-
return solidState;
|
|
1038
|
-
}
|
|
1039
|
-
function fromAsync(derivation) {
|
|
1615
|
+
function fromNode(node) {
|
|
1616
|
+
let initialized = false;
|
|
1040
1617
|
const solidResource = new SolidResource(async () => {
|
|
1041
|
-
|
|
1618
|
+
let value;
|
|
1619
|
+
if (initialized) {
|
|
1620
|
+
value = await node.get(null);
|
|
1621
|
+
} else {
|
|
1622
|
+
value = await node.pick();
|
|
1623
|
+
initialized = true;
|
|
1624
|
+
}
|
|
1042
1625
|
return value;
|
|
1043
1626
|
});
|
|
1044
1627
|
let fx;
|
|
1045
1628
|
onMount(() => {
|
|
1046
1629
|
fx = new FlowEffect(async (t) => {
|
|
1047
|
-
|
|
1630
|
+
node.watch(t);
|
|
1048
1631
|
solidResource.refetch();
|
|
1049
1632
|
});
|
|
1050
1633
|
});
|
|
1051
1634
|
onCleanup(() => fx.dispose());
|
|
1052
1635
|
return solidResource;
|
|
1053
1636
|
}
|
|
1054
|
-
function
|
|
1055
|
-
const
|
|
1056
|
-
const isAsync = initialValue instanceof Promise;
|
|
1057
|
-
if (isAsync) {
|
|
1058
|
-
return fromAsync(flow);
|
|
1059
|
-
}
|
|
1060
|
-
return fromSync(flow);
|
|
1061
|
-
}
|
|
1062
|
-
function deepFrom(getter) {
|
|
1063
|
-
const derivation = new FlowDerivation((t) => {
|
|
1637
|
+
function fromGetter(getter) {
|
|
1638
|
+
const derivation = new FlowNodeAsync(async (t) => {
|
|
1064
1639
|
return getter(t);
|
|
1065
1640
|
});
|
|
1066
|
-
|
|
1067
|
-
const isAsync = initialValue instanceof Promise;
|
|
1068
|
-
if (isAsync) {
|
|
1069
|
-
return fromAsync(derivation);
|
|
1070
|
-
}
|
|
1071
|
-
return fromSync(derivation);
|
|
1641
|
+
return fromNode(derivation);
|
|
1072
1642
|
}
|
|
1073
1643
|
function from(flow) {
|
|
1074
|
-
if (flow instanceof
|
|
1075
|
-
return
|
|
1644
|
+
if (flow instanceof FlowNodeAsync || flow instanceof FlowNode) {
|
|
1645
|
+
return fromNode(flow);
|
|
1646
|
+
}
|
|
1647
|
+
if (typeof flow === "function") {
|
|
1648
|
+
return fromGetter(flow);
|
|
1076
1649
|
}
|
|
1077
|
-
|
|
1650
|
+
throw new Error("Invalid flow type");
|
|
1078
1651
|
}
|
|
1079
1652
|
|
|
1080
|
-
export { FlowArray,
|
|
1653
|
+
export { FlowArray, FlowEffect, FlowGraph, FlowMap, FlowNode, FlowNodeAsync, FlowSignal, SolidDerivation, SolidResource, SolidState, array, constant, constantAsync, derivation, derivationAsync, effect, from, isDisposable, map, signal, state, stateAsync };
|