@zintrust/core 1.5.4 → 1.5.5

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/bin/z.js CHANGED
@@ -18,23 +18,90 @@ const nodeArgs = target.endsWith('.ts')
18
18
  ? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
19
19
  : [target, ...process.argv.slice(2)];
20
20
  const child = spawn(process.execPath, nodeArgs, {
21
- stdio: 'inherit',
21
+ stdio: ['inherit', 'pipe', 'pipe'],
22
22
  env: process.env,
23
23
  });
24
+ child.stdout?.on('data', (chunk) => {
25
+ process.stdout.write(chunk);
26
+ });
27
+ child.stderr?.on('data', (chunk) => {
28
+ process.stderr.write(chunk);
29
+ });
30
+ let childClosed = false;
31
+ let delayedSignalTimer;
32
+ const clearDelayedSignal = () => {
33
+ if (delayedSignalTimer === undefined)
34
+ return;
35
+ clearTimeout(delayedSignalTimer);
36
+ delayedSignalTimer = undefined;
37
+ };
38
+ const forwardSignal = (signal) => {
39
+ if (childClosed)
40
+ return;
41
+ try {
42
+ child.kill(signal);
43
+ }
44
+ catch {
45
+ // best-effort
46
+ }
47
+ };
48
+ const scheduleSignalForward = (signal) => {
49
+ if (childClosed || delayedSignalTimer !== undefined)
50
+ return;
51
+ delayedSignalTimer = globalThis.setTimeout(() => {
52
+ delayedSignalTimer = undefined;
53
+ forwardSignal(signal);
54
+ }, 1500);
55
+ delayedSignalTimer.unref?.();
56
+ };
57
+ const onSigint = () => {
58
+ if (process.stdin.isTTY === true) {
59
+ scheduleSignalForward('SIGINT');
60
+ return;
61
+ }
62
+ forwardSignal('SIGINT');
63
+ };
64
+ const onSigterm = () => {
65
+ if (process.stdin.isTTY === true) {
66
+ scheduleSignalForward('SIGTERM');
67
+ return;
68
+ }
69
+ forwardSignal('SIGTERM');
70
+ };
71
+ process.on('SIGINT', onSigint);
72
+ process.on('SIGTERM', onSigterm);
24
73
  const result = await new Promise((resolve, reject) => {
25
74
  let settled = false;
26
- const finalize = (exitCode, signal) => {
75
+ let childResult = {
76
+ exitCode: null,
77
+ signal: null,
78
+ };
79
+ const finalize = () => {
27
80
  if (settled) {
28
81
  return;
29
82
  }
30
83
  settled = true;
84
+ childClosed = true;
85
+ clearDelayedSignal();
86
+ process.off('SIGINT', onSigint);
87
+ process.off('SIGTERM', onSigterm);
31
88
  child.off?.('error', reject);
32
- child.off?.('exit', finalize);
33
- child.off?.('close', finalize);
34
- resolve({ exitCode, signal });
89
+ child.off?.('exit', handleExit);
90
+ child.off?.('close', handleClose);
91
+ resolve(childResult);
92
+ };
93
+ const handleExit = (exitCode, signal) => {
94
+ childResult = { exitCode, signal };
95
+ };
96
+ const handleClose = (exitCode, signal) => {
97
+ childResult = {
98
+ exitCode: childResult.exitCode ?? exitCode,
99
+ signal: childResult.signal ?? signal,
100
+ };
101
+ finalize();
35
102
  };
36
103
  child.once('error', reject);
37
- child.once('exit', finalize);
38
- child.once('close', finalize);
104
+ child.once('exit', handleExit);
105
+ child.once('close', handleClose);
39
106
  });
40
107
  process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
package/bin/zin.js CHANGED
@@ -8,6 +8,21 @@ import { existsSync } from 'node:fs';
8
8
  import { createRequire } from 'node:module';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
+ const CLI_SPAWN_TRACE_ENV_KEYS = ['CLI_SPAWN_TRACE', 'ZIN_SPAWN_TRACE'];
12
+ const isCliSpawnTraceEnabled = () => {
13
+ return CLI_SPAWN_TRACE_ENV_KEYS.some((key) => {
14
+ const raw = process.env[key];
15
+ if (typeof raw !== 'string')
16
+ return false;
17
+ const normalized = raw.trim().toLowerCase();
18
+ return (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on');
19
+ });
20
+ };
21
+ const writeCliSpawnTrace = (label, details = {}) => {
22
+ if (!isCliSpawnTraceEnabled())
23
+ return;
24
+ process.stderr.write(`${JSON.stringify({ trace: 'cli-wrapper', label, pid: process.pid, details })}\n`);
25
+ };
11
26
  const here = path.dirname(fileURLToPath(import.meta.url));
12
27
  const require = createRequire(import.meta.url);
13
28
  const tsTarget = path.join(here, 'zintrust-main.ts');
@@ -18,23 +33,165 @@ const nodeArgs = target.endsWith('.ts')
18
33
  ? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
19
34
  : [target, ...process.argv.slice(2)];
20
35
  const child = spawn(process.execPath, nodeArgs, {
21
- stdio: 'inherit',
36
+ stdio: ['inherit', 'pipe', 'pipe'],
22
37
  env: process.env,
23
38
  });
39
+ writeCliSpawnTrace('wrapper.child.started', {
40
+ childPid: child.pid,
41
+ command: process.execPath,
42
+ args: nodeArgs,
43
+ });
44
+ child.stdout?.on('data', (chunk) => {
45
+ writeCliSpawnTrace('wrapper.child.stdout.data', {
46
+ childPid: child.pid,
47
+ bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
48
+ });
49
+ process.stdout.write(chunk);
50
+ });
51
+ child.stdout?.on('end', () => {
52
+ writeCliSpawnTrace('wrapper.child.stdout.end', {
53
+ childPid: child.pid,
54
+ });
55
+ });
56
+ child.stdout?.on('close', () => {
57
+ writeCliSpawnTrace('wrapper.child.stdout.close', {
58
+ childPid: child.pid,
59
+ });
60
+ });
61
+ child.stderr?.on('data', (chunk) => {
62
+ writeCliSpawnTrace('wrapper.child.stderr.data', {
63
+ childPid: child.pid,
64
+ bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
65
+ });
66
+ process.stderr.write(chunk);
67
+ });
68
+ child.stderr?.on('end', () => {
69
+ writeCliSpawnTrace('wrapper.child.stderr.end', {
70
+ childPid: child.pid,
71
+ });
72
+ });
73
+ child.stderr?.on('close', () => {
74
+ writeCliSpawnTrace('wrapper.child.stderr.close', {
75
+ childPid: child.pid,
76
+ });
77
+ });
78
+ let childClosed = false;
79
+ let delayedSignalTimer;
80
+ const clearDelayedSignal = () => {
81
+ if (delayedSignalTimer === undefined)
82
+ return;
83
+ clearTimeout(delayedSignalTimer);
84
+ delayedSignalTimer = undefined;
85
+ };
86
+ const forwardSignal = (signal) => {
87
+ if (childClosed)
88
+ return;
89
+ try {
90
+ writeCliSpawnTrace('wrapper.signal.forward.attempt', {
91
+ childPid: child.pid,
92
+ signal,
93
+ });
94
+ child.kill(signal);
95
+ writeCliSpawnTrace('wrapper.signal.forward.complete', {
96
+ childPid: child.pid,
97
+ signal,
98
+ });
99
+ }
100
+ catch {
101
+ // best-effort
102
+ }
103
+ };
104
+ const scheduleSignalForward = (signal) => {
105
+ if (childClosed || delayedSignalTimer !== undefined)
106
+ return;
107
+ delayedSignalTimer = globalThis.setTimeout(() => {
108
+ delayedSignalTimer = undefined;
109
+ writeCliSpawnTrace('wrapper.signal.delay.fire', {
110
+ childPid: child.pid,
111
+ signal,
112
+ });
113
+ forwardSignal(signal);
114
+ }, 1500);
115
+ writeCliSpawnTrace('wrapper.signal.delay.schedule', {
116
+ childPid: child.pid,
117
+ signal,
118
+ delayMs: 1500,
119
+ });
120
+ delayedSignalTimer.unref?.();
121
+ };
122
+ const onSigint = () => {
123
+ writeCliSpawnTrace('wrapper.signal.received', {
124
+ childPid: child.pid,
125
+ signal: 'SIGINT',
126
+ tty: process.stdin.isTTY === true,
127
+ });
128
+ if (process.stdin.isTTY === true) {
129
+ scheduleSignalForward('SIGINT');
130
+ return;
131
+ }
132
+ forwardSignal('SIGINT');
133
+ };
134
+ const onSigterm = () => {
135
+ writeCliSpawnTrace('wrapper.signal.received', {
136
+ childPid: child.pid,
137
+ signal: 'SIGTERM',
138
+ tty: process.stdin.isTTY === true,
139
+ });
140
+ if (process.stdin.isTTY === true) {
141
+ scheduleSignalForward('SIGTERM');
142
+ return;
143
+ }
144
+ forwardSignal('SIGTERM');
145
+ };
146
+ process.on('SIGINT', onSigint);
147
+ process.on('SIGTERM', onSigterm);
24
148
  const result = await new Promise((resolve, reject) => {
25
149
  let settled = false;
26
- const finalize = (exitCode, signal) => {
150
+ let childResult = {
151
+ exitCode: null,
152
+ signal: null,
153
+ };
154
+ const finalize = () => {
27
155
  if (settled) {
28
156
  return;
29
157
  }
30
158
  settled = true;
159
+ writeCliSpawnTrace('wrapper.child.finalize', {
160
+ childPid: child.pid,
161
+ exitCode: childResult.exitCode,
162
+ signal: childResult.signal,
163
+ });
164
+ childClosed = true;
165
+ clearDelayedSignal();
166
+ process.off('SIGINT', onSigint);
167
+ process.off('SIGTERM', onSigterm);
31
168
  child.off?.('error', reject);
32
- child.off?.('exit', finalize);
33
- child.off?.('close', finalize);
34
- resolve({ exitCode, signal });
169
+ child.off?.('exit', handleExit);
170
+ child.off?.('close', handleClose);
171
+ resolve(childResult);
172
+ };
173
+ const handleExit = (exitCode, signal) => {
174
+ writeCliSpawnTrace('wrapper.child.exit', {
175
+ childPid: child.pid,
176
+ exitCode,
177
+ signal,
178
+ });
179
+ childResult = { exitCode, signal };
180
+ };
181
+ const handleClose = (exitCode, signal) => {
182
+ writeCliSpawnTrace('wrapper.child.close', {
183
+ childPid: child.pid,
184
+ exitCode,
185
+ signal,
186
+ });
187
+ childResult = {
188
+ exitCode: childResult.exitCode ?? exitCode,
189
+ signal: childResult.signal ?? signal,
190
+ };
191
+ finalize();
35
192
  };
36
193
  child.once('error', reject);
37
- child.once('exit', finalize);
38
- child.once('close', finalize);
194
+ child.once('exit', handleExit);
195
+ child.once('close', handleClose);
39
196
  });
40
197
  process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
@@ -1 +1 @@
1
- {"version":3,"file":"zintrust.d.ts","sourceRoot":"","sources":["../../bin/zintrust.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AA8CH,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"zintrust.d.ts","sourceRoot":"","sources":["../../bin/zintrust.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AA6HH,OAAO,EAAE,CAAC"}
package/bin/zintrust.js CHANGED
@@ -21,23 +21,90 @@ const nodeArgs = target.endsWith('.ts')
21
21
  ? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
22
22
  : [target, ...process.argv.slice(2)];
23
23
  const child = spawn(process.execPath, nodeArgs, {
24
- stdio: 'inherit',
24
+ stdio: ['inherit', 'pipe', 'pipe'],
25
25
  env: process.env,
26
26
  });
27
+ child.stdout?.on('data', (chunk) => {
28
+ process.stdout.write(chunk);
29
+ });
30
+ child.stderr?.on('data', (chunk) => {
31
+ process.stderr.write(chunk);
32
+ });
33
+ let childClosed = false;
34
+ let delayedSignalTimer;
35
+ const clearDelayedSignal = () => {
36
+ if (delayedSignalTimer === undefined)
37
+ return;
38
+ clearTimeout(delayedSignalTimer);
39
+ delayedSignalTimer = undefined;
40
+ };
41
+ const forwardSignal = (signal) => {
42
+ if (childClosed)
43
+ return;
44
+ try {
45
+ child.kill(signal);
46
+ }
47
+ catch {
48
+ // best-effort
49
+ }
50
+ };
51
+ const scheduleSignalForward = (signal) => {
52
+ if (childClosed || delayedSignalTimer !== undefined)
53
+ return;
54
+ delayedSignalTimer = globalThis.setTimeout(() => {
55
+ delayedSignalTimer = undefined;
56
+ forwardSignal(signal);
57
+ }, 1500);
58
+ delayedSignalTimer.unref?.();
59
+ };
60
+ const onSigint = () => {
61
+ if (process.stdin.isTTY === true) {
62
+ scheduleSignalForward('SIGINT');
63
+ return;
64
+ }
65
+ forwardSignal('SIGINT');
66
+ };
67
+ const onSigterm = () => {
68
+ if (process.stdin.isTTY === true) {
69
+ scheduleSignalForward('SIGTERM');
70
+ return;
71
+ }
72
+ forwardSignal('SIGTERM');
73
+ };
74
+ process.on('SIGINT', onSigint);
75
+ process.on('SIGTERM', onSigterm);
27
76
  const result = await new Promise((resolve, reject) => {
28
77
  let settled = false;
29
- const finalize = (exitCode, signal) => {
78
+ let childResult = {
79
+ exitCode: null,
80
+ signal: null,
81
+ };
82
+ const finalize = () => {
30
83
  if (settled) {
31
84
  return;
32
85
  }
33
86
  settled = true;
87
+ childClosed = true;
88
+ clearDelayedSignal();
89
+ process.off('SIGINT', onSigint);
90
+ process.off('SIGTERM', onSigterm);
34
91
  child.off?.('error', reject);
35
- child.off?.('exit', finalize);
36
- child.off?.('close', finalize);
37
- resolve({ exitCode, signal });
92
+ child.off?.('exit', handleExit);
93
+ child.off?.('close', handleClose);
94
+ resolve(childResult);
95
+ };
96
+ const handleExit = (exitCode, signal) => {
97
+ childResult = { exitCode, signal };
98
+ };
99
+ const handleClose = (exitCode, signal) => {
100
+ childResult = {
101
+ exitCode: childResult.exitCode ?? exitCode,
102
+ signal: childResult.signal ?? signal,
103
+ };
104
+ finalize();
38
105
  };
39
106
  child.once('error', reject);
40
- child.once('exit', finalize);
41
- child.once('close', finalize);
107
+ child.once('exit', handleExit);
108
+ child.once('close', handleClose);
42
109
  });
43
110
  process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
package/bin/zt.js CHANGED
@@ -15,13 +15,90 @@ const nodeArgs = target.endsWith('.ts')
15
15
  ? ['--import', 'tsx', target, ...process.argv.slice(2)]
16
16
  : [target, ...process.argv.slice(2)];
17
17
  const child = spawn(process.execPath, nodeArgs, {
18
- stdio: 'inherit',
18
+ stdio: ['inherit', 'pipe', 'pipe'],
19
19
  env: process.env,
20
20
  });
21
+ child.stdout?.on('data', (chunk) => {
22
+ process.stdout.write(chunk);
23
+ });
24
+ child.stderr?.on('data', (chunk) => {
25
+ process.stderr.write(chunk);
26
+ });
27
+ let childClosed = false;
28
+ let delayedSignalTimer;
29
+ const clearDelayedSignal = () => {
30
+ if (delayedSignalTimer === undefined)
31
+ return;
32
+ clearTimeout(delayedSignalTimer);
33
+ delayedSignalTimer = undefined;
34
+ };
35
+ const forwardSignal = (signal) => {
36
+ if (childClosed)
37
+ return;
38
+ try {
39
+ child.kill(signal);
40
+ }
41
+ catch {
42
+ // best-effort
43
+ }
44
+ };
45
+ const scheduleSignalForward = (signal) => {
46
+ if (childClosed || delayedSignalTimer !== undefined)
47
+ return;
48
+ delayedSignalTimer = globalThis.setTimeout(() => {
49
+ delayedSignalTimer = undefined;
50
+ forwardSignal(signal);
51
+ }, 1500);
52
+ delayedSignalTimer.unref?.();
53
+ };
54
+ const onSigint = () => {
55
+ if (process.stdin.isTTY === true) {
56
+ scheduleSignalForward('SIGINT');
57
+ return;
58
+ }
59
+ forwardSignal('SIGINT');
60
+ };
61
+ const onSigterm = () => {
62
+ if (process.stdin.isTTY === true) {
63
+ scheduleSignalForward('SIGTERM');
64
+ return;
65
+ }
66
+ forwardSignal('SIGTERM');
67
+ };
68
+ process.on('SIGINT', onSigint);
69
+ process.on('SIGTERM', onSigterm);
21
70
  const result = await new Promise((resolve, reject) => {
71
+ let settled = false;
72
+ let childResult = {
73
+ exitCode: null,
74
+ signal: null,
75
+ };
76
+ const finalize = () => {
77
+ if (settled) {
78
+ return;
79
+ }
80
+ settled = true;
81
+ childClosed = true;
82
+ clearDelayedSignal();
83
+ process.off('SIGINT', onSigint);
84
+ process.off('SIGTERM', onSigterm);
85
+ child.off?.('error', reject);
86
+ child.off?.('exit', handleExit);
87
+ child.off?.('close', handleClose);
88
+ resolve(childResult);
89
+ };
90
+ const handleExit = (exitCode, signal) => {
91
+ childResult = { exitCode, signal };
92
+ };
93
+ const handleClose = (exitCode, signal) => {
94
+ childResult = {
95
+ exitCode: childResult.exitCode ?? exitCode,
96
+ signal: childResult.signal ?? signal,
97
+ };
98
+ finalize();
99
+ };
22
100
  child.once('error', reject);
23
- child.once('close', (exitCode, signal) => {
24
- resolve({ exitCode, signal });
25
- });
101
+ child.once('exit', handleExit);
102
+ child.once('close', handleClose);
26
103
  });
27
104
  process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyWH,eAAO,MAAM,cAAc,eAAmB,CAAC"}
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgbH,eAAO,MAAM,cAAc,eAAmB,CAAC"}
@@ -11,6 +11,7 @@ import { Env } from '../config/env.js';
11
11
  import { Logger } from '../config/logger.js';
12
12
  import { shutdownRedisConnections } from '../config/workers.js';
13
13
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
14
+ import { ShutdownTrace } from '../helper/ShutdownTrace.js';
14
15
  import { ProjectRuntime } from '../runtime/ProjectRuntime.js';
15
16
  import { StartupErrorLogging } from '../runtime/StartupErrorLogging.js';
16
17
  import { WorkerProjectAutoImports } from '../runtime/WorkerProjectAutoImports.js';
@@ -92,66 +93,130 @@ const withTimeout = async (promise, timeoutMs, label) => {
92
93
  globalThis.clearTimeout(timeoutId);
93
94
  }
94
95
  };
96
+ const shutdownWorkersIfNeeded = async (signal, remainingMs) => {
97
+ if (appConfig.worker !== true ||
98
+ (appConfig.detectRuntime() !== 'nodejs' && appConfig.detectRuntime() !== 'lambda') ||
99
+ appConfig.dockerWorker === true) {
100
+ return;
101
+ }
102
+ try {
103
+ ShutdownTrace.log('bootstrap.graceful-shutdown.worker-shutdown.start', {
104
+ signal,
105
+ remainingMs: remainingMs(),
106
+ });
107
+ const workers = await loadWorkersModule();
108
+ const workerBudgetMs = Math.min(15000, remainingMs());
109
+ await withTimeout(workers.WorkerShutdown.shutdown({
110
+ signal,
111
+ timeout: workerBudgetMs,
112
+ forceExit: false,
113
+ }), workerBudgetMs, 'Worker shutdown timed out');
114
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.worker-shutdown.complete', {
115
+ workerBudgetMs,
116
+ remainingMs: remainingMs(),
117
+ });
118
+ }
119
+ catch (error) {
120
+ Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
121
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.worker-shutdown.failed', {
122
+ remainingMs: remainingMs(),
123
+ });
124
+ }
125
+ };
126
+ const shutdownServerIfNeeded = async (remainingMs) => {
127
+ if (serverInstance === undefined)
128
+ return;
129
+ ShutdownTrace.log('bootstrap.graceful-shutdown.server-close.start', {
130
+ remainingMs: remainingMs(),
131
+ });
132
+ await serverInstance.close();
133
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.server-close.complete', {
134
+ remainingMs: remainingMs(),
135
+ });
136
+ };
137
+ const shutdownAppIfNeeded = async (remainingMs) => {
138
+ if (appInstance === undefined)
139
+ return;
140
+ try {
141
+ const appBudgetMs = Math.min(5000, remainingMs());
142
+ ShutdownTrace.log('bootstrap.graceful-shutdown.app-shutdown.start', {
143
+ appBudgetMs,
144
+ remainingMs: remainingMs(),
145
+ });
146
+ await withTimeout(appInstance.shutdown(), appBudgetMs, 'App shutdown timed out');
147
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.app-shutdown.complete', {
148
+ appBudgetMs,
149
+ remainingMs: remainingMs(),
150
+ });
151
+ }
152
+ catch (error) {
153
+ Logger.warn('App shutdown failed or timed out, forcing exit', error);
154
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.app-shutdown.failed', {
155
+ remainingMs: remainingMs(),
156
+ });
157
+ }
158
+ };
159
+ const shutdownTrackedRedis = async (remainingMs) => {
160
+ try {
161
+ const redisBudgetMs = Math.max(250, Math.min(3000, remainingMs()));
162
+ ShutdownTrace.log('bootstrap.graceful-shutdown.redis-shutdown.start', {
163
+ redisBudgetMs,
164
+ remainingMs: remainingMs(),
165
+ });
166
+ await withTimeout(shutdownRedisConnections(), redisBudgetMs, 'Redis connection shutdown timed out');
167
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.redis-shutdown.complete', {
168
+ redisBudgetMs,
169
+ remainingMs: remainingMs(),
170
+ });
171
+ }
172
+ catch (error) {
173
+ Logger.warn('Redis connection shutdown failed (continuing with app shutdown)', error);
174
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.redis-shutdown.failed', {
175
+ remainingMs: remainingMs(),
176
+ });
177
+ }
178
+ };
179
+ const runGracefulShutdownPhases = async (signal, remainingMs) => {
180
+ await shutdownWorkersIfNeeded(signal, remainingMs);
181
+ await shutdownServerIfNeeded(remainingMs);
182
+ await shutdownAppIfNeeded(remainingMs);
183
+ await shutdownTrackedRedis(remainingMs);
184
+ Logger.info('✅ Application shut down successfully');
185
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.complete', {
186
+ remainingMs: remainingMs(),
187
+ });
188
+ };
95
189
  const gracefulShutdown = async (signal) => {
96
190
  if (isShuttingDown)
97
191
  return;
98
192
  isShuttingDown = true;
99
- const shutdownBudgetMs = Env.getInt('SHUTDOWN_TIMEOUT', 1500);
193
+ const shutdownBudgetMs = Env.getInt('SHUTDOWN_TIMEOUT', 10000);
100
194
  const minForceExitMs = shutdownBudgetMs + 250;
101
195
  const forceExitMs = Math.max(Env.getInt('SHUTDOWN_FORCE_EXIT_MS', 10000), minForceExitMs);
102
196
  const deadlineMs = Date.now() + shutdownBudgetMs;
103
197
  const remainingMs = () => Math.max(0, deadlineMs - Date.now());
104
198
  Logger.info(`${signal} received, shutting down gracefully...`);
199
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.received', {
200
+ signal,
201
+ shutdownBudgetMs,
202
+ forceExitMs,
203
+ workerEnabled: appConfig.worker,
204
+ });
105
205
  try {
106
206
  const forceExitTimer = globalThis.setTimeout(() => {
107
207
  process.exit(0);
108
208
  }, forceExitMs);
109
209
  // Best-effort: don't keep the process alive just for this timer
110
210
  forceExitTimer.unref?.();
111
- await withTimeout((async () => {
112
- // Shutdown worker management system FIRST (before database closes)
113
- if (appConfig.worker === true &&
114
- (appConfig.detectRuntime() === 'nodejs' || appConfig.detectRuntime() === 'lambda') &&
115
- appConfig.dockerWorker === false) {
116
- try {
117
- const workers = await loadWorkersModule();
118
- const workerBudgetMs = Math.min(15000, remainingMs());
119
- await withTimeout(workers.WorkerShutdown.shutdown({
120
- signal,
121
- timeout: workerBudgetMs,
122
- forceExit: false,
123
- }), workerBudgetMs, 'Worker shutdown timed out');
124
- }
125
- catch (error) {
126
- Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
127
- }
128
- }
129
- if (serverInstance !== undefined) {
130
- await serverInstance.close();
131
- }
132
- if (appInstance !== undefined) {
133
- try {
134
- const appBudgetMs = Math.min(5000, remainingMs());
135
- await withTimeout(appInstance.shutdown(), appBudgetMs, 'App shutdown timed out');
136
- }
137
- catch (error) {
138
- Logger.warn('App shutdown failed or timed out, forcing exit', error);
139
- }
140
- }
141
- try {
142
- const redisBudgetMs = Math.max(250, Math.min(3000, remainingMs()));
143
- await withTimeout(shutdownRedisConnections(), redisBudgetMs, 'Redis connection shutdown timed out');
144
- }
145
- catch (error) {
146
- Logger.warn('Redis connection shutdown failed (continuing with app shutdown)', error);
147
- }
148
- Logger.info('✅ Application shut down successfully');
149
- })(), shutdownBudgetMs, 'Graceful shutdown timed out');
211
+ await withTimeout(runGracefulShutdownPhases(signal, remainingMs), shutdownBudgetMs, 'Graceful shutdown timed out');
150
212
  globalThis.clearTimeout(forceExitTimer);
151
213
  process.exit(0);
152
214
  }
153
215
  catch (error) {
154
216
  Logger.error('Graceful shutdown failed:', error);
217
+ ShutdownTrace.logHandles('bootstrap.graceful-shutdown.error', {
218
+ error: error instanceof Error ? error.message : String(error),
219
+ });
155
220
  process.exit(1);
156
221
  }
157
222
  };
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/boot/registry/runtime.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AASvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AA+P9C,eAAO,MAAM,8BAA8B,GAAI,iBAAiB,gBAAgB,KAAG,IA6BlF,CAAC;AAwUF,eAAO,MAAM,eAAe,GAAI,QAAQ;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC,KAAG;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAyF7D,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/boot/registry/runtime.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAUvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAkQ9C,eAAO,MAAM,8BAA8B,GAAI,iBAAiB,gBAAgB,KAAG,IA6BlF,CAAC;AA8UF,eAAO,MAAM,eAAe,GAAI,QAAQ;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC,KAAG;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAsH7D,CAAC"}