@zintrust/workers 0.1.28 → 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 +9 -5
  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
package/README.md CHANGED
@@ -647,6 +647,11 @@ registerWorkerRoutes(Router);
647
647
  // ... and many more endpoints
648
648
  ```
649
649
 
650
+ Processor specs can be file paths or URL specs (recommended for production). Remote processors
651
+ must export a named `ZinTrustProcessor` function.
652
+
653
+ Workers support `activeStatus` to pause without deletion; inactive workers do not auto-start.
654
+
650
655
  See the [API Reference](#api-reference) section for all available endpoints.
651
656
 
652
657
  ## Lifecycle Management
@@ -705,6 +710,16 @@ ENABLE_HEALTH_CHECKS=true
705
710
 
706
711
  # Observability
707
712
  OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
713
+
714
+ # Processor spec resolver
715
+ REMOTE_PROCESSOR_ALLOWLIST=wk.zintrust.com
716
+ PROCESSOR_FETCH_TIMEOUT=30000
717
+ PROCESSOR_FETCH_MAX_SIZE=524288
718
+ PROCESSOR_FETCH_RETRY_ATTEMPTS=3
719
+ PROCESSOR_FETCH_RETRY_BACKOFF_MS=1000
720
+ PROCESSOR_CACHE_DEFAULT_TTL=3600
721
+ PROCESSOR_CACHE_MAX_TTL=604800
722
+ PROCESSOR_CACHE_MAX_SIZE=52428800
708
723
  ```
709
724
 
710
725
  ## Testing
@@ -844,7 +859,7 @@ MIT © ZinTrust
844
859
 
845
860
  ## Support
846
861
 
847
- - Documentation: https://docs.zintrust.dev/workers
862
+ - Documentation: https://doc.zintrust.com/workers
848
863
  - Issues: https://github.com/ZinTrust/zintrust/issues
849
864
  - Discord: https://discord.gg/zintrust
850
865
 
@@ -62,6 +62,10 @@ export declare const AnomalyDetection: Readonly<{
62
62
  * Configure anomaly detection for a worker
63
63
  */
64
64
  configure(config: IAnomalyConfig): void;
65
+ /**
66
+ * Cleanup anomaly models for a worker
67
+ */
68
+ cleanup(workerName: string): void;
65
69
  /**
66
70
  * Train baseline model
67
71
  */
@@ -115,6 +115,14 @@ export const AnomalyDetection = Object.freeze({
115
115
  configs.set(config.workerName, { ...config });
116
116
  Logger.info(`Anomaly detection configured for ${config.workerName}`);
117
117
  },
118
+ /**
119
+ * Cleanup anomaly models for a worker
120
+ */
121
+ cleanup(workerName) {
122
+ configs.delete(workerName);
123
+ models.delete(workerName);
124
+ Logger.debug(`Anomaly detection cleanup completed for ${workerName}`);
125
+ },
118
126
  /**
119
127
  * Train baseline model
120
128
  */
@@ -11,11 +11,13 @@ export declare const BroadcastWorker: Readonly<{
11
11
  queueName?: string;
12
12
  driverName?: string;
13
13
  maxItems?: number;
14
+ maxDurationMs?: number;
14
15
  }) => Promise<number>;
15
16
  startWorker: (opts?: {
16
17
  queueName?: string;
17
18
  driverName?: string;
18
19
  signal?: AbortSignal;
20
+ maxDurationMs?: number;
19
21
  }) => Promise<number>;
20
22
  }>;
21
23
  export default BroadcastWorker;
@@ -103,7 +103,18 @@ const incrementTraffic = (workerName) => {
103
103
  }
104
104
  // eslint-disable-next-line no-restricted-syntax
105
105
  const timer = setTimeout(() => {
106
- CanaryController.complete(workerName);
106
+ try {
107
+ CanaryController.complete(workerName);
108
+ }
109
+ catch (error) {
110
+ Logger.error('Error during canary completion callback', error);
111
+ }
112
+ finally {
113
+ const current = canaryTimers.get(`${workerName}:complete`);
114
+ if (current === timer) {
115
+ canaryTimers.delete(`${workerName}:complete`);
116
+ }
117
+ }
107
118
  }, config.monitoringDuration * 1000);
108
119
  canaryTimers.set(`${workerName}:complete`, timer);
109
120
  }
@@ -116,7 +127,18 @@ const incrementTraffic = (workerName) => {
116
127
  }
117
128
  // eslint-disable-next-line no-restricted-syntax
118
129
  const timer = setTimeout(() => {
119
- incrementTraffic(workerName);
130
+ try {
131
+ incrementTraffic(workerName);
132
+ }
133
+ catch (error) {
134
+ Logger.error('Error during canary increment callback', error);
135
+ }
136
+ finally {
137
+ const current = canaryTimers.get(workerName);
138
+ if (current === timer) {
139
+ canaryTimers.delete(workerName);
140
+ }
141
+ }
120
142
  }, config.incrementInterval * 1000);
121
143
  canaryTimers.set(workerName, timer);
122
144
  }
@@ -124,7 +146,7 @@ const incrementTraffic = (workerName) => {
124
146
  const appendHistory = (deployment, entry) => {
125
147
  deployment.history.push(entry);
126
148
  if (deployment.history.length > MAX_HISTORY) {
127
- deployment.history.shift();
149
+ deployment.history.splice(0, deployment.history.length - MAX_HISTORY);
128
150
  }
129
151
  };
130
152
  /**
@@ -176,7 +198,18 @@ export const CanaryController = Object.freeze({
176
198
  }
177
199
  // eslint-disable-next-line no-restricted-syntax
178
200
  const timer = setTimeout(() => {
179
- incrementTraffic(workerName);
201
+ try {
202
+ incrementTraffic(workerName);
203
+ }
204
+ catch (error) {
205
+ Logger.error('Error during canary start callback', error);
206
+ }
207
+ finally {
208
+ const current = canaryTimers.get(workerName);
209
+ if (current === timer) {
210
+ canaryTimers.delete(workerName);
211
+ }
212
+ }
180
213
  }, config.monitoringDuration * 1000);
181
214
  canaryTimers.set(workerName, timer);
182
215
  },
@@ -212,7 +245,18 @@ export const CanaryController = Object.freeze({
212
245
  }
213
246
  // eslint-disable-next-line no-restricted-syntax
214
247
  const timer = setTimeout(() => {
215
- incrementTraffic(workerName);
248
+ try {
249
+ incrementTraffic(workerName);
250
+ }
251
+ catch (error) {
252
+ Logger.error('Error during canary resume callback', error);
253
+ }
254
+ finally {
255
+ const current = canaryTimers.get(workerName);
256
+ if (current === timer) {
257
+ canaryTimers.delete(workerName);
258
+ }
259
+ }
216
260
  }, deployment.config.incrementInterval * 1000);
217
261
  canaryTimers.set(workerName, timer);
218
262
  Logger.info('Canary deployment resumed', { workerName });
@@ -9,6 +9,18 @@ import { Observability } from './Observability';
9
9
  import { ResourceMonitor } from './ResourceMonitor';
10
10
  import { WorkerRegistry } from './WorkerRegistry';
11
11
  const experiments = new Map();
12
+ const EXPERIMENT_RETENTION_MS = 24 * 60 * 60 * 1000;
13
+ const cleanupExpiredExperiments = () => {
14
+ const cutoff = Date.now() - EXPERIMENT_RETENTION_MS;
15
+ for (const [id, record] of experiments.entries()) {
16
+ if (record.status.state !== 'completed')
17
+ continue;
18
+ const endedAt = record.status.endedAt?.getTime() ?? 0;
19
+ if (endedAt > 0 && endedAt < cutoff) {
20
+ experiments.delete(id);
21
+ }
22
+ }
23
+ };
12
24
  const getTargetWorkers = (config) => {
13
25
  const candidates = config.target.workers ?? WorkerRegistry.listRunning();
14
26
  if (candidates.length === 0)
@@ -151,6 +163,7 @@ export const ChaosEngineering = Object.freeze({
151
163
  id: experimentId,
152
164
  duration: record.config.duration,
153
165
  });
166
+ cleanupExpiredExperiments();
154
167
  },
155
168
  /**
156
169
  * Get experiment status
@@ -3,9 +3,20 @@
3
3
  * Distributed locking using Redis for multi-instance worker coordination
4
4
  * Sealed namespace for immutability
5
5
  */
6
- import { ErrorFactory, Logger, createRedisConnection, generateUuid, } from '@zintrust/core';
7
- // Generate unique instance ID for this process
8
- const INSTANCE_ID = `worker-${process.pid}-${Date.now()}-${generateUuid()}`;
6
+ import { Cloudflare, ErrorFactory, Logger, createRedisConnection, generateUuid, } from '@zintrust/core';
7
+ let INSTANCE_ID = '';
8
+ const createInstanceId = () => {
9
+ const workers = Cloudflare.getWorkersEnv() !== null;
10
+ const pid = typeof process !== 'undefined' && typeof process.pid === 'number' ? process.pid : 0;
11
+ const prefix = workers ? 'worker-cf' : 'worker';
12
+ return `${prefix}-${pid}-${Date.now()}-${generateUuid()}`;
13
+ };
14
+ const getInstanceId = () => {
15
+ if (INSTANCE_ID !== '')
16
+ return INSTANCE_ID;
17
+ INSTANCE_ID = createInstanceId();
18
+ return INSTANCE_ID;
19
+ };
9
20
  // Redis key prefixes
10
21
  const LOCK_PREFIX = 'worker:lock:';
11
22
  const AUDIT_PREFIX = 'worker:audit:lock:';
@@ -50,7 +61,7 @@ const auditLockOperation = async (client, entry) => {
50
61
  const extendLockTTL = async (client, lockKey, ttl) => {
51
62
  const redisKey = getLockKey(lockKey);
52
63
  const value = await client.get(redisKey);
53
- if (value === null || value !== INSTANCE_ID) {
64
+ if (value === null || value !== getInstanceId()) {
54
65
  return false; // Lock not held by this instance
55
66
  }
56
67
  const result = await client.expire(redisKey, ttl);
@@ -80,7 +91,7 @@ const startHeartbeat = (client) => {
80
91
  timestamp: now,
81
92
  operation: 'extend',
82
93
  lockKey,
83
- instanceId: INSTANCE_ID,
94
+ instanceId: getInstanceId(),
84
95
  success: true,
85
96
  });
86
97
  }
@@ -122,7 +133,7 @@ export const ClusterLock = Object.freeze({
122
133
  }
123
134
  redisClient = createRedisConnection(config);
124
135
  startHeartbeat(redisClient);
125
- Logger.info('ClusterLock initialized', { instanceId: INSTANCE_ID });
136
+ Logger.info('ClusterLock initialized', { instanceId: getInstanceId() });
126
137
  },
127
138
  /**
128
139
  * Acquire a distributed lock
@@ -136,12 +147,12 @@ export const ClusterLock = Object.freeze({
136
147
  const now = new Date();
137
148
  try {
138
149
  // Try to acquire lock using SET NX EX (set if not exists with expiry)
139
- const result = await redisClient.set(redisKey, INSTANCE_ID, 'EX', ttl, 'NX');
150
+ const result = await redisClient.set(redisKey, getInstanceId(), 'EX', ttl, 'NX');
140
151
  const success = result === 'OK';
141
152
  if (success) {
142
153
  const lockInfo = {
143
154
  lockKey,
144
- instanceId: INSTANCE_ID,
155
+ instanceId: getInstanceId(),
145
156
  acquiredAt: now,
146
157
  expiresAt: new Date(now.getTime() + ttl * 1000),
147
158
  region,
@@ -158,7 +169,7 @@ export const ClusterLock = Object.freeze({
158
169
  timestamp: now,
159
170
  operation: 'acquire',
160
171
  lockKey,
161
- instanceId: INSTANCE_ID,
172
+ instanceId: getInstanceId(),
162
173
  userId,
163
174
  success: true,
164
175
  });
@@ -169,7 +180,7 @@ export const ClusterLock = Object.freeze({
169
180
  timestamp: now,
170
181
  operation: 'acquire',
171
182
  lockKey,
172
- instanceId: INSTANCE_ID,
183
+ instanceId: getInstanceId(),
173
184
  userId,
174
185
  success: false,
175
186
  });
@@ -4,9 +4,13 @@
4
4
  * Sealed namespace for immutability
5
5
  */
6
6
  import { ErrorFactory, Logger, createRedisConnection } from '@zintrust/core';
7
- // Redis key prefixes
8
- const DLQ_PREFIX = 'worker:dlq:';
9
- const AUDIT_PREFIX = 'worker:dlq:audit:';
7
+ // Redis key prefixes - using workers package prefix system
8
+ const getDLQPrefix = () => {
9
+ return 'worker:dlq:';
10
+ };
11
+ const getAuditPrefix = () => {
12
+ return 'worker:dlq:audit:';
13
+ };
10
14
  // Internal state
11
15
  let redisClient = null;
12
16
  let retentionPolicy = null;
@@ -15,13 +19,13 @@ let cleanupInterval = null;
15
19
  * Helper: Get DLQ key
16
20
  */
17
21
  const getDLQKey = (queueName) => {
18
- return `${DLQ_PREFIX}${queueName}`;
22
+ return `${getDLQPrefix()}${queueName}`;
19
23
  };
20
24
  /**
21
25
  * Helper: Get audit key
22
26
  */
23
27
  const getAuditKey = (failedJobId) => {
24
- return `${AUDIT_PREFIX}${failedJobId}`;
28
+ return `${getAuditPrefix()}${failedJobId}`;
25
29
  };
26
30
  /**
27
31
  * Helper: Record audit entry
@@ -104,7 +108,7 @@ const cleanupOldEntries = async () => {
104
108
  try {
105
109
  const cutoffTimestamp = Date.now() - policy.autoDeleteAfterDays * 24 * 60 * 60 * 1000;
106
110
  // Find all DLQ keys
107
- const pattern = `${DLQ_PREFIX}*`;
111
+ const pattern = `${getDLQPrefix()}*`;
108
112
  const keys = await client.keys(pattern);
109
113
  const cleanedCounts = await Promise.all(keys.map(async (key) => {
110
114
  const oldEntries = await client.zrangebyscore(key, '-inf', cutoffTimestamp);
@@ -424,10 +428,10 @@ export const DeadLetterQueue = Object.freeze({
424
428
  }
425
429
  try {
426
430
  const client = redisClient;
427
- const pattern = `${DLQ_PREFIX}*`;
431
+ const pattern = `${getDLQPrefix()}*`;
428
432
  const keys = await client.keys(pattern);
429
433
  const entriesByQueue = await Promise.all(keys.map(async (key) => {
430
- const queueName = key.replace(DLQ_PREFIX, '');
434
+ const queueName = key.replace(getDLQPrefix(), '');
431
435
  const entries = await client.zrange(key, 0, -1);
432
436
  return {
433
437
  queueName,
@@ -37,7 +37,7 @@ export declare const MultiQueueWorker: Readonly<{
37
37
  /**
38
38
  * Create multi-queue worker
39
39
  */
40
- create(config: MultiQueueWorkerConfig): void;
40
+ create(config: MultiQueueWorkerConfig): Promise<void>;
41
41
  /**
42
42
  * Start processing for a specific queue
43
43
  */
@@ -11,8 +11,8 @@ const multiQueueWorkers = new Map();
11
11
  /**
12
12
  * Helper: Create worker for a queue
13
13
  */
14
- const createQueueWorker = (workerName, queueConfig, processor) => {
15
- const queue = PriorityQueue.getQueueInstance(queueConfig.name);
14
+ const createQueueWorker = async (workerName, queueConfig, processor) => {
15
+ const queue = await PriorityQueue.getQueueInstance(queueConfig.name);
16
16
  const connection = queue.opts.connection;
17
17
  const workerOptions = {
18
18
  connection,
@@ -80,7 +80,7 @@ export const MultiQueueWorker = Object.freeze({
80
80
  /**
81
81
  * Create multi-queue worker
82
82
  */
83
- create(config) {
83
+ async create(config) {
84
84
  if (multiQueueWorkers.has(config.workerName)) {
85
85
  throw ErrorFactory.createWorkerError(`Multi-queue worker "${config.workerName}" already exists`);
86
86
  }
@@ -88,9 +88,14 @@ export const MultiQueueWorker = Object.freeze({
88
88
  const stats = new Map();
89
89
  // Sort queues by priority (higher first)
90
90
  const sortedQueues = [...config.queues].sort((a, b) => b.priority - a.priority);
91
- // Create workers for each queue
92
- for (const queueConfig of sortedQueues) {
93
- const worker = createQueueWorker(config.workerName, queueConfig, config.processor);
91
+ // Create workers for each queue in parallel
92
+ const workerPromises = sortedQueues.map(async (queueConfig) => {
93
+ const worker = await createQueueWorker(config.workerName, queueConfig, config.processor);
94
+ return { queueConfig, worker };
95
+ });
96
+ const workerResults = await Promise.all(workerPromises);
97
+ // Store workers and stats
98
+ for (const { queueConfig, worker } of workerResults) {
94
99
  workers.set(queueConfig.name, worker);
95
100
  stats.set(queueConfig.name, initializeQueueStats(queueConfig.name, queueConfig.enabled));
96
101
  }
@@ -200,7 +205,7 @@ export const MultiQueueWorker = Object.freeze({
200
205
  queueConfig.concurrency = concurrency;
201
206
  // Update worker concurrency (requires restart in BullMQ)
202
207
  await worker.close();
203
- const newWorker = createQueueWorker(workerName, queueConfig, mqw.config.processor);
208
+ const newWorker = await createQueueWorker(workerName, queueConfig, mqw.config.processor);
204
209
  mqw.workers.set(queueName, newWorker);
205
210
  Logger.info(`Queue concurrency updated: ${queueName}`, { workerName, concurrency });
206
211
  },
@@ -11,11 +11,13 @@ export declare const NotificationWorker: Readonly<{
11
11
  queueName?: string;
12
12
  driverName?: string;
13
13
  maxItems?: number;
14
+ maxDurationMs?: number;
14
15
  }) => Promise<number>;
15
16
  startWorker: (opts?: {
16
17
  queueName?: string;
17
18
  driverName?: string;
18
19
  signal?: AbortSignal;
20
+ maxDurationMs?: number;
19
21
  }) => Promise<number>;
20
22
  }>;
21
23
  export default NotificationWorker;
@@ -81,7 +81,7 @@ export declare const PriorityQueue: Readonly<{
81
81
  /**
82
82
  * Get all queue names
83
83
  */
84
- getQueueNames(): string[];
84
+ getQueueNames(): Promise<string[]>;
85
85
  /**
86
86
  * Drain queue (remove all jobs)
87
87
  */
@@ -105,7 +105,7 @@ export declare const PriorityQueue: Readonly<{
105
105
  /**
106
106
  * Get queue instance (internal use)
107
107
  */
108
- getQueueInstance(queueName: string): Queue;
108
+ getQueueInstance(queueName: string): Promise<Queue>;
109
109
  /**
110
110
  * Close a queue
111
111
  */
@@ -3,7 +3,7 @@
3
3
  * BullMQ priority levels with datacenter affinity
4
4
  * Sealed namespace for immutability
5
5
  */
6
- import { ErrorFactory, Logger, NodeSingletons } from '@zintrust/core';
6
+ import { ErrorFactory, Logger } from '@zintrust/core';
7
7
  // Priority mappings
8
8
  const PRIORITY_VALUES = {
9
9
  critical: 10,
@@ -11,14 +11,13 @@ const PRIORITY_VALUES = {
11
11
  normal: 1,
12
12
  low: 0,
13
13
  };
14
- const require = NodeSingletons.module.createRequire(import.meta.url);
15
14
  let queueRedisModule;
16
15
  let hasWarnedMissingQueueRedis = false;
17
- const loadQueueRedisModule = () => {
16
+ const loadQueueRedisModule = async () => {
18
17
  if (queueRedisModule)
19
18
  return queueRedisModule;
20
19
  try {
21
- queueRedisModule = require('@zintrust/queue-redis');
20
+ queueRedisModule = (await import('@zintrust/queue-redis'));
22
21
  return queueRedisModule;
23
22
  }
24
23
  catch (error) {
@@ -32,8 +31,8 @@ const loadQueueRedisModule = () => {
32
31
  /**
33
32
  * Helper: Get or create queue via shared driver
34
33
  */
35
- const getQueue = (queueName) => {
36
- const queueRedis = loadQueueRedisModule();
34
+ const getQueue = async (queueName) => {
35
+ const queueRedis = await loadQueueRedisModule();
37
36
  if (!queueRedis) {
38
37
  throw ErrorFactory.createWorkerError('Optional package "@zintrust/queue-redis" is required for PriorityQueue. Install it to use queue features.');
39
38
  }
@@ -98,7 +97,7 @@ export const PriorityQueue = Object.freeze({
98
97
  * Add a job to the queue with priority
99
98
  */
100
99
  async addJob(queueName, jobName, data, options) {
101
- const queue = getQueue(queueName);
100
+ const queue = await getQueue(queueName);
102
101
  const jobOptions = buildJobOptions(options);
103
102
  try {
104
103
  const job = await queue.add(jobName, data, jobOptions);
@@ -121,7 +120,7 @@ export const PriorityQueue = Object.freeze({
121
120
  * Add multiple jobs in bulk
122
121
  */
123
122
  async addBulk(queueName, jobs) {
124
- const queue = getQueue(queueName);
123
+ const queue = await getQueue(queueName);
125
124
  try {
126
125
  const bulkJobs = jobs.map((job) => ({
127
126
  name: job.name,
@@ -146,14 +145,14 @@ export const PriorityQueue = Object.freeze({
146
145
  * Get job by ID
147
146
  */
148
147
  async getJob(queueName, jobId) {
149
- const queue = getQueue(queueName);
148
+ const queue = await getQueue(queueName);
150
149
  return queue.getJob(jobId);
151
150
  },
152
151
  /**
153
152
  * Remove a job
154
153
  */
155
154
  async removeJob(queueName, jobId) {
156
- const queue = getQueue(queueName);
155
+ const queue = await getQueue(queueName);
157
156
  const job = await queue.getJob(jobId);
158
157
  if (job) {
159
158
  await job.remove();
@@ -164,7 +163,7 @@ export const PriorityQueue = Object.freeze({
164
163
  * Pause a queue
165
164
  */
166
165
  async pause(queueName) {
167
- const queue = getQueue(queueName);
166
+ const queue = await getQueue(queueName);
168
167
  await queue.pause();
169
168
  Logger.info(`Paused queue "${queueName}"`);
170
169
  },
@@ -172,7 +171,7 @@ export const PriorityQueue = Object.freeze({
172
171
  * Resume a queue
173
172
  */
174
173
  async resume(queueName) {
175
- const queue = getQueue(queueName);
174
+ const queue = await getQueue(queueName);
176
175
  await queue.resume();
177
176
  Logger.info(`Resumed queue "${queueName}"`);
178
177
  },
@@ -180,7 +179,7 @@ export const PriorityQueue = Object.freeze({
180
179
  * Get queue information
181
180
  */
182
181
  async getQueueInfo(queueName) {
183
- const queue = getQueue(queueName);
182
+ const queue = await getQueue(queueName);
184
183
  const isPaused = await queue.isPaused();
185
184
  const jobCounts = await queue.getJobCounts();
186
185
  return {
@@ -199,8 +198,8 @@ export const PriorityQueue = Object.freeze({
199
198
  /**
200
199
  * Get all queue names
201
200
  */
202
- getQueueNames() {
203
- const queueRedis = loadQueueRedisModule();
201
+ async getQueueNames() {
202
+ const queueRedis = await loadQueueRedisModule();
204
203
  if (!queueRedis)
205
204
  return [];
206
205
  return queueRedis.BullMQRedisQueue.getQueueNames();
@@ -209,7 +208,7 @@ export const PriorityQueue = Object.freeze({
209
208
  * Drain queue (remove all jobs)
210
209
  */
211
210
  async drain(queueName, delayed = false) {
212
- const queue = getQueue(queueName);
211
+ const queue = await getQueue(queueName);
213
212
  await queue.drain(delayed);
214
213
  Logger.info(`Drained queue "${queueName}"`, { delayed });
215
214
  },
@@ -217,7 +216,7 @@ export const PriorityQueue = Object.freeze({
217
216
  * Clean old jobs from queue
218
217
  */
219
218
  async clean(queueName, grace, limit, type = 'completed') {
220
- const queue = getQueue(queueName);
219
+ const queue = await getQueue(queueName);
221
220
  const jobs = await queue.clean(grace, limit, type);
222
221
  Logger.info(`Cleaned ${jobs.length} ${type} jobs from queue "${queueName}"`);
223
222
  return jobs;
@@ -226,9 +225,9 @@ export const PriorityQueue = Object.freeze({
226
225
  * Obliterate queue (remove all data including queue itself)
227
226
  */
228
227
  async obliterate(queueName, force = false) {
229
- const queue = getQueue(queueName);
228
+ const queue = await getQueue(queueName);
230
229
  await queue.obliterate({ force });
231
- const queueRedis = loadQueueRedisModule();
230
+ const queueRedis = await loadQueueRedisModule();
232
231
  if (queueRedis) {
233
232
  await queueRedis.BullMQRedisQueue.closeQueue(queueName);
234
233
  }
@@ -256,7 +255,7 @@ export const PriorityQueue = Object.freeze({
256
255
  * Close a queue
257
256
  */
258
257
  async closeQueue(queueName) {
259
- const queueRedis = loadQueueRedisModule();
258
+ const queueRedis = await loadQueueRedisModule();
260
259
  if (!queueRedis)
261
260
  return;
262
261
  await queueRedis.BullMQRedisQueue.closeQueue(queueName);
@@ -267,7 +266,7 @@ export const PriorityQueue = Object.freeze({
267
266
  */
268
267
  async shutdown() {
269
268
  Logger.info('PriorityQueue shutting down via BullMQRedisQueue...');
270
- const queueRedis = loadQueueRedisModule();
269
+ const queueRedis = await loadQueueRedisModule();
271
270
  if (!queueRedis)
272
271
  return;
273
272
  await queueRedis.BullMQRedisQueue.shutdown();