@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
@@ -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;
@@ -42,7 +42,7 @@ __export(compat_exports, {
42
42
  module.exports = __toCommonJS(compat_exports);
43
43
 
44
44
  // src/version.generated.ts
45
- var VERSION = "1.0.0-alpha.104";
45
+ var VERSION = "1.0.0-alpha.106";
46
46
 
47
47
  // src/lib/signal/graph/state.ts
48
48
  var MAJOR = VERSION.split(".")[0];
@@ -184,6 +184,7 @@ var Computed = class extends Source {
184
184
  super();
185
185
  this._threw = false;
186
186
  this._initialized = false;
187
+ this._disposed = false;
187
188
  this.state = DIRTY;
188
189
  /** Nodes this computation read on its last run. Rebuilt on every run. */
189
190
  this.sources = null;
@@ -199,8 +200,23 @@ var Computed = class extends Source {
199
200
  if (this._threw) throw this._error;
200
201
  return this._value;
201
202
  }
203
+ /**
204
+ * Unlink from every source, for good. Without this, a source outlives the
205
+ * computation and keeps it (and everything its function captured) reachable
206
+ * through its observers set: a disposed list row stayed in memory that way.
207
+ * The last value stays readable; the node simply never updates again.
208
+ */
209
+ dispose() {
210
+ if (this._disposed) return;
211
+ this._disposed = true;
212
+ if (this.sources) {
213
+ for (const s of this.sources) s.observers?.delete(this);
214
+ this.sources = null;
215
+ }
216
+ }
202
217
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
203
218
  updateIfNecessary() {
219
+ if (this._disposed) return;
204
220
  if (this.state === CHECK) {
205
221
  if (this.sources) {
206
222
  for (const s of this.sources) {
@@ -244,7 +260,7 @@ var Computed = class extends Source {
244
260
  reportReactiveRun(this);
245
261
  if (changed) {
246
262
  this.version++;
247
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
263
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
248
264
  notifyWatchers(this);
249
265
  }
250
266
  }
@@ -258,7 +274,7 @@ var Computed = class extends Source {
258
274
  const wasClean = this.state === CLEAN;
259
275
  this.state = level;
260
276
  if (wasClean) {
261
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
277
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
262
278
  notifyWatchers(this);
263
279
  }
264
280
  }
@@ -297,6 +313,10 @@ var Watcher = class {
297
313
  this._pending.clear();
298
314
  return out;
299
315
  }
316
+ /** Drain pending without the array, for callers that only need it empty. */
317
+ clearPending() {
318
+ this._pending.clear();
319
+ }
300
320
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
301
321
  _notify(node) {
302
322
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -440,14 +460,14 @@ var EffectNode = class {
440
460
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
441
461
  });
442
462
  });
443
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
463
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
444
464
  this.watcher = new Watcher(() => enqueue(this));
445
465
  this.run();
446
466
  this.watcher.watch(this.computed);
447
467
  }
448
468
  run() {
449
469
  if (this.disposed) return;
450
- this.watcher.getPending();
470
+ this.watcher.clearPending();
451
471
  try {
452
472
  this.computed.get();
453
473
  } catch (e) {
@@ -464,6 +484,7 @@ var EffectNode = class {
464
484
  this.disposed = true;
465
485
  reportReactiveDispose(this.computed);
466
486
  this.watcher.unwatch(this.computed);
487
+ this.computed.dispose();
467
488
  disposeOwner(this.owner);
468
489
  }
469
490
  };
@@ -502,13 +523,15 @@ function createSignal(value, options) {
502
523
  },
503
524
  enumerable: false
504
525
  });
505
- reportReactiveNode({
506
- kind: "signal",
507
- node: state,
508
- handle: read,
509
- name: options?.name,
510
- set: write
511
- });
526
+ if (isObserved()) {
527
+ reportReactiveNode({
528
+ kind: "signal",
529
+ node: state,
530
+ handle: read,
531
+ name: options?.name,
532
+ set: write
533
+ });
534
+ }
512
535
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
513
536
  return [read, write];
514
537
  }
@@ -532,8 +555,11 @@ function createMemo(fn, value, options) {
532
555
  const read = (() => c.get());
533
556
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
534
557
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
535
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
536
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
558
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
559
+ onCleanup(() => {
560
+ c.dispose();
561
+ if (isObserved()) reportReactiveDispose(c);
562
+ });
537
563
  return read;
538
564
  }
539
565
  function createEffect(fn, value, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/compat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/compat.ts"],"names":[],"mappings":"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"}
@@ -57,13 +57,17 @@ export function createSignal(value, options) {
57
57
  },
58
58
  enumerable: false,
59
59
  });
60
- reportReactiveNode({
61
- kind: 'signal',
62
- node: state,
63
- handle: read,
64
- name: options?.name,
65
- set: write,
66
- });
60
+ // The report object is built only when someone is listening: at list scale
61
+ // an unobserved build per signal is creation cost with no reader.
62
+ if (isObserved()) {
63
+ reportReactiveNode({
64
+ kind: 'signal',
65
+ node: state,
66
+ handle: read,
67
+ name: options?.name,
68
+ set: write,
69
+ });
70
+ }
67
71
  // A signal goes when its owner does, the same as a memo. A list rebuilt without a key
68
72
  // makes one per row per update, and a watcher that is never told keeps every one of them.
69
73
  if (isObserved())
@@ -99,11 +103,16 @@ export function createMemo(fn, value, options) {
99
103
  // as reactive, caches it as a component, and freezes it -> updates never apply.
100
104
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
101
105
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
102
- reportReactiveNode({ kind: 'memo', node: c, handle: read, name: options?.name });
103
- // A memo has no dispose of its own, it goes when its owner does. Without saying so, a
104
- // list that re-renders leaves every row it ever had in a watcher's graph.
105
106
  if (isObserved())
106
- l1onCleanup(() => reportReactiveDispose(c));
107
+ reportReactiveNode({ kind: 'memo', node: c, handle: read, name: options?.name });
108
+ // A memo goes when its owner does: unlink it from its sources, or a signal
109
+ // that outlives the list keeps every row it ever had in its observers set.
110
+ // Registered whether or not a watcher is looking; the report itself is not.
111
+ l1onCleanup(() => {
112
+ c.dispose();
113
+ if (isObserved())
114
+ reportReactiveDispose(c);
115
+ });
107
116
  return read;
108
117
  }
109
118
  /**
@@ -1,7 +1,7 @@
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
 
3
3
  // src/version.generated.ts
4
- var VERSION = "1.0.0-alpha.104";
4
+ var VERSION = "1.0.0-alpha.106";
5
5
 
6
6
  // src/lib/signal/graph/state.ts
7
7
  var MAJOR = VERSION.split(".")[0];
@@ -143,6 +143,7 @@ var Computed = class extends Source {
143
143
  super();
144
144
  this._threw = false;
145
145
  this._initialized = false;
146
+ this._disposed = false;
146
147
  this.state = DIRTY;
147
148
  /** Nodes this computation read on its last run. Rebuilt on every run. */
148
149
  this.sources = null;
@@ -158,8 +159,23 @@ var Computed = class extends Source {
158
159
  if (this._threw) throw this._error;
159
160
  return this._value;
160
161
  }
162
+ /**
163
+ * Unlink from every source, for good. Without this, a source outlives the
164
+ * computation and keeps it (and everything its function captured) reachable
165
+ * through its observers set: a disposed list row stayed in memory that way.
166
+ * The last value stays readable; the node simply never updates again.
167
+ */
168
+ dispose() {
169
+ if (this._disposed) return;
170
+ this._disposed = true;
171
+ if (this.sources) {
172
+ for (const s of this.sources) s.observers?.delete(this);
173
+ this.sources = null;
174
+ }
175
+ }
161
176
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
162
177
  updateIfNecessary() {
178
+ if (this._disposed) return;
163
179
  if (this.state === CHECK) {
164
180
  if (this.sources) {
165
181
  for (const s of this.sources) {
@@ -203,7 +219,7 @@ var Computed = class extends Source {
203
219
  reportReactiveRun(this);
204
220
  if (changed) {
205
221
  this.version++;
206
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
222
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
207
223
  notifyWatchers(this);
208
224
  }
209
225
  }
@@ -217,7 +233,7 @@ var Computed = class extends Source {
217
233
  const wasClean = this.state === CLEAN;
218
234
  this.state = level;
219
235
  if (wasClean) {
220
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
236
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
221
237
  notifyWatchers(this);
222
238
  }
223
239
  }
@@ -256,6 +272,10 @@ var Watcher = class {
256
272
  this._pending.clear();
257
273
  return out;
258
274
  }
275
+ /** Drain pending without the array, for callers that only need it empty. */
276
+ clearPending() {
277
+ this._pending.clear();
278
+ }
259
279
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
260
280
  _notify(node) {
261
281
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -399,14 +419,14 @@ var EffectNode = class {
399
419
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
400
420
  });
401
421
  });
402
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
422
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
403
423
  this.watcher = new Watcher(() => enqueue(this));
404
424
  this.run();
405
425
  this.watcher.watch(this.computed);
406
426
  }
407
427
  run() {
408
428
  if (this.disposed) return;
409
- this.watcher.getPending();
429
+ this.watcher.clearPending();
410
430
  try {
411
431
  this.computed.get();
412
432
  } catch (e) {
@@ -423,6 +443,7 @@ var EffectNode = class {
423
443
  this.disposed = true;
424
444
  reportReactiveDispose(this.computed);
425
445
  this.watcher.unwatch(this.computed);
446
+ this.computed.dispose();
426
447
  disposeOwner(this.owner);
427
448
  }
428
449
  };
@@ -461,13 +482,15 @@ function createSignal(value, options) {
461
482
  },
462
483
  enumerable: false
463
484
  });
464
- reportReactiveNode({
465
- kind: "signal",
466
- node: state,
467
- handle: read,
468
- name: options?.name,
469
- set: write
470
- });
485
+ if (isObserved()) {
486
+ reportReactiveNode({
487
+ kind: "signal",
488
+ node: state,
489
+ handle: read,
490
+ name: options?.name,
491
+ set: write
492
+ });
493
+ }
471
494
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
472
495
  return [read, write];
473
496
  }
@@ -491,8 +514,11 @@ function createMemo(fn, value, options) {
491
514
  const read = (() => c.get());
492
515
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
493
516
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
494
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
495
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
517
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
518
+ onCleanup(() => {
519
+ c.dispose();
520
+ if (isObserved()) reportReactiveDispose(c);
521
+ });
496
522
  return read;
497
523
  }
498
524
  function createEffect(fn, value, options) {
@@ -25,7 +25,7 @@ __export(context_children_exports, {
25
25
  module.exports = __toCommonJS(context_children_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];
@@ -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];
@@ -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;
@@ -31,7 +31,7 @@ __export(core_exports, {
31
31
  module.exports = __toCommonJS(core_exports);
32
32
 
33
33
  // src/version.generated.ts
34
- var VERSION = "1.0.0-alpha.104";
34
+ var VERSION = "1.0.0-alpha.106";
35
35
 
36
36
  // src/lib/signal/graph/state.ts
37
37
  var MAJOR = VERSION.split(".")[0];
@@ -157,6 +157,7 @@ var Computed = class extends Source {
157
157
  super();
158
158
  this._threw = false;
159
159
  this._initialized = false;
160
+ this._disposed = false;
160
161
  this.state = DIRTY;
161
162
  /** Nodes this computation read on its last run. Rebuilt on every run. */
162
163
  this.sources = null;
@@ -172,8 +173,23 @@ var Computed = class extends Source {
172
173
  if (this._threw) throw this._error;
173
174
  return this._value;
174
175
  }
176
+ /**
177
+ * Unlink from every source, for good. Without this, a source outlives the
178
+ * computation and keeps it (and everything its function captured) reachable
179
+ * through its observers set: a disposed list row stayed in memory that way.
180
+ * The last value stays readable; the node simply never updates again.
181
+ */
182
+ dispose() {
183
+ if (this._disposed) return;
184
+ this._disposed = true;
185
+ if (this.sources) {
186
+ for (const s of this.sources) s.observers?.delete(this);
187
+ this.sources = null;
188
+ }
189
+ }
175
190
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
176
191
  updateIfNecessary() {
192
+ if (this._disposed) return;
177
193
  if (this.state === CHECK) {
178
194
  if (this.sources) {
179
195
  for (const s of this.sources) {
@@ -217,7 +233,7 @@ var Computed = class extends Source {
217
233
  reportReactiveRun(this);
218
234
  if (changed) {
219
235
  this.version++;
220
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
236
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
221
237
  notifyWatchers(this);
222
238
  }
223
239
  }
@@ -231,7 +247,7 @@ var Computed = class extends Source {
231
247
  const wasClean = this.state === CLEAN;
232
248
  this.state = level;
233
249
  if (wasClean) {
234
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
250
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
235
251
  notifyWatchers(this);
236
252
  }
237
253
  }
@@ -270,6 +286,10 @@ var Watcher = class {
270
286
  this._pending.clear();
271
287
  return out;
272
288
  }
289
+ /** Drain pending without the array, for callers that only need it empty. */
290
+ clearPending() {
291
+ this._pending.clear();
292
+ }
273
293
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
274
294
  _notify(node) {
275
295
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -52,6 +52,7 @@ export declare class Computed<T> extends Source {
52
52
  private _error;
53
53
  private _threw;
54
54
  private _initialized;
55
+ private _disposed;
55
56
  state: NodeState;
56
57
  /** Nodes this computation read on its last run. Rebuilt on every run. */
57
58
  sources: Set<Source> | null;
@@ -61,6 +62,13 @@ export declare class Computed<T> extends Source {
61
62
  equals?: ((a: T, b: T) => boolean) | false;
62
63
  });
63
64
  get(): T;
65
+ /**
66
+ * Unlink from every source, for good. Without this, a source outlives the
67
+ * computation and keeps it (and everything its function captured) reachable
68
+ * through its observers set: a disposed list row stayed in memory that way.
69
+ * The last value stays readable; the node simply never updates again.
70
+ */
71
+ dispose(): void;
64
72
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
65
73
  updateIfNecessary(): void;
66
74
  private recompute;
@@ -86,6 +94,8 @@ export declare class Watcher {
86
94
  unwatch(...nodes: Source[]): void;
87
95
  /** Watched nodes that may have changed since the last getPending(). */
88
96
  getPending(): Source[];
97
+ /** Drain pending without the array, for callers that only need it empty. */
98
+ clearPending(): void;
89
99
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
90
100
  _notify(node: Source): void;
91
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/core.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/core.ts"],"names":[],"mappings":"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"}
@@ -91,6 +91,7 @@ export class Computed extends Source {
91
91
  _error;
92
92
  _threw = false;
93
93
  _initialized = false;
94
+ _disposed = false;
94
95
  state = DIRTY;
95
96
  /** Nodes this computation read on its last run. Rebuilt on every run. */
96
97
  sources = null;
@@ -112,8 +113,26 @@ export class Computed extends Source {
112
113
  throw this._error;
113
114
  return this._value;
114
115
  }
116
+ /**
117
+ * Unlink from every source, for good. Without this, a source outlives the
118
+ * computation and keeps it (and everything its function captured) reachable
119
+ * through its observers set: a disposed list row stayed in memory that way.
120
+ * The last value stays readable; the node simply never updates again.
121
+ */
122
+ dispose() {
123
+ if (this._disposed)
124
+ return;
125
+ this._disposed = true;
126
+ if (this.sources) {
127
+ for (const s of this.sources)
128
+ s.observers?.delete(this);
129
+ this.sources = null;
130
+ }
131
+ }
115
132
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
116
133
  updateIfNecessary() {
134
+ if (this._disposed)
135
+ return;
117
136
  if (this.state === CHECK) {
118
137
  // Pull each source; if one recomputes to a new value it will have flipped
119
138
  // us to DIRTY (markDirty), so stop early.
@@ -169,8 +188,10 @@ export class Computed extends Source {
169
188
  if (changed) {
170
189
  this.version++;
171
190
  // Our value moved -> observers that were merely CHECK must now recompute.
191
+ // Marking runs no user code and never recomputes, so the set is walked
192
+ // as it stands; State.set is the loop that needs a copy, not this one.
172
193
  if (this.observers)
173
- for (const o of [...this.observers])
194
+ for (const o of this.observers)
174
195
  o.markDirty(DIRTY);
175
196
  notifyWatchers(this);
176
197
  }
@@ -187,7 +208,7 @@ export class Computed extends Source {
187
208
  this.state = level;
188
209
  if (wasClean) {
189
210
  if (this.observers)
190
- for (const o of [...this.observers])
211
+ for (const o of this.observers)
191
212
  o.markDirty(CHECK);
192
213
  notifyWatchers(this);
193
214
  }
@@ -236,6 +257,10 @@ export class Watcher {
236
257
  this._pending.clear();
237
258
  return out;
238
259
  }
260
+ /** Drain pending without the array, for callers that only need it empty. */
261
+ clearPending() {
262
+ this._pending.clear();
263
+ }
239
264
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
240
265
  _notify(node) {
241
266
  if (!this._watched.has(node) || this._pending.has(node))
@@ -1,7 +1,7 @@
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
 
3
3
  // src/version.generated.ts
4
- var VERSION = "1.0.0-alpha.104";
4
+ var VERSION = "1.0.0-alpha.106";
5
5
 
6
6
  // src/lib/signal/graph/state.ts
7
7
  var MAJOR = VERSION.split(".")[0];
@@ -127,6 +127,7 @@ var Computed = class extends Source {
127
127
  super();
128
128
  this._threw = false;
129
129
  this._initialized = false;
130
+ this._disposed = false;
130
131
  this.state = DIRTY;
131
132
  /** Nodes this computation read on its last run. Rebuilt on every run. */
132
133
  this.sources = null;
@@ -142,8 +143,23 @@ var Computed = class extends Source {
142
143
  if (this._threw) throw this._error;
143
144
  return this._value;
144
145
  }
146
+ /**
147
+ * Unlink from every source, for good. Without this, a source outlives the
148
+ * computation and keeps it (and everything its function captured) reachable
149
+ * through its observers set: a disposed list row stayed in memory that way.
150
+ * The last value stays readable; the node simply never updates again.
151
+ */
152
+ dispose() {
153
+ if (this._disposed) return;
154
+ this._disposed = true;
155
+ if (this.sources) {
156
+ for (const s of this.sources) s.observers?.delete(this);
157
+ this.sources = null;
158
+ }
159
+ }
145
160
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
146
161
  updateIfNecessary() {
162
+ if (this._disposed) return;
147
163
  if (this.state === CHECK) {
148
164
  if (this.sources) {
149
165
  for (const s of this.sources) {
@@ -187,7 +203,7 @@ var Computed = class extends Source {
187
203
  reportReactiveRun(this);
188
204
  if (changed) {
189
205
  this.version++;
190
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
206
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
191
207
  notifyWatchers(this);
192
208
  }
193
209
  }
@@ -201,7 +217,7 @@ var Computed = class extends Source {
201
217
  const wasClean = this.state === CLEAN;
202
218
  this.state = level;
203
219
  if (wasClean) {
204
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
220
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
205
221
  notifyWatchers(this);
206
222
  }
207
223
  }
@@ -240,6 +256,10 @@ var Watcher = class {
240
256
  this._pending.clear();
241
257
  return out;
242
258
  }
259
+ /** Drain pending without the array, for callers that only need it empty. */
260
+ clearPending() {
261
+ this._pending.clear();
262
+ }
243
263
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
244
264
  _notify(node) {
245
265
  if (!this._watched.has(node) || this._pending.has(node)) return;