@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
341 lines • 12.6 kB
JavaScript
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