apollographql / persistgraphql

A build tool for GraphQL projects.
MIT License
425 stars 57 forks source link

How should we namespace open source queryTransformers? #62

Open toddtarsi opened 6 years ago

toddtarsi commented 6 years ago

First off, let me say thanks for the awesome codebase and toolkit! I am using it with Apollo 2.x now and am really enjoying the lighter network footprint! To help others work with apollo-link-state and persistgraphql, I have a small queryTransformer which strips any fields with the @client directive, and I'd like to share it for others to use. Is there a repo naming pattern I should aim for, or anything like that?

ex: persistgraphql-query-transform-trim-client-fields

Thanks again!

oluwie commented 6 years ago

Hi @toddtarsi, I was hoping you could hep me out. I have trouble getting this library to work with Apollo 2.0. I was hoping you could help me in my implementation to see what I'm doing wrong.

This is how I'm implementing my Apollo client. const link = ApolloLink.from([addHeaders, stateLink, restLink, httpLink]); const apolloClient = new ApolloClient({ cache, link });

const newApolloClient = addPersistedQueries(apolloClient, queryMap); export default newApolloClient;`

I'm using the apollo graphql function from react-apollo for querying but the queries don't seem to be getting transformed.

Any help would be greatly appreciated.

Thank you!

toddtarsi commented 6 years ago

I'll do my best @oluwie -

Okay, so this is how I'm using persistgraphql with a small commander script to build my queries:

// @flow

/* eslint-disable no-console */
/* eslint-disable import/no-extraneous-dependencies */

import program from 'commander';
import { ExtractGQL } from 'persistgraphql/lib/src/ExtractGQL';
import queryTransformers from '../helpers/queryTransformers';

program
  .version('0.1.0')
  .usage('[options] <file...>')
  .option('-i, --input <p>', 'Input Directory', '')
  .option('-o, --output <p>', 'Output file', '')
  .parse(process.argv);

new ExtractGQL({
  inputFilePath: program.input,
  outputFilePath: program.output,
  queryTransformers,
}).extract();

My queryTransformers file is the following. It looks a bit crazy, but uses the addTypename transform, as well as another transform I wrote to strip all client fields. I intend to open source that this weekend (edit: i fixed the d.name.value check later):

// @flow

/* eslint-disable no-console */
/* eslint-disable import/no-extraneous-dependencies */

import {
  DefinitionNode,
  DocumentNode,
} from 'graphql';
import { QueryTransformer } from 'persistgraphql/lib/src/common';
import { addTypenameTransformer } from 'persistgraphql/lib/src/queryTransformers';

function removeClientFieldsFromSelectionSet(selectionSet) {
  if (selectionSet.selections) {
    // eslint-disable-next-line no-param-reassign
    selectionSet.selections = selectionSet.selections.map((selection) => {
      if (selection.directives && selection.directives.length) {
        const hasClient = selection.directives.find(d => d.name.value === 'client');
        if (hasClient) return false;
      }
      if (selection.kind === 'Field' || selection.kind === 'InlineFragment') {
        if (selection.selectionSet) {
          removeClientFieldsFromSelectionSet(selection.selectionSet);
        }
      }
      return selection;
    }).filter(s => !!s);
  }
  return selectionSet;
}

const removeClientFieldsTransformer: QueryTransformer = (doc: DocumentNode) => {
  const docClone = JSON.parse(JSON.stringify(doc));

  docClone.definitions.forEach((definition: DefinitionNode) => {
    const isRoot = definition.kind === 'OperationDefinition';
    removeClientFieldsFromSelectionSet(definition.selectionSet, isRoot);
  });

  return docClone;
};

export default [
  addTypenameTransformer,
  removeClientFieldsTransformer,
];

As a note, it's currently impossible to use custom transforms with https://github.com/leoasis/graphql-persisted-document-loader until https://github.com/leoasis/graphql-persisted-document-loader/pull/2 is accepted, at which point you'll be able to pass in your own queryTransformers via the webpack loader. I have a fork doing this, which I'm using here: https://github.com/toddtarsi/graphql-persisted-document-loader. This is what that looks like inline with the webpack stuff:

import persistedQueries from './persistedQueries.json';
import queryTransformers from './queryTransformers';
...
        {
          exclude: /node_modules/,
          test: /\.(graphql|gql)$/,
          use: [
            {
              loader: 'graphql-persisted-document-loader',
              options: {
                generateId: query => persistedQueries[query],
                queryTransformers,
              },
            }, // <= Before graphql-tag/loader!
            { loader: 'graphql-tag/loader' },
          ],
        },
...

Note, if this isn't accepted, we're both over a barrel, hah!

Also, in case it's an issue in the link ordering, this is the overall order of my link components too: [error, context, state, persisted-queries, http].

Hope this helps!

toddtarsi commented 6 years ago

Oh, @oluwie - This is how I'm building my persisted-queries link more granularly:

  const persistOpts = {
    generateHash: ({ documentId }) => documentId,
    useGETForHashedQueries: false,
  };
  const httpLink = from([
    createPersistedQueryLink(persistOpts),
    new HttpLink(network),
  ]);
toddtarsi commented 6 years ago

To provide a bit more explanation of why this is needed (last post honest):

In 2.0, our issue is this:

By the time the query has reached the http request, it has been transformed since the initial expectation (in our case, missing client fields). If we remove these fields from the fragments, we lose our data dependencies. If we keep these fields, the serverside queries will fail to match what the client sent up.

So, to get our shape back to matching, we have to transform our persisted queries to be the correct shape right before it goes out over http. By trimming our client fields here, the persisted query looked up on the server will match what the client actually sends after the whole apollo-link-state transformation.

oluwie commented 6 years ago

@toddtarsi I see. You're using the apollo-link-persisted-queries for the actual as the network interface. From the README, I was under the impression that you use the addPersistedQueries function exported by the ApolloNetworkInterface.ts file to modify the network interface to use the queryMap object provided to that function.

I'm looking for a way to use the network interface provided by this module w/o having to import the apollo-link-persisted-queries library.

toddtarsi commented 6 years ago

In that case, I don't have too much I can offer to help, but I see in your initial comment this:

const link = ApolloLink.from([addHeaders, stateLink, restLink, httpLink]);
const apolloClient = new ApolloClient({ cache, link });
const newApolloClient = addPersistedQueries(apolloClient, queryMap);
export default newApolloClient;

I think addPersistedQueries takes a networkInterface, not an apollo client instance. The whole networkInterface API around 2.x was converted to simple network options for httpLink. If you're on 1.x, then you'd instead pass it in to something like this:

const networkInterface = createNetworkInterface({
  uri: 'http://api.example.com/graphql'
});
const client = new ApolloClient({
  networkInterface: addPersistedQueries(networkInterface)
});

On a side note, if you're using stateLink and httpLink, I really think you should look at the persisted queries link again. It fits right in with that code paradigm quite nicely. Otherwise, you'll need an intermediate link before the httpLink and after the stateLink to vacuum your query down to the variables and id in your request. If you use this anywhere else, you either won't be able to resolve the client fields using apollo-link-state, or you won't have the client fields persisted in the query body. All in all, its a lot of stress and headaches, for a solution that's already mostly there.

toddtarsi commented 6 years ago

Hey all, I hope I am not raising too much trouble. I'll namespace the query transformer script above as:

persistgraphql-query-transform-trim-client-fields

Thanks again for the amazing tools, and you all are awesome!