@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,638 @@
1
+ import { ErrorFactory, Logger } from '@zintrust/core';
2
+ import { WorkerFactory } from '../WorkerFactory';
3
+ import { WorkerMetrics as WorkerMetricsManager } from '../WorkerMetrics';
4
+ const DEFAULT_PAGE_SIZE = 100;
5
+ const MAX_PAGE_SIZE = 200;
6
+ export async function getWorkers(query) {
7
+ const page = Math.max(1, query.page || 1);
8
+ const limit = Math.min(MAX_PAGE_SIZE, Math.max(1, query.limit || DEFAULT_PAGE_SIZE));
9
+ const offset = (page - 1) * limit;
10
+ // Get workers from persistence based on configuration
11
+ const persistence = await getWorkersFromPersistence(page, limit, query.driver);
12
+ // Apply filters
13
+ let filteredWorkers = applyFilters(persistence.workers, query);
14
+ // Apply search
15
+ if (query.search) {
16
+ filteredWorkers = applySearch(filteredWorkers, query.search);
17
+ }
18
+ // Apply sorting
19
+ filteredWorkers = applySorting(filteredWorkers, query.sortBy, query.sortOrder);
20
+ // Get queue data
21
+ const queueData = await getQueueData();
22
+ // Apply pagination
23
+ const paginatedWorkers = persistence.prePaginated
24
+ ? filteredWorkers
25
+ : filteredWorkers.slice(offset, offset + persistence.effectiveLimit);
26
+ const workersWithMetrics = await enrichWithMetrics(paginatedWorkers);
27
+ // Include details if requested
28
+ if (query.includeDetails) {
29
+ const enrichedWorkers = await enrichWithDetails(workersWithMetrics);
30
+ return {
31
+ workers: enrichedWorkers,
32
+ queueData,
33
+ pagination: {
34
+ page,
35
+ limit: persistence.effectiveLimit,
36
+ total: persistence.prePaginated ? persistence.total : filteredWorkers.length,
37
+ totalPages: Math.ceil((persistence.prePaginated ? persistence.total : filteredWorkers.length) /
38
+ persistence.effectiveLimit),
39
+ hasNext: offset + persistence.effectiveLimit <
40
+ (persistence.prePaginated ? persistence.total : filteredWorkers.length),
41
+ hasPrev: page > 1,
42
+ },
43
+ drivers: persistence.drivers,
44
+ };
45
+ }
46
+ return {
47
+ workers: workersWithMetrics,
48
+ queueData,
49
+ pagination: {
50
+ page,
51
+ limit: persistence.effectiveLimit,
52
+ total: persistence.prePaginated ? persistence.total : filteredWorkers.length,
53
+ totalPages: Math.ceil((persistence.prePaginated ? persistence.total : filteredWorkers.length) /
54
+ persistence.effectiveLimit),
55
+ hasNext: offset + persistence.effectiveLimit <
56
+ (persistence.prePaginated ? persistence.total : filteredWorkers.length),
57
+ hasPrev: page > 1,
58
+ },
59
+ drivers: persistence.drivers,
60
+ };
61
+ }
62
+ async function getWorkersFromPersistence(page, limit, driverFilter) {
63
+ const offset = (page - 1) * limit;
64
+ const persistenceDriver = process.env['WORKER_PERSISTENCE_DRIVER'] ?? 'memory';
65
+ const isMixedPersistence = persistenceDriver === 'database' || persistenceDriver === 'db';
66
+ if (driverFilter) {
67
+ return getWorkersByDriverFilter(driverFilter, offset, limit);
68
+ }
69
+ if (isMixedPersistence) {
70
+ return getWorkersFromMixedPersistence(offset, limit);
71
+ }
72
+ return getWorkersFromSinglePersistence(persistenceDriver, offset, limit);
73
+ }
74
+ async function getWorkersByDriverFilter(driverFilter, offset, limit) {
75
+ try {
76
+ const driverRecords = await WorkerFactory.listPersistedRecords({ driver: driverFilter }, { offset, limit });
77
+ const workers = transformToWorkerData(driverRecords, driverFilter);
78
+ return {
79
+ workers,
80
+ total: driverRecords.length === limit ? offset + limit + 100 : offset + driverRecords.length,
81
+ drivers: getAvailableDriversFromDrivers([driverFilter]),
82
+ effectiveLimit: limit,
83
+ prePaginated: true,
84
+ };
85
+ }
86
+ catch (error) {
87
+ Logger.error(`Error fetching workers from ${driverFilter}:`, error);
88
+ return {
89
+ workers: [],
90
+ total: 0,
91
+ drivers: getAvailableDriversFromDrivers([driverFilter]),
92
+ effectiveLimit: limit,
93
+ prePaginated: true,
94
+ };
95
+ }
96
+ }
97
+ async function getWorkersFromMixedPersistence(offset, limit) {
98
+ try {
99
+ const dbRecords = await WorkerFactory.listPersistedRecords({ driver: 'database' }, { offset, limit });
100
+ const redisRecords = await WorkerFactory.listPersistedRecords({ driver: 'redis' }, { offset, limit });
101
+ const workers = [
102
+ ...transformToWorkerData(dbRecords, 'database'),
103
+ ...transformToWorkerData(redisRecords, 'redis'),
104
+ ];
105
+ return {
106
+ workers,
107
+ total: dbRecords.length + redisRecords.length >= limit
108
+ ? offset + limit * 2
109
+ : offset + dbRecords.length + redisRecords.length,
110
+ drivers: getAvailableDriversFromDrivers(['database', 'redis']),
111
+ effectiveLimit: Math.min(MAX_PAGE_SIZE, limit * 2),
112
+ prePaginated: true,
113
+ };
114
+ }
115
+ catch (error) {
116
+ Logger.error('Error fetching workers from mixed persistence:', error);
117
+ return {
118
+ workers: [],
119
+ total: 0,
120
+ drivers: getAvailableDriversFromDrivers(['database', 'redis']),
121
+ effectiveLimit: Math.min(MAX_PAGE_SIZE, limit * 2),
122
+ prePaginated: true,
123
+ };
124
+ }
125
+ }
126
+ async function getWorkersFromSinglePersistence(persistenceDriver, offset, limit) {
127
+ try {
128
+ const normalizedDriver = normalizeDriver(persistenceDriver);
129
+ const driverRecords = await WorkerFactory.listPersistedRecords({ driver: normalizedDriver }, { offset, limit });
130
+ const workers = transformToWorkerData(driverRecords, normalizedDriver);
131
+ return {
132
+ workers,
133
+ total: driverRecords.length === limit ? offset + limit + 100 : offset + driverRecords.length,
134
+ drivers: getAvailableDriversFromDrivers([normalizedDriver]),
135
+ effectiveLimit: limit,
136
+ prePaginated: true,
137
+ };
138
+ }
139
+ catch (error) {
140
+ Logger.error(`Error fetching workers from ${persistenceDriver}:`, error);
141
+ return {
142
+ workers: [],
143
+ total: 0,
144
+ drivers: getAvailableDriversFromDrivers([normalizeDriver(persistenceDriver)]),
145
+ effectiveLimit: limit,
146
+ prePaginated: false,
147
+ };
148
+ }
149
+ }
150
+ const normalizeDriver = (driver) => {
151
+ if (driver === 'db' || driver === 'database')
152
+ return 'database';
153
+ if (driver === 'redis')
154
+ return 'redis';
155
+ return 'memory';
156
+ };
157
+ const getAvailableDriversFromDrivers = (drivers) => {
158
+ const uniqueDrivers = new Set(drivers);
159
+ return Array.from(uniqueDrivers);
160
+ };
161
+ function transformToWorkerData(workers, driver) {
162
+ return workers.map((worker) => {
163
+ if (typeof worker === 'string') {
164
+ return buildWorkerFromRaw({ name: worker }, driver);
165
+ }
166
+ if (isWorkerRecord(worker)) {
167
+ return buildWorkerFromRecord(worker, driver);
168
+ }
169
+ return buildWorkerFromRaw(worker, driver);
170
+ });
171
+ }
172
+ const isWorkerRecord = (worker) => {
173
+ return 'autoStart' in worker && 'queueName' in worker && 'createdAt' in worker;
174
+ };
175
+ const buildWorkerFromRecord = (record, driver) => {
176
+ const status = normalizeStatus(record.status);
177
+ const rawData = {
178
+ name: record.name,
179
+ queueName: record.queueName,
180
+ status,
181
+ version: record.version ?? '1.0.0',
182
+ autoStart: record.autoStart,
183
+ lastError: record.lastError,
184
+ };
185
+ return buildWorkerFromRaw(rawData, driver);
186
+ };
187
+ const buildWorkerFromRaw = (workerData, driver) => {
188
+ const status = normalizeStatus(workerData.status ?? 'stopped');
189
+ return {
190
+ name: workerData.name,
191
+ queueName: workerData.queueName || `${workerData.name}-queue`,
192
+ status,
193
+ health: determineHealth({ ...workerData, status }),
194
+ driver,
195
+ version: workerData.version || '1.0.0',
196
+ processed: workerData.processed || 0,
197
+ avgTime: workerData.avgTime || 0,
198
+ memory: workerData.memory || 0,
199
+ autoStart: workerData.autoStart || false,
200
+ details: workerData.details || {
201
+ configuration: {},
202
+ health: {},
203
+ metrics: {},
204
+ recentLogs: [],
205
+ },
206
+ };
207
+ };
208
+ const normalizeStatus = (status) => {
209
+ if (status === 'running' || status === 'stopped' || status === 'error' || status === 'paused') {
210
+ return status;
211
+ }
212
+ return 'stopped';
213
+ };
214
+ function determineHealth(worker) {
215
+ let status = 'healthy';
216
+ const checks = [];
217
+ const lastCheck = new Date().toISOString();
218
+ if (worker.status === 'error') {
219
+ status = 'unhealthy';
220
+ checks.push({
221
+ name: 'worker-status',
222
+ status: 'fail',
223
+ message: 'Worker is in error state',
224
+ });
225
+ }
226
+ else if (worker.status === 'stopped') {
227
+ status = 'warning';
228
+ checks.push({
229
+ name: 'worker-status',
230
+ status: 'warn',
231
+ message: 'Worker is stopped',
232
+ });
233
+ }
234
+ if (worker.lastError) {
235
+ const timeSinceLastError = Date.now() - new Date(worker.lastError).getTime();
236
+ if (timeSinceLastError < 300000) {
237
+ status = 'warning';
238
+ checks.push({
239
+ name: 'recent-error',
240
+ status: 'warn',
241
+ message: `Last error occurred ${Math.round(timeSinceLastError / 1000)} seconds ago`,
242
+ });
243
+ }
244
+ }
245
+ return {
246
+ status,
247
+ checks,
248
+ lastCheck,
249
+ };
250
+ }
251
+ function applyFilters(workers, query) {
252
+ let filtered = [...workers];
253
+ if (query.status) {
254
+ filtered = filtered.filter((w) => w.status === query.status);
255
+ }
256
+ if (query.driver) {
257
+ filtered = filtered.filter((w) => w.driver === query.driver);
258
+ }
259
+ return filtered;
260
+ }
261
+ function applySearch(workers, searchTerm) {
262
+ const term = searchTerm.toLowerCase();
263
+ return workers.filter((worker) => worker.name.toLowerCase().includes(term) ||
264
+ worker.queueName.toLowerCase().includes(term) ||
265
+ worker.version.toLowerCase().includes(term));
266
+ }
267
+ function applySorting(workers, sortBy, sortOrder = 'asc') {
268
+ if (!sortBy)
269
+ return workers;
270
+ return [...workers].sort((a, b) => {
271
+ let aVal;
272
+ let bVal;
273
+ switch (sortBy) {
274
+ case 'name':
275
+ aVal = a.name.toLowerCase();
276
+ bVal = b.name.toLowerCase();
277
+ break;
278
+ case 'status':
279
+ aVal = a.status;
280
+ bVal = b.status;
281
+ break;
282
+ case 'driver':
283
+ aVal = a.driver;
284
+ bVal = b.driver;
285
+ break;
286
+ case 'health':
287
+ aVal = a.health.status;
288
+ bVal = b.health.status;
289
+ break;
290
+ case 'version':
291
+ aVal = a.version;
292
+ bVal = b.version;
293
+ break;
294
+ case 'processed':
295
+ aVal = a.processed;
296
+ bVal = b.processed;
297
+ break;
298
+ default:
299
+ return 0;
300
+ }
301
+ if (aVal < bVal)
302
+ return sortOrder === 'asc' ? -1 : 1;
303
+ if (aVal > bVal)
304
+ return sortOrder === 'asc' ? 1 : -1;
305
+ return 0;
306
+ });
307
+ }
308
+ async function getQueueData() {
309
+ const queueDriver = process.env.QUEUE_DRIVER || 'redis';
310
+ try {
311
+ // Get queue statistics based on QUEUE_DRIVER
312
+ switch (queueDriver) {
313
+ case 'redis':
314
+ return getRedisQueueData();
315
+ case 'database':
316
+ return getDatabaseQueueData();
317
+ case 'db':
318
+ return getDatabaseQueueData();
319
+ default:
320
+ return getMemoryQueueData();
321
+ }
322
+ }
323
+ catch (error) {
324
+ Logger.error('Error fetching queue data:', error);
325
+ return getMemoryQueueData();
326
+ }
327
+ }
328
+ async function getRedisQueueData() {
329
+ try {
330
+ // Use existing queue monitor infrastructure
331
+ const { QueueMonitor } = await import('@zintrust/queue-monitor');
332
+ const { queueConfig } = await import('@zintrust/core');
333
+ const redisConfig = queueConfig.drivers.redis;
334
+ if (redisConfig?.driver !== 'redis') {
335
+ throw ErrorFactory.createConfigError('Redis driver not configured');
336
+ }
337
+ const monitor = QueueMonitor.create({ redis: redisConfig });
338
+ const snapshot = await monitor.getSnapshot();
339
+ let totalJobs = 0;
340
+ let processingJobs = 0;
341
+ let failedJobs = 0;
342
+ // Aggregate stats from all queues
343
+ for (const queue of snapshot.queues) {
344
+ totalJobs +=
345
+ (queue.counts.waiting || 0) +
346
+ (queue.counts.active || 0) +
347
+ (queue.counts.completed || 0) +
348
+ (queue.counts.failed || 0);
349
+ processingJobs += queue.counts.active || 0;
350
+ failedJobs += queue.counts.failed || 0;
351
+ }
352
+ return {
353
+ driver: 'redis',
354
+ totalQueues: snapshot.queues.length,
355
+ totalJobs,
356
+ processingJobs,
357
+ failedJobs,
358
+ };
359
+ }
360
+ catch (error) {
361
+ Logger.error('Error fetching Redis queue data:', error);
362
+ return {
363
+ driver: 'redis',
364
+ totalQueues: 0,
365
+ totalJobs: 0,
366
+ processingJobs: 0,
367
+ failedJobs: 0,
368
+ };
369
+ }
370
+ }
371
+ async function getDatabaseQueueData() {
372
+ try {
373
+ // For database queues, use the existing database connection
374
+ const { useEnsureDbConnected } = await import('@zintrust/core');
375
+ const db = await useEnsureDbConnected();
376
+ // Get queue statistics from actual database tables using proper query builder
377
+ const queueStats = (await db
378
+ .table('queue_jobs')
379
+ .select('COUNT(DISTINCT queue) as totalQueues')
380
+ .selectAs('COUNT(*)', 'totalJobs')
381
+ .selectAs('SUM(CASE WHEN reserved_at IS NOT NULL AND failed_at IS NULL THEN 1 ELSE 0 END)', 'processingJobs')
382
+ .selectAs('SUM(CASE WHEN failed_at IS NOT NULL THEN 1 ELSE 0 END)', 'failedJobs')
383
+ .first());
384
+ const stats = queueStats || {
385
+ totalQueues: 0,
386
+ totalJobs: 0,
387
+ processingJobs: 0,
388
+ failedJobs: 0,
389
+ };
390
+ return {
391
+ driver: 'database',
392
+ totalQueues: Number(stats.totalQueues) || 0,
393
+ totalJobs: Number(stats.totalJobs) || 0,
394
+ processingJobs: Number(stats.processingJobs) || 0,
395
+ failedJobs: Number(stats.failedJobs) || 0,
396
+ };
397
+ }
398
+ catch (error) {
399
+ Logger.error('Error fetching database queue data:', error);
400
+ return {
401
+ driver: 'database',
402
+ totalQueues: 0,
403
+ totalJobs: 0,
404
+ processingJobs: 0,
405
+ failedJobs: 0,
406
+ };
407
+ }
408
+ }
409
+ async function getMemoryQueueData() {
410
+ // For memory queues, we need to access the in-memory queue registry
411
+ // This is a simplified implementation - in practice you'd need to
412
+ // access the actual queue registry from the queue system
413
+ // Since memory queues don't persist, we return basic info
414
+ // In a real implementation, you'd track active memory queues
415
+ return {
416
+ driver: 'memory',
417
+ totalQueues: 0, // Memory queues are not persisted
418
+ totalJobs: 0,
419
+ processingJobs: 0,
420
+ failedJobs: 0,
421
+ };
422
+ }
423
+ async function enrichWithMetrics(workers) {
424
+ const now = Date.now();
425
+ const oneHourAgo = new Date(now - 60 * 60 * 1000);
426
+ const endDate = new Date(now);
427
+ if (workers.length === 0)
428
+ return workers;
429
+ const metricRequests = workers.flatMap((worker) => [
430
+ {
431
+ workerName: worker.name,
432
+ metricType: 'processed',
433
+ granularity: 'hourly',
434
+ startDate: oneHourAgo,
435
+ endDate,
436
+ },
437
+ {
438
+ workerName: worker.name,
439
+ metricType: 'duration',
440
+ granularity: 'hourly',
441
+ startDate: oneHourAgo,
442
+ endDate,
443
+ },
444
+ {
445
+ workerName: worker.name,
446
+ metricType: 'memory',
447
+ granularity: 'hourly',
448
+ startDate: oneHourAgo,
449
+ endDate,
450
+ },
451
+ ]);
452
+ try {
453
+ const results = await WorkerMetricsManager.aggregateBatch(metricRequests);
454
+ return workers.map((worker, index) => {
455
+ const baseIdx = index * 3;
456
+ const processedMetric = results[baseIdx];
457
+ const durationMetric = results[baseIdx + 1];
458
+ const memoryMetric = results[baseIdx + 2];
459
+ const processed = processedMetric && Number.isFinite(processedMetric.total)
460
+ ? Math.round(processedMetric.total)
461
+ : worker.processed;
462
+ const avgTime = durationMetric && Number.isFinite(durationMetric.average)
463
+ ? Math.round(durationMetric.average)
464
+ : worker.avgTime;
465
+ const memory = memoryMetric && Number.isFinite(memoryMetric.average)
466
+ ? Math.round(memoryMetric.average)
467
+ : worker.memory;
468
+ return {
469
+ ...worker,
470
+ processed,
471
+ avgTime,
472
+ memory,
473
+ };
474
+ });
475
+ }
476
+ catch (error) {
477
+ Logger.debug('Batch metrics unavailable', error);
478
+ return workers;
479
+ }
480
+ }
481
+ async function enrichWithDetails(workers) {
482
+ return Promise.all(workers.map((worker) => buildWorkerDetails(worker)));
483
+ }
484
+ async function buildWorkerDetails(worker) {
485
+ try {
486
+ const persistenceOverride = resolvePersistenceOverride(worker.driver);
487
+ const persisted = await WorkerFactory.getPersisted(worker.name, persistenceOverride);
488
+ const health = await getWorkerHealthSnapshot(worker.name, worker.health);
489
+ const metrics = await getWorkerMetricsSnapshot(worker.name, worker);
490
+ const configuration = buildWorkerConfiguration(worker, persisted);
491
+ return {
492
+ ...worker,
493
+ processed: metrics.processed,
494
+ avgTime: metrics.avgTime,
495
+ memory: metrics.memory,
496
+ details: {
497
+ configuration,
498
+ health,
499
+ metrics,
500
+ recentLogs: worker.details?.recentLogs ?? [],
501
+ },
502
+ };
503
+ }
504
+ catch (error) {
505
+ Logger.error(`Error fetching details for worker ${worker.name}:`, error);
506
+ return worker;
507
+ }
508
+ }
509
+ function buildWorkerConfiguration(worker, persisted) {
510
+ if (!persisted) {
511
+ return {
512
+ queueName: worker.queueName,
513
+ concurrency: null,
514
+ region: null,
515
+ processorPath: null,
516
+ version: worker.version,
517
+ features: null,
518
+ infrastructure: null,
519
+ datacenter: null,
520
+ };
521
+ }
522
+ return {
523
+ queueName: persisted.queueName ?? worker.queueName,
524
+ concurrency: persisted.concurrency ?? null,
525
+ region: persisted.region ?? null,
526
+ processorPath: persisted.processorPath ?? null,
527
+ version: persisted.version ?? worker.version,
528
+ features: persisted.features ?? null,
529
+ infrastructure: persisted.infrastructure ?? null,
530
+ datacenter: persisted.datacenter ?? null,
531
+ };
532
+ }
533
+ const resolvePersistenceOverride = (driver) => {
534
+ if (driver === 'database')
535
+ return { driver: 'database' };
536
+ if (driver === 'redis')
537
+ return { driver: 'redis' };
538
+ return { driver: 'memory' };
539
+ };
540
+ const getWorkerHealthSnapshot = async (name, fallback) => {
541
+ try {
542
+ const health = (await WorkerFactory.getHealth(name));
543
+ if (health && typeof health.status === 'string') {
544
+ return health;
545
+ }
546
+ }
547
+ catch (error) {
548
+ Logger.debug(`Health snapshot unavailable for worker ${name}`, error);
549
+ }
550
+ return fallback;
551
+ };
552
+ const getWorkerMetricsSnapshot = async (name, fallback) => {
553
+ const now = Date.now();
554
+ const oneHourAgo = new Date(now - 60 * 60 * 1000);
555
+ const endDate = new Date(now);
556
+ try {
557
+ const [processedMetric, durationMetric, memoryMetric] = await Promise.all([
558
+ WorkerMetricsManager.aggregate({
559
+ workerName: name,
560
+ metricType: 'processed',
561
+ granularity: 'hourly',
562
+ startDate: oneHourAgo,
563
+ endDate,
564
+ }),
565
+ WorkerMetricsManager.aggregate({
566
+ workerName: name,
567
+ metricType: 'duration',
568
+ granularity: 'hourly',
569
+ startDate: oneHourAgo,
570
+ endDate,
571
+ }),
572
+ WorkerMetricsManager.aggregate({
573
+ workerName: name,
574
+ metricType: 'memory',
575
+ granularity: 'hourly',
576
+ startDate: oneHourAgo,
577
+ endDate,
578
+ }),
579
+ ]);
580
+ return {
581
+ processed: Number.isFinite(processedMetric.total)
582
+ ? Math.round(processedMetric.total)
583
+ : fallback.processed,
584
+ failed: 0,
585
+ avgTime: Number.isFinite(durationMetric.average)
586
+ ? Math.round(durationMetric.average)
587
+ : fallback.avgTime,
588
+ memory: Number.isFinite(memoryMetric.average)
589
+ ? Math.round(memoryMetric.average)
590
+ : fallback.memory,
591
+ cpu: 0,
592
+ uptime: 0,
593
+ };
594
+ }
595
+ catch (error) {
596
+ Logger.debug(`Metrics snapshot unavailable for worker ${name}`, error);
597
+ return {
598
+ processed: fallback.processed,
599
+ failed: 0,
600
+ avgTime: fallback.avgTime,
601
+ memory: fallback.memory,
602
+ cpu: 0,
603
+ uptime: 0,
604
+ };
605
+ }
606
+ };
607
+ export async function toggleAutoStart(name, enabled) {
608
+ await WorkerFactory.setAutoStart(name, enabled);
609
+ }
610
+ export async function getWorkerDetails(name, driver) {
611
+ const persistenceDriver = (driver || process.env['WORKER_PERSISTENCE_DRIVER']) ?? 'memory';
612
+ const isMixedPersistence = persistenceDriver === 'database';
613
+ let worker;
614
+ if (isMixedPersistence) {
615
+ const dbRecord = await WorkerFactory.getPersisted(name, { driver: 'database' });
616
+ if (dbRecord) {
617
+ worker = buildWorkerFromRecord(dbRecord, 'database');
618
+ }
619
+ else {
620
+ const redisRecord = await WorkerFactory.getPersisted(name, { driver: 'redis' });
621
+ if (redisRecord) {
622
+ worker = buildWorkerFromRecord(redisRecord, 'redis');
623
+ }
624
+ }
625
+ }
626
+ else {
627
+ const normalizedDriver = normalizeDriver(persistenceDriver);
628
+ const record = await WorkerFactory.getPersisted(name, { driver: normalizedDriver });
629
+ if (record) {
630
+ worker = buildWorkerFromRecord(record, normalizedDriver);
631
+ }
632
+ }
633
+ if (!worker) {
634
+ throw ErrorFactory.createWorkerError(`Worker ${name} not found`);
635
+ }
636
+ const enrichedWorkers = await enrichWithDetails([worker]);
637
+ return enrichedWorkers[0];
638
+ }
@@ -0,0 +1,3 @@
1
+ export type { WorkersDashboardUiOptions } from './types';
2
+ declare const getWorkersDashboardStyles: () => string;
3
+ export { getWorkersDashboardStyles };