@ladjs/graceful
Advanced tools
Comparing version 2.0.1 to 3.0.0
@@ -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 @@ |
150
index.js
@@ -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" | ||
} |
157
README.md
# [**@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 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
20209
253
215
2
22
1
+ Addedlil-http-terminator@^1.2.2
+ Addedp-is-promise@3
+ Addedlil-http-terminator@1.2.3(transitive)
+ Addedp-is-promise@3.0.0(transitive)
- Removeddebug@^4.3.4
- Removeddebug@4.3.7(transitive)
- Removedms@2.1.3(transitive)