@n8n/n8n-nodes-langchain
Version:
175 lines • 6.8 kB
JavaScript
"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