libs fully refactored
This commit is contained in:
parent
63baeaec70
commit
1b34da9a69
10 changed files with 181 additions and 21 deletions
234
libs/core/di/src/service-container.ts
Normal file
234
libs/core/di/src/service-container.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
import { getLogger, type Logger } from '@stock-bot/logger';
|
||||
import type { ConnectionFactory } from './connection-factory';
|
||||
|
||||
export interface ServiceRegistration<T = any> {
|
||||
name: string;
|
||||
factory: () => T | Promise<T>;
|
||||
singleton?: boolean;
|
||||
dispose?: (instance: T) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface ServiceResolver {
|
||||
resolve<T>(name: string, options?: any): T;
|
||||
resolveAsync<T>(name: string, options?: any): Promise<T>;
|
||||
}
|
||||
|
||||
export class ServiceContainer implements ServiceResolver {
|
||||
private readonly logger: Logger;
|
||||
private readonly registrations = new Map<string, ServiceRegistration>();
|
||||
private readonly instances = new Map<string, any>();
|
||||
private readonly scopedInstances = new Map<string, any>();
|
||||
private readonly parent?: ServiceContainer;
|
||||
|
||||
constructor(name: string, parent?: ServiceContainer) {
|
||||
this.logger = getLogger(`service-container:${name}`);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
register<T>(registration: ServiceRegistration<T>): void {
|
||||
this.registrations.set(registration.name, registration);
|
||||
this.logger.debug('Service registered', { name: registration.name, singleton: registration.singleton });
|
||||
}
|
||||
|
||||
resolve<T>(name: string, _options?: any): T {
|
||||
// Check scoped instances first
|
||||
if (this.scopedInstances.has(name)) {
|
||||
return this.scopedInstances.get(name);
|
||||
}
|
||||
|
||||
// Check singleton instances
|
||||
if (this.instances.has(name)) {
|
||||
return this.instances.get(name);
|
||||
}
|
||||
|
||||
// Get registration from this container or parent
|
||||
const registration = this.getRegistration(name);
|
||||
if (!registration) {
|
||||
throw new Error(`Service ${name} not registered`);
|
||||
}
|
||||
|
||||
// Create instance synchronously
|
||||
const instance = registration.factory();
|
||||
|
||||
// Check if factory returned a promise
|
||||
if (instance instanceof Promise) {
|
||||
throw new Error(`Service ${name} is async. Use resolveAsync() instead.`);
|
||||
}
|
||||
|
||||
// Store based on singleton flag
|
||||
if (registration.singleton) {
|
||||
this.instances.set(name, instance);
|
||||
} else {
|
||||
this.scopedInstances.set(name, instance);
|
||||
}
|
||||
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
async resolveAsync<T>(name: string, _options?: any): Promise<T> {
|
||||
// Check scoped instances first
|
||||
if (this.scopedInstances.has(name)) {
|
||||
return this.scopedInstances.get(name);
|
||||
}
|
||||
|
||||
// Check singleton instances
|
||||
if (this.instances.has(name)) {
|
||||
return this.instances.get(name);
|
||||
}
|
||||
|
||||
// Get registration from this container or parent
|
||||
const registration = this.getRegistration(name);
|
||||
if (!registration) {
|
||||
throw new Error(`Service ${name} not registered`);
|
||||
}
|
||||
|
||||
// Create instance
|
||||
const instance = await Promise.resolve(registration.factory());
|
||||
|
||||
// Store based on singleton flag
|
||||
if (registration.singleton) {
|
||||
this.instances.set(name, instance);
|
||||
} else {
|
||||
this.scopedInstances.set(name, instance);
|
||||
}
|
||||
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
createScope(): ServiceContainer {
|
||||
return new ServiceContainer('scoped', this);
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
// Dispose scoped instances
|
||||
for (const [name, instance] of this.scopedInstances.entries()) {
|
||||
const registration = this.getRegistration(name);
|
||||
if (registration?.dispose) {
|
||||
await registration.dispose(instance);
|
||||
}
|
||||
}
|
||||
this.scopedInstances.clear();
|
||||
|
||||
// Only dispose singletons if this is the root container
|
||||
if (!this.parent) {
|
||||
for (const [name, instance] of this.instances.entries()) {
|
||||
const registration = this.registrations.get(name);
|
||||
if (registration?.dispose) {
|
||||
await registration.dispose(instance);
|
||||
}
|
||||
}
|
||||
this.instances.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private getRegistration(name: string): ServiceRegistration | undefined {
|
||||
return this.registrations.get(name) || this.parent?.getRegistration(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced service container factory with infrastructure services
|
||||
export function createServiceContainer(
|
||||
serviceName: string,
|
||||
connectionFactory: ConnectionFactory,
|
||||
config?: any
|
||||
): ServiceContainer {
|
||||
const container = new ServiceContainer(serviceName);
|
||||
|
||||
// Register configuration if provided
|
||||
if (config) {
|
||||
container.register({
|
||||
name: 'config',
|
||||
factory: () => config,
|
||||
singleton: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Register connection factories
|
||||
container.register({
|
||||
name: 'mongodb',
|
||||
factory: async () => {
|
||||
const pool = await connectionFactory.createMongoDB({
|
||||
name: 'default',
|
||||
config: {} as any, // Config injected by factory
|
||||
});
|
||||
return pool.client;
|
||||
},
|
||||
singleton: true,
|
||||
});
|
||||
|
||||
container.register({
|
||||
name: 'postgres',
|
||||
factory: async () => {
|
||||
const pool = await connectionFactory.createPostgreSQL({
|
||||
name: 'default',
|
||||
config: {} as any, // Config injected by factory
|
||||
});
|
||||
return pool.client;
|
||||
},
|
||||
singleton: true,
|
||||
});
|
||||
|
||||
container.register({
|
||||
name: 'cache',
|
||||
factory: () => {
|
||||
const pool = connectionFactory.createCache({
|
||||
name: 'default',
|
||||
config: {} as any, // Config injected by factory
|
||||
});
|
||||
return pool.client;
|
||||
},
|
||||
singleton: true,
|
||||
});
|
||||
|
||||
container.register({
|
||||
name: 'queue',
|
||||
factory: () => {
|
||||
const pool = connectionFactory.createQueue({
|
||||
name: 'default',
|
||||
config: {} as any, // Config injected by factory
|
||||
});
|
||||
return pool.client;
|
||||
},
|
||||
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,
|
||||
// });
|
||||
|
||||
return container;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue