gatsby: [bug] ☂️ umbrella issue for schema customization issues

This is a meta issue for all the issues with 2.2.0, that were introduced by the schema refactoring.

What?

See the blog post for the details about why we did the refactoring and what’s it all about.

See the release blog post for release notes and final updates.

How?

Install latest version of Gatsby and try running your site. Hopefully it will all just work. If you want, you could also try the two new APIs (createTypes and createResolvers).

yarn add gatsby

Changelog

gatsby@2.5.0

  • Published

gatsby@2.5.0-rc.1

  • Moved to explicit directives instead of addResolver

gatsby@2.4.0-alpha.2

  • Master back merge, upgrade graphql-compose

gatsby@2.4.0-alpha.1

gatsby@2.2.0

  • 🍾

gatsby@2.2.0-rc.2

  • Fixed a regression when empty strings mixed with dates cause stuff to not be intrepreted as dates

gatsby@2.2.0-rc.1

  • merge latest master
  • docs update

gatsby@2.2.0-alpha.6

  • merge latest master
  • better SDL parse error messages

gatsby@2.2.0-alpha.5

  • fix regression in connection naming in case types are lowercase named

gatsby@2.2.0-alpha.4

  • Fixed custom scalars not having any filters
  • Added errors when trying to override Node interface or generated filter/sort types.

gatsby@2.2.0-alpha.3

  • Added a new convenience API modelled after graphql-compose. See using-type-definitions example.
exports.sourceNodes = ({ actions, schema }) => {
  const { createTypes } = actions
  createTypes([
    schema.buildObjectType({
      name: `CommentJson`,
      fields: {
        text: `String!`,
        blog: {
          type: `BlogJson`,
          resolve(parent, args, context) {
            return context.nodeModel.getNodeById({
              id: parent.author,
              type: `BlogJson`,
            })
          },
        },
        author: {
          type: `AuthorJson`,
          resolve(parent, args, context) {
            return context.nodeModel.getNodeById({
              id: parent.author,
              type: `AuthorJson`,
            })
          },
        },
      },
      interfaces: [`Node`],
    }),
  ])
}

gatsby@2.2.0-alpha.2

  • Fixed regression with Node id field not being a String, like in current master.
  • Upgraded to graphql-compose@5.11.0
  • FilterInput types are now not prefixed by output type, reducing proliferation of types

gatsby@2.2.0-alpha.1

  • Fixed issue with pagination over null query results
  • @dontInfer(noDefaultResolvers: false) actually works
  • in createResolvers resolvers info.originalResolver is available even if there is no resolver on original field

gatsby@2.2.0-alpha.0

  • Redone alpha version with proper release version
  • Fix Node types without actual nodes not being added

gatsby@2.1.20-alpha.0

  • Initial alpha

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 4
  • Comments: 131 (102 by maintainers)

Commits related to this issue

Most upvoted comments

Published gatsby@2.2.0. Thank you everyone!

@pauleveritt One way to set up foreign-key relations between fields is with the @link directive. For a typical blog (posts as .md files, author info and tags in .yaml files) it could look like this:

exports.sourceNodes = ({ actions }) => {
  actions.createTypes(`
    type MarkdownRemark implements Node @dontInfer {
      frontmatter: Frontmatter
    }
    type Frontmatter {
      title: String!
      author: AuthorYaml @link(by: "email")
      tags: [TagYaml] @link(by: "slug")
    }
    type AuthorYaml implements Node @dontInfer {
      name: String!
      email: String!
      info: String
      posts: [MarkdownRemark] @link(by: "frontmatter.author.email", from: "email")
    }
    type TagYaml implements Node @dontInfer {
      slug: String!
      title: String!
      description: String
      posts: [MarkdownRemark] @link(by: "frontmatter.tags.slug", from: "slug")
    }
  `)
}

I’ve also put this together in this repo.

We also finally have a bit of documentation on the schema customization APIs here. Please let us know if anything there is unclear or missing.

Didn’t know that clicking that would affect everyone.

@stefanprobst I got this working and it works with ImageSharp. Very very cool and a game changer for working with a 3rd party schema 🎉 🎉

As for the second approach, I’d be interested in your findings – it should be possible to query for the added remote File nodes in the resolver with context.nodeModel.getAllNodes({ type: 'File' }) or with something like context.nodeModel.runQuery({ type: 'File', query: { filter: { name: { regex: "/^remote/" } } } })

Here’s what I did …

exports.sourceNodes = async ({ actions, store, cache, createNodeId }) => {
  ... do createRemoteFileNode stuff ...
}

exports.createResolvers = ({ createResolvers, schema }) => {
  createResolvers({
    CMS_Thing: {
      thumbFile: {
        type: 'File!',
        async resolve(source, args, context, info) {
          const data = await context.nodeModel.runQuery({
            type: 'File',
            query: { filter: { fields: { ThingThumb: { eq: 'true' }, thingId: { eq: source.id } } } }
          })
          return data[0];
        }
      }
    }
  });
}

(This depends on me creating the file nodes with some fields, obviously. )

The optimal path (for my use case) will be to be able to use createRemoteFileNode in createResolvers so hopefully we can figure that out.

@janosh

  • When changing the field type of frontmatter.tags from [String] to [Tag], the field argument of group would also have to be adjusted to either frontmatter___tags___title or frontmatter___tags___slug. UNFORTUNATELY this does not yet work correctly, because we don’t call field resolvers for group fields, only for filter and sort fields (see #11368).
  • For the moment, you can workaround this by using a dummy filter to trigger the field resolver:
{
  allMarkdownRemark(filter: {frontmatter: {tags: {elemMatch: {title: {ne: null}}}}}) {
    group(field: frontmatter___tags___title) {
      fieldValue
      totalCount
      nodes {
        frontmatter {
          title
        }
      }
    }
  }
}

Fixing this is on my todo list – i’ll get on it asap

  • The issue with your createResolvers snippet is this: getAllNodes will retrieve nodes by type, where “node” means objects with a unique ID created by source or transformer plugins (with the createNode action). So you would have to retrieve MarkdownRemark nodes, and then further manipulate the results in the resolver.
  • Depending on your usecase, you might not even need to use createTypes, but could simply add a custom root query field to group posts by tag, and include a slug field:
// gatsby-node.js
const { kebabCase } = require(`lodash`)

exports.createResolvers = ({ createResolvers }) => {
  createResolvers({
    Query: {
      allMarkdownRemarkGroupedByTag: {
        type: [
          `type MarkdownRemarkGroup {
            nodes: [MarkdownRemark]
            count: Int
            tag: String
            slug: String
          }`,
        ],
        resolve(source, args, context, info) {
          const allMarkdownRemarkNodes = context.nodeModel.getAllNodes({
            type: `MarkdownRemark`,
          })

          const groupedByTag = allMarkdownRemarkNodes.reduce((acc, node) => {
            const { tags } = node.frontmatter
            tags.forEach(tag => {
              acc[tag] = (acc[tag] || []).concat(node)
            })
            return acc
          }, {})

          return Object.entries(groupedByTag).map(([tag, nodes]) => {
            return {
              nodes,
              count: nodes.length,
              tag,
              slug: kebabCase(tag),
            }
          })
        },
      },
    },
  })
}

Published gatsby@2.5.0.

@angeloashmore It should be fixed with https://github.com/gatsbyjs/gatsby/pull/13028, I’ll make a pre-release version today so you could test.

@danoc Currently there is no way to disable warnings like that. With https://github.com/gatsbyjs/gatsby/pull/13028 if you use @dontInfer, no example value checking should happen and then there won’t be a warning. However there won’t be any inference too.

@d4rekanguok Could you provide a small reproduction for this? It should work as you described.

Edit: This no longer happens, even though I didn’t update anything. I have no idea what happened, or what led me to believe that it didn’t work; but it works now… great work folks!

Hi folks, I played around with this code:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions
  createTypes(`
    type MarkdownRemarkFrontmatter {
      image: File  // <---- could be a relative path, could be an empty string
    }

    type MarkdownRemark implements Node {
      frontmatter: MarkdownRemarkFrontmatter
    }
  `)
}

I was expecting to get either a File node or null when query for a markdown remark node. Instead, I always get null… I’d have to manually look up the file:

createResolvers({
    MarkdownRemarkFrontmatter: {
      image: {
        type: `File`,
        resolve(src, args, context) {
          const filePath = src[info.fieldName]
          // find & return the file node
        }
      }
    }
  })

Is there a way for me to simply tell gatsby, “this will be a File node, please find it or return null”?

@LoicMahieu 👍 Something like this is part of our roadmap.

@NickyMeuleman thanks for experimenting! after looking at this i think it’s something we need to fix in core: both issues have to with the fact that we use the interfaces’s resolver when manually preparing nodes so we can run a filter on them. instead, we should run the resolver of the type we get from the interface’s resolveType, which is also what happens when graphql processes the selection set.

@laradevitt ah, sorry, should have checked the readme.

I still don’t know why it broke with 2.2.0.

This is very probably a regression in Gatsby, as with 2.2.0 type inference changed a bit – but normalizing paths in gatsby-plugin-netlify-cms-paths definitely seems more correct 👍

@janosh grouping on resolved fields should now work with gatsby@2.8.7

@Spiderpig86 Do you have this issue when upgrading to 2.4? I wonder if you could provide a reproduction project (or code to your app).

@motleydev Hi! So createTypes and mappings are ran pretty much at the same time. Types defined by users always take priority over mapping types. You need to use custom resolver in this case. Gatsby can’t really know that you want an object that will be serialized as string in this case, as String and CategoriesYaml aren’t compatible types.

@eddiemf Thanks a lot! Got the error, will investigate.

Lots of small fixes in 2.2.10, including explicit Node relationships working correctly.

Published gatsby@2.2.0-rc.2.

@hilja The new schema customization API is not about running queries, but about schema generation, so this has not changed yet. There is experimental work going on to allow field resolvers to offload work to other processes, but that is not ready yet.

Tentative goal is to merge this to master and release this next week. Please comment and try it 😃

@skinandbones Very cool! Btw, you can use firstOnly: true in runQuery to only get the first result.

@stefanprobst Here is a example:

  1. https://github.com/LoicMahieu/test-gatsby-refactor-schema Here we get able to link comments to post by a complex lookup on parent File.

  2. https://github.com/LoicMahieu/test-gatsby-refactor-schema/tree/custom-transformer-json Here we get able to link them by using a custom JSON transformer where we could transform the object and also change the id. This method works but: if post is deleted and reference still exists in comments, gatsby will fail. It could be fixed by not use the ___NODE way but the new createResolvers: demo

@NicoleEtLui Please update gatsby-plugin-manifest and gatsby-plugin-sharp, this was fixed in those packages with https://github.com/gatsbyjs/gatsby/pull/12332