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

process-supervisor

v1.0.0

Published

Automatic graceful shutdown for Node.js applications. Manage child processes, file watchers, and servers with zero boilerplate and zero dependencies.

Readme

🛸 Process Supervisor

Test

Node Versions Supported

Automatic graceful shutdown for Node.js applications. Manage child processes, file watchers, and servers with zero boilerplate and zero dependencies.

Motivation

Building Node.js applications often involves managing multiple resources like development servers, file watchers, and background processes. Each needs proper startup, shutdown, and error handling — especially when the application exits. Without proper cleanup, you're left with orphaned processes, file handles, and lingering connections.

Writing signal handlers, managing cleanup order, and handling edge cases quickly becomes complex boilerplate that's repeated across projects.

Process Supervisor eliminates this complexity by providing a simple API that automatically handles graceful shutdown on process signals (Ctrl+C, SIGTERM) and uncaught errors. Register your resources once, and the supervisor ensures they're properly cleaned up when your application exits — no more manual signal handlers or cleanup code.

Example

import { spawn } from 'node:child_process'

import { ProcessSupervisor } from 'process-supervisor'

const supervisor = new ProcessSupervisor()

supervisor.register('server', {
  start: () => spawn('tsx', ['watch', 'src/index.ts']),
  stop: proc => {
    proc.kill('SIGTERM')
    return new Promise(resolve => proc.on('exit', resolve))
  },
})

await supervisor.start('server')

// That's it! Automatic graceful shutdown on:
// - Ctrl+C (SIGINT) and SIGTERM
// - Uncaught errors (uncaughtException, unhandledRejection)
// Plus: 5s timeout enforcement and proper exit codes

Common Use Cases

Child Processes (e.g., execa, spawn)

supervisor.register('dev-server', {
  start: () => spawn('tsx', ['watch', 'src/index.ts']),
  stop: proc => {
    proc.kill('SIGTERM')
    return new Promise(resolve => proc.on('exit', resolve))
  },
})

File Watchers (e.g., chokidar)

import chokidar from 'chokidar'

supervisor.register('watcher', {
  start: () => chokidar.watch('src/**/*.ts'),
  stop: async watcher => await watcher.close(),
})

Servers (e.g., Webpack DevServer)

import { WebpackDevServer } from 'webpack-dev-server'

supervisor.register('webpack', {
  start: async () => {
    const server = new WebpackDevServer(options, compiler)
    await server.start()
    return server
  },
  stop: async server => await server.stop(),
})

Usage

Installation

Install the package as a dependency:

# Using npm
npm install process-supervisor

# Using yarn
yarn add process-supervisor

Importing

You can import ProcessSupervisor using either CommonJS or ES Modules:

// Using CommonJS
const { ProcessSupervisor } = require('process-supervisor')

// Using ES Modules
import { ProcessSupervisor } from 'process-supervisor'

API

Constructor Options

new ProcessSupervisor(options?)

| Property | Type | Required | Default | Description | |------------------------|--------------------------------------------|----------|---------|-------------------------------------------------------------------------------------------------| | defaultTimeout | number | - | 5000 | Default timeout in milliseconds for stopping resources. | | handleSignals | boolean | NodeJS.Signals[] | - | true | Automatically handle process signals. Pass true for SIGINT/SIGTERM, array for custom signals. | | handleUncaughtErrors | boolean | - | true | Automatically handle uncaught exceptions and unhandled promise rejections. | | onError | (error: unknown) => void | Promise<void> | - | - | Callback invoked before automatic shutdown when an uncaught error occurs. | | onSignal | (signal: string) => void | Promise<void> | - | - | Callback invoked before automatic shutdown when a signal is received. |

Methods

register<T>(id: string, config: ResourceConfig<T>): void

Register a new resource with the supervisor.

unregister(id: string): Promise<void>

Unregister a resource from the supervisor. Automatically stops the resource if it is running.

getInstance<T>(id: string): T | null

Get the instance of a resource. Returns null if the resource has never been started.

start(id: string): Promise<void>

Start a resource.

stop(id: string): Promise<void>

Stop a running resource. Enforces timeout configured on the resource.

shutdownAll(): Promise<boolean>

Stop all resources in parallel. Returns true if any errors occurred.

getState(id: string): ProcessState | undefined

Get the current state of a resource. States: IDLE, STARTING, RUNNING, STOPPING, STOPPED, FAILED.

getAllStates(): ReadonlyMap<string, ProcessState>

Get the current state of all resources.

has(id: string): boolean

Check if a resource is registered.

size: number

Get the total number of registered resources.

Resource Configuration

interface ResourceConfig<T> {
  start: () => T | Promise<T>
  stop: (instance: T) => void | Promise<void>
  timeout?: number
}

| Property | Type | Required | Description | |-----------|-----------------------------------------|----------|------------------------------------------------------------------------------------------| | start | () => T | Promise<T> | ✅ | Function that creates and returns the resource instance. | | stop | (instance: T) => void | Promise<void> | ✅ | Function that cleans up the resource. Receives the running instance. | | timeout | number | - | Maximum time in milliseconds to wait for stop to complete. Defaults to defaultTimeout. |

Important: The start function must return a function that creates the resource, not the resource itself:

// ✅ Correct
start: () => spawn('command')

// ❌ Wrong
start: spawn('command')

Advanced Usage

Manual Shutdown Control

By default, the supervisor handles shutdown automatically. You can also trigger shutdown manually:

// Stop a specific resource
await supervisor.stop('server')

// Stop all resources
await supervisor.shutdownAll()

This is useful when you need shutdown logic in custom signal handlers or specific error scenarios.

Lifecycle Hooks

Lifecycle hooks let you run custom logic before automatic shutdown.

Note: Hooks require automatic handlers to be enabled: onError requires handleUncaughtErrors to be enabled, and onSignal requires handleSignals to be enabled. For full control over shutdown behaviour, see Custom Error Handling and Custom Signal Handling.

const supervisor = new ProcessSupervisor({
  onError: async error => {
    await reportToSentry(error)
  },
  onSignal: async signal => {
    await reportToSentry({ event: 'shutdown', signal })
  },
})

The hooks are called before shutdownAll() runs, allowing you to:

  • Report shutdown events to monitoring services (Sentry, Datadog, etc.)
  • Log shutdown reasons to files or external services
  • Send metrics or analytics
  • Perform custom cleanup that doesn't fit the resource model

Error handling: If a hook throws an error, it will be logged but won't prevent shutdown. This ensures your application always cleans up resources properly, even if custom logic fails.

Custom Error Handling

Disable automatic error handling when you need custom crash behaviour:

const supervisor = new ProcessSupervisor({
  handleUncaughtErrors: false  // Disable automatic error handling
})

// Now you control error behaviour
process.on('uncaughtException', async error => {
  console.error('Unexpected error:', error)
  await reportToSentry(error)
  await supervisor.shutdownAll()
  process.exit(1)
})

process.on('unhandledRejection', async error => {
  console.error('Unhandled promise:', error)
  await reportToSentry(error)
  await supervisor.shutdownAll()
  process.exit(1)
})

Custom Signal Handling

Disable automatic signal handling when you need custom behaviour:

const supervisor = new ProcessSupervisor({
  handleSignals: false,  // Disable automatic SIGINT/SIGTERM handling
})

// Now you control signal behaviour
process.on('SIGINT', async () => {
  console.log('Gracefully shutting down...')
  await supervisor.shutdownAll()
  process.exit(0)
})

Or handle specific signals only:

const supervisor = new ProcessSupervisor({
  handleSignals: ['SIGTERM'],  // Only handle SIGTERM, not SIGINT
})

Accessing Resource Instances

Use getInstance to access the underlying resource for advanced control:

// Force-kill a process after timeout
try {
  await supervisor.stop('server')
} catch (error) {
  const proc = supervisor.getInstance<ChildProcess>('server')
  proc?.kill('SIGKILL')
}

Changing Resource Configuration

To change a resource's configuration, unregister and re-register it:

await supervisor.unregister('server')
supervisor.register('server', newConfig)
await supervisor.start('server')