import { computed, ref, onMounted, onBeforeUnmount, reactive, Ref, } from "@nuxtjs/composition-api" import { DocumentNode } from "graphql/language" import { createClient, TypedDocumentNode, OperationResult, defaultExchanges, } from "@urql/core" import { devtoolsExchange } from "@urql/devtools" import * as E from "fp-ts/Either" import * as TE from "fp-ts/TaskEither" import { pipe, constVoid } from "fp-ts/function" import { subscribe } from "wonka" import clone from "lodash/clone" import { getAuthIDToken } from "~/helpers/fb/auth" const BACKEND_GQL_URL = process.env.CONTEXT === "production" ? "https://api.hoppscotch.io/graphql" : "https://api.hoppscotch.io/graphql" const client = createClient({ url: BACKEND_GQL_URL, fetchOptions: () => { const token = getAuthIDToken() return { headers: { authorization: token ? `Bearer ${token}` : "", }, } }, exchanges: [devtoolsExchange, ...defaultExchanges], }) /** * A wrapper type for defining errors possible in a GQL operation */ export type GQLError = | { type: "network_error" error: Error } | { type: "gql_error" error: T } const DEFAULT_QUERY_OPTIONS = { noPolling: false, pause: undefined as Ref | undefined, } type GQL_QUERY_OPTIONS = typeof DEFAULT_QUERY_OPTIONS type UseQueryLoading = { loading: true } type UseQueryLoaded< QueryFailType extends string = "", QueryReturnType = any > = { loading: false data: E.Either, QueryReturnType> } type UseQueryReturn = | UseQueryLoading | UseQueryLoaded export function isLoadedGQLQuery( x: UseQueryReturn ): x is { loading: false data: E.Either, QueryReturnType> } { return !x.loading } export function useGQLQuery< QueryReturnType = any, QueryFailType extends string = "", QueryVariables extends object = {} >( query: string | DocumentNode | TypedDocumentNode, variables?: QueryVariables, options: Partial = DEFAULT_QUERY_OPTIONS ): | { loading: false; data: E.Either, QueryReturnType> } | { loading: true } { type DataType = E.Either, QueryReturnType> const finalOptions = Object.assign(clone(DEFAULT_QUERY_OPTIONS), options) const data = ref() let subscription: { unsubscribe(): void } | null = null onMounted(() => { const gqlQuery = client.query(query, variables) const processResult = (result: OperationResult) => pipe( // The target result.data as QueryReturnType | undefined, // Define what happens if data does not exist (it is an error) E.fromNullable( pipe( // Take the network error value result.error?.networkError, // If it null, set the left to the generic error name E.fromNullable(result.error?.name), E.match( // The left case (network error was null) (gqlErr) => >{ type: "gql_error", error: gqlErr as QueryFailType, }, // The right case (it was a GraphQL Error) (networkErr) => >{ type: "network_error", error: networkErr, } ) ) ) ) if (finalOptions.noPolling) { gqlQuery.toPromise().then((result) => { data.value = processResult(result) }) } else { subscription = pipe( gqlQuery, subscribe((result) => { data.value = processResult(result) }) ) } }) onBeforeUnmount(() => { subscription?.unsubscribe() }) return reactive({ loading: computed(() => !data.value), data: data!, }) as | { loading: false data: DataType } | { loading: true } } export const runMutation = < MutationReturnType = any, MutationFailType extends string = "", MutationVariables extends {} = {} >( mutation: string | DocumentNode | TypedDocumentNode, variables?: MutationVariables ): TE.TaskEither, NonNullable> => pipe( TE.tryCatch( () => client.mutation(mutation, variables).toPromise(), () => constVoid() as never // The mutation function can never fail, so this will never be called ;) ), TE.chainEitherK((result) => pipe( result.data as MutationReturnType, // If we have the result, then okay E.fromNullable( // Result is null pipe( result.error?.networkError, // Check for network error E.fromNullable(result.error?.name), // If it is null, then it is a GQL error E.match( // The left case (network error was null) (gqlErr) => >{ type: "gql_error", error: gqlErr as MutationFailType, }, // The right case (it was a GraphQL Error) (networkErr) => >{ type: "network_error", error: networkErr, } ) ) ) ) ) )