Skip to content
This repository was archived by the owner on Sep 24, 2025. It is now read-only.

feat (packages/nhost-js): graphql.post -> graphql.request#41

Merged
dbarrosop merged 3 commits into
mainfrom
request
Jun 30, 2025
Merged

feat (packages/nhost-js): graphql.post -> graphql.request#41
dbarrosop merged 3 commits into
mainfrom
request

Conversation

@dbarrosop

@dbarrosop dbarrosop commented Jun 27, 2025

Copy link
Copy Markdown
Member

PR Type

enhancement, documentation


Description

  • Rename graphql.post method to graphql.request throughout codebase

  • Update all usage examples and documentation to use request

  • Refactor GraphQL client interface and implementation for new method name

  • Update tests and demos to reflect API change


Changes walkthrough 📝

Relevant files
Enhancement
12 files
client.ts
Rename GraphQL client method from post to request               
+6/-6     
profile.tsx
Use graphql.request instead of post in profile fetch         
+1/-1     
upload.tsx
Use graphql.request for file fetching                                       
+1/-1     
index.ts
Replace graphql.post with graphql.request in endpoints     
+2/-2     
SecurityKeyClient.tsx
Use graphql.request for deleting security keys                     
+1/-1     
page.tsx
Use graphql.request for MFA status query                                 
+1/-1     
security-keys.tsx
Use graphql.request for fetching security keys                     
+1/-1     
page.tsx
Use graphql.request for server-side file fetch                     
+1/-1     
SecurityKeys.tsx
Replace graphql.post with graphql.request for key queries/mutations
+2/-2     
Profile.tsx
Use graphql.request for MFA status fetch                                 
+1/-1     
Upload.tsx
Use graphql.request for file fetching                                       
+1/-1     
queryHooks.ts
Use graphql.request in custom fetcher hook                             
+1/-1     
Documentation
4 files
README.md
Update usage example to use graphql.request                           
+1/-1     
graphql.mdx
Update API docs to reference graphql.request method           
+4/-4     
main.mdx
Update documentation example to use graphql.request           
+1/-1     
README.md
Update GraphQL fetcher example to use request                       
+1/-1     
Tests
3 files
docstrings.test.ts
Update tests to use graphql.request instead of post           
+1/-1     
graphql.test.ts
Refactor all GraphQL tests to use request method                 
+13/-9   
docstrings.test.ts
Update error handling test to use graphql.request               
+1/-1     
Configuration changes
1 files
nhost.toml
Bump auth version in configuration file                                   
+1/-1     

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @github-actions

    Copy link
    Copy Markdown

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    API Consistency

    Ensure that all references to the old post method are fully replaced by request in both implementation and documentation, and that no legacy code paths or aliases remain that could cause confusion or backward compatibility issues.

    export interface Client {
      /**
       * Execute a GraphQL query operation
       *
       * Queries are used to fetch data and should not modify any data on the server.
       *
       * @param request - GraphQL request object containing query and optional variables
       * @param options - Additional fetch options to apply to the request
       * @returns Promise with the GraphQL response and metadata
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      request<TResponseData = any, TVariables = GraphQLVariables>(
        request: GraphQLRequest<TVariables>,
        options?: RequestInit,
      ): Promise<FetchResponse<GraphQLResponse<TResponseData>>>;
    
      /**
       * Execute a GraphQL query operation using a typed document node
       *
       * @param document - TypedDocumentNode containing the query and type information
       * @param variables - Variables for the GraphQL operation
       * @param options - Additional fetch options to apply to the request
       * @returns Promise with the GraphQL response and metadata
       */
      request<TResponseData, TVariables = GraphQLVariables>(
        document: TypedDocumentNode<TResponseData, TVariables>,
        variables?: TVariables,
        options?: RequestInit,
      ): Promise<FetchResponse<GraphQLResponse<TResponseData>>>;
    
      /**
       * URL for the GraphQL endpoint.
       */
      url: string;
    }
    
    /**
     * Creates a GraphQL API client for interacting with a GraphQL endpoint.
     *
     * This client provides methods for executing queries and mutations against
     * a GraphQL API, with support for middleware functions to handle authentication,
     * error handling, and other cross-cutting concerns.
     *
     * @param url - Base URL for the GraphQL endpoint
     * @param chainFunctions - Array of middleware functions for the fetch chain
     * @returns GraphQL client with query and mutation methods
     */
    export const createAPIClient = (
      url: string,
      chainFunctions: ChainFunction[] = [],
    ): Client => {
      const enhancedFetch = createEnhancedFetch(chainFunctions);
    
      const executeOperation = async <
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        TResponseData = any,
        TVariables = GraphQLVariables,
      >(
        request: GraphQLRequest<TVariables>,
        options?: RequestInit,
      ): Promise<FetchResponse<GraphQLResponse<TResponseData>>> => {
        const response = await enhancedFetch(`${url}`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(request),
          ...options,
        });
    
        const body = await response.text();
        const data: GraphQLResponse<TResponseData> = (
          body ? JSON.parse(body) : {}
        ) as GraphQLResponse<TResponseData>;
    
        const resp = {
          body: data,
          status: response.status,
          headers: response.headers,
        };
    
        if (data.errors) {
          throw new FetchError(data, response.status, response.headers);
        }
    
        return resp;
      };
    
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      function request<TResponseData = any, TVariables = GraphQLVariables>(
        request: GraphQLRequest<TVariables>,
        options?: RequestInit,
      ): Promise<FetchResponse<GraphQLResponse<TResponseData>>>;
      function request<TResponseData, TVariables = GraphQLVariables>(
        document: TypedDocumentNode<TResponseData, TVariables>,
        variables?: TVariables,
        options?: RequestInit,
      ): Promise<FetchResponse<GraphQLResponse<TResponseData>>>;
      function request<TResponseData, TVariables = GraphQLVariables>(
        requestOrDocument:
          | GraphQLRequest<TVariables>
          | TypedDocumentNode<TResponseData, TVariables>,
        variablesOrOptions?: TVariables | RequestInit,
        options?: RequestInit,
      ): Promise<FetchResponse<GraphQLResponse<TResponseData>>> {
        if (typeof requestOrDocument === "object" && "kind" in requestOrDocument) {
          const definition = requestOrDocument.definitions[0];
    
          const request: GraphQLRequest<TVariables> = {
            query: requestOrDocument.loc?.source.body || "",
            variables: variablesOrOptions as TVariables,
            operationName:
              definition && "name" in definition
                ? definition.name?.value
                : undefined,
          };
          return executeOperation(request, options);
        } else {
          // Handle GraphQLRequest
          const request = requestOrDocument;
          const requestOptions = variablesOrOptions as RequestInit;
          return executeOperation(request, requestOptions);
        }
      }
    
      return {
        request,
        url,
      } as Client;
    };
    Test Coverage for API Change

    Confirm that all test cases previously using post are now using request, and that the tests still comprehensively cover both query and mutation scenarios, including error handling.

      });
    
      if (!resp.body.session) {
        throw new Error("Session is null");
      }
    });
    
    it("query", async () => {
      const users = await nhost.graphql.request<GetUsersResponse>({
        query: `query GetUsers {
            users {
              id
              displayName
              metadata
            }
          }`,
      });
    
      expect(users.body.data?.users).toBeDefined();
      expect(users.body.data?.users[0].id).toBeDefined();
      expect(users.body.data?.users[0].displayName).toBeDefined();
      expect(users.body.data?.users[0].metadata).toBeDefined();
      expect(users.body.data?.users[0].metadata.source).toBe("test");
    
      userID = users.body.data?.users[0].id || "";
    });
    
    it("mutate", async () => {
      const resp = await nhost.graphql.request<UpdateUsersDisplayNameResponse>({
        query: `mutation UpdateUsersDisplayName($id: uuid!, $displayName: String!) {
            updateUser(pk_columns: {id: $id}, _set: {displayName: $displayName}) {
              id
              displayName
            }
          }`,
        variables: {
          id: userID,
          displayName: "My New Display Name",
        },
        operationName: "UpdateUsersDisplayName",
      });
    
      expect(resp.body.data?.updateUser).toBeDefined();
      expect(resp.body.data?.updateUser.id).toBeDefined();
      expect(resp.body.data?.updateUser.displayName).toBe("My New Display Name");
    });
    
    it("errors: bad query", async () => {
      try {
        await nhost.graphql.request({
          query: `wrong query`,
        });
    
        expect(true).toBe(false);
      } catch (error) {
        const resp = error as FetchError<GraphQLResponse>;
    
        expect(resp.body.errors).toBeDefined();
        expect(resp.body.errors).toHaveLength(1);
        const errors = resp.body.errors!;
        expect(errors[0].message).toBe("not a valid graphql query");
        expect(error.message).toBe("not a valid graphql query");
        expect(errors[0].extensions?.path).toBe("$.query");
        expect(errors[0].extensions?.code).toBe("validation-failed");
      }
    });
    
    it("errors: no permissions or doesn't exist", async () => {
      try {
        await nhost.graphql.request({
          query: `query { restricted { id } }`,
        });
    
        expect(true).toBe(false);
      } catch (error) {
        const resp = error as FetchError<GraphQLResponse>;
        expect(resp.body.errors).toBeDefined();
        expect(resp.body.errors).toHaveLength(1);
        const errors = resp.body.errors!;
        expect(errors[0]?.message).toBe(
          "field 'restricted' not found in type: 'query_root'",
        );
        expect(error.message).toBe(
          "field 'restricted' not found in type: 'query_root'",
        );
        expect(errors[0].extensions?.path).toBe("$.selectionSet.restricted");
        expect(errors[0].extensions?.code).toBe("validation-failed");
      }
    });
    
    it("query with TypedDocumentNode", async () => {
      const GetUsersDocument = gql`
        query GetUsers($limit: Int) {
          users(limit: $limit) {
            id
            displayName
            metadata
          }
        }
      `;
    
      const users = await nhost.graphql.request<GetUsersResponse>(
        GetUsersDocument,
        {
          limit: 10,
        },
      );
    
      console.log(users.body.data?.users);
    
      expect(users.body.data?.users).toBeDefined();
      expect(users.body.data?.users[0].id).toBeDefined();
      expect(users.body.data?.users[0].displayName).toBeDefined();
      expect(users.body.data?.users[0].metadata).toBeDefined();
      expect(users.body.data?.users[0].metadata.source).toBe("test");
    });
    
    it("query with TypedDocumentNode without variables", async () => {
      const GetUsersDocument = gql`
        query GetUsers($limit: Int) {
          users(limit: $limit) {
            id
            displayName
            metadata
          }
        }
      `;
    
      const users =
        await nhost.graphql.request<GetUsersResponse>(GetUsersDocument);
    
      console.log(users.body.data?.users);
    
      expect(users.body.data?.users).toBeDefined();
      expect(users.body.data?.users[0].id).toBeDefined();
      expect(users.body.data?.users[0].displayName).toBeDefined();
      expect(users.body.data?.users[0].metadata).toBeDefined();
    });
    
    it("query with TypedDocumentNode errors", async () => {
      try {
        const RestrictedQuery = gql`
          query {
            restricted {
              id
            }
          }
        `;
        await nhost.graphql.request(RestrictedQuery);
    
        expect(true).toBe(false);
      } catch (error) {
        const resp = error as FetchError<GraphQLResponse>;

    @github-actions

    Copy link
    Copy Markdown

    PR Code Suggestions ✨

    No code suggestions found for the PR.

    @dbarrosop dbarrosop merged commit ea16040 into main Jun 30, 2025
    9 checks passed
    @dbarrosop dbarrosop deleted the request branch June 30, 2025 12:12
    Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants