UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

341 lines 12.6 kB
import { EventEmitter } from 'events'; import { NodeCronAdapter } from './cron-adapter.js'; export class TriggerManager extends EventEmitter { engine; store; scheduledTriggers = new Map(); activeTriggers = new Map(); triggerHistory = new Map(); isRunning = false; checkInterval; cronAdapter; constructor(engine, store, cronAdapter) { super(); this.engine = engine; this.store = store; this.cronAdapter = cronAdapter || new NodeCronAdapter(); } async initialize() { await this.start(); } async start() { if (this.isRunning) return; this.isRunning = true; await this.loadTriggers(); // Check for triggers every minute this.checkInterval = setInterval(() => { this.checkTriggers(); }, 60000); // Initial check this.checkTriggers(); } async stop() { this.isRunning = false; if (this.checkInterval) { clearInterval(this.checkInterval); } // Clear all scheduled triggers for (const scheduled of this.scheduledTriggers.values()) { if (scheduled.interval) { // If it's a cron task, call destroy if (scheduled.interval.destroy) { scheduled.interval.destroy(); } else { clearInterval(scheduled.interval); } } } this.scheduledTriggers.clear(); } async loadTriggers() { const processes = await this.store.getAllProcesses(); for (const process of processes) { for (const trigger of process.triggers) { if (trigger.enabled) { await this.registerTrigger(process, trigger); } } } } async registerTriggerInternal(process, trigger) { const key = `${process.id}:${trigger.id}`; switch (trigger.type) { case 'schedule': this.registerScheduleTrigger(process, trigger); break; case 'event': // Register event listeners this.registerEventTrigger(process, trigger); break; case 'webhook': // Register webhook endpoint this.registerWebhookTrigger(process, trigger); break; case 'condition': // Add to condition check list this.registerConditionTrigger(process, trigger); break; case 'manual': // No registration needed for manual triggers break; } } registerScheduleTrigger(process, trigger) { if (!trigger.config.cron) return; const key = `${process.id}:${trigger.id}`; // Simple cron parser for demo (in production use node-cron) const schedule = this.parseCronExpression(trigger.config.cron); if (schedule.interval) { const scheduled = { processId: process.id, triggerId: trigger.id, interval: setInterval(() => { this.executeTrigger(process.id, trigger.id); }, schedule.interval), nextRun: schedule.nextRun }; this.scheduledTriggers.set(key, scheduled); } } registerEventTrigger(process, trigger) { // In a real implementation, this would register with an event bus console.log(`Registered event trigger: ${trigger.config.event} for process ${process.id}`); } registerWebhookTrigger(process, trigger) { // In a real implementation, this would register an HTTP endpoint console.log(`Registered webhook trigger: ${trigger.config.endpoint} for process ${process.id}`); } registerConditionTrigger(process, trigger) { // Add to list of conditions to check periodically console.log(`Registered condition trigger for process ${process.id}`); } async checkTriggers() { // Check condition-based triggers const processes = await this.store.getAllProcesses(); for (const process of processes) { for (const trigger of process.triggers) { if (trigger.type === 'condition' && trigger.enabled) { await this.checkConditionTrigger(process, trigger); } } } } async checkConditionTrigger(process, trigger) { // Evaluate condition // In a real implementation, this would evaluate the condition expression const conditionMet = false; // Placeholder if (conditionMet) { await this.executeTrigger(process.id, trigger.id); } } parseCronExpression(cron) { // Very simplified cron parser for demo // In production, use a proper cron library const parts = cron.split(' '); // Handle simple cases if (cron === '* * * * *') { // Every minute return { interval: 60000 }; } else if (cron === '0 * * * *') { // Every hour return { interval: 3600000 }; } else if (cron === '0 0 * * *') { // Daily return { interval: 86400000 }; } // For more complex expressions, would need proper parsing return {}; } // Public methods for manual trigger management async registerTrigger(process, trigger) { if (!trigger.enabled) return; const key = `${process.id}:${trigger.id}`; this.activeTriggers.set(key, trigger); if (trigger.type === 'schedule' && trigger.config.cron) { // Use node-cron for scheduling if (!this.cronAdapter.validate(trigger.config.cron)) { throw new Error(`Invalid cron expression: ${trigger.config.cron}`); } const task = this.cronAdapter.schedule(trigger.config.cron, async () => { await this.executeTrigger(process.id, trigger.id); }, { scheduled: false }); task.start(); this.scheduledTriggers.set(key, { processId: process.id, triggerId: trigger.id, interval: task }); } } async unregisterTrigger(processId, triggerId) { const key = `${processId}:${triggerId}`; this.activeTriggers.delete(key); const scheduled = this.scheduledTriggers.get(key); if (scheduled && scheduled.interval) { const task = scheduled.interval; if (task.stop) task.stop(); if (task.destroy) task.destroy(); this.scheduledTriggers.delete(key); } } getActiveTriggers(processId) { const triggers = []; for (const [key, trigger] of this.activeTriggers) { const [pid] = key.split(':'); if (!processId || pid === processId) { triggers.push({ ...trigger, processId: pid }); } } return triggers; } async executeTrigger(processId, triggerId, context) { const process = await this.store.getProcess(processId); if (!process) { throw new Error('Process not found'); } const trigger = process.triggers.find(t => t.id === triggerId); if (!trigger) { throw new Error('Trigger not found'); } if (!trigger.enabled) { throw new Error('Trigger is disabled'); } const execution = await this.engine.executeProcess(process, triggerId, context); // Record in history const key = `${processId}:${triggerId}`; if (!this.triggerHistory.has(key)) { this.triggerHistory.set(key, []); } const history = this.triggerHistory.get(key); history.unshift({ triggerId, processId, executionId: execution.id, executedAt: new Date().toISOString(), status: execution.status === 'completed' ? 'success' : 'failed' }); // Keep only last 100 entries if (history.length > 100) { history.splice(100); } return execution; } getTriggerHistory(processId, triggerId) { const key = `${processId}:${triggerId}`; return this.triggerHistory.get(key) || []; } async updateTrigger(processId, triggerId, updates) { const process = await this.store.getProcess(processId); if (!process) { throw new Error('Process not found'); } const trigger = process.triggers.find(t => t.id === triggerId); if (!trigger) { throw new Error('Trigger not found'); } // If disabling, unregister first if (updates.enabled === false && trigger.enabled) { await this.unregisterTrigger(processId, triggerId); } // Update the trigger Object.assign(trigger, updates); // Save the updated process await this.store.saveProcess(process); // If enabling or if already enabled and config changed, re-register if (trigger.enabled) { // Unregister first to clean up old configuration await this.unregisterTrigger(processId, triggerId); // Re-register with new configuration await this.registerTrigger(process, trigger); } } async handleEvent(eventType, eventData) { for (const [key, trigger] of this.activeTriggers) { if (trigger.type === 'event' && trigger.config.event === eventType) { const [processId] = key.split(':'); const process = await this.store.getProcess(processId); if (process) { await this.executeTrigger(processId, trigger.id, { event: eventType, eventData }); } } } } stopAll() { this.stop(); for (const scheduled of this.scheduledTriggers.values()) { if (scheduled.interval) { const task = scheduled.interval; if (task.stop) task.stop(); if (task.destroy) task.destroy(); } } this.scheduledTriggers.clear(); this.activeTriggers.clear(); } async triggerProcess(processId, variables) { const process = await this.store.getProcess(processId); if (!process) { throw new Error('Process not found'); } // Find manual trigger or use first trigger const trigger = process.triggers.find(t => t.type === 'manual') || process.triggers[0]; if (!trigger) { // Create a temporary manual trigger const manualTrigger = { id: 'manual-temp', type: 'manual', name: 'Manual Execution', enabled: true, config: {} }; await this.engine.executeProcess(process, manualTrigger.id, variables); } else { await this.engine.executeProcess(process, trigger.id, variables); } } async enableTrigger(processId, triggerId) { const process = await this.store.getProcess(processId); if (!process) return; const trigger = process.triggers.find(t => t.id === triggerId); if (trigger && !trigger.enabled) { trigger.enabled = true; await this.store.saveProcess(process); await this.registerTrigger(process, trigger); } } async disableTrigger(processId, triggerId) { const process = await this.store.getProcess(processId); if (!process) return; const trigger = process.triggers.find(t => t.id === triggerId); if (trigger && trigger.enabled) { trigger.enabled = false; await this.store.saveProcess(process); // Remove from scheduled triggers const key = `${processId}:${triggerId}`; const scheduled = this.scheduledTriggers.get(key); if (scheduled && scheduled.interval) { clearInterval(scheduled.interval); this.scheduledTriggers.delete(key); } } } } //# sourceMappingURL=trigger-manager.js.map