nodejs-event-driven
Version:
NodeJS agnostic event driven with EventEmitter support
149 lines • 5.16 kB
JavaScript
import { EventEmitterBusService } from '@main/infra/event-bus/event-emitter/event-emitter-bus.service.js';
export var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
LogLevel[LogLevel["WARN"] = 2] = "WARN";
LogLevel[LogLevel["INFO"] = 4] = "INFO";
LogLevel[LogLevel["DEBUG"] = 5] = "DEBUG";
})(LogLevel || (LogLevel = {}));
export const ERROR_MESSAGE_NOT_INITIALIZED = 'Kafka is not initialized!';
export class KafkaEventBusService extends EventEmitterBusService {
static DEFAULT_CLIENT_ID;
static DEFAULT_URL = 'localhost:9092';
#logger;
#kafka = null;
#consumers = new Map();
#kafkaConfig;
#logCreator;
constructor(config) {
super(config);
this.#logger = config.logger;
this.#kafkaConfig = { ...config };
this.#logCreator = (logLevel) => (entry) => {
const message = `kafka - ${entry.log.message}`;
switch (logLevel) {
case LogLevel.ERROR:
this.#logger?.error(message);
break;
case LogLevel.WARN:
this.#logger?.warn(message);
break;
case LogLevel.INFO:
this.#logger?.info(message);
break;
case LogLevel.DEBUG:
this.#logger?.debug(message);
break;
default:
}
};
}
async #createTopic(eventName) {
const kafka = this.#kafka;
if (!kafka) {
throw new Error(ERROR_MESSAGE_NOT_INITIALIZED);
}
const admin = kafka.admin();
await admin.connect();
await admin.createTopics({
topics: [
{
topic: eventName,
numPartitions: 1,
replicationFactor: 1,
},
],
});
await admin.disconnect();
}
async #consume(eventName, listener, once = false) {
const kafka = this.#kafka;
if (!kafka) {
throw new Error(ERROR_MESSAGE_NOT_INITIALIZED);
}
await this.#createTopic(eventName);
const consumer = kafka.consumer({
groupId: eventName,
allowAutoTopicCreation: true,
});
await consumer.connect();
this.#consumers.set(eventName, consumer);
await consumer.subscribe({
topic: this.getTopic(eventName),
fromBeginning: true,
});
await consumer.run({
eachMessage: async ({ message }) => {
if (once) {
this.off(eventName);
}
const value = message.value?.toString();
const data = value ? JSON.parse(value) : undefined;
listener(data);
},
});
}
async #produce(eventName, data) {
const kafka = this.#kafka;
if (!kafka) {
throw new Error(ERROR_MESSAGE_NOT_INITIALIZED);
}
await this.#createTopic(eventName);
const producer = kafka.producer({ allowAutoTopicCreation: true });
await producer.connect();
try {
await producer.send({
topic: this.getTopic(eventName),
messages: [{ value: JSON.stringify(data) }],
});
}
finally {
await producer.disconnect();
}
}
async #stopConsumer(consumer) {
await consumer.stop();
await consumer.disconnect();
}
getTopic(eventName) {
return this.#kafkaConfig.topicPrefix
? `${this.#kafkaConfig.topicPrefix}-${eventName}`
: `${eventName}`;
}
on(eventName, listener) {
this.#logger?.debug(`register listener for topic: ${eventName}`);
void this.#consume(eventName, listener);
}
once(eventName, listener) {
this.#logger?.debug(`register once listener for channel: ${eventName}`);
void this.#consume(eventName, listener, true);
}
off(eventName) {
const consumer = this.#consumers.get(eventName);
if (!consumer) {
return;
}
this.#logger?.debug(`unregister listener for topic: ${eventName}`);
this.#consumers.delete(eventName);
void this.#stopConsumer(consumer);
}
send(eventName, data) {
this.#logger?.debug(`sending ${String(data)} to topic ${eventName}`);
void this.#produce(eventName, data);
}
async start() {
const { Kafka } = await import('kafkajs');
this.#kafka = new Kafka({
logCreator: this.#logCreator,
brokers: this.#kafkaConfig.brokers ?? [KafkaEventBusService.DEFAULT_URL],
clientId: this.#kafkaConfig.clientId ?? KafkaEventBusService.DEFAULT_CLIENT_ID,
});
}
async stop() {
await Promise.all(this.#consumers.keys().map((eventName) => {
this.off(eventName);
}));
}
}
export const createKafkaEventBusService = (config) => new KafkaEventBusService(config);
//# sourceMappingURL=kafka-event-bus.service.js.map