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

@cotera/watchtower

v0.1.2

Published

Observable values for React: derivation, polling, event-driven refresh, optimistic writes

Readme

@cotera/watchtower

Observable values for React — the base layer of WatchTower.

A Watchable<T> is a value you can read synchronously, subscribe to, and derive from. The variants each add one concern: polling, persistence, a per-key record, or a derived value a user can override.

This package stands alone. The layers built on it ship separately: @cotera/watchtower-events for the event bus and event-driven refresh, @cotera/watchtower-models for viewmodels, @cotera/watchtower-actions for actions, and @cotera/watchtower-query for TanStack Query bindings.

Install

bun add @cotera/watchtower
bun add jotai react   # peers

Reads inside Watchable.from register as dependencies, so a derived value recomputes when any source changes.

import { Watchable, useWatchableValue } from '@cotera/watchtower';

const firstName = Watchable.fromValue('Ada');
const lastName = Watchable.fromValue('Lovelace');

const fullName = Watchable.from((get) => `${get(firstName)} ${get(lastName)}`);

fullName.snapshot(); // 'Ada Lovelace'
firstName.set('Grace');
fullName.snapshot(); // 'Grace Lovelace'

const unsubscribe = fullName.subscribe((name) => console.log(name));

In a component, useWatchableValue subscribes and re-renders on change:

function Greeting() {
  const name = useWatchableValue(fullName);
  return <h1>Hello {name}</h1>;
}

Mutation methods live on Watchable; Watchable.from returns a ReadonlyWatchable<T>, which exposes only snapshot, subscribe, map, and asAtom. That distinction is the API's way of saying a derived value has no setter.

set runs the configured updater and skips the write when equalityFn says the value is unchanged. setFromSource skips the updater — use it when syncing from an external source of truth that is already up to date (a URL, say) so you do not write straight back to it.

The variants

Each one is a Watchable with one extra concern handled for you.

PollingWatchable — refetches on an interval, and can stop itself.

const status = PollingWatchable.create(async () => fetchStatus(), {
  intervalMs: 2_000,
  initialValue: 'pending',
  stopWhen: (value) => value === 'complete',
});

status.restart();
status.unsubscribe();

EventWatchable and TwoWayEventWatchable — refetch when a named event arrives on a push stream, with optimistic writes on top. They live in @cotera/watchtower-events, which owns the event bus they listen on, so an application with no WebSocket never takes that code.

MixedSourceWatchable — derived, but a user's set overrides the derived value until the sources change again. shouldAcceptDerived decides whether an incoming derived value is allowed to discard the override, which is how you keep a user's in-progress edit from being clobbered by a slightly older server push.

PersistentWatchable — reads its initial value from a storage adapter and writes back on every set.

WatchableRecord — a record whose keys are each their own watchable, so a component can subscribe to one field without re-rendering on the others.

StalenessWatchable — compares lastUpdateTime across a value and its dependencies, and reports 'stale' | 'ok'.

The event bus

Values kept live by a WebSocket or SSE stream are the job of @cotera/watchtower-events. It carries the process-wide bus your transport emits into, EventWatchable for refreshing off it, and the fallback polling that takes over while the stream is down. Nothing in this package imports it.

TanStack Query

Bindings live in @cotera/watchtower-queryQueryWatchable mirrors a query cache entry into a watchable, and QueryClientEventWatchable invalidates cache prefixes when an event arrives. They are a separate package so @tanstack/react-query is never a dependency of this one.

Development

bun install       # from the workspace root
bun run test:run
bun run typecheck