graphql-zeus icon indicating copy to clipboard operation
graphql-zeus copied to clipboard

Native support for file uploads

Open GauBen opened this issue 3 years ago • 5 comments

Hello!

I would love it if Zeus natively supported file uploads. I'll probably open a PR in the few weeks.

Related works:

  • spec
  • emcell offers a simple implementation here #150

GauBen avatar Jun 09 '22 06:06 GauBen

Wait Ill try to improve the plug-in system so produce lib script can take them into consideration

aexol avatar Jun 09 '22 19:06 aexol

What's the status of this? Is there a PR or a branch about file uploads?

BlueSialia avatar Nov 10 '22 07:11 BlueSialia

Hey @BlueSialia!

Aexol is (at this point) against adding dependencies: the generated code should be standalone until Zeus has a plugin system.

I created a userland implementation for a few features that I needed:

https://git.inpt.fr/inp-net/centraverse/-/blob/main/packages/app/src/lib/zeus.ts#L51-86

It relies on extract-files

GauBen avatar Nov 10 '22 08:11 GauBen

I cannot figure out how you are managing to send files like that. I keep geting errors about the map fields of https://github.com/jaydenseric/graphql-multipart-request-spec being defined incorrectly. Or the variable not being recognized as a variable.

BlueSialia avatar Jan 16 '23 20:01 BlueSialia

As of now, I have this monstrosity:

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

const scalars = ZeusScalars({
  JSONObject: {
    encode: (e: unknown) => stringifyObject(e, { indent: '', singleQuotes: false }),
  },
});

const graphql = (type: 'query' | 'mutation', options?: { scalars: any }) => {
  const headers: { [key: string]: string } = {};
  headers['Authorization'] = `Bearer 0123456789`;
  return Thunder(async (query, variables) => {
    let preQuery: string, body: BodyInit;
    const { clone, files } = extractFiles(variables, isExtractableFile, 'variables');

    if (files.size > 0) {
      const map: { [key: string]: string } = {};
      preQuery = '(';
      [...files.values()].forEach((value, i) => {
        map[`${i}`] = value;
        preQuery = `${preQuery}$${value[0].replace('variables.', '')}: ${value[0].split(GRAPHQL_TYPE_SEPARATOR)[1]}!, `;
      });
      preQuery = preQuery.replace(/, $/, ')');
      body = new FormData();
      body.set('operations', JSON.stringify({ query, variables: clone }).replace('mutation  {', `mutation ${preQuery} {`).replace(/__\$GRAPHQL__/g, '__GRAPHQL__'));
      body.set('map', JSON.stringify(map));
      for (const [i, [file]] of [...files].entries()) body.set(`${i}`, file);
    } else {
      headers['Content-Type'] = 'application/json';
      body = JSON.stringify({ query, variables });
    }
    const response = await fetch(`${process.env.API}/graphql`, { body, method: 'POST', headers });

    return response
      .json()
      .catch((e) => {
        throw new Error(`The server returned an error. ${inspect(e)}`);
      })
      .then((graphqlResponse: GraphQLResponse) => {
        if (graphqlResponse.errors || !response.ok) throw new GraphQLError(graphqlResponse);
        return graphqlResponse.data;
      });
  })(type as any, options);
};

const sendNode = (body: {
  id: string;
  name?: string;
  description?: string;
  data: object;
  objects?: Buffer[];
}) => {
  const variables: { [key: string]: Buffer } = {};
  for (let i = 0; i < body.objects.length; i++) {
    const object = body.objects[i];
    variables[`ZEUS_VARobject${i}${GRAPHQL_TYPE_SEPARATOR}Upload`] = object;
  }
  return graphql('mutation', { scalars })({
    createNode: [{ ...body, objects: body.objects.map((_, i) => $(`object${i}`, 'Upload')) }, { createdAt: true }],
  }, { variables }
  ).catch(() => {
    graphql('mutation', { scalars })({
      updateNode: [{ ...body, objects: body.objects.map((_, i) => $(`object${i}`, 'Upload')) }, { createdAt: true }]
    }, { variables }
    ).catch(error => {
      console.error(inspect(error).split('\n').map(str => str.trim()).join(' - '));
    });
  });
}

@aexol What is the $ function for? I'm using it and I feel it only makes my life more difficult. I need to do multiple weird stuff to complete/fix the use of variables. See the preQuery variable or the ZEUS_VARobject${i}${GRAPHQL_TYPE_SEPARATOR}Upload line.

Am I just using it completely wrong? Am I missing something else?

Thanks.

BlueSialia avatar Jan 17 '23 08:01 BlueSialia