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

@damatjs/jobs

v1.0.4

Published

Durable PostgreSQL background jobs and fenced workers for Damat apps

Readme

@damatjs/jobs

Durable PostgreSQL background jobs for Damat applications.

Jobs persist their run, retry policy, attempts, progress, structured logs, activity, cancellation, result, and errors. Workers use fenced leases and FOR UPDATE SKIP LOCKED, so several processes can safely poll the same queue.

Install and migrate

bun add @damatjs/jobs
damat-orm migrate:up

Framework applications configure projectConfig.databaseUrl and services.jobs. Redis is not required. Optional Redis wake-ups reduce polling latency without becoming the source of truth.

Define and enqueue

import { defineJob, enqueueJob } from "@damatjs/jobs";

declare module "@damatjs/jobs" {
  interface JobMap {
    "send-welcome": { userId: string };
  }
}

defineJob(
  "send-welcome",
  async ({ userId }, context) => {
    await context.progress(25, { phase: "render" });
    await context.log("info", "Sending welcome", { userId });
    await mailer.sendWelcome(userId);
    return { sent: true };
  },
  { queue: "mail", maxAttempts: 5, backoffMs: 2_000 },
);

const run = await enqueueJob(
  "send-welcome",
  { userId: "u1" },
  {
    priority: 10,
    metadata: { source: "signup" },
  },
);

enqueueJob may receive an active durability transaction executor so the domain write and enqueue commit together. Without one it uses the configured durability client. PostgreSQL calculates relative enqueue delays from its own clock, so immediate jobs remain claimable when an application host's wall clock differs slightly from the database.

Schedule jobs

import { createJobSchedule, listJobSchedules } from "@damatjs/jobs";

await createJobSchedule({
  name: "daily-cleanup",
  jobName: "cleanup",
  payload: {},
  schedule: { kind: "interval", everyMs: 86_400_000 },
});

const schedules = await listJobSchedules({ enabled: true });

One-time and fixed-interval schedules are supported. Cron is rejected. Each occurrence and the schedule's next occurrence advance in one transaction. Unique schedule/time identities make overlapping reconcilers idempotent. Optional schedule deduplication suppresses runs until its TTL expires.

Run a worker

import { JobWorker } from "@damatjs/jobs";

const worker = new JobWorker({
  queue: "mail",
  concurrency: 4,
  pollIntervalMs: 5_000,
  registryHeartbeatIntervalMs: 30_000,
});

worker.start();
await worker.stop({ graceMs: 30_000 });

Framework apps normally use:

projectConfig: {
  databaseUrl: process.env.DATABASE_URL,
},
services: {
  jobs: { queue: "mail", concurrency: 4 },
},
runtime: {
  mode: "worker", // or "all" to serve HTTP too
  workers: ["jobs"],
},

The framework defaults to runtime.mode: "all" and selects enabled durable capabilities, so the runtime block can be omitted when one process serves HTTP and jobs. A dedicated worker deployment can instead set DAMAT_RUNTIME_MODE=worker and DAMAT_WORKER_TYPES=jobs without changing the application build.

The worker starts after modules register their definitions and after PostgreSQL migration readiness succeeds. Calling start() again while that worker is running is idempotent. A worker instance is one-shot: after stop begins, another start() throws synchronously; construct a new worker to run again. stop() stops new claims, marks the worker as stopping, waits up to the grace period, then aborts unfinished handler signals and stops renewing their leases. Shutdown and terminal transitions wait for an in-flight renewal to settle, so no heartbeat can extend a lease after either boundary returns. The registry remains stopping while handler code is still running and changes to stopped only after it settles and PostgreSQL persists that transition. Persistence failures reject stop() and a later stop() retries them. Registry heartbeats and reconciliation remain active through the graceful drain phase, then quiesce before the worker persists its final stopped transition.

The worker also owns bounded lease, retry, schedule, idempotency, and retention reconciliation. Expired leases, not worker-registry state, decide recovery. Recovery activity retains the expired worker and lease token. An immediately reclaimed run records that recovery to queued, then records a separate claim for the new worker and token. Framework-managed workers use Redis wake-ups while healthy and perform a PostgreSQL safety scan every 30 seconds. If Redis is missing or unauthorized, the shared coordinator falls back to PostgreSQL discovery within five seconds. Standalone workers default to five-second PostgreSQL polling.

Configure enqueue-side wake-ups with configureJobWakeupPublisher(redis) and pass a Redis-compatible client as wakeupRedis to a worker. The worker uses a dedicated duplicated subscriber. Missing Redis, malformed messages, and publish failures only reduce responsiveness to the polling interval. Subscriber connection errors are handled through structured warnings rather than escaping as raw unhandled Redis events. Mutations inside a caller-owned transaction do not publish directly. They still write a transactional acceleration-outbox row; the framework relay publishes it only after the outer transaction commits.

Worker options are validated when the worker is constructed. Queue and supplied worker IDs must be non-empty, concurrency and log limits must be positive integers, timing values must be finite and positive, and the progress interval may also be zero. The job heartbeat must be shorter than its lease. Registry heartbeats default to durable snapshots every 30 seconds and are capped at 120 seconds. Framework Redis liveness expires after 10 seconds; inspection treats PostgreSQL snapshots as stale after 90 seconds by default. Concurrency is capped at PostgreSQL's signed 32-bit maximum; timers are capped at 2,147,483,647 ms; log limits must be safe integers. stop({ graceMs }) applies the same timer ceiling and accepts zero for an immediate abort.

Delivery semantics

  • Delivery is at least once; handlers should make side effects idempotent.
  • Claims, attempts, and activity are created in one transaction.
  • Every heartbeat and terminal transition matches run, worker, lease token, and lease expiry. A stale worker cannot finish reclaimed work.
  • Failures use persisted exponential backoff and become dead letters after the final attempt. An invalid or overflowing retry date dead-letters rather than retaining a stuck lease. Unknown definitions dead-letter immediately.
  • Queued work cancels immediately. Running cancellation reaches the handler through context.signal; completion also rechecks the request atomically.
  • Handler results must be JSON-safe. Invalid results fail visibly through the normal retry/dead-letter path.
  • Terminal activity includes the latest persisted progress snapshot. Structured log byte limits use the same PostgreSQL jsonb representation that is stored.

Inspection and administration

Pipeline-created jobs carry internal _damatPipeline ownership metadata. Their fenced terminal transaction writes a pipeline acceleration-outbox signal in the same commit, so the graph router cannot observe a completion that PostgreSQL did not commit. Ordinary job retention excludes these runs: pipeline retention deletes the owning node records, complete child tree, and backing jobs together. The framework integration hook lives at @damatjs/jobs/pipeline-integration; applications should use pipeline clients rather than replacing that hook.

The package exposes a typed headless client rather than unauthenticated routes:

import { createJobInspectionClient } from "@damatjs/jobs";

const jobs = createJobInspectionClient({
  cursorSigningKey: process.env.INSPECTION_CURSOR_KEY!,
  visibility: "metadata",
  redaction: { keys: ["token", "password"] },
});

const page = await jobs.listRuns({
  views: ["processing", "failed"],
  limit: 50,
});
const detail = await jobs.getRun(page.items[0]!.id);

Lists use signed timestamp-and-UUID cursors. Native statuses remain unchanged; upcoming, processing, retrying, failed, and completed are derived views, while recovery is an independent flag. Detail reads use one repeatable-read snapshot and include attempts, activity, logs, leases, workers, queue controls, and schedule history. Visibility defaults to metadata; payload and result access requires full. Queue-control history returns at most 500 entries and sets controlHistoryTruncated when more records exist.

Operational summaries measure queue wait from availableAt, so intentional schedule delay is excluded, and attribute the sample to its half-open attempt-start window. Every immutable attempt captures availableAt and waitMs atomically from the run's current availability, preserving retry waits. Legacy attempts keep unknown timing omitted and are excluded from distributions rather than treated as zero. Capacity includes active workers only; active and stale counts plus heartbeat diagnostics exclude stopping and stopped workers. Active and stale per-worker records expose capabilities, state, concurrency, in-flight load, and heartbeat age; stopped and stopping history is excluded. Application and deployment metadata follow inspection visibility and redaction. Throughput rows are grouped by bucket, queue, and job name. Configured redaction also applies to grouped failure messages.

Bounded summaries expose current status, wait, lease, worker, and dead-letter state plus half-open time-window throughput, activity, and duration metrics. Administrative cancellation, retry, queue pause/resume, schedule enable/disable, and retention require a validated actor and append immutable audit history. Applications decide how to authenticate and present these records.

Retention defaults to 90 days and accepts "forever". PostgreSQL keeps the complete inspection timeline; Redis stores only rebuildable ready identifiers, worker liveness, and invalidation signals.

Public definition API

JobMap, defineJob, getJobDefinition, getAllJobDefinitions, and clearJobDefinitions use one process-wide registry. Raw Redis queue access is not part of the jobs API.

License

MIT