"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "packages/hoppscotch-app/helpers/backend/GQLClient.ts" between
hoppscotch-2.2.1.tar.gz and hoppscotch-3.0.0.tar.gz

About: Hoppscotch is a light-weight, web based API development suite.

GQLClient.ts  (hoppscotch-2.2.1):GQLClient.ts  (hoppscotch-3.0.0)
import { // TODO: fix cache
ref, import { ref } from "vue"
reactive,
Ref,
unref,
watchEffect,
watchSyncEffect,
WatchStopHandle,
set,
isRef,
} from "@nuxtjs/composition-api"
import { import {
createClient, createClient,
TypedDocumentNode, TypedDocumentNode,
OperationResult,
dedupExchange, dedupExchange,
OperationContext, OperationContext,
fetchExchange, fetchExchange,
makeOperation, makeOperation,
GraphQLRequest,
createRequest, createRequest,
subscriptionExchange, subscriptionExchange,
} from "@urql/core" } from "@urql/core"
import { authExchange } from "@urql/exchange-auth" import { authExchange } from "@urql/exchange-auth"
import { offlineExchange } from "@urql/exchange-graphcache" // import { offlineExchange } from "@urql/exchange-graphcache"
import { makeDefaultStorage } from "@urql/exchange-graphcache/default-storage" // import { makeDefaultStorage } from "@urql/exchange-graphcache/default-storage
"
import { devtoolsExchange } from "@urql/devtools" import { devtoolsExchange } from "@urql/devtools"
import { SubscriptionClient } from "subscriptions-transport-ws" import { SubscriptionClient } from "subscriptions-transport-ws"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe, constVoid } from "fp-ts/function" import { pipe, constVoid, flow } from "fp-ts/function"
import { Source, subscribe, pipe as wonkaPipe, onEnd } from "wonka" import { subscribe, pipe as wonkaPipe } from "wonka"
import { keyDefs } from "./caching/keys" import { filter, map, Subject } from "rxjs"
import { optimisticDefs } from "./caching/optimistic" // import { keyDefs } from "./caching/keys"
import { updatesDef } from "./caching/updates" // import { optimisticDefs } from "./caching/optimistic"
import { resolversDef } from "./caching/resolvers" // import { updatesDef } from "./caching/updates"
import schema from "./backend-schema.json" // import { resolversDef } from "./caching/resolvers"
// import schema from "./backend-schema.json"
import { import {
authIdToken$, authIdToken$,
getAuthIDToken, getAuthIDToken,
probableUser$, probableUser$,
waitProbableLoginToConfirm, waitProbableLoginToConfirm,
} from "~/helpers/fb/auth" } from "~/helpers/fb/auth"
const BACKEND_GQL_URL = const BACKEND_GQL_URL =
process.env.context === "production" import.meta.env.VITE_BACKEND_GQL_URL ?? "https://api.hoppscotch.io/graphql"
? "https://api.hoppscotch.io/graphql" const BACKEND_WS_URL =
: "https://api.hoppscotch.io/graphql" import.meta.env.VITE_BACKEND_WS_URL ?? "wss://api.hoppscotch.io/graphql"
const storage = makeDefaultStorage({ // const storage = makeDefaultStorage({
idbName: "hoppcache-v1", // idbName: "hoppcache-v1",
maxAge: 7, // maxAge: 7,
// })
const subscriptionClient = new SubscriptionClient(BACKEND_WS_URL, {
reconnect: true,
connectionParams: () => {
return {
authorization: `Bearer ${authIdToken$.value}`,
}
},
}) })
const subscriptionClient = new SubscriptionClient(
process.env.context === "production"
? "wss://api.hoppscotch.io/graphql"
: "wss://api.hoppscotch.io/graphql",
{
reconnect: true,
connectionParams: () => {
return {
authorization: `Bearer ${authIdToken$.value}`,
}
},
}
)
authIdToken$.subscribe(() => { authIdToken$.subscribe(() => {
subscriptionClient.client?.close() subscriptionClient.client?.close()
}) })
const createHoppClient = () => const createHoppClient = () =>
createClient({ createClient({
url: BACKEND_GQL_URL, url: BACKEND_GQL_URL,
exchanges: [ exchanges: [
devtoolsExchange, devtoolsExchange,
dedupExchange, dedupExchange,
offlineExchange({ // offlineExchange({
schema: schema as any, // schema: schema as any,
keys: keyDefs, // keys: keyDefs,
optimistic: optimisticDefs, // optimistic: optimisticDefs,
updates: updatesDef, // updates: updatesDef,
resolvers: resolversDef, // resolvers: resolversDef,
storage, // storage,
}), // }),
authExchange({ authExchange({
addAuthToOperation({ authState, operation }) { addAuthToOperation({ authState, operation }) {
if (!authState || !authState.authToken) { if (!authState || !authState.authToken) {
return operation return operation
} }
const fetchOptions = const fetchOptions =
typeof operation.context.fetchOptions === "function" typeof operation.context.fetchOptions === "function"
? operation.context.fetchOptions() ? operation.context.fetchOptions()
: operation.context.fetchOptions || {} : operation.context.fetchOptions || {}
skipping to change at line 125 skipping to change at line 110
await waitProbableLoginToConfirm() await waitProbableLoginToConfirm()
return { return {
authToken: getAuthIDToken(), authToken: getAuthIDToken(),
} }
}, },
}), }),
fetchExchange, fetchExchange,
subscriptionExchange({ subscriptionExchange({
forwardSubscription: (operation) => forwardSubscription: (operation) =>
// @ts-expect-error: An issue with the Urql typing
subscriptionClient.request(operation), subscriptionClient.request(operation),
}), }),
], ],
}) })
export const client = ref(createHoppClient()) export const client = ref(createHoppClient())
authIdToken$.subscribe(() => { authIdToken$.subscribe(() => {
client.value = createHoppClient() client.value = createHoppClient()
}) })
type MaybeRef<X> = X | Ref<X> type RunQueryOptions<T = any, V = object> = {
type UseQueryOptions<T = any, V = object> = {
query: TypedDocumentNode<T, V> query: TypedDocumentNode<T, V>
variables?: MaybeRef<V> variables?: V
updateSubs?: MaybeRef<GraphQLRequest<any, object>[]>
defer?: boolean
pollDuration?: number | undefined
} }
/** /**
* A wrapper type for defining errors possible in a GQL operation * A wrapper type for defining errors possible in a GQL operation
*/ */
export type GQLError<T extends string> = export type GQLError<T extends string> =
| { | {
type: "network_error" type: "network_error"
error: Error error: Error
} }
| { | {
type: "gql_error" type: "gql_error"
error: T error: T
} }
export const useGQLQuery = <DocType, DocVarType, DocErrorType extends string>( export const runGQLQuery = <DocType, DocVarType, DocErrorType extends string>(
_args: UseQueryOptions<DocType, DocVarType> args: RunQueryOptions<DocType, DocVarType>
) => { ): Promise<E.Either<GQLError<DocErrorType>, DocType>> => {
const stops: WatchStopHandle[] = [] const request = createRequest<DocType, DocVarType>(args.query, args.variables)
const source = client.value.executeQuery(request, {
const args = reactive(_args) requestPolicy: "network-only",
})
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) set(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( return new Promise((resolve) => {
watchEffect((onInvalidate) => { const sub = wonkaPipe(
if (pollDuration.value !== null && !isPaused.value) { source,
const handle = setInterval(() => { subscribe((res) => {
pollerTick.value = !pollerTick.value if (sub) {
}, pollDuration.value) sub.unsubscribe()
}
onInvalidate(() => {
clearInterval(handle)
})
}
})
)
stops.push( pipe(
watchEffect( // The target
() => { res.data as DocType | undefined,
const newRequest = createRequest<DocType, DocVarType>( // Define what happens if data does not exist (it is an error)
args.query, E.fromNullable(
unref<DocVarType>(args.variables as any) as any 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,
}
)
)
),
resolve
) )
})
if (request.value.key !== newRequest.key) {
request.value = newRequest
}
},
{ flush: "pre" }
) )
) })
}
stops.push( // TODO: The subscription system seems to be firing multiple updates for certain
watchEffect( subscriptions.
() => { // Make sure to handle cases if the subscription fires with the same update mult
// Just listen to the polling ticks iple times
// eslint-disable-next-line no-unused-expressions export const runGQLSubscription = <
pollerTick.value DocType,
DocVarType,
source.value = !isPaused.value DocErrorType extends string
? client.value.executeQuery<DocType, DocVarType>(request.value, { >(
requestPolicy: "cache-and-network", args: RunQueryOptions<DocType, DocVarType>
}) ) => {
: undefined const result$ = new Subject<E.Either<GQLError<DocErrorType>, DocType>>()
},
{ flush: "pre" }
)
)
watchSyncEffect((onInvalidate) => { const source = client.value.executeSubscription(
if (source.value) { createRequest(args.query, args.variables)
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( const sub = wonkaPipe(
wonkaPipe( source,
source.value, subscribe((res) => {
onEnd(() => { result$.next(
loading.value = false pipe(
isStale.value = false // The target
}), res.data as DocType | undefined,
subscribe((res) => { // Define what happens if data does not exist (it is an error)
if (res.operation.key === request.value.key) { E.fromNullable(
data.value = pipe( pipe(
// The target // Take the network error value
res.data as DocType | undefined, res.error?.networkError,
// Define what happens if data does not exist (it is an error) // If it null, set the left to the generic error name
E.fromNullable( E.fromNullable(res.error?.message),
pipe( E.match(
// Take the network error value // The left case (network error was null)
res.error?.networkError, (gqlErr) =>
// If it null, set the left to the generic error name <GQLError<DocErrorType>>{
E.fromNullable(res.error?.message), type: "gql_error",
E.match( error: parseGQLErrorString(gqlErr ?? "") as DocErrorType,
// The left case (network error was null) },
(gqlErr) => // The right case (it was a GraphQL Error)
<GQLError<DocErrorType>>{ (networkErr) =>
type: "gql_error", <GQLError<DocErrorType>>{
error: parseGQLErrorString( type: "network_error",
gqlErr ?? "" error: networkErr,
) 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())) // Returns the stream and a subscription handle to unsub
} return [result$, sub] as const
}) }
const execute = (updatedVars?: DocVarType) => { /**
if (updatedVars) { * Same as `runGQLSubscription` but stops the subscription silently
if (isRef(args.variables)) { * if there is an authentication error because of logged out
args.variables.value = updatedVars */
} else { export const runAuthOnlyGQLSubscription = flow(
set(args, "variables", updatedVars) runGQLSubscription,
} ([result$, sub]) => {
} const updatedResult$ = result$.pipe(
map((res) => {
if (
E.isLeft(res) &&
res.left.type === "gql_error" &&
res.left.error === "auth/fail"
) {
sub.unsubscribe()
return null
} else return res
}),
filter((res): res is Exclude<typeof res, null> => res !== null)
)
isPaused.value = false return [updatedResult$, sub] as const
} }
)
const response = reactive({ export const parseGQLErrorString = (s: string) =>
loading,
data,
isStale,
execute,
})
return response
}
const parseGQLErrorString = (s: string) =>
s.startsWith("[GraphQL] ") ? s.split("[GraphQL] ")[1] : s s.startsWith("[GraphQL] ") ? s.split("[GraphQL] ")[1] : s
export const runMutation = < export const runMutation = <
DocType, DocType,
DocVariables extends object | undefined, DocVariables extends object | undefined,
DocErrors extends string DocErrors extends string
>( >(
mutation: TypedDocumentNode<DocType, DocVariables>, mutation: TypedDocumentNode<DocType, DocVariables>,
variables?: DocVariables, variables?: DocVariables,
additionalConfig?: Partial<OperationContext> additionalConfig?: Partial<OperationContext>
 End of changes. 26 change blocks. 
211 lines changed or deleted 156 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)