finished initial event-bus with streams
This commit is contained in:
parent
5cd24ade09
commit
fc4cf71a70
1 changed files with 0 additions and 169 deletions
|
|
@ -1,169 +0,0 @@
|
|||
import { EventEmitter } from 'eventemitter3';
|
||||
import Redis from 'ioredis';
|
||||
import { createLogger } from '@stock-bot/logger';
|
||||
import { dragonflyConfig } from '@stock-bot/config';
|
||||
|
||||
export interface EventBusMessage {
|
||||
id: string;
|
||||
type: string;
|
||||
source: string;
|
||||
timestamp: number;
|
||||
data: any;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface EventHandler<T = any> {
|
||||
(message: EventBusMessage & { data: T }): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface EventBusOptions {
|
||||
|
||||
serviceName: string;
|
||||
enablePersistence?: boolean;
|
||||
}
|
||||
|
||||
export class EventBus extends EventEmitter {
|
||||
private redis?: Redis;
|
||||
private subscriber?: Redis;
|
||||
private serviceName: string;
|
||||
private logger
|
||||
private enablePersistence: boolean;
|
||||
|
||||
constructor(options: EventBusOptions) {
|
||||
super();
|
||||
this.serviceName = options.serviceName;
|
||||
this.enablePersistence = options.enablePersistence ?? true;
|
||||
this.logger = createLogger(`event-bus:${this.serviceName}`);
|
||||
|
||||
this.redis = new Redis({
|
||||
host: dragonflyConfig.DRAGONFLY_HOST,
|
||||
port: dragonflyConfig.DRAGONFLY_PORT,
|
||||
password: dragonflyConfig.DRAGONFLY_PASSWORD,
|
||||
db: dragonflyConfig.DRAGONFLY_DATABASE || 0,
|
||||
maxRetriesPerRequest: dragonflyConfig.DRAGONFLY_MAX_RETRIES,
|
||||
});
|
||||
|
||||
this.subscriber = new Redis({
|
||||
host: dragonflyConfig.DRAGONFLY_HOST,
|
||||
port: dragonflyConfig.DRAGONFLY_PORT,
|
||||
password: dragonflyConfig.DRAGONFLY_PASSWORD,
|
||||
db: dragonflyConfig.DRAGONFLY_DATABASE || 0,
|
||||
});
|
||||
|
||||
this.subscriber.on('message', this.handleRedisMessage.bind(this));
|
||||
this.logger.info('Redis event bus initialized');
|
||||
}
|
||||
|
||||
private handleRedisMessage(channel: string, message: string) {
|
||||
try {
|
||||
const eventMessage: EventBusMessage = JSON.parse(message);
|
||||
|
||||
// Don't process our own messages
|
||||
if (eventMessage.source === this.serviceName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit(eventMessage.type, eventMessage);
|
||||
this.logger.debug(`Received event: ${eventMessage.type} from ${eventMessage.source}`);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to parse Redis message', { error, message });
|
||||
}
|
||||
}
|
||||
|
||||
async publish<T = any>(type: string, data: T, metadata?: Record<string, any>): Promise<void> {
|
||||
const message: EventBusMessage = {
|
||||
id: this.generateId(),
|
||||
type,
|
||||
source: this.serviceName,
|
||||
timestamp: Date.now(),
|
||||
data,
|
||||
metadata,
|
||||
};
|
||||
|
||||
// Emit locally first
|
||||
this.emit(type, message);
|
||||
|
||||
// Publish to Redis if available
|
||||
if (this.redis && this.enablePersistence) {
|
||||
try {
|
||||
await this.redis.publish(`events:${type}`, JSON.stringify(message));
|
||||
this.logger.debug(`Published event: ${type}`, { messageId: message.id });
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to publish event: ${type}`, { error, messageId: message.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async subscribe<T = any>(eventType: string, handler: EventHandler<T>): Promise<void> {
|
||||
// Subscribe locally
|
||||
this.on(eventType, handler);
|
||||
|
||||
// Subscribe to Redis channel if available
|
||||
if (this.subscriber && this.enablePersistence) {
|
||||
try {
|
||||
await this.subscriber.subscribe(`events:${eventType}`);
|
||||
this.logger.debug(`Subscribed to event: ${eventType}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to subscribe to event: ${eventType}`, { error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async unsubscribe(eventType: string, handler?: EventHandler): Promise<void> {
|
||||
if (handler) {
|
||||
this.off(eventType, handler);
|
||||
} else {
|
||||
this.removeAllListeners(eventType);
|
||||
}
|
||||
|
||||
if (this.subscriber && this.enablePersistence) {
|
||||
try {
|
||||
await this.subscriber.unsubscribe(`events:${eventType}`);
|
||||
this.logger.debug(`Unsubscribed from event: ${eventType}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to unsubscribe from event: ${eventType}`, { error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.redis) {
|
||||
await this.redis.quit();
|
||||
}
|
||||
if (this.subscriber) {
|
||||
await this.subscriber.quit();
|
||||
}
|
||||
this.removeAllListeners();
|
||||
this.logger.info('Event bus closed');
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return `${this.serviceName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
// Utility methods for common event patterns
|
||||
async publishMarketData(symbol: string, data: any): Promise<void> {
|
||||
await this.publish('market.data', { symbol, ...data });
|
||||
}
|
||||
|
||||
async publishOrderUpdate(orderId: string, status: string, data: any): Promise<void> {
|
||||
await this.publish('order.update', { orderId, status, ...data });
|
||||
}
|
||||
|
||||
async publishStrategySignal(strategyId: string, signal: any): Promise<void> {
|
||||
await this.publish('strategy.signal', { strategyId, ...signal });
|
||||
}
|
||||
|
||||
async publishPortfolioUpdate(portfolioId: string, data: any): Promise<void> {
|
||||
await this.publish('portfolio.update', { portfolioId, ...data });
|
||||
}
|
||||
|
||||
async publishBacktestUpdate(backtestId: string, progress: number, data?: any): Promise<void> {
|
||||
await this.publish('backtest.update', { backtestId, progress, ...data });
|
||||
}
|
||||
}
|
||||
|
||||
// Factory function for easy initialization
|
||||
export function createEventBus(options: EventBusOptions): EventBus {
|
||||
return new EventBus(options);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue