@zintrust/core 1.5.3 → 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 +102 -2
- package/bin/zin.js +192 -2
- package/bin/zintrust-main.d.ts.map +1 -1
- package/bin/zintrust-main.js +32 -6
- package/bin/zintrust.d.ts.map +1 -1
- package/bin/zintrust.js +102 -2
- package/bin/zt.js +99 -2
- package/package.json +1 -1
- package/src/boot/bootstrap.d.ts +1 -1
- package/src/boot/bootstrap.d.ts.map +1 -1
- package/src/boot/bootstrap.js +119 -49
- package/src/boot/registry/runtime.d.ts.map +1 -1
- package/src/boot/registry/runtime.js +37 -2
- package/src/boot/registry/worker.d.ts.map +1 -1
- package/src/boot/registry/worker.js +3 -2
- package/src/cli/commands/StartCommand.d.ts.map +1 -1
- package/src/cli/commands/StartCommand.js +1 -0
- package/src/cli/utils/spawn.d.ts +1 -0
- package/src/cli/utils/spawn.d.ts.map +1 -1
- package/src/cli/utils/spawn.js +311 -38
- package/src/config/index.d.ts +2 -1
- package/src/config/index.d.ts.map +1 -1
- package/src/config/index.js +3 -3
- package/src/config/queue.d.ts.map +1 -1
- package/src/config/queue.js +32 -3
- package/src/helper/ShutdownTrace.d.ts +9 -0
- package/src/helper/ShutdownTrace.d.ts.map +1 -0
- package/src/helper/ShutdownTrace.js +165 -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/http/RequestContext.d.ts.map +1 -1
- package/src/http/RequestContext.js +6 -3
- package/src/index.js +3 -3
- package/src/migrations/schema/Schema.d.ts.map +1 -1
- package/src/migrations/schema/Schema.js +4 -3
- package/src/runtime/PluginManager.d.ts.map +1 -1
- package/src/runtime/PluginManager.js +9 -3
- package/src/runtime/WorkerAdapterImports.d.ts +2 -2
- package/src/runtime/WorkerAdapterImports.js +1 -1
- package/src/runtime/plugins/trace-runtime.d.ts.map +1 -1
- package/src/runtime/plugins/trace-runtime.js +2 -1
- package/src/runtime/plugins/trace.d.ts +1 -0
- package/src/runtime/plugins/trace.d.ts.map +1 -1
- package/src/runtime/plugins/trace.js +7 -5
- package/src/tools/queue/QueueReliabilityOrchestrator.d.ts.map +1 -1
- package/src/tools/queue/QueueReliabilityOrchestrator.js +11 -0
package/src/cli/utils/spawn.js
CHANGED
|
@@ -4,6 +4,36 @@ import { spawn } from '../../node-singletons/child-process.js';
|
|
|
4
4
|
import { existsSync } from '../../node-singletons/fs.js';
|
|
5
5
|
import * as path from '../../node-singletons/path.js';
|
|
6
6
|
import { fileURLToPath } from '../../node-singletons/url.js';
|
|
7
|
+
const CLI_SPAWN_TRACE_ENV_KEYS = ['CLI_SPAWN_TRACE', 'ZIN_SPAWN_TRACE'];
|
|
8
|
+
const getCliSpawnTracePid = () => {
|
|
9
|
+
if (typeof process === 'undefined')
|
|
10
|
+
return undefined;
|
|
11
|
+
return process.pid;
|
|
12
|
+
};
|
|
13
|
+
const isCliSpawnTraceEnabled = () => {
|
|
14
|
+
if (typeof process === 'undefined')
|
|
15
|
+
return false;
|
|
16
|
+
return CLI_SPAWN_TRACE_ENV_KEYS.some((key) => {
|
|
17
|
+
const raw = process.env[key];
|
|
18
|
+
if (typeof raw !== 'string')
|
|
19
|
+
return false;
|
|
20
|
+
const normalized = raw.trim().toLowerCase();
|
|
21
|
+
return (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on');
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
const writeCliSpawnTrace = (label, details = {}) => {
|
|
25
|
+
if (!isCliSpawnTraceEnabled())
|
|
26
|
+
return;
|
|
27
|
+
const line = JSON.stringify({
|
|
28
|
+
trace: 'cli-spawn',
|
|
29
|
+
label,
|
|
30
|
+
pid: getCliSpawnTracePid(),
|
|
31
|
+
details,
|
|
32
|
+
});
|
|
33
|
+
if (typeof process !== 'undefined' && typeof process.stderr?.write === 'function') {
|
|
34
|
+
process.stderr.write(`${line}\n`);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
7
37
|
const getExitCode = (exitCode, signal) => {
|
|
8
38
|
if (typeof exitCode === 'number')
|
|
9
39
|
return exitCode;
|
|
@@ -45,60 +75,299 @@ const buildCommandNotFoundMessage = (command) => {
|
|
|
45
75
|
}
|
|
46
76
|
return `Error: '${command}' not found on PATH.`;
|
|
47
77
|
};
|
|
78
|
+
const waitForChildExit = async (child, onExit) => {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
let settled = false;
|
|
81
|
+
let childResult = {
|
|
82
|
+
exitCode: null,
|
|
83
|
+
signal: null,
|
|
84
|
+
};
|
|
85
|
+
const finish = () => {
|
|
86
|
+
if (settled)
|
|
87
|
+
return;
|
|
88
|
+
settled = true;
|
|
89
|
+
writeCliSpawnTrace('spawn.wait.finish', {
|
|
90
|
+
childPid: child.pid,
|
|
91
|
+
exitCode: childResult.exitCode,
|
|
92
|
+
signal: childResult.signal,
|
|
93
|
+
});
|
|
94
|
+
child.off?.('exit', onExitEvent);
|
|
95
|
+
child.off?.('close', onCloseEvent);
|
|
96
|
+
onExit();
|
|
97
|
+
resolve(childResult);
|
|
98
|
+
};
|
|
99
|
+
const onExitEvent = (code, signal) => {
|
|
100
|
+
writeCliSpawnTrace('spawn.child.exit', {
|
|
101
|
+
childPid: child.pid,
|
|
102
|
+
exitCode: code,
|
|
103
|
+
signal,
|
|
104
|
+
});
|
|
105
|
+
childResult = { exitCode: code, signal };
|
|
106
|
+
};
|
|
107
|
+
const onCloseEvent = (code, signal) => {
|
|
108
|
+
writeCliSpawnTrace('spawn.child.close', {
|
|
109
|
+
childPid: child.pid,
|
|
110
|
+
exitCode: code,
|
|
111
|
+
signal,
|
|
112
|
+
});
|
|
113
|
+
childResult = {
|
|
114
|
+
exitCode: childResult.exitCode ?? code,
|
|
115
|
+
signal: childResult.signal ?? signal,
|
|
116
|
+
};
|
|
117
|
+
finish();
|
|
118
|
+
};
|
|
119
|
+
child.once('error', (error) => {
|
|
120
|
+
writeCliSpawnTrace('spawn.child.error', {
|
|
121
|
+
childPid: child.pid,
|
|
122
|
+
error: error instanceof Error ? error.message : String(error),
|
|
123
|
+
});
|
|
124
|
+
reject(error);
|
|
125
|
+
});
|
|
126
|
+
child.once('exit', onExitEvent);
|
|
127
|
+
child.once('close', onCloseEvent);
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const resolveSignalHandling = (input) => {
|
|
131
|
+
const forwardSignals = typeof input.forwardSignals === 'boolean' ? input.forwardSignals : !process.stdin.isTTY;
|
|
132
|
+
const ttySignalForwardDelayMs = process.stdin.isTTY === true && forwardSignals === false
|
|
133
|
+
? Math.max(0, input.ttySignalForwardDelayMs ?? 0)
|
|
134
|
+
: 0;
|
|
135
|
+
return {
|
|
136
|
+
forwardSignals,
|
|
137
|
+
ttySignalForwardDelayMs,
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
const spawnChildProcess = (input) => {
|
|
141
|
+
writeCliSpawnTrace('spawn.child.start', {
|
|
142
|
+
command: input.command,
|
|
143
|
+
args: input.args,
|
|
144
|
+
cwd: input.cwd,
|
|
145
|
+
shell: input.shell,
|
|
146
|
+
});
|
|
147
|
+
const child = spawn(input.command, input.args, {
|
|
148
|
+
cwd: input.cwd,
|
|
149
|
+
env: input.env,
|
|
150
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
151
|
+
shell: input.shell,
|
|
152
|
+
});
|
|
153
|
+
child.stdout?.on('data', (chunk) => {
|
|
154
|
+
writeCliSpawnTrace('spawn.child.stdout.data', {
|
|
155
|
+
childPid: child.pid,
|
|
156
|
+
bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
|
|
157
|
+
});
|
|
158
|
+
process.stdout.write(chunk);
|
|
159
|
+
});
|
|
160
|
+
child.stdout?.on('end', () => {
|
|
161
|
+
writeCliSpawnTrace('spawn.child.stdout.end', {
|
|
162
|
+
childPid: child.pid,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
child.stdout?.on('close', () => {
|
|
166
|
+
writeCliSpawnTrace('spawn.child.stdout.close', {
|
|
167
|
+
childPid: child.pid,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
child.stderr?.on('data', (chunk) => {
|
|
171
|
+
writeCliSpawnTrace('spawn.child.stderr.data', {
|
|
172
|
+
childPid: child.pid,
|
|
173
|
+
bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
|
|
174
|
+
});
|
|
175
|
+
process.stderr.write(chunk);
|
|
176
|
+
});
|
|
177
|
+
child.stderr?.on('end', () => {
|
|
178
|
+
writeCliSpawnTrace('spawn.child.stderr.end', {
|
|
179
|
+
childPid: child.pid,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
child.stderr?.on('close', () => {
|
|
183
|
+
writeCliSpawnTrace('spawn.child.stderr.close', {
|
|
184
|
+
childPid: child.pid,
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
writeCliSpawnTrace('spawn.child.started', {
|
|
188
|
+
childPid: child.pid,
|
|
189
|
+
command: input.command,
|
|
190
|
+
});
|
|
191
|
+
return child;
|
|
192
|
+
};
|
|
193
|
+
const createForwardSignal = (input) => {
|
|
194
|
+
return (signal) => {
|
|
195
|
+
writeCliSpawnTrace('spawn.signal.forward.attempt', {
|
|
196
|
+
childPid: input.child.pid,
|
|
197
|
+
signal,
|
|
198
|
+
});
|
|
199
|
+
try {
|
|
200
|
+
input.child.kill(signal);
|
|
201
|
+
writeCliSpawnTrace('spawn.signal.forward.complete', {
|
|
202
|
+
childPid: input.child.pid,
|
|
203
|
+
signal,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
writeCliSpawnTrace('spawn.signal.forward.failed', {
|
|
208
|
+
childPid: input.child.pid,
|
|
209
|
+
signal,
|
|
210
|
+
error: error instanceof Error ? error.message : String(error),
|
|
211
|
+
});
|
|
212
|
+
const wrapped = ErrorFactory.createTryCatchError('Failed to forward signal to child process', error);
|
|
213
|
+
try {
|
|
214
|
+
process.stderr.write(`${String(wrapped.message)}\n`);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// ignore
|
|
218
|
+
}
|
|
219
|
+
throw wrapped;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
const createSignalHandlers = (input) => {
|
|
224
|
+
const onSigint = () => {
|
|
225
|
+
writeCliSpawnTrace('spawn.signal.received', {
|
|
226
|
+
signal: 'SIGINT',
|
|
227
|
+
forwardSignals: input.forwardSignals,
|
|
228
|
+
});
|
|
229
|
+
if (input.forwardSignals) {
|
|
230
|
+
input.forwardSignal('SIGINT');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
input.delayedSignalForwarder.schedule('SIGINT');
|
|
234
|
+
};
|
|
235
|
+
const onSigterm = () => {
|
|
236
|
+
writeCliSpawnTrace('spawn.signal.received', {
|
|
237
|
+
signal: 'SIGTERM',
|
|
238
|
+
forwardSignals: input.forwardSignals,
|
|
239
|
+
});
|
|
240
|
+
if (input.forwardSignals) {
|
|
241
|
+
input.forwardSignal('SIGTERM');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
input.delayedSignalForwarder.schedule('SIGTERM');
|
|
245
|
+
};
|
|
246
|
+
return { onSigint, onSigterm };
|
|
247
|
+
};
|
|
248
|
+
const createDelayedSignalForwarder = (input) => {
|
|
249
|
+
let delayedSignalTimer;
|
|
250
|
+
let escalationTimer;
|
|
251
|
+
const clearEscalation = () => {
|
|
252
|
+
if (escalationTimer === undefined)
|
|
253
|
+
return;
|
|
254
|
+
clearTimeout(escalationTimer);
|
|
255
|
+
escalationTimer = undefined;
|
|
256
|
+
};
|
|
257
|
+
const clear = () => {
|
|
258
|
+
if (delayedSignalTimer !== undefined) {
|
|
259
|
+
clearTimeout(delayedSignalTimer);
|
|
260
|
+
delayedSignalTimer = undefined;
|
|
261
|
+
}
|
|
262
|
+
clearEscalation();
|
|
263
|
+
writeCliSpawnTrace('spawn.signal.delay.clear');
|
|
264
|
+
};
|
|
265
|
+
const scheduleEscalation = (signal) => {
|
|
266
|
+
if (escalationTimer !== undefined || input.isChildClosed())
|
|
267
|
+
return;
|
|
268
|
+
const nextSignal = signal === 'SIGINT' ? 'SIGTERM' : signal;
|
|
269
|
+
const escalationDelayMs = Math.max(250, Math.min(1000, input.ttySignalForwardDelayMs));
|
|
270
|
+
escalationTimer = globalThis.setTimeout(() => {
|
|
271
|
+
escalationTimer = undefined;
|
|
272
|
+
if (input.isChildClosed())
|
|
273
|
+
return;
|
|
274
|
+
try {
|
|
275
|
+
writeCliSpawnTrace('spawn.signal.escalation.fire', {
|
|
276
|
+
signal: nextSignal,
|
|
277
|
+
});
|
|
278
|
+
input.forwardSignal(nextSignal);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// best-effort fallback for interactive watch processes
|
|
282
|
+
}
|
|
283
|
+
}, escalationDelayMs);
|
|
284
|
+
escalationTimer.unref?.();
|
|
285
|
+
};
|
|
286
|
+
const schedule = (signal) => {
|
|
287
|
+
if (input.ttySignalForwardDelayMs <= 0 ||
|
|
288
|
+
delayedSignalTimer !== undefined ||
|
|
289
|
+
input.isChildClosed()) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
delayedSignalTimer = globalThis.setTimeout(() => {
|
|
293
|
+
delayedSignalTimer = undefined;
|
|
294
|
+
if (input.isChildClosed())
|
|
295
|
+
return;
|
|
296
|
+
try {
|
|
297
|
+
writeCliSpawnTrace('spawn.signal.delay.fire', {
|
|
298
|
+
signal,
|
|
299
|
+
});
|
|
300
|
+
input.forwardSignal(signal);
|
|
301
|
+
scheduleEscalation(signal);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// best-effort fallback for interactive watch processes
|
|
305
|
+
}
|
|
306
|
+
}, input.ttySignalForwardDelayMs);
|
|
307
|
+
writeCliSpawnTrace('spawn.signal.delay.schedule', {
|
|
308
|
+
signal,
|
|
309
|
+
delayMs: input.ttySignalForwardDelayMs,
|
|
310
|
+
});
|
|
311
|
+
delayedSignalTimer.unref?.();
|
|
312
|
+
};
|
|
313
|
+
return { clear, schedule };
|
|
314
|
+
};
|
|
48
315
|
export const SpawnUtil = Object.freeze({
|
|
49
316
|
async spawnAndWait(input) {
|
|
50
317
|
const cwd = input.cwd ?? process.cwd();
|
|
51
318
|
const resolvedCommand = input.shell === true ? input.command : resolveLocalBin(input.command, cwd);
|
|
52
|
-
const
|
|
319
|
+
const signalHandling = resolveSignalHandling(input);
|
|
320
|
+
writeCliSpawnTrace('spawn.and-wait.start', {
|
|
321
|
+
command: resolvedCommand,
|
|
322
|
+
args: input.args,
|
|
323
|
+
cwd,
|
|
324
|
+
shell: input.shell === true,
|
|
325
|
+
forwardSignals: signalHandling.forwardSignals,
|
|
326
|
+
ttySignalForwardDelayMs: signalHandling.ttySignalForwardDelayMs,
|
|
327
|
+
});
|
|
328
|
+
const child = spawnChildProcess({
|
|
329
|
+
command: resolvedCommand,
|
|
330
|
+
args: input.args,
|
|
53
331
|
cwd,
|
|
54
332
|
env: input.env ?? appConfig.getSafeEnv(),
|
|
55
|
-
stdio: 'inherit',
|
|
56
333
|
shell: input.shell === true,
|
|
57
334
|
});
|
|
58
335
|
// In interactive shells, the foreground process group already receives SIGINT
|
|
59
336
|
// (and often SIGTERM) so forwarding can cause duplicates. `tsx watch` is
|
|
60
337
|
// especially sensitive here and can print "Previous process hasn't exited yet. Force killing...".
|
|
61
|
-
const forwardSignals =
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
throw wrapped;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const onSigint = () => {
|
|
79
|
-
if (forwardSignals) {
|
|
80
|
-
forwardSignal('SIGINT');
|
|
81
|
-
}
|
|
82
|
-
// If not forwarding, handle to prevent the parent from exiting before the child
|
|
83
|
-
// finishes its graceful shutdown (child receives SIGINT directly in TTY).
|
|
84
|
-
};
|
|
85
|
-
const onSigterm = () => {
|
|
86
|
-
if (forwardSignals) {
|
|
87
|
-
forwardSignal('SIGTERM');
|
|
88
|
-
}
|
|
89
|
-
// Same rationale as SIGINT: keep parent alive while waiting for child to exit.
|
|
90
|
-
};
|
|
338
|
+
const forwardSignals = signalHandling.forwardSignals;
|
|
339
|
+
const ttySignalForwardDelayMs = signalHandling.ttySignalForwardDelayMs;
|
|
340
|
+
let childClosed = false;
|
|
341
|
+
const forwardSignal = createForwardSignal({ child });
|
|
342
|
+
const delayedSignalForwarder = createDelayedSignalForwarder({
|
|
343
|
+
ttySignalForwardDelayMs,
|
|
344
|
+
isChildClosed: () => childClosed,
|
|
345
|
+
forwardSignal,
|
|
346
|
+
});
|
|
347
|
+
const { onSigint, onSigterm } = createSignalHandlers({
|
|
348
|
+
forwardSignals,
|
|
349
|
+
delayedSignalForwarder,
|
|
350
|
+
forwardSignal,
|
|
351
|
+
});
|
|
91
352
|
process.on('SIGINT', onSigint);
|
|
92
353
|
process.on('SIGTERM', onSigterm);
|
|
354
|
+
writeCliSpawnTrace('spawn.signal.handlers.registered', {
|
|
355
|
+
childPid: child.pid,
|
|
356
|
+
});
|
|
93
357
|
try {
|
|
94
|
-
const result = await
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
resolve({ exitCode: code, signal });
|
|
358
|
+
const result = await waitForChildExit(child, () => {
|
|
359
|
+
childClosed = true;
|
|
360
|
+
delayedSignalForwarder.clear();
|
|
361
|
+
writeCliSpawnTrace('spawn.child.mark-closed', {
|
|
362
|
+
childPid: child.pid,
|
|
100
363
|
});
|
|
101
364
|
});
|
|
365
|
+
writeCliSpawnTrace('spawn.and-wait.result', {
|
|
366
|
+
childPid: child.pid,
|
|
367
|
+
exitCode: result.exitCode,
|
|
368
|
+
signal: result.signal,
|
|
369
|
+
normalizedExitCode: getExitCode(result.exitCode, result.signal),
|
|
370
|
+
});
|
|
102
371
|
return getExitCode(result.exitCode, result.signal);
|
|
103
372
|
}
|
|
104
373
|
catch (error) {
|
|
@@ -109,8 +378,12 @@ export const SpawnUtil = Object.freeze({
|
|
|
109
378
|
throw ErrorFactory.createTryCatchError('Failed to spawn child process', error);
|
|
110
379
|
}
|
|
111
380
|
finally {
|
|
381
|
+
delayedSignalForwarder.clear();
|
|
112
382
|
process.off('SIGINT', onSigint);
|
|
113
383
|
process.off('SIGTERM', onSigterm);
|
|
384
|
+
writeCliSpawnTrace('spawn.signal.handlers.removed', {
|
|
385
|
+
childPid: child.pid,
|
|
386
|
+
});
|
|
114
387
|
}
|
|
115
388
|
},
|
|
116
389
|
});
|
package/src/config/index.d.ts
CHANGED
|
@@ -12,13 +12,13 @@ export { notificationConfig, type NotificationConfig } from './notification';
|
|
|
12
12
|
export { queueConfig, type QueueConfig } from './queue';
|
|
13
13
|
export { securityConfig } from './security';
|
|
14
14
|
export { storageConfig, type StorageConfig } from './storage';
|
|
15
|
+
export type { MiddlewareConfigType } from './type';
|
|
15
16
|
export { createRedisConnection } from './workers';
|
|
16
17
|
/**
|
|
17
18
|
* Combined configuration object
|
|
18
19
|
* Sealed namespace for immutability
|
|
19
20
|
*/
|
|
20
21
|
export declare const config: Readonly<{
|
|
21
|
-
readonly middleware: import("./type").MiddlewareConfigType;
|
|
22
22
|
readonly app: Readonly<{
|
|
23
23
|
readonly name: string;
|
|
24
24
|
readonly prefix: string;
|
|
@@ -211,6 +211,7 @@ export declare const config: Readonly<{
|
|
|
211
211
|
readonly namespace: string;
|
|
212
212
|
};
|
|
213
213
|
}>;
|
|
214
|
+
readonly middleware: import("./type").MiddlewareConfigType;
|
|
214
215
|
readonly cache: {
|
|
215
216
|
default: string;
|
|
216
217
|
drivers: import("./type").CacheConfigInput["drivers"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;8BAkCgc,CAAC;;;;;;;;;;;;;;;;;;;;iFA/Cjb,CAAA;;;;;;;;;;;;;;;;;;;;8BAD2B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAgDoU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EAHzX,CAAC;AAEZ,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
|
package/src/config/index.js
CHANGED
|
@@ -28,9 +28,6 @@ export { createRedisConnection } from './workers.js';
|
|
|
28
28
|
* Sealed namespace for immutability
|
|
29
29
|
*/
|
|
30
30
|
export const config = Object.freeze({
|
|
31
|
-
get middleware() {
|
|
32
|
-
return middlewareConfig;
|
|
33
|
-
},
|
|
34
31
|
get app() {
|
|
35
32
|
return appConfig;
|
|
36
33
|
},
|
|
@@ -52,6 +49,9 @@ export const config = Object.freeze({
|
|
|
52
49
|
get microservices() {
|
|
53
50
|
return microservicesConfig;
|
|
54
51
|
},
|
|
52
|
+
get middleware() {
|
|
53
|
+
return middlewareConfig;
|
|
54
|
+
},
|
|
55
55
|
get cache() {
|
|
56
56
|
return cacheConfig;
|
|
57
57
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/config/queue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../src/config/queue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAyChG,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC;IACzC,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACnF,OAAO,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAClC,WAAW,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH,CAAC,CAAC;AA+EH;;GAEG;AACH,eAAO,MAAM,iBAAiB,QAAO,kBA4DnC,CAAC;AAgDH,QAAA,MAAM,iBAAiB,QAAO;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,CAAC,YAAY,EAAE,sBAAsB,KAAK,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACzF,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACnF,OAAO,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAClC,WAAW,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;CAuEH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAqB/D,eAAO,MAAM,WAAW,EAAE,WAYxB,CAAC"}
|
package/src/config/queue.js
CHANGED
|
@@ -6,10 +6,39 @@
|
|
|
6
6
|
import { Cloudflare } from './cloudflare.js';
|
|
7
7
|
import { Env } from './env.js';
|
|
8
8
|
import { Logger } from './logger.js';
|
|
9
|
-
import { isKnownMiddlewareName, middlewareConfig } from './middleware.js';
|
|
10
9
|
import { ErrorFactory } from '../exceptions/ZintrustError.js';
|
|
11
10
|
import { ZintrustLang } from '../lang/lang.js';
|
|
12
11
|
import { StartupConfigFile, StartupConfigFileRegistry } from '../runtime/StartupConfigFileRegistry.js';
|
|
12
|
+
const StaticMiddlewareKeys = Object.freeze({
|
|
13
|
+
log: true,
|
|
14
|
+
error: true,
|
|
15
|
+
security: true,
|
|
16
|
+
rateLimit: true,
|
|
17
|
+
sanitizeBody: true,
|
|
18
|
+
fillRateLimit: true,
|
|
19
|
+
authRateLimit: true,
|
|
20
|
+
userMutationRateLimit: true,
|
|
21
|
+
csrf: true,
|
|
22
|
+
auth: true,
|
|
23
|
+
jwt: true,
|
|
24
|
+
bulletproof: true,
|
|
25
|
+
validateLogin: true,
|
|
26
|
+
validateRegister: true,
|
|
27
|
+
validateUserStore: true,
|
|
28
|
+
validateUserUpdate: true,
|
|
29
|
+
validateUserFill: true,
|
|
30
|
+
});
|
|
31
|
+
const isKnownQueueMonitorMiddlewareName = (value) => {
|
|
32
|
+
return (Object.hasOwn(StaticMiddlewareKeys, value) || /^rateLimit:\d+:\d+(?:\.\d+)?$/.test(value.trim()));
|
|
33
|
+
};
|
|
34
|
+
const getConfiguredQueueMonitorRouteKeys = () => {
|
|
35
|
+
const middlewareOverrides = StartupConfigFileRegistry.get(StartupConfigFile.Middleware) ?? {};
|
|
36
|
+
const routeConfig = middlewareOverrides.route;
|
|
37
|
+
if (typeof routeConfig !== 'object' || routeConfig === null || Array.isArray(routeConfig)) {
|
|
38
|
+
return new Set();
|
|
39
|
+
}
|
|
40
|
+
return new Set(Object.keys(routeConfig));
|
|
41
|
+
};
|
|
13
42
|
const getQueueDriver = (driverConfig) => {
|
|
14
43
|
const driverName = driverConfig.default;
|
|
15
44
|
return driverConfig.drivers[driverName];
|
|
@@ -124,9 +153,9 @@ const createBaseMonitor = () => {
|
|
|
124
153
|
.map((m) => m.trim())
|
|
125
154
|
.filter((m) => m.length > 0);
|
|
126
155
|
if (enabled && middleware.length > 0) {
|
|
127
|
-
const knownKeys =
|
|
156
|
+
const knownKeys = getConfiguredQueueMonitorRouteKeys();
|
|
128
157
|
const unknownKeys = middleware.filter((name) => {
|
|
129
|
-
return !knownKeys.has(name) && !
|
|
158
|
+
return !knownKeys.has(name) && !isKnownQueueMonitorMiddlewareName(name);
|
|
130
159
|
});
|
|
131
160
|
if (unknownKeys.length > 0) {
|
|
132
161
|
Logger.error('Unknown QUEUE_MONITOR_MIDDLEWARE keys configured', {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type ShutdownTraceDetails = Record<string, unknown>;
|
|
2
|
+
export declare const ShutdownTrace: Readonly<{
|
|
3
|
+
isEnabled: () => boolean;
|
|
4
|
+
log: (label: string, details?: ShutdownTraceDetails) => void;
|
|
5
|
+
logHandles: (label: string, details?: ShutdownTraceDetails) => void;
|
|
6
|
+
logBullMQWorker: (label: string, worker: unknown, details?: ShutdownTraceDetails) => void;
|
|
7
|
+
}>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ShutdownTrace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShutdownTrace.d.ts","sourceRoot":"","sources":["../../../src/helper/ShutdownTrace.ts"],"names":[],"mappings":"AAKA,KAAK,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAwMpD,eAAO,MAAM,aAAa;qBAlLJ,OAAO;iBAwFT,MAAM,YAAW,oBAAoB,KAAQ,IAAI;wBAY1C,MAAM,YAAW,oBAAoB,KAAQ,IAAI;6BA6CnE,MAAM,UACL,OAAO,YACN,oBAAoB,KAC5B,IAAI;EAmCL,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const TRACE_ENV_KEYS = ['SHUTDOWN_TRACE', 'DEBUG_SHUTDOWN_TRACE', 'WORKER_SHUTDOWN_TRACE'];
|
|
2
|
+
const MAX_HANDLE_DETAILS = 20;
|
|
3
|
+
const writeLine = (line) => {
|
|
4
|
+
const nodeProcess = getNodeProcess();
|
|
5
|
+
if (nodeProcess?.stderr && typeof nodeProcess.stderr.write === 'function') {
|
|
6
|
+
nodeProcess.stderr.write(`${line}\n`);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (typeof console !== 'undefined' && typeof console.info === 'function') {
|
|
10
|
+
console.info(line);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const getNodeProcess = () => {
|
|
14
|
+
if (typeof process === 'undefined')
|
|
15
|
+
return null;
|
|
16
|
+
return process;
|
|
17
|
+
};
|
|
18
|
+
const isEnabled = () => {
|
|
19
|
+
const nodeProcess = getNodeProcess();
|
|
20
|
+
if (nodeProcess === null)
|
|
21
|
+
return false;
|
|
22
|
+
return TRACE_ENV_KEYS.some((key) => {
|
|
23
|
+
const raw = nodeProcess.env[key];
|
|
24
|
+
if (typeof raw !== 'string')
|
|
25
|
+
return false;
|
|
26
|
+
const normalized = raw.trim().toLowerCase();
|
|
27
|
+
return (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on');
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
const getConstructorName = (value) => {
|
|
31
|
+
if (typeof value !== 'object' || value === null)
|
|
32
|
+
return typeof value;
|
|
33
|
+
const constructorValue = value.constructor;
|
|
34
|
+
return typeof constructorValue?.name === 'string' ? constructorValue.name : 'Unknown';
|
|
35
|
+
};
|
|
36
|
+
const hasFunction = (value, key) => {
|
|
37
|
+
return (typeof value === 'object' &&
|
|
38
|
+
value !== null &&
|
|
39
|
+
key in value &&
|
|
40
|
+
typeof value[key] === 'function');
|
|
41
|
+
};
|
|
42
|
+
const getOptionalValue = (value, key) => {
|
|
43
|
+
if (typeof value !== 'object' || value === null || !(key in value))
|
|
44
|
+
return undefined;
|
|
45
|
+
return value[key];
|
|
46
|
+
};
|
|
47
|
+
const summarizeHandle = (handle) => {
|
|
48
|
+
const constructorName = getConstructorName(handle);
|
|
49
|
+
const summary = {
|
|
50
|
+
type: constructorName,
|
|
51
|
+
};
|
|
52
|
+
const fd = getOptionalValue(handle, 'fd');
|
|
53
|
+
if (typeof fd === 'number') {
|
|
54
|
+
summary['fd'] = fd;
|
|
55
|
+
}
|
|
56
|
+
const localPort = getOptionalValue(handle, 'localPort');
|
|
57
|
+
if (typeof localPort === 'number') {
|
|
58
|
+
summary['localPort'] = localPort;
|
|
59
|
+
}
|
|
60
|
+
const remotePort = getOptionalValue(handle, 'remotePort');
|
|
61
|
+
if (typeof remotePort === 'number') {
|
|
62
|
+
summary['remotePort'] = remotePort;
|
|
63
|
+
}
|
|
64
|
+
const repeat = getOptionalValue(handle, '_repeat');
|
|
65
|
+
if (typeof repeat === 'number') {
|
|
66
|
+
summary['repeatMs'] = repeat;
|
|
67
|
+
}
|
|
68
|
+
const destroyed = getOptionalValue(handle, 'destroyed');
|
|
69
|
+
if (typeof destroyed === 'boolean') {
|
|
70
|
+
summary['destroyed'] = destroyed;
|
|
71
|
+
}
|
|
72
|
+
if (hasFunction(handle, 'hasRef')) {
|
|
73
|
+
try {
|
|
74
|
+
summary['hasRef'] = handle.hasRef();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
summary['hasRef'] = 'error';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return summary;
|
|
81
|
+
};
|
|
82
|
+
const countTypes = (items) => {
|
|
83
|
+
return items.reduce((counts, item) => {
|
|
84
|
+
const type = getConstructorName(item);
|
|
85
|
+
counts[type] = (counts[type] ?? 0) + 1;
|
|
86
|
+
return counts;
|
|
87
|
+
}, {});
|
|
88
|
+
};
|
|
89
|
+
const log = (label, details = {}) => {
|
|
90
|
+
if (!isEnabled())
|
|
91
|
+
return;
|
|
92
|
+
writeLine(JSON.stringify({
|
|
93
|
+
level: 'info',
|
|
94
|
+
trace: 'shutdown',
|
|
95
|
+
label,
|
|
96
|
+
details,
|
|
97
|
+
}));
|
|
98
|
+
};
|
|
99
|
+
const logHandles = (label, details = {}) => {
|
|
100
|
+
if (!isEnabled())
|
|
101
|
+
return;
|
|
102
|
+
const nodeProcess = getNodeProcess();
|
|
103
|
+
if (nodeProcess === null) {
|
|
104
|
+
writeLine(JSON.stringify({
|
|
105
|
+
level: 'info',
|
|
106
|
+
trace: 'shutdown',
|
|
107
|
+
label,
|
|
108
|
+
details: {
|
|
109
|
+
...details,
|
|
110
|
+
available: false,
|
|
111
|
+
reason: 'process unavailable',
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const handles = typeof nodeProcess._getActiveHandles === 'function' ? nodeProcess._getActiveHandles() : [];
|
|
117
|
+
const requests = typeof nodeProcess._getActiveRequests === 'function' ? nodeProcess._getActiveRequests() : [];
|
|
118
|
+
writeLine(JSON.stringify({
|
|
119
|
+
level: 'info',
|
|
120
|
+
trace: 'shutdown',
|
|
121
|
+
label,
|
|
122
|
+
details: {
|
|
123
|
+
...details,
|
|
124
|
+
available: true,
|
|
125
|
+
handleCount: handles.length,
|
|
126
|
+
requestCount: requests.length,
|
|
127
|
+
handleTypes: countTypes(handles),
|
|
128
|
+
requestTypes: countTypes(requests),
|
|
129
|
+
handles: handles.slice(0, MAX_HANDLE_DETAILS).map((handle) => summarizeHandle(handle)),
|
|
130
|
+
requests: requests.slice(0, MAX_HANDLE_DETAILS).map((request) => summarizeHandle(request)),
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
133
|
+
};
|
|
134
|
+
const logBullMQWorker = (label, worker, details = {}) => {
|
|
135
|
+
if (!isEnabled())
|
|
136
|
+
return;
|
|
137
|
+
const name = getOptionalValue(worker, 'name');
|
|
138
|
+
const opts = getOptionalValue(worker, 'opts');
|
|
139
|
+
const connection = getOptionalValue(opts, 'connection');
|
|
140
|
+
const prefix = getOptionalValue(opts, 'prefix');
|
|
141
|
+
const concurrency = getOptionalValue(opts, 'concurrency');
|
|
142
|
+
const autorun = getOptionalValue(opts, 'autorun');
|
|
143
|
+
const closing = getOptionalValue(worker, 'closing');
|
|
144
|
+
writeLine(JSON.stringify({
|
|
145
|
+
level: 'info',
|
|
146
|
+
trace: 'shutdown',
|
|
147
|
+
label,
|
|
148
|
+
details: {
|
|
149
|
+
...details,
|
|
150
|
+
workerType: getConstructorName(worker),
|
|
151
|
+
queueName: typeof name === 'string' ? name : undefined,
|
|
152
|
+
prefix: typeof prefix === 'string' ? prefix : undefined,
|
|
153
|
+
concurrency: typeof concurrency === 'number' ? concurrency : undefined,
|
|
154
|
+
autorun: typeof autorun === 'boolean' ? autorun : undefined,
|
|
155
|
+
connectionType: getConstructorName(connection),
|
|
156
|
+
closingState: closing === undefined ? 'idle' : getConstructorName(closing),
|
|
157
|
+
},
|
|
158
|
+
}));
|
|
159
|
+
};
|
|
160
|
+
export const ShutdownTrace = Object.freeze({
|
|
161
|
+
isEnabled,
|
|
162
|
+
log,
|
|
163
|
+
logHandles,
|
|
164
|
+
logBullMQWorker,
|
|
165
|
+
});
|