@claude-flow/cli 3.0.0-alpha.29 → 3.0.0-alpha.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.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Worker Queue Service
3
+ * Redis-based task queue for distributed headless worker execution.
4
+ *
5
+ * ADR-020: Headless Worker Integration Architecture - Phase 4
6
+ * - Priority-based task scheduling
7
+ * - Result persistence and retrieval
8
+ * - Distributed locking for task assignment
9
+ * - Dead letter queue for failed tasks
10
+ * - Metrics and monitoring
11
+ *
12
+ * Key Features:
13
+ * - FIFO with priority levels (critical, high, normal, low)
14
+ * - Automatic retry with exponential backoff
15
+ * - Task timeout detection
16
+ * - Result caching with TTL
17
+ * - Worker heartbeat monitoring
18
+ */
19
+ import { EventEmitter } from 'events';
20
+ import type { HeadlessWorkerType, HeadlessExecutionResult, WorkerPriority } from './headless-worker-executor.js';
21
+ /**
22
+ * Task status
23
+ */
24
+ export type TaskStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'timeout' | 'cancelled';
25
+ /**
26
+ * Queue task
27
+ */
28
+ export interface QueueTask {
29
+ id: string;
30
+ workerType: HeadlessWorkerType;
31
+ priority: WorkerPriority;
32
+ payload: {
33
+ prompt?: string;
34
+ contextPatterns?: string[];
35
+ sandbox?: string;
36
+ model?: string;
37
+ timeoutMs?: number;
38
+ };
39
+ status: TaskStatus;
40
+ createdAt: Date;
41
+ startedAt?: Date;
42
+ completedAt?: Date;
43
+ workerId?: string;
44
+ retryCount: number;
45
+ maxRetries: number;
46
+ result?: HeadlessExecutionResult;
47
+ error?: string;
48
+ }
49
+ /**
50
+ * Queue configuration
51
+ */
52
+ export interface WorkerQueueConfig {
53
+ /** Redis connection URL */
54
+ redisUrl: string;
55
+ /** Queue name prefix */
56
+ queuePrefix: string;
57
+ /** Default task timeout in ms */
58
+ defaultTimeoutMs: number;
59
+ /** Maximum retries for failed tasks */
60
+ maxRetries: number;
61
+ /** Task result TTL in seconds */
62
+ resultTtlSeconds: number;
63
+ /** Worker heartbeat interval in ms */
64
+ heartbeatIntervalMs: number;
65
+ /** Dead letter queue enabled */
66
+ deadLetterEnabled: boolean;
67
+ /** Visibility timeout in ms (task processing lock) */
68
+ visibilityTimeoutMs: number;
69
+ }
70
+ /**
71
+ * Queue statistics
72
+ */
73
+ export interface QueueStats {
74
+ pending: number;
75
+ processing: number;
76
+ completed: number;
77
+ failed: number;
78
+ deadLetter: number;
79
+ byPriority: Record<WorkerPriority, number>;
80
+ byWorkerType: Partial<Record<HeadlessWorkerType, number>>;
81
+ averageWaitTimeMs: number;
82
+ averageProcessingTimeMs: number;
83
+ }
84
+ /**
85
+ * Worker registration info
86
+ */
87
+ export interface WorkerRegistration {
88
+ workerId: string;
89
+ workerTypes: HeadlessWorkerType[];
90
+ maxConcurrent: number;
91
+ currentTasks: number;
92
+ lastHeartbeat: Date;
93
+ registeredAt: Date;
94
+ hostname?: string;
95
+ containerId?: string;
96
+ }
97
+ /**
98
+ * WorkerQueue - Redis-based task queue for distributed worker execution
99
+ */
100
+ export declare class WorkerQueue extends EventEmitter {
101
+ private config;
102
+ private store;
103
+ private workerId;
104
+ private heartbeatTimer?;
105
+ private processingTasks;
106
+ private isShuttingDown;
107
+ private maxConcurrent;
108
+ private initialized;
109
+ constructor(config?: Partial<WorkerQueueConfig>);
110
+ /**
111
+ * Initialize the queue (starts cleanup timers)
112
+ */
113
+ initialize(): Promise<void>;
114
+ /**
115
+ * Enqueue a new task
116
+ */
117
+ enqueue(workerType: HeadlessWorkerType, payload?: QueueTask['payload'], options?: {
118
+ priority?: WorkerPriority;
119
+ maxRetries?: number;
120
+ timeoutMs?: number;
121
+ }): Promise<string>;
122
+ /**
123
+ * Dequeue a task for processing
124
+ */
125
+ dequeue(workerTypes: HeadlessWorkerType[]): Promise<QueueTask | null>;
126
+ /**
127
+ * Complete a task with result
128
+ */
129
+ complete(taskId: string, result: HeadlessExecutionResult): Promise<void>;
130
+ /**
131
+ * Fail a task with error
132
+ */
133
+ fail(taskId: string, error: string, retryable?: boolean): Promise<void>;
134
+ /**
135
+ * Get task status
136
+ */
137
+ getTask(taskId: string): Promise<QueueTask | null>;
138
+ /**
139
+ * Get task result
140
+ */
141
+ getResult(taskId: string): Promise<HeadlessExecutionResult | null>;
142
+ /**
143
+ * Cancel a pending task
144
+ */
145
+ cancel(taskId: string): Promise<boolean>;
146
+ /**
147
+ * Register this instance as a worker
148
+ */
149
+ registerWorker(workerTypes: HeadlessWorkerType[], options?: {
150
+ maxConcurrent?: number;
151
+ hostname?: string;
152
+ containerId?: string;
153
+ }): Promise<string>;
154
+ /**
155
+ * Unregister this worker
156
+ */
157
+ unregisterWorker(): Promise<void>;
158
+ /**
159
+ * Get all registered workers
160
+ */
161
+ getWorkers(): Promise<WorkerRegistration[]>;
162
+ /**
163
+ * Get queue statistics
164
+ */
165
+ getStats(): Promise<QueueStats>;
166
+ /**
167
+ * Start processing tasks
168
+ */
169
+ start(workerTypes: HeadlessWorkerType[], handler: (task: QueueTask) => Promise<HeadlessExecutionResult>, options?: {
170
+ maxConcurrent?: number;
171
+ }): Promise<void>;
172
+ /**
173
+ * Process a single task
174
+ */
175
+ private processTask;
176
+ /**
177
+ * Shutdown the queue gracefully
178
+ */
179
+ shutdown(): Promise<void>;
180
+ /**
181
+ * Get queue name for worker type
182
+ */
183
+ private getQueueName;
184
+ /**
185
+ * Start heartbeat timer
186
+ */
187
+ private startHeartbeat;
188
+ /**
189
+ * Stop heartbeat timer
190
+ */
191
+ private stopHeartbeat;
192
+ }
193
+ export default WorkerQueue;
194
+ //# sourceMappingURL=worker-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-queue.d.ts","sourceRoot":"","sources":["../../../src/services/worker-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,511 @@
1
+ /**
2
+ * Worker Queue Service
3
+ * Redis-based task queue for distributed headless worker execution.
4
+ *
5
+ * ADR-020: Headless Worker Integration Architecture - Phase 4
6
+ * - Priority-based task scheduling
7
+ * - Result persistence and retrieval
8
+ * - Distributed locking for task assignment
9
+ * - Dead letter queue for failed tasks
10
+ * - Metrics and monitoring
11
+ *
12
+ * Key Features:
13
+ * - FIFO with priority levels (critical, high, normal, low)
14
+ * - Automatic retry with exponential backoff
15
+ * - Task timeout detection
16
+ * - Result caching with TTL
17
+ * - Worker heartbeat monitoring
18
+ */
19
+ import { EventEmitter } from 'events';
20
+ import { randomUUID } from 'crypto';
21
+ // ============================================
22
+ // Constants
23
+ // ============================================
24
+ const DEFAULT_CONFIG = {
25
+ redisUrl: 'redis://localhost:6379',
26
+ queuePrefix: 'claude-flow:queue',
27
+ defaultTimeoutMs: 300000, // 5 minutes
28
+ maxRetries: 3,
29
+ resultTtlSeconds: 86400, // 24 hours
30
+ heartbeatIntervalMs: 30000, // 30 seconds
31
+ deadLetterEnabled: true,
32
+ visibilityTimeoutMs: 60000, // 1 minute
33
+ };
34
+ const PRIORITY_SCORES = {
35
+ critical: 4,
36
+ high: 3,
37
+ normal: 2,
38
+ low: 1,
39
+ };
40
+ // ============================================
41
+ // In-Memory Redis Simulation (for non-Redis environments)
42
+ // ============================================
43
+ /**
44
+ * Simple in-memory queue implementation for environments without Redis
45
+ * Production should use actual Redis connection
46
+ */
47
+ class InMemoryStore {
48
+ tasks = new Map();
49
+ queues = new Map();
50
+ workers = new Map();
51
+ results = new Map();
52
+ cleanupTimer;
53
+ /**
54
+ * Start cleanup timer (called after initialization)
55
+ */
56
+ startCleanup() {
57
+ if (this.cleanupTimer)
58
+ return;
59
+ this.cleanupTimer = setInterval(() => this.cleanupExpired(), 60000);
60
+ }
61
+ /**
62
+ * Stop cleanup timer
63
+ */
64
+ stopCleanup() {
65
+ if (this.cleanupTimer) {
66
+ clearInterval(this.cleanupTimer);
67
+ this.cleanupTimer = undefined;
68
+ }
69
+ }
70
+ // Task operations
71
+ setTask(id, task) {
72
+ this.tasks.set(id, task);
73
+ }
74
+ getTask(id) {
75
+ return this.tasks.get(id);
76
+ }
77
+ deleteTask(id) {
78
+ this.tasks.delete(id);
79
+ }
80
+ // Queue operations
81
+ pushToQueue(queue, taskId, priority) {
82
+ const queueTasks = this.queues.get(queue) || [];
83
+ // Insert based on priority (higher priority = earlier in queue)
84
+ let insertIndex = queueTasks.length;
85
+ for (let i = 0; i < queueTasks.length; i++) {
86
+ const task = this.tasks.get(queueTasks[i]);
87
+ if (task && PRIORITY_SCORES[task.priority] < priority) {
88
+ insertIndex = i;
89
+ break;
90
+ }
91
+ }
92
+ queueTasks.splice(insertIndex, 0, taskId);
93
+ this.queues.set(queue, queueTasks);
94
+ }
95
+ popFromQueue(queue) {
96
+ const queueTasks = this.queues.get(queue) || [];
97
+ if (queueTasks.length === 0)
98
+ return null;
99
+ return queueTasks.shift() || null;
100
+ }
101
+ getQueueLength(queue) {
102
+ return (this.queues.get(queue) || []).length;
103
+ }
104
+ // Worker operations
105
+ setWorker(workerId, registration) {
106
+ this.workers.set(workerId, registration);
107
+ }
108
+ getWorker(workerId) {
109
+ return this.workers.get(workerId);
110
+ }
111
+ deleteWorker(workerId) {
112
+ this.workers.delete(workerId);
113
+ }
114
+ getAllWorkers() {
115
+ return Array.from(this.workers.values());
116
+ }
117
+ // Result operations
118
+ setResult(taskId, result, ttlSeconds) {
119
+ this.results.set(taskId, {
120
+ result,
121
+ expiresAt: Date.now() + ttlSeconds * 1000,
122
+ });
123
+ }
124
+ getResult(taskId) {
125
+ const entry = this.results.get(taskId);
126
+ if (!entry)
127
+ return undefined;
128
+ if (Date.now() > entry.expiresAt) {
129
+ this.results.delete(taskId);
130
+ return undefined;
131
+ }
132
+ return entry.result;
133
+ }
134
+ // Stats
135
+ getStats() {
136
+ return {
137
+ tasks: this.tasks.size,
138
+ workers: this.workers.size,
139
+ results: this.results.size,
140
+ };
141
+ }
142
+ // Cleanup
143
+ cleanupExpired() {
144
+ const now = Date.now();
145
+ for (const [id, entry] of this.results) {
146
+ if (now > entry.expiresAt) {
147
+ this.results.delete(id);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ // ============================================
153
+ // WorkerQueue Class
154
+ // ============================================
155
+ /**
156
+ * WorkerQueue - Redis-based task queue for distributed worker execution
157
+ */
158
+ export class WorkerQueue extends EventEmitter {
159
+ config;
160
+ store;
161
+ workerId;
162
+ heartbeatTimer;
163
+ processingTasks = new Set();
164
+ isShuttingDown = false;
165
+ maxConcurrent = 1;
166
+ initialized = false;
167
+ constructor(config) {
168
+ super();
169
+ this.config = { ...DEFAULT_CONFIG, ...config };
170
+ this.store = new InMemoryStore();
171
+ this.workerId = `worker-${randomUUID().slice(0, 8)}`;
172
+ }
173
+ /**
174
+ * Initialize the queue (starts cleanup timers)
175
+ */
176
+ async initialize() {
177
+ if (this.initialized)
178
+ return;
179
+ this.store.startCleanup();
180
+ this.initialized = true;
181
+ this.emit('initialized', { workerId: this.workerId });
182
+ }
183
+ // ============================================
184
+ // Public API - Task Management
185
+ // ============================================
186
+ /**
187
+ * Enqueue a new task
188
+ */
189
+ async enqueue(workerType, payload = {}, options) {
190
+ // Initialize if needed
191
+ if (!this.initialized) {
192
+ await this.initialize();
193
+ }
194
+ // Validate worker type
195
+ if (!workerType || typeof workerType !== 'string') {
196
+ throw new Error('Invalid worker type');
197
+ }
198
+ // Validate priority
199
+ const priority = options?.priority || 'normal';
200
+ if (!['critical', 'high', 'normal', 'low'].includes(priority)) {
201
+ throw new Error(`Invalid priority: ${priority}`);
202
+ }
203
+ const taskId = `task-${Date.now()}-${randomUUID().slice(0, 8)}`;
204
+ const task = {
205
+ id: taskId,
206
+ workerType,
207
+ priority,
208
+ payload: {
209
+ ...payload,
210
+ timeoutMs: options?.timeoutMs || this.config.defaultTimeoutMs,
211
+ },
212
+ status: 'pending',
213
+ createdAt: new Date(),
214
+ retryCount: 0,
215
+ maxRetries: options?.maxRetries ?? this.config.maxRetries,
216
+ };
217
+ // Store task
218
+ this.store.setTask(taskId, task);
219
+ // Add to priority queue
220
+ const queueName = this.getQueueName(workerType);
221
+ this.store.pushToQueue(queueName, taskId, PRIORITY_SCORES[priority]);
222
+ this.emit('taskEnqueued', { taskId, workerType, priority });
223
+ return taskId;
224
+ }
225
+ /**
226
+ * Dequeue a task for processing
227
+ */
228
+ async dequeue(workerTypes) {
229
+ if (this.isShuttingDown)
230
+ return null;
231
+ // Check queues in priority order
232
+ for (const workerType of workerTypes) {
233
+ const queueName = this.getQueueName(workerType);
234
+ const taskId = this.store.popFromQueue(queueName);
235
+ if (taskId) {
236
+ const task = this.store.getTask(taskId);
237
+ if (task && task.status === 'pending') {
238
+ task.status = 'processing';
239
+ task.startedAt = new Date();
240
+ task.workerId = this.workerId;
241
+ this.store.setTask(taskId, task);
242
+ this.processingTasks.add(taskId);
243
+ this.emit('taskDequeued', { taskId, workerType });
244
+ return task;
245
+ }
246
+ }
247
+ }
248
+ return null;
249
+ }
250
+ /**
251
+ * Complete a task with result
252
+ */
253
+ async complete(taskId, result) {
254
+ const task = this.store.getTask(taskId);
255
+ if (!task) {
256
+ this.emit('warning', { message: `Task ${taskId} not found for completion` });
257
+ return;
258
+ }
259
+ task.status = 'completed';
260
+ task.completedAt = new Date();
261
+ task.result = result;
262
+ this.store.setTask(taskId, task);
263
+ // Store result with TTL
264
+ this.store.setResult(taskId, result, this.config.resultTtlSeconds);
265
+ this.processingTasks.delete(taskId);
266
+ this.emit('taskCompleted', { taskId, result, duration: task.completedAt.getTime() - (task.startedAt?.getTime() || 0) });
267
+ }
268
+ /**
269
+ * Fail a task with error
270
+ */
271
+ async fail(taskId, error, retryable = true) {
272
+ const task = this.store.getTask(taskId);
273
+ if (!task) {
274
+ this.emit('warning', { message: `Task ${taskId} not found for failure` });
275
+ return;
276
+ }
277
+ this.processingTasks.delete(taskId);
278
+ // Check if we should retry
279
+ if (retryable && task.retryCount < task.maxRetries) {
280
+ task.retryCount++;
281
+ task.status = 'pending';
282
+ task.startedAt = undefined;
283
+ task.workerId = undefined;
284
+ task.error = error;
285
+ this.store.setTask(taskId, task);
286
+ // Re-queue with delay (exponential backoff)
287
+ const delay = Math.min(30000, 1000 * Math.pow(2, task.retryCount));
288
+ setTimeout(() => {
289
+ const queueName = this.getQueueName(task.workerType);
290
+ this.store.pushToQueue(queueName, taskId, PRIORITY_SCORES[task.priority]);
291
+ }, delay);
292
+ this.emit('taskRetrying', { taskId, retryCount: task.retryCount, delay });
293
+ }
294
+ else {
295
+ // Move to failed/dead letter
296
+ task.status = 'failed';
297
+ task.completedAt = new Date();
298
+ task.error = error;
299
+ this.store.setTask(taskId, task);
300
+ if (this.config.deadLetterEnabled) {
301
+ const dlqName = `${this.config.queuePrefix}:dlq`;
302
+ this.store.pushToQueue(dlqName, taskId, 0);
303
+ }
304
+ this.emit('taskFailed', { taskId, error, retryCount: task.retryCount });
305
+ }
306
+ }
307
+ /**
308
+ * Get task status
309
+ */
310
+ async getTask(taskId) {
311
+ return this.store.getTask(taskId) || null;
312
+ }
313
+ /**
314
+ * Get task result
315
+ */
316
+ async getResult(taskId) {
317
+ return this.store.getResult(taskId) || null;
318
+ }
319
+ /**
320
+ * Cancel a pending task
321
+ */
322
+ async cancel(taskId) {
323
+ const task = this.store.getTask(taskId);
324
+ if (!task || task.status !== 'pending') {
325
+ return false;
326
+ }
327
+ task.status = 'cancelled';
328
+ task.completedAt = new Date();
329
+ this.store.setTask(taskId, task);
330
+ this.emit('taskCancelled', { taskId });
331
+ return true;
332
+ }
333
+ // ============================================
334
+ // Public API - Worker Management
335
+ // ============================================
336
+ /**
337
+ * Register this instance as a worker
338
+ */
339
+ async registerWorker(workerTypes, options) {
340
+ // Initialize if needed
341
+ if (!this.initialized) {
342
+ await this.initialize();
343
+ }
344
+ // Validate worker types
345
+ if (!Array.isArray(workerTypes) || workerTypes.length === 0) {
346
+ throw new Error('Worker types must be a non-empty array');
347
+ }
348
+ this.maxConcurrent = options?.maxConcurrent || 1;
349
+ const registration = {
350
+ workerId: this.workerId,
351
+ workerTypes,
352
+ maxConcurrent: this.maxConcurrent,
353
+ currentTasks: 0,
354
+ lastHeartbeat: new Date(),
355
+ registeredAt: new Date(),
356
+ hostname: options?.hostname,
357
+ containerId: options?.containerId,
358
+ };
359
+ this.store.setWorker(this.workerId, registration);
360
+ // Start heartbeat
361
+ this.startHeartbeat();
362
+ this.emit('workerRegistered', { workerId: this.workerId, workerTypes });
363
+ return this.workerId;
364
+ }
365
+ /**
366
+ * Unregister this worker
367
+ */
368
+ async unregisterWorker() {
369
+ this.stopHeartbeat();
370
+ this.store.deleteWorker(this.workerId);
371
+ this.emit('workerUnregistered', { workerId: this.workerId });
372
+ }
373
+ /**
374
+ * Get all registered workers
375
+ */
376
+ async getWorkers() {
377
+ return this.store.getAllWorkers();
378
+ }
379
+ // ============================================
380
+ // Public API - Statistics
381
+ // ============================================
382
+ /**
383
+ * Get queue statistics
384
+ */
385
+ async getStats() {
386
+ const storeStats = this.store.getStats();
387
+ // This is a simplified implementation
388
+ // Full implementation would aggregate across all queues
389
+ const stats = {
390
+ pending: 0,
391
+ processing: this.processingTasks.size,
392
+ completed: 0,
393
+ failed: 0,
394
+ deadLetter: 0,
395
+ byPriority: { critical: 0, high: 0, normal: 0, low: 0 },
396
+ byWorkerType: {},
397
+ averageWaitTimeMs: 0,
398
+ averageProcessingTimeMs: 0,
399
+ };
400
+ return stats;
401
+ }
402
+ // ============================================
403
+ // Public API - Lifecycle
404
+ // ============================================
405
+ /**
406
+ * Start processing tasks
407
+ */
408
+ async start(workerTypes, handler, options) {
409
+ await this.registerWorker(workerTypes, { maxConcurrent: options?.maxConcurrent });
410
+ const processLoop = async () => {
411
+ while (!this.isShuttingDown) {
412
+ try {
413
+ // Respect concurrency limit
414
+ if (this.processingTasks.size >= this.maxConcurrent) {
415
+ await new Promise(resolve => setTimeout(resolve, 100));
416
+ continue;
417
+ }
418
+ const task = await this.dequeue(workerTypes);
419
+ if (task) {
420
+ // Process task without blocking the loop (allows concurrency)
421
+ this.processTask(task, handler).catch(error => {
422
+ this.emit('error', { taskId: task.id, error: String(error) });
423
+ });
424
+ }
425
+ else {
426
+ // No task available, wait before polling again
427
+ await new Promise(resolve => setTimeout(resolve, 1000));
428
+ }
429
+ }
430
+ catch (error) {
431
+ this.emit('error', { error: error instanceof Error ? error.message : String(error) });
432
+ // Wait before retrying to avoid tight error loop
433
+ await new Promise(resolve => setTimeout(resolve, 5000));
434
+ }
435
+ }
436
+ };
437
+ // Start processing
438
+ processLoop().catch(error => {
439
+ this.emit('error', { error: error instanceof Error ? error.message : String(error) });
440
+ });
441
+ }
442
+ /**
443
+ * Process a single task
444
+ */
445
+ async processTask(task, handler) {
446
+ try {
447
+ const result = await handler(task);
448
+ await this.complete(task.id, result);
449
+ }
450
+ catch (error) {
451
+ await this.fail(task.id, error instanceof Error ? error.message : String(error));
452
+ }
453
+ }
454
+ /**
455
+ * Shutdown the queue gracefully
456
+ */
457
+ async shutdown() {
458
+ if (this.isShuttingDown)
459
+ return;
460
+ this.isShuttingDown = true;
461
+ // Wait for processing tasks to complete (with timeout)
462
+ const timeout = 30000;
463
+ const start = Date.now();
464
+ while (this.processingTasks.size > 0 && Date.now() - start < timeout) {
465
+ await new Promise(resolve => setTimeout(resolve, 1000));
466
+ }
467
+ // Force fail remaining tasks
468
+ for (const taskId of this.processingTasks) {
469
+ await this.fail(taskId, 'Worker shutdown', false);
470
+ }
471
+ // Stop store cleanup
472
+ this.store.stopCleanup();
473
+ await this.unregisterWorker();
474
+ this.initialized = false;
475
+ this.emit('shutdown', {});
476
+ }
477
+ // ============================================
478
+ // Private Methods
479
+ // ============================================
480
+ /**
481
+ * Get queue name for worker type
482
+ */
483
+ getQueueName(workerType) {
484
+ return `${this.config.queuePrefix}:${workerType}`;
485
+ }
486
+ /**
487
+ * Start heartbeat timer
488
+ */
489
+ startHeartbeat() {
490
+ this.heartbeatTimer = setInterval(() => {
491
+ const registration = this.store.getWorker(this.workerId);
492
+ if (registration) {
493
+ registration.lastHeartbeat = new Date();
494
+ registration.currentTasks = this.processingTasks.size;
495
+ this.store.setWorker(this.workerId, registration);
496
+ }
497
+ }, this.config.heartbeatIntervalMs);
498
+ }
499
+ /**
500
+ * Stop heartbeat timer
501
+ */
502
+ stopHeartbeat() {
503
+ if (this.heartbeatTimer) {
504
+ clearInterval(this.heartbeatTimer);
505
+ this.heartbeatTimer = undefined;
506
+ }
507
+ }
508
+ }
509
+ // Export default
510
+ export default WorkerQueue;
511
+ //# sourceMappingURL=worker-queue.js.map