deepsource-mcp-server
Version:
Model Context Protocol server for DeepSource
171 lines (152 loc) • 5.53 kB
text/typescript
/**
* @fileoverview Base client for interacting with the DeepSource API
* This module provides a base client class with core functionality.
*/
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { createLogger } from '../utils/logging/logger.js';
import { handleApiError } from '../utils/errors/handlers.js';
import { GraphQLResponse } from '../types/graphql-responses.js';
/**
* Configuration options for the DeepSource client
* @public
*/
export interface DeepSourceClientConfig {
/** The base URL for the API */
baseURL?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Custom headers to include in requests */
headers?: Record<string, string>;
}
/**
* Default configuration for the DeepSource client
* @private
*/
const DEFAULT_CONFIG: DeepSourceClientConfig = {
baseURL: 'https://api.deepsource.io/graphql/',
timeout: 30000,
};
/**
* Base client for DeepSource API with core HTTP functionality
* @class
* @public
*/
export class BaseDeepSourceClient {
/**
* HTTP client for making API requests to DeepSource
* @protected
*/
protected client: AxiosInstance;
/**
* Logger instance for the client
* @protected
*/
protected logger = createLogger('DeepSourceClient');
/**
* Creates a new BaseDeepSourceClient instance
* @param apiKey - The DeepSource API key for authentication
* @param config - Optional configuration options
* @throws {Error} When apiKey is not provided
* @public
*/
constructor(apiKey: string, config: DeepSourceClientConfig = {}) {
if (!apiKey) {
throw new Error('DeepSource API key is required');
}
// Merge default config with provided config
const mergedConfig: AxiosRequestConfig = {
...DEFAULT_CONFIG,
...config,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
...config.headers,
},
};
this.client = axios.create(mergedConfig);
}
/**
* Execute a GraphQL query against the DeepSource API
* @param query The GraphQL query to execute
* @returns The query response data
* @throws {ClassifiedError} When the query fails
* @protected
*/
protected async executeGraphQL<T>(query: string): Promise<GraphQLResponse<T>> {
try {
// Log full query for debugging
this.logger.debug('Executing GraphQL query', {
query,
queryLength: query.length,
requestHeaders: this.client.defaults.headers,
});
// Execute the query
const startTime = Date.now();
const response = await this.client.post('', { query });
const duration = Date.now() - startTime;
this.logger.debug('GraphQL response received', {
status: response.status,
statusText: response.statusText,
duration: `${duration}ms`,
dataSize: JSON.stringify(response.data).length,
hasData: Boolean(response.data?.data),
hasErrors: Boolean(response.data?.errors),
});
// Check for GraphQL errors in the response
if (response.data.errors) {
this.logger.error('GraphQL query returned errors', {
errors: response.data.errors,
query: query.substring(0, 100) + (query.length > 100 ? '...' : ''),
});
throw new Error(`GraphQL Errors: ${JSON.stringify(response.data.errors)}`);
}
return response.data as GraphQLResponse<T>;
} catch (error) {
// Log detailed error information
this.logger.error('Error executing GraphQL query', {
errorType: typeof error,
errorName: error instanceof Error ? error.name : 'Unknown error type',
errorMessage: error instanceof Error ? error.message : String(error),
errorResponse: (error as Record<string, unknown>)?.response
? {
status: (error as Record<string, Record<string, unknown>>).response.status,
statusText: (error as Record<string, Record<string, unknown>>).response.statusText,
data: (error as Record<string, Record<string, unknown>>).response.data,
}
: 'No response data available',
query: query.substring(0, 100) + (query.length > 100 ? '...' : ''),
});
// Handle the error properly
const handledError = handleApiError(error);
this.logger.debug('Handled API error', {
originalMessage: error instanceof Error ? error.message : String(error),
handledMessage: handledError.message,
handledName: handledError.name,
});
throw handledError;
}
}
/**
* Execute a GraphQL mutation against the DeepSource API
* @param mutation The GraphQL mutation to execute
* @returns The mutation response data
* @throws {ClassifiedError} When the mutation fails
* @protected
*/
protected async executeGraphQLMutation<T>(mutation: string): Promise<T> {
try {
this.logger.debug('Executing GraphQL mutation', { mutation });
const response = await this.client.post('', { query: mutation });
// Check for GraphQL errors in the response
if (response.data.errors) {
this.logger.error('GraphQL mutation returned errors', response.data.errors);
throw new Error(`GraphQL Errors: ${JSON.stringify(response.data.errors)}`);
}
return response.data as T;
} catch (error) {
this.logger.error('Error executing GraphQL mutation', error);
throw handleApiError(error);
}
}
}