Skip to main content
Glama
service-container.ts7.23 kB
import { KuzuDBClient } from '../../db/kuzu'; import { RepositoryProvider } from '../../db/repository-provider'; import { ToolHandlerContext } from '../../mcp/types/sdk-custom'; import { Mutex } from '../../utils/mutex'; import { ensureAbsolutePath } from '../../utils/path.utils'; import { SnapshotService } from '../snapshot.service'; import { IContextService, IEntityService, IGraphAnalysisService, IGraphQueryService, IMemoryBankService, IMetadataService, IServiceContainer, } from './service-container.interface'; /** * Service container implementation using lazy loading and dependency injection * Eliminates circular dependencies by providing services through interfaces */ export class ServiceContainer implements IServiceContainer { private static instance: ServiceContainer; private static lock = new Mutex(); // Core infrastructure private repositoryProvider!: RepositoryProvider; private kuzuClients: Map<string, KuzuDBClient> = new Map(); private snapshotServices: Map<string, SnapshotService> = new Map(); // Lazy-loaded service instances private serviceInstances: Map<string, any> = new Map(); private servicePromises: Map<string, Promise<any>> = new Map(); private constructor() {} /** * Get singleton instance with proper initialization */ static async getInstance(): Promise<ServiceContainer> { if (!ServiceContainer.instance) { const release = await ServiceContainer.lock.acquire(); try { if (!ServiceContainer.instance) { ServiceContainer.instance = new ServiceContainer(); await ServiceContainer.instance.initialize(); } } finally { release(); } } return ServiceContainer.instance; } /** * Initialize core infrastructure */ private async initialize(): Promise<void> { this.repositoryProvider = await RepositoryProvider.getInstance(); } /** * Get repository provider */ getRepositoryProvider(): RepositoryProvider { return this.repositoryProvider; } /** * Get or create KuzuDB client for a project root */ async getKuzuClient( mcpContext: ToolHandlerContext, clientProjectRoot: string, ): Promise<KuzuDBClient> { const logger = mcpContext.logger || console; clientProjectRoot = ensureAbsolutePath(clientProjectRoot); if (this.kuzuClients.has(clientProjectRoot)) { return this.kuzuClients.get(clientProjectRoot)!; } logger.info( `[ServiceContainer.getKuzuClient] Creating new KuzuDBClient for: ${clientProjectRoot}`, ); try { const newClient = new KuzuDBClient(clientProjectRoot); await newClient.initialize(mcpContext); this.kuzuClients.set(clientProjectRoot, newClient); await this.repositoryProvider.initializeRepositories(clientProjectRoot, newClient); // Initialize SnapshotService for this new client if (!this.snapshotServices.has(clientProjectRoot)) { const snapshotService = new SnapshotService(newClient); this.snapshotServices.set(clientProjectRoot, snapshotService); } return newClient; } catch (error) { logger.error( `[ServiceContainer.getKuzuClient] Failed to initialize KuzuDBClient for ${clientProjectRoot}:`, error, ); throw new Error( `Failed to initialize database for project root ${clientProjectRoot}: ${error instanceof Error ? error.message : String(error)}`, ); } } /** * Get SnapshotService for a project root */ async getSnapshotService( mcpContext: ToolHandlerContext, clientProjectRoot: string, ): Promise<SnapshotService> { clientProjectRoot = ensureAbsolutePath(clientProjectRoot); if (!this.snapshotServices.has(clientProjectRoot)) { // Ensure KuzuClient exists first await this.getKuzuClient(mcpContext, clientProjectRoot); } return this.snapshotServices.get(clientProjectRoot)!; } /** * Lazy-load service with singleton pattern */ private async getServiceInstance<T>( serviceKey: string, serviceFactory: () => Promise<T>, ): Promise<T> { if (this.serviceInstances.has(serviceKey)) { return this.serviceInstances.get(serviceKey); } // Check if service is currently being created if (this.servicePromises.has(serviceKey)) { return this.servicePromises.get(serviceKey); } // Create service promise const servicePromise = serviceFactory(); this.servicePromises.set(serviceKey, servicePromise); try { const serviceInstance = await servicePromise; this.serviceInstances.set(serviceKey, serviceInstance); this.servicePromises.delete(serviceKey); return serviceInstance; } catch (error) { this.servicePromises.delete(serviceKey); throw error; } } /** * Get MemoryBankService instance (lazy-loaded) */ async getMemoryBankService(): Promise<IMemoryBankService> { return this.getServiceInstance('memoryBank', async () => { const { MemoryBankService } = await import('../domain/memory-bank.service'); return new MemoryBankService(this); }); } /** * Get MetadataService instance (lazy-loaded) */ async getMetadataService(): Promise<IMetadataService> { return this.getServiceInstance('metadata', async () => { const { MetadataService } = await import('../domain/metadata.service'); return new MetadataService(this); }); } /** * Get EntityService instance (lazy-loaded) */ async getEntityService(): Promise<IEntityService> { return this.getServiceInstance('entity', async () => { const { EntityService } = await import('../domain/entity.service'); return new EntityService(this); }); } /** * Get ContextService instance (lazy-loaded) */ async getContextService(): Promise<IContextService> { return this.getServiceInstance('context', async () => { const { ContextService } = await import('../domain/context.service'); return new ContextService(this); }); } /** * Get GraphQueryService instance (lazy-loaded) */ async getGraphQueryService(): Promise<IGraphQueryService> { return this.getServiceInstance('graphQuery', async () => { const { GraphQueryService } = await import('../domain/graph-query.service'); return new GraphQueryService(this); }); } /** * Get GraphAnalysisService instance (lazy-loaded) */ async getGraphAnalysisService(): Promise<IGraphAnalysisService> { return this.getServiceInstance('graphAnalysis', async () => { const { GraphAnalysisService } = await import('../domain/graph-analysis.service'); return new GraphAnalysisService(this); }); } /** * Shutdown all services and cleanup resources */ async shutdown(): Promise<void> { // Close all KuzuDB clients for (const [path, client] of Array.from(this.kuzuClients.entries())) { try { await client.close(); } catch (error) { console.warn(`Failed to close KuzuDB client for ${path}:`, error); } } this.kuzuClients.clear(); this.snapshotServices.clear(); this.serviceInstances.clear(); this.servicePromises.clear(); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jakedismo/KuzuMem-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server