@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.
- package/bin/launcher.d.ts +6 -0
- package/bin/launcher.d.ts.map +1 -0
- package/bin/launcher.js +167 -0
- package/bin/z.js +2 -35
- package/bin/zin.js +2 -35
- package/bin/zintrust.d.ts +1 -1
- package/bin/zintrust.d.ts.map +1 -1
- package/bin/zintrust.js +3 -36
- package/bin/zt.d.ts +1 -1
- package/bin/zt.js +3 -23
- package/package.json +1 -1
- package/src/boot/bootstrap.d.ts.map +1 -1
- package/src/boot/bootstrap.js +105 -40
- package/src/boot/registry/runtime.d.ts +4 -2
- package/src/boot/registry/runtime.d.ts.map +1 -1
- package/src/boot/registry/runtime.js +67 -26
- package/src/boot/registry/worker.d.ts.map +1 -1
- package/src/boot/registry/worker.js +3 -2
- package/src/cli/utils/spawn.d.ts.map +1 -1
- package/src/cli/utils/spawn.js +255 -46
- package/src/helper/ShutdownTrace.d.ts +9 -0
- package/src/helper/ShutdownTrace.d.ts.map +1 -0
- package/src/helper/ShutdownTrace.js +148 -0
- package/src/helper/index.d.ts +1 -0
- package/src/helper/index.d.ts.map +1 -1
- package/src/helper/index.js +1 -0
- package/src/index.js +3 -3
- package/src/tools/queue/QueueReliabilityOrchestrator.d.ts.map +1 -1
- package/src/tools/queue/QueueReliabilityOrchestrator.js +11 -0
- package/src/zintrust.plugins.d.ts +3 -6
- package/src/zintrust.plugins.d.ts.map +1 -1
- package/src/zintrust.plugins.js +3 -6
|
@@ -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"}
|
package/bin/launcher.js
ADDED
|
@@ -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 {
|
|
7
|
-
|
|
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 {
|
|
7
|
-
|
|
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
|
|
7
|
+
* shortcuts (zin/z/zt) to reuse the same launcher behavior.
|
|
8
8
|
*/
|
|
9
9
|
export {};
|
|
10
10
|
//# sourceMappingURL=zintrust.d.ts.map
|
package/bin/zintrust.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zintrust.d.ts","sourceRoot":"","sources":["../../bin/zintrust.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG
|
|
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
|
|
7
|
+
* shortcuts (zin/z/zt) to reuse the same launcher behavior.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
|
|
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
package/bin/zt.js
CHANGED
|
@@ -1,27 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* ZinTrust CLI Shortcut - '
|
|
3
|
+
* ZinTrust CLI Shortcut - 'zt'
|
|
4
4
|
* Mirrors bin/zintrust.ts for convenience
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgbH,eAAO,MAAM,cAAc,eAAmB,CAAC"}
|
package/src/boot/bootstrap.js
CHANGED
|
@@ -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',
|
|
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((
|
|
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
|
-
|
|
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;
|
|
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"}
|