UNPKG

@mulutime/plugin-sdk

Version:

SDK for developing MuluTime booking platform plugins

353 lines 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ActionExecutor = void 0; exports.createActionExecutor = createActionExecutor; class ActionExecutor { constructor(registry, options = {}) { this.executionLogs = []; this.activeExecutions = new Map(); this.registry = registry; this.options = { enableMetrics: true, enableTracing: true, maxConcurrentExecutions: 100, executionTimeout: 30000, ...options }; } /** * Execute event actions for a system event */ async executeEventActions(event, context) { const executionId = this.generateExecutionId('event'); if (this.options.enableTracing) { context.logger.debug('Executing event actions', { eventType: event.eventType, eventId: event.eventId, executionId }); } try { await this.executeWithMetrics('event', context.pluginId, event.eventType, async () => { await this.registry.getEventHandler().handleEvent(event, context); }, { eventType: event.eventType, eventId: event.eventId }); } catch (error) { context.logger.error('Event action execution failed', error, { eventType: event.eventType, eventId: event.eventId, executionId }); throw error; } } /** * Execute a scheduled action */ async executeScheduledAction(pluginId, actionName, context) { const executionId = this.generateExecutionId('scheduled'); if (this.options.enableTracing) { context.logger.debug('Executing scheduled action', { pluginId, actionName, executionId }); } try { await this.executeWithMetrics('scheduled', pluginId, actionName, async () => { await this.registry.getScheduledHandler().executeNow(actionName, context); }, { pluginId, actionName }); } catch (error) { context.logger.error('Scheduled action execution failed', error, { pluginId, actionName, executionId }); throw error; } } /** * Execute an API action */ async executeAPIAction(method, path, request, context) { const executionId = this.generateExecutionId('api'); if (this.options.enableTracing) { context.logger.debug('Executing API action', { method, path, userId: request.userId, executionId }); } try { return await this.executeWithMetrics('api', context.pluginId, `${method} ${path}`, async () => { return await this.registry.getAPIHandler().handleRequest(method, path, request, context); }, { method, path, userId: request.userId }); } catch (error) { context.logger.error('API action execution failed', error, { method, path, userId: request.userId, executionId }); // Return error response instead of throwing return { status: 500, error: 'Internal server error' }; } } /** * Execute a lifecycle action */ async executeLifecycleAction(pluginId, actionType, context, metadata) { const executionId = this.generateExecutionId('lifecycle'); if (this.options.enableTracing) { context.logger.debug('Executing lifecycle action', { pluginId, actionType, executionId }); } try { await this.executeWithMetrics('lifecycle', pluginId, actionType, async () => { const handler = this.registry.getLifecycleHandler(); switch (actionType) { case 'install': await handler.executeInstall(pluginId, context); break; case 'uninstall': await handler.executeUninstall(pluginId, context); break; case 'update': await handler.executeUpdate(pluginId, context, metadata?.oldVersion || ''); break; case 'enable': await handler.executeEnable(pluginId, context); break; case 'disable': await handler.executeDisable(pluginId, context); break; default: throw new Error(`Unknown lifecycle action: ${actionType}`); } }, { pluginId, actionType, ...metadata }); } catch (error) { context.logger.error('Lifecycle action execution failed', error, { pluginId, actionType, executionId }); throw error; } } /** * Validate plugin configuration */ async validatePluginConfig(pluginId, config) { try { return await this.executeWithMetrics('lifecycle', pluginId, 'validateConfig', async () => { return await this.registry.getLifecycleHandler().validateConfig(pluginId, config); }, { pluginId, configKeys: Object.keys(config) }); } catch (error) { return { valid: false, errors: [error instanceof Error ? error.message : 'Configuration validation failed'] }; } } /** * Execute an action with metrics collection and error handling */ async executeWithMetrics(type, pluginId, actionIdentifier, executor, metadata) { const startTime = Date.now(); const executionId = this.generateExecutionId(type); // Check concurrent execution limit if (this.activeExecutions.size >= (this.options.maxConcurrentExecutions || 100)) { throw new Error('Maximum concurrent executions reached'); } const executionLog = { id: executionId, type, pluginId, actionIdentifier, timestamp: new Date(), duration: 0, success: false, metadata }; // Create execution promise with timeout const executionPromise = this.executeWithTimeout(executor, this.options.executionTimeout); this.activeExecutions.set(executionId, executionPromise); try { const result = await executionPromise; executionLog.success = true; executionLog.duration = Date.now() - startTime; return result; } catch (error) { executionLog.success = false; executionLog.duration = Date.now() - startTime; executionLog.error = error instanceof Error ? error.message : String(error); throw error; } finally { this.activeExecutions.delete(executionId); if (this.options.enableMetrics) { this.logExecution(executionLog); } } } /** * Execute a function with timeout */ async executeWithTimeout(executor, timeout) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Action execution timeout')), timeout); }); return Promise.race([executor(), timeoutPromise]); } /** * Generate a unique execution ID */ generateExecutionId(type) { return `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Log execution metrics */ logExecution(log) { this.executionLogs.push(log); // Keep only last 1000 executions if (this.executionLogs.length > 1000) { this.executionLogs = this.executionLogs.slice(-1000); } } /** * Get execution metrics */ getMetrics() { const totalExecutions = this.executionLogs.length; const successfulExecutions = this.executionLogs.filter(log => log.success).length; const failedExecutions = totalExecutions - successfulExecutions; const averageExecutionTime = totalExecutions > 0 ? this.executionLogs.reduce((sum, log) => sum + log.duration, 0) / totalExecutions : 0; const executionsByType = { event: this.executionLogs.filter(log => log.type === 'event').length, scheduled: this.executionLogs.filter(log => log.type === 'scheduled').length, api: this.executionLogs.filter(log => log.type === 'api').length, lifecycle: this.executionLogs.filter(log => log.type === 'lifecycle').length }; const recentExecutions = this.executionLogs .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, 50); return { totalExecutions, successfulExecutions, failedExecutions, averageExecutionTime, executionsByType, recentExecutions }; } /** * Get execution logs for a specific plugin */ getPluginExecutionLogs(pluginId, limit = 100) { return this.executionLogs .filter(log => log.pluginId === pluginId) .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, limit); } /** * Get currently active executions */ getActiveExecutions() { const now = Date.now(); return Array.from(this.activeExecutions.keys()).map(id => { const timestamp = parseInt(id.split('-')[1]); return { id, startTime: new Date(timestamp), duration: now - timestamp }; }); } /** * Cancel an active execution (if possible) */ async cancelExecution(executionId) { const execution = this.activeExecutions.get(executionId); if (!execution) { return false; } // In a real implementation, you'd need a proper cancellation mechanism // For now, we just remove it from tracking this.activeExecutions.delete(executionId); return true; } /** * Get action executor health status */ getHealthStatus() { const metrics = this.getMetrics(); const activeExecutions = this.activeExecutions.size; const maxConcurrentExecutions = this.options.maxConcurrentExecutions || 100; // Calculate error rate for recent executions (last 100) const recentExecutions = this.executionLogs.slice(-100); const recentErrorRate = recentExecutions.length > 0 ? (recentExecutions.filter(log => !log.success).length / recentExecutions.length) * 100 : 0; let status = 'healthy'; if (recentErrorRate > 50 || activeExecutions > maxConcurrentExecutions * 0.9) { status = 'unhealthy'; } else if (recentErrorRate > 20 || activeExecutions > maxConcurrentExecutions * 0.7) { status = 'degraded'; } return { status, activeExecutions, maxConcurrentExecutions, recentErrorRate, averageExecutionTime: metrics.averageExecutionTime }; } /** * Clear execution logs and reset metrics */ clearMetrics() { this.executionLogs = []; } /** * Start scheduled actions for all registered plugins */ startScheduledActions(context) { this.registry.startAllScheduledActions(context); } /** * Stop all scheduled actions */ stopScheduledActions() { this.registry.stopAllScheduledActions(); } /** * Get the underlying action registry */ getActionRegistry() { return this.registry; } } exports.ActionExecutor = ActionExecutor; // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function createActionExecutor(registry, options) { return new ActionExecutor(registry, options); } //# sourceMappingURL=action-executor.js.map