UNPKG

nestjs-temporal-core

Version:

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

221 lines 9.59 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 TemporalSchedulesService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.TemporalSchedulesService = void 0; const common_1 = require("@nestjs/common"); const temporal_discovery_service_1 = require("../discovery/temporal-discovery.service"); const constants_1 = require("../constants"); const logger_1 = require("../utils/logger"); let TemporalSchedulesService = TemporalSchedulesService_1 = class TemporalSchedulesService { constructor(options, discoveryService) { this.options = options; this.discoveryService = discoveryService; this.managedSchedules = new Map(); this.logger = (0, logger_1.createLogger)(TemporalSchedulesService_1.name); } async onModuleInit() { await this.initializeSchedules(); this.logScheduleSummary(); } async initializeSchedules() { const scheduledWorkflows = this.discoveryService.getScheduledWorkflows(); for (const scheduledWorkflow of scheduledWorkflows) { try { const scheduleInfo = this.createScheduleInfo(scheduledWorkflow); this.managedSchedules.set(scheduledWorkflow.scheduleOptions.scheduleId, scheduleInfo); this.logger.debug(`Registered schedule: ${scheduledWorkflow.scheduleOptions.scheduleId} -> ${scheduledWorkflow.workflowName}`); } catch (error) { this.logger.error(`Failed to register schedule ${scheduledWorkflow.scheduleOptions.scheduleId}:`, error.stack); } } this.logger.log(`Initialized ${this.managedSchedules.size} schedules`); } createScheduleInfo(scheduledWorkflow) { const { scheduleOptions, workflowName, handler, controllerInfo } = scheduledWorkflow; return { scheduleId: scheduleOptions.scheduleId, workflowName, cronExpression: scheduleOptions.cron, intervalExpression: scheduleOptions.interval, description: scheduleOptions.description, timezone: scheduleOptions.timezone || this.options.defaultTimezone || 'UTC', overlapPolicy: scheduleOptions.overlapPolicy || 'SKIP', isActive: !scheduleOptions.startPaused, autoStart: scheduleOptions.autoStart !== false, taskQueue: scheduleOptions.taskQueue, handler, controllerInfo, createdAt: new Date(), lastTriggered: undefined, nextRun: this.calculateNextRun(scheduleOptions), }; } calculateNextRun(scheduleOptions) { if (scheduleOptions.cron) { return new Date(Date.now() + 60000); } if (scheduleOptions.interval) { const intervalMs = this.parseInterval(scheduleOptions.interval); return new Date(Date.now() + intervalMs); } return undefined; } parseInterval(interval) { const match = interval.match(/^(\d+)(ms|[smhd])$/); if (!match) return 60000; const value = parseInt(match[1]); const unit = match[2]; switch (unit) { case 'ms': return value; case 's': return value * 1000; case 'm': return value * 60 * 1000; case 'h': return value * 60 * 60 * 1000; case 'd': return value * 24 * 60 * 60 * 1000; default: return 60000; } } getManagedSchedules() { return Array.from(this.managedSchedules.values()); } getSchedule(scheduleId) { return this.managedSchedules.get(scheduleId); } getScheduleIds() { return Array.from(this.managedSchedules.keys()); } isScheduleManaged(scheduleId) { return this.managedSchedules.has(scheduleId); } getActiveSchedules() { return this.getManagedSchedules().filter((schedule) => schedule.isActive); } getSchedulesByWorkflow(workflowName) { return this.getManagedSchedules().filter((schedule) => schedule.workflowName === workflowName); } getSchedulesByTaskQueue(taskQueue) { return this.getManagedSchedules().filter((schedule) => schedule.taskQueue === taskQueue); } async updateScheduleStatus(scheduleId, isActive) { const schedule = this.managedSchedules.get(scheduleId); if (!schedule) { throw new Error(`Schedule ${scheduleId} not found`); } schedule.isActive = isActive; schedule.lastModified = new Date(); this.logger.log(`Schedule ${scheduleId} ${isActive ? 'activated' : 'deactivated'}`); } async recordScheduleTrigger(scheduleId) { const schedule = this.managedSchedules.get(scheduleId); if (!schedule) { throw new Error(`Schedule ${scheduleId} not found`); } schedule.lastTriggered = new Date(); schedule.triggerCount = (schedule.triggerCount || 0) + 1; schedule.nextRun = this.calculateNextRun(schedule); this.logger.debug(`Recorded trigger for schedule ${scheduleId}`); } getScheduleStats() { const schedules = this.getManagedSchedules(); return { total: schedules.length, active: schedules.filter((s) => s.isActive).length, inactive: schedules.filter((s) => !s.isActive).length, cron: schedules.filter((s) => s.cronExpression).length, interval: schedules.filter((s) => s.intervalExpression).length, errors: schedules.filter((s) => s.lastError).length, }; } validateSchedules() { const issues = []; const warnings = []; const schedules = this.getManagedSchedules(); const scheduleIds = schedules.map((s) => s.scheduleId); const duplicates = scheduleIds.filter((id, index) => scheduleIds.indexOf(id) !== index); if (duplicates.length > 0) { issues.push(`Duplicate schedule IDs found: ${duplicates.join(', ')}`); } schedules.forEach((schedule) => { if (schedule.cronExpression && !this.isValidCronExpression(schedule.cronExpression)) { issues.push(`Invalid cron expression for schedule ${schedule.scheduleId}: ${schedule.cronExpression}`); } }); const unscheduled = schedules.filter((s) => s.isActive && !s.nextRun); if (unscheduled.length > 0) { warnings.push(`Some active schedules have no next run time: ${unscheduled.map((s) => s.scheduleId).join(', ')}`); } return { isValid: issues.length === 0, issues, warnings, }; } getHealthStatus() { const stats = this.getScheduleStats(); const validation = this.validateSchedules(); let status; if (!validation.isValid) { status = 'unhealthy'; } else if (validation.warnings.length > 0 || stats.errors > 0) { status = 'degraded'; } else { status = 'healthy'; } return { status, schedules: { total: stats.total, active: stats.active, errors: stats.errors, }, validation, }; } isValidCronExpression(cron) { const parts = cron.trim().split(/\s+/); return parts.length >= 5 && parts.length <= 6; } logScheduleSummary() { const stats = this.getScheduleStats(); const validation = this.validateSchedules(); this.logger.log(`Schedule discovery completed: ${stats.total} total, ${stats.active} active, ${stats.cron} cron, ${stats.interval} interval`); if (stats.total > 0) { const scheduleIds = this.getScheduleIds(); this.logger.debug(`Managed schedules: ${scheduleIds.join(', ')}`); } if (validation.warnings.length > 0) { validation.warnings.forEach((warning) => this.logger.warn(warning)); } if (validation.issues.length > 0) { validation.issues.forEach((issue) => this.logger.error(issue)); } } }; exports.TemporalSchedulesService = TemporalSchedulesService; exports.TemporalSchedulesService = TemporalSchedulesService = TemporalSchedulesService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_1.Inject)(constants_1.SCHEDULES_MODULE_OPTIONS)), __metadata("design:paramtypes", [Object, temporal_discovery_service_1.TemporalDiscoveryService]) ], TemporalSchedulesService); //# sourceMappingURL=temporal-schedules.service.js.map