@ersbeth/picoflow 2.0.3 → 2.2.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/LICENSE +21 -0
- package/README.md +15 -0
- package/SKILL.md +6 -1
- package/dist/picoflow.js +414 -241
- package/dist/types/api/base/flowConfig.d.ts +18 -0
- package/dist/types/api/base/flowConfig.d.ts.map +1 -0
- package/dist/types/api/base/flowObservable.d.ts +3 -3
- package/dist/types/api/base/flowObservable.d.ts.map +1 -1
- package/dist/types/api/base/flowSubscribable.d.ts +2 -2
- package/dist/types/api/base/flowSubscribable.d.ts.map +1 -1
- package/dist/types/api/base/flowTracker.d.ts +1 -1
- package/dist/types/api/base/flowTracker.d.ts.map +1 -1
- package/dist/types/api/base/index.d.ts +5 -4
- package/dist/types/api/base/index.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +2 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts +1 -1
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts +3 -3
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowStateAsync.d.ts +3 -3
- package/dist/types/api/nodes/async/flowStateAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts +2 -2
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts.map +1 -1
- package/dist/types/api/nodes/async/index.d.ts +4 -4
- package/dist/types/api/nodes/async/index.d.ts.map +1 -1
- package/dist/types/api/nodes/collections/flowArray.d.ts +7 -4
- package/dist/types/api/nodes/collections/flowArray.d.ts.map +1 -1
- package/dist/types/api/nodes/collections/flowMap.d.ts +3 -2
- package/dist/types/api/nodes/collections/flowMap.d.ts.map +1 -1
- package/dist/types/api/nodes/collections/index.d.ts +2 -2
- package/dist/types/api/nodes/collections/index.d.ts.map +1 -1
- package/dist/types/api/nodes/flowEffect.d.ts +2 -2
- package/dist/types/api/nodes/flowEffect.d.ts.map +1 -1
- package/dist/types/api/nodes/flowSignal.d.ts +1 -1
- package/dist/types/api/nodes/flowSignal.d.ts.map +1 -1
- package/dist/types/api/nodes/flowValue.d.ts +2 -2
- package/dist/types/api/nodes/flowValue.d.ts.map +1 -1
- package/dist/types/api/nodes/index.d.ts +7 -7
- package/dist/types/api/nodes/index.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowConstant.d.ts +2 -2
- package/dist/types/api/nodes/sync/flowConstant.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowDerivation.d.ts +3 -3
- package/dist/types/api/nodes/sync/flowDerivation.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowState.d.ts +3 -3
- package/dist/types/api/nodes/sync/flowState.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts +2 -2
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts.map +1 -1
- package/dist/types/api/nodes/sync/index.d.ts +4 -4
- package/dist/types/api/nodes/sync/index.d.ts.map +1 -1
- package/dist/types/api/nodes/utils.d.ts +1 -1
- package/dist/types/api/nodes/utils.d.ts.map +1 -1
- package/dist/types/base/disposable.d.ts +4 -4
- package/dist/types/base/disposable.d.ts.map +1 -1
- package/dist/types/base/executionStack.d.ts +26 -1
- package/dist/types/base/executionStack.d.ts.map +1 -1
- package/dist/types/base/index.d.ts +5 -5
- package/dist/types/base/index.d.ts.map +1 -1
- package/dist/types/base/node.d.ts +6 -5
- package/dist/types/base/node.d.ts.map +1 -1
- package/dist/types/base/observable.d.ts +3 -3
- package/dist/types/base/observable.d.ts.map +1 -1
- package/dist/types/base/observer.d.ts +11 -2
- package/dist/types/base/observer.d.ts.map +1 -1
- package/dist/types/converters/index.d.ts +1 -1
- package/dist/types/converters/index.d.ts.map +1 -1
- package/dist/types/converters/solid.d.ts +12 -8
- package/dist/types/converters/solid.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/nodes/actionNode.d.ts +23 -0
- package/dist/types/nodes/actionNode.d.ts.map +1 -0
- package/dist/types/nodes/arrayNode.d.ts +4 -3
- package/dist/types/nodes/arrayNode.d.ts.map +1 -1
- package/dist/types/nodes/effectNode.d.ts +18 -2
- package/dist/types/nodes/effectNode.d.ts.map +1 -1
- package/dist/types/nodes/index.d.ts +8 -7
- package/dist/types/nodes/index.d.ts.map +1 -1
- package/dist/types/nodes/mapNode.d.ts +4 -3
- package/dist/types/nodes/mapNode.d.ts.map +1 -1
- package/dist/types/nodes/signalNode.d.ts +2 -2
- package/dist/types/nodes/signalNode.d.ts.map +1 -1
- package/dist/types/nodes/valueAsyncNode.d.ts +3 -3
- package/dist/types/nodes/valueAsyncNode.d.ts.map +1 -1
- package/dist/types/nodes/valueNode.d.ts +10 -3
- package/dist/types/nodes/valueNode.d.ts.map +1 -1
- package/dist/types/nodes/valueSyncNode.d.ts +3 -3
- package/dist/types/nodes/valueSyncNode.d.ts.map +1 -1
- package/dist/types/schedulers/asyncScheduler.d.ts +2 -1
- package/dist/types/schedulers/asyncScheduler.d.ts.map +1 -1
- package/dist/types/schedulers/index.d.ts +4 -4
- package/dist/types/schedulers/index.d.ts.map +1 -1
- package/dist/types/schedulers/syncResolver.d.ts +1 -2
- package/dist/types/schedulers/syncResolver.d.ts.map +1 -1
- package/dist/types/schedulers/syncScheduler.d.ts +1 -1
- package/dist/types/schedulers/syncScheduler.d.ts.map +1 -1
- package/package.json +22 -2
package/dist/picoflow.js
CHANGED
|
@@ -1,5 +1,242 @@
|
|
|
1
1
|
import { createResource, onMount, createEffect, resetErrorBoundaries, onCleanup } from 'solid-js';
|
|
2
2
|
|
|
3
|
+
class Disposable {
|
|
4
|
+
_disposed = false;
|
|
5
|
+
get disposed() {
|
|
6
|
+
return this._disposed;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class ExecutionStack {
|
|
11
|
+
static _MAX_FLUSH_STEPS = 1e4;
|
|
12
|
+
static _pendingQueue = [];
|
|
13
|
+
static _effectQueue = [];
|
|
14
|
+
static _executionScheduled;
|
|
15
|
+
static _coalesceResetScheduled = false;
|
|
16
|
+
/** Routes any error escaping the flush loop; defaults to logging rather than crashing (A2-SEM-2, A2-SEC-1). */
|
|
17
|
+
static _onFlushError = (error) => console.error(error);
|
|
18
|
+
/**
|
|
19
|
+
* True once the current synchronous call stack has finished and microtasks have started.
|
|
20
|
+
* EffectNode uses this to treat microtask notifications (e.g. async resolve) differently
|
|
21
|
+
* from coalesced sync-batch notifications.
|
|
22
|
+
*/
|
|
23
|
+
static _pastSyncTurn = false;
|
|
24
|
+
/** @internal Used by EffectNode to distinguish sync-batch from microtask notifications. */
|
|
25
|
+
static get pastSyncTurn() {
|
|
26
|
+
return ExecutionStack._pastSyncTurn;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @internal Runs after the current sync turn ends (queueMicrotask).
|
|
30
|
+
* Marks pastSyncTurn so the next coalesced effect notify can bump its epoch,
|
|
31
|
+
* and resets per-effect coalesce flags via onReset.
|
|
32
|
+
*/
|
|
33
|
+
static scheduleCoalesceReset(onReset) {
|
|
34
|
+
if (ExecutionStack._coalesceResetScheduled) return;
|
|
35
|
+
ExecutionStack._coalesceResetScheduled = true;
|
|
36
|
+
queueMicrotask(() => {
|
|
37
|
+
ExecutionStack._pastSyncTurn = true;
|
|
38
|
+
ExecutionStack._coalesceResetScheduled = false;
|
|
39
|
+
onReset();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** @internal Wired to the public `onFlushError()` API in `src/api/base/flowConfig.ts`. */
|
|
43
|
+
static setFlushErrorHandler(handler) {
|
|
44
|
+
ExecutionStack._onFlushError = handler;
|
|
45
|
+
}
|
|
46
|
+
static pushPending(node) {
|
|
47
|
+
ExecutionStack._beginSyncTurn();
|
|
48
|
+
ExecutionStack._scheduleExecution();
|
|
49
|
+
ExecutionStack._pendingQueue.push(node);
|
|
50
|
+
}
|
|
51
|
+
static pushEffect(effect) {
|
|
52
|
+
ExecutionStack._beginSyncTurn();
|
|
53
|
+
ExecutionStack._scheduleExecution();
|
|
54
|
+
ExecutionStack._effectQueue.push(effect);
|
|
55
|
+
}
|
|
56
|
+
static _beginSyncTurn() {
|
|
57
|
+
ExecutionStack._pastSyncTurn = false;
|
|
58
|
+
}
|
|
59
|
+
static _scheduleExecution() {
|
|
60
|
+
if (ExecutionStack._executionScheduled) return;
|
|
61
|
+
ExecutionStack._executionScheduled = new Promise((resolve) => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
ExecutionStack._executionScheduled = void 0;
|
|
64
|
+
ExecutionStack._execute();
|
|
65
|
+
resolve();
|
|
66
|
+
}, 0);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
static _drain(queue) {
|
|
70
|
+
for (let i = 0; i < queue.length; i++) {
|
|
71
|
+
if (i >= ExecutionStack._MAX_FLUSH_STEPS) {
|
|
72
|
+
ExecutionStack._pendingQueue.length = 0;
|
|
73
|
+
ExecutionStack._effectQueue.length = 0;
|
|
74
|
+
ExecutionStack._onFlushError(new Error("[PicoFlow] Reactive update cycle exceeded maximum depth"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const node = queue[i];
|
|
78
|
+
try {
|
|
79
|
+
node?.execute();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
ExecutionStack._onFlushError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
queue.length = 0;
|
|
85
|
+
}
|
|
86
|
+
static _execute() {
|
|
87
|
+
ExecutionStack._drain(ExecutionStack._pendingQueue);
|
|
88
|
+
ExecutionStack._drain(ExecutionStack._effectQueue);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class Node extends Disposable {
|
|
93
|
+
_dependencies = /* @__PURE__ */ new Set();
|
|
94
|
+
_dependents = /* @__PURE__ */ new Set();
|
|
95
|
+
_status = "resolved";
|
|
96
|
+
get status() {
|
|
97
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
98
|
+
return this._status;
|
|
99
|
+
}
|
|
100
|
+
set status(status) {
|
|
101
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
102
|
+
this._status = status;
|
|
103
|
+
}
|
|
104
|
+
registerDependency(dependency) {
|
|
105
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
106
|
+
this._dependencies.add(dependency);
|
|
107
|
+
dependency.registerDependent(this);
|
|
108
|
+
}
|
|
109
|
+
unregisterDependency(dependency) {
|
|
110
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
111
|
+
this._dependencies.delete(dependency);
|
|
112
|
+
dependency.unregisterDependent(this);
|
|
113
|
+
}
|
|
114
|
+
clearDependencies() {
|
|
115
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
116
|
+
this._dependencies.forEach((dependency) => {
|
|
117
|
+
dependency.unregisterDependent(this);
|
|
118
|
+
});
|
|
119
|
+
this._dependencies.clear();
|
|
120
|
+
}
|
|
121
|
+
registerDependent(dependent) {
|
|
122
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
123
|
+
this._dependents.add(dependent);
|
|
124
|
+
}
|
|
125
|
+
unregisterDependent(dependent) {
|
|
126
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
127
|
+
this._dependents.delete(dependent);
|
|
128
|
+
}
|
|
129
|
+
markDependencyDisposed() {
|
|
130
|
+
}
|
|
131
|
+
notifyDependents() {
|
|
132
|
+
if (this.disposed) return;
|
|
133
|
+
this._dependents.forEach((dependent) => {
|
|
134
|
+
dependent.notify();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
watch(tracker) {
|
|
138
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
139
|
+
tracker.registerDependency(this);
|
|
140
|
+
}
|
|
141
|
+
trigger() {
|
|
142
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
143
|
+
this.notifyDependents();
|
|
144
|
+
}
|
|
145
|
+
dispose() {
|
|
146
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
147
|
+
Array.from(this._dependents).forEach((dependant) => {
|
|
148
|
+
dependant.markDependencyDisposed();
|
|
149
|
+
dependant.unregisterDependency(this);
|
|
150
|
+
this.unregisterDependent(dependant);
|
|
151
|
+
});
|
|
152
|
+
Array.from(this._dependencies).forEach((dependency) => {
|
|
153
|
+
this.unregisterDependency(dependency);
|
|
154
|
+
});
|
|
155
|
+
this._disposed = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class Observable extends Disposable {
|
|
160
|
+
_dependents = /* @__PURE__ */ new Set();
|
|
161
|
+
_status = "resolved";
|
|
162
|
+
get status() {
|
|
163
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
164
|
+
return this._status;
|
|
165
|
+
}
|
|
166
|
+
set status(status) {
|
|
167
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
168
|
+
this._status = status;
|
|
169
|
+
}
|
|
170
|
+
registerDependent(dependent) {
|
|
171
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
172
|
+
this._dependents.add(dependent);
|
|
173
|
+
}
|
|
174
|
+
unregisterDependent(dependent) {
|
|
175
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
176
|
+
this._dependents.delete(dependent);
|
|
177
|
+
}
|
|
178
|
+
notifyDependents() {
|
|
179
|
+
if (this.disposed) return;
|
|
180
|
+
this._dependents.forEach((dependent) => {
|
|
181
|
+
dependent.notify();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
watch(tracker) {
|
|
185
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
186
|
+
tracker.registerDependency(this);
|
|
187
|
+
}
|
|
188
|
+
trigger() {
|
|
189
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
190
|
+
this.notifyDependents();
|
|
191
|
+
}
|
|
192
|
+
dispose() {
|
|
193
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
194
|
+
Array.from(this._dependents).forEach((dependant) => {
|
|
195
|
+
dependant.markDependencyDisposed();
|
|
196
|
+
dependant.unregisterDependency(this);
|
|
197
|
+
this.unregisterDependent(dependant);
|
|
198
|
+
});
|
|
199
|
+
this._disposed = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class Observer extends Disposable {
|
|
204
|
+
_dependencies = /* @__PURE__ */ new Set();
|
|
205
|
+
/** Set when a dependency disposed while this observer was queued; consumed by the next execute(). */
|
|
206
|
+
_dependencyDisposed = false;
|
|
207
|
+
markDependencyDisposed() {
|
|
208
|
+
this._dependencyDisposed = true;
|
|
209
|
+
}
|
|
210
|
+
registerDependency(dependency) {
|
|
211
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
212
|
+
this._dependencies.add(dependency);
|
|
213
|
+
dependency.registerDependent(this);
|
|
214
|
+
}
|
|
215
|
+
unregisterDependency(dependency) {
|
|
216
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
217
|
+
this._dependencies.delete(dependency);
|
|
218
|
+
dependency.unregisterDependent(this);
|
|
219
|
+
}
|
|
220
|
+
clearDependencies() {
|
|
221
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
222
|
+
this._dependencies.forEach((dependency) => {
|
|
223
|
+
dependency.unregisterDependent(this);
|
|
224
|
+
});
|
|
225
|
+
this._dependencies.clear();
|
|
226
|
+
}
|
|
227
|
+
dispose() {
|
|
228
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
229
|
+
Array.from(this._dependencies).forEach((dependency) => {
|
|
230
|
+
this.unregisterDependency(dependency);
|
|
231
|
+
});
|
|
232
|
+
this._disposed = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function onFlushError(handler) {
|
|
237
|
+
ExecutionStack.setFlushErrorHandler(handler);
|
|
238
|
+
}
|
|
239
|
+
|
|
3
240
|
function isDisposable(obj) {
|
|
4
241
|
return obj !== null && obj !== void 0 && typeof obj.dispose === "function";
|
|
5
242
|
}
|
|
@@ -7,7 +244,7 @@ function isDisposable(obj) {
|
|
|
7
244
|
class PendingError extends Error {
|
|
8
245
|
pendingPromise;
|
|
9
246
|
constructor(promise) {
|
|
10
|
-
super("[PicoFlow]
|
|
247
|
+
super("[PicoFlow] Computation is pending");
|
|
11
248
|
this.name = "PendingError";
|
|
12
249
|
this.pendingPromise = promise;
|
|
13
250
|
}
|
|
@@ -36,8 +273,8 @@ class AsyncResolver {
|
|
|
36
273
|
return this._finished;
|
|
37
274
|
}
|
|
38
275
|
compute() {
|
|
39
|
-
if (this._finished) throw new Error("[
|
|
40
|
-
if (this._aborted) throw new Error("[
|
|
276
|
+
if (this._finished) throw new Error("[PicoFlow] AsyncResolver: Can't restart a settled resolver");
|
|
277
|
+
if (this._aborted) throw new Error("[PicoFlow] AsyncResolver: Can't restart an aborted resolver");
|
|
41
278
|
this._iteration++;
|
|
42
279
|
const currentIteration = this._iteration;
|
|
43
280
|
this._compute().then((value) => {
|
|
@@ -51,8 +288,8 @@ class AsyncResolver {
|
|
|
51
288
|
});
|
|
52
289
|
}
|
|
53
290
|
overwrite(promise) {
|
|
54
|
-
if (this._finished) throw new Error("[
|
|
55
|
-
if (this._aborted) throw new Error("[
|
|
291
|
+
if (this._finished) throw new Error("[PicoFlow] AsyncResolver: Can't overwrite a settled resolver");
|
|
292
|
+
if (this._aborted) throw new Error("[PicoFlow] AsyncResolver: Can't overwrite an aborted resolver");
|
|
56
293
|
this._iteration++;
|
|
57
294
|
const currentIteration = this._iteration;
|
|
58
295
|
promise.then((value) => {
|
|
@@ -81,7 +318,7 @@ class AsyncScheduler {
|
|
|
81
318
|
this._onResolve = onResolve;
|
|
82
319
|
this._onReject = onReject;
|
|
83
320
|
this._resolver = new AsyncResolver(compute);
|
|
84
|
-
this.
|
|
321
|
+
this._attachHandlers(this._resolver);
|
|
85
322
|
}
|
|
86
323
|
get settled() {
|
|
87
324
|
if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
|
|
@@ -95,7 +332,7 @@ class AsyncScheduler {
|
|
|
95
332
|
if (this._resolver.finished) {
|
|
96
333
|
this._resolver = new AsyncResolver(this._compute);
|
|
97
334
|
this._settled = Promise.withResolvers();
|
|
98
|
-
this.
|
|
335
|
+
this._attachHandlers(this._resolver);
|
|
99
336
|
}
|
|
100
337
|
this._resolver.overwrite(promise);
|
|
101
338
|
}
|
|
@@ -104,10 +341,19 @@ class AsyncScheduler {
|
|
|
104
341
|
if (this._resolver.finished) {
|
|
105
342
|
this._resolver = new AsyncResolver(this._compute);
|
|
106
343
|
this._settled = Promise.withResolvers();
|
|
107
|
-
this.
|
|
344
|
+
this._attachHandlers(this._resolver);
|
|
108
345
|
}
|
|
109
346
|
this._resolver.compute();
|
|
110
347
|
}
|
|
348
|
+
_attachHandlers(resolver) {
|
|
349
|
+
resolver.computed.then((value) => {
|
|
350
|
+
if (this._disposed) return;
|
|
351
|
+
this._onResolve(value);
|
|
352
|
+
}).catch((error) => {
|
|
353
|
+
if (this._disposed) return;
|
|
354
|
+
this._onReject(error);
|
|
355
|
+
}).finally(() => this._settled.resolve());
|
|
356
|
+
}
|
|
111
357
|
dispose() {
|
|
112
358
|
this._resolver.abort();
|
|
113
359
|
this._disposed = true;
|
|
@@ -116,7 +362,6 @@ class AsyncScheduler {
|
|
|
116
362
|
|
|
117
363
|
class SyncResolver {
|
|
118
364
|
_compute;
|
|
119
|
-
_iteration = 0;
|
|
120
365
|
_aborted = false;
|
|
121
366
|
_finished = false;
|
|
122
367
|
_onValue;
|
|
@@ -133,18 +378,16 @@ class SyncResolver {
|
|
|
133
378
|
return this._finished;
|
|
134
379
|
}
|
|
135
380
|
compute() {
|
|
136
|
-
if (this._finished) throw new Error("[
|
|
137
|
-
if (this._aborted) throw new Error("[
|
|
138
|
-
this._iteration++;
|
|
139
|
-
const currentIteration = this._iteration;
|
|
381
|
+
if (this._finished) throw new Error("[PicoFlow] SyncResolver: Can't restart a settled resolver");
|
|
382
|
+
if (this._aborted) throw new Error("[PicoFlow] SyncResolver: Can't restart an aborted resolver");
|
|
140
383
|
try {
|
|
141
384
|
const value = this._compute();
|
|
142
|
-
if (this.
|
|
385
|
+
if (!this._finished && !this._aborted) {
|
|
143
386
|
this._finished = true;
|
|
144
387
|
this._onValue(value);
|
|
145
388
|
}
|
|
146
389
|
} catch (error) {
|
|
147
|
-
if (this.
|
|
390
|
+
if (!this._finished && !this._aborted) {
|
|
148
391
|
if (!(error instanceof PendingError)) {
|
|
149
392
|
this._finished = true;
|
|
150
393
|
this._onError(error);
|
|
@@ -153,17 +396,12 @@ class SyncResolver {
|
|
|
153
396
|
}
|
|
154
397
|
}
|
|
155
398
|
overwrite(value) {
|
|
156
|
-
if (this._finished) throw new Error("[
|
|
157
|
-
if (this._aborted) throw new Error("[
|
|
158
|
-
this.
|
|
159
|
-
|
|
160
|
-
if (this._iteration === currentIteration && !this._aborted) {
|
|
161
|
-
this._finished = true;
|
|
162
|
-
this._onValue(value);
|
|
163
|
-
}
|
|
399
|
+
if (this._finished) throw new Error("[PicoFlow] SyncResolver: Can't overwrite a settled resolver");
|
|
400
|
+
if (this._aborted) throw new Error("[PicoFlow] SyncResolver: Can't overwrite an aborted resolver");
|
|
401
|
+
this._finished = true;
|
|
402
|
+
this._onValue(value);
|
|
164
403
|
}
|
|
165
404
|
abort() {
|
|
166
|
-
this._iteration++;
|
|
167
405
|
this._aborted = true;
|
|
168
406
|
}
|
|
169
407
|
}
|
|
@@ -212,230 +450,91 @@ class SyncScheduler {
|
|
|
212
450
|
}
|
|
213
451
|
}
|
|
214
452
|
|
|
215
|
-
class Disposable {
|
|
216
|
-
_disposed = false;
|
|
217
|
-
get disposed() {
|
|
218
|
-
return this._disposed;
|
|
219
|
-
}
|
|
220
|
-
dispose() {
|
|
221
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
222
|
-
this._disposed = true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
class ExecutionStack {
|
|
227
|
-
static _pendingQueue = [];
|
|
228
|
-
static _effectQueue = [];
|
|
229
|
-
static _executionScheduled;
|
|
230
|
-
static pushPending(node) {
|
|
231
|
-
ExecutionStack._scheduleExecution();
|
|
232
|
-
ExecutionStack._pendingQueue.push(node);
|
|
233
|
-
}
|
|
234
|
-
static pushEffect(effect) {
|
|
235
|
-
ExecutionStack._scheduleExecution();
|
|
236
|
-
ExecutionStack._effectQueue.push(effect);
|
|
237
|
-
}
|
|
238
|
-
static _scheduleExecution() {
|
|
239
|
-
if (ExecutionStack._executionScheduled) return;
|
|
240
|
-
ExecutionStack._executionScheduled = new Promise((resolve) => {
|
|
241
|
-
setTimeout(() => {
|
|
242
|
-
ExecutionStack._executionScheduled = void 0;
|
|
243
|
-
ExecutionStack._execute();
|
|
244
|
-
resolve();
|
|
245
|
-
}, 0);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
static _execute() {
|
|
249
|
-
ExecutionStack._pendingQueue.forEach((node) => {
|
|
250
|
-
node.execute();
|
|
251
|
-
});
|
|
252
|
-
ExecutionStack._pendingQueue.length = 0;
|
|
253
|
-
ExecutionStack._effectQueue.forEach((effect) => {
|
|
254
|
-
effect.execute();
|
|
255
|
-
});
|
|
256
|
-
ExecutionStack._effectQueue.length = 0;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
class Node extends Disposable {
|
|
261
|
-
_dependencies = /* @__PURE__ */ new Set();
|
|
262
|
-
_dependents = /* @__PURE__ */ new Set();
|
|
263
|
-
_status = "resolved";
|
|
264
|
-
get status() {
|
|
265
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
266
|
-
return this._status;
|
|
267
|
-
}
|
|
268
|
-
set status(status) {
|
|
269
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
270
|
-
this._status = status;
|
|
271
|
-
}
|
|
272
|
-
registerDependency(dependency) {
|
|
273
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
274
|
-
this._dependencies.add(dependency);
|
|
275
|
-
dependency.registerDependent(this);
|
|
276
|
-
}
|
|
277
|
-
unregisterDependency(dependency) {
|
|
278
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
279
|
-
this._dependencies.delete(dependency);
|
|
280
|
-
dependency.unregisterDependent(this);
|
|
281
|
-
}
|
|
282
|
-
clearDependencies() {
|
|
283
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
284
|
-
this._dependencies.forEach((dependency) => {
|
|
285
|
-
dependency.unregisterDependent(this);
|
|
286
|
-
});
|
|
287
|
-
this._dependencies.clear();
|
|
288
|
-
}
|
|
289
|
-
registerDependent(dependent) {
|
|
290
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
291
|
-
this._dependents.add(dependent);
|
|
292
|
-
}
|
|
293
|
-
unregisterDependent(dependent) {
|
|
294
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
295
|
-
this._dependents.delete(dependent);
|
|
296
|
-
}
|
|
297
|
-
notifyDependents() {
|
|
298
|
-
if (this.disposed) return;
|
|
299
|
-
this._dependents.forEach((dependent) => {
|
|
300
|
-
dependent.notify();
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
watch(tracker) {
|
|
304
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
305
|
-
tracker.registerDependency(this);
|
|
306
|
-
}
|
|
307
|
-
trigger() {
|
|
308
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
309
|
-
this.notifyDependents();
|
|
310
|
-
}
|
|
311
|
-
notify() {
|
|
312
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
313
|
-
if (this.status === "dirty") return;
|
|
314
|
-
this.status = "dirty";
|
|
315
|
-
this.notifyDependents();
|
|
316
|
-
}
|
|
317
|
-
dispose() {
|
|
318
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
319
|
-
Array.from(this._dependents).forEach((dependant) => {
|
|
320
|
-
dependant.unregisterDependency(this);
|
|
321
|
-
this.unregisterDependent(dependant);
|
|
322
|
-
});
|
|
323
|
-
Array.from(this._dependencies).forEach((dependency) => {
|
|
324
|
-
this.unregisterDependency(dependency);
|
|
325
|
-
});
|
|
326
|
-
this._disposed = true;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
class Observable extends Disposable {
|
|
331
|
-
_dependents = /* @__PURE__ */ new Set();
|
|
332
|
-
_status = "resolved";
|
|
333
|
-
get status() {
|
|
334
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
335
|
-
return this._status;
|
|
336
|
-
}
|
|
337
|
-
set status(status) {
|
|
338
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
339
|
-
this._status = status;
|
|
340
|
-
}
|
|
341
|
-
registerDependent(dependent) {
|
|
342
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
343
|
-
this._dependents.add(dependent);
|
|
344
|
-
}
|
|
345
|
-
unregisterDependent(dependent) {
|
|
346
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
347
|
-
this._dependents.delete(dependent);
|
|
348
|
-
}
|
|
349
|
-
notifyDependents() {
|
|
350
|
-
if (this.disposed) return;
|
|
351
|
-
this._dependents.forEach((dependent) => {
|
|
352
|
-
dependent.notify();
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
watch(tracker) {
|
|
356
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
357
|
-
tracker.registerDependency(this);
|
|
358
|
-
}
|
|
359
|
-
trigger() {
|
|
360
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
361
|
-
this.notifyDependents();
|
|
362
|
-
}
|
|
363
|
-
dispose() {
|
|
364
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
365
|
-
Array.from(this._dependents).forEach((dependant) => {
|
|
366
|
-
dependant.unregisterDependency(this);
|
|
367
|
-
this.unregisterDependent(dependant);
|
|
368
|
-
});
|
|
369
|
-
this._disposed = true;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
class Observer extends Disposable {
|
|
374
|
-
_dependencies = /* @__PURE__ */ new Set();
|
|
375
|
-
registerDependency(dependency) {
|
|
376
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
377
|
-
this._dependencies.add(dependency);
|
|
378
|
-
dependency.registerDependent(this);
|
|
379
|
-
}
|
|
380
|
-
unregisterDependency(dependency) {
|
|
381
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
382
|
-
this._dependencies.delete(dependency);
|
|
383
|
-
dependency.unregisterDependent(this);
|
|
384
|
-
}
|
|
385
|
-
clearDependencies() {
|
|
386
|
-
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
387
|
-
this._dependencies.forEach((dependency) => {
|
|
388
|
-
dependency.unregisterDependent(this);
|
|
389
|
-
});
|
|
390
|
-
this._dependencies.clear();
|
|
391
|
-
}
|
|
392
|
-
dispose() {
|
|
393
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
394
|
-
Array.from(this._dependencies).forEach((dependency) => {
|
|
395
|
-
this.unregisterDependency(dependency);
|
|
396
|
-
});
|
|
397
|
-
this._disposed = true;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
453
|
class EffectNode extends Observer {
|
|
402
454
|
_data;
|
|
403
455
|
_onData;
|
|
404
456
|
_onError;
|
|
405
457
|
_onPending;
|
|
458
|
+
/** True while this effect is already waiting in the effect queue for the next flush. */
|
|
459
|
+
_queued = false;
|
|
460
|
+
/** Guards the single coalesced bump of _notifyEpoch while _queued during one sync turn. */
|
|
461
|
+
_coalescedWhileQueued = false;
|
|
462
|
+
/** Monotonic count of notifications that must eventually be executed. */
|
|
463
|
+
_notifyEpoch = 0;
|
|
464
|
+
/** How many notification epochs have been executed so far. */
|
|
465
|
+
_executedEpoch = 0;
|
|
406
466
|
constructor(data, onData, onError, onPending) {
|
|
407
467
|
super();
|
|
408
468
|
this._data = data;
|
|
409
469
|
this._onData = onData;
|
|
410
470
|
this._onError = onError;
|
|
411
471
|
this._onPending = onPending;
|
|
472
|
+
this._notifyEpoch = 1;
|
|
412
473
|
this.execute();
|
|
413
474
|
}
|
|
414
475
|
notify() {
|
|
415
476
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
477
|
+
if (this._queued) {
|
|
478
|
+
if (!this._coalescedWhileQueued) {
|
|
479
|
+
this._coalescedWhileQueued = true;
|
|
480
|
+
if (ExecutionStack.pastSyncTurn) {
|
|
481
|
+
this._notifyEpoch++;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
this._notifyEpoch++;
|
|
487
|
+
this._queued = true;
|
|
488
|
+
ExecutionStack.scheduleCoalesceReset(() => {
|
|
489
|
+
this._coalescedWhileQueued = false;
|
|
490
|
+
});
|
|
416
491
|
ExecutionStack.pushEffect(this);
|
|
417
492
|
}
|
|
418
493
|
execute() {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
this.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
494
|
+
this._queued = false;
|
|
495
|
+
if (this._disposed) {
|
|
496
|
+
this._executedEpoch = this._notifyEpoch;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (this._dependencyDisposed) {
|
|
500
|
+
this._dependencyDisposed = false;
|
|
501
|
+
this._executedEpoch = this._notifyEpoch;
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const targetEpoch = this._notifyEpoch;
|
|
505
|
+
while (this._executedEpoch < targetEpoch) {
|
|
506
|
+
this._executedEpoch++;
|
|
507
|
+
if (this._disposed) {
|
|
508
|
+
this._executedEpoch = this._notifyEpoch;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (this._dependencyDisposed) {
|
|
512
|
+
this._dependencyDisposed = false;
|
|
513
|
+
this._executedEpoch = this._notifyEpoch;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
this.clearDependencies();
|
|
518
|
+
const data = this._data(this);
|
|
519
|
+
this._onData(data);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error instanceof PendingError) {
|
|
522
|
+
this._onPending?.();
|
|
430
523
|
} else {
|
|
431
|
-
|
|
524
|
+
if (this._onError) {
|
|
525
|
+
this._onError(error instanceof Error ? error : new Error(String(error)));
|
|
526
|
+
} else {
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
432
529
|
}
|
|
433
530
|
}
|
|
434
531
|
}
|
|
435
532
|
}
|
|
436
533
|
}
|
|
437
534
|
|
|
535
|
+
const PICOFLOW_VALUE_NODE = /* @__PURE__ */ Symbol.for("picoflow.node");
|
|
438
536
|
class ValueNode extends Node {
|
|
537
|
+
[PICOFLOW_VALUE_NODE] = true;
|
|
439
538
|
_value;
|
|
440
539
|
_error;
|
|
441
540
|
watch(tracker) {
|
|
@@ -570,12 +669,14 @@ class ValueAsyncNode extends ValueNode {
|
|
|
570
669
|
);
|
|
571
670
|
}
|
|
572
671
|
_onResolve(value) {
|
|
672
|
+
if (this.disposed) return;
|
|
573
673
|
this.status = "resolved";
|
|
574
674
|
this._value = value;
|
|
575
675
|
this._error = void 0;
|
|
576
676
|
this.notifyDependents();
|
|
577
677
|
}
|
|
578
678
|
_onReject(error) {
|
|
679
|
+
if (this.disposed) return;
|
|
579
680
|
this.status = "error";
|
|
580
681
|
this._error = error;
|
|
581
682
|
this.notifyDependents();
|
|
@@ -724,11 +825,66 @@ class ValueSyncNode extends ValueNode {
|
|
|
724
825
|
}
|
|
725
826
|
}
|
|
726
827
|
|
|
828
|
+
class ActionNode extends ValueSyncNode {
|
|
829
|
+
_slots = /* @__PURE__ */ new Set();
|
|
830
|
+
_enqueueForSubscribers(action) {
|
|
831
|
+
for (const slot of this._slots) {
|
|
832
|
+
slot.queue.push(action);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
set(valueOrUpdater) {
|
|
836
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
837
|
+
if (typeof valueOrUpdater === "function") {
|
|
838
|
+
const updater = valueOrUpdater;
|
|
839
|
+
const nextValue2 = updater(this._value);
|
|
840
|
+
if (this.status === "resolved" && nextValue2 === this._value) return;
|
|
841
|
+
this._enqueueForSubscribers(nextValue2);
|
|
842
|
+
super.set(updater);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const nextValue = valueOrUpdater;
|
|
846
|
+
if (this.status === "resolved" && nextValue === this._value) return;
|
|
847
|
+
this._enqueueForSubscribers(nextValue);
|
|
848
|
+
super.set(nextValue);
|
|
849
|
+
}
|
|
850
|
+
subscribe(onValue, onError, onPending) {
|
|
851
|
+
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
852
|
+
const slot = { queue: [] };
|
|
853
|
+
this._slots.add(slot);
|
|
854
|
+
const effect = new EffectNode(
|
|
855
|
+
(tracker) => {
|
|
856
|
+
this.watch(tracker);
|
|
857
|
+
if (slot.queue.length === 0) {
|
|
858
|
+
return [this.get(tracker)];
|
|
859
|
+
}
|
|
860
|
+
return slot.queue.splice(0);
|
|
861
|
+
},
|
|
862
|
+
(actions) => {
|
|
863
|
+
for (const action of actions) {
|
|
864
|
+
onValue(action);
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
onError,
|
|
868
|
+
onPending
|
|
869
|
+
);
|
|
870
|
+
const originalDispose = effect.dispose.bind(effect);
|
|
871
|
+
effect.dispose = () => {
|
|
872
|
+
this._slots.delete(slot);
|
|
873
|
+
originalDispose();
|
|
874
|
+
};
|
|
875
|
+
return effect;
|
|
876
|
+
}
|
|
877
|
+
dispose() {
|
|
878
|
+
this._slots.clear();
|
|
879
|
+
super.dispose();
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
727
883
|
class ArrayNode extends ValueSyncNode {
|
|
728
884
|
$lastAction;
|
|
729
885
|
constructor(value = []) {
|
|
730
886
|
super(value);
|
|
731
|
-
this.$lastAction = new
|
|
887
|
+
this.$lastAction = new ActionNode({
|
|
732
888
|
type: "set",
|
|
733
889
|
setItems: value,
|
|
734
890
|
clearedItems: []
|
|
@@ -759,7 +915,7 @@ class ArrayNode extends ValueSyncNode {
|
|
|
759
915
|
}
|
|
760
916
|
update(index, item) {
|
|
761
917
|
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
762
|
-
if (index < 0 || index >= this._value.length) {
|
|
918
|
+
if (index < 0 || index >= this._value.length || !Number.isInteger(index)) {
|
|
763
919
|
throw new Error("[PicoFlow] Index out of bounds");
|
|
764
920
|
}
|
|
765
921
|
const previousValue = this._value[index];
|
|
@@ -781,6 +937,7 @@ class ArrayNode extends ValueSyncNode {
|
|
|
781
937
|
}
|
|
782
938
|
pop() {
|
|
783
939
|
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
940
|
+
if (this._value.length === 0) return void 0;
|
|
784
941
|
const item = this._value.pop();
|
|
785
942
|
this.notifyDependents();
|
|
786
943
|
this.$lastAction.set({ type: "pop", removedItem: item });
|
|
@@ -794,6 +951,7 @@ class ArrayNode extends ValueSyncNode {
|
|
|
794
951
|
}
|
|
795
952
|
shift() {
|
|
796
953
|
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
954
|
+
if (this._value.length === 0) return void 0;
|
|
797
955
|
const item = this._value.shift();
|
|
798
956
|
this.notifyDependents();
|
|
799
957
|
this.$lastAction.set({ type: "shift", removedItem: item });
|
|
@@ -822,9 +980,9 @@ class ArrayNode extends ValueSyncNode {
|
|
|
822
980
|
}
|
|
823
981
|
dispose() {
|
|
824
982
|
super.dispose();
|
|
825
|
-
this.
|
|
826
|
-
|
|
827
|
-
}
|
|
983
|
+
if (!this.$lastAction.disposed) {
|
|
984
|
+
this.$lastAction.dispose();
|
|
985
|
+
}
|
|
828
986
|
this._value = [];
|
|
829
987
|
}
|
|
830
988
|
}
|
|
@@ -837,7 +995,7 @@ class MapNode extends ValueSyncNode {
|
|
|
837
995
|
$lastAction;
|
|
838
996
|
constructor(value = /* @__PURE__ */ new Map()) {
|
|
839
997
|
super(value);
|
|
840
|
-
this.$lastAction = new
|
|
998
|
+
this.$lastAction = new ActionNode({
|
|
841
999
|
type: "set",
|
|
842
1000
|
setMap: value,
|
|
843
1001
|
clearedMap: /* @__PURE__ */ new Map()
|
|
@@ -845,8 +1003,7 @@ class MapNode extends ValueSyncNode {
|
|
|
845
1003
|
}
|
|
846
1004
|
add(key, value) {
|
|
847
1005
|
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
848
|
-
|
|
849
|
-
if (previousValue) {
|
|
1006
|
+
if (this._value.has(key)) {
|
|
850
1007
|
throw new Error("[PicoFlow] Key already exists");
|
|
851
1008
|
}
|
|
852
1009
|
this._value.set(key, value);
|
|
@@ -855,8 +1012,8 @@ class MapNode extends ValueSyncNode {
|
|
|
855
1012
|
}
|
|
856
1013
|
update(key, value) {
|
|
857
1014
|
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
1015
|
+
if (!this._value.has(key)) throw new Error("[PicoFlow] Key does not exist");
|
|
858
1016
|
const previousValue = this._value.get(key);
|
|
859
|
-
if (!previousValue) throw new Error("[PicoFlow] Key does not exist");
|
|
860
1017
|
this._value.set(key, value);
|
|
861
1018
|
this.notifyDependents();
|
|
862
1019
|
this.$lastAction.set({
|
|
@@ -869,8 +1026,8 @@ class MapNode extends ValueSyncNode {
|
|
|
869
1026
|
}
|
|
870
1027
|
delete(key) {
|
|
871
1028
|
if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
1029
|
+
if (!this._value.has(key)) throw new Error("[PicoFlow] Key does not exist");
|
|
872
1030
|
const value = this._value.get(key);
|
|
873
|
-
if (value === void 0) throw new Error("[PicoFlow] Key does not exist");
|
|
874
1031
|
this._value.delete(key);
|
|
875
1032
|
this.notifyDependents();
|
|
876
1033
|
this.$lastAction.set({ type: "delete", key, removedValue: value });
|
|
@@ -897,9 +1054,9 @@ class MapNode extends ValueSyncNode {
|
|
|
897
1054
|
}
|
|
898
1055
|
dispose() {
|
|
899
1056
|
super.dispose();
|
|
900
|
-
this.
|
|
901
|
-
|
|
902
|
-
}
|
|
1057
|
+
if (!this.$lastAction.disposed) {
|
|
1058
|
+
this.$lastAction.dispose();
|
|
1059
|
+
}
|
|
903
1060
|
this._value.clear();
|
|
904
1061
|
}
|
|
905
1062
|
}
|
|
@@ -943,7 +1100,18 @@ function writableDerivation(compute) {
|
|
|
943
1100
|
return new ValueSyncNode(compute);
|
|
944
1101
|
}
|
|
945
1102
|
|
|
946
|
-
function
|
|
1103
|
+
function describeFlowInput(value) {
|
|
1104
|
+
if (value === null) return "null";
|
|
1105
|
+
if (typeof value === "object") {
|
|
1106
|
+
const name = value.constructor?.name;
|
|
1107
|
+
return name ? `object (${name})` : "object";
|
|
1108
|
+
}
|
|
1109
|
+
return typeof value;
|
|
1110
|
+
}
|
|
1111
|
+
function isValueNode(flow) {
|
|
1112
|
+
return typeof flow === "object" && flow !== null && PICOFLOW_VALUE_NODE in flow;
|
|
1113
|
+
}
|
|
1114
|
+
function fromNode(node, options) {
|
|
947
1115
|
const [resource, { refetch }] = createResource(() => node.pick());
|
|
948
1116
|
let fx;
|
|
949
1117
|
onMount(() => {
|
|
@@ -968,23 +1136,28 @@ function fromNode(node) {
|
|
|
968
1136
|
resource.error ?? resource();
|
|
969
1137
|
resetErrorBoundaries();
|
|
970
1138
|
});
|
|
971
|
-
onCleanup(() =>
|
|
1139
|
+
onCleanup(() => {
|
|
1140
|
+
fx.dispose();
|
|
1141
|
+
if (options?.disposeNode && !node.disposed) {
|
|
1142
|
+
node.dispose();
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
972
1145
|
return resource;
|
|
973
1146
|
}
|
|
974
1147
|
function fromGetter(getter) {
|
|
975
1148
|
const derivation = new ValueSyncNode((t) => {
|
|
976
1149
|
return getter(t);
|
|
977
1150
|
});
|
|
978
|
-
return fromNode(derivation);
|
|
1151
|
+
return fromNode(derivation, { disposeNode: true });
|
|
979
1152
|
}
|
|
980
1153
|
function from(flow) {
|
|
981
|
-
if (flow
|
|
1154
|
+
if (isValueNode(flow)) {
|
|
982
1155
|
return fromNode(flow);
|
|
983
1156
|
}
|
|
984
1157
|
if (typeof flow === "function") {
|
|
985
1158
|
return fromGetter(flow);
|
|
986
1159
|
}
|
|
987
|
-
throw new Error(
|
|
1160
|
+
throw new Error(`[PicoFlow] from(): expected a FlowValue or getter function, received ${describeFlowInput(flow)}`);
|
|
988
1161
|
}
|
|
989
1162
|
|
|
990
|
-
export { array, constant, constantAsync, derivation, derivationAsync, from, isDisposable, map, signal, state, stateAsync, subscribe, writableDerivation, writableDerivationAsync };
|
|
1163
|
+
export { array, constant, constantAsync, derivation, derivationAsync, from, isDisposable, map, onFlushError, signal, state, stateAsync, subscribe, writableDerivation, writableDerivationAsync };
|