nestjs-temporal-core
Version:
Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features
607 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 TemporalScheduleService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemporalScheduleService = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const client_1 = require("@temporalio/client");
const constants_1 = require("../constants");
const temporal_metadata_service_1 = require("./temporal-metadata.service");
const logger_1 = require("../utils/logger");
let TemporalScheduleService = TemporalScheduleService_1 = class TemporalScheduleService {
constructor(options, client, discoveryService, metadataAccessor) {
this.options = options;
this.client = client;
this.discoveryService = discoveryService;
this.metadataAccessor = metadataAccessor;
this.scheduleHandles = new Map();
this.isInitialized = false;
this.logger = (0, logger_1.createLogger)(TemporalScheduleService_1.name, {
enableLogger: options.enableLogger,
logLevel: options.logLevel,
});
}
extractErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return 'Unknown error';
}
normalizeScheduleOptions(opts) {
const { timezones, ...specRest } = opts.spec;
const sdkSpec = {
...specRest,
...(timezones && timezones.length > 0 && !specRest.timezone
? { timezone: timezones[0] }
: {}),
};
const action = opts.action;
const { retryPolicy, ...actionRest } = action;
const sdkAction = {
...actionRest,
...(retryPolicy && !actionRest.retry ? { retry: retryPolicy } : {}),
};
return {
...opts,
spec: sdkSpec,
action: sdkAction,
};
}
async onModuleInit() {
try {
this.logger.verbose('Initializing Temporal Schedule Service...');
await this.initializeScheduleClient();
await this.discoverAndRegisterSchedules();
this.isInitialized = true;
this.logger.info(`Schedule Service initialized (${this.scheduleHandles.size} schedules)`);
}
catch (error) {
this.logger.error(`Failed to initialize Temporal Schedule Service: ${this.extractErrorMessage(error)}`, error);
throw error;
}
}
async onModuleDestroy() {
try {
const count = this.scheduleHandles.size;
this.scheduleHandles.clear();
this.isInitialized = false;
this.logger.info(`Schedule Service shut down (${count} schedule${count === 1 ? '' : 's'} cleared)`);
}
catch (error) {
this.logger.error('Error during schedule service shutdown', error);
}
}
async initializeScheduleClient() {
try {
if (this.client?.schedule) {
this.scheduleClient = this.client.schedule;
this.logger.verbose('Schedule client initialized from existing client');
return {
success: true,
client: this.scheduleClient,
source: 'existing',
};
}
else {
try {
this.scheduleClient = new client_1.ScheduleClient({
connection: this.client?.connection,
namespace: this.options.connection?.namespace || 'default',
});
this.logger.verbose('Schedule client initialized successfully');
return {
success: true,
client: this.scheduleClient,
source: 'new',
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.warn(`Schedule client not available: ${errorMessage}`);
this.scheduleClient = undefined;
return {
success: false,
error: error instanceof Error ? error : new Error(errorMessage),
source: 'none',
};
}
}
}
catch (error) {
this.logger.error('Failed to initialize schedule client', error);
this.scheduleClient = undefined;
return {
success: false,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
source: 'none',
};
}
}
async discoverAndRegisterSchedules() {
const startTime = Date.now();
const discoveredCount = 0;
try {
this.logger.verbose('Schedule discovery skipped - decorators not implemented');
const duration = Date.now() - startTime;
this.logger.verbose(`Discovered ${discoveredCount} scheduled workflows`);
return {
success: true,
discoveredCount,
errors: [],
duration,
};
}
catch (error) {
this.logger.error('Failed to discover schedules', error);
return {
success: false,
discoveredCount: 0,
errors: [{ schedule: 'discovery', error: this.extractErrorMessage(error) }],
duration: Date.now() - startTime,
};
}
}
async registerScheduledWorkflow(instance, metatype, scheduleMetadata) {
try {
const scheduleId = scheduleMetadata.scheduleId || `${metatype.name}-schedule`;
const workflowType = scheduleMetadata.workflowType || metatype.name;
const scheduleSpecResult = this.buildScheduleSpec(scheduleMetadata);
if (!scheduleSpecResult.success) {
return {
success: false,
scheduleId,
error: scheduleSpecResult.error,
};
}
const workflowOptions = this.buildWorkflowOptions(scheduleMetadata);
const action = {
type: 'startWorkflow',
workflowType,
taskQueue: scheduleMetadata.taskQueue || 'default',
args: scheduleMetadata.args || [],
...workflowOptions,
};
const sdkOptions = this.normalizeScheduleOptions({
scheduleId,
spec: scheduleSpecResult.spec,
action,
memo: scheduleMetadata.memo || undefined,
searchAttributes: scheduleMetadata.searchAttributes || undefined,
});
const scheduleHandle = await this.scheduleClient.create(sdkOptions);
this.scheduleHandles.set(scheduleId, scheduleHandle);
this.logger.debug(`Registered scheduled workflow: ${scheduleId} with type: ${workflowType}`);
return {
success: true,
scheduleId,
handle: scheduleHandle,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to register scheduled workflow: ${errorMessage}`, error);
return {
success: false,
scheduleId: scheduleMetadata.scheduleId || `${metatype.name}-schedule`,
error: error instanceof Error ? error : new Error(errorMessage),
};
}
}
buildScheduleSpec(scheduleMetadata) {
try {
const spec = {};
if (scheduleMetadata.cron) {
spec.cronExpressions = (Array.isArray(scheduleMetadata.cron)
? scheduleMetadata.cron
: [scheduleMetadata.cron]);
}
if (scheduleMetadata.interval) {
const intervals = (Array.isArray(scheduleMetadata.interval)
? scheduleMetadata.interval
: [scheduleMetadata.interval]);
const parsedIntervals = intervals
.map((interval) => {
const result = this.parseInterval(interval);
return result.success ? result.interval : null;
})
.filter((interval) => interval !== null);
if (parsedIntervals.length > 0) {
spec.intervals = parsedIntervals;
}
}
if (scheduleMetadata.calendar) {
spec.calendars = (Array.isArray(scheduleMetadata.calendar)
? scheduleMetadata.calendar
: [scheduleMetadata.calendar]);
}
if (scheduleMetadata.timezone) {
spec.timezone = scheduleMetadata.timezone;
}
if (scheduleMetadata.jitter) {
spec.jitter = scheduleMetadata.jitter;
}
return {
success: true,
spec,
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('Unknown error'),
};
}
}
buildWorkflowOptions(scheduleMetadata) {
const options = {};
if (scheduleMetadata.taskQueue) {
options.taskQueue = scheduleMetadata.taskQueue;
}
if (scheduleMetadata.workflowId) {
options.workflowId = scheduleMetadata.workflowId;
}
if (scheduleMetadata.workflowExecutionTimeout) {
options.workflowExecutionTimeout =
scheduleMetadata.workflowExecutionTimeout;
}
if (scheduleMetadata.workflowRunTimeout) {
options.workflowRunTimeout = scheduleMetadata.workflowRunTimeout;
}
if (scheduleMetadata.workflowTaskTimeout) {
options.workflowTaskTimeout = scheduleMetadata.workflowTaskTimeout;
}
if (scheduleMetadata.retryPolicy) {
options.retryPolicy =
scheduleMetadata.retryPolicy;
}
if (scheduleMetadata.args) {
options.args = scheduleMetadata.args;
}
return options;
}
parseInterval(interval) {
try {
if (typeof interval === 'number') {
return {
success: true,
interval: { every: `${interval}ms` },
};
}
const intervalStr = interval.toString().toLowerCase();
if (intervalStr.includes('ms')) {
return {
success: true,
interval: { every: intervalStr },
};
}
if (intervalStr.includes('s')) {
return {
success: true,
interval: { every: intervalStr },
};
}
if (intervalStr.includes('m')) {
return {
success: true,
interval: { every: intervalStr },
};
}
if (intervalStr.includes('h')) {
return {
success: true,
interval: { every: intervalStr },
};
}
return {
success: true,
interval: { every: `${interval}ms` },
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('Unknown error'),
};
}
}
buildScheduleOptions(options) {
const policies = {};
if (options.overlapPolicy) {
policies.overlap =
options.overlapPolicy.toUpperCase();
}
if (options.catchupWindow) {
policies.catchupWindow = options.catchupWindow;
}
if (options.pauseOnFailure !== undefined) {
policies.pauseOnFailure = options.pauseOnFailure;
}
const state = {};
if (options.paused !== undefined)
state.paused = options.paused;
if (options.description)
state.note = options.description;
return this.normalizeScheduleOptions({
scheduleId: options.scheduleId,
spec: options.spec,
action: options.action,
...(options.memo && { memo: options.memo }),
...(options.searchAttributes && {
searchAttributes: options.searchAttributes,
}),
...(Object.keys(policies).length > 0 && { policies }),
...(Object.keys(state).length > 0 && { state }),
});
}
async createSchedule(options) {
this.ensureInitialized();
try {
const scheduleOptions = this.buildScheduleOptions(options);
const scheduleHandle = await this.scheduleClient.create(scheduleOptions);
this.scheduleHandles.set(options.scheduleId, scheduleHandle);
this.logger.info(`Created schedule '${options.scheduleId}'`);
return {
success: true,
scheduleId: options.scheduleId,
handle: scheduleHandle,
};
}
catch (error) {
this.logger.error(`Failed to create schedule '${options.scheduleId}'`, error);
return {
success: false,
scheduleId: options.scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async upsertSchedule(options) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(options.scheduleId);
let exists = true;
try {
await handle.describe();
}
catch (error) {
if (error instanceof client_1.ScheduleNotFoundError) {
exists = false;
}
else {
throw error;
}
}
if (!exists) {
const result = await this.createSchedule(options);
return { ...result, action: 'created' };
}
const { scheduleId: _scheduleId, memo: _memo, ...updateOptions } = this.buildScheduleOptions(options);
await handle.update(() => updateOptions);
this.scheduleHandles.set(options.scheduleId, handle);
this.logger.info(`Upserted (updated) schedule '${options.scheduleId}'`);
return {
success: true,
scheduleId: options.scheduleId,
handle,
action: 'updated',
};
}
catch (error) {
this.logger.error(`Failed to upsert schedule '${options.scheduleId}'`, error);
return {
success: false,
scheduleId: options.scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async getSchedule(scheduleId) {
this.ensureInitialized();
try {
let scheduleHandle = this.scheduleHandles.get(scheduleId);
if (!scheduleHandle) {
scheduleHandle = this.scheduleClient.getHandle(scheduleId);
this.scheduleHandles.set(scheduleId, scheduleHandle);
}
return {
success: true,
handle: scheduleHandle,
};
}
catch (error) {
this.logger.error(`Failed to get schedule '${scheduleId}'`, error);
return {
success: false,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
resolveScheduleHandle(scheduleId) {
let scheduleHandle = this.scheduleHandles.get(scheduleId);
if (!scheduleHandle) {
scheduleHandle = this.scheduleClient.getHandle(scheduleId);
this.scheduleHandles.set(scheduleId, scheduleHandle);
}
return scheduleHandle;
}
async pauseSchedule(scheduleId, note) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(scheduleId);
await handle.pause(note);
this.logger.info(`Paused schedule '${scheduleId}'`);
return { success: true, scheduleId };
}
catch (error) {
this.logger.error(`Failed to pause schedule '${scheduleId}'`, error);
return {
success: false,
scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async unpauseSchedule(scheduleId, note) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(scheduleId);
await handle.unpause(note);
this.logger.info(`Unpaused schedule '${scheduleId}'`);
return { success: true, scheduleId };
}
catch (error) {
this.logger.error(`Failed to unpause schedule '${scheduleId}'`, error);
return {
success: false,
scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async triggerSchedule(scheduleId, overlapPolicy) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(scheduleId);
await handle.trigger(overlapPolicy);
this.logger.info(`Triggered schedule '${scheduleId}'`);
return { success: true, scheduleId };
}
catch (error) {
this.logger.error(`Failed to trigger schedule '${scheduleId}'`, error);
return {
success: false,
scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async deleteSchedule(scheduleId) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(scheduleId);
await handle.delete();
this.scheduleHandles.delete(scheduleId);
this.logger.info(`Deleted schedule '${scheduleId}'`);
return { success: true, scheduleId };
}
catch (error) {
this.logger.error(`Failed to delete schedule '${scheduleId}'`, error);
return {
success: false,
scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async updateSchedule(scheduleId, updateFn) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(scheduleId);
await handle.update(updateFn);
this.logger.info(`Updated schedule '${scheduleId}'`);
return { success: true, scheduleId };
}
catch (error) {
this.logger.error(`Failed to update schedule '${scheduleId}'`, error);
return {
success: false,
scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
async describeSchedule(scheduleId) {
this.ensureInitialized();
try {
const handle = this.resolveScheduleHandle(scheduleId);
const description = await handle.describe();
return { success: true, scheduleId, description };
}
catch (error) {
this.logger.error(`Failed to describe schedule '${scheduleId}'`, error);
return {
success: false,
scheduleId,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
listSchedules(options) {
if (!this.scheduleClient) {
return {
success: false,
error: new Error('Schedule client is not available'),
};
}
try {
return { success: true, schedules: this.scheduleClient.list(options) };
}
catch (error) {
this.logger.error('Failed to list schedules', error);
return {
success: false,
error: error instanceof Error ? error : new Error(this.extractErrorMessage(error)),
};
}
}
isHealthy() {
return this.isInitialized;
}
getScheduleStats() {
return {
total: this.scheduleHandles.size,
active: this.scheduleHandles.size,
inactive: 0,
errors: 0,
lastUpdated: new Date(),
};
}
getStatus() {
return {
available: this.isInitialized,
healthy: this.isHealthy(),
schedulesSupported: !!this.scheduleClient,
initialized: this.isInitialized,
};
}
getHealth() {
return {
status: this.isInitialized ? 'healthy' : 'unhealthy',
schedulesCount: this.scheduleHandles.size,
isInitialized: this.isInitialized,
details: {
scheduleIds: Array.from(this.scheduleHandles.keys()),
hasScheduleClient: !!this.scheduleClient,
},
};
}
ensureInitialized() {
if (!this.isInitialized) {
throw new Error('Temporal Schedule Service is not initialized');
}
}
};
exports.TemporalScheduleService = TemporalScheduleService;
exports.TemporalScheduleService = TemporalScheduleService = TemporalScheduleService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)(constants_1.TEMPORAL_MODULE_OPTIONS)),
__param(1, (0, common_1.Inject)(constants_1.TEMPORAL_CLIENT)),
__metadata("design:paramtypes", [Object, Object, core_1.DiscoveryService,
temporal_metadata_service_1.TemporalMetadataAccessor])
], TemporalScheduleService);
//# sourceMappingURL=temporal-schedule.service.js.map