@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.
Files changed (47) hide show
  1. package/bin/z.js +102 -2
  2. package/bin/zin.js +192 -2
  3. package/bin/zintrust-main.d.ts.map +1 -1
  4. package/bin/zintrust-main.js +32 -6
  5. package/bin/zintrust.d.ts.map +1 -1
  6. package/bin/zintrust.js +102 -2
  7. package/bin/zt.js +99 -2
  8. package/package.json +1 -1
  9. package/src/boot/bootstrap.d.ts +1 -1
  10. package/src/boot/bootstrap.d.ts.map +1 -1
  11. package/src/boot/bootstrap.js +119 -49
  12. package/src/boot/registry/runtime.d.ts.map +1 -1
  13. package/src/boot/registry/runtime.js +37 -2
  14. package/src/boot/registry/worker.d.ts.map +1 -1
  15. package/src/boot/registry/worker.js +3 -2
  16. package/src/cli/commands/StartCommand.d.ts.map +1 -1
  17. package/src/cli/commands/StartCommand.js +1 -0
  18. package/src/cli/utils/spawn.d.ts +1 -0
  19. package/src/cli/utils/spawn.d.ts.map +1 -1
  20. package/src/cli/utils/spawn.js +311 -38
  21. package/src/config/index.d.ts +2 -1
  22. package/src/config/index.d.ts.map +1 -1
  23. package/src/config/index.js +3 -3
  24. package/src/config/queue.d.ts.map +1 -1
  25. package/src/config/queue.js +32 -3
  26. package/src/helper/ShutdownTrace.d.ts +9 -0
  27. package/src/helper/ShutdownTrace.d.ts.map +1 -0
  28. package/src/helper/ShutdownTrace.js +165 -0
  29. package/src/helper/index.d.ts +1 -0
  30. package/src/helper/index.d.ts.map +1 -1
  31. package/src/helper/index.js +1 -0
  32. package/src/http/RequestContext.d.ts.map +1 -1
  33. package/src/http/RequestContext.js +6 -3
  34. package/src/index.js +3 -3
  35. package/src/migrations/schema/Schema.d.ts.map +1 -1
  36. package/src/migrations/schema/Schema.js +4 -3
  37. package/src/runtime/PluginManager.d.ts.map +1 -1
  38. package/src/runtime/PluginManager.js +9 -3
  39. package/src/runtime/WorkerAdapterImports.d.ts +2 -2
  40. package/src/runtime/WorkerAdapterImports.js +1 -1
  41. package/src/runtime/plugins/trace-runtime.d.ts.map +1 -1
  42. package/src/runtime/plugins/trace-runtime.js +2 -1
  43. package/src/runtime/plugins/trace.d.ts +1 -0
  44. package/src/runtime/plugins/trace.d.ts.map +1 -1
  45. package/src/runtime/plugins/trace.js +7 -5
  46. package/src/tools/queue/QueueReliabilityOrchestrator.d.ts.map +1 -1
  47. package/src/tools/queue/QueueReliabilityOrchestrator.js +11 -0
@@ -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 child = spawn(resolvedCommand, input.args, {
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 = typeof input.forwardSignals === 'boolean' ? input.forwardSignals : !process.stdin.isTTY;
62
- const forwardSignal = (signal) => {
63
- try {
64
- child.kill(signal);
65
- }
66
- catch (error) {
67
- const wrapped = ErrorFactory.createTryCatchError('Failed to forward signal to child process', error);
68
- // Best-effort logging; then rethrow (tests/assertions rely on this behavior).
69
- try {
70
- process.stderr.write(`${String(wrapped.message)}\n`);
71
- }
72
- catch {
73
- // ignore
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 new Promise((resolve, reject) => {
95
- child.once('error', (error) => {
96
- reject(error);
97
- });
98
- child.once('close', (code, signal) => {
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
  });
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;8BAkC0f,CAAC;;;;;;;;;;;;;;;;;;;;2EA9C3e,CAAA;;;;;;;;;;;;;;;;;;;;8BAD2B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA+C8X,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EAHnb,CAAC;AAEZ,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
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"}
@@ -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;AAGhG,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"}
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"}
@@ -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 = new Set(Object.keys(middlewareConfig.route ?? {}));
156
+ const knownKeys = getConfiguredQueueMonitorRouteKeys();
128
157
  const unknownKeys = middleware.filter((name) => {
129
- return !knownKeys.has(name) && !isKnownMiddlewareName(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
+ });