GQLClient.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {
  2. computed,
  3. ref,
  4. onMounted,
  5. onBeforeUnmount,
  6. reactive,
  7. Ref,
  8. } from "@nuxtjs/composition-api"
  9. import { DocumentNode } from "graphql/language"
  10. import {
  11. createClient,
  12. TypedDocumentNode,
  13. OperationResult,
  14. defaultExchanges,
  15. } from "@urql/core"
  16. import { devtoolsExchange } from "@urql/devtools"
  17. import * as E from "fp-ts/Either"
  18. import { pipe } from "fp-ts/function"
  19. import { subscribe } from "wonka"
  20. import clone from "lodash/clone"
  21. import { getAuthIDToken } from "~/helpers/fb/auth"
  22. const BACKEND_GQL_URL =
  23. process.env.CONTEXT === "production"
  24. ? "https://api.hoppscotch.io/graphql"
  25. : "https://api.hoppscotch.io/graphql"
  26. const client = createClient({
  27. url: BACKEND_GQL_URL,
  28. fetchOptions: () => {
  29. const token = getAuthIDToken()
  30. return {
  31. headers: {
  32. authorization: token ? `Bearer ${token}` : "",
  33. },
  34. }
  35. },
  36. exchanges: [devtoolsExchange, ...defaultExchanges],
  37. })
  38. /**
  39. * A wrapper type for defining errors possible in a GQL operation
  40. */
  41. export type GQLError<T extends string> =
  42. | {
  43. type: "network_error"
  44. error: Error
  45. }
  46. | {
  47. type: "gql_error"
  48. err: T
  49. }
  50. const DEFAULT_QUERY_OPTIONS = {
  51. noPolling: false,
  52. pause: undefined as Ref<boolean> | undefined,
  53. }
  54. type GQL_QUERY_OPTIONS = typeof DEFAULT_QUERY_OPTIONS
  55. type UseQueryLoading = {
  56. loading: true
  57. }
  58. type UseQueryLoaded<
  59. QueryFailType extends string = "",
  60. QueryReturnType = any
  61. > = {
  62. loading: false
  63. data: E.Either<GQLError<QueryFailType>, QueryReturnType>
  64. }
  65. type UseQueryReturn<QueryFailType extends string = "", QueryReturnType = any> =
  66. | UseQueryLoading
  67. | UseQueryLoaded<QueryFailType, QueryReturnType>
  68. export function isLoadedGQLQuery<QueryFailType extends string, QueryReturnType>(
  69. x: UseQueryReturn<QueryFailType, QueryReturnType>
  70. ): x is {
  71. loading: false
  72. data: E.Either<GQLError<QueryFailType>, QueryReturnType>
  73. } {
  74. return !x.loading
  75. }
  76. export function useGQLQuery<
  77. QueryReturnType = any,
  78. QueryFailType extends string = "",
  79. QueryVariables extends object = {}
  80. >(
  81. query: string | DocumentNode | TypedDocumentNode<any, QueryVariables>,
  82. variables?: QueryVariables,
  83. options: Partial<GQL_QUERY_OPTIONS> = DEFAULT_QUERY_OPTIONS
  84. ):
  85. | { loading: false; data: E.Either<GQLError<QueryFailType>, QueryReturnType> }
  86. | { loading: true } {
  87. type DataType = E.Either<GQLError<QueryFailType>, QueryReturnType>
  88. const finalOptions = Object.assign(clone(DEFAULT_QUERY_OPTIONS), options)
  89. const data = ref<DataType>()
  90. let subscription: { unsubscribe(): void } | null = null
  91. onMounted(() => {
  92. const gqlQuery = client.query<any, QueryVariables>(query, variables)
  93. const processResult = (result: OperationResult<any, QueryVariables>) =>
  94. pipe(
  95. // The target
  96. result.data as QueryReturnType | undefined,
  97. // Define what happens if data does not exist (it is an error)
  98. E.fromNullable(
  99. pipe(
  100. // Take the network error value
  101. result.error?.networkError,
  102. // If it null, set the left to the generic error name
  103. E.fromNullable(result.error?.name),
  104. E.match(
  105. // The left case (network error was null)
  106. (gqlErr) =>
  107. <GQLError<QueryFailType>>{
  108. type: "gql_error",
  109. err: gqlErr as QueryFailType,
  110. },
  111. // The right case (it was a GraphQL Error)
  112. (networkErr) =>
  113. <GQLError<QueryFailType>>{
  114. type: "network_error",
  115. error: networkErr,
  116. }
  117. )
  118. )
  119. )
  120. )
  121. if (finalOptions.noPolling) {
  122. gqlQuery.toPromise().then((result) => {
  123. data.value = processResult(result)
  124. })
  125. } else {
  126. subscription = pipe(
  127. gqlQuery,
  128. subscribe((result) => {
  129. data.value = processResult(result)
  130. })
  131. )
  132. }
  133. })
  134. onBeforeUnmount(() => {
  135. subscription?.unsubscribe()
  136. })
  137. return reactive({
  138. loading: computed(() => !data.value),
  139. data: data!,
  140. }) as
  141. | {
  142. loading: false
  143. data: DataType
  144. }
  145. | { loading: true }
  146. }