import { HTTPClient, HTTPClientFactory, HTTPResponse, } from "../../../utils/http-client.js"; import { CoreConfiguration } from "../../data-sync-service/configuration-manager.js"; import { GraphQLResponse } from "./types.js"; import { ErrorHandler } from "../errors.js"; import { APIResponse } from "../types.js"; import { ERPApiConfig } from "../types.js"; export class GraphQLService { private client: HTTPClient; private config: ERPApiConfig; private endpoint: string; constructor( config: ERPApiConfig, endpoint: string, retryAttempts?: number, timeoutSeconds?: number ) { this.config = config; this.endpoint = endpoint ? endpoint.startsWith("/") ? endpoint : `/${endpoint}` : ""; const effectiveRetryAttempts = retryAttempts ?? this.config.retryAttempts ?? CoreConfiguration.inst().erpApiRetryAttempts; const effectiveTimeoutSeconds = timeoutSeconds ?? this.config.timeoutSeconds ?? CoreConfiguration.inst().erpApiTimeoutSeconds; this.client = HTTPClientFactory.getInstance({ baseUrl: config.apiUrl, retryAttempts: effectiveRetryAttempts, timeoutMs: effectiveTimeoutSeconds * 1000, }); } private async getHeaders(): Promise> { const token = await this.config.getAuthToken(); return { "Content-Type": "application/json", Accept: "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }; } /** * Handles a GraphQL response, properly handling both successful responses * and GraphQL-level errors (which come in a 200 HTTP response) * * From the GraphQL specification (https://spec.graphql.org/draft/#sec-Errors): > * "When a GraphQL server encounters an error, it should return a response with a > * top-level "errors" field containing the error information. The response may still contain * a partial result in the "data" field if the error occurred after some data was already resolved." * * And specifically about HTTP status codes: * "A server should return a 200 status code when a GraphQL operation successfully executes, including when * the operation returns errors. A server should return a 400 status code when a GraphQL operation fails to execute." */ private handleGraphQLResponse( response: HTTPResponse> ): APIResponse { return { data: response.data.data as T, metadata: { errors: response.data.errors, extensions: response.data.extensions, }, }; } /** * Executes a GraphQL query * @param query The GraphQL query string * @param variables Optional variables for the query * @returns The query result * @throws {HTTPError} For HTTP/network errors only */ async query( query: string, variables?: Record ): Promise> { try { const headers = await this.getHeaders(); const response = await this.client.request>({ method: "POST", url: this.endpoint, data: { query, variables }, headers, }); return this.handleGraphQLResponse(response); } catch (error) { ErrorHandler.handle(error); } } /** * Executes a GraphQL mutation * @param mutation The GraphQL mutation string * @param variables Optional variables for the mutation * @returns The mutation result * @throws {HTTPError} For HTTP/network errors only */ async mutate( mutation: string, variables?: Record ): Promise> { try { const headers = await this.getHeaders(); const response = await this.client.request>({ method: "POST", url: this.endpoint, data: { query: mutation, variables }, headers, }); return this.handleGraphQLResponse(response); } catch (error) { ErrorHandler.handle(error); } } /** * Cleanup all HTTP connections and resources * Call this when the service is no longer needed */ async destroy(): Promise { await this.client.destroy(); } }