@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.
- package/README.md +16 -1
- package/dist/AnomalyDetection.d.ts +4 -0
- package/dist/AnomalyDetection.js +8 -0
- package/dist/BroadcastWorker.d.ts +2 -0
- package/dist/CanaryController.js +49 -5
- package/dist/ChaosEngineering.js +13 -0
- package/dist/ClusterLock.js +21 -10
- package/dist/DeadLetterQueue.js +12 -8
- package/dist/MultiQueueWorker.d.ts +1 -1
- package/dist/MultiQueueWorker.js +12 -7
- package/dist/NotificationWorker.d.ts +2 -0
- package/dist/PriorityQueue.d.ts +2 -2
- package/dist/PriorityQueue.js +20 -21
- package/dist/ResourceMonitor.js +65 -38
- package/dist/WorkerFactory.d.ts +23 -3
- package/dist/WorkerFactory.js +420 -40
- package/dist/WorkerInit.js +8 -3
- package/dist/WorkerMetrics.d.ts +2 -1
- package/dist/WorkerMetrics.js +152 -93
- package/dist/WorkerRegistry.d.ts +6 -0
- package/dist/WorkerRegistry.js +70 -1
- package/dist/WorkerShutdown.d.ts +21 -0
- package/dist/WorkerShutdown.js +82 -9
- package/dist/WorkerShutdownDurableObject.d.ts +12 -0
- package/dist/WorkerShutdownDurableObject.js +41 -0
- package/dist/build-manifest.json +171 -99
- package/dist/createQueueWorker.d.ts +2 -0
- package/dist/createQueueWorker.js +42 -27
- package/dist/dashboard/types.d.ts +5 -0
- package/dist/dashboard/workers-api.js +136 -43
- package/dist/http/WorkerApiController.js +1 -0
- package/dist/http/WorkerController.js +133 -85
- package/dist/http/WorkerMonitoringService.d.ts +11 -0
- package/dist/http/WorkerMonitoringService.js +62 -0
- package/dist/http/middleware/CustomValidation.js +1 -1
- package/dist/http/middleware/EditWorkerValidation.d.ts +1 -1
- package/dist/http/middleware/EditWorkerValidation.js +7 -6
- package/dist/http/middleware/ProcessorPathSanitizer.js +101 -35
- package/dist/http/middleware/WorkerValidationChain.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/routes/workers.js +48 -6
- package/dist/storage/WorkerStore.d.ts +4 -1
- package/dist/storage/WorkerStore.js +55 -7
- package/dist/telemetry/api/TelemetryAPI.d.ts +46 -0
- package/dist/telemetry/api/TelemetryAPI.js +219 -0
- package/dist/telemetry/api/TelemetryMonitoringService.d.ts +17 -0
- package/dist/telemetry/api/TelemetryMonitoringService.js +113 -0
- package/dist/telemetry/components/AlertPanel.d.ts +1 -0
- package/dist/telemetry/components/AlertPanel.js +13 -0
- package/dist/telemetry/components/CostTracking.d.ts +1 -0
- package/dist/telemetry/components/CostTracking.js +14 -0
- package/dist/telemetry/components/ResourceUsageChart.d.ts +1 -0
- package/dist/telemetry/components/ResourceUsageChart.js +11 -0
- package/dist/telemetry/components/WorkerHealthChart.d.ts +1 -0
- package/dist/telemetry/components/WorkerHealthChart.js +11 -0
- package/dist/telemetry/index.d.ts +15 -0
- package/dist/telemetry/index.js +60 -0
- package/dist/telemetry/routes/dashboard.d.ts +6 -0
- package/dist/telemetry/routes/dashboard.js +608 -0
- package/dist/ui/router/EmbeddedAssets.d.ts +4 -0
- package/dist/ui/router/EmbeddedAssets.js +13 -0
- package/dist/ui/router/ui.js +100 -4
- package/package.json +10 -6
- package/src/AnomalyDetection.ts +9 -0
- package/src/CanaryController.ts +41 -5
- package/src/ChaosEngineering.ts +14 -0
- package/src/ClusterLock.ts +22 -9
- package/src/DeadLetterQueue.ts +13 -8
- package/src/MultiQueueWorker.ts +15 -8
- package/src/PriorityQueue.ts +21 -22
- package/src/ResourceMonitor.ts +72 -40
- package/src/WorkerFactory.ts +545 -49
- package/src/WorkerInit.ts +8 -3
- package/src/WorkerMetrics.ts +183 -105
- package/src/WorkerRegistry.ts +80 -1
- package/src/WorkerShutdown.ts +115 -9
- package/src/WorkerShutdownDurableObject.ts +64 -0
- package/src/createQueueWorker.ts +73 -30
- package/src/dashboard/types.ts +5 -0
- package/src/dashboard/workers-api.ts +165 -52
- package/src/http/WorkerApiController.ts +1 -0
- package/src/http/WorkerController.ts +167 -90
- package/src/http/WorkerMonitoringService.ts +77 -0
- package/src/http/middleware/CustomValidation.ts +1 -1
- package/src/http/middleware/EditWorkerValidation.ts +7 -6
- package/src/http/middleware/ProcessorPathSanitizer.ts +123 -36
- package/src/http/middleware/WorkerValidationChain.ts +1 -0
- package/src/index.ts +6 -1
- package/src/routes/workers.ts +66 -9
- package/src/storage/WorkerStore.ts +59 -9
- package/src/telemetry/api/TelemetryAPI.ts +292 -0
- package/src/telemetry/api/TelemetryMonitoringService.ts +149 -0
- package/src/telemetry/components/AlertPanel.ts +13 -0
- package/src/telemetry/components/CostTracking.ts +14 -0
- package/src/telemetry/components/ResourceUsageChart.ts +11 -0
- package/src/telemetry/components/WorkerHealthChart.ts +11 -0
- package/src/telemetry/index.ts +121 -0
- package/src/telemetry/public/assets/zintrust-logo.svg +15 -0
- package/src/telemetry/routes/dashboard.ts +638 -0
- package/src/telemetry/styles/tailwind.css +1 -0
- package/src/telemetry/styles/zintrust-theme.css +8 -0
- package/src/ui/router/EmbeddedAssets.ts +13 -0
- package/src/ui/router/ui.ts +112 -5
- package/src/ui/workers/index.html +2 -2
- package/src/ui/workers/main.js +232 -61
- package/src/ui/workers/zintrust.svg +30 -0
- package/dist/dashboard/workers-dashboard-ui.d.ts +0 -3
- package/dist/dashboard/workers-dashboard-ui.js +0 -1026
- package/dist/dashboard/workers-dashboard.d.ts +0 -4
- 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://
|
|
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
|
*/
|
package/dist/AnomalyDetection.js
CHANGED
|
@@ -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;
|
package/dist/CanaryController.js
CHANGED
|
@@ -103,7 +103,18 @@ const incrementTraffic = (workerName) => {
|
|
|
103
103
|
}
|
|
104
104
|
// eslint-disable-next-line no-restricted-syntax
|
|
105
105
|
const timer = setTimeout(() => {
|
|
106
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 });
|
package/dist/ChaosEngineering.js
CHANGED
|
@@ -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
|
package/dist/ClusterLock.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
const
|
|
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 !==
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
183
|
+
instanceId: getInstanceId(),
|
|
173
184
|
userId,
|
|
174
185
|
success: false,
|
|
175
186
|
});
|
package/dist/DeadLetterQueue.js
CHANGED
|
@@ -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
|
|
9
|
-
|
|
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 `${
|
|
22
|
+
return `${getDLQPrefix()}${queueName}`;
|
|
19
23
|
};
|
|
20
24
|
/**
|
|
21
25
|
* Helper: Get audit key
|
|
22
26
|
*/
|
|
23
27
|
const getAuditKey = (failedJobId) => {
|
|
24
|
-
return `${
|
|
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 = `${
|
|
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 = `${
|
|
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(
|
|
434
|
+
const queueName = key.replace(getDLQPrefix(), '');
|
|
431
435
|
const entries = await client.zrange(key, 0, -1);
|
|
432
436
|
return {
|
|
433
437
|
queueName,
|
package/dist/MultiQueueWorker.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/PriorityQueue.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/PriorityQueue.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* BullMQ priority levels with datacenter affinity
|
|
4
4
|
* Sealed namespace for immutability
|
|
5
5
|
*/
|
|
6
|
-
import { ErrorFactory, Logger
|
|
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 =
|
|
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();
|