@zintrust/workers 0.1.29 → 0.1.30

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 (111) hide show
  1. package/README.md +16 -1
  2. package/dist/AnomalyDetection.d.ts +4 -0
  3. package/dist/AnomalyDetection.js +8 -0
  4. package/dist/BroadcastWorker.d.ts +2 -0
  5. package/dist/CanaryController.js +49 -5
  6. package/dist/ChaosEngineering.js +13 -0
  7. package/dist/ClusterLock.js +21 -10
  8. package/dist/DeadLetterQueue.js +12 -8
  9. package/dist/MultiQueueWorker.d.ts +1 -1
  10. package/dist/MultiQueueWorker.js +12 -7
  11. package/dist/NotificationWorker.d.ts +2 -0
  12. package/dist/PriorityQueue.d.ts +2 -2
  13. package/dist/PriorityQueue.js +20 -21
  14. package/dist/ResourceMonitor.js +65 -38
  15. package/dist/WorkerFactory.d.ts +23 -3
  16. package/dist/WorkerFactory.js +420 -40
  17. package/dist/WorkerInit.js +8 -3
  18. package/dist/WorkerMetrics.d.ts +2 -1
  19. package/dist/WorkerMetrics.js +152 -93
  20. package/dist/WorkerRegistry.d.ts +6 -0
  21. package/dist/WorkerRegistry.js +70 -1
  22. package/dist/WorkerShutdown.d.ts +21 -0
  23. package/dist/WorkerShutdown.js +82 -9
  24. package/dist/WorkerShutdownDurableObject.d.ts +12 -0
  25. package/dist/WorkerShutdownDurableObject.js +41 -0
  26. package/dist/build-manifest.json +171 -99
  27. package/dist/createQueueWorker.d.ts +2 -0
  28. package/dist/createQueueWorker.js +42 -27
  29. package/dist/dashboard/types.d.ts +5 -0
  30. package/dist/dashboard/workers-api.js +136 -43
  31. package/dist/http/WorkerApiController.js +1 -0
  32. package/dist/http/WorkerController.js +133 -85
  33. package/dist/http/WorkerMonitoringService.d.ts +11 -0
  34. package/dist/http/WorkerMonitoringService.js +62 -0
  35. package/dist/http/middleware/CustomValidation.js +1 -1
  36. package/dist/http/middleware/EditWorkerValidation.d.ts +1 -1
  37. package/dist/http/middleware/EditWorkerValidation.js +7 -6
  38. package/dist/http/middleware/ProcessorPathSanitizer.js +101 -35
  39. package/dist/http/middleware/WorkerValidationChain.js +1 -0
  40. package/dist/index.d.ts +2 -1
  41. package/dist/index.js +1 -0
  42. package/dist/routes/workers.js +48 -6
  43. package/dist/storage/WorkerStore.d.ts +4 -1
  44. package/dist/storage/WorkerStore.js +55 -7
  45. package/dist/telemetry/api/TelemetryAPI.d.ts +46 -0
  46. package/dist/telemetry/api/TelemetryAPI.js +219 -0
  47. package/dist/telemetry/api/TelemetryMonitoringService.d.ts +17 -0
  48. package/dist/telemetry/api/TelemetryMonitoringService.js +113 -0
  49. package/dist/telemetry/components/AlertPanel.d.ts +1 -0
  50. package/dist/telemetry/components/AlertPanel.js +13 -0
  51. package/dist/telemetry/components/CostTracking.d.ts +1 -0
  52. package/dist/telemetry/components/CostTracking.js +14 -0
  53. package/dist/telemetry/components/ResourceUsageChart.d.ts +1 -0
  54. package/dist/telemetry/components/ResourceUsageChart.js +11 -0
  55. package/dist/telemetry/components/WorkerHealthChart.d.ts +1 -0
  56. package/dist/telemetry/components/WorkerHealthChart.js +11 -0
  57. package/dist/telemetry/index.d.ts +15 -0
  58. package/dist/telemetry/index.js +60 -0
  59. package/dist/telemetry/routes/dashboard.d.ts +6 -0
  60. package/dist/telemetry/routes/dashboard.js +608 -0
  61. package/dist/ui/router/EmbeddedAssets.d.ts +4 -0
  62. package/dist/ui/router/EmbeddedAssets.js +13 -0
  63. package/dist/ui/router/ui.js +100 -4
  64. package/package.json +10 -6
  65. package/src/AnomalyDetection.ts +9 -0
  66. package/src/CanaryController.ts +41 -5
  67. package/src/ChaosEngineering.ts +14 -0
  68. package/src/ClusterLock.ts +22 -9
  69. package/src/DeadLetterQueue.ts +13 -8
  70. package/src/MultiQueueWorker.ts +15 -8
  71. package/src/PriorityQueue.ts +21 -22
  72. package/src/ResourceMonitor.ts +72 -40
  73. package/src/WorkerFactory.ts +545 -49
  74. package/src/WorkerInit.ts +8 -3
  75. package/src/WorkerMetrics.ts +183 -105
  76. package/src/WorkerRegistry.ts +80 -1
  77. package/src/WorkerShutdown.ts +115 -9
  78. package/src/WorkerShutdownDurableObject.ts +64 -0
  79. package/src/createQueueWorker.ts +73 -30
  80. package/src/dashboard/types.ts +5 -0
  81. package/src/dashboard/workers-api.ts +165 -52
  82. package/src/http/WorkerApiController.ts +1 -0
  83. package/src/http/WorkerController.ts +167 -90
  84. package/src/http/WorkerMonitoringService.ts +77 -0
  85. package/src/http/middleware/CustomValidation.ts +1 -1
  86. package/src/http/middleware/EditWorkerValidation.ts +7 -6
  87. package/src/http/middleware/ProcessorPathSanitizer.ts +123 -36
  88. package/src/http/middleware/WorkerValidationChain.ts +1 -0
  89. package/src/index.ts +6 -1
  90. package/src/routes/workers.ts +66 -9
  91. package/src/storage/WorkerStore.ts +59 -9
  92. package/src/telemetry/api/TelemetryAPI.ts +292 -0
  93. package/src/telemetry/api/TelemetryMonitoringService.ts +149 -0
  94. package/src/telemetry/components/AlertPanel.ts +13 -0
  95. package/src/telemetry/components/CostTracking.ts +14 -0
  96. package/src/telemetry/components/ResourceUsageChart.ts +11 -0
  97. package/src/telemetry/components/WorkerHealthChart.ts +11 -0
  98. package/src/telemetry/index.ts +121 -0
  99. package/src/telemetry/public/assets/zintrust-logo.svg +15 -0
  100. package/src/telemetry/routes/dashboard.ts +638 -0
  101. package/src/telemetry/styles/tailwind.css +1 -0
  102. package/src/telemetry/styles/zintrust-theme.css +8 -0
  103. package/src/ui/router/EmbeddedAssets.ts +13 -0
  104. package/src/ui/router/ui.ts +112 -5
  105. package/src/ui/workers/index.html +2 -2
  106. package/src/ui/workers/main.js +232 -61
  107. package/src/ui/workers/zintrust.svg +30 -0
  108. package/dist/dashboard/workers-dashboard-ui.d.ts +0 -3
  109. package/dist/dashboard/workers-dashboard-ui.js +0 -1026
  110. package/dist/dashboard/workers-dashboard.d.ts +0 -4
  111. package/dist/dashboard/workers-dashboard.js +0 -904
@@ -1,4 +1,4 @@
1
- import { ErrorFactory, Logger } from '@zintrust/core';
1
+ import { Env, ErrorFactory, Logger } from '@zintrust/core';
2
2
  import { WorkerFactory } from '../WorkerFactory';
3
3
  import { WorkerMetrics as WorkerMetricsManager } from '../WorkerMetrics';
4
4
  import type { WorkerRecord } from '../storage/WorkerStore';
@@ -27,59 +27,134 @@ type PersistenceResult = {
27
27
  prePaginated: boolean;
28
28
  };
29
29
 
30
+ // Helper for timeout handling
31
+ async function withTimeout<T>(
32
+ promise: Promise<T>,
33
+ timeoutMs: number,
34
+ errorMsg: string
35
+ ): Promise<T> {
36
+ let timer: NodeJS.Timeout | undefined;
37
+
38
+ const timeoutPromise = new Promise<never>((_, reject) => {
39
+ // eslint-disable-next-line no-restricted-syntax
40
+ timer = setTimeout(() => reject(new Error(errorMsg)), timeoutMs);
41
+ });
42
+
43
+ try {
44
+ const result = await Promise.race([promise, timeoutPromise]);
45
+ if (timer) clearTimeout(timer);
46
+ return result;
47
+ } catch (error) {
48
+ if (timer) clearTimeout(timer);
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ async function fetchPersistenceWithTimeout(
54
+ page: number,
55
+ limit: number,
56
+ query: GetWorkersQuery
57
+ ): Promise<PersistenceResult> {
58
+ const driver = Env.get('WORKER_PERSISTENCE_DRIVER', 'memory');
59
+ try {
60
+ const result = await withTimeout(
61
+ getWorkersFromPersistence(page, limit, query.driver, query),
62
+ 5000,
63
+ 'Persistence timeout'
64
+ );
65
+ return result;
66
+ } catch (err) {
67
+ Logger.error(
68
+ `[getWorkers] Persistence hung or failed (driver=${driver}), resetting connection state`,
69
+ err
70
+ );
71
+ if (typeof WorkerFactory.resetPersistence === 'function') {
72
+ await WorkerFactory.resetPersistence();
73
+ }
74
+ return {
75
+ workers: [],
76
+ total: 0,
77
+ drivers: ['memory'],
78
+ effectiveLimit: limit,
79
+ prePaginated: true,
80
+ };
81
+ }
82
+ }
83
+
84
+ async function fetchQueueDataSafe(): Promise<QueueData> {
85
+ const defaultData: QueueData = {
86
+ driver: 'memory',
87
+ totalQueues: 0,
88
+ totalJobs: 0,
89
+ processingJobs: 0,
90
+ failedJobs: 0,
91
+ };
92
+
93
+ try {
94
+ return await withTimeout(getQueueData(), 3000, 'Queue data timeout');
95
+ } catch (err) {
96
+ Logger.warn('[getWorkers] Queue data fetch failed or timed out', err);
97
+ return defaultData;
98
+ }
99
+ }
100
+
101
+ async function enrichWithMetricsSafe(workers: WorkerData[]): Promise<WorkerData[]> {
102
+ try {
103
+ return await withTimeout(enrichWithMetrics(workers), 5000, 'Metrics timeout');
104
+ } catch (err) {
105
+ Logger.warn('[getWorkers] Metrics fetch failed or timed out', err);
106
+
107
+ // Reset metrics connection to avoid hanging next request
108
+ // We use fire-and-forget here because the request is already delayed/timed-out
109
+ // and we want to ensure the NEXT request has a clean slate (redisClient=null)
110
+ WorkerMetricsManager.shutdown().catch((e) =>
111
+ Logger.error('Failed to reset metrics connection', e)
112
+ );
113
+
114
+ return workers;
115
+ }
116
+ }
117
+
30
118
  export async function getWorkers(query: GetWorkersQuery): Promise<WorkersListResponse> {
119
+ const start = Date.now();
120
+ Logger.debug('[getWorkers] Start', query);
121
+
31
122
  const page = Math.max(1, query.page || 1);
32
123
  const limit = Math.min(MAX_PAGE_SIZE, Math.max(1, query.limit || DEFAULT_PAGE_SIZE));
33
124
  const offset = (page - 1) * limit;
34
125
 
35
126
  // Get workers from persistence based on configuration
36
- const persistence = await getWorkersFromPersistence(page, limit, query.driver);
127
+ const persistenceStart = Date.now();
128
+ const persistence = await fetchPersistenceWithTimeout(page, limit, query);
129
+ Logger.debug('[getWorkers] Persistence took ' + (Date.now() - persistenceStart) + 'ms', {
130
+ count: persistence.workers.length,
131
+ total: persistence.total,
132
+ });
37
133
 
38
- // Apply filters
134
+ // Apply filters/search/sorting
39
135
  let filteredWorkers = applyFilters(persistence.workers, query);
40
-
41
- // Apply search
42
136
  if (query.search) {
43
137
  filteredWorkers = applySearch(filteredWorkers, query.search);
44
138
  }
45
-
46
- // Apply sorting
47
139
  filteredWorkers = applySorting(filteredWorkers, query.sortBy, query.sortOrder);
48
140
 
49
141
  // Get queue data
50
- const queueData = await getQueueData();
142
+ const queueStart = Date.now();
143
+ const queueData = await fetchQueueDataSafe();
144
+ Logger.debug('[getWorkers] Queue data took ' + (Date.now() - queueStart) + 'ms');
51
145
 
52
146
  // Apply pagination
53
147
  const paginatedWorkers = persistence.prePaginated
54
148
  ? filteredWorkers
55
149
  : filteredWorkers.slice(offset, offset + persistence.effectiveLimit);
56
150
 
57
- const workersWithMetrics = await enrichWithMetrics(paginatedWorkers);
151
+ // Enrich with metrics
152
+ const metricsStart = Date.now();
153
+ const workersWithMetrics = await enrichWithMetricsSafe(paginatedWorkers);
154
+ Logger.debug('[getWorkers] Metrics took ' + (Date.now() - metricsStart) + 'ms');
58
155
 
59
- // Include details if requested
60
- if (query.includeDetails) {
61
- const enrichedWorkers = await enrichWithDetails(workersWithMetrics);
62
- return {
63
- workers: enrichedWorkers,
64
- queueData,
65
- pagination: {
66
- page,
67
- limit: persistence.effectiveLimit,
68
- total: persistence.prePaginated ? persistence.total : filteredWorkers.length,
69
- totalPages: Math.ceil(
70
- (persistence.prePaginated ? persistence.total : filteredWorkers.length) /
71
- persistence.effectiveLimit
72
- ),
73
- hasNext:
74
- offset + persistence.effectiveLimit <
75
- (persistence.prePaginated ? persistence.total : filteredWorkers.length),
76
- hasPrev: page > 1,
77
- },
78
- drivers: persistence.drivers,
79
- };
80
- }
81
-
82
- return {
156
+ // Prepare result
157
+ const result: WorkersListResponse = {
83
158
  workers: workersWithMetrics,
84
159
  queueData,
85
160
  pagination: {
@@ -97,38 +172,54 @@ export async function getWorkers(query: GetWorkersQuery): Promise<WorkersListRes
97
172
  },
98
173
  drivers: persistence.drivers,
99
174
  };
175
+
176
+ // Include details if requested
177
+ if (query.includeDetails) {
178
+ const detailsStart = Date.now();
179
+ try {
180
+ result.workers = await enrichWithDetails(result.workers);
181
+ } catch (err) {
182
+ Logger.warn('[getWorkers] Details fetch failed', err);
183
+ }
184
+ Logger.debug('[getWorkers] Details took ' + (Date.now() - detailsStart) + 'ms');
185
+ }
186
+
187
+ Logger.debug('[getWorkers] Total took ' + (Date.now() - start) + 'ms');
188
+ return result;
100
189
  }
101
190
 
102
191
  async function getWorkersFromPersistence(
103
192
  page: number,
104
193
  limit: number,
105
- driverFilter?: WorkerDriver
194
+ driverFilter: WorkerDriver | undefined,
195
+ query: GetWorkersQuery
106
196
  ): Promise<PersistenceResult> {
107
197
  const offset = (page - 1) * limit;
108
198
 
109
- const persistenceDriver = process.env['WORKER_PERSISTENCE_DRIVER'] ?? 'memory';
199
+ const persistenceDriver = Env.get('WORKER_PERSISTENCE_DRIVER', 'memory');
110
200
  const isMixedPersistence = persistenceDriver === 'database' || persistenceDriver === 'db';
111
201
 
112
202
  if (driverFilter) {
113
- return getWorkersByDriverFilter(driverFilter, offset, limit);
203
+ return getWorkersByDriverFilter(driverFilter, offset, limit, query);
114
204
  }
115
205
 
116
206
  if (isMixedPersistence) {
117
- return getWorkersFromMixedPersistence(offset, limit);
207
+ return getWorkersFromMixedPersistence(offset, limit, query);
118
208
  }
119
209
 
120
- return getWorkersFromSinglePersistence(persistenceDriver, offset, limit);
210
+ return getWorkersFromSinglePersistence(persistenceDriver, offset, limit, query);
121
211
  }
122
212
 
123
213
  async function getWorkersByDriverFilter(
124
214
  driverFilter: WorkerDriver,
125
215
  offset: number,
126
- limit: number
216
+ limit: number,
217
+ query: GetWorkersQuery
127
218
  ): Promise<PersistenceResult> {
128
219
  try {
129
220
  const driverRecords = await WorkerFactory.listPersistedRecords(
130
221
  { driver: driverFilter },
131
- { offset, limit }
222
+ { offset, limit, includeInactive: query.includeInactive }
132
223
  );
133
224
  const workers = transformToWorkerData(driverRecords, driverFilter);
134
225
 
@@ -153,18 +244,35 @@ async function getWorkersByDriverFilter(
153
244
 
154
245
  async function getWorkersFromMixedPersistence(
155
246
  offset: number,
156
- limit: number
247
+ limit: number,
248
+ query: GetWorkersQuery
157
249
  ): Promise<PersistenceResult> {
250
+ const includeInactive = query.includeInactive;
251
+ let dbRecords: WorkerRecord[] = [];
252
+ let redisRecords: WorkerRecord[] = [];
253
+
158
254
  try {
159
- const dbRecords = await WorkerFactory.listPersistedRecords(
160
- { driver: 'database' },
161
- { offset, limit }
255
+ dbRecords = await WorkerFactory.listPersistedRecords(
256
+ { driver: 'database', connection: 'mysql' },
257
+ { offset, limit, includeInactive }
162
258
  );
163
- const redisRecords = await WorkerFactory.listPersistedRecords(
259
+ } catch (error) {
260
+ // In some environments (like Cloudflare), database access might not be available.
261
+ // We log this as debug instead of error to avoid noise.
262
+ Logger.debug('Failed to fetch from database persistence:', error);
263
+ }
264
+
265
+ try {
266
+ redisRecords = await WorkerFactory.listPersistedRecords(
164
267
  { driver: 'redis' },
165
- { offset, limit }
268
+ { offset, limit, includeInactive }
166
269
  );
270
+ } catch (error) {
271
+ // Similarly for Redis if direct connection is not available.
272
+ Logger.debug('Failed to fetch from redis persistence:', error);
273
+ }
167
274
 
275
+ try {
168
276
  const workers = [
169
277
  ...transformToWorkerData(dbRecords, 'database'),
170
278
  ...transformToWorkerData(redisRecords, 'redis'),
@@ -181,7 +289,7 @@ async function getWorkersFromMixedPersistence(
181
289
  prePaginated: true,
182
290
  };
183
291
  } catch (error) {
184
- Logger.error('Error fetching workers from mixed persistence:', error);
292
+ Logger.error('Error transforming workers from mixed persistence:', error);
185
293
  return {
186
294
  workers: [],
187
295
  total: 0,
@@ -195,13 +303,14 @@ async function getWorkersFromMixedPersistence(
195
303
  async function getWorkersFromSinglePersistence(
196
304
  persistenceDriver: string,
197
305
  offset: number,
198
- limit: number
306
+ limit: number,
307
+ query: GetWorkersQuery
199
308
  ): Promise<PersistenceResult> {
200
309
  try {
201
310
  const normalizedDriver = normalizeDriver(persistenceDriver);
202
311
  const driverRecords = await WorkerFactory.listPersistedRecords(
203
312
  { driver: normalizedDriver },
204
- { offset, limit }
313
+ { offset, limit, includeInactive: query.includeInactive }
205
314
  );
206
315
  const workers = transformToWorkerData(driverRecords, normalizedDriver);
207
316
 
@@ -265,6 +374,7 @@ const buildWorkerFromRecord = (record: WorkerRecord, driver: WorkerDriver): Work
265
374
  version: record.version ?? '1.0.0',
266
375
  autoStart: record.autoStart,
267
376
  lastError: record.lastError,
377
+ activeStatus: record.activeStatus ?? true,
268
378
  };
269
379
 
270
380
  return buildWorkerFromRaw(rawData, driver);
@@ -283,6 +393,7 @@ const buildWorkerFromRaw = (workerData: RawWorkerData, driver: WorkerDriver): Wo
283
393
  avgTime: workerData.avgTime || 0,
284
394
  memory: workerData.memory || 0,
285
395
  autoStart: workerData.autoStart || false,
396
+ activeStatus: workerData.activeStatus ?? true,
286
397
  details: workerData.details || {
287
398
  configuration: {} as WorkerConfiguration,
288
399
  health: {} as WorkerHealth,
@@ -410,7 +521,7 @@ function applySorting(
410
521
  }
411
522
 
412
523
  async function getQueueData(): Promise<QueueData> {
413
- const queueDriver = process.env.QUEUE_DRIVER || 'redis';
524
+ const queueDriver = Env.get('QUEUE_DRIVER', 'redis');
414
525
 
415
526
  try {
416
527
  // Get queue statistics based on QUEUE_DRIVER
@@ -647,7 +758,8 @@ function buildWorkerConfiguration(
647
758
  queueName: worker.queueName,
648
759
  concurrency: null,
649
760
  region: null,
650
- processorPath: null,
761
+ processorSpec: null,
762
+ activeStatus: null,
651
763
  version: worker.version,
652
764
  features: null,
653
765
  infrastructure: null,
@@ -659,7 +771,8 @@ function buildWorkerConfiguration(
659
771
  queueName: persisted.queueName ?? worker.queueName,
660
772
  concurrency: persisted.concurrency ?? null,
661
773
  region: persisted.region ?? null,
662
- processorPath: persisted.processorPath ?? null,
774
+ processorSpec: persisted.processorSpec ?? null,
775
+ activeStatus: persisted.activeStatus ?? true,
663
776
  version: persisted.version ?? worker.version,
664
777
  features: persisted.features ?? null,
665
778
  infrastructure: persisted.infrastructure ?? null,
@@ -112,6 +112,7 @@ export const listWorkers = async (req: IRequest, res: IResponse): Promise<void>
112
112
  ] as const),
113
113
  search: getQueryParam(query, 'search'),
114
114
  includeDetails: getBooleanParam(query, 'includeDetails', false),
115
+ includeInactive: getBooleanParam(query, 'includeInactive', false),
115
116
  };
116
117
 
117
118
  const result = await getWorkers(queryParams);