UNPKG

@sailboat-computer/event-bus

Version:

Standardized event bus for sailboat computer v3 with resilience features and offline capabilities

235 lines 7.86 kB
"use strict"; /** * In-memory adapter for the event bus */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createMemoryAdapter = exports.MemoryAdapter = void 0; const uuid_1 = require("uuid"); const base_adapter_1 = require("./base-adapter"); const utils_1 = require("../utils"); /** * In-memory adapter for the event bus */ class MemoryAdapter extends base_adapter_1.BaseAdapter { constructor() { super(...arguments); /** * Events by type */ this.events = new Map(); /** * Event TTL in milliseconds */ this.eventTtl = 3600000; // Default: 1 hour /** * Whether to simulate latency */ this.simulateLatency = false; /** * Latency range in milliseconds [min, max] */ this.latencyRange = [10, 50]; /** * Cleanup interval */ this.cleanupInterval = null; } /** * Initialize the adapter * * @param config - Adapter configuration */ async initialize(config) { await super.initialize(config); this.eventTtl = config.eventTtl ?? 3600000; // Default: 1 hour this.simulateLatency = config.simulateLatency ?? false; this.latencyRange = config.latencyRange ?? [10, 50]; // Start cleanup interval if (this.eventTtl > 0) { this.cleanupInterval = setInterval(() => this.cleanup(), Math.min(this.eventTtl / 2, 60000)); } this.connected = true; utils_1.logger.info(`Memory adapter initialized for service ${this.config.serviceName}`); } /** * Shutdown the adapter */ async shutdown() { await super.shutdown(); // Clear cleanup interval if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } // Clear events this.events.clear(); this.connected = false; utils_1.logger.info(`Memory adapter shut down for service ${this.config.serviceName}`); } /** * Publish an event * * @param event - Event envelope * @returns Event ID */ async publish(event) { this.ensureInitialized(); // Generate event ID if not provided const eventId = event.id || (0, uuid_1.v4)(); // Create event with ID const eventWithId = { ...event, id: eventId }; // Create events map for this event type if it doesn't exist if (!this.events.has(event.type)) { this.events.set(event.type, new Map()); } // Store event this.events.get(event.type).set(eventId, { event: eventWithId, timestamp: Date.now(), acknowledged: false }); // Process event asynchronously this.processEvent(eventWithId); utils_1.logger.debug(`Published event ${eventId} of type ${event.type}`); // Simulate latency if enabled if (this.simulateLatency) { await this.simulateNetworkLatency(); } return eventId; } /** * Acknowledge an event * * @param eventId - Event ID * @param eventType - Event type */ async acknowledgeEvent(eventId, eventType) { this.ensureInitialized(); // Get events map for this event type const eventTypeMap = this.events.get(eventType); if (!eventTypeMap) { return; } // Get event const eventStorage = eventTypeMap.get(eventId); if (!eventStorage) { return; } // Mark as acknowledged eventStorage.acknowledged = true; utils_1.logger.debug(`Acknowledged event ${eventId} of type ${eventType}`); // Simulate latency if enabled if (this.simulateLatency) { await this.simulateNetworkLatency(); } } /** * Process an event * * @param event - Event envelope */ async processEvent(event) { // Get subscriptions for this event type const eventSubscriptions = this.subscriptions.get(event.type); if (!eventSubscriptions || eventSubscriptions.size === 0) { return; } // Call all handlers const promises = []; for (const handler of eventSubscriptions.values()) { promises.push(this.processEventWithHandler(event, handler)); } // Wait for all promises to resolve if (promises.length > 0) { await Promise.all(promises); } } /** * Process an event with a handler * * @param event - Event envelope * @param handler - Event handler */ async processEventWithHandler(event, handler) { try { // Record start time for metrics const startTime = Date.now(); try { // Call handler const result = handler(event); // Wait for result if it's a promise if (result instanceof Promise) { await result; } // Record processing time for metrics if (this.config.simulateLatency) { // Add simulated latency for testing const [min, max] = this.config.latencyRange || [10, 50]; const latency = Math.random() * (max - min) + min; await new Promise(resolve => setTimeout(resolve, latency)); } // Acknowledge event await this.acknowledgeEvent(event.id, event.type); } catch (handlerError) { // Log the error but don't re-throw it // The error will be handled by the event bus wrapper utils_1.logger.error(`Error in handler for event ${event.id} of type ${event.type}: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`, handlerError); } } catch (error) { utils_1.logger.error(`Error processing event ${event.id} of type ${event.type}: ${error instanceof Error ? error.message : String(error)}`, error); } } /** * Clean up expired events */ cleanup() { if (!this.initialized || this.eventTtl <= 0) { return; } const now = Date.now(); const expiredBefore = now - this.eventTtl; let removedCount = 0; // Check all event types for (const [eventType, eventTypeMap] of this.events.entries()) { // Check all events of this type for (const [eventId, eventStorage] of eventTypeMap.entries()) { // Remove if expired and acknowledged if (eventStorage.timestamp < expiredBefore && eventStorage.acknowledged) { eventTypeMap.delete(eventId); removedCount++; } } // Remove event type if empty if (eventTypeMap.size === 0) { this.events.delete(eventType); } } if (removedCount > 0) { utils_1.logger.debug(`Cleaned up ${removedCount} expired events`); } } /** * Simulate network latency */ async simulateNetworkLatency() { const [min, max] = this.latencyRange; const latency = Math.random() * (max - min) + min; await new Promise(resolve => setTimeout(resolve, latency)); } } exports.MemoryAdapter = MemoryAdapter; /** * Create a new memory adapter * * @returns Memory adapter */ function createMemoryAdapter() { return new MemoryAdapter(); } exports.createMemoryAdapter = createMemoryAdapter; //# sourceMappingURL=memory-adapter.js.map