nestjs-temporal-core
Version:
Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features
238 lines • 11.2 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 TemporalScheduleManagerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemporalScheduleManagerService = void 0;
const common_1 = require("@nestjs/common");
const temporal_discovery_service_1 = require("./temporal-discovery.service");
const client_1 = require("../client");
const logger_1 = require("../utils/logger");
let TemporalScheduleManagerService = TemporalScheduleManagerService_1 = class TemporalScheduleManagerService {
constructor(discoveryService, scheduleService) {
this.discoveryService = discoveryService;
this.scheduleService = scheduleService;
this.logger = (0, logger_1.createLogger)(TemporalScheduleManagerService_1.name);
this.managedSchedules = new Map();
this.setupPromises = new Map();
}
async onApplicationBootstrap() {
await this.setupDiscoveredSchedules();
}
async onModuleDestroy() {
this.logger.log('Schedule manager shutting down');
}
async setupDiscoveredSchedules() {
const scheduledWorkflows = this.discoveryService.getScheduledWorkflows();
if (scheduledWorkflows.length === 0) {
this.logger.log('No scheduled workflows found');
return;
}
this.logger.log(`Setting up ${scheduledWorkflows.length} scheduled workflows`);
const setupPromises = scheduledWorkflows.map((scheduled) => this.setupSingleSchedule(scheduled));
const results = await Promise.allSettled(setupPromises);
this.logSetupResults(results, scheduledWorkflows);
}
async setupSingleSchedule(scheduled) {
const { scheduleOptions, workflowName } = scheduled;
const { scheduleId } = scheduleOptions;
if (this.setupPromises.has(scheduleId)) {
await this.setupPromises.get(scheduleId);
return;
}
const setupPromise = this.performScheduleSetup(scheduled);
this.setupPromises.set(scheduleId, setupPromise);
try {
await setupPromise;
this.updateScheduleStatus(scheduleId, workflowName, true, true);
this.logger.debug(`Successfully set up schedule: ${scheduleId}`);
}
catch (error) {
this.updateScheduleStatus(scheduleId, workflowName, false, false, error.message);
this.logger.error(`Failed to setup schedule ${scheduleId}: ${error.message}`);
}
finally {
this.setupPromises.delete(scheduleId);
}
}
async performScheduleSetup(scheduled) {
const { scheduleOptions, workflowName, controllerInfo } = scheduled;
const { scheduleId } = scheduleOptions;
if (scheduleOptions.autoStart === false) {
this.logger.debug(`Auto-start disabled for schedule ${scheduleId}`);
this.updateScheduleStatus(scheduleId, workflowName, true, false, 'Auto-start disabled');
return;
}
if (await this.scheduleService.scheduleExists(scheduleId)) {
this.logger.debug(`Schedule ${scheduleId} already exists, skipping creation`);
this.updateScheduleStatus(scheduleId, workflowName, true, true, 'Already exists');
return;
}
const taskQueue = this.resolveTaskQueue(scheduleOptions, controllerInfo);
if (scheduleOptions.cron) {
await this.createCronSchedule(scheduled, taskQueue);
}
else if (scheduleOptions.interval) {
await this.createIntervalSchedule(scheduled, taskQueue);
}
else {
throw new Error('Schedule must have either cron or interval configuration');
}
if (scheduleOptions.startPaused) {
await this.scheduleService.pauseSchedule(scheduleId, 'Started in paused state');
this.logger.debug(`Schedule ${scheduleId} created in paused state`);
}
}
async createCronSchedule(scheduled, taskQueue) {
const { scheduleOptions, workflowName } = scheduled;
await this.scheduleService.createCronSchedule(scheduleOptions.scheduleId, workflowName, scheduleOptions.cron, taskQueue, [], {
description: scheduleOptions.description,
timezone: scheduleOptions.timezone,
overlapPolicy: scheduleOptions.overlapPolicy,
startPaused: scheduleOptions.startPaused,
});
this.logger.log(`Created cron schedule: ${scheduleOptions.scheduleId} -> ${workflowName} (${scheduleOptions.cron})`);
}
async createIntervalSchedule(scheduled, taskQueue) {
const { scheduleOptions, workflowName } = scheduled;
await this.scheduleService.createIntervalSchedule(scheduleOptions.scheduleId, workflowName, scheduleOptions.interval, taskQueue, [], {
description: scheduleOptions.description,
overlapPolicy: scheduleOptions.overlapPolicy,
startPaused: scheduleOptions.startPaused,
});
this.logger.log(`Created interval schedule: ${scheduleOptions.scheduleId} -> ${workflowName} (${scheduleOptions.interval})`);
}
async triggerSchedule(scheduleId) {
this.ensureScheduleManaged(scheduleId);
await this.scheduleService.triggerSchedule(scheduleId);
this.logger.log(`Triggered schedule: ${scheduleId}`);
}
async pauseSchedule(scheduleId, note) {
this.ensureScheduleManaged(scheduleId);
await this.scheduleService.pauseSchedule(scheduleId, note);
this.updateScheduleStatus(scheduleId, this.getScheduleWorkflowName(scheduleId), true, false);
this.logger.log(`Paused schedule: ${scheduleId}${note ? ` (${note})` : ''}`);
}
async resumeSchedule(scheduleId, note) {
this.ensureScheduleManaged(scheduleId);
await this.scheduleService.resumeSchedule(scheduleId, note);
this.updateScheduleStatus(scheduleId, this.getScheduleWorkflowName(scheduleId), true, true);
this.logger.log(`Resumed schedule: ${scheduleId}${note ? ` (${note})` : ''}`);
}
async deleteSchedule(scheduleId, force = false) {
this.ensureScheduleManaged(scheduleId);
if (!force) {
this.logger.warn(`Deleting schedule ${scheduleId} requires force=true. This action cannot be undone.`);
throw new Error('Schedule deletion requires force=true confirmation');
}
await this.scheduleService.deleteSchedule(scheduleId);
this.managedSchedules.delete(scheduleId);
this.logger.log(`Deleted schedule: ${scheduleId}`);
}
async retryFailedSetups() {
const failedSchedules = this.discoveryService
.getScheduledWorkflows()
.filter((scheduled) => {
const status = this.managedSchedules.get(scheduled.scheduleOptions.scheduleId);
return !status || !status.isManaged;
});
if (failedSchedules.length === 0) {
this.logger.log('No failed schedules to retry');
return;
}
this.logger.log(`Retrying ${failedSchedules.length} failed schedule setups`);
for (const scheduled of failedSchedules) {
await this.setupSingleSchedule(scheduled);
}
}
getManagedSchedules() {
return Array.from(this.managedSchedules.keys());
}
getManagedScheduleStatuses() {
return Array.from(this.managedSchedules.values());
}
getScheduleStatus(scheduleId) {
return this.managedSchedules.get(scheduleId);
}
isScheduleManaged(scheduleId) {
return this.managedSchedules.has(scheduleId);
}
isScheduleActive(scheduleId) {
const status = this.managedSchedules.get(scheduleId);
return status?.isActive || false;
}
getScheduleStats() {
const statuses = Array.from(this.managedSchedules.values());
return {
total: statuses.length,
active: statuses.filter((s) => s.isActive).length,
inactive: statuses.filter((s) => !s.isActive).length,
errors: statuses.filter((s) => s.lastError).length,
};
}
getHealthStatus() {
const stats = this.getScheduleStats();
let status;
if (stats.errors > 0) {
status = stats.errors === stats.total ? 'unhealthy' : 'degraded';
}
else {
status = 'healthy';
}
return {
status,
managedSchedules: stats.total,
activeSchedules: stats.active,
errorCount: stats.errors,
};
}
resolveTaskQueue(scheduleOptions, controllerInfo) {
return (scheduleOptions.taskQueue ||
controllerInfo.taskQueue ||
'default');
}
updateScheduleStatus(scheduleId, workflowName, isManaged, isActive, error) {
const existing = this.managedSchedules.get(scheduleId);
const now = new Date();
this.managedSchedules.set(scheduleId, {
scheduleId,
workflowName,
isManaged,
isActive,
lastError: error,
createdAt: existing?.createdAt || now,
lastUpdatedAt: now,
});
}
logSetupResults(results, scheduledWorkflows) {
const successful = results.filter((r) => r.status === 'fulfilled').length;
const failed = results.filter((r) => r.status === 'rejected').length;
this.logger.log(`Schedule setup completed: ${successful} successful, ${failed} failed out of ${scheduledWorkflows.length} total`);
if (failed > 0) {
this.logger.warn(`${failed} schedules failed to setup. Use retryFailedSetups() to retry.`);
}
}
ensureScheduleManaged(scheduleId) {
if (!this.isScheduleManaged(scheduleId)) {
throw new Error(`Schedule '${scheduleId}' is not managed by this service`);
}
}
getScheduleWorkflowName(scheduleId) {
const status = this.managedSchedules.get(scheduleId);
return status?.workflowName || 'unknown';
}
};
exports.TemporalScheduleManagerService = TemporalScheduleManagerService;
exports.TemporalScheduleManagerService = TemporalScheduleManagerService = TemporalScheduleManagerService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [temporal_discovery_service_1.TemporalDiscoveryService,
client_1.TemporalScheduleService])
], TemporalScheduleManagerService);
//# sourceMappingURL=temporal-schedule-manager.service.js.map