@zintrust/core 1.5.4 → 1.6.0

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.
@@ -0,0 +1,6 @@
1
+ type RunCliWrapperInput = {
2
+ traceName?: string;
3
+ };
4
+ export declare const runCliWrapper: (input?: RunCliWrapperInput) => Promise<void>;
5
+ export {};
6
+ //# sourceMappingURL=launcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../bin/launcher.ts"],"names":[],"mappings":"AAMA,KAAK,kBAAkB,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA6LF,eAAO,MAAM,aAAa,GAAU,QAAO,kBAAuB,KAAG,OAAO,CAAC,IAAI,CAoBhF,CAAC"}
@@ -0,0 +1,167 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ const CLI_SPAWN_TRACE_ENV_KEYS = ['CLI_SPAWN_TRACE', 'ZIN_SPAWN_TRACE'];
7
+ const here = path.dirname(fileURLToPath(import.meta.url));
8
+ const require = createRequire(import.meta.url);
9
+ const isCliSpawnTraceEnabled = () => {
10
+ return CLI_SPAWN_TRACE_ENV_KEYS.some((key) => {
11
+ const raw = process.env[key];
12
+ if (typeof raw !== 'string')
13
+ return false;
14
+ const normalized = raw.trim().toLowerCase();
15
+ return (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on');
16
+ });
17
+ };
18
+ const writeCliSpawnTrace = (input, label, details = {}) => {
19
+ if (input.traceName === undefined || !isCliSpawnTraceEnabled())
20
+ return;
21
+ process.stderr.write(`${JSON.stringify({ trace: input.traceName, label, pid: process.pid, details })}\n`);
22
+ };
23
+ const resolveNodeArgs = () => {
24
+ const tsTarget = path.join(here, 'zintrust-main.ts');
25
+ const jsTarget = path.join(here, 'zintrust-main.js');
26
+ const target = existsSync(tsTarget) ? tsTarget : jsTarget;
27
+ if (!target.endsWith('.ts')) {
28
+ return [target, ...process.argv.slice(2)];
29
+ }
30
+ return ['--import', require.resolve('tsx'), target, ...process.argv.slice(2)];
31
+ };
32
+ const getExitCode = (exitCode, signal) => {
33
+ if (typeof exitCode === 'number')
34
+ return exitCode;
35
+ if (signal === 'SIGINT' || signal === 'SIGTERM')
36
+ return 0;
37
+ return 1;
38
+ };
39
+ const attachOutputRelay = (input, child, stream) => {
40
+ const childStream = child[stream];
41
+ const targetStream = stream === 'stdout' ? process.stdout : process.stderr;
42
+ childStream?.on('data', (chunk) => {
43
+ writeCliSpawnTrace(input, `wrapper.child.${stream}.data`, {
44
+ childPid: child.pid,
45
+ bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
46
+ });
47
+ targetStream.write(chunk);
48
+ });
49
+ };
50
+ const waitForChildClose = async (input, child, onBeforeResolve) => {
51
+ return new Promise((resolve, reject) => {
52
+ let settled = false;
53
+ let childResult = {
54
+ exitCode: null,
55
+ signal: null,
56
+ };
57
+ const finalize = () => {
58
+ if (settled)
59
+ return;
60
+ settled = true;
61
+ onBeforeResolve();
62
+ child.off?.('error', reject);
63
+ child.off?.('exit', handleExit);
64
+ child.off?.('close', handleClose);
65
+ writeCliSpawnTrace(input, 'wrapper.child.finalize', {
66
+ childPid: child.pid,
67
+ exitCode: childResult.exitCode,
68
+ signal: childResult.signal,
69
+ });
70
+ resolve(childResult);
71
+ };
72
+ const handleExit = (exitCode, signal) => {
73
+ childResult = { exitCode, signal };
74
+ };
75
+ const handleClose = (exitCode, signal) => {
76
+ childResult = {
77
+ exitCode: childResult.exitCode ?? exitCode,
78
+ signal: childResult.signal ?? signal,
79
+ };
80
+ finalize();
81
+ };
82
+ child.once('error', reject);
83
+ child.once('exit', handleExit);
84
+ child.once('close', handleClose);
85
+ });
86
+ };
87
+ const registerSignalHandlers = (input, child) => {
88
+ let childClosed = false;
89
+ let delayedSignalTimer;
90
+ const clearDelayedSignal = () => {
91
+ if (delayedSignalTimer === undefined)
92
+ return;
93
+ clearTimeout(delayedSignalTimer);
94
+ delayedSignalTimer = undefined;
95
+ };
96
+ const forwardSignal = (signal) => {
97
+ if (childClosed)
98
+ return;
99
+ try {
100
+ writeCliSpawnTrace(input, 'wrapper.signal.forward', {
101
+ childPid: child.pid,
102
+ signal,
103
+ });
104
+ child.kill(signal);
105
+ }
106
+ catch {
107
+ // best-effort
108
+ }
109
+ };
110
+ const scheduleSignalForward = (signal) => {
111
+ if (childClosed || delayedSignalTimer !== undefined)
112
+ return;
113
+ delayedSignalTimer = globalThis.setTimeout(() => {
114
+ delayedSignalTimer = undefined;
115
+ writeCliSpawnTrace(input, 'wrapper.signal.delay.fire', {
116
+ childPid: child.pid,
117
+ signal,
118
+ });
119
+ forwardSignal(signal);
120
+ }, 1500);
121
+ writeCliSpawnTrace(input, 'wrapper.signal.delay.schedule', {
122
+ childPid: child.pid,
123
+ signal,
124
+ delayMs: 1500,
125
+ });
126
+ delayedSignalTimer.unref?.();
127
+ };
128
+ const onSigint = () => {
129
+ if (process.stdin.isTTY === true) {
130
+ scheduleSignalForward('SIGINT');
131
+ return;
132
+ }
133
+ forwardSignal('SIGINT');
134
+ };
135
+ const onSigterm = () => {
136
+ if (process.stdin.isTTY === true) {
137
+ scheduleSignalForward('SIGTERM');
138
+ return;
139
+ }
140
+ forwardSignal('SIGTERM');
141
+ };
142
+ process.on('SIGINT', onSigint);
143
+ process.on('SIGTERM', onSigterm);
144
+ return () => {
145
+ childClosed = true;
146
+ clearDelayedSignal();
147
+ process.off('SIGINT', onSigint);
148
+ process.off('SIGTERM', onSigterm);
149
+ };
150
+ };
151
+ export const runCliWrapper = async (input = {}) => {
152
+ const nodeArgs = resolveNodeArgs();
153
+ const child = spawn(process.execPath, nodeArgs, {
154
+ stdio: ['inherit', 'pipe', 'pipe'],
155
+ env: process.env,
156
+ });
157
+ writeCliSpawnTrace(input, 'wrapper.child.started', {
158
+ childPid: child.pid,
159
+ command: process.execPath,
160
+ args: nodeArgs,
161
+ });
162
+ attachOutputRelay(input, child, 'stdout');
163
+ attachOutputRelay(input, child, 'stderr');
164
+ const unregisterSignalHandlers = registerSignalHandlers(input, child);
165
+ const result = await waitForChildClose(input, child, unregisterSignalHandlers);
166
+ process.exit(getExitCode(result.exitCode, result.signal));
167
+ };
package/bin/z.js CHANGED
@@ -3,38 +3,5 @@
3
3
  * ZinTrust CLI Shortcut - 'z'
4
4
  * Mirrors bin/zintrust.ts for convenience
5
5
  */
6
- import { spawn } from 'node:child_process';
7
- import { existsSync } from 'node:fs';
8
- import { createRequire } from 'node:module';
9
- import path from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
- const here = path.dirname(fileURLToPath(import.meta.url));
12
- const require = createRequire(import.meta.url);
13
- const tsTarget = path.join(here, 'zintrust-main.ts');
14
- const jsTarget = path.join(here, 'zintrust-main.js');
15
- const target = existsSync(tsTarget) ? tsTarget : jsTarget;
16
- const tsxImportPath = require.resolve('tsx');
17
- const nodeArgs = target.endsWith('.ts')
18
- ? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
19
- : [target, ...process.argv.slice(2)];
20
- const child = spawn(process.execPath, nodeArgs, {
21
- stdio: 'inherit',
22
- env: process.env,
23
- });
24
- const result = await new Promise((resolve, reject) => {
25
- let settled = false;
26
- const finalize = (exitCode, signal) => {
27
- if (settled) {
28
- return;
29
- }
30
- settled = true;
31
- child.off?.('error', reject);
32
- child.off?.('exit', finalize);
33
- child.off?.('close', finalize);
34
- resolve({ exitCode, signal });
35
- };
36
- child.once('error', reject);
37
- child.once('exit', finalize);
38
- child.once('close', finalize);
39
- });
40
- process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
6
+ import { runCliWrapper } from './launcher.js';
7
+ await runCliWrapper();
package/bin/zin.js CHANGED
@@ -3,38 +3,5 @@
3
3
  * ZinTrust CLI Shortcut - 'zin'
4
4
  * Mirrors bin/zintrust.ts for convenience
5
5
  */
6
- import { spawn } from 'node:child_process';
7
- import { existsSync } from 'node:fs';
8
- import { createRequire } from 'node:module';
9
- import path from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
- const here = path.dirname(fileURLToPath(import.meta.url));
12
- const require = createRequire(import.meta.url);
13
- const tsTarget = path.join(here, 'zintrust-main.ts');
14
- const jsTarget = path.join(here, 'zintrust-main.js');
15
- const target = existsSync(tsTarget) ? tsTarget : jsTarget;
16
- const tsxImportPath = require.resolve('tsx');
17
- const nodeArgs = target.endsWith('.ts')
18
- ? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
19
- : [target, ...process.argv.slice(2)];
20
- const child = spawn(process.execPath, nodeArgs, {
21
- stdio: 'inherit',
22
- env: process.env,
23
- });
24
- const result = await new Promise((resolve, reject) => {
25
- let settled = false;
26
- const finalize = (exitCode, signal) => {
27
- if (settled) {
28
- return;
29
- }
30
- settled = true;
31
- child.off?.('error', reject);
32
- child.off?.('exit', finalize);
33
- child.off?.('close', finalize);
34
- resolve({ exitCode, signal });
35
- };
36
- child.once('error', reject);
37
- child.once('exit', finalize);
38
- child.once('close', finalize);
39
- });
40
- process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
6
+ import { runCliWrapper } from './launcher.js';
7
+ await runCliWrapper({ traceName: 'cli-wrapper' });
package/bin/zintrust.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * This bin script is a thin wrapper around the hashbang-free implementation in
6
6
  * bin/zintrust-main.ts. Keeping the implementation hashbang-free allows other
7
- * shortcuts (zin/z/zt) to import it without parse issues.
7
+ * shortcuts (zin/z/zt) to reuse the same launcher behavior.
8
8
  */
9
9
  export {};
10
10
  //# sourceMappingURL=zintrust.d.ts.map
@@ -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"}
package/bin/zintrust.js CHANGED
@@ -4,40 +4,7 @@
4
4
  *
5
5
  * This bin script is a thin wrapper around the hashbang-free implementation in
6
6
  * bin/zintrust-main.ts. Keeping the implementation hashbang-free allows other
7
- * shortcuts (zin/z/zt) to import it without parse issues.
7
+ * shortcuts (zin/z/zt) to reuse the same launcher behavior.
8
8
  */
9
- import { spawn } from 'node:child_process';
10
- import { existsSync } from 'node:fs';
11
- import { createRequire } from 'node:module';
12
- import path from 'node:path';
13
- import { fileURLToPath } from 'node:url';
14
- const here = path.dirname(fileURLToPath(import.meta.url));
15
- const require = createRequire(import.meta.url);
16
- const tsTarget = path.join(here, 'zintrust-main.ts');
17
- const jsTarget = path.join(here, 'zintrust-main.js');
18
- const target = existsSync(tsTarget) ? tsTarget : jsTarget;
19
- const tsxImportPath = require.resolve('tsx');
20
- const nodeArgs = target.endsWith('.ts')
21
- ? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
22
- : [target, ...process.argv.slice(2)];
23
- const child = spawn(process.execPath, nodeArgs, {
24
- stdio: 'inherit',
25
- env: process.env,
26
- });
27
- const result = await new Promise((resolve, reject) => {
28
- let settled = false;
29
- const finalize = (exitCode, signal) => {
30
- if (settled) {
31
- return;
32
- }
33
- settled = true;
34
- child.off?.('error', reject);
35
- child.off?.('exit', finalize);
36
- child.off?.('close', finalize);
37
- resolve({ exitCode, signal });
38
- };
39
- child.once('error', reject);
40
- child.once('exit', finalize);
41
- child.once('close', finalize);
42
- });
43
- process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
9
+ import { runCliWrapper } from './launcher.js';
10
+ await runCliWrapper();
package/bin/zt.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env -S node --import tsx
2
2
  /**
3
- * ZinTrust CLI Shortcut - 'z'
3
+ * ZinTrust CLI Shortcut - 'zt'
4
4
  * Mirrors bin/zintrust.ts for convenience
5
5
  */
6
6
  export {};
package/bin/zt.js CHANGED
@@ -1,27 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * ZinTrust CLI Shortcut - 'z'
3
+ * ZinTrust CLI Shortcut - 'zt'
4
4
  * Mirrors bin/zintrust.ts for convenience
5
5
  */
6
- import { spawn } from 'node:child_process';
7
- import { existsSync } from 'node:fs';
8
- import path from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
- const here = path.dirname(fileURLToPath(import.meta.url));
11
- const tsTarget = path.join(here, 'zintrust-main.ts');
12
- const jsTarget = path.join(here, 'zintrust-main.js');
13
- const target = existsSync(tsTarget) ? tsTarget : jsTarget;
14
- const nodeArgs = target.endsWith('.ts')
15
- ? ['--import', 'tsx', target, ...process.argv.slice(2)]
16
- : [target, ...process.argv.slice(2)];
17
- const child = spawn(process.execPath, nodeArgs, {
18
- stdio: 'inherit',
19
- env: process.env,
20
- });
21
- const result = await new Promise((resolve, reject) => {
22
- child.once('error', reject);
23
- child.once('close', (exitCode, signal) => {
24
- resolve({ exitCode, signal });
25
- });
26
- });
27
- process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
6
+ import { runCliWrapper } from './launcher.js';
7
+ await runCliWrapper();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "1.5.4",
3
+ "version": "1.6.0",
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,15 +1,17 @@
1
1
  import type { IShutdownManager } from './type';
2
2
  import type { IRouter } from '../../index.js';
3
3
  export declare const registerFrameworkShutdownHooks: (shutdownManager: IShutdownManager) => void;
4
- export declare const createLifecycle: (params: {
4
+ type LifecycleParams = {
5
5
  environment: string;
6
6
  resolvedBasePath: string;
7
7
  router: IRouter;
8
8
  shutdownManager: IShutdownManager;
9
9
  getBooted: () => boolean;
10
10
  setBooted: (value: boolean) => void;
11
- }) => {
11
+ };
12
+ export declare const createLifecycle: (params: LifecycleParams) => {
12
13
  boot: () => Promise<void>;
13
14
  shutdown: () => Promise<void>;
14
15
  };
16
+ export {};
15
17
  //# sourceMappingURL=runtime.d.ts.map
@@ -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,KAAK,eAAe,GAAG;IACrB,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,CAAC;AA4HF,eAAO,MAAM,eAAe,GAC1B,QAAQ,eAAe,KACtB;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAK5D,CAAC"}