3
votes

I have a headless Craft CMS that is returning data to my Nuxtjs app through a GraphQL endpoint through Apollo. I have a field that can return one of three different block types: richText, image, and pullQuote.

My GraphQL endpoint looks like this:

query($section:[String], $slug:[String]) {
    entries(section: $section, slug: $slug) {
        id,
        title,
        uri,
        ... on blog_blog_Entry{
            contentEngine{
                __typename,
                ...on contentEngine_richText_BlockType{
                    __typename,
                    id,
                    richText
                    fontColor,
                    backgroundColor
                }
                ...on contentEngine_image_BlockType{
                    __typename,
                    id,
                    backgroundColor,
                    imageWidth,
                    image {
                        id,
                        url
                    }
                }
                ...on contentEngine_pullQuote_BlockType{
                    __typename,
                    id,
                    backgroundColor,
                    fontColor,
                    quote
                }
            }
        }
    }
}

It returns data just fine, but I'm getting this error when trying to use it within my Nuxt component:

You are using the simple (heuristic) fragment matcher, but your queries contain union or interface types. Apollo Client will not be able to accurately map fragments. To make this error go away, use the IntrospectionFragmentMatcher as described in the docs: https://www.apollographql.com/docs/react/advanced/fragments.html#fragment-matcher

The infuriating thing is that this documentation leads to a 404. I've found a few other GitHub tickets that reference this link, so I'm not sure what steps I should be following.

I think what I need to do is to teach Apollo's memory cache. Since my response isn't that complicated, I think I can get away with Defining PossibleTypes manually.

I've tried the following, but I don't think I'm understanding how to set this up properly:

const cache = new InMemoryCache({
    possibleTypes: {
        contentEngine: [
            "contentEngine_richText_BlockType", 
            "contentEngine_pullQuote_BlockType", 
            "contentEngine_image_BlockType"
        ],
    },
});

Any help for getting around this issue would be a huge help.

WARNING: heuristic fragment matching going on!

3

3 Answers

2
votes

As the documentation states, using possibleTypes is only possible if using apollo-client version 3.0 or higher, which is currently in beta. Unless you're using the @apollo/client package as shown here, passing in the possibleTypes parameter will do nothing.

Additionally, you need to make sure each property in the object you pass in is the name of a union or interface, not the name of a field. From the docs

You can pass a possibleTypes option to the InMemoryCache constructor to specify supertype-subtype relationships in your schema. This object maps the name of an interface or union type (the supertype) to the types that implement or belong to it (the subtypes).

In other words, instead of using contentEngine as the key, you would use whatever the name of type of the contentEngine field was.

If you're using an earlier version of apollo-client, you'll need to create an IntrospectionFragmentMatcher as outlined in the docs here.

4
votes

Anyone coming here because they're using Craft CMS headless the answer above is correct but to save you some reading basically you have to let Apollo of the schema.

import possibleTypes from './possibleTypes.json';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
const fragmentMatcher = new IntrospectionFragmentMatcher({
    introspectionQueryResultData: possibleTypes
})

Where you define your client add the following

cache: new InMemoryCache({
    fragmentMatcher
})

To generate your schema you can either do the query below once and then paste the results into possibleTypes.json (wouldn't recommend as you'll be doing it a lot) OR alternatively add that process to your nuxtInitServer function to keep it updated automatically.

query {
  __schema {
    types {
      name
      kind
      possibleTypes {
        name
        description
      }
    }
  }
}
4
votes

I struggled with this myself, but after @mylesthe.dev (who responded above) spoke to me directly to provide fantastic support and some examples, I figured it out. So for anyone else still struggling like I was, here's the code (thanks to his work) which finally got things working for me:

First of all, in your nuxt.config.js set up your apollo configs:

// Apollo config and endpoint for graph ql
apollo: {
  includeNodeModules: true,
  clientConfigs: {
    default: '@/apollo/client-configs/default.js' // This is where you'll set up the client and import the possible fragment types
  }
},

Now we create the apollo client set up with the fragment schema file (which we'll create) in apollo/client-configs/default.js

import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import schema from './schema.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
    introspectionQueryResultData: schema
  })

export default ({req, app}) => {
    const token = process.env.GRAPHQL_TOKEN
    return {
        httpEndpoint: process.env.API_ENDPOINT,
        getAuth: () => `Bearer ${token}`, // remove if you're using the public schema
        cache: new InMemoryCache({ fragmentMatcher }),
    }
}


Now save an empty schema.json file in apollo/client-configs/.

Next we need to set up the script to query and generate this schema on nuxtServerInit. You'll need fs to write your schema file. You can install it with NPM: npm install --save fs.

Once installed, go back to your nuxt.config and add fs to the build:

build: {
  extend (config, ctx) {
    config.node = {
      fs: 'empty'
    }
  }
}

Then in your store/index.js:

import Vuex from 'vuex';
import fetch from 'node-fetch';
import fs from 'fs';


const createStore = () => {
  return new Vuex.Store({
    actions: {
        async nuxtServerInit({commit}, {app}) {

            // only update fragements locally
            if (process.env.NODE_ENV == 'local') {
                 
                // LOAD FRAGMENT TYPES AND STORE IN FILE
                // APOLLO READS THIS FILE LATER
                fetch(process.env.API_ENDPOINT, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json', authorization: 'Bearer ' + process.env.GRAPHQL_TOKEN, },
                    body: JSON.stringify({
                        variables: {},
                        query: `
                            {
                                __schema {
                                    types {
                                        kind
                                        name
                                        possibleTypes {
                                            name
                                        }
                                    }
                                }
                            }
                        `,
                    }),
                })
                .then(result => result.json())
                .then(result => {
                    // here we're filtering out any type information unrelated to unions or interfaces
                    const filteredData = result.data.__schema.types.filter(
                    type => type.possibleTypes !== null,
                    );
                    result.data.__schema.types = filteredData;
                    fs.writeFile('./apollo/client-configs/schema.json', JSON.stringify(result.data), err => {
                        if (err) {
                            console.error('Error writing fragmentTypes file', err);
                        }
                    });
                });

            }
            
        },
    }
  });
};

export default createStore

Your schema should now be generated locally to the schema file and that file will be stored in the apollo cache.