@sailboat-computer/event-bus
Version:
Standardized event bus for sailboat computer v3 with resilience features and offline capabilities
235 lines • 7.86 kB
JavaScript
;
/**
* 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