| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- import {
- computed,
- ref,
- onMounted,
- onBeforeUnmount,
- reactive,
- Ref,
- } from "@nuxtjs/composition-api"
- import { DocumentNode } from "graphql/language"
- import {
- createClient,
- TypedDocumentNode,
- OperationResult,
- dedupExchange,
- OperationContext,
- fetchExchange,
- makeOperation,
- } from "@urql/core"
- import { authExchange } from "@urql/exchange-auth"
- import { offlineExchange } from "@urql/exchange-graphcache"
- import { makeDefaultStorage } from "@urql/exchange-graphcache/default-storage"
- 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 gql from "graphql-tag"
- import {
- getAuthIDToken,
- probableUser$,
- waitProbableLoginToConfirm,
- } from "~/helpers/fb/auth"
- const BACKEND_GQL_URL =
- process.env.CONTEXT === "production"
- ? "https://api.hoppscotch.io/graphql"
- : "https://api.hoppscotch.io/graphql"
- const storage = makeDefaultStorage({
- idbName: "hoppcache-v1",
- maxAge: 7,
- })
- const client = createClient({
- url: BACKEND_GQL_URL,
- exchanges: [
- devtoolsExchange,
- dedupExchange,
- // TODO: Extract this outttttttt
- offlineExchange({
- keys: {
- User: (data) => (data as any).uid,
- TeamMember: (data) => (data as any).membershipID,
- Team: (data) => data.id as any,
- },
- optimistic: {
- deleteTeam: () => true,
- leaveTeam: () => true,
- },
- updates: {
- Mutation: {
- deleteTeam: (_r, { teamID }, cache, _info) => {
- cache.updateQuery(
- {
- query: gql`
- query {
- myTeams {
- id
- }
- }
- `,
- },
- (data: any) => {
- console.log(data)
- data.myTeams = (data as any).myTeams.filter(
- (x: any) => x.id !== teamID
- )
- return data
- }
- )
- cache.invalidate({
- __typename: "Team",
- id: teamID as any,
- })
- },
- leaveTeam: (_r, { teamID }, cache, _info) => {
- cache.updateQuery(
- {
- query: gql`
- query {
- myTeams {
- id
- }
- }
- `,
- },
- (data: any) => {
- console.log(data)
- data.myTeams = (data as any).myTeams.filter(
- (x: any) => x.id !== teamID
- )
- return data
- }
- )
- cache.invalidate({
- __typename: "Team",
- id: teamID as any,
- })
- },
- createTeam: (result, _args, cache, _info) => {
- cache.updateQuery(
- {
- query: gql`
- {
- myTeams {
- id
- }
- }
- `,
- },
- (data: any) => {
- console.log(result)
- console.log(data)
- data.myTeams.push(result.createTeam)
- return data
- }
- )
- },
- },
- },
- storage,
- }),
- authExchange({
- addAuthToOperation({ authState, operation }) {
- if (!authState || !authState.authToken) {
- return operation
- }
- const fetchOptions =
- typeof operation.context.fetchOptions === "function"
- ? operation.context.fetchOptions()
- : operation.context.fetchOptions || {}
- return makeOperation(operation.kind, operation, {
- ...operation.context,
- fetchOptions: {
- ...fetchOptions,
- headers: {
- ...fetchOptions.headers,
- Authorization: `Bearer ${authState.authToken}`,
- },
- },
- })
- },
- willAuthError({ authState }) {
- return !authState || !authState.authToken
- },
- getAuth: async () => {
- if (!probableUser$.value) return { authToken: null }
- await waitProbableLoginToConfirm()
- return {
- authToken: getAuthIDToken(),
- }
- },
- }),
- fetchExchange,
- ],
- })
- /**
- * A wrapper type for defining errors possible in a GQL operation
- */
- export type GQLError<T extends string> =
- | {
- type: "network_error"
- error: Error
- }
- | {
- type: "gql_error"
- error: T
- }
- const DEFAULT_QUERY_OPTIONS = {
- noPolling: false,
- pause: undefined as Ref<boolean> | 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<GQLError<QueryFailType>, QueryReturnType>
- }
- type UseQueryReturn<QueryFailType extends string = "", QueryReturnType = any> =
- | UseQueryLoading
- | UseQueryLoaded<QueryFailType, QueryReturnType>
- export function isLoadedGQLQuery<QueryFailType extends string, QueryReturnType>(
- x: UseQueryReturn<QueryFailType, QueryReturnType>
- ): x is {
- loading: false
- data: E.Either<GQLError<QueryFailType>, QueryReturnType>
- } {
- return !x.loading
- }
- export function useGQLQuery<
- QueryReturnType = any,
- QueryFailType extends string = "",
- QueryVariables extends object = {}
- >(
- query: string | DocumentNode | TypedDocumentNode<any, QueryVariables>,
- variables?: QueryVariables,
- options: Partial<GQL_QUERY_OPTIONS> = DEFAULT_QUERY_OPTIONS
- ):
- | { loading: false; data: E.Either<GQLError<QueryFailType>, QueryReturnType> }
- | { loading: true } {
- type DataType = E.Either<GQLError<QueryFailType>, QueryReturnType>
- const finalOptions = Object.assign(clone(DEFAULT_QUERY_OPTIONS), options)
- const data = ref<DataType>()
- let subscription: { unsubscribe(): void } | null = null
- onMounted(() => {
- const gqlQuery = client.query<any, QueryVariables>(query, variables, {
- requestPolicy: "cache-and-network",
- })
- const processResult = (result: OperationResult<any, QueryVariables>) =>
- 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?.message),
- E.match(
- // The left case (network error was null)
- (gqlErr) =>
- <GQLError<QueryFailType>>{
- type: "gql_error",
- error: gqlErr as QueryFailType,
- },
- // The right case (it was a GraphQL Error)
- (networkErr) =>
- <GQLError<QueryFailType>>{
- 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<any, MutationVariables>,
- variables?: MutationVariables,
- additionalConfig?: Partial<OperationContext>
- ): TE.TaskEither<GQLError<MutationFailType>, NonNullable<MutationReturnType>> =>
- pipe(
- TE.tryCatch(
- () =>
- client
- .mutation<MutationReturnType>(mutation, variables, {
- requestPolicy: "cache-and-network",
- ...additionalConfig,
- })
- .toPromise(),
- () => constVoid() as never // The mutation function can never fail, so this will never be called ;)
- ),
- TE.chainEitherK((result) =>
- pipe(
- result.data as MutationReturnType,
- E.fromNullable(
- // Result is null
- pipe(
- result.error?.networkError,
- E.fromNullable(result.error?.name),
- E.match(
- // The left case (network error was null)
- (gqlErr) =>
- <GQLError<MutationFailType>>{
- type: "gql_error",
- error: gqlErr as MutationFailType,
- },
- // The right case (it was a GraphQL Error)
- (networkErr) =>
- <GQLError<MutationFailType>>{
- type: "network_error",
- error: networkErr,
- }
- )
- )
- )
- )
- )
- )
|