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

@profullstack/watchdog

v0.2.0

Published

Notice when a dependency has stopped answering the client your process uses, and restart instead of hanging. Probes the SHARED pool or connection, races the timeout so a promise that never settles is caught, and exits after N consecutive failures. Zero de

Readme

@profullstack/watchdog

Notice when a dependency has stopped answering the client your process uses, and restart instead of hanging.

Zero dependencies. No build step. One module.

npm install @profullstack/watchdog

The failure it exists for

A web process can be wedged and look perfectly healthy. It has happened to us three times on two sites:

  • 2026-09-07, Postgres. Every page hung forever. The container was up, the database was healthy, the platform reported the service Online. The process had run about 29 hours and its connection pool had lost every slot. A query queued for a connection that was never going to arrive, and the pool had no queue deadline, so the request never failed and never answered.

  • 2026-09-13 and 2026-09-22, Redis. Same shape. The shared ioredis client is built with maxRetriesPerRequest: null because BullMQ requires it, and that turns a disconnect into a hang rather than a rejection. Code written as "a Redis blip must not take the site down" catches nothing, because there is no error to catch:

    try {
      const hit = await redis.get(key); // never returns
    } catch {
      // never runs
    }

Every time, /healthz answered in milliseconds, because a health endpoint that touches no dependency is measuring the wrong thing. Restarting the datastore did not help either: the wedge is on the client side of the socket, so only replacing the process recovered it. Every time, a person had to notice first.

Two rules

The probe goes through the same client the requests use. During the Postgres outage a freshly opened pool inside that same container ran a real query in 51ms. A watchdog on its own connection would have reported everything fine throughout.

The timeout is a race, not a driver option. The symptom is a promise that never settles, so awaiting the command alone hangs the watchdog in exactly the case it exists for.

Use

Most services want the same two, so there is one call for it:

import { watchDependencies } from '@profullstack/watchdog';

const watchdogs = watchDependencies({
  postgres: () => healthcheck(),   // resolve truthy, or it counts as a failure
  redis: () => connection.ping(),  // must answer PONG
});

process.on('SIGTERM', async () => {
  watchdogs.stop(); // FIRST, or a clean drain looks like a wedge
  await drain();
});

That reads DB_WATCHDOG_INTERVAL_MS, DB_WATCHDOG_TIMEOUT_MS, DB_WATCHDOG_FAILURES and the REDIS_WATCHDOG_* equivalents, all with working defaults, so a service needs no new variables. Redis is allowed one more failure than the pool, because a healthy Redis is routinely unreachable for a minute or two while it reloads its snapshot from disk, and a watchdog that trips during a normal restart is worse than no watchdog at all.

Pass the probes rather than the clients: this package stays zero-dependency and never needs to know whether you are on bun:sql, pg or ioredis.

Anything else

import { startWatchdogs } from '@profullstack/watchdog';

const watchdogs = startWatchdogs([
  {
    subject: 'the database pool',
    probe: async () => {
      if (!(await healthcheck())) throw new Error('select 1 did not come back');
    },
  },
  {
    subject: 'redis',
    // Redis often restarts by reading a snapshot before it accepts anything, so
    // it earns a longer rope than the pool does.
    failures: 4,
    probe: async () => {
      if ((await connection.ping()) !== 'PONG') throw new Error('PING did not come back');
    },
  },
]);

process.on('SIGTERM', async () => {
  // First, before you close the clients: a clean drain must not look like a wedge.
  watchdogs.stop();
  await drain();
});

startWatchdog(options) is the single one. startWatchdogs(specs, shared) applies shared under each spec, so common timings live in one place and a spec overrides what it needs.

Options

| option | default | what it is | | --- | --- | --- | | probe | required | runs a trivial command on the shared client; throw to report trouble | | subject | 'the dependency' | names the client in the log and the give-up reason | | intervalMs | 30000 | gap between probes | | timeoutMs | 10000 | how long one probe may take before it counts as a failure | | failures | 3 | consecutive failures before giving up | | onGiveUp | process.exit(1) | what to do when the client is declared gone | | log | console | needs error and warn; either may be missing |

The bar is consecutive failures, so a slow minute never costs a restart. The wedge this watches for does not recover on its own, so its count never clears.

check() runs one probe now and resolves true if the client answered. That is what the tests drive, and it is useful behind an admin route.

Why exiting is right

It reads as drastic for a web server. It is the cheapest correct move: the failure is process-local state that no request can repair, a restart demonstrably clears it, and a platform replaces the container in about a minute.

Hanging forever is not the safer option. It is the outage.

Note that a platform health check usually gates a new deploy and is never re-run, so nothing else is going to restart a wedged-but-alive container.

Licence

MIT