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