@fluixi/reactive 1.0.0-alpha.105 → 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
@@ -32,7 +32,7 @@ __export(api_exports, {
32
32
  module.exports = __toCommonJS(api_exports);
33
33
 
34
34
  // src/version.generated.ts
35
- var VERSION = "1.0.0-alpha.105";
35
+ var VERSION = "1.0.0-alpha.106";
36
36
 
37
37
  // src/lib/signal/graph/state.ts
38
38
  var MAJOR = VERSION.split(".")[0];
@@ -182,6 +182,7 @@ var Computed = class extends Source {
182
182
  super();
183
183
  this._threw = false;
184
184
  this._initialized = false;
185
+ this._disposed = false;
185
186
  this.state = DIRTY;
186
187
  /** Nodes this computation read on its last run. Rebuilt on every run. */
187
188
  this.sources = null;
@@ -197,8 +198,23 @@ var Computed = class extends Source {
197
198
  if (this._threw) throw this._error;
198
199
  return this._value;
199
200
  }
201
+ /**
202
+ * Unlink from every source, for good. Without this, a source outlives the
203
+ * computation and keeps it (and everything its function captured) reachable
204
+ * through its observers set: a disposed list row stayed in memory that way.
205
+ * The last value stays readable; the node simply never updates again.
206
+ */
207
+ dispose() {
208
+ if (this._disposed) return;
209
+ this._disposed = true;
210
+ if (this.sources) {
211
+ for (const s of this.sources) s.observers?.delete(this);
212
+ this.sources = null;
213
+ }
214
+ }
200
215
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
201
216
  updateIfNecessary() {
217
+ if (this._disposed) return;
202
218
  if (this.state === CHECK) {
203
219
  if (this.sources) {
204
220
  for (const s of this.sources) {
@@ -242,7 +258,7 @@ var Computed = class extends Source {
242
258
  reportReactiveRun(this);
243
259
  if (changed) {
244
260
  this.version++;
245
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
261
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
246
262
  notifyWatchers(this);
247
263
  }
248
264
  }
@@ -256,7 +272,7 @@ var Computed = class extends Source {
256
272
  const wasClean = this.state === CLEAN;
257
273
  this.state = level;
258
274
  if (wasClean) {
259
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
275
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
260
276
  notifyWatchers(this);
261
277
  }
262
278
  }
@@ -295,6 +311,10 @@ var Watcher = class {
295
311
  this._pending.clear();
296
312
  return out;
297
313
  }
314
+ /** Drain pending without the array, for callers that only need it empty. */
315
+ clearPending() {
316
+ this._pending.clear();
317
+ }
298
318
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
299
319
  _notify(node) {
300
320
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -448,14 +468,14 @@ var EffectNode = class {
448
468
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
449
469
  });
450
470
  });
451
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
471
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
452
472
  this.watcher = new Watcher(() => enqueue(this));
453
473
  this.run();
454
474
  this.watcher.watch(this.computed);
455
475
  }
456
476
  run() {
457
477
  if (this.disposed) return;
458
- this.watcher.getPending();
478
+ this.watcher.clearPending();
459
479
  try {
460
480
  this.computed.get();
461
481
  } catch (e) {
@@ -472,6 +492,7 @@ var EffectNode = class {
472
492
  this.disposed = true;
473
493
  reportReactiveDispose(this.computed);
474
494
  this.watcher.unwatch(this.computed);
495
+ this.computed.dispose();
475
496
  disposeOwner(this.owner);
476
497
  }
477
498
  };
@@ -510,13 +531,15 @@ function createSignal(value, options) {
510
531
  },
511
532
  enumerable: false
512
533
  });
513
- reportReactiveNode({
514
- kind: "signal",
515
- node: state,
516
- handle: read,
517
- name: options?.name,
518
- set: write
519
- });
534
+ if (isObserved()) {
535
+ reportReactiveNode({
536
+ kind: "signal",
537
+ node: state,
538
+ handle: read,
539
+ name: options?.name,
540
+ set: write
541
+ });
542
+ }
520
543
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
521
544
  return [read, write];
522
545
  }
@@ -540,8 +563,11 @@ function createMemo(fn, value, options) {
540
563
  const read = (() => c.get());
541
564
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
542
565
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
543
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
544
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
566
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
567
+ onCleanup(() => {
568
+ c.dispose();
569
+ if (isObserved()) reportReactiveDispose(c);
570
+ });
545
571
  return read;
546
572
  }
547
573
  function createEffect(fn, value, options) {
@@ -1,5 +1,5 @@
1
1
  // src/version.generated.ts
2
- var VERSION = "1.0.0-alpha.105";
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];
@@ -149,6 +149,7 @@ var Computed = class extends Source {
149
149
  super();
150
150
  this._threw = false;
151
151
  this._initialized = false;
152
+ this._disposed = false;
152
153
  this.state = DIRTY;
153
154
  /** Nodes this computation read on its last run. Rebuilt on every run. */
154
155
  this.sources = null;
@@ -164,8 +165,23 @@ var Computed = class extends Source {
164
165
  if (this._threw) throw this._error;
165
166
  return this._value;
166
167
  }
168
+ /**
169
+ * Unlink from every source, for good. Without this, a source outlives the
170
+ * computation and keeps it (and everything its function captured) reachable
171
+ * through its observers set: a disposed list row stayed in memory that way.
172
+ * The last value stays readable; the node simply never updates again.
173
+ */
174
+ dispose() {
175
+ if (this._disposed) return;
176
+ this._disposed = true;
177
+ if (this.sources) {
178
+ for (const s of this.sources) s.observers?.delete(this);
179
+ this.sources = null;
180
+ }
181
+ }
167
182
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
168
183
  updateIfNecessary() {
184
+ if (this._disposed) return;
169
185
  if (this.state === CHECK) {
170
186
  if (this.sources) {
171
187
  for (const s of this.sources) {
@@ -209,7 +225,7 @@ var Computed = class extends Source {
209
225
  reportReactiveRun(this);
210
226
  if (changed) {
211
227
  this.version++;
212
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
228
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
213
229
  notifyWatchers(this);
214
230
  }
215
231
  }
@@ -223,7 +239,7 @@ var Computed = class extends Source {
223
239
  const wasClean = this.state === CLEAN;
224
240
  this.state = level;
225
241
  if (wasClean) {
226
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
242
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
227
243
  notifyWatchers(this);
228
244
  }
229
245
  }
@@ -262,6 +278,10 @@ var Watcher = class {
262
278
  this._pending.clear();
263
279
  return out;
264
280
  }
281
+ /** Drain pending without the array, for callers that only need it empty. */
282
+ clearPending() {
283
+ this._pending.clear();
284
+ }
265
285
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
266
286
  _notify(node) {
267
287
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -415,14 +435,14 @@ var EffectNode = class {
415
435
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
416
436
  });
417
437
  });
418
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
438
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
419
439
  this.watcher = new Watcher(() => enqueue(this));
420
440
  this.run();
421
441
  this.watcher.watch(this.computed);
422
442
  }
423
443
  run() {
424
444
  if (this.disposed) return;
425
- this.watcher.getPending();
445
+ this.watcher.clearPending();
426
446
  try {
427
447
  this.computed.get();
428
448
  } catch (e) {
@@ -439,6 +459,7 @@ var EffectNode = class {
439
459
  this.disposed = true;
440
460
  reportReactiveDispose(this.computed);
441
461
  this.watcher.unwatch(this.computed);
462
+ this.computed.dispose();
442
463
  disposeOwner(this.owner);
443
464
  }
444
465
  };
@@ -477,13 +498,15 @@ function createSignal(value, options) {
477
498
  },
478
499
  enumerable: false
479
500
  });
480
- reportReactiveNode({
481
- kind: "signal",
482
- node: state,
483
- handle: read,
484
- name: options?.name,
485
- set: write
486
- });
501
+ if (isObserved()) {
502
+ reportReactiveNode({
503
+ kind: "signal",
504
+ node: state,
505
+ handle: read,
506
+ name: options?.name,
507
+ set: write
508
+ });
509
+ }
487
510
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
488
511
  return [read, write];
489
512
  }
@@ -507,8 +530,11 @@ function createMemo(fn, value, options) {
507
530
  const read = (() => c.get());
508
531
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
509
532
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
510
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
511
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
533
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
534
+ onCleanup(() => {
535
+ c.dispose();
536
+ if (isObserved()) reportReactiveDispose(c);
537
+ });
512
538
  return read;
513
539
  }
514
540
  function createEffect(fn, value, options) {
@@ -1,4 +1,4 @@
1
- /*! @fluixi/reactive v1.0.0-alpha.105 | (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;
@@ -28,7 +28,7 @@ __export(cached_resource_exports, {
28
28
  module.exports = __toCommonJS(cached_resource_exports);
29
29
 
30
30
  // src/version.generated.ts
31
- var VERSION = "1.0.0-alpha.105";
31
+ var VERSION = "1.0.0-alpha.106";
32
32
 
33
33
  // src/lib/signal/graph/state.ts
34
34
  var MAJOR = VERSION.split(".")[0];
@@ -170,6 +170,7 @@ var Computed = class extends Source {
170
170
  super();
171
171
  this._threw = false;
172
172
  this._initialized = false;
173
+ this._disposed = false;
173
174
  this.state = DIRTY;
174
175
  /** Nodes this computation read on its last run. Rebuilt on every run. */
175
176
  this.sources = null;
@@ -185,8 +186,23 @@ var Computed = class extends Source {
185
186
  if (this._threw) throw this._error;
186
187
  return this._value;
187
188
  }
189
+ /**
190
+ * Unlink from every source, for good. Without this, a source outlives the
191
+ * computation and keeps it (and everything its function captured) reachable
192
+ * through its observers set: a disposed list row stayed in memory that way.
193
+ * The last value stays readable; the node simply never updates again.
194
+ */
195
+ dispose() {
196
+ if (this._disposed) return;
197
+ this._disposed = true;
198
+ if (this.sources) {
199
+ for (const s of this.sources) s.observers?.delete(this);
200
+ this.sources = null;
201
+ }
202
+ }
188
203
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
189
204
  updateIfNecessary() {
205
+ if (this._disposed) return;
190
206
  if (this.state === CHECK) {
191
207
  if (this.sources) {
192
208
  for (const s of this.sources) {
@@ -230,7 +246,7 @@ var Computed = class extends Source {
230
246
  reportReactiveRun(this);
231
247
  if (changed) {
232
248
  this.version++;
233
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
249
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
234
250
  notifyWatchers(this);
235
251
  }
236
252
  }
@@ -244,7 +260,7 @@ var Computed = class extends Source {
244
260
  const wasClean = this.state === CLEAN;
245
261
  this.state = level;
246
262
  if (wasClean) {
247
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
263
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
248
264
  notifyWatchers(this);
249
265
  }
250
266
  }
@@ -283,6 +299,10 @@ var Watcher = class {
283
299
  this._pending.clear();
284
300
  return out;
285
301
  }
302
+ /** Drain pending without the array, for callers that only need it empty. */
303
+ clearPending() {
304
+ this._pending.clear();
305
+ }
286
306
  /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
287
307
  _notify(node) {
288
308
  if (!this._watched.has(node) || this._pending.has(node)) return;
@@ -400,14 +420,14 @@ var EffectNode = class {
400
420
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
401
421
  });
402
422
  });
403
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
423
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
404
424
  this.watcher = new Watcher(() => enqueue(this));
405
425
  this.run();
406
426
  this.watcher.watch(this.computed);
407
427
  }
408
428
  run() {
409
429
  if (this.disposed) return;
410
- this.watcher.getPending();
430
+ this.watcher.clearPending();
411
431
  try {
412
432
  this.computed.get();
413
433
  } catch (e) {
@@ -424,6 +444,7 @@ var EffectNode = class {
424
444
  this.disposed = true;
425
445
  reportReactiveDispose(this.computed);
426
446
  this.watcher.unwatch(this.computed);
447
+ this.computed.dispose();
427
448
  disposeOwner(this.owner);
428
449
  }
429
450
  };
@@ -458,13 +479,15 @@ function createSignal(value, options) {
458
479
  },
459
480
  enumerable: false
460
481
  });
461
- reportReactiveNode({
462
- kind: "signal",
463
- node: state,
464
- handle: read,
465
- name: options?.name,
466
- set: write
467
- });
482
+ if (isObserved()) {
483
+ reportReactiveNode({
484
+ kind: "signal",
485
+ node: state,
486
+ handle: read,
487
+ name: options?.name,
488
+ set: write
489
+ });
490
+ }
468
491
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
469
492
  return [read, write];
470
493
  }
@@ -1,7 +1,7 @@
1
- /*! @fluixi/reactive v1.0.0-alpha.105 | (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.105";
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;
@@ -373,14 +393,14 @@ var EffectNode = class {
373
393
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
374
394
  });
375
395
  });
376
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
396
+ if (isObserved()) reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
377
397
  this.watcher = new Watcher(() => enqueue(this));
378
398
  this.run();
379
399
  this.watcher.watch(this.computed);
380
400
  }
381
401
  run() {
382
402
  if (this.disposed) return;
383
- this.watcher.getPending();
403
+ this.watcher.clearPending();
384
404
  try {
385
405
  this.computed.get();
386
406
  } catch (e) {
@@ -397,6 +417,7 @@ var EffectNode = class {
397
417
  this.disposed = true;
398
418
  reportReactiveDispose(this.computed);
399
419
  this.watcher.unwatch(this.computed);
420
+ this.computed.dispose();
400
421
  disposeOwner(this.owner);
401
422
  }
402
423
  };
@@ -431,13 +452,15 @@ function createSignal(value, options) {
431
452
  },
432
453
  enumerable: false
433
454
  });
434
- reportReactiveNode({
435
- kind: "signal",
436
- node: state,
437
- handle: read,
438
- name: options?.name,
439
- set: write
440
- });
455
+ if (isObserved()) {
456
+ reportReactiveNode({
457
+ kind: "signal",
458
+ node: state,
459
+ handle: read,
460
+ name: options?.name,
461
+ set: write
462
+ });
463
+ }
441
464
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
442
465
  return [read, write];
443
466
  }
@@ -25,7 +25,7 @@ __export(children_exports, {
25
25
  module.exports = __toCommonJS(children_exports);
26
26
 
27
27
  // src/version.generated.ts
28
- var VERSION = "1.0.0-alpha.105";
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];
@@ -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
  }
@@ -290,14 +306,17 @@ function createMemo(fn, value, options) {
290
306
  const read = (() => c.get());
291
307
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
292
308
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
293
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
294
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
309
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
310
+ onCleanup(() => {
311
+ c.dispose();
312
+ if (isObserved()) reportReactiveDispose(c);
313
+ });
295
314
  return read;
296
315
  }
297
316
 
298
317
  // src/lib/signal/graph/detect.ts
299
318
  var __BROWSER_PROD__ = typeof window === "undefined" ? false : window.__FLUIXI_PROD__ === true;
300
- var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : false;
319
+ var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : process.env.NODE_ENV !== "production";
301
320
  var $SIGNAL2 = /* @__PURE__ */ Symbol.for("signal");
302
321
  function isSignal(value) {
303
322
  return typeof value === "function" && value[$SIGNAL2] === true;
@@ -1,5 +1,5 @@
1
1
  // src/version.generated.ts
2
- var VERSION = "1.0.0-alpha.105";
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];
@@ -115,6 +115,7 @@ var Computed = class extends Source {
115
115
  super();
116
116
  this._threw = false;
117
117
  this._initialized = false;
118
+ this._disposed = false;
118
119
  this.state = DIRTY;
119
120
  /** Nodes this computation read on its last run. Rebuilt on every run. */
120
121
  this.sources = null;
@@ -130,8 +131,23 @@ var Computed = class extends Source {
130
131
  if (this._threw) throw this._error;
131
132
  return this._value;
132
133
  }
134
+ /**
135
+ * Unlink from every source, for good. Without this, a source outlives the
136
+ * computation and keeps it (and everything its function captured) reachable
137
+ * through its observers set: a disposed list row stayed in memory that way.
138
+ * The last value stays readable; the node simply never updates again.
139
+ */
140
+ dispose() {
141
+ if (this._disposed) return;
142
+ this._disposed = true;
143
+ if (this.sources) {
144
+ for (const s of this.sources) s.observers?.delete(this);
145
+ this.sources = null;
146
+ }
147
+ }
133
148
  /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
134
149
  updateIfNecessary() {
150
+ if (this._disposed) return;
135
151
  if (this.state === CHECK) {
136
152
  if (this.sources) {
137
153
  for (const s of this.sources) {
@@ -175,7 +191,7 @@ var Computed = class extends Source {
175
191
  reportReactiveRun(this);
176
192
  if (changed) {
177
193
  this.version++;
178
- if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
194
+ if (this.observers) for (const o of this.observers) o.markDirty(DIRTY);
179
195
  notifyWatchers(this);
180
196
  }
181
197
  }
@@ -189,7 +205,7 @@ var Computed = class extends Source {
189
205
  const wasClean = this.state === CLEAN;
190
206
  this.state = level;
191
207
  if (wasClean) {
192
- if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
208
+ if (this.observers) for (const o of this.observers) o.markDirty(CHECK);
193
209
  notifyWatchers(this);
194
210
  }
195
211
  }
@@ -264,14 +280,17 @@ function createMemo(fn, value, options) {
264
280
  const read = (() => c.get());
265
281
  Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
266
282
  Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
267
- reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
268
- if (isObserved()) onCleanup(() => reportReactiveDispose(c));
283
+ if (isObserved()) reportReactiveNode({ kind: "memo", node: c, handle: read, name: options?.name });
284
+ onCleanup(() => {
285
+ c.dispose();
286
+ if (isObserved()) reportReactiveDispose(c);
287
+ });
269
288
  return read;
270
289
  }
271
290
 
272
291
  // src/lib/signal/graph/detect.ts
273
292
  var __BROWSER_PROD__ = typeof window === "undefined" ? false : window.__FLUIXI_PROD__ === true;
274
- var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : false;
293
+ var __DEV__ = typeof process === "undefined" ? __BROWSER_PROD__ : process.env.NODE_ENV !== "production";
275
294
  var $SIGNAL2 = /* @__PURE__ */ Symbol.for("signal");
276
295
  function isSignal(value) {
277
296
  return typeof value === "function" && value[$SIGNAL2] === true;