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-query

v0.1.2

Published

TanStack Query bindings for WatchTower watchables

Readme

@cotera/watchtower-query

TanStack Query bindings for @cotera/watchtower.

Two watchables that source their values from a QueryClient, kept in their own package so @tanstack/react-query is a dependency only of the applications that actually use them — the core library never imports it.

bun add @cotera/watchtower-query
# peers
bun add jotai @tanstack/react-query

QueryWatchable

Mirrors a query cache entry into a watchable, so data fetched by TanStack can be read, derived from, and subscribed to like any other watchable value — including from a model or an action, where there is no component to call useQuery in.

import { QueryWatchable } from '@cotera/watchtower-query';

const user = QueryWatchable.for(queryClient, { queryKey: ['user', id] });

user.snapshot(); // QueryObserverResult: { status, data, error, ... }
const name = user.map((result) => result.data?.name);

user.unsubscribe(); // releases the underlying QueryObserver

It is backed by a QueryObserver, so it follows the cache entry for as long as it is subscribed — refetches, invalidations, and background updates all land in the watchable. select and enabled are accepted alongside queryKey.

Because the value is the full QueryObserverResult, loading and error states come along with the data rather than being modelled separately. map down to what a given consumer needs.

QueryClientEventWatchable

An EventWatchable that clears TanStack cache prefixes before it refetches. This is the shape you want when a WebSocket tells you something changed and several cached slices are now suspect.

import { QueryClientEventWatchable } from '@cotera/watchtower-query';

const agents = QueryClientEventWatchable.for({
  queryClient,
  queryKeys: [['org', orgId, 'agents']],
  event: 'agent.updated',
  initialValue: [],
  fn: async () => queryClient.fetchQuery(agentsQuery),
});

Every option of EventWatchable applies here too — events, filter, debounceMs, reconcile, fallback, serializeRefreshes.

Invalidation is event-scoped by default. Prefixes are cleared only when the refresh came from an event or from a refresh(payload) call with an argument. Mount, a bare refresh(), and reconcile ticks all pass payload === undefined and skip invalidation, so those paths keep honouring staleTime instead of forcing a broad refetch. Set invalidatePrefixesOnEveryRefresh: true to invalidate on every refresh regardless.

Prefix shapes

One prefix is a plain query key. Several unrelated prefixes go in a nested array — the first element being an array is what distinguishes the two:

queryKeys: ['org', orgId, 'agents'];                       // one prefix
queryKeys: [['org', orgId, 'agents'], ['org', orgId, 'runs']]; // two

queryKeys also takes a function, which makes the prefixes reactive: it receives a get for reading other watchables, and the prefix list recomputes whenever those change.

queryKeys: (get) => [['org', get(currentOrg), 'agents']],

Call unsubscribe() when done; it releases the event subscription and any watchable the dynamic prefix resolver established.

Development

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