UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

188 lines (164 loc) • 6.36 kB
import { InMemoryCache } from "apollo-cache-inmemory"; import ApolloClient from "apollo-client"; import { ApolloLink } from "apollo-link"; import { onError } from "apollo-link-error"; import { createHttpLink } from "apollo-link-http"; import axios from "axios"; import { buildAxiosFetch } from "axios-fetch"; import gql, { disableFragmentWarnings } from "graphql-tag"; import * as stringify from "json-stringify-safe"; import * as trace from "stack-trace"; import * as internalGraphql from "../internal/graph/graphQL"; import * as namespace from "../internal/util/cls"; import { configureProxy } from "../internal/util/http"; import { guid, replacer, } from "../internal/util/string"; import { GraphClient, MutationOptions, QueryOptions, } from "../spi/graph/GraphClient"; import { logger } from "../util/logger"; disableFragmentWarnings(); /** * Implementation of GraphClient using Apollo Client. */ export class ApolloGraphClient implements GraphClient { /** * Configured Apollo Client instance subclasses can use directly */ public readonly client: ApolloClient<any>; /** * Create a new GraphClient * @param endpoint GraphQL endpoint * @param headers any special headers to use * @param fetch configured GlobalFetch instance to use for this GraphClient */ constructor(public endpoint: string, headers: any = {}, fetch: WindowOrWorkerGlobalScope["fetch"] = buildAxiosFetch(axios.create(configureProxy({})))) { const cache = new InMemoryCache({ addTypename: false, }); const httpLink = createHttpLink({ uri: endpoint, fetch, }); const middlewareLink = new ApolloLink((operation, forward) => { // attach the correlation-id to the request const correlationId = namespace.get() ? namespace.get().correlationId : undefined; if (!!correlationId) { headers["correlation-id"] = correlationId; } const invocationId = namespace.get() ? namespace.get().invocationId : guid(); if (!!invocationId) { headers["x-request-id"] = invocationId; } operation.setContext({ headers, }); return forward(operation); }); const errorLink = onError(({ graphQLErrors, networkError, response }) => { let msg = `GraphQL operation failed:`; if (!!graphQLErrors) { const g = graphQLErrors.map(({ message }) => ` [GraphQL]: ${message}`, ); msg += `${g.join(" ")}`; } if (!!networkError) { msg += ` [Network]: ${networkError}`; } if (!!response) { msg += ` [Response]: ${stringify(response, replacer)}`; } logger.error(msg); }); const link = errorLink.concat(middlewareLink.concat(httpLink)); this.client = new ApolloClient({ link, cache, }); } public query<T, Q>(options: QueryOptions<Q> | string): Promise<T> { if (typeof options === "string") { options = { name: options, }; } const q = internalGraphql.query({ query: options.query, path: options.path, name: options.name, moduleDir: (options as any).moduleDir ? (options as any).moduleDir : trace.get()[1].getFileName(), }); return this.executeQuery<T, Q>(q, options.variables, options.options); } public mutate<T, Q>(options: MutationOptions<Q> | string): Promise<T> { if (typeof options === "string") { options = { name: options, }; } const m = internalGraphql.mutate({ mutation: options.mutation, path: options.path, name: options.name, moduleDir: (options as any).moduleDir ? (options as any).moduleDir : trace.get()[1].getFileName(), }); return this.executeMutation<T, Q>(m, options.variables, options.options); } private executeQuery<T, Q>(q: string, variables?: Q, queryOptions?: any): Promise<T> { const log = !queryOptions || (queryOptions && queryOptions.log === undefined) || (queryOptions && queryOptions.log === true); if (log) { logger.debug(`Querying '%s' with variables '%s' and query: %s`, this.endpoint, stringify(variables || {}), internalGraphql.inlineQuery(q)); } const query = gql(q); return this.client.query<T>({ query, variables, errorPolicy: "all", ...queryOptions, }) .then(result => { if (log) { // The following statement is needed for debugging; we can always disable that later logger.debug("Query returned data: %s", stringify(result.data, replacer)); } return result.data; }); } private executeMutation<T, Q>(m: string, variables?: Q, mutationOptions?: any): Promise<any> { const log = !mutationOptions || (mutationOptions && mutationOptions.log === undefined) || (mutationOptions && mutationOptions.log === true); if (log) { logger.debug(`Mutating '%s' with variables '%s' and mutation: %s`, this.endpoint, stringify(variables || {}), internalGraphql.inlineQuery(m)); } const mutation = gql(m); return this.client.mutate<T>({ mutation, variables, errorPolicy: "all", ...mutationOptions, }) .then(response => { if (log) { // The following statement is needed for debugging; we can always disable that later logger.debug("Mutation returned data: %s", stringify(response.data, replacer)); } return response.data; }); } }