@mindees/core 0.16.0 → 0.19.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/dist/index.d.ts CHANGED
@@ -7,14 +7,14 @@ import { GestureHandlers, PanEvent, PanState, PinchEvent, PinchState, PointerSam
7
7
  import { panAnimated } from "./gesture/animated.js";
8
8
  import { notImplemented } from "./not-implemented.js";
9
9
  import { Priority, ScheduleOptions, ScheduledTask, Scheduler, SchedulerOptions, Task, createScheduler } from "./scheduler/scheduler.js";
10
- import { Accessor, ComputedOptions, EffectOptions, EqualsFn, Memo, Owner, Signal, SignalOptions, batch, computed, createRoot, deferred, effect, getOwner, memo, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack } from "./reactive/reactive.js";
10
+ import { Accessor, ComputedOptions, EffectOptions, EqualsFn, Memo, Owner, Signal, SignalOptions, batch, computed, createRoot, deferred, effect, getOwner, memo, on, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack } from "./reactive/reactive.js";
11
11
  import { ThreadPool, WorkerLike, WorkerPoolOptions, createInlineThreadPool, createNativeThreadPool, createWorkerPool } from "./threading/thread-pool.js";
12
12
 
13
13
  //#region src/index.d.ts
14
14
  /** The npm package name. */
15
15
  declare const name = "@mindees/core";
16
16
  /** The package version. All `@mindees/*` packages share one locked version line. */
17
- declare const VERSION = "0.16.0";
17
+ declare const VERSION = "0.19.0";
18
18
  /**
19
19
  * Current maturity of this package. See the repository `STATUS.md`.
20
20
  *
@@ -31,5 +31,5 @@ declare const maturity: Maturity;
31
31
  */
32
32
  declare const info: PackageInfo;
33
33
  //#endregion
34
- export { type Accessor, type AnimatedValue, type AnimationHandle, type Component, type ComputedOptions, type Context, type ContextProvider, ELEMENT_TYPE, type Easing, type EffectOptions, type ElementType, type EqualsFn, Fragment, type FrameSource, type GestureHandlers, KEYED_REGION, type KeyedRegion, type KeyedRegionOptions, type Maturity, type Memo, type MindeesElement, type MindeesNode, NotImplementedError, type Owner, PORTAL, type PackageInfo, type PanEvent, type PanState, type PinchEvent, type PinchState, type PointerSample, type PortalRegion, type Priority, type Recognizer, type ScheduleOptions, type ScheduledTask, Scheduler, type SchedulerOptions, type SelectorEquals, type Signal, type SignalOptions, type SwipeDirection, type SwipeEvent, type TapState, type Task, type ThreadPool, VERSION, type WorkerLike, type WorkerPoolOptions, _activeAnimationCount, _resetAnimation, _setGestureClock, animate, batch, composeGestures, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, cubicBezier, deferred, easeInOutQuad, easeInQuad, easeOutCubic, easeOutQuad, effect, getFrameSource, getOwner, hasOwner, info, interpolate, isElement, isKeyedRegion, isPortal, keyedRegion, linear, longPress, manualFrameSource, maturity, memo, name, normalizePointer, notImplemented, onCleanup, pan, panAnimated, pinch, portal, rafFrameSource, renderComponent, runWithOwner, setFrameSource, setReactiveScheduler, signal, spring, startTransition, swipe, tap, timing, untrack };
34
+ export { type Accessor, type AnimatedValue, type AnimationHandle, type Component, type ComputedOptions, type Context, type ContextProvider, ELEMENT_TYPE, type Easing, type EffectOptions, type ElementType, type EqualsFn, Fragment, type FrameSource, type GestureHandlers, KEYED_REGION, type KeyedRegion, type KeyedRegionOptions, type Maturity, type Memo, type MindeesElement, type MindeesNode, NotImplementedError, type Owner, PORTAL, type PackageInfo, type PanEvent, type PanState, type PinchEvent, type PinchState, type PointerSample, type PortalRegion, type Priority, type Recognizer, type ScheduleOptions, type ScheduledTask, Scheduler, type SchedulerOptions, type SelectorEquals, type Signal, type SignalOptions, type SwipeDirection, type SwipeEvent, type TapState, type Task, type ThreadPool, VERSION, type WorkerLike, type WorkerPoolOptions, _activeAnimationCount, _resetAnimation, _setGestureClock, animate, batch, composeGestures, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, cubicBezier, deferred, easeInOutQuad, easeInQuad, easeOutCubic, easeOutQuad, effect, getFrameSource, getOwner, hasOwner, info, interpolate, isElement, isKeyedRegion, isPortal, keyedRegion, linear, longPress, manualFrameSource, maturity, memo, name, normalizePointer, notImplemented, on, onCleanup, pan, panAnimated, pinch, portal, rafFrameSource, renderComponent, runWithOwner, setFrameSource, setReactiveScheduler, signal, spring, startTransition, swipe, tap, timing, untrack };
35
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;cA4Ia,IAAA;;cAGA,OAAA;;;;;;;;;cAUA,QAAA,EAAU,QAAyB;;;;;;cAOnC,IAAA,EAAM,WAAiE"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;cA6Ia,IAAA;;cAGA,OAAA;;;;;;;;;cAUA,QAAA,EAAU,QAAyB;;;;;;cAOnC,IAAA,EAAM,WAAiE"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { batch, computed, createRoot, deferred, effect, getOwner, memo, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack } from "./reactive/reactive.js";
1
+ import { batch, computed, createRoot, deferred, effect, getOwner, memo, on, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack } from "./reactive/reactive.js";
2
2
  import { cubicBezier, easeInOutQuad, easeInQuad, easeOutCubic, easeOutQuad, linear } from "./animation/easing.js";
3
3
  import { _activeAnimationCount, _resetAnimation, animate, getFrameSource, interpolate, manualFrameSource, rafFrameSource, setFrameSource, spring, timing } from "./animation/animation.js";
4
4
  import { ELEMENT_TYPE, Fragment, KEYED_REGION, PORTAL, createContext, createElement, createProvider, hasOwner, isElement, isKeyedRegion, isPortal, keyedRegion, portal, renderComponent } from "./component/component.js";
@@ -12,7 +12,7 @@ import { createInlineThreadPool, createNativeThreadPool, createWorkerPool } from
12
12
  /** The npm package name. */
13
13
  const name = "@mindees/core";
14
14
  /** The package version. All `@mindees/*` packages share one locked version line. */
15
- const VERSION = "0.16.0";
15
+ const VERSION = "0.19.0";
16
16
  /**
17
17
  * Current maturity of this package. See the repository `STATUS.md`.
18
18
  *
@@ -33,6 +33,6 @@ const info = Object.freeze({
33
33
  maturity
34
34
  });
35
35
  //#endregion
36
- export { ELEMENT_TYPE, Fragment, KEYED_REGION, NotImplementedError, PORTAL, Scheduler, VERSION, _activeAnimationCount, _resetAnimation, _setGestureClock, animate, batch, composeGestures, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, cubicBezier, deferred, easeInOutQuad, easeInQuad, easeOutCubic, easeOutQuad, effect, getFrameSource, getOwner, hasOwner, info, interpolate, isElement, isKeyedRegion, isPortal, keyedRegion, linear, longPress, manualFrameSource, maturity, memo, name, normalizePointer, notImplemented, onCleanup, pan, panAnimated, pinch, portal, rafFrameSource, renderComponent, runWithOwner, setFrameSource, setReactiveScheduler, signal, spring, startTransition, swipe, tap, timing, untrack };
36
+ export { ELEMENT_TYPE, Fragment, KEYED_REGION, NotImplementedError, PORTAL, Scheduler, VERSION, _activeAnimationCount, _resetAnimation, _setGestureClock, animate, batch, composeGestures, computed, createContext, createElement, createInlineThreadPool, createNativeThreadPool, createProvider, createRoot, createScheduler, createWorkerPool, cubicBezier, deferred, easeInOutQuad, easeInQuad, easeOutCubic, easeOutQuad, effect, getFrameSource, getOwner, hasOwner, info, interpolate, isElement, isKeyedRegion, isPortal, keyedRegion, linear, longPress, manualFrameSource, maturity, memo, name, normalizePointer, notImplemented, on, onCleanup, pan, panAnimated, pinch, portal, rafFrameSource, renderComponent, runWithOwner, setFrameSource, setReactiveScheduler, signal, spring, startTransition, swipe, tap, timing, untrack };
37
37
 
38
38
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Animation engine: reactive animated values + timing/spring drivers + interpolate, driven by an\n * injected frame source (RN Animated/Reanimated + Flutter AnimationController parity).\n */\nexport {\n _activeAnimationCount,\n _resetAnimation,\n type AnimatedValue,\n type AnimationHandle,\n animate,\n cubicBezier,\n type Easing,\n easeInOutQuad,\n easeInQuad,\n easeOutCubic,\n easeOutQuad,\n type FrameSource,\n getFrameSource,\n interpolate,\n linear,\n manualFrameSource,\n rafFrameSource,\n setFrameSource,\n spring,\n timing,\n} from './animation'\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n isPortal,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n PORTAL,\n type PortalRegion,\n portal,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\n/**\n * Gesture recognizers: tap/longPress/pan/pinch/swipe → reactive state that drives styles and the\n * animation engine (RN Gesture Handler / Flutter GestureDetector parity).\n */\nexport {\n _setGestureClock,\n composeGestures,\n type GestureHandlers,\n longPress,\n normalizePointer,\n type PanEvent,\n type PanState,\n type PinchEvent,\n type PinchState,\n type PointerSample,\n pan,\n panAnimated,\n pinch,\n type Recognizer,\n type SwipeDirection,\n type SwipeEvent,\n swipe,\n type TapState,\n tap,\n} from './gesture'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n deferred,\n type EffectOptions,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n setReactiveScheduler,\n signal,\n startTransition,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.16.0'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;;;;;AA4IA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Animation engine: reactive animated values + timing/spring drivers + interpolate, driven by an\n * injected frame source (RN Animated/Reanimated + Flutter AnimationController parity).\n */\nexport {\n _activeAnimationCount,\n _resetAnimation,\n type AnimatedValue,\n type AnimationHandle,\n animate,\n cubicBezier,\n type Easing,\n easeInOutQuad,\n easeInQuad,\n easeOutCubic,\n easeOutQuad,\n type FrameSource,\n getFrameSource,\n interpolate,\n linear,\n manualFrameSource,\n rafFrameSource,\n setFrameSource,\n spring,\n timing,\n} from './animation'\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n isPortal,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n PORTAL,\n type PortalRegion,\n portal,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\n/**\n * Gesture recognizers: tap/longPress/pan/pinch/swipe → reactive state that drives styles and the\n * animation engine (RN Gesture Handler / Flutter GestureDetector parity).\n */\nexport {\n _setGestureClock,\n composeGestures,\n type GestureHandlers,\n longPress,\n normalizePointer,\n type PanEvent,\n type PanState,\n type PinchEvent,\n type PinchState,\n type PointerSample,\n pan,\n panAnimated,\n pinch,\n type Recognizer,\n type SwipeDirection,\n type SwipeEvent,\n swipe,\n type TapState,\n tap,\n} from './gesture'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n deferred,\n type EffectOptions,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n on,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n setReactiveScheduler,\n signal,\n startTransition,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.19.0'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;;;;;AA6IA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
@@ -137,6 +137,18 @@ declare function deferred<T>(source: Accessor<T>): Accessor<T>;
137
137
  declare function batch<T>(fn: () => T): T;
138
138
  /** Read reactive values without subscribing the current computation to them. */
139
139
  declare function untrack<T>(fn: () => T): T;
140
+ /**
141
+ * Make a computation react to an EXPLICIT dependency only — the body runs untracked, so signals it
142
+ * reads don't subscribe; only `deps` does. Wrap it in `effect`/`memo`: `effect(on(() => a(), (av) => …))`
143
+ * re-runs when `a` changes but not when other signals the body reads change. The callback receives the
144
+ * current dep value, the previous dep value, and its own previous return. With `{ defer: true }` the
145
+ * first run is skipped (deps are still tracked) — react only to *changes*, not the initial value.
146
+ *
147
+ * @example effect(on(() => userId(), (id) => { void load(id) }, { defer: true }))
148
+ */
149
+ declare function on<S, U>(deps: Accessor<S>, fn: (input: S, prevInput: S | undefined, prev: U | undefined) => U, options?: {
150
+ defer?: boolean;
151
+ }): () => U | undefined;
140
152
  /**
141
153
  * Register a cleanup to run before the owning computation re-runs and when it
142
154
  * is disposed. No-op outside a reactive scope.
@@ -159,5 +171,5 @@ declare function getOwner(): Owner | null;
159
171
  /** Run `fn` with `owner` as the active scope (e.g. to re-attach cleanups). */
160
172
  declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
161
173
  //#endregion
162
- export { Accessor, ComputedOptions, EffectOptions, EqualsFn, Memo, Owner, Signal, SignalOptions, batch, computed, createRoot, deferred, effect, getOwner, memo, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack };
174
+ export { Accessor, ComputedOptions, EffectOptions, EqualsFn, Memo, Owner, Signal, SignalOptions, batch, computed, createRoot, deferred, effect, getOwner, memo, on, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack };
163
175
  //# sourceMappingURL=reactive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.d.ts","names":[],"sources":["../../src/reactive/reactive.ts"],"mappings":";;;;cAwCc,WAAA;;AAgCuB;AAoDrC;;;;AAAgE;AA2XhE;;;;AAAiC;AAGjC;;UAlciB,KAAA;EAocE;EAAA,UAlcP,WAAW;AAAA;;KAcX,QAAA,OAAe,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC;;AAobhB;AAIrB;;;iBApYgB,oBAAA,CAAqB,SAA2B,EAAhB,SAAS;;KA2X7C,QAAA,YAAoB,CAAC;;UAGhB,aAAA;EAcN;EAZT,MAAA,GAAS,QAAQ,CAAC,CAAA;AAAA;;UAIH,MAAA;EAIJ;EAAA,IAFP,CAAA;EAEW;EAAf,GAAA,CAAI,KAAA,EAAO,CAAA,GAAI,CAAA;EAEG;EAAlB,MAAA,CAAO,EAAA,GAAK,IAAA,EAAM,CAAA,KAAM,CAAA,GAAI,CAAA;EAAJ;EAExB,IAAA,IAAQ,CAAA;AAAA;;UAIO,eAAA;EAJN;EAMT,MAAA,GAAS,QAAQ,CAAC,CAAA;AAAA;;UAIH,IAAA;EAJE;EAAA,IAMb,CAAA;EANJ;EAQA,IAAA,IAAQ,CAAC;AAAA;;AARU;AAIrB;;;;;;;iBAgBgB,MAAA,IAAU,KAAA,EAAO,CAAA,EAAG,OAAA,GAAU,aAAA,CAAc,CAAA,IAAK,MAAA,CAAO,CAAA;;AAZ7D;AAYX;;;;;;iBAiBgB,QAAA,IAAY,EAAA,QAAU,CAAA,EAAG,OAAA,GAAU,eAAA,CAAgB,CAAA,IAAK,IAAA,CAAK,CAAA;;cAYhE,IAAA,SAAI,QAAW;;;;;;;;;;;AA7B6C;AAiBzE;;;;;;;;;;;UAoCiB,aAAA;EApCW;;;;;EA0C1B,QAAA,GAAW,QAAQ;AAAA;AAAA,iBAGL,MAAA,CAAO,EAAA,cAAgB,OAAA,GAAU,aAAa;AAjC9D;;;;AAA4B;AAwB5B;;;;AAMqB;AAGrB;AAjCA,iBAmEgB,eAAA,CAAgB,EAAc;;;;;;;iBAe9B,QAAA,IAAY,MAAA,EAAQ,QAAA,CAAS,CAAA,IAAK,QAAA,CAAS,CAAA;AAf3D;;;;AAAA,iBAyBgB,KAAA,IAAS,EAAA,QAAU,CAAA,GAAI,CAAC;AAVxC;AAAA,iBAsBgB,OAAA,IAAW,EAAA,QAAU,CAAA,GAAI,CAAC;;;;;iBAc1B,SAAA,CAAU,EAAc;;;;;;;;;;AApCoB;AAU5D;iBA2CgB,UAAA,IAAc,EAAA,GAAK,OAAA,iBAAwB,CAAA,GAAI,CAAC;;iBAehD,QAAA,IAAY,KAAK;;iBAOjB,YAAA,IAAgB,KAAA,EAAO,KAAA,SAAc,EAAA,QAAU,CAAA,GAAI,CAAA"}
1
+ {"version":3,"file":"reactive.d.ts","names":[],"sources":["../../src/reactive/reactive.ts"],"mappings":";;;;cAwCc,WAAA;;AAgCuB;AAoDrC;;;;AAAgE;AA2XhE;;;;AAAiC;AAGjC;;UAlciB,KAAA;EAocE;EAAA,UAlcP,WAAW;AAAA;;KAcX,QAAA,OAAe,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC;;AAobhB;AAIrB;;;iBApYgB,oBAAA,CAAqB,SAA2B,EAAhB,SAAS;;KA2X7C,QAAA,YAAoB,CAAC;;UAGhB,aAAA;EAcN;EAZT,MAAA,GAAS,QAAQ,CAAC,CAAA;AAAA;;UAIH,MAAA;EAIJ;EAAA,IAFP,CAAA;EAEW;EAAf,GAAA,CAAI,KAAA,EAAO,CAAA,GAAI,CAAA;EAEG;EAAlB,MAAA,CAAO,EAAA,GAAK,IAAA,EAAM,CAAA,KAAM,CAAA,GAAI,CAAA;EAAJ;EAExB,IAAA,IAAQ,CAAA;AAAA;;UAIO,eAAA;EAJN;EAMT,MAAA,GAAS,QAAQ,CAAC,CAAA;AAAA;;UAIH,IAAA;EAJE;EAAA,IAMb,CAAA;EANJ;EAQA,IAAA,IAAQ,CAAC;AAAA;;AARU;AAIrB;;;;;;;iBAgBgB,MAAA,IAAU,KAAA,EAAO,CAAA,EAAG,OAAA,GAAU,aAAA,CAAc,CAAA,IAAK,MAAA,CAAO,CAAA;;AAZ7D;AAYX;;;;;;iBAiBgB,QAAA,IAAY,EAAA,QAAU,CAAA,EAAG,OAAA,GAAU,eAAA,CAAgB,CAAA,IAAK,IAAA,CAAK,CAAA;;cAYhE,IAAA,SAAI,QAAW;;;;;;;;;;;AA7B6C;AAiBzE;;;;;;;;;;;UAoCiB,aAAA;EApCW;;;;;EA0C1B,QAAA,GAAW,QAAQ;AAAA;AAAA,iBAGL,MAAA,CAAO,EAAA,cAAgB,OAAA,GAAU,aAAa;AAjC9D;;;;AAA4B;AAwB5B;;;;AAMqB;AAGrB;AAjCA,iBAmEgB,eAAA,CAAgB,EAAc;;;;;;;iBAe9B,QAAA,IAAY,MAAA,EAAQ,QAAA,CAAS,CAAA,IAAK,QAAA,CAAS,CAAA;AAf3D;;;;AAAA,iBAyBgB,KAAA,IAAS,EAAA,QAAU,CAAA,GAAI,CAAC;AAVxC;AAAA,iBAsBgB,OAAA,IAAW,EAAA,QAAU,CAAA,GAAI,CAAC;;;;;;;;;;iBAmB1B,EAAA,OACd,IAAA,EAAM,QAAA,CAAS,CAAA,GACf,EAAA,GAAK,KAAA,EAAO,CAAA,EAAG,SAAA,EAAW,CAAA,cAAe,IAAA,EAAM,CAAA,iBAAkB,CAAA,EACjE,OAAA;EAAY,KAAA;AAAA,UACL,CAAA;;;AA7CmD;AAU5D;iBAwDgB,SAAA,CAAU,EAAc;;;;;;;;AAxDA;AAYxC;;;iBA6DgB,UAAA,IAAc,EAAA,GAAK,OAAA,iBAAwB,CAAA,GAAI,CAAC;;iBAehD,QAAA,IAAY,KAAK;;iBAOjB,YAAA,IAAgB,KAAA,EAAO,KAAA,SAAc,EAAA,QAAU,CAAA,GAAI,CAAA"}
@@ -401,6 +401,31 @@ function untrack(fn) {
401
401
  }
402
402
  }
403
403
  /**
404
+ * Make a computation react to an EXPLICIT dependency only — the body runs untracked, so signals it
405
+ * reads don't subscribe; only `deps` does. Wrap it in `effect`/`memo`: `effect(on(() => a(), (av) => …))`
406
+ * re-runs when `a` changes but not when other signals the body reads change. The callback receives the
407
+ * current dep value, the previous dep value, and its own previous return. With `{ defer: true }` the
408
+ * first run is skipped (deps are still tracked) — react only to *changes*, not the initial value.
409
+ *
410
+ * @example effect(on(() => userId(), (id) => { void load(id) }, { defer: true }))
411
+ */
412
+ function on(deps, fn, options) {
413
+ let defer = options?.defer ?? false;
414
+ let prevInput;
415
+ let prevResult;
416
+ return () => {
417
+ const input = deps();
418
+ if (defer) {
419
+ defer = false;
420
+ prevInput = input;
421
+ return;
422
+ }
423
+ prevResult = untrack(() => fn(input, prevInput, prevResult));
424
+ prevInput = input;
425
+ return prevResult;
426
+ };
427
+ }
428
+ /**
404
429
  * Register a cleanup to run before the owning computation re-runs and when it
405
430
  * is disposed. No-op outside a reactive scope.
406
431
  */
@@ -451,6 +476,6 @@ function runWithOwner(owner, fn) {
451
476
  }
452
477
  }
453
478
  //#endregion
454
- export { batch, computed, createRoot, deferred, effect, getOwner, memo, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack };
479
+ export { batch, computed, createRoot, deferred, effect, getOwner, memo, on, onCleanup, runWithOwner, setReactiveScheduler, signal, startTransition, untrack };
455
480
 
456
481
  //# sourceMappingURL=reactive.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.js","names":[],"sources":["../../src/reactive/reactive.ts"],"sourcesContent":["/**\n * MindeesNative reactivity — fine-grained, glitch-free, lazy signals.\n *\n * Algorithm: push–pull with graph coloring (`CLEAN` / `CHECK` / `DIRTY`), in the\n * lineage of SolidJS and the \"reactively\" library. A write *pushes* staleness\n * markers through the observer graph; a read *pulls*, recomputing a node only\n * when one of its sources actually changed. This guarantees:\n *\n * - **Glitch freedom** — no observer ever sees an inconsistent intermediate\n * state (the classic diamond dependency recomputes its consumer exactly once).\n * - **No redundant recomputation** — a node recomputes at most once per change,\n * and an equal recomputation does not propagate to its observers.\n * - **Deterministic, synchronous propagation** — effects run in a predictable\n * order, batched to the end of the outermost write/batch.\n * - **Complete disposal** — disposing an owner unlinks every subscription, so\n * there are no leaked observers.\n *\n * @module\n */\n\nimport type { Priority, ScheduledTask, Scheduler } from '../scheduler'\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\n/**\n * @internal Engine-side disposal scope. Computations and roots own the cleanups\n * and child computations created while they are the active owner; disposing an\n * owner tears all of them down. This concrete shape stays internal — see the\n * public {@link Owner} opaque handle.\n */\ninterface OwnerNode {\n /** Child computations created while this owner was active. */\n owned: AnyComputation[] | null\n /** Cleanup callbacks registered via {@link onCleanup}. */\n cleanups: Array<() => void> | null\n}\n\n/** @internal Phantom brand making {@link Owner} nominal — see below. */\ndeclare const OWNER_BRAND: unique symbol\n\n/**\n * An opaque disposal-scope handle. Obtain one with {@link getOwner} and re-enter\n * it with {@link runWithOwner}. Its internal shape — the reactive graph nodes it\n * owns — is intentionally not part of the public type surface, so the type-erased\n * {@link Computation} graph never leaks (no `any`, no internal mutable fields)\n * into consumers' types. Treat it as a token: hold it, pass it back; do not reach\n * inside it.\n *\n * It is **nominal** (branded with a private phantom symbol) so a structural\n * object literal — e.g. `{}` — is *not* assignable to it. Only a value handed out\n * by {@link getOwner} is a valid `Owner`; this prevents a fabricated owner from\n * flowing through {@link runWithOwner} into `onCleanup`/`adopt` and crashing on a\n * missing `owned`/`cleanups` field.\n */\nexport interface Owner {\n /** @internal Phantom brand — never present at runtime; blocks fabrication. */\n readonly [OWNER_BRAND]: never\n}\n\n// ---------------------------------------------------------------------------\n// Node state\n// ---------------------------------------------------------------------------\n\ntype State = 0 | 1 | 2 | 3\nconst CLEAN: State = 0\nconst CHECK: State = 1\nconst DIRTY: State = 2\nconst DISPOSED: State = 3\n\n/** Equality comparator used to decide whether a value actually changed. */\nexport type EqualsFn<T> = (a: T, b: T) => boolean\n\n// Default comparator: `Object.is`, matching context `select()` so the whole\n// package shares one semantics. Unlike `===`, this treats `NaN` as equal to\n// itself (so `set(NaN)` after `NaN` does not re-notify) and `-0`/`+0` as\n// different — the conventional choice for signal libraries.\nconst equalsDefault = (a: unknown, b: unknown): boolean => Object.is(a, b)\n\n/**\n * The reactive graph is intentionally type-erased: a node may observe, and be\n * observed by, computations of unrelated value types. Internal graph links use\n * this alias; the public API (`Signal<T>` / `Memo<T>` / `Accessor<T>`) stays\n * fully typed.\n */\n// biome-ignore lint/suspicious/noExplicitAny: type-erased reactive graph links\ntype AnyComputation = Computation<any>\n\n// ---------------------------------------------------------------------------\n// Globals (tracking + scheduling)\n// ---------------------------------------------------------------------------\n\n/** The computation currently executing, used for automatic dependency tracking. */\nlet currentObserver: AnyComputation | null = null\n/** The active disposal scope for onCleanup / child registration. */\nlet currentOwner: OwnerNode | null = null\n/** Outstanding `batch()` nesting depth; effects flush when this returns to 0. */\nlet batchDepth = 0\n/** Effects marked stale and awaiting a flush. */\nconst effectQueue: AnyComputation[] = []\n/** Guard against re-entrant flushes. */\nlet flushing = false\n/** Safety valve against accidental infinite reactive loops. */\nconst MAX_FLUSH_ITERATIONS = 100_000\n\n/**\n * The scheduler that `'normal'`-lane effects defer through. `null` by default — and while it is null\n * EVERY effect (including `priority: 'normal'`) flushes synchronously, so the sync default is\n * unchanged for all of SSR + tests until a host opts in. Set once at app bootstrap.\n */\nlet reactiveScheduler: Scheduler | null = null\n/** Monotonic counter for default `'normal'`-lane dedup keys. */\nlet effectKeySeq = 0\n/** >0 while inside a {@link startTransition}; effects staled during it defer (when a scheduler exists). */\nlet transitionDepth = 0\n/** Sync effects staled inside the current transition — treated as deferred for THIS drain only. */\nconst transitionTagged = new Set<AnyComputation>()\n\n/**\n * Inject (or clear) the {@link Scheduler} that `effect(fn, { priority: 'normal' })` defers through.\n * With no scheduler (the default), `'normal'`-lane effects flush synchronously — so behavior is\n * identical to today until a host wires one in. Pass `null` to detach.\n */\nexport function setReactiveScheduler(scheduler: Scheduler | null): void {\n reactiveScheduler = scheduler\n}\n\n/** @internal Test-only handle to a node behind an accessor. Not public API. */\nexport const NODE: unique symbol = Symbol('mindees.reactive.node')\n\ninterface WithNode<T> {\n [NODE]: Computation<T>\n}\n\n// ---------------------------------------------------------------------------\n// Computation: the unit of reactivity (signal, computed, or effect)\n// ---------------------------------------------------------------------------\n\nclass Computation<T> implements OwnerNode {\n value: T\n fn: (() => T) | null\n state: State\n sources: AnyComputation[] | null = null\n observers: AnyComputation[] | null = null\n owned: AnyComputation[] | null = null\n cleanups: Array<() => void> | null = null\n equals: EqualsFn<T> | false\n readonly isEffect: boolean\n /**\n * Flush lane. `'sync'` (the default for every signal/computed and every plain `effect`) flushes\n * inline exactly as before. Only `effect(fn, { priority: 'normal' })` sets `'normal'`, and even\n * then it only defers when a scheduler is injected via {@link setReactiveScheduler} — otherwise it\n * falls back to synchronous flush. This keeps the synchronous default provably untouched.\n */\n lane: Priority = 'sync'\n /** A pending scheduled flush for a `'normal'`-lane effect (cancelled on disposal). */\n pendingTask: ScheduledTask | null = null\n /** Scheduler dedup key for a `'normal'`-lane effect (so rapid re-stales coalesce). */\n schedKey: string | undefined\n /**\n * Whether {@link value} holds a real computed result yet. Derivations start\n * uninitialized (their initial `value` is a placeholder); the first\n * computation must NOT call `equals(oldValue, …)` against that placeholder —\n * a custom comparator would receive `undefined` and could throw.\n */\n private initialized: boolean\n /**\n * True only while this node's own {@link update} is on the stack. Lets\n * {@link markStale} recognize a *self-write* — the body writing a signal the\n * node observes — instead of dropping the mark (the node is already DIRTY).\n */\n private running = false\n /**\n * Set by {@link markStale} when a self-write occurs mid-update. {@link update}'s\n * loop recomputes once more so the node converges on the value it just produced,\n * honoring the contract that a computation reflects its dependencies' latest\n * values. Reset at the start of every pass.\n */\n private restaleRequested = false\n\n constructor(value: T, fn: (() => T) | null, equals: EqualsFn<T> | false, isEffect: boolean) {\n this.value = value\n this.fn = fn\n this.equals = equals\n this.isEffect = isEffect\n // Signals (no fn) start CLEAN and already hold a real value; derivations\n // start DIRTY (compute lazily) and uninitialized.\n this.state = fn ? DIRTY : CLEAN\n this.initialized = fn === null\n }\n\n /** Read the current value, tracking a dependency if a computation is running. */\n read(): T {\n if (this.state === DISPOSED) return this.value\n if (currentObserver) link(currentObserver, this)\n if (this.fn) this.updateIfNecessary()\n return this.value\n }\n\n /** Write a new value (signals only); pushes staleness to observers. */\n write(value: T): T {\n if (this.equals !== false && this.equals(this.value, value)) return this.value\n this.value = value\n if (this.observers) {\n for (const o of this.observers) o.markStale(DIRTY)\n }\n if (batchDepth === 0) flushEffects()\n return value\n }\n\n /** Color this node (and, transitively, its observers) as stale. */\n markStale(state: State): void {\n // Self-write: this node is being marked stale while its own update() is\n // running (its body just wrote a signal it observes). It is already DIRTY, so\n // the gate below would silently drop the mark and the change would be lost.\n // Instead request one more recompute pass (handled by update()'s loop); don't\n // re-propagate here — the re-run will, once it produces a new value.\n if (this.running) {\n this.restaleRequested = true\n return\n }\n if (this.state < state) {\n const wasClean = this.state === CLEAN\n this.state = state\n if (this.isEffect && wasClean) {\n effectQueue.push(this)\n // Inside a startTransition (with a scheduler), tag a SYNC effect so this drain defers it —\n // without permanently changing its lane. No-op on every default path (transitionDepth 0).\n if (transitionDepth > 0 && reactiveScheduler && this.lane === 'sync') {\n transitionTagged.add(this)\n }\n }\n if (this.observers) {\n for (const o of this.observers) o.markStale(CHECK)\n }\n }\n }\n\n /** Bring this node up to date, recomputing only if a source truly changed. */\n updateIfNecessary(): void {\n if (this.state === CLEAN || this.state === DISPOSED) return\n if (this.state === CHECK && this.sources) {\n for (const src of this.sources) {\n src.updateIfNecessary()\n if (this.state === DIRTY) break\n }\n }\n try {\n if (this.state === DIRTY) this.update()\n } finally {\n // Always settle to CLEAN (unless disposed) even if update() — the body or a\n // child cleanup — threw. Otherwise the node would stay DIRTY forever and\n // markStale's wasClean gate would never re-queue it (a permanent zombie).\n // Sources read before the throw are re-linked, so a later change recovers it.\n if (this.state !== DISPOSED) this.state = CLEAN\n }\n }\n\n /**\n * Recompute the derivation, re-tracking dependencies and notifying observers.\n *\n * Runs in a bounded loop. If the body writes a signal it itself observes (a\n * self-write), {@link markStale} sets {@link restaleRequested} rather than the\n * mark being lost, and we recompute again so the node converges on the value it\n * just produced. The loop is capped by {@link MAX_FLUSH_ITERATIONS} so a\n * non-terminating self-writer (e.g. `effect(() => a.set(a() + 1))`) throws\n * instead of hanging. A prior-run cleanup that throws during teardown must not\n * abort the re-track/recompute (that would strand the node's children and\n * dynamic deps); its error is captured and rethrown only after the node has\n * rebuilt a consistent graph.\n */\n private update(): void {\n const oldValue = this.value\n let cleanupError: unknown\n let hasCleanupError = false\n let iterations = 0\n this.running = true\n try {\n do {\n this.restaleRequested = false\n try {\n disposeChildren(this)\n } catch (err) {\n if (!hasCleanupError) {\n cleanupError = err\n hasCleanupError = true\n }\n }\n unlinkSources(this)\n\n const prevObserver = currentObserver\n const prevOwner = currentOwner\n currentObserver = this\n currentOwner = this\n try {\n // biome-ignore lint/style/noNonNullAssertion: update() only runs for derivations (fn != null).\n this.value = this.fn!()\n } finally {\n currentObserver = prevObserver\n currentOwner = prevOwner\n }\n\n if (++iterations > MAX_FLUSH_ITERATIONS) {\n this.restaleRequested = false\n throw new Error(\n 'MindeesNative: potential infinite reactive loop detected — a computation keeps writing a signal it reads.',\n )\n }\n } while (this.restaleRequested)\n } finally {\n this.running = false\n }\n\n // On the first computation there is no prior value to compare against, so\n // the result is always \"changed\". Afterwards, apply the equality check\n // (unless equals is false, meaning \"always changed\").\n const wasInitialized = this.initialized\n this.initialized = true\n const changed = !wasInitialized || this.equals === false || !this.equals(oldValue, this.value)\n if (changed && this.observers) {\n // Observers are already CHECK from the original push; promote them to DIRTY\n // so the in-flight pull recomputes them.\n for (const o of this.observers) {\n o.state = DIRTY\n }\n }\n\n // The graph is consistent again; now surface any error a previous-run cleanup\n // threw during teardown (the body still re-ran, so children/deps are rebuilt).\n if (hasCleanupError) throw cleanupError\n }\n}\n\n// ---------------------------------------------------------------------------\n// Graph maintenance\n// ---------------------------------------------------------------------------\n\nfunction link(observer: AnyComputation, source: AnyComputation): void {\n // Never subscribe a disposed observer — e.g. a node that disposed itself\n // mid-run and then read another signal. The subscription would leak: nothing\n // tears down a DISPOSED node again.\n if (observer.state === DISPOSED) return\n if (observer.sources === null) observer.sources = []\n const sources = observer.sources\n if (!sources.includes(source)) {\n sources.push(source)\n if (source.observers === null) source.observers = []\n source.observers.push(observer)\n }\n}\n\nfunction unlinkSources(node: AnyComputation): void {\n if (!node.sources) return\n for (const src of node.sources) {\n const obs = src.observers\n if (!obs) continue\n const idx = obs.indexOf(node)\n if (idx >= 0) {\n const last = obs.pop()\n if (last && idx < obs.length) obs[idx] = last\n }\n }\n node.sources = null\n}\n\nfunction disposeChildren(owner: OwnerNode): void {\n // Dispose every owned child AND run every cleanup even if some throw, so a\n // single faulty child/cleanup can't strand the rest or leak observers. `owned`\n // is nulled up front so a throw can't leave a half-disposed array behind.\n // Failures are collected and surfaced together afterward.\n const errors: unknown[] = []\n if (owner.owned) {\n const owned = owner.owned\n owner.owned = null\n for (const child of owned) {\n try {\n disposeComputation(child)\n } catch (err) {\n errors.push(err)\n }\n }\n }\n if (owner.cleanups) {\n const cleanups = owner.cleanups\n owner.cleanups = null\n for (const c of cleanups) {\n try {\n c()\n } catch (err) {\n errors.push(err)\n }\n }\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) throw new AggregateError(errors, 'disposal threw')\n}\n\nfunction disposeComputation(node: AnyComputation): void {\n if (node.state === DISPOSED) return\n // If a node disposes itself mid-run, stop tracking and adopting onto it for the\n // rest of the (now-aborted) body: a later tracked read would otherwise\n // re-subscribe this DISPOSED node, and onCleanup/adopt would register work on a\n // dead scope — none of which is ever torn down again (a leak). Clearing the\n // globals makes read()/onCleanup/adopt no-op for the remainder of the body;\n // update()'s finally restores the previous owner afterward.\n if (node === currentObserver) currentObserver = null\n if (node === currentOwner) currentOwner = null\n try {\n disposeChildren(node)\n } finally {\n // Always fully unlink + mark disposed, even if a descendant cleanup threw,\n // so this node never leaks as a subscribed zombie. The error (if any) still\n // propagates to the caller, which aggregates it across siblings.\n unlinkSources(node)\n node.observers = null\n node.state = DISPOSED\n // Cancel a pending deferred flush so a torn-down effect never runs against a dead graph.\n // (Always null on the synchronous default path — zero behavior change there.)\n if (node.pendingTask) {\n node.pendingTask.cancel()\n node.pendingTask = null\n }\n }\n}\n\nfunction adopt(node: AnyComputation): void {\n if (!currentOwner) return\n if (currentOwner.owned === null) currentOwner.owned = []\n currentOwner.owned.push(node)\n}\n\n// ---------------------------------------------------------------------------\n// Effect scheduling\n// ---------------------------------------------------------------------------\n\nfunction flushEffects(): void {\n if (flushing) return\n flushing = true\n // Isolate each effect: one throwing effect must not abort the flush or strand\n // the effects queued after it (each effect also self-recovers to CLEAN via\n // updateIfNecessary's finally). Errors are collected and surfaced after drain.\n const errors: unknown[] = []\n try {\n let i = 0\n let iterations = 0\n while (i < effectQueue.length) {\n if (++iterations > MAX_FLUSH_ITERATIONS) {\n effectQueue.length = 0\n throw new Error(\n 'MindeesNative: potential infinite reactive loop detected while flushing effects.',\n )\n }\n const e = effectQueue[i]\n i++\n if (e && e.state !== CLEAN && e.state !== DISPOSED) {\n // Defer when the effect's own lane is 'normal', OR it was staled inside a startTransition\n // (tagged for THIS drain only) — but only if a scheduler is injected. Otherwise sync.\n if (reactiveScheduler && (e.lane === 'normal' || transitionTagged.has(e))) {\n // Deferred lane: hand the flush to the scheduler under a stable key, so rapid re-stales\n // coalesce (latest wins) and the body runs later — reading the LATEST values at run time\n // (updateIfNecessary recomputes sources in order), so glitch-freedom is preserved.\n const node = e\n const sched = reactiveScheduler\n // A transition-tagged sync effect has no key yet; mint a stable one so its re-stales coalesce.\n if (node.schedKey === undefined) node.schedKey = `mindees:effect:${effectKeySeq++}`\n node.pendingTask = sched.schedule(\n () => {\n node.pendingTask = null\n if (node.state !== CLEAN && node.state !== DISPOSED) node.updateIfNecessary()\n },\n { priority: 'normal', key: node.schedKey },\n )\n } else {\n // Synchronous lane — byte-identical to the pre-scheduler behavior (and the path every\n // existing test + SSR takes, since `lane` is 'sync' and/or no scheduler is injected).\n try {\n e.updateIfNecessary()\n } catch (err) {\n errors.push(err)\n }\n }\n }\n }\n } finally {\n effectQueue.length = 0\n transitionTagged.clear() // transition tags apply only to the drain that observed them\n flushing = false\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) throw new AggregateError(errors, 'effect(s) threw during flush')\n}\n\nfunction attachNode<T, A extends object>(accessor: A, node: Computation<T>): A {\n ;(accessor as A & WithNode<T>)[NODE] = node\n return accessor\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A read accessor for a derived/reactive value. */\nexport type Accessor<T> = () => T\n\n/** Options accepted by {@link signal}. */\nexport interface SignalOptions<T> {\n /** Custom equality. `false` means \"always notify\" (every write propagates). */\n equals?: EqualsFn<T> | false\n}\n\n/** A writable reactive value: call to read; `.set`/`.update` to write. */\nexport interface Signal<T> {\n /** Read the value (tracks a dependency inside a computation). */\n (): T\n /** Replace the value; returns the new value. */\n set(value: T): T\n /** Update from the previous value; returns the new value. */\n update(fn: (prev: T) => T): T\n /** Read without tracking a dependency. */\n peek(): T\n}\n\n/** Options accepted by {@link computed}. */\nexport interface ComputedOptions<T> {\n /** Custom equality used to decide whether downstream observers re-run. */\n equals?: EqualsFn<T> | false\n}\n\n/** A memoized derived value: call to read; `.peek` to read without tracking. */\nexport interface Memo<T> {\n /** Read the (lazily recomputed) value, tracking a dependency. */\n (): T\n /** Read without tracking a dependency. */\n peek(): T\n}\n\n/**\n * Create a writable reactive value.\n *\n * @example\n * const count = signal(0)\n * count() // read → 0\n * count.set(1) // write\n * count.update(n => n + 1)\n */\nexport function signal<T>(value: T, options?: SignalOptions<T>): Signal<T> {\n const node = new Computation<T>(value, null, options?.equals ?? equalsDefault, false)\n const accessor = (() => node.read()) as Signal<T>\n accessor.set = (v: T) => node.write(v)\n accessor.update = (fn: (prev: T) => T) => node.write(fn(node.value))\n accessor.peek = () => node.value\n return attachNode(accessor, node)\n}\n\n/**\n * Create a memoized derived value. The function re-runs only when one of the\n * reactive values it reads has actually changed, and only when the result is\n * observed (lazy).\n *\n * @example\n * const doubled = computed(() => count() * 2)\n */\nexport function computed<T>(fn: () => T, options?: ComputedOptions<T>): Memo<T> {\n const node = new Computation<T>(undefined as T, fn, options?.equals ?? equalsDefault, false)\n adopt(node)\n const accessor = (() => node.read()) as Memo<T>\n accessor.peek = () => {\n node.updateIfNecessary()\n return node.value\n }\n return attachNode(accessor, node)\n}\n\n/** Alias of {@link computed}. */\nexport const memo = computed\n\n/**\n * Run a side effect that re-runs whenever its reactive dependencies change.\n * Runs once immediately to establish dependencies.\n *\n * To clean up before each re-run and on disposal, either return a cleanup\n * function from the effect, or call {@link onCleanup}. Any non-function return\n * value is ignored (so expression-bodied effects like `() => list.push(x())`\n * are fine).\n *\n * @returns A disposer that stops the effect and runs its cleanups.\n *\n * @example\n * const stop = effect(() => console.log(count()))\n * stop() // unsubscribe\n *\n * @example\n * effect(() => {\n * const id = setInterval(tick, 1000)\n * return () => clearInterval(id) // cleanup\n * })\n */\n/** Options for {@link effect}. */\nexport interface EffectOptions {\n /**\n * `'sync'` (default) flushes the effect inline on every change — the synchronous, glitch-free\n * behavior. `'normal'` defers the effect through the injected {@link setReactiveScheduler scheduler}\n * (interaction priority / deferred heavy work); with no scheduler it falls back to synchronous.\n */\n priority?: Priority\n}\n\nexport function effect(fn: () => void, options?: EffectOptions): () => void {\n const node = new Computation<void>(\n undefined,\n () => {\n const result: unknown = (fn as () => unknown)()\n if (typeof result === 'function') onCleanup(result as () => void)\n },\n false,\n true,\n )\n // Opt into the deferred lane. `effect(fn)` and `effect(fn, { priority: 'sync' })` are identical to\n // before (synchronous flush). `'normal'` only defers once a scheduler is injected.\n if (options?.priority === 'normal') {\n node.lane = 'normal'\n // ALWAYS a unique per-node key: this coalesces THIS effect's own rapid re-stales (latest wins)\n // without ever sharing a scheduler entry with another effect (which would cross-cancel them).\n node.schedKey = `mindees:effect:${effectKeySeq++}`\n }\n adopt(node)\n node.updateIfNecessary() // first run is ALWAYS synchronous (establishes deps + initial paint)\n return () => disposeComputation(node)\n}\n\n/**\n * Apply the writes in `fn` as a low-priority **transition**: the writes take effect immediately\n * (reads inside/after see the latest values), but the effects they invalidate are **deferred**\n * through the injected {@link setReactiveScheduler scheduler} instead of running synchronously — so a\n * heavy re-render triggered by, say, a keystroke doesn't block the interaction. With no scheduler\n * injected, this is a plain synchronous batch (no deferral) — safe for SSR/tests.\n *\n * @example\n * input.set(value) // urgent: the field updates now\n * startTransition(() => query.set(value)) // the expensive results re-render can lag\n */\nexport function startTransition(fn: () => void): void {\n transitionDepth++\n try {\n batch(fn) // coalesce the transition's writes into one flush; effects staled in it get tagged\n } finally {\n transitionDepth--\n }\n}\n\n/**\n * A **deferred view** of `source`: it tracks `source` on the scheduler's low-priority lane, so under\n * sustained updates it lags behind the live value (the React `useDeferredValue` pattern — show stale\n * results while the latest are computed). With no scheduler injected it mirrors `source`\n * synchronously (no lag), so SSR/tests see the live value. Must be created inside an owner.\n */\nexport function deferred<T>(source: Accessor<T>): Accessor<T> {\n const out = signal(source())\n effect(() => out.set(source()), { priority: 'normal' })\n return () => out()\n}\n\n/**\n * Batch multiple writes so dependent effects run once, after the batch. Reads\n * inside a batch still observe the latest written values synchronously.\n */\nexport function batch<T>(fn: () => T): T {\n if (batchDepth > 0) return fn()\n batchDepth++\n try {\n return fn()\n } finally {\n batchDepth--\n flushEffects()\n }\n}\n\n/** Read reactive values without subscribing the current computation to them. */\nexport function untrack<T>(fn: () => T): T {\n const prev = currentObserver\n currentObserver = null\n try {\n return fn()\n } finally {\n currentObserver = prev\n }\n}\n\n/**\n * Register a cleanup to run before the owning computation re-runs and when it\n * is disposed. No-op outside a reactive scope.\n */\nexport function onCleanup(fn: () => void): void {\n if (!currentOwner) return\n if (currentOwner.cleanups === null) currentOwner.cleanups = []\n currentOwner.cleanups.push(fn)\n}\n\n/**\n * Create a non-tracked root scope that owns everything created within it. The\n * scope lives until the provided `dispose` function is called.\n *\n * @example\n * const dispose = createRoot((dispose) => {\n * effect(() => console.log(count()))\n * return dispose\n * })\n * dispose() // tear down the effect\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const root: OwnerNode = { owned: null, cleanups: null }\n const prevObserver = currentObserver\n const prevOwner = currentOwner\n currentObserver = null\n currentOwner = root\n try {\n return fn(() => disposeChildren(root))\n } finally {\n currentObserver = prevObserver\n currentOwner = prevOwner\n }\n}\n\n/** The current owner scope, or `null` outside any reactive scope. */\nexport function getOwner(): Owner | null {\n // OwnerNode → the nominal public Owner. The brand is phantom (never present at\n // runtime), so the only honest way to produce an Owner is through this cast.\n return currentOwner as unknown as Owner | null\n}\n\n/** Run `fn` with `owner` as the active scope (e.g. to re-attach cleanups). */\nexport function runWithOwner<T>(owner: Owner | null, fn: () => T): T {\n const prev = currentOwner\n // `owner` is an opaque public handle; internally it is always an OwnerNode we\n // handed out via getOwner(). This is the one trusted boundary cast.\n currentOwner = owner as unknown as OwnerNode | null\n try {\n return fn()\n } finally {\n currentOwner = prev\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal test helpers (exported from this module only — NOT from the package\n// public entry point). Used to assert white-box invariants like leak-freedom.\n// ---------------------------------------------------------------------------\n\n/** @internal Number of live observers subscribed to the node behind `accessor`. */\nexport function _observerCount(accessor: object): number {\n const node = (accessor as Partial<WithNode<unknown>>)[NODE]\n return node?.observers?.length ?? 0\n}\n\n/** @internal Number of sources the node behind `accessor` depends on. */\nexport function _sourceCount(accessor: object): number {\n const node = (accessor as Partial<WithNode<unknown>>)[NODE]\n return node?.sources?.length ?? 0\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reactive.js","names":[],"sources":["../../src/reactive/reactive.ts"],"sourcesContent":["/**\n * MindeesNative reactivity — fine-grained, glitch-free, lazy signals.\n *\n * Algorithm: push–pull with graph coloring (`CLEAN` / `CHECK` / `DIRTY`), in the\n * lineage of SolidJS and the \"reactively\" library. A write *pushes* staleness\n * markers through the observer graph; a read *pulls*, recomputing a node only\n * when one of its sources actually changed. This guarantees:\n *\n * - **Glitch freedom** — no observer ever sees an inconsistent intermediate\n * state (the classic diamond dependency recomputes its consumer exactly once).\n * - **No redundant recomputation** — a node recomputes at most once per change,\n * and an equal recomputation does not propagate to its observers.\n * - **Deterministic, synchronous propagation** — effects run in a predictable\n * order, batched to the end of the outermost write/batch.\n * - **Complete disposal** — disposing an owner unlinks every subscription, so\n * there are no leaked observers.\n *\n * @module\n */\n\nimport type { Priority, ScheduledTask, Scheduler } from '../scheduler'\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\n/**\n * @internal Engine-side disposal scope. Computations and roots own the cleanups\n * and child computations created while they are the active owner; disposing an\n * owner tears all of them down. This concrete shape stays internal — see the\n * public {@link Owner} opaque handle.\n */\ninterface OwnerNode {\n /** Child computations created while this owner was active. */\n owned: AnyComputation[] | null\n /** Cleanup callbacks registered via {@link onCleanup}. */\n cleanups: Array<() => void> | null\n}\n\n/** @internal Phantom brand making {@link Owner} nominal — see below. */\ndeclare const OWNER_BRAND: unique symbol\n\n/**\n * An opaque disposal-scope handle. Obtain one with {@link getOwner} and re-enter\n * it with {@link runWithOwner}. Its internal shape — the reactive graph nodes it\n * owns — is intentionally not part of the public type surface, so the type-erased\n * {@link Computation} graph never leaks (no `any`, no internal mutable fields)\n * into consumers' types. Treat it as a token: hold it, pass it back; do not reach\n * inside it.\n *\n * It is **nominal** (branded with a private phantom symbol) so a structural\n * object literal — e.g. `{}` — is *not* assignable to it. Only a value handed out\n * by {@link getOwner} is a valid `Owner`; this prevents a fabricated owner from\n * flowing through {@link runWithOwner} into `onCleanup`/`adopt` and crashing on a\n * missing `owned`/`cleanups` field.\n */\nexport interface Owner {\n /** @internal Phantom brand — never present at runtime; blocks fabrication. */\n readonly [OWNER_BRAND]: never\n}\n\n// ---------------------------------------------------------------------------\n// Node state\n// ---------------------------------------------------------------------------\n\ntype State = 0 | 1 | 2 | 3\nconst CLEAN: State = 0\nconst CHECK: State = 1\nconst DIRTY: State = 2\nconst DISPOSED: State = 3\n\n/** Equality comparator used to decide whether a value actually changed. */\nexport type EqualsFn<T> = (a: T, b: T) => boolean\n\n// Default comparator: `Object.is`, matching context `select()` so the whole\n// package shares one semantics. Unlike `===`, this treats `NaN` as equal to\n// itself (so `set(NaN)` after `NaN` does not re-notify) and `-0`/`+0` as\n// different — the conventional choice for signal libraries.\nconst equalsDefault = (a: unknown, b: unknown): boolean => Object.is(a, b)\n\n/**\n * The reactive graph is intentionally type-erased: a node may observe, and be\n * observed by, computations of unrelated value types. Internal graph links use\n * this alias; the public API (`Signal<T>` / `Memo<T>` / `Accessor<T>`) stays\n * fully typed.\n */\n// biome-ignore lint/suspicious/noExplicitAny: type-erased reactive graph links\ntype AnyComputation = Computation<any>\n\n// ---------------------------------------------------------------------------\n// Globals (tracking + scheduling)\n// ---------------------------------------------------------------------------\n\n/** The computation currently executing, used for automatic dependency tracking. */\nlet currentObserver: AnyComputation | null = null\n/** The active disposal scope for onCleanup / child registration. */\nlet currentOwner: OwnerNode | null = null\n/** Outstanding `batch()` nesting depth; effects flush when this returns to 0. */\nlet batchDepth = 0\n/** Effects marked stale and awaiting a flush. */\nconst effectQueue: AnyComputation[] = []\n/** Guard against re-entrant flushes. */\nlet flushing = false\n/** Safety valve against accidental infinite reactive loops. */\nconst MAX_FLUSH_ITERATIONS = 100_000\n\n/**\n * The scheduler that `'normal'`-lane effects defer through. `null` by default — and while it is null\n * EVERY effect (including `priority: 'normal'`) flushes synchronously, so the sync default is\n * unchanged for all of SSR + tests until a host opts in. Set once at app bootstrap.\n */\nlet reactiveScheduler: Scheduler | null = null\n/** Monotonic counter for default `'normal'`-lane dedup keys. */\nlet effectKeySeq = 0\n/** >0 while inside a {@link startTransition}; effects staled during it defer (when a scheduler exists). */\nlet transitionDepth = 0\n/** Sync effects staled inside the current transition — treated as deferred for THIS drain only. */\nconst transitionTagged = new Set<AnyComputation>()\n\n/**\n * Inject (or clear) the {@link Scheduler} that `effect(fn, { priority: 'normal' })` defers through.\n * With no scheduler (the default), `'normal'`-lane effects flush synchronously — so behavior is\n * identical to today until a host wires one in. Pass `null` to detach.\n */\nexport function setReactiveScheduler(scheduler: Scheduler | null): void {\n reactiveScheduler = scheduler\n}\n\n/** @internal Test-only handle to a node behind an accessor. Not public API. */\nexport const NODE: unique symbol = Symbol('mindees.reactive.node')\n\ninterface WithNode<T> {\n [NODE]: Computation<T>\n}\n\n// ---------------------------------------------------------------------------\n// Computation: the unit of reactivity (signal, computed, or effect)\n// ---------------------------------------------------------------------------\n\nclass Computation<T> implements OwnerNode {\n value: T\n fn: (() => T) | null\n state: State\n sources: AnyComputation[] | null = null\n observers: AnyComputation[] | null = null\n owned: AnyComputation[] | null = null\n cleanups: Array<() => void> | null = null\n equals: EqualsFn<T> | false\n readonly isEffect: boolean\n /**\n * Flush lane. `'sync'` (the default for every signal/computed and every plain `effect`) flushes\n * inline exactly as before. Only `effect(fn, { priority: 'normal' })` sets `'normal'`, and even\n * then it only defers when a scheduler is injected via {@link setReactiveScheduler} — otherwise it\n * falls back to synchronous flush. This keeps the synchronous default provably untouched.\n */\n lane: Priority = 'sync'\n /** A pending scheduled flush for a `'normal'`-lane effect (cancelled on disposal). */\n pendingTask: ScheduledTask | null = null\n /** Scheduler dedup key for a `'normal'`-lane effect (so rapid re-stales coalesce). */\n schedKey: string | undefined\n /**\n * Whether {@link value} holds a real computed result yet. Derivations start\n * uninitialized (their initial `value` is a placeholder); the first\n * computation must NOT call `equals(oldValue, …)` against that placeholder —\n * a custom comparator would receive `undefined` and could throw.\n */\n private initialized: boolean\n /**\n * True only while this node's own {@link update} is on the stack. Lets\n * {@link markStale} recognize a *self-write* — the body writing a signal the\n * node observes — instead of dropping the mark (the node is already DIRTY).\n */\n private running = false\n /**\n * Set by {@link markStale} when a self-write occurs mid-update. {@link update}'s\n * loop recomputes once more so the node converges on the value it just produced,\n * honoring the contract that a computation reflects its dependencies' latest\n * values. Reset at the start of every pass.\n */\n private restaleRequested = false\n\n constructor(value: T, fn: (() => T) | null, equals: EqualsFn<T> | false, isEffect: boolean) {\n this.value = value\n this.fn = fn\n this.equals = equals\n this.isEffect = isEffect\n // Signals (no fn) start CLEAN and already hold a real value; derivations\n // start DIRTY (compute lazily) and uninitialized.\n this.state = fn ? DIRTY : CLEAN\n this.initialized = fn === null\n }\n\n /** Read the current value, tracking a dependency if a computation is running. */\n read(): T {\n if (this.state === DISPOSED) return this.value\n if (currentObserver) link(currentObserver, this)\n if (this.fn) this.updateIfNecessary()\n return this.value\n }\n\n /** Write a new value (signals only); pushes staleness to observers. */\n write(value: T): T {\n if (this.equals !== false && this.equals(this.value, value)) return this.value\n this.value = value\n if (this.observers) {\n for (const o of this.observers) o.markStale(DIRTY)\n }\n if (batchDepth === 0) flushEffects()\n return value\n }\n\n /** Color this node (and, transitively, its observers) as stale. */\n markStale(state: State): void {\n // Self-write: this node is being marked stale while its own update() is\n // running (its body just wrote a signal it observes). It is already DIRTY, so\n // the gate below would silently drop the mark and the change would be lost.\n // Instead request one more recompute pass (handled by update()'s loop); don't\n // re-propagate here — the re-run will, once it produces a new value.\n if (this.running) {\n this.restaleRequested = true\n return\n }\n if (this.state < state) {\n const wasClean = this.state === CLEAN\n this.state = state\n if (this.isEffect && wasClean) {\n effectQueue.push(this)\n // Inside a startTransition (with a scheduler), tag a SYNC effect so this drain defers it —\n // without permanently changing its lane. No-op on every default path (transitionDepth 0).\n if (transitionDepth > 0 && reactiveScheduler && this.lane === 'sync') {\n transitionTagged.add(this)\n }\n }\n if (this.observers) {\n for (const o of this.observers) o.markStale(CHECK)\n }\n }\n }\n\n /** Bring this node up to date, recomputing only if a source truly changed. */\n updateIfNecessary(): void {\n if (this.state === CLEAN || this.state === DISPOSED) return\n if (this.state === CHECK && this.sources) {\n for (const src of this.sources) {\n src.updateIfNecessary()\n if (this.state === DIRTY) break\n }\n }\n try {\n if (this.state === DIRTY) this.update()\n } finally {\n // Always settle to CLEAN (unless disposed) even if update() — the body or a\n // child cleanup — threw. Otherwise the node would stay DIRTY forever and\n // markStale's wasClean gate would never re-queue it (a permanent zombie).\n // Sources read before the throw are re-linked, so a later change recovers it.\n if (this.state !== DISPOSED) this.state = CLEAN\n }\n }\n\n /**\n * Recompute the derivation, re-tracking dependencies and notifying observers.\n *\n * Runs in a bounded loop. If the body writes a signal it itself observes (a\n * self-write), {@link markStale} sets {@link restaleRequested} rather than the\n * mark being lost, and we recompute again so the node converges on the value it\n * just produced. The loop is capped by {@link MAX_FLUSH_ITERATIONS} so a\n * non-terminating self-writer (e.g. `effect(() => a.set(a() + 1))`) throws\n * instead of hanging. A prior-run cleanup that throws during teardown must not\n * abort the re-track/recompute (that would strand the node's children and\n * dynamic deps); its error is captured and rethrown only after the node has\n * rebuilt a consistent graph.\n */\n private update(): void {\n const oldValue = this.value\n let cleanupError: unknown\n let hasCleanupError = false\n let iterations = 0\n this.running = true\n try {\n do {\n this.restaleRequested = false\n try {\n disposeChildren(this)\n } catch (err) {\n if (!hasCleanupError) {\n cleanupError = err\n hasCleanupError = true\n }\n }\n unlinkSources(this)\n\n const prevObserver = currentObserver\n const prevOwner = currentOwner\n currentObserver = this\n currentOwner = this\n try {\n // biome-ignore lint/style/noNonNullAssertion: update() only runs for derivations (fn != null).\n this.value = this.fn!()\n } finally {\n currentObserver = prevObserver\n currentOwner = prevOwner\n }\n\n if (++iterations > MAX_FLUSH_ITERATIONS) {\n this.restaleRequested = false\n throw new Error(\n 'MindeesNative: potential infinite reactive loop detected — a computation keeps writing a signal it reads.',\n )\n }\n } while (this.restaleRequested)\n } finally {\n this.running = false\n }\n\n // On the first computation there is no prior value to compare against, so\n // the result is always \"changed\". Afterwards, apply the equality check\n // (unless equals is false, meaning \"always changed\").\n const wasInitialized = this.initialized\n this.initialized = true\n const changed = !wasInitialized || this.equals === false || !this.equals(oldValue, this.value)\n if (changed && this.observers) {\n // Observers are already CHECK from the original push; promote them to DIRTY\n // so the in-flight pull recomputes them.\n for (const o of this.observers) {\n o.state = DIRTY\n }\n }\n\n // The graph is consistent again; now surface any error a previous-run cleanup\n // threw during teardown (the body still re-ran, so children/deps are rebuilt).\n if (hasCleanupError) throw cleanupError\n }\n}\n\n// ---------------------------------------------------------------------------\n// Graph maintenance\n// ---------------------------------------------------------------------------\n\nfunction link(observer: AnyComputation, source: AnyComputation): void {\n // Never subscribe a disposed observer — e.g. a node that disposed itself\n // mid-run and then read another signal. The subscription would leak: nothing\n // tears down a DISPOSED node again.\n if (observer.state === DISPOSED) return\n if (observer.sources === null) observer.sources = []\n const sources = observer.sources\n if (!sources.includes(source)) {\n sources.push(source)\n if (source.observers === null) source.observers = []\n source.observers.push(observer)\n }\n}\n\nfunction unlinkSources(node: AnyComputation): void {\n if (!node.sources) return\n for (const src of node.sources) {\n const obs = src.observers\n if (!obs) continue\n const idx = obs.indexOf(node)\n if (idx >= 0) {\n const last = obs.pop()\n if (last && idx < obs.length) obs[idx] = last\n }\n }\n node.sources = null\n}\n\nfunction disposeChildren(owner: OwnerNode): void {\n // Dispose every owned child AND run every cleanup even if some throw, so a\n // single faulty child/cleanup can't strand the rest or leak observers. `owned`\n // is nulled up front so a throw can't leave a half-disposed array behind.\n // Failures are collected and surfaced together afterward.\n const errors: unknown[] = []\n if (owner.owned) {\n const owned = owner.owned\n owner.owned = null\n for (const child of owned) {\n try {\n disposeComputation(child)\n } catch (err) {\n errors.push(err)\n }\n }\n }\n if (owner.cleanups) {\n const cleanups = owner.cleanups\n owner.cleanups = null\n for (const c of cleanups) {\n try {\n c()\n } catch (err) {\n errors.push(err)\n }\n }\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) throw new AggregateError(errors, 'disposal threw')\n}\n\nfunction disposeComputation(node: AnyComputation): void {\n if (node.state === DISPOSED) return\n // If a node disposes itself mid-run, stop tracking and adopting onto it for the\n // rest of the (now-aborted) body: a later tracked read would otherwise\n // re-subscribe this DISPOSED node, and onCleanup/adopt would register work on a\n // dead scope — none of which is ever torn down again (a leak). Clearing the\n // globals makes read()/onCleanup/adopt no-op for the remainder of the body;\n // update()'s finally restores the previous owner afterward.\n if (node === currentObserver) currentObserver = null\n if (node === currentOwner) currentOwner = null\n try {\n disposeChildren(node)\n } finally {\n // Always fully unlink + mark disposed, even if a descendant cleanup threw,\n // so this node never leaks as a subscribed zombie. The error (if any) still\n // propagates to the caller, which aggregates it across siblings.\n unlinkSources(node)\n node.observers = null\n node.state = DISPOSED\n // Cancel a pending deferred flush so a torn-down effect never runs against a dead graph.\n // (Always null on the synchronous default path — zero behavior change there.)\n if (node.pendingTask) {\n node.pendingTask.cancel()\n node.pendingTask = null\n }\n }\n}\n\nfunction adopt(node: AnyComputation): void {\n if (!currentOwner) return\n if (currentOwner.owned === null) currentOwner.owned = []\n currentOwner.owned.push(node)\n}\n\n// ---------------------------------------------------------------------------\n// Effect scheduling\n// ---------------------------------------------------------------------------\n\nfunction flushEffects(): void {\n if (flushing) return\n flushing = true\n // Isolate each effect: one throwing effect must not abort the flush or strand\n // the effects queued after it (each effect also self-recovers to CLEAN via\n // updateIfNecessary's finally). Errors are collected and surfaced after drain.\n const errors: unknown[] = []\n try {\n let i = 0\n let iterations = 0\n while (i < effectQueue.length) {\n if (++iterations > MAX_FLUSH_ITERATIONS) {\n effectQueue.length = 0\n throw new Error(\n 'MindeesNative: potential infinite reactive loop detected while flushing effects.',\n )\n }\n const e = effectQueue[i]\n i++\n if (e && e.state !== CLEAN && e.state !== DISPOSED) {\n // Defer when the effect's own lane is 'normal', OR it was staled inside a startTransition\n // (tagged for THIS drain only) — but only if a scheduler is injected. Otherwise sync.\n if (reactiveScheduler && (e.lane === 'normal' || transitionTagged.has(e))) {\n // Deferred lane: hand the flush to the scheduler under a stable key, so rapid re-stales\n // coalesce (latest wins) and the body runs later — reading the LATEST values at run time\n // (updateIfNecessary recomputes sources in order), so glitch-freedom is preserved.\n const node = e\n const sched = reactiveScheduler\n // A transition-tagged sync effect has no key yet; mint a stable one so its re-stales coalesce.\n if (node.schedKey === undefined) node.schedKey = `mindees:effect:${effectKeySeq++}`\n node.pendingTask = sched.schedule(\n () => {\n node.pendingTask = null\n if (node.state !== CLEAN && node.state !== DISPOSED) node.updateIfNecessary()\n },\n { priority: 'normal', key: node.schedKey },\n )\n } else {\n // Synchronous lane — byte-identical to the pre-scheduler behavior (and the path every\n // existing test + SSR takes, since `lane` is 'sync' and/or no scheduler is injected).\n try {\n e.updateIfNecessary()\n } catch (err) {\n errors.push(err)\n }\n }\n }\n }\n } finally {\n effectQueue.length = 0\n transitionTagged.clear() // transition tags apply only to the drain that observed them\n flushing = false\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) throw new AggregateError(errors, 'effect(s) threw during flush')\n}\n\nfunction attachNode<T, A extends object>(accessor: A, node: Computation<T>): A {\n ;(accessor as A & WithNode<T>)[NODE] = node\n return accessor\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A read accessor for a derived/reactive value. */\nexport type Accessor<T> = () => T\n\n/** Options accepted by {@link signal}. */\nexport interface SignalOptions<T> {\n /** Custom equality. `false` means \"always notify\" (every write propagates). */\n equals?: EqualsFn<T> | false\n}\n\n/** A writable reactive value: call to read; `.set`/`.update` to write. */\nexport interface Signal<T> {\n /** Read the value (tracks a dependency inside a computation). */\n (): T\n /** Replace the value; returns the new value. */\n set(value: T): T\n /** Update from the previous value; returns the new value. */\n update(fn: (prev: T) => T): T\n /** Read without tracking a dependency. */\n peek(): T\n}\n\n/** Options accepted by {@link computed}. */\nexport interface ComputedOptions<T> {\n /** Custom equality used to decide whether downstream observers re-run. */\n equals?: EqualsFn<T> | false\n}\n\n/** A memoized derived value: call to read; `.peek` to read without tracking. */\nexport interface Memo<T> {\n /** Read the (lazily recomputed) value, tracking a dependency. */\n (): T\n /** Read without tracking a dependency. */\n peek(): T\n}\n\n/**\n * Create a writable reactive value.\n *\n * @example\n * const count = signal(0)\n * count() // read → 0\n * count.set(1) // write\n * count.update(n => n + 1)\n */\nexport function signal<T>(value: T, options?: SignalOptions<T>): Signal<T> {\n const node = new Computation<T>(value, null, options?.equals ?? equalsDefault, false)\n const accessor = (() => node.read()) as Signal<T>\n accessor.set = (v: T) => node.write(v)\n accessor.update = (fn: (prev: T) => T) => node.write(fn(node.value))\n accessor.peek = () => node.value\n return attachNode(accessor, node)\n}\n\n/**\n * Create a memoized derived value. The function re-runs only when one of the\n * reactive values it reads has actually changed, and only when the result is\n * observed (lazy).\n *\n * @example\n * const doubled = computed(() => count() * 2)\n */\nexport function computed<T>(fn: () => T, options?: ComputedOptions<T>): Memo<T> {\n const node = new Computation<T>(undefined as T, fn, options?.equals ?? equalsDefault, false)\n adopt(node)\n const accessor = (() => node.read()) as Memo<T>\n accessor.peek = () => {\n node.updateIfNecessary()\n return node.value\n }\n return attachNode(accessor, node)\n}\n\n/** Alias of {@link computed}. */\nexport const memo = computed\n\n/**\n * Run a side effect that re-runs whenever its reactive dependencies change.\n * Runs once immediately to establish dependencies.\n *\n * To clean up before each re-run and on disposal, either return a cleanup\n * function from the effect, or call {@link onCleanup}. Any non-function return\n * value is ignored (so expression-bodied effects like `() => list.push(x())`\n * are fine).\n *\n * @returns A disposer that stops the effect and runs its cleanups.\n *\n * @example\n * const stop = effect(() => console.log(count()))\n * stop() // unsubscribe\n *\n * @example\n * effect(() => {\n * const id = setInterval(tick, 1000)\n * return () => clearInterval(id) // cleanup\n * })\n */\n/** Options for {@link effect}. */\nexport interface EffectOptions {\n /**\n * `'sync'` (default) flushes the effect inline on every change — the synchronous, glitch-free\n * behavior. `'normal'` defers the effect through the injected {@link setReactiveScheduler scheduler}\n * (interaction priority / deferred heavy work); with no scheduler it falls back to synchronous.\n */\n priority?: Priority\n}\n\nexport function effect(fn: () => void, options?: EffectOptions): () => void {\n const node = new Computation<void>(\n undefined,\n () => {\n const result: unknown = (fn as () => unknown)()\n if (typeof result === 'function') onCleanup(result as () => void)\n },\n false,\n true,\n )\n // Opt into the deferred lane. `effect(fn)` and `effect(fn, { priority: 'sync' })` are identical to\n // before (synchronous flush). `'normal'` only defers once a scheduler is injected.\n if (options?.priority === 'normal') {\n node.lane = 'normal'\n // ALWAYS a unique per-node key: this coalesces THIS effect's own rapid re-stales (latest wins)\n // without ever sharing a scheduler entry with another effect (which would cross-cancel them).\n node.schedKey = `mindees:effect:${effectKeySeq++}`\n }\n adopt(node)\n node.updateIfNecessary() // first run is ALWAYS synchronous (establishes deps + initial paint)\n return () => disposeComputation(node)\n}\n\n/**\n * Apply the writes in `fn` as a low-priority **transition**: the writes take effect immediately\n * (reads inside/after see the latest values), but the effects they invalidate are **deferred**\n * through the injected {@link setReactiveScheduler scheduler} instead of running synchronously — so a\n * heavy re-render triggered by, say, a keystroke doesn't block the interaction. With no scheduler\n * injected, this is a plain synchronous batch (no deferral) — safe for SSR/tests.\n *\n * @example\n * input.set(value) // urgent: the field updates now\n * startTransition(() => query.set(value)) // the expensive results re-render can lag\n */\nexport function startTransition(fn: () => void): void {\n transitionDepth++\n try {\n batch(fn) // coalesce the transition's writes into one flush; effects staled in it get tagged\n } finally {\n transitionDepth--\n }\n}\n\n/**\n * A **deferred view** of `source`: it tracks `source` on the scheduler's low-priority lane, so under\n * sustained updates it lags behind the live value (the React `useDeferredValue` pattern — show stale\n * results while the latest are computed). With no scheduler injected it mirrors `source`\n * synchronously (no lag), so SSR/tests see the live value. Must be created inside an owner.\n */\nexport function deferred<T>(source: Accessor<T>): Accessor<T> {\n const out = signal(source())\n effect(() => out.set(source()), { priority: 'normal' })\n return () => out()\n}\n\n/**\n * Batch multiple writes so dependent effects run once, after the batch. Reads\n * inside a batch still observe the latest written values synchronously.\n */\nexport function batch<T>(fn: () => T): T {\n if (batchDepth > 0) return fn()\n batchDepth++\n try {\n return fn()\n } finally {\n batchDepth--\n flushEffects()\n }\n}\n\n/** Read reactive values without subscribing the current computation to them. */\nexport function untrack<T>(fn: () => T): T {\n const prev = currentObserver\n currentObserver = null\n try {\n return fn()\n } finally {\n currentObserver = prev\n }\n}\n\n/**\n * Make a computation react to an EXPLICIT dependency only — the body runs untracked, so signals it\n * reads don't subscribe; only `deps` does. Wrap it in `effect`/`memo`: `effect(on(() => a(), (av) => …))`\n * re-runs when `a` changes but not when other signals the body reads change. The callback receives the\n * current dep value, the previous dep value, and its own previous return. With `{ defer: true }` the\n * first run is skipped (deps are still tracked) — react only to *changes*, not the initial value.\n *\n * @example effect(on(() => userId(), (id) => { void load(id) }, { defer: true }))\n */\nexport function on<S, U>(\n deps: Accessor<S>,\n fn: (input: S, prevInput: S | undefined, prev: U | undefined) => U,\n options?: { defer?: boolean },\n): () => U | undefined {\n let defer = options?.defer ?? false\n let prevInput: S | undefined\n let prevResult: U | undefined\n return () => {\n const input = deps() // the ONLY tracked read\n if (defer) {\n defer = false\n prevInput = input\n return undefined\n }\n prevResult = untrack(() => fn(input, prevInput, prevResult))\n prevInput = input\n return prevResult\n }\n}\n\n/**\n * Register a cleanup to run before the owning computation re-runs and when it\n * is disposed. No-op outside a reactive scope.\n */\nexport function onCleanup(fn: () => void): void {\n if (!currentOwner) return\n if (currentOwner.cleanups === null) currentOwner.cleanups = []\n currentOwner.cleanups.push(fn)\n}\n\n/**\n * Create a non-tracked root scope that owns everything created within it. The\n * scope lives until the provided `dispose` function is called.\n *\n * @example\n * const dispose = createRoot((dispose) => {\n * effect(() => console.log(count()))\n * return dispose\n * })\n * dispose() // tear down the effect\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const root: OwnerNode = { owned: null, cleanups: null }\n const prevObserver = currentObserver\n const prevOwner = currentOwner\n currentObserver = null\n currentOwner = root\n try {\n return fn(() => disposeChildren(root))\n } finally {\n currentObserver = prevObserver\n currentOwner = prevOwner\n }\n}\n\n/** The current owner scope, or `null` outside any reactive scope. */\nexport function getOwner(): Owner | null {\n // OwnerNode → the nominal public Owner. The brand is phantom (never present at\n // runtime), so the only honest way to produce an Owner is through this cast.\n return currentOwner as unknown as Owner | null\n}\n\n/** Run `fn` with `owner` as the active scope (e.g. to re-attach cleanups). */\nexport function runWithOwner<T>(owner: Owner | null, fn: () => T): T {\n const prev = currentOwner\n // `owner` is an opaque public handle; internally it is always an OwnerNode we\n // handed out via getOwner(). This is the one trusted boundary cast.\n currentOwner = owner as unknown as OwnerNode | null\n try {\n return fn()\n } finally {\n currentOwner = prev\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal test helpers (exported from this module only — NOT from the package\n// public entry point). Used to assert white-box invariants like leak-freedom.\n// ---------------------------------------------------------------------------\n\n/** @internal Number of live observers subscribed to the node behind `accessor`. */\nexport function _observerCount(accessor: object): number {\n const node = (accessor as Partial<WithNode<unknown>>)[NODE]\n return node?.observers?.length ?? 0\n}\n\n/** @internal Number of sources the node behind `accessor` depends on. */\nexport function _sourceCount(accessor: object): number {\n const node = (accessor as Partial<WithNode<unknown>>)[NODE]\n return node?.sources?.length ?? 0\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/core",
3
- "version": "0.16.0",
3
+ "version": "0.19.0",
4
4
  "description": "MindeesNative core — fine-grained reactivity (signals/computed/effect/batch), component model with selector-isolated context, priority scheduler, and a thread-pool abstraction (Web Worker + inline; native is a research track).",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",