npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xaendar/signals

v0.6.4

Published

A library implementing a reactive system for Xaendar framework. This signal implementation is based on the TC39 Stage1 proposal for reactive primitives.

Readme

@xaendar/signals

A complete implementation of the TC39 Signals proposal — reactive primitives (State, Computed, Watcher) plus a high-level effect helper, exposed as the Signal global namespace.

npm version license


Installation

npm install @xaendar/signals

Overview

| Primitive | Description | |-----------|-------------| | Signal.State | Mutable reactive value — the source of truth | | Signal.Computed | Lazy derived value — recomputed only when stale and read | | Signal.subtle.Watcher | Low-level push observer — notified synchronously on change | | effect(fn) | High-level helper — re-runs fn on every dependency change | | loadSignals() | Bootstraps the Signal global — call once at application startup |

Granular updates — signals form a fine-grained reactive graph. When a State value changes, only the Computed nodes and Watchers that transitively depend on that exact signal are marked stale or notified. Every other node in the graph is left completely untouched, making updates O(changed signals) rather than O(application size).


Initialization

Call loadSignals() once before using any signal primitive. It installs the Signal namespace on globalThis.

import { loadSignals } from '@xaendar/signals';

loadSignals();                      // production
loadSignals({ devMode: true });     // enables additional runtime checks

Signal.State

The fundamental mutable reactive value.

const count = new Signal.State(0);

count.get();        // 0 — registers as a dependency if inside a Computed
count.set(1);       // propagates change to all dependents
count.set(1);       // no-op — Object.is(1, 1) === true, no propagation

Options

const price = new Signal.State(9.99, {
  // Custom equality — prevent propagation when change is negligible
  equals(oldVal, newVal) {
    return Math.abs(oldVal - newVal) < 0.001;
  },
  // Called when the first Watcher/Computed subscribes to this signal
  watched() {
    console.log('price is now observed');
  },
  // Called when the last subscriber unsubscribes
  unwatched() {
    console.log('price is no longer observed');
  },
});

Signal.Computed

A lazy, cached derived value. The callback is executed only when:

  1. The computed value is explicitly read via .get(), and
  2. At least one of its dependencies has changed since the last evaluation.

Between reads, the cached result is reused with zero cost.

const firstName = new Signal.State('Ada');
const lastName  = new Signal.State('Lovelace');

const fullName = new Signal.Computed(() =>
  `${firstName.get()} ${lastName.get()}`
);

fullName.get(); // 'Ada Lovelace'   — computed and cached

lastName.set('Byron');
fullName.get(); // 'Ada Byron'      — recomputed (lastName changed)
fullName.get(); // 'Ada Byron'      — served from cache

Dependencies are tracked dynamically: if a branch is not entered during an evaluation, signals inside that branch are not tracked.

const showTitle = new Signal.State(false);
const title     = new Signal.State('Dr.');

const label = new Signal.Computed(() =>
  showTitle.get() ? `${title.get()} ${firstName.get()}` : firstName.get()
);
// While showTitle is false, title is NOT a dependency of label.

effect(fn)

Runs a side-effectful function and automatically re-runs it whenever any signal read inside it changes. Re-execution is scheduled as a microtask, so multiple synchronous signal writes are batched into a single re-run.

Returns a disposer that permanently stops the effect and releases all subscriptions.

import { effect } from '@xaendar/signals';

const count = new Signal.State(0);

const stop = effect(() => {
  console.log('count is', count.get());
});
// → logs: "count is 0"  (runs synchronously on creation)

count.set(1); // → microtask logs: "count is 1"
count.set(2); // → microtask logs: "count is 2"

stop();       // disposer — unsubscribes everything
count.set(3); // → silent

Signal.subtle.Watcher

The low-level primitive used by frameworks to implement scheduling. The notify callback fires synchronously the first time a watched dependency changes after each watch() call.

const sig = new Signal.State(0);

const watcher = new Signal.subtle.Watcher(() => {
  // Called synchronously when sig (or any watched computed) changes.
  // No signal reads or writes are allowed here.
  console.log('something changed — schedule a re-read');
  queueMicrotask(() => {
    watcher.getPending().forEach(s => s.get()); // pull new value
    watcher.watch();                             // re-arm
  });
});

const derived = new Signal.Computed(() => sig.get() * 2);
watcher.watch(derived);
derived.get(); // initial evaluation

sig.set(5); // → "something changed — schedule a re-read"

Signal.subtle utilities

| Function | Description | |----------|-------------| | untrack(fn) | Executes fn without registering any dependency | | currentComputed() | Returns the Computed currently being evaluated, or null | | introspectSources(node) | Lists the signals a Computed or Watcher depends on | | introspectSinks(node) | Lists the dependents of a State or Computed | | hasSources(node) | true if a Computed or Watcher has at least one source | | hasSinks(node) | true if a State or Computed has at least one sink |

const a = new Signal.State(1);
const b = new Signal.Computed(() => a.get() + 1);

// Read b without tracking it as a dependency
const value = Signal.subtle.untrack(() => b.get());

Signal.subtle.introspectSources(b); // [a]
Signal.subtle.introspectSinks(a);   // [b]
Signal.subtle.hasSinks(a);          // false — b is not yet watched

How the reactive graph works

Signal.State  ──────────►  Signal.Computed  ──────────►  Signal.subtle.Watcher
   (source)                   (derived)                      (observer)
     │                            │                               │
  .set(v)                    lazy .get()                    notify() callback
     │                            │                               │
     └── marks dependents stale ──┘     schedules microtask ──────┘
  1. State.set() marks all direct Computed dependents as dirty and all reachable Watchers as pending, invoking their notify callback synchronously.
  2. A Computed is only re-evaluated when .get() is called on a stale node — pull-based, not push-based.
  3. Watcher.notify is push-based and fires synchronously; the actual value read happens separately, in a microtask or scheduler tick.
  4. Signals with no active Watcher are not tracked and can be garbage-collected independently.

TypeScript

SignalOptions and SignalEqual are exported for use in custom signal subclasses.

import type { SignalOptions, SignalEqual } from '@xaendar/signals';

const myEquals: SignalEqual<number> = (a, b) => Math.abs(a - b) < 0.01;

const opts: SignalOptions<number> = {
  equals: myEquals,
  watched()   { /* ... */ },
  unwatched() { /* ... */ },
};

Related packages

| Package | Description | |---------|-------------| | @xaendar/core | Web Component base class, decorators, and InputSignal | | @xaendar/types | Shared TypeScript utility types | | @xaendar/compiler | Template compiler |


License

MIT © Kaitenjo