@fluixi/reactive 1.0.0-alpha.104 → 1.0.0-alpha.106

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 (82) hide show
  1. package/dist/cdn/reactive.cjs +74 -34
  2. package/dist/cdn/reactive.global.js +2 -2
  3. package/dist/cdn/reactive.mjs +74 -34
  4. package/dist/cdn/signal.cjs +23 -3
  5. package/dist/cdn/signal.global.js +2 -2
  6. package/dist/cdn/signal.mjs +23 -3
  7. package/dist/index.cjs +74 -34
  8. package/dist/index.mjs +74 -34
  9. package/dist/lib/index.cjs +75 -35
  10. package/dist/lib/index.mjs +75 -35
  11. package/dist/lib/observable/index.cjs +1 -1
  12. package/dist/lib/observable/index.mjs +1 -1
  13. package/dist/lib/signal/api.cjs +40 -14
  14. package/dist/lib/signal/api.mjs +40 -14
  15. package/dist/lib/signal/cached-resource.cjs +36 -13
  16. package/dist/lib/signal/cached-resource.mjs +36 -13
  17. package/dist/lib/signal/graph/children.cjs +25 -6
  18. package/dist/lib/signal/graph/children.mjs +25 -6
  19. package/dist/lib/signal/graph/compat.cjs +41 -15
  20. package/dist/lib/signal/graph/compat.d.ts.map +1 -1
  21. package/dist/lib/signal/graph/compat.js +20 -11
  22. package/dist/lib/signal/graph/compat.mjs +41 -15
  23. package/dist/lib/signal/graph/context-children.cjs +1 -1
  24. package/dist/lib/signal/graph/context-children.mjs +1 -1
  25. package/dist/lib/signal/graph/core.cjs +24 -4
  26. package/dist/lib/signal/graph/core.d.ts +10 -0
  27. package/dist/lib/signal/graph/core.d.ts.map +1 -1
  28. package/dist/lib/signal/graph/core.js +27 -2
  29. package/dist/lib/signal/graph/core.mjs +24 -4
  30. package/dist/lib/signal/graph/debounced.cjs +35 -12
  31. package/dist/lib/signal/graph/debounced.mjs +35 -12
  32. package/dist/lib/signal/graph/derived.cjs +35 -12
  33. package/dist/lib/signal/graph/derived.mjs +35 -12
  34. package/dist/lib/signal/graph/detect.cjs +1 -1
  35. package/dist/lib/signal/graph/detect.mjs +1 -1
  36. package/dist/lib/signal/graph/error-boundary.cjs +10 -8
  37. package/dist/lib/signal/graph/error-boundary.mjs +10 -8
  38. package/dist/lib/signal/graph/index.cjs +24 -4
  39. package/dist/lib/signal/graph/index.mjs +24 -4
  40. package/dist/lib/signal/graph/observe.cjs +1 -1
  41. package/dist/lib/signal/graph/observe.mjs +1 -1
  42. package/dist/lib/signal/graph/resource.cjs +35 -12
  43. package/dist/lib/signal/graph/resource.mjs +35 -12
  44. package/dist/lib/signal/graph/runtime.cjs +29 -5
  45. package/dist/lib/signal/graph/runtime.d.ts.map +1 -1
  46. package/dist/lib/signal/graph/runtime.js +5 -3
  47. package/dist/lib/signal/graph/runtime.mjs +29 -5
  48. package/dist/lib/signal/graph/signal-next.cjs +67 -29
  49. package/dist/lib/signal/graph/signal-next.mjs +67 -29
  50. package/dist/lib/signal/graph/state.cjs +1 -1
  51. package/dist/lib/signal/graph/state.mjs +1 -1
  52. package/dist/lib/signal/graph/suspense.cjs +10 -8
  53. package/dist/lib/signal/graph/suspense.mjs +10 -8
  54. package/dist/lib/signal/index-legacy.cjs +67 -29
  55. package/dist/lib/signal/index-legacy.mjs +67 -29
  56. package/dist/lib/signal/index.cjs +67 -29
  57. package/dist/lib/signal/index.mjs +67 -29
  58. package/dist/lib/signal/list.cjs +58 -25
  59. package/dist/lib/signal/list.d.ts +3 -8
  60. package/dist/lib/signal/list.d.ts.map +1 -1
  61. package/dist/lib/signal/list.js +40 -18
  62. package/dist/lib/signal/list.mjs +58 -25
  63. package/dist/lib/signal/resource-api.cjs +35 -12
  64. package/dist/lib/signal/resource-api.mjs +35 -12
  65. package/dist/lib/signal/signal.cjs +41 -15
  66. package/dist/lib/signal/signal.mjs +41 -15
  67. package/dist/lib/signal/utilities.cjs +41 -15
  68. package/dist/lib/signal/utilities.mjs +41 -15
  69. package/dist/lib/store/api.cjs +9 -7
  70. package/dist/lib/store/api.mjs +9 -7
  71. package/dist/lib/store/index.cjs +10 -8
  72. package/dist/lib/store/index.mjs +10 -8
  73. package/dist/lib/store/store.cjs +9 -7
  74. package/dist/lib/store/store.d.ts.map +1 -1
  75. package/dist/lib/store/store.js +8 -6
  76. package/dist/lib/store/store.mjs +9 -7
  77. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  78. package/dist/version.generated.cjs +2 -2
  79. package/dist/version.generated.d.ts +1 -1
  80. package/dist/version.generated.js +1 -1
  81. package/dist/version.generated.mjs +2 -2
  82. package/package.json +2 -2
@@ -25,7 +25,7 @@ __export(resource_api_exports, {
25
25
  module.exports = __toCommonJS(resource_api_exports);
26
26
 
27
27
  // src/version.generated.ts
28
- var VERSION = "1.0.0-alpha.104";
28
+ var VERSION = "1.0.0-alpha.106";
29
29
 
30
30
  // src/lib/signal/graph/state.ts
31
31
  var MAJOR = VERSION.split(".")[0];
@@ -167,6 +167,7 @@ var Computed = class extends Source {
167
167
  super();
168
168
  this._threw = false;
169
169
  this._initialized = false;
170
+ this._disposed = false;
170
171
  this.state = DIRTY;
171
172
  /** Nodes this computation read on its last run. Rebuilt on every run. */
172
173
  this.sources = null;
@@ -182,8 +183,23 @@ var Computed = class extends Source {
182
183
  if (this._threw) throw this._error;
183
184
  return this._value;
184
185
  }
186
+ /**
187
+ * Unlink from every source, for good. Without this, a source outlives the
188
+ * computation and keeps it (and everything its function captured) reachable
189
+ * through its observers set: a disposed list row stayed in memory that way.
190
+ * The last value stays readable; the node simply never updates again.
191
+ */
192
+ dispose() {
193
+ if (this._disposed) return;
194
+ this._disposed = true;
195
+ if (this.sources) {
196
+ for (const s of this.sources) s.observers?.delete(this);
197
+ this.sources = null;
198
+ }
199
+ }
185
200
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
186
201
  updateIfNecessary() {
202
+ if (this._disposed) return;
187
203
  if (this.state === CHECK) {
188
204
  if (this.sources) {
189
205
  for (const s of this.sources) {
@@ -227,7 +243,7 @@ var Computed = class extends Source {
227
243
  reportReactiveRun(this);
228
244
  if (changed) {
229
245
  this.version++;
230
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
246
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
231
247
  notifyWatchers(this);
232
248
  }
233
249
  }
@@ -241,7 +257,7 @@ var Computed = class extends Source {
241
257
  const wasClean = this.state === CLEAN;
242
258
  this.state = level;
243
259
  if (wasClean) {
244
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
260
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
245
261
  notifyWatchers(this);
246
262
  }
247
263
  }
@@ -280,6 +296,10 @@ var Watcher = class {
280
296
  this._pending.clear();
281
297
  return out;
282
298
  }
299
+ /** Drain pending without the array, for callers that only need it empty. */
300
+ clearPending() {
301
+ this._pending.clear();
302
+ }
283
303
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
284
304
  _notify(node) {
285
305
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -397,14 +417,14 @@ var EffectNode = class {
397
417
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
398
418
  });
399
419
  });
400
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
420
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
401
421
  this.watcher = new Watcher(() => enqueue(this));
402
422
  this.run();
403
423
  this.watcher.watch(this.computed);
404
424
  }
405
425
  run() {
406
426
  if (this.disposed) return;
407
- this.watcher.getPending();
427
+ this.watcher.clearPending();
408
428
  try {
409
429
  this.computed.get();
410
430
  } catch (e) {
@@ -421,6 +441,7 @@ var EffectNode = class {
421
441
  this.disposed = true;
422
442
  reportReactiveDispose(this.computed);
423
443
  this.watcher.unwatch(this.computed);
444
+ this.computed.dispose();
424
445
  disposeOwner(this.owner);
425
446
  }
426
447
  };
@@ -455,13 +476,15 @@ function createSignal(value, options) {
455
476
  },
456
477
  enumerable: false
457
478
  });
458
- reportReactiveNode({
459
- kind: "signal",
460
- node: state,
461
- handle: read,
462
- name: options?.name,
463
- set: write
464
- });
479
+ if (isObserved()) {
480
+ reportReactiveNode({
481
+ kind: "signal",
482
+ node: state,
483
+ handle: read,
484
+ name: options?.name,
485
+ set: write
486
+ });
487
+ }
465
488
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
466
489
  return [read, write];
467
490
  }
@@ -1,5 +1,5 @@
1
1
  // src/version.generated.ts
2
- var VERSION = "1.0.0-alpha.104";
2
+ var VERSION = "1.0.0-alpha.106";
3
3
 
4
4
  // src/lib/signal/graph/state.ts
5
5
  var MAJOR = VERSION.split(".")[0];
@@ -141,6 +141,7 @@ var Computed = class extends Source {
141
141
  super();
142
142
  this._threw = false;
143
143
  this._initialized = false;
144
+ this._disposed = false;
144
145
  this.state = DIRTY;
145
146
  /** Nodes this computation read on its last run. Rebuilt on every run. */
146
147
  this.sources = null;
@@ -156,8 +157,23 @@ var Computed = class extends Source {
156
157
  if (this._threw) throw this._error;
157
158
  return this._value;
158
159
  }
160
+ /**
161
+ * Unlink from every source, for good. Without this, a source outlives the
162
+ * computation and keeps it (and everything its function captured) reachable
163
+ * through its observers set: a disposed list row stayed in memory that way.
164
+ * The last value stays readable; the node simply never updates again.
165
+ */
166
+ dispose() {
167
+ if (this._disposed) return;
168
+ this._disposed = true;
169
+ if (this.sources) {
170
+ for (const s of this.sources) s.observers?.delete(this);
171
+ this.sources = null;
172
+ }
173
+ }
159
174
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
160
175
  updateIfNecessary() {
176
+ if (this._disposed) return;
161
177
  if (this.state === CHECK) {
162
178
  if (this.sources) {
163
179
  for (const s of this.sources) {
@@ -201,7 +217,7 @@ var Computed = class extends Source {
201
217
  reportReactiveRun(this);
202
218
  if (changed) {
203
219
  this.version++;
204
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
220
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
205
221
  notifyWatchers(this);
206
222
  }
207
223
  }
@@ -215,7 +231,7 @@ var Computed = class extends Source {
215
231
  const wasClean = this.state === CLEAN;
216
232
  this.state = level;
217
233
  if (wasClean) {
218
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
234
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
219
235
  notifyWatchers(this);
220
236
  }
221
237
  }
@@ -254,6 +270,10 @@ var Watcher = class {
254
270
  this._pending.clear();
255
271
  return out;
256
272
  }
273
+ /** Drain pending without the array, for callers that only need it empty. */
274
+ clearPending() {
275
+ this._pending.clear();
276
+ }
257
277
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
258
278
  _notify(node) {
259
279
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -371,14 +391,14 @@ var EffectNode = class {
371
391
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
372
392
  });
373
393
  });
374
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
394
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
375
395
  this.watcher = new Watcher(() => enqueue(this));
376
396
  this.run();
377
397
  this.watcher.watch(this.computed);
378
398
  }
379
399
  run() {
380
400
  if (this.disposed) return;
381
- this.watcher.getPending();
401
+ this.watcher.clearPending();
382
402
  try {
383
403
  this.computed.get();
384
404
  } catch (e) {
@@ -395,6 +415,7 @@ var EffectNode = class {
395
415
  this.disposed = true;
396
416
  reportReactiveDispose(this.computed);
397
417
  this.watcher.unwatch(this.computed);
418
+ this.computed.dispose();
398
419
  disposeOwner(this.owner);
399
420
  }
400
421
  };
@@ -429,13 +450,15 @@ function createSignal(value, options) {
429
450
  },
430
451
  enumerable: false
431
452
  });
432
- reportReactiveNode({
433
- kind: "signal",
434
- node: state,
435
- handle: read,
436
- name: options?.name,
437
- set: write
438
- });
453
+ if (isObserved()) {
454
+ reportReactiveNode({
455
+ kind: "signal",
456
+ node: state,
457
+ handle: read,
458
+ name: options?.name,
459
+ set: write
460
+ });
461
+ }
439
462
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
440
463
  return [read, write];
441
464
  }
@@ -434,7 +434,7 @@ var Observable = class extends Subscription {
434
434
 
435
435
  // src/lib/signal/graph/detect.ts
436
436
  var __BROWSER_PROD__ = typeof window === "undefined" ? false : window.__FLUIXI_PROD__ === true;
437
- var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : false;
437
+ var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : process.env.NODE_ENV !== "production";
438
438
  var $SIGNAL = /* @__PURE__ */ Symbol.for("signal");
439
439
  var $MEMO = /* @__PURE__ */ Symbol.for("memo");
440
440
  var $RESOURCE = /* @__PURE__ */ Symbol.for("resource");
@@ -468,7 +468,7 @@ function makeArrayFlat(values) {
468
468
  }
469
469
 
470
470
  // src/version.generated.ts
471
- var VERSION = "1.0.0-alpha.104";
471
+ var VERSION = "1.0.0-alpha.106";
472
472
 
473
473
  // src/lib/signal/graph/state.ts
474
474
  var MAJOR = VERSION.split(".")[0];
@@ -610,6 +610,7 @@ var Computed = class extends Source {
610
610
  super();
611
611
  this._threw = false;
612
612
  this._initialized = false;
613
+ this._disposed = false;
613
614
  this.state = DIRTY;
614
615
  /** Nodes this computation read on its last run. Rebuilt on every run. */
615
616
  this.sources = null;
@@ -625,8 +626,23 @@ var Computed = class extends Source {
625
626
  if (this._threw) throw this._error;
626
627
  return this._value;
627
628
  }
629
+ /**
630
+ * Unlink from every source, for good. Without this, a source outlives the
631
+ * computation and keeps it (and everything its function captured) reachable
632
+ * through its observers set: a disposed list row stayed in memory that way.
633
+ * The last value stays readable; the node simply never updates again.
634
+ */
635
+ dispose() {
636
+ if (this._disposed) return;
637
+ this._disposed = true;
638
+ if (this.sources) {
639
+ for (const s of this.sources) s.observers?.delete(this);
640
+ this.sources = null;
641
+ }
642
+ }
628
643
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
629
644
  updateIfNecessary() {
645
+ if (this._disposed) return;
630
646
  if (this.state === CHECK) {
631
647
  if (this.sources) {
632
648
  for (const s of this.sources) {
@@ -670,7 +686,7 @@ var Computed = class extends Source {
670
686
  reportReactiveRun(this);
671
687
  if (changed) {
672
688
  this.version++;
673
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
689
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
674
690
  notifyWatchers(this);
675
691
  }
676
692
  }
@@ -684,7 +700,7 @@ var Computed = class extends Source {
684
700
  const wasClean = this.state === CLEAN;
685
701
  this.state = level;
686
702
  if (wasClean) {
687
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
703
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
688
704
  notifyWatchers(this);
689
705
  }
690
706
  }
@@ -723,6 +739,10 @@ var Watcher = class {
723
739
  this._pending.clear();
724
740
  return out;
725
741
  }
742
+ /** Drain pending without the array, for callers that only need it empty. */
743
+ clearPending() {
744
+ this._pending.clear();
745
+ }
726
746
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
727
747
  _notify(node) {
728
748
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -833,14 +853,14 @@ var EffectNode = class {
833
853
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
834
854
  });
835
855
  });
836
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
856
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
837
857
  this.watcher = new Watcher(() => enqueue(this));
838
858
  this.run();
839
859
  this.watcher.watch(this.computed);
840
860
  }
841
861
  run() {
842
862
  if (this.disposed) return;
843
- this.watcher.getPending();
863
+ this.watcher.clearPending();
844
864
  try {
845
865
  this.computed.get();
846
866
  } catch (e) {
@@ -857,6 +877,7 @@ var EffectNode = class {
857
877
  this.disposed = true;
858
878
  reportReactiveDispose(this.computed);
859
879
  this.watcher.unwatch(this.computed);
880
+ this.computed.dispose();
860
881
  disposeOwner(this.owner);
861
882
  }
862
883
  };
@@ -895,13 +916,15 @@ function createSignal(value, options) {
895
916
  },
896
917
  enumerable: false
897
918
  });
898
- reportReactiveNode({
899
- kind: "signal",
900
- node: state,
901
- handle: read,
902
- name: options?.name,
903
- set: write
904
- });
919
+ if (isObserved()) {
920
+ reportReactiveNode({
921
+ kind: "signal",
922
+ node: state,
923
+ handle: read,
924
+ name: options?.name,
925
+ set: write
926
+ });
927
+ }
905
928
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
906
929
  return [read, write];
907
930
  }
@@ -925,8 +948,11 @@ function createMemo(fn, value, options) {
925
948
  const read = (() => c.get());
926
949
  Object.defineProperty(read, $SIGNAL2, { value: true, enumerable: true });
927
950
  Object.defineProperty(read, $MEMO2, { value: true, enumerable: true });
928
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
929
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
951
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
952
+ onCleanup(() => {
953
+ c.dispose();
954
+ if (isObserved()) reportReactiveDispose(c);
955
+ });
930
956
  return read;
931
957
  }
932
958
  function createEffect(fn, value, options) {
@@ -356,7 +356,7 @@ var Observable = class extends Subscription {
356
356
 
357
357
  // src/lib/signal/graph/detect.ts
358
358
  var __BROWSER_PROD__ = typeof window === "undefined" ? false : window.__FLUIXI_PROD__ === true;
359
- var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : false;
359
+ var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : process.env.NODE_ENV !== "production";
360
360
  var $SIGNAL = /* @__PURE__ */ Symbol.for("signal");
361
361
  var $MEMO = /* @__PURE__ */ Symbol.for("memo");
362
362
  var $RESOURCE = /* @__PURE__ */ Symbol.for("resource");
@@ -390,7 +390,7 @@ function makeArrayFlat(values) {
390
390
  }
391
391
 
392
392
  // src/version.generated.ts
393
- var VERSION = "1.0.0-alpha.104";
393
+ var VERSION = "1.0.0-alpha.106";
394
394
 
395
395
  // src/lib/signal/graph/state.ts
396
396
  var MAJOR = VERSION.split(".")[0];
@@ -532,6 +532,7 @@ var Computed = class extends Source {
532
532
  super();
533
533
  this._threw = false;
534
534
  this._initialized = false;
535
+ this._disposed = false;
535
536
  this.state = DIRTY;
536
537
  /** Nodes this computation read on its last run. Rebuilt on every run. */
537
538
  this.sources = null;
@@ -547,8 +548,23 @@ var Computed = class extends Source {
547
548
  if (this._threw) throw this._error;
548
549
  return this._value;
549
550
  }
551
+ /**
552
+ * Unlink from every source, for good. Without this, a source outlives the
553
+ * computation and keeps it (and everything its function captured) reachable
554
+ * through its observers set: a disposed list row stayed in memory that way.
555
+ * The last value stays readable; the node simply never updates again.
556
+ */
557
+ dispose() {
558
+ if (this._disposed) return;
559
+ this._disposed = true;
560
+ if (this.sources) {
561
+ for (const s of this.sources) s.observers?.delete(this);
562
+ this.sources = null;
563
+ }
564
+ }
550
565
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
551
566
  updateIfNecessary() {
567
+ if (this._disposed) return;
552
568
  if (this.state === CHECK) {
553
569
  if (this.sources) {
554
570
  for (const s of this.sources) {
@@ -592,7 +608,7 @@ var Computed = class extends Source {
592
608
  reportReactiveRun(this);
593
609
  if (changed) {
594
610
  this.version++;
595
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
611
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
596
612
  notifyWatchers(this);
597
613
  }
598
614
  }
@@ -606,7 +622,7 @@ var Computed = class extends Source {
606
622
  const wasClean = this.state === CLEAN;
607
623
  this.state = level;
608
624
  if (wasClean) {
609
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
625
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
610
626
  notifyWatchers(this);
611
627
  }
612
628
  }
@@ -645,6 +661,10 @@ var Watcher = class {
645
661
  this._pending.clear();
646
662
  return out;
647
663
  }
664
+ /** Drain pending without the array, for callers that only need it empty. */
665
+ clearPending() {
666
+ this._pending.clear();
667
+ }
648
668
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
649
669
  _notify(node) {
650
670
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -755,14 +775,14 @@ var EffectNode = class {
755
775
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
756
776
  });
757
777
  });
758
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
778
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
759
779
  this.watcher = new Watcher(() => enqueue(this));
760
780
  this.run();
761
781
  this.watcher.watch(this.computed);
762
782
  }
763
783
  run() {
764
784
  if (this.disposed) return;
765
- this.watcher.getPending();
785
+ this.watcher.clearPending();
766
786
  try {
767
787
  this.computed.get();
768
788
  } catch (e) {
@@ -779,6 +799,7 @@ var EffectNode = class {
779
799
  this.disposed = true;
780
800
  reportReactiveDispose(this.computed);
781
801
  this.watcher.unwatch(this.computed);
802
+ this.computed.dispose();
782
803
  disposeOwner(this.owner);
783
804
  }
784
805
  };
@@ -817,13 +838,15 @@ function createSignal(value, options) {
817
838
  },
818
839
  enumerable: false
819
840
  });
820
- reportReactiveNode({
821
- kind: "signal",
822
- node: state,
823
- handle: read,
824
- name: options?.name,
825
- set: write
826
- });
841
+ if (isObserved()) {
842
+ reportReactiveNode({
843
+ kind: "signal",
844
+ node: state,
845
+ handle: read,
846
+ name: options?.name,
847
+ set: write
848
+ });
849
+ }
827
850
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
828
851
  return [read, write];
829
852
  }
@@ -847,8 +870,11 @@ function createMemo(fn, value, options) {
847
870
  const read = (() => c.get());
848
871
  Object.defineProperty(read, $SIGNAL2, { value: true, enumerable: true });
849
872
  Object.defineProperty(read, $MEMO2, { value: true, enumerable: true });
850
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
851
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
873
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
874
+ onCleanup(() => {
875
+ c.dispose();
876
+ if (isObserved()) reportReactiveDispose(c);
877
+ });
852
878
  return read;
853
879
  }
854
880
  function createEffect(fn, value, options) {
@@ -1,4 +1,4 @@
1
- /*! @fluixi/reactive v1.0.0-alpha.104 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/reactive v1.0.0-alpha.106 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -45,7 +45,7 @@ __export(utilities_exports, {
45
45
  module.exports = __toCommonJS(utilities_exports);
46
46
 
47
47
  // src/version.generated.ts
48
- var VERSION = "1.0.0-alpha.104";
48
+ var VERSION = "1.0.0-alpha.106";
49
49
 
50
50
  // src/lib/signal/graph/state.ts
51
51
  var MAJOR = VERSION.split(".")[0];
@@ -187,6 +187,7 @@ var Computed = class extends Source {
187
187
  super();
188
188
  this._threw = false;
189
189
  this._initialized = false;
190
+ this._disposed = false;
190
191
  this.state = DIRTY;
191
192
  /** Nodes this computation read on its last run. Rebuilt on every run. */
192
193
  this.sources = null;
@@ -202,8 +203,23 @@ var Computed = class extends Source {
202
203
  if (this._threw) throw this._error;
203
204
  return this._value;
204
205
  }
206
+ /**
207
+ * Unlink from every source, for good. Without this, a source outlives the
208
+ * computation and keeps it (and everything its function captured) reachable
209
+ * through its observers set: a disposed list row stayed in memory that way.
210
+ * The last value stays readable; the node simply never updates again.
211
+ */
212
+ dispose() {
213
+ if (this._disposed) return;
214
+ this._disposed = true;
215
+ if (this.sources) {
216
+ for (const s of this.sources) s.observers?.delete(this);
217
+ this.sources = null;
218
+ }
219
+ }
205
220
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
206
221
  updateIfNecessary() {
222
+ if (this._disposed) return;
207
223
  if (this.state === CHECK) {
208
224
  if (this.sources) {
209
225
  for (const s of this.sources) {
@@ -247,7 +263,7 @@ var Computed = class extends Source {
247
263
  reportReactiveRun(this);
248
264
  if (changed) {
249
265
  this.version++;
250
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
266
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
251
267
  notifyWatchers(this);
252
268
  }
253
269
  }
@@ -261,7 +277,7 @@ var Computed = class extends Source {
261
277
  const wasClean = this.state === CLEAN;
262
278
  this.state = level;
263
279
  if (wasClean) {
264
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
280
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
265
281
  notifyWatchers(this);
266
282
  }
267
283
  }
@@ -300,6 +316,10 @@ var Watcher = class {
300
316
  this._pending.clear();
301
317
  return out;
302
318
  }
319
+ /** Drain pending without the array, for callers that only need it empty. */
320
+ clearPending() {
321
+ this._pending.clear();
322
+ }
303
323
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
304
324
  _notify(node) {
305
325
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -410,14 +430,14 @@ var EffectNode = class {
410
430
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
411
431
  });
412
432
  });
413
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
433
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
414
434
  this.watcher = new Watcher(() => enqueue(this));
415
435
  this.run();
416
436
  this.watcher.watch(this.computed);
417
437
  }
418
438
  run() {
419
439
  if (this.disposed) return;
420
- this.watcher.getPending();
440
+ this.watcher.clearPending();
421
441
  try {
422
442
  this.computed.get();
423
443
  } catch (e) {
@@ -434,6 +454,7 @@ var EffectNode = class {
434
454
  this.disposed = true;
435
455
  reportReactiveDispose(this.computed);
436
456
  this.watcher.unwatch(this.computed);
457
+ this.computed.dispose();
437
458
  disposeOwner(this.owner);
438
459
  }
439
460
  };
@@ -472,13 +493,15 @@ function createSignal(value, options) {
472
493
  },
473
494
  enumerable: false
474
495
  });
475
- reportReactiveNode({
476
- kind: "signal",
477
- node: state,
478
- handle: read,
479
- name: options?.name,
480
- set: write
481
- });
496
+ if (isObserved()) {
497
+ reportReactiveNode({
498
+ kind: "signal",
499
+ node: state,
500
+ handle: read,
501
+ name: options?.name,
502
+ set: write
503
+ });
504
+ }
482
505
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
483
506
  return [read, write];
484
507
  }
@@ -502,8 +525,11 @@ function createMemo(fn, value, options) {
502
525
  const read = (() => c.get());
503
526
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
504
527
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
505
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
506
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
528
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
529
+ onCleanup(() => {
530
+ c.dispose();
531
+ if (isObserved()) reportReactiveDispose(c);
532
+ });
507
533
  return read;
508
534
  }
509
535
  function createEffect(fn, value, options) {