UNPKG

coddyger

Version:

Coddyger est une bibliothèque JavaScript/TypeScript qui fournit des fonctions communes et des plugins pour la gestion des données, la communication entre services, et des utilitaires avancés pour le développement d'applications.

274 lines (238 loc) 6.99 kB
import { Kafka, logLevel } from 'kafkajs'; import env from '../globals'; import { LoggerService, LogLevel } from './logger.service'; import coddyger from '../coddyger'; interface TopicInterface { id: string; set?: new () => DAO; handlers?: { [key: string]: (data: any) => Promise<void>; }; } interface DataAction { _id?: string; action?: 'remove' | 'no-crud'; [key: string]: any; } interface DAO { remove: (query: { _id: string }) => Promise<{ error?: any }>; exist: (query: { _id: string }) => Promise<boolean | { error: any }>; save: (data: any) => Promise<{ error?: any }>; update: (query: { _id: string }, data: any) => Promise<{ error?: any }>; } interface KafkaMessage { topic: string; partition: number; message: { offset: string; value: Buffer; }; } interface EventHandler { action: string; handler: (data: any) => Promise<void>; } async function logError(error: any, location: string, method: string): Promise<void> { LoggerService.log({ type: LogLevel.Error, content: JSON.stringify(error), location, method }); } async function handleRemove(dao: DAO, _id: string, label: string): Promise<void> { const result = await dao.remove({ _id }); if (result.error) { await logError(result, 'TransporterCore', `${label}-remove`); } else { coddyger.konsole(`${label} removed successfully :: ${_id}`); } } async function handleCreate(dao: DAO, data: DataAction, label: string): Promise<void> { const result = await dao.save(data); if (result.error) { await logError(result, 'TransporterCore', `${label}-save`); } else { coddyger.konsole(`${label} saved successfully :: ${data._id}`); } } async function handleUpdate(dao: DAO, _id: string, data: DataAction, label: string): Promise<void> { const result = await dao.update({ _id }, data); if (result.error) { await logError(result, 'TransporterCore', `${label}-update`); } else { coddyger.konsole(`${label} edited successfully :: ${_id}`); } } async function handleNoCrud(data: DataAction, handler: (data: DataAction) => Promise<void>, label: string): Promise<void> { try { await handler(data); coddyger.konsole(`${label} no-crud action executed successfully`); } catch (error) { await logError(error, 'TransporterCore', `${label}-no-crud`); } } async function getData(data: DataAction, set: any, label: string): Promise<void> { const _id = data._id; const dao: DAO = new set(); try { if (!_id) { throw new Error('Missing _id in data'); } if (!coddyger.string.isValidObjectId(_id)) { await logError('Wrong _id detected::' + _id, 'TransporterCore', 'getData'); return; } if (data.action === 'remove') { await handleRemove(dao, _id, label); return; } const existResult = await dao.exist({ _id }); if (existResult && typeof existResult === 'object' && 'error' in existResult) { await logError(existResult.error, 'TransporterCore', `${label}-exist`); return; } if (!existResult) { await handleCreate(dao, data, label); } else { const { _id, ...updateData } = data; await handleUpdate(dao, _id, updateData, label); } } catch (error: any) { await logError(error, 'TransporterCore', 'getData'); } } export class TransporterService { topics: TopicInterface[] = []; private isConnected: boolean = false; private reconnectAttempts: number = 0; private readonly MAX_RECONNECT_ATTEMPTS: number = 5; constructor(topics?: TopicInterface[]) { this.topics = topics || []; } private readonly kafka: Kafka = new Kafka({ clientId: env.transporter.client, brokers: env.transporter.broker.split(','), logLevel: logLevel.ERROR }); readonly producer = this.kafka.producer(); readonly consumer = this.kafka.consumer({ groupId: env.transporter.group }); async send(value: string, topic?: string): Promise<void> { try { if (!this.isConnected) { await this.connect(); } await this.producer.send({ topic: !coddyger.string.isEmpty(topic) ? topic : env.transporter.topic, messages: [{ value }] }); } catch (error) { await logError(error, 'TransporterService', 'send'); throw error; } } async connect(): Promise<void> { try { await Promise.all([ this.producer.connect(), this.consumer.connect() ]); this.isConnected = true; this.reconnectAttempts = 0; coddyger.konsole(`Transporter connected! :: ${env.transporter.broker}`); } catch (error) { this.isConnected = false; await logError(error, 'TransporterService', 'connect'); throw error; } } async disconnect(): Promise<void> { try { await Promise.all([ this.producer.disconnect(), this.consumer.disconnect() ]); this.isConnected = false; coddyger.konsole('Transporter disconnected successfully'); } catch (error) { await logError(error, 'TransporterService', 'disconnect'); throw error; } } private async handleReconnect(): Promise<void> { if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) { await logError('Max reconnection attempts reached', 'TransporterService', 'handleReconnect'); return; } this.reconnectAttempts++; try { await this.connect(); } catch (error) { await logError(error, 'TransporterService', 'handleReconnect'); setTimeout(() => this.handleReconnect(), 5000 * this.reconnectAttempts); } } async handleEvent(data: any, topic: TopicInterface): Promise<void> { const action = data.action; if (action === 'remove' && topic.set) { await getData(data, topic.set, topic.id); return; } if (topic.handlers && topic.handlers[action]) { try { await topic.handlers[action](data); coddyger.konsole(`Event ${action} handled successfully for topic ${topic.id}`); return; } catch (error) { await logError(error, 'TransporterService', `${topic.id}-${action}`); return; } } if (topic.set && !action) { await getData(data, topic.set, topic.id); return; } await logError( `No handler found for action ${action} in topic ${topic.id}`, 'TransporterService', 'handleEvent' ); } async get(): Promise<void> { try { await this.connect(); for (const topic of this.topics) { await this.consumer.subscribe({ topic: topic.id, fromBeginning: true }); } await this.consumer.run({ eachMessage: async (payload: KafkaMessage) => { try { const msg = { partition: payload.partition, offset: payload.message.offset, value: payload.message.value.toString() }; coddyger.konsole(`Consuming from :: ${payload.topic}`); const data = JSON.parse(msg.value); for (const topic of this.topics) { if (payload.topic === topic.id) { await this.handleEvent(data, topic); } } } catch (error) { await logError(error, 'TransporterService', 'eachMessage'); } }, autoCommit: true }); } catch (error) { await logError(error, 'TransporterService', 'get'); await this.handleReconnect(); } } }