nestjs-temporal-core 3.2.0 → 3.2.2
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 +15 -6
- package/dist/interfaces.d.ts +19 -11
- package/dist/services/temporal-client.service.js +45 -33
- package/dist/services/temporal-client.service.js.map +1 -1
- package/dist/services/temporal-discovery.service.js +4 -6
- package/dist/services/temporal-discovery.service.js.map +1 -1
- package/dist/services/temporal-schedule.service.d.ts +1 -0
- package/dist/services/temporal-schedule.service.js +30 -24
- package/dist/services/temporal-schedule.service.js.map +1 -1
- package/dist/services/temporal-worker.service.d.ts +10 -9
- package/dist/services/temporal-worker.service.js +238 -224
- package/dist/services/temporal-worker.service.js.map +1 -1
- package/dist/services/temporal.service.js +13 -13
- package/dist/services/temporal.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/logger.js +6 -7
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
|
@@ -26,7 +26,6 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
26
26
|
this.injectedConnection = injectedConnection;
|
|
27
27
|
this.worker = null;
|
|
28
28
|
this.restartCount = 0;
|
|
29
|
-
this.maxRestarts = 3;
|
|
30
29
|
this.isInitialized = false;
|
|
31
30
|
this.isRunning = false;
|
|
32
31
|
this.lastError = null;
|
|
@@ -40,15 +39,25 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
40
39
|
logLevel: options.logLevel,
|
|
41
40
|
});
|
|
42
41
|
}
|
|
42
|
+
get maxRestarts() {
|
|
43
|
+
return this.options.worker?.maxRestarts ?? this.options.maxRestarts ?? 3;
|
|
44
|
+
}
|
|
45
|
+
get autoRestartEnabled() {
|
|
46
|
+
const workerAutoRestart = this.options.worker?.autoRestart;
|
|
47
|
+
if (workerAutoRestart !== undefined) {
|
|
48
|
+
return workerAutoRestart;
|
|
49
|
+
}
|
|
50
|
+
return this.options.autoRestart !== false;
|
|
51
|
+
}
|
|
43
52
|
async onModuleInit() {
|
|
44
53
|
try {
|
|
45
|
-
this.logger.
|
|
54
|
+
this.logger.verbose('Initializing Temporal worker manager...');
|
|
46
55
|
if (this.options.workers && this.options.workers.length > 0) {
|
|
47
56
|
await this.initializeMultipleWorkers();
|
|
48
57
|
return;
|
|
49
58
|
}
|
|
50
59
|
if (!this.shouldInitializeWorker()) {
|
|
51
|
-
this.logger.info('Worker initialization skipped - no
|
|
60
|
+
this.logger.info('Worker initialization skipped - no configuration provided');
|
|
52
61
|
return;
|
|
53
62
|
}
|
|
54
63
|
const initResult = await this.initializeWorker();
|
|
@@ -60,7 +69,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
60
69
|
this.lastError = initResult.error?.message || 'Unknown initialization error';
|
|
61
70
|
this.logger.error('Failed to initialize worker manager', initResult.error);
|
|
62
71
|
if (this.options.allowConnectionFailure === true) {
|
|
63
|
-
this.logger.warn('
|
|
72
|
+
this.logger.warn('Continuing without worker (connection failures allowed)');
|
|
64
73
|
return;
|
|
65
74
|
}
|
|
66
75
|
throw initResult.error || new Error('Worker initialization failed');
|
|
@@ -70,7 +79,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
70
79
|
this.lastError = this.extractErrorMessage(error);
|
|
71
80
|
this.logger.error('Failed to initialize worker manager', error);
|
|
72
81
|
if (this.options.allowConnectionFailure === true) {
|
|
73
|
-
this.logger.warn('
|
|
82
|
+
this.logger.warn('Continuing without worker (connection failures allowed)');
|
|
74
83
|
return;
|
|
75
84
|
}
|
|
76
85
|
throw error;
|
|
@@ -153,7 +162,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
153
162
|
this.logger.info(`Successfully initialized ${this.workers.size} workers`);
|
|
154
163
|
}
|
|
155
164
|
async createWorkerFromDefinition(workerDef) {
|
|
156
|
-
this.logger.
|
|
165
|
+
this.logger.verbose(`Creating worker for task queue '${workerDef.taskQueue}'`);
|
|
157
166
|
if (this.workers.has(workerDef.taskQueue)) {
|
|
158
167
|
throw new Error(`Worker for task queue '${workerDef.taskQueue}' already exists`);
|
|
159
168
|
}
|
|
@@ -194,10 +203,10 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
194
203
|
startedAt: null,
|
|
195
204
|
restartCount: 0,
|
|
196
205
|
activities,
|
|
197
|
-
workflowSource: this.
|
|
206
|
+
workflowSource: this.getWorkflowSource(workerDef),
|
|
198
207
|
};
|
|
199
208
|
this.workers.set(workerDef.taskQueue, workerInstance);
|
|
200
|
-
this.logger.info(`
|
|
209
|
+
this.logger.info(`Created worker '${workerDef.taskQueue}' (${activities.size} activities)`);
|
|
201
210
|
return workerInstance;
|
|
202
211
|
}
|
|
203
212
|
async registerWorker(workerDef) {
|
|
@@ -219,7 +228,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
219
228
|
};
|
|
220
229
|
}
|
|
221
230
|
catch (error) {
|
|
222
|
-
this.logger.error(`Failed to
|
|
231
|
+
this.logger.error(`Failed to register worker '${workerDef.taskQueue}'`, error);
|
|
223
232
|
return {
|
|
224
233
|
success: false,
|
|
225
234
|
taskQueue: workerDef.taskQueue,
|
|
@@ -257,21 +266,18 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
257
266
|
return;
|
|
258
267
|
}
|
|
259
268
|
try {
|
|
260
|
-
this.logger.
|
|
269
|
+
this.logger.verbose(`Starting worker '${taskQueue}'...`);
|
|
261
270
|
workerInstance.isRunning = true;
|
|
262
271
|
workerInstance.startedAt = new Date();
|
|
263
272
|
workerInstance.lastError = null;
|
|
264
|
-
workerInstance.
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.logger.error(`Worker '${taskQueue}' failed`, error);
|
|
268
|
-
});
|
|
269
|
-
this.logger.info(`Worker for '${taskQueue}' started successfully`);
|
|
273
|
+
workerInstance.restartCount = 0;
|
|
274
|
+
this.runWorkerWithAutoRestartByTaskQueue(taskQueue);
|
|
275
|
+
this.logger.info(`Worker '${taskQueue}' started (${workerInstance.activities.size} activities, ${workerInstance.workflowSource})`);
|
|
270
276
|
}
|
|
271
277
|
catch (error) {
|
|
272
278
|
workerInstance.lastError = this.extractErrorMessage(error);
|
|
273
279
|
workerInstance.isRunning = false;
|
|
274
|
-
this.logger.error(`Failed to start worker
|
|
280
|
+
this.logger.error(`Failed to start worker '${taskQueue}'`, error);
|
|
275
281
|
throw error;
|
|
276
282
|
}
|
|
277
283
|
}
|
|
@@ -281,49 +287,91 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
281
287
|
throw new Error(`Worker for task queue '${taskQueue}' not found`);
|
|
282
288
|
}
|
|
283
289
|
if (!workerInstance.isRunning) {
|
|
284
|
-
this.logger.
|
|
290
|
+
this.logger.verbose(`Worker '${taskQueue}' is not running`);
|
|
285
291
|
return;
|
|
286
292
|
}
|
|
287
293
|
try {
|
|
288
|
-
this.logger.
|
|
289
|
-
|
|
290
|
-
const workerState = workerInstance.worker.getState();
|
|
291
|
-
this.logger.debug(`Worker '${taskQueue}' current state: ${workerState}`);
|
|
292
|
-
if (workerState === 'INITIALIZED' ||
|
|
293
|
-
workerState === 'RUNNING' ||
|
|
294
|
-
workerState === 'FAILED') {
|
|
295
|
-
await workerInstance.worker.shutdown();
|
|
296
|
-
this.logger.info(`Worker for '${taskQueue}' stopped successfully`);
|
|
297
|
-
}
|
|
298
|
-
else if (workerState === 'STOPPING' ||
|
|
299
|
-
workerState === 'DRAINING' ||
|
|
300
|
-
workerState === 'DRAINED') {
|
|
301
|
-
this.logger.info(`Worker for '${taskQueue}' is already shutting down (state: ${workerState})`);
|
|
302
|
-
}
|
|
303
|
-
else if (workerState === 'STOPPED') {
|
|
304
|
-
this.logger.debug(`Worker for '${taskQueue}' is already stopped`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
catch (shutdownError) {
|
|
308
|
-
const errorMessage = shutdownError instanceof Error ? shutdownError.message : String(shutdownError);
|
|
309
|
-
if (errorMessage.includes('Not running') ||
|
|
310
|
-
errorMessage.includes('DRAINING') ||
|
|
311
|
-
errorMessage.includes('STOPPING')) {
|
|
312
|
-
this.logger.debug(`Worker '${taskQueue}' is already shutting down or stopped`);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
throw shutdownError;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
294
|
+
this.logger.verbose(`Stopping worker '${taskQueue}'...`);
|
|
295
|
+
this.safeShutdownWorker(workerInstance.worker, taskQueue, workerInstance.startedAt);
|
|
318
296
|
workerInstance.isRunning = false;
|
|
319
297
|
workerInstance.startedAt = null;
|
|
320
298
|
}
|
|
321
299
|
catch (error) {
|
|
322
300
|
workerInstance.lastError = this.extractErrorMessage(error);
|
|
323
|
-
this.logger.warn(`Error
|
|
301
|
+
this.logger.warn(`Error stopping worker '${taskQueue}'`, error);
|
|
302
|
+
workerInstance.isRunning = false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
runWorkerWithAutoRestartByTaskQueue(taskQueue) {
|
|
306
|
+
const workerInstance = this.workers.get(taskQueue);
|
|
307
|
+
if (!workerInstance)
|
|
308
|
+
return;
|
|
309
|
+
const workerDef = this.options.workers?.find((w) => w.taskQueue === taskQueue);
|
|
310
|
+
const maxRestarts = workerDef?.maxRestarts ?? this.options.maxRestarts ?? 3;
|
|
311
|
+
workerInstance.worker.run().catch((error) => {
|
|
312
|
+
workerInstance.lastError = this.extractErrorMessage(error);
|
|
324
313
|
workerInstance.isRunning = false;
|
|
314
|
+
const autoRestartEnabled = workerDef?.autoRestart ?? this.options.autoRestart;
|
|
315
|
+
if (autoRestartEnabled !== false && workerInstance.restartCount < maxRestarts) {
|
|
316
|
+
workerInstance.restartCount++;
|
|
317
|
+
this.logger.warn(`Worker '${taskQueue}' failed, auto-restarting in 1s (attempt ${workerInstance.restartCount}/${maxRestarts})`, error);
|
|
318
|
+
setTimeout(() => {
|
|
319
|
+
this.autoRestartWorkerByTaskQueue(taskQueue).catch((restartError) => {
|
|
320
|
+
this.logger.error(`Auto-restart failed for '${taskQueue}'`, restartError);
|
|
321
|
+
});
|
|
322
|
+
}, 1000);
|
|
323
|
+
}
|
|
324
|
+
else if (workerInstance.restartCount >= maxRestarts) {
|
|
325
|
+
this.logger.error(`Worker '${taskQueue}' failed after ${maxRestarts} restart attempts, giving up`, error);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
this.logger.error(`Worker '${taskQueue}' run failed`, error);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
async autoRestartWorkerByTaskQueue(taskQueue) {
|
|
333
|
+
this.logger.info(`Auto-restarting worker '${taskQueue}'...`);
|
|
334
|
+
try {
|
|
335
|
+
const workerDef = this.options.workers?.find((w) => w.taskQueue === taskQueue);
|
|
336
|
+
if (!workerDef) {
|
|
337
|
+
throw new Error(`Worker definition for '${taskQueue}' not found`);
|
|
338
|
+
}
|
|
339
|
+
const oldInstance = this.workers.get(taskQueue);
|
|
340
|
+
const restartCount = oldInstance?.restartCount ?? 0;
|
|
341
|
+
await this.cleanupWorkerForRestartByTaskQueue(taskQueue);
|
|
342
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
343
|
+
this.workers.delete(taskQueue);
|
|
344
|
+
const newInstance = await this.createWorkerFromDefinition(workerDef);
|
|
345
|
+
newInstance.restartCount = restartCount;
|
|
346
|
+
newInstance.isRunning = true;
|
|
347
|
+
newInstance.startedAt = new Date();
|
|
348
|
+
newInstance.lastError = null;
|
|
349
|
+
this.runWorkerWithAutoRestartByTaskQueue(taskQueue);
|
|
350
|
+
this.logger.info(`Worker '${taskQueue}' auto-restarted successfully`);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
const workerInstance = this.workers.get(taskQueue);
|
|
354
|
+
if (workerInstance) {
|
|
355
|
+
workerInstance.lastError = this.extractErrorMessage(error);
|
|
356
|
+
workerInstance.isRunning = false;
|
|
357
|
+
}
|
|
358
|
+
this.logger.error(`Auto-restart failed for '${taskQueue}'`, error);
|
|
359
|
+
throw error;
|
|
325
360
|
}
|
|
326
361
|
}
|
|
362
|
+
async cleanupWorkerForRestartByTaskQueue(taskQueue) {
|
|
363
|
+
const workerInstance = this.workers.get(taskQueue);
|
|
364
|
+
if (!workerInstance?.worker)
|
|
365
|
+
return;
|
|
366
|
+
try {
|
|
367
|
+
this.safeShutdownWorker(workerInstance.worker, taskQueue);
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
this.logger.warn(`Error during worker '${taskQueue}' cleanup (continuing with restart)`, error);
|
|
371
|
+
}
|
|
372
|
+
workerInstance.isRunning = false;
|
|
373
|
+
workerInstance.startedAt = null;
|
|
374
|
+
}
|
|
327
375
|
getConnection() {
|
|
328
376
|
return this.connection;
|
|
329
377
|
}
|
|
@@ -331,12 +379,11 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
331
379
|
const uptime = workerInstance.startedAt
|
|
332
380
|
? Date.now() - workerInstance.startedAt.getTime()
|
|
333
381
|
: undefined;
|
|
382
|
+
const isHealthy = this.calculateWorkerHealth(workerInstance);
|
|
334
383
|
return {
|
|
335
384
|
isInitialized: workerInstance.isInitialized,
|
|
336
385
|
isRunning: workerInstance.isRunning,
|
|
337
|
-
isHealthy
|
|
338
|
-
!workerInstance.lastError &&
|
|
339
|
-
workerInstance.isRunning,
|
|
386
|
+
isHealthy,
|
|
340
387
|
taskQueue: workerInstance.taskQueue,
|
|
341
388
|
namespace: workerInstance.namespace,
|
|
342
389
|
workflowSource: workerInstance.workflowSource,
|
|
@@ -347,16 +394,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
347
394
|
};
|
|
348
395
|
}
|
|
349
396
|
async loadActivitiesForWorker(activities, activityClasses) {
|
|
350
|
-
|
|
351
|
-
const maxAttempts = 30;
|
|
352
|
-
while (attempts < maxAttempts) {
|
|
353
|
-
const healthStatus = this.discoveryService.getHealthStatus();
|
|
354
|
-
if (healthStatus.isComplete) {
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
358
|
-
attempts++;
|
|
359
|
-
}
|
|
397
|
+
await this.waitForDiscoveryCompletion();
|
|
360
398
|
if (!activityClasses || activityClasses.length === 0) {
|
|
361
399
|
const allActivities = this.discoveryService.getAllActivities();
|
|
362
400
|
for (const [activityName, handler] of Object.entries(allActivities)) {
|
|
@@ -381,13 +419,6 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
381
419
|
}
|
|
382
420
|
}
|
|
383
421
|
}
|
|
384
|
-
getWorkflowSourceFromDef(workerDef) {
|
|
385
|
-
if (workerDef.workflowBundle)
|
|
386
|
-
return 'bundle';
|
|
387
|
-
if (workerDef.workflowsPath)
|
|
388
|
-
return 'filesystem';
|
|
389
|
-
return 'none';
|
|
390
|
-
}
|
|
391
422
|
async startWorker() {
|
|
392
423
|
if (!this.worker) {
|
|
393
424
|
throw new Error('Worker not initialized. Cannot start worker.');
|
|
@@ -421,12 +452,10 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
421
452
|
try {
|
|
422
453
|
this.worker.run().catch((error) => {
|
|
423
454
|
this.lastError = this.extractErrorMessage(error);
|
|
424
|
-
this.logger.error('Worker run failed', error);
|
|
425
455
|
this.isRunning = false;
|
|
426
|
-
if (this.
|
|
427
|
-
this.restartCount < this.maxRestarts) {
|
|
456
|
+
if (this.autoRestartEnabled && this.restartCount < this.maxRestarts) {
|
|
428
457
|
this.restartCount++;
|
|
429
|
-
this.logger.
|
|
458
|
+
this.logger.warn(`Worker failed, auto-restarting in 1s (attempt ${this.restartCount}/${this.maxRestarts})`, error);
|
|
430
459
|
setTimeout(async () => {
|
|
431
460
|
try {
|
|
432
461
|
await this.autoRestartWorker();
|
|
@@ -437,7 +466,10 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
437
466
|
}, 1000);
|
|
438
467
|
}
|
|
439
468
|
else if (this.restartCount >= this.maxRestarts) {
|
|
440
|
-
this.logger.error(`
|
|
469
|
+
this.logger.error(`Worker failed after ${this.maxRestarts} restart attempts, giving up`, error);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
this.logger.error('Worker run failed', error);
|
|
441
473
|
}
|
|
442
474
|
});
|
|
443
475
|
setTimeout(() => resolve(), 500);
|
|
@@ -455,14 +487,17 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
455
487
|
async autoRestartWorker() {
|
|
456
488
|
this.logger.info('Auto-restarting Temporal worker...');
|
|
457
489
|
try {
|
|
458
|
-
|
|
459
|
-
await this.worker.shutdown();
|
|
460
|
-
}
|
|
490
|
+
await this.cleanupWorkerForRestart();
|
|
461
491
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
492
|
+
const initResult = await this.initializeWorker();
|
|
493
|
+
if (!initResult.success) {
|
|
494
|
+
throw initResult.error || new Error('Failed to reinitialize worker');
|
|
495
|
+
}
|
|
496
|
+
this.isInitialized = true;
|
|
462
497
|
this.isRunning = true;
|
|
463
498
|
this.startedAt = new Date();
|
|
464
499
|
this.lastError = null;
|
|
465
|
-
this.runWorkerWithAutoRestart();
|
|
500
|
+
await this.runWorkerWithAutoRestart();
|
|
466
501
|
this.logger.info('Temporal worker auto-restarted successfully');
|
|
467
502
|
}
|
|
468
503
|
catch (error) {
|
|
@@ -472,29 +507,29 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
472
507
|
throw error;
|
|
473
508
|
}
|
|
474
509
|
}
|
|
510
|
+
async cleanupWorkerForRestart() {
|
|
511
|
+
if (!this.worker) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
this.safeShutdownWorker(this.worker, 'legacy');
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
this.logger.warn('Error during worker cleanup (continuing with restart)', error);
|
|
519
|
+
}
|
|
520
|
+
this.worker = null;
|
|
521
|
+
this.isRunning = false;
|
|
522
|
+
this.startedAt = null;
|
|
523
|
+
}
|
|
475
524
|
async stopWorker() {
|
|
476
525
|
if (!this.worker || !this.isRunning) {
|
|
477
526
|
this.logger.debug('Worker is not running or not initialized');
|
|
478
527
|
return;
|
|
479
528
|
}
|
|
480
529
|
try {
|
|
481
|
-
this.logger.
|
|
482
|
-
const
|
|
483
|
-
this.
|
|
484
|
-
if (workerState === 'INITIALIZED' ||
|
|
485
|
-
workerState === 'RUNNING' ||
|
|
486
|
-
workerState === 'FAILED') {
|
|
487
|
-
await this.worker.shutdown();
|
|
488
|
-
this.logger.info('Temporal worker stopped successfully');
|
|
489
|
-
}
|
|
490
|
-
else if (workerState === 'STOPPING' ||
|
|
491
|
-
workerState === 'DRAINING' ||
|
|
492
|
-
workerState === 'DRAINED') {
|
|
493
|
-
this.logger.info(`Worker is already shutting down (state: ${workerState})`);
|
|
494
|
-
}
|
|
495
|
-
else if (workerState === 'STOPPED') {
|
|
496
|
-
this.logger.debug('Worker is already stopped');
|
|
497
|
-
}
|
|
530
|
+
this.logger.verbose('Stopping Temporal worker...');
|
|
531
|
+
const taskQueue = this.options.taskQueue || 'default';
|
|
532
|
+
this.safeShutdownWorker(this.worker, taskQueue, this.startedAt);
|
|
498
533
|
this.isRunning = false;
|
|
499
534
|
this.startedAt = null;
|
|
500
535
|
}
|
|
@@ -532,10 +567,11 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
532
567
|
}
|
|
533
568
|
getWorkerStatus() {
|
|
534
569
|
const uptime = this.startedAt ? Date.now() - this.startedAt.getTime() : undefined;
|
|
570
|
+
const isHealthy = this.calculateWorkerHealth();
|
|
535
571
|
return {
|
|
536
572
|
isInitialized: this.isInitialized,
|
|
537
573
|
isRunning: this.isRunning,
|
|
538
|
-
isHealthy
|
|
574
|
+
isHealthy,
|
|
539
575
|
taskQueue: this.options.taskQueue || 'default',
|
|
540
576
|
namespace: this.options.connection?.namespace || 'default',
|
|
541
577
|
workflowSource: this.getWorkflowSource(),
|
|
@@ -556,27 +592,18 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
556
592
|
const errors = [];
|
|
557
593
|
let registeredCount = 0;
|
|
558
594
|
try {
|
|
559
|
-
|
|
560
|
-
const maxAttempts = 30;
|
|
561
|
-
while (attempts < maxAttempts) {
|
|
562
|
-
const healthStatus = this.discoveryService.getHealthStatus();
|
|
563
|
-
if (healthStatus.isComplete) {
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
567
|
-
attempts++;
|
|
568
|
-
}
|
|
595
|
+
await this.waitForDiscoveryCompletion();
|
|
569
596
|
const allActivities = this.discoveryService.getAllActivities();
|
|
570
597
|
for (const [activityName, handler] of Object.entries(allActivities)) {
|
|
571
598
|
try {
|
|
572
599
|
this.activities.set(activityName, handler);
|
|
573
600
|
registeredCount++;
|
|
574
|
-
this.logger.
|
|
601
|
+
this.logger.verbose(`Registered activity: ${activityName}`);
|
|
575
602
|
}
|
|
576
603
|
catch (error) {
|
|
577
604
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
578
605
|
errors.push({ activityName, error: errorMessage });
|
|
579
|
-
this.logger.warn(`Failed to register activity ${activityName}: ${errorMessage}`);
|
|
606
|
+
this.logger.warn(`Failed to register activity '${activityName}': ${errorMessage}`);
|
|
580
607
|
}
|
|
581
608
|
}
|
|
582
609
|
this.logger.info(`Registered ${registeredCount} activities from discovery service`);
|
|
@@ -602,13 +629,11 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
602
629
|
isWorkerRunning() {
|
|
603
630
|
return this.isRunning;
|
|
604
631
|
}
|
|
605
|
-
getStatus() {
|
|
606
|
-
return this.getWorkerStatus();
|
|
607
|
-
}
|
|
608
632
|
getHealthStatus() {
|
|
609
633
|
const uptime = this.startedAt ? Date.now() - this.startedAt.getTime() : undefined;
|
|
634
|
+
const isHealthy = this.calculateWorkerHealth();
|
|
610
635
|
return {
|
|
611
|
-
isHealthy
|
|
636
|
+
isHealthy,
|
|
612
637
|
isRunning: this.isRunning,
|
|
613
638
|
isInitialized: this.isInitialized,
|
|
614
639
|
lastError: this.lastError || undefined,
|
|
@@ -645,30 +670,6 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
645
670
|
throw new Error('Cannot specify both workflowsPath and workflowBundle');
|
|
646
671
|
}
|
|
647
672
|
}
|
|
648
|
-
getEnvironmentDefaults() {
|
|
649
|
-
return {
|
|
650
|
-
taskQueue: this.options.taskQueue || 'default',
|
|
651
|
-
namespace: this.options.connection?.namespace || 'default',
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
buildWorkerOptions() {
|
|
655
|
-
const baseOptions = this.getEnvironmentDefaults();
|
|
656
|
-
if (this.options.worker?.workflowsPath) {
|
|
657
|
-
Object.assign(baseOptions, { workflowsPath: this.options.worker.workflowsPath });
|
|
658
|
-
}
|
|
659
|
-
else if (this.options.worker?.workflowBundle) {
|
|
660
|
-
Object.assign(baseOptions, { workflowBundle: this.options.worker.workflowBundle });
|
|
661
|
-
}
|
|
662
|
-
const activitiesObj = {};
|
|
663
|
-
for (const [name, func] of this.activities.entries()) {
|
|
664
|
-
activitiesObj[name] = func;
|
|
665
|
-
}
|
|
666
|
-
Object.assign(baseOptions, { activities: activitiesObj });
|
|
667
|
-
if (this.options.worker?.workerOptions) {
|
|
668
|
-
Object.assign(baseOptions, this.options.worker.workerOptions);
|
|
669
|
-
}
|
|
670
|
-
return baseOptions;
|
|
671
|
-
}
|
|
672
673
|
async createConnection() {
|
|
673
674
|
if (this.injectedConnection) {
|
|
674
675
|
this.connection = this.injectedConnection;
|
|
@@ -690,51 +691,21 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
690
691
|
authorization: `Bearer ${this.options.connection.apiKey}`,
|
|
691
692
|
};
|
|
692
693
|
}
|
|
693
|
-
this.logger.
|
|
694
|
+
this.logger.verbose(`Connecting to Temporal server at ${address}...`);
|
|
694
695
|
this.connection = await worker_1.NativeConnection.connect(connectOptions);
|
|
695
|
-
this.
|
|
696
|
+
const ns = this.options.connection?.namespace || 'default';
|
|
697
|
+
this.logger.info(`Connected to ${address} (namespace: ${ns})`);
|
|
696
698
|
}
|
|
697
699
|
catch (error) {
|
|
698
700
|
this.logger.error('Failed to create connection', error);
|
|
699
701
|
if (this.options.allowConnectionFailure !== false) {
|
|
700
|
-
this.logger.warn('
|
|
702
|
+
this.logger.warn('Connection failed, continuing without worker');
|
|
701
703
|
this.connection = null;
|
|
702
704
|
return;
|
|
703
705
|
}
|
|
704
706
|
throw error;
|
|
705
707
|
}
|
|
706
708
|
}
|
|
707
|
-
async createWorker() {
|
|
708
|
-
await this.createConnection();
|
|
709
|
-
if (!this.connection) {
|
|
710
|
-
throw new Error('Connection not established');
|
|
711
|
-
}
|
|
712
|
-
const workerConfig = await this.createWorkerConfig();
|
|
713
|
-
const { Worker } = await Promise.resolve().then(() => require('@temporalio/worker'));
|
|
714
|
-
this.worker = await Worker.create(workerConfig);
|
|
715
|
-
}
|
|
716
|
-
logWorkerConfiguration() {
|
|
717
|
-
this.logger.debug(`Worker configuration: ${JSON.stringify(this.options.worker)}`);
|
|
718
|
-
}
|
|
719
|
-
async runWorkerLoop() {
|
|
720
|
-
if (!this.worker) {
|
|
721
|
-
throw new Error('Temporal worker not initialized');
|
|
722
|
-
}
|
|
723
|
-
try {
|
|
724
|
-
await this.worker.run();
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
this.logger.error('Worker execution failed', error);
|
|
728
|
-
throw new Error('Execution error');
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
startWorkerInBackground() {
|
|
732
|
-
if (this.worker && !this.isRunning) {
|
|
733
|
-
this.startWorker().catch((error) => {
|
|
734
|
-
this.logger.error('Background worker start failed', error);
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
709
|
shouldInitializeWorker() {
|
|
739
710
|
return Boolean(this.options.worker &&
|
|
740
711
|
(this.options.worker.workflowsPath ||
|
|
@@ -768,7 +739,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
768
739
|
const workerConfig = await this.createWorkerConfig();
|
|
769
740
|
const { Worker } = await Promise.resolve().then(() => require('@temporalio/worker'));
|
|
770
741
|
this.worker = await Worker.create(workerConfig);
|
|
771
|
-
this.logger.
|
|
742
|
+
this.logger.verbose(`Worker created: queue='${workerConfig.taskQueue}', activities=${this.activities.size}, workflows=${this.getWorkflowSource()}`);
|
|
772
743
|
return {
|
|
773
744
|
success: true,
|
|
774
745
|
worker: this.worker,
|
|
@@ -795,31 +766,23 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
795
766
|
let discoveredActivities = 0;
|
|
796
767
|
let loadedActivities = 0;
|
|
797
768
|
try {
|
|
798
|
-
|
|
799
|
-
const maxAttempts = 30;
|
|
800
|
-
while (attempts < maxAttempts) {
|
|
801
|
-
const healthStatus = this.discoveryService.getHealthStatus();
|
|
802
|
-
if (healthStatus.isComplete) {
|
|
803
|
-
break;
|
|
804
|
-
}
|
|
805
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
806
|
-
attempts++;
|
|
807
|
-
}
|
|
769
|
+
await this.waitForDiscoveryCompletion();
|
|
808
770
|
const allActivities = this.discoveryService.getAllActivities();
|
|
809
771
|
discoveredActivities = Object.keys(allActivities).length;
|
|
810
772
|
for (const [activityName, handler] of Object.entries(allActivities)) {
|
|
811
773
|
try {
|
|
812
774
|
this.activities.set(activityName, handler);
|
|
813
775
|
loadedActivities++;
|
|
814
|
-
this.logger.
|
|
776
|
+
this.logger.verbose(`Loaded activity: ${activityName}`);
|
|
815
777
|
}
|
|
816
778
|
catch (error) {
|
|
817
779
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
818
|
-
errors.push({
|
|
819
|
-
this.logger.warn(`Failed to load activity ${activityName}: ${errorMessage}`);
|
|
780
|
+
errors.push({ name: activityName, error: errorMessage });
|
|
781
|
+
this.logger.warn(`Failed to load activity '${activityName}': ${errorMessage}`);
|
|
820
782
|
}
|
|
821
783
|
}
|
|
822
|
-
|
|
784
|
+
const duration = Date.now() - startTime;
|
|
785
|
+
this.logger.info(`Loaded ${loadedActivities} ${loadedActivities === 1 ? 'activity' : 'activities'} from discovery in ${duration}ms`);
|
|
823
786
|
return {
|
|
824
787
|
success: errors.length === 0,
|
|
825
788
|
discoveredActivities,
|
|
@@ -835,7 +798,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
835
798
|
success: false,
|
|
836
799
|
discoveredActivities,
|
|
837
800
|
loadedActivities,
|
|
838
|
-
errors: [{
|
|
801
|
+
errors: [{ name: 'discovery', error: errorMessage }],
|
|
839
802
|
duration: Date.now() - startTime,
|
|
840
803
|
};
|
|
841
804
|
}
|
|
@@ -854,14 +817,14 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
854
817
|
};
|
|
855
818
|
if (this.options.worker?.workflowsPath) {
|
|
856
819
|
config.workflowsPath = this.options.worker.workflowsPath;
|
|
857
|
-
this.logger.
|
|
820
|
+
this.logger.verbose(`Using workflows from: ${this.options.worker.workflowsPath}`);
|
|
858
821
|
}
|
|
859
822
|
else if (this.options.worker?.workflowBundle) {
|
|
860
823
|
config.workflowBundle = this.options.worker.workflowBundle;
|
|
861
|
-
this.logger.
|
|
824
|
+
this.logger.verbose('Using workflow bundle');
|
|
862
825
|
}
|
|
863
826
|
else {
|
|
864
|
-
this.logger.warn('No workflow configuration
|
|
827
|
+
this.logger.warn('No workflow configuration - worker will only handle activities');
|
|
865
828
|
}
|
|
866
829
|
if (this.options.worker?.workerOptions) {
|
|
867
830
|
Object.assign(config, this.options.worker.workerOptions);
|
|
@@ -886,38 +849,7 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
886
849
|
try {
|
|
887
850
|
if (workerInstance.isRunning && workerInstance.worker) {
|
|
888
851
|
this.logger.debug(`Stopping worker for '${taskQueue}'...`);
|
|
889
|
-
|
|
890
|
-
const workerState = workerInstance.worker.getState();
|
|
891
|
-
if (workerState === 'INITIALIZED' ||
|
|
892
|
-
workerState === 'RUNNING' ||
|
|
893
|
-
workerState === 'FAILED') {
|
|
894
|
-
await workerInstance.worker.shutdown();
|
|
895
|
-
this.logger.debug(`Worker '${taskQueue}' shut down successfully`);
|
|
896
|
-
}
|
|
897
|
-
else if (workerState === 'STOPPING' ||
|
|
898
|
-
workerState === 'DRAINING' ||
|
|
899
|
-
workerState === 'DRAINED') {
|
|
900
|
-
this.logger.debug(`Worker '${taskQueue}' is already shutting down (state: ${workerState})`);
|
|
901
|
-
}
|
|
902
|
-
else if (workerState === 'STOPPED') {
|
|
903
|
-
this.logger.debug(`Worker '${taskQueue}' is already stopped`);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
catch (shutdownError) {
|
|
907
|
-
const errorMessage = shutdownError instanceof Error
|
|
908
|
-
? shutdownError.message
|
|
909
|
-
: String(shutdownError);
|
|
910
|
-
if (errorMessage.includes('Not running') ||
|
|
911
|
-
errorMessage.includes('DRAINING') ||
|
|
912
|
-
errorMessage.includes('STOPPING')) {
|
|
913
|
-
this.logger.debug(`Worker '${taskQueue}' is already shutting down or stopped`);
|
|
914
|
-
}
|
|
915
|
-
else {
|
|
916
|
-
this.logger.warn(`Unexpected error shutting down worker '${taskQueue}': ${errorMessage}`, shutdownError instanceof Error
|
|
917
|
-
? shutdownError.stack
|
|
918
|
-
: undefined);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
852
|
+
this.safeShutdownWorker(workerInstance.worker, taskQueue);
|
|
921
853
|
workerInstance.isRunning = false;
|
|
922
854
|
}
|
|
923
855
|
}
|
|
@@ -955,13 +887,95 @@ let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class Tempor
|
|
|
955
887
|
this.shutdownPromise = null;
|
|
956
888
|
}
|
|
957
889
|
}
|
|
958
|
-
getWorkflowSource() {
|
|
959
|
-
|
|
890
|
+
getWorkflowSource(config) {
|
|
891
|
+
const source = config ?? this.options.worker;
|
|
892
|
+
if (source?.workflowBundle)
|
|
960
893
|
return 'bundle';
|
|
961
|
-
if (
|
|
894
|
+
if (source?.workflowsPath)
|
|
962
895
|
return 'filesystem';
|
|
963
896
|
return 'none';
|
|
964
897
|
}
|
|
898
|
+
getNativeState() {
|
|
899
|
+
if (!this.worker) {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
return this.worker.getState();
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
calculateWorkerHealth(workerInstance = null) {
|
|
910
|
+
if (workerInstance) {
|
|
911
|
+
let nativeState = null;
|
|
912
|
+
try {
|
|
913
|
+
nativeState = workerInstance.worker?.getState() ?? null;
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
nativeState = null;
|
|
917
|
+
}
|
|
918
|
+
return (workerInstance.isInitialized &&
|
|
919
|
+
!workerInstance.lastError &&
|
|
920
|
+
workerInstance.isRunning &&
|
|
921
|
+
nativeState === 'RUNNING');
|
|
922
|
+
}
|
|
923
|
+
const nativeState = this.getNativeState();
|
|
924
|
+
return this.isInitialized && !this.lastError && this.isRunning && nativeState === 'RUNNING';
|
|
925
|
+
}
|
|
926
|
+
async waitForDiscoveryCompletion(maxWaitMs = 3000) {
|
|
927
|
+
const pollInterval = 100;
|
|
928
|
+
const maxAttempts = Math.ceil(maxWaitMs / pollInterval);
|
|
929
|
+
let attempts = 0;
|
|
930
|
+
while (attempts < maxAttempts) {
|
|
931
|
+
const healthStatus = this.discoveryService.getHealthStatus();
|
|
932
|
+
if (healthStatus.isComplete) {
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
936
|
+
attempts++;
|
|
937
|
+
}
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
safeShutdownWorker(worker, identifier, startedAt) {
|
|
941
|
+
try {
|
|
942
|
+
const workerState = worker.getState();
|
|
943
|
+
if (workerState === 'INITIALIZED' ||
|
|
944
|
+
workerState === 'RUNNING' ||
|
|
945
|
+
workerState === 'FAILED') {
|
|
946
|
+
worker.shutdown();
|
|
947
|
+
if (startedAt) {
|
|
948
|
+
const uptime = Math.round((Date.now() - startedAt.getTime()) / 1000);
|
|
949
|
+
this.logger.info(`Worker '${identifier}' stopped (uptime: ${uptime}s)`);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
this.logger.verbose(`Worker '${identifier}' shut down`);
|
|
953
|
+
}
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
956
|
+
if (workerState === 'STOPPING' ||
|
|
957
|
+
workerState === 'DRAINING' ||
|
|
958
|
+
workerState === 'DRAINED') {
|
|
959
|
+
this.logger.verbose(`Worker '${identifier}' already shutting down (${workerState})`);
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
if (workerState === 'STOPPED') {
|
|
963
|
+
this.logger.verbose(`Worker '${identifier}' already stopped`);
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
970
|
+
if (msg.includes('Not running') ||
|
|
971
|
+
msg.includes('DRAINING') ||
|
|
972
|
+
msg.includes('STOPPING')) {
|
|
973
|
+
this.logger.verbose(`Worker '${identifier}' already shutting down`);
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
throw error;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
965
979
|
extractErrorMessage(error) {
|
|
966
980
|
if (error instanceof Error) {
|
|
967
981
|
return error.message;
|