UNPKG

deepsource-mcp-server

Version:
171 lines (152 loc) 5.53 kB
/** * @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); } } }