Files
WRNexusJSDoc/app/pages/packages/reactive.wrn
T

219 lines
17 KiB
Plaintext

page wrnexusreactive {
seo {
title = "@wrnexus/reactive"
description = "Small type-safe reactive signal primitives."
}
view {
<div class="docs-shell">
<a href="#main" class="skip-link">Skip to content</a>
<header class="topbar">
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.8.8</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
</header>
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
<main id="main" class="portal-main docs-layout docs-layout--navigation"><aside class="docs-section-menu" aria-label="Documentation sections"><nav>
<section><strong>Get started</strong><a href="/getting-started">Quick start</a><a href="/tutorial">Tutorial</a><a href="/guides/full-stack-auth-demo">Auth dashboard demo</a><a href="/guides/configuration-and-profiles">Configuration</a></section>
<section><strong>Build</strong><a href="/guides/pages-and-components">Pages and components</a><a href="/guides/routing">Routing</a><a href="/guides/server-data">Server data</a><a href="/guides/forms-and-validation">Forms and validation</a><a href="/guides/database">Database</a></section>
<section><strong>Auth and security</strong><a href="/guides/authentication">Authentication</a><a href="/guides/authorization">Authorization</a><a href="/guides/security">Application security</a><a href="/security">Security policy</a></section>
<section><strong>Reference</strong><a href="/packages">Packages overview</a><a href="/packages/cli">CLI</a><a href="/language">.wrn language</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/examples">Examples</a></section>
<section class="package-menu"><strong>Packages</strong><a href="/packages/ai">@wrnexus/ai</a><a href="/packages/auth">@wrnexus/auth</a><a href="/packages/authz">@wrnexus/authz</a><a href="/packages/benchmark">@wrnexus/benchmark</a><a href="/packages/cache">@wrnexus/cache</a><a href="/packages/captcha">@wrnexus/captcha</a><a href="/packages/cli">@wrnexus/cli</a><a href="/packages/compiler">@wrnexus/compiler</a><a href="/packages/content">@wrnexus/content</a><a href="/packages/core">@wrnexus/core</a><a href="/packages/csr">@wrnexus/csr</a><a href="/packages/db">@wrnexus/db</a><a href="/packages/dev-server">@wrnexus/dev-server</a><a href="/packages/dev-toolbar">@wrnexus/dev-toolbar</a><a href="/packages/encryption">@wrnexus/encryption</a><a href="/packages/graphql">@wrnexus/graphql</a><a href="/packages/helpers">@wrnexus/helpers</a><a href="/packages/i18n">@wrnexus/i18n</a><a href="/packages/identity">@wrnexus/identity</a><a href="/packages/image">@wrnexus/image</a><a href="/packages/jwt">@wrnexus/jwt</a><a href="/packages/language-server">@wrnexus/language-server</a><a href="/packages/mcp">@wrnexus/mcp</a><a href="/packages/mobile">@wrnexus/mobile</a><a href="/packages/native">@wrnexus/native</a><a href="/packages/oauth">@wrnexus/oauth</a><a href="/packages/observability">@wrnexus/observability</a><a href="/packages/playground">@wrnexus/playground</a><a href="/packages/plugin">@wrnexus/plugin</a><a href="/packages/pubsub">@wrnexus/pubsub</a><a href="/packages/pwa">@wrnexus/pwa</a><a href="/packages/queue">@wrnexus/queue</a><a href="/packages/reactive">@wrnexus/reactive</a><a href="/packages/realtime">@wrnexus/realtime</a><a href="/packages/router">@wrnexus/router</a><a href="/packages/security">@wrnexus/security</a><a href="/packages/ssr">@wrnexus/ssr</a><a href="/packages/store">@wrnexus/store</a><a href="/packages/styles">@wrnexus/styles</a><a href="/packages/syntax">@wrnexus/syntax</a><a href="/packages/test">@wrnexus/test</a><a href="/packages/tracking">@wrnexus/tracking</a><a href="/packages/typecheck">@wrnexus/typecheck</a><a href="/packages/ui">@wrnexus/ui</a><a href="/packages/uploader">@wrnexus/uploader</a><a href="/packages/validation">@wrnexus/validation</a></section>
</nav></aside><div class="docs-reading-column">
<article class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/reactive</span></nav><section class="doc-intro"><span class="eyebrow">Frontend · Package reference</span><h1>@wrnexus/reactive</h1><p>Small type-safe reactive signal primitives.</p><div class="doc-meta"><span>v0.8.8</span><span>Private registry</span><span>Frontend</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/reactive@0.8.8</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><blockquote>Tiny, type-safe reactive primitives (signals) with zero dependencies.</blockquote>
<p>Part of the <strong>WRNexusJS</strong> framework — an SSR-first, Bun-native full-stack web framework.</p>
<h3 id="overview">Overview</h3>
<p><code>@wrnexus/reactive</code> is the seed of WRNexusJS's reactivity layer: a minimal <code>signal</code> 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 <code>.wrn</code> compiler's <code>state</code> blocks) can build reactive bindings on top of it. Reach for it when you need observable state without pulling in a full reactivity library.</p>
<pre data-language="bash"><code>bun add @wrnexus/reactive</code></pre>
<blockquote>Private package — the machine must be authenticated to the <code>wrnexus</code> npm org</blockquote>
<blockquote>(a read token in <code>~/.npmrc</code>). Requires <strong>Bun</strong> (Node is not supported).</blockquote>
<h3 id="api">API</h3>
<p>The package has a single entry point (<code>.</code>) exporting one function and three types.</p>
<h4 id="signal-t-initial-t-signal-t"><code>signal&lt;T&gt;(initial: T): Signal&lt;T&gt;</code></h4>
<p>Creates a reactive signal seeded with <code>initial</code>. Returns a <code>Signal&lt;T&gt;</code>:</p>
<div class="table-wrap"><table>
<thead><tr><th>Member</th><th>Signature</th><th>Description</th></tr></thead>
<tbody><tr><td><code>get</code></td><td><code>(): T</code></td><td>Read the current value.</td></tr><tr><td><code>set</code></td><td><code>(next: T): void</code></td><td>Write a new value. Subscribers run <strong>only when the value actually changes</strong> (compared with <code>Object.is</code>).</td></tr><tr><td><code>update</code></td><td><code>(fn: (current: T) =&gt; T): void</code></td><td>Apply a function to the current value; equivalent to <code>set(fn(get()))</code>.</td></tr><tr><td><code>subscribe</code></td><td><code>(fn: Subscriber&lt;T&gt;): Unsubscribe</code></td><td>Register a subscriber; returns a function that removes it.</td></tr></tbody></table></div>
<h4 id="types">Types</h4>
<pre data-language="ts"><code>type Subscriber&lt;T&gt; = (value: T) =&gt; void;
type Unsubscribe = () =&gt; void;
interface Signal&lt;T&gt; &#123;
get(): T;
set(next: T): void;
update(fn: (current: T) =&gt; T): void;
subscribe(fn: Subscriber&lt;T&gt;): Unsubscribe;
&#125;</code></pre>
<p>Notes on semantics:</p>
<ul>
<li><strong>No-op updates are skipped.</strong> <code>set</code> compares the incoming value to the current</li>
<p>one with <code>Object.is</code>; identical values do not notify subscribers.</p>
<li><strong>Safe unsubscribe during notification.</strong> Subscribers are iterated over a copy of</li>
<p>the subscriber set, so a subscriber may call its own (or another's) unsubscribe while a notification is in flight.</p>
</ul>
<h3 id="usage">Usage</h3>
<pre data-language="ts"><code>import &#123; signal &#125; from &quot;@wrnexus/reactive&quot;;
const count = signal(0);
count.get(); // 0
// Subscribe; the returned function unsubscribes.
const off = count.subscribe((value) =&gt; &#123;
console.log(&quot;count is now&quot;, value);
&#125;);
count.set(1); // logs: count is now 1
count.set(1); // no-op — value unchanged, no notification
count.update((n) =&gt; n + 1); // logs: count is now 2
off(); // stop listening
count.set(3); // nothing logged</code></pre>
<p>Typed signals infer <code>T</code> from the initial value, or can be annotated explicitly:</p>
<pre data-language="ts"><code>import &#123; signal, type Signal &#125; from &quot;@wrnexus/reactive&quot;;
const user: Signal&lt;&#123; name: string &#125; | null&gt; = signal(null);
user.set(&#123; name: &quot;Ada&quot; &#125;);</code></pre>
<h3 id="requirements-notes">Requirements / Notes</h3>
<ul>
<li><strong>Bun-only.</strong> Distributed as TypeScript source (<code>main</code>/<code>exports</code> point at</li>
<p><code>src/index.ts</code>); consume it under Bun, which runs <code>.ts</code> directly.</p>
<li><strong>Zero dependencies.</strong> The only runtime API used is the standard <code>Object.is</code>.</li>
<li>Foundational primitive for WRNexusJS client islands and the forthcoming <code>.wrn</code></li>
<p>compiler <code>state</code> blocks.</p>
</ul></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>/**
* Fine-grained reactive primitives shared by server utilities and client code.
* Updates are synchronous by default and coalesced inside `batch()`.
*/
type Subscriber&lt;T&gt; = (value: T, previous?: T) =&gt; void;
type Unsubscribe = () =&gt; void;
type Cleanup = () =&gt; void;
interface Signal&lt;T&gt; &#123;
get(): T;
set(next: T): void;
update(fn: (current: T) =&gt; T): void;
subscribe(fn: Subscriber&lt;T&gt;): Unsubscribe;
&#125;
interface ReadonlySignal&lt;T&gt; &#123;
get(): T;
subscribe(fn: Subscriber&lt;T&gt;): Unsubscribe;
&#125;
/** Coalesce every signal notification made by `fn` into one flush. */
declare function batch&lt;T&gt;(fn: () =&gt; T): T;
/** Read reactive values without recording dependencies. */
declare function untrack&lt;T&gt;(fn: () =&gt; T): T;
declare function signal&lt;T&gt;(initial: T): Signal&lt;T&gt;;
/**
* Run a dependency-tracked side effect. Dependencies are rebuilt after every
* execution, preventing stale subscriptions when conditional reads change.
*/
declare function effect(run: () =&gt; void | Cleanup): Cleanup;
/** Create a lazily readable derived signal with automatic dependency tracking. */
declare function computed&lt;T&gt;(read: () =&gt; T): ReadonlySignal&lt;T&gt;;
interface WatchOptions&lt;T&gt; &#123;
immediate?: boolean;
equals?: (left: T, right: T) =&gt; boolean;
&#125;
declare function watch&lt;T&gt;(read: () =&gt; T, listener: (value: T, previous: T | undefined) =&gt; void | Cleanup, options?: WatchOptions&lt;T&gt;): Cleanup;
type ResourceStatus = &quot;idle&quot; | &quot;pending&quot; | &quot;success&quot; | &quot;error&quot;;
interface Resource&lt;T&gt; &#123;
data: ReadonlySignal&lt;T | undefined&gt;;
error: ReadonlySignal&lt;unknown&gt;;
status: ReadonlySignal&lt;ResourceStatus&gt;;
loading: ReadonlySignal&lt;boolean&gt;;
run(): Promise&lt;T | undefined&gt;;
abort(reason?: unknown): void;
reset(): void;
&#125;
interface ResourceOptions&lt;T&gt; &#123;
initial?: T;
immediate?: boolean;
keepPrevious?: boolean;
&#125;
declare function resource&lt;T&gt;(loader: (signal: AbortSignal) =&gt; Promise&lt;T&gt;, options?: ResourceOptions&lt;T&gt;): Resource&lt;T&gt;;
interface ReactiveScope &#123;
add(cleanup: Cleanup): Cleanup;
dispose(): void;
readonly disposed: boolean;
&#125;
declare function createScope(): ReactiveScope;
interface HistorySignal&lt;T&gt; extends Signal&lt;T&gt; &#123;
undo(): boolean;
redo(): boolean;
canUndo(): boolean;
canRedo(): boolean;
clearHistory(): void;
&#125;
declare function historySignal&lt;T&gt;(initial: T, options?: &#123;
limit?: number;
equals?: (left: T, right: T) =&gt; boolean;
&#125;): HistorySignal&lt;T&gt;;
interface UrlStateOptions&lt;T&gt; &#123;
url?: URL;
parameter: string;
parse?: (value: string | null) =&gt; T;
serialize?: (value: T) =&gt; string | null;
replace?: (url: URL) =&gt; void;
&#125;
declare function urlSignal&lt;T&gt;(initial: T, options: UrlStateOptions&lt;T&gt;): Signal&lt;T&gt;;
interface ReactiveContext&lt;T&gt; &#123;
provide&lt;R&gt;(value: T, run: () =&gt; R): R;
use(): T;
&#125;
declare function createContextProvider&lt;T&gt;(defaultValue?: T): ReactiveContext&lt;T&gt;;
declare function mountPortal(content: Node | string, target: Element): () =&gt; void;
declare function transition(update: () =&gt; void, options?: &#123;
className?: string;
target?: Element;
durationMs?: number;
signal?: AbortSignal;
&#125;): Promise&lt;void&gt;;
interface TimelineStep &#123;
durationMs: number;
delayMs?: number;
easing?: (progress: number) =&gt; number;
update(progress: number): void;
&#125;
interface AnimationTimeline &#123;
play(options?: &#123;
reverse?: boolean;
signal?: AbortSignal;
&#125;): Promise&lt;void&gt;;
cancel(reason?: unknown): void;
readonly running: boolean;
&#125;
declare function createTimeline(steps: TimelineStep[], options?: &#123;
now?: () =&gt; number;
frame?: (callback: () =&gt; void) =&gt; unknown;
&#125;): AnimationTimeline;
export &#123; type AnimationTimeline, type Cleanup, type HistorySignal, type ReactiveContext, type ReactiveScope, type ReadonlySignal, type Resource, type ResourceOptions, type ResourceStatus, type Signal, type Subscriber, type TimelineStep, type Unsubscribe, type UrlStateOptions, type WatchOptions, batch, computed, createContextProvider, createScope, createTimeline, effect, historySignal, mountPortal, resource, signal, transition, untrack, urlSignal, watch &#125;;
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>Typical usage</h3><pre data-language="ts"><code>import &#123; signal &#125; from &quot;@wrnexus/reactive&quot;;
const count = signal(0);
count.get(); // 0
// Subscribe; the returned function unsubscribes.
const off = count.subscribe((value) =&gt; &#123;
console.log(&quot;count is now&quot;, value);
&#125;);
count.set(1); // logs: count is now 1
count.set(1); // no-op — value unchanged, no notification
count.update((n) =&gt; n + 1); // logs: count is now 2
off(); // stop listening
count.set(3); // nothing logged</code></pre></article><article class="example-card"><h3>Typed signals infer T from the initial value, or can be annotated explicitly</h3><pre data-language="ts"><code>import &#123; signal, type Signal &#125; from &quot;@wrnexus/reactive&quot;;
const user: Signal&lt;&#123; name: string &#125; | null&gt; = signal(null);
user.set(&#123; name: &quot;Ada&quot; &#125;);</code></pre></article></div></section></article></div>
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#overview">Overview</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-4" href="#signal-t-initial-t-signal-t">signal&lt;T&gt;(initial: T): Signal&lt;T&gt;</a><a class="toc-level-4" href="#types">Types</a><a class="toc-level-3" href="#usage">Usage</a><a class="toc-level-3" href="#requirements-notes">Requirements / Notes</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
</main>
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.8.8</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
</div>
}
}