UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

216 lines 8.37 kB
import Typesense from 'typesense'; import { config } from './config.js'; import { logger } from './logger.js'; const defaultTypesenseConfig = { connectionTimeoutSeconds: config.typesenseConnectionTimeout || 10, retryIntervalSeconds: 2, // Retry failed requests after 2 seconds numRetries: config.typesenseRetries || 3, healthcheckIntervalSeconds: 30 // Health check every 30 seconds }; class TypesenseConnectionManager { constructor(customConfig) { this.client = null; this.lastHealthCheck = 0; this.isHealthy = false; this.config = { ...defaultTypesenseConfig, ...customConfig }; this.initializeClient(); this.startHealthCheckInterval(); } initializeClient() { try { this.client = new Typesense.Client({ nodes: [ { host: config.typesenseHost, port: parseInt(config.typesensePort), protocol: config.typesenseProtocol } ], apiKey: config.typesenseApiKey, connectionTimeoutSeconds: this.config.connectionTimeoutSeconds, retryIntervalSeconds: this.config.retryIntervalSeconds, numRetries: this.config.numRetries, healthcheckIntervalSeconds: this.config.healthcheckIntervalSeconds, // Additional configuration for better performance sendApiKeyAsQueryParam: true, // Better for some proxy configurations useServerSideSearchCache: true // Enable server-side caching }); logger.info('Typesense client initialized', { host: config.typesenseHost, port: config.typesensePort, protocol: config.typesenseProtocol, connectionTimeout: this.config.connectionTimeoutSeconds, retries: this.config.numRetries }); } catch (error) { logger.error('Failed to initialize Typesense client', { error }); throw error; } } startHealthCheckInterval() { setInterval(async () => { await this.performHealthCheck(); }, this.config.healthcheckIntervalSeconds * 1000); } async performHealthCheck() { try { if (this.client) { // Perform a simple health check by listing collections await this.client.collections().retrieve(); this.isHealthy = true; this.lastHealthCheck = Date.now(); logger.debug('Typesense health check passed'); } } catch (error) { this.isHealthy = false; logger.error('Typesense health check failed', { error }); // Attempt to reinitialize client try { this.initializeClient(); logger.info('Typesense client reinitialized after health check failure'); } catch (reinitError) { logger.error('Failed to reinitialize Typesense client', { error: reinitError }); } } } async searchDocuments(collection, query, options = {}) { if (!this.client) { throw new Error('Typesense client not initialized'); } if (!this.isHealthy) { logger.warn('Attempting search on unhealthy Typesense connection'); } const searchParams = { q: query, query_by: options.query_by || 'content', limit: Math.min(options.limit || 50, 250), // Cap at 250 to prevent memory issues page: options.page || 1, ...options }; try { const startTime = Date.now(); const result = await this.client.collections(collection).documents().search(searchParams); const duration = Date.now() - startTime; logger.info('Typesense search completed', { collection, query: query.length > 50 ? query.substring(0, 50) + '...' : query, found: result.found, duration, page: searchParams.page, limit: searchParams.limit }); return result; } catch (error) { logger.error('Typesense search failed', { collection, query: query.length > 50 ? query.substring(0, 50) + '...' : query, error: error instanceof Error ? error.message : String(error), searchParams }); throw error; } } async pushToTypesense(data, action = 'upsert', collectionName = 'casefiles') { if (!this.client) { throw new Error('Typesense client not initialized'); } try { const startTime = Date.now(); await this.client.collections(collectionName).documents().import([data], { action }); const duration = Date.now() - startTime; logger.info('Data pushed to Typesense successfully', { collection: collectionName, action, duration, dataId: data.id || 'unknown' }); } catch (error) { logger.error('Failed to push data to Typesense', { collection: collectionName, action, error: error instanceof Error ? error.message : String(error), dataId: data.id || 'unknown' }); throw error; } } async getCollectionInfo(collectionName) { if (!this.client) { throw new Error('Typesense client not initialized'); } try { return await this.client.collections(collectionName).retrieve(); } catch (error) { logger.error('Failed to retrieve collection info', { collection: collectionName, error: error instanceof Error ? error.message : String(error) }); throw error; } } getConnectionStats() { return { healthy: this.isHealthy, lastHealthCheck: this.lastHealthCheck, config: this.config, client: { host: config.typesenseHost, port: config.typesensePort, protocol: config.typesenseProtocol } }; } async shutdown() { logger.info('Typesense connection manager shutdown complete'); } } // Create singleton instance const typesenseManager = new TypesenseConnectionManager(); // Export the improved functions export async function searchDocuments(collection, query, options = {}) { return typesenseManager.searchDocuments(collection, query, options); } export async function pushToTypesense(data, action = 'upsert', collectionName = 'casefiles') { return typesenseManager.pushToTypesense(data, action, collectionName); } export async function getCollectionInfo(collectionName) { return typesenseManager.getCollectionInfo(collectionName); } export function getTypesenseStats() { return typesenseManager.getConnectionStats(); } // Legacy export for backward compatibility - expose the actual client export const typesenseClient = new Typesense.Client({ nodes: [ { host: config.typesenseHost, port: parseInt(config.typesensePort), protocol: config.typesenseProtocol } ], apiKey: config.typesenseApiKey, connectionTimeoutSeconds: defaultTypesenseConfig.connectionTimeoutSeconds, retryIntervalSeconds: defaultTypesenseConfig.retryIntervalSeconds, numRetries: defaultTypesenseConfig.numRetries, healthcheckIntervalSeconds: defaultTypesenseConfig.healthcheckIntervalSeconds, sendApiKeyAsQueryParam: true, useServerSideSearchCache: true }); // Constants const TYPESENSE_COLLECTION = 'casefiles'; // Handle graceful shutdown process.on('SIGINT', async () => { logger.info('Shutting down Typesense connection manager'); await typesenseManager.shutdown(); }); process.on('SIGTERM', async () => { logger.info('Shutting down Typesense connection manager'); await typesenseManager.shutdown(); }); //# sourceMappingURL=typesense.js.map