gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.19k stars 10.33k forks source link

[bug] ☂️ umbrella issue for schema customization issues #12272

Closed freiksenet closed 4 years ago

freiksenet commented 5 years ago

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

gatsby@2.5.0-rc.1

gatsby@2.4.0-alpha.2

gatsby@2.4.0-alpha.1

gatsby@2.2.0

gatsby@2.2.0-rc.2

gatsby@2.2.0-rc.1

gatsby@2.2.0-alpha.6

gatsby@2.2.0-alpha.5

gatsby@2.2.0-alpha.4

gatsby@2.2.0-alpha.3

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

gatsby@2.2.0-alpha.1

gatsby@2.2.0-alpha.0

gatsby@2.1.20-alpha.0

eddiemf commented 5 years ago

I'm having exactly the same problem as @kennedyrose , but in my case the solution provided by @stefanprobst didn't quite help, although I'm not really sure what I'm doing, I just tried every possible way of creating this schema but it always returns an error regarding multiple types named....

Here's the code:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions;
  const typeDefs = `
    type Wordpress__PAGEAcfIntro {
      title: String
      content: String
    }
    type Wordpress__PAGEAcfThe_problem {
      title: String
      content: String
    }
    type Wordpress__PAGEAcfThe_solution {
      title: String
      content: String
    }
    type Wordpress__PAGEAcf {
      Intro: Wordpress__PAGEAcfIntro
      The_problem: Wordpress__PAGEAcfThe_problem
      The_solution: Wordpress__PAGEAcfThe_solution
    }
    type Wordpress__PAGE implements Node {
      acf: Wordpress__PAGEAcf
    }
  `;
  createTypes(typeDefs);
};

At first I tried setting only the types I needed (the first 3) with their fields, but then I got Error: Schema must contain unique named types but contains multiple types named "Wordpress__PAGEAcfIntro".. While searching for a solution I tried @stefanprobst solution (as in my code, not sure if correct though) but didn't have any luck.

Also, this is quite a lot to just make some fields have optional string values (the query breaks if any of the fields doesn't have a title or content), and this is just the initial implementation, I'll probably have to do the same in the whole website for a lot more fields that also might be optional, so I'm very scared of this solution and really hope there's a better way of solving it.

eddiemf commented 5 years ago

Well, after more digging around and trying stuff without actually know what I'm doing, I came up with this snippet that solved the problem:

exports.createResolvers = ({ createResolvers }) => {
  createResolvers({
    Wordpress__PAGEAcfIntro: {
      title: { type: `String` },
      content: { type: `String` },
    },
    Wordpress__PAGEAcfThe_problem: {
      title: { type: `String` },
      content: { type: `String` },
    },
    Wordpress__PAGEAcfThe_solution: {
      title: { type: `String` },
      content: { type: `String` },
    },
  });
};

Although I'm still not sure why my first solution didn't work. This seems to be a lot easier to maintain though.

Final thoughts is that I'm not sure if I just lack the knowledge of Gatsby APIs or the docs are misleading for this specific issue. I think it should be easier to find a solution for the question how to have optional fields in a graphql query using gatsby, because it's an extremely basic thing when working with a CMS like WordPress where you can have multiple custom fields and most likely a lot of them are going to be optional.

s-appdeveloper commented 5 years ago

Declaring a field as an array of files doesn't work for me.

  const { createTypes } = actions
  const typeDefs = `
    type MarkdownRemarkFrontmatter implements Node {
      images: [File]
    }
    type MarkdownRemark implements Node {
      frontmatter: MarkdownRemarkFrontmatter
    }
  `
  createTypes(typeDefs)
}

Probably has something to do with https://github.com/gatsbyjs/gatsby/issues/12696

Wolfsun commented 5 years ago

I'm having exactly the same problem as @kennedyrose , but in my case the solution provided by @stefanprobst didn't quite help, although I'm not really sure what I'm doing, I just tried every possible way of creating this schema but it always returns an error regarding multiple types named....

Here's the code:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions;
  const typeDefs = `
    type Wordpress__PAGEAcfIntro {
      title: String
      content: String
    }
    type Wordpress__PAGEAcfThe_problem {
      title: String
      content: String
    }
    type Wordpress__PAGEAcfThe_solution {
      title: String
      content: String
    }
    type Wordpress__PAGEAcf {
      Intro: Wordpress__PAGEAcfIntro
      The_problem: Wordpress__PAGEAcfThe_problem
      The_solution: Wordpress__PAGEAcfThe_solution
    }
    type Wordpress__PAGE implements Node {
      acf: Wordpress__PAGEAcf
    }
  `;
  createTypes(typeDefs);
};

At first I tried setting only the types I needed (the first 3) with their fields, but then I got Error: Schema must contain unique named types but contains multiple types named "Wordpress__PAGEAcfIntro".. While searching for a solution I tried @stefanprobst solution (as in my code, not sure if correct though) but didn't have any luck.

Also, this is quite a lot to just make some fields have optional string values (the query breaks if any of the fields doesn't have a title or content), and this is just the initial implementation, I'll probably have to do the same in the whole website for a lot more fields that also might be optional, so I'm very scared of this solution and really hope there's a better way of solving it.

@eddiemf, not totally sure but I think the issue you could be having is that the types you are trying to define have already been inferred from the Wordpress data source. So in effect they are being defined twice. Using createResolvers will simply create new fields on existing types, or override existing fields(?) by the looks of things.

freiksenet commented 5 years ago

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

eddiemf commented 5 years ago

@Wolfsun Yeah that's exactly what I thought about it, but I feel like the docs lead you to use createTypes as a solution for this problem, when in fact you should use createResolvers.

As I've said, I think this is a super common problem when dealing with WordPress, so it would be good to have a well documented solution for this.

stefanprobst commented 5 years ago

@eddiemf I am not super familiar with the wordpress plugin, but extending types in the using-wordpress example seemed to work as expected. If you are able to provide a link to your repo I could take a look what might be the issue.

Please note that we're aware that we are still very light on documentation. My guess is that the issue has to do with which types must implement the Node interface -- this is something which should mostly be handled by the plugins themselves (once they are ported to the new APIs).

The semantics of the Node interface is to mark the types that are backed by actual nodes in Gatsby's internal data store, i.e. the objects that are created by the createNode action by plugins, and have an id field. This means that while the Frontmatter type is just there to define the shape on the frontmatter field on MarkdownRemark nodes, I would guess that the wordpress plugin actually registers lots of top-level node types.

eddiemf commented 5 years ago

@stefanprobst I completely understand about being light on the documentation for now, I'm just trying to help :)

In short, a WordPress page in my app has a structure kinda like page -> acf -> someGroupOfFields -> actualField. In a query I would do the following as an example:

wordpressPage(slug: { eq: "home" }) {
  acf {
    intro {
      title
      content
    }
  }
}

The types are automatically generated to be something like WordPress__PAGE -> WordPress__PAGEAcf -> WordPress__PAGEAcfIntro. The problem is that these fields might very often be just optional fields (title and content), so if one of these fields were not provided my query would break since it's generated automatically based on the fetched data.

I see no possible way for the plugin to create these fields automatically (how would it know?), but I might be wrong. So my first solution was to just use createTypes and create this WordPress__PAGEAcfIntro type with the proper fields, but then I got the mentioned error.

I would say there's some conflict between my type creation and Gatsby/source plugin creating the same type after fetching the data, but I'm really not in the best position to make assumptions because I'm not at all experienced with GraphQL or Gatsby API, so I'm not sure how things should actually work.

But as I've said, for now, creating the resolvers just as in my last post worked very nicely, so I'm basically creating types for everything I need to fetch, since they can always be optional in the CMS.

You can check my gatsby-node.js here to see how I did it.

freiksenet commented 5 years ago

@eddiemf This is interesting. Do you have an example with the "Schema must contain unique named types but contains multiple types named "Wordpress__PAGEAcfIntro"" error? Gatsby should be able to handle overriding types that are inferred, so I'm not sure why your code doesn't work.

eddiemf commented 5 years ago

@freiksenet I got this error when trying to solve it like this:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions;
  const typeDefs = `
    type Wordpress__PAGEAcfIntro {
      title: String
      content: String
    }
  `;
  createTypes(typeDefs);
};

The title field was set in the CMS, but content was empty, so I tried the snippet above and got this unique name error.

I also thought the same about overriding types being possible because that's what the docs and the article regarding this new feature also says, so I was surprised to see it didn't work as expected.

I think this is almost the same problem as the following:

I'm trying to set the schema for the frontmatter nodes generated by gatsby-transformer-remark and always run into this error: Error: Schema must contain unique named types but contains multiple types named "MarkdownRemarkFrontmatter".

My schema looks like this:

type MarkdownRemarkFrontmatter implements Node {
  title: String
}

With the difference that the solution that worked for him didn't actually work for me. But it also looks like the implements Node part is wrongly used here, while in my case it should be correct.

freiksenet commented 5 years ago

@eddiemf you need to use the non-node types somewhere to override it. So MarkdownFrontmatter (without implements Node) needs to be used in Markdown node and Wordpress__PageAcfIntro should be used in WorpressPage. We'll add a better error, but we've made this behavior this way so that one doesn't accidentally override an inline node.

eddiemf commented 5 years ago

@freiksenet you mean like I did here?

I'm having exactly the same problem as @kennedyrose , but in my case the solution provided by @stefanprobst didn't quite help, although I'm not really sure what I'm doing, I just tried every possible way of creating this schema but it always returns an error regarding multiple types named....

Here's the code:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions;
  const typeDefs = `
    type Wordpress__PAGEAcfIntro {
      title: String
      content: String
    }
    type Wordpress__PAGEAcfThe_problem {
      title: String
      content: String
    }
    type Wordpress__PAGEAcfThe_solution {
      title: String
      content: String
    }
    type Wordpress__PAGEAcf {
      Intro: Wordpress__PAGEAcfIntro
      The_problem: Wordpress__PAGEAcfThe_problem
      The_solution: Wordpress__PAGEAcfThe_solution
    }
    type Wordpress__PAGE implements Node {
      acf: Wordpress__PAGEAcf
    }
  `;
  createTypes(typeDefs);
};

At first I tried setting only the types I needed (the first 3) with their fields, but then I got Error: Schema must contain unique named types but contains multiple types named "Wordpress__PAGEAcfIntro".. While searching for a solution I tried @stefanprobst solution (as in my code, not sure if correct though) but didn't have any luck.

Also, this is quite a lot to just make some fields have optional string values (the query breaks if any of the fields doesn't have a title or content), and this is just the initial implementation, I'll probably have to do the same in the whole website for a lot more fields that also might be optional, so I'm very scared of this solution and really hope there's a better way of solving it.

I tried to create, let's say, the "whole type tree" or "type model", by setting every possible field and use them as in the example, but I got the same error (sometimes a slightly different error accusing some other type of not being unique).

Although I must admit that I had no idea what I was doing when I tried this, I was just trying every possible combination of solutions, but all of them failed for me.

freiksenet commented 5 years ago

@eddiemf Would it be possible to get the url of the wordpress site that you are using? I want to test it myself to find out what causes the error.

eddiemf commented 5 years ago

@freiksenet Sure, the url is https://gatsbyislove.com I'll leave the title field from the intro section empty so you can check against it.

You can see at https://gatsbyislove.netlify.com/ that the intro section has no title, but it's working properly because of the resolvers that I created.

freiksenet commented 5 years ago

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

freiksenet commented 5 years ago

Lol, that's the funniest error. So we accidentally uppercased the wordpress types. They should all start with wordpress. Instead, we kept Nodes lowercased, but uppercased inner objects. So this would have worked:

     type Wordpress__PAGEAcfIntro {
       title: String
       content: String
     }

    type Wordpress__PAGEAcf {
      Intro: Wordpress__PAGEAcfIntro
    }

    type wordpress__PAGE implements Node {
      acf: wordpress__PAGEAcf
    }

I'm pushing a fix now, so all types would start with lowercase w.

eddiemf commented 5 years ago

Awesome 😄 But now I think, at least for this specific situation, that using the resolvers can keep the code better organized since I don't need to declare these nested fields all the way up to the main one that implements Node.

Is it really better or am I not seeing something here?

Anyway I'll play around with different solutions and see which one fits me best.

Thanks for the great support!

freiksenet commented 5 years ago

@eddiemf It is fine to use createResolvers, but you won't get those fields in filter and sort parameters of this type's root fields. If it's okay for you, then use createResolvers.

Here is the fix: https://github.com/gatsbyjs/gatsby/pull/12837/files

eddiemf commented 5 years ago

I see, that shouldn't be a problem in most cases since filter and sort are usually made on fields that are always populated, but I'll keep that in mind. Thanks again :)

d4rekanguok commented 5 years ago

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"?

danoc commented 5 years ago

Hi! Is it possible for warning There are conflicting field types in your data. GraphQL schema will omit those fields to appear even if createTypes is set up, preventing GraphQL from omitting the fields?

This is the warning from gatsby develop:

ThumbprintToken.tokens.value.web:
 - type: number
   value: 1025
   source: File "../packages/thumbprint-tokens/src/tokens/breakpoint.json"
 - type: string
   value: '4px'
   source: File "../packages/thumbprint-tokens/src/tokens/border-radius.json"

And this is the usage of createTypes:

exports.sourceNodes = ({ actions }) => {
    const { createTypes } = actions;
    const typeDefs = `
        type ThumbprintToken implements Node {
            tokens: [ThumbprintTokenTokens!]!
        }

        type ThumbprintTokenTokens {
            value: ThumbprintTokenTokensValue!
        }

        type ThumbprintTokenTokensValue {
            web: String
            ios: String
            android: String
        }
    `;
    createTypes(typeDefs);
};

Without using createTypes, GraphQL does indeed omit the fields. The data for those JSON files comes from gatsby-source-filesystem and gatsby-transformer-json. I'm using a function for the typeName config in gatsby-transformer-json.

Happy to make a small repro if it seems that the warning shouldn't be appearing.

angeloashmore commented 5 years ago

Hello!

I am getting an error when using the built-in JSON type.

// gatsby-node.js

export const sourceNodes = ({ actions: { createTypes }, schema }) => {
  createTypes([
    schema.buildObjectType({
      name: 'MyTypeName',
      fields: {
        id: 'ID!',
        json: 'JSON!',
      },
    }),
  ])
}

The following error is emitted:

success source and transform nodes — 1.550 s
error UNHANDLED REJECTION

  Error: Schema must contain unique named types but contains multiple types named "JSON".

  - Array.reduce

  - SchemaComposer.js:122 SchemaComposer.buildSchema
    [gatsby-ww]/[graphql-compose]/lib/SchemaComposer.js:122:12

  - schema.js:480
    [gatsby-starter-ww]/[gatsby]/dist/schema/schema.js:480:47

  - Generator.next

  - new Promise

  - schema.js:539 addCustomResolveFunctions
    [gatsby-starter-ww]/[gatsby]/dist/schema/schema.js:539:18

  - schema.js:162
    [gatsby-starter-ww]/[gatsby]/dist/schema/schema.js:162:11

  - Generator.next

Could this be a result of graphql-compose providing its own JSON type rather than using the gatsby/graphql version?


gatsby info output:

  System:
    OS: macOS 10.14.2
    CPU: (4) x64 Intel(R) Core(TM) i5-7600K CPU @ 3.80GHz
    Shell: 5.6.2 - /usr/local/bin/zsh
  Binaries:
    Node: 10.15.3 - /var/folders/3z/fgqk0pmx30l2pc4801884_sm0000gn/T/yarn--1554423671012-0.23391063288947023/node
    Yarn: 1.12.3 - /var/folders/3z/fgqk0pmx30l2pc4801884_sm0000gn/T/yarn--1554423671012-0.23391063288947023/yarn
    npm: 6.4.1 - ~/.n/bin/npm
  Languages:
    Python: 2.7.10 - /usr/bin/python
  Browsers:
    Chrome: 73.0.3683.86
    Firefox: 64.0
    Safari: 12.0.2
  npmPackages:
    gatsby: 2.3.11 => 2.3.11
freiksenet commented 5 years ago

@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.

freiksenet commented 5 years ago

Published gatsby@2.4.0-alpha.1

LekoArts commented 5 years ago

@samovertonjr @smurrayatwork Pleae don't unpin this issue. Thanks!

samovertonjr commented 5 years ago

Didn't know that clicking that would affect everyone.

freiksenet commented 5 years ago

Published 2.4.0-alpha.2.

LekoArts commented 5 years ago

@prashant-andani Pleae don't unpin this issue. Thanks!

prashant-andani commented 5 years ago

Hi, that was accidental... sorry for that

On Tue, 30 Apr 2019 at 6:38 PM, Lennart notifications@github.com wrote:

@prashant-andani https://github.com/prashant-andani Pleae don't unpin this issue. Thanks!

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/gatsbyjs/gatsby/issues/12272#issuecomment-487944961, or mute the thread https://github.com/notifications/unsubscribe-auth/AAGKROYK4RRFYRE3MOQL2NLPTBAE7ANCNFSM4G3OVU5Q .

-- Regards Prashant S Andani

motleydev commented 5 years ago

What's the order of execution for mapping? I have a set of posts that I am trying to map to categories like the post -> authors example. My raw source has id's in numbers, so I used create types to enforce a string assumption. Unfortunately, mapping doesn't seem to work if I use createTypes. I thought createTypes ran before the master schema was created?

//gatsby-config.js
  mapping: {
    "MarkdownRemark.frontmatter.speaker": `AuthorsYaml.name`, // works as expected
    "MarkdownRemark.frontmatter.categories": `CategoriesYaml`,
  },
//gatsby-node.js
exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions
  const typeDefs = `
  type MarkdownRemarkFrontmatter {
    categories: [String]
  }

  type MarkdownRemark implements Node {
    frontmatter: MarkdownRemarkFrontmatter
  }
  `
  createTypes(typeDefs)
}

Querying…

{
  allMarkdownRemark {
    nodes {
      frontmatter {
        categories
        speaker {
          name
        }
      }
    }
  }
}

Yields…

    "allMarkdownRemark": {
      "nodes": [
        {
          "frontmatter": {
            "categories": [
              "22"
            ],
            "speaker": {
              "name": "Sean Higgins"
            }
          }
        }
]
motleydev commented 5 years ago

I found a working solution where I tell the type to expect a CategoryYaml type instead of coercing it to a string and use a custom resolver to fetch the data.

It just seems like with the mapping mechanism in place, I probably should be able to force the string coercion, and then use the mapping to do the hookup for me.

Maybe I'm missing something?

awilkins commented 5 years ago

Nested types :

e.g.

PrismicNews
  PrismicNewsBody
    PrismicNewsBody
      PrismicNewsBodyPrimary
      PrismicNewsBodyItems

Types have the notion of repeatable groups (which end up in the Items) but if you don't have anything in the group, querying for it fails (because you're querying a type member that hasn't been inferred).

However, if you define the type so that you can declare items, it's called out as a duplicate once you actually use the repeating group ; leaving you in the state where you have a [0..] piece of data but can only have code that supports 0 or 1..

So could the code (is there already a way) merge types that are not implements Node ?


Next one : Prismic's type system is a royal PITA and when using slices, etc, creates types with names appended to their container's name. This is reasonable because of the way it behaves - it has a "Slice library" concept, but it's a copy library, not a reference library - the type definition is just copied into the type def for the content type.

OTOH programmers hate duplication. So I've taken care to make sure the Slice type of a given name is the same everywhere by defining it on one page, saving it to the "library" and copying it outward - could we (do we?) have the ability to add interfaces so instead of

query {
  pageOne {
    data {
      body {
        ... on PageOneBodyMyslice {
          primary {
            field
          }
        }
      }
    }
  }
  pageTwo {
    data {
      body {
        ... on PageTwoBodyMyslice {
          primary {
            field
          }
        }
      }
    }
  }
}

... we could do

interface MySlice {
  primary {
    field
  }
}

# somehow we mark the page slice types as implementing this

query {
  pageOne {
    data {
      body {
        ... on MySlice {
          ... MySliceFragment

# etc
angeloashmore commented 5 years ago

@awilkins gatsby-source-prismic author here.

We're working on implementing automatic schema loading so Gatsby knows everything about your custom type shapes. We're hoping to publish a beta this week (which also includes a preview system!).

You will need to provide your schemas to the plugin going forward.

Re: your question about interfaces, the idea is sound, but I'm not sure if the types are merged. All slices implement the Node interface and follow the pascalcase(${customType} ${sliceZoneName} ${sliceName}) naming convention.

freiksenet commented 5 years ago

@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.

awilkins commented 5 years ago

@angeloashmore Thanks for the response! I'll look forward to it ; my client no doubt will want changes and this will make things much easier.

interfaces

I'm sure you've noted that due to the way that Prismic does things, it's "slice library" just lets you copy slice type information into your custom type - so the different type names are necessary from the POV that you can actually have radically different slice types with the same name in different page types.

It's a PITA to update slice types in the CMS because you have to do it in one place, then copy that to all the other types, then open all the content and update it (if you introduced any incompatible changes).

But... if you're well behaved about it and do this consistently (or just have a very well used and mature set of slices), the benefits of being able to declare one global GraphQL fragment for each slice type or (maybe?) even one for all slices would be lovely - but fragments can only be applied to a type or interface, hence the desire to assign common interfaces to the different-but-identical-slice-types with the same name on different pages.

freiksenet commented 5 years ago

Published gatsby@2.5.0.

Spiderpig86 commented 5 years ago

Hello everyone. I have updated to Gatsby 2.5.0 but seem to run into the same type conflicts as @angeloashmore between graphql-compose and gatsby with the JSON type. Is there any simple way to resolve this conflict?

I am upgrading from Gatsby 1.9.x if that helps. Logs are below:

error UNHANDLED REJECTION

  Error: Schema must contain uniquely named types but contains multiple types named "JSON".

  - Array.reduce

  - Array.reduce

  - SchemaComposer.js:130 SchemaComposer.buildSchema
    [blog]/[graphql-compose]/lib/SchemaComposer.js:130:12

  - schema.js:500
    [blog]/[gatsby]/dist/schema/schema.js:500:47

  - Generator.next

  - debuggability.js:313 Promise._execute
    [npm]/[gatsby-cli]/[bluebird]/js/release/debuggability.js:313:9

  - promise.js:483 Promise._resolveFromExecutor
    [npm]/[gatsby-cli]/[bluebird]/js/release/promise.js:483:18

  - promise.js:79 new Promise
    [npm]/[gatsby-cli]/[bluebird]/js/release/promise.js:79:10

  - schema.js:559 addCustomResolveFunctions
    [blog]/[gatsby]/dist/schema/schema.js:559:18

  - schema.js:163
    [blog]/[gatsby]/dist/schema/schema.js:163:11

  - Generator.next

  - util.js:16 tryCatcher
    [npm]/[gatsby-cli]/[bluebird]/js/release/util.js:16:23

  - promise.js:512 Promise._settlePromiseFromHandler
    [npm]/[gatsby-cli]/[bluebird]/js/release/promise.js:512:31

  - promise.js:569 Promise._settlePromise
    [npm]/[gatsby-cli]/[bluebird]/js/release/promise.js:569:18

  - promise.js:606 Promise._settlePromiseCtx
    [npm]/[gatsby-cli]/[bluebird]/js/release/promise.js:606:10

  - async.js:142 _drainQueueStep
    [npm]/[gatsby-cli]/[bluebird]/js/release/async.js:142:12
freiksenet commented 5 years ago

@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).

Spiderpig86 commented 5 years ago

In the end, I ended up scrapping whatever dependencies I had and reinstalling all of it completely. This is the configuration I use now and it works very well.

angeloashmore commented 5 years ago

We're running into an issue with gatsby-source-prismic v3.0.0-alpha.2 with gatsby-source-filesystem File nodes.

We tell Gatsby image fields will have a localFile field with type File, but if no File nodes are created anywhere in the site, Gatsby does not know what File is.

Error: Type with name "File" does not exists

Sorry, this might be a gatsby-source-filesystem question more than a schema customization question, but just wondering if there's a way we can reliably tell Gatsby what File is. Would gatsby-source-filesystem need to export a File type definition from the package where source plugins could then explicitly provide to Gatsby?

Related issue: https://github.com/angeloashmore/gatsby-source-prismic/issues/97

stefanprobst commented 5 years ago

@angeloashmore we could make gatsby-source-filesystem explicitly register the File type (and make it not panic when the configured path does not exist). This way it would be possible to just provide gatsby-source-filesystem (without options) in gatsby-config and have the File type available in the schema. Would something like this work in your case? This would still require gatsby-source-filesystem to be installed because it owns the type -- but there are not-yet-concrete plans to maybe promote File to a core type at some point.

angeloashmore commented 5 years ago

@stefanprobst Yes, something like that looks like it could work.

In regards to plugin authors, if the plugin nondeterministically creates a File node, this would mean:

  1. Plugin authors should ask users of the plugin to always add gatsby-source-filesystem to their gatsby-config.js to ensure Gatsby knows the File type, or
  2. Plugin authors only include fields that use the File type in type definitions if the plugin can determine a File node will be created. I.e. if we know no nodes will be created with a File field, don't include the File field on the node that uses it. This could break builds if queries contain the now non-existent field.

With Option 1, users could see the nonexistent path error message on every develop or build if they do not provide a path. In some projects, there may not be a need to use gatsby-source-filesystem except to provide the File type. Maybe we could change it so it only checks if the path exists if a path is provided?

With Option 2, plugin authors need to deal with the increased complexity of tracking File node creation. This could be as simple as updating a global variable but it's still something to consider.

If gatsby-source-filesystem exported the File type (via GraphQL object or SDL string) and allowed plugin authors to register it with createType, users do not need to change their workflow and plugin authors do not need to track File usage.

This could potentially cause issues in the future if/when the File type changes and plugins use different gatsby-source-filesystem versions with incompatible File definitions. This works okay now since, from what I understand, Gatsby infers the shape aggregately.

For reference, this is where we define a field using File: standardTypes.graphql. This gets passed directly to createTypes.

freiksenet commented 5 years ago

Correct me if I'm wrong @pieh, @stefanprobst, but I think making File a core type isn't going to be a breaking change. It will break only the cases where it used to fail because File wasn't there. So I propose that we do it.

pauleveritt commented 5 years ago

Apologies for plopping this in here, if you can steer me in the right direction, I'll file a more focused ticket.

tl;dr Frontmatter-driven types.

I come from a CMS background so controlling my schemas is attractive. My Gatsby site uses Markdown frontmatter with type: author in the YAML to specify the "type", but since everything is a MarkdownRemark node type, I don't get real schemas.

I now have an Author type via createTypes. I have tried a number of avenues to make an Author node, preferably as a child of the MarkdownRemark node (so it gets deleted for free), but I can't find a way to do so. createNode doesn't appear to be available in a resolver.

stefanprobst commented 5 years ago

@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.

stefanprobst commented 5 years ago

@freiksenet I think it depends if it means moving the type definitions for File to core, or if (parts of) what is now gatsby-source-filesystem should be moved with it as well. Moving File alone should be ok I think.

pauleveritt commented 5 years ago

@stefanprobst Neat concept...but I think the challenge is here:

https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-remark/src/on-node-create.js#L45

I don't know how I can get a node created as a AuthorYaml based on the frontmatter coming from the existing gatsby-source-filesystem and gatsby-transformer-remark.

It's likely I need to stop thinking about link targets as Markdown content and use .yaml gatsby-transformer-yaml which lets me name the node type.

stefanprobst commented 5 years ago

@pauleveritt

use .yaml gatsby-transformer-yaml

yes, this is what I do in the linked example. Any reason to put author info in markdown files?

pauleveritt commented 5 years ago

@stefanprobst "Any reason to put author info in markdown files?"

I want everything -- author, category, tag, etc. -- to be a rich "resource" that people can edit, add images, code blocks, MDX stuff in the Markdown, etc.

But I'm not going to easily get that. :) I'll switch to YAML for those. Alas my "regular content" (tutorials, articles, blog posts) could also use being types with different schema, but I'll stick with inferred schema, instead of the new world of explicit types/schemas promoted since 2.2.

stefanprobst commented 5 years ago

@pauleveritt yes, gatsby-transformer-remark will only create one MarkdownRemark type - and using that to represent different entities will be problematic. so this is not really a schema related issue, but you'd need a markdown transformer plugin that can create differently named makrkdown nodes (which should be quite doable).

that said (only as an experiment), it is possible to do this with MarkdownRemark nodes only and use @link on frontmatter fields. it looks a bit ugly but it works, see here for the example.