@xmachines/play-signals
Documentation / @xmachines/play-signals
Canonical Signals substrate for XMachines with Stage 1 API isolation
@xmachines/play-signals re-exports Signal from signal-polyfill as the single import boundary for XMachines packages.
Why This Package Exists
- Keep the raw
SignalAPI as the canonical substrate surface. - Isolate Stage 1 proposal churn behind one package boundary.
- Preserve Play invariants: Signal-only reactivity, passive infrastructure, and event-only mutation paths.
This package does not add business behavior to signals. Adapters and renderers observe signals and forward events; they do not mutate business state directly.
Installation
npm install @xmachines/play-signalsCurrent Exports
Signal(re-export fromsignal-polyfill)- Type exports from
src/types.ts:SignalState,SignalComputed,SignalWatcher,SignalOptions,ComputedOptions,WatcherNotify
Quick Start
import { Signal } from "@xmachines/play-signals";
const count = new Signal.State(0);const doubled = new Signal.Computed(() => count.get() * 2);
const watcher = new Signal.subtle.Watcher(() => { queueMicrotask(() => { const pending = watcher.getPending(); for (const signal of pending) { signal.get(); } watcher.watch(...pending); });});
watcher.watch(doubled);doubled.get();
count.set(2);
const dispose = () => { watcher.unwatch(doubled);};
void dispose;Canonical Watcher Lifecycle
Use one lifecycle pattern everywhere (React, Vue, Solid, router bridges, helper wrappers):
notifycallback runs.- Schedule work with
queueMicrotask. - Drain
watcher.getPending(). - Perform reads/effects.
- Re-arm watcher with
watch()orwatch(...signals).
Watcher notifications are one-shot. If you do not re-arm, you will miss future updates.
Cleanup Contract
Always dispose explicitly. Do not rely on GC-only cleanup guidance.
- If you called
watch(...), callunwatch(...)in teardown. - Framework lifecycles (
useEffectcleanup,onUnmounted,onCleanup) must unwatch. - Bridge lifecycles (
disconnect,dispose) must unwatch and unsubscribe.
Optional Helper Direction
Raw Signal remains canonical. Helper APIs are optional, additive guidance for consistency:
watchSignals(signals, onChange, options)createSignalEffect(effect, options)toSubscribable(signal, options)
These helpers are intended to codify lifecycle-safe watcher scheduling and deterministic teardown. They do not replace direct Signal usage.
API Surface
Signal.State<T>: writable signal state (get,set)Signal.Computed<T>: lazy memoized derivationsSignal.subtle.Watcher: low-level watcher (watch,unwatch,getPending)
Architecture Notes
- Signal-Only Reactivity (INV-05): Signals are the reactive substrate.
- Passive Infrastructure (INV-04): Adapters and frameworks only observe/forward.
- Actor Authority (INV-01): Business validity and transitions stay in actors.
- Event-only mutation path: Signals are not a business mutation channel.
Resources
License
Copyright (c) 2016 Mikael Karon. All rights reserved.
This work is licensed under the terms of the MIT license.
For a copy, see https://opensource.org/licenses/MIT.
TC39 Signals Polyfill for XMachines Play Architecture
Provides fine-grained reactive state primitives based on the TC39 Signals proposal (Stage 1). This package isolates the TC39 polyfill to protect the codebase from Stage 1 API changes.
Architectural Context: Implements Signal-Only Reactivity (INV-05) by providing the reactive primitives that enable Actor-to-Infrastructure communication without subscriptions or event emitters. All state propagation in Play Architecture uses TC39 Signals for automatic dependency tracking and glitch-free updates.
Example
Basic Signal usage
import { Signal } from "@xmachines/play-signals";
// Create state signalconst count = new Signal.State(0);
// Create computed signalconst doubled = new Signal.Computed(() => count.get() * 2);
// Observe changesconst watcher = new Signal.subtle.Watcher(() => { console.log("Count:", count.get(), "Doubled:", doubled.get());});watcher.watch(count);
count.set(5); // Logs: Count: 5 Doubled: 10See
Remarks
Stage 1 Status: TC39 Signals is currently Stage 1 in the TC39 process. This package
uses the official signal-polyfill reference implementation to isolate the codebase
from potential API changes as the proposal evolves. All signal imports should go through
this package to maintain isolation.
Why Isolation: By re-exporting the polyfill through this dedicated package, we can update the polyfill version or adapt to API changes in one place without touching consuming packages. This architectural decision protects against Stage 1 API churn.