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
JavaScript
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
;