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
JavaScript
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