Files
hoppscotch/packages/hoppscotch-common/src/composables/graphql.ts
Anwarul Islam cabee0ecc8 fix: memory leak issue on TeamInvite modal (#3440)
* fix: memory leak issue

* feat: added rerun ability

* chore: lint fix
2023-10-11 07:59:12 +05:30

222 lines
5.2 KiB
TypeScript

import * as E from "fp-ts/Either"
import { pipe } from "fp-ts/function"
import {
reactive,
ref,
Ref,
unref,
isRef,
watchEffect,
WatchStopHandle,
watchSyncEffect,
watch,
nextTick,
} from "vue"
import {
client,
GQLError,
parseGQLErrorString,
} from "@helpers/backend/GQLClient"
import {
AnyVariables,
createRequest,
GraphQLRequest,
OperationResult,
TypedDocumentNode,
} from "@urql/core"
import { Source, pipe as wonkaPipe, onEnd, subscribe } from "wonka"
type MaybeRef<X> = X | Ref<X>
type UseQueryOptions<T = any, V = object> = {
query: TypedDocumentNode<T, V>
variables?: MaybeRef<V>
updateSubs?: MaybeRef<GraphQLRequest<any, object>[]>
defer?: boolean
pollDuration?: number | undefined
}
export const useGQLQuery = <
DocType,
DocVarType extends AnyVariables,
DocErrorType extends string,
>(
_args: UseQueryOptions<DocType, DocVarType>
) => {
const stops: WatchStopHandle[] = []
const args = reactive(_args)
const loading: Ref<boolean> = ref(true)
const isStale: Ref<boolean> = ref(true)
const data: Ref<E.Either<GQLError<DocErrorType>, DocType>> = ref() as any
if (!args.updateSubs) args.updateSubs = []
const isPaused: Ref<boolean> = ref(args.defer ?? false)
const pollDuration: Ref<number | null> = ref(args.pollDuration ?? null)
const request: Ref<GraphQLRequest<DocType, DocVarType>> = ref(
createRequest<DocType, DocVarType>(
args.query,
unref<DocVarType>(args.variables as any) as any
)
) as any
const source: Ref<Source<OperationResult> | undefined> = ref()
// Toggles between true and false to cause the polling operation to tick
const pollerTick: Ref<boolean> = ref(true)
stops.push(
watchEffect((onInvalidate) => {
if (pollDuration.value !== null && !isPaused.value) {
const handle = setInterval(() => {
pollerTick.value = !pollerTick.value
}, pollDuration.value)
onInvalidate(() => {
clearInterval(handle)
})
}
})
)
stops.push(
watchEffect(
() => {
const newRequest = createRequest<DocType, DocVarType>(
args.query,
unref<DocVarType>(args.variables as any) as any
)
if (request.value.key !== newRequest.key) {
request.value = newRequest
}
},
{ flush: "pre" }
)
)
const rerunQuery = () => {
source.value = !isPaused.value
? client.value?.executeQuery<DocType, DocVarType>(request.value, {
requestPolicy: "network-only",
})
: undefined
}
stops.push(
watch(
pollerTick,
() => {
rerunQuery()
},
{
flush: "pre",
}
)
)
watchSyncEffect((onInvalidate) => {
if (source.value) {
loading.value = true
isStale.value = false
const invalidateStops = args.updateSubs!.map((sub) => {
return wonkaPipe(
client.value!.executeSubscription(sub),
onEnd(() => {
if (source.value) execute()
}),
subscribe(() => {
return execute()
})
).unsubscribe
})
invalidateStops.push(
wonkaPipe(
source.value,
onEnd(() => {
loading.value = false
isStale.value = false
}),
subscribe((res) => {
if (res.operation.key === request.value.key) {
data.value = pipe(
// The target
res.data as DocType | undefined,
// Define what happens if data does not exist (it is an error)
E.fromNullable(
pipe(
// Take the network error value
res.error?.networkError,
// If it null, set the left to the generic error name
E.fromNullable(res.error?.message),
E.match(
// The left case (network error was null)
(gqlErr) =>
<GQLError<DocErrorType>>{
type: "gql_error",
error: parseGQLErrorString(
gqlErr ?? ""
) as DocErrorType,
},
// The right case (it was a GraphQL Error)
(networkErr) =>
<GQLError<DocErrorType>>{
type: "network_error",
error: networkErr,
}
)
)
)
)
loading.value = false
}
})
).unsubscribe
)
onInvalidate(() => invalidateStops.forEach((unsub) => unsub()))
}
})
const execute = (updatedVars?: DocVarType) => {
if (updatedVars) {
if (isRef(args.variables)) {
args.variables.value = updatedVars
} else {
args.variables = updatedVars
}
nextTick(rerunQuery)
} else {
rerunQuery()
}
isPaused.value = false
}
const pause = () => {
isPaused.value = true
}
const unpause = () => {
isPaused.value = false
}
const response = reactive({
loading,
data,
pause,
unpause,
isStale,
execute,
})
return response
}