nestjs-temporal-core
Version:
Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features
982 lines • 42.7 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var TemporalWorkerManagerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemporalWorkerManagerService = void 0;
const common_1 = require("@nestjs/common");
const worker_1 = require("@temporalio/worker");
const constants_1 = require("../constants");
const temporal_discovery_service_1 = require("./temporal-discovery.service");
const logger_1 = require("../utils/logger");
let TemporalWorkerManagerService = TemporalWorkerManagerService_1 = class TemporalWorkerManagerService {
constructor(discoveryService, options, injectedConnection) {
this.discoveryService = discoveryService;
this.options = options;
this.injectedConnection = injectedConnection;
this.worker = null;
this.restartCount = 0;
this.maxRestarts = 3;
this.isInitialized = false;
this.isRunning = false;
this.lastError = null;
this.startedAt = null;
this.activities = new Map();
this.workers = new Map();
this.connection = null;
this.shutdownPromise = null;
this.logger = (0, logger_1.createLogger)(TemporalWorkerManagerService_1.name, {
enableLogger: options.enableLogger,
logLevel: options.logLevel,
});
}
async onModuleInit() {
try {
this.logger.debug('Initializing Temporal worker manager...');
if (this.options.workers && this.options.workers.length > 0) {
await this.initializeMultipleWorkers();
return;
}
if (!this.shouldInitializeWorker()) {
this.logger.info('Worker initialization skipped - no worker configuration provided');
return;
}
const initResult = await this.initializeWorker();
this.isInitialized = initResult.success;
if (initResult.success) {
this.logger.info('Temporal worker manager initialized successfully');
}
else {
this.lastError = initResult.error?.message || 'Unknown initialization error';
this.logger.error('Failed to initialize worker manager', initResult.error);
if (this.options.allowConnectionFailure === true) {
this.logger.warn('Worker initialization failed but connection failures are allowed');
return;
}
throw initResult.error || new Error('Worker initialization failed');
}
}
catch (error) {
this.lastError = this.extractErrorMessage(error);
this.logger.error('Failed to initialize worker manager', error);
if (this.options.allowConnectionFailure === true) {
this.logger.warn('Worker initialization failed but connection failures are allowed');
return;
}
throw error;
}
}
async onApplicationBootstrap() {
try {
if (this.options.workers && this.options.workers.length > 0) {
this.logger.info('Starting configured workers...');
const startPromises = [];
for (const [taskQueue] of this.workers.entries()) {
const workerDef = this.options.workers.find((w) => w.taskQueue === taskQueue);
if (workerDef?.autoStart !== false) {
startPromises.push(this.startWorkerByTaskQueue(taskQueue).catch((error) => {
this.logger.error(`Failed to start worker '${taskQueue}'`, error);
if (this.options.allowConnectionFailure !== true) {
throw error;
}
}));
}
}
await Promise.all(startPromises);
this.logger.info(`Started ${startPromises.length} workers successfully`);
return;
}
if (this.worker && this.options.worker?.autoStart !== false) {
this.logger.info('Starting worker...');
await this.startWorker();
this.logger.info('Worker started successfully');
}
}
catch (error) {
this.logger.error('Error during worker startup', error);
if (this.options.allowConnectionFailure !== true) {
throw error;
}
}
}
async beforeApplicationShutdown(signal) {
if (signal) {
this.logger.info(`Received shutdown signal: ${signal}`);
}
this.logger.info('Initiating graceful worker shutdown...');
const shutdownTimeout = this.options.shutdownTimeout || 30000;
const shutdownPromise = this.shutdownWorker();
const timeoutPromise = new Promise((resolve) => {
setTimeout(() => {
this.logger.warn(`Shutdown timeout (${shutdownTimeout}ms) reached`);
resolve();
}, shutdownTimeout);
});
try {
await Promise.race([shutdownPromise, timeoutPromise]);
}
catch (error) {
this.logger.error('Error during graceful shutdown', error);
}
}
async onModuleDestroy() {
await this.shutdownWorker();
}
async initializeMultipleWorkers() {
this.logger.info(`Initializing ${this.options.workers.length} workers...`);
await this.createConnection();
if (!this.connection && this.options.allowConnectionFailure !== false) {
this.logger.warn('Connection failed, skipping worker initialization');
return;
}
for (const workerDef of this.options.workers) {
try {
await this.createWorkerFromDefinition(workerDef);
}
catch (error) {
this.logger.error(`Failed to initialize worker for task queue '${workerDef.taskQueue}'`, error);
if (this.options.allowConnectionFailure !== true) {
throw error;
}
}
}
this.logger.info(`Successfully initialized ${this.workers.size} workers`);
}
async createWorkerFromDefinition(workerDef) {
this.logger.debug(`Creating worker for task queue: ${workerDef.taskQueue}`);
if (this.workers.has(workerDef.taskQueue)) {
throw new Error(`Worker for task queue '${workerDef.taskQueue}' already exists`);
}
const activities = new Map();
if (workerDef.activityClasses && workerDef.activityClasses.length > 0) {
await this.loadActivitiesForWorker(activities, workerDef.activityClasses);
}
else {
const allActivities = this.discoveryService.getAllActivities();
for (const [name, handler] of Object.entries(allActivities)) {
activities.set(name, handler);
}
}
const workerConfig = {
taskQueue: workerDef.taskQueue,
namespace: this.options.connection?.namespace || 'default',
connection: this.connection,
activities: Object.fromEntries(activities),
};
if (workerDef.workflowsPath) {
workerConfig.workflowsPath = workerDef.workflowsPath;
}
else if (workerDef.workflowBundle) {
workerConfig.workflowBundle = workerDef.workflowBundle;
}
if (workerDef.workerOptions) {
Object.assign(workerConfig, workerDef.workerOptions);
}
const { Worker } = await Promise.resolve().then(() => require('@temporalio/worker'));
const worker = await Worker.create(workerConfig);
const workerInstance = {
worker,
taskQueue: workerDef.taskQueue,
namespace: this.options.connection?.namespace || 'default',
isRunning: false,
isInitialized: true,
lastError: null,
startedAt: null,
restartCount: 0,
activities,
workflowSource: this.getWorkflowSourceFromDef(workerDef),
};
this.workers.set(workerDef.taskQueue, workerInstance);
this.logger.info(`Worker created for task queue '${workerDef.taskQueue}' with ${activities.size} activities`);
return workerInstance;
}
async registerWorker(workerDef) {
try {
if (!workerDef.taskQueue || workerDef.taskQueue.trim().length === 0) {
throw new Error('Task queue is required');
}
if (!this.connection) {
await this.createConnection();
}
const workerInstance = await this.createWorkerFromDefinition(workerDef);
if (workerDef.autoStart !== false) {
await this.startWorkerByTaskQueue(workerDef.taskQueue);
}
return {
success: true,
taskQueue: workerDef.taskQueue,
worker: workerInstance.worker,
};
}
catch (error) {
this.logger.error(`Failed to create worker for '${workerDef.taskQueue}'`, error);
return {
success: false,
taskQueue: workerDef.taskQueue,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
getWorker(taskQueue) {
const workerInstance = this.workers.get(taskQueue);
return workerInstance ? workerInstance.worker : null;
}
getAllWorkers() {
const workersStatus = new Map();
for (const [taskQueue, workerInstance] of this.workers.entries()) {
workersStatus.set(taskQueue, this.getWorkerStatusFromInstance(workerInstance));
}
return {
workers: workersStatus,
totalWorkers: this.workers.size,
runningWorkers: Array.from(this.workers.values()).filter((w) => w.isRunning).length,
healthyWorkers: Array.from(this.workers.values()).filter((w) => w.isInitialized && !w.lastError && w.isRunning).length,
};
}
getWorkerStatusByTaskQueue(taskQueue) {
const workerInstance = this.workers.get(taskQueue);
return workerInstance ? this.getWorkerStatusFromInstance(workerInstance) : null;
}
async startWorkerByTaskQueue(taskQueue) {
const workerInstance = this.workers.get(taskQueue);
if (!workerInstance) {
throw new Error(`Worker for task queue '${taskQueue}' not found`);
}
if (workerInstance.isRunning) {
this.logger.warn(`Worker for '${taskQueue}' is already running`);
return;
}
try {
this.logger.info(`Starting worker for task queue '${taskQueue}'...`);
workerInstance.isRunning = true;
workerInstance.startedAt = new Date();
workerInstance.lastError = null;
workerInstance.worker.run().catch((error) => {
workerInstance.lastError = this.extractErrorMessage(error);
workerInstance.isRunning = false;
this.logger.error(`Worker '${taskQueue}' failed`, error);
});
this.logger.info(`Worker for '${taskQueue}' started successfully`);
}
catch (error) {
workerInstance.lastError = this.extractErrorMessage(error);
workerInstance.isRunning = false;
this.logger.error(`Failed to start worker for '${taskQueue}'`, error);
throw error;
}
}
async stopWorkerByTaskQueue(taskQueue) {
const workerInstance = this.workers.get(taskQueue);
if (!workerInstance) {
throw new Error(`Worker for task queue '${taskQueue}' not found`);
}
if (!workerInstance.isRunning) {
this.logger.debug(`Worker for '${taskQueue}' is not running`);
return;
}
try {
this.logger.info(`Stopping worker for '${taskQueue}'...`);
try {
const workerState = workerInstance.worker.getState();
this.logger.debug(`Worker '${taskQueue}' current state: ${workerState}`);
if (workerState === 'INITIALIZED' ||
workerState === 'RUNNING' ||
workerState === 'FAILED') {
await workerInstance.worker.shutdown();
this.logger.info(`Worker for '${taskQueue}' stopped successfully`);
}
else if (workerState === 'STOPPING' ||
workerState === 'DRAINING' ||
workerState === 'DRAINED') {
this.logger.info(`Worker for '${taskQueue}' is already shutting down (state: ${workerState})`);
}
else if (workerState === 'STOPPED') {
this.logger.debug(`Worker for '${taskQueue}' is already stopped`);
}
}
catch (shutdownError) {
const errorMessage = shutdownError instanceof Error ? shutdownError.message : String(shutdownError);
if (errorMessage.includes('Not running') ||
errorMessage.includes('DRAINING') ||
errorMessage.includes('STOPPING')) {
this.logger.debug(`Worker '${taskQueue}' is already shutting down or stopped`);
}
else {
throw shutdownError;
}
}
workerInstance.isRunning = false;
workerInstance.startedAt = null;
}
catch (error) {
workerInstance.lastError = this.extractErrorMessage(error);
this.logger.warn(`Error while stopping worker for '${taskQueue}'`, error);
workerInstance.isRunning = false;
}
}
getConnection() {
return this.connection;
}
getWorkerStatusFromInstance(workerInstance) {
const uptime = workerInstance.startedAt
? Date.now() - workerInstance.startedAt.getTime()
: undefined;
return {
isInitialized: workerInstance.isInitialized,
isRunning: workerInstance.isRunning,
isHealthy: workerInstance.isInitialized &&
!workerInstance.lastError &&
workerInstance.isRunning,
taskQueue: workerInstance.taskQueue,
namespace: workerInstance.namespace,
workflowSource: workerInstance.workflowSource,
activitiesCount: workerInstance.activities.size,
lastError: workerInstance.lastError || undefined,
startedAt: workerInstance.startedAt || undefined,
uptime,
};
}
async loadActivitiesForWorker(activities, activityClasses) {
let attempts = 0;
const maxAttempts = 30;
while (attempts < maxAttempts) {
const healthStatus = this.discoveryService.getHealthStatus();
if (healthStatus.isComplete) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
attempts++;
}
if (!activityClasses || activityClasses.length === 0) {
const allActivities = this.discoveryService.getAllActivities();
for (const [activityName, handler] of Object.entries(allActivities)) {
activities.set(activityName, handler);
}
return;
}
const discoveredActivities = this.discoveryService.getDiscoveredActivities();
const allowedClasses = new Set(activityClasses);
const allowedClassNames = new Set(activityClasses.map((cls) => cls.name));
for (const [activityName, activityInfo] of discoveredActivities.entries()) {
const activityInstance = activityInfo.instance;
const activityConstructor = activityInstance &&
typeof activityInstance === 'object' &&
'constructor' in activityInstance
? activityInstance.constructor
: null;
const matchesByConstructor = activityConstructor && allowedClasses.has(activityConstructor);
const matchesByName = allowedClassNames.has(activityInfo.className);
if (matchesByConstructor || matchesByName) {
activities.set(activityName, activityInfo.handler);
}
}
}
getWorkflowSourceFromDef(workerDef) {
if (workerDef.workflowBundle)
return 'bundle';
if (workerDef.workflowsPath)
return 'filesystem';
return 'none';
}
async startWorker() {
if (!this.worker) {
throw new Error('Worker not initialized. Cannot start worker.');
}
if (this.isRunning) {
this.logger.warn('Worker is already running');
return;
}
try {
this.logger.info('Starting Temporal worker...');
this.isRunning = true;
this.startedAt = new Date();
this.lastError = null;
this.restartCount = 0;
await this.runWorkerWithAutoRestart();
this.logger.info('Temporal worker started successfully');
}
catch (error) {
this.lastError = this.extractErrorMessage(error);
this.logger.error('Failed to start worker', error);
this.isRunning = false;
throw error;
}
}
async runWorkerWithAutoRestart() {
if (!this.worker)
return;
try {
await new Promise((resolve, reject) => {
setImmediate(async () => {
try {
this.worker.run().catch((error) => {
this.lastError = this.extractErrorMessage(error);
this.logger.error('Worker run failed', error);
this.isRunning = false;
if (this.options.autoRestart !== false &&
this.restartCount < this.maxRestarts) {
this.restartCount++;
this.logger.info(`Auto-restart enabled, attempting to restart worker (attempt ${this.restartCount}/${this.maxRestarts}) in 1 second...`);
setTimeout(async () => {
try {
await this.autoRestartWorker();
}
catch (restartError) {
this.logger.error('Auto-restart failed', restartError);
}
}, 1000);
}
else if (this.restartCount >= this.maxRestarts) {
this.logger.error(`Max restart attempts (${this.maxRestarts}) exceeded. Stopping auto-restart.`);
}
});
setTimeout(() => resolve(), 500);
}
catch (error) {
reject(error);
}
});
});
}
catch (error) {
throw error;
}
}
async autoRestartWorker() {
this.logger.info('Auto-restarting Temporal worker...');
try {
if (this.worker) {
await this.worker.shutdown();
}
await new Promise((resolve) => setTimeout(resolve, 500));
this.isRunning = true;
this.startedAt = new Date();
this.lastError = null;
this.runWorkerWithAutoRestart();
this.logger.info('Temporal worker auto-restarted successfully');
}
catch (error) {
this.lastError = this.extractErrorMessage(error);
this.logger.error('Auto-restart failed', error);
this.isRunning = false;
throw error;
}
}
async stopWorker() {
if (!this.worker || !this.isRunning) {
this.logger.debug('Worker is not running or not initialized');
return;
}
try {
this.logger.info('Stopping Temporal worker...');
const workerState = this.worker.getState();
this.logger.debug(`Worker current state: ${workerState}`);
if (workerState === 'INITIALIZED' ||
workerState === 'RUNNING' ||
workerState === 'FAILED') {
await this.worker.shutdown();
this.logger.info('Temporal worker stopped successfully');
}
else if (workerState === 'STOPPING' ||
workerState === 'DRAINING' ||
workerState === 'DRAINED') {
this.logger.info(`Worker is already shutting down (state: ${workerState})`);
}
else if (workerState === 'STOPPED') {
this.logger.debug('Worker is already stopped');
}
this.isRunning = false;
this.startedAt = null;
}
catch (error) {
this.lastError = this.extractErrorMessage(error);
this.logger.warn('Error stopping worker gracefully', error);
this.isRunning = false;
}
}
async shutdown() {
await this.shutdownWorker();
}
async restartWorker() {
this.logger.info('Restarting Temporal worker...');
try {
await this.stopWorker();
await new Promise((resolve) => setTimeout(resolve, 1000));
await this.startWorker();
return {
success: true,
restartCount: this.restartCount,
maxRestarts: this.maxRestarts,
};
}
catch (error) {
this.lastError = this.extractErrorMessage(error);
this.logger.error('Failed to restart worker', error);
return {
success: false,
error: error instanceof Error ? error : new Error(this.lastError),
restartCount: this.restartCount,
maxRestarts: this.maxRestarts,
};
}
}
getWorkerStatus() {
const uptime = this.startedAt ? Date.now() - this.startedAt.getTime() : undefined;
return {
isInitialized: this.isInitialized,
isRunning: this.isRunning,
isHealthy: this.isInitialized && !this.lastError && this.isRunning,
taskQueue: this.options.taskQueue || 'default',
namespace: this.options.connection?.namespace || 'default',
workflowSource: this.getWorkflowSource(),
activitiesCount: this.activities.size,
lastError: this.lastError || undefined,
startedAt: this.startedAt || undefined,
uptime,
};
}
getRegisteredActivities() {
const result = {};
for (const [name, func] of this.activities.entries()) {
result[name] = func;
}
return result;
}
async registerActivitiesFromDiscovery() {
const errors = [];
let registeredCount = 0;
try {
let attempts = 0;
const maxAttempts = 30;
while (attempts < maxAttempts) {
const healthStatus = this.discoveryService.getHealthStatus();
if (healthStatus.isComplete) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
attempts++;
}
const allActivities = this.discoveryService.getAllActivities();
for (const [activityName, handler] of Object.entries(allActivities)) {
try {
this.activities.set(activityName, handler);
registeredCount++;
this.logger.debug(`Registered activity: ${activityName}`);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
errors.push({ activityName, error: errorMessage });
this.logger.warn(`Failed to register activity ${activityName}: ${errorMessage}`);
}
}
this.logger.info(`Registered ${registeredCount} activities from discovery service`);
return {
success: errors.length === 0,
registeredCount,
errors,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error('Failed to register activities from discovery', error);
return {
success: false,
registeredCount,
errors: [{ activityName: 'discovery', error: errorMessage }],
};
}
}
isWorkerAvailable() {
return this.worker !== null;
}
isWorkerRunning() {
return this.isRunning;
}
getStatus() {
return this.getWorkerStatus();
}
getHealthStatus() {
const uptime = this.startedAt ? Date.now() - this.startedAt.getTime() : undefined;
return {
isHealthy: this.isInitialized && !this.lastError && this.isRunning,
isRunning: this.isRunning,
isInitialized: this.isInitialized,
lastError: this.lastError || undefined,
uptime,
activitiesCount: this.activities.size,
restartCount: this.restartCount,
maxRestarts: this.maxRestarts,
};
}
getStats() {
const uptime = this.startedAt ? Date.now() - this.startedAt.getTime() : undefined;
return {
isInitialized: this.isInitialized,
isRunning: this.isRunning,
activitiesCount: this.activities.size,
restartCount: this.restartCount,
maxRestarts: this.maxRestarts,
uptime,
startedAt: this.startedAt || undefined,
lastError: this.lastError || undefined,
taskQueue: this.options.taskQueue || 'default',
namespace: this.options.connection?.namespace || 'default',
workflowSource: this.getWorkflowSource(),
};
}
validateConfiguration() {
if (!this.options.taskQueue) {
throw new Error('Task queue is required');
}
if (!this.options.connection?.address) {
throw new Error('Connection address is required');
}
if (this.options.worker?.workflowsPath && this.options.worker?.workflowBundle) {
throw new Error('Cannot specify both workflowsPath and workflowBundle');
}
}
getEnvironmentDefaults() {
return {
taskQueue: this.options.taskQueue || 'default',
namespace: this.options.connection?.namespace || 'default',
};
}
buildWorkerOptions() {
const baseOptions = this.getEnvironmentDefaults();
if (this.options.worker?.workflowsPath) {
Object.assign(baseOptions, { workflowsPath: this.options.worker.workflowsPath });
}
else if (this.options.worker?.workflowBundle) {
Object.assign(baseOptions, { workflowBundle: this.options.worker.workflowBundle });
}
const activitiesObj = {};
for (const [name, func] of this.activities.entries()) {
activitiesObj[name] = func;
}
Object.assign(baseOptions, { activities: activitiesObj });
if (this.options.worker?.workerOptions) {
Object.assign(baseOptions, this.options.worker.workerOptions);
}
return baseOptions;
}
async createConnection() {
if (this.injectedConnection) {
this.connection = this.injectedConnection;
this.logger.debug('Using injected connection');
return;
}
if (!this.options.connection?.address) {
throw new Error('Connection address is required');
}
try {
const address = this.options.connection.address;
const connectOptions = {
address,
tls: this.options.connection.tls,
};
if (this.options.connection.apiKey) {
connectOptions.metadata = {
...(this.options.connection.metadata || {}),
authorization: `Bearer ${this.options.connection.apiKey}`,
};
}
this.logger.debug(`Creating NativeConnection to ${address}`);
this.connection = await worker_1.NativeConnection.connect(connectOptions);
this.logger.info(`Connection established to ${address}`);
}
catch (error) {
this.logger.error('Failed to create connection', error);
if (this.options.allowConnectionFailure !== false) {
this.logger.warn('Worker connection failed - continuing without worker functionality');
this.connection = null;
return;
}
throw error;
}
}
async createWorker() {
await this.createConnection();
if (!this.connection) {
throw new Error('Connection not established');
}
const workerConfig = await this.createWorkerConfig();
const { Worker } = await Promise.resolve().then(() => require('@temporalio/worker'));
this.worker = await Worker.create(workerConfig);
}
logWorkerConfiguration() {
this.logger.debug(`Worker configuration: ${JSON.stringify(this.options.worker)}`);
}
async runWorkerLoop() {
if (!this.worker) {
throw new Error('Temporal worker not initialized');
}
try {
await this.worker.run();
}
catch (error) {
this.logger.error('Worker execution failed', error);
throw new Error('Execution error');
}
}
startWorkerInBackground() {
if (this.worker && !this.isRunning) {
this.startWorker().catch((error) => {
this.logger.error('Background worker start failed', error);
});
}
}
shouldInitializeWorker() {
return Boolean(this.options.worker &&
(this.options.worker.workflowsPath ||
this.options.worker.workflowBundle ||
this.options.worker.activityClasses?.length));
}
async initializeWorker() {
if (!this.options.worker) {
return {
success: false,
error: new Error('Worker configuration is required'),
activitiesCount: 0,
taskQueue: this.options.taskQueue || 'default',
namespace: this.options.connection?.namespace || 'default',
};
}
try {
this.validateConfiguration();
await this.createConnection();
if (!this.connection && this.options.allowConnectionFailure !== false) {
this.logger.info('Worker initialization skipped due to connection failure');
return {
success: false,
error: new Error('No worker connection available'),
activitiesCount: 0,
taskQueue: this.options.taskQueue || 'default',
namespace: this.options.connection?.namespace || 'default',
};
}
await this.loadActivitiesFromDiscovery();
const workerConfig = await this.createWorkerConfig();
const { Worker } = await Promise.resolve().then(() => require('@temporalio/worker'));
this.worker = await Worker.create(workerConfig);
this.logger.debug(`Worker created - TaskQueue: ${workerConfig.taskQueue}, Activities: ${this.activities.size}, Source: ${this.getWorkflowSource()}`);
return {
success: true,
worker: this.worker,
activitiesCount: this.activities.size,
taskQueue: workerConfig.taskQueue,
namespace: workerConfig.namespace,
};
}
catch (error) {
this.lastError = this.extractErrorMessage(error);
this.logger.error('Failed to initialize worker', error);
return {
success: false,
error: error instanceof Error ? error : new Error(this.lastError),
activitiesCount: this.activities.size,
taskQueue: this.options.taskQueue || 'default',
namespace: this.options.connection?.namespace || 'default',
};
}
}
async loadActivitiesFromDiscovery() {
const startTime = Date.now();
const errors = [];
let discoveredActivities = 0;
let loadedActivities = 0;
try {
let attempts = 0;
const maxAttempts = 30;
while (attempts < maxAttempts) {
const healthStatus = this.discoveryService.getHealthStatus();
if (healthStatus.isComplete) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
attempts++;
}
const allActivities = this.discoveryService.getAllActivities();
discoveredActivities = Object.keys(allActivities).length;
for (const [activityName, handler] of Object.entries(allActivities)) {
try {
this.activities.set(activityName, handler);
loadedActivities++;
this.logger.debug(`Loaded activity: ${activityName}`);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
errors.push({ component: activityName, error: errorMessage });
this.logger.warn(`Failed to load activity ${activityName}: ${errorMessage}`);
}
}
this.logger.info(`Loaded ${loadedActivities} activities from discovery service`);
return {
success: errors.length === 0,
discoveredActivities,
loadedActivities,
errors,
duration: Date.now() - startTime,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error('Failed to load activities from discovery', error);
return {
success: false,
discoveredActivities,
loadedActivities,
errors: [{ component: 'discovery', error: errorMessage }],
duration: Date.now() - startTime,
};
}
}
async createWorkerConfig() {
const taskQueue = this.options.taskQueue || 'default';
const namespace = this.options.connection?.namespace || 'default';
if (!this.connection) {
throw new Error('Connection not established');
}
const config = {
taskQueue,
namespace,
connection: this.connection,
activities: Object.fromEntries(this.activities),
};
if (this.options.worker?.workflowsPath) {
config.workflowsPath = this.options.worker.workflowsPath;
this.logger.debug(`Using workflows from path: ${this.options.worker.workflowsPath}`);
}
else if (this.options.worker?.workflowBundle) {
config.workflowBundle = this.options.worker.workflowBundle;
this.logger.debug('Using workflow bundle');
}
else {
this.logger.warn('No workflow configuration provided - worker will only handle activities');
}
if (this.options.worker?.workerOptions) {
Object.assign(config, this.options.worker.workerOptions);
}
return config;
}
async shutdownWorker() {
if (this.shutdownPromise) {
return this.shutdownPromise;
}
this.shutdownPromise = this.performShutdown();
return this.shutdownPromise;
}
async performShutdown() {
try {
this.logger.info('Shutting down Temporal worker manager...');
if (this.workers.size > 0) {
this.logger.info(`Shutting down ${this.workers.size} workers...`);
const shutdownPromises = [];
for (const [taskQueue, workerInstance] of this.workers.entries()) {
const shutdownPromise = (async () => {
try {
if (workerInstance.isRunning && workerInstance.worker) {
this.logger.debug(`Stopping worker for '${taskQueue}'...`);
try {
const workerState = workerInstance.worker.getState();
if (workerState === 'INITIALIZED' ||
workerState === 'RUNNING' ||
workerState === 'FAILED') {
await workerInstance.worker.shutdown();
this.logger.debug(`Worker '${taskQueue}' shut down successfully`);
}
else if (workerState === 'STOPPING' ||
workerState === 'DRAINING' ||
workerState === 'DRAINED') {
this.logger.debug(`Worker '${taskQueue}' is already shutting down (state: ${workerState})`);
}
else if (workerState === 'STOPPED') {
this.logger.debug(`Worker '${taskQueue}' is already stopped`);
}
}
catch (shutdownError) {
const errorMessage = shutdownError instanceof Error
? shutdownError.message
: String(shutdownError);
if (errorMessage.includes('Not running') ||
errorMessage.includes('DRAINING') ||
errorMessage.includes('STOPPING')) {
this.logger.debug(`Worker '${taskQueue}' is already shutting down or stopped`);
}
else {
this.logger.warn(`Unexpected error shutting down worker '${taskQueue}': ${errorMessage}`, shutdownError instanceof Error
? shutdownError.stack
: undefined);
}
}
workerInstance.isRunning = false;
}
}
catch (error) {
this.logger.warn(`Error shutting down worker '${taskQueue}'`, error);
}
})();
shutdownPromises.push(shutdownPromise);
}
await Promise.allSettled(shutdownPromises);
this.workers.clear();
this.logger.info('All workers shut down successfully');
}
if (this.worker) {
await this.stopWorker();
this.worker = null;
}
if (this.connection && !this.injectedConnection) {
try {
await this.connection.close();
this.logger.info('Connection closed successfully');
}
catch (error) {
this.logger.warn('Error closing connection', error);
}
this.connection = null;
}
this.isInitialized = false;
this.logger.info('Worker manager shutdown completed');
}
catch (error) {
this.logger.error('Error during worker shutdown', error);
}
finally {
this.shutdownPromise = null;
}
}
getWorkflowSource() {
if (this.options.worker?.workflowBundle)
return 'bundle';
if (this.options.worker?.workflowsPath)
return 'filesystem';
return 'none';
}
extractErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return 'Unknown error';
}
};
exports.TemporalWorkerManagerService = TemporalWorkerManagerService;
exports.TemporalWorkerManagerService = TemporalWorkerManagerService = TemporalWorkerManagerService_1 = __decorate([
(0, common_1.Injectable)(),
__param(1, (0, common_1.Inject)(constants_1.TEMPORAL_MODULE_OPTIONS)),
__param(2, (0, common_1.Inject)(constants_1.TEMPORAL_CONNECTION)),
__metadata("design:paramtypes", [temporal_discovery_service_1.TemporalDiscoveryService, Object, Object])
], TemporalWorkerManagerService);
//# sourceMappingURL=temporal-worker.service.js.map