UNPKG

synaptra

Version:

A high-performance Model Context Protocol server for GraphQL APIs with advanced features, type-safety, and developer experience improvements

214 lines 8.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphQLClientService = void 0; const graphql_1 = require("graphql"); const graphql_request_1 = require("graphql-request"); const types_1 = require("../types"); class GraphQLClientService { maxRetries; endpoint; client; schema = null; constructor(endpoint, maxRetries = 3) { this.maxRetries = maxRetries; this.endpoint = endpoint; this.client = new graphql_request_1.GraphQLClient(endpoint.url, { headers: endpoint.headers, }); } async introspectSchema(requestHeaders) { try { const introspectionQuery = (0, graphql_1.getIntrospectionQuery)(); const queryInfo = { query: introspectionQuery, }; if (requestHeaders) { queryInfo.headers = requestHeaders; } const queryResult = await this.executeQuery(queryInfo); if (queryResult.errors || !queryResult.data) { throw new types_1.SchemaError('Failed to introspect schema', { errors: queryResult.errors }); } const schema = (0, graphql_1.buildClientSchema)(queryResult.data); const sdl = (0, graphql_1.printSchema)(schema); const schemaInfo = { schema, sdl, lastUpdated: new Date(), version: this.generateSchemaVersion(sdl), }; this.schema = schemaInfo; return schemaInfo; } catch (error) { if (error instanceof types_1.SchemaError) { throw error; } throw new types_1.SchemaError(`Schema introspection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error }); } } async executeQuery(queryInfo, dryRun = false) { const metrics = { startTime: Date.now() }; try { // Parse query if it's a string const queryDoc = typeof queryInfo.query === 'string' ? (0, graphql_1.parse)(queryInfo.query) : queryInfo.query; const queryString = (0, graphql_1.print)(queryDoc); // Validate query against schema if available if (this.schema) { const validationErrors = (0, graphql_1.validate)(this.schema.schema, queryDoc); if (validationErrors.length > 0) { throw new types_1.ValidationError('Query validation failed', { errors: validationErrors }); } } if (dryRun) { return { data: null, extensions: { dryRun: true, query: queryString, variables: queryInfo.variables, metrics, }, }; } // Merge default headers with request-specific headers const headers = { ...this.endpoint.headers, ...(queryInfo.headers || {}), }; // Execute query with retry logic let lastError = null; for (let attempt = 0; attempt <= this.maxRetries; attempt++) { try { const result = await this.client.request(queryDoc, queryInfo.variables, headers); metrics.endTime = Date.now(); metrics.duration = metrics.endTime - metrics.startTime; const queryResult = { data: result, extensions: { metrics }, }; return queryResult; } catch (error) { lastError = error; if (attempt < this.maxRetries) { // Wait before retry (exponential backoff) await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } } throw new types_1.NetworkError(`Query execution failed after ${this.maxRetries + 1} attempts`, { originalError: lastError }); } catch (error) { if (error instanceof types_1.ValidationError || error instanceof types_1.NetworkError) { throw error; } throw new types_1.QueryError(`Query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error }); } } async analyzeQuery(query, variables) { try { const queryDoc = (0, graphql_1.parse)(query); const analysis = { operation: this.getOperationType(queryDoc), complexity: this.calculateComplexity(queryDoc), depth: this.calculateDepth(queryDoc), fields: this.extractFields(queryDoc), variables: variables ? Object.keys(variables) : [], fragments: this.extractFragments(queryDoc), }; return analysis; } catch (error) { throw new types_1.ValidationError(`Query analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error }); } } getSchema() { return this.schema; } updateEndpoint(endpoint) { this.endpoint = { ...this.endpoint, ...endpoint }; this.client = new graphql_request_1.GraphQLClient(this.endpoint.url, { headers: this.endpoint.headers, }); } generateSchemaVersion(sdl) { const crypto = require('crypto'); return crypto.createHash('md5').update(sdl).digest('hex').substring(0, 8); } getOperationType(query) { const operationDef = query.definitions.find(def => def.kind === 'OperationDefinition'); return operationDef?.kind === 'OperationDefinition' ? operationDef.operation : 'unknown'; } calculateComplexity(query) { // Simple complexity calculation - count field selections let complexity = 0; const visit = (node) => { if (node.kind === 'Field') { complexity++; } if (node.selectionSet) { node.selectionSet.selections.forEach(visit); } }; query.definitions.forEach(def => { if (def.kind === 'OperationDefinition' && def.selectionSet) { def.selectionSet.selections.forEach(visit); } }); return complexity; } calculateDepth(query) { let maxDepth = 0; const visit = (node, depth) => { maxDepth = Math.max(maxDepth, depth); if (node.selectionSet) { node.selectionSet.selections.forEach((selection) => { if (selection.kind === 'Field') { visit(selection, depth + 1); } }); } }; query.definitions.forEach(def => { if (def.kind === 'OperationDefinition' && def.selectionSet) { def.selectionSet.selections.forEach((selection) => { if (selection.kind === 'Field') { visit(selection, 1); } }); } }); return maxDepth; } extractFields(query) { const fields = []; const visit = (node) => { if (node.kind === 'Field') { fields.push(node.name.value); } if (node.selectionSet) { node.selectionSet.selections.forEach(visit); } }; query.definitions.forEach(def => { if (def.kind === 'OperationDefinition' && def.selectionSet) { def.selectionSet.selections.forEach(visit); } }); return [...new Set(fields)]; // Remove duplicates } extractFragments(query) { const fragments = []; query.definitions.forEach(def => { if (def.kind === 'FragmentDefinition') { fragments.push(def.name.value); } }); return fragments; } } exports.GraphQLClientService = GraphQLClientService; //# sourceMappingURL=graphql-client.js.map