nestjs-temporal-core
Version:
Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features
622 lines • 23.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 TemporalService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemporalService = void 0;
const common_1 = require("@nestjs/common");
const constants_1 = require("../constants");
const temporal_client_service_1 = require("./temporal-client.service");
const temporal_worker_service_1 = require("./temporal-worker.service");
const temporal_schedule_service_1 = require("./temporal-schedule.service");
const temporal_discovery_service_1 = require("./temporal-discovery.service");
const temporal_metadata_service_1 = require("./temporal-metadata.service");
const logger_1 = require("../utils/logger");
const constants_2 = require("../constants");
let TemporalService = TemporalService_1 = class TemporalService {
constructor(options, clientService, workerService, scheduleService, discoveryService, metadataAccessor) {
this.options = options;
this.clientService = clientService;
this.workerService = workerService;
this.scheduleService = scheduleService;
this.discoveryService = discoveryService;
this.metadataAccessor = metadataAccessor;
this.isInitialized = false;
this.shutdownPromise = null;
this.logger = (0, logger_1.createLogger)(TemporalService_1.name, {
enableLogger: options.enableLogger,
logLevel: options.logLevel,
});
}
async onModuleInit() {
const startTime = Date.now();
try {
this.logger.info('Initializing Temporal Service...');
const initResult = await this.waitForServicesInitialization();
this.isInitialized = true;
this.logger.info('Temporal Service initialized successfully');
return {
success: true,
servicesInitialized: initResult,
initializationTime: Date.now() - startTime,
};
}
catch (error) {
const errorMessage = this.extractErrorMessage(error);
this.logger.error(`Failed to initialize Temporal Service: ${errorMessage}`, error);
return {
success: false,
error: error instanceof Error ? error : new Error(errorMessage),
servicesInitialized: {
client: false,
worker: false,
schedule: false,
discovery: false,
metadata: false,
},
initializationTime: Date.now() - startTime,
};
}
}
async onModuleDestroy() {
if (this.shutdownPromise) {
return this.shutdownPromise;
}
this.shutdownPromise = this.performShutdown();
return this.shutdownPromise;
}
async waitForServicesInitialization() {
const maxWaitTime = 30000;
const startTime = Date.now();
const servicesStatus = {
client: false,
worker: false,
schedule: false,
discovery: false,
metadata: false,
};
while (Date.now() - startTime < maxWaitTime) {
try {
servicesStatus.client = this.clientService.isHealthy();
servicesStatus.discovery = this.discoveryService.getHealthStatus().isComplete;
servicesStatus.worker = this.workerService?.isWorkerAvailable() || false;
servicesStatus.schedule = this.scheduleService.isHealthy();
servicesStatus.metadata = true;
if (servicesStatus.client && servicesStatus.discovery) {
return servicesStatus;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
catch {
this.logger.warn('Service readiness check failed');
}
}
this.logger.warn('Service initialization timeout - continuing anyway');
return servicesStatus;
}
async performShutdown() {
try {
this.logger.info('Shutting down Temporal Service...');
if (this.workerService && this.workerService.isWorkerRunning()) {
await this.workerService.stopWorker();
}
this.isInitialized = false;
this.logger.info('Temporal Service shut down successfully');
}
catch (error) {
this.logger.error(`Error during service shutdown: ${this.extractErrorMessage(error)}`, error);
}
finally {
this.shutdownPromise = null;
}
}
async startWorkflow(workflowType, args, options) {
const startTime = Date.now();
try {
this.ensureInitialized();
const enhancedOptions = this.enhanceWorkflowOptions(options || {});
const result = await this.clientService.startWorkflow(workflowType, args || [], enhancedOptions);
return {
success: true,
result: result,
executionTime: Date.now() - startTime,
};
}
catch (error) {
this.logger.error(`Failed to start workflow '${workflowType}': ${this.extractErrorMessage(error)}`, error);
throw error instanceof Error ? error : new Error(this.extractErrorMessage(error));
}
}
async signalWorkflow(workflowId, signalName, args) {
if (!workflowId || workflowId.trim() === '') {
throw new Error('Workflow ID is required');
}
try {
this.ensureInitialized();
await this.clientService.signalWorkflow(workflowId, signalName, args || []);
return {
success: true,
workflowId,
signalName,
};
}
catch (error) {
this.logger.error(`Failed to signal workflow '${workflowId}' with signal '${signalName}': ${this.extractErrorMessage(error)}`, error);
throw error instanceof Error ? error : new Error(this.extractErrorMessage(error));
}
}
async queryWorkflow(workflowId, queryName, args) {
if (!workflowId || workflowId.trim() === '') {
throw new Error('Workflow ID is required');
}
try {
this.ensureInitialized();
const result = await this.clientService.queryWorkflow(workflowId, queryName, args || []);
return {
success: true,
result: result,
workflowId,
queryName,
};
}
catch (error) {
this.logger.error(`Failed to query workflow '${workflowId}' with query '${queryName}': ${this.extractErrorMessage(error)}`, error);
throw error instanceof Error ? error : new Error(this.extractErrorMessage(error));
}
}
async getWorkflowHandle(workflowId, runId) {
this.ensureInitialized();
const handle = this.clientService.getWorkflowHandle(workflowId, runId);
return handle;
}
async terminateWorkflow(workflowId, reason) {
try {
this.ensureInitialized();
await this.clientService.terminateWorkflow(workflowId, reason);
return {
success: true,
workflowId,
reason,
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
workflowId,
reason,
};
}
}
async cancelWorkflow(workflowId) {
try {
this.ensureInitialized();
await this.clientService.cancelWorkflow(workflowId);
return {
success: true,
workflowId,
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
workflowId,
};
}
}
async startWorker() {
this.ensureInitialized();
return this.workerService.startWorker();
}
async stopWorker() {
this.ensureInitialized();
return this.workerService.stopWorker();
}
isWorkerRunning() {
this.ensureInitialized();
if (!this.workerService) {
return false;
}
return this.workerService.isWorkerRunning();
}
hasWorker() {
return this.workerService?.isWorkerAvailable() || false;
}
getWorkerStatus() {
this.ensureInitialized();
if (!this.workerService) {
return null;
}
return this.workerService.getStatus();
}
getWorkerManager() {
return this.workerService;
}
getWorker(taskQueue) {
this.ensureInitialized();
if (!this.workerService) {
return null;
}
return this.workerService.getWorker(taskQueue);
}
getAllWorkers() {
this.ensureInitialized();
if (!this.workerService) {
return null;
}
return this.workerService.getAllWorkers();
}
getWorkerStatusByTaskQueue(taskQueue) {
this.ensureInitialized();
if (!this.workerService) {
return null;
}
return this.workerService.getWorkerStatusByTaskQueue(taskQueue);
}
async startWorkerByTaskQueue(taskQueue) {
this.ensureInitialized();
if (!this.workerService) {
throw new Error('Worker service not available');
}
return this.workerService.startWorkerByTaskQueue(taskQueue);
}
async stopWorkerByTaskQueue(taskQueue) {
this.ensureInitialized();
if (!this.workerService) {
throw new Error('Worker service not available');
}
return this.workerService.stopWorkerByTaskQueue(taskQueue);
}
async registerWorker(workerDef) {
this.ensureInitialized();
if (!this.workerService) {
return {
success: false,
taskQueue: workerDef.taskQueue,
error: new Error('Worker service not available'),
};
}
return this.workerService.registerWorker(workerDef);
}
async executeActivity(name, ...args) {
const startTime = Date.now();
try {
this.ensureInitialized();
const result = await this.discoveryService.executeActivity(name, ...args);
return {
success: true,
result: result,
activityName: name,
executionTime: Date.now() - startTime,
args,
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
activityName: name,
executionTime: Date.now() - startTime,
args,
};
}
}
getActivity(name) {
this.ensureInitialized();
return this.discoveryService.getActivity(name);
}
getAllActivities() {
this.ensureInitialized();
return this.discoveryService.getAllActivities();
}
hasActivity(name) {
this.ensureInitialized();
return this.discoveryService.hasActivity(name);
}
getActivityNames() {
this.ensureInitialized();
return this.discoveryService.getActivityNames();
}
isActivity(target) {
return this.metadataAccessor.isActivity(target);
}
isActivityMethod(target, methodName) {
try {
const targetObj = target;
return (Reflect.hasMetadata('TEMPORAL_ACTIVITY_METHOD', target, methodName) ||
(targetObj.constructor?.prototype !== undefined &&
Reflect.hasMetadata('TEMPORAL_ACTIVITY_METHOD', targetObj.constructor.prototype, methodName)));
}
catch {
return false;
}
}
getActivityMetadata(target) {
const metadata = this.metadataAccessor.getActivityMetadata(target);
return metadata;
}
extractActivityMethods(target) {
return this.metadataAccessor.extractActivityMethodsFromClass(target);
}
async getOverallHealth() {
const health = this.getHealth();
return {
status: health.status,
components: {
client: {
status: health.services.client.status,
isInitialized: this.isInitialized,
lastError: undefined,
uptime: undefined,
details: health.services.client.details || {},
},
worker: {
status: health.services.worker.status,
isInitialized: this.isInitialized,
lastError: undefined,
uptime: undefined,
details: health.services.worker.details || {},
},
schedule: {
status: health.services.schedule.status,
isInitialized: this.isInitialized,
lastError: undefined,
uptime: undefined,
details: health.services.schedule.details || {},
},
activity: {
status: health.services.activity.status,
isInitialized: this.isInitialized,
lastError: undefined,
uptime: undefined,
details: health.services.activity.details || {},
},
discovery: {
status: health.services.discovery.status,
isInitialized: this.isInitialized,
lastError: undefined,
uptime: undefined,
details: health.services.discovery.details || {},
},
},
isInitialized: health.isInitialized,
namespace: health.namespace,
summary: health.summary,
timestamp: new Date(),
};
}
async getWorkerHealth() {
try {
if (!this.hasWorker()) {
return { status: 'not_available' };
}
if (!this.workerService) {
return { status: 'not_available' };
}
const workerStatus = this.workerService.getWorkerStatus();
let status;
if (workerStatus.isHealthy) {
status = 'healthy';
}
else if (workerStatus.isRunning) {
status = 'degraded';
}
else {
status = 'unhealthy';
}
return {
status,
details: workerStatus,
};
}
catch {
return {
status: 'unhealthy',
details: undefined,
};
}
}
getHealth() {
const clientHealth = this.getClientHealth();
const workerHealth = this.getWorkerHealthStatus();
const scheduleHealth = this.getScheduleHealth();
const activityHealth = this.getActivityHealth();
const discoveryHealth = this.getDiscoveryHealth();
const services = {
client: clientHealth,
worker: workerHealth,
schedule: scheduleHealth,
activity: activityHealth,
discovery: discoveryHealth,
};
const allHealthy = Object.values(services).every((service) => service.status === 'healthy');
const anyUnhealthy = Object.values(services).some((service) => service.status === 'unhealthy');
let overallStatus;
if (allHealthy) {
overallStatus = 'healthy';
}
else if (anyUnhealthy) {
overallStatus = 'unhealthy';
}
else {
overallStatus = 'degraded';
}
return {
status: overallStatus,
services,
isInitialized: this.isInitialized,
namespace: this.options.connection?.namespace || 'default',
summary: {
totalActivities: activityHealth.activitiesCount?.total || 0,
totalSchedules: scheduleHealth.schedulesCount || 0,
workerRunning: workerHealth.status === 'healthy',
clientConnected: clientHealth.status === 'healthy',
},
};
}
getStats() {
this.ensureInitialized();
const activityCount = this.getActivityCount();
const scheduleCount = this.getScheduleCount();
const workerStatus = this.workerService.getWorkerStatus();
const clientHealth = this.getClientHealth();
const discoveryStats = this.discoveryService.getStats();
return {
activities: {
classes: activityCount.classes,
methods: activityCount.methods,
total: activityCount.total,
registered: activityCount.total,
available: activityCount.total,
},
schedules: {
total: scheduleCount,
active: scheduleCount,
paused: 0,
},
worker: {
isRunning: workerStatus.isRunning,
isHealthy: workerStatus.isHealthy,
activitiesCount: workerStatus.activitiesCount,
uptime: workerStatus.uptime,
},
client: {
isConnected: clientHealth.status === 'healthy',
isHealthy: clientHealth.status === 'healthy',
namespace: this.options.connection?.namespace || 'default',
},
discovery: {
isComplete: this.discoveryService.getHealthStatus().isComplete,
discoveredCount: discoveryStats.methods,
errors: 0,
},
};
}
logServiceStatus() {
const health = this.getHealth();
const stats = this.getStats();
this.logger.debug(`Service Status - Overall: ${health.status}`);
this.logger.debug(`Client: ${health.services.client.status}, Worker: ${health.services.worker.status}`);
this.logger.debug(`Activities: ${stats.activities.total}, Schedules: ${stats.schedules}`);
this.logger.debug(`Namespace: ${health.namespace}`);
}
ensureInitialized() {
if (!this.isInitialized) {
throw new Error('Temporal Service is not initialized');
}
}
enhanceWorkflowOptions(options) {
if (!options.taskQueue) {
options.taskQueue = this.options.taskQueue || constants_2.DEFAULT_TASK_QUEUE;
}
return options;
}
getClientHealth() {
const isHealthy = this.clientService.isHealthy();
return { status: isHealthy ? 'healthy' : 'unhealthy' };
}
getWorkerHealthStatus() {
if (!this.workerService) {
return {
status: 'unhealthy',
details: { error: 'Worker service not available' },
};
}
const workerStatus = this.workerService.getWorkerStatus();
return {
status: workerStatus.isHealthy ? 'healthy' : 'degraded',
details: {
isInitialized: workerStatus.isInitialized,
isRunning: workerStatus.isRunning,
isHealthy: workerStatus.isHealthy,
taskQueue: workerStatus.taskQueue,
namespace: workerStatus.namespace,
workflowSource: workerStatus.workflowSource,
activitiesCount: workerStatus.activitiesCount,
workflowsCount: workerStatus.workflowsCount,
lastError: workerStatus.lastError,
startedAt: workerStatus.startedAt,
uptime: workerStatus.uptime,
},
};
}
getScheduleHealth() {
const isHealthy = this.scheduleService.isHealthy();
const stats = this.scheduleService.getScheduleStats();
return { status: isHealthy ? 'healthy' : 'unhealthy', schedulesCount: stats.total };
}
getActivityHealth() {
const healthStatus = this.discoveryService.getHealthStatus();
const count = this.discoveryService.getActivityNames().length;
return {
status: healthStatus.status === 'healthy' ? 'healthy' : 'unhealthy',
activitiesCount: { total: count },
};
}
getDiscoveryHealth() {
const healthStatus = this.discoveryService.getHealthStatus();
return { status: healthStatus.status };
}
getActivityCount() {
const names = this.discoveryService.getActivityNames();
return { classes: names.length, methods: names.length, total: names.length };
}
getScheduleCount() {
const stats = this.scheduleService.getScheduleStats();
return stats.total;
}
get client() {
this.ensureInitialized();
return this.clientService;
}
get worker() {
this.ensureInitialized();
return this.workerService;
}
get schedule() {
this.ensureInitialized();
return this.scheduleService;
}
get activity() {
this.ensureInitialized();
return this.discoveryService;
}
get discovery() {
this.ensureInitialized();
return this.discoveryService;
}
get metadata() {
return this.metadataAccessor;
}
extractErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return 'Unknown error';
}
};
exports.TemporalService = TemporalService;
exports.TemporalService = TemporalService = TemporalService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)(constants_1.TEMPORAL_MODULE_OPTIONS)),
__metadata("design:paramtypes", [Object, temporal_client_service_1.TemporalClientService,
temporal_worker_service_1.TemporalWorkerManagerService,
temporal_schedule_service_1.TemporalScheduleService,
temporal_discovery_service_1.TemporalDiscoveryService,
temporal_metadata_service_1.TemporalMetadataAccessor])
], TemporalService);
//# sourceMappingURL=temporal.service.js.map