From 9673ae70ef8121ee89611906bff249c269dcf567 Mon Sep 17 00:00:00 2001 From: Boki Date: Sat, 21 Jun 2025 19:15:58 -0400 Subject: [PATCH] libs ready i think --- apps/data-ingestion/src/index.ts | 2 +- .../src/setup/database-setup.ts | 44 ++------- libs/core/di/src/connection-factory.ts | 92 +++++++++++++++-- libs/core/di/src/operation-context.ts | 99 ++++++++++++++++--- libs/core/di/src/service-container.ts | 49 ++------- libs/core/di/src/types.ts | 4 +- libs/core/di/test/di.test.ts | 31 ++++++ libs/services/queue/src/index.ts | 5 +- libs/utils/src/calculations/index.ts | 45 ++++----- 9 files changed, 242 insertions(+), 129 deletions(-) diff --git a/apps/data-ingestion/src/index.ts b/apps/data-ingestion/src/index.ts index bac61f3..45b7099 100644 --- a/apps/data-ingestion/src/index.ts +++ b/apps/data-ingestion/src/index.ts @@ -6,7 +6,7 @@ import { cors } from 'hono/cors'; import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger'; import type { QueueManager } from '@stock-bot/queue'; import { Shutdown } from '@stock-bot/shutdown'; -import { ProxyManager } from '@stock-bot/di'; +import { ProxyManager } from '@stock-bot/utils'; import type { ServiceContainer } from '@stock-bot/di'; // Local imports import { setupServiceContainer } from './setup/database-setup'; diff --git a/apps/data-ingestion/src/setup/database-setup.ts b/apps/data-ingestion/src/setup/database-setup.ts index 6de2349..fd02b99 100644 --- a/apps/data-ingestion/src/setup/database-setup.ts +++ b/apps/data-ingestion/src/setup/database-setup.ts @@ -115,18 +115,13 @@ export async function setupServiceContainer(): Promise { // Register Cache container.register({ name: 'cache', - factory: () => { - const pool = connectionFactory.createCache({ + factory: async () => { + const pool = await connectionFactory.createCache({ name: 'default', config: { - redisConfig: { - host: dbConfig.dragonfly.host, - port: dbConfig.dragonfly.port, - db: dbConfig.dragonfly.db, - }, - keyPrefix: 'data-ingestion:', - ttl: 3600, - enableMetrics: true, + host: dbConfig.dragonfly.host, + port: dbConfig.dragonfly.port, + db: dbConfig.dragonfly.db, } }); return pool.client; @@ -137,32 +132,13 @@ export async function setupServiceContainer(): Promise { // Register QueueManager container.register({ name: 'queue', - factory: () => { - const pool = connectionFactory.createQueue({ + factory: async () => { + const pool = await connectionFactory.createQueue({ name: 'default', config: { - redis: queueConfig?.redis || { - host: dbConfig.dragonfly.host, - port: dbConfig.dragonfly.port, - db: dbConfig.dragonfly.db || 1, - }, - defaultQueueOptions: { - defaultJobOptions: queueConfig?.defaultJobOptions || { - attempts: 3, - backoff: { - type: 'exponential', - delay: 1000, - }, - removeOnComplete: 10, - removeOnFail: 5, - }, - workers: 2, - concurrency: 1, - enableMetrics: true, - enableDLQ: true, - }, - enableScheduledJobs: true, - delayWorkerStart: true, + host: dbConfig.dragonfly.host, + port: dbConfig.dragonfly.port, + db: dbConfig.dragonfly.db || 1, } }); return pool.client; diff --git a/libs/core/di/src/connection-factory.ts b/libs/core/di/src/connection-factory.ts index 2c77330..e769719 100644 --- a/libs/core/di/src/connection-factory.ts +++ b/libs/core/di/src/connection-factory.ts @@ -71,7 +71,9 @@ export class ConnectionFactory implements IConnectionFactory { } }, dispose: async () => { - await client.disconnect(); + if (client && typeof client.disconnect === 'function') { + await client.disconnect(); + } this.pools.delete(key); }, }; @@ -116,7 +118,9 @@ export class ConnectionFactory implements IConnectionFactory { metrics: client.getPoolMetrics(), health: async () => client.connected, dispose: async () => { - await client.disconnect(); + if (client && typeof client.disconnect === 'function') { + await client.disconnect(); + } this.pools.delete(key); }, }; @@ -129,7 +133,7 @@ export class ConnectionFactory implements IConnectionFactory { } } - createCache(poolConfig: CachePoolConfig): ConnectionPool { + async createCache(poolConfig: CachePoolConfig): Promise> { const key = `cache:${poolConfig.name}`; if (this.pools.has(key)) { @@ -142,16 +146,51 @@ export class ConnectionFactory implements IConnectionFactory { }); try { - // TODO: Implement cache creation with dynamic import - throw new Error('Cache creation temporarily disabled'); + const { createCache } = await import('@stock-bot/cache'); + const client = createCache({ + redisConfig: poolConfig.config as any, + keyPrefix: 'app:', + ttl: 3600, + enableMetrics: true, + }); + + await client.waitForReady(10000); + + const pool: ConnectionPool = { + name: poolConfig.name, + client, + metrics: { + created: new Date(), + totalConnections: 1, + activeConnections: 1, + idleConnections: 0, + waitingRequests: 0, + errors: 0, + }, + health: async () => { + try { + await client.waitForReady(1000); + return true; + } catch { + return false; + } + }, + dispose: async () => { + // Cache provider manages its own connections + this.pools.delete(key); + }, + }; + + this.pools.set(key, pool); + return pool; } catch (error) { this.logger.error('Failed to create cache pool', { name: poolConfig.name, error }); throw error; } } - createQueue(poolConfig: QueuePoolConfig): ConnectionPool { + async createQueue(poolConfig: QueuePoolConfig): Promise> { const key = `queue:${poolConfig.name}`; if (this.pools.has(key)) { @@ -164,9 +203,46 @@ export class ConnectionFactory implements IConnectionFactory { }); try { - // TODO: Implement queue creation with dynamic import - throw new Error('Queue creation temporarily disabled'); + const { QueueManager } = await import('@stock-bot/queue'); + const manager = QueueManager.initialize({ + redis: poolConfig.config as any, + defaultQueueOptions: { + defaultJobOptions: { + removeOnComplete: 100, + removeOnFail: 50, + }, + }, + }); + + const pool: ConnectionPool = { + name: poolConfig.name, + client: manager, + metrics: { + created: new Date(), + totalConnections: 1, + activeConnections: 1, + idleConnections: 0, + waitingRequests: 0, + errors: 0, + }, + health: async () => { + try { + return true; // QueueManager doesn't have isHealthy method yet + } catch { + return false; + } + }, + dispose: async () => { + if (manager && typeof manager.shutdown === 'function') { + await manager.shutdown(); + } + this.pools.delete(key); + }, + }; + + this.pools.set(key, pool); + return pool; } catch (error) { this.logger.error('Failed to create queue manager', { name: poolConfig.name, error }); throw error; diff --git a/libs/core/di/src/operation-context.ts b/libs/core/di/src/operation-context.ts index be34264..682e47f 100644 --- a/libs/core/di/src/operation-context.ts +++ b/libs/core/di/src/operation-context.ts @@ -1,8 +1,5 @@ /** * OperationContext - Unified context for handler operations - * - * TEMPORARILY DISABLED to avoid circular dependencies during library build - * Will be re-enabled once all core libraries are built */ import { getLogger, type Logger } from '@stock-bot/logger'; @@ -13,25 +10,41 @@ export interface OperationContextOptions { operationName: string; parentLogger?: Logger; container?: ServiceResolver; + metadata?: Record; + traceId?: string; } export class OperationContext { public readonly logger: Logger; + public readonly traceId: string; + public readonly metadata: Record; private readonly container?: ServiceResolver; + private readonly startTime: Date; constructor(options: OperationContextOptions) { this.container = options.container; - this.logger = options.parentLogger || getLogger(`${options.handlerName}:${options.operationName}`); + this.metadata = options.metadata || {}; + this.traceId = options.traceId || this.generateTraceId(); + this.startTime = new Date(); + + this.logger = options.parentLogger || getLogger(`${options.handlerName}:${options.operationName}`, { + traceId: this.traceId, + metadata: this.metadata, + }); } /** * Creates a new OperationContext with automatic resource management - * TEMPORARILY SIMPLIFIED - full implementation will be restored after build fixes */ static create( handlerName: string, operationName: string, - options: { container?: ServiceResolver; parentLogger?: Logger } = {} + options: { + container?: ServiceResolver; + parentLogger?: Logger; + metadata?: Record; + traceId?: string; + } = {} ): OperationContext { return new OperationContext({ handlerName, @@ -41,21 +54,85 @@ export class OperationContext { } /** - * Cleanup method - simplified for now + * Resolve a service from the container */ - async dispose(): Promise { - // Cleanup will be implemented when dependencies are resolved + resolve(serviceName: string): T { + if (!this.container) { + throw new Error('No service container available'); + } + return this.container.resolve(serviceName); } /** - * Create child context - simplified for now + * Resolve a service asynchronously from the container */ - createChild(operationName: string): OperationContext { + async resolveAsync(serviceName: string): Promise { + if (!this.container) { + throw new Error('No service container available'); + } + return this.container.resolveAsync(serviceName); + } + + /** + * Add metadata to the context + */ + addMetadata(key: string, value: any): void { + this.metadata[key] = value; + } + + /** + * Get execution time in milliseconds + */ + getExecutionTime(): number { + return Date.now() - this.startTime.getTime(); + } + + /** + * Log operation completion with metrics + */ + logCompletion(success: boolean, error?: Error): void { + const executionTime = this.getExecutionTime(); + + if (success) { + this.logger.info('Operation completed successfully', { + executionTime, + metadata: this.metadata, + }); + } else { + this.logger.error('Operation failed', { + executionTime, + error: error?.message, + stack: error?.stack, + metadata: this.metadata, + }); + } + } + + /** + * Cleanup method + */ + async dispose(): Promise { + this.logCompletion(true); + } + + /** + * Create child context + */ + createChild(operationName: string, metadata?: Record): OperationContext { return new OperationContext({ handlerName: 'child', operationName, parentLogger: this.logger, container: this.container, + traceId: this.traceId, + metadata: { ...this.metadata, ...metadata }, }); } + + /** + * Generate a unique trace ID + */ + private generateTraceId(): string { + return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + } } \ No newline at end of file diff --git a/libs/core/di/src/service-container.ts b/libs/core/di/src/service-container.ts index d25ac3d..4b8175e 100644 --- a/libs/core/di/src/service-container.ts +++ b/libs/core/di/src/service-container.ts @@ -170,8 +170,8 @@ export function createServiceContainer( container.register({ name: 'cache', - factory: () => { - const pool = connectionFactory.createCache({ + factory: async () => { + const pool = await connectionFactory.createCache({ name: 'default', config: {} as any, // Config injected by factory }); @@ -182,8 +182,8 @@ export function createServiceContainer( container.register({ name: 'queue', - factory: () => { - const pool = connectionFactory.createQueue({ + factory: async () => { + const pool = await connectionFactory.createQueue({ name: 'default', config: {} as any, // Config injected by factory }); @@ -192,43 +192,10 @@ export function createServiceContainer( singleton: true, }); - // Optional services - comment out for now to avoid circular dependencies - // These can be registered manually by apps that need them - - // // Register ProxyManager - // container.register({ - // name: 'proxyManager', - // factory: async () => { - // const { ProxyManager } = await import('@stock-bot/utils'); - // await ProxyManager.initialize(); - // return ProxyManager.getInstance(); - // }, - // singleton: true, - // }); - - // // Register Browser service - // container.register({ - // name: 'browser', - // factory: async () => { - // const { Browser } = await import('@stock-bot/browser'); - // return Browser; - // }, - // singleton: true, - // }); - - // // Register HttpClient with default configuration - // container.register({ - // name: 'httpClient', - // factory: async () => { - // const { createHttpClient } = await import('@stock-bot/http'); - // return createHttpClient({ - // timeout: 30000, - // retries: 3, - // userAgent: 'stock-bot/1.0', - // }); - // }, - // singleton: true, - // }); + // Note: Additional services can be registered by individual applications as needed: + // - ProxyManager: container.register({ name: 'proxyManager', factory: () => ProxyManager.getInstance() }) + // - Browser: container.register({ name: 'browser', factory: () => Browser }) + // - HttpClient: container.register({ name: 'httpClient', factory: () => createHttpClient(...) }) return container; } \ No newline at end of file diff --git a/libs/core/di/src/types.ts b/libs/core/di/src/types.ts index 807839c..bca003b 100644 --- a/libs/core/di/src/types.ts +++ b/libs/core/di/src/types.ts @@ -60,8 +60,8 @@ export interface PoolMetrics { export interface ConnectionFactory { createMongoDB(config: MongoDBPoolConfig): Promise>; createPostgreSQL(config: PostgreSQLPoolConfig): Promise>; - createCache(config: CachePoolConfig): ConnectionPool; - createQueue(config: QueuePoolConfig): ConnectionPool; + createCache(config: CachePoolConfig): Promise>; + createQueue(config: QueuePoolConfig): Promise>; getPool(type: 'mongodb' | 'postgres' | 'cache' | 'queue', name: string): ConnectionPool | undefined; listPools(): Array<{ type: string; name: string; metrics: PoolMetrics }>; disposeAll(): Promise; diff --git a/libs/core/di/test/di.test.ts b/libs/core/di/test/di.test.ts index 08201ad..5843073 100644 --- a/libs/core/di/test/di.test.ts +++ b/libs/core/di/test/di.test.ts @@ -88,6 +88,37 @@ describe('DI Library', () => { expect(disposed).toBe(true); }); + test('OperationContext - enhanced functionality', () => { + const container = new ServiceContainer('test'); + const context = OperationContext.create('test-handler', 'test-operation', { + container, + metadata: { userId: '123' }, + }); + + expect(context).toBeDefined(); + expect(context.logger).toBeDefined(); + expect(context.traceId).toBeDefined(); + expect(context.metadata.userId).toBe('123'); + expect(context.getExecutionTime()).toBeGreaterThanOrEqual(0); + }); + + test('OperationContext - service resolution', () => { + const container = new ServiceContainer('test'); + + container.register({ + name: 'testService', + factory: () => ({ value: 'resolved' }), + singleton: true, + }); + + const context = OperationContext.create('test-handler', 'test-operation', { + container, + }); + + const service = context.resolve<{ value: string }>('testService'); + expect(service.value).toBe('resolved'); + }); + test('ConnectionFactory - creation', () => { const factory = new ConnectionFactory({ service: 'test', diff --git a/libs/services/queue/src/index.ts b/libs/services/queue/src/index.ts index d409bed..992b1ab 100644 --- a/libs/services/queue/src/index.ts +++ b/libs/services/queue/src/index.ts @@ -9,11 +9,8 @@ export { handlerRegistry } from '@stock-bot/handlers'; // Batch processing export { processBatchJob, processItems } from './batch-processor'; -// Queue factory functions -// QueueFactory removed - use QueueManager directly - // DLQ handling -export { DeadLetterQueueHandler, DeadLetterQueueHandler as DLQHandler } from './dlq-handler'; +export { DeadLetterQueueHandler } from './dlq-handler'; // Metrics export { QueueMetricsCollector } from './queue-metrics'; diff --git a/libs/utils/src/calculations/index.ts b/libs/utils/src/calculations/index.ts index e24dd5f..3fa4e29 100644 --- a/libs/utils/src/calculations/index.ts +++ b/libs/utils/src/calculations/index.ts @@ -83,33 +83,22 @@ export { coppockCurve } from './technical-indicators'; export * from './risk-metrics'; -// export * from './portfolio-analytics'; -// export * from './options-pricing'; -// export * from './position-sizing'; export * from './performance-metrics'; -// export * from './market-statistics'; -// export * from './volatility-models'; -// export * from './correlation-analysis'; -// TODO: Re-enable when performance-metrics and risk-metrics are fixed -// // Convenience function for comprehensive portfolio analysis -// export function analyzePortfolio( -// returns: number[], -// equityCurve: Array<{ value: number; date: Date }>, -// benchmarkReturns?: number[], -// riskFreeRate: number = 0.02 -// ): { -// performance: PortfolioAnalysis; -// risk: RiskMetrics; -// trades?: any; -// drawdown?: any; -// } { -// const performance = calculateStrategyMetrics(equityCurve, benchmarkReturns, riskFreeRate); -// const equityValues = equityCurve.map(point => point.value); -// const risk = calculateRiskMetrics(returns, equityValues, benchmarkReturns, riskFreeRate); - -// return { -// performance, -// risk, -// }; -// } +// Convenience function for comprehensive portfolio analysis +export function analyzePortfolio( + _returns: number[], + _equityCurve: Array<{ value: number; date: Date }>, + _benchmarkReturns?: number[], + _riskFreeRate: number = 0.02 +): { + performance: any; + risk: any; +} { + // Note: Implementation depends on performance-metrics and risk-metrics + // This is a placeholder for the full implementation + return { + performance: {}, + risk: {}, + }; +}