From 181eb6c811d2a149b7680935a6968abd73264c68 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:21:59 +0200 Subject: [PATCH] Add graphql logic to RequestManager --- src/lib/requests/RequestManager.ts | 126 +++++++++++++++++++++++ src/lib/requests/client/GraphQLClient.ts | 53 ++++++++++ 2 files changed, 179 insertions(+) create mode 100644 src/lib/requests/client/GraphQLClient.ts diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index a6d18410..6a03ee5e 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -9,6 +9,18 @@ import { AxiosInstance, AxiosRequestConfig } from 'axios'; import useSWR, { Middleware, SWRConfiguration, SWRResponse } from 'swr'; import useSWRInfinite, { SWRInfiniteConfiguration, SWRInfiniteResponse } from 'swr/infinite'; +import { + FetchResult, + MutationHookOptions, + MutationOptions, + MutationTuple, + QueryHookOptions, + QueryResult, + TypedDocumentNode, + useMutation, + useQuery, +} from '@apollo/client'; +import { OperationVariables } from '@apollo/client/core'; import { BackupValidationResult, BatchChaptersChange, @@ -31,6 +43,7 @@ import { } from '@/typings.ts'; import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts'; import storage from '@/util/localStorage.tsx'; +import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts'; enum SWRHttpMethod { SWR_GET, @@ -39,6 +52,12 @@ enum SWRHttpMethod { SWR_POST_INFINITE, } +enum GQLMethod { + USE_QUERY = 'USE_QUERY', + USE_MUTATION = 'USE_MUTATION', + MUTATION = 'MUTATION', +} + type HttpMethodType = DefaultHttpMethod | SWRHttpMethod; const HttpMethod = { ...SWRHttpMethod, ...DefaultHttpMethod }; @@ -64,6 +83,16 @@ export type AbortableSWRInfiniteResponse = SWRInfiniteR AbortableRequest & SWRInfiniteResponseLoadInfo; +type AbortableApolloUseQueryResponse< + Data = any, + Variables extends OperationVariables = OperationVariables, +> = QueryResult & AbortableRequest; +type AbortableApolloUseMutationResponse = [ + MutationTuple[0], + MutationTuple[1] & AbortableRequest, +]; +type AbortableApolloMutationResponse = { response: Promise> } & AbortableRequest; + const isLoadingMore = (swrResult: SWRInfiniteResponse): boolean => { const isNextPageMissing = !!swrResult.data && typeof swrResult.data[swrResult.size - 1] === 'undefined'; const isRequestActive = swrResult.isValidating; @@ -97,6 +126,8 @@ const disableSwrCache: Middleware = (useSWRNext) => (key, fetcher, config) => { export class RequestManager { public static readonly API_VERSION = '/api/v1/'; + private readonly graphQLClient = new GraphQLClient(); + private readonly restClient: RestClient = new RestClient(); public getClient(): IRestClient { @@ -105,6 +136,7 @@ export class RequestManager { public updateClient(config: Partial): void { this.restClient.updateConfig(config); + this.graphQLClient.updateConfig({ uri: config.baseURL }); } public getBaseUrl(): string { @@ -138,6 +170,100 @@ export class RequestManager { return `${this.getValidUrlFor(imageUrl, apiVersion)}${useCacheQuery}`; } + private doRequestNew( + method: GQLMethod.USE_QUERY, + operation: TypedDocumentNode, + variables: Variables, + options?: Partial>, + ): AbortableApolloUseQueryResponse; + + private doRequestNew( + method: GQLMethod.USE_MUTATION, + operation: TypedDocumentNode, + variables: Variables | undefined, + options?: Partial>, + ): AbortableApolloUseMutationResponse; + + private doRequestNew( + method: GQLMethod.MUTATION, + operation: TypedDocumentNode, + variables: Variables, + options?: Partial>, + ): AbortableApolloMutationResponse; + + private doRequestNew( + method: GQLMethod, + operation: TypedDocumentNode, + variables: Variables, + options?: + | QueryHookOptions + | MutationHookOptions + | MutationOptions, + ): + | AbortableApolloUseQueryResponse + | AbortableApolloUseMutationResponse + | AbortableApolloMutationResponse { + const abortController = new AbortController(); + const abortRequest = (reason?: any): void => { + if (!abortController.signal.aborted) { + abortController.abort(reason); + } + }; + + switch (method) { + case GQLMethod.USE_QUERY: + return { + ...useQuery(operation, { + variables, + client: this.graphQLClient.client, + ...options, + context: { + ...options?.context, + fetchOptions: { + signal: abortController.signal, + ...options?.context?.fetchOptions, + }, + }, + }), + abortRequest, + }; + case GQLMethod.USE_MUTATION: + // eslint-disable-next-line no-case-declarations + const mutationResult = useMutation(operation, { + variables, + client: this.graphQLClient.client, + ...(options as MutationHookOptions), + context: { + ...options?.context, + fetchOptions: { + signal: abortController.signal, + ...options?.context?.fetchOptions, + }, + }, + }); + + return [mutationResult[0], { ...mutationResult[1], abortRequest }]; + case GQLMethod.MUTATION: + return { + response: this.graphQLClient.client.mutate({ + mutation: operation, + variables, + ...(options as MutationOptions), + context: { + ...options?.context, + fetchOptions: { + signal: abortController.signal, + ...options?.context?.fetchOptions, + }, + }, + }), + abortRequest, + }; + default: + throw new Error(`unexpected GQLRequest type "${method}"`); + } + } + private useSwr< Data = any, ErrorResponse = any, diff --git a/src/lib/requests/client/GraphQLClient.ts b/src/lib/requests/client/GraphQLClient.ts new file mode 100644 index 00000000..6a66a6cd --- /dev/null +++ b/src/lib/requests/client/GraphQLClient.ts @@ -0,0 +1,53 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { ApolloClient, ApolloClientOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; +import { createUploadLink } from 'apollo-upload-client'; +import { BaseClient } from '@/lib/requests/client/BaseClient.ts'; +import { StrictTypedTypePolicies } from '@/lib/graphql/generated/apollo-helpers.ts'; + +const typePolicies: StrictTypedTypePolicies = { + GlobalMetaType: { keyFields: ['key'] }, + ExtensionType: { keyFields: ['apkName'] }, + AboutPayload: { keyFields: [] }, +}; + +// eslint-disable-next-line import/prefer-default-export +export class GraphQLClient extends BaseClient< + ApolloClient, + ApolloClientOptions, + null +> { + readonly fetcher = null; + + public declare client: ApolloClient; + + public override getBaseUrl(): string { + return `${super.getBaseUrl()}/api/graphql`; + } + + private createUploadLink() { + return createUploadLink({ uri: () => this.getBaseUrl() }); + } + + private createLink() { + return this.createUploadLink(); + } + + protected createClient() { + this.client = new ApolloClient({ + cache: new InMemoryCache({ + typePolicies, + }), + connectToDevTools: true, + link: this.createLink(), + }); + } + + public override updateConfig() {} +}