UNPKG

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
"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