npm package: @joint-ops/kaput
Description: Zero-config graceful shutdown for Node.js
Author: Joint Ops
Latest version: 0.1.0

# kaput

> Zero-config graceful shutdown for Node.js

[![npm version](https://img.shields.io/npm/v/@joint-ops/kaput.svg)](https://www.npmjs.com/package/@joint-ops/kaput)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## The Problem

Graceful shutdown in Node.js is harder than it looks. Here's what most apps do:

```typescript
// 50+ lines of boilerplate that's still broken
process.on('SIGTERM', async () => {
  console.log('Shutting down...');

  // Hope this order is right...
  server.close();
  await redis.quit();
  await prisma.$disconnect();

  // Did we forget something?
  // What about in-flight requests?
  // What if close() hangs?

  process.exit(0);
});

// Oh wait, SIGINT too
process.on('SIGINT', /* copy-paste the same thing */);

// And uncaught exceptions...
// And unhandled rejections...
// And health checks during shutdown...
```

## The Solution

```typescript
import 'kaput';
```

That's it. kaput handles everything automatically.

## Features

- **Zero config** — Auto-detects servers, databases, and caches
- **Correct shutdown order** — HTTP first, databases last
- **Health checks** — Returns 503 during shutdown
- **Kubernetes ready** — Works with SIGTERM, liveness, and readiness probes
- **Handles edge cases** — Timeouts, stuck connections, force exit
- **TypeScript first** — Full type definitions included

## Installation

```bash
npm install @joint-ops/kaput
```

## Quick Start

### Magic Import (Recommended)

Add one line at the top of your entry file:

```typescript
import 'kaput';

import express from 'express';

const app = express();
app.get('/', (req, res) => res.send('Hello'));
app.listen(3000);

// kaput automatically:
// - Detects the HTTP server
// - Handles SIGTERM/SIGINT
// - Waits for in-flight requests
// - Exits cleanly
```

### Explicit Registration

When you need more control:

```typescript
import { kaput } from '@joint-ops/kaput';
import express from 'express';
import { PrismaClient } from '@prisma/client';
import Redis from 'ioredis';

const app = express();
const prisma = new PrismaClient();
const redis = new Redis();

const server = app.listen(3000);

// Register resources explicitly
kaput.register(server);
kaput.register(prisma);
kaput.register(redis);
```

### Health Checks

Return 503 during shutdown so load balancers stop sending traffic:

```typescript
import { kaput } from '@joint-ops/kaput';

app.get('/health', (req, res) => {
  if (kaput.isShutdown()) {
    return res.status(503).json({ status: 'shutting_down' });
  }
  res.json({ status: 'healthy' });
});
```

## Supported Resources

| Resource | Auto-detected | Shutdown Priority |
|----------|--------------|-------------------|
| HTTP/HTTPS Server | Yes | 10 (first) |
| Express/Fastify | Yes | 10 |
| Prisma Client | Yes | 60 (last) |
| Redis (ioredis) | Yes | 50 |
| BullMQ Worker | Yes | 30 |
| BullMQ Queue | Yes | 40 |
| Mongoose | Yes | 60 |
| Knex | Yes | 60 |
| Custom (close/disconnect) | Manual | 100 |

## Configuration

```typescript
import { Kaput } from '@joint-ops/kaput';

const kaput = new Kaput({
  timeout: 30000,        // Max shutdown time (default: 30s)
  gracePeriod: 10000,    // Wait for in-flight requests (default: 10s)
  signals: ['SIGTERM', 'SIGINT'],
  logLevel: 'info',      // 'debug' | 'info' | 'warn' | 'error' | 'silent'
});
```

## Lifecycle Hooks

```typescript
kaput.on({
  onShutdownStart: (event) => {
    console.log(`Shutdown started: ${event.signal}`);
  },
  onShutdownComplete: () => {
    console.log('Shutdown complete');
  },
});
```

## Performance

kaput adds negligible overhead to your shutdown sequence:

| Operation | Avg Time | Throughput |
|-----------|----------|------------|
| Empty shutdown | 0.02ms | 45,000 ops/sec |
| Single resource | 0.03ms | 38,000 ops/sec |
| 10 resources | 0.05ms | 21,000 ops/sec |
| 100 resources | 0.21ms | 4,700 ops/sec |
| Registration (1000x) | 0.66ms | - |

- **Memory overhead**: ~500 bytes per registered resource
- **Test coverage**: 350+ tests including adversarial edge cases
- **Zero dependencies**: No external runtime dependencies

The actual shutdown time is dominated by your resources (database connections, HTTP draining), not kaput's coordination.

## Documentation

Full documentation at **[kaput.dev/docs](https://kaput.jointops.dev/docs)**

- [Getting Started](https://kaput.jointops.dev/docs/getting-started/quick-start)
- [Express Guide](https://kaput.jointops.dev/docs/guides/express)
- [Kubernetes Guide](https://kaput.jointops.dev/docs/guides/kubernetes)
- [API Reference](https://kaput.jointops.dev/docs/api/overview)

## License

MIT