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

background-job

v1.0.1

Published

Managed background jobs for Node.js — suspension, disabling, and controlled execution loops.

Readme

background-job

npm version

Managed background jobs for Node.js. Provides suspension, disabling, and controlled execution loops for long-running worker processes.

Installation

npm i background-job

Ships a dual ESM/CJS build, so both forms are supported:

// ES Module
import {BackgroundJob, BackgroundJobRunner} from "background-job";

// CommonJS
const {BackgroundJob, BackgroundJobRunner} = require("background-job");

Usage

Subclass BackgroundJob for the work and BackgroundJobRunner to enumerate which jobs run.

Fixed worker pool

A set of identical workers that each loop with a delay — sized from config, backing off when there is nothing to do:

import {BackgroundJob, BackgroundJobRunner} from "background-job";

class QueueWorker extends BackgroundJob {
    async execute() {
        const item = await pullNextItem();
        if (!item) {
            return this.disable(); // stop and cool down; a later poll re-enables it
        }
        await processItem(item);
    }
}

class QueueRunner extends BackgroundJobRunner {
    async getExecutableJobs() {
        const workers = config.get("queue.workers", 3);
        return this.constructor.iteratingJobList(workers, QueueWorker, {delayMs: 1000});
    }
}

await new QueueRunner("queue").executeJobs(); // runs forever, polling for executable jobs

One job per entity

When the work is dynamic — one job per row from a data source — return the BackgroundJobDefinition objects directly. initParams carries per-job context into init(), and the lifecycle hooks set up and tear down resources:

import {BackgroundJob, BackgroundJobRunner} from "background-job";

class AccountJob extends BackgroundJob {
    init(account) {
        this.account = account;          // the definition's initParams
        this.api = createApiClient(account);
    }

    async execute() {
        await this.api.sync(this.account);
    }

    destroy() {
        this.api.close();                // called once when the job stops
    }
}

class AccountRunner extends BackgroundJobRunner {
    async getExecutableJobs() {
        const accounts = await loadActiveAccounts(); // dynamic, e.g. from a DB
        return accounts.map((account) => ({
            id: account.id,
            jobClass: AccountJob,
            initParams: account,
            delayMs: 2500,
        }));
    }
}

await new AccountRunner("account").executeJobs();

Returning a job's id keeps it running; omitting it on a later poll disables it (and triggers its destroy()). getExecutableJobs() is the single source of truth for what should be active.

How it works

executeJobs() runs a polling loop every pollIntervalMs. On each tick it calls getExecutableJobs(), lifts any expired suspensions, and starts every enabled job that is not already running. Each started job is jittered to avoid a thundering herd, then loops its own execute() with the configured delay until it is disabled.

API

BackgroundJob

Base class for an individual job. The runner instantiates it for you — subclass and override the hooks.

| Member | Description | |-------------------------------------|-------------------------------------------------------------------------------------------------| | async execute() | Abstract. The job's work; must be overridden or it throws. Called repeatedly while enabled. | | init(params) | Optional setup hook, called once before the loop starts. | | destroy() | Optional teardown hook, called once after the loop ends. | | isEnabled() | boolean — whether this job is still enabled in the runner. | | disable(suspendDurationMs = 5000) | Disables the job and suspends it for a cooldown; pass 0 to disable without suspending. | | runner / id / name | The owning runner, this job's id, and its derived name ("<base> #<id>"). |

BackgroundJobRunner

Orchestrator that owns enabled/running/suspended state per job id.

| Member | Description | |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------| | new BackgroundJobRunner(name = "BackgroundJob") | name is the base name jobs derive from; " Runner" is appended for the runner's own name. | | async getExecutableJobs() | Abstract. Return the BackgroundJobDefinition[] that should be running; must be overridden or it throws. | | async executeJobs(pollIntervalMs = 1000) | Starts the main loop. Runs indefinitely. | | isJobEnabled(id) / isJobRunning(id) | boolean state checks by id. | | disableJob(id) | Disables a job by id (stops its loop on the next iteration). | | suspendJob(id, durationMs) | Suspends a job by id for durationMs, preventing re-enable until it elapses. | | static iteratingJobList(count, jobClass, options) | Builds count identical definitions with sequential 1-based index ids. |

BackgroundJobDefinition

The object returned (per job) by getExecutableJobs():

| Field | Type | Description | |---------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| | id | string \| number | Unique job identifier. | | jobClass | typeof BackgroundJob | The BackgroundJob subclass to instantiate. | | initParams | * | Optional — passed to the job's init(). | | delayMs | number | Optional (default 0) — delay between executions in ms. | | isFixedRate | boolean | Optional (default false) — when true, execute time counts toward the delay (fixed-rate); otherwise the full delay is waited after each execute (fixed-delay). |

License

The MIT License (MIT). Please see License File for more information.