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

lastcall

v0.1.1

Published

Graceful process lifecycle management for Node.js and Bun

Readme

lastcall

npm version npm provenance CI CodeQL license

Graceful process lifecycle management for Node.js and Bun.

Published on npm as lastcall. Source repository: sakurablush/lastcall. Documentation: sakurablush.github.io/lastcall.

lastcall is a zero-dependency, TypeScript-first library for managing graceful shutdown in production applications. It handles SIGTERM/SIGINT from Docker, Kubernetes, and PM2 — with priorities, dependencies, phases, and HTTP server draining built in.

~22 KB ESM bundle · zero production dependencies · 100% line coverage · integration tests via child processes

npm install lastcall

Try it locally (clone, install, run tests):

git clone https://github.com/sakurablush/lastcall.git && cd lastcall && npm ci && npm test

Documentation

Browse online: sakurablush.github.io/lastcall

📖 VitePress docs cover getting started, API reference, guides (Kubernetes, Docker, Bun), and examples.

Automation: CI and automation · Security: SECURITY.md · See Contributing.

Cursor users: project Agent Skills in .cursor/skills/ guide the AI through development, testing, and docs.

Community

| Document | Purpose | | ---------------------------------------- | ---------------------------------- | | CONTRIBUTING.md | Development setup and PR checklist | | CODE_OF_CONDUCT.md | Community standards | | CONTRIBUTORS.md | How contributors are recognized | | CHANGELOG.md | Release history | | SECURITY.md | Vulnerability reporting |

Why lastcall?

| Problem | lastcall solution | | ------------------------------------- | ------------------------------------- | | In-flight requests dropped on SIGTERM | withHttpServer() drains connections | | Cleanup runs in wrong order | Priorities + dependency graph | | Custom signal handlers are buggy | Test-covered, idempotent shutdown | | Hard to test shutdown | simulateSignal(), autoExit: false | | No observability | Metrics hooks + lifecycle events |

Quick start

import { createServer } from 'node:http';
import { createLastcall } from 'lastcall';

const server = createServer((req, res) => {
  res.end('ok');
});

const lastcall = createLastcall();

lastcall.withHttpServer(server); // before listen()

lastcall.register(
  'database',
  async () => {
    await db.disconnect();
  },
  { critical: true },
);

server.listen(3000);
// SIGTERM / SIGINT trigger graceful shutdown automatically.

API

createLastcall(options?)

| Option | Default | Description | | --------------------------- | ---------------------------------------- | ------------------------------------ | | signals | ['SIGTERM', 'SIGINT', 'SIGHUP'] | Signals to listen for | | shutdownTimeoutMs | 30000 | Global shutdown timeout (> 0) | | phases | ['pre', 'drain', 'cleanup', 'post'] | Shutdown phases (no duplicates) | | onHandlerStart | — | Metrics hook when handler starts | | onHandlerEnd | — | Metrics hook when handler ends | | captureUncaughtException | false | Shutdown on uncaught exceptions | | captureUnhandledRejection | false | Shutdown on unhandled rejections | | autoExit | true | Call process.exit() after shutdown | | logger | console.error with [lastcall] prefix | Custom logger |

lastcall.register(name, fn, options?)

| Option | Default | Description | | ----------- | ----------- | --------------------------------------------- | | priority | 100 | Lower runs earlier (within same phase) | | timeoutMs | — | Per-handler timeout | | critical | false | Failure affects exit code | | deps | [] | Names that must finish first (same phase) | | phase | 'cleanup' | Shutdown phase |

Shutdown phases

  1. pre — Mark unhealthy, stop accepting new work
  2. drain — Wait for in-flight requests/jobs
  3. cleanup — Close DB, Redis, queues, files
  4. post — Final logs, metrics flush

getDefaultLastcall(options?)

Returns a process-wide singleton. Options apply only on the first call.

Other methods

await lastcall.shutdown('reason', 'SIGTERM'); // Manual trigger (optional signal)
lastcall.isShuttingDown(); // True once shutdown starts (stays true after completion)
lastcall.isShutdownComplete(); // True after shutdown finishes
lastcall.getState(); // 'idle' | 'shutting_down' | 'done' | 'force_exit'
lastcall.simulateSignal('SIGTERM'); // Testing
lastcall.withHttpServer(server); // HTTP drain
lastcall.unregister('name'); // Remove handler (blocked if others depend on it)
lastcall.hasHandler('name'); // Check if a handler is registered
lastcall.listHandlers(); // Snapshot for logging / health dashboards
lastcall.on('beforeShutdown', fn); // Events

Events

  • beforeShutdown{ reason, signal? }
  • afterShutdown{ exitCode }
  • handlerError{ name, phase, durationMs, error }
  • handlerCompleted{ name, phase, durationMs, error? }

HTTP server integration

import http from 'node:http';
import { createLastcall } from 'lastcall';

const server = http.createServer(handler);
const lastcall = createLastcall();

lastcall.withHttpServer(server, {
  drainTimeoutMs: 10_000,
  priority: 10,
  phase: 'drain',
});

server.listen(3000);

Works with Node.js http/https/http2 servers. Framework examples: Express, Fastify, Hono.

Dependencies between handlers

lastcall.register(
  'database',
  async () => {
    /* ... */
  },
  { phase: 'cleanup' },
);
lastcall.register(
  'cache',
  async () => {
    /* ... */
  },
  {
    phase: 'cleanup',
    deps: ['database'],
  },
);

Handlers with deps wait for their dependencies to complete within the same phase. Circular dependencies throw at shutdown time. Cross-phase dependencies are rejected at registration time.

Examples

| Example | Description | | ------------------------------------------------ | ---------------------- | | standalone-worker | Worker without HTTP | | express | Express + health check | | fastify | Fastify server | | hono | Hono server | | node-http | Raw Node.js http | | docker-k8s | K8s/Docker guide | | nextjs-standalone | Next.js custom server | | pm2-cluster | PM2 cluster mode |

See examples/README.md for how to run them.

Testing

npm test                  # silent
npm run test:log          # verbose proof logging (LASTCALL_TEST_LOG=1)
npm run test:coverage     # 100% line coverage (98% branch threshold)
import { createLastcall } from 'lastcall';

const lastcall = createLastcall({
  autoExit: false,
  signals: [],
});

lastcall.register('cleanup', vi.fn());
await lastcall.shutdown('test');

See docs/testing/running-tests.md for the full guide.

Kubernetes / Docker

See examples/docker-k8s/ for deployment best practices.

Key settings:

  • Set terminationGracePeriodSeconds > shutdownTimeoutMs
  • Use preStop hook for load balancer drain
  • Register health check as unhealthy in pre phase
// Readiness probe — fail fast once shutdown starts
app.get('/health', (_req, res) => {
  if (lastcall.isShuttingDown()) {
    res.status(503).send('shutting down');
    return;
  }
  res.status(200).send('ok');
});

Bun compatibility

lastcall works with Bun. Signal handling may differ on Windows — SIGHUP is not available on all platforms and is safely skipped.

Troubleshooting

Handler hangs forever

Set timeoutMs on the handler. lastcall will log a clear timeout message and continue shutdown.

Multiple SIGTERM signals

Shutdown is idempotent — subsequent signals are ignored while shutting down.

Exit code is 1

A handler marked critical: true failed or timed out. Check logs for Handler "name" failed.

Tests call process.exit

Use autoExit: false in test configuration. Use resetDefaultLastcall() when testing code that calls getDefaultLastcall().

Sustain development (optional)

lastcall is MIT-licensed and free on npm — no paywall, no paid tier, no feature lock.

Keeping it current still has a real cost: cross-platform signal testing, integration suites via child processes, docs, CI, and compatibility with Node.js and Bun releases. That work runs on a self-funded stack.

Contribute in code if that fits you: Issues or a pull request per CONTRIBUTING.md.

Financial support is optional — only if lastcall saved you time or you want to help cover the next development cycle. No tiers, no perks, no obligation:

| Coin | Address | | -------- | --------------------------------------------- | | Bitcoin | bc1qcrj9wreunffxm75fcz0a2gjkw87jshcwvmxv68 | | Ethereum | 0xB85Bf389E5fC5E12636FB6F17b68Df7fC990a3dA | | Litecoin | ltc1q0nsarncmnz8vk34dav7l0vgu9m5k48drr8z6js | | Dogecoin | D6YGoYWpFZxW8JXvEfT5iB9uNXcs8AeY7L |

License

MIT