@zintrust/workers 0.1.27

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 (178) hide show
  1. package/README.md +861 -0
  2. package/dist/AnomalyDetection.d.ts +102 -0
  3. package/dist/AnomalyDetection.js +321 -0
  4. package/dist/AutoScaler.d.ts +127 -0
  5. package/dist/AutoScaler.js +425 -0
  6. package/dist/BroadcastWorker.d.ts +21 -0
  7. package/dist/BroadcastWorker.js +24 -0
  8. package/dist/CanaryController.d.ts +103 -0
  9. package/dist/CanaryController.js +380 -0
  10. package/dist/ChaosEngineering.d.ts +79 -0
  11. package/dist/ChaosEngineering.js +216 -0
  12. package/dist/CircuitBreaker.d.ts +106 -0
  13. package/dist/CircuitBreaker.js +374 -0
  14. package/dist/ClusterLock.d.ts +90 -0
  15. package/dist/ClusterLock.js +385 -0
  16. package/dist/ComplianceManager.d.ts +177 -0
  17. package/dist/ComplianceManager.js +556 -0
  18. package/dist/DatacenterOrchestrator.d.ts +133 -0
  19. package/dist/DatacenterOrchestrator.js +404 -0
  20. package/dist/DeadLetterQueue.d.ts +122 -0
  21. package/dist/DeadLetterQueue.js +539 -0
  22. package/dist/HealthMonitor.d.ts +42 -0
  23. package/dist/HealthMonitor.js +301 -0
  24. package/dist/MultiQueueWorker.d.ts +89 -0
  25. package/dist/MultiQueueWorker.js +277 -0
  26. package/dist/NotificationWorker.d.ts +21 -0
  27. package/dist/NotificationWorker.js +23 -0
  28. package/dist/Observability.d.ts +153 -0
  29. package/dist/Observability.js +530 -0
  30. package/dist/PluginManager.d.ts +123 -0
  31. package/dist/PluginManager.js +392 -0
  32. package/dist/PriorityQueue.d.ts +117 -0
  33. package/dist/PriorityQueue.js +244 -0
  34. package/dist/ResourceMonitor.d.ts +164 -0
  35. package/dist/ResourceMonitor.js +605 -0
  36. package/dist/SLAMonitor.d.ts +110 -0
  37. package/dist/SLAMonitor.js +274 -0
  38. package/dist/WorkerFactory.d.ts +193 -0
  39. package/dist/WorkerFactory.js +1507 -0
  40. package/dist/WorkerInit.d.ts +85 -0
  41. package/dist/WorkerInit.js +223 -0
  42. package/dist/WorkerMetrics.d.ts +114 -0
  43. package/dist/WorkerMetrics.js +509 -0
  44. package/dist/WorkerRegistry.d.ts +145 -0
  45. package/dist/WorkerRegistry.js +319 -0
  46. package/dist/WorkerShutdown.d.ts +61 -0
  47. package/dist/WorkerShutdown.js +159 -0
  48. package/dist/WorkerVersioning.d.ts +107 -0
  49. package/dist/WorkerVersioning.js +300 -0
  50. package/dist/build-manifest.json +462 -0
  51. package/dist/config/workerConfig.d.ts +3 -0
  52. package/dist/config/workerConfig.js +19 -0
  53. package/dist/createQueueWorker.d.ts +23 -0
  54. package/dist/createQueueWorker.js +113 -0
  55. package/dist/dashboard/index.d.ts +1 -0
  56. package/dist/dashboard/index.js +1 -0
  57. package/dist/dashboard/types.d.ts +117 -0
  58. package/dist/dashboard/types.js +1 -0
  59. package/dist/dashboard/workers-api.d.ts +4 -0
  60. package/dist/dashboard/workers-api.js +638 -0
  61. package/dist/dashboard/workers-dashboard-ui.d.ts +3 -0
  62. package/dist/dashboard/workers-dashboard-ui.js +1026 -0
  63. package/dist/dashboard/workers-dashboard.d.ts +4 -0
  64. package/dist/dashboard/workers-dashboard.js +904 -0
  65. package/dist/helper/index.d.ts +5 -0
  66. package/dist/helper/index.js +10 -0
  67. package/dist/http/WorkerApiController.d.ts +38 -0
  68. package/dist/http/WorkerApiController.js +312 -0
  69. package/dist/http/WorkerController.d.ts +374 -0
  70. package/dist/http/WorkerController.js +1351 -0
  71. package/dist/http/middleware/CustomValidation.d.ts +92 -0
  72. package/dist/http/middleware/CustomValidation.js +270 -0
  73. package/dist/http/middleware/DatacenterValidator.d.ts +3 -0
  74. package/dist/http/middleware/DatacenterValidator.js +94 -0
  75. package/dist/http/middleware/EditWorkerValidation.d.ts +7 -0
  76. package/dist/http/middleware/EditWorkerValidation.js +55 -0
  77. package/dist/http/middleware/FeaturesValidator.d.ts +3 -0
  78. package/dist/http/middleware/FeaturesValidator.js +60 -0
  79. package/dist/http/middleware/InfrastructureValidator.d.ts +31 -0
  80. package/dist/http/middleware/InfrastructureValidator.js +226 -0
  81. package/dist/http/middleware/OptionsValidator.d.ts +3 -0
  82. package/dist/http/middleware/OptionsValidator.js +112 -0
  83. package/dist/http/middleware/PayloadSanitizer.d.ts +7 -0
  84. package/dist/http/middleware/PayloadSanitizer.js +42 -0
  85. package/dist/http/middleware/ProcessorPathSanitizer.d.ts +3 -0
  86. package/dist/http/middleware/ProcessorPathSanitizer.js +74 -0
  87. package/dist/http/middleware/QueueNameSanitizer.d.ts +3 -0
  88. package/dist/http/middleware/QueueNameSanitizer.js +45 -0
  89. package/dist/http/middleware/ValidateDriver.d.ts +7 -0
  90. package/dist/http/middleware/ValidateDriver.js +20 -0
  91. package/dist/http/middleware/VersionSanitizer.d.ts +3 -0
  92. package/dist/http/middleware/VersionSanitizer.js +25 -0
  93. package/dist/http/middleware/WorkerNameSanitizer.d.ts +3 -0
  94. package/dist/http/middleware/WorkerNameSanitizer.js +46 -0
  95. package/dist/http/middleware/WorkerValidationChain.d.ts +27 -0
  96. package/dist/http/middleware/WorkerValidationChain.js +185 -0
  97. package/dist/index.d.ts +46 -0
  98. package/dist/index.js +48 -0
  99. package/dist/routes/workers.d.ts +12 -0
  100. package/dist/routes/workers.js +81 -0
  101. package/dist/storage/WorkerStore.d.ts +45 -0
  102. package/dist/storage/WorkerStore.js +195 -0
  103. package/dist/type.d.ts +76 -0
  104. package/dist/type.js +1 -0
  105. package/dist/ui/router/ui.d.ts +3 -0
  106. package/dist/ui/router/ui.js +83 -0
  107. package/dist/ui/types/worker-ui.d.ts +229 -0
  108. package/dist/ui/types/worker-ui.js +5 -0
  109. package/package.json +53 -0
  110. package/src/AnomalyDetection.ts +434 -0
  111. package/src/AutoScaler.ts +654 -0
  112. package/src/BroadcastWorker.ts +34 -0
  113. package/src/CanaryController.ts +531 -0
  114. package/src/ChaosEngineering.ts +301 -0
  115. package/src/CircuitBreaker.ts +495 -0
  116. package/src/ClusterLock.ts +499 -0
  117. package/src/ComplianceManager.ts +815 -0
  118. package/src/DatacenterOrchestrator.ts +561 -0
  119. package/src/DeadLetterQueue.ts +733 -0
  120. package/src/HealthMonitor.ts +390 -0
  121. package/src/MultiQueueWorker.ts +431 -0
  122. package/src/NotificationWorker.ts +33 -0
  123. package/src/Observability.ts +696 -0
  124. package/src/PluginManager.ts +551 -0
  125. package/src/PriorityQueue.ts +351 -0
  126. package/src/ResourceMonitor.ts +769 -0
  127. package/src/SLAMonitor.ts +408 -0
  128. package/src/WorkerFactory.ts +2108 -0
  129. package/src/WorkerInit.ts +313 -0
  130. package/src/WorkerMetrics.ts +709 -0
  131. package/src/WorkerRegistry.ts +443 -0
  132. package/src/WorkerShutdown.ts +210 -0
  133. package/src/WorkerVersioning.ts +422 -0
  134. package/src/config/workerConfig.ts +25 -0
  135. package/src/createQueueWorker.ts +174 -0
  136. package/src/dashboard/index.ts +6 -0
  137. package/src/dashboard/types.ts +141 -0
  138. package/src/dashboard/workers-api.ts +785 -0
  139. package/src/dashboard/zintrust.svg +30 -0
  140. package/src/helper/index.ts +11 -0
  141. package/src/http/WorkerApiController.ts +369 -0
  142. package/src/http/WorkerController.ts +1512 -0
  143. package/src/http/middleware/CustomValidation.ts +360 -0
  144. package/src/http/middleware/DatacenterValidator.ts +124 -0
  145. package/src/http/middleware/EditWorkerValidation.ts +74 -0
  146. package/src/http/middleware/FeaturesValidator.ts +82 -0
  147. package/src/http/middleware/InfrastructureValidator.ts +295 -0
  148. package/src/http/middleware/OptionsValidator.ts +144 -0
  149. package/src/http/middleware/PayloadSanitizer.ts +52 -0
  150. package/src/http/middleware/ProcessorPathSanitizer.ts +86 -0
  151. package/src/http/middleware/QueueNameSanitizer.ts +55 -0
  152. package/src/http/middleware/ValidateDriver.ts +29 -0
  153. package/src/http/middleware/VersionSanitizer.ts +30 -0
  154. package/src/http/middleware/WorkerNameSanitizer.ts +56 -0
  155. package/src/http/middleware/WorkerValidationChain.ts +230 -0
  156. package/src/index.ts +98 -0
  157. package/src/routes/workers.ts +154 -0
  158. package/src/storage/WorkerStore.ts +240 -0
  159. package/src/type.ts +89 -0
  160. package/src/types/queue-monitor.d.ts +38 -0
  161. package/src/types/queue-redis.d.ts +38 -0
  162. package/src/ui/README.md +13 -0
  163. package/src/ui/components/JsonEditor.js +670 -0
  164. package/src/ui/components/JsonViewer.js +387 -0
  165. package/src/ui/components/WorkerCard.js +178 -0
  166. package/src/ui/components/WorkerExpandPanel.js +257 -0
  167. package/src/ui/components/fetcher.js +42 -0
  168. package/src/ui/components/sla-scorecard.js +32 -0
  169. package/src/ui/components/styles.css +30 -0
  170. package/src/ui/components/table-expander.js +34 -0
  171. package/src/ui/integration/worker-ui-integration.js +565 -0
  172. package/src/ui/router/ui.ts +99 -0
  173. package/src/ui/services/workerApi.js +240 -0
  174. package/src/ui/types/worker-ui.ts +283 -0
  175. package/src/ui/utils/jsonValidator.js +444 -0
  176. package/src/ui/workers/index.html +202 -0
  177. package/src/ui/workers/main.js +1781 -0
  178. package/src/ui/workers/styles.css +1350 -0
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Worker Registry
3
+ * Central registry for all background workers with lifecycle management
4
+ * Sealed namespace for immutability
5
+ */
6
+
7
+ import { ErrorFactory, Logger, type WorkerConfig, type WorkerStatus } from '@zintrust/core';
8
+
9
+ export type WorkerMetadata = {
10
+ name: string;
11
+ status: WorkerStatus;
12
+ version: string;
13
+ region: string;
14
+ queueName: string;
15
+ concurrency: number;
16
+ startedAt: Date | null;
17
+ stoppedAt: Date | null;
18
+ lastProcessedAt: Date | null;
19
+ restartCount: number;
20
+ processedCount: number;
21
+ errorCount: number;
22
+ lockKey: string | null;
23
+ priority: number;
24
+ memoryUsage: number;
25
+ cpuUsage: number;
26
+ circuitState: 'closed' | 'open' | 'half-open';
27
+ queues: ReadonlyArray<string>;
28
+ plugins: ReadonlyArray<string>;
29
+ datacenter: string;
30
+ canaryPercentage: number;
31
+ config: Partial<WorkerConfig>;
32
+ };
33
+
34
+ export type WorkerInstance = {
35
+ metadata: WorkerMetadata;
36
+ instance: unknown; // The actual worker instance (BullMQ Worker, etc.)
37
+ start: () => void;
38
+ stop: () => Promise<void>;
39
+ drain: () => Promise<void>;
40
+ sleep: () => Promise<void>;
41
+ wakeup: () => void;
42
+ getStatus: () => WorkerStatus;
43
+ getHealth: () => 'green' | 'yellow' | 'red';
44
+ };
45
+
46
+ export type RegisterWorkerOptions = {
47
+ name: string;
48
+ config: Partial<WorkerConfig>;
49
+ version?: string;
50
+ region?: string;
51
+ queues?: ReadonlyArray<string>;
52
+ factory: () => Promise<WorkerInstance>;
53
+ };
54
+
55
+ export type WorkerRegistrySnapshot = {
56
+ timestamp: Date;
57
+ totalWorkers: number;
58
+ runningWorkers: number;
59
+ stoppedWorkers: number;
60
+ sleepingWorkers: number;
61
+ unhealthyWorkers: number;
62
+ workers: ReadonlyArray<{
63
+ name: string;
64
+ status: WorkerStatus;
65
+ health: 'green' | 'yellow' | 'red';
66
+ uptime: number | null;
67
+ processedCount: number;
68
+ errorCount: number;
69
+ }>;
70
+ };
71
+
72
+ type Rego = { workers: string[]; count: number };
73
+
74
+ // Internal storage
75
+ const workers = new Map<string, WorkerInstance>();
76
+ const registrations = new Map<string, RegisterWorkerOptions>();
77
+
78
+ /**
79
+ * Helper: Calculate uptime in seconds
80
+ */
81
+ const calculateUptime = (startedAt: Date | null): number | null => {
82
+ if (!startedAt) return null;
83
+ return Math.floor((Date.now() - startedAt.getTime()) / 1000);
84
+ };
85
+
86
+ /**
87
+ * Helper: Validate worker name
88
+ */
89
+ const validateWorkerName = (name: string): void => {
90
+ if (!name || typeof name !== 'string') {
91
+ throw ErrorFactory.createWorkerError('Worker name must be a non-empty string');
92
+ }
93
+ if (!/^[a-z0-9-]+$/.test(name)) {
94
+ throw ErrorFactory.createWorkerError(
95
+ 'Worker name must contain only lowercase letters, numbers, and hyphens'
96
+ );
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Worker Registry - Sealed namespace
102
+ */
103
+ export const WorkerRegistry = Object.freeze({
104
+ /**
105
+ * Register a worker with the registry
106
+ */
107
+ register(options: RegisterWorkerOptions): void {
108
+ validateWorkerName(options.name);
109
+
110
+ if (registrations.has(options.name)) {
111
+ Logger.warn(`Worker "${options.name}" is already registered. Skipping.`);
112
+ return;
113
+ }
114
+
115
+ registrations.set(options.name, options);
116
+ Logger.info(`Worker "${options.name}" registered successfully`);
117
+ },
118
+
119
+ /**
120
+ * Start a worker
121
+ */
122
+ async start(name: string, version?: string): Promise<void> {
123
+ validateWorkerName(name);
124
+
125
+ const registration = registrations.get(name);
126
+ if (!registration) {
127
+ throw ErrorFactory.createWorkerError(`Worker "${name}" is not registered`);
128
+ }
129
+
130
+ if (workers.has(name)) {
131
+ const existing = workers.get(name);
132
+ if (existing?.metadata.status === 'running') {
133
+ Logger.warn(`Worker "${name}" is already running`);
134
+ return;
135
+ }
136
+ }
137
+
138
+ const versionSuffix = version === undefined ? '' : ` version ${version}`;
139
+ Logger.info(`Starting worker "${name}"${versionSuffix}...`);
140
+
141
+ try {
142
+ const instance = await registration.factory();
143
+ instance.metadata.status = 'starting';
144
+ instance.metadata.version = version ?? '1.0.0';
145
+
146
+ workers.set(name, instance);
147
+
148
+ instance.start();
149
+
150
+ instance.metadata.status = 'running';
151
+ instance.metadata.startedAt = new Date();
152
+ instance.metadata.stoppedAt = null;
153
+
154
+ Logger.info(`Worker "${name}" started successfully`);
155
+ } catch (error) {
156
+ Logger.error(`Failed to start worker "${name}"`, error);
157
+ throw error;
158
+ }
159
+ },
160
+
161
+ /**
162
+ * Stop a worker
163
+ */
164
+ async stop(name: string): Promise<void> {
165
+ validateWorkerName(name);
166
+
167
+ const instance = workers.get(name);
168
+ if (!instance) {
169
+ Logger.warn(`Worker "${name}" is not running`);
170
+ return;
171
+ }
172
+
173
+ if (instance.metadata.status === 'stopped') {
174
+ Logger.warn(`Worker "${name}" is already stopped`);
175
+ return;
176
+ }
177
+
178
+ Logger.info(`Stopping worker "${name}"...`);
179
+
180
+ try {
181
+ instance.metadata.status = 'stopping';
182
+ await instance.stop();
183
+ instance.metadata.status = 'stopped';
184
+ instance.metadata.stoppedAt = new Date();
185
+
186
+ Logger.info(`Worker "${name}" stopped successfully`);
187
+ } catch (error) {
188
+ Logger.error(`Failed to stop worker "${name}"`, error);
189
+ throw error;
190
+ }
191
+ },
192
+
193
+ /**
194
+ * Restart a worker (stop + start)
195
+ */
196
+ async restart(name: string): Promise<void> {
197
+ validateWorkerName(name);
198
+
199
+ const instance = workers.get(name);
200
+ if (instance) {
201
+ await WorkerRegistry.stop(name);
202
+ instance.metadata.restartCount += 1;
203
+ }
204
+
205
+ await WorkerRegistry.start(name);
206
+ Logger.info(`Worker "${name}" restarted successfully`);
207
+ },
208
+
209
+ /**
210
+ * Sleep a worker (pause processing but keep lock)
211
+ */
212
+ async sleep(name: string): Promise<void> {
213
+ validateWorkerName(name);
214
+
215
+ const instance = workers.get(name);
216
+ if (!instance) {
217
+ throw ErrorFactory.createWorkerError(`Worker "${name}" is not running`);
218
+ }
219
+
220
+ if (instance.metadata.status === 'sleeping') {
221
+ Logger.warn(`Worker "${name}" is already sleeping`);
222
+ return;
223
+ }
224
+
225
+ Logger.info(`Putting worker "${name}" to sleep...`);
226
+
227
+ try {
228
+ await instance.sleep();
229
+ instance.metadata.status = 'sleeping';
230
+ Logger.info(`Worker "${name}" is now sleeping`);
231
+ } catch (error) {
232
+ Logger.error(`Failed to sleep worker "${name}"`, error);
233
+ throw error;
234
+ }
235
+ },
236
+
237
+ /**
238
+ * Wakeup a worker (resume from sleep)
239
+ */
240
+ async wakeup(name: string): Promise<void> {
241
+ validateWorkerName(name);
242
+
243
+ const instance = workers.get(name);
244
+ if (!instance) {
245
+ throw ErrorFactory.createWorkerError(`Worker "${name}" is not found`);
246
+ }
247
+
248
+ if (instance.metadata.status !== 'sleeping') {
249
+ Logger.warn(`Worker "${name}" is not sleeping (status: ${instance.metadata.status})`);
250
+ return;
251
+ }
252
+
253
+ Logger.info(`Waking up worker "${name}"...`);
254
+
255
+ try {
256
+ instance.wakeup();
257
+ instance.metadata.status = 'running';
258
+ Logger.info(`Worker "${name}" is now awake and running`);
259
+ } catch (error) {
260
+ Logger.error(`Failed to wake up worker "${name}"`, error);
261
+ throw error;
262
+ }
263
+ },
264
+
265
+ /**
266
+ * Get worker status
267
+ */
268
+ status(name: string): WorkerMetadata | null {
269
+ validateWorkerName(name);
270
+
271
+ const instance = workers.get(name);
272
+ if (!instance) {
273
+ return null;
274
+ }
275
+
276
+ return { ...instance.metadata };
277
+ },
278
+
279
+ /**
280
+ * List all registered workers
281
+ */
282
+ list(): ReadonlyArray<string> {
283
+ return Array.from(registrations.keys());
284
+ },
285
+
286
+ /**
287
+ * List all running workers
288
+ */
289
+ listRunning(): ReadonlyArray<string> {
290
+ const running: string[] = [];
291
+ for (const [name, instance] of workers.entries()) {
292
+ if (instance.metadata.status === 'running') {
293
+ running.push(name);
294
+ }
295
+ }
296
+ return running;
297
+ },
298
+
299
+ /**
300
+ * Stop all running workers
301
+ */
302
+ async stopAll(): Promise<void> {
303
+ Logger.info('Stopping all running workers...');
304
+
305
+ const running = WorkerRegistry.listRunning();
306
+ const tasks = running.map(async (name) => WorkerRegistry.stop(name));
307
+
308
+ try {
309
+ await Promise.all(tasks);
310
+ Logger.info(`Stopped ${running.length} workers successfully`);
311
+ } catch (error) {
312
+ Logger.error('Failed to stop some workers', error);
313
+ throw error;
314
+ }
315
+ },
316
+
317
+ /**
318
+ * Get worker metrics
319
+ */
320
+ getMetrics(
321
+ name: string
322
+ ): Pick<WorkerMetadata, 'processedCount' | 'errorCount' | 'memoryUsage' | 'cpuUsage'> | null {
323
+ validateWorkerName(name);
324
+
325
+ const instance = workers.get(name);
326
+ if (!instance) {
327
+ return null;
328
+ }
329
+
330
+ return {
331
+ processedCount: instance.metadata.processedCount,
332
+ errorCount: instance.metadata.errorCount,
333
+ memoryUsage: instance.metadata.memoryUsage,
334
+ cpuUsage: instance.metadata.cpuUsage,
335
+ };
336
+ },
337
+
338
+ /**
339
+ * Get worker health status
340
+ */
341
+ getHealth(name: string): 'green' | 'yellow' | 'red' | null {
342
+ validateWorkerName(name);
343
+
344
+ const instance = workers.get(name);
345
+ if (!instance) {
346
+ return null;
347
+ }
348
+
349
+ return instance.getHealth();
350
+ },
351
+
352
+ /**
353
+ * Get registry snapshot
354
+ */
355
+ getSnapshot(): WorkerRegistrySnapshot {
356
+ const allWorkers = Array.from(workers.entries()).map(([name, instance]) => ({
357
+ name,
358
+ status: instance.metadata.status,
359
+ health: instance.getHealth(),
360
+ uptime: calculateUptime(instance.metadata.startedAt),
361
+ processedCount: instance.metadata.processedCount,
362
+ errorCount: instance.metadata.errorCount,
363
+ }));
364
+
365
+ const statusCounts = allWorkers.reduce(
366
+ (acc, w) => {
367
+ if (w.status === 'running') acc.running++;
368
+ else if (w.status === 'stopped') acc.stopped++;
369
+ else if (w.status === 'sleeping') acc.sleeping++;
370
+ if (w.health === 'red') acc.unhealthy++;
371
+ return acc;
372
+ },
373
+ { running: 0, stopped: 0, sleeping: 0, unhealthy: 0 }
374
+ );
375
+
376
+ return {
377
+ timestamp: new Date(),
378
+ totalWorkers: registrations.size,
379
+ runningWorkers: statusCounts.running,
380
+ stoppedWorkers: statusCounts.stopped,
381
+ sleepingWorkers: statusCounts.sleeping,
382
+ unhealthyWorkers: statusCounts.unhealthy,
383
+ workers: allWorkers,
384
+ };
385
+ },
386
+
387
+ /**
388
+ * Get worker topology (cluster view)
389
+ */
390
+ getTopology(): Record<string, { workers: string[]; count: number }> {
391
+ const topology: Record<string, Rego> = {};
392
+
393
+ for (const [name, instance] of workers.entries()) {
394
+ const region = instance.metadata.region;
395
+ if (topology[region].count <= 0) {
396
+ topology[region] = { workers: [], count: 0 };
397
+ }
398
+ topology[region].workers.push(name);
399
+ topology[region].count++;
400
+ }
401
+
402
+ return topology;
403
+ },
404
+
405
+ /**
406
+ * Unregister a worker and clear its instance
407
+ */
408
+ unregister(name: string): void {
409
+ validateWorkerName(name);
410
+
411
+ const instance = workers.get(name);
412
+ if (instance?.metadata.status === 'running') {
413
+ Logger.warn(`Worker "${name}" is still running during unregister`);
414
+ }
415
+
416
+ workers.delete(name);
417
+ registrations.delete(name);
418
+
419
+ Logger.info(`Worker "${name}" unregistered`);
420
+ },
421
+
422
+ /**
423
+ * Check if worker is registered
424
+ */
425
+ isRegistered(name: string): boolean {
426
+ return registrations.has(name);
427
+ },
428
+
429
+ /**
430
+ * Check if worker is running
431
+ */
432
+ isRunning(name: string): boolean {
433
+ const instance = workers.get(name);
434
+ return instance?.metadata.status === 'running';
435
+ },
436
+
437
+ /**
438
+ * Get worker instance (internal use)
439
+ */
440
+ getInstance(name: string): WorkerInstance | null {
441
+ return workers.get(name) ?? null;
442
+ },
443
+ });
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Worker Shutdown Coordinator
3
+ *
4
+ * Centralized graceful shutdown handling for the worker management system.
5
+ * Coordinates orderly shutdown of all worker modules and the WorkerFactory.
6
+ */
7
+
8
+ import { Logger } from '@zintrust/core';
9
+ import { WorkerFactory } from './WorkerFactory';
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ export interface IShutdownOptions {
16
+ /**
17
+ * Timeout for graceful shutdown in milliseconds
18
+ */
19
+ timeout?: number;
20
+
21
+ /**
22
+ * Whether to force exit after timeout
23
+ */
24
+ forceExit?: boolean;
25
+
26
+ /**
27
+ * Signal that triggered shutdown (SIGTERM, SIGINT, etc.)
28
+ */
29
+ signal?: string;
30
+ }
31
+
32
+ interface IShutdownState {
33
+ isShuttingDown: boolean;
34
+ completedAt: Date | null;
35
+ startedAt: Date | null;
36
+ reason: string | null;
37
+ }
38
+
39
+ // ============================================================================
40
+ // Implementation
41
+ // ============================================================================
42
+
43
+ const state: IShutdownState = {
44
+ isShuttingDown: false,
45
+ completedAt: null,
46
+ startedAt: null,
47
+ reason: null,
48
+ };
49
+
50
+ let shutdownHandlersRegistered = false;
51
+
52
+ /**
53
+ * Perform graceful shutdown of all worker modules
54
+ */
55
+ async function shutdown(options: IShutdownOptions = {}): Promise<void> {
56
+ const { timeout = 30000, forceExit = true, signal = 'unknown' } = options;
57
+
58
+ // Prevent concurrent shutdowns
59
+ if (state.isShuttingDown) {
60
+ Logger.warn('Shutdown already in progress, ignoring duplicate request');
61
+ return;
62
+ }
63
+
64
+ state.isShuttingDown = true;
65
+ state.startedAt = new Date();
66
+ state.reason = `Signal: ${signal}`;
67
+
68
+ Logger.info('🛑 Initiating graceful shutdown of worker management system', {
69
+ signal,
70
+ timeout,
71
+ forceExit,
72
+ });
73
+
74
+ // Setup timeout for forced shutdown
75
+ let timeoutHandle: NodeJS.Timeout | null = null;
76
+ if (forceExit && timeout > 0) {
77
+ // eslint-disable-next-line no-restricted-syntax
78
+ timeoutHandle = setTimeout(() => {
79
+ Logger.error('❌ Graceful shutdown timeout exceeded, forcing exit', { timeout });
80
+ process.exit(1);
81
+ }, timeout);
82
+ }
83
+
84
+ try {
85
+ // Shutdown WorkerFactory - this will coordinate shutdown of all modules
86
+ await WorkerFactory.shutdown();
87
+
88
+ state.completedAt = new Date();
89
+ const duration = state.completedAt.getTime() - (state.startedAt?.getTime() ?? 0);
90
+
91
+ Logger.info('✅ Worker management system shutdown complete', {
92
+ duration: `${duration}ms`,
93
+ signal,
94
+ });
95
+
96
+ // Clear timeout if successful
97
+ if (timeoutHandle) {
98
+ clearTimeout(timeoutHandle);
99
+ }
100
+ } catch (error) {
101
+ Logger.error('❌ Error during worker management system shutdown', error);
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Register process signal handlers for graceful shutdown
108
+ */
109
+ function registerShutdownHandlers(): void {
110
+ if (shutdownHandlersRegistered) {
111
+ Logger.debug('Shutdown handlers already registered, skipping');
112
+ return;
113
+ }
114
+
115
+ Logger.debug('Registering worker management system shutdown handlers');
116
+
117
+ // SIGTERM - graceful shutdown (Docker, systemd, etc.)
118
+ process.on('SIGTERM', async () => {
119
+ Logger.info('📨 Received SIGTERM signal');
120
+ try {
121
+ await shutdown({ signal: 'SIGTERM', timeout: 30000, forceExit: true });
122
+ } catch (error) {
123
+ Logger.error('Error during SIGTERM shutdown', error);
124
+ }
125
+ });
126
+
127
+ // SIGINT - user interrupt (Ctrl+C) - REMOVED: handled by bootstrap.ts to prevent race condition
128
+ // process.on('SIGINT', async () => {
129
+ // Logger.info('📨 Received SIGINT signal');
130
+ // try {
131
+ // await shutdown({ signal: 'SIGINT', timeout: 30000, forceExit: true });
132
+ // } catch (error) {
133
+ // Logger.error('Error during SIGINT shutdown', error);
134
+ // }
135
+ // });
136
+
137
+ // SIGHUP - terminal closed
138
+ process.on('SIGHUP', async () => {
139
+ Logger.info('📨 Received SIGHUP signal');
140
+ try {
141
+ await shutdown({ signal: 'SIGHUP', timeout: 30000, forceExit: true });
142
+ } catch (error) {
143
+ Logger.error('Error during SIGHUP shutdown', error);
144
+ }
145
+ });
146
+
147
+ // Handle uncaught errors during shutdown
148
+ process.on('uncaughtException', async (error: Error) => {
149
+ Logger.error('💥 Uncaught exception during worker operations', error);
150
+ try {
151
+ await shutdown({ signal: 'uncaughtException', timeout: 10000, forceExit: true });
152
+ } catch {
153
+ // Ignore errors during emergency shutdown
154
+ }
155
+ process.exit(1);
156
+ });
157
+
158
+ process.on('unhandledRejection', async (reason: unknown) => {
159
+ Logger.error('💥 Unhandled promise rejection during worker operations', reason);
160
+ try {
161
+ await shutdown({ signal: 'unhandledRejection', timeout: 10000, forceExit: true });
162
+ } catch {
163
+ // Ignore errors during emergency shutdown
164
+ }
165
+ process.exit(1);
166
+ });
167
+
168
+ shutdownHandlersRegistered = true;
169
+ Logger.debug('Worker management system shutdown handlers registered');
170
+ }
171
+
172
+ /**
173
+ * Check if system is currently shutting down
174
+ */
175
+ function isShuttingDown(): boolean {
176
+ return state.isShuttingDown;
177
+ }
178
+
179
+ /**
180
+ * Get current shutdown state
181
+ */
182
+ function getShutdownState(): Readonly<IShutdownState> {
183
+ return { ...state };
184
+ }
185
+
186
+ // ============================================================================
187
+ // Public API (Sealed Namespace)
188
+ // ============================================================================
189
+
190
+ export const WorkerShutdown = Object.freeze({
191
+ /**
192
+ * Perform graceful shutdown of all worker modules
193
+ */
194
+ shutdown,
195
+
196
+ /**
197
+ * Register process signal handlers for graceful shutdown
198
+ */
199
+ registerShutdownHandlers,
200
+
201
+ /**
202
+ * Check if system is currently shutting down
203
+ */
204
+ isShuttingDown,
205
+
206
+ /**
207
+ * Get current shutdown state
208
+ */
209
+ getShutdownState,
210
+ });