UNPKG

@logistically/events

Version:

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

117 lines (116 loc) 5.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisStreamConsumer = void 0; const consumer_1 = require("./consumer"); const event_types_1 = require("../event-types"); class RedisStreamConsumer { constructor(options) { var _a, _b, _c; this.running = true; this.redis = options.redis; this.stream = options.stream; this.group = options.group; this.consumer = options.consumer; this.blockMs = (_a = options.blockMs) !== null && _a !== void 0 ? _a : 5000; this.count = (_b = options.count) !== null && _b !== void 0 ? _b : 10; this.deadLetterStream = options.deadLetterStream; this.verbose = (_c = options.verbose) !== null && _c !== void 0 ? _c : false; // Create a default validator if none provided const validator = options.validator || new event_types_1.DefaultEventValidator(); this.eventConsumer = new consumer_1.EventConsumer({ handlers: options.handlers, onError: options.onError, validator, }); } async pollAndHandle() { var _a; if (this.verbose) console.log('[RedisStreamConsumer] pollAndHandle called'); const res = await this.redis.xreadgroup('GROUP', this.group, this.consumer, 'COUNT', this.count, 'BLOCK', this.blockMs, 'STREAMS', this.stream, '>'); if (!res || res.length === 0) { if (this.verbose) console.log('[RedisStreamConsumer] No messages found in this poll.'); } else { const messages = ((_a = res[0]) === null || _a === void 0 ? void 0 : _a[1]) || []; if (this.verbose) console.log(`[RedisStreamConsumer] Received ${messages.length} messages from stream.`); } if (!res || res.length === 0) return; for (const [stream, messages] of res) { for (const [id, fields] of messages) { try { if (this.verbose) console.log(`[RedisStreamConsumer] Raw fields for id ${id}:`, JSON.stringify(fields)); const raw = fields.data ? JSON.parse(fields.data) : fields; if (this.verbose) console.log(`[RedisStreamConsumer] Parsed raw for id ${id}:`, JSON.stringify(raw)); // Handle the case where raw is an array with the JSON string as the second element let messageData; if (Array.isArray(raw) && raw.length >= 2) { // raw is ["data", "json_string"], so parse the second element messageData = JSON.parse(raw[1]); } else if (raw.data) { // raw is an object with a data property messageData = raw.data; } else { // raw is already the message data messageData = raw; } if (this.verbose) console.log(`[RedisStreamConsumer] Message data for id ${id}:`, JSON.stringify(messageData)); // Extract the envelope from the data field const envelope = messageData.data || messageData; if (this.verbose) console.log(`[RedisStreamConsumer] Extracted envelope for id ${id}:`, JSON.stringify(envelope)); if (envelope.header) { if (this.verbose) console.log(`[RedisStreamConsumer] Envelope event type: ${envelope.header.type}`); } else { console.warn(`[RedisStreamConsumer] Envelope missing header for id ${id}`); } await this.eventConsumer.handleMessage(envelope); if (this.verbose) console.log(`[RedisStreamConsumer] Successfully handled message for id ${id}`); await this.redis.xack(this.stream, this.group, id); if (this.verbose) console.log(`[RedisStreamConsumer] ACKed message for id ${id}`); } catch (err) { console.error(`[RedisStreamConsumer] Error processing message for id ${id}:`, err); if (this.verbose) console.error('[RedisStreamConsumer] Message fields:', JSON.stringify(fields)); if (err.stack) console.error(err.stack); if (this.deadLetterStream) { try { await this.redis.xadd(this.deadLetterStream, '*', 'data', fields.data || JSON.stringify(fields)); await this.redis.xack(this.stream, this.group, id); if (this.verbose) console.log(`[RedisStreamConsumer] Sent message for id ${id} to DLQ and ACKed.`); } catch (dlqErr) { console.error(`[RedisStreamConsumer] Failed to push to dead letter stream for id ${id}:`, dlqErr); if (dlqErr.stack) console.error(dlqErr.stack); // Do not ack, so the message can be retried } } else { console.error(`[RedisStreamConsumer] Message handling failed and no dead letter stream is configured for id ${id}:`, err); // Do not ack } } } } } stop() { this.running = false; } } exports.RedisStreamConsumer = RedisStreamConsumer;