Small, framework-independent signals for TypeScript.
signal provides a small reactive core with explicit ownership, lazy memos, synchronous effects and async resources. Its API is heavily inspired by SolidJS while staying usable as a standalone library in browsers and Node.js, without framework lifecycles.
Observable interop is available through @kayahr/observable.
Scope management and ownership boundaries are provided by @kayahr/scope.
untracktoSignal, toObservable and toSubscribernpm install @kayahr/signal
Use signals directly when no explicit ownership boundary is needed:
import { createEffect, createMemo, createSignal } from "@kayahr/signal";
const [ count, setCount ] = createSignal(0);
const doubled = createMemo(() => count() * 2);
createEffect(() => {
console.log(`count=${count()} doubled=${doubled()}`);
});
setCount(1);
setCount(previous => previous + 1);
Use a scope when you want explicit ownership and later cleanup for a whole reactive subgraph:
import { createScope } from "@kayahr/scope";
import { ResourceStatus, createEffect, createMemo, createResource, createSignal } from "@kayahr/signal";
const scope = createScope();
const { setCount, setUserId } = scope.run(() => {
const [ count, setCount ] = createSignal(0);
const doubled = createMemo(() => count() * 2);
const [ userId, setUserId ] = createSignal(1);
const [ user, resource ] = createResource(userId, async (id, abortSignal) => {
const response = await fetch(`/api/users/${id}`, { signal: abortSignal });
return response.json() as Promise<{ name: string }>;
});
createEffect(() => {
console.log(`count=${count()} doubled=${doubled()}`);
if (resource.status() === ResourceStatus.Ready) {
console.log(user()?.name);
}
});
return { setCount, setUserId };
});
setCount(1);
setCount(previous => previous + 1);
setUserId(2);
// ...
scope.dispose();