This is a meta issue for all the issues with 2.2.0, that were introduced by the schema refactoring.
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.
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
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
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`],
}),
])
}
[email protected]
[email protected]
FilterInput
types are now not prefixed by output type, reducing proliferation of types [email protected]
@dontInfer(noDefaultResolvers: false)
actually workscreateResolvers
resolvers info.originalResolver
is available even if there is no resolver on original field[email protected]
[email protected]
The filters for ID fields now expect an ID as input where they previously wanted a string. This breaks certain queries, for instance:
export const query = graphql`
query BlogPostTemplateQuery($id: String!) {
post: sanityPost(id: { eq: $id }) {
id
title
}
}
`
Will report:
error GraphQL Error Variable "$id" of type "String!" used in position expecting type "ID".
While it may be more correct to update the query to reflect this, it is a breaking change, so I thought I would report it.
@rexxars Nice find! I assume we used to convert ID filters to String. I'll restore old behaviour.
Released new version.
Trying to query the schema in the resolver:
exports.createResolvers = ({ createResolvers, schema }) => {
createResolvers({
MenuJson: {
someResolver: {
type: `String!`,
async resolve(source, args, context, info) {
const foo = await graphql(schema, `
{
allPageJson {
nodes {
id
}
}
}
`, {})
console.log(foo)
return 'WIP'
},
},
},
})
}
TypeError: Cannot read property 'nodeModel' of undefined
at /private/tmp/test-gatsby/node_modules/gatsby/dist/schema/resolvers.js:22:15
at /private/tmp/test-gatsby/node_modules/gatsby/dist/schema/resolvers.js:49:44
at Generator.next (<anonymous>)
[...]
@NicoleEtLui For queries in the field resolver, use the methods provided on context.nodeModel
, i.e. you can use context.nodeModel.getAllNodes({ type: 'PageJson' })
or for more sophisticated queries you can use context.nodeModel.runQuery
. There are some basic examples here.
If you need to access to the schema, note that the schema
argument is just an intermediate representation - in the resolver you have access to the final built schema on info.schema
.
Published [email protected]
@stefanprobst Thanks for the quick answer and all the great work you did !
Hi!
Any suggestion to handle relationships between nodes ?
For example, files are stored in JSON (without id field, assume the file name as id):
data/comments/some-uuid.json
= { "message": "Hello", "postId": "some-post" }
data/posts/some-post.json
= { "content": "post" }
Using the source-filesystem
and transformer-json
plugins, that makes the nodes have an unpredictable ID
since the transformer use createNodeId()
. It makes difficult to find post
from comment
.
Hi !
Trying to start gatsby project with any of these in gatsby-config.js
:
gatsby-transformer-sharp
gatsby-plugin-sharp
gatsby-plugin-manifest
will throw:
error Plugin gatsby-transformer-sharp returned an error
Error: Cannot find module 'gatsby/dist/utils/cpu-core-count'
- loader.js:581 Function.Module._resolveFilename
internal/modules/cjs/loader.js:581:15
- loader.js:507 Function.Module._load
internal/modules/cjs/loader.js:507:25
- loader.js:637 Module.require
internal/modules/cjs/loader.js:637:17
- v8-compile-cache.js:159 require
[keemotion-corporate]/[v8-compile-cache]/v8-compile-cache.js:159:20
[...]
@NicoleEtLui Please update gatsby-plugin-manifest
and gatsby-plugin-sharp
, this was fixed in those packages with https://github.com/gatsbyjs/gatsby/pull/12332
Published [email protected]
@LoicMahieu you can manually provide ids for the nodes, then you can do relationships by specifying fieldName___NODE
fields.
Published [email protected]
Q: Is this use case suitable for createResolvers
?
I am using a remote CMS via gatsby-source-graphql
which includes references to several remote files. I am currently pulling in those files with createRemoteFileNode
. However, the page queries get awkward quickly because it's easy to get into situations where I need the result of the cms query (on the gatsby-source-graphql
data source) to figure out which files I will need from gatsby-source-filesystem
.
Ideally, I'd like to add/link/join (?) these remote file nodes into the cms nodes from gatsby-source-graphql
. Is this a situation that createResolvers
can help with?
@skinandbones If I am understanding correctly, the short answer is "maybe but probably not quite yet".
We do support extending field configs on types added from a third-party schema, so it is possible to add a field resolver to a type from your CMS schema with createResolvers
, and use createRemoteFileNode
in the resolver. For example:
https://github.com/stefanprobst/gatsby/blob/5bbfee29b5ec38f13a3070b13de4877aaddd6483/examples/using-gatsby-source-graphql/gatsby-node.js#L56-L71
The problem is that createRemoteFileNode
will trigger onCreateNode
API calls, and in the field resolver we have currently no way of knowing when those subsequent API calls have finished and it is safe for the resolver to return. (One approach to solve this could be #12202.) So depending on what exactly you intend to do with the remote files, this might or might not yet work.
@LoicMahieu Do you have an example project you can link to?
@stefanprobst Yes, you understand correctly and the async issue makes sense. The example you linked is pretty similar to what I was expecting to do so I can give that a shot and see what happens. My plan is to run these file nodes through gatsby-transformer-sharp
in the page query.
Is another viable approach to use createRemoteFileNode
via the sourceNodes
API (as usual) and then link those nodes into the 3rd party schema using the new API? Up to this point, I haven't been able to get into the 3rd party schema to do this.
@skinandbones sorry, i should have clarified: the part in the example i linked to is currently not yet working, exactly because of the issue that when the field resolver returns, the File
node will have been created, but the ImageSharp
node (which is created in the triggered onCreateNode
API call) not yet.
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/" } } } })
@stefanprobst
Here is a example:
comments
to post
by a complex lookup on parent File
.___NODE
way but the new createResolvers
: demoHey, I just read this blog post about schema customization and the birthday example kinda seemed strange to me. In the example you're creating a custom resolver, which tries to resolve a date field into a Date, and falls back to a bogus value (01-01-1970) if the input date is not a proper Date. If you ever actually had a date in a real system, you would never want to replace incorrect inputs with bogus data. You would want to flag them as unknown/incorrect with something like null values. The fact that null wasn't used in the example made me wonder: is there some kind of limitation in Gatsby/GraphQL wrt. null values?
@baobabKoodaa
is there some kind of limitation in Gatsby/GraphQL wrt. null values?
No. In GraphQL, you can explicitly set if a field should be nullable or not. More info here.
@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 withcontext.nodeModel.getAllNodes({ type: 'File' })
or with something likecontext.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.
@skinandbones Very cool! Btw, you can use firstOnly: true
in runQuery
to only get the first result.
Released [email protected]
.
Tentative goal is to merge this to master and release this next week. Please comment and try it :)
Seems that filters for fields of type resolved in a createResolvers()
are not generated.
Example:
post.json
: { "id": "some-post", "author": "Loic", message: "Hello" }
comment.json
: { "postId": "some-post", message: "World" }
createResolvers({
CommentsJson: {
post: {
type: `PostsJson`,
resolve (source, args, context, info) {
const allNodes = context.nodeModel.getNodeById({ id: source.postId, type: 'PostsJson' })
}
}
}
})
CommentsJsonFilterInput
type does not contains post
field. So query like this could not work:
{
allCommentsJson(filter: {post: {author: {eq: "foo"}}}) {
nodes {
id
}
}
}
Thanks
@LoicMahieu This is intended behavior (at least for now): new fields added in createResolvers
will not be present in the input filter, because createResolvers
is run last in schema generation. The recommended approach here is to define a field type with createTypes
action and then extend it in createResolvers
- or use the recently added buildObjectType
helper (see above).
Sorry that this is not yet better documented - for now you can have a look at the API docs directly in the branch: createResolvers
and createTypes
.
@LoicMahieu this is intentional, add resolvers happens after all processing and is meant as a tool to do final adjustments to the schema. You should add fields in createTypes
if you want them to show in the filters. There is a new shorthand syntax that you can use, see the top post.
EDIT: Oops, didn't see @stefanprobst replying already :D
Thanks both for clarification. createResolvers
and createTypes
seems to achieve the same goal.
One thing that could be really useful is to save the generated schema. It would allow to "snapshot" the types and make sure that the schema will remain as we expect.
I wrote a POC that seems to works well:
const { printType } = require("graphql")
const fs = require("fs-extra")
const path = require("path")
const schemaFilePath = path.join(__dirname, "./src/schema.gql")
exports.sourceNodes = async ({ actions }) => {
const { createTypes } = actions
if (await fs.exists(schemaFilePath)) {
const typeDefs = (await fs.readFile(schemaFilePath)).toString()
createTypes(typeDefs)
}
}
exports.onPostBootstrap = async ({ store }) => {
const { schema } = store.getState()
const types = ["CommentsJson", "PostsJson"]
const typeDefs = types
.map(type => printType(schema.getType(type)))
.join("\n\n")
await fs.writeFile(schemaFilePath, typeDefs + "\n")
}
With this we can even delete all the data and the schema is still as we expect.
Demo: https://github.com/LoicMahieu/test-gatsby-refactor-schema/commit/9696c8e386fd066262164f8e38f5689a14f111e1
@LoicMahieu :+1: Something like this is part of our roadmap.
This does not touch the threading of schema building, right?
Referring to the comment: https://github.com/gatsbyjs/gatsby/issues/7373#issuecomment-413570634
Only one Node process working (this screencap is _not_ from the beta release, just adding it here to illustrate my point):
@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.
Released 2.2.0-rc.1
. Soon.
Query sorting on a newly created fields seems buggy.
I can create a new field by doing:
exports.sourceNodes = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type MyNode implements Node {
copyOfId: ID!
}
`
createTypes(typeDefs)
}
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
MyNode: {
copyOfId: {
resolve(source, args, context, info) {
return info.originalResolver(
{
...source,
copyOfId: source.id,
},
args,
context,
info,
)
},
},
},
})
}
But then if I query:
query {
allMyNode (sort: {fields: [copyOfId]}) {
nodes {
copyOfId
}
}
}
results are not sorted and appear in original order.
If I sort on id
everything is fine of course.
I guess this is the same problem as @LoicMahieu mentioned earlier but this is really misleading. If we can't do it then it has to be super well documented because it just seem natural to me to ease my filtering and sorting by creating the appropriate fields with these new API methods.
PS: forgot to mention... this new API is awesome!! :D Don't let my comment mislead you in thinking I am in a pessimistic mood. I'm just trying to be useful. ;) Thanks for the great work!
@MarcCoet Thanks for testing! This is a known issue - we don't call field resolvers for sort fields currently, see #11368. It's the next thing we'll tackle once this is merged!
Oh, sorry. I didn't think about looking for setFieldsOnGraphQLNodeType
issues. My bad.
I am happy to know it is being worked on.
so there is really no way to get around this with the new API? We have to stick with the old ways of createNodeField
?
I tried with buildObjectType
but same result.
exports.sourceNodes = ({ actions, schema }) => {
const { createTypes } = actions
createTypes([
schema.buildObjectType({
name: `MyNode`,
fields: {
copyOfId: {
type: `ID!`,
resolve(parent) {
return parent.id
},
},
},
interfaces: [`Node`],
}),
])
}
Any other undocumented way to achieve sorting on new node fields by chance?
Appart of that I just had a syntax error with the @infer()
directive. But I guess that is what 'Allow inference options on non-SDL types' means in the known issues above?!
@MarcCoet
syntax error with the
@infer()
directive
Does this work for you?
createTypes('type MyNode implements Node @infer { foo: Boolean }')
As for the sorting issue: this problem is a bit orthogonal to the schema customization refactor. The issue is: when we want to filter or sort on fields that are not on the node object itself but added by field resolvers, we need to call those resolvers before so we have the full nodes available for filtering and sorting. E.g. in your example, the node object does not have a copyOfId
field -- for it to be available we need to call the resolver before filtering and sorting. Currently, we only do this for filter fields, but not for sort fields. As a workaround you could try adding a dummy filter for copyOfId
to your query:
query {
allMyNode (sort: { fields: [copyOfId] }, filter: { copyOfId: { ne: null } }) {
nodes {
copyOfId
}
}
}
See also this comment.
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
}
@kennedyrose Thanks for testing! Does the following work for you?
exports.sourceNodes = ({ actions }) => {
const { createTypes } = actions
createTypes(`
type MarkdownRemarkFrontmatter {
title: String
}
type MarkdownRemark implements Node {
frontmatter: MarkdownRemarkFrontmatter
}
`)
}
Only top-level types, i.e. node types generated by source and transformer plugins (like MarkdownRemark
or ImageSharp
) should implement the Node interface, not nested types like the frontmatter type.
(Although we do seem to have a bug when targeting nested infered types directly)
That works. Thanks!
Published [email protected]
.
Published [email protected]
. Thank you everyone!
@stefanprobst
Does this work for you?
createTypes('type MyNode implements Node @infer { foo: Boolean }')
It does! It is just the original article that is misleading then.
I guess the fog will raise with the arrival of proper documentation. ;)
About the sorting issue, thanks a lot for the clarifications. That is really helpful.
The workaround seems to be working so this is perfect for now.
I am very excited about the new possibilities of Gatsby. Thanks a lot for the work you guys put in Gatsby.
Want to x-post gatsbyjs/gatsby#12696, specifically this post
Looks like file inference/detection may have been slightly tweaked here? Updating to 2.2.2
broke, whereas ~2.1.0
seems to correctly infer the file node.
Just found this when experimenting with creating custom interfaces using the API. Using the resolveType function on the interface works, but using isTypeOf functions instead on the objects that share the interface does not. I think this is down to the below code in schema.js, which assumes the resolveType should be node, when no function is declared. This assumption is now incorrect with custom interface types.
if (!typeComposer.getResolveType()) {
typeComposer.setResolveType(node => node.internal.type);
}
Great work by the way, just in time for my project!
Hi @Wolfsun Yes, since providing resolveType
via SDL isn't possible we provide a default which should work in most cases, and allow providing a custom one via graphql-js
types. It's true this does not play well with isTypeOf
. Out of curiosity: is there a reason for using isTypeOf
over resolveType
?
Hi @stefanprobst, thanks for the explanation. Using resolveType is perfectly workable for me at this stage, however using with isTypeOf would be slightly cleaner as I have one interface with lots of implementing objects. I'm using a data structure to represent fairly complex grid based layouts, I have a Card interface, and many CardTypes implementing this interface, which are stored in the data structure relative to their position on the grid. As I don't know the CardType in advance an interface is required. Potentially isTypeOf could be useful if I want to use a plugin system for additional Cards, but I've not given this much thought yet, and it may not even be a good idea, so very much an issue for another day!
For discoverability I'm linking to this example that maybe others find useful as well. Hope this is ok.
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.
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.
Declaring a field as an array of files doesn't work for me.
exports.sourceNodes = ({ actions }) => {
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
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.
Lots of small fixes in 2.2.10
, including explicit Node relationships working correctly.
@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.
@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.
@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.
@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.
@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.
@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.
@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.
@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.
@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.
@eddiemf Thanks a lot! Got the error, will investigate.
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
.
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!
@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
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 :)
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"?
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.
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
@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.
Published [email protected]
@samovertonjr @smurrayatwork Pleae don't unpin this issue. Thanks!
Didn't know that clicking that would affect everyone.
Published 2.4.0-alpha.2
.
@prashant-andani Pleae don't unpin this issue. Thanks!
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
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"
}
}
}
]
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?
Nested types :
gatsby-source-prismic
e.g.
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
}
}
query {
pageOne {
data {
body {
... on MySlice {
... MySliceFragment
```
@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.
@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.
@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.
Published [email protected]
.
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
````
@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).
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.
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
@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.
@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:
gatsby-source-filesystem
to their gatsby-config.js
to ensure Gatsby knows the File
type, orFile
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
.
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.
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.
@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.
@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.
@stefanprobst Neat concept...but I think the challenge is here:
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.
@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?
@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.
@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.
@stefanprobst I understand your point, but I think if the goal is to do less inferred schema and more declared schema, Markdown frontmatter should be in scope. Gatsby with Markdown files is a popular combo. I
Having different types/schema in Markdown would be easier if gatsby-transformer-remark
didn't make the surface area of forking so big. There's a lot going on in the arrow function that does the node type assignment.
Let's close out this conversation. I'm likely in a minority of those that want different content types in Markdown. Thanks a bunch for the sample code, that's the direction I'll take for now.
Regarding runQuery
and its query
parameter...I have filter
working correctly but I can't get an example of sort
working. The tests that use sort seem to use runQuery
from a template string. When I try sort: { order: 'DESC', fields: ['frontmatter___date'] }
(or with 'desc' as the string) the resolve bails out.
@pauleveritt Does it work with an Array on order
?
@stefanprobst Alas, no. This:
sort: { order: ['DESC'], fields: ['frontmatter___date'] }
...leads to:
error gatsby-node.js returned an error
TypeError: Cannot read property 'resolve' of undefined
- prepare-nodes.js:34 awaitSiftField
[gatsby-theme-bulmaio]/[gatsby]/dist/redux/prepare-nodes.js:34:13
- prepare-nodes.js:52
[gatsby-theme-bulmaio]/[gatsby]/dist/redux/prepare-nodes.js:52:69
- Array.map
- prepare-nodes.js:52 resolveRecursive
[gatsby-theme-bulmaio]/[gatsby]/dist/redux/prepare-nodes.js:52:44
...
@pauleveritt unfortunately I was not able to reproduce with this basic setup -- would you be able to provide a small repro for this issue to look into? This would be very helpful, thanks!
@stefanprobst Found the cause: it's 2.8.x -- want me to file a new ticket?
Imagine your gatsby-blog
for YAML repo. Add this to gatsby-node.js
:
exports.createResolvers = ({ createResolvers, schema }) => {
createResolvers({
Query: {
allResourcesByType: {
type: ['MarkdownRemark'],
args: {
resourceType: 'String'
},
resolve(source, args, context, info) {
return context.nodeModel.runQuery({
query: {
filter: {
frontmatter: {}
},
sort: { fields: ["frontmatter___title"], order: ["ASC"] },
},
type: `MarkdownRemark`
})
}
}
}
})
}
In gatsby 2.7.6 you can query for allResourcesByType
in the explorer. In 2.8.0-2.8.2 you get a resolve error.
@stefanprobst I'm trying to convert tags that I specify as an array of strings in the frontmatter of my posts into a type Tag
with fields title
and slug
where title
is just the string I wrote and slug = _.kebabCase(title)
. I threw together this snippet
exports.sourceNodes = ({ actions, schema }) => {
actions.createTypes([
`type MarkdownRemark implements Node {
frontmatter: MarkdownRemarkFrontmatter
}`,
`type Tag { title: String!, slug: String! }`,
schema.buildObjectType({
name: `MarkdownRemarkFrontmatter`,
fields: {
tags: {
type: `[Tag!]`,
resolve(source) {
if (!source.tags) return null
return source.tags.map(tag => ({
title: tag,
slug: kebabCase(tag),
}))
},
},
},
}),
])
}
which works for adding the Tag
type to MarkdownRemarkFrontmatter.tags
. But of course, when I try to group all tags with
tags: allMarkdownRemark {
group(field: frontmatter___tags) {
title: fieldValue
count: totalCount
}
}
I get only the string I wrote into the frontmatter. I tried writing a resolver for allTags
like so
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
Query: {
allTags: {
type: [`Tag`],
resolve(source, args, context) {
return context.nodeModel.getAllNodes({
type: `MarkdownRemarkFrontmatterTags`,
})
},
},
},
}
createResolvers(resolvers)
}
but can't get it to work. Any advice?
@janosh
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).{
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
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.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),
}
})
},
},
},
})
}
@stefanprobst Thanks for the quick reply!
So you would have to retrieve MarkdownRemark nodes, and then further manipulate the results in the resolver.
I thought about doing that but suspected that I was overlooking something and hence approaching this from the wrong angle. Thanks for clearing this up and for planning to add support for field resolvers for group
fields!
Maybe another solution would be to let Tag
implement Node
such that Gatsby creates an allTag
query automatically.
type Tag implements Node { title: String!, slug: String! }
Then all I would have to do is to actually create nodes of type Tag
every time I come across a new one in the call to schema.buildObjectType
for MarkdownRemarkFrontmatter
. Is it possible to apply side effects like this from within resolvers? I.e. in this case check if Tag
with title MyTag
exists and if not createNode({type: `Tag`, title: `MyTag, slug: `my-tag` })
.
@pauleveritt sorry for the late reply!
In your example, sorting should work with this:
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
Query: {
allResourcesByType: {
type: ["MarkdownRemark"],
args: {
resourceType: "String",
},
resolve(source, args, context, info) {
return context.nodeModel.runQuery({
query: {
sort: { fields: ["frontmatter.post_title"], order: ["DESC"] },
},
type: "MarkdownRemark",
})
},
},
},
})
}
Note that both sort fields
and order
are arrays, and fields
uses the dot-notation instead of triple underscore to separate fields. (We have to document this! EDIT: #14681)
There are two reasons why this is different in runQuery
than it is when using a query string (i.e. in the Graph_i_ql explorer):
DESC
). we currently don't do that with runQuery
(we probably should)fields
and order
are GraphQLEnum
fields. In Graph_i_ql you would use the enum keys, while runQuery
needs the internal enum values. E.g. the fields
enum separates fields by triple underscore because graphql does not allow dots, but internally this translates to fields in dot notation.@janosh grouping on resolved fields should now work with [email protected]
@stefanprobst Wow, that was fast! Thanks for the update.
Is there a way to get both the title and slug of a tag in a single grouping? I can only figure out how to group by either and then only have access to one of them.
{
tags: allMarkdownRemark {
group(field: frontmatter___tags___(slug|title)) {
(slug|title): fieldValue
count: totalCount
}
}
}
If I try to group by frontmatter___tags
instead,
{
tags: allMarkdownRemark {
group(field: frontmatter___tags) {
tag: fieldValue
count: totalCount
}
}
}
I get only a single result
{
"data": {
"tags": {
"group": [
{
"tag": "[object Object]",
"count": 52
}
]
}
}
}
@janosh you can only group by one field (hierarchical sub-groupings are not possible). if you need the values of both title
and slug
, you can group by one, and get the value of the other from the results (maybe I'm misunderstanding?):
{
allMarkdownRemark {
group(field: frontmatter___tags___title) {
fieldValue
totalCount
nodes {
frontmatter {
tags {
slug
title
}
}
}
}
}
}
@stefanprobst That doesn't fit my use case but it's only a minor inconvenience. Thanks for all the awesome work you're putting into schema customization!
I've been having an issue since 2.2.0 which seems to be OS-related. I would be grateful to get another Windows 10 user's eyes on it for a test so I can either confirm or rule it out: https://github.com/escaladesports/gatsby-plugin-netlify-cms-paths/issues/10
Thanks!
@laradevitt I don't currently have access to a windows machine, and I'm also not familiar with gatsby-plugin-netlify-cms-paths
-- but does it work if you replace the path on frontmatter.image
to a relative path, i.e. ../static/media/gatsby-astronaut.png
?
@stefanprobst - Thanks for your reply! Yes, it does, but the point of the plugin is that you should not have to use relative paths:
A gatsby plugin to change file paths in your markdown files to Gatsby-friendly paths when using Netlify CMS to edit them.
I've created a pull request with a fix that makes the returned relative path platform agnostic.
I still don't know why it broke with 2.2.0. 🤷♀
@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 :+1:
@stefanprobst
I just created a new ticket related to schema customization. If it is better for me to close that and put it in this umbrella thread just let me know.
https://github.com/gatsbyjs/gatsby/issues/16099
Thanks!
Arguments in a graphQL query don't work when using schema customization.
After watching the learn with Jason stream on advanced GraphQL(broken up in multiple parts due to technical difficulties) I incorporated a lot of what was talked about into my theme.
However using graphQL arguments (like filter and sort) that rely on values from the customized schema doesn't work.
run this branch of my theme locally.
Fire up /___graphql and query allBlogPosts.
Now try to query allBlogPosts again with a sort by descending date.
RESULT: no change in order (ASC, or DESC)
Query for all posts and show their titles.
query MyQuery {
allBlogPost {
nodes {
title
}
}
}
RESULT: works
now query for all posts that are have a tag with slug "lorem-ipsum"
query MyQuery {
allBlogPost(filter: {tags: {elemMatch: {slug: {eq: "lorem-ipsum"}}}}) {
nodes {
title
}
}
}
RESULT: empty nodes array.
Try to query a single BlogPost (picks first one by default if no arguments are given).
Now try to query blogPost(slug: {eq: "herp-derpsum"})
.
This works, I think because I added the slug to the MdxBlogPost node.
https://github.com/NickyMeuleman/gatsby-theme-nicky-blog/blob/d29c966e639f4733caf9ee43e9f5755df42db71d/theme/gatsby-node.js#L209-L210
It seems like the graphql arguments use data from the MdxBlogPost node rather than the eventual result after it runs its resolvers. Is my suspicion close?
System:
OS: Linux 4.19 Ubuntu 18.04.2 LTS (Bionic Beaver)
CPU: (4) x64 Intel(R) Core(TM) i5-2500K CPU @ 3.30GHz
Shell: 4.4.19 - /bin/bash
Binaries:
Node: 12.4.0 - /tmp/yarn--1565124765846-0.45931526383359134/node
Yarn: 1.16.0 - /tmp/yarn--1565124765846-0.45931526383359134/yarn
npm: 6.9.0 - ~/.nvm/versions/node/v12.4.0/bin/npm
Languages:
Python: 2.7.15+ - /usr/bin/python
Relevant parts of code are in gatsby-node.js
in the theme directory.
specifically the createSchemaCustomization
and onCreateNode
lifecycles.
I tried to heavily comment to show my though process.
@NickyMeuleman sorry haven't had time yet to look closely but it seems you are missing a link extension on the BlogPost interface.
You define your MdxBlogPost.tags
field to link by name:
tags: {
type: "[Tag]",
extensions: {
link: { by: "name" },
},
},
in the typedefs for the BlogPost interface you have:
interface BlogPost @nodeInterface {
tags: [Tag]
}
Does it work with
interface BlogPost @nodeInterface {
tags: [Tag] @link(by: "name")
}
Related: #16466
After adding your suggestion, querying allBlogPost
with a filter
based on a tag did work! 🎉 Thanks!
n the future, will I be able to remove the @link
from the interface, since Tags might be linked differently per type that implements the BlogPost interface? (same question for Author, filtering there doesn't work, even with the @link
moved up to the interface level.
@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.
After https://github.com/gatsbyjs/gatsby/pull/17284 got merged I tried removing the logic in my theme to work around this.
Used Gatsby version: 2.15.14
It doesn't appear to be working.
https://github.com/NickyMeuleman/gatsby-theme-nicky-blog/blob/bbc782332e6938daaa2fca1b25d6df7e78f19c6c/theme/gatsby-node.js#L278-L282
When you comment out the lines linked above, filtering in GraphQL on these fields is no longer possible.
LekoArts suggested I ask this question more publicly than in the discord, since it might be a good topic for documentation.
How do I describe Many to Many relationships with createSchemaCustomization?
The use cases I have are the following:
@Everspace Try this for 1. add to gatsby-node.js
. I've not tried this but I am doing something similar linking from a custom type (using the node interface) to an image, and it works. It does rely on the link extension so you will need to use IDs assigned by Gatsby. I guess you could map whatever IDs you are using in your datasource to Gatsby IDs during createPages
?
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = [
schema.buildObjectType({
name: 'Store',
fields: {
products: {
type: "[Product]",
extensions: {
link: {},
},
},
},
interfaces: ['Node'],
}),
schema.buildObjectType({
name: 'Product',
fields: {
stores: {
type: "[Store]",
extensions: {
link: {},
},
},
},
interfaces: ['Node'],
}),
]
createTypes(typeDefs)
}
@NickyMeuleman Do you mean you don't get results or that you don't see fields in input object?
When I comment out the date
key in that link, sorting by date no longer has any effect.
query MyQuery {
allBlogPost(sort: {fields: date, order: DESC}) {
nodes {
date(fromNow: true)
}
}
}
has the same (non-empty) result as the same query with the order set to ASC
.
Was hoping that wouldn't be necessary since there is a custom date resolver
Are there any examples out there for how to handle schema customization for an optional image coming from Contentful?
Been a while since Schema Customisation has been released and things are stable. I think it's time to close this issue 🙂
Incredible work @freiksenet and @stefanoverna ❤️
Folks, if you're having trouble related to Schema Customisation, let's open independent issues. Thanks!
Most helpful comment
Published
[email protected]
. Thank you everyone!