adding data-services

This commit is contained in:
Bojan Kucera 2025-06-03 07:42:48 -04:00
parent e3bfd05b90
commit 405b818c86
139 changed files with 55943 additions and 416 deletions

42
libs/event-bus/README.md Normal file
View file

@ -0,0 +1,42 @@
# Event Bus Library
A Redis-based event bus implementation for inter-service communication in the stock-bot project.
## Features
- Publish/subscribe pattern for asynchronous messaging
- Support for typed events based on `@stock-bot/shared-types`
- Reliable message delivery
- Channel-based subscriptions
## Usage
```typescript
import { createEventBus } from '@stock-bot/event-bus';
import { MarketDataEvent } from '@stock-bot/shared-types';
// Create an event bus instance
const eventBus = createEventBus({
redisHost: 'localhost',
redisPort: 6379
});
// Subscribe to market data events
eventBus.subscribe('market.data', async (event: MarketDataEvent) => {
console.log(`Received price update for ${event.data.symbol}: ${event.data.price}`);
});
// Publish an event
await eventBus.publish('market.data', {
type: 'MARKET_DATA',
data: {
symbol: 'AAPL',
price: 150.25,
bid: 150.20,
ask: 150.30,
volume: 1000000,
timestamp: new Date()
},
timestamp: new Date()
});
```

View file

@ -0,0 +1,22 @@
{
"name": "@stock-bot/event-bus",
"version": "1.0.0",
"description": "Event bus implementation for inter-service communication",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "jest"
},
"dependencies": {
"@stock-bot/shared-types": "workspace:*",
"ioredis": "^5.3.2"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"jest": "^29.5.0",
"typescript": "^5.4.5"
}
}

View file

@ -0,0 +1,131 @@
import Redis from 'ioredis';
import { Event } from '@stock-bot/shared-types';
export type EventHandler<T extends Event = Event> = (event: T) => Promise<void> | void;
export interface EventBusConfig {
redisHost: string;
redisPort: number;
redisPassword?: string;
}
/**
* Event Bus for publishing and subscribing to events in the system
* Provides reliable message delivery and pattern-based subscriptions
*/
export class EventBus {
private publisher: Redis;
private subscriber: Redis;
private handlers: Map<string, EventHandler[]>;
private isConnected: boolean = false;
constructor(private config: EventBusConfig) {
this.publisher = new Redis({
host: config.redisHost,
port: config.redisPort,
password: config.redisPassword,
});
this.subscriber = new Redis({
host: config.redisHost,
port: config.redisPort,
password: config.redisPassword,
});
this.handlers = new Map<string, EventHandler[]>();
this.setupConnectionHandlers();
}
/**
* Set up Redis connection event handlers
*/
private setupConnectionHandlers(): void {
this.publisher.on('connect', () => {
console.log('Publisher connected to Redis');
this.isConnected = true;
});
this.publisher.on('error', (err) => {
console.error('Publisher Redis error', err);
this.isConnected = false;
});
this.subscriber.on('connect', () => {
console.log('Subscriber connected to Redis');
});
this.subscriber.on('error', (err) => {
console.error('Subscriber Redis error', err);
});
this.subscriber.on('message', (channel, message) => {
try {
const event = JSON.parse(message) as Event;
this.processEvent(channel, event);
} catch (err) {
console.error('Error processing event', err);
}
});
}
/**
* Process an incoming event by calling all registered handlers
*/
private processEvent(channel: string, event: Event): void {
const handlers = this.handlers.get(channel) || [];
handlers.forEach(handler => {
try {
handler(event);
} catch (err) {
console.error(`Error in event handler for ${channel}:`, err);
}
});
}
/**
* Publish an event to the specified channel
*/
public async publish(channel: string, event: Event): Promise<void> {
if (!this.isConnected) {
throw new Error('Not connected to Redis');
}
await this.publisher.publish(channel, JSON.stringify(event));
}
/**
* Subscribe to events on a specific channel
*/
public subscribe(channel: string, handler: EventHandler): () => void {
if (!this.handlers.has(channel)) {
this.handlers.set(channel, []);
this.subscriber.subscribe(channel);
}
const handlers = this.handlers.get(channel)!;
handlers.push(handler);
// Return unsubscribe function
return () => {
const index = handlers.indexOf(handler);
if (index >= 0) {
handlers.splice(index, 1);
}
if (handlers.length === 0) {
this.handlers.delete(channel);
this.subscriber.unsubscribe(channel);
}
};
}
/**
* Close connections
*/
public async close(): Promise<void> {
await this.publisher.quit();
await this.subscriber.quit();
this.isConnected = false;
}
}

View file

@ -0,0 +1,8 @@
import { EventBus, EventBusConfig, EventHandler } from './EventBus';
export { EventBus, EventBusConfig, EventHandler };
// Convenience function to create an event bus with the default configuration
export function createEventBus(config: EventBusConfig): EventBus {
return new EventBus(config);
}

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"declaration": true,
"outDir": "dist",
"strict": true,
"sourceMap": true
},
"include": ["src/**/*"]
}