@pyreon/reactivity 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,7 @@ batch(() => {
55
55
 
56
56
  ### Scopes
57
57
 
58
- - **`effectScope(): EffectScope`** -- Creates a scope that collects effects for bulk disposal.
58
+ - **`effectScope(): EffectScope`** -- Creates a scope that collects effects for bulk disposal. Internal arrays (`_effects`, `_updateHooks`) are lazy-allocated on first use -- scopes with no effects cost only the object itself.
59
59
  - **`getCurrentScope(): EffectScope | undefined`** -- Returns the active effect scope.
60
60
  - **`setCurrentScope(scope)`** -- Manually sets the current effect scope.
61
61
 
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"20eb7817-1","name":"batch.ts"},{"uid":"20eb7817-3","name":"cell.ts"},{"uid":"20eb7817-5","name":"scope.ts"},{"uid":"20eb7817-7","name":"tracking.ts"},{"uid":"20eb7817-9","name":"effect.ts"},{"uid":"20eb7817-11","name":"computed.ts"},{"uid":"20eb7817-13","name":"createSelector.ts"},{"uid":"20eb7817-15","name":"debug.ts"},{"uid":"20eb7817-17","name":"signal.ts"},{"uid":"20eb7817-19","name":"store.ts"},{"uid":"20eb7817-21","name":"reconcile.ts"},{"uid":"20eb7817-23","name":"resource.ts"},{"uid":"20eb7817-25","name":"watch.ts"},{"uid":"20eb7817-27","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"20eb7817-1":{"renderedLength":953,"gzipLength":489,"brotliLength":0,"metaUid":"20eb7817-0"},"20eb7817-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"20eb7817-2"},"20eb7817-5":{"renderedLength":1787,"gzipLength":764,"brotliLength":0,"metaUid":"20eb7817-4"},"20eb7817-7":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"20eb7817-6"},"20eb7817-9":{"renderedLength":4535,"gzipLength":1610,"brotliLength":0,"metaUid":"20eb7817-8"},"20eb7817-11":{"renderedLength":3761,"gzipLength":1177,"brotliLength":0,"metaUid":"20eb7817-10"},"20eb7817-13":{"renderedLength":1810,"gzipLength":833,"brotliLength":0,"metaUid":"20eb7817-12"},"20eb7817-15":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"20eb7817-14"},"20eb7817-17":{"renderedLength":2322,"gzipLength":1077,"brotliLength":0,"metaUid":"20eb7817-16"},"20eb7817-19":{"renderedLength":2879,"gzipLength":1056,"brotliLength":0,"metaUid":"20eb7817-18"},"20eb7817-21":{"renderedLength":2109,"gzipLength":867,"brotliLength":0,"metaUid":"20eb7817-20"},"20eb7817-23":{"renderedLength":1029,"gzipLength":475,"brotliLength":0,"metaUid":"20eb7817-22"},"20eb7817-25":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"20eb7817-24"},"20eb7817-27":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"20eb7817-26"}},"nodeMetas":{"20eb7817-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"20eb7817-1"},"imported":[],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-16"},{"uid":"20eb7817-6"}]},"20eb7817-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"20eb7817-3"},"imported":[],"importedBy":[{"uid":"20eb7817-26"}]},"20eb7817-4":{"id":"/src/scope.ts","moduleParts":{"index.js":"20eb7817-5"},"imported":[],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-10"},{"uid":"20eb7817-8"}]},"20eb7817-6":{"id":"/src/tracking.ts","moduleParts":{"index.js":"20eb7817-7"},"imported":[{"uid":"20eb7817-0"}],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-10"},{"uid":"20eb7817-12"},{"uid":"20eb7817-8"},{"uid":"20eb7817-22"},{"uid":"20eb7817-16"}]},"20eb7817-8":{"id":"/src/effect.ts","moduleParts":{"index.js":"20eb7817-9"},"imported":[{"uid":"20eb7817-4"},{"uid":"20eb7817-6"}],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-10"},{"uid":"20eb7817-12"},{"uid":"20eb7817-22"},{"uid":"20eb7817-24"}]},"20eb7817-10":{"id":"/src/computed.ts","moduleParts":{"index.js":"20eb7817-11"},"imported":[{"uid":"20eb7817-8"},{"uid":"20eb7817-4"},{"uid":"20eb7817-6"}],"importedBy":[{"uid":"20eb7817-26"}]},"20eb7817-12":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"20eb7817-13"},"imported":[{"uid":"20eb7817-8"},{"uid":"20eb7817-6"}],"importedBy":[{"uid":"20eb7817-26"}]},"20eb7817-14":{"id":"/src/debug.ts","moduleParts":{"index.js":"20eb7817-15"},"imported":[],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-16"}]},"20eb7817-16":{"id":"/src/signal.ts","moduleParts":{"index.js":"20eb7817-17"},"imported":[{"uid":"20eb7817-0"},{"uid":"20eb7817-14"},{"uid":"20eb7817-6"}],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-22"},{"uid":"20eb7817-18"}]},"20eb7817-18":{"id":"/src/store.ts","moduleParts":{"index.js":"20eb7817-19"},"imported":[{"uid":"20eb7817-16"}],"importedBy":[{"uid":"20eb7817-26"},{"uid":"20eb7817-20"}]},"20eb7817-20":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"20eb7817-21"},"imported":[{"uid":"20eb7817-18"}],"importedBy":[{"uid":"20eb7817-26"}]},"20eb7817-22":{"id":"/src/resource.ts","moduleParts":{"index.js":"20eb7817-23"},"imported":[{"uid":"20eb7817-8"},{"uid":"20eb7817-16"},{"uid":"20eb7817-6"}],"importedBy":[{"uid":"20eb7817-26"}]},"20eb7817-24":{"id":"/src/watch.ts","moduleParts":{"index.js":"20eb7817-25"},"imported":[{"uid":"20eb7817-8"}],"importedBy":[{"uid":"20eb7817-26"}]},"20eb7817-26":{"id":"/src/index.ts","moduleParts":{"index.js":"20eb7817-27"},"imported":[{"uid":"20eb7817-0"},{"uid":"20eb7817-2"},{"uid":"20eb7817-10"},{"uid":"20eb7817-12"},{"uid":"20eb7817-14"},{"uid":"20eb7817-8"},{"uid":"20eb7817-20"},{"uid":"20eb7817-22"},{"uid":"20eb7817-4"},{"uid":"20eb7817-16"},{"uid":"20eb7817-18"},{"uid":"20eb7817-6"},{"uid":"20eb7817-24"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"1ddc8440-1","name":"batch.ts"},{"uid":"1ddc8440-3","name":"cell.ts"},{"uid":"1ddc8440-5","name":"scope.ts"},{"uid":"1ddc8440-7","name":"tracking.ts"},{"uid":"1ddc8440-9","name":"effect.ts"},{"uid":"1ddc8440-11","name":"computed.ts"},{"uid":"1ddc8440-13","name":"createSelector.ts"},{"uid":"1ddc8440-15","name":"debug.ts"},{"uid":"1ddc8440-17","name":"signal.ts"},{"uid":"1ddc8440-19","name":"store.ts"},{"uid":"1ddc8440-21","name":"reconcile.ts"},{"uid":"1ddc8440-23","name":"resource.ts"},{"uid":"1ddc8440-25","name":"watch.ts"},{"uid":"1ddc8440-27","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"1ddc8440-1":{"renderedLength":1079,"gzipLength":520,"brotliLength":0,"metaUid":"1ddc8440-0"},"1ddc8440-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"1ddc8440-2"},"1ddc8440-5":{"renderedLength":1977,"gzipLength":786,"brotliLength":0,"metaUid":"1ddc8440-4"},"1ddc8440-7":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"1ddc8440-6"},"1ddc8440-9":{"renderedLength":5389,"gzipLength":1826,"brotliLength":0,"metaUid":"1ddc8440-8"},"1ddc8440-11":{"renderedLength":4068,"gzipLength":1253,"brotliLength":0,"metaUid":"1ddc8440-10"},"1ddc8440-13":{"renderedLength":1810,"gzipLength":833,"brotliLength":0,"metaUid":"1ddc8440-12"},"1ddc8440-15":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"1ddc8440-14"},"1ddc8440-17":{"renderedLength":2671,"gzipLength":1169,"brotliLength":0,"metaUid":"1ddc8440-16"},"1ddc8440-19":{"renderedLength":2879,"gzipLength":1056,"brotliLength":0,"metaUid":"1ddc8440-18"},"1ddc8440-21":{"renderedLength":2109,"gzipLength":867,"brotliLength":0,"metaUid":"1ddc8440-20"},"1ddc8440-23":{"renderedLength":1029,"gzipLength":475,"brotliLength":0,"metaUid":"1ddc8440-22"},"1ddc8440-25":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"1ddc8440-24"},"1ddc8440-27":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"1ddc8440-26"}},"nodeMetas":{"1ddc8440-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"1ddc8440-1"},"imported":[],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-16"},{"uid":"1ddc8440-6"}]},"1ddc8440-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"1ddc8440-3"},"imported":[],"importedBy":[{"uid":"1ddc8440-26"}]},"1ddc8440-4":{"id":"/src/scope.ts","moduleParts":{"index.js":"1ddc8440-5"},"imported":[],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-10"},{"uid":"1ddc8440-8"}]},"1ddc8440-6":{"id":"/src/tracking.ts","moduleParts":{"index.js":"1ddc8440-7"},"imported":[{"uid":"1ddc8440-0"}],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-10"},{"uid":"1ddc8440-12"},{"uid":"1ddc8440-8"},{"uid":"1ddc8440-22"},{"uid":"1ddc8440-16"}]},"1ddc8440-8":{"id":"/src/effect.ts","moduleParts":{"index.js":"1ddc8440-9"},"imported":[{"uid":"1ddc8440-4"},{"uid":"1ddc8440-6"}],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-10"},{"uid":"1ddc8440-12"},{"uid":"1ddc8440-22"},{"uid":"1ddc8440-24"}]},"1ddc8440-10":{"id":"/src/computed.ts","moduleParts":{"index.js":"1ddc8440-11"},"imported":[{"uid":"1ddc8440-8"},{"uid":"1ddc8440-4"},{"uid":"1ddc8440-6"}],"importedBy":[{"uid":"1ddc8440-26"}]},"1ddc8440-12":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"1ddc8440-13"},"imported":[{"uid":"1ddc8440-8"},{"uid":"1ddc8440-6"}],"importedBy":[{"uid":"1ddc8440-26"}]},"1ddc8440-14":{"id":"/src/debug.ts","moduleParts":{"index.js":"1ddc8440-15"},"imported":[],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-16"}]},"1ddc8440-16":{"id":"/src/signal.ts","moduleParts":{"index.js":"1ddc8440-17"},"imported":[{"uid":"1ddc8440-0"},{"uid":"1ddc8440-14"},{"uid":"1ddc8440-6"}],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-22"},{"uid":"1ddc8440-18"}]},"1ddc8440-18":{"id":"/src/store.ts","moduleParts":{"index.js":"1ddc8440-19"},"imported":[{"uid":"1ddc8440-16"}],"importedBy":[{"uid":"1ddc8440-26"},{"uid":"1ddc8440-20"}]},"1ddc8440-20":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"1ddc8440-21"},"imported":[{"uid":"1ddc8440-18"}],"importedBy":[{"uid":"1ddc8440-26"}]},"1ddc8440-22":{"id":"/src/resource.ts","moduleParts":{"index.js":"1ddc8440-23"},"imported":[{"uid":"1ddc8440-8"},{"uid":"1ddc8440-16"},{"uid":"1ddc8440-6"}],"importedBy":[{"uid":"1ddc8440-26"}]},"1ddc8440-24":{"id":"/src/watch.ts","moduleParts":{"index.js":"1ddc8440-25"},"imported":[{"uid":"1ddc8440-8"}],"importedBy":[{"uid":"1ddc8440-26"}]},"1ddc8440-26":{"id":"/src/index.ts","moduleParts":{"index.js":"1ddc8440-27"},"imported":[{"uid":"1ddc8440-0"},{"uid":"1ddc8440-2"},{"uid":"1ddc8440-10"},{"uid":"1ddc8440-12"},{"uid":"1ddc8440-14"},{"uid":"1ddc8440-8"},{"uid":"1ddc8440-20"},{"uid":"1ddc8440-22"},{"uid":"1ddc8440-4"},{"uid":"1ddc8440-16"},{"uid":"1ddc8440-18"},{"uid":"1ddc8440-6"},{"uid":"1ddc8440-24"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -10,10 +10,17 @@ function batch(fn) {
10
10
  } finally {
11
11
  batchDepth--;
12
12
  if (batchDepth === 0 && pendingNotifications.size > 0) {
13
- const flush = pendingNotifications;
14
- pendingNotifications = flush === setA ? setB : setA;
15
- for (const notify of flush) notify();
16
- flush.clear();
13
+ batchDepth = 1;
14
+ try {
15
+ while (pendingNotifications.size > 0) {
16
+ const flush = pendingNotifications;
17
+ pendingNotifications = flush === setA ? setB : setA;
18
+ for (const notify of flush) notify();
19
+ flush.clear();
20
+ }
21
+ } finally {
22
+ batchDepth = 0;
23
+ }
17
24
  }
18
25
  }
19
26
  }
@@ -102,13 +109,15 @@ function cell(value) {
102
109
  //#endregion
103
110
  //#region src/scope.ts
104
111
  var EffectScope = class {
105
- _effects = [];
112
+ _effects = null;
106
113
  _active = true;
107
- _updateHooks = [];
114
+ _updateHooks = null;
108
115
  _updatePending = false;
109
116
  /** Register an effect/computed to be disposed when this scope stops. */
110
117
  add(e) {
111
- if (this._active) this._effects.push(e);
118
+ if (!this._active) return;
119
+ if (this._effects === null) this._effects = [];
120
+ this._effects.push(e);
112
121
  }
113
122
  /**
114
123
  * Temporarily re-activate this scope so effects created inside `fn` are
@@ -127,6 +136,7 @@ var EffectScope = class {
127
136
  }
128
137
  /** Register a callback to run after any reactive update in this scope. */
129
138
  addUpdateHook(fn) {
139
+ if (this._updateHooks === null) this._updateHooks = [];
130
140
  this._updateHooks.push(fn);
131
141
  }
132
142
  /**
@@ -134,11 +144,11 @@ var EffectScope = class {
134
144
  * Schedules onUpdate hooks via microtask so all synchronous effects settle first.
135
145
  */
136
146
  notifyEffectRan() {
137
- if (!this._active || this._updateHooks.length === 0 || this._updatePending) return;
147
+ if (!this._active || !this._updateHooks || this._updateHooks.length === 0 || this._updatePending) return;
138
148
  this._updatePending = true;
139
149
  queueMicrotask(() => {
140
150
  this._updatePending = false;
141
- if (!this._active) return;
151
+ if (!this._active || !this._updateHooks) return;
142
152
  for (const fn of this._updateHooks) try {
143
153
  fn();
144
154
  } catch (err) {
@@ -149,9 +159,9 @@ var EffectScope = class {
149
159
  /** Dispose all tracked effects. */
150
160
  stop() {
151
161
  if (!this._active) return;
152
- for (const e of this._effects) e.dispose();
153
- this._effects = [];
154
- this._updateHooks = [];
162
+ if (this._effects) for (const e of this._effects) e.dispose();
163
+ this._effects = null;
164
+ this._updateHooks = null;
155
165
  this._updatePending = false;
156
166
  this._active = false;
157
167
  }
@@ -262,6 +272,7 @@ function runUntracked(fn) {
262
272
 
263
273
  //#endregion
264
274
  //#region src/effect.ts
275
+ const _countSink$2 = globalThis;
265
276
  let _cleanupCollector = null;
266
277
  /**
267
278
  * Register a cleanup function inside an effect. The cleanup runs:
@@ -283,6 +294,7 @@ let _cleanupCollector = null;
283
294
  function onCleanup(fn) {
284
295
  if (_cleanupCollector) _cleanupCollector.push(fn);
285
296
  }
297
+ let _innerEffectCollector = null;
286
298
  let _errorHandler = (err) => {
287
299
  console.error("[pyreon] Unhandled effect error:", err);
288
300
  };
@@ -306,7 +318,16 @@ function effect(fn) {
306
318
  let cleanup;
307
319
  const deps = [];
308
320
  let cleanups;
321
+ let innerEffects = null;
309
322
  const runCleanup = () => {
323
+ if (innerEffects) {
324
+ for (const ie of innerEffects) try {
325
+ ie.dispose();
326
+ } catch (err) {
327
+ _errorHandler(err);
328
+ }
329
+ innerEffects = null;
330
+ }
310
331
  if (cleanups) {
311
332
  for (const c of cleanups) try {
312
333
  c();
@@ -326,7 +347,11 @@ function effect(fn) {
326
347
  };
327
348
  const run = () => {
328
349
  if (disposed) return;
350
+ if (import.meta.env?.DEV === true) _countSink$2.__pyreon_count__?.("reactivity.effectRun");
329
351
  runCleanup();
352
+ const outerCollector = _innerEffectCollector;
353
+ const myInners = [];
354
+ _innerEffectCollector = myInners;
330
355
  try {
331
356
  cleanupLocalDeps$1(deps, run);
332
357
  setDepsCollector(deps);
@@ -340,7 +365,10 @@ function effect(fn) {
340
365
  _cleanupCollector = null;
341
366
  setDepsCollector(null);
342
367
  _errorHandler(err);
368
+ } finally {
369
+ _innerEffectCollector = outerCollector;
343
370
  }
371
+ if (myInners.length > 0) innerEffects = myInners;
344
372
  if (!isFirstRun) scope?.notifyEffectRan();
345
373
  isFirstRun = false;
346
374
  };
@@ -350,7 +378,8 @@ function effect(fn) {
350
378
  disposed = true;
351
379
  cleanupLocalDeps$1(deps, run);
352
380
  } };
353
- getCurrentScope()?.add(e);
381
+ if (_innerEffectCollector !== null) _innerEffectCollector.push(e);
382
+ else getCurrentScope()?.add(e);
354
383
  return e;
355
384
  }
356
385
  /**
@@ -419,9 +448,20 @@ function renderEffectFullTrack(deps, run, fn) {
419
448
  function renderEffect(fn) {
420
449
  const deps = [];
421
450
  let disposed = false;
451
+ let isFirstRun = true;
422
452
  const run = () => {
423
453
  if (disposed) return;
424
- renderEffectFullTrack(deps, run, fn);
454
+ if (isFirstRun) {
455
+ isFirstRun = false;
456
+ setDepsCollector(deps);
457
+ _setActiveEffect(run);
458
+ try {
459
+ fn();
460
+ } finally {
461
+ _restoreActiveEffect();
462
+ setDepsCollector(null);
463
+ }
464
+ } else renderEffectFullTrack(deps, run, fn);
425
465
  };
426
466
  run();
427
467
  const dispose = () => {
@@ -437,6 +477,7 @@ function renderEffect(fn) {
437
477
 
438
478
  //#endregion
439
479
  //#region src/computed.ts
480
+ const _countSink$1 = globalThis;
440
481
  /** Remove a computed from all dependency subscriber sets (local deps array). */
441
482
  function cleanupLocalDeps(deps, fn) {
442
483
  for (let i = 0; i < deps.length; i++) deps[i].delete(fn);
@@ -479,6 +520,7 @@ function computedLazy(fn) {
479
520
  const read = () => {
480
521
  trackSubscriber(host);
481
522
  if (dirty) {
523
+ if (import.meta.env?.DEV === true) _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
482
524
  try {
483
525
  if (tracked) {
484
526
  setSkipDepsCollection(true);
@@ -489,7 +531,6 @@ function computedLazy(fn) {
489
531
  tracked = true;
490
532
  }
491
533
  } catch (err) {
492
- setSkipDepsCollection(false);
493
534
  _errorHandler(err);
494
535
  }
495
536
  dirty = false;
@@ -535,6 +576,7 @@ function computedWithEquals(fn, equals) {
535
576
  let directFns = null;
536
577
  const recompute = () => {
537
578
  if (disposed) return;
579
+ if (import.meta.env?.DEV === true) _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
538
580
  cleanupLocalDeps(deps, recompute);
539
581
  try {
540
582
  const next = trackWithLocalDeps(deps, recompute, fn);
@@ -552,6 +594,7 @@ function computedWithEquals(fn, equals) {
552
594
  const read = () => {
553
595
  trackSubscriber(host);
554
596
  if (dirty) {
597
+ if (import.meta.env?.DEV === true) _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
555
598
  cleanupLocalDeps(deps, recompute);
556
599
  try {
557
600
  value = trackWithLocalDeps(deps, recompute, fn);
@@ -751,16 +794,23 @@ function inspectSignal(sig) {
751
794
  //#endregion
752
795
  //#region src/signal.ts
753
796
  const __DEV__ = typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
797
+ const _countSink = globalThis;
754
798
  function _peek() {
755
799
  return this._v;
756
800
  }
757
801
  function _set(newValue) {
758
802
  if (Object.is(this._v, newValue)) return;
803
+ if (import.meta.env?.DEV === true) _countSink.__pyreon_count__?.("reactivity.signalWrite");
759
804
  const prev = this._v;
760
805
  this._v = newValue;
761
806
  if (isTracing()) _notifyTraceListeners(this, prev, newValue);
762
- if (this._d) notifyDirect(this._d);
763
- if (this._s) notifySubscribers(this._s);
807
+ if (isBatching()) {
808
+ if (this._d) notifyDirect(this._d);
809
+ if (this._s) notifySubscribers(this._s);
810
+ } else batch(() => {
811
+ if (this._d) notifyDirect(this._d);
812
+ if (this._s) notifySubscribers(this._s);
813
+ });
764
814
  }
765
815
  function _update(fn) {
766
816
  _set.call(this, fn(this._v));
@@ -810,6 +860,7 @@ function _debug() {
810
860
  * update, subscribe) are shared across all signals — not per-signal closures.
811
861
  */
812
862
  function signal(initialValue, options) {
863
+ if (import.meta.env?.DEV === true) _countSink.__pyreon_count__?.("reactivity.signalCreate");
813
864
  const read = ((...args) => {
814
865
  if (__DEV__ && args.length > 0) console.warn("[Pyreon] signal() was called with an argument. Use signal.set(value) or signal.update(fn) to write. signal(value) only reads — the argument is ignored.");
815
866
  trackSubscriber(read);
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["cleanupLocalDeps"],"sources":["../src/batch.ts","../src/cell.ts","../src/scope.ts","../src/tracking.ts","../src/effect.ts","../src/computed.ts","../src/createSelector.ts","../src/debug.ts","../src/signal.ts","../src/store.ts","../src/reconcile.ts","../src/resource.ts","../src/watch.ts"],"sourcesContent":["// Batch multiple signal updates into a single notification pass.\n// Uses a Set so the same subscriber is never flushed more than once per batch,\n// even if multiple signals it depends on change within the same batch.\n\nlet batchDepth = 0\n\n// Two pre-allocated Sets swapped on each flush — avoids allocating a new Set()\n// on every batch exit. The \"active\" set collects enqueued notifications; on flush\n// we swap to the other set and iterate the captured one, then clear it for reuse.\nconst setA = new Set<() => void>()\nconst setB = new Set<() => void>()\nlet pendingNotifications = setA\n\nexport function batch(fn: () => void): void {\n batchDepth++\n try {\n fn()\n } finally {\n batchDepth--\n if (batchDepth === 0 && pendingNotifications.size > 0) {\n // Swap to the other pre-allocated Set before flushing so new enqueues\n // during notification land in the alternate Set, not mixed into the\n // current iteration.\n const flush = pendingNotifications\n pendingNotifications = flush === setA ? setB : setA\n for (const notify of flush) notify()\n flush.clear()\n }\n }\n}\n\nexport function isBatching(): boolean {\n return batchDepth > 0\n}\n\nexport function enqueuePendingNotification(notify: () => void): void {\n pendingNotifications.add(notify)\n}\n\n/**\n * Returns a Promise that resolves after all currently-pending microtasks have flushed.\n * Useful when you need to read the DOM after a batch of signal updates has settled.\n *\n * @example\n * count.set(1); count.set(2)\n * await nextTick()\n * // DOM is now up-to-date\n */\nexport function nextTick(): Promise<void> {\n return new Promise((resolve) => queueMicrotask(resolve))\n}\n","/**\n * Lightweight reactive cell — class-based alternative to signal().\n *\n * - 1 object allocation vs signal()'s 6 closures\n * - Same API surface: peek(), set(), update(), subscribe(), listen()\n * - NOT callable as a getter (no effect tracking) — use for fixed subscriptions\n * - Methods on prototype, shared across all instances\n * - Single-listener fast path: no Set allocated when ≤1 subscriber\n *\n * Use when you need reactive state but don't need automatic effect dependency tracking.\n * Ideal for list item labels in keyed reconcilers where subscribe() is used directly.\n */\nexport class Cell<T> {\n /** @internal */ _v: T\n /** @internal */ _l: (() => void) | null = null // single-listener fast path\n /** @internal */ _s: Set<() => void> | null = null // multi-listener fallback\n\n constructor(value: T) {\n this._v = value\n }\n\n peek(): T {\n return this._v\n }\n\n set(value: T): void {\n if (Object.is(this._v, value)) return\n this._v = value\n if (this._l) this._l()\n else if (this._s) for (const fn of this._s) fn()\n }\n\n update(fn: (current: T) => T): void {\n this.set(fn(this._v))\n }\n\n /**\n * Fire-and-forget subscription — no unsubscribe returned.\n * Use when the listener's lifetime matches the cell's (e.g., list rows).\n * Saves 1 closure allocation per call vs subscribe().\n */\n listen(listener: () => void): void {\n if (!this._l && !this._s) {\n this._l = listener\n } else {\n // Promote to Set\n if (!this._s) {\n this._s = new Set()\n if (this._l) {\n this._s.add(this._l)\n this._l = null\n }\n }\n this._s.add(listener)\n }\n }\n\n subscribe(listener: () => void): () => void {\n this.listen(listener)\n // The listener could be in _l (single) or _s (multi).\n // A later subscribe() call may promote it from _l to _s,\n // so the disposer must check both locations.\n return () => {\n if (this._l === listener) this._l = null\n else this._s?.delete(listener)\n }\n }\n}\n\nexport function cell<T>(value: T): Cell<T> {\n return new Cell(value)\n}\n","// EffectScope — auto-tracks effects created during a component's setup\n// and disposes them all at once when the component unmounts.\n\nexport class EffectScope {\n private _effects: { dispose(): void }[] = []\n private _active = true\n private _updateHooks: (() => void)[] = []\n private _updatePending = false\n\n /** Register an effect/computed to be disposed when this scope stops. */\n add(e: { dispose(): void }): void {\n if (this._active) this._effects.push(e)\n }\n\n /**\n * Temporarily re-activate this scope so effects created inside `fn` are\n * auto-tracked and will be disposed when the scope stops.\n * Used to ensure effects created in `onMount` callbacks belong to their\n * component's scope rather than leaking as global effects.\n */\n runInScope<T>(fn: () => T): T {\n const prev = _currentScope\n _currentScope = this\n try {\n return fn()\n } finally {\n _currentScope = prev\n }\n }\n\n /** Register a callback to run after any reactive update in this scope. */\n addUpdateHook(fn: () => void): void {\n this._updateHooks.push(fn)\n }\n\n /**\n * Called by effects after each non-initial re-run.\n * Schedules onUpdate hooks via microtask so all synchronous effects settle first.\n */\n notifyEffectRan(): void {\n if (!this._active || this._updateHooks.length === 0 || this._updatePending) return\n this._updatePending = true\n queueMicrotask(() => {\n this._updatePending = false\n if (!this._active) return\n for (const fn of this._updateHooks) {\n try {\n fn()\n } catch (err) {\n console.error('[pyreon] onUpdate hook error:', err)\n }\n }\n })\n }\n\n /** Dispose all tracked effects. */\n stop(): void {\n if (!this._active) return\n for (const e of this._effects) e.dispose()\n this._effects = []\n this._updateHooks = []\n this._updatePending = false\n this._active = false\n }\n}\n\nlet _currentScope: EffectScope | null = null\n\nexport function getCurrentScope(): EffectScope | null {\n return _currentScope\n}\n\nexport function setCurrentScope(scope: EffectScope | null): void {\n _currentScope = scope\n}\n\n/** Create a new EffectScope. */\nexport function effectScope(): EffectScope {\n return new EffectScope()\n}\n","// Global subscriber tracking context\n\nimport { enqueuePendingNotification, isBatching } from './batch'\n\nlet activeEffect: (() => void) | null = null\n\n// Tracks which subscriber sets each effect is registered in, so we can\n// clean them up before a re-run (dynamic dependency tracking).\nconst effectDeps = new WeakMap<() => void, Set<Set<() => void>>>()\n\n// Fast deps collector for renderEffect — avoids WeakMap overhead entirely.\n// When set, trackSubscriber pushes subscriber sets here instead of effectDeps.\nlet _depsCollector: Set<() => void>[] | null = null\n\n// Skip deps collection mode — for re-evaluating computeds/effects with static deps.\n// When true, trackSubscriber only does Set.add (no-op if already subscribed) and skips\n// the _depsCollector.push / WeakMap work entirely.\nlet _skipDepsCollection = false\n\nexport function setDepsCollector(collector: Set<() => void>[] | null): void {\n _depsCollector = collector\n}\n\nexport function setSkipDepsCollection(skip: boolean): void {\n _skipDepsCollection = skip\n}\n\n/**\n * Subscriber host — any reactive source that can have downstream subscribers.\n * Signals, computeds, and createSelector buckets all implement this interface.\n * The Set is created lazily — only allocated when an effect actually tracks this source.\n */\nexport interface SubscriberHost {\n /** @internal subscriber set — null until first tracked by an effect */\n _s: Set<() => void> | null\n}\n\n/**\n * Register the active effect as a subscriber of the given reactive source.\n * The subscriber Set is created lazily on the host — sources read only outside\n * effects never allocate a Set.\n */\nexport function trackSubscriber(host: SubscriberHost) {\n if (activeEffect) {\n if (!host._s) host._s = new Set()\n host._s.add(activeEffect)\n // Skip collection mode: we're already subscribed (Set.add is no-op),\n // just need activeEffect set for nested computed reads to work.\n if (_skipDepsCollection) return\n if (_depsCollector) {\n // Fast path: renderEffect stores deps inline, no WeakMap\n _depsCollector.push(host._s)\n } else {\n // Record this dep so we can remove it on cleanup\n let deps = effectDeps.get(activeEffect)\n if (!deps) {\n deps = new Set()\n effectDeps.set(activeEffect, deps)\n }\n deps.add(host._s)\n }\n }\n}\n\n/**\n * Remove an effect from every subscriber set it was registered in,\n * then clear its dep record. Call this before each re-run and on dispose.\n */\nexport function cleanupEffect(fn: () => void): void {\n const deps = effectDeps.get(fn)\n if (deps) {\n for (const sub of deps) sub.delete(fn)\n deps.clear()\n }\n}\n\nexport function notifySubscribers(subscribers: Set<() => void>) {\n if (subscribers.size === 0) return\n // Single-subscriber fast path: avoid any iteration overhead.\n if (subscribers.size === 1) {\n const sub = subscribers.values().next().value as () => void\n if (isBatching()) enqueuePendingNotification(sub)\n else sub()\n return\n }\n if (isBatching()) {\n // Effects are queued not run inline — no re-entrancy risk, iterate the live Set directly.\n for (const sub of subscribers) enqueuePendingNotification(sub)\n } else {\n // Effects run inline and may call cleanupEffect (removes) + trackSubscriber (re-adds).\n // Instead of snapshotting with [...subscribers] (allocates an array), we iterate the\n // live Set but cap iterations at the original size to prevent infinite loops from\n // re-inserted entries. This is safe because:\n // - cleanupEffect removes the effect from the Set (no double-fire)\n // - trackSubscriber may re-add it (but we stop after originalSize iterations)\n // - Any effects re-added during this pass are already up-to-date (just ran)\n const originalSize = subscribers.size\n let i = 0\n for (const sub of subscribers) {\n if (i >= originalSize) break\n sub()\n i++\n }\n }\n}\n\nexport function withTracking<T>(fn: () => void, compute: () => T): T {\n const prev = activeEffect\n activeEffect = fn\n try {\n return compute()\n } finally {\n activeEffect = prev\n }\n}\n\n// Stack for inlined tracking in renderEffect — avoids withTracking function call overhead.\nlet _prevEffect: (() => void) | null = null\n\nexport function _setActiveEffect(fn: () => void): void {\n _prevEffect = activeEffect\n activeEffect = fn\n}\n\nexport function _restoreActiveEffect(): void {\n activeEffect = _prevEffect\n _prevEffect = null\n}\n\n/** Read signals without subscribing. Alias: `untrack`. */\nexport function runUntracked<T>(fn: () => T): T {\n const prev = activeEffect\n activeEffect = null\n try {\n return fn()\n } finally {\n activeEffect = prev\n }\n}\n","import { getCurrentScope } from './scope'\nimport { _restoreActiveEffect, _setActiveEffect, setDepsCollector, withTracking } from './tracking'\n\nexport interface Effect {\n dispose(): void\n}\n\n// ─── onCleanup ───────────────────────────────────────────────────────────────\n// Thread-local collector for cleanup functions registered via onCleanup()\n// during effect execution. Pushed/popped around the user callback in effect().\nlet _cleanupCollector: (() => void)[] | null = null\n\n/**\n * Register a cleanup function inside an effect. The cleanup runs:\n * - Before the effect re-runs (when dependencies change)\n * - When the effect is disposed\n *\n * Can be called multiple times — all cleanups run in registration order.\n * Must be called synchronously during effect setup (like onMount/onUnmount).\n *\n * @example\n * effect(() => {\n * const controller = new AbortController()\n * onCleanup(() => controller.abort())\n * fetch(`/api/user/${userId()}`, { signal: controller.signal })\n * .then(r => r.json())\n * .then(data => user.set(data))\n * })\n */\nexport function onCleanup(fn: () => void): void {\n if (_cleanupCollector) {\n _cleanupCollector.push(fn)\n }\n}\n\n// Global error handler — called for unhandled errors thrown inside effects.\n// Defaults to console.error so silent failures are never swallowed.\nexport let _errorHandler: (err: unknown) => void = (err) => {\n console.error('[pyreon] Unhandled effect error:', err)\n}\n\nexport function setErrorHandler(fn: (err: unknown) => void): void {\n _errorHandler = fn\n}\n\n/** Remove an effect from all dependency subscriber sets (local deps array). */\nfunction cleanupLocalDeps(deps: Set<() => void>[], fn: () => void): void {\n if (deps.length === 1) {\n ;(deps[0] as Set<() => void>).delete(fn)\n deps.length = 0\n } else if (deps.length > 1) {\n for (let i = 0; i < deps.length; i++) (deps[i] as Set<() => void>).delete(fn)\n deps.length = 0\n }\n}\n\nexport function effect(fn: () => (() => void) | void): Effect {\n // Capture the scope at creation time — remains correct during future re-runs\n // even after setCurrentScope(null) has been called post-setup.\n const scope = getCurrentScope()\n let disposed = false\n let isFirstRun = true\n let cleanup: (() => void) | undefined\n // Local deps array — avoids WeakMap overhead (like renderEffect)\n const deps: Set<() => void>[] = []\n\n let cleanups: (() => void)[] | undefined\n\n const runCleanup = () => {\n if (cleanups) {\n for (const c of cleanups) {\n try {\n c()\n } catch (err) {\n _errorHandler(err)\n }\n }\n cleanups = undefined\n }\n if (typeof cleanup === 'function') {\n try {\n cleanup()\n } catch (err) {\n _errorHandler(err)\n }\n cleanup = undefined\n }\n }\n\n const run = () => {\n if (disposed) return\n // Run previous cleanup before re-running\n runCleanup()\n try {\n cleanupLocalDeps(deps, run)\n setDepsCollector(deps)\n // Collect onCleanup() registrations during execution\n const collected: (() => void)[] = []\n _cleanupCollector = collected\n cleanup = withTracking(run, fn) || undefined\n _cleanupCollector = null\n if (collected.length > 0) cleanups = collected\n setDepsCollector(null)\n } catch (err) {\n _cleanupCollector = null\n setDepsCollector(null)\n _errorHandler(err)\n }\n // Notify scope after each reactive re-run (not the initial synchronous run)\n // so onUpdate hooks fire after the DOM has settled.\n if (!isFirstRun) scope?.notifyEffectRan()\n isFirstRun = false\n }\n\n run()\n\n const e: Effect = {\n dispose() {\n runCleanup()\n disposed = true\n cleanupLocalDeps(deps, run)\n },\n }\n\n // Auto-register with the active EffectScope (if any)\n getCurrentScope()?.add(e)\n\n return e\n}\n\n/**\n * Lightweight effect for DOM render bindings.\n *\n * Differences from `effect()`:\n * - No EffectScope registration (caller owns the dispose lifecycle)\n * - No error handler (errors propagate naturally)\n * - No onUpdate notification\n * - Deps stored in a local array instead of the global WeakMap — faster\n * creation and disposal (~200ns saved per effect vs WeakMap path)\n *\n * Returns a dispose function (not an Effect object — saves 1 allocation).\n */\n/**\n * Static-dep binding — compiler helper for template expressions.\n *\n * Like renderEffect but assumes dependencies never change (true for all\n * compiler-emitted template bindings like `_tpl()` text/attribute updates).\n *\n * Tracks dependencies only on the first run. Re-runs skip cleanup, re-tracking,\n * and tracking context save/restore entirely — just calls `fn()` directly.\n *\n * Per re-run savings vs renderEffect:\n * - No deps iteration + Set.delete (cleanup)\n * - No setDepsCollector + withTracking (re-registration)\n * - Signal reads hit `if (activeEffect)` null check → instant return\n */\nexport function _bind(fn: () => void): () => void {\n const deps: Set<() => void>[] = []\n let disposed = false\n\n const run = () => {\n if (disposed) return\n fn()\n }\n\n // First run: track deps so we know what to unsubscribe on dispose\n setDepsCollector(deps)\n withTracking(run, fn)\n setDepsCollector(null)\n\n const dispose = () => {\n if (disposed) return\n disposed = true\n for (const s of deps) s.delete(run)\n deps.length = 0\n }\n\n // Auto-register with scope so template bindings are disposed during teardown\n getCurrentScope()?.add({ dispose })\n\n return dispose\n}\n\n/** Full re-track path for renderEffect: cleanup old deps, evaluate with tracking. */\nfunction renderEffectFullTrack(deps: Set<() => void>[], run: () => void, fn: () => void): void {\n if (deps.length === 1) {\n ;(deps[0] as Set<() => void>).delete(run)\n deps.length = 0\n } else if (deps.length > 1) {\n for (const s of deps) s.delete(run)\n deps.length = 0\n }\n setDepsCollector(deps)\n _setActiveEffect(run)\n try {\n fn()\n } finally {\n _restoreActiveEffect()\n setDepsCollector(null)\n }\n}\n\nexport function renderEffect(fn: () => void): () => void {\n const deps: Set<() => void>[] = []\n let disposed = false\n\n const run = () => {\n if (disposed) return\n renderEffectFullTrack(deps, run, fn)\n }\n\n run()\n\n const dispose = () => {\n if (disposed) return\n disposed = true\n if (deps.length === 1) {\n ;(deps[0] as Set<() => void>).delete(run)\n } else {\n for (const s of deps) s.delete(run)\n }\n deps.length = 0\n }\n\n // Auto-register with scope so render effects are disposed during teardown\n getCurrentScope()?.add({ dispose })\n\n return dispose\n}\n","import { _errorHandler } from './effect'\nimport { getCurrentScope } from './scope'\nimport {\n cleanupEffect,\n notifySubscribers,\n setDepsCollector,\n setSkipDepsCollection,\n trackSubscriber,\n withTracking,\n} from './tracking'\n\nexport interface Computed<T> {\n (): T\n /** Remove this computed from all its reactive dependencies. */\n dispose(): void\n /** Cached value for compiler-emitted direct bindings (_bindText, _bindDirect). */\n _v: T\n /** Register a direct updater — used by compiler-emitted _bindText/_bindDirect. */\n direct(updater: () => void): () => void\n}\n\nexport interface ComputedOptions<T> {\n /**\n * Custom equality function. When provided, the computed eagerly re-evaluates\n * on dependency change and only notifies downstream if `equals(prev, next)`\n * returns false. Useful for derived objects/arrays to skip spurious updates.\n *\n * @example\n * const sorted = computed(() => items().slice().sort(), {\n * equals: (a, b) => a.length === b.length && a.every((v, i) => v === b[i])\n * })\n */\n equals?: (prev: T, next: T) => boolean\n}\n\n/** Remove a computed from all dependency subscriber sets (local deps array). */\nfunction cleanupLocalDeps(deps: Set<() => void>[], fn: () => void): void {\n for (let i = 0; i < deps.length; i++) (deps[i] as Set<() => void>).delete(fn)\n deps.length = 0\n}\n\n/** Re-track dependencies using the local deps array collector. */\nfunction trackWithLocalDeps<T>(deps: Set<() => void>[], effect: () => void, fn: () => T): T {\n setDepsCollector(deps)\n const result = withTracking(effect, fn)\n setDepsCollector(null)\n return result\n}\n\nexport function computed<T>(fn: () => T, options?: ComputedOptions<T>): Computed<T> {\n return options?.equals ? computedWithEquals(fn, options.equals) : computedLazy(fn)\n}\n\n/**\n * Default computed — lazy evaluation with deferred cleanup.\n *\n * On notification: just marks dirty and propagates (no cleanup/re-track).\n * On read: cleans up old deps, re-evaluates, re-tracks.\n *\n * The `if (dirty) return` early exit in recompute prevents double-propagation\n * in diamond patterns (a→b,c→d: b notifies d, c tries to notify d again —\n * skipped because d is already dirty).\n */\nfunction computedLazy<T>(fn: () => T): Computed<T> {\n let value: T\n let dirty = true\n let disposed = false\n let tracked = false\n const deps: Set<() => void>[] = []\n const host: { _s: Set<() => void> | null } = { _s: null }\n let directFns: ((() => void) | null)[] | null = null\n\n const recompute = () => {\n if (disposed || dirty) return\n dirty = true\n if (host._s) notifySubscribers(host._s)\n if (directFns) for (const f of directFns) f?.()\n }\n\n const read = (): T => {\n trackSubscriber(host)\n if (dirty) {\n try {\n if (tracked) {\n setSkipDepsCollection(true)\n value = withTracking(recompute, fn)\n setSkipDepsCollection(false)\n } else {\n value = trackWithLocalDeps(deps, recompute, fn)\n tracked = true\n }\n } catch (err) {\n setSkipDepsCollection(false)\n _errorHandler(err)\n }\n dirty = false\n }\n return value as T\n }\n\n read.dispose = () => {\n disposed = true\n cleanupLocalDeps(deps, recompute)\n }\n\n Object.defineProperty(read, '_v', {\n get: () => {\n if (dirty) read() // ensure value is fresh\n return value\n },\n enumerable: false,\n })\n\n read.direct = (updater: () => void): (() => void) => {\n if (!directFns) directFns = []\n const arr = directFns\n const idx = arr.length\n arr.push(updater)\n return () => {\n arr[idx] = null\n }\n }\n\n getCurrentScope()?.add({ dispose: read.dispose })\n return read as Computed<T>\n}\n\n/**\n * Computed with custom equality — eager evaluation on notification.\n *\n * Re-evaluates immediately when deps change and only notifies downstream\n * if `equals(prev, next)` returns false.\n */\nfunction computedWithEquals<T>(fn: () => T, equals: (prev: T, next: T) => boolean): Computed<T> {\n let value: T\n let dirty = true\n let initialized = false\n let disposed = false\n const deps: Set<() => void>[] = []\n const host: { _s: Set<() => void> | null } = { _s: null }\n let directFns: ((() => void) | null)[] | null = null\n\n const recompute = () => {\n if (disposed) return\n cleanupLocalDeps(deps, recompute)\n try {\n const next = trackWithLocalDeps(deps, recompute, fn)\n if (initialized && equals(value as T, next)) return\n value = next\n dirty = false\n initialized = true\n } catch (err) {\n _errorHandler(err)\n return\n }\n if (host._s) notifySubscribers(host._s)\n if (directFns) for (const f of directFns) f?.()\n }\n\n const read = (): T => {\n trackSubscriber(host)\n if (dirty) {\n cleanupLocalDeps(deps, recompute)\n try {\n value = trackWithLocalDeps(deps, recompute, fn)\n } catch (err) {\n _errorHandler(err)\n }\n dirty = false\n initialized = true\n }\n return value as T\n }\n\n read.dispose = () => {\n disposed = true\n cleanupLocalDeps(deps, recompute)\n cleanupEffect(recompute)\n }\n\n Object.defineProperty(read, '_v', {\n get: () => {\n if (dirty) read()\n return value\n },\n enumerable: false,\n })\n\n read.direct = (updater: () => void): (() => void) => {\n if (!directFns) directFns = []\n const arr = directFns\n const idx = arr.length\n arr.push(updater)\n return () => {\n arr[idx] = null\n }\n }\n\n getCurrentScope()?.add({ dispose: read.dispose })\n return read as Computed<T>\n}\n","import { effect } from './effect'\nimport { trackSubscriber } from './tracking'\n\n/**\n * Notify a subscriber bucket without snapshot allocation.\n * Caps iteration at the original size to avoid infinite loops from\n * re-inserted entries (same pattern as notifySubscribers in tracking.ts).\n */\nfunction notifyBucket(bucket: Set<() => void>): void {\n if (bucket.size === 0) return\n if (bucket.size === 1) {\n ;(bucket.values().next().value as () => void)()\n return\n }\n const originalSize = bucket.size\n let i = 0\n for (const fn of bucket) {\n if (i >= originalSize) break\n fn()\n i++\n }\n}\n\n/**\n * Create an equality selector — returns a reactive predicate that is true\n * only for the currently selected value.\n *\n * Unlike a plain `() => source() === value`, this only triggers the TWO\n * affected subscribers (deselected + newly selected) instead of ALL\n * subscribers, making selection O(1) regardless of list size.\n *\n * @example\n * const isSelected = createSelector(selectedId)\n * // In each row:\n * class: () => (isSelected(row.id) ? \"selected\" : \"\")\n */\nexport function createSelector<T>(source: () => T): (value: T) => boolean {\n const subs = new Map<T, Set<() => void>>()\n let current: T\n let initialized = false\n\n effect(() => {\n const next = source()\n if (!initialized) {\n initialized = true\n current = next\n return\n }\n if (Object.is(next, current)) return\n const old = current\n current = next\n // Only notify the two affected buckets — O(1) regardless of list size.\n // Iteration-capped loop avoids [...bucket] snapshot allocation.\n const oldBucket = subs.get(old)\n const newBucket = subs.get(next)\n if (oldBucket) notifyBucket(oldBucket)\n if (newBucket) notifyBucket(newBucket)\n })\n\n // Reusable hosts per value — avoids allocating a closure per trackSubscriber call\n const hosts = new Map<T, { _s: Set<() => void> | null }>()\n\n return (value: T): boolean => {\n let host = hosts.get(value)\n if (!host) {\n let bucket = subs.get(value)\n if (!bucket) {\n bucket = new Set()\n subs.set(value, bucket)\n }\n host = { _s: bucket }\n hosts.set(value, host)\n }\n trackSubscriber(host)\n return Object.is(current, value)\n }\n}\n","/**\n * @pyreon/reactivity debug utilities.\n *\n * Development-only tools for tracing signal updates, inspecting reactive\n * graphs, and understanding why DOM nodes re-render.\n *\n * All utilities are tree-shakeable — they compile away in production builds\n * when unused.\n */\n\nimport type { Signal, SignalDebugInfo } from './signal'\n\n// ─── Signal update tracing ───────────────────────────────────────────────────\n\ninterface SignalUpdateEvent {\n /** The signal that changed */\n signal: Signal<unknown>\n /** Signal name (from options or label) */\n name: string | undefined\n /** Previous value */\n prev: unknown\n /** New value */\n next: unknown\n /** Stack trace at the point of the .set() / .update() call */\n stack: string\n /** Timestamp */\n timestamp: number\n}\n\ntype SignalUpdateListener = (event: SignalUpdateEvent) => void\n\nlet _traceListeners: SignalUpdateListener[] | null = null\n\n/**\n * Register a listener that fires on every signal write.\n * Returns a dispose function.\n *\n * @example\n * const dispose = onSignalUpdate(e => {\n * console.log(`${e.name ?? 'anonymous'}: ${e.prev} → ${e.next}`)\n * })\n */\nexport function onSignalUpdate(listener: SignalUpdateListener): () => void {\n if (!_traceListeners) _traceListeners = []\n _traceListeners.push(listener)\n return () => {\n if (!_traceListeners) return\n _traceListeners = _traceListeners.filter((l) => l !== listener)\n if (_traceListeners.length === 0) _traceListeners = null\n }\n}\n\n/** @internal — called from signal.set() when tracing is active */\nexport function _notifyTraceListeners(sig: Signal<unknown>, prev: unknown, next: unknown): void {\n if (!_traceListeners) return\n const event: SignalUpdateEvent = {\n signal: sig,\n name: sig.label,\n prev,\n next,\n stack: new Error().stack ?? '',\n timestamp: performance.now(),\n }\n for (const l of _traceListeners) l(event)\n}\n\n/** Check if any trace listeners are active (fast path for signal.set) */\nexport function isTracing(): boolean {\n return _traceListeners !== null\n}\n\n// ─── why() — trace which signal caused a re-run ──────────────────────────────\n\nlet _whyActive = false\nlet _whyLog: { name: string | undefined; prev: unknown; next: unknown }[] = []\n\n/**\n * Trace the next signal update. Logs which signals fire and what changed.\n * Call before triggering a state change to see what updates and why.\n *\n * @example\n * why()\n * count.set(5)\n * // Console: [pyreon:why] \"count\": 3 → 5 (2 subscribers)\n */\nexport function why(): void {\n if (_whyActive) return\n _whyActive = true\n _whyLog = []\n\n const dispose = onSignalUpdate((e) => {\n const _subCount = (e.signal as unknown as { _s: Set<unknown> | null })._s?.size ?? 0\n const _name = e.name ? `\"${e.name}\"` : '(anonymous signal)'\n\n console.log(\n `[pyreon:why] ${_name}: ${JSON.stringify(e.prev)} → ${JSON.stringify(e.next)} (${_subCount} subscriber${_subCount === 1 ? '' : 's'})`,\n )\n _whyLog.push({ name: e.name, prev: e.prev, next: e.next })\n })\n\n // Auto-dispose after the current microtask (captures the synchronous batch)\n queueMicrotask(() => {\n dispose()\n if (_whyLog.length === 0) {\n console.log('[pyreon:why] No signal updates detected')\n }\n _whyActive = false\n _whyLog = []\n })\n}\n\n// ─── inspectSignal — rich console output ─────────────────────────────────────\n\n/**\n * Print a signal's current state to the console in a readable format.\n *\n * @example\n * const count = signal(42, { name: \"count\" })\n * inspectSignal(count)\n * // Console:\n * // 🔍 Signal \"count\"\n * // value: 42\n * // subscribers: 3\n */\nexport function inspectSignal<T>(sig: Signal<T>): SignalDebugInfo<T> {\n const info = sig.debug()\n\n console.group(`🔍 Signal ${info.name ? `\"${info.name}\"` : '(anonymous)'}`)\n console.log('value:', info.value)\n console.log('subscribers:', info.subscriberCount)\n console.groupEnd()\n\n return info\n}\n","declare const process: { env: { NODE_ENV?: string } } | undefined\n\nconst __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'\n\nimport { enqueuePendingNotification, isBatching } from './batch'\nimport { _notifyTraceListeners, isTracing } from './debug'\nimport { notifySubscribers, trackSubscriber } from './tracking'\n\nexport interface SignalDebugInfo<T> {\n /** Signal name (set via options or inferred) */\n name: string | undefined\n /** Current value (same as peek()) */\n value: T\n /** Number of active subscribers */\n subscriberCount: number\n}\n\n/**\n * Read-only reactive value — the common interface that both Signal and Computed satisfy.\n * Use this as the parameter type when a function only needs to read a reactive value.\n */\nexport type ReadonlySignal<T> = () => T\n\nexport interface Signal<T> {\n (): T\n /** Read the current value WITHOUT registering a reactive dependency. */\n peek(): T\n set(value: T): void\n update(fn: (current: T) => T): void\n /**\n * Subscribe a static listener directly — no effect overhead (no withTracking,\n * no cleanupEffect, no effectDeps WeakMap). Use when the dependency is fixed\n * and dynamic re-tracking is not needed.\n * Returns a disposer that removes the subscription.\n */\n subscribe(listener: () => void): () => void\n /**\n * Register a direct updater — even lighter than subscribe().\n * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete).\n * Intended for compiler-emitted DOM bindings (_bindText, _bindDirect).\n * Returns a disposer that nulls the slot.\n */\n direct(updater: () => void): () => void\n /** Debug name — useful for devtools and logging. */\n label: string | undefined\n /** Returns a snapshot of the signal's debug info (value, name, subscriber count). */\n debug(): SignalDebugInfo<T>\n}\n\nexport interface SignalOptions {\n /** Debug name for this signal — shows up in devtools and debug() output. */\n name?: string\n}\n\n// Internal shape of a signal function — state stored as properties on the\n// function object so methods can be shared via assignment (not per-signal closures).\ninterface SignalFn<T> {\n (): T\n /** @internal current value */\n _v: T\n /** @internal subscriber set (lazily allocated by trackSubscriber) */\n _s: Set<() => void> | null\n /** @internal direct updaters array — compiler-emitted DOM updaters (lazily allocated) */\n _d: ((() => void) | null)[] | null\n peek(): T\n set(value: T): void\n update(fn: (current: T) => T): void\n subscribe(listener: () => void): () => void\n /** Register a direct updater — lighter than subscribe, uses array index disposal. */\n direct(updater: () => void): () => void\n label: string | undefined\n debug(): SignalDebugInfo<T>\n}\n\n// Shared method implementations — defined once, assigned to every signal.\n// Uses `this` binding (signal methods are always called as `signal.method()`).\nfunction _peek(this: SignalFn<unknown>) {\n return this._v\n}\n\nfunction _set(this: SignalFn<unknown>, newValue: unknown) {\n if (Object.is(this._v, newValue)) return\n const prev = this._v\n this._v = newValue\n if (isTracing()) _notifyTraceListeners(this as unknown as Signal<unknown>, prev, newValue)\n // Direct updaters — flat array, no Set overhead, batch-aware\n if (this._d) notifyDirect(this._d)\n if (this._s) notifySubscribers(this._s)\n}\n\nfunction _update(this: SignalFn<unknown>, fn: (current: unknown) => unknown) {\n _set.call(this, fn(this._v))\n}\n\nfunction _subscribe(this: SignalFn<unknown>, listener: () => void): () => void {\n if (!this._s) this._s = new Set()\n this._s.add(listener)\n return () => this._s?.delete(listener)\n}\n\n/**\n * Register a direct updater — lighter than subscribe().\n * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete overhead).\n * Used by compiler-emitted _bindText/_bindDirect for zero-overhead DOM bindings.\n */\nfunction _directFn(this: SignalFn<unknown>, updater: () => void): () => void {\n if (!this._d) this._d = []\n const arr = this._d\n const idx = arr.length\n arr.push(updater)\n return () => {\n arr[idx] = null\n }\n}\n\n/**\n * Notify direct updaters — flat array iteration, batch-aware.\n * Null slots (from disposed updaters) are skipped.\n */\nfunction notifyDirect(updaters: ((() => void) | null)[]): void {\n if (isBatching()) {\n for (let i = 0; i < updaters.length; i++) {\n const fn = updaters[i]\n if (fn) enqueuePendingNotification(fn)\n }\n } else {\n for (let i = 0; i < updaters.length; i++) {\n updaters[i]?.()\n }\n }\n}\n\nfunction _debug(this: SignalFn<unknown>): SignalDebugInfo<unknown> {\n return {\n name: this.label,\n value: this._v,\n subscriberCount: this._s?.size ?? 0,\n }\n}\n\n/**\n * Create a reactive signal.\n *\n * Only 1 closure is allocated (the read function). State is stored as\n * properties on the function object (_v, _s) and methods (peek, set,\n * update, subscribe) are shared across all signals — not per-signal closures.\n */\nexport function signal<T>(initialValue: T, options?: SignalOptions): Signal<T> {\n // The read function is the only per-signal closure.\n // It doubles as the SubscriberHost (_s property) for trackSubscriber.\n const read = ((...args: unknown[]) => {\n if (__DEV__ && args.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(\n '[Pyreon] signal() was called with an argument. ' +\n 'Use signal.set(value) or signal.update(fn) to write. ' +\n 'signal(value) only reads — the argument is ignored.',\n )\n }\n trackSubscriber(read as SignalFn<T>)\n return read._v\n }) as unknown as SignalFn<T>\n\n read._v = initialValue\n read._s = null\n read._d = null\n read.peek = _peek as () => T\n read.set = _set as (value: T) => void\n read.update = _update as (fn: (current: T) => T) => void\n read.subscribe = _subscribe as (listener: () => void) => () => void\n read.direct = _directFn as (updater: () => void) => () => void\n read.debug = _debug as () => SignalDebugInfo<T>\n read.label = options?.name\n\n return read as unknown as Signal<T>\n}\n","/**\n * createStore — deep reactive Proxy store.\n *\n * Wraps a plain object/array in a Proxy that creates a fine-grained signal for\n * every property. Direct mutations (`store.count++`, `store.items[0].label = \"x\"`)\n * trigger only the signals for the mutated properties — not the whole tree.\n *\n * @example\n * const state = createStore({ count: 0, items: [{ id: 1, text: \"hello\" }] })\n *\n * effect(() => console.log(state.count)) // tracks state.count only\n * state.count++ // only the count effect re-runs\n * state.items[0].text = \"world\" // only text-tracking effects re-run\n */\n\nimport { type Signal, signal } from './signal'\n\n// WeakMap: raw object → its reactive proxy (ensures each raw object gets one proxy)\nconst proxyCache = new WeakMap<object, object>()\n\nconst IS_STORE = Symbol('pyreon.store')\n\n/** Returns true if the value is a createStore proxy. */\nexport function isStore(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[IS_STORE] === true\n )\n}\n\n/**\n * Create a deep reactive store from a plain object or array.\n * Returns a proxy — mutations to the proxy trigger fine-grained reactive updates.\n */\nexport function createStore<T extends object>(initial: T): T {\n return wrap(initial) as T\n}\n\nfunction wrap(raw: object): object {\n const cached = proxyCache.get(raw)\n if (cached) return cached\n\n // Per-property signals. Lazily created on first access.\n const propSignals = new Map<PropertyKey, Signal<unknown>>()\n // For arrays: track length changes separately (push/pop/splice affect length)\n const isArray = Array.isArray(raw)\n const lengthSig = isArray ? signal((raw as unknown[]).length) : null\n\n function getOrCreateSignal(key: PropertyKey): Signal<unknown> {\n if (!propSignals.has(key)) {\n propSignals.set(key, signal((raw as Record<PropertyKey, unknown>)[key]))\n }\n return propSignals.get(key) as Signal<unknown>\n }\n\n const proxy = new Proxy(raw, {\n get(target, key) {\n // Pass through the identity marker and non-string/number keys (symbols, etc.)\n if (key === IS_STORE) return true\n if (typeof key === 'symbol') return (target as Record<symbol, unknown>)[key]\n\n // Array length — tracked via dedicated signal for push/pop/splice reactivity\n if (isArray && key === 'length') return lengthSig?.()\n\n // Non-own properties: prototype methods (forEach, map, push, …)\n // These must be returned untracked so array methods work normally.\n // Array methods will then go through set/get on indices via the proxy.\n if (!Object.hasOwn(target, key)) {\n return (target as Record<PropertyKey, unknown>)[key]\n }\n\n // Track via per-property signal\n const value = getOrCreateSignal(key)()\n\n // Deep reactivity: wrap nested objects/arrays transparently\n if (value !== null && typeof value === 'object') {\n return wrap(value as object)\n }\n\n return value\n },\n\n set(target, key, value) {\n if (typeof key === 'symbol') {\n ;(target as Record<symbol, unknown>)[key] = value\n return true\n }\n\n const prevLength = isArray ? (target as unknown[]).length : 0\n ;(target as Record<PropertyKey, unknown>)[key] = value\n\n // Array length set directly (e.g. arr.length = 0)\n if (isArray && key === 'length') {\n lengthSig?.set(value as number)\n return true\n }\n\n // Update or create signal for this property\n if (propSignals.has(key)) {\n propSignals.get(key)?.set(value)\n } else {\n propSignals.set(key, signal(value))\n }\n\n // If array length changed (e.g. via push/splice index assignment), update it\n if (isArray && (target as unknown[]).length !== prevLength) {\n lengthSig?.set((target as unknown[]).length)\n }\n\n return true\n },\n\n deleteProperty(target, key) {\n delete (target as Record<PropertyKey, unknown>)[key]\n if (typeof key !== 'symbol' && propSignals.has(key)) {\n propSignals.get(key)?.set(undefined)\n propSignals.delete(key)\n }\n if (isArray) lengthSig?.set((target as unknown[]).length)\n return true\n },\n\n has(target, key) {\n return Reflect.has(target, key)\n },\n\n ownKeys(target) {\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, key) {\n return Reflect.getOwnPropertyDescriptor(target, key)\n },\n })\n\n proxyCache.set(raw, proxy)\n return proxy\n}\n","/**\n * reconcile — surgically diff new state into an existing createStore proxy.\n *\n * Instead of replacing the store root (which would trigger all downstream effects),\n * reconcile walks both the new value and the store in parallel and only calls\n * `.set()` on signals whose value actually changed.\n *\n * Ideal for applying API responses to a long-lived store:\n *\n * @example\n * const state = createStore({ user: { name: \"Alice\", age: 30 }, items: [] })\n *\n * // API response arrives:\n * reconcile({ user: { name: \"Alice\", age: 31 }, items: [{ id: 1 }] }, state)\n * // → only state.user.age signal fires (name unchanged)\n * // → state.items[0] is newly created\n *\n * Arrays are reconciled by index — elements at the same index are recursively\n * diffed rather than replaced wholesale. Excess old elements are removed.\n */\n\nimport { isStore } from './store'\n\ntype AnyObject = Record<PropertyKey, unknown>\n\nexport function reconcile<T extends object>(source: T, target: T): void {\n _reconcileInner(source, target, new WeakSet())\n}\n\nfunction _reconcileInner(source: object, target: object, seen: WeakSet<object>): void {\n if (seen.has(source)) return // circular reference — stop recursion\n seen.add(source)\n if (Array.isArray(source) && Array.isArray(target)) {\n _reconcileArray(source as unknown[], target as unknown[], seen)\n } else {\n _reconcileObject(source as AnyObject, target as AnyObject, seen)\n }\n}\n\nfunction _reconcileArray(source: unknown[], target: unknown[], seen: WeakSet<object>): void {\n const targetLen = target.length\n const sourceLen = source.length\n\n // Update / add entries\n for (let i = 0; i < sourceLen; i++) {\n const sv = source[i]\n const tv = (target as unknown[])[i]\n\n if (\n i < targetLen &&\n sv !== null &&\n typeof sv === 'object' &&\n tv !== null &&\n typeof tv === 'object'\n ) {\n // Both sides are objects — recurse\n _reconcileInner(sv as object, tv as object, seen)\n } else {\n // Scalar or new entry — write directly (signal will skip if equal via Object.is)\n ;(target as unknown[])[i] = sv\n }\n }\n\n // Trim excess entries\n if (targetLen > sourceLen) {\n target.length = sourceLen\n }\n}\n\nfunction _reconcileObject(source: AnyObject, target: AnyObject, seen: WeakSet<object>): void {\n const sourceKeys = Object.keys(source)\n const targetKeys = new Set(Object.keys(target))\n\n for (const key of sourceKeys) {\n const sv = source[key]\n const tv = target[key]\n\n if (sv !== null && typeof sv === 'object' && tv !== null && typeof tv === 'object') {\n if (isStore(tv)) {\n // Both objects — recurse into the store node\n _reconcileInner(sv as object, tv as object, seen)\n } else {\n // Target is a raw object (not yet proxied) — just assign\n target[key] = sv\n }\n } else {\n // Scalar: assign (store proxy's set trap skips if Object.is equal)\n target[key] = sv\n }\n\n targetKeys.delete(key)\n }\n\n // Remove keys that no longer exist in source\n for (const key of targetKeys) {\n delete target[key]\n }\n}\n","import { effect } from './effect'\nimport type { Signal } from './signal'\nimport { signal } from './signal'\nimport { runUntracked } from './tracking'\n\nexport interface Resource<T> {\n /** The latest resolved value (undefined while loading or on error). */\n data: Signal<T | undefined>\n /** True while a fetch is in flight. */\n loading: Signal<boolean>\n /** The last error thrown by the fetcher, or undefined. */\n error: Signal<unknown>\n /** Re-run the fetcher with the current source value. */\n refetch(): void\n}\n\n/**\n * Async data primitive. Fetches data reactively whenever `source()` changes.\n *\n * @example\n * const userId = signal(1)\n * const user = createResource(userId, (id) => fetchUser(id))\n * // user.data() — the fetched user (undefined while loading)\n * // user.loading() — true while in flight\n * // user.error() — last error\n */\nexport function createResource<T, P>(\n source: () => P,\n fetcher: (param: P) => Promise<T>,\n): Resource<T> {\n const data = signal<T | undefined>(undefined)\n const loading = signal(false)\n const error = signal<unknown>(undefined)\n let requestId = 0\n\n const doFetch = (param: P) => {\n const id = ++requestId\n loading.set(true)\n error.set(undefined)\n fetcher(param)\n .then((result) => {\n if (id !== requestId) return\n data.set(result)\n loading.set(false)\n })\n .catch((err: unknown) => {\n if (id !== requestId) return\n error.set(err)\n loading.set(false)\n })\n }\n\n effect(() => {\n const param = source()\n runUntracked(() => doFetch(param))\n })\n\n return {\n data,\n loading,\n error,\n refetch() {\n runUntracked(() => doFetch(source()))\n },\n }\n}\n","import { effect } from './effect'\n\nexport interface WatchOptions {\n /** If true, call the callback immediately with the current value on setup. Default: false. */\n immediate?: boolean\n}\n\n/**\n * Watch a reactive source and run a callback whenever it changes.\n *\n * Returns a stop function that disposes the watcher.\n *\n * The callback receives (newValue, oldValue). On the first call (when\n * `immediate` is true) oldValue is `undefined`.\n *\n * The callback may return a cleanup function that is called before each\n * re-run and on stop — useful for cancelling async work.\n *\n * @example\n * const stop = watch(\n * () => userId(),\n * async (id, prev) => {\n * const data = await fetch(`/api/user/${id}`)\n * setUser(await data.json())\n * },\n * )\n * // Later: stop()\n */\nexport function watch<T>(\n source: () => T,\n callback: (newVal: T, oldVal: T | undefined) => void | (() => void),\n opts: WatchOptions = {},\n): () => void {\n let oldVal: T | undefined\n let isFirst = true\n let cleanupFn: (() => void) | undefined\n\n const e = effect(() => {\n const newVal = source()\n\n if (isFirst) {\n isFirst = false\n oldVal = newVal\n if (opts.immediate) {\n const result = callback(newVal, undefined)\n if (typeof result === 'function') cleanupFn = result\n }\n return\n }\n\n if (cleanupFn) {\n cleanupFn()\n cleanupFn = undefined\n }\n\n const result = callback(newVal, oldVal)\n if (typeof result === 'function') cleanupFn = result\n oldVal = newVal\n })\n\n return () => {\n e.dispose()\n if (cleanupFn) {\n cleanupFn()\n cleanupFn = undefined\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["_countSink","cleanupLocalDeps","_countSink"],"sources":["../src/batch.ts","../src/cell.ts","../src/scope.ts","../src/tracking.ts","../src/effect.ts","../src/computed.ts","../src/createSelector.ts","../src/debug.ts","../src/signal.ts","../src/store.ts","../src/reconcile.ts","../src/resource.ts","../src/watch.ts"],"sourcesContent":["// Batch multiple signal updates into a single notification pass.\n// Uses a Set so the same subscriber is never flushed more than once per batch,\n// even if multiple signals it depends on change within the same batch.\n\nlet batchDepth = 0\n\n// Two pre-allocated Sets swapped on each flush — avoids allocating a new Set()\n// on every batch exit. The \"active\" set collects enqueued notifications; on flush\n// we swap to the other set and iterate the captured one, then clear it for reuse.\nconst setA = new Set<() => void>()\nconst setB = new Set<() => void>()\nlet pendingNotifications = setA\n\nexport function batch(fn: () => void): void {\n batchDepth++\n try {\n fn()\n } finally {\n batchDepth--\n if (batchDepth === 0 && pendingNotifications.size > 0) {\n // Keep batching active during flush so cascade-notifications emitted\n // by flushing subscribers enqueue into the pending Set (and dedupe\n // against what's already queued) instead of firing inline. The\n // while-loop drains any cascade rounds until the graph is stable.\n //\n // Without this, a diamond dependency (a → b, c → d → effect) re-fires\n // the apex effect TWICE per signal write: the first notification path\n // through `b` reaches `effect`, whose read clears `d`'s dirty flag;\n // then when `c` is notified (still in the first flush round), it\n // re-dirties `d`, which re-notifies `effect`. Keeping batchDepth at 1\n // during flush routes those cascade-notifications through the Set,\n // which dedupes on `d`'s recompute and on `effect`'s run.\n //\n // See `packages/internals/perf-harness/src/tests/diamond-probe.test.ts`\n // for the empirical probe that caught this.\n batchDepth = 1\n try {\n while (pendingNotifications.size > 0) {\n // Swap to the other pre-allocated Set before flushing so new\n // enqueues during notification land in the alternate Set, not\n // mixed into the current iteration.\n const flush = pendingNotifications\n pendingNotifications = flush === setA ? setB : setA\n for (const notify of flush) notify()\n flush.clear()\n }\n } finally {\n batchDepth = 0\n }\n }\n }\n}\n\nexport function isBatching(): boolean {\n return batchDepth > 0\n}\n\nexport function enqueuePendingNotification(notify: () => void): void {\n pendingNotifications.add(notify)\n}\n\n/**\n * Returns a Promise that resolves after all currently-pending microtasks have flushed.\n * Useful when you need to read the DOM after a batch of signal updates has settled.\n *\n * @example\n * count.set(1); count.set(2)\n * await nextTick()\n * // DOM is now up-to-date\n */\nexport function nextTick(): Promise<void> {\n return new Promise((resolve) => queueMicrotask(resolve))\n}\n","/**\n * Lightweight reactive cell — class-based alternative to signal().\n *\n * - 1 object allocation vs signal()'s 6 closures\n * - Same API surface: peek(), set(), update(), subscribe(), listen()\n * - NOT callable as a getter (no effect tracking) — use for fixed subscriptions\n * - Methods on prototype, shared across all instances\n * - Single-listener fast path: no Set allocated when ≤1 subscriber\n *\n * Use when you need reactive state but don't need automatic effect dependency tracking.\n * Ideal for list item labels in keyed reconcilers where subscribe() is used directly.\n */\nexport class Cell<T> {\n /** @internal */ _v: T\n /** @internal */ _l: (() => void) | null = null // single-listener fast path\n /** @internal */ _s: Set<() => void> | null = null // multi-listener fallback\n\n constructor(value: T) {\n this._v = value\n }\n\n peek(): T {\n return this._v\n }\n\n set(value: T): void {\n if (Object.is(this._v, value)) return\n this._v = value\n if (this._l) this._l()\n else if (this._s) for (const fn of this._s) fn()\n }\n\n update(fn: (current: T) => T): void {\n this.set(fn(this._v))\n }\n\n /**\n * Fire-and-forget subscription — no unsubscribe returned.\n * Use when the listener's lifetime matches the cell's (e.g., list rows).\n * Saves 1 closure allocation per call vs subscribe().\n */\n listen(listener: () => void): void {\n if (!this._l && !this._s) {\n this._l = listener\n } else {\n // Promote to Set\n if (!this._s) {\n this._s = new Set()\n if (this._l) {\n this._s.add(this._l)\n this._l = null\n }\n }\n this._s.add(listener)\n }\n }\n\n subscribe(listener: () => void): () => void {\n this.listen(listener)\n // The listener could be in _l (single) or _s (multi).\n // A later subscribe() call may promote it from _l to _s,\n // so the disposer must check both locations.\n return () => {\n if (this._l === listener) this._l = null\n else this._s?.delete(listener)\n }\n }\n}\n\nexport function cell<T>(value: T): Cell<T> {\n return new Cell(value)\n}\n","// EffectScope — auto-tracks effects created during a component's setup\n// and disposes them all at once when the component unmounts.\n\nexport class EffectScope {\n private _effects: { dispose(): void }[] | null = null\n private _active = true\n private _updateHooks: (() => void)[] | null = null\n private _updatePending = false\n\n /** Register an effect/computed to be disposed when this scope stops. */\n add(e: { dispose(): void }): void {\n if (!this._active) return\n if (this._effects === null) this._effects = []\n this._effects.push(e)\n }\n\n /**\n * Temporarily re-activate this scope so effects created inside `fn` are\n * auto-tracked and will be disposed when the scope stops.\n * Used to ensure effects created in `onMount` callbacks belong to their\n * component's scope rather than leaking as global effects.\n */\n runInScope<T>(fn: () => T): T {\n const prev = _currentScope\n _currentScope = this\n try {\n return fn()\n } finally {\n _currentScope = prev\n }\n }\n\n /** Register a callback to run after any reactive update in this scope. */\n addUpdateHook(fn: () => void): void {\n if (this._updateHooks === null) this._updateHooks = []\n this._updateHooks.push(fn)\n }\n\n /**\n * Called by effects after each non-initial re-run.\n * Schedules onUpdate hooks via microtask so all synchronous effects settle first.\n */\n notifyEffectRan(): void {\n if (!this._active || !this._updateHooks || this._updateHooks.length === 0 || this._updatePending) return\n this._updatePending = true\n queueMicrotask(() => {\n this._updatePending = false\n if (!this._active || !this._updateHooks) return\n for (const fn of this._updateHooks) {\n try {\n fn()\n } catch (err) {\n console.error('[pyreon] onUpdate hook error:', err)\n }\n }\n })\n }\n\n /** Dispose all tracked effects. */\n stop(): void {\n if (!this._active) return\n if (this._effects) {\n for (const e of this._effects) e.dispose()\n }\n this._effects = null\n this._updateHooks = null\n this._updatePending = false\n this._active = false\n }\n}\n\nlet _currentScope: EffectScope | null = null\n\nexport function getCurrentScope(): EffectScope | null {\n return _currentScope\n}\n\nexport function setCurrentScope(scope: EffectScope | null): void {\n _currentScope = scope\n}\n\n/** Create a new EffectScope. */\nexport function effectScope(): EffectScope {\n return new EffectScope()\n}\n","// Global subscriber tracking context\n\nimport { enqueuePendingNotification, isBatching } from './batch'\n\nlet activeEffect: (() => void) | null = null\n\n// Tracks which subscriber sets each effect is registered in, so we can\n// clean them up before a re-run (dynamic dependency tracking).\nconst effectDeps = new WeakMap<() => void, Set<Set<() => void>>>()\n\n// Fast deps collector for renderEffect — avoids WeakMap overhead entirely.\n// When set, trackSubscriber pushes subscriber sets here instead of effectDeps.\nlet _depsCollector: Set<() => void>[] | null = null\n\n// Skip deps collection mode — for re-evaluating computeds/effects with static deps.\n// When true, trackSubscriber only does Set.add (no-op if already subscribed) and skips\n// the _depsCollector.push / WeakMap work entirely.\nlet _skipDepsCollection = false\n\nexport function setDepsCollector(collector: Set<() => void>[] | null): void {\n _depsCollector = collector\n}\n\nexport function setSkipDepsCollection(skip: boolean): void {\n _skipDepsCollection = skip\n}\n\n/**\n * Subscriber host — any reactive source that can have downstream subscribers.\n * Signals, computeds, and createSelector buckets all implement this interface.\n * The Set is created lazily — only allocated when an effect actually tracks this source.\n */\nexport interface SubscriberHost {\n /** @internal subscriber set — null until first tracked by an effect */\n _s: Set<() => void> | null\n}\n\n/**\n * Register the active effect as a subscriber of the given reactive source.\n * The subscriber Set is created lazily on the host — sources read only outside\n * effects never allocate a Set.\n */\nexport function trackSubscriber(host: SubscriberHost) {\n if (activeEffect) {\n if (!host._s) host._s = new Set()\n host._s.add(activeEffect)\n // Skip collection mode: we're already subscribed (Set.add is no-op),\n // just need activeEffect set for nested computed reads to work.\n if (_skipDepsCollection) return\n if (_depsCollector) {\n // Fast path: renderEffect stores deps inline, no WeakMap\n _depsCollector.push(host._s)\n } else {\n // Record this dep so we can remove it on cleanup\n let deps = effectDeps.get(activeEffect)\n if (!deps) {\n deps = new Set()\n effectDeps.set(activeEffect, deps)\n }\n deps.add(host._s)\n }\n }\n}\n\n/**\n * Remove an effect from every subscriber set it was registered in,\n * then clear its dep record. Call this before each re-run and on dispose.\n */\nexport function cleanupEffect(fn: () => void): void {\n const deps = effectDeps.get(fn)\n if (deps) {\n for (const sub of deps) sub.delete(fn)\n deps.clear()\n }\n}\n\nexport function notifySubscribers(subscribers: Set<() => void>) {\n if (subscribers.size === 0) return\n // Single-subscriber fast path: avoid any iteration overhead.\n if (subscribers.size === 1) {\n const sub = subscribers.values().next().value as () => void\n if (isBatching()) enqueuePendingNotification(sub)\n else sub()\n return\n }\n if (isBatching()) {\n // Effects are queued not run inline — no re-entrancy risk, iterate the live Set directly.\n for (const sub of subscribers) enqueuePendingNotification(sub)\n } else {\n // Effects run inline and may call cleanupEffect (removes) + trackSubscriber (re-adds).\n // Instead of snapshotting with [...subscribers] (allocates an array), we iterate the\n // live Set but cap iterations at the original size to prevent infinite loops from\n // re-inserted entries. This is safe because:\n // - cleanupEffect removes the effect from the Set (no double-fire)\n // - trackSubscriber may re-add it (but we stop after originalSize iterations)\n // - Any effects re-added during this pass are already up-to-date (just ran)\n const originalSize = subscribers.size\n let i = 0\n for (const sub of subscribers) {\n if (i >= originalSize) break\n sub()\n i++\n }\n }\n}\n\nexport function withTracking<T>(fn: () => void, compute: () => T): T {\n const prev = activeEffect\n activeEffect = fn\n try {\n return compute()\n } finally {\n activeEffect = prev\n }\n}\n\n// Stack for inlined tracking in renderEffect — avoids withTracking function call overhead.\nlet _prevEffect: (() => void) | null = null\n\nexport function _setActiveEffect(fn: () => void): void {\n _prevEffect = activeEffect\n activeEffect = fn\n}\n\nexport function _restoreActiveEffect(): void {\n activeEffect = _prevEffect\n _prevEffect = null\n}\n\n/** Read signals without subscribing. Alias: `untrack`. */\nexport function runUntracked<T>(fn: () => T): T {\n const prev = activeEffect\n activeEffect = null\n try {\n return fn()\n } finally {\n activeEffect = prev\n }\n}\n","import { getCurrentScope } from './scope'\nimport { _restoreActiveEffect, _setActiveEffect, setDepsCollector, withTracking } from './tracking'\n\n// Dev-time counter sink — see packages/internals/perf-harness for contract.\ninterface ViteMeta {\n readonly env?: { readonly DEV?: boolean }\n}\nconst _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }\n\nexport interface Effect {\n dispose(): void\n}\n\n// ─── onCleanup ───────────────────────────────────────────────────────────────\n// Thread-local collector for cleanup functions registered via onCleanup()\n// during effect execution. Pushed/popped around the user callback in effect().\nlet _cleanupCollector: (() => void)[] | null = null\n\n/**\n * Register a cleanup function inside an effect. The cleanup runs:\n * - Before the effect re-runs (when dependencies change)\n * - When the effect is disposed\n *\n * Can be called multiple times — all cleanups run in registration order.\n * Must be called synchronously during effect setup (like onMount/onUnmount).\n *\n * @example\n * effect(() => {\n * const controller = new AbortController()\n * onCleanup(() => controller.abort())\n * fetch(`/api/user/${userId()}`, { signal: controller.signal })\n * .then(r => r.json())\n * .then(data => user.set(data))\n * })\n */\nexport function onCleanup(fn: () => void): void {\n if (_cleanupCollector) {\n _cleanupCollector.push(fn)\n }\n}\n\n// Thread-local collector for nested effects — captures effect() calls made\n// inside another effect's fn() body so the parent can dispose them on\n// re-run / disposal. Without this, inner effects leak across outer\n// lifecycle boundaries (caught by cleanup-nested.test.ts).\nlet _innerEffectCollector: Effect[] | null = null\n\n// Global error handler — called for unhandled errors thrown inside effects.\n// Defaults to console.error so silent failures are never swallowed.\nexport let _errorHandler: (err: unknown) => void = (err) => {\n console.error('[pyreon] Unhandled effect error:', err)\n}\n\nexport function setErrorHandler(fn: (err: unknown) => void): void {\n _errorHandler = fn\n}\n\n/** Remove an effect from all dependency subscriber sets (local deps array). */\nfunction cleanupLocalDeps(deps: Set<() => void>[], fn: () => void): void {\n if (deps.length === 1) {\n ;(deps[0] as Set<() => void>).delete(fn)\n deps.length = 0\n } else if (deps.length > 1) {\n for (let i = 0; i < deps.length; i++) (deps[i] as Set<() => void>).delete(fn)\n deps.length = 0\n }\n}\n\nexport function effect(fn: () => (() => void) | void): Effect {\n // Capture the scope at creation time — remains correct during future re-runs\n // even after setCurrentScope(null) has been called post-setup.\n const scope = getCurrentScope()\n let disposed = false\n let isFirstRun = true\n let cleanup: (() => void) | undefined\n // Local deps array — avoids WeakMap overhead (like renderEffect)\n const deps: Set<() => void>[] = []\n\n let cleanups: (() => void)[] | undefined\n // Inner effects created during this effect's fn() body. Disposed on\n // outer re-run (before the next fn()) and on outer dispose(). Without\n // this, nested effects leak across outer lifecycle boundaries.\n let innerEffects: Effect[] | null = null\n\n const runCleanup = () => {\n if (innerEffects) {\n for (const ie of innerEffects) {\n try {\n ie.dispose()\n } catch (err) {\n _errorHandler(err)\n }\n }\n innerEffects = null\n }\n if (cleanups) {\n for (const c of cleanups) {\n try {\n c()\n } catch (err) {\n _errorHandler(err)\n }\n }\n cleanups = undefined\n }\n if (typeof cleanup === 'function') {\n try {\n cleanup()\n } catch (err) {\n _errorHandler(err)\n }\n cleanup = undefined\n }\n }\n\n const run = () => {\n if (disposed) return\n if ((import.meta as ViteMeta).env?.DEV === true)\n _countSink.__pyreon_count__?.('reactivity.effectRun')\n // Run previous cleanup before re-running\n runCleanup()\n // Start a new inner-effect collection window. Effects created during\n // fn() will push themselves into this array and be disposed on the\n // next re-run or on dispose.\n const outerCollector = _innerEffectCollector\n const myInners: Effect[] = []\n _innerEffectCollector = myInners\n try {\n cleanupLocalDeps(deps, run)\n setDepsCollector(deps)\n // Collect onCleanup() registrations during execution\n const collected: (() => void)[] = []\n _cleanupCollector = collected\n cleanup = withTracking(run, fn) || undefined\n _cleanupCollector = null\n if (collected.length > 0) cleanups = collected\n setDepsCollector(null)\n } catch (err) {\n _cleanupCollector = null\n setDepsCollector(null)\n _errorHandler(err)\n } finally {\n _innerEffectCollector = outerCollector\n }\n if (myInners.length > 0) innerEffects = myInners\n // Notify scope after each reactive re-run (not the initial synchronous run)\n // so onUpdate hooks fire after the DOM has settled.\n if (!isFirstRun) scope?.notifyEffectRan()\n isFirstRun = false\n }\n\n run()\n\n const e: Effect = {\n dispose() {\n runCleanup()\n disposed = true\n cleanupLocalDeps(deps, run)\n },\n }\n\n // If we're inside another effect's run, register with it so the outer\n // disposes this inner automatically.\n if (_innerEffectCollector !== null) {\n _innerEffectCollector.push(e)\n } else {\n // Otherwise auto-register with the active EffectScope (if any)\n getCurrentScope()?.add(e)\n }\n\n return e\n}\n\n/**\n * Lightweight effect for DOM render bindings.\n *\n * Differences from `effect()`:\n * - No EffectScope registration (caller owns the dispose lifecycle)\n * - No error handler (errors propagate naturally)\n * - No onUpdate notification\n * - Deps stored in a local array instead of the global WeakMap — faster\n * creation and disposal (~200ns saved per effect vs WeakMap path)\n *\n * Returns a dispose function (not an Effect object — saves 1 allocation).\n */\n/**\n * Static-dep binding — compiler helper for template expressions.\n *\n * Like renderEffect but assumes dependencies never change (true for all\n * compiler-emitted template bindings like `_tpl()` text/attribute updates).\n *\n * Tracks dependencies only on the first run. Re-runs skip cleanup, re-tracking,\n * and tracking context save/restore entirely — just calls `fn()` directly.\n *\n * Per re-run savings vs renderEffect:\n * - No deps iteration + Set.delete (cleanup)\n * - No setDepsCollector + withTracking (re-registration)\n * - Signal reads hit `if (activeEffect)` null check → instant return\n */\nexport function _bind(fn: () => void): () => void {\n const deps: Set<() => void>[] = []\n let disposed = false\n\n const run = () => {\n if (disposed) return\n fn()\n }\n\n // First run: track deps so we know what to unsubscribe on dispose\n setDepsCollector(deps)\n withTracking(run, fn)\n setDepsCollector(null)\n\n const dispose = () => {\n if (disposed) return\n disposed = true\n for (const s of deps) s.delete(run)\n deps.length = 0\n }\n\n // Auto-register with scope so template bindings are disposed during teardown\n getCurrentScope()?.add({ dispose })\n\n return dispose\n}\n\n/** Full re-track path for renderEffect: cleanup old deps, evaluate with tracking. */\nfunction renderEffectFullTrack(deps: Set<() => void>[], run: () => void, fn: () => void): void {\n if (deps.length === 1) {\n ;(deps[0] as Set<() => void>).delete(run)\n deps.length = 0\n } else if (deps.length > 1) {\n for (const s of deps) s.delete(run)\n deps.length = 0\n }\n setDepsCollector(deps)\n _setActiveEffect(run)\n try {\n fn()\n } finally {\n _restoreActiveEffect()\n setDepsCollector(null)\n }\n}\n\nexport function renderEffect(fn: () => void): () => void {\n const deps: Set<() => void>[] = []\n let disposed = false\n let isFirstRun = true\n\n const run = () => {\n if (disposed) return\n // After first run, if deps haven't changed structure, we can skip\n // the full cleanup+retrack path. However, renderEffect deps CAN\n // change (unlike _bind), so we always do the full track.\n // Optimization: skip cleanup on first run (deps are empty).\n if (isFirstRun) {\n isFirstRun = false\n setDepsCollector(deps)\n _setActiveEffect(run)\n try {\n fn()\n } finally {\n _restoreActiveEffect()\n setDepsCollector(null)\n }\n } else {\n renderEffectFullTrack(deps, run, fn)\n }\n }\n\n run()\n\n const dispose = () => {\n if (disposed) return\n disposed = true\n if (deps.length === 1) {\n ;(deps[0] as Set<() => void>).delete(run)\n } else {\n for (const s of deps) s.delete(run)\n }\n deps.length = 0\n }\n\n // Auto-register with scope so render effects are disposed during teardown\n getCurrentScope()?.add({ dispose })\n\n return dispose\n}\n","import { _errorHandler } from './effect'\nimport { getCurrentScope } from './scope'\nimport {\n cleanupEffect,\n notifySubscribers,\n setDepsCollector,\n setSkipDepsCollection,\n trackSubscriber,\n withTracking,\n} from './tracking'\n\n// Dev-time counter sink — see packages/internals/perf-harness for contract.\ninterface ViteMeta {\n readonly env?: { readonly DEV?: boolean }\n}\nconst _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }\n\nexport interface Computed<T> {\n (): T\n /** Remove this computed from all its reactive dependencies. */\n dispose(): void\n /** Cached value for compiler-emitted direct bindings (_bindText, _bindDirect). */\n _v: T\n /** Register a direct updater — used by compiler-emitted _bindText/_bindDirect. */\n direct(updater: () => void): () => void\n}\n\nexport interface ComputedOptions<T> {\n /**\n * Custom equality function. When provided, the computed eagerly re-evaluates\n * on dependency change and only notifies downstream if `equals(prev, next)`\n * returns false. Useful for derived objects/arrays to skip spurious updates.\n *\n * @example\n * const sorted = computed(() => items().slice().sort(), {\n * equals: (a, b) => a.length === b.length && a.every((v, i) => v === b[i])\n * })\n */\n equals?: (prev: T, next: T) => boolean\n}\n\n/** Remove a computed from all dependency subscriber sets (local deps array). */\nfunction cleanupLocalDeps(deps: Set<() => void>[], fn: () => void): void {\n for (let i = 0; i < deps.length; i++) (deps[i] as Set<() => void>).delete(fn)\n deps.length = 0\n}\n\n/** Re-track dependencies using the local deps array collector. */\nfunction trackWithLocalDeps<T>(deps: Set<() => void>[], effect: () => void, fn: () => T): T {\n setDepsCollector(deps)\n const result = withTracking(effect, fn)\n setDepsCollector(null)\n return result\n}\n\nexport function computed<T>(fn: () => T, options?: ComputedOptions<T>): Computed<T> {\n return options?.equals ? computedWithEquals(fn, options.equals) : computedLazy(fn)\n}\n\n/**\n * Default computed — lazy evaluation with deferred cleanup.\n *\n * On notification: just marks dirty and propagates (no cleanup/re-track).\n * On read: cleans up old deps, re-evaluates, re-tracks.\n *\n * The `if (dirty) return` early exit in recompute prevents double-propagation\n * in diamond patterns (a→b,c→d: b notifies d, c tries to notify d again —\n * skipped because d is already dirty).\n */\nfunction computedLazy<T>(fn: () => T): Computed<T> {\n let value: T\n let dirty = true\n let disposed = false\n let tracked = false\n const deps: Set<() => void>[] = []\n const host: { _s: Set<() => void> | null } = { _s: null }\n let directFns: ((() => void) | null)[] | null = null\n\n const recompute = () => {\n if (disposed || dirty) return\n dirty = true\n if (host._s) notifySubscribers(host._s)\n if (directFns) for (const f of directFns) f?.()\n }\n\n const read = (): T => {\n trackSubscriber(host)\n if (dirty) {\n if ((import.meta as ViteMeta).env?.DEV === true)\n _countSink.__pyreon_count__?.('reactivity.computedRecompute')\n try {\n if (tracked) {\n // Deps already established from first run — skip adding to\n // subscriber Sets again (they already contain recompute).\n // Still need withTracking so activeEffect is set correctly\n // for any NEW signals read on this evaluation.\n setSkipDepsCollection(true)\n value = withTracking(recompute, fn)\n setSkipDepsCollection(false)\n } else {\n value = trackWithLocalDeps(deps, recompute, fn)\n tracked = true\n }\n } catch (err) {\n _errorHandler(err)\n }\n dirty = false\n }\n return value as T\n }\n\n read.dispose = () => {\n disposed = true\n cleanupLocalDeps(deps, recompute)\n }\n\n Object.defineProperty(read, '_v', {\n get: () => {\n if (dirty) read() // ensure value is fresh\n return value\n },\n enumerable: false,\n })\n\n read.direct = (updater: () => void): (() => void) => {\n if (!directFns) directFns = []\n const arr = directFns\n const idx = arr.length\n arr.push(updater)\n return () => {\n arr[idx] = null\n }\n }\n\n getCurrentScope()?.add({ dispose: read.dispose })\n return read as Computed<T>\n}\n\n/**\n * Computed with custom equality — eager evaluation on notification.\n *\n * Re-evaluates immediately when deps change and only notifies downstream\n * if `equals(prev, next)` returns false.\n */\nfunction computedWithEquals<T>(fn: () => T, equals: (prev: T, next: T) => boolean): Computed<T> {\n let value: T\n let dirty = true\n let initialized = false\n let disposed = false\n const deps: Set<() => void>[] = []\n const host: { _s: Set<() => void> | null } = { _s: null }\n let directFns: ((() => void) | null)[] | null = null\n\n const recompute = () => {\n if (disposed) return\n if ((import.meta as ViteMeta).env?.DEV === true)\n _countSink.__pyreon_count__?.('reactivity.computedRecompute')\n cleanupLocalDeps(deps, recompute)\n try {\n const next = trackWithLocalDeps(deps, recompute, fn)\n if (initialized && equals(value as T, next)) return\n value = next\n dirty = false\n initialized = true\n } catch (err) {\n _errorHandler(err)\n return\n }\n if (host._s) notifySubscribers(host._s)\n if (directFns) for (const f of directFns) f?.()\n }\n\n const read = (): T => {\n trackSubscriber(host)\n if (dirty) {\n if ((import.meta as ViteMeta).env?.DEV === true)\n _countSink.__pyreon_count__?.('reactivity.computedRecompute')\n cleanupLocalDeps(deps, recompute)\n try {\n value = trackWithLocalDeps(deps, recompute, fn)\n } catch (err) {\n _errorHandler(err)\n }\n dirty = false\n initialized = true\n }\n return value as T\n }\n\n read.dispose = () => {\n disposed = true\n cleanupLocalDeps(deps, recompute)\n cleanupEffect(recompute)\n }\n\n Object.defineProperty(read, '_v', {\n get: () => {\n if (dirty) read()\n return value\n },\n enumerable: false,\n })\n\n read.direct = (updater: () => void): (() => void) => {\n if (!directFns) directFns = []\n const arr = directFns\n const idx = arr.length\n arr.push(updater)\n return () => {\n arr[idx] = null\n }\n }\n\n getCurrentScope()?.add({ dispose: read.dispose })\n return read as Computed<T>\n}\n","import { effect } from './effect'\nimport { trackSubscriber } from './tracking'\n\n/**\n * Notify a subscriber bucket without snapshot allocation.\n * Caps iteration at the original size to avoid infinite loops from\n * re-inserted entries (same pattern as notifySubscribers in tracking.ts).\n */\nfunction notifyBucket(bucket: Set<() => void>): void {\n if (bucket.size === 0) return\n if (bucket.size === 1) {\n ;(bucket.values().next().value as () => void)()\n return\n }\n const originalSize = bucket.size\n let i = 0\n for (const fn of bucket) {\n if (i >= originalSize) break\n fn()\n i++\n }\n}\n\n/**\n * Create an equality selector — returns a reactive predicate that is true\n * only for the currently selected value.\n *\n * Unlike a plain `() => source() === value`, this only triggers the TWO\n * affected subscribers (deselected + newly selected) instead of ALL\n * subscribers, making selection O(1) regardless of list size.\n *\n * @example\n * const isSelected = createSelector(selectedId)\n * // In each row:\n * class: () => (isSelected(row.id) ? \"selected\" : \"\")\n */\nexport function createSelector<T>(source: () => T): (value: T) => boolean {\n const subs = new Map<T, Set<() => void>>()\n let current: T\n let initialized = false\n\n effect(() => {\n const next = source()\n if (!initialized) {\n initialized = true\n current = next\n return\n }\n if (Object.is(next, current)) return\n const old = current\n current = next\n // Only notify the two affected buckets — O(1) regardless of list size.\n // Iteration-capped loop avoids [...bucket] snapshot allocation.\n const oldBucket = subs.get(old)\n const newBucket = subs.get(next)\n if (oldBucket) notifyBucket(oldBucket)\n if (newBucket) notifyBucket(newBucket)\n })\n\n // Reusable hosts per value — avoids allocating a closure per trackSubscriber call\n const hosts = new Map<T, { _s: Set<() => void> | null }>()\n\n return (value: T): boolean => {\n let host = hosts.get(value)\n if (!host) {\n let bucket = subs.get(value)\n if (!bucket) {\n bucket = new Set()\n subs.set(value, bucket)\n }\n host = { _s: bucket }\n hosts.set(value, host)\n }\n trackSubscriber(host)\n return Object.is(current, value)\n }\n}\n","/**\n * @pyreon/reactivity debug utilities.\n *\n * Development-only tools for tracing signal updates, inspecting reactive\n * graphs, and understanding why DOM nodes re-render.\n *\n * All utilities are tree-shakeable — they compile away in production builds\n * when unused.\n */\n\nimport type { Signal, SignalDebugInfo } from './signal'\n\n// ─── Signal update tracing ───────────────────────────────────────────────────\n\ninterface SignalUpdateEvent {\n /** The signal that changed */\n signal: Signal<unknown>\n /** Signal name (from options or label) */\n name: string | undefined\n /** Previous value */\n prev: unknown\n /** New value */\n next: unknown\n /** Stack trace at the point of the .set() / .update() call */\n stack: string\n /** Timestamp */\n timestamp: number\n}\n\ntype SignalUpdateListener = (event: SignalUpdateEvent) => void\n\nlet _traceListeners: SignalUpdateListener[] | null = null\n\n/**\n * Register a listener that fires on every signal write.\n * Returns a dispose function.\n *\n * @example\n * const dispose = onSignalUpdate(e => {\n * console.log(`${e.name ?? 'anonymous'}: ${e.prev} → ${e.next}`)\n * })\n */\nexport function onSignalUpdate(listener: SignalUpdateListener): () => void {\n if (!_traceListeners) _traceListeners = []\n _traceListeners.push(listener)\n return () => {\n if (!_traceListeners) return\n _traceListeners = _traceListeners.filter((l) => l !== listener)\n if (_traceListeners.length === 0) _traceListeners = null\n }\n}\n\n/** @internal — called from signal.set() when tracing is active */\nexport function _notifyTraceListeners(sig: Signal<unknown>, prev: unknown, next: unknown): void {\n if (!_traceListeners) return\n const event: SignalUpdateEvent = {\n signal: sig,\n name: sig.label,\n prev,\n next,\n stack: new Error().stack ?? '',\n timestamp: performance.now(),\n }\n for (const l of _traceListeners) l(event)\n}\n\n/** Check if any trace listeners are active (fast path for signal.set) */\nexport function isTracing(): boolean {\n return _traceListeners !== null\n}\n\n// ─── why() — trace which signal caused a re-run ──────────────────────────────\n\nlet _whyActive = false\nlet _whyLog: { name: string | undefined; prev: unknown; next: unknown }[] = []\n\n/**\n * Trace the next signal update. Logs which signals fire and what changed.\n * Call before triggering a state change to see what updates and why.\n *\n * @example\n * why()\n * count.set(5)\n * // Console: [pyreon:why] \"count\": 3 → 5 (2 subscribers)\n */\nexport function why(): void {\n if (_whyActive) return\n _whyActive = true\n _whyLog = []\n\n const dispose = onSignalUpdate((e) => {\n const _subCount = (e.signal as unknown as { _s: Set<unknown> | null })._s?.size ?? 0\n const _name = e.name ? `\"${e.name}\"` : '(anonymous signal)'\n\n console.log(\n `[pyreon:why] ${_name}: ${JSON.stringify(e.prev)} → ${JSON.stringify(e.next)} (${_subCount} subscriber${_subCount === 1 ? '' : 's'})`,\n )\n _whyLog.push({ name: e.name, prev: e.prev, next: e.next })\n })\n\n // Auto-dispose after the current microtask (captures the synchronous batch)\n queueMicrotask(() => {\n dispose()\n if (_whyLog.length === 0) {\n console.log('[pyreon:why] No signal updates detected')\n }\n _whyActive = false\n _whyLog = []\n })\n}\n\n// ─── inspectSignal — rich console output ─────────────────────────────────────\n\n/**\n * Print a signal's current state to the console in a readable format.\n *\n * @example\n * const count = signal(42, { name: \"count\" })\n * inspectSignal(count)\n * // Console:\n * // 🔍 Signal \"count\"\n * // value: 42\n * // subscribers: 3\n */\nexport function inspectSignal<T>(sig: Signal<T>): SignalDebugInfo<T> {\n const info = sig.debug()\n\n console.group(`🔍 Signal ${info.name ? `\"${info.name}\"` : '(anonymous)'}`)\n console.log('value:', info.value)\n console.log('subscribers:', info.subscriberCount)\n console.groupEnd()\n\n return info\n}\n","declare const process: { env: { NODE_ENV?: string } } | undefined\n\nconst __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'\n\nimport { batch, enqueuePendingNotification, isBatching } from './batch'\nimport { _notifyTraceListeners, isTracing } from './debug'\nimport { notifySubscribers, trackSubscriber } from './tracking'\n\n// Dev-time counter sink — see packages/internals/perf-harness for contract.\ninterface ViteMeta {\n readonly env?: { readonly DEV?: boolean }\n}\nconst _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }\n\nexport interface SignalDebugInfo<T> {\n /** Signal name (set via options or inferred) */\n name: string | undefined\n /** Current value (same as peek()) */\n value: T\n /** Number of active subscribers */\n subscriberCount: number\n}\n\n/**\n * Read-only reactive value — the common interface that both Signal and Computed satisfy.\n * Use this as the parameter type when a function only needs to read a reactive value.\n */\nexport type ReadonlySignal<T> = () => T\n\nexport interface Signal<T> {\n (): T\n /** Read the current value WITHOUT registering a reactive dependency. */\n peek(): T\n set(value: T): void\n update(fn: (current: T) => T): void\n /**\n * Subscribe a static listener directly — no effect overhead (no withTracking,\n * no cleanupEffect, no effectDeps WeakMap). Use when the dependency is fixed\n * and dynamic re-tracking is not needed.\n * Returns a disposer that removes the subscription.\n */\n subscribe(listener: () => void): () => void\n /**\n * Register a direct updater — even lighter than subscribe().\n * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete).\n * Intended for compiler-emitted DOM bindings (_bindText, _bindDirect).\n * Returns a disposer that nulls the slot.\n */\n direct(updater: () => void): () => void\n /** Debug name — useful for devtools and logging. */\n label: string | undefined\n /** Returns a snapshot of the signal's debug info (value, name, subscriber count). */\n debug(): SignalDebugInfo<T>\n}\n\nexport interface SignalOptions {\n /** Debug name for this signal — shows up in devtools and debug() output. */\n name?: string\n}\n\n// Internal shape of a signal function — state stored as properties on the\n// function object so methods can be shared via assignment (not per-signal closures).\ninterface SignalFn<T> {\n (): T\n /** @internal current value */\n _v: T\n /** @internal subscriber set (lazily allocated by trackSubscriber) */\n _s: Set<() => void> | null\n /** @internal direct updaters array — compiler-emitted DOM updaters (lazily allocated) */\n _d: ((() => void) | null)[] | null\n peek(): T\n set(value: T): void\n update(fn: (current: T) => T): void\n subscribe(listener: () => void): () => void\n /** Register a direct updater — lighter than subscribe, uses array index disposal. */\n direct(updater: () => void): () => void\n label: string | undefined\n debug(): SignalDebugInfo<T>\n}\n\n// Shared method implementations — defined once, assigned to every signal.\n// Uses `this` binding (signal methods are always called as `signal.method()`).\nfunction _peek(this: SignalFn<unknown>) {\n return this._v\n}\n\nfunction _set(this: SignalFn<unknown>, newValue: unknown) {\n if (Object.is(this._v, newValue)) return\n if ((import.meta as ViteMeta).env?.DEV === true)\n _countSink.__pyreon_count__?.('reactivity.signalWrite')\n const prev = this._v\n this._v = newValue\n if (isTracing()) _notifyTraceListeners(this as unknown as Signal<unknown>, prev, newValue)\n // Auto-batch the notification chain. Without this, a diamond dependency\n // graph (a → b, c → d → effect) fires the apex effect TWICE per write\n // because subscribers cascade inline: the first path through `b` reaches\n // `effect`, whose read clears `d`'s dirty flag; then `c`'s notification\n // re-dirties `d` and re-notifies `effect`. Wrapping the notify chain in\n // `batch()` routes cascade-notifications through the pending Set, which\n // dedupes on `d.recompute` and on `effect.run`.\n //\n // The batch is synchronous — observable behaviour is unchanged for the\n // common case (subscribers still fire immediately after the write). Only\n // the dedup semantics change, which is a bug fix.\n //\n // Short-circuit when already inside a batch so we don't wrap redundantly.\n if (isBatching()) {\n if (this._d) notifyDirect(this._d)\n if (this._s) notifySubscribers(this._s)\n } else {\n batch(() => {\n if (this._d) notifyDirect(this._d)\n if (this._s) notifySubscribers(this._s)\n })\n }\n}\n\nfunction _update(this: SignalFn<unknown>, fn: (current: unknown) => unknown) {\n _set.call(this, fn(this._v))\n}\n\nfunction _subscribe(this: SignalFn<unknown>, listener: () => void): () => void {\n if (!this._s) this._s = new Set()\n this._s.add(listener)\n return () => this._s?.delete(listener)\n}\n\n/**\n * Register a direct updater — lighter than subscribe().\n * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete overhead).\n * Used by compiler-emitted _bindText/_bindDirect for zero-overhead DOM bindings.\n */\nfunction _directFn(this: SignalFn<unknown>, updater: () => void): () => void {\n if (!this._d) this._d = []\n const arr = this._d\n const idx = arr.length\n arr.push(updater)\n return () => {\n arr[idx] = null\n }\n}\n\n/**\n * Notify direct updaters — flat array iteration, batch-aware.\n * Null slots (from disposed updaters) are skipped.\n */\nfunction notifyDirect(updaters: ((() => void) | null)[]): void {\n if (isBatching()) {\n for (let i = 0; i < updaters.length; i++) {\n const fn = updaters[i]\n if (fn) enqueuePendingNotification(fn)\n }\n } else {\n for (let i = 0; i < updaters.length; i++) {\n updaters[i]?.()\n }\n }\n}\n\nfunction _debug(this: SignalFn<unknown>): SignalDebugInfo<unknown> {\n return {\n name: this.label,\n value: this._v,\n subscriberCount: this._s?.size ?? 0,\n }\n}\n\n/**\n * Create a reactive signal.\n *\n * Only 1 closure is allocated (the read function). State is stored as\n * properties on the function object (_v, _s) and methods (peek, set,\n * update, subscribe) are shared across all signals — not per-signal closures.\n */\nexport function signal<T>(initialValue: T, options?: SignalOptions): Signal<T> {\n if ((import.meta as ViteMeta).env?.DEV === true)\n _countSink.__pyreon_count__?.('reactivity.signalCreate')\n // The read function is the only per-signal closure.\n // It doubles as the SubscriberHost (_s property) for trackSubscriber.\n const read = ((...args: unknown[]) => {\n if (__DEV__ && args.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(\n '[Pyreon] signal() was called with an argument. ' +\n 'Use signal.set(value) or signal.update(fn) to write. ' +\n 'signal(value) only reads — the argument is ignored.',\n )\n }\n trackSubscriber(read as SignalFn<T>)\n return read._v\n }) as unknown as SignalFn<T>\n\n read._v = initialValue\n read._s = null\n read._d = null\n read.peek = _peek as () => T\n read.set = _set as (value: T) => void\n read.update = _update as (fn: (current: T) => T) => void\n read.subscribe = _subscribe as (listener: () => void) => () => void\n read.direct = _directFn as (updater: () => void) => () => void\n read.debug = _debug as () => SignalDebugInfo<T>\n read.label = options?.name\n\n return read as unknown as Signal<T>\n}\n","/**\n * createStore — deep reactive Proxy store.\n *\n * Wraps a plain object/array in a Proxy that creates a fine-grained signal for\n * every property. Direct mutations (`store.count++`, `store.items[0].label = \"x\"`)\n * trigger only the signals for the mutated properties — not the whole tree.\n *\n * @example\n * const state = createStore({ count: 0, items: [{ id: 1, text: \"hello\" }] })\n *\n * effect(() => console.log(state.count)) // tracks state.count only\n * state.count++ // only the count effect re-runs\n * state.items[0].text = \"world\" // only text-tracking effects re-run\n */\n\nimport { type Signal, signal } from './signal'\n\n// WeakMap: raw object → its reactive proxy (ensures each raw object gets one proxy)\nconst proxyCache = new WeakMap<object, object>()\n\nconst IS_STORE = Symbol('pyreon.store')\n\n/** Returns true if the value is a createStore proxy. */\nexport function isStore(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n (value as Record<symbol, unknown>)[IS_STORE] === true\n )\n}\n\n/**\n * Create a deep reactive store from a plain object or array.\n * Returns a proxy — mutations to the proxy trigger fine-grained reactive updates.\n */\nexport function createStore<T extends object>(initial: T): T {\n return wrap(initial) as T\n}\n\nfunction wrap(raw: object): object {\n const cached = proxyCache.get(raw)\n if (cached) return cached\n\n // Per-property signals. Lazily created on first access.\n const propSignals = new Map<PropertyKey, Signal<unknown>>()\n // For arrays: track length changes separately (push/pop/splice affect length)\n const isArray = Array.isArray(raw)\n const lengthSig = isArray ? signal((raw as unknown[]).length) : null\n\n function getOrCreateSignal(key: PropertyKey): Signal<unknown> {\n if (!propSignals.has(key)) {\n propSignals.set(key, signal((raw as Record<PropertyKey, unknown>)[key]))\n }\n return propSignals.get(key) as Signal<unknown>\n }\n\n const proxy = new Proxy(raw, {\n get(target, key) {\n // Pass through the identity marker and non-string/number keys (symbols, etc.)\n if (key === IS_STORE) return true\n if (typeof key === 'symbol') return (target as Record<symbol, unknown>)[key]\n\n // Array length — tracked via dedicated signal for push/pop/splice reactivity\n if (isArray && key === 'length') return lengthSig?.()\n\n // Non-own properties: prototype methods (forEach, map, push, …)\n // These must be returned untracked so array methods work normally.\n // Array methods will then go through set/get on indices via the proxy.\n if (!Object.hasOwn(target, key)) {\n return (target as Record<PropertyKey, unknown>)[key]\n }\n\n // Track via per-property signal\n const value = getOrCreateSignal(key)()\n\n // Deep reactivity: wrap nested objects/arrays transparently\n if (value !== null && typeof value === 'object') {\n return wrap(value as object)\n }\n\n return value\n },\n\n set(target, key, value) {\n if (typeof key === 'symbol') {\n ;(target as Record<symbol, unknown>)[key] = value\n return true\n }\n\n const prevLength = isArray ? (target as unknown[]).length : 0\n ;(target as Record<PropertyKey, unknown>)[key] = value\n\n // Array length set directly (e.g. arr.length = 0)\n if (isArray && key === 'length') {\n lengthSig?.set(value as number)\n return true\n }\n\n // Update or create signal for this property\n if (propSignals.has(key)) {\n propSignals.get(key)?.set(value)\n } else {\n propSignals.set(key, signal(value))\n }\n\n // If array length changed (e.g. via push/splice index assignment), update it\n if (isArray && (target as unknown[]).length !== prevLength) {\n lengthSig?.set((target as unknown[]).length)\n }\n\n return true\n },\n\n deleteProperty(target, key) {\n delete (target as Record<PropertyKey, unknown>)[key]\n if (typeof key !== 'symbol' && propSignals.has(key)) {\n propSignals.get(key)?.set(undefined)\n propSignals.delete(key)\n }\n if (isArray) lengthSig?.set((target as unknown[]).length)\n return true\n },\n\n has(target, key) {\n return Reflect.has(target, key)\n },\n\n ownKeys(target) {\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, key) {\n return Reflect.getOwnPropertyDescriptor(target, key)\n },\n })\n\n proxyCache.set(raw, proxy)\n return proxy\n}\n","/**\n * reconcile — surgically diff new state into an existing createStore proxy.\n *\n * Instead of replacing the store root (which would trigger all downstream effects),\n * reconcile walks both the new value and the store in parallel and only calls\n * `.set()` on signals whose value actually changed.\n *\n * Ideal for applying API responses to a long-lived store:\n *\n * @example\n * const state = createStore({ user: { name: \"Alice\", age: 30 }, items: [] })\n *\n * // API response arrives:\n * reconcile({ user: { name: \"Alice\", age: 31 }, items: [{ id: 1 }] }, state)\n * // → only state.user.age signal fires (name unchanged)\n * // → state.items[0] is newly created\n *\n * Arrays are reconciled by index — elements at the same index are recursively\n * diffed rather than replaced wholesale. Excess old elements are removed.\n */\n\nimport { isStore } from './store'\n\ntype AnyObject = Record<PropertyKey, unknown>\n\nexport function reconcile<T extends object>(source: T, target: T): void {\n _reconcileInner(source, target, new WeakSet())\n}\n\nfunction _reconcileInner(source: object, target: object, seen: WeakSet<object>): void {\n if (seen.has(source)) return // circular reference — stop recursion\n seen.add(source)\n if (Array.isArray(source) && Array.isArray(target)) {\n _reconcileArray(source as unknown[], target as unknown[], seen)\n } else {\n _reconcileObject(source as AnyObject, target as AnyObject, seen)\n }\n}\n\nfunction _reconcileArray(source: unknown[], target: unknown[], seen: WeakSet<object>): void {\n const targetLen = target.length\n const sourceLen = source.length\n\n // Update / add entries\n for (let i = 0; i < sourceLen; i++) {\n const sv = source[i]\n const tv = (target as unknown[])[i]\n\n if (\n i < targetLen &&\n sv !== null &&\n typeof sv === 'object' &&\n tv !== null &&\n typeof tv === 'object'\n ) {\n // Both sides are objects — recurse\n _reconcileInner(sv as object, tv as object, seen)\n } else {\n // Scalar or new entry — write directly (signal will skip if equal via Object.is)\n ;(target as unknown[])[i] = sv\n }\n }\n\n // Trim excess entries\n if (targetLen > sourceLen) {\n target.length = sourceLen\n }\n}\n\nfunction _reconcileObject(source: AnyObject, target: AnyObject, seen: WeakSet<object>): void {\n const sourceKeys = Object.keys(source)\n const targetKeys = new Set(Object.keys(target))\n\n for (const key of sourceKeys) {\n const sv = source[key]\n const tv = target[key]\n\n if (sv !== null && typeof sv === 'object' && tv !== null && typeof tv === 'object') {\n if (isStore(tv)) {\n // Both objects — recurse into the store node\n _reconcileInner(sv as object, tv as object, seen)\n } else {\n // Target is a raw object (not yet proxied) — just assign\n target[key] = sv\n }\n } else {\n // Scalar: assign (store proxy's set trap skips if Object.is equal)\n target[key] = sv\n }\n\n targetKeys.delete(key)\n }\n\n // Remove keys that no longer exist in source\n for (const key of targetKeys) {\n delete target[key]\n }\n}\n","import { effect } from './effect'\nimport type { Signal } from './signal'\nimport { signal } from './signal'\nimport { runUntracked } from './tracking'\n\nexport interface Resource<T> {\n /** The latest resolved value (undefined while loading or on error). */\n data: Signal<T | undefined>\n /** True while a fetch is in flight. */\n loading: Signal<boolean>\n /** The last error thrown by the fetcher, or undefined. */\n error: Signal<unknown>\n /** Re-run the fetcher with the current source value. */\n refetch(): void\n}\n\n/**\n * Async data primitive. Fetches data reactively whenever `source()` changes.\n *\n * @example\n * const userId = signal(1)\n * const user = createResource(userId, (id) => fetchUser(id))\n * // user.data() — the fetched user (undefined while loading)\n * // user.loading() — true while in flight\n * // user.error() — last error\n */\nexport function createResource<T, P>(\n source: () => P,\n fetcher: (param: P) => Promise<T>,\n): Resource<T> {\n const data = signal<T | undefined>(undefined)\n const loading = signal(false)\n const error = signal<unknown>(undefined)\n let requestId = 0\n\n const doFetch = (param: P) => {\n const id = ++requestId\n loading.set(true)\n error.set(undefined)\n fetcher(param)\n .then((result) => {\n if (id !== requestId) return\n data.set(result)\n loading.set(false)\n })\n .catch((err: unknown) => {\n if (id !== requestId) return\n error.set(err)\n loading.set(false)\n })\n }\n\n effect(() => {\n const param = source()\n runUntracked(() => doFetch(param))\n })\n\n return {\n data,\n loading,\n error,\n refetch() {\n runUntracked(() => doFetch(source()))\n },\n }\n}\n","import { effect } from './effect'\n\nexport interface WatchOptions {\n /** If true, call the callback immediately with the current value on setup. Default: false. */\n immediate?: boolean\n}\n\n/**\n * Watch a reactive source and run a callback whenever it changes.\n *\n * Returns a stop function that disposes the watcher.\n *\n * The callback receives (newValue, oldValue). On the first call (when\n * `immediate` is true) oldValue is `undefined`.\n *\n * The callback may return a cleanup function that is called before each\n * re-run and on stop — useful for cancelling async work.\n *\n * @example\n * const stop = watch(\n * () => userId(),\n * async (id, prev) => {\n * const data = await fetch(`/api/user/${id}`)\n * setUser(await data.json())\n * },\n * )\n * // Later: stop()\n */\nexport function watch<T>(\n source: () => T,\n callback: (newVal: T, oldVal: T | undefined) => void | (() => void),\n opts: WatchOptions = {},\n): () => void {\n let oldVal: T | undefined\n let isFirst = true\n let cleanupFn: (() => void) | undefined\n\n const e = effect(() => {\n const newVal = source()\n\n if (isFirst) {\n isFirst = false\n oldVal = newVal\n if (opts.immediate) {\n const result = callback(newVal, undefined)\n if (typeof result === 'function') cleanupFn = result\n }\n return\n }\n\n if (cleanupFn) {\n cleanupFn()\n cleanupFn = undefined\n }\n\n const result = callback(newVal, oldVal)\n if (typeof result === 'function') cleanupFn = result\n oldVal = newVal\n })\n\n return () => {\n e.dispose()\n if (cleanupFn) {\n cleanupFn()\n cleanupFn = undefined\n }\n }\n}\n"],"mappings":";AAIA,IAAI,aAAa;AAKjB,MAAM,uBAAO,IAAI,KAAiB;AAClC,MAAM,uBAAO,IAAI,KAAiB;AAClC,IAAI,uBAAuB;AAE3B,SAAgB,MAAM,IAAsB;AAC1C;AACA,KAAI;AACF,MAAI;WACI;AACR;AACA,MAAI,eAAe,KAAK,qBAAqB,OAAO,GAAG;AAgBrD,gBAAa;AACb,OAAI;AACF,WAAO,qBAAqB,OAAO,GAAG;KAIpC,MAAM,QAAQ;AACd,4BAAuB,UAAU,OAAO,OAAO;AAC/C,UAAK,MAAM,UAAU,MAAO,SAAQ;AACpC,WAAM,OAAO;;aAEP;AACR,iBAAa;;;;;AAMrB,SAAgB,aAAsB;AACpC,QAAO,aAAa;;AAGtB,SAAgB,2BAA2B,QAA0B;AACnE,sBAAqB,IAAI,OAAO;;;;;;;;;;;AAYlC,SAAgB,WAA0B;AACxC,QAAO,IAAI,SAAS,YAAY,eAAe,QAAQ,CAAC;;;;;;;;;;;;;;;;;AC3D1D,IAAa,OAAb,MAAqB;kBACF;kBACA,KAA0B;kBAC1B,KAA6B;CAE9C,YAAY,OAAU;AACpB,OAAK,KAAK;;CAGZ,OAAU;AACR,SAAO,KAAK;;CAGd,IAAI,OAAgB;AAClB,MAAI,OAAO,GAAG,KAAK,IAAI,MAAM,CAAE;AAC/B,OAAK,KAAK;AACV,MAAI,KAAK,GAAI,MAAK,IAAI;WACb,KAAK,GAAI,MAAK,MAAM,MAAM,KAAK,GAAI,KAAI;;CAGlD,OAAO,IAA6B;AAClC,OAAK,IAAI,GAAG,KAAK,GAAG,CAAC;;;;;;;CAQvB,OAAO,UAA4B;AACjC,MAAI,CAAC,KAAK,MAAM,CAAC,KAAK,GACpB,MAAK,KAAK;OACL;AAEL,OAAI,CAAC,KAAK,IAAI;AACZ,SAAK,qBAAK,IAAI,KAAK;AACnB,QAAI,KAAK,IAAI;AACX,UAAK,GAAG,IAAI,KAAK,GAAG;AACpB,UAAK,KAAK;;;AAGd,QAAK,GAAG,IAAI,SAAS;;;CAIzB,UAAU,UAAkC;AAC1C,OAAK,OAAO,SAAS;AAIrB,eAAa;AACX,OAAI,KAAK,OAAO,SAAU,MAAK,KAAK;OAC/B,MAAK,IAAI,OAAO,SAAS;;;;AAKpC,SAAgB,KAAQ,OAAmB;AACzC,QAAO,IAAI,KAAK,MAAM;;;;;ACnExB,IAAa,cAAb,MAAyB;CACvB,AAAQ,WAAyC;CACjD,AAAQ,UAAU;CAClB,AAAQ,eAAsC;CAC9C,AAAQ,iBAAiB;;CAGzB,IAAI,GAA8B;AAChC,MAAI,CAAC,KAAK,QAAS;AACnB,MAAI,KAAK,aAAa,KAAM,MAAK,WAAW,EAAE;AAC9C,OAAK,SAAS,KAAK,EAAE;;;;;;;;CASvB,WAAc,IAAgB;EAC5B,MAAM,OAAO;AACb,kBAAgB;AAChB,MAAI;AACF,UAAO,IAAI;YACH;AACR,mBAAgB;;;;CAKpB,cAAc,IAAsB;AAClC,MAAI,KAAK,iBAAiB,KAAM,MAAK,eAAe,EAAE;AACtD,OAAK,aAAa,KAAK,GAAG;;;;;;CAO5B,kBAAwB;AACtB,MAAI,CAAC,KAAK,WAAW,CAAC,KAAK,gBAAgB,KAAK,aAAa,WAAW,KAAK,KAAK,eAAgB;AAClG,OAAK,iBAAiB;AACtB,uBAAqB;AACnB,QAAK,iBAAiB;AACtB,OAAI,CAAC,KAAK,WAAW,CAAC,KAAK,aAAc;AACzC,QAAK,MAAM,MAAM,KAAK,aACpB,KAAI;AACF,QAAI;YACG,KAAK;AACZ,YAAQ,MAAM,iCAAiC,IAAI;;IAGvD;;;CAIJ,OAAa;AACX,MAAI,CAAC,KAAK,QAAS;AACnB,MAAI,KAAK,SACP,MAAK,MAAM,KAAK,KAAK,SAAU,GAAE,SAAS;AAE5C,OAAK,WAAW;AAChB,OAAK,eAAe;AACpB,OAAK,iBAAiB;AACtB,OAAK,UAAU;;;AAInB,IAAI,gBAAoC;AAExC,SAAgB,kBAAsC;AACpD,QAAO;;AAGT,SAAgB,gBAAgB,OAAiC;AAC/D,iBAAgB;;;AAIlB,SAAgB,cAA2B;AACzC,QAAO,IAAI,aAAa;;;;;AC/E1B,IAAI,eAAoC;AAIxC,MAAM,6BAAa,IAAI,SAA2C;AAIlE,IAAI,iBAA2C;AAK/C,IAAI,sBAAsB;AAE1B,SAAgB,iBAAiB,WAA2C;AAC1E,kBAAiB;;AAGnB,SAAgB,sBAAsB,MAAqB;AACzD,uBAAsB;;;;;;;AAkBxB,SAAgB,gBAAgB,MAAsB;AACpD,KAAI,cAAc;AAChB,MAAI,CAAC,KAAK,GAAI,MAAK,qBAAK,IAAI,KAAK;AACjC,OAAK,GAAG,IAAI,aAAa;AAGzB,MAAI,oBAAqB;AACzB,MAAI,eAEF,gBAAe,KAAK,KAAK,GAAG;OACvB;GAEL,IAAI,OAAO,WAAW,IAAI,aAAa;AACvC,OAAI,CAAC,MAAM;AACT,2BAAO,IAAI,KAAK;AAChB,eAAW,IAAI,cAAc,KAAK;;AAEpC,QAAK,IAAI,KAAK,GAAG;;;;;;;;AASvB,SAAgB,cAAc,IAAsB;CAClD,MAAM,OAAO,WAAW,IAAI,GAAG;AAC/B,KAAI,MAAM;AACR,OAAK,MAAM,OAAO,KAAM,KAAI,OAAO,GAAG;AACtC,OAAK,OAAO;;;AAIhB,SAAgB,kBAAkB,aAA8B;AAC9D,KAAI,YAAY,SAAS,EAAG;AAE5B,KAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,MAAM,YAAY,QAAQ,CAAC,MAAM,CAAC;AACxC,MAAI,YAAY,CAAE,4BAA2B,IAAI;MAC5C,MAAK;AACV;;AAEF,KAAI,YAAY,CAEd,MAAK,MAAM,OAAO,YAAa,4BAA2B,IAAI;MACzD;EAQL,MAAM,eAAe,YAAY;EACjC,IAAI,IAAI;AACR,OAAK,MAAM,OAAO,aAAa;AAC7B,OAAI,KAAK,aAAc;AACvB,QAAK;AACL;;;;AAKN,SAAgB,aAAgB,IAAgB,SAAqB;CACnE,MAAM,OAAO;AACb,gBAAe;AACf,KAAI;AACF,SAAO,SAAS;WACR;AACR,iBAAe;;;AAKnB,IAAI,cAAmC;AAEvC,SAAgB,iBAAiB,IAAsB;AACrD,eAAc;AACd,gBAAe;;AAGjB,SAAgB,uBAA6B;AAC3C,gBAAe;AACf,eAAc;;;AAIhB,SAAgB,aAAgB,IAAgB;CAC9C,MAAM,OAAO;AACb,gBAAe;AACf,KAAI;AACF,SAAO,IAAI;WACH;AACR,iBAAe;;;;;;ACjInB,MAAMA,eAAa;AASnB,IAAI,oBAA2C;;;;;;;;;;;;;;;;;;AAmB/C,SAAgB,UAAU,IAAsB;AAC9C,KAAI,kBACF,mBAAkB,KAAK,GAAG;;AAQ9B,IAAI,wBAAyC;AAI7C,IAAW,iBAAyC,QAAQ;AAC1D,SAAQ,MAAM,oCAAoC,IAAI;;AAGxD,SAAgB,gBAAgB,IAAkC;AAChE,iBAAgB;;;AAIlB,SAASC,mBAAiB,MAAyB,IAAsB;AACvE,KAAI,KAAK,WAAW,GAAG;AACpB,EAAC,KAAK,GAAuB,OAAO,GAAG;AACxC,OAAK,SAAS;YACL,KAAK,SAAS,GAAG;AAC1B,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAK,CAAC,KAAK,GAAuB,OAAO,GAAG;AAC7E,OAAK,SAAS;;;AAIlB,SAAgB,OAAO,IAAuC;CAG5D,MAAM,QAAQ,iBAAiB;CAC/B,IAAI,WAAW;CACf,IAAI,aAAa;CACjB,IAAI;CAEJ,MAAM,OAA0B,EAAE;CAElC,IAAI;CAIJ,IAAI,eAAgC;CAEpC,MAAM,mBAAmB;AACvB,MAAI,cAAc;AAChB,QAAK,MAAM,MAAM,aACf,KAAI;AACF,OAAG,SAAS;YACL,KAAK;AACZ,kBAAc,IAAI;;AAGtB,kBAAe;;AAEjB,MAAI,UAAU;AACZ,QAAK,MAAM,KAAK,SACd,KAAI;AACF,OAAG;YACI,KAAK;AACZ,kBAAc,IAAI;;AAGtB,cAAW;;AAEb,MAAI,OAAO,YAAY,YAAY;AACjC,OAAI;AACF,aAAS;YACF,KAAK;AACZ,kBAAc,IAAI;;AAEpB,aAAU;;;CAId,MAAM,YAAY;AAChB,MAAI,SAAU;AACd,MAAK,OAAO,KAAkB,KAAK,QAAQ,KACzC,cAAW,mBAAmB,uBAAuB;AAEvD,cAAY;EAIZ,MAAM,iBAAiB;EACvB,MAAM,WAAqB,EAAE;AAC7B,0BAAwB;AACxB,MAAI;AACF,sBAAiB,MAAM,IAAI;AAC3B,oBAAiB,KAAK;GAEtB,MAAM,YAA4B,EAAE;AACpC,uBAAoB;AACpB,aAAU,aAAa,KAAK,GAAG,IAAI;AACnC,uBAAoB;AACpB,OAAI,UAAU,SAAS,EAAG,YAAW;AACrC,oBAAiB,KAAK;WACf,KAAK;AACZ,uBAAoB;AACpB,oBAAiB,KAAK;AACtB,iBAAc,IAAI;YACV;AACR,2BAAwB;;AAE1B,MAAI,SAAS,SAAS,EAAG,gBAAe;AAGxC,MAAI,CAAC,WAAY,QAAO,iBAAiB;AACzC,eAAa;;AAGf,MAAK;CAEL,MAAM,IAAY,EAChB,UAAU;AACR,cAAY;AACZ,aAAW;AACX,qBAAiB,MAAM,IAAI;IAE9B;AAID,KAAI,0BAA0B,KAC5B,uBAAsB,KAAK,EAAE;KAG7B,kBAAiB,EAAE,IAAI,EAAE;AAG3B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,MAAM,IAA4B;CAChD,MAAM,OAA0B,EAAE;CAClC,IAAI,WAAW;CAEf,MAAM,YAAY;AAChB,MAAI,SAAU;AACd,MAAI;;AAIN,kBAAiB,KAAK;AACtB,cAAa,KAAK,GAAG;AACrB,kBAAiB,KAAK;CAEtB,MAAM,gBAAgB;AACpB,MAAI,SAAU;AACd,aAAW;AACX,OAAK,MAAM,KAAK,KAAM,GAAE,OAAO,IAAI;AACnC,OAAK,SAAS;;AAIhB,kBAAiB,EAAE,IAAI,EAAE,SAAS,CAAC;AAEnC,QAAO;;;AAIT,SAAS,sBAAsB,MAAyB,KAAiB,IAAsB;AAC7F,KAAI,KAAK,WAAW,GAAG;AACpB,EAAC,KAAK,GAAuB,OAAO,IAAI;AACzC,OAAK,SAAS;YACL,KAAK,SAAS,GAAG;AAC1B,OAAK,MAAM,KAAK,KAAM,GAAE,OAAO,IAAI;AACnC,OAAK,SAAS;;AAEhB,kBAAiB,KAAK;AACtB,kBAAiB,IAAI;AACrB,KAAI;AACF,MAAI;WACI;AACR,wBAAsB;AACtB,mBAAiB,KAAK;;;AAI1B,SAAgB,aAAa,IAA4B;CACvD,MAAM,OAA0B,EAAE;CAClC,IAAI,WAAW;CACf,IAAI,aAAa;CAEjB,MAAM,YAAY;AAChB,MAAI,SAAU;AAKd,MAAI,YAAY;AACd,gBAAa;AACb,oBAAiB,KAAK;AACtB,oBAAiB,IAAI;AACrB,OAAI;AACF,QAAI;aACI;AACR,0BAAsB;AACtB,qBAAiB,KAAK;;QAGxB,uBAAsB,MAAM,KAAK,GAAG;;AAIxC,MAAK;CAEL,MAAM,gBAAgB;AACpB,MAAI,SAAU;AACd,aAAW;AACX,MAAI,KAAK,WAAW,EACjB,CAAC,KAAK,GAAuB,OAAO,IAAI;MAEzC,MAAK,MAAM,KAAK,KAAM,GAAE,OAAO,IAAI;AAErC,OAAK,SAAS;;AAIhB,kBAAiB,EAAE,IAAI,EAAE,SAAS,CAAC;AAEnC,QAAO;;;;;AChRT,MAAMC,eAAa;;AA2BnB,SAAS,iBAAiB,MAAyB,IAAsB;AACvE,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAK,CAAC,KAAK,GAAuB,OAAO,GAAG;AAC7E,MAAK,SAAS;;;AAIhB,SAAS,mBAAsB,MAAyB,QAAoB,IAAgB;AAC1F,kBAAiB,KAAK;CACtB,MAAM,SAAS,aAAa,QAAQ,GAAG;AACvC,kBAAiB,KAAK;AACtB,QAAO;;AAGT,SAAgB,SAAY,IAAa,SAA2C;AAClF,QAAO,SAAS,SAAS,mBAAmB,IAAI,QAAQ,OAAO,GAAG,aAAa,GAAG;;;;;;;;;;;;AAapF,SAAS,aAAgB,IAA0B;CACjD,IAAI;CACJ,IAAI,QAAQ;CACZ,IAAI,WAAW;CACf,IAAI,UAAU;CACd,MAAM,OAA0B,EAAE;CAClC,MAAM,OAAuC,EAAE,IAAI,MAAM;CACzD,IAAI,YAA4C;CAEhD,MAAM,kBAAkB;AACtB,MAAI,YAAY,MAAO;AACvB,UAAQ;AACR,MAAI,KAAK,GAAI,mBAAkB,KAAK,GAAG;AACvC,MAAI,UAAW,MAAK,MAAM,KAAK,UAAW,MAAK;;CAGjD,MAAM,aAAgB;AACpB,kBAAgB,KAAK;AACrB,MAAI,OAAO;AACT,OAAK,OAAO,KAAkB,KAAK,QAAQ,KACzC,cAAW,mBAAmB,+BAA+B;AAC/D,OAAI;AACF,QAAI,SAAS;AAKX,2BAAsB,KAAK;AAC3B,aAAQ,aAAa,WAAW,GAAG;AACnC,2BAAsB,MAAM;WACvB;AACL,aAAQ,mBAAmB,MAAM,WAAW,GAAG;AAC/C,eAAU;;YAEL,KAAK;AACZ,kBAAc,IAAI;;AAEpB,WAAQ;;AAEV,SAAO;;AAGT,MAAK,gBAAgB;AACnB,aAAW;AACX,mBAAiB,MAAM,UAAU;;AAGnC,QAAO,eAAe,MAAM,MAAM;EAChC,WAAW;AACT,OAAI,MAAO,OAAM;AACjB,UAAO;;EAET,YAAY;EACb,CAAC;AAEF,MAAK,UAAU,YAAsC;AACnD,MAAI,CAAC,UAAW,aAAY,EAAE;EAC9B,MAAM,MAAM;EACZ,MAAM,MAAM,IAAI;AAChB,MAAI,KAAK,QAAQ;AACjB,eAAa;AACX,OAAI,OAAO;;;AAIf,kBAAiB,EAAE,IAAI,EAAE,SAAS,KAAK,SAAS,CAAC;AACjD,QAAO;;;;;;;;AAST,SAAS,mBAAsB,IAAa,QAAoD;CAC9F,IAAI;CACJ,IAAI,QAAQ;CACZ,IAAI,cAAc;CAClB,IAAI,WAAW;CACf,MAAM,OAA0B,EAAE;CAClC,MAAM,OAAuC,EAAE,IAAI,MAAM;CACzD,IAAI,YAA4C;CAEhD,MAAM,kBAAkB;AACtB,MAAI,SAAU;AACd,MAAK,OAAO,KAAkB,KAAK,QAAQ,KACzC,cAAW,mBAAmB,+BAA+B;AAC/D,mBAAiB,MAAM,UAAU;AACjC,MAAI;GACF,MAAM,OAAO,mBAAmB,MAAM,WAAW,GAAG;AACpD,OAAI,eAAe,OAAO,OAAY,KAAK,CAAE;AAC7C,WAAQ;AACR,WAAQ;AACR,iBAAc;WACP,KAAK;AACZ,iBAAc,IAAI;AAClB;;AAEF,MAAI,KAAK,GAAI,mBAAkB,KAAK,GAAG;AACvC,MAAI,UAAW,MAAK,MAAM,KAAK,UAAW,MAAK;;CAGjD,MAAM,aAAgB;AACpB,kBAAgB,KAAK;AACrB,MAAI,OAAO;AACT,OAAK,OAAO,KAAkB,KAAK,QAAQ,KACzC,cAAW,mBAAmB,+BAA+B;AAC/D,oBAAiB,MAAM,UAAU;AACjC,OAAI;AACF,YAAQ,mBAAmB,MAAM,WAAW,GAAG;YACxC,KAAK;AACZ,kBAAc,IAAI;;AAEpB,WAAQ;AACR,iBAAc;;AAEhB,SAAO;;AAGT,MAAK,gBAAgB;AACnB,aAAW;AACX,mBAAiB,MAAM,UAAU;AACjC,gBAAc,UAAU;;AAG1B,QAAO,eAAe,MAAM,MAAM;EAChC,WAAW;AACT,OAAI,MAAO,OAAM;AACjB,UAAO;;EAET,YAAY;EACb,CAAC;AAEF,MAAK,UAAU,YAAsC;AACnD,MAAI,CAAC,UAAW,aAAY,EAAE;EAC9B,MAAM,MAAM;EACZ,MAAM,MAAM,IAAI;AAChB,MAAI,KAAK,QAAQ;AACjB,eAAa;AACX,OAAI,OAAO;;;AAIf,kBAAiB,EAAE,IAAI,EAAE,SAAS,KAAK,SAAS,CAAC;AACjD,QAAO;;;;;;;;;;AC9MT,SAAS,aAAa,QAA+B;AACnD,KAAI,OAAO,SAAS,EAAG;AACvB,KAAI,OAAO,SAAS,GAAG;AACpB,EAAC,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAsB;AAC/C;;CAEF,MAAM,eAAe,OAAO;CAC5B,IAAI,IAAI;AACR,MAAK,MAAM,MAAM,QAAQ;AACvB,MAAI,KAAK,aAAc;AACvB,MAAI;AACJ;;;;;;;;;;;;;;;;AAiBJ,SAAgB,eAAkB,QAAwC;CACxE,MAAM,uBAAO,IAAI,KAAyB;CAC1C,IAAI;CACJ,IAAI,cAAc;AAElB,cAAa;EACX,MAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,aAAa;AAChB,iBAAc;AACd,aAAU;AACV;;AAEF,MAAI,OAAO,GAAG,MAAM,QAAQ,CAAE;EAC9B,MAAM,MAAM;AACZ,YAAU;EAGV,MAAM,YAAY,KAAK,IAAI,IAAI;EAC/B,MAAM,YAAY,KAAK,IAAI,KAAK;AAChC,MAAI,UAAW,cAAa,UAAU;AACtC,MAAI,UAAW,cAAa,UAAU;GACtC;CAGF,MAAM,wBAAQ,IAAI,KAAwC;AAE1D,SAAQ,UAAsB;EAC5B,IAAI,OAAO,MAAM,IAAI,MAAM;AAC3B,MAAI,CAAC,MAAM;GACT,IAAI,SAAS,KAAK,IAAI,MAAM;AAC5B,OAAI,CAAC,QAAQ;AACX,6BAAS,IAAI,KAAK;AAClB,SAAK,IAAI,OAAO,OAAO;;AAEzB,UAAO,EAAE,IAAI,QAAQ;AACrB,SAAM,IAAI,OAAO,KAAK;;AAExB,kBAAgB,KAAK;AACrB,SAAO,OAAO,GAAG,SAAS,MAAM;;;;;;AC3CpC,IAAI,kBAAiD;;;;;;;;;;AAWrD,SAAgB,eAAe,UAA4C;AACzE,KAAI,CAAC,gBAAiB,mBAAkB,EAAE;AAC1C,iBAAgB,KAAK,SAAS;AAC9B,cAAa;AACX,MAAI,CAAC,gBAAiB;AACtB,oBAAkB,gBAAgB,QAAQ,MAAM,MAAM,SAAS;AAC/D,MAAI,gBAAgB,WAAW,EAAG,mBAAkB;;;;AAKxD,SAAgB,sBAAsB,KAAsB,MAAe,MAAqB;AAC9F,KAAI,CAAC,gBAAiB;CACtB,MAAM,QAA2B;EAC/B,QAAQ;EACR,MAAM,IAAI;EACV;EACA;EACA,wBAAO,IAAI,OAAO,EAAC,SAAS;EAC5B,WAAW,YAAY,KAAK;EAC7B;AACD,MAAK,MAAM,KAAK,gBAAiB,GAAE,MAAM;;;AAI3C,SAAgB,YAAqB;AACnC,QAAO,oBAAoB;;AAK7B,IAAI,aAAa;AACjB,IAAI,UAAwE,EAAE;;;;;;;;;;AAW9E,SAAgB,MAAY;AAC1B,KAAI,WAAY;AAChB,cAAa;AACb,WAAU,EAAE;CAEZ,MAAM,UAAU,gBAAgB,MAAM;EACpC,MAAM,YAAa,EAAE,OAAkD,IAAI,QAAQ;EACnF,MAAM,QAAQ,EAAE,OAAO,IAAI,EAAE,KAAK,KAAK;AAEvC,UAAQ,IACN,gBAAgB,MAAM,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,KAAK,CAAC,IAAI,UAAU,aAAa,cAAc,IAAI,KAAK,IAAI,GACpI;AACD,UAAQ,KAAK;GAAE,MAAM,EAAE;GAAM,MAAM,EAAE;GAAM,MAAM,EAAE;GAAM,CAAC;GAC1D;AAGF,sBAAqB;AACnB,WAAS;AACT,MAAI,QAAQ,WAAW,EACrB,SAAQ,IAAI,0CAA0C;AAExD,eAAa;AACb,YAAU,EAAE;GACZ;;;;;;;;;;;;;AAgBJ,SAAgB,cAAiB,KAAoC;CACnE,MAAM,OAAO,IAAI,OAAO;AAExB,SAAQ,MAAM,aAAa,KAAK,OAAO,IAAI,KAAK,KAAK,KAAK,gBAAgB;AAC1E,SAAQ,IAAI,UAAU,KAAK,MAAM;AACjC,SAAQ,IAAI,gBAAgB,KAAK,gBAAgB;AACjD,SAAQ,UAAU;AAElB,QAAO;;;;;AClIT,MAAM,UAAU,OAAO,YAAY,eAAe,SAAS,KAAK,aAAa;AAU7E,MAAM,aAAa;AAsEnB,SAAS,QAA+B;AACtC,QAAO,KAAK;;AAGd,SAAS,KAA8B,UAAmB;AACxD,KAAI,OAAO,GAAG,KAAK,IAAI,SAAS,CAAE;AAClC,KAAK,OAAO,KAAkB,KAAK,QAAQ,KACzC,YAAW,mBAAmB,yBAAyB;CACzD,MAAM,OAAO,KAAK;AAClB,MAAK,KAAK;AACV,KAAI,WAAW,CAAE,uBAAsB,MAAoC,MAAM,SAAS;AAc1F,KAAI,YAAY,EAAE;AAChB,MAAI,KAAK,GAAI,cAAa,KAAK,GAAG;AAClC,MAAI,KAAK,GAAI,mBAAkB,KAAK,GAAG;OAEvC,aAAY;AACV,MAAI,KAAK,GAAI,cAAa,KAAK,GAAG;AAClC,MAAI,KAAK,GAAI,mBAAkB,KAAK,GAAG;GACvC;;AAIN,SAAS,QAAiC,IAAmC;AAC3E,MAAK,KAAK,MAAM,GAAG,KAAK,GAAG,CAAC;;AAG9B,SAAS,WAAoC,UAAkC;AAC7E,KAAI,CAAC,KAAK,GAAI,MAAK,qBAAK,IAAI,KAAK;AACjC,MAAK,GAAG,IAAI,SAAS;AACrB,cAAa,KAAK,IAAI,OAAO,SAAS;;;;;;;AAQxC,SAAS,UAAmC,SAAiC;AAC3E,KAAI,CAAC,KAAK,GAAI,MAAK,KAAK,EAAE;CAC1B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,IAAI;AAChB,KAAI,KAAK,QAAQ;AACjB,cAAa;AACX,MAAI,OAAO;;;;;;;AAQf,SAAS,aAAa,UAAyC;AAC7D,KAAI,YAAY,CACd,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,KAAK,SAAS;AACpB,MAAI,GAAI,4BAA2B,GAAG;;KAGxC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IACnC,UAAS,MAAM;;AAKrB,SAAS,SAA0D;AACjE,QAAO;EACL,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,iBAAiB,KAAK,IAAI,QAAQ;EACnC;;;;;;;;;AAUH,SAAgB,OAAU,cAAiB,SAAoC;AAC7E,KAAK,OAAO,KAAkB,KAAK,QAAQ,KACzC,YAAW,mBAAmB,0BAA0B;CAG1D,MAAM,SAAS,GAAG,SAAoB;AACpC,MAAI,WAAW,KAAK,SAAS,EAE3B,SAAQ,KACN,0JAGD;AAEH,kBAAgB,KAAoB;AACpC,SAAO,KAAK;;AAGd,MAAK,KAAK;AACV,MAAK,KAAK;AACV,MAAK,KAAK;AACV,MAAK,OAAO;AACZ,MAAK,MAAM;AACX,MAAK,SAAS;AACd,MAAK,YAAY;AACjB,MAAK,SAAS;AACd,MAAK,QAAQ;AACb,MAAK,QAAQ,SAAS;AAEtB,QAAO;;;;;;;;;;;;;;;;;;;ACzLT,MAAM,6BAAa,IAAI,SAAyB;AAEhD,MAAM,WAAW,OAAO,eAAe;;AAGvC,SAAgB,QAAQ,OAAyB;AAC/C,QACE,UAAU,QACV,OAAO,UAAU,YAChB,MAAkC,cAAc;;;;;;AAQrD,SAAgB,YAA8B,SAAe;AAC3D,QAAO,KAAK,QAAQ;;AAGtB,SAAS,KAAK,KAAqB;CACjC,MAAM,SAAS,WAAW,IAAI,IAAI;AAClC,KAAI,OAAQ,QAAO;CAGnB,MAAM,8BAAc,IAAI,KAAmC;CAE3D,MAAM,UAAU,MAAM,QAAQ,IAAI;CAClC,MAAM,YAAY,UAAU,OAAQ,IAAkB,OAAO,GAAG;CAEhE,SAAS,kBAAkB,KAAmC;AAC5D,MAAI,CAAC,YAAY,IAAI,IAAI,CACvB,aAAY,IAAI,KAAK,OAAQ,IAAqC,KAAK,CAAC;AAE1E,SAAO,YAAY,IAAI,IAAI;;CAG7B,MAAM,QAAQ,IAAI,MAAM,KAAK;EAC3B,IAAI,QAAQ,KAAK;AAEf,OAAI,QAAQ,SAAU,QAAO;AAC7B,OAAI,OAAO,QAAQ,SAAU,QAAQ,OAAmC;AAGxE,OAAI,WAAW,QAAQ,SAAU,QAAO,aAAa;AAKrD,OAAI,CAAC,OAAO,OAAO,QAAQ,IAAI,CAC7B,QAAQ,OAAwC;GAIlD,MAAM,QAAQ,kBAAkB,IAAI,EAAE;AAGtC,OAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO,KAAK,MAAgB;AAG9B,UAAO;;EAGT,IAAI,QAAQ,KAAK,OAAO;AACtB,OAAI,OAAO,QAAQ,UAAU;AAC1B,IAAC,OAAmC,OAAO;AAC5C,WAAO;;GAGT,MAAM,aAAa,UAAW,OAAqB,SAAS;AAC3D,GAAC,OAAwC,OAAO;AAGjD,OAAI,WAAW,QAAQ,UAAU;AAC/B,eAAW,IAAI,MAAgB;AAC/B,WAAO;;AAIT,OAAI,YAAY,IAAI,IAAI,CACtB,aAAY,IAAI,IAAI,EAAE,IAAI,MAAM;OAEhC,aAAY,IAAI,KAAK,OAAO,MAAM,CAAC;AAIrC,OAAI,WAAY,OAAqB,WAAW,WAC9C,YAAW,IAAK,OAAqB,OAAO;AAG9C,UAAO;;EAGT,eAAe,QAAQ,KAAK;AAC1B,UAAQ,OAAwC;AAChD,OAAI,OAAO,QAAQ,YAAY,YAAY,IAAI,IAAI,EAAE;AACnD,gBAAY,IAAI,IAAI,EAAE,IAAI,OAAU;AACpC,gBAAY,OAAO,IAAI;;AAEzB,OAAI,QAAS,YAAW,IAAK,OAAqB,OAAO;AACzD,UAAO;;EAGT,IAAI,QAAQ,KAAK;AACf,UAAO,QAAQ,IAAI,QAAQ,IAAI;;EAGjC,QAAQ,QAAQ;AACd,UAAO,QAAQ,QAAQ,OAAO;;EAGhC,yBAAyB,QAAQ,KAAK;AACpC,UAAO,QAAQ,yBAAyB,QAAQ,IAAI;;EAEvD,CAAC;AAEF,YAAW,IAAI,KAAK,MAAM;AAC1B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;AChHT,SAAgB,UAA4B,QAAW,QAAiB;AACtE,iBAAgB,QAAQ,wBAAQ,IAAI,SAAS,CAAC;;AAGhD,SAAS,gBAAgB,QAAgB,QAAgB,MAA6B;AACpF,KAAI,KAAK,IAAI,OAAO,CAAE;AACtB,MAAK,IAAI,OAAO;AAChB,KAAI,MAAM,QAAQ,OAAO,IAAI,MAAM,QAAQ,OAAO,CAChD,iBAAgB,QAAqB,QAAqB,KAAK;KAE/D,kBAAiB,QAAqB,QAAqB,KAAK;;AAIpE,SAAS,gBAAgB,QAAmB,QAAmB,MAA6B;CAC1F,MAAM,YAAY,OAAO;CACzB,MAAM,YAAY,OAAO;AAGzB,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;EAClC,MAAM,KAAK,OAAO;EAClB,MAAM,KAAM,OAAqB;AAEjC,MACE,IAAI,aACJ,OAAO,QACP,OAAO,OAAO,YACd,OAAO,QACP,OAAO,OAAO,SAGd,iBAAgB,IAAc,IAAc,KAAK;MAGhD,CAAC,OAAqB,KAAK;;AAKhC,KAAI,YAAY,UACd,QAAO,SAAS;;AAIpB,SAAS,iBAAiB,QAAmB,QAAmB,MAA6B;CAC3F,MAAM,aAAa,OAAO,KAAK,OAAO;CACtC,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,OAAO,CAAC;AAE/C,MAAK,MAAM,OAAO,YAAY;EAC5B,MAAM,KAAK,OAAO;EAClB,MAAM,KAAK,OAAO;AAElB,MAAI,OAAO,QAAQ,OAAO,OAAO,YAAY,OAAO,QAAQ,OAAO,OAAO,SACxE,KAAI,QAAQ,GAAG,CAEb,iBAAgB,IAAc,IAAc,KAAK;MAGjD,QAAO,OAAO;MAIhB,QAAO,OAAO;AAGhB,aAAW,OAAO,IAAI;;AAIxB,MAAK,MAAM,OAAO,WAChB,QAAO,OAAO;;;;;;;;;;;;;;;ACrElB,SAAgB,eACd,QACA,SACa;CACb,MAAM,OAAO,OAAsB,OAAU;CAC7C,MAAM,UAAU,OAAO,MAAM;CAC7B,MAAM,QAAQ,OAAgB,OAAU;CACxC,IAAI,YAAY;CAEhB,MAAM,WAAW,UAAa;EAC5B,MAAM,KAAK,EAAE;AACb,UAAQ,IAAI,KAAK;AACjB,QAAM,IAAI,OAAU;AACpB,UAAQ,MAAM,CACX,MAAM,WAAW;AAChB,OAAI,OAAO,UAAW;AACtB,QAAK,IAAI,OAAO;AAChB,WAAQ,IAAI,MAAM;IAClB,CACD,OAAO,QAAiB;AACvB,OAAI,OAAO,UAAW;AACtB,SAAM,IAAI,IAAI;AACd,WAAQ,IAAI,MAAM;IAClB;;AAGN,cAAa;EACX,MAAM,QAAQ,QAAQ;AACtB,qBAAmB,QAAQ,MAAM,CAAC;GAClC;AAEF,QAAO;EACL;EACA;EACA;EACA,UAAU;AACR,sBAAmB,QAAQ,QAAQ,CAAC,CAAC;;EAExC;;;;;;;;;;;;;;;;;;;;;;;;;;ACpCH,SAAgB,MACd,QACA,UACA,OAAqB,EAAE,EACX;CACZ,IAAI;CACJ,IAAI,UAAU;CACd,IAAI;CAEJ,MAAM,IAAI,aAAa;EACrB,MAAM,SAAS,QAAQ;AAEvB,MAAI,SAAS;AACX,aAAU;AACV,YAAS;AACT,OAAI,KAAK,WAAW;IAClB,MAAM,SAAS,SAAS,QAAQ,OAAU;AAC1C,QAAI,OAAO,WAAW,WAAY,aAAY;;AAEhD;;AAGF,MAAI,WAAW;AACb,cAAW;AACX,eAAY;;EAGd,MAAM,SAAS,SAAS,QAAQ,OAAO;AACvC,MAAI,OAAO,WAAW,WAAY,aAAY;AAC9C,WAAS;GACT;AAEF,cAAa;AACX,IAAE,SAAS;AACX,MAAI,WAAW;AACb,cAAW;AACX,eAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/batch.ts","../../../src/cell.ts","../../../src/computed.ts","../../../src/createSelector.ts","../../../src/signal.ts","../../../src/debug.ts","../../../src/effect.ts","../../../src/reconcile.ts","../../../src/resource.ts","../../../src/scope.ts","../../../src/store.ts","../../../src/tracking.ts","../../../src/watch.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/batch.ts","../../../src/cell.ts","../../../src/computed.ts","../../../src/createSelector.ts","../../../src/signal.ts","../../../src/debug.ts","../../../src/effect.ts","../../../src/reconcile.ts","../../../src/resource.ts","../../../src/scope.ts","../../../src/store.ts","../../../src/tracking.ts","../../../src/watch.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/reactivity",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Signals-based reactivity system for Pyreon",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/reactivity#readme",
6
6
  "bugs": {
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "devDependencies": {
37
- "@pyreon/manifest": "0.13.0"
37
+ "@pyreon/manifest": "0.13.1"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "vl_rolldown_build",
package/src/batch.ts CHANGED
@@ -18,13 +18,35 @@ export function batch(fn: () => void): void {
18
18
  } finally {
19
19
  batchDepth--
20
20
  if (batchDepth === 0 && pendingNotifications.size > 0) {
21
- // Swap to the other pre-allocated Set before flushing so new enqueues
22
- // during notification land in the alternate Set, not mixed into the
23
- // current iteration.
24
- const flush = pendingNotifications
25
- pendingNotifications = flush === setA ? setB : setA
26
- for (const notify of flush) notify()
27
- flush.clear()
21
+ // Keep batching active during flush so cascade-notifications emitted
22
+ // by flushing subscribers enqueue into the pending Set (and dedupe
23
+ // against what's already queued) instead of firing inline. The
24
+ // while-loop drains any cascade rounds until the graph is stable.
25
+ //
26
+ // Without this, a diamond dependency (a b, c → d → effect) re-fires
27
+ // the apex effect TWICE per signal write: the first notification path
28
+ // through `b` reaches `effect`, whose read clears `d`'s dirty flag;
29
+ // then when `c` is notified (still in the first flush round), it
30
+ // re-dirties `d`, which re-notifies `effect`. Keeping batchDepth at 1
31
+ // during flush routes those cascade-notifications through the Set,
32
+ // which dedupes on `d`'s recompute and on `effect`'s run.
33
+ //
34
+ // See `packages/internals/perf-harness/src/tests/diamond-probe.test.ts`
35
+ // for the empirical probe that caught this.
36
+ batchDepth = 1
37
+ try {
38
+ while (pendingNotifications.size > 0) {
39
+ // Swap to the other pre-allocated Set before flushing so new
40
+ // enqueues during notification land in the alternate Set, not
41
+ // mixed into the current iteration.
42
+ const flush = pendingNotifications
43
+ pendingNotifications = flush === setA ? setB : setA
44
+ for (const notify of flush) notify()
45
+ flush.clear()
46
+ }
47
+ } finally {
48
+ batchDepth = 0
49
+ }
28
50
  }
29
51
  }
30
52
  }
package/src/computed.ts CHANGED
@@ -9,6 +9,12 @@ import {
9
9
  withTracking,
10
10
  } from './tracking'
11
11
 
12
+ // Dev-time counter sink — see packages/internals/perf-harness for contract.
13
+ interface ViteMeta {
14
+ readonly env?: { readonly DEV?: boolean }
15
+ }
16
+ const _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }
17
+
12
18
  export interface Computed<T> {
13
19
  (): T
14
20
  /** Remove this computed from all its reactive dependencies. */
@@ -80,8 +86,14 @@ function computedLazy<T>(fn: () => T): Computed<T> {
80
86
  const read = (): T => {
81
87
  trackSubscriber(host)
82
88
  if (dirty) {
89
+ if ((import.meta as ViteMeta).env?.DEV === true)
90
+ _countSink.__pyreon_count__?.('reactivity.computedRecompute')
83
91
  try {
84
92
  if (tracked) {
93
+ // Deps already established from first run — skip adding to
94
+ // subscriber Sets again (they already contain recompute).
95
+ // Still need withTracking so activeEffect is set correctly
96
+ // for any NEW signals read on this evaluation.
85
97
  setSkipDepsCollection(true)
86
98
  value = withTracking(recompute, fn)
87
99
  setSkipDepsCollection(false)
@@ -90,7 +102,6 @@ function computedLazy<T>(fn: () => T): Computed<T> {
90
102
  tracked = true
91
103
  }
92
104
  } catch (err) {
93
- setSkipDepsCollection(false)
94
105
  _errorHandler(err)
95
106
  }
96
107
  dirty = false
@@ -142,6 +153,8 @@ function computedWithEquals<T>(fn: () => T, equals: (prev: T, next: T) => boolea
142
153
 
143
154
  const recompute = () => {
144
155
  if (disposed) return
156
+ if ((import.meta as ViteMeta).env?.DEV === true)
157
+ _countSink.__pyreon_count__?.('reactivity.computedRecompute')
145
158
  cleanupLocalDeps(deps, recompute)
146
159
  try {
147
160
  const next = trackWithLocalDeps(deps, recompute, fn)
@@ -160,6 +173,8 @@ function computedWithEquals<T>(fn: () => T, equals: (prev: T, next: T) => boolea
160
173
  const read = (): T => {
161
174
  trackSubscriber(host)
162
175
  if (dirty) {
176
+ if ((import.meta as ViteMeta).env?.DEV === true)
177
+ _countSink.__pyreon_count__?.('reactivity.computedRecompute')
163
178
  cleanupLocalDeps(deps, recompute)
164
179
  try {
165
180
  value = trackWithLocalDeps(deps, recompute, fn)
package/src/effect.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { getCurrentScope } from './scope'
2
2
  import { _restoreActiveEffect, _setActiveEffect, setDepsCollector, withTracking } from './tracking'
3
3
 
4
+ // Dev-time counter sink — see packages/internals/perf-harness for contract.
5
+ interface ViteMeta {
6
+ readonly env?: { readonly DEV?: boolean }
7
+ }
8
+ const _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }
9
+
4
10
  export interface Effect {
5
11
  dispose(): void
6
12
  }
@@ -33,6 +39,12 @@ export function onCleanup(fn: () => void): void {
33
39
  }
34
40
  }
35
41
 
42
+ // Thread-local collector for nested effects — captures effect() calls made
43
+ // inside another effect's fn() body so the parent can dispose them on
44
+ // re-run / disposal. Without this, inner effects leak across outer
45
+ // lifecycle boundaries (caught by cleanup-nested.test.ts).
46
+ let _innerEffectCollector: Effect[] | null = null
47
+
36
48
  // Global error handler — called for unhandled errors thrown inside effects.
37
49
  // Defaults to console.error so silent failures are never swallowed.
38
50
  export let _errorHandler: (err: unknown) => void = (err) => {
@@ -65,8 +77,22 @@ export function effect(fn: () => (() => void) | void): Effect {
65
77
  const deps: Set<() => void>[] = []
66
78
 
67
79
  let cleanups: (() => void)[] | undefined
80
+ // Inner effects created during this effect's fn() body. Disposed on
81
+ // outer re-run (before the next fn()) and on outer dispose(). Without
82
+ // this, nested effects leak across outer lifecycle boundaries.
83
+ let innerEffects: Effect[] | null = null
68
84
 
69
85
  const runCleanup = () => {
86
+ if (innerEffects) {
87
+ for (const ie of innerEffects) {
88
+ try {
89
+ ie.dispose()
90
+ } catch (err) {
91
+ _errorHandler(err)
92
+ }
93
+ }
94
+ innerEffects = null
95
+ }
70
96
  if (cleanups) {
71
97
  for (const c of cleanups) {
72
98
  try {
@@ -89,8 +115,16 @@ export function effect(fn: () => (() => void) | void): Effect {
89
115
 
90
116
  const run = () => {
91
117
  if (disposed) return
118
+ if ((import.meta as ViteMeta).env?.DEV === true)
119
+ _countSink.__pyreon_count__?.('reactivity.effectRun')
92
120
  // Run previous cleanup before re-running
93
121
  runCleanup()
122
+ // Start a new inner-effect collection window. Effects created during
123
+ // fn() will push themselves into this array and be disposed on the
124
+ // next re-run or on dispose.
125
+ const outerCollector = _innerEffectCollector
126
+ const myInners: Effect[] = []
127
+ _innerEffectCollector = myInners
94
128
  try {
95
129
  cleanupLocalDeps(deps, run)
96
130
  setDepsCollector(deps)
@@ -105,7 +139,10 @@ export function effect(fn: () => (() => void) | void): Effect {
105
139
  _cleanupCollector = null
106
140
  setDepsCollector(null)
107
141
  _errorHandler(err)
142
+ } finally {
143
+ _innerEffectCollector = outerCollector
108
144
  }
145
+ if (myInners.length > 0) innerEffects = myInners
109
146
  // Notify scope after each reactive re-run (not the initial synchronous run)
110
147
  // so onUpdate hooks fire after the DOM has settled.
111
148
  if (!isFirstRun) scope?.notifyEffectRan()
@@ -122,8 +159,14 @@ export function effect(fn: () => (() => void) | void): Effect {
122
159
  },
123
160
  }
124
161
 
125
- // Auto-register with the active EffectScope (if any)
126
- getCurrentScope()?.add(e)
162
+ // If we're inside another effect's run, register with it so the outer
163
+ // disposes this inner automatically.
164
+ if (_innerEffectCollector !== null) {
165
+ _innerEffectCollector.push(e)
166
+ } else {
167
+ // Otherwise auto-register with the active EffectScope (if any)
168
+ getCurrentScope()?.add(e)
169
+ }
127
170
 
128
171
  return e
129
172
  }
@@ -203,10 +246,27 @@ function renderEffectFullTrack(deps: Set<() => void>[], run: () => void, fn: ()
203
246
  export function renderEffect(fn: () => void): () => void {
204
247
  const deps: Set<() => void>[] = []
205
248
  let disposed = false
249
+ let isFirstRun = true
206
250
 
207
251
  const run = () => {
208
252
  if (disposed) return
209
- renderEffectFullTrack(deps, run, fn)
253
+ // After first run, if deps haven't changed structure, we can skip
254
+ // the full cleanup+retrack path. However, renderEffect deps CAN
255
+ // change (unlike _bind), so we always do the full track.
256
+ // Optimization: skip cleanup on first run (deps are empty).
257
+ if (isFirstRun) {
258
+ isFirstRun = false
259
+ setDepsCollector(deps)
260
+ _setActiveEffect(run)
261
+ try {
262
+ fn()
263
+ } finally {
264
+ _restoreActiveEffect()
265
+ setDepsCollector(null)
266
+ }
267
+ } else {
268
+ renderEffectFullTrack(deps, run, fn)
269
+ }
210
270
  }
211
271
 
212
272
  run()
package/src/scope.ts CHANGED
@@ -2,14 +2,16 @@
2
2
  // and disposes them all at once when the component unmounts.
3
3
 
4
4
  export class EffectScope {
5
- private _effects: { dispose(): void }[] = []
5
+ private _effects: { dispose(): void }[] | null = null
6
6
  private _active = true
7
- private _updateHooks: (() => void)[] = []
7
+ private _updateHooks: (() => void)[] | null = null
8
8
  private _updatePending = false
9
9
 
10
10
  /** Register an effect/computed to be disposed when this scope stops. */
11
11
  add(e: { dispose(): void }): void {
12
- if (this._active) this._effects.push(e)
12
+ if (!this._active) return
13
+ if (this._effects === null) this._effects = []
14
+ this._effects.push(e)
13
15
  }
14
16
 
15
17
  /**
@@ -30,6 +32,7 @@ export class EffectScope {
30
32
 
31
33
  /** Register a callback to run after any reactive update in this scope. */
32
34
  addUpdateHook(fn: () => void): void {
35
+ if (this._updateHooks === null) this._updateHooks = []
33
36
  this._updateHooks.push(fn)
34
37
  }
35
38
 
@@ -38,11 +41,11 @@ export class EffectScope {
38
41
  * Schedules onUpdate hooks via microtask so all synchronous effects settle first.
39
42
  */
40
43
  notifyEffectRan(): void {
41
- if (!this._active || this._updateHooks.length === 0 || this._updatePending) return
44
+ if (!this._active || !this._updateHooks || this._updateHooks.length === 0 || this._updatePending) return
42
45
  this._updatePending = true
43
46
  queueMicrotask(() => {
44
47
  this._updatePending = false
45
- if (!this._active) return
48
+ if (!this._active || !this._updateHooks) return
46
49
  for (const fn of this._updateHooks) {
47
50
  try {
48
51
  fn()
@@ -56,9 +59,11 @@ export class EffectScope {
56
59
  /** Dispose all tracked effects. */
57
60
  stop(): void {
58
61
  if (!this._active) return
59
- for (const e of this._effects) e.dispose()
60
- this._effects = []
61
- this._updateHooks = []
62
+ if (this._effects) {
63
+ for (const e of this._effects) e.dispose()
64
+ }
65
+ this._effects = null
66
+ this._updateHooks = null
62
67
  this._updatePending = false
63
68
  this._active = false
64
69
  }
package/src/signal.ts CHANGED
@@ -2,10 +2,16 @@ declare const process: { env: { NODE_ENV?: string } } | undefined
2
2
 
3
3
  const __DEV__ = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'
4
4
 
5
- import { enqueuePendingNotification, isBatching } from './batch'
5
+ import { batch, enqueuePendingNotification, isBatching } from './batch'
6
6
  import { _notifyTraceListeners, isTracing } from './debug'
7
7
  import { notifySubscribers, trackSubscriber } from './tracking'
8
8
 
9
+ // Dev-time counter sink — see packages/internals/perf-harness for contract.
10
+ interface ViteMeta {
11
+ readonly env?: { readonly DEV?: boolean }
12
+ }
13
+ const _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }
14
+
9
15
  export interface SignalDebugInfo<T> {
10
16
  /** Signal name (set via options or inferred) */
11
17
  name: string | undefined
@@ -80,12 +86,33 @@ function _peek(this: SignalFn<unknown>) {
80
86
 
81
87
  function _set(this: SignalFn<unknown>, newValue: unknown) {
82
88
  if (Object.is(this._v, newValue)) return
89
+ if ((import.meta as ViteMeta).env?.DEV === true)
90
+ _countSink.__pyreon_count__?.('reactivity.signalWrite')
83
91
  const prev = this._v
84
92
  this._v = newValue
85
93
  if (isTracing()) _notifyTraceListeners(this as unknown as Signal<unknown>, prev, newValue)
86
- // Direct updaters flat array, no Set overhead, batch-aware
87
- if (this._d) notifyDirect(this._d)
88
- if (this._s) notifySubscribers(this._s)
94
+ // Auto-batch the notification chain. Without this, a diamond dependency
95
+ // graph (a → b, c → d → effect) fires the apex effect TWICE per write
96
+ // because subscribers cascade inline: the first path through `b` reaches
97
+ // `effect`, whose read clears `d`'s dirty flag; then `c`'s notification
98
+ // re-dirties `d` and re-notifies `effect`. Wrapping the notify chain in
99
+ // `batch()` routes cascade-notifications through the pending Set, which
100
+ // dedupes on `d.recompute` and on `effect.run`.
101
+ //
102
+ // The batch is synchronous — observable behaviour is unchanged for the
103
+ // common case (subscribers still fire immediately after the write). Only
104
+ // the dedup semantics change, which is a bug fix.
105
+ //
106
+ // Short-circuit when already inside a batch so we don't wrap redundantly.
107
+ if (isBatching()) {
108
+ if (this._d) notifyDirect(this._d)
109
+ if (this._s) notifySubscribers(this._s)
110
+ } else {
111
+ batch(() => {
112
+ if (this._d) notifyDirect(this._d)
113
+ if (this._s) notifySubscribers(this._s)
114
+ })
115
+ }
89
116
  }
90
117
 
91
118
  function _update(this: SignalFn<unknown>, fn: (current: unknown) => unknown) {
@@ -146,6 +173,8 @@ function _debug(this: SignalFn<unknown>): SignalDebugInfo<unknown> {
146
173
  * update, subscribe) are shared across all signals — not per-signal closures.
147
174
  */
148
175
  export function signal<T>(initialValue: T, options?: SignalOptions): Signal<T> {
176
+ if ((import.meta as ViteMeta).env?.DEV === true)
177
+ _countSink.__pyreon_count__?.('reactivity.signalCreate')
149
178
  // The read function is the only per-signal closure.
150
179
  // It doubles as the SubscriberHost (_s property) for trackSubscriber.
151
180
  const read = ((...args: unknown[]) => {