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