UNPKG

@n8n/n8n-nodes-langchain

Version:
175 lines 6.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.McpClientsManager = void 0; const config_1 = require("@n8n/config"); const di_1 = require("@n8n/di"); const EVICTION_INTERVAL_MS = 30_000; let McpClientsManager = class McpClientsManager { constructor(config) { this.config = config; this.activeClients = new Map(); this.pendingConnections = new Map(); this.connectionEpoch = 0; this.exitHandler = () => this.shutdown(); this.cleanupTimer = setInterval(() => this.evictStale(), EVICTION_INTERVAL_MS); this.cleanupTimer.unref(); process.on('exit', this.exitHandler); } get size() { return this.activeClients.size; } getEntry(key) { return this.activeClients.get(key); } async getOrConnect(key, factory, opts = {}) { const cached = this.activeClients.get(key); if (cached) { opts.logger?.debug('McpClientsManager: cache hit', { cacheKey: key }); cached.lastUsedAt = Date.now(); this.registerLifecycleCleanup(key, cached.epoch, opts); return { client: cached.client, mcpTools: cached.mcpTools }; } const inFlight = this.pendingConnections.get(key); if (inFlight) { opts.logger?.debug('McpClientsManager: awaiting in-flight connection', { cacheKey: key }); return await inFlight; } opts.logger?.debug('McpClientsManager: cache miss, connecting', { cacheKey: key }); const pending = factory(); this.pendingConnections.set(key, pending); try { const result = await pending; const epoch = ++this.connectionEpoch; this.activeClients.set(key, { client: result.client, mcpTools: result.mcpTools, lastUsedAt: Date.now(), epoch, detachLifecycle: this.attachTransportLifecycle(result.client, key, epoch, opts.logger), }); this.enforceMaxSize(); this.registerLifecycleCleanup(key, epoch, opts); return result; } finally { this.pendingConnections.delete(key); } } refresh(key) { const entry = this.activeClients.get(key); if (entry) entry.lastUsedAt = Date.now(); } remove(key, logger) { const entry = this.activeClients.get(key); if (!entry) return; this.activeClients.delete(key); entry.detachLifecycle(); void entry.client.close().catch((error) => { logger?.warn('McpClientsManager: failed to close cached client', { cacheKey: key, error }); }); } registerLifecycleCleanup(key, epoch, opts) { const closeIfCurrent = () => this.removeIfCurrent(key, epoch, opts.logger); opts.onExecutionCancellation?.(closeIfCurrent); opts.onExecutionFinish?.(closeIfCurrent); } removeIfCurrent(key, epoch, logger) { if (this.activeClients.get(key)?.epoch === epoch) { this.remove(key, logger); } } evictStale() { if (this.activeClients.size === 0) return; const now = Date.now(); for (const [key, entry] of this.activeClients) { if (now - entry.lastUsedAt > this.config.cacheTtl) { this.remove(key); } } this.enforceMaxSize(); } enforceMaxSize() { let excess = this.activeClients.size - Math.max(1, this.config.cacheMaxSize); for (const [key] of this.activeClients) { if (excess <= 0) break; this.remove(key); excess--; } } attachTransportLifecycle(client, key, epoch, logger) { const prevOnClose = client.onclose; const prevOnError = client.onerror; const detach = () => { if (client.onclose === onClose) client.onclose = prevOnClose; if (client.onerror === onError) client.onerror = prevOnError; }; const onClose = () => { logger?.debug('McpClientsManager: transport closed, evicting cache entry', { cacheKey: key }); detach(); if (this.activeClients.get(key)?.epoch === epoch) { this.activeClients.delete(key); } try { prevOnClose?.(); } catch (error) { logger?.warn('McpClientsManager: chained onclose threw', { error }); } }; const onError = (error) => { logger?.warn('McpClientsManager: transport error, closing client', { cacheKey: key, error, }); detach(); if (this.activeClients.get(key)?.epoch === epoch) { this.remove(key, logger); } else { void client.close().catch((closeError) => { logger?.warn('McpClientsManager: failed to close errored client', { cacheKey: key, error: closeError, }); }); } try { prevOnError?.(error); } catch (chained) { logger?.warn('McpClientsManager: chained onerror threw', { error: chained }); } }; client.onclose = onClose; client.onerror = onError; return detach; } shutdown() { clearInterval(this.cleanupTimer); process.off('exit', this.exitHandler); for (const key of [...this.activeClients.keys()]) { this.remove(key); } } }; exports.McpClientsManager = McpClientsManager; exports.McpClientsManager = McpClientsManager = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [config_1.McpClientConfig]) ], McpClientsManager); //# sourceMappingURL=McpClientsManager.js.map