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
text/typescript
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();
}
}
}