API reference
Exports of @sigx/reactivity v0.13.0.
All examples assume a named import from the package root:
import { signal, computed, effect } from '@sigx/reactivity';
Functions
signal
Create a reactive signal. Primitives are wrapped in a { value } box accessed via .value; objects and arrays become deeply reactive proxies you mutate directly, plus a $set method to replace the whole value.
export function signal<T extends Primitive>(target: T): PrimitiveSignal<T>;
export function signal<T extends object>(target: T): Signal<T>;
target— the initial value. APrimitivereturns aPrimitiveSignal<T>; anobjectreturns aSignal<T>(deep reactive proxy).- Returns a
PrimitiveSignal<T>orSignal<T>depending on the input.
signal is idempotent: passing an already-reactive proxy (or a raw object that already has a proxy) returns the existing proxy. Exotic built-ins such as Date and RegExp are returned unwrapped and are not reactive.
computed
Create a lazy, cached, reactive derived value. Pass a getter for a read-only Computed<T>, or a { get, set } options object for a WritableComputed<T>.
export function computed<T>(getter: ComputedGetter<T>): Computed<T>;
export function computed<T>(options: WritableComputedOptions<T>): WritableComputed<T>;
getter— a function returning the derived value; produces a read-onlyComputed<T>.options— aWritableComputedOptions<T>({ get, set }); produces aWritableComputed<T>.- Returns a computed signal read (and, when writable, written) via
.value.
Recomputes only on access after a dependency changed. Reads are tracked like any other signal.
effect
Run a side-effect function immediately and re-run it whenever its tracked reactive dependencies change.
export function effect(fn: EffectFn): EffectRunner;
fn— the effect body; runs once immediately and on every dependency change.- Returns an
EffectRunner— a callable that re-runs the effect, with a.stop()method to dispose it.
Effects have a re-entrancy guard: a synchronous self-trigger during a run is ignored rather than recursing.
batch
Batch multiple reactive updates so dependent effects flush only once after the function completes.
export function batch(fn: () => void): void;
fn— a function containing the writes to batch.- Returns nothing.
Nestable via an internal depth counter; only the outermost batch flushes pending effects.
untrack
Run fn while suppressing dependency tracking, so reactive reads inside it do not create subscriptions.
export function untrack<T>(fn: () => T): T;
fn— a function to run without tracking its reactive reads.- Returns the result of
fn.
effectScope
Create a scope object to run reactive work and dispose it in bulk.
export function effectScope(detached?: boolean): {
run<T>(fn: () => T): T | undefined;
stop(fromParent?: boolean): void;
};
detached— whentrue, the scope is not collected by (and not stopped with) a surrounding parent scope.- Returns an object with
run(fn)(executesfnwithin the scope, returning its result orundefinedif the scope is stopped) andstop()(disposes everything created insiderun()).
stop() disposes the effects and watchers created inside run(). Nested scopes are stopped with their parent unless created detached (effectScope(true)).
onScopeDispose
Register an arbitrary teardown callback with the currently-active scope — the component setup being collected, or the innermost running effectScope(). It uses the same registration path effect() and watch() use for their own disposers, exposed for non-reactive resources (event listeners, timers, observers) whose lifetime must match the owning scope's.
export function onScopeDispose(fn: () => void): boolean;
fn— a disposer to run when the active scope stops.- Returns
trueif a scope captured the disposer, orfalse(without retainingfn) when no scope is active — the caller owns the teardown itself in that case.
function useIntervalFn(fn: () => void, ms: number) {
const id = setInterval(fn, ms);
onScopeDispose(() => clearInterval(id));
}
const tick = () => console.log('tick');
const scope = effectScope();
scope.run(() => useIntervalFn(tick, 1000));
scope.stop(); // interval cleared
This is the primitive that composables build teardown on: a use* helper registers its own cleanup with whatever scope is active — no disposer argument to thread through. The boolean return reports whether a scope actually captured it, so a helper meant to also work outside a scope can check it and fall back to returning a manual disposer.
watch
Watch a reactive source and invoke a callback when it changes.
export function watch<T>(
source: WatchSource<T>,
cb: WatchCallback<T>,
options?: WatchOptions
): WatchHandle;
source— a getter function or a value (WatchSource<T>).cb— aWatchCallback<T>receiving(newValue, oldValue, onCleanup).options— optionalWatchOptions:immediate,deep(boolean or depth number), andonce.- Returns a
WatchHandlethat is itself callable to stop, plusstop,pause, andresumemethods.
immediate fires the callback once at setup with oldValue undefined. While paused, changes are buffered and the latest is delivered on resume if it differs from the old value (Object.is). onCleanup runs before each subsequent callback and on stop.
toRaw
Return the original raw object underlying a reactive proxy.
export function toRaw<T>(observed: T): T;
observed— a reactive proxy (or any value).- Returns the underlying raw object, recursively unwrapped; returns the value unchanged if it is not a proxy.
toSignal
Create a signal-shaped view over one property of a reactive object. Unlike destructuring (which snapshots the value), the returned object reads and writes through to the source, so tracking and triggering work.
export function toSignal<T extends object, K extends SignalKey<T>>(
source: T,
key: K
): PropertySignal<T[K]>;
source— a reactive object (e.g. fromsignal({...})).key— the property to view. Any string key except$set.- Returns a
PropertySignal<T[K]>— a{ value }box delegating to the source.
const state = signal({ count: 0 });
const count = toSignal(state, 'count');
count.value++; // triggers effects watching state.count
Writes go through Reflect.set, so a rejected write (read-only property, refusing proxy) throws instead of failing silently.
toSignals
Create signal-shaped views for every own enumerable property of a reactive object, so it can be destructured without losing reactivity.
export function toSignals<T extends object>(source: T): ToSignals<T>;
source— a reactive object.- Returns a
ToSignals<T>— onePropertySignalper own enumerable string key ($setexcluded).
const state = signal({ count: 0, name: 'Ada' });
const { count, name } = toSignals(state);
count.value++; // still reactive
isReactive
Check whether a value is a reactive proxy created by signal().
export function isReactive(value: unknown): boolean;
value— the value to test.- Returns
trueif it is a reactive proxy.
isComputed
Type guard for computed signals.
export function isComputed(value: unknown): value is Computed<unknown>;
value— the value to test.- Returns
trueif the value carries the internalComputedSymbol, narrowing it toComputed<unknown>.
isSignal
Check whether a value is a signal-shaped { value } handle: a primitive-wrapper signal from signal(primitive), or a property view from toSignal / toSignals. Orthogonal to isReactive and isComputed — it matches the { value } box shape, not object proxies or computeds.
export function isSignal(value: unknown): value is { value: unknown };
value— the value to test.- Returns
truefor primitive-wrapper and property-view signals, narrowing to{ value: unknown };falsefor object signals (useisReactive), computeds (useisComputed), and unbranded plain{ value }objects.
const state = signal({ x: 1 });
isSignal(signal(0)); // true — primitive-wrapper signal
isSignal(toSignal(state, 'x')); // true — property view
isSignal(state); // false — object signal (use isReactive)
isSignal({ value: 1 }); // false — plain object
detectAccess
Run a selector while observing property accesses and return the last [target, key] pair read.
export function detectAccess(selector: () => any): [any, string | symbol] | null;
selector— a function whose property accesses are observed.- Returns the last
[target, key]pair read, ornullif none.
Used internally by the model / two-way-binding system; computed getters suspend the observer to prevent leakage.
Constants
ComputedSymbol
export const ComputedSymbol: unique symbol = Symbol('computed');
Unique symbol used to brand computed signals. It is the key behind isComputed and the Computed / WritableComputed interfaces.
Types
Primitive
export type Primitive = string | number | boolean | symbol | bigint | null | undefined;
Union of primitive value types that signal() wraps in a { value: T } box.
Widen
export type Widen<T> =
T extends boolean ? boolean :
T extends number ? number :
T extends string ? string :
T extends bigint ? bigint :
T extends symbol ? symbol :
T;
Widens literal types to their base primitive (e.g. false to boolean, 'hello' to string). Applied to the value of a PrimitiveSignal.
PrimitiveSignal
export type PrimitiveSignal<T> = { value: Widen<T> };
Type of a primitive signal: a { value } box whose value is widened to its base primitive type. No $set.
Signal
export type Signal<T> = T & {
$set: (newValue: T) => void;
};
Type of an object/array signal: the original shape T plus a $set(newValue) method for replacing the whole object reactively.
PropertySignal
export type PropertySignal<T> = { value: T };
A signal-shaped live view over a single property of a reactive object, returned by toSignal. Reads and writes delegate to the source, so reactivity is preserved.
ToSignals
export type ToSignals<T extends object> = {
[K in keyof T as K extends SignalKey<T> ? K : never]: PropertySignal<T[K]>;
};
Return type of toSignals — per-key PropertySignal views. SignalKey<T> is the (non-exported) constraint behind both functions: string keys of T excluding $set. At runtime, views exist only for keys Object.keys yields (own enumerable string keys).
EffectFn
export type EffectFn = () => void;
Signature of an effect body: a zero-argument function returning void.
EffectScope
export type EffectScope = {
run<T>(fn: () => T): T | undefined;
stop(fromParent?: boolean): void;
};
Shape of an effect scope. Note that effectScope() returns a structurally-identical inline object literal rather than being annotated with this named type.
Widen, Signal, and PrimitiveSignal together
signal() has two overloads. For Primitive inputs it returns PrimitiveSignal<T> — read/write through .value, no $set, with literal types widened. For object inputs it returns Signal<T> — a deep reactive proxy where you mutate properties directly and call $set() to replace the whole object.
WatchSource
export type WatchSource<T = any> = T | (() => T);
A watch source: either a raw value or a getter function that returns the watched value.
WatchCallback
export type WatchCallback<V = any, OV = any> =
(value: V, oldValue: OV, onCleanup: (fn: () => void) => void) => any;
Watch callback receiving the new value, the old value, and an onCleanup registrar invoked before the next run and on stop.
Interfaces
EffectRunner
export interface EffectRunner<T = void> {
(): T;
stop: () => void;
}
Callable returned by effect(). Invoking it re-runs the effect; .stop() disposes it and cleans up its dependencies.
Subscriber
export interface Subscriber extends EffectFn {
deps: Set<Subscriber>[];
}
Internal reactive subscriber: an effect function carrying its list of dependency sets. Exported as a type but primarily an implementation detail.
WatchOptions
export interface WatchOptions<Immediate = boolean> {
immediate?: Immediate;
deep?: boolean | number;
once?: boolean;
}
Options for watch(). immediate fires the callback on setup; deep enables deep traversal (true for Infinity depth, a number for limited depth); once stops after the first callback.
WatchHandle
export interface WatchHandle {
(): void; // callable to stop
stop: () => void;
pause: () => void;
resume: () => void;
}
Handle returned by watch(): callable to stop, plus stop/pause/resume methods. While paused, changes are buffered and the latest is delivered on resume if it differs from the old value.
Computed
export interface Computed<T> {
readonly value: T;
readonly [ComputedSymbol]: true;
}
Read-only computed signal. Access the derived value via the readonly .value getter.
WritableComputed
export interface WritableComputed<T> {
value: T;
readonly [ComputedSymbol]: true;
}
Writable computed signal. .value is both readable (computed) and assignable (routes to the provided setter).
ComputedGetter
export interface ComputedGetter<T> {
(): T;
}
Getter function for a computed: returns the derived value of type T.
ComputedSetter
export interface ComputedSetter<T> {
(value: T): void;
}
Setter function for a writable computed: receives the assigned value.
WritableComputedOptions
export interface WritableComputedOptions<T> {
get: ComputedGetter<T>;
set: ComputedSetter<T>;
}
Options object passed to computed() to create a writable computed, supplying both get and set.
See also
- Usage guide — practical patterns and examples.
- Overview — package summary and installation.
