UNPKG

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
"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