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

@shiit/watchdog

v1.0.4

Published

The dog that hunts — a generic Oban-style durable job engine for Cloudflare Durable Objects

Readme

@shiit/watchdog

Beware of dog! Let it crash, the dog will guard the yard.

A durable job engine for Cloudflare Durable Objects. Oban for DOs. One class, a dozen methods, zero dependencies.

What it's for

Work that has to outlive the request. You hand the dog a job, it keeps the books: queued, executing, settled. If your DO gets evicted mid-job (and it will, eventually), the dog notices and you decide what happens next. Nothing just disappears.

The job row is the job. payload is opaque: the engine stores it, returns it on claim, hands it back on settle, and never reads it. Not even in the sweep. Your jobs, your meaning.

Let's go

npm i @shiit/watchdog

Make a dog in your Durable Object:

import { Watchdog } from "@shiit/watchdog";

export class MyDO {
  dog: Watchdog;

  constructor(state: DurableObjectState) {
    this.dog = new Watchdog({
      storage: state.storage,
      getNodeId: () => state.id.toString(),
      requestWake: () => state.storage.setAlarm(Date.now() + 1000),
      isAlive: async (job) => this.isJobRunning(job),
      onClaimed: async (job) => this.runJob(job),
      onSettled: async (job, outcome) => this.recordIt(job, outcome),
    });
  }

  async alarm() {
    await this.dog.tick();
  }
}

That's the whole setup. Now the lifecycle:

// Put work in the yard
await dog.ingest({
  jobId: "email-123",
  queue: "emails",
  payload: { to: "[email protected]" },
});

// Pull work out (when you're ready to run it)
const job = await dog.claim("emails"); // null if the cap is busy or the queue is empty

// Finish it
await dog.complete(job.jobId, { outcome: "completed" });

And the alarm keeps everything moving: tick() sweeps stale work and claims queued work for idle queues. Point your DO's alarm() at it and forget it exists.

Things worth knowing

Retry is opt-in. Default is never. Every job system ships a silent default of "run it again." Double-sent emails are born there. So the dog asks at the door: what happens if this runs twice? No retry declaration, no re-drive. A crash settles the job interrupted and your onSettled hears about it. Loud over silent. If you want retries, say so: ingest({ ..., retry: { maxAttempts: 10 } }) and only genuinely transient errors (429s, 5xx, network hiccups) will re-queue with backoff. Permanent errors settle immediately even then.

The sweep asks, never guesses. Before the dog settles a job that looks dead, it calls your isAlive(job). You define alive. For long-running work, read your worker's state. (Our agent harness answers two questions: is the session still streaming, and has it done anything in the last 30 minutes. A live-but-silent run past that bound is a wedge, and it gets buried. Everything else gets left alone.) No clocks that convict. Timers schedule looks; verdicts come from the owner or from boot evidence.

Quiet jobs stay warm. DOs unload when they go quiet, and a long job with a quiet stretch would die mid-run. So the dog arms a 30-second alarm while a job executes. Each wake keeps the isolate warm and runs the sweep. complete disarms it. No executing row, no wake: the alarm is recomputed from state every time and never develops its own momentum.

Priority is the only ordering knob. Higher priority first, then oldest first. That's the whole ordering story. Our agent product's convention, if you want one: 0 interactive work, -1 cron, 10 mid-run steers.

The whole surface

dog.ingest({ jobId, queue, payload, priority?, retry? });
dog.claim(queue);                    // -> JobRow | null
dog.complete(jobId, { outcome, result? });
dog.tick();
dog.abort(queue);                    // settles the executing job as cancelled
dog.purge(queue);
dog.queuePosition(queue);
dog.nextWakeNeeded(now);             // for your alarm scheduling

One DO, one engine, one executing job by default (maxConcurrency if you want more).

What's next

Want agent mail on top? @shiit/bird-dog: this engine plus the pigeon (the mail layer), composed. The dog finds the letters even in the hand of a dead pigeon.

Part of the shiit stack. MIT.