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

@ambicuity/shutdown-manager

v1.0.0

Published

Zero-drama shutdown orchestration for production Node.js HTTP services. Drain connections, stop new traffic, run cleanup hooks, and exit safely across Docker, Kubernetes, Express, Fastify, Koa, and native HTTP.

Readme

@ambicuity/shutdown-manager

Zero-drama shutdown orchestration for production Node.js HTTP services. Drain connections, stop new traffic, run cleanup hooks, and exit safely across Docker, Kubernetes, Express, Fastify, Koa, and native HTTP/HTTP2.

CI npm types runtime deps license Buy Me a Coffee

Author: Ritesh Rana — [email protected] Support development: buymeacoffee.com/ritesh.rana


Why?

server.close() does not actually drain your server. It stops accepting new connections and waits for existing ones to close on their own — which, with HTTP keep-alive, never happens until the client disconnects. Meanwhile Kubernetes already started routing new requests away three seconds ago, your Postgres pool is sitting on uncommitted work, and the SIGKILL clock is ticking. That is how rolling deploys drop requests.

@ambicuity/shutdown-manager orchestrates the full shutdown lifecycle:

  1. Flip readiness to false so load balancers stop sending new traffic.
  2. Optionally wait for the LB to react (preStopDelayMs).
  3. Refuse new connections, set Connection: close on idle keep-alives, finish in-flight requests.
  4. Run your cleanup hooks (Postgres, Redis, queues, telemetry flush) in priority order.
  5. Exit 0 if everything was clean, 1 if a critical resource failed or a timeout fired.

Install

npm install @ambicuity/shutdown-manager

Requires Node.js ≥ 18.17. Ships dual ESM + CJS with built-in TypeScript types.

Quickstart (TypeScript)

import express from 'express';
import { ShutdownManager } from '@ambicuity/shutdown-manager';

const app = express();
const server = app.listen(3000);

const shutdown = new ShutdownManager({ timeout: 15_000 })
  .attach(server)
  .register('postgres', () => pool.end(), { priority: 10, critical: true })
  .register('redis', () => redis.quit())
  .register('bullmq', () => worker.close());

app.get('/ready', (_req, res) =>
  shutdown.isReady() ? res.send('ok') : res.status(503).send('shutting down'),
);

That's it. SIGTERM and SIGINT are wired automatically. On signal, the manager walks the lifecycle and exits the process when it's done.

CommonJS

const { ShutdownManager } = require('@ambicuity/shutdown-manager');
const shutdown = new ShutdownManager().attach(server);

Lifecycle

  idle  ─►  preShutdown  ─►  draining  ─►  cleanup  ─►  done | failed
            (flip ready,     (server.close,  (run resources    (exit
             optional         drain sockets,  by priority,      0 or 1)
             preStop delay)   force on        critical=true
                              timeout)        ⇒ failed)

Every phase emits a typed event you can hook for logging or metrics.

API

new ShutdownManager(options?)

interface ShutdownManagerOptions {
  signals?: NodeJS.Signals[]; // default ['SIGTERM','SIGINT']
  timeout?: number; // total budget ms, default 30_000
  developmentMode?: boolean; // default: NODE_ENV === 'development'
  forceExit?: boolean; // default true
  logger?: Logger; // default: silent noop
  poll?: { intervalMs?: number }; // default 100
  autoStart?: boolean; // default true
}

Methods

| Method | Purpose | | ---------------------------- | -------------------------------------------------------------- | | .attach(server) | Track an http, https, http2, or http2.secure server | | .detach(server) | Stop tracking a server | | .register(name, fn, opts?) | Register a cleanup hook | | .unregister(name) | Remove a previously registered hook | | .kubernetes(opts) | Enable preStop delay + readiness flip | | .isReady() | false once shutdown begins (wire to your /ready endpoint) | | .isShuttingDown() | true while a shutdown is in progress | | .phase() | Current LifecyclePhase | | .trigger(reason?) | Manually run the shutdown lifecycle (returns ShutdownResult) | | .start() / .stop() | Attach / detach signal handlers | | .on(event, handler) | Typed event subscription |

Resource registry

shutdown.register(
  'postgres',
  async (signal) => {
    // signal is an AbortSignal — honor it for cooperative cancellation
    await pool.end();
  },
  {
    timeout: 5_000, // per-resource budget; default 10_000
    priority: 10, // lower runs first; default 0
    concurrency: 'parallel', // 'parallel' (default) or 'sequential' within a priority group
    critical: true, // a failure marks the whole shutdown as failed (exit 1)
  },
);

Events

shutdown.on('phase',            ({ name, durationMs }) => …);
shutdown.on('connection:closed', ({ remaining, secure }) => …);
shutdown.on('resource:start',    ({ name }) => …);
shutdown.on('resource:done',     ({ name, durationMs }) => …);
shutdown.on('resource:error',    ({ name, error, critical }) => …);
shutdown.on('timeout',           ({ phase, elapsedMs }) => …);
shutdown.on('forced',            ({ socketsDestroyed }) => …);
shutdown.on('error',             (err) => …);

Kubernetes

const shutdown = new ShutdownManager({ timeout: 15_000 })
  .attach(server)
  .kubernetes({ preStopDelayMs: 5_000 });

What this does:

  • Flips isReady() to false immediately on SIGTERM.
  • Waits preStopDelayMs before calling server.close() so the Service has time to mark the pod NotReady and stop routing new traffic.
  • Lets your terminationGracePeriodSeconds budget remain predictable (≈ preStopDelayMs + timeout + 1–2s buffer).

See examples/kubernetes/ for the full Deployment YAML.

Docker

Set STOPSIGNAL SIGTERM in your Dockerfile (Node's default npm wrapper sometimes swallows signals — running node directly avoids that). Use tini or dumb-init as PID 1 if you need to reap zombies.

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
STOPSIGNAL SIGTERM
CMD ["node", "dist/server.js"]

Framework support

| Framework | Attach | | --------- | ------------------------------------------------------------ | | Express | manager.attach(app.listen(...)) | | Koa | manager.attach(app.listen(...)) | | Fastify | manager.attach(fastify.server) | | NestJS | manager.attach(app.getHttpServer()) | | Hono | manager.attach(serve({ fetch: app.fetch })) (Node adapter) | | raw HTTP | manager.attach(http.createServer(...)) | | HTTP/2 | manager.attach(http2.createSecureServer(...)) |

Testing

import { ShutdownManager, createTestHarness } from '@ambicuity/shutdown-manager';

const manager = new ShutdownManager({ signals: ['SIGUSR2'], forceExit: true });
const harness = createTestHarness({ manager });

await harness.sendSignal('SIGUSR2');

expect(harness.exitCode()).toBe(0);
expect(harness.timeline().map((p) => p.name)).toEqual([
  'preShutdown',
  'draining',
  'cleanup',
  'done',
]);

The harness intercepts process.exit so the test process stays alive.

Comparison

| Feature | this | http-graceful-shutdown | terminus | lil-http-terminator | | --------------------------------------- | :--: | :--------------------: | :------: | :-----------------: | | TypeScript-first | ✅ | ❌ | ⚠️ | ⚠️ | | ESM + CJS dual build | ✅ | ❌ | ⚠️ | ✅ | | Zero runtime deps | ✅ | ❌ | ❌ | ✅ | | Resource registry (priority + parallel) | ✅ | ❌ | ✅ | ❌ | | Typed lifecycle events | ✅ | ⚠️ | ✅ | ❌ | | Kubernetes-aware (readiness + preStop) | ✅ | ❌ | ⚠️ | ❌ | | Testing harness | ✅ | ❌ | ❌ | ❌ | | npm provenance | ✅ | ❌ | ❌ | ❌ |

How to start using this in an existing codebase?

import { ShutdownManager } from '@ambicuity/shutdown-manager';
const manager = new ShutdownManager({ timeout: 10_000 }).attach(server);

Zero runtime dependencies

This package has zero runtime dependencies. It uses only the Node.js standard library. Releases are published with --provenance and signed via GitHub Actions OIDC.

Support

If this package saves you a 3am incident, consider buying me a coffee: buymeacoffee.com/ritesh.rana.

For commercial support or custom integration questions, email [email protected].

License

MIT © Ritesh Rana ([email protected])