@mulutime/plugin-sdk
Version:
SDK for developing MuluTime booking platform plugins
353 lines • 12.7 kB
JavaScript
"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