@ecdt/server-common 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecdt/server-common",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Conjunto de ferramentas e configurações comuns nos servidores da Econodata",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const bodyParser = require("body-parser");
2
2
  const { devMktTokenSanitaze } = require("./services/tokenService.js");
3
3
  const { expressCors } = require("./services/corsService.js");
4
+ const { setupGracefulShutdown } = require("./services/shutdownService.js");
4
5
 
5
6
  function expressCommonMiddlewares({ cookieName } = {}){
6
7
  return [bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}), devMktTokenSanitaze({ cookieName })];
@@ -9,4 +10,4 @@ function expressCommonMiddlewares({ cookieName } = {}){
9
10
 
10
11
 
11
12
 
12
- module.exports = { expressCommonMiddlewares, expressCors }
13
+ module.exports = { expressCommonMiddlewares, expressCors, setupGracefulShutdown }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Graceful shutdown padrão dos microsserviços da Econodata em Kubernetes.
3
+ * Extraído do dev-mkt-busca: o preStop (20s) já tirou o pod do EndpointSlice
4
+ * antes do SIGTERM, então aqui só drenamos as conexões em voo e encerramos o
5
+ * processo antes do SIGKILL.
6
+ *
7
+ * @param {import('http').Server} server - servidor retornado por http.createServer(app).listen(...) ou app.listen(...)
8
+ * @param {object} [options]
9
+ * @param {() => (Promise<void>|void)} [options.onShutdown] - cleanup extra (fechar pools, Redis, etc.) executado após o server fechar
10
+ * @param {number} [options.failsafeTimeoutMs] - tempo máximo do graceful antes de forçar a saída (default 10s: grace 40s − 20s de preStop)
11
+ */
12
+ function setupGracefulShutdown(server, { onShutdown, failsafeTimeoutMs = 10 * 1000 } = {}) {
13
+ let shuttingDown = false;
14
+ const gracefulShutdown = signal => {
15
+ if (shuttingDown) return;
16
+ shuttingDown = true;
17
+ console.warn(JSON.stringify({
18
+ severity: 'WARNING',
19
+ message: `[shutdown] ${signal} recebido — encerrando`,
20
+ }));
21
+
22
+ server.close(async err => {
23
+ if (err) {
24
+ console.error('[shutdown] Erro ao fechar o server HTTP:', err.message || err);
25
+ } else {
26
+ console.warn(JSON.stringify({
27
+ severity: 'WARNING',
28
+ message: '[shutdown] Server HTTP fechado, conexões em voo drenadas',
29
+ }));
30
+ }
31
+ if (onShutdown) {
32
+ try {
33
+ await onShutdown();
34
+ } catch (cleanupErr) {
35
+ console.warn(JSON.stringify({
36
+ severity: 'WARNING',
37
+ message: `[shutdown] Falha no cleanup: ${cleanupErr.message || cleanupErr}`,
38
+ }));
39
+ }
40
+ }
41
+ process.exit(err ? 1 : 0);
42
+ });
43
+
44
+ // fecha conexões keep-alive ociosas, senão um keepAliveTimeout alto segura o close()
45
+ server.closeIdleConnections();
46
+
47
+ // failsafe antes do SIGKILL (grace 40s − 20s de preStop)
48
+ setTimeout(() => {
49
+ console.error('[shutdown] Timeout no graceful shutdown, forçando saída');
50
+ process.exit(1);
51
+ }, failsafeTimeoutMs).unref();
52
+ };
53
+
54
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
55
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
56
+ }
57
+
58
+ module.exports = { setupGracefulShutdown };