UNPKG

@message-in-the-middle/core

Version:

Framework-agnostic middleware pattern for message queue processing. Core package with all middlewares.

250 lines 9.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageMiddlewareManager = void 0; const events_1 = require("events"); const pipeline_1 = require("./pipeline"); const DEFAULT_MAX_MESSAGE_SIZE = 256 * 1024; class MessageMiddlewareManager extends events_1.EventEmitter { inboundPipeline; outboundPipeline; resources = []; maxMessageSize; eventsEnabled; emitMiddlewareEvents; slowProcessingThreshold; startedAt = new Date(); inboundProcessedCount = 0; outboundProcessedCount = 0; inboundErrorCount = 0; outboundErrorCount = 0; totalInboundProcessingTime = 0; totalOutboundProcessingTime = 0; constructor(options = {}) { super(); this.inboundPipeline = new pipeline_1.MiddlewarePipeline(); this.outboundPipeline = new pipeline_1.MiddlewarePipeline(); this.maxMessageSize = options.maxMessageSize ?? DEFAULT_MAX_MESSAGE_SIZE; this.eventsEnabled = options.events?.enabled ?? true; this.emitMiddlewareEvents = options.events?.emitMiddlewareEvents ?? false; this.slowProcessingThreshold = options.events?.slowProcessingThreshold ?? 5000; } emit(event, ...args) { if (!this.eventsEnabled) { return false; } return super.emit(event, ...args); } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } off(event, listener) { return super.off(event, listener); } use(middleware) { if ('process' in middleware && typeof middleware.process === 'function') { return this.addInboundMiddleware(middleware); } else if ('processOutbound' in middleware && typeof middleware.processOutbound === 'function') { return this.addOutboundMiddleware(middleware); } else { throw new Error('Invalid middleware: must implement either InboundMiddleware (process method) or OutboundMiddleware (processOutbound method)'); } } addInboundMiddleware(middleware) { this.inboundPipeline.use(middleware); if ('destroy' in middleware && typeof middleware.destroy === 'function') { this.resources.push(middleware); } return this; } addOutboundMiddleware(middleware) { this.outboundPipeline.use(middleware); if ('destroy' in middleware && typeof middleware.destroy === 'function') { this.resources.push(middleware); } return this; } async processInbound(message, raw, attributes) { if (message === null || message === undefined) { throw new Error('Message cannot be null or undefined. ' + 'Provide a valid message object to process.'); } try { const messageStr = JSON.stringify(message); if (messageStr.length > this.maxMessageSize) { throw new Error(`Message size (${messageStr.length} bytes) exceeds maximum allowed size (${this.maxMessageSize} bytes). ` + `Consider increasing maxMessageSize in ManagerOptions or reducing message payload.`); } } catch (error) { if (error instanceof TypeError && error.message.includes('circular')) { throw new Error('Message contains circular references and cannot be serialized. ' + 'Remove circular references before processing.'); } throw error; } const startTime = Date.now(); const context = { message, raw, metadata: {}, attributes: attributes || {}, }; this.emit('message:start', context); try { await this.inboundPipeline.execute(context); const duration = Date.now() - startTime; this.inboundProcessedCount++; this.totalInboundProcessingTime += duration; this.emit('message:complete', context, duration); if (duration > this.slowProcessingThreshold) { this.emit('slow:processing', context, duration); } return context; } catch (error) { const duration = Date.now() - startTime; this.inboundErrorCount++; this.totalInboundProcessingTime += duration; this.emit('message:error', error, context); throw error; } } async processOutbound(message, metadata = {}, attributes = {}) { if (message === null || message === undefined) { throw new Error('Message cannot be null or undefined. ' + 'Provide a valid message object to process.'); } try { const messageStr = JSON.stringify(message); if (messageStr.length > this.maxMessageSize) { throw new Error(`Message size (${messageStr.length} bytes) exceeds maximum allowed size (${this.maxMessageSize} bytes). ` + `Consider increasing maxMessageSize in ManagerOptions or reducing message payload.`); } } catch (error) { if (error instanceof TypeError && error.message.includes('circular')) { throw new Error('Message contains circular references and cannot be serialized. ' + 'Remove circular references before processing.'); } throw error; } const startTime = Date.now(); const context = { message, metadata, attributes, }; this.emit('message:start', context); try { await this.outboundPipeline.execute(context); const duration = Date.now() - startTime; this.outboundProcessedCount++; this.totalOutboundProcessingTime += duration; this.emit('message:complete', context, duration); return context; } catch (error) { const duration = Date.now() - startTime; this.outboundErrorCount++; this.totalOutboundProcessingTime += duration; this.emit('message:error', error, context); throw error; } } getMiddlewares() { return { inbound: this.inboundPipeline.middlewares.map((m) => ({ name: m.name, metadata: m.metadata, })), outbound: this.outboundPipeline.middlewares.map((m) => ({ name: m.name, metadata: m.metadata, })), }; } getStats() { return { inboundProcessed: this.inboundProcessedCount, outboundProcessed: this.outboundProcessedCount, inboundErrors: this.inboundErrorCount, outboundErrors: this.outboundErrorCount, avgInboundProcessingTimeMs: this.inboundProcessedCount > 0 ? this.totalInboundProcessingTime / this.inboundProcessedCount : 0, avgOutboundProcessingTimeMs: this.outboundProcessedCount > 0 ? this.totalOutboundProcessingTime / this.outboundProcessedCount : 0, startedAt: this.startedAt, uptimeMs: Date.now() - this.startedAt.getTime(), }; } async getHealth() { const componentHealthChecks = []; const errors = []; for (const resource of this.resources) { if (this.isHealthable(resource)) { try { const health = await resource.getHealth(); componentHealthChecks.push(health); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); componentHealthChecks.push({ status: 'unhealthy', message: `Health check failed: ${error instanceof Error ? error.message : String(error)}`, }); } } } let overallStatus = 'healthy'; if (errors.length > 0 || componentHealthChecks.some(h => h.status === 'unhealthy')) { overallStatus = 'unhealthy'; } else if (componentHealthChecks.some(h => h.status === 'degraded')) { overallStatus = 'degraded'; } else if (componentHealthChecks.some(h => h.status === 'unknown')) { overallStatus = 'unknown'; } return { status: overallStatus, message: overallStatus === 'healthy' ? 'All components healthy' : `${componentHealthChecks.filter(h => h.status !== 'healthy').length} components unhealthy or degraded`, details: { components: componentHealthChecks, resourceCount: this.resources.length, healthableCount: componentHealthChecks.length, }, timestamp: new Date(), }; } isHealthable(resource) { return resource && typeof resource.getHealth === 'function'; } async destroy() { const errors = []; for (const resource of this.resources) { if (resource.destroy && typeof resource.destroy === 'function') { try { await resource.destroy(); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); } } } this.resources = []; if (errors.length > 0) { throw new AggregateError(errors, `Failed to destroy ${errors.length} resource(s)`); } } } exports.MessageMiddlewareManager = MessageMiddlewareManager; //# sourceMappingURL=manager.js.map