UNPKG

exstack

Version:

A utility library designed to simplify and enhance express.js applications.

91 lines (90 loc) 3.42 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); //#region src/serve.ts /** * Start an Express app or HTTP/HTTPS server with graceful shutdown support * * @param app - Express application or HTTP/HTTPS server instance * @param options - Server configuration options * @returns HTTP/HTTPS server instance * * @example * ```typescript * // Basic usage with Express * const app = express(); * serve(app, { port: 3000 }); * * // With HTTPS * const httpsServer = https.createServer({ cert, key }, app); * serve(httpsServer, { port: 443 }); * * // Custom graceful shutdown timeout (10 seconds) * serve(app, { port: 3000, gracefulShutdown: 10 }); * * // Disable graceful shutdown * serve(app, { gracefulShutdown: false }); * ``` * * @remarks * Graceful shutdown behavior: * - On SIGINT/SIGTERM: stops accepting new connections, waits for active requests * - Shows countdown timer with remaining time * - Press Ctrl+C again to force close immediately * - After timeout: automatically force closes all connections * - Long-running connections (SSE, WebSocket) will be force closed on timeout */ const serve = (app, options = {}) => { const port = options.port ?? (Number.parseInt(process.env.PORT || "") || 3e3); const hostname = options.host ?? process.env.HOST ?? "localhost"; const gracefulConfig = options.gracefulShutdown ?? true; const silent = options.silent ?? false; const connections = /* @__PURE__ */ new Set(); let closeCalled = false; let isShuttingDown = false; const server = app.listen(port, hostname, () => { if (!silent) { const url = `http://${hostname}:${port}/`; console.log(`\x1b[32m➜ Listening on:\x1b[0m \x1b[36m${url}\x1b[0m`); } }); server.on("connection", (socket) => { connections.add(socket); socket.on("close", () => connections.delete(socket)); }); if (gracefulConfig !== false && !(typeof gracefulConfig === "number" && gracefulConfig <= 0) && !process.env.CI && !process.env.TEST) { const gracefulTimeout = typeof gracefulConfig === "number" ? gracefulConfig : Number.parseInt(process.env.SERVER_SHUTDOWN_TIMEOUT || "") || 5; const closeServer = () => new Promise((resolve, reject) => { if (closeCalled) return resolve(); closeCalled = true; server.close((err) => err ? reject(err) : resolve()); }); const forceClose = async () => { process.stderr.write("\x1B[31m\x1B[2K\rForcibly closing connections...\n\x1B[0m"); for (const socket of connections) socket.destroy(); await closeServer(); process.stderr.write("\x1B[33mServer closed.\n\x1B[0m"); process.exit(0); }; const shutdown = async () => { if (isShuttingDown) { await forceClose(); return; } isShuttingDown = true; const closePromise = closeServer(); for (let remaining = gracefulTimeout; remaining > 0; remaining--) { process.stderr.write(`\x1b[90m\rStopping server gracefully (${remaining}s)... Press \x1b[1mCtrl+C\x1b[0m\x1b[90m again to force close.\x1b[0m`); if (await Promise.race([closePromise.then(() => true), new Promise((r) => setTimeout(() => r(false), 1e3))])) { process.stderr.write("\x1B[2K\r\x1B[32mServer closed successfully.\n\x1B[0m"); process.exit(0); } } process.stderr.write("\x1B[2K\rGraceful shutdown timed out.\n"); await forceClose(); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); } return server; }; //#endregion exports.serve = serve;