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

@ecopages/signals

v0.3.0-alpha.25

Published

Renderer-agnostic signal primitives based on the TC39 Signals proposal

Readme

Ecopages Signals

@ecopages/signals is a renderer-agnostic signals package that can be used standalone or underneath Radiant.

Its core model is based on the TC39 Signals proposal, with a smaller surface area and a few convenience helpers for application code today.

The public entrypoint remains index.ts, while the implementation now lives in focused modules under src/ so the core runtime, effects, watcher support, and store logic can evolve independently.

Current Scope

This package currently provides:

  • State<T> for writable values
  • Computed<T> for lazily derived values
  • currentComputed() for advanced derived helpers that need the active computed context
  • effect(...) for reactive side effects with scheduled re-execution
  • watch(...) for observing derived values with previous-value access
  • untrack(...) and peek(...) for non-tracking reads
  • subtle.Watcher plus watched and unwatched hooks for low-level invalidation workflows
  • createStore(...) for deep reactive object and array state
  • isStore(...) for detecting signal-backed store proxies
  • snapshot(...) for materializing plain nested data
  • automatic dependency discovery during Computed evaluation
  • subscription support for renderer or framework adapters

Source Layout

The package is organized around a stable public barrel and smaller implementation files:

  • index.ts re-exports the public API and exposes subtle
  • src/types.ts defines the public contracts, options, and low-level symbols
  • src/state.ts implements writable State signals
  • src/computed.ts implements lazy derived Computed signals and active-computation helpers
  • src/effect.ts and src/watch.ts implement effect scheduling and derived-value observation
  • src/watcher.ts implements proposal-shaped low-level watchers
  • src/tracking.ts contains non-tracking read helpers
  • src/store.ts contains deep store proxying and snapshot materialization

Design Position

This package is renderer-agnostic.

  • It does not know about JSX.
  • It does not know about Radiant components.
  • It is meant to work both as a standalone package and underneath adapters in those packages.

TC39 Relationship

This package is based on the current TC39 Signals proposal and tracks the same broad model around:

  • State and Computed signal classes
  • lazy pull-based recomputation with cached values
  • automatic dependency discovery during computed evaluation
  • custom equality functions for writable and computed signals
  • untracked reads as an escape hatch

It is not a drop-in implementation of the current proposal draft.

It is best understood as proposal-aligned in its core semantics, but not yet fully API-compatible with the draft surface.

  • It exposes convenience helpers such as effect(...), watch(...), createStore(...), and snapshot(...) directly.
  • It currently exposes manual subscribe(...) hooks for adapter and library integration.
  • It exposes a proposal-shaped subtle.Watcher API, while still keeping the existing convenience helpers.
  • Its subtle.Watcher follows the proposal-style re-arm behavior, where calling watch(...) resets the pending set and notification latch for the next invalidation cycle.
  • It does not yet expose the full TC39 subtle introspection surface.

API Notes

  • subscribe(...) is intended for adapter-style push integration. Application-level derived work is usually better expressed with Computed, effect(...), or watch(...).
  • watch(...) is built on top of a computed signal plus an effect, so it inherits computed equality behavior and effect scheduling.
  • subtle.Watcher reports staleness rather than recalculated values. Calling watch(...) is both registration and reset.
  • createStore(...) wraps nested plain objects and arrays, while snapshot(...) detaches the current plain value graph for logging, serialization, or comparison.

Example

import { Computed, State, createStore, effect, watch } from '@ecopages/signals';

const count = new State(0);
const parity = new Computed(() => ((count.get() & 1) === 0 ? 'even' : 'odd'));
const store = createStore({ profile: { name: 'Ada' } });

const dispose = effect(() => {
	console.log(parity.get(), store.profile.name);
});

const stopWatching = watch(
	() => store.profile.name,
	(nextName, previousName) => {
		console.log(previousName, '->', nextName);
	},
);

count.set(1);
store.profile.name = 'Grace';
dispose();
stopWatching();

Limits

This implementation is still smaller than the current TC39 proposal draft.

  • no full TC39 Watcher and subtle semantics surface yet
  • no full proposal-style subtle introspection helpers yet
  • no batching or transaction model
  • no framework-owned disposal tree or component ownership integration yet

Those omissions are deliberate. The goal is to keep a small, useful standalone package while leaving room to align further as the proposal evolves.