@ersbeth/picoflow 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +22 -0
  2. package/SKILL.md +11 -1
  3. package/dist/picoflow.js +449 -299
  4. package/dist/types/api/base/flowConfig.d.ts +17 -0
  5. package/dist/types/api/base/flowDisposable.d.ts +0 -1
  6. package/dist/types/api/base/flowErrors.d.ts +1 -0
  7. package/dist/types/api/base/flowObservable.d.ts +0 -1
  8. package/dist/types/api/base/flowPrimitiveOptions.d.ts +14 -0
  9. package/dist/types/api/base/flowSubscribable.d.ts +0 -1
  10. package/dist/types/api/base/flowTracker.d.ts +0 -1
  11. package/dist/types/api/base/index.d.ts +3 -1
  12. package/dist/types/api/index.d.ts +0 -1
  13. package/dist/types/api/nodes/async/flowConstantAsync.d.ts +4 -3
  14. package/dist/types/api/nodes/async/flowDerivationAsync.d.ts +3 -3
  15. package/dist/types/api/nodes/async/flowStateAsync.d.ts +4 -3
  16. package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts +3 -2
  17. package/dist/types/api/nodes/async/index.d.ts +0 -1
  18. package/dist/types/api/nodes/collections/flowArray.d.ts +3 -2
  19. package/dist/types/api/nodes/collections/flowMap.d.ts +3 -2
  20. package/dist/types/api/nodes/collections/index.d.ts +0 -1
  21. package/dist/types/api/nodes/flowEffect.d.ts +0 -1
  22. package/dist/types/api/nodes/flowSignal.d.ts +3 -2
  23. package/dist/types/api/nodes/flowValue.d.ts +0 -1
  24. package/dist/types/api/nodes/index.d.ts +0 -1
  25. package/dist/types/api/nodes/sync/flowConstant.d.ts +3 -2
  26. package/dist/types/api/nodes/sync/flowDerivation.d.ts +3 -2
  27. package/dist/types/api/nodes/sync/flowState.d.ts +4 -3
  28. package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts +3 -2
  29. package/dist/types/api/nodes/sync/index.d.ts +0 -1
  30. package/dist/types/api/nodes/utils.d.ts +0 -1
  31. package/dist/types/base/dependenciesRegistry.d.ts +1 -0
  32. package/dist/types/base/dependentsRegistry.d.ts +1 -0
  33. package/dist/types/base/disposable.d.ts +8 -4
  34. package/dist/types/base/errors.d.ts +31 -0
  35. package/dist/types/base/executionStack.d.ts +5 -9
  36. package/dist/types/base/index.d.ts +3 -1
  37. package/dist/types/base/node.d.ts +4 -4
  38. package/dist/types/base/observable.d.ts +1 -2
  39. package/dist/types/base/observer.d.ts +10 -2
  40. package/dist/types/converters/index.d.ts +0 -1
  41. package/dist/types/converters/solid.d.ts +0 -1
  42. package/dist/types/index.d.ts +0 -1
  43. package/dist/types/nodes/actionNode.d.ts +1 -23
  44. package/dist/types/nodes/arrayNode.d.ts +1 -26
  45. package/dist/types/nodes/effectNode.d.ts +1 -32
  46. package/dist/types/nodes/index.d.ts +0 -1
  47. package/dist/types/nodes/mapNode.d.ts +1 -20
  48. package/dist/types/nodes/signalNode.d.ts +1 -10
  49. package/dist/types/nodes/valueAsyncNode.d.ts +1 -23
  50. package/dist/types/nodes/valueNode.d.ts +1 -20
  51. package/dist/types/nodes/valueSyncNode.d.ts +1 -23
  52. package/dist/types/schedulers/asyncResolver.d.ts +1 -19
  53. package/dist/types/schedulers/asyncScheduler.d.ts +1 -21
  54. package/dist/types/schedulers/index.d.ts +0 -1
  55. package/dist/types/schedulers/pendingError.d.ts +1 -9
  56. package/dist/types/schedulers/scheduler.d.ts +1 -10
  57. package/dist/types/schedulers/syncResolver.d.ts +1 -18
  58. package/dist/types/schedulers/syncScheduler.d.ts +1 -19
  59. package/package.json +14 -11
  60. package/dist/types/api/base/flowDisposable.d.ts.map +0 -1
  61. package/dist/types/api/base/flowObservable.d.ts.map +0 -1
  62. package/dist/types/api/base/flowSubscribable.d.ts.map +0 -1
  63. package/dist/types/api/base/flowTracker.d.ts.map +0 -1
  64. package/dist/types/api/base/index.d.ts.map +0 -1
  65. package/dist/types/api/index.d.ts.map +0 -1
  66. package/dist/types/api/nodes/async/flowConstantAsync.d.ts.map +0 -1
  67. package/dist/types/api/nodes/async/flowDerivationAsync.d.ts.map +0 -1
  68. package/dist/types/api/nodes/async/flowStateAsync.d.ts.map +0 -1
  69. package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts.map +0 -1
  70. package/dist/types/api/nodes/async/index.d.ts.map +0 -1
  71. package/dist/types/api/nodes/collections/flowArray.d.ts.map +0 -1
  72. package/dist/types/api/nodes/collections/flowMap.d.ts.map +0 -1
  73. package/dist/types/api/nodes/collections/index.d.ts.map +0 -1
  74. package/dist/types/api/nodes/flowEffect.d.ts.map +0 -1
  75. package/dist/types/api/nodes/flowSignal.d.ts.map +0 -1
  76. package/dist/types/api/nodes/flowValue.d.ts.map +0 -1
  77. package/dist/types/api/nodes/index.d.ts.map +0 -1
  78. package/dist/types/api/nodes/sync/flowConstant.d.ts.map +0 -1
  79. package/dist/types/api/nodes/sync/flowDerivation.d.ts.map +0 -1
  80. package/dist/types/api/nodes/sync/flowState.d.ts.map +0 -1
  81. package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts.map +0 -1
  82. package/dist/types/api/nodes/sync/index.d.ts.map +0 -1
  83. package/dist/types/api/nodes/utils.d.ts.map +0 -1
  84. package/dist/types/base/disposable.d.ts.map +0 -1
  85. package/dist/types/base/executionStack.d.ts.map +0 -1
  86. package/dist/types/base/index.d.ts.map +0 -1
  87. package/dist/types/base/node.d.ts.map +0 -1
  88. package/dist/types/base/observable.d.ts.map +0 -1
  89. package/dist/types/base/observer.d.ts.map +0 -1
  90. package/dist/types/converters/index.d.ts.map +0 -1
  91. package/dist/types/converters/solid.d.ts.map +0 -1
  92. package/dist/types/index.d.ts.map +0 -1
  93. package/dist/types/nodes/actionNode.d.ts.map +0 -1
  94. package/dist/types/nodes/arrayNode.d.ts.map +0 -1
  95. package/dist/types/nodes/effectNode.d.ts.map +0 -1
  96. package/dist/types/nodes/index.d.ts.map +0 -1
  97. package/dist/types/nodes/mapNode.d.ts.map +0 -1
  98. package/dist/types/nodes/signalNode.d.ts.map +0 -1
  99. package/dist/types/nodes/valueAsyncNode.d.ts.map +0 -1
  100. package/dist/types/nodes/valueNode.d.ts.map +0 -1
  101. package/dist/types/nodes/valueSyncNode.d.ts.map +0 -1
  102. package/dist/types/schedulers/asyncResolver.d.ts.map +0 -1
  103. package/dist/types/schedulers/asyncScheduler.d.ts.map +0 -1
  104. package/dist/types/schedulers/index.d.ts.map +0 -1
  105. package/dist/types/schedulers/pendingError.d.ts.map +0 -1
  106. package/dist/types/schedulers/scheduler.d.ts.map +0 -1
  107. package/dist/types/schedulers/syncResolver.d.ts.map +0 -1
  108. package/dist/types/schedulers/syncScheduler.d.ts.map +0 -1
package/dist/picoflow.js CHANGED
@@ -1,5 +1,310 @@
1
1
  import { createResource, onMount, createEffect, resetErrorBoundaries, onCleanup } from 'solid-js';
2
2
 
3
+ class DependenciesRegistry {
4
+ _dependencies = /* @__PURE__ */ new Set();
5
+ register(dependency, dependent) {
6
+ this._dependencies.add(dependency);
7
+ dependency.registerDependent(dependent);
8
+ }
9
+ unregister(dependency, dependent) {
10
+ this._dependencies.delete(dependency);
11
+ dependency.unregisterDependent(dependent);
12
+ }
13
+ clear(dependent) {
14
+ this._dependencies.forEach((dependency) => {
15
+ dependency.unregisterDependent(dependent);
16
+ });
17
+ this._dependencies.clear();
18
+ }
19
+ forEach(fn) {
20
+ Array.from(this._dependencies).forEach(fn);
21
+ }
22
+ }
23
+
24
+ class DependentsRegistry {
25
+ _dependents = /* @__PURE__ */ new Set();
26
+ register(dependent) {
27
+ this._dependents.add(dependent);
28
+ }
29
+ unregister(dependent) {
30
+ this._dependents.delete(dependent);
31
+ }
32
+ notifyAll() {
33
+ this._dependents.forEach((dependent) => {
34
+ dependent.notify();
35
+ });
36
+ }
37
+ forEach(fn) {
38
+ Array.from(this._dependents).forEach(fn);
39
+ }
40
+ }
41
+
42
+ class PicoFlowError extends Error {
43
+ constructor(message) {
44
+ super(message);
45
+ this.name = "PicoFlowError";
46
+ }
47
+ }
48
+ class PicoFlowDisposedError extends PicoFlowError {
49
+ constructor(message) {
50
+ super(message);
51
+ this.name = "PicoFlowDisposedError";
52
+ }
53
+ }
54
+ class PicoFlowCycleError extends PicoFlowError {
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = "PicoFlowCycleError";
58
+ }
59
+ }
60
+ class PicoFlowInternalError extends PicoFlowError {
61
+ constructor(message) {
62
+ super(message);
63
+ this.name = "PicoFlowInternalError";
64
+ }
65
+ }
66
+
67
+ class Disposable {
68
+ _disposed = false;
69
+ _name;
70
+ constructor(name) {
71
+ this._name = name;
72
+ }
73
+ get disposed() {
74
+ return this._disposed;
75
+ }
76
+ /** Builds the disposed-primitive error, naming the primitive when a `name` was given at creation. */
77
+ _disposedError() {
78
+ return new PicoFlowDisposedError(
79
+ this._name ? `[PicoFlow] Primitive "${this._name}" is disposed` : "[PicoFlow] Primitive is disposed"
80
+ );
81
+ }
82
+ }
83
+
84
+ class ExecutionStack {
85
+ static _MAX_FLUSH_STEPS = 1e4;
86
+ static _pendingQueue = [];
87
+ static _effectQueue = [];
88
+ static _executionScheduled;
89
+ static _coalesceResetScheduled = false;
90
+ /** Per-effect resets accumulated for the microtask scheduled below, so notifying more than one
91
+ * effect in the same sync turn doesn't discard every reset after the first (A2-SEM-5). */
92
+ static _coalesceResets = /* @__PURE__ */ new Set();
93
+ /** Routes any error escaping the flush loop; defaults to logging rather than crashing (A2-SEM-2, A2-SEC-1). */
94
+ static _onFlushError = (error) => console.error(error);
95
+ /**
96
+ * True once the current synchronous call stack has finished and microtasks have started.
97
+ * EffectNode uses this to treat microtask notifications (e.g. async resolve) differently
98
+ * from coalesced sync-batch notifications.
99
+ */
100
+ static _pastSyncTurn = false;
101
+ /** @internal Used by EffectNode to distinguish sync-batch from microtask notifications. */
102
+ static get pastSyncTurn() {
103
+ return ExecutionStack._pastSyncTurn;
104
+ }
105
+ /**
106
+ * @internal Runs after the current sync turn ends (queueMicrotask).
107
+ * Marks pastSyncTurn so the next coalesced effect notify can bump its epoch,
108
+ * and resets per-effect coalesce flags via onReset.
109
+ */
110
+ static scheduleCoalesceReset(onReset) {
111
+ ExecutionStack._coalesceResets.add(onReset);
112
+ if (ExecutionStack._coalesceResetScheduled) return;
113
+ ExecutionStack._coalesceResetScheduled = true;
114
+ queueMicrotask(() => {
115
+ ExecutionStack._pastSyncTurn = true;
116
+ ExecutionStack._coalesceResetScheduled = false;
117
+ const resets = ExecutionStack._coalesceResets;
118
+ ExecutionStack._coalesceResets = /* @__PURE__ */ new Set();
119
+ resets.forEach((reset) => {
120
+ reset();
121
+ });
122
+ });
123
+ }
124
+ /** @internal Wired to the public `onFlushError()` API in `src/api/base/flowConfig.ts`. */
125
+ static setFlushErrorHandler(handler) {
126
+ ExecutionStack._onFlushError = handler;
127
+ }
128
+ static pushPending(node) {
129
+ ExecutionStack._beginSyncTurn();
130
+ ExecutionStack._scheduleExecution();
131
+ ExecutionStack._pendingQueue.push(node);
132
+ }
133
+ static pushEffect(effect) {
134
+ ExecutionStack._beginSyncTurn();
135
+ ExecutionStack._scheduleExecution();
136
+ ExecutionStack._effectQueue.push(effect);
137
+ }
138
+ static _beginSyncTurn() {
139
+ ExecutionStack._pastSyncTurn = false;
140
+ }
141
+ static _scheduleExecution() {
142
+ if (ExecutionStack._executionScheduled) return;
143
+ ExecutionStack._executionScheduled = new Promise((resolve) => {
144
+ setTimeout(() => {
145
+ ExecutionStack._executionScheduled = void 0;
146
+ ExecutionStack._execute();
147
+ resolve();
148
+ }, 0);
149
+ });
150
+ }
151
+ static _drain(queue) {
152
+ for (let i = 0; i < queue.length; i++) {
153
+ if (i >= ExecutionStack._MAX_FLUSH_STEPS) {
154
+ ExecutionStack._pendingQueue.length = 0;
155
+ ExecutionStack._effectQueue.length = 0;
156
+ ExecutionStack._onFlushError(
157
+ new PicoFlowCycleError("[PicoFlow] Reactive update cycle exceeded maximum depth")
158
+ );
159
+ return;
160
+ }
161
+ const node = queue[i];
162
+ try {
163
+ node?.execute();
164
+ } catch (error) {
165
+ ExecutionStack._onFlushError(error);
166
+ }
167
+ }
168
+ queue.length = 0;
169
+ }
170
+ static _execute() {
171
+ ExecutionStack._drain(ExecutionStack._pendingQueue);
172
+ ExecutionStack._drain(ExecutionStack._effectQueue);
173
+ }
174
+ }
175
+
176
+ class Node extends Disposable {
177
+ _dependenciesRegistry = new DependenciesRegistry();
178
+ _dependentsRegistry = new DependentsRegistry();
179
+ _status = "resolved";
180
+ get status() {
181
+ if (this.disposed) throw this._disposedError();
182
+ return this._status;
183
+ }
184
+ set status(status) {
185
+ if (this.disposed) throw this._disposedError();
186
+ this._status = status;
187
+ }
188
+ registerDependency(dependency) {
189
+ if (this._disposed) throw this._disposedError();
190
+ this._dependenciesRegistry.register(dependency, this);
191
+ }
192
+ unregisterDependency(dependency) {
193
+ if (this._disposed) throw this._disposedError();
194
+ this._dependenciesRegistry.unregister(dependency, this);
195
+ }
196
+ clearDependencies() {
197
+ if (this.disposed) throw this._disposedError();
198
+ this._dependenciesRegistry.clear(this);
199
+ }
200
+ registerDependent(dependent) {
201
+ if (this.disposed) throw this._disposedError();
202
+ this._dependentsRegistry.register(dependent);
203
+ }
204
+ unregisterDependent(dependent) {
205
+ if (this.disposed) throw this._disposedError();
206
+ this._dependentsRegistry.unregister(dependent);
207
+ }
208
+ markDependencyDisposed() {
209
+ }
210
+ notifyDependents() {
211
+ if (this.disposed) return;
212
+ this._dependentsRegistry.notifyAll();
213
+ }
214
+ watch(tracker) {
215
+ if (this.disposed) throw this._disposedError();
216
+ tracker.registerDependency(this);
217
+ }
218
+ trigger() {
219
+ if (this.disposed) throw this._disposedError();
220
+ this.notifyDependents();
221
+ }
222
+ dispose() {
223
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
224
+ this._dependentsRegistry.forEach((dependant) => {
225
+ dependant.markDependencyDisposed();
226
+ dependant.unregisterDependency(this);
227
+ this.unregisterDependent(dependant);
228
+ });
229
+ this._dependenciesRegistry.forEach((dependency) => {
230
+ this.unregisterDependency(dependency);
231
+ });
232
+ this._disposed = true;
233
+ }
234
+ }
235
+
236
+ class Observable extends Disposable {
237
+ _dependentsRegistry = new DependentsRegistry();
238
+ _status = "resolved";
239
+ get status() {
240
+ if (this.disposed) throw this._disposedError();
241
+ return this._status;
242
+ }
243
+ set status(status) {
244
+ if (this.disposed) throw this._disposedError();
245
+ this._status = status;
246
+ }
247
+ registerDependent(dependent) {
248
+ if (this.disposed) throw this._disposedError();
249
+ this._dependentsRegistry.register(dependent);
250
+ }
251
+ unregisterDependent(dependent) {
252
+ if (this.disposed) throw this._disposedError();
253
+ this._dependentsRegistry.unregister(dependent);
254
+ }
255
+ notifyDependents() {
256
+ if (this.disposed) return;
257
+ this._dependentsRegistry.notifyAll();
258
+ }
259
+ watch(tracker) {
260
+ if (this.disposed) throw this._disposedError();
261
+ tracker.registerDependency(this);
262
+ }
263
+ trigger() {
264
+ if (this.disposed) throw this._disposedError();
265
+ this.notifyDependents();
266
+ }
267
+ dispose() {
268
+ if (this._disposed) throw this._disposedError();
269
+ this._dependentsRegistry.forEach((dependant) => {
270
+ dependant.markDependencyDisposed();
271
+ dependant.unregisterDependency(this);
272
+ this.unregisterDependent(dependant);
273
+ });
274
+ this._disposed = true;
275
+ }
276
+ }
277
+
278
+ class Observer extends Disposable {
279
+ _dependenciesRegistry = new DependenciesRegistry();
280
+ /** Set when a dependency disposed while this observer was queued; consumed by the next execute(). */
281
+ _dependencyDisposed = false;
282
+ markDependencyDisposed() {
283
+ this._dependencyDisposed = true;
284
+ }
285
+ registerDependency(dependency) {
286
+ if (this._disposed) throw this._disposedError();
287
+ this._dependenciesRegistry.register(dependency, this);
288
+ }
289
+ unregisterDependency(dependency) {
290
+ if (this._disposed) throw this._disposedError();
291
+ this._dependenciesRegistry.unregister(dependency, this);
292
+ }
293
+ clearDependencies() {
294
+ if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
295
+ this._dependenciesRegistry.clear(this);
296
+ }
297
+ dispose() {
298
+ if (this._disposed) throw this._disposedError();
299
+ this._dependenciesRegistry.clear(this);
300
+ this._disposed = true;
301
+ }
302
+ }
303
+
304
+ function onFlushError(handler) {
305
+ ExecutionStack.setFlushErrorHandler(handler);
306
+ }
307
+
3
308
  function isDisposable(obj) {
4
309
  return obj !== null && obj !== void 0 && typeof obj.dispose === "function";
5
310
  }
@@ -84,14 +389,14 @@ class AsyncScheduler {
84
389
  this._attachHandlers(this._resolver);
85
390
  }
86
391
  get settled() {
87
- if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
392
+ if (this._disposed) throw new PicoFlowDisposedError("[PicoFlow] ComputationScheduler is disposed");
88
393
  return this._settled.promise;
89
394
  }
90
395
  get disposed() {
91
396
  return this._disposed;
92
397
  }
93
398
  overwrite(promise) {
94
- if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
399
+ if (this._disposed) throw new PicoFlowDisposedError("[PicoFlow] ComputationScheduler is disposed");
95
400
  if (this._resolver.finished) {
96
401
  this._resolver = new AsyncResolver(this._compute);
97
402
  this._settled = Promise.withResolvers();
@@ -100,7 +405,7 @@ class AsyncScheduler {
100
405
  this._resolver.overwrite(promise);
101
406
  }
102
407
  compute() {
103
- if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
408
+ if (this._disposed) throw new PicoFlowDisposedError("[PicoFlow] ComputationScheduler is disposed");
104
409
  if (this._resolver.finished) {
105
410
  this._resolver = new AsyncResolver(this._compute);
106
411
  this._settled = Promise.withResolvers();
@@ -192,7 +497,7 @@ class SyncScheduler {
192
497
  return this._settled.promise;
193
498
  }
194
499
  overwrite(value) {
195
- if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
500
+ if (this._disposed) throw new PicoFlowDisposedError("[PicoFlow] ComputationScheduler is disposed");
196
501
  if (this._resolver.finished) {
197
502
  this._settled = Promise.withResolvers();
198
503
  this._resolver = new SyncResolver(this._compute, this._onResolve, this._onReject);
@@ -200,7 +505,7 @@ class SyncScheduler {
200
505
  this._resolver.overwrite(value);
201
506
  }
202
507
  compute() {
203
- if (this._disposed) throw new Error("[PicoFlow] ComputationScheduler is disposed");
508
+ if (this._disposed) throw new PicoFlowDisposedError("[PicoFlow] ComputationScheduler is disposed");
204
509
  if (this._resolver.finished) {
205
510
  this._settled = Promise.withResolvers();
206
511
  this._resolver = new SyncResolver(this._compute, this._onResolve, this._onReject);
@@ -213,229 +518,6 @@ class SyncScheduler {
213
518
  }
214
519
  }
215
520
 
216
- class Disposable {
217
- _disposed = false;
218
- get disposed() {
219
- return this._disposed;
220
- }
221
- dispose() {
222
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
223
- this._disposed = true;
224
- }
225
- }
226
-
227
- class ExecutionStack {
228
- static _MAX_FLUSH_STEPS = 1e4;
229
- static _pendingQueue = [];
230
- static _effectQueue = [];
231
- static _executionScheduled;
232
- static _coalesceResetScheduled = false;
233
- /**
234
- * True once the current synchronous call stack has finished and microtasks have started.
235
- * EffectNode uses this to treat microtask notifications (e.g. async resolve) differently
236
- * from coalesced sync-batch notifications.
237
- */
238
- static _pastSyncTurn = false;
239
- /** @internal Used by EffectNode to distinguish sync-batch from microtask notifications. */
240
- static get pastSyncTurn() {
241
- return ExecutionStack._pastSyncTurn;
242
- }
243
- /**
244
- * @internal Runs after the current sync turn ends (queueMicrotask).
245
- * Marks pastSyncTurn so the next coalesced effect notify can bump its epoch,
246
- * and resets per-effect coalesce flags via onReset.
247
- */
248
- static scheduleCoalesceReset(onReset) {
249
- if (ExecutionStack._coalesceResetScheduled) return;
250
- ExecutionStack._coalesceResetScheduled = true;
251
- queueMicrotask(() => {
252
- ExecutionStack._pastSyncTurn = true;
253
- ExecutionStack._coalesceResetScheduled = false;
254
- onReset();
255
- });
256
- }
257
- static pushPending(node) {
258
- ExecutionStack._beginSyncTurn();
259
- ExecutionStack._scheduleExecution();
260
- ExecutionStack._pendingQueue.push(node);
261
- }
262
- static pushEffect(effect) {
263
- ExecutionStack._beginSyncTurn();
264
- ExecutionStack._scheduleExecution();
265
- ExecutionStack._effectQueue.push(effect);
266
- }
267
- static _beginSyncTurn() {
268
- ExecutionStack._pastSyncTurn = false;
269
- }
270
- static _scheduleExecution() {
271
- if (ExecutionStack._executionScheduled) return;
272
- ExecutionStack._executionScheduled = new Promise((resolve) => {
273
- setTimeout(() => {
274
- ExecutionStack._executionScheduled = void 0;
275
- ExecutionStack._execute();
276
- resolve();
277
- }, 0);
278
- });
279
- }
280
- static _drain(queue) {
281
- for (let i = 0; i < queue.length; i++) {
282
- if (i >= ExecutionStack._MAX_FLUSH_STEPS) {
283
- ExecutionStack._pendingQueue.length = 0;
284
- ExecutionStack._effectQueue.length = 0;
285
- throw new Error("[PicoFlow] Reactive update cycle exceeded maximum depth");
286
- }
287
- const node = queue[i];
288
- node?.execute();
289
- }
290
- queue.length = 0;
291
- }
292
- static _execute() {
293
- ExecutionStack._drain(ExecutionStack._pendingQueue);
294
- ExecutionStack._drain(ExecutionStack._effectQueue);
295
- }
296
- }
297
-
298
- class Node extends Disposable {
299
- _dependencies = /* @__PURE__ */ new Set();
300
- _dependents = /* @__PURE__ */ new Set();
301
- _status = "resolved";
302
- get status() {
303
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
304
- return this._status;
305
- }
306
- set status(status) {
307
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
308
- this._status = status;
309
- }
310
- registerDependency(dependency) {
311
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
312
- this._dependencies.add(dependency);
313
- dependency.registerDependent(this);
314
- }
315
- unregisterDependency(dependency) {
316
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
317
- this._dependencies.delete(dependency);
318
- dependency.unregisterDependent(this);
319
- }
320
- clearDependencies() {
321
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
322
- this._dependencies.forEach((dependency) => {
323
- dependency.unregisterDependent(this);
324
- });
325
- this._dependencies.clear();
326
- }
327
- registerDependent(dependent) {
328
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
329
- this._dependents.add(dependent);
330
- }
331
- unregisterDependent(dependent) {
332
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
333
- this._dependents.delete(dependent);
334
- }
335
- notifyDependents() {
336
- if (this.disposed) return;
337
- this._dependents.forEach((dependent) => {
338
- dependent.notify();
339
- });
340
- }
341
- watch(tracker) {
342
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
343
- tracker.registerDependency(this);
344
- }
345
- trigger() {
346
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
347
- this.notifyDependents();
348
- }
349
- notify() {
350
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
351
- if (this.status === "dirty") return;
352
- this.status = "dirty";
353
- this.notifyDependents();
354
- }
355
- dispose() {
356
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
357
- Array.from(this._dependents).forEach((dependant) => {
358
- dependant.unregisterDependency(this);
359
- this.unregisterDependent(dependant);
360
- });
361
- Array.from(this._dependencies).forEach((dependency) => {
362
- this.unregisterDependency(dependency);
363
- });
364
- this._disposed = true;
365
- }
366
- }
367
-
368
- class Observable extends Disposable {
369
- _dependents = /* @__PURE__ */ new Set();
370
- _status = "resolved";
371
- get status() {
372
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
373
- return this._status;
374
- }
375
- set status(status) {
376
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
377
- this._status = status;
378
- }
379
- registerDependent(dependent) {
380
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
381
- this._dependents.add(dependent);
382
- }
383
- unregisterDependent(dependent) {
384
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
385
- this._dependents.delete(dependent);
386
- }
387
- notifyDependents() {
388
- if (this.disposed) return;
389
- this._dependents.forEach((dependent) => {
390
- dependent.notify();
391
- });
392
- }
393
- watch(tracker) {
394
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
395
- tracker.registerDependency(this);
396
- }
397
- trigger() {
398
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
399
- this.notifyDependents();
400
- }
401
- dispose() {
402
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
403
- Array.from(this._dependents).forEach((dependant) => {
404
- dependant.unregisterDependency(this);
405
- this.unregisterDependent(dependant);
406
- });
407
- this._disposed = true;
408
- }
409
- }
410
-
411
- class Observer extends Disposable {
412
- _dependencies = /* @__PURE__ */ new Set();
413
- registerDependency(dependency) {
414
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
415
- this._dependencies.add(dependency);
416
- dependency.registerDependent(this);
417
- }
418
- unregisterDependency(dependency) {
419
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
420
- this._dependencies.delete(dependency);
421
- dependency.unregisterDependent(this);
422
- }
423
- clearDependencies() {
424
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
425
- this._dependencies.forEach((dependency) => {
426
- dependency.unregisterDependent(this);
427
- });
428
- this._dependencies.clear();
429
- }
430
- dispose() {
431
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
432
- Array.from(this._dependencies).forEach((dependency) => {
433
- this.unregisterDependency(dependency);
434
- });
435
- this._disposed = true;
436
- }
437
- }
438
-
439
521
  class EffectNode extends Observer {
440
522
  _data;
441
523
  _onData;
@@ -449,6 +531,8 @@ class EffectNode extends Observer {
449
531
  _notifyEpoch = 0;
450
532
  /** How many notification epochs have been executed so far. */
451
533
  _executedEpoch = 0;
534
+ /** Callbacks registered via {@link onDispose}, run once after dispose() tears down the graph edges. */
535
+ _disposeListeners = [];
452
536
  constructor(data, onData, onError, onPending) {
453
537
  super();
454
538
  this._data = data;
@@ -458,8 +542,23 @@ class EffectNode extends Observer {
458
542
  this._notifyEpoch = 1;
459
543
  this.execute();
460
544
  }
545
+ /**
546
+ * Registers a callback to run once this effect is disposed, after the graph edges are torn
547
+ * down. Lets a caller that wraps an effect (e.g. {@link ActionNode.subscribe}) attach its own
548
+ * teardown without patching `dispose` on the returned instance (A2-ARCH-8).
549
+ * @internal
550
+ */
551
+ onDispose(fn) {
552
+ this._disposeListeners.push(fn);
553
+ }
554
+ dispose() {
555
+ super.dispose();
556
+ this._disposeListeners.forEach((fn) => {
557
+ fn();
558
+ });
559
+ }
461
560
  notify() {
462
- if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
561
+ if (this._disposed) throw this._disposedError();
463
562
  if (this._queued) {
464
563
  if (!this._coalescedWhileQueued) {
465
564
  this._coalescedWhileQueued = true;
@@ -482,6 +581,11 @@ class EffectNode extends Observer {
482
581
  this._executedEpoch = this._notifyEpoch;
483
582
  return;
484
583
  }
584
+ if (this._dependencyDisposed) {
585
+ this._dependencyDisposed = false;
586
+ this._executedEpoch = this._notifyEpoch;
587
+ return;
588
+ }
485
589
  const targetEpoch = this._notifyEpoch;
486
590
  while (this._executedEpoch < targetEpoch) {
487
591
  this._executedEpoch++;
@@ -489,6 +593,11 @@ class EffectNode extends Observer {
489
593
  this._executedEpoch = this._notifyEpoch;
490
594
  return;
491
595
  }
596
+ if (this._dependencyDisposed) {
597
+ this._dependencyDisposed = false;
598
+ this._executedEpoch = this._notifyEpoch;
599
+ return;
600
+ }
492
601
  try {
493
602
  this.clearDependencies();
494
603
  const data = this._data(this);
@@ -508,11 +617,27 @@ class EffectNode extends Observer {
508
617
  }
509
618
  }
510
619
 
620
+ const PICOFLOW_VALUE_NODE = /* @__PURE__ */ Symbol.for("picoflow.node");
511
621
  class ValueNode extends Node {
622
+ [PICOFLOW_VALUE_NODE] = true;
512
623
  _value;
513
624
  _error;
625
+ /** Builds the "unreachable state" error, naming the primitive when a `name` was given at creation. */
626
+ _internalError() {
627
+ return new PicoFlowInternalError(
628
+ this._name ? `[PicoFlow] Internal error (${this._name})` : "[PicoFlow] Internal error"
629
+ );
630
+ }
631
+ /**
632
+ * Hook called just before a tracked read throws `PendingError`, i.e. a dependent registered while
633
+ * this node is mid-flight and has no other way to learn the computation later settled (A2-PERF-4).
634
+ * No-op by default; overridden by {@link ValueAsyncNode} to know when its own post-resolve
635
+ * `notifyDependents()` call cannot be skipped even if the resolved value is unchanged.
636
+ */
637
+ _markPendingRead() {
638
+ }
514
639
  watch(tracker) {
515
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
640
+ if (this.disposed) throw this._disposedError();
516
641
  super.watch(tracker);
517
642
  switch (this.status) {
518
643
  case "resolved":
@@ -520,26 +645,28 @@ class ValueNode extends Node {
520
645
  case "error":
521
646
  throw this._error;
522
647
  case "pending": {
648
+ this._markPendingRead();
523
649
  throw new PendingError(this._scheduler.settled);
524
650
  }
525
651
  case "dirty": {
526
652
  this.execute();
527
653
  switch (this.status) {
528
654
  case "pending":
655
+ this._markPendingRead();
529
656
  throw new PendingError(this._scheduler.settled);
530
657
  case "resolved":
531
658
  return;
532
659
  case "error":
533
660
  throw this._error;
534
661
  case "dirty": {
535
- throw new Error("[PicoFlow] Internal error");
662
+ throw this._internalError();
536
663
  }
537
664
  }
538
665
  }
539
666
  }
540
667
  }
541
668
  notify() {
542
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
669
+ if (this.disposed) throw this._disposedError();
543
670
  if (this.status === "dirty") return;
544
671
  if (this.status === "pending") {
545
672
  ExecutionStack.pushPending(this);
@@ -552,7 +679,7 @@ class ValueNode extends Node {
552
679
  this._scheduler.dispose();
553
680
  }
554
681
  get(tracker) {
555
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
682
+ if (this.disposed) throw this._disposedError();
556
683
  super.watch(tracker);
557
684
  switch (this.status) {
558
685
  case "resolved":
@@ -560,6 +687,7 @@ class ValueNode extends Node {
560
687
  case "error":
561
688
  throw this._error;
562
689
  case "pending": {
690
+ this._markPendingRead();
563
691
  throw new PendingError(this._scheduler.settled);
564
692
  }
565
693
  case "dirty": {
@@ -570,17 +698,18 @@ class ValueNode extends Node {
570
698
  case "error":
571
699
  throw this._error;
572
700
  case "pending": {
701
+ this._markPendingRead();
573
702
  throw new PendingError(this._scheduler.settled);
574
703
  }
575
704
  case "dirty": {
576
- throw new Error("[PicoFlow] Internal error");
705
+ throw this._internalError();
577
706
  }
578
707
  }
579
708
  }
580
709
  }
581
710
  }
582
711
  async pick() {
583
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
712
+ if (this.disposed) throw this._disposedError();
584
713
  switch (this.status) {
585
714
  case "resolved":
586
715
  return this._value;
@@ -588,7 +717,7 @@ class ValueNode extends Node {
588
717
  await this._scheduler.settled;
589
718
  if (this.status === "resolved") return this._value;
590
719
  if (this.status === "error") throw this._error;
591
- throw new Error("[PicoFlow] Internal error");
720
+ throw this._internalError();
592
721
  }
593
722
  case "error":
594
723
  throw this._error;
@@ -601,12 +730,12 @@ class ValueNode extends Node {
601
730
  await this._scheduler.settled;
602
731
  if (this.status === "resolved") return this._value;
603
732
  if (this.status === "error") throw this._error;
604
- throw new Error("[PicoFlow] Internal error");
733
+ throw this._internalError();
605
734
  }
606
735
  case "error":
607
736
  throw this._error;
608
737
  case "dirty": {
609
- throw new Error("[PicoFlow] Internal error");
738
+ throw this._internalError();
610
739
  }
611
740
  }
612
741
  }
@@ -619,7 +748,7 @@ class ValueNode extends Node {
619
748
  this._scheduler.compute();
620
749
  }
621
750
  subscribe(onValue, onError, onPending) {
622
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
751
+ if (this.disposed) throw this._disposedError();
623
752
  const effect = new EffectNode((t) => this.get(t), onValue, onError, onPending);
624
753
  return effect;
625
754
  }
@@ -628,8 +757,17 @@ class ValueNode extends Node {
628
757
  class ValueAsyncNode extends ValueNode {
629
758
  _scheduler;
630
759
  _compute;
631
- constructor(promiseOrCompute) {
632
- super();
760
+ /** Set by {@link ValueNode._markPendingRead} when a dependent registers while this node is mid-flight. */
761
+ _hasPendingReader = false;
762
+ /**
763
+ * True only while the in-flight computation was started by `set()`, which already ran its own
764
+ * eager `notifyDependents()` cascade before this resolution. `refresh()` and a recompute pulled
765
+ * lazily after an upstream `notify()` have no such pre-cascade — for them, `_onResolve`'s own
766
+ * `notifyDependents()` call is the only notification, so it must never be skipped (A2-PERF-4).
767
+ */
768
+ _pendingFromSet = false;
769
+ constructor(promiseOrCompute, name) {
770
+ super(name);
633
771
  if (typeof promiseOrCompute === "function") {
634
772
  this._compute = () => promiseOrCompute(this, this._value);
635
773
  } else {
@@ -644,10 +782,18 @@ class ValueAsyncNode extends ValueNode {
644
782
  }
645
783
  _onResolve(value) {
646
784
  if (this.disposed) return;
785
+ const changed = value !== this._value;
786
+ const hadPendingReader = this._hasPendingReader;
787
+ const pendingFromSet = this._pendingFromSet;
788
+ this._hasPendingReader = false;
789
+ this._pendingFromSet = false;
647
790
  this.status = "resolved";
648
791
  this._value = value;
649
792
  this._error = void 0;
650
- this.notifyDependents();
793
+ if (changed || hadPendingReader || !pendingFromSet) this.notifyDependents();
794
+ }
795
+ _markPendingRead() {
796
+ this._hasPendingReader = true;
651
797
  }
652
798
  _onReject(error) {
653
799
  if (this.disposed) return;
@@ -656,12 +802,13 @@ class ValueAsyncNode extends ValueNode {
656
802
  this.notifyDependents();
657
803
  }
658
804
  set(promiseOrUpdater) {
659
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
805
+ if (this.disposed) throw this._disposedError();
660
806
  if (typeof promiseOrUpdater === "function") {
661
807
  const updater = promiseOrUpdater;
662
808
  switch (this.status) {
663
809
  case "resolved": {
664
810
  this.status = "pending";
811
+ this._pendingFromSet = true;
665
812
  this._scheduler.overwrite(updater(this._value));
666
813
  this.notifyDependents();
667
814
  return;
@@ -670,6 +817,7 @@ class ValueAsyncNode extends ValueNode {
670
817
  case "error":
671
818
  case "dirty": {
672
819
  this.status = "pending";
820
+ this._pendingFromSet = true;
673
821
  this._scheduler.overwrite(updater(this._value));
674
822
  this.notifyDependents();
675
823
  return;
@@ -677,38 +825,39 @@ class ValueAsyncNode extends ValueNode {
677
825
  }
678
826
  } else {
679
827
  this.status = "pending";
828
+ this._pendingFromSet = true;
680
829
  const promise = promiseOrUpdater;
681
830
  this._scheduler.overwrite(promise);
682
831
  this.notifyDependents();
683
832
  }
684
833
  }
685
834
  refresh() {
686
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
835
+ if (this.disposed) throw this._disposedError();
687
836
  this.execute();
688
837
  }
689
838
  }
690
839
 
691
- function constantAsync(valueOrInitializer) {
692
- return new ValueAsyncNode(valueOrInitializer);
840
+ function constantAsync(valueOrInitializer, options) {
841
+ return new ValueAsyncNode(valueOrInitializer, options?.name);
693
842
  }
694
843
 
695
- function derivationAsync(compute) {
696
- return new ValueAsyncNode(compute);
844
+ function derivationAsync(compute, options) {
845
+ return new ValueAsyncNode(compute, options?.name);
697
846
  }
698
847
 
699
- function stateAsync(valueOrInitializer) {
700
- return new ValueAsyncNode(valueOrInitializer);
848
+ function stateAsync(valueOrInitializer, options) {
849
+ return new ValueAsyncNode(valueOrInitializer, options?.name);
701
850
  }
702
851
 
703
- function writableDerivationAsync(compute) {
704
- return new ValueAsyncNode(compute);
852
+ function writableDerivationAsync(compute, options) {
853
+ return new ValueAsyncNode(compute, options?.name);
705
854
  }
706
855
 
707
856
  class ValueSyncNode extends ValueNode {
708
857
  _scheduler;
709
858
  _compute;
710
- constructor(valueOrCompute) {
711
- super();
859
+ constructor(valueOrCompute, name) {
860
+ super(name);
712
861
  if (typeof valueOrCompute === "function") {
713
862
  this._compute = () => valueOrCompute(this, this._value);
714
863
  } else {
@@ -734,7 +883,7 @@ class ValueSyncNode extends ValueNode {
734
883
  this._error = error;
735
884
  }
736
885
  set(valueOrUpdater) {
737
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
886
+ if (this.disposed) throw this._disposedError();
738
887
  if (typeof valueOrUpdater === "function") {
739
888
  const updater = valueOrUpdater;
740
889
  switch (this.status) {
@@ -778,7 +927,7 @@ class ValueSyncNode extends ValueNode {
778
927
  }
779
928
  }
780
929
  refresh() {
781
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
930
+ if (this.disposed) throw this._disposedError();
782
931
  const currentValue = this._value;
783
932
  const currentStatus = this.status;
784
933
  this.execute();
@@ -807,7 +956,7 @@ class ActionNode extends ValueSyncNode {
807
956
  }
808
957
  }
809
958
  set(valueOrUpdater) {
810
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
959
+ if (this.disposed) throw this._disposedError();
811
960
  if (typeof valueOrUpdater === "function") {
812
961
  const updater = valueOrUpdater;
813
962
  const nextValue2 = updater(this._value);
@@ -822,7 +971,7 @@ class ActionNode extends ValueSyncNode {
822
971
  super.set(nextValue);
823
972
  }
824
973
  subscribe(onValue, onError, onPending) {
825
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
974
+ if (this.disposed) throw this._disposedError();
826
975
  const slot = { queue: [] };
827
976
  this._slots.add(slot);
828
977
  const effect = new EffectNode(
@@ -841,11 +990,9 @@ class ActionNode extends ValueSyncNode {
841
990
  onError,
842
991
  onPending
843
992
  );
844
- const originalDispose = effect.dispose.bind(effect);
845
- effect.dispose = () => {
993
+ effect.onDispose(() => {
846
994
  this._slots.delete(slot);
847
- originalDispose();
848
- };
995
+ });
849
996
  return effect;
850
997
  }
851
998
  dispose() {
@@ -856,8 +1003,8 @@ class ActionNode extends ValueSyncNode {
856
1003
 
857
1004
  class ArrayNode extends ValueSyncNode {
858
1005
  $lastAction;
859
- constructor(value = []) {
860
- super(value);
1006
+ constructor(value = [], name) {
1007
+ super(value, name);
861
1008
  this.$lastAction = new ActionNode({
862
1009
  type: "set",
863
1010
  setItems: value,
@@ -873,11 +1020,11 @@ class ArrayNode extends ValueSyncNode {
873
1020
  return [...values];
874
1021
  }
875
1022
  get length() {
876
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1023
+ if (this.disposed) throw this._disposedError();
877
1024
  return this._value.length;
878
1025
  }
879
1026
  set(itemsOrUpdater) {
880
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1027
+ if (this.disposed) throw this._disposedError();
881
1028
  const previousValue = this._value;
882
1029
  super.set(itemsOrUpdater);
883
1030
  this.$lastAction.set({
@@ -888,8 +1035,8 @@ class ArrayNode extends ValueSyncNode {
888
1035
  return previousValue;
889
1036
  }
890
1037
  update(index, item) {
891
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
892
- if (index < 0 || index >= this._value.length) {
1038
+ if (this.disposed) throw this._disposedError();
1039
+ if (index < 0 || index >= this._value.length || !Number.isInteger(index)) {
893
1040
  throw new Error("[PicoFlow] Index out of bounds");
894
1041
  }
895
1042
  const previousValue = this._value[index];
@@ -904,13 +1051,13 @@ class ArrayNode extends ValueSyncNode {
904
1051
  return previousValue;
905
1052
  }
906
1053
  push(item) {
907
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1054
+ if (this.disposed) throw this._disposedError();
908
1055
  this._value.push(item);
909
1056
  this.notifyDependents();
910
1057
  this.$lastAction.set({ type: "push", addedItem: item });
911
1058
  }
912
1059
  pop() {
913
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1060
+ if (this.disposed) throw this._disposedError();
914
1061
  if (this._value.length === 0) return void 0;
915
1062
  const item = this._value.pop();
916
1063
  this.notifyDependents();
@@ -918,13 +1065,13 @@ class ArrayNode extends ValueSyncNode {
918
1065
  return item;
919
1066
  }
920
1067
  unshift(item) {
921
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1068
+ if (this.disposed) throw this._disposedError();
922
1069
  this._value.unshift(item);
923
1070
  this.notifyDependents();
924
1071
  this.$lastAction.set({ type: "unshift", addedItem: item });
925
1072
  }
926
1073
  shift() {
927
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1074
+ if (this.disposed) throw this._disposedError();
928
1075
  if (this._value.length === 0) return void 0;
929
1076
  const item = this._value.shift();
930
1077
  this.notifyDependents();
@@ -932,7 +1079,7 @@ class ArrayNode extends ValueSyncNode {
932
1079
  return item;
933
1080
  }
934
1081
  splice(start, deleteCount, ...newItems) {
935
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1082
+ if (this.disposed) throw this._disposedError();
936
1083
  const items = this._value.splice(start, deleteCount, ...newItems);
937
1084
  this.notifyDependents();
938
1085
  this.$lastAction.set({
@@ -945,7 +1092,7 @@ class ArrayNode extends ValueSyncNode {
945
1092
  return items;
946
1093
  }
947
1094
  clear() {
948
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1095
+ if (this.disposed) throw this._disposedError();
949
1096
  const previousValue = [...this._value];
950
1097
  this._value = [];
951
1098
  this.notifyDependents();
@@ -961,14 +1108,14 @@ class ArrayNode extends ValueSyncNode {
961
1108
  }
962
1109
  }
963
1110
 
964
- function array(initial) {
965
- return new ArrayNode(initial);
1111
+ function array(initial, options) {
1112
+ return new ArrayNode(initial, options?.name);
966
1113
  }
967
1114
 
968
1115
  class MapNode extends ValueSyncNode {
969
1116
  $lastAction;
970
- constructor(value = /* @__PURE__ */ new Map()) {
971
- super(value);
1117
+ constructor(value = /* @__PURE__ */ new Map(), name) {
1118
+ super(value, name);
972
1119
  this.$lastAction = new ActionNode({
973
1120
  type: "set",
974
1121
  setMap: value,
@@ -976,7 +1123,7 @@ class MapNode extends ValueSyncNode {
976
1123
  });
977
1124
  }
978
1125
  add(key, value) {
979
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1126
+ if (this.disposed) throw this._disposedError();
980
1127
  if (this._value.has(key)) {
981
1128
  throw new Error("[PicoFlow] Key already exists");
982
1129
  }
@@ -985,7 +1132,7 @@ class MapNode extends ValueSyncNode {
985
1132
  this.$lastAction.set({ type: "add", key, addedValue: value });
986
1133
  }
987
1134
  update(key, value) {
988
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1135
+ if (this.disposed) throw this._disposedError();
989
1136
  if (!this._value.has(key)) throw new Error("[PicoFlow] Key does not exist");
990
1137
  const previousValue = this._value.get(key);
991
1138
  this._value.set(key, value);
@@ -999,7 +1146,7 @@ class MapNode extends ValueSyncNode {
999
1146
  return previousValue;
1000
1147
  }
1001
1148
  delete(key) {
1002
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1149
+ if (this.disposed) throw this._disposedError();
1003
1150
  if (!this._value.has(key)) throw new Error("[PicoFlow] Key does not exist");
1004
1151
  const value = this._value.get(key);
1005
1152
  this._value.delete(key);
@@ -1008,7 +1155,7 @@ class MapNode extends ValueSyncNode {
1008
1155
  return value;
1009
1156
  }
1010
1157
  set(mapOrUpdater) {
1011
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1158
+ if (this.disposed) throw this._disposedError();
1012
1159
  const previousValue = this._value;
1013
1160
  super.set(mapOrUpdater);
1014
1161
  this.$lastAction.set({
@@ -1019,7 +1166,7 @@ class MapNode extends ValueSyncNode {
1019
1166
  return previousValue;
1020
1167
  }
1021
1168
  clear() {
1022
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1169
+ if (this.disposed) throw this._disposedError();
1023
1170
  const previousValue = this._value;
1024
1171
  this._value = /* @__PURE__ */ new Map();
1025
1172
  this.notifyDependents();
@@ -1035,11 +1182,11 @@ class MapNode extends ValueSyncNode {
1035
1182
  }
1036
1183
  }
1037
1184
 
1038
- function map(initial) {
1185
+ function map(initial, options) {
1039
1186
  if (initial instanceof Map) {
1040
- return new MapNode(initial);
1187
+ return new MapNode(initial, options?.name);
1041
1188
  }
1042
- return new MapNode(new Map(initial ? Object.entries(initial) : []));
1189
+ return new MapNode(new Map(initial ? Object.entries(initial) : []), options?.name);
1043
1190
  }
1044
1191
 
1045
1192
  function subscribe(data, onData, onError, onPending) {
@@ -1048,30 +1195,30 @@ function subscribe(data, onData, onError, onPending) {
1048
1195
 
1049
1196
  class SignalNode extends Observable {
1050
1197
  subscribe(onTrigger, onError, onPending) {
1051
- if (this.disposed) throw new Error("[PicoFlow] Primitive is disposed");
1198
+ if (this.disposed) throw this._disposedError();
1052
1199
  const effect = new EffectNode((t) => this.watch(t), onTrigger, onError, onPending);
1053
1200
  return effect;
1054
1201
  }
1055
1202
  }
1056
1203
 
1057
- function signal() {
1058
- return new SignalNode();
1204
+ function signal(options) {
1205
+ return new SignalNode(options?.name);
1059
1206
  }
1060
1207
 
1061
- function constant(initializer) {
1062
- return new ValueSyncNode(initializer);
1208
+ function constant(initializer, options) {
1209
+ return new ValueSyncNode(initializer, options?.name);
1063
1210
  }
1064
1211
 
1065
- function derivation(compute) {
1066
- return new ValueSyncNode(compute);
1212
+ function derivation(compute, options) {
1213
+ return new ValueSyncNode(compute, options?.name);
1067
1214
  }
1068
1215
 
1069
- function state(valueOrInitializer) {
1070
- return new ValueSyncNode(valueOrInitializer);
1216
+ function state(valueOrInitializer, options) {
1217
+ return new ValueSyncNode(valueOrInitializer, options?.name);
1071
1218
  }
1072
1219
 
1073
- function writableDerivation(compute) {
1074
- return new ValueSyncNode(compute);
1220
+ function writableDerivation(compute, options) {
1221
+ return new ValueSyncNode(compute, options?.name);
1075
1222
  }
1076
1223
 
1077
1224
  function describeFlowInput(value) {
@@ -1082,6 +1229,9 @@ function describeFlowInput(value) {
1082
1229
  }
1083
1230
  return typeof value;
1084
1231
  }
1232
+ function isValueNode(flow) {
1233
+ return typeof flow === "object" && flow !== null && PICOFLOW_VALUE_NODE in flow;
1234
+ }
1085
1235
  function fromNode(node, options) {
1086
1236
  const [resource, { refetch }] = createResource(() => node.pick());
1087
1237
  let fx;
@@ -1122,7 +1272,7 @@ function fromGetter(getter) {
1122
1272
  return fromNode(derivation, { disposeNode: true });
1123
1273
  }
1124
1274
  function from(flow) {
1125
- if (flow instanceof ValueAsyncNode || flow instanceof ValueSyncNode) {
1275
+ if (isValueNode(flow)) {
1126
1276
  return fromNode(flow);
1127
1277
  }
1128
1278
  if (typeof flow === "function") {
@@ -1131,4 +1281,4 @@ function from(flow) {
1131
1281
  throw new Error(`[PicoFlow] from(): expected a FlowValue or getter function, received ${describeFlowInput(flow)}`);
1132
1282
  }
1133
1283
 
1134
- export { array, constant, constantAsync, derivation, derivationAsync, from, isDisposable, map, signal, state, stateAsync, subscribe, writableDerivation, writableDerivationAsync };
1284
+ export { PicoFlowCycleError, PicoFlowDisposedError, PicoFlowError, PicoFlowInternalError, array, constant, constantAsync, derivation, derivationAsync, from, isDisposable, map, onFlushError, signal, state, stateAsync, subscribe, writableDerivation, writableDerivationAsync };