Socket
Socket
Sign inDemoInstall

@ladjs/graceful

Package Overview
Dependencies
2
Maintainers
5
Versions
19
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.1 to 3.0.0

22

index.d.ts

@@ -0,14 +1,22 @@

interface Logger {
info(): unknown;
warn(): unknown;
error(): unknown;
}
interface LilHttpTerminator {
gracefulTerminationTimeout?: number;
maxWaitTimeout?: number;
logger?: Logger;
}
export interface GracefulOptions {
servers?: Array<{ close(): unknown }>;
mongooses?: Array<{ disconnect(): Promise<void> }>;
bulls?: Array<{ close(): unknown }>;
brees?: Array<{ stop(): Promise<void> }>;
redisClients?: Array<{ disconnect(): unknown }>;
mongooses?: Array<{ disconnect(): Promise<void> }>;
customHandlers?: Array<() => unknown>;
logger?: Logger;
timeoutMs?: number;
logger?: {
info(): unknown;
warn(): unknown;
error(): unknown;
};
lilHttpTerminator?: LilHttpTerminator;
}

@@ -15,0 +23,0 @@

@@ -1,4 +0,11 @@

const process = require('process');
const debug = require('debug')('@ladjs/graceful');
const http = require('node:http');
const net = require('node:net');
const process = require('node:process');
const util = require('node:util');
const isPromise = require('p-is-promise');
const HttpTerminator = require('lil-http-terminator');
const debug = util.debuglog('@ladjs/graceful');
class Graceful {

@@ -8,18 +15,49 @@ constructor(config) {

servers: [],
brees: [],
redisClients: [],
mongooses: [],
bulls: [],
brees: [],
customHandlers: [],
logger: console,
timeoutMs: 5000,
lilHttpTerminator: {},
...config
};
// noop logger if false
if (this.config.logger === false)
this.config.logger = {
info() {},
warn() {},
error() {}
};
// shortcut logger
this.logger = this.config.logger;
// if lilHttpTerminator does not have a logger set then re-use `this.logger`
if (!this.config.lilHttpTerminator.logger)
this.config.lilHttpTerminator.logger = this.logger;
// prevent multiple SIGTERM/SIGHUP/SIGINT from firing graceful exit
this._isExiting = false;
//
// create instances of HTTP terminator in advance for faster shutdown
//
for (const server of this.config.servers) {
// backwards compatible support (get the right http or net server object instance)
let serverInstance = server;
if (serverInstance.server instanceof net.Server)
serverInstance = serverInstance.server;
else if (!(serverInstance instanceof net.Server))
throw new Error('Servers passed must be instances of net.Server');
if (serverInstance instanceof http.Server) {
server.terminator = new HttpTerminator({
server: serverInstance,
...this.config.lilHttpTerminator
});
}
}
// bind this to everything

@@ -33,4 +71,2 @@ this.listen = this.listen.bind(this);

this.stopMongooses = this.stopMongooses.bind(this);
this.stopBull = this.stopBull.bind(this);
this.stopBulls = this.stopBulls.bind(this);
this.stopBree = this.stopBree.bind(this);

@@ -87,83 +123,90 @@ this.stopBrees = this.stopBrees.bind(this);

async stopServer(server) {
async stopServer(server, code) {
try {
await server.close();
if (server.terminator) {
// HTTP servers
debug('server.terminator');
const { error } = await server.terminator.terminate();
if (error) throw error;
} else if (server.stop) {
// support for `stoppable`
debug('server.stop');
await (isPromise(server.stop)
? server.stop()
: util.promisify(server.stop).bind(server)());
} else if (server.close) {
// all other servers (e.g. SMTP)
debug('server.close');
await (isPromise(server.close)
? server.close()
: util.promisify(server.close).bind(server)());
}
} catch (err) {
this.config.logger.error(err);
this.logger.error(err, { code });
}
}
async stopServers() {
async stopServers(code) {
await Promise.all(
this.config.servers.map((server) => this.stopServer(server))
this.config.servers.map((server) => this.stopServer(server, code))
);
}
async stopRedisClient(client) {
async stopRedisClient(client, code) {
if (client.status === 'end') return;
// TODO: give it a max of 500ms
// https://github.com/OptimalBits/bull/blob/develop/lib/queue.js#L516
try {
await client.disconnect();
} catch (err) {
this.config.logger.error(err);
this.logger.error(err, { code });
}
}
async stopRedisClients() {
async stopRedisClients(code) {
await Promise.all(
this.config.redisClients.map((client) => this.stopRedisClient(client))
this.config.redisClients.map((client) =>
this.stopRedisClient(client, code)
)
);
}
async stopMongoose(mongoose) {
async stopMongoose(mongoose, code) {
try {
await mongoose.disconnect();
} catch (err) {
this.config.logger.error(err);
this.logger.error(err, { code });
}
}
async stopMongooses() {
async stopMongooses(code) {
await Promise.all(
this.config.mongooses.map((mongoose) => this.stopMongoose(mongoose))
this.config.mongooses.map((mongoose) => this.stopMongoose(mongoose, code))
);
}
async stopBull(bull) {
async stopBree(bree, code) {
try {
await bull.close();
} catch (err) {
this.config.logger.error(err);
}
}
async stopBulls() {
await Promise.all(this.config.bulls.map((bull) => this.stopBull(bull)));
}
async stopBree(bree) {
try {
await bree.stop();
} catch (err) {
this.config.logger.error(err);
this.logger.error(err, { code });
}
}
async stopBrees() {
await Promise.all(this.config.brees.map((bree) => this.stopBree(bree)));
async stopBrees(code) {
await Promise.all(
this.config.brees.map((bree) => this.stopBree(bree, code))
);
}
async stopCustomHandler(handler) {
async stopCustomHandler(handler, code) {
try {
await handler();
} catch (err) {
this.config.logger.error(err);
this.logger.error(err, { code });
}
}
stopCustomHandlers() {
stopCustomHandlers(code) {
return Promise.all(
this.config.customHandlers.map((handler) =>
this.stopCustomHandler(handler)
this.stopCustomHandler(handler, code)
)

@@ -174,6 +217,6 @@ );

async exit(code) {
if (code) this.logger.info(`Gracefully exiting from ${code}`);
if (code) this.logger.info('Gracefully exiting', { code });
if (this._isExiting) {
this.logger.info('Graceful exit already in progress');
this.logger.info('Graceful exit already in progress', { code });
return;

@@ -184,4 +227,2 @@ }

debug('graceful exit started');
// give it only X ms to gracefully exit

@@ -192,3 +233,4 @@ setTimeout(() => {

`Graceful exit failed, timeout of ${this.config.timeoutMs}ms was exceeded`
)
),
{ code }
);

@@ -202,19 +244,17 @@ // eslint-disable-next-line unicorn/no-process-exit

// servers
this.stopServers(),
this.stopServers(code),
// redisClients
this.stopRedisClients(),
this.stopRedisClients(code),
// mongooses
this.stopMongooses(),
// bulls
this.stopBulls(),
this.stopMongooses(code),
// brees
this.stopBrees(),
this.stopBrees(code),
// custom handlers
this.stopCustomHandlers()
this.stopCustomHandlers(code)
]);
this.logger.info('Gracefully exited');
this.logger.info('Gracefully exited', { code });
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
} catch (err) {
this.logger.error(err);
this.logger.error(err, { code });
// eslint-disable-next-line unicorn/no-process-exit

@@ -221,0 +261,0 @@ process.exit(1);

{
"name": "@ladjs/graceful",
"description": "Gracefully exit server (Koa), database (Mongo/Mongoose), Bree job schedulers, Bull job schedulers, and custom handlers.",
"version": "2.0.1",
"description": "Gracefully exit HTTP servers (Express/Koa/Fastify/etc), databases (Mongo/Mongoose), Bree job schedulers, and custom handlers.",
"version": "3.0.0",
"author": "Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)",

@@ -10,33 +10,38 @@ "bugs": {

},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"contributors": [
"Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)",
"Felix Mosheev (https://github.com/felixmosh)",
"Nicholai Nissen <nicholainissen@gmail.com> (https://nicholai.dev)"
"Nicholai Nissen <nicholainissen@gmail.com> (https://nicholai.dev)",
"Spencer Snyder <sasnyde2@gmail.com> (https://spencersnyder.io)"
],
"dependencies": {
"debug": "^4.3.4"
"lil-http-terminator": "^1.2.2",
"p-is-promise": "3"
},
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"ava": "^4.1.0",
"codecov": "^3.8.2",
"@commitlint/cli": "^17.0.2",
"@commitlint/config-conventional": "^17.0.2",
"@ladjs/api": "^10.0.3",
"@ladjs/web": "^14.0.3",
"ava": "^4.3.0",
"cross-env": "^7.0.3",
"eslint": "8.12.0",
"eslint-config-xo-lass": "^1.0.6",
"eslint": "8.17.0",
"eslint-config-xo-lass": "^2.0.1",
"express": "^4.18.1",
"fastify": "^3.29.0",
"fixpack": "^4.0.0",
"husky": "^7.0.4",
"lint-staged": "^12.3.7",
"get-port": "5",
"husky": "^8.0.1",
"ioredis": "^5.0.6",
"ioredis-mock": "^8.2.2",
"koa": "^2.13.4",
"lint-staged": "^13.0.0",
"nyc": "^15.1.0",
"remark-cli": "^10.0.1",
"remark-preset-github": "^4.0.1",
"xo": "^0.48.0"
"remark-preset-github": "^4.0.4",
"smtp-server": "^3.11.0",
"xo": "^0.49.0"
},
"engines": {
"node": ">=8.3"
"node": ">=14"
},

@@ -50,3 +55,2 @@ "files": [

"bree",
"bull",
"close",

@@ -80,27 +84,6 @@ "database",

"license": "MIT",
"lint-staged": {
"*.js": [
"xo --fix"
],
"*.md": [
"remark . -qfo"
],
"package.json": [
"fixpack"
]
},
"main": "index.js",
"prettier": {
"singleQuote": true,
"bracketSpacing": true,
"trailingComma": "none"
},
"publishConfig": {
"access": "public"
},
"remarkConfig": {
"plugins": [
"preset-github"
]
},
"repository": {

@@ -111,16 +94,9 @@ "type": "git",

"scripts": {
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"lint": "xo && remark . -qfo",
"lint": "xo --fix && remark . -qfo && fixpack",
"prepare": "husky install",
"test": "npm run lint && npm run test-coverage",
"pretest": "npm run lint",
"test": "npm run test-coverage",
"test-coverage": "cross-env NODE_ENV=test nyc ava"
},
"types": "index.d.ts",
"xo": {
"prettier": true,
"space": true,
"extends": [
"xo-lass"
]
}
"types": "index.d.ts"
}
# [**@ladjs/graceful**](https://github.com/ladjs/graceful)
[![build status](https://img.shields.io/travis/ladjs/graceful.svg)](https://travis-ci.org/ladjs/graceful)
[![code coverage](https://img.shields.io/codecov/c/github/ladjs/graceful.svg)](https://codecov.io/gh/ladjs/graceful)
[![build status](https://github.com/ladjs/graceful/actions/workflows/ci.yml/badge.svg)](https://github.com/ladjs/graceful/actions/workflows/ci.yml)
[![code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)

@@ -10,3 +9,3 @@ [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)

> Gracefully exit server (Koa), database (Mongo/Mongoose), Redis clients, Bree job schedulers, Bull job schedulers, and custom handlers.
> Gracefully exit HTTP servers (Express/Koa/Fastify/etc), databases (Mongo/Mongoose), Redis clients, Bree job schedulers, and custom handlers.

@@ -18,2 +17,8 @@

* [Usage](#usage)
* [Express](#express)
* [Koa](#koa)
* [Fastify](#fastify)
* [Other](#other)
* [Instance Options](#instance-options)
* [Examples](#examples)
* [Contributors](#contributors)

@@ -31,11 +36,62 @@ * [License](#license)

[yarn][]:
```sh
yarn add @ladjs/graceful
```
## Usage
See the [Express](#express), [Koa](#koa), [Fastify](#fastify), or [Other](#other) code snippet examples and [Instance Options](#instance-options) below.
## Usage
**You can pass [Instance Options](#instance-options) to customize your graceful handler** (e.g. if you have more than one server, or wish to close both a Redis connection and a server at the same time).
```js
const Graceful = require('@ladjs/graceful');
//
// ...
//
//
// see Instance Options in the README below and examples for different projects (e.g. Koa or Express)
//
const graceful = new Graceful({
//
// http or net servers
// (this supports Express/Koa/Fastify/etc)
// (basically anything created with http.createServer or net.createServer)
// <https://github.com/expressjs/express>
// <https://github.com/koajs/koa>
// <https://github.com/fastify/fastify>
//
servers: [],
// bree clients
// <https://github.com/breejs/bree>
brees: [],
// redis clients
// <https://github.com/luin/ioredis>
// <https://github.com/redis/node-redis>
redisClients: [],
// mongoose clients
// <https://github.com/Automattic/mongoose>
mongooses: [],
// custom handlers to invoke upon shutdown
customHandlers: [],
// logger
logger: console,
// how long to wait in ms for exit to finish
timeoutMs: 5000,
// options to pass to `lil-http-terminator` to override defaults
lilHttpTerminator: {}
});
//
// NOTE: YOU MUST INVOKE `graceful.listen()` IN ORDER FOR THIS TO WORK!
//
graceful.listen();
```
Using this package will bind process event listeners when `graceful.listen()` is called:

@@ -54,7 +110,81 @@

See one of these following files from [Lad][] for the most up to date usage example:
For `servers` passed, we use [lil-http-terminator][] under the hood. Default configuration options can be overridden by passing a `lilHttpTerminator` configuration object. See [index.js](index.js) for more insight.
### Express
```js
const express = require('express');
const Graceful = require('@ladjs/graceful');
const app = express();
const server = app.listen();
const graceful = new Graceful({ servers: [server] });
graceful.listen();
```
### Koa
```js
const Koa = require('koa');
const Graceful = require('@ladjs/graceful');
const app = new Koa();
const server = app.listen();
const graceful = new Graceful({ servers: [server] });
graceful.listen();
```
### Fastify
```js
const fastify = require('fastify');
const Graceful = require('@ladjs/graceful');
const app = fastify();
app.listen();
//
// NOTE: @ladjs/graceful is smart and detects `app.server` automatically
//
const graceful = new Graceful({ servers: [app] });
graceful.listen();
```
### Other
This package works with any server created with `http.createServer` or `net.createServer` (Node's internal HTTP and Net packages).
**Please defer to the [test folder files](test/test.js) for example usage.**
## Instance Options
Here is the full list of options and their defaults. See [index.js](index.js) for more insight if necessary.
| Property | Type | Default Value | Description |
| ------------------- | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `servers` | Array | `[]` | An array of HTTP or NET servers to gracefully close on exit |
| `brees` | Array | `[]` | An array of [Bree][] instances to gracefully exit |
| `redisClients` | Array | `[]` | An array of Redis client instances to gracefully exit |
| `mongooses` | Array | `[]` | An array of Mongoose connections to gracefully exit |
| `customHandlers` | Array | `[]` | An array of functions (custom handlers) to invoke upon graceful exit |
| `logger` | Object | `console` | This is the default logger. **We recommend using [Cabin][cabin]** instead of using `console` as your default logger. Set this value to `false` to disable logging entirely (uses noop function) |
| `timeoutMs` | Number | `5000` | A number in milliseconds for how long to wait to gracefully exit |
| `lilHttpTerminator` | Object | `{}` | An object of options to pass to `lil-http-terminator` to override default options provided |
## Examples
You can refer Forward Email for more complex usage:
* API - <https://github.com/forwardemail/forwardemail.net/blob/master/api.js>
* Web - <https://github.com/forwardemail/forwardemail.net/blob/master/web.js>
* Bree - <https://github.com/forwardemail/forwardemail.net/blob/master/bree.js>
* Proxy - <https://github.com/forwardemail/forwardemail.net/blob/master/proxy.js>
Additionally you can also refer to [Lad][] usage:
* API - <https://github.com/ladjs/lad/blob/master/template/api.js>
* Web - <https://github.com/ladjs/lad/blob/master/template/web.js>
* Bull - <https://github.com/ladjs/lad/blob/master/template/bull.js>
* Bree - <https://github.com/ladjs/lad/blob/master/template/bree.js>
* Proxy - <https://github.com/ladjs/lad/blob/master/template/proxy.js>

@@ -72,2 +202,3 @@

| **Nicholai Nissen** | <https://nicholai.dev> |
| **Spencer Snyder** | <https://spencersnyder.io> |

@@ -84,4 +215,6 @@

[yarn]: https://yarnpkg.com/
[lad]: https://lad.js.org
[lad]: https://lad.js.org
[lil-http-terminator]: https://github.com/flash-oss/lil-http-terminator
[cabin]: https://cabinjs.com
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc