UNPKG

@logistically/events

Version:

A production-ready event-driven architecture library for NestJS with Redis Streams, comprehensive batching, reliable consumption, and enterprise-grade features.

119 lines (118 loc) 4.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisStreamsServer = void 0; const microservices_1 = require("@nestjs/microservices"); const ioredis_1 = require("ioredis"); const redis_streams_consumer_1 = require("../event-consumer/redis-streams-consumer"); class RedisStreamsServer extends microservices_1.Server { constructor(options) { var _a, _b; super(); this.isRunning = false; this.options = options; this.consumerName = options.consumer || 'consumer-' + Math.random().toString(36).slice(2); this.client = new ioredis_1.default({ host: options.host, port: options.port, password: options.password, tls: options.tls, connectTimeout: 10000, // Add connection timeout commandTimeout: 5000, // Add command timeout maxRetriesPerRequest: 3, }); this.blockTimeoutMs = (_a = options.blockTimeoutMs) !== null && _a !== void 0 ? _a : 10000; this.verbose = (_b = options.verbose) !== null && _b !== void 0 ? _b : false; if (this.verbose) { console.log('[RedisStreamsServer] Constructed with config:', { host: options.host, port: options.port, stream: options.stream, group: options.group, consumer: this.consumerName, blockTimeoutMs: this.blockTimeoutMs, deadLetterStream: options.deadLetterStream, }); } } async listen(callback) { if (this.verbose) console.log('[RedisStreamsServer] Starting listen method'); // Create consumer group if it doesn't exist try { await this.client.xgroup('CREATE', this.options.stream, this.options.group, '$', 'MKSTREAM'); if (this.verbose) console.log('[RedisStreamsServer] Consumer group created or already exists'); } catch (e) { if (!String(e.message).includes('BUSYGROUP')) { console.error(`[RedisStreamsServer] Error creating group: ${e.message}`); } else { if (this.verbose) console.log('[RedisStreamsServer] Consumer group already exists (BUSYGROUP)'); } } // Map NestJS handlers to event handlers const patternHandlers = {}; for (const [pattern, handler] of this.getHandlers().entries()) { patternHandlers[pattern] = async (payload, header, raw) => { await handler(payload); // NestJS expects just the payload }; } if (this.verbose) console.log('[RedisStreamsServer] Registered handlers:', Object.keys(patternHandlers)); if (Object.keys(patternHandlers).length === 0) { console.warn('[RedisStreamsServer] WARNING: No handlers registered!'); } // Create the Redis Streams consumer this.consumer = new redis_streams_consumer_1.RedisStreamConsumer({ redis: this.client, stream: this.options.stream, group: this.options.group, consumer: this.consumerName, handlers: patternHandlers, blockMs: this.blockTimeoutMs, deadLetterStream: this.options.deadLetterStream, verbose: this.verbose, onError: (err, eventType, envelope) => { console.error(`[RedisStreamsServer] Error for event ${eventType}: ${err.message}`); if (err.stack) console.error(err.stack); }, }); this.isRunning = true; if (this.verbose) console.log('[RedisStreamsServer] Calling callback and starting poll loop'); callback(); // Start polling in the background this.pollLoop(); } async pollLoop() { if (!this.consumer) return; if (this.verbose) console.log('[RedisStreamsServer] Starting poll loop'); while (this.isRunning) { try { if (this.verbose) console.log('[RedisStreamsServer] Polling for messages...'); await this.consumer.pollAndHandle(); // Use setImmediate instead of setTimeout for better performance await new Promise(resolve => setImmediate(resolve)); } catch (error) { console.error('[RedisStreamsServer] Poll loop error:', error); // Add delay on error to prevent tight loops await new Promise(resolve => setTimeout(resolve, 1000)); } } } async close() { this.isRunning = false; if (this.consumer) { this.consumer.stop(); } await this.client.quit(); } } exports.RedisStreamsServer = RedisStreamsServer;