@j13b/state
Framework-agnostic reactive state primitives for TypeScript — Signals, Runners, and broadcast subscriptions.
Install
npm install @j13b/state
This package has no dependencies and works anywhere TypeScript runs — Node, workers, the browser, any framework. Using React? @j13b/react-state provides hooks (useSignalValue, useRunnerStatus, …) built on these primitives.
Quick start
import { Signal } from "@j13b/state";
const counter = new Signal(0);
const subscription = counter.subscribe((value) => {
console.log("counter is now", value);
});
counter.set(1); // logs: counter is now 1
counter.set(2); // logs: counter is now 2
subscription.unsubscribe();
Entry points
Both entry points are React-free and export the same primitives:
| Import path | Use when |
|---|---|
@j13b/state |
The default — everything the package offers. |
@j13b/state/core |
You want an explicit "core primitives" import path; alias of the root. |
import { Signal, Runner } from "@j13b/state";
// or, equivalently
import { Signal, Runner } from "@j13b/state/core";
React hooks live in the companion package @j13b/react-state.
What's in the box
Signal<T>— reactive value with weakly-held subscriptions,set/transform/wait/waitFor.DerivedSignal— values computed from one or more upstream signals.Runner— async-task lifecycle abstraction with status broadcasting.Broadcast/Subscription— the underlying interfaces that signals and runners implement.- Events —
Event,AsyncEvent,BaseEventfor fire-and-forget messaging. - Helpers —
PollAsync,WeakPromise,delay.
Subscription lifecycle
Subscriptions are tracked via WeakRef — if you don't keep a reference to the returned subscription, the garbage collector will eventually drop it and your callback will stop firing. Always store the subscription (in a variable, instance field, or hook ref) for as long as you want to receive updates.
// Wrong — subscription gets GC'd, callback stops working
counter.subscribe((v) => console.log(v));
// Right — held by `sub`, lives until you unsubscribe
const sub = counter.subscribe((v) => console.log(v));
sub.unsubscribe();
Scripts
npm run build # vite + dts → dist/
npm run check:types # tsc --noEmit
npm test # vitest watch
npm run test:run # vitest run (CI)
Origins
This package is a fork of @tcn/state (Copyright 2024 TCN). See NOTICE for upstream attribution.
License
Apache License 2.0 — see LICENSE.