UNPKG

nestjs-temporal-core

Version:

Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features

994 lines 42.5 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.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, }); } get maxRestarts() { return this.options.worker?.maxRestarts ?? this.options.maxRestarts ?? 3; } get autoRestartEnabled() { const workerAutoRestart = this.options.worker?.autoRestart; if (workerAutoRestart !== undefined) { return workerAutoRestart; } return this.options.autoRestart !== false; } async onModuleInit() { try { this.logger.verbose('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 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('Continuing without worker (connection failures 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('Continuing without worker (connection failures 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.verbose(`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 = { ...workerDef.workerOptions, 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; } 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.getWorkflowSource(workerDef), }; this.workers.set(workerDef.taskQueue, workerInstance); this.logger.info(`Created worker '${workerDef.taskQueue}' (${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 register worker '${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.verbose(`Starting worker '${taskQueue}'...`); workerInstance.isRunning = true; workerInstance.startedAt = new Date(); workerInstance.lastError = null; workerInstance.restartCount = 0; this.runWorkerWithAutoRestartByTaskQueue(taskQueue); this.logger.info(`Worker '${taskQueue}' started (${workerInstance.activities.size} activities, ${workerInstance.workflowSource})`); } catch (error) { workerInstance.lastError = this.extractErrorMessage(error); workerInstance.isRunning = false; this.logger.error(`Failed to start worker '${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.verbose(`Worker '${taskQueue}' is not running`); return; } try { this.logger.verbose(`Stopping worker '${taskQueue}'...`); this.safeShutdownWorker(workerInstance.worker, taskQueue, workerInstance.startedAt); workerInstance.isRunning = false; workerInstance.startedAt = null; } catch (error) { workerInstance.lastError = this.extractErrorMessage(error); this.logger.warn(`Error stopping worker '${taskQueue}'`, error); workerInstance.isRunning = false; } } runWorkerWithAutoRestartByTaskQueue(taskQueue) { const workerInstance = this.workers.get(taskQueue); if (!workerInstance) return; const workerDef = this.options.workers?.find((w) => w.taskQueue === taskQueue); const maxRestarts = workerDef?.maxRestarts ?? this.options.maxRestarts ?? 3; workerInstance.worker.run().catch((error) => { workerInstance.lastError = this.extractErrorMessage(error); workerInstance.isRunning = false; const autoRestartEnabled = workerDef?.autoRestart ?? this.options.autoRestart; if (autoRestartEnabled !== false && workerInstance.restartCount < maxRestarts) { workerInstance.restartCount++; this.logger.warn(`Worker '${taskQueue}' failed, auto-restarting in 1s (attempt ${workerInstance.restartCount}/${maxRestarts})`, error); setTimeout(() => { this.autoRestartWorkerByTaskQueue(taskQueue).catch((restartError) => { this.logger.error(`Auto-restart failed for '${taskQueue}'`, restartError); }); }, 1000); } else if (workerInstance.restartCount >= maxRestarts) { this.logger.error(`Worker '${taskQueue}' failed after ${maxRestarts} restart attempts, giving up`, error); } else { this.logger.error(`Worker '${taskQueue}' run failed`, error); } }); } async autoRestartWorkerByTaskQueue(taskQueue) { this.logger.info(`Auto-restarting worker '${taskQueue}'...`); try { const workerDef = this.options.workers?.find((w) => w.taskQueue === taskQueue); if (!workerDef) { throw new Error(`Worker definition for '${taskQueue}' not found`); } const oldInstance = this.workers.get(taskQueue); const restartCount = oldInstance?.restartCount ?? 0; await this.cleanupWorkerForRestartByTaskQueue(taskQueue); await new Promise((resolve) => setTimeout(resolve, 500)); this.workers.delete(taskQueue); const newInstance = await this.createWorkerFromDefinition(workerDef); newInstance.restartCount = restartCount; newInstance.isRunning = true; newInstance.startedAt = new Date(); newInstance.lastError = null; this.runWorkerWithAutoRestartByTaskQueue(taskQueue); this.logger.info(`Worker '${taskQueue}' auto-restarted successfully`); } catch (error) { const workerInstance = this.workers.get(taskQueue); if (workerInstance) { workerInstance.lastError = this.extractErrorMessage(error); workerInstance.isRunning = false; } this.logger.error(`Auto-restart failed for '${taskQueue}'`, error); throw error; } } async cleanupWorkerForRestartByTaskQueue(taskQueue) { const workerInstance = this.workers.get(taskQueue); if (!workerInstance?.worker) return; try { this.safeShutdownWorker(workerInstance.worker, taskQueue); } catch (error) { this.logger.warn(`Error during worker '${taskQueue}' cleanup (continuing with restart)`, error); } workerInstance.isRunning = false; workerInstance.startedAt = null; } getConnection() { return this.connection; } getWorkerStatusFromInstance(workerInstance) { const uptime = workerInstance.startedAt ? Date.now() - workerInstance.startedAt.getTime() : undefined; const isHealthy = this.calculateWorkerHealth(workerInstance); return { isInitialized: workerInstance.isInitialized, isRunning: workerInstance.isRunning, isHealthy, 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) { await this.waitForDiscoveryCompletion(); 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); } } } 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.isRunning = false; if (this.autoRestartEnabled && this.restartCount < this.maxRestarts) { this.restartCount++; this.logger.warn(`Worker failed, auto-restarting in 1s (attempt ${this.restartCount}/${this.maxRestarts})`, error); 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(`Worker failed after ${this.maxRestarts} restart attempts, giving up`, error); } else { this.logger.error('Worker run failed', error); } }); setTimeout(() => resolve(), 500); } catch (error) { reject(error); } }); }); } catch (error) { throw error; } } async autoRestartWorker() { this.logger.info('Auto-restarting Temporal worker...'); try { await this.cleanupWorkerForRestart(); await new Promise((resolve) => setTimeout(resolve, 500)); const initResult = await this.initializeWorker(); if (!initResult.success) { throw initResult.error || new Error('Failed to reinitialize worker'); } this.isInitialized = true; this.isRunning = true; this.startedAt = new Date(); this.lastError = null; await 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 cleanupWorkerForRestart() { if (!this.worker) { return; } try { this.safeShutdownWorker(this.worker, 'legacy'); } catch (error) { this.logger.warn('Error during worker cleanup (continuing with restart)', error); } this.worker = null; this.isRunning = false; this.startedAt = null; } async stopWorker() { if (!this.worker || !this.isRunning) { this.logger.debug('Worker is not running or not initialized'); return; } try { this.logger.verbose('Stopping Temporal worker...'); const taskQueue = this.options.taskQueue || 'default'; this.safeShutdownWorker(this.worker, taskQueue, this.startedAt); 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; const isHealthy = this.calculateWorkerHealth(); return { isInitialized: this.isInitialized, isRunning: this.isRunning, isHealthy, 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 { await this.waitForDiscoveryCompletion(); const allActivities = this.discoveryService.getAllActivities(); for (const [activityName, handler] of Object.entries(allActivities)) { try { this.activities.set(activityName, handler); registeredCount++; this.logger.verbose(`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; } getHealthStatus() { const uptime = this.startedAt ? Date.now() - this.startedAt.getTime() : undefined; const isHealthy = this.calculateWorkerHealth(); return { isHealthy, 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'); } } 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.verbose(`Connecting to Temporal server at ${address}...`); this.connection = await worker_1.NativeConnection.connect(connectOptions); const ns = this.options.connection?.namespace || 'default'; this.logger.info(`Connected to ${address} (namespace: ${ns})`); } catch (error) { this.logger.error('Failed to create connection', error); if (this.options.allowConnectionFailure !== false) { this.logger.warn('Connection failed, continuing without worker'); this.connection = null; return; } throw 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.verbose(`Worker created: queue='${workerConfig.taskQueue}', activities=${this.activities.size}, workflows=${this.getWorkflowSource()}`); return { success: true, worker: this.worker, activitiesCount: this.activities.size, taskQueue: workerConfig.taskQueue, namespace: workerConfig.namespace ?? this.options.connection?.namespace ?? 'default', }; } 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 { await this.waitForDiscoveryCompletion(); 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.verbose(`Loaded activity: ${activityName}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push({ name: activityName, error: errorMessage }); this.logger.warn(`Failed to load activity '${activityName}': ${errorMessage}`); } } const duration = Date.now() - startTime; this.logger.info(`Loaded ${loadedActivities} ${loadedActivities === 1 ? 'activity' : 'activities'} from discovery in ${duration}ms`); 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: [{ name: '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 = { ...this.options.worker?.workerOptions, taskQueue, namespace, connection: this.connection, activities: Object.fromEntries(this.activities), }; if (this.options.worker?.workflowsPath) { config.workflowsPath = this.options.worker.workflowsPath; this.logger.verbose(`Using workflows from: ${this.options.worker.workflowsPath}`); } else if (this.options.worker?.workflowBundle) { config.workflowBundle = this.options.worker .workflowBundle; this.logger.verbose('Using workflow bundle'); } else { this.logger.warn('No workflow configuration - worker will only handle activities'); } 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}'...`); this.safeShutdownWorker(workerInstance.worker, taskQueue); 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(config) { const source = config ?? this.options.worker; if (source?.workflowBundle) return 'bundle'; if (source?.workflowsPath) return 'filesystem'; return 'none'; } getNativeState() { if (!this.worker) { return null; } try { return this.worker.getState(); } catch { return null; } } calculateWorkerHealth(workerInstance = null) { if (workerInstance) { let nativeState = null; try { nativeState = workerInstance.worker?.getState() ?? null; } catch { nativeState = null; } return (workerInstance.isInitialized && !workerInstance.lastError && workerInstance.isRunning && nativeState === 'RUNNING'); } const nativeState = this.getNativeState(); return this.isInitialized && !this.lastError && this.isRunning && nativeState === 'RUNNING'; } async waitForDiscoveryCompletion(maxWaitMs = 3000) { const pollInterval = 100; const maxAttempts = Math.ceil(maxWaitMs / pollInterval); let attempts = 0; while (attempts < maxAttempts) { const healthStatus = this.discoveryService.getHealthStatus(); if (healthStatus.isComplete) { return true; } await new Promise((resolve) => setTimeout(resolve, pollInterval)); attempts++; } return false; } safeShutdownWorker(worker, identifier, startedAt) { try { const workerState = worker.getState(); if (workerState === 'INITIALIZED' || workerState === 'RUNNING' || workerState === 'FAILED') { worker.shutdown(); if (startedAt) { const uptime = Math.round((Date.now() - startedAt.getTime()) / 1000); this.logger.info(`Worker '${identifier}' stopped (uptime: ${uptime}s)`); } else { this.logger.verbose(`Worker '${identifier}' shut down`); } return true; } if (workerState === 'STOPPING' || workerState === 'DRAINING' || workerState === 'DRAINED') { this.logger.verbose(`Worker '${identifier}' already shutting down (${workerState})`); return false; } if (workerState === 'STOPPED') { this.logger.verbose(`Worker '${identifier}' already stopped`); return false; } return false; } catch (error) { const msg = error instanceof Error ? error.message : String(error); if (msg.includes('Not running') || msg.includes('DRAINING') || msg.includes('STOPPING')) { this.logger.verbose(`Worker '${identifier}' already shutting down`); return false; } throw error; } } 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