what-core 0.12.3 → 0.13.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/src/reactive.js CHANGED
@@ -30,6 +30,7 @@ export const __DEV__ =
30
30
 
31
31
  // DevTools hooks — set by what-devtools when installed.
32
32
  // These are no-ops in production (dead-code eliminated with __DEV__).
33
+ /** @type {WhatDevToolsHooks | null} */
33
34
  export let __devtools = null;
34
35
 
35
36
  /** @internal Install devtools hooks. Called by what-devtools. */
@@ -96,7 +97,7 @@ export function signal(initial, debugName) {
96
97
  // Invalidate lastTracked since value changed — any effect that reads
97
98
  // this signal during re-run needs to re-track.
98
99
  lastTracked = null;
99
- if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
100
+ if (__DEV__ && __devtools) __devtools.onSignalUpdate?.(sig);
100
101
  if (subs.size > 0) notify(subs);
101
102
  }
102
103
 
@@ -134,11 +135,21 @@ export function signal(initial, debugName) {
134
135
  sig._signal = true;
135
136
  if (__DEV__) {
136
137
  sig._subs = subs;
138
+ // Back-reference from the subscriber Set to its signal, for trackSignals()
139
+ // in testing.js. An effect records dependencies as the `subs` Sets it
140
+ // joined (see the read path above), which is one-directional: given an
141
+ // effect you can count its deps but not name them.
142
+ //
143
+ // Deliberately NOT `_owner`. That property is load-bearing for topological
144
+ // level computation, and a signal's Set is documented above as having
145
+ // `_owner === undefined` precisely because signals are level 0. A separate
146
+ // dev-only name keeps that invariant intact.
147
+ subs._signalOwner = sig;
137
148
  if (debugName) sig._debugName = debugName;
138
149
  }
139
150
 
140
151
  // Notify devtools of signal creation
141
- if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);
152
+ if (__DEV__ && __devtools) __devtools.onSignalCreate?.(sig);
142
153
 
143
154
  return sig;
144
155
  }
@@ -337,7 +348,7 @@ export function effect(fn, opts) {
337
348
  // effect could never re-fire anyway, so releasing is safe.
338
349
  if (e.deps.length === 0 && e._cleanup === null) {
339
350
  e.disposed = true;
340
- if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
351
+ if (__DEV__ && __devtools) __devtools.onEffectDispose?.(e);
341
352
  return _noopDispose;
342
353
  }
343
354
 
@@ -369,6 +380,7 @@ function _createEffect(fn, lazy) {
369
380
  // IMPORTANT: V8 optimizes objects with a consistent "hidden class" (shape).
370
381
  // All properties must be declared upfront even if null — adding properties
371
382
  // later causes shape transitions which deoptimize property access globally.
383
+ /** @type {WhatEffectNode} */
372
384
  const e = {
373
385
  fn,
374
386
  deps: [], // array of subscriber sets (cheaper than Set for typical 1-3 deps)
@@ -385,7 +397,7 @@ function _createEffect(fn, lazy) {
385
397
  _cleanup: null, // cleanup function returned by effect fn (declared upfront for shape)
386
398
  _epoch: 0, // incremented on cleanup — used by signal lastTracked cache
387
399
  };
388
- if (__DEV__ && __devtools) __devtools.onEffectCreate(e);
400
+ if (__DEV__ && __devtools) __devtools.onEffectCreate?.(e);
389
401
  return e;
390
402
  }
391
403
 
@@ -409,12 +421,12 @@ function _runEffect(e) {
409
421
  const result = e.fn();
410
422
  if (typeof result === 'function') e._cleanup = result;
411
423
  } catch (err) {
412
- if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
424
+ if (__devtools?.onError) __devtools.onError?.(err, { type: 'effect', effect: e });
413
425
  if (__DEV__) console.warn('[what] Error in stable effect:', err);
414
426
  } finally {
415
427
  currentEffect = prev;
416
428
  }
417
- if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
429
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun?.(e);
418
430
  return;
419
431
  }
420
432
 
@@ -426,7 +438,7 @@ function _runEffect(e) {
426
438
  // Run effect cleanup from previous run
427
439
  if (e._cleanup) {
428
440
  try { e._cleanup(); } catch (err) {
429
- if (__DEV__ && __devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e });
441
+ if (__DEV__ && __devtools?.onError) __devtools.onError?.(err, { type: 'effect-cleanup', effect: e });
430
442
  if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
431
443
  }
432
444
  e._cleanup = null;
@@ -441,7 +453,7 @@ function _runEffect(e) {
441
453
  }
442
454
  } catch (err) {
443
455
  if (err === NEEDS_UPSTREAM) throw err; // Iterative eval sentinel — not a real error
444
- if (__DEV__ && __devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
456
+ if (__DEV__ && __devtools?.onError) __devtools.onError?.(err, { type: 'effect', effect: e });
445
457
  throw err;
446
458
  } finally {
447
459
  currentEffect = prev;
@@ -458,12 +470,12 @@ function _runEffect(e) {
458
470
  e._stable = true;
459
471
  }
460
472
 
461
- if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
473
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun?.(e);
462
474
  }
463
475
 
464
476
  function _disposeEffect(e) {
465
477
  e.disposed = true;
466
- if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
478
+ if (__DEV__ && __devtools) __devtools.onEffectDispose?.(e);
467
479
  cleanup(e);
468
480
  // Run cleanup on dispose
469
481
  if (e._cleanup) {
@@ -509,11 +521,11 @@ function _processSubscriber(e) {
509
521
  try {
510
522
  const result = e.fn();
511
523
  if (typeof result === 'function') {
512
- if (e._cleanup) try { e._cleanup(); } catch (err) { /* ignore */ }
524
+ if (e._cleanup) try { e._cleanup(); } catch { /* ignore */ }
513
525
  e._cleanup = result;
514
526
  }
515
527
  } catch (err) {
516
- if (__DEV__ && __devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
528
+ if (__DEV__ && __devtools?.onError) __devtools.onError?.(err, { type: 'effect', effect: e });
517
529
  if (__DEV__) console.warn('[what] Error in stable effect:', err);
518
530
  } finally {
519
531
  currentEffect = prev;
@@ -617,7 +629,7 @@ function flush() {
617
629
  _runEffect(e);
618
630
  } catch (err) {
619
631
  if (err === NEEDS_UPSTREAM) throw err;
620
- if (__DEV__ && __devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
632
+ if (__DEV__ && __devtools?.onError) __devtools.onError?.(err, { type: 'effect', effect: e });
621
633
  // Surface in production too — an uncaught reactive-update error is a
622
634
  // real bug; staying silent (as the old throw-out-of-flush did once it
623
635
  // escaped) hides it. console.error never aborts the batch.
@@ -786,6 +798,7 @@ export function createRoot(fn) {
786
798
  const prevRoot = currentRoot;
787
799
  const prevOwner = currentOwner;
788
800
  const root = {
801
+ /** @type {Array<() => void>} */
789
802
  disposals: [],
790
803
  owner: currentOwner, // parent owner for ownership tree
791
804
  children: [], // child roots (ownership tree)
@@ -856,6 +869,7 @@ export function _createItemScope(fn) {
856
869
  const prevRoot = currentRoot;
857
870
  const prevOwner = currentOwner;
858
871
  const scope = {
872
+ /** @type {Array<() => void>} */
859
873
  disposals: [],
860
874
  owner: null, // No parent registration
861
875
  children: [], // Kept for compat with effects that create sub-roots
@@ -914,6 +928,7 @@ if (__DEV__ && typeof WeakRef !== 'undefined') {
914
928
  // needed before the devtools entry point runs; once devtools install, the
915
929
  // buffer is drained and subsequent creations flow through the real hooks.
916
930
  const PREINSTALL_CAP = 2000;
931
+ /** @type {{ signals: Set<any>, effects: Set<any>, components: any[] }} */
917
932
  const buffer = { signals: new Set(), effects: new Set(), components: [] };
918
933
  __devtools = {
919
934
  __isPreinstallBuffer: true,
@@ -940,9 +955,10 @@ if (__DEV__ && typeof WeakRef !== 'undefined') {
940
955
  * __setDevToolsHooks replaces the placeholder. Returns arrays of live refs.
941
956
  */
942
957
  export function __drainPreinstallBuffer() {
943
- if (!__DEV__) return { signals: [], effects: [], components: [] };
958
+ if (!__DEV__) return /** @type {{ signals: any[], effects: any[], components: any[] }} */ ({ signals: [], effects: [], components: [] });
944
959
  // If the current __devtools is the real one (no __isPreinstallBuffer), the
945
960
  // caller installed late and there is nothing to drain from this side.
961
+ /** @type {{ signals: any[], effects: any[], components: any[] }} */
946
962
  const out = { signals: [], effects: [], components: [] };
947
963
  const buf = (typeof __preinstallSnapshot !== 'undefined') ? __preinstallSnapshot : null;
948
964
  if (!buf) return out;
@@ -954,6 +970,7 @@ export function __drainPreinstallBuffer() {
954
970
 
955
971
  // Capture the placeholder buffer at module load so __drainPreinstallBuffer
956
972
  // can return it AFTER __setDevToolsHooks has replaced __devtools.
973
+ /** @type {WhatDevToolsHooks['__buffer'] | null} */
957
974
  let __preinstallSnapshot = null;
958
975
  if (__DEV__ && __devtools?.__isPreinstallBuffer) {
959
976
  __preinstallSnapshot = __devtools.__buffer;