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

@boring-stack-pkg/eslint-plugin-bullmq

v0.1.3

Published

ESLint plugin enforcing operational-safety rules for BullMQ projects.

Readme

eslint-plugin-bullmq

npm source

ESLint plugin enforcing operational-safety rules for BullMQ projects.

Why

BullMQ is fast, durable, and unopinionated — which means most production failure modes live in code patterns that the framework happily accepts. A worker without a close method abandons in-flight jobs on deploy. A worker without a failed listener swallows every error silently. A queue without removeOnComplete fills Redis. Job retries without backoff fire back-to-back. A concurrency: 0 boots a worker that processes nothing.

These seven rules pin those patterns down at lint time so they fail in PR review instead of on a 3 a.m. page.

Install

pnpm add -D @boring-stack-pkg/eslint-plugin-bullmq @typescript-eslint/parser

Usage (flat config)

// eslint.config.mjs
import tsParser from "@typescript-eslint/parser";
import bullmq from "@boring-stack-pkg/eslint-plugin-bullmq";

export default [
  {
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { ecmaVersion: "latest", sourceType: "module" },
    },
    plugins: { bullmq },
    rules: bullmq.configs.recommended.rules,
  },
];

The recommended preset enables all seven rules at "error".

Rules

| Rule | Category | Description | | -------------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------- | | worker-must-implement-close | Lifecycle | Classes that own a new Worker(...) must declare close() (or alias) for graceful shutdown. | | worker-must-listen-failed | Visibility | Every Worker must register .on("failed", ...) so failures aren't silent. | | job-name-must-be-constant | Convention | <queue>.add(name, ...) job names must be identifiers, not inline string literals. | | queue-options-must-set-removeoncomplete | Retention | removeOnComplete must be configured per-call or via defaultJobOptions. | | queue-options-must-set-removeonfail | Retention | removeOnFail must be configured per-call or via defaultJobOptions. | | job-options-must-set-attempts | Resilience | attempts must be configured; when attempts > 1, backoff is also required. | | no-blocking-concurrency-zero | Correctness | Disallow new Worker(..., { concurrency: <numericLiteral ≤ 0> }). |

Examples

worker-must-implement-close

// ❌
export class JobService {
  private worker = new Worker("queue", async () => {});
}

// ✅
export class JobService {
  private worker = new Worker("queue", async () => {});
  async close() {
    await this.worker.close();
  }
}

worker-must-listen-failed

// ❌
const worker = new Worker("queue", async () => {});

// ✅
const worker = new Worker("queue", async () => {});
worker.on("failed", (job, err) => logger.error({ id: job?.id, err }));

job-options-must-set-attempts

// ❌
const emailQueue = new Queue("email");
emailQueue.add(SEND_EMAIL, { to: "x" }, {});

// ✅  (queue-level defaults apply to every add())
const emailQueue = new Queue("email", {
  defaultJobOptions: {
    removeOnComplete: 1000,
    removeOnFail: 5000,
    attempts: 5,
    backoff: { type: "exponential", delay: 1000 },
  },
});

For full per-rule docs and ❌/✅ snippets, see docs/rules/ and the runnable examples/.

Operational rationale

Each rule maps to a real production failure mode:

| Rule | Failure mode it prevents | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | worker-must-implement-close | Connection leaks on deploy; jobs in flight are abandoned mid-execution. | | worker-must-listen-failed | Silent task drops — failures don't show up in logs / metrics / alerts. | | job-name-must-be-constant | Drift between producers, workers, and dashboards when a string is renamed in only one place. | | queue-options-must-set-removeoncomplete / removeonfail | Redis OOM as completed/failed jobs accumulate forever. | | job-options-must-set-attempts | No retries on transient failures; or retries that fire so fast they exhaust the budget on identical errors. | | no-blocking-concurrency-zero | Worker boots, listens, processes nothing. Often the symptom of a missing config default. |

Limitations of static analysis

  • Cross-file queue/worker tracking is out of scope. A queue defined in one module and used in another can't have its defaultJobOptions consulted from the call site.
  • Queue identification falls back to the Queue$ name suffix. A non-BullMQ object whose variable name happens to end in Queue will be treated as one. Tighten via queueNamePattern if needed.
  • Listeners attached via helpers (e.g., attachStandardListeners(worker)) are invisible to the rule. Subscribe inline.
  • worker-must-implement-close only checks classes — module-level new Worker(...) instances need their cleanup wired into process.on("SIGTERM") directly (see examples/valid/standalone-worker.ts).

Development

pnpm install
pnpm test
pnpm typecheck
pnpm build

Release

Tag v* locally and push the tag — .github/workflows/release.yml runs pnpm publish --access public with NPM_TOKEN.

License

MIT.