UNPKG

@mulutime/plugin-sdk

Version:

SDK for developing MuluTime booking platform plugins

395 lines 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LifecycleActionBuilder = exports.LifecycleActionHandler = void 0; exports.OnInstall = OnInstall; exports.OnUninstall = OnUninstall; exports.OnUpdate = OnUpdate; exports.OnEnable = OnEnable; exports.OnDisable = OnDisable; exports.ValidateConfig = ValidateConfig; exports.createLifecycleActions = createLifecycleActions; exports.extractLifecycleActions = extractLifecycleActions; class LifecycleActionHandler { constructor(options = {}) { this.actions = new Map(); this.executions = []; this.options = { enableLogging: true, timeout: 60000, // 1 minute timeout for lifecycle actions maxRetries: 2, retryDelay: 2000, ...options }; } /** * Register lifecycle actions for a plugin */ register(pluginId, actions) { this.actions.set(pluginId, actions); } /** * Unregister lifecycle actions for a plugin */ unregister(pluginId) { this.actions.delete(pluginId); } /** * Execute the install lifecycle action */ async executeInstall(pluginId, context) { const actions = this.actions.get(pluginId); if (!actions?.onInstall) { return; } await this.executeLifecycleAction('onInstall', pluginId, context, () => actions.onInstall(context)); } /** * Execute the uninstall lifecycle action */ async executeUninstall(pluginId, context) { const actions = this.actions.get(pluginId); if (!actions?.onUninstall) { return; } await this.executeLifecycleAction('onUninstall', pluginId, context, () => actions.onUninstall(context)); } /** * Execute the update lifecycle action */ async executeUpdate(pluginId, context, oldVersion) { const actions = this.actions.get(pluginId); if (!actions?.onUpdate) { return; } await this.executeLifecycleAction('onUpdate', pluginId, context, () => actions.onUpdate(context, oldVersion), { oldVersion }); } /** * Execute the enable lifecycle action */ async executeEnable(pluginId, context) { const actions = this.actions.get(pluginId); if (!actions?.onEnable) { return; } await this.executeLifecycleAction('onEnable', pluginId, context, () => actions.onEnable(context)); } /** * Execute the disable lifecycle action */ async executeDisable(pluginId, context) { const actions = this.actions.get(pluginId); if (!actions?.onDisable) { return; } await this.executeLifecycleAction('onDisable', pluginId, context, () => actions.onDisable(context)); } /** * Execute configuration validation */ async validateConfig(pluginId, config) { const actions = this.actions.get(pluginId); if (!actions?.validateConfig) { return { valid: true }; } try { const result = await this.executeWithTimeout(() => actions.validateConfig(config), this.options.timeout); return result; } catch (error) { return { valid: false, errors: [error instanceof Error ? error.message : 'Configuration validation failed'] }; } } /** * Execute a lifecycle action with error handling and retries */ async executeLifecycleAction(actionType, pluginId, context, executor, metadata) { const startTime = Date.now(); const execution = { id: `lifecycle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, action: actionType, timestamp: new Date(), duration: 0, success: false, pluginId, oldVersion: metadata?.oldVersion }; let lastError = null; for (let attempt = 0; attempt <= (this.options.maxRetries || 0); attempt++) { try { if (this.options.enableLogging) { context.logger.info(`Executing ${actionType} lifecycle action`, { pluginId, attempt: attempt + 1, executionId: execution.id }); } await this.executeWithTimeout(executor, this.options.timeout); // Success execution.success = true; execution.duration = Date.now() - startTime; if (this.options.enableLogging) { context.logger.info(`${actionType} lifecycle action completed successfully`, { pluginId, duration: execution.duration, executionId: execution.id }); } break; } catch (error) { lastError = error; execution.error = error instanceof Error ? error.message : String(error); if (this.options.enableLogging) { context.logger.warn(`${actionType} lifecycle action failed`, { pluginId, attempt: attempt + 1, maxRetries: this.options.maxRetries, error: execution.error, executionId: execution.id }); } // If this is not the last attempt, wait before retrying if (attempt < (this.options.maxRetries || 0)) { await this.delay(this.options.retryDelay || 2000); } } } if (!execution.success) { execution.duration = Date.now() - startTime; if (this.options.enableLogging) { context.logger.error(`${actionType} lifecycle action failed after all retries`, lastError || undefined, { pluginId, attempts: (this.options.maxRetries || 0) + 1, duration: execution.duration, executionId: execution.id }); } // Store failed execution this.logExecution(execution); throw lastError || new Error(`${actionType} lifecycle action failed`); } // Store successful execution this.logExecution(execution); } /** * Execute a function with timeout */ async executeWithTimeout(executor, timeout) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Lifecycle action timeout')), timeout); }); return Promise.race([executor(), timeoutPromise]); } /** * Delay utility for retries */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Log lifecycle execution */ logExecution(execution) { this.executions.push(execution); // Keep only last 500 executions if (this.executions.length > 500) { this.executions = this.executions.slice(-500); } } /** * Get execution history for a plugin */ getExecutionHistory(pluginId, limit = 50) { let executions = this.executions; if (pluginId) { executions = executions.filter(e => e.pluginId === pluginId); } return executions .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, limit); } /** * Get lifecycle statistics */ getStats() { const totalExecutions = this.executions.length; const successfulExecutions = this.executions.filter(e => e.success).length; const failedExecutions = totalExecutions - successfulExecutions; const averageExecutionTime = totalExecutions > 0 ? this.executions.reduce((sum, e) => sum + e.duration, 0) / totalExecutions : 0; const executionsByAction = { onInstall: 0, onUninstall: 0, onUpdate: 0, onEnable: 0, onDisable: 0, validateConfig: 0 }; const executionsByPlugin = {}; this.executions.forEach(execution => { executionsByAction[execution.action]++; if (!executionsByPlugin[execution.pluginId]) { executionsByPlugin[execution.pluginId] = 0; } executionsByPlugin[execution.pluginId]++; }); return { totalExecutions, successfulExecutions, failedExecutions, averageExecutionTime, executionsByAction, executionsByPlugin }; } /** * Check if a plugin has lifecycle actions registered */ hasActions(pluginId) { return this.actions.has(pluginId); } /** * Get the lifecycle actions for a plugin */ getActions(pluginId) { return this.actions.get(pluginId); } /** * Get all registered plugins */ getRegisteredPlugins() { return Array.from(this.actions.keys()); } /** * Clear all registered actions and execution history */ clear() { this.actions.clear(); this.executions = []; } /** * Validate plugin state before lifecycle operations */ async validatePluginState(pluginId, actionType, context) { const errors = []; // Check if plugin is registered if (!this.actions.has(pluginId)) { errors.push(`Plugin ${pluginId} is not registered`); } // Check context validity if (!context.pluginId) { errors.push('Plugin context is missing plugin ID'); } if (context.pluginId !== pluginId) { errors.push('Plugin ID mismatch between context and request'); } // Action-specific validations switch (actionType) { case 'onInstall': // Check if plugin is already installed break; case 'onUninstall': // Check if plugin is currently installed break; case 'onUpdate': // Check if plugin is installed and version is different break; case 'onEnable': // Check if plugin is installed but disabled break; case 'onDisable': // Check if plugin is currently enabled break; } return { valid: errors.length === 0, errors: errors.length > 0 ? errors : undefined }; } } exports.LifecycleActionHandler = LifecycleActionHandler; // ============================================================================= // LIFECYCLE ACTION BUILDER // ============================================================================= class LifecycleActionBuilder { constructor() { this.actions = {}; } static create() { return new LifecycleActionBuilder(); } onInstall(handler) { this.actions.onInstall = handler; return this; } onUninstall(handler) { this.actions.onUninstall = handler; return this; } onUpdate(handler) { this.actions.onUpdate = handler; return this; } onEnable(handler) { this.actions.onEnable = handler; return this; } onDisable(handler) { this.actions.onDisable = handler; return this; } validateConfig(handler) { this.actions.validateConfig = handler; return this; } build() { return this.actions; } } exports.LifecycleActionBuilder = LifecycleActionBuilder; // ============================================================================= // LIFECYCLE ACTION DECORATORS // ============================================================================= function OnInstall() { return createLifecycleDecorator('onInstall'); } function OnUninstall() { return createLifecycleDecorator('onUninstall'); } function OnUpdate() { return createLifecycleDecorator('onUpdate'); } function OnEnable() { return createLifecycleDecorator('onEnable'); } function OnDisable() { return createLifecycleDecorator('onDisable'); } function ValidateConfig() { return createLifecycleDecorator('validateConfig'); } function createLifecycleDecorator(actionType) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; // Add metadata to the class if (!target.constructor.__lifecycleActions) { target.constructor.__lifecycleActions = {}; } target.constructor.__lifecycleActions[actionType] = originalMethod; return descriptor; }; } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function createLifecycleActions(handlers) { return handlers; } function extractLifecycleActions(pluginClass) { return pluginClass.__lifecycleActions || {}; } //# sourceMappingURL=lifecycle-handler.js.map