UNPKG

@inso_web/els-mcp

Version:

MCP-сервер поверх INSO Error Logs Service. Read-only tools (search, analytics, fingerprinting, correlations) для подключения Claude Desktop/Code и ChatGPT к логам ошибок. Streamable HTTP transport + stdio для npx-запуска.

134 lines 4.28 kB
import IORedisModule from 'ioredis'; // ioredis с verbatimModuleSyntax=false и NodeNext: класс находится в default экспорте. // `IORedisModule` в зависимости от bundler может быть либо классом, либо { default: class }. const IORedis = IORedisModule.default ?? IORedisModule; export class RedisService { client; log; connected = false; disabled; constructor(opts) { this.log = opts.log; this.disabled = opts.disabled === true; if (this.disabled) { this.client = null; this.log?.info?.('Redis cache disabled via config'); return; } if (opts.client) { this.client = opts.client; this.connected = true; this.attachListeners(); return; } const ioOpts = { lazyConnect: true, maxRetriesPerRequest: 2, enableOfflineQueue: false, retryStrategy: (times) => Math.min(times * 200, 10_000), }; const client = new IORedis(opts.url, ioOpts); this.client = client; this.attachListeners(); // Lazy connect — стартуем коннект в фоне, не валим startup client.connect().catch((err) => { this.log?.warn?.({ err: err?.message }, 'Redis initial connect failed; cache will be no-op until reconnected'); }); } attachListeners() { if (!this.client) return; this.client.on('ready', () => { this.connected = true; this.log?.info?.('Redis connected'); }); this.client.on('end', () => { this.connected = false; this.log?.warn?.('Redis connection ended'); }); this.client.on('error', (err) => { this.connected = false; this.log?.warn?.({ err: err.message }, 'Redis error'); }); } /** True если Redis недоступен или disabled. */ get unavailable() { return this.disabled || !this.client || !this.connected; } get raw() { return this.client; } /** * Безопасный GET, возвращает null при любых ошибках/недоступности. */ async get(key) { if (this.unavailable || !this.client) return null; try { return await this.client.get(key); } catch (err) { this.log?.warn?.({ err: err.message, key }, 'Redis GET failed'); return null; } } /** * Безопасный SETEX. Возвращает true если записали, false при ошибке. */ async setex(key, ttlSec, value) { if (this.unavailable || !this.client) return false; try { await this.client.setex(key, ttlSec, value); return true; } catch (err) { this.log?.warn?.({ err: err.message, key }, 'Redis SETEX failed'); return false; } } /** Health-check (ping → PONG). Возвращает true если ответил. */ async ping() { if (this.disabled || !this.client) return false; try { const res = await this.client.ping(); return res === 'PONG'; } catch { return false; } } async close() { if (!this.client) return; try { await this.client.quit(); } catch { this.client.disconnect(); } } } let singleton = null; /** * Получить (или создать) singleton экземпляр. * * В тестах используем `setRedisServiceForTests(...)`. */ export function getRedisService(opts) { if (!singleton) { if (!opts) { throw new Error('getRedisService(): первый вызов требует opts'); } singleton = new RedisService(opts); } return singleton; } export function setRedisServiceForTests(service) { singleton = service; } export function resetRedisSingleton() { singleton = null; } //# sourceMappingURL=redis.js.map