added new di with connection pool and rebuild of the database/cache services
This commit is contained in:
parent
be6afef832
commit
09d907a10c
26 changed files with 4844 additions and 205 deletions
|
|
@ -11,11 +11,23 @@
|
|||
import { createCache, type CacheProvider } from '@stock-bot/cache';
|
||||
import { getLogger, type Logger } from '@stock-bot/logger';
|
||||
import { getDatabaseConfig } from '@stock-bot/config';
|
||||
import type { ServiceResolver } from '@stock-bot/connection-factory';
|
||||
import type { MongoDBClient } from '@stock-bot/mongodb-client';
|
||||
import type { PostgreSQLClient } from '@stock-bot/postgres-client';
|
||||
|
||||
export interface OperationContextOptions {
|
||||
handlerName: string;
|
||||
operationName: string;
|
||||
parentLogger?: Logger;
|
||||
container?: ServiceResolver;
|
||||
}
|
||||
|
||||
export class OperationContext {
|
||||
public readonly logger: Logger;
|
||||
public readonly mongodb: any; // MongoDB client - imported dynamically
|
||||
public readonly postgres: any; // PostgreSQL client - imported dynamically
|
||||
private readonly container?: ServiceResolver;
|
||||
private _mongodb?: MongoDBClient;
|
||||
private _postgres?: PostgreSQLClient;
|
||||
private _cache?: CacheProvider;
|
||||
|
||||
private static sharedCache: CacheProvider | null = null;
|
||||
private static parentLoggers = new Map<string, Logger>();
|
||||
|
|
@ -24,21 +36,64 @@ export class OperationContext {
|
|||
constructor(
|
||||
public readonly handlerName: string,
|
||||
public readonly operationName: string,
|
||||
parentLogger?: Logger
|
||||
parentLoggerOrOptions?: Logger | OperationContextOptions
|
||||
) {
|
||||
// Create child logger from parent or create handler parent
|
||||
const parent = parentLogger || this.getOrCreateParentLogger();
|
||||
this.logger = parent.child(operationName, {
|
||||
handler: handlerName,
|
||||
operation: operationName
|
||||
});
|
||||
|
||||
// Set up database access
|
||||
this.mongodb = this.getDatabaseClient('mongodb');
|
||||
this.postgres = this.getDatabaseClient('postgres');
|
||||
// Handle both old and new constructor signatures
|
||||
if (parentLoggerOrOptions && 'container' in parentLoggerOrOptions) {
|
||||
const options = parentLoggerOrOptions;
|
||||
this.container = options.container;
|
||||
const parent = options.parentLogger || this.getOrCreateParentLogger();
|
||||
this.logger = parent.child(operationName, {
|
||||
handler: handlerName,
|
||||
operation: operationName
|
||||
});
|
||||
} else {
|
||||
// Legacy support
|
||||
const parentLogger = parentLoggerOrOptions as Logger | undefined;
|
||||
const parent = parentLogger || this.getOrCreateParentLogger();
|
||||
this.logger = parent.child(operationName, {
|
||||
handler: handlerName,
|
||||
operation: operationName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getDatabaseClient(type: 'mongodb' | 'postgres'): any {
|
||||
// Lazy load MongoDB client
|
||||
get mongodb(): MongoDBClient {
|
||||
if (!this._mongodb) {
|
||||
if (this.container) {
|
||||
try {
|
||||
this._mongodb = this.container.resolve<MongoDBClient>('mongodb');
|
||||
} catch (error) {
|
||||
this.logger.warn('Failed to resolve MongoDB from container, falling back to singleton', { error });
|
||||
this._mongodb = this.getLegacyDatabaseClient('mongodb') as MongoDBClient;
|
||||
}
|
||||
} else {
|
||||
this._mongodb = this.getLegacyDatabaseClient('mongodb') as MongoDBClient;
|
||||
}
|
||||
}
|
||||
return this._mongodb!;
|
||||
}
|
||||
|
||||
// Lazy load PostgreSQL client
|
||||
get postgres(): PostgreSQLClient {
|
||||
if (!this._postgres) {
|
||||
if (this.container) {
|
||||
try {
|
||||
this._postgres = this.container.resolve<PostgreSQLClient>('postgres');
|
||||
} catch (error) {
|
||||
this.logger.warn('Failed to resolve PostgreSQL from container, falling back to singleton', { error });
|
||||
this._postgres = this.getLegacyDatabaseClient('postgres') as PostgreSQLClient;
|
||||
}
|
||||
} else {
|
||||
this._postgres = this.getLegacyDatabaseClient('postgres') as PostgreSQLClient;
|
||||
}
|
||||
}
|
||||
return this._postgres!;
|
||||
}
|
||||
|
||||
// Legacy method for backward compatibility
|
||||
private getLegacyDatabaseClient(type: 'mongodb' | 'postgres'): any {
|
||||
try {
|
||||
if (type === 'mongodb') {
|
||||
// Dynamic import to avoid TypeScript issues during build
|
||||
|
|
@ -71,6 +126,23 @@ export class OperationContext {
|
|||
* Keys are automatically prefixed as: "operations:handlerName:operationName:key"
|
||||
*/
|
||||
get cache(): CacheProvider {
|
||||
if (!this._cache) {
|
||||
if (this.container) {
|
||||
try {
|
||||
const baseCache = this.container.resolve<CacheProvider>('cache');
|
||||
this._cache = this.createContextualCache(baseCache);
|
||||
} catch (error) {
|
||||
this.logger.warn('Failed to resolve cache from container, using shared cache', { error });
|
||||
this._cache = this.getOrCreateSharedCache();
|
||||
}
|
||||
} else {
|
||||
this._cache = this.getOrCreateSharedCache();
|
||||
}
|
||||
}
|
||||
return this._cache!;
|
||||
}
|
||||
|
||||
private getOrCreateSharedCache(): CacheProvider {
|
||||
if (!OperationContext.sharedCache) {
|
||||
// Get Redis configuration from database config
|
||||
if (!OperationContext.databaseConfig) {
|
||||
|
|
@ -91,28 +163,28 @@ export class OperationContext {
|
|||
redisConfig
|
||||
});
|
||||
}
|
||||
return this.createContextualCache();
|
||||
return this.createContextualCache(OperationContext.sharedCache);
|
||||
}
|
||||
|
||||
private createContextualCache(): CacheProvider {
|
||||
private createContextualCache(baseCache: CacheProvider): CacheProvider {
|
||||
const contextPrefix = `${this.handlerName}:${this.operationName}:`;
|
||||
|
||||
// Return a proxy that automatically prefixes keys with context
|
||||
return {
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
return OperationContext.sharedCache!.get(`${contextPrefix}${key}`);
|
||||
return baseCache.get(`${contextPrefix}${key}`);
|
||||
},
|
||||
|
||||
async set<T>(key: string, value: T, options?: any): Promise<T | null> {
|
||||
return OperationContext.sharedCache!.set(`${contextPrefix}${key}`, value, options);
|
||||
return baseCache.set(`${contextPrefix}${key}`, value, options);
|
||||
},
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
return OperationContext.sharedCache!.del(`${contextPrefix}${key}`);
|
||||
return baseCache.del(`${contextPrefix}${key}`);
|
||||
},
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
return OperationContext.sharedCache!.exists(`${contextPrefix}${key}`);
|
||||
return baseCache.exists(`${contextPrefix}${key}`);
|
||||
},
|
||||
|
||||
async clear(): Promise<void> {
|
||||
|
|
@ -122,23 +194,23 @@ export class OperationContext {
|
|||
|
||||
async keys(pattern: string): Promise<string[]> {
|
||||
const fullPattern = `${contextPrefix}${pattern}`;
|
||||
return OperationContext.sharedCache!.keys(fullPattern);
|
||||
return baseCache.keys(fullPattern);
|
||||
},
|
||||
|
||||
getStats() {
|
||||
return OperationContext.sharedCache!.getStats();
|
||||
return baseCache.getStats();
|
||||
},
|
||||
|
||||
async health(): Promise<boolean> {
|
||||
return OperationContext.sharedCache!.health();
|
||||
return baseCache.health();
|
||||
},
|
||||
|
||||
async waitForReady(timeout?: number): Promise<void> {
|
||||
return OperationContext.sharedCache!.waitForReady(timeout);
|
||||
return baseCache.waitForReady(timeout);
|
||||
},
|
||||
|
||||
isReady(): boolean {
|
||||
return OperationContext.sharedCache!.isReady();
|
||||
return baseCache.isReady();
|
||||
}
|
||||
} as CacheProvider;
|
||||
}
|
||||
|
|
@ -146,8 +218,15 @@ export class OperationContext {
|
|||
/**
|
||||
* Factory method to create OperationContext
|
||||
*/
|
||||
static create(handlerName: string, operationName: string, parentLogger?: Logger): OperationContext {
|
||||
return new OperationContext(handlerName, operationName, parentLogger);
|
||||
static create(handlerName: string, operationName: string, parentLoggerOrOptions?: Logger | OperationContextOptions): OperationContext {
|
||||
if (parentLoggerOrOptions && 'container' in parentLoggerOrOptions) {
|
||||
return new OperationContext(handlerName, operationName, {
|
||||
...parentLoggerOrOptions,
|
||||
handlerName,
|
||||
operationName
|
||||
});
|
||||
}
|
||||
return new OperationContext(handlerName, operationName, parentLoggerOrOptions as Logger | undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -161,12 +240,38 @@ export class OperationContext {
|
|||
* Create a child context for sub-operations
|
||||
*/
|
||||
createChild(subOperationName: string): OperationContext {
|
||||
if (this.container) {
|
||||
return new OperationContext(
|
||||
this.handlerName,
|
||||
`${this.operationName}:${subOperationName}`,
|
||||
{
|
||||
handlerName: this.handlerName,
|
||||
operationName: `${this.operationName}:${subOperationName}`,
|
||||
parentLogger: this.logger,
|
||||
container: this.container
|
||||
}
|
||||
);
|
||||
}
|
||||
return new OperationContext(
|
||||
this.handlerName,
|
||||
`${this.operationName}:${subOperationName}`,
|
||||
this.logger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of resources if using container-based connections
|
||||
* This is a no-op for legacy singleton connections
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
// If using container, it will handle cleanup
|
||||
// For singleton connections, they persist
|
||||
this.logger.debug('OperationContext disposed', {
|
||||
handler: this.handlerName,
|
||||
operation: this.operationName,
|
||||
hasContainer: !!this.container
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default OperationContext;
|
||||
Loading…
Add table
Add a link
Reference in a new issue