UNPKG

@mulutime/plugin-sdk

Version:

SDK for developing MuluTime booking platform plugins

280 lines 9.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventActionBuilder = exports.EventActionHandler = void 0; exports.OnEvent = OnEvent; exports.createEventAction = createEventAction; exports.extractEventActions = extractEventActions; class EventActionHandler { constructor(options = {}) { this.actions = new Map(); this.options = { maxRetries: 3, retryDelay: 1000, timeout: 30000, enableFiltering: true, ...options }; } /** * Register an event action handler */ register(action) { if (!this.actions.has(action.eventType)) { this.actions.set(action.eventType, []); } const handlers = this.actions.get(action.eventType); handlers.push(action); // Sort by priority (higher priority first) handlers.sort((a, b) => (b.priority || 0) - (a.priority || 0)); } /** * Register multiple event actions */ registerMany(actions) { actions.forEach(action => this.register(action)); } /** * Unregister an event action handler */ unregister(eventType, handler) { const handlers = this.actions.get(eventType); if (handlers) { const index = handlers.findIndex(action => action.handler === handler); if (index !== -1) { handlers.splice(index, 1); } } } /** * Handle an incoming system event */ async handleEvent(event, context) { const handlers = this.actions.get(event.eventType); if (!handlers || handlers.length === 0) { return; } // Filter and execute enabled handlers const enabledHandlers = handlers.filter(action => action.enabled && this.shouldExecuteHandler(action, event, context)); // Execute handlers in parallel (they should be independent) const promises = enabledHandlers.map(action => this.executeHandler(action, event, context)); await Promise.allSettled(promises); } /** * Check if a handler should be executed based on filters */ shouldExecuteHandler(action, event, context) { if (!this.options.enableFiltering || !action.filters) { return true; } const { filters } = action; // Organization filter if (filters.organizationId && event.organizationId !== filters.organizationId) { return false; } // Custom conditions filter if (filters.conditions) { for (const [key, expectedValue] of Object.entries(filters.conditions)) { const actualValue = this.getNestedValue(event.data, key); if (actualValue !== expectedValue) { return false; } } } return true; } /** * Execute a single event handler with retry logic */ async executeHandler(action, event, context) { let lastError = null; for (let attempt = 0; attempt <= (this.options.maxRetries || 0); attempt++) { try { // Add timeout wrapper const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Handler execution timeout')), this.options.timeout); }); const handlerPromise = action.handler(event, context); await Promise.race([handlerPromise, timeoutPromise]); // Success - log and return context.logger.debug(`Event handler executed successfully`, { eventType: event.eventType, eventId: event.eventId, handlerName: action.handler.name, attempt: attempt + 1 }); return; } catch (error) { lastError = error; context.logger.warn(`Event handler failed`, { eventType: event.eventType, eventId: event.eventId, handlerName: action.handler.name, attempt: attempt + 1, maxRetries: this.options.maxRetries, error: error instanceof Error ? error.message : String(error) }); // If this is not the last attempt, wait before retrying if (attempt < (this.options.maxRetries || 0)) { await this.delay(this.options.retryDelay || 1000); } } } // All attempts failed context.logger.error(`Event handler failed after all retries`, lastError || undefined, { eventType: event.eventType, eventId: event.eventId, handlerName: action.handler.name, attempts: (this.options.maxRetries || 0) + 1 }); throw lastError || new Error('Event handler failed'); } /** * Get a nested value from an object using dot notation */ getNestedValue(obj, path) { return path.split('.').reduce((current, key) => current?.[key], obj); } /** * Delay utility for retries */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Get all registered event types */ getRegisteredEventTypes() { return Array.from(this.actions.keys()); } /** * Get handlers for a specific event type */ getHandlers(eventType) { return this.actions.get(eventType) || []; } /** * Clear all registered handlers */ clear() { this.actions.clear(); } /** * Get statistics about registered handlers */ getStats() { let totalHandlers = 0; let enabledHandlers = 0; const handlersByEventType = {}; for (const [eventType, handlers] of this.actions.entries()) { totalHandlers += handlers.length; enabledHandlers += handlers.filter(h => h.enabled).length; handlersByEventType[eventType] = handlers.length; } return { totalEventTypes: this.actions.size, totalHandlers, enabledHandlers, handlersByEventType }; } } exports.EventActionHandler = EventActionHandler; // ============================================================================= // EVENT ACTION BUILDER // ============================================================================= class EventActionBuilder { constructor() { this.action = {}; } static create() { return new EventActionBuilder(); } eventType(type) { this.action.eventType = type; return this; } handler(handler) { this.action.handler = handler; return this; } priority(priority) { this.action.priority = priority; return this; } enabled(enabled = true) { this.action.enabled = enabled; return this; } filterByOrganization(organizationId) { if (!this.action.filters) { this.action.filters = {}; } this.action.filters.organizationId = organizationId; return this; } filterByConditions(conditions) { if (!this.action.filters) { this.action.filters = {}; } this.action.filters.conditions = conditions; return this; } build() { if (!this.action.eventType || !this.action.handler) { throw new Error('Event type and handler are required'); } return { eventType: this.action.eventType, handler: this.action.handler, priority: this.action.priority || 0, enabled: this.action.enabled ?? true, filters: this.action.filters }; } } exports.EventActionBuilder = EventActionBuilder; // ============================================================================= // EVENT ACTION DECORATORS // ============================================================================= function OnEvent(eventTypes, options) { return function (target, propertyKey, descriptor) { if (!descriptor) { throw new Error(`@OnEvent decorator can only be applied to methods, not properties. Applied to: ${propertyKey}`); } const originalMethod = descriptor.value; if (typeof originalMethod !== 'function') { throw new Error(`@OnEvent decorator can only be applied to methods. ${propertyKey} is not a function.`); } // Add metadata to the method for each event type if (!target.constructor.__eventActions) { target.constructor.__eventActions = []; } const types = Array.isArray(eventTypes) ? eventTypes : [eventTypes]; for (const eventType of types) { target.constructor.__eventActions.push({ eventType, handler: originalMethod, priority: options?.priority || 0, enabled: true, filters: options?.filters }); } return descriptor; }; } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function createEventAction(eventType, handler, options) { return { eventType, handler, priority: options?.priority || 0, enabled: options?.enabled ?? true, filters: options?.filters }; } function extractEventActions(pluginClass) { return pluginClass.__eventActions || []; } //# sourceMappingURL=event-handler.js.map