@mulutime/plugin-sdk
Version:
SDK for developing MuluTime booking platform plugins
280 lines • 9.79 kB
JavaScript
"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(` decorator can only be applied to methods, not properties. Applied to: ${propertyKey}`);
}
const originalMethod = descriptor.value;
if (typeof originalMethod !== 'function') {
throw new Error(` 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