atomic-queues 1.0.13
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 +686 -0
- package/dist/decorators/decorators.d.ts +67 -0
- package/dist/decorators/decorators.d.ts.map +1 -0
- package/dist/decorators/decorators.js +91 -0
- package/dist/decorators/decorators.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +18 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/domain/index.d.ts +5 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +21 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/interfaces.d.ts +614 -0
- package/dist/domain/interfaces.d.ts.map +1 -0
- package/dist/domain/interfaces.js +19 -0
- package/dist/domain/interfaces.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/module/atomic-queues.module.d.ts +97 -0
- package/dist/module/atomic-queues.module.d.ts.map +1 -0
- package/dist/module/atomic-queues.module.js +197 -0
- package/dist/module/atomic-queues.module.js.map +1 -0
- package/dist/module/index.d.ts +2 -0
- package/dist/module/index.d.ts.map +1 -0
- package/dist/module/index.js +18 -0
- package/dist/module/index.js.map +1 -0
- package/dist/services/constants.d.ts +10 -0
- package/dist/services/constants.d.ts.map +1 -0
- package/dist/services/constants.js +13 -0
- package/dist/services/constants.js.map +1 -0
- package/dist/services/cron-manager/cron-manager.service.d.ts +188 -0
- package/dist/services/cron-manager/cron-manager.service.d.ts.map +1 -0
- package/dist/services/cron-manager/cron-manager.service.js +534 -0
- package/dist/services/cron-manager/cron-manager.service.js.map +1 -0
- package/dist/services/cron-manager/index.d.ts +2 -0
- package/dist/services/cron-manager/index.d.ts.map +1 -0
- package/dist/services/cron-manager/index.js +18 -0
- package/dist/services/cron-manager/index.js.map +1 -0
- package/dist/services/index-manager/index-manager.service.d.ts +146 -0
- package/dist/services/index-manager/index-manager.service.d.ts.map +1 -0
- package/dist/services/index-manager/index-manager.service.js +337 -0
- package/dist/services/index-manager/index-manager.service.js.map +1 -0
- package/dist/services/index-manager/index.d.ts +2 -0
- package/dist/services/index-manager/index.d.ts.map +1 -0
- package/dist/services/index-manager/index.js +18 -0
- package/dist/services/index-manager/index.js.map +1 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +26 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/job-processor/index.d.ts +2 -0
- package/dist/services/job-processor/index.d.ts.map +1 -0
- package/dist/services/job-processor/index.js +18 -0
- package/dist/services/job-processor/index.js.map +1 -0
- package/dist/services/job-processor/job-processor.service.d.ts +156 -0
- package/dist/services/job-processor/job-processor.service.d.ts.map +1 -0
- package/dist/services/job-processor/job-processor.service.js +331 -0
- package/dist/services/job-processor/job-processor.service.js.map +1 -0
- package/dist/services/queue-manager/index.d.ts +2 -0
- package/dist/services/queue-manager/index.d.ts.map +1 -0
- package/dist/services/queue-manager/index.js +18 -0
- package/dist/services/queue-manager/index.js.map +1 -0
- package/dist/services/queue-manager/queue-manager.service.d.ts +128 -0
- package/dist/services/queue-manager/queue-manager.service.d.ts.map +1 -0
- package/dist/services/queue-manager/queue-manager.service.js +308 -0
- package/dist/services/queue-manager/queue-manager.service.js.map +1 -0
- package/dist/services/resource-lock/index.d.ts +2 -0
- package/dist/services/resource-lock/index.d.ts.map +1 -0
- package/dist/services/resource-lock/index.js +18 -0
- package/dist/services/resource-lock/index.js.map +1 -0
- package/dist/services/resource-lock/resource-lock.service.d.ts +124 -0
- package/dist/services/resource-lock/resource-lock.service.d.ts.map +1 -0
- package/dist/services/resource-lock/resource-lock.service.js +379 -0
- package/dist/services/resource-lock/resource-lock.service.js.map +1 -0
- package/dist/services/service-queue/index.d.ts +2 -0
- package/dist/services/service-queue/index.d.ts.map +1 -0
- package/dist/services/service-queue/index.js +18 -0
- package/dist/services/service-queue/index.js.map +1 -0
- package/dist/services/service-queue/service-queue.service.d.ts +232 -0
- package/dist/services/service-queue/service-queue.service.d.ts.map +1 -0
- package/dist/services/service-queue/service-queue.service.js +647 -0
- package/dist/services/service-queue/service-queue.service.js.map +1 -0
- package/dist/services/shutdown-state/index.d.ts +2 -0
- package/dist/services/shutdown-state/index.d.ts.map +1 -0
- package/dist/services/shutdown-state/index.js +18 -0
- package/dist/services/shutdown-state/index.js.map +1 -0
- package/dist/services/shutdown-state/shutdown-state.service.d.ts +69 -0
- package/dist/services/shutdown-state/shutdown-state.service.d.ts.map +1 -0
- package/dist/services/shutdown-state/shutdown-state.service.js +127 -0
- package/dist/services/shutdown-state/shutdown-state.service.js.map +1 -0
- package/dist/services/worker-manager/index.d.ts +2 -0
- package/dist/services/worker-manager/index.d.ts.map +1 -0
- package/dist/services/worker-manager/index.js +18 -0
- package/dist/services/worker-manager/index.js.map +1 -0
- package/dist/services/worker-manager/worker-manager.service.d.ts +163 -0
- package/dist/services/worker-manager/worker-manager.service.d.ts.map +1 -0
- package/dist/services/worker-manager/worker-manager.service.js +460 -0
- package/dist/services/worker-manager/worker-manager.service.js.map +1 -0
- package/dist/utils/helpers.d.ts +124 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +229 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
var WorkerManagerService_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.WorkerManagerService = void 0;
|
|
20
|
+
const common_1 = require("@nestjs/common");
|
|
21
|
+
const bullmq_1 = require("bullmq");
|
|
22
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
23
|
+
const uuid_1 = require("uuid");
|
|
24
|
+
const constants_1 = require("../constants");
|
|
25
|
+
/**
|
|
26
|
+
* WorkerManagerService
|
|
27
|
+
*
|
|
28
|
+
* Manages worker lifecycle with features from both Whatsapi and bl-blackjack-service:
|
|
29
|
+
*
|
|
30
|
+
* - Dynamic worker creation per entity (user message queue workers, table workers)
|
|
31
|
+
* - Heartbeat-based liveness tracking with TTL
|
|
32
|
+
* - Graceful shutdown via Redis pub/sub
|
|
33
|
+
* - Node-aware worker tracking (multi-instance support)
|
|
34
|
+
* - Automatic cleanup on application shutdown
|
|
35
|
+
*
|
|
36
|
+
* Architecture Notes:
|
|
37
|
+
* - Each worker registers itself with a heartbeat TTL
|
|
38
|
+
* - Workers subscribe to their own shutdown channel
|
|
39
|
+
* - A cron process monitors worker health and spawns/terminates as needed
|
|
40
|
+
* - On application shutdown, all node workers are signaled to close gracefully
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const worker = await workerManager.createWorker({
|
|
45
|
+
* workerName: `user-${userId}-worker`,
|
|
46
|
+
* queueName: `user-${userId}-queue`,
|
|
47
|
+
* processor: async (job) => {
|
|
48
|
+
* // Process job
|
|
49
|
+
* },
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
let WorkerManagerService = WorkerManagerService_1 = class WorkerManagerService {
|
|
54
|
+
constructor(redis, config) {
|
|
55
|
+
this.redis = redis;
|
|
56
|
+
this.config = config;
|
|
57
|
+
this.logger = new common_1.Logger(WorkerManagerService_1.name);
|
|
58
|
+
this.workers = new Map();
|
|
59
|
+
this.workerStates = new Map();
|
|
60
|
+
this.heartbeatIntervals = new Map();
|
|
61
|
+
this.shutdownSubscriptions = new Map();
|
|
62
|
+
this.subscriberClient = null;
|
|
63
|
+
this.nodeId = this.generateNodeId();
|
|
64
|
+
this.keyPrefix = config.keyPrefix || 'aq';
|
|
65
|
+
this.logger.log(`WorkerManager initialized with nodeId: ${this.nodeId}`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Initialize subscriber client for pub/sub communication.
|
|
69
|
+
*/
|
|
70
|
+
onModuleInit() {
|
|
71
|
+
this.subscriberClient = this.createSubscriberClient();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a new worker with automatic lifecycle management.
|
|
75
|
+
*
|
|
76
|
+
* This method:
|
|
77
|
+
* 1. Creates a BullMQ Worker
|
|
78
|
+
* 2. Sets up heartbeat TTL tracking
|
|
79
|
+
* 3. Subscribes to shutdown channel for graceful termination
|
|
80
|
+
* 4. Registers lifecycle event handlers
|
|
81
|
+
*/
|
|
82
|
+
async createWorker(options) {
|
|
83
|
+
const { workerName, queueName, config, events, processor } = options;
|
|
84
|
+
// Check if worker already exists
|
|
85
|
+
if (await this.workerExists(workerName)) {
|
|
86
|
+
this.logger.warn(`Worker ${workerName} already exists, skipping creation`);
|
|
87
|
+
const existingWorker = this.workers.get(workerName);
|
|
88
|
+
if (existingWorker)
|
|
89
|
+
return existingWorker;
|
|
90
|
+
}
|
|
91
|
+
this.logger.log(`Creating worker: ${workerName} for queue: ${queueName}`);
|
|
92
|
+
const workerConfig = this.mergeWorkerConfig(config);
|
|
93
|
+
// Create the BullMQ worker
|
|
94
|
+
const worker = new bullmq_1.Worker(queueName, processor, {
|
|
95
|
+
connection: this.redis.duplicate(),
|
|
96
|
+
concurrency: workerConfig.concurrency,
|
|
97
|
+
stalledInterval: workerConfig.stalledInterval,
|
|
98
|
+
lockDuration: workerConfig.lockDuration,
|
|
99
|
+
maxStalledCount: workerConfig.maxStalledCount,
|
|
100
|
+
});
|
|
101
|
+
// Store worker instance
|
|
102
|
+
this.workers.set(workerName, worker);
|
|
103
|
+
// Initialize worker state
|
|
104
|
+
const state = {
|
|
105
|
+
workerId: (0, uuid_1.v4)(),
|
|
106
|
+
workerName,
|
|
107
|
+
nodeId: this.nodeId,
|
|
108
|
+
status: 'starting',
|
|
109
|
+
createdAt: new Date(),
|
|
110
|
+
lastHeartbeat: new Date(),
|
|
111
|
+
};
|
|
112
|
+
this.workerStates.set(workerName, state);
|
|
113
|
+
// Set up heartbeat
|
|
114
|
+
this.setupHeartbeat(workerName, workerConfig.heartbeatTTL || 3);
|
|
115
|
+
// Set up shutdown subscription
|
|
116
|
+
await this.subscribeToShutdown(workerName, worker);
|
|
117
|
+
// Register event handlers
|
|
118
|
+
this.registerWorkerEvents(worker, workerName, events);
|
|
119
|
+
return worker;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if a worker exists and is alive (has valid heartbeat).
|
|
123
|
+
*/
|
|
124
|
+
async workerExists(workerName) {
|
|
125
|
+
const key = this.getWorkerKey(workerName);
|
|
126
|
+
const exists = await this.redis.exists(key);
|
|
127
|
+
return exists === 1;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get all running workers for the current node.
|
|
131
|
+
*/
|
|
132
|
+
async getNodeWorkers() {
|
|
133
|
+
const pattern = `${this.keyPrefix}:worker:${this.nodeId}:*`;
|
|
134
|
+
const keys = await this.redis.keys(pattern);
|
|
135
|
+
return keys.map((key) => key.split(':').pop());
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get all running workers across all nodes.
|
|
139
|
+
*/
|
|
140
|
+
async getAllWorkers() {
|
|
141
|
+
const pattern = `${this.keyPrefix}:worker:*:*`;
|
|
142
|
+
const keys = await this.redis.keys(pattern);
|
|
143
|
+
return keys.map((key) => key.split(':').pop());
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all workers for a specific entity.
|
|
147
|
+
* Uses the worker heartbeat TTL keys as the single source of truth.
|
|
148
|
+
* Worker names follow the pattern: {entityId}-worker
|
|
149
|
+
*/
|
|
150
|
+
async getEntityWorkers(entityType, entityId) {
|
|
151
|
+
// Worker heartbeat keys follow pattern: {prefix}:worker:{nodeId}:{workerName}
|
|
152
|
+
// Worker names follow pattern: {entityId}-worker
|
|
153
|
+
const workerName = `${entityId}-worker`;
|
|
154
|
+
const pattern = `${this.keyPrefix}:worker:*:${workerName}`;
|
|
155
|
+
const keys = await this.redis.keys(pattern);
|
|
156
|
+
return keys.map((key) => key.split(':').pop());
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Signal a worker to close gracefully via pub/sub.
|
|
160
|
+
*/
|
|
161
|
+
async signalWorkerClose(workerName) {
|
|
162
|
+
const channel = this.getWorkerShutdownChannel(workerName);
|
|
163
|
+
await this.redis.publish(channel, 'shutdown');
|
|
164
|
+
this.logger.log(`Sent shutdown signal to worker: ${workerName}`);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Signal all workers on current node to close.
|
|
168
|
+
*/
|
|
169
|
+
async signalNodeWorkersClose() {
|
|
170
|
+
const workers = await this.getNodeWorkers();
|
|
171
|
+
this.logger.log(`Signaling ${workers.length} workers to close on node ${this.nodeId}`);
|
|
172
|
+
await Promise.all(workers.map((workerName) => this.signalWorkerClose(workerName)));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Close all workers managed by this instance.
|
|
176
|
+
* This is the public API for external callers to gracefully shutdown workers.
|
|
177
|
+
*/
|
|
178
|
+
async closeAllWorkers(timeoutMs = 30000) {
|
|
179
|
+
this.logger.warn(`Closing all workers on node ${this.nodeId}`);
|
|
180
|
+
// Clear all heartbeat intervals
|
|
181
|
+
for (const [workerName, interval] of this.heartbeatIntervals) {
|
|
182
|
+
clearInterval(interval);
|
|
183
|
+
this.heartbeatIntervals.delete(workerName);
|
|
184
|
+
}
|
|
185
|
+
// Signal all workers to close
|
|
186
|
+
await this.signalNodeWorkersClose();
|
|
187
|
+
// Wait for workers to close
|
|
188
|
+
try {
|
|
189
|
+
await this.waitForWorkersToClose(timeoutMs);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logger.warn(`Some workers did not close gracefully: ${error}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Wait for all node workers to close with timeout.
|
|
197
|
+
*/
|
|
198
|
+
async waitForWorkersToClose(timeoutMs = 60000) {
|
|
199
|
+
const startTime = Date.now();
|
|
200
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
201
|
+
const workers = await this.getNodeWorkers();
|
|
202
|
+
if (workers.length === 0) {
|
|
203
|
+
this.logger.log(`All workers on node ${this.nodeId} have closed.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.logger.debug(`Waiting... ${workers.length} workers remaining.`);
|
|
207
|
+
await this.sleep(1000);
|
|
208
|
+
}
|
|
209
|
+
throw new Error(`Timeout reached while waiting for workers to close on node ${this.nodeId}`);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Reset worker heartbeat TTL.
|
|
213
|
+
*/
|
|
214
|
+
async resetWorkerHeartbeat(workerName, ttlSeconds) {
|
|
215
|
+
const ttl = ttlSeconds || this.config.workerDefaults?.heartbeatTTL || 3;
|
|
216
|
+
const key = this.getWorkerKey(workerName);
|
|
217
|
+
const exists = await this.redis.exists(key);
|
|
218
|
+
if (exists) {
|
|
219
|
+
await this.redis.expire(key, ttl);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
await this.redis.set(key, '1', 'EX', ttl);
|
|
223
|
+
}
|
|
224
|
+
// Update local state
|
|
225
|
+
const state = this.workerStates.get(workerName);
|
|
226
|
+
if (state) {
|
|
227
|
+
state.lastHeartbeat = new Date();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Remove worker heartbeat (mark as dead).
|
|
232
|
+
*/
|
|
233
|
+
async removeWorkerHeartbeat(workerName) {
|
|
234
|
+
const key = this.getWorkerKey(workerName);
|
|
235
|
+
await this.redis.del(key);
|
|
236
|
+
this.logger.debug(`Removed heartbeat for worker: ${workerName}`);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get the node ID for this instance.
|
|
240
|
+
*/
|
|
241
|
+
getNodeId() {
|
|
242
|
+
return this.nodeId;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Index a worker for an entity (for entity-based tracking).
|
|
246
|
+
*/
|
|
247
|
+
async indexEntityWorker(entityType, entityId, workerId, ttlSeconds = 3) {
|
|
248
|
+
const key = `${this.keyPrefix}:entity-worker:${entityType}:${entityId}:${workerId}`;
|
|
249
|
+
await this.redis.set(key, '1', 'EX', ttlSeconds);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Remove entity worker index.
|
|
253
|
+
*/
|
|
254
|
+
async removeEntityWorkerIndex(entityType, entityId, workerId) {
|
|
255
|
+
const key = `${this.keyPrefix}:entity-worker:${entityType}:${entityId}:${workerId}`;
|
|
256
|
+
await this.redis.del(key);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get subscriber client for pub/sub operations.
|
|
260
|
+
*/
|
|
261
|
+
getSubscriberClient() {
|
|
262
|
+
if (!this.subscriberClient) {
|
|
263
|
+
this.subscriberClient = this.createSubscriberClient();
|
|
264
|
+
}
|
|
265
|
+
return this.subscriberClient;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Graceful shutdown on application termination.
|
|
269
|
+
*/
|
|
270
|
+
async onApplicationShutdown() {
|
|
271
|
+
this.logger.warn(`Application shutting down, closing workers on node ${this.nodeId}`);
|
|
272
|
+
// Clear all heartbeat intervals
|
|
273
|
+
for (const [workerName, interval] of this.heartbeatIntervals) {
|
|
274
|
+
clearInterval(interval);
|
|
275
|
+
this.heartbeatIntervals.delete(workerName);
|
|
276
|
+
}
|
|
277
|
+
// Signal all workers to close
|
|
278
|
+
await this.signalNodeWorkersClose();
|
|
279
|
+
// Wait for workers to close
|
|
280
|
+
try {
|
|
281
|
+
await this.waitForWorkersToClose(30000);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
this.logger.warn(`Some workers did not close gracefully: ${error}`);
|
|
285
|
+
}
|
|
286
|
+
// Close subscriber client
|
|
287
|
+
if (this.subscriberClient) {
|
|
288
|
+
await this.subscriberClient.quit();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// =========================================================================
|
|
292
|
+
// PRIVATE METHODS
|
|
293
|
+
// =========================================================================
|
|
294
|
+
/**
|
|
295
|
+
* Generate a unique node ID for this instance.
|
|
296
|
+
*/
|
|
297
|
+
generateNodeId() {
|
|
298
|
+
return (0, uuid_1.v4)();
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Create a subscriber client for pub/sub.
|
|
302
|
+
*/
|
|
303
|
+
createSubscriberClient() {
|
|
304
|
+
return this.redis.duplicate();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get the Redis key for a worker's heartbeat.
|
|
308
|
+
*/
|
|
309
|
+
getWorkerKey(workerName) {
|
|
310
|
+
return `${this.keyPrefix}:worker:${this.nodeId}:${workerName}`;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get the shutdown channel for a worker.
|
|
314
|
+
*/
|
|
315
|
+
getWorkerShutdownChannel(workerName) {
|
|
316
|
+
return `${this.keyPrefix}:worker:${workerName}:shutdown`;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Merge worker config with defaults.
|
|
320
|
+
*/
|
|
321
|
+
mergeWorkerConfig(config) {
|
|
322
|
+
const defaults = {
|
|
323
|
+
concurrency: 1,
|
|
324
|
+
stalledInterval: 1000,
|
|
325
|
+
lockDuration: 30000,
|
|
326
|
+
maxStalledCount: Number.MAX_SAFE_INTEGER,
|
|
327
|
+
heartbeatTTL: 3,
|
|
328
|
+
heartbeatInterval: 1000,
|
|
329
|
+
};
|
|
330
|
+
return { ...defaults, ...this.config.workerDefaults, ...config };
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Set up heartbeat interval for a worker.
|
|
334
|
+
*/
|
|
335
|
+
setupHeartbeat(workerName, ttlSeconds) {
|
|
336
|
+
const interval = setInterval(async () => {
|
|
337
|
+
try {
|
|
338
|
+
await this.resetWorkerHeartbeat(workerName, ttlSeconds);
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
this.logger.error(`Failed to reset heartbeat for worker ${workerName}:`, error);
|
|
342
|
+
}
|
|
343
|
+
}, (ttlSeconds * 1000) / 2); // Update at half the TTL
|
|
344
|
+
this.heartbeatIntervals.set(workerName, interval);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Subscribe to shutdown channel for graceful termination.
|
|
348
|
+
*/
|
|
349
|
+
async subscribeToShutdown(workerName, worker) {
|
|
350
|
+
const channel = this.getWorkerShutdownChannel(workerName);
|
|
351
|
+
const subscriber = this.getSubscriberClient();
|
|
352
|
+
await subscriber.subscribe(channel);
|
|
353
|
+
this.logger.debug(`Subscribed to shutdown channel: ${channel}`);
|
|
354
|
+
const messageHandler = async (msgChannel) => {
|
|
355
|
+
if (msgChannel === channel) {
|
|
356
|
+
this.logger.log(`Received shutdown signal for worker: ${workerName}`);
|
|
357
|
+
await this.closeWorker(workerName, worker);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
subscriber.on('message', messageHandler);
|
|
361
|
+
// Store cleanup function
|
|
362
|
+
this.shutdownSubscriptions.set(workerName, () => {
|
|
363
|
+
subscriber.off('message', messageHandler);
|
|
364
|
+
subscriber.unsubscribe(channel).catch(() => { });
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Close a worker and clean up resources.
|
|
369
|
+
*/
|
|
370
|
+
async closeWorker(workerName, worker) {
|
|
371
|
+
// Clear heartbeat interval
|
|
372
|
+
const interval = this.heartbeatIntervals.get(workerName);
|
|
373
|
+
if (interval) {
|
|
374
|
+
clearInterval(interval);
|
|
375
|
+
this.heartbeatIntervals.delete(workerName);
|
|
376
|
+
}
|
|
377
|
+
// Unsubscribe from shutdown channel
|
|
378
|
+
const cleanup = this.shutdownSubscriptions.get(workerName);
|
|
379
|
+
if (cleanup) {
|
|
380
|
+
cleanup();
|
|
381
|
+
this.shutdownSubscriptions.delete(workerName);
|
|
382
|
+
}
|
|
383
|
+
// Remove heartbeat
|
|
384
|
+
await this.removeWorkerHeartbeat(workerName);
|
|
385
|
+
// Close worker
|
|
386
|
+
await worker.close();
|
|
387
|
+
// Remove from maps
|
|
388
|
+
this.workers.delete(workerName);
|
|
389
|
+
this.workerStates.delete(workerName);
|
|
390
|
+
this.logger.log(`Worker ${workerName} closed and cleaned up.`);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Register event handlers for a worker.
|
|
394
|
+
*/
|
|
395
|
+
registerWorkerEvents(worker, workerName, events) {
|
|
396
|
+
worker.on('ready', async () => {
|
|
397
|
+
const state = this.workerStates.get(workerName);
|
|
398
|
+
if (state)
|
|
399
|
+
state.status = 'ready';
|
|
400
|
+
await this.resetWorkerHeartbeat(workerName);
|
|
401
|
+
this.logger.log(`Worker ${workerName} is ready.`);
|
|
402
|
+
if (events?.onReady) {
|
|
403
|
+
await events.onReady(worker, workerName);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
worker.on('completed', async (job) => {
|
|
407
|
+
this.logger.debug(`Worker ${workerName} completed job: ${job.id}`);
|
|
408
|
+
if (events?.onCompleted) {
|
|
409
|
+
await events.onCompleted(job, workerName);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
worker.on('failed', async (job, error) => {
|
|
413
|
+
this.logger.error(`Worker ${workerName} failed job ${job?.id}: ${error.message}`);
|
|
414
|
+
if (events?.onFailed) {
|
|
415
|
+
await events.onFailed(job, error, workerName);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
worker.on('progress', async (job, progress) => {
|
|
419
|
+
this.logger.debug(`Worker ${workerName} job ${job.id} progress: ${JSON.stringify(progress)}`);
|
|
420
|
+
if (events?.onProgress) {
|
|
421
|
+
await events.onProgress(job, progress);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
worker.on('stalled', async (jobId) => {
|
|
425
|
+
this.logger.warn(`Worker ${workerName} job ${jobId} stalled`);
|
|
426
|
+
if (events?.onStalled) {
|
|
427
|
+
await events.onStalled(jobId, workerName);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
worker.on('closing', () => {
|
|
431
|
+
const state = this.workerStates.get(workerName);
|
|
432
|
+
if (state)
|
|
433
|
+
state.status = 'closing';
|
|
434
|
+
this.logger.log(`Worker ${workerName} is closing...`);
|
|
435
|
+
if (events?.onClosing) {
|
|
436
|
+
events.onClosing(workerName);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
worker.on('closed', async () => {
|
|
440
|
+
this.logger.log(`Worker ${workerName} closed.`);
|
|
441
|
+
if (events?.onClosed) {
|
|
442
|
+
await events.onClosed(workerName);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Sleep utility.
|
|
448
|
+
*/
|
|
449
|
+
sleep(ms) {
|
|
450
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
exports.WorkerManagerService = WorkerManagerService;
|
|
454
|
+
exports.WorkerManagerService = WorkerManagerService = WorkerManagerService_1 = __decorate([
|
|
455
|
+
(0, common_1.Injectable)(),
|
|
456
|
+
__param(0, (0, common_1.Inject)(constants_1.ATOMIC_QUEUES_REDIS)),
|
|
457
|
+
__param(1, (0, common_1.Inject)(constants_1.ATOMIC_QUEUES_CONFIG)),
|
|
458
|
+
__metadata("design:paramtypes", [ioredis_1.default, Object])
|
|
459
|
+
], WorkerManagerService);
|
|
460
|
+
//# sourceMappingURL=worker-manager.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-manager.service.js","sourceRoot":"","sources":["../../../src/services/worker-manager/worker-manager.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,2CAMwB;AACxB,mCAAqC;AACrC,sDAA4B;AAC5B,+BAAoC;AAQpC,4CAAyE;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEI,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAY/B,YAC+B,KAA6B,EAE1D,MAAkD;QAFJ,UAAK,GAAL,KAAK,CAAO;QAEzC,WAAM,GAAN,MAAM,CAA2B;QAZnC,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;QAE/C,YAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;QACzC,iBAAY,GAA8B,IAAI,GAAG,EAAE,CAAC;QACpD,uBAAkB,GAAgC,IAAI,GAAG,EAAE,CAAC;QAC5D,0BAAqB,GAA4B,IAAI,GAAG,EAAE,CAAC;QACpE,qBAAgB,GAAiB,IAAI,CAAC;QAQ5C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,0CAA0C,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED;;OAEG;IACH,YAAY;QACV,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,sBAAsB,EAAE,CAAC;IACxD,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,YAAY,CAAC,OAA+B;QAChD,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAErE,iCAAiC;QACjC,IAAI,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,UAAU,UAAU,oCAAoC,CACzD,CAAC;YACF,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACpD,IAAI,cAAc;gBAAE,OAAO,cAAc,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,UAAU,eAAe,SAAS,EAAE,CAAC,CAAC;QAE1E,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAEpD,2BAA2B;QAC3B,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,SAAS,EAAE,SAAS,EAAE;YAC9C,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;YAClC,WAAW,EAAE,YAAY,CAAC,WAAW;YACrC,eAAe,EAAE,YAAY,CAAC,eAAe;YAC7C,YAAY,EAAE,YAAY,CAAC,YAAY;YACvC,eAAe,EAAE,YAAY,CAAC,eAAe;SAC9C,CAAC,CAAC;QAEH,wBAAwB;QACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAErC,0BAA0B;QAC1B,MAAM,KAAK,GAAiB;YAC1B,QAAQ,EAAE,IAAA,SAAM,GAAE;YAClB,UAAU;YACV,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,aAAa,EAAE,IAAI,IAAI,EAAE;SAC1B,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAEzC,mBAAmB;QACnB,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,YAAY,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC;QAEhE,+BAA+B;QAC/B,MAAM,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAEnD,0BAA0B;QAC1B,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QAEtD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,UAAkB;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5C,OAAO,MAAM,KAAK,CAAC,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc;QAClB,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,WAAW,IAAI,CAAC,MAAM,IAAI,CAAC;QAC5D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,aAAa,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC;IAClD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,gBAAgB,CACpB,UAAkB,EAClB,QAAgB;QAEhB,8EAA8E;QAC9E,iDAAiD;QACjD,MAAM,UAAU,GAAG,GAAG,QAAQ,SAAS,CAAC;QACxC,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,aAAa,UAAU,EAAE,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,UAAkB;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC;QAC1D,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mCAAmC,UAAU,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,sBAAsB;QAC1B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,OAAO,CAAC,MAAM,6BAA6B,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAEvF,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAChE,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CAAC,SAAS,GAAG,KAAK;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+BAA+B,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAE/D,gCAAgC;QAChC,KAAK,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7D,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,CAAC;QAED,8BAA8B;QAC9B,MAAM,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAEpC,4BAA4B;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,qBAAqB,CAAC,SAAS,GAAG,KAAK;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;YAC1C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;YAE5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC;gBACnE,OAAO;YACT,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,OAAO,CAAC,MAAM,qBAAqB,CAAC,CAAC;YACrE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAED,MAAM,IAAI,KAAK,CACb,8DAA8D,IAAI,CAAC,MAAM,EAAE,CAC5E,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,oBAAoB,CACxB,UAAkB,EAClB,UAAmB;QAEnB,MAAM,GAAG,GAAG,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,YAAY,IAAI,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC5C,CAAC;QAED,qBAAqB;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,qBAAqB,CAAC,UAAkB;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,UAAU,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CACrB,UAAkB,EAClB,QAAgB,EAChB,QAAgB,EAChB,UAAU,GAAG,CAAC;QAEd,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,kBAAkB,UAAU,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;QACpF,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAC3B,UAAkB,EAClB,QAAgB,EAChB,QAAgB;QAEhB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,kBAAkB,UAAU,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;QACpF,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,mBAAmB;QACjB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,sBAAsB,EAAE,CAAC;QACxD,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,qBAAqB;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sDAAsD,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAEtF,gCAAgC;QAChC,KAAK,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7D,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,CAAC;QAED,8BAA8B;QAC9B,MAAM,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAEpC,4BAA4B;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,0BAA0B;QAC1B,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;QACrC,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,kBAAkB;IAClB,4EAA4E;IAE5E;;OAEG;IACK,cAAc;QACpB,OAAO,IAAA,SAAM,GAAE,CAAC;IAClB,CAAC;IAED;;OAEG;IACK,sBAAsB;QAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACK,YAAY,CAAC,UAAkB;QACrC,OAAO,GAAG,IAAI,CAAC,SAAS,WAAW,IAAI,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;IACjE,CAAC;IAED;;OAEG;IACK,wBAAwB,CAAC,UAAkB;QACjD,OAAO,GAAG,IAAI,CAAC,SAAS,WAAW,UAAU,WAAW,CAAC;IAC3D,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,MAAsB;QAC9C,MAAM,QAAQ,GAA4B;YACxC,WAAW,EAAE,CAAC;YACd,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,MAAM,CAAC,gBAAgB;YACxC,YAAY,EAAE,CAAC;YACf,iBAAiB,EAAE,IAAI;SACxB,CAAC;QAEF,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IACnE,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,UAAkB,EAAE,UAAkB;QAC3D,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YACtC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,oBAAoB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC1D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,wCAAwC,UAAU,GAAG,EACrD,KAAK,CACN,CAAC;YACJ,CAAC;QACH,CAAC,EAAE,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,yBAAyB;QAEtD,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB,CAC/B,UAAkB,EAClB,MAAc;QAEd,MAAM,OAAO,GAAG,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAE9C,MAAM,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,OAAO,EAAE,CAAC,CAAC;QAEhE,MAAM,cAAc,GAAG,KAAK,EAAE,UAAkB,EAAE,EAAE;YAClD,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;gBAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,wCAAwC,UAAU,EAAE,CAAC,CAAC;gBACtE,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC;QAEF,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QAEzC,yBAAyB;QACzB,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE;YAC9C,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;YAC1C,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,WAAW,CAAC,UAAkB,EAAE,MAAc;QAC1D,2BAA2B;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACzD,IAAI,QAAQ,EAAE,CAAC;YACb,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,CAAC;QAED,oCAAoC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3D,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC;QAED,mBAAmB;QACnB,MAAM,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;QAE7C,eAAe;QACf,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAErB,mBAAmB;QACnB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAErC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,UAAU,yBAAyB,CAAC,CAAC;IACjE,CAAC;IAED;;OAEG;IACK,oBAAoB,CAC1B,MAAc,EACd,UAAkB,EAClB,MAAyC;QAEzC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAChD,IAAI,KAAK;gBAAE,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC;YAElC,MAAM,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,UAAU,YAAY,CAAC,CAAC;YAElD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;YACxC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,UAAU,mBAAmB,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YACnE,IAAI,MAAM,EAAE,WAAW,EAAE,CAAC;gBACxB,MAAM,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAoB,EAAE,KAAY,EAAE,EAAE;YAC/D,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,UAAU,UAAU,eAAe,GAAG,EAAE,EAAE,KAAK,KAAK,CAAC,OAAO,EAAE,CAC/D,CAAC;YACF,IAAI,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACrB,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;YAChD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,GAAQ,EAAE,QAAa,EAAE,EAAE;YACtD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,UAAU,QAAQ,GAAG,CAAC,EAAE,cAAc,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC9F,IAAI,MAAM,EAAE,UAAU,EAAE,CAAC;gBACvB,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,KAAa,EAAE,EAAE;YAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,UAAU,QAAQ,KAAK,UAAU,CAAC,CAAC;YAC9D,IAAI,MAAM,EAAE,SAAS,EAAE,CAAC;gBACtB,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAChD,IAAI,KAAK;gBAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;YAEpC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,UAAU,gBAAgB,CAAC,CAAC;YACtD,IAAI,MAAM,EAAE,SAAS,EAAE,CAAC;gBACtB,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;YAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,UAAU,UAAU,CAAC,CAAC;YAChD,IAAI,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACrB,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YACpC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,EAAU;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;CACF,CAAA;AAxfY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAcR,WAAA,IAAA,eAAM,EAAC,+BAAmB,CAAC,CAAA;IAC3B,WAAA,IAAA,eAAM,EAAC,gCAAoB,CAAC,CAAA;qCADwB,iBAAK;GAbjD,oBAAoB,CAwfhC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { IAtomicJobData, IJobOptions } from '../domain';
|
|
2
|
+
/**
|
|
3
|
+
* Create an atomic job data payload.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const jobData = createAtomicJobData({
|
|
8
|
+
* entityType: 'user',
|
|
9
|
+
* entityId: '123',
|
|
10
|
+
* type: 'command',
|
|
11
|
+
* commandName: 'SendMessageCommand',
|
|
12
|
+
* payload: { message: 'Hello!' },
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* await queueManager.addJob('user-123-queue', 'send-message', jobData);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function createAtomicJobData<T = unknown>(options: {
|
|
19
|
+
entityType: string;
|
|
20
|
+
entityId: string;
|
|
21
|
+
type: 'command' | 'query' | 'custom';
|
|
22
|
+
commandName?: string;
|
|
23
|
+
payload: T;
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}): IAtomicJobData<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Create default job options with common settings.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const options = createDefaultJobOptions({
|
|
32
|
+
* priority: 0, // Highest priority
|
|
33
|
+
* attempts: 5,
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function createDefaultJobOptions(overrides?: Partial<IJobOptions>): IJobOptions;
|
|
38
|
+
/**
|
|
39
|
+
* Create high-priority job options (for SIGTERM signals, etc.)
|
|
40
|
+
*/
|
|
41
|
+
export declare function createHighPriorityJobOptions(overrides?: Partial<IJobOptions>): IJobOptions;
|
|
42
|
+
/**
|
|
43
|
+
* Sleep utility for async operations.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Retry utility with exponential backoff.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const result = await retry(
|
|
52
|
+
* () => someUnreliableOperation(),
|
|
53
|
+
* { maxAttempts: 3, baseDelay: 1000 },
|
|
54
|
+
* );
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function retry<T>(fn: () => Promise<T>, options: {
|
|
58
|
+
maxAttempts: number;
|
|
59
|
+
baseDelay: number;
|
|
60
|
+
maxDelay?: number;
|
|
61
|
+
exponential?: boolean;
|
|
62
|
+
onRetry?: (attempt: number, error: Error) => void;
|
|
63
|
+
}): Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* Generate a queue name for an entity.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getEntityQueueName(entityType: string, entityId: string, prefix?: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Generate a worker name for an entity.
|
|
70
|
+
*/
|
|
71
|
+
export declare function getEntityWorkerName(entityType: string, entityId: string, prefix?: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Parse a queue name to extract entity info.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseQueueName(queueName: string): {
|
|
76
|
+
prefix: string;
|
|
77
|
+
entityType: string;
|
|
78
|
+
entityId: string;
|
|
79
|
+
} | null;
|
|
80
|
+
/**
|
|
81
|
+
* Create a SIGTERM job payload for worker termination.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createSigtermPayload<T = unknown>(entityType: string, entityId: string): IAtomicJobData<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Check if a job is a SIGTERM signal.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isSigtermJob(data: IAtomicJobData): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Batch utility for processing items in chunks.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* await batch(items, 10, async (chunk) => {
|
|
94
|
+
* await Promise.all(chunk.map(processItem));
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function batch<T>(items: T[], batchSize: number, processor: (batch: T[]) => Promise<void>): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a timeout promise that rejects after specified ms.
|
|
101
|
+
*/
|
|
102
|
+
export declare function createTimeout(ms: number, message?: string): Promise<never>;
|
|
103
|
+
/**
|
|
104
|
+
* Run an operation with a timeout.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const result = await withTimeout(
|
|
109
|
+
* someAsyncOperation(),
|
|
110
|
+
* 5000,
|
|
111
|
+
* 'Operation took too long',
|
|
112
|
+
* );
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
|
|
116
|
+
/**
|
|
117
|
+
* Debounce utility for functions.
|
|
118
|
+
*/
|
|
119
|
+
export declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): (...args: Parameters<T>) => void;
|
|
120
|
+
/**
|
|
121
|
+
* Throttle utility for functions.
|
|
122
|
+
*/
|
|
123
|
+
export declare function throttle<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): (...args: Parameters<T>) => void;
|
|
124
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"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"}
|