stock-bot/libs/core/di/src/operation-context.ts
2025-06-22 17:55:51 -04:00

144 lines
3.4 KiB
TypeScript

/**
* OperationContext - Unified context for handler operations
*/
import { getLogger, type Logger } from '@stock-bot/logger';
interface ServiceResolver {
resolve<T>(serviceName: string): T;
resolveAsync<T>(serviceName: string): Promise<T>;
}
export interface OperationContextOptions {
handlerName: string;
operationName: string;
parentLogger?: Logger;
container?: ServiceResolver;
metadata?: Record<string, any>;
traceId?: string;
}
export class OperationContext {
public readonly logger: Logger;
public readonly traceId: string;
public readonly metadata: Record<string, any>;
private readonly container?: ServiceResolver;
private readonly startTime: Date;
constructor(options: OperationContextOptions) {
this.container = options.container;
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
*/
static create(
handlerName: string,
operationName: string,
options: {
container?: ServiceResolver;
parentLogger?: Logger;
metadata?: Record<string, any>;
traceId?: string;
} = {}
): OperationContext {
return new OperationContext({
handlerName,
operationName,
...options,
});
}
/**
* Resolve a service from the container
*/
resolve<T>(serviceName: string): T {
if (!this.container) {
throw new Error('No service container available');
}
return this.container.resolve<T>(serviceName);
}
/**
* Resolve a service asynchronously from the container
*/
async resolveAsync<T>(serviceName: string): Promise<T> {
if (!this.container) {
throw new Error('No service container available');
}
return this.container.resolveAsync<T>(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<void> {
this.logCompletion(true);
}
/**
* Create child context
*/
createChild(operationName: string, metadata?: Record<string, any>): 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)}`;
}
}