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