graphql-editor / graphql-zeus

GraphQL client and GraphQL code generator with GraphQL autocomplete library generation ⚡⚡⚡ for browser,nodejs and react native ( apollo compatible )
https://graphqleditor.com/docs/tools/zeus/index/
MIT License
1.94k stars 104 forks source link

[Feature Request] Support graphql upload specification #150

Closed Saeid-Za closed 2 years ago

Saeid-Za commented 4 years ago

Hello, first of all, let me thank the author and contributors of this project, I really enjoy using this simple yet productive way of working with graphql. Keep up the hard work! As the title of this proposal suggests, I'd like to propose a feature to support uploading files in graphql. As the majority of developers using graphql have use cases around uploading files, a specification for upload files in graphql, has emerged and many have embraced it. There are libraries that would support this specification but they are all for apollo-client. As a user of zeus-graphl, I believe this project has much potential and in my opinion can evolve over time and even remove the need for Apollo client. Before talking about the details, I would like to know the opinion of the author and other contributors, would it make sense to you to add this functionality or would it be in contrast to what you had in your mind for zeus? I implemented a working wrapper for zeus to support this use-case, but i'd love to see it as a part of the project. Looking forward to hear some comments about this feature.

emcell commented 3 years ago

Had the same problem. You can do this with Thunder. Here's an example

import { extractFiles } from 'extract-files';

const valueIsBuffer = (value: any): value is Buffer => {
  return value instanceof Buffer;
};

async function fetchGraphQlMutlipart<T>(
  url: string,
  query: string,
  variables: Record<string, any> | undefined,
  files: Map<T, string[]>,
): Promise<Response> {
  const formData = new FormData();

  formData.append('operations', JSON.stringify({ query, variables }));
  const map: any = {};
  let i = 0;
  files.forEach((paths) => {
    map[++i] = paths;
  });
  formData.append('map', JSON.stringify(map));

  i = 0;
  files.forEach((paths, file) => {
    formData.append(`${++i}`, file);
  });
  //body = formData;
  return fetch(url, {
    method: 'POST',
    body: formData as any,
  });
}

async function fetchGraphQl(
  url: string,
  query: string,
  variables?: Record<string, any>,
): Promise<Response> {
  return fetch(url, {
    method: 'POST',
    body: JSON.stringify({ query, variables }),
    headers: {
      'Content-Type': 'application/json',
    },
  });
}

function createThunder(url: string) {
  return Thunder(async (query, variables) => {
    const { clone, files } = extractFiles<Buffer>(
      variables,
      'variables',
      valueIsBuffer,
    );
    let response: Response;
    if (files.size) {
      response = await fetchGraphQlMutlipart(url, query, clone, files);
    } else {
      response = await fetchGraphQl(url, query, variables);
    }
    if (!response.ok) {
      return new Promise((resolve, reject) => {
        response
          .text()
          .then((text) => {
            try {
              reject(JSON.parse(text));
            } catch (err) {
              reject(text);
            }
          })
          .catch(reject);
      });
    }
    const json = (await response.json()) as GraphQLResponse;
    if (json.errors) throw new GraphQLError(json);
    return json.data;
  }, apiSubscription([url]));
}

usage:

      const chain = createThunder('http://localhost:3000/graphql');
      const result = await chain.mutation(
        {
          test: [
            { file: $`file` },
            { id: true },
          ],
        },
        {
          file: Buffer.from('hello world'),
        },
      );

Note: I've been using Buffer as a source for my file since I'm currently working in a node.js environment. If you're working in a browser environment you can exchange valueIsBuffer with isExtractableFile from extract-files package. This will allow the usage of File and Blob as a source for uploading

pedrosimao commented 2 years ago

Finally, I have managed to use your piece of code on my app. Thanks a lot for sharing!

BlueSialia commented 1 year ago

Hi @emcell and @pedrosimao. If you are still using this, could you share how you have adapted this code to the changes to the $ function? Thanks.

emcell commented 1 year ago

Hi @BlueSialia, haven't upgraded zeus in the project since my post. Since then, many things changed in zeus. I'm not sure if it is working this way in current versions.

BlueSialia commented 1 year ago

Would you mind sharing which version specifically of graphql-zeus you are using? @emcell