3.6 KiB
@wrnexus/reactive
Tiny, type-safe reactive primitives (signals) with zero dependencies.
Part of the WrNexus framework — an SSR-first, Bun-native full-stack web framework.
Overview
@wrnexus/reactive is the seed of WrNexus's reactivity layer: a minimal signal
primitive that holds a value, notifies subscribers when it changes, and hands back
an unsubscribe function. It is deliberately small and framework-agnostic — it powers
nothing on its own, but is shaped so client islands (and later the .wrn compiler's
state blocks) can build reactive bindings on top of it. Reach for it when you need
observable state without pulling in a full reactivity library.
Installation
bun add @wrnexus/reactive
Private package — the machine must be authenticated to the
wrnexusnpm org (a read token in~/.npmrc). Requires Bun (Node is not supported).
API
The package has a single entry point (.) exporting one function and three types.
signal<T>(initial: T): Signal<T>
Creates a reactive signal seeded with initial. Returns a Signal<T>:
| Member | Signature | Description |
|---|---|---|
get |
(): T |
Read the current value. |
set |
(next: T): void |
Write a new value. Subscribers run only when the value actually changes (compared with Object.is). |
update |
(fn: (current: T) => T): void |
Apply a function to the current value; equivalent to set(fn(get())). |
subscribe |
(fn: Subscriber<T>): Unsubscribe |
Register a subscriber; returns a function that removes it. |
Types
type Subscriber<T> = (value: T) => void;
type Unsubscribe = () => void;
interface Signal<T> {
get(): T;
set(next: T): void;
update(fn: (current: T) => T): void;
subscribe(fn: Subscriber<T>): Unsubscribe;
}
Notes on semantics:
- No-op updates are skipped.
setcompares the incoming value to the current one withObject.is; identical values do not notify subscribers. - Safe unsubscribe during notification. Subscribers are iterated over a copy of the subscriber set, so a subscriber may call its own (or another's) unsubscribe while a notification is in flight.
Usage
import { signal } from "@wrnexus/reactive";
const count = signal(0);
count.get(); // 0
// Subscribe; the returned function unsubscribes.
const off = count.subscribe((value) => {
console.log("count is now", value);
});
count.set(1); // logs: count is now 1
count.set(1); // no-op — value unchanged, no notification
count.update((n) => n + 1); // logs: count is now 2
off(); // stop listening
count.set(3); // nothing logged
Typed signals infer T from the initial value, or can be annotated explicitly:
import { signal, type Signal } from "@wrnexus/reactive";
const user: Signal<{ name: string } | null> = signal(null);
user.set({ name: "Ada" });
Requirements / Notes
- Bun-only. Distributed as TypeScript source (
main/exportspoint atsrc/index.ts); consume it under Bun, which runs.tsdirectly. - Zero dependencies. The only runtime API used is the standard
Object.is. - Foundational primitive for WrNexus client islands and the forthcoming
.wrncompilerstateblocks.