Skip to content

Repository files navigation

eliware.org

@eliware/signals npm versionlicensebuild status

Graceful shutdown signal handler utility for Node.js


Table of Contents

Features

  • Register handlers for process signals (e.g., SIGTERM, SIGINT, SIGHUP)
  • Register async shutdown hooks to run on signals, explicit shutdown, or beforeExit
  • Customizable logger and process object
  • Idempotent registration with repeat-safe listener cleanup
  • AbortSignal support for lifecycle-managed applications
  • Concurrent shutdown protection and per-hook error isolation
  • Configurable exit code and optional non-exiting mode
  • Simple ESM API
  • TypeScript type definitions included
  • Well-tested with Jest

Requirements

  • Node.js 26 or newer
  • A process-like application lifecycle that can receive shutdown signals

Installation

npm install @eliware/signals

Usage

ESM Example

import log from '@eliware/log';
import registerSignals from '@eliware/signals'; // Default export
// or: import { registerSignals } from '@eliware/signals';
const { shutdown, getShuttingDown } = registerSignals({ log });

Shutdown Hooks Example

import log from '@eliware/log';
import registerSignals from '@eliware/signals';

You can call registerSignals multiple times to add async shutdown hooks. All hooks will be run (in order of registration) when a signal is received or Node emits beforeExit. Repeated registrations must use the same lifecycle options (log, signals, exitCode, exit, and signal); conflicting options throw TypeError.

// Simulate a resource that needs cleanup (e.g., database connection)
const fakeDb = {
  close: async () => {
    return new Promise(resolve => setTimeout(() => {
      log.info('Fake DB connection closed');
      resolve();
    }, 100));
  }
};

// Register signal handlers
registerSignals({ log });

// Add shutdown hook for closing the fake DB connection
registerSignals({
  log,
  shutdownHook: async (signal) => {
    await fakeDb.close();
    log.info(`Cleanup complete on ${signal}`);
  }
});

API

registerSignals(options?)

Registers shutdown handlers for the specified signals and allows registering async shutdown hooks.

Options

  • processObj (default: process): Process-like object to attach handlers to; must provide on, with optional off and exit.
  • log (default: @eliware/log): Logger for output. Must have debug, warn, and error methods; invalid loggers throw TypeError. Custom loggers are responsible for their own error serialization/redaction.
  • signals (default: [ 'SIGTERM', 'SIGINT', 'SIGHUP' ]): Array of signals to listen for.
  • shutdownHook (optional): A sync or async function to run during shutdown. Multiple registrations add hooks in order.
  • exitCode (default: 0): Exit code used after signal-driven shutdown.
  • exit (default: true): Set to false for embedded applications and tests that should not call process.exit.
  • signal (optional): An AbortSignal that removes all registered listeners when aborted.

Returns

An object with:

  • shutdown(signal: string): Promise<void> — Manually trigger shutdown logic.
  • getShuttingDown(): boolean — Returns whether shutdown is in progress.
  • removeHandlers(): void — Detaches registered listeners; safe to call repeatedly.
  • removed: boolean — Indicates whether cleanup has completed.

**Shutdown hooks run on signals, explicit shutdown(), or beforeExit. They are intentionally not run from Node’s exit event because asynchronous cleanup cannot complete reliably there.

TypeScript

Type definitions are included:

import registerSignals, { RegisterSignalsOptions } from '@eliware/signals';

// Optionally provide options
const options: RegisterSignalsOptions = {
  processObj: process, // optional, defaults to process
  log: myLogger,       // optional; must provide debug, warn, and error
  signals: ['SIGTERM', 'SIGINT', 'SIGHUP'], // optional, defaults as shown
  shutdownHook: async (signal) => { /* ... */ } // optional
};

const { shutdown, getShuttingDown, removeHandlers, removed } = registerSignals(options);

// Types:
// interface RegisterSignalsOptions {
//   processObj?: ProcessLike;
//   log?: SignalsLogger;
//   signals?: NodeSignal[];
//   shutdownHook?: (signal: string) => void | Promise<void>;
//   exitCode?: number;
//   exit?: boolean;
//   signal?: AbortSignal;
// }
//
// function registerSignals(options?: RegisterSignalsOptions): SignalsRegistration;

Errors / Troubleshooting

Shutdown hooks run in registration order, and a failing hook is logged without preventing later hooks from running. Use exit: false for embedded applications and tests. Prefer explicit shutdown() or beforeExit for asynchronous cleanup. Always call removeHandlers() when a registration is no longer needed.

Development

npm test
npm run test:gaps
npm run lint
npm run typecheck
npm run pack

Examples are safe to inspect and should be run only in a controlled process when testing signal behavior.

Security

Set LOG_LEVEL=debug to see diagnostic messages from the default logger. Do not log secrets or sensitive shutdown context. Keep cleanup hooks bounded and avoid relying on asynchronous work after the process has entered the exit event.

Support

For help, questions, or to chat with the author and community, visit:

Discordeliware.org

eliware.org on Discord

License

MIT © 2025 Eli Sterling, eliware.org

Links

About

Graceful shutdown signal handler utility for Node.js (ESM and CommonJS)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages