@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
JavaScript
"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;