diff --git a/docs/docs/node-creation.md b/docs/docs/node-creation.md index f7f7ab40bbff6..271dbea897b41 100644 --- a/docs/docs/node-creation.md +++ b/docs/docs/node-creation.md @@ -26,7 +26,7 @@ All nodes in Gatsby are stored in a flat structure in the redux `nodes` namespac } ``` -An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. This has some implications on [child field inference](/docs/schema-gql-type/#child-fields-creation) in the Schema Generation phase. +An important note here is that we do not store a distinct collection of each type of child. Rather we store a single collection that they're all packed into. This has some implications on [child field inference](/docs/schema-generation/#parent-children-relationships) in the Schema Generation phase. ### Explicitly recording a parent/child relationship @@ -36,7 +36,7 @@ This does **not** automatically create a `parent` field on the child node. If a ### Foreign Key reference (`___NODE`) -We've established that child nodes are stored at the top level in redux, and are referenced via ids in their parent's `children` collection. The same mechanism drives foreign key relationships. Foreign key fields have a `___NODE` suffix on the field name. At query time, Gatsby will take the field's value as an ID, and search redux for a matching node. This is explained in more detail in [schema gqlTypes](/docs/schema-gql-type#foreign-key-reference-___node). +We've established that child nodes are stored at the top level in redux, and are referenced via ids in their parent's `children` collection. The same mechanism drives foreign key relationships. Foreign key fields have a `___NODE` suffix on the field name. At query time, Gatsby will take the field's value as an ID, and search redux for a matching node. This is explained in more detail in [schema gqlTypes](/docs/schema-inference#foreign-key-reference-___node). ### Plain objects at creation time @@ -53,7 +53,7 @@ Let's say you create the following node by passing it to `createNode` The value for `baz` is itself an object. That value's parent is the top level object. In this case, Gatsby simply saves the top level node as is to redux. It doesn't attempt to extract `baz` into its own node. It does however track the subobject's root NodeID using [Node Tracking](/docs/node-tracking/) -During schema compilation, Gatsby will infer the sub object's type while [creating the gqlType](/docs/schema-gql-type#plain-object-or-value-field). +During schema compilation, Gatsby will infer the sub object's type while [creating the gqlType](/docs/schema-inference#plain-object-or-value-field). ## Fresh/stale nodes diff --git a/docs/docs/schema-connections.md b/docs/docs/schema-connections.md deleted file mode 100644 index 6756cf15133f3..0000000000000 --- a/docs/docs/schema-connections.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Schema Connections ---- - -> This documentation isn't up to date with the latest [schema customization changes](/docs/schema-customization). -> -> Outdated areas are: -> -> - add `nodes` as a sibling to `edges` -> - there might be more convenience fields (ask @freiksenet) -> -> You can help by making a PR to [update this documentation](https://github.com/gatsbyjs/gatsby/issues/14228). - -## What are schema connections? - -So far in schema generation, we have covered how [GraphQL types are inferred](/docs/schema-gql-type), how [query arguments for types](/docs/schema-input-gql) are created, and how [sift resolvers](/docs/schema-sift) work. But all of these only allow querying down to a single node of a type. Schema connections is the ability to query over **collections** of nodes of a type. For example, if we want to query all markdown nodes by some criteria, it will allow us to write queries such as: - -```graphql -{ - allMarkdownRemark(filter: {frontmatter: {tags: {in: "wordpress"}}}) { - edges { - node { - ... - } - } - } -} -``` - -Other features covered by schema connections are aggregators and reducers such as `distinct`, `group` and `totalCount`, `edges`, `skip`, `limit`, and more. - -### Connection/Edge - -A connection is an abstraction that describes a collection of nodes of a type, and how to query and navigate through them. In the above example query, `allMarkdownRemark` is a Connection Type. Its field `edges` is analogous to `results`. Each Edge points at a `node` (in the collection of all markdownRemark nodes), but it also points to the logical `next` and `previous` nodes, relative to the `node` in the collection (meaningful if you provided a `sort` arg). - -_Fun Fact: This stuff is all based on [relay connections](https://facebook.github.io/relay/graphql/connections.htm) concepts_ - -The ConnectionType also defines input args to perform paging using the `skip/limit` pattern. The actual logic for paging is defined in the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library in [arrayconnection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/arrayconnection.js). It is invoked as the last part of the [run-sift](/docs/schema-sift#5-run-sift-query-on-all-nodes) function. To aid in paging, the ConnectionType also defines a `pageInfo` field with a `hasNextPage` field. - -The ConnectionType is defined in the [graphql-skip-limit connection.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/graphql-skip-limit/src/connection/connection.js) file. Its construction function takes a Type, and uses it to create a connectionType. E.g. passing in `MarkdownRemark` Type would result in a `MarkdownRemarkConnection` type whose `edges` field would be of type `MarkdownRemarkEdge`. - -### GroupConnection - -A GroupConnection is a Connection with extended functionality. Instead of simply providing the means to access nodes in a collection, it allows you to group those nodes by one of its fields. It _is_ a `Connection` Type itself, but with 3 new fields: `field`, `fieldValue`, and `totalCount`. It adds a new input argument to `ConnectionType` whose value can be any (possibly nested) field on the original type. - -The creation of the GroupConnection is handled in [build-connection-fields.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L57). It's added as the `group` field to the top level type connection. This is most easily shown in the below diagram. - -```dot -digraph structs { - node [shape=Mrecord]; - mdConn [ label = "{ MarkdownRemarkConnection\l (allMarkdownRemark) | pageInfo | edges | group | distinct | totalCount }" ]; - mdEdge [ label = "{ MarkdownRemarkEdge | node | next | previous }" ]; - mdGroupConn [ label = "{ MarkdownRemarkGroupConnectionConnection | pageInfo | edges | field | fieldValue | totalCount }" ]; - mdGroupConnEdge [ label = "{ MarkdownRemarkGroupConnectionEdge | node | next | previous }" ]; - mdConn:group -> mdGroupConn; - mdConn:edges -> mdEdge; - mdGroupConn:edges -> mdGroupConnEdge; -} -``` - -Let's see this in practice. Say we were trying to group all markdown nodes by their author. We would query the top level `MarkdownRemarkConnection` (`allMarkdownRemark`) which would return a `MarkdownRemarkConnection` with this new group input argument, which would return a `MarkdownRemarkGroupConnectionConnection` field. E.g: - -```graphql -{ - allMarkdownRemark { - group(field: frontmatter___author) { - fieldValue - edges { - node { - frontmatter { - title - } - } - } - } - } -} -``` - -#### Field enum value - -The `frontmatter___author` value is interesting. It describes a nested field. I.e, we want to group all markdown nodes by their `frontmatter.author` field. The author field in each frontmatter subobject. So why not use a period? The problem is that GraphQL doesn't allow periods in fields names, so we instead use `___`, and then in the [resolver](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L69), we convert it back to a period. - -The second interesting thing is that `frontmatter___author` is not a string, but rather a GraphQL enum. You can verify this by using intellisense in GraphiQL to see all possible values. This implies that Gatsby has generated all possible field names. Which is true! To do this, we create an [exampleValue](/docs/schema-gql-type#gqltype-creation) and then use the [flat](https://www.npmjs.com/package/flat) library to flatten the nested object into string keys, using `___` delimiters. This is handled by the [data-tree-utils.js/buildFieldEnumValues](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/data-tree-utils.js#L277) function. - -Note, the same enum mechanism is used for creation of `distinct` fields - -#### Group Resolver - -The resolver for the Group type is created in [build-connection-fields.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-connection-fields.js#L57). It operates on the result of the core connection query (e.g. `allMarkdownRemark`), which is a `Connection` object with edges. From these edges, we retrieve all the nodes (each edge has a `node` field). And now we can use Lodash to group those nodes by the fieldname argument (e.g. `field: frontmatter___author`). - -If sorting was specified ([see below](#sorting)), we sort the groups by fieldname, and then apply any `skip/limit` arguments using the [graphql-skip-limit](https://www.npmjs.com/package/graphql-skip-limit) library. Finally we are ready to fill in our `field`, `fieldValue`, and `totalCount` fields on each group, and we can return our resolved node. - -### Input filter creation - -Just like in [gql type input filters](/docs/schema-input-gql), we must generate standard input filters on our connectiontype arguments. As a reminder, these allow us to query any fields by predicates such as `{ eq: "value" }`, or `{ glob: "foo*" }`. This is covered by the same functions (in [infer-graphql-object-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js)), except that we're passing in Connection types instead of basic types. The only difference is that we use the `sort` field ([see below](#sorting)) - -### Sorting - -A `sort` argument can be added to the `Connection` type (not the `GroupConnection` type). You can sort by any (possibly nested) field in the connection results. These are enums that are created via the same mechanism described in [enum fields](#field-enum-value). Except that the inference of these enums occurs in [infer-graphql-input-type.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L302). - -The Sort Input Type itself is created in [build-node-connections.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-connections.js#L49) and implemented by [create-sort-field.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/create-sort-field.js). The actual sorting occurs in run-sift (below). - -### Connection Resolver (sift) - -Finally, we're ready to define the resolver for our Connection type (in [build-node-connections.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/build-node-connections.js#L65)). This is where we come up with the name `all${type}` (e.g. `allMarkdownRemark`) that is so common in Gatsby queries. The resolver is fairly simple. It uses the [sift.js](https://www.npmjs.com/package/sift) library to query across all nodes of the same type in redux. The big difference is that we supply the `connection: true` parameter to `run-sift.js` which is where sorting, and pagination is actually executed. See [Querying with Sift](/docs/schema-sift) for how this actually works. diff --git a/docs/docs/schema-generation.md b/docs/docs/schema-generation.md index 9a315a986f0a6..70cca94b549a6 100644 --- a/docs/docs/schema-generation.md +++ b/docs/docs/schema-generation.md @@ -2,66 +2,66 @@ title: Schema Generation --- -> This documentation isn't up to date with the latest [schema customization changes](/docs/schema-customization). -> You can help by making a PR to [update this documentation](https://github.com/gatsbyjs/gatsby/issues/14228). +Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. Gatsby Schema is different from many regular GraphQL schemas in that it combines plugin or user defined schema information with data inferred from the nodes' shapes. The latter is called _schema inference_. Users or plugins can explicitly define the schema, in whole or in part, using the [schema customization API](/docs/schema-customization). Usually, every node will get a GraphQL Type based on its `node.internal.type` field. When using Schema Customization, all types that implement the `Node` interface become GraphQL Node Types and thus get root level fields for accessing them. -Once the nodes have been sourced and transformed, the next step is to generate the GraphQL Schema. This is one of the more complex parts of the Gatsby code base. In fact, as of writing, it accounts for a third of the lines of code in core Gatsby. It involves inferring a GraphQL schema from all the nodes that have been sourced and transformed so far. Read on to find out how it's done. +## GraphQL Compose -## Group all nodes by type +Schema creation is done using the [`graphql-compose`](https://github.com/graphql-compose/graphql-compose) library. GraphQL Compose is a toolkit for creating schemas programmatically. It has great tools to add types and fields in an iterative manner. Gatsby does lots of processing and schema generation, so a library like this fits the use case perfectly. -Each sourced or transformed node has a `node.internal.type`, which is set by the plugin that created it. E.g, the `source-filesystem` plugin [sets the type to File](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/create-file-node.js#L46). The `transformer-json` plugin creates a dynamic type [based on the parent node](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-json/src/gatsby-node.js#L48). E.g. `PostsJson` for a `posts.json` file. +You can create a schema in GraphQL Compose by adding types to a Schema Composer - an intermediate object that holds all the schema types inside itself. After all modifications are done, the composer is converted into a regular GraphQL Schema. -During the schema generation phase, we must generate what's called a `ProcessedNodeType` in Gatsby. This is a simple structure that builds on top of a [graphql-js GraphQLObjectType](https://graphql.org/graphql-js/type/#graphqlobjecttype). Our goal in the below steps is to infer and construct this object for each unique node type in redux. +## 1. Schema inference -The flow is summarized by the below graph. It shows the intermediate transformations or relevant parts of the user's GraphQL query that are performed by code in the Gatsby [schema folder](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby/src/schema), finally resulting in the [ProcessedNodeType](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/__tests__/build-node-types.js#L182). It uses the example of building a `File` GraphQL type. +Every time a node is created, Gatsby will generate _inference metadata_ for it. Metadata for each node can be merged with other metadata, meaning that it's possible to derive the least generic possible schema for a particular node type. Inference metadata can also detect if some data is conflicting. In most cases, this would mean that a warning will be reported for the user and the field won't appear in the data. -```dot -digraph graphname { - "pluginFields" [ label = "custom plugin fields\l{\l publicURL: {\l type: GraphQLString,\l resolve(file, a, c) { ... }\l }\l}\l ", shape = box ]; - "typeNodes" [ label = "all redux nodes of type\le.g. internal.type === `File`", shape = "box" ]; - "exampleValue" [ label = "exampleValue\l{\l relativePath: `blogs/my-blog.md`,\l accessTime: 8292387234\l}\l ", shape = "box" ]; - "resolve" [ label = "ProcessedNodeType\l including final resolve()", shape = box ]; - "gqlType" [ label = "gqlType (GraphQLObjectType)\l{\l fields,\l name: `File`\l}\l ", shape = box ]; - "parentChild" [ label = "Parent/Children fields\lnode {\l childMarkdownRemark { html }\l parent { id }\l}\l ", shape = "box" ]; - "objectFields" [ label = "Object node fields\l node {\l relativePath,\l accessTime\l}\l ", shape = "box" ]; - "inputFilters" [ label = "InputFilters\lfile({\l relativePath: {\l eq: `blogs/my-blog.md`\l }\l})\l ", shape = box ] +This step is explained in more detail in [Schema Inference](/docs/schema-inference) - "pluginFields" -> "inputFilters"; - "pluginFields" -> "gqlType"; - "objectFields" -> "gqlType"; - "parentChild" -> "gqlType" - "gqlType" -> "inputFilters"; - "typeNodes" -> "exampleValue"; - "typeNodes" -> "parentChild"; - "typeNodes" -> "resolve"; - "exampleValue" -> "objectFields"; - "inputFilters" -> "resolve"; - "gqlType" -> "resolve"; -} -``` +## 2. Adding types -## For each unique Type +When users and plugins add types using `createTypes`, those types are added to the schema composer. The types that don't have inference disabled will also get types created from Schema Inference merged into them, with user created fields having priority. After that, inferred types that haven't been created are also added to the composer. -The majority of schema generation code kicks off in [build-node-types.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/__tests__/build-node-types.js). The below steps will be executed for each unique type. +## 3. Legacy schema customization -### 1. Plugins create custom fields +Before schema customization was added, there were several ways that one could modify the schema. Those were the `createNodeField` action, `setFieldsOnGraphQLType` API and `graphql-config.js` mappings. -Gatsby infers GraphQL Types from the fields on the sourced and transformed nodes. But before that, we allow plugins to create their own custom fields. For example, `source-filesystem` creates a [publicURL](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L11) field that when resolved, will copy the file into the `public/static` directory and return the new path. +### `createNodeField` -To declare custom fields, plugins implement the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API and apply the change only to types that they care about (e.g. source-filesystem [only proceeds if type.name = `File`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-filesystem/src/extend-file-node.js#L6). During schema generation, Gatsby will call this API, allowing the plugin to declare these custom fields, [which are returned](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/__tests__/build-node-types.js#L151) to the main schema process. +This adds a field under the `fields` field. Plugins can't modify types that they haven't created, so you can use this method to add data to nodes that your plugin doesn't own. This doesn't modify the schema directly. Instead, those fields are picked by inference. There are no plans to deprecate this API at the moment. -### 2. Create a "GQLType" +### `setFieldsOnGraphQLType` -This step is quite complex, but at its most basic, it infers GraphQL Fields by constructing an `exampleObject` that merges all fields of the type in Redux. It uses this to infer all possible fields and their types, and construct GraphQL versions of them. It does the same for fields created by plugins (like in step 1). This step is explained in detail in [GraphQL Node Types Creation](/docs/schema-gql-type). +This allows adding GraphQL Fields to any node type. This operates on GraphQL types itself and the syntax matches `graphql-js` field definitions. This API will be marked as deprecated in Gatsby v3, moved under a flag in Gatsby v4, and removed from Gatsby v5. `createTypes` and `addResolvers` should solve all the use cases for this API. -### 3. Create Input filters +### `graphql-config.js` mapping -This step creates GraphQL input filters for each field so the objects can be queried by them. More details in [Building the Input Filters](/docs/schema-input-gql). +[Node Type Mapping](/docs/gatsby-config/#mapping-node-types) allows customizing schema by using site configuration. There are currently no plans to deprecate this API at the moment. -### 4. ProcessedTypeNode creation with resolve implementation +## 4. Parent / children relationships -Finally, we have everything we need to construct our final Gatsby Type object (known as `ProcessedTypeNode`). This contains the input filters and gqlType created above, and implements a resolve function for it using sift. More detail in the [Querying with Sift](/docs/schema-sift) section. +Nodes can be connected into _child-parent_ relationships either by using [`createParentChildLink`](/docs/actions/#createParentChildLink) or by adding the `parent` field to raw node data. Child types can always access parent with the `parent` field in GraphQL. Parent types also get `children` fields as well as "convenience child fields" `child[TypeName]` or `children[TypeName]`. -### 5. Create Connections for each type +Children types are either inferred from data or created using `@childOf` directive, either by parent type name or by `mimeType` (only for File parent types). -We've inferred all GraphQL Types, and the ability to query for a single node. But now we need to be able to query for collections of that type (e.g. `allMarkdownRemark`). [Schema Connections](/docs/schema-connections/) takes care of that. +## 5. Processing each type and adding root fields + +See [Schema Root Fields and Utility Types](/docs/schema-root-fields) for a more detailed description of this step. + +For each type, utility types are created. Those are input object types, used for searching, sorting and filtering, and types for paginated data (Connections and Edges). + +For searching and sorting, Gatsby goes through every field in the type and converts them to corresponding Input GraphQL types. Scalars are converted to objects with filter operator fields like "eq" or "ni". Object types are converted to Input Object types. For sorting, enums are created out of all fields so that one can use that to specify the sort. + +Those types are used to create _root fields_ of the schema in `Query` type. For each node type a root field for querying one item and paginated items are created (for example, for type BlogPost it would be `blogPost` and `allBlogPost`). + +## 6. Merging in third-party schemas + +If a plugin like `gatsby-source-graphql` is used, all third-party schemas that it provided are merged into the Gatsby schema. + +## 7. Adding custom resolvers + +[`createResolvers`](/docs/schema-customization/#createresolvers-api) API is called, allowing users to add additional customization on top of created schema. This is an "escape hatch" API, as it allows to modify any fields or types in the Schema, including Query type. + +## 8. Second schema build for SitePage + +Because SitePage nodes are created after a schema is first created (at `createPages`) API call, the type of the `SitePage.context` field can change based on which context was passed to pages. Therefore, an additional schema inference pass happens and then the schema is updated. + +Note that this behavior will be removed in Gatsby v3 and context will become a list of key/value pairs in GraphQL. diff --git a/docs/docs/schema-gql-type.md b/docs/docs/schema-inference.md similarity index 98% rename from docs/docs/schema-gql-type.md rename to docs/docs/schema-inference.md index 407322a57be87..526c762af2b86 100644 --- a/docs/docs/schema-gql-type.md +++ b/docs/docs/schema-inference.md @@ -1,7 +1,9 @@ --- -title: GraphQL Node Types Creation +title: Schema Inference --- +> This used to be called GraphQL Node Types Creation and should contain info about schema inference and inference > metadata +> > This documentation isn't up to date with the latest [schema customization changes](/docs/schema-customization). > > Outdated areas are: diff --git a/docs/docs/schema-input-gql.md b/docs/docs/schema-input-gql.md deleted file mode 100644 index d6d2a7a066fae..0000000000000 --- a/docs/docs/schema-input-gql.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: Inferring Input Filters ---- - -> This documentation isn't up to date with the latest [schema customization changes](/docs/schema-customization). -> -> Outdated areas are: -> -> - `inferObjectStructureFromNodes` does not exist anymore -> - input fields are generated differently. Gatsby previously inferred values one by one, now graphql-compose handles this -> -> You can help by making a PR to [update this documentation](https://github.com/gatsbyjs/gatsby/issues/14228). - -## Input Filters vs gqlType - -In [gqlTypes](/docs/schema-gql-type), we inferred a Gatsby Node's main fields. These allow us to query a node's children, parent and object fields. But these are only useful once a top level GraphQL Query has returned results. In order to query by those fields, we must create GraphQL objects for input filters. E.g, querying for all markdownRemark nodes that have 4 paragraphs. - -```graphql -{ - markdownRemark(wordCount: { paragraphs: { eq: 4 } }) { - html - } -} -``` - -The arguments (`wordcount: {paragraphs: {eq: 4}}`) to the query are known as Input filters. In graphql-js, they are the [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype). This section covers how these Input filters are inferred. - -### Inferring input filters from example node values - -The first step is to generate an input field for each type of field on the redux nodes. For example, we might want to query markdown nodes by their front matter author: - -```graphql -{ - markdownRemark(frontmatter: { author: { eq: "F. Scott Fitzgerald" } }) { - id - } -} -``` - -This step is handled by [`inferInputObjectStructureFromNodes`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields.js#L235). First, we generate an example Value (see [gqlTypes](/docs/schema-gql-type#gqltype-creation)). For each field on the example value (e.g. `author`), we create a [GraphQLInputObjectType](https://graphql.org/graphql-js/type/#graphqlinputobjecttype) with an appropriate name. The fields for Input Objects are predicates that depend on the value's `typeof` result. E.g. for a String, we need to be able to query by `eq`, `regex` etc. If the value is an object itself, then we recurse, building its fields as above. - -If the key is a foreign key reference (ends in `___NODE`), then we find the field's linked Type first, and progress as above (for more on how foreign keys are implemented, see [gqlType](/docs/schema-gql-type#foreign-key-reference-___node)). After this step, we will end up with an Input Object type such as . - -```javascript -{ - `MarkdownRemarkFrontmatterAuthor`: { - name: `MarkdownRemarkFrontmatterAuthorInputObject`, - fields: { - `MarkdownRemarkFrontmatterAuthorName` : { - name: `MarkdownRemarkFrontmatterAuthorNameQueryString`, - fields: { - eq: { type: GraphQLString }, - ne: { type: GraphQLString }, - regex: { type: GraphQLString }, - glob: { type: GraphQLString }, - in: { type: new GraphQLList(GraphQLString) }, - } - } - } - } -} -``` - -### Inferring input filters from plugin fields - -Plugins themselves have the opportunity to create custom fields that apply to ALL nodes of a particular type, as opposed to having to explicitly add the field on every node creation. An example would be `markdownRemark` which adds a `wordcount` field to each node automatically. This section deals with the generation of input filters so that we can query by these fields as well. E.g: - -```graphql -{ - markdownRemark(wordCount: { paragraphs: { eq: 4 } }) { - html - } -} -``` - -Plugins add custom fields by implementing the [setFieldsOnGraphQLNodeType](/docs/node-apis/#setFieldsOnGraphQLNodeType) API. They must return a full GraphQLObjectType, complete with `resolve` function. Once this API has been run, the fields are passed to [`inferInputObjectStructureFromFields`](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js#L195), which will generate input filters for the new fields. The result would look something like: - -```javascript -{ //GraphQLInputObjectType - name: `WordCountwordcountInputObject`, - fields: { - `paragraphs`: { - type: { // GraphQLInputObjectType - name: `WordCountParagraphsQueryInt`, - fields: { - eq: { type: GraphQLInt }, - ne: { type: GraphQLInt }, - gt: { type: GraphQLInt }, - gte: { type: GraphQLInt }, - lt: { type: GraphQLInt }, - lte: { type: GraphQLInt }, - in: { type: new GraphQLList(GraphQLInt) }, - } - } - } - } -} -``` - -As usual, the input filter fields (`eq`, `lt`, `gt`, etc) are based on the type of the field (`Int` in this case), which is defined by the plugin. - -### Merged result - -Now that we've generated input fields from the redux nodes and from custom plugin fields, we merge them together. E.g - -```javascript -{ - - // from infer input fields from object - `MarkdownRemarkAuthor`: { - name: `MarkdownRemarkAuthorInputObject`, - fields: { - `MarkdownRemarkAuthorName` : { - name: `MarkdownRemarkAuthorNameQueryString`, - fields: { - eq: { type: GraphQLString }, - ne: { type: GraphQLString }, - regex: { type: GraphQLString }, - glob: { type: GraphQLString }, - in: { type: new GraphQLList(GraphQLString) }, - } - } - } - }, - - // From infer input fields from fields - `wordCount`: { //GraphQLInputObjectType - name: `WordCountwordcountInputObject`, - fields: { - `paragraphs`: { - type: { // GraphQLInputObjectType - name: `WordCountParagraphsQueryInt`, - fields: { - eq: { type: GraphQLInt }, - ne: { type: GraphQLInt }, - gt: { type: GraphQLInt }, - gte: { type: GraphQLInt }, - lt: { type: GraphQLInt }, - lte: { type: GraphQLInt }, - in: { type: new GraphQLList(GraphQLInt) }, - } - } - } - } - } -} -``` diff --git a/docs/docs/schema-root-fields.md b/docs/docs/schema-root-fields.md new file mode 100644 index 0000000000000..2e0cad78c4487 --- /dev/null +++ b/docs/docs/schema-root-fields.md @@ -0,0 +1,106 @@ +--- +title: Schema Root Fields and Utility Types +--- + +## What are Schema Root Fields? + +Schema Root Fields are the "entry point" of any GraphQL query. Gatsby generates two root fields for each `Node` type that gets created as a result of [Schema Generation](/docs/schema-generation). Third-party schemas and the `createResolvers` API can add additional root fields on top of those. + +The root fields that Gatsby generates are used to retrieve either one item of a certain type or many items of that type. For example, for type `BlogPost`, Gatsby will create `blogPost` and `allBlogPost` root fields. + +While those fields can be used without any arguments, the additional power lies in the fact that they accept additional parameters to filter, sort or paginate the resulting data. As those parameters depend on a particular type that they are used for, Gatsby generates _Utility Types_ to support those. Those types are used in the root fields to support filtering or sorting, as well as to return Paginated Data. + +## Plural root fields + +Plural fields accept four arguments - `filter`, `sort`, `skip` and `limit`. `filter` allows filtering based on node field values (see Filter Types below), `sort` sorts the result (see Sorting types below). `skip` and `limit` offsets the result by `skip` nodes and limits it to `limit` items. Plural root fields return a Connection type (see Pagination Types below) for the returning type (for example, `BlogPostConnection`). + +```graphql +{ + allBlogPost(filter: { date: { lt: "2020-01-01" }}, sort: {fields: [date], order: ASC) { + nodes { + id + } + } +} +``` + +## Singular root fields + +Singular root fields accept the same `filter` parameter as plural, but the `filter` is spread directly into arguments. Thus filter parameters need to be passed to the field directly. They return the resulting object directly. + +If no parameters are passed, they return a random node of that type (if any exist). That random node type is explicitly undefined and there is no guarantee that it will be stable (or unstable) between builds or rebuilds. If no node was found according to the filter, null is returned. + +```graphql +{ + blogPost(id: { slug: "graphql-is-the-best" }) { + id + } +} +``` + +## Pagination types + +Gatsby uses a common pattern in GraphQL called [Connections](https://relay.dev/graphql/connections.htm). This pattern has nothing to do with connecting anything, but rather is an abstraction over pagination. When you query a connection, you get a slice of the resulting data based on passed `skip` and `limit` parameters. It also allows doing additional operations on a list, like doing grouping or distinct operations. + +- `edges` - edge is the actual Node object together with additional metadata regarding it's location in the page. `edge` contains `node` - the actual object, and `next`/`prev` objects to get next or previous object from the current one. +- `nodes` - a flat list of Node objects +- `pageInfo` - additional pagination metadata +- `pageInfo.totalCount` - (also available as `totalCount` number of all nodes that match the filter, before pagination +- `pageInfo.currentPage` - index of the current page (based on 1) +- `pageInfo.hasNextPage`, `pageInfo.hasPreviousPage` - whether there is a next or previous page based on current pagination +- `pageInfoitemCount` - number of items on current page +- `perPage` - requested number of items on each page +- `pageCount` - total number of pages +- `distinct(field)` - print distinct values for given field +- `group(field)` - return values grouped by a field + +## Filter types + +For every Node type, a filter GraphQL input type is created. Gatsby has pre-created "operator types" for each scalar, like `StringQueryOperatorType`, that has keys as possible operators (for example, `eq`, `ne`) and values as appropriate values for them. Then Gatsby looks at every field in the type and executes roughly the following algorithm. + +1. If field is a scalar + 1.1 Get a corresponding operator type for that scalar type + 1.2 Replace field with that type +2. If field is not a scalar - recursively go through the nested type's fields and then assign resulting input object type to the field. + +Abridged resulting types: + +```graphql +input StringQueryOperatorInput { + eq: String + ne: String + in: [String] + nin: [String] + regex: String + glob: String +} + +input BlogFilterInput { + title: StringQueryOperatorInput + comments: CommentFilterInput + # and so forth +} +``` + +## Sorting types + +For sort, GraphQL creates an enum of all fields (including up to 3 levels of nesting) for a particular type. + +```graphql +enum BlogFieldsEnum { + id + title + date + parent___id + # and so forth +} +``` + +This field is combined with enum that contains ordering (`ASC` or `DESC`) into a sort input type. + +```graphql +input BlogSortInput { + fields: [BlogFieldsEnum] + order: [SortOrderEnum] = [ASC] +} +``` diff --git a/www/redirects.yaml b/www/redirects.yaml index ee01e3bc329de..1a16c879216b7 100644 --- a/www/redirects.yaml +++ b/www/redirects.yaml @@ -156,5 +156,11 @@ toPath: /docs/conditional-page-builds/ - fromPath: /docs/contributing/translation/ui-messages/ toPath: /docs/contributing/translation/ +- fromPath: /docs/schema-gql-type/ + toPath: /docs/schema-inference/ +- fromPath: /docs/schema-input-gql/ + toPath: /docs/schema-root-fields/ +- fromPath: /docs/schema-connections/ + toPath: /docs/schema-root-fields/ - fromPath: /contributing/website-contributions/ toPath: /contributing/ diff --git a/www/src/data/sidebars/doc-links.yaml b/www/src/data/sidebars/doc-links.yaml index 0857b8bed6c94..1b7172bef12e7 100644 --- a/www/src/data/sidebars/doc-links.yaml +++ b/www/src/data/sidebars/doc-links.yaml @@ -656,10 +656,12 @@ - title: Schema Generation link: /docs/schema-generation/ items: - - title: Building the GqlType - link: /docs/schema-gql-type/ - - title: Building the Input Filters - link: /docs/schema-input-gql/ + - title: Schema Inference + link: /docs/schema-inference/ + - title: Schema Root Fields and Utility Types + link: /docs/schema-root-fields/ + - title: Querying with Sift + link: /docs/schema-sift/ - title: Query Filters link: /docs/query-filters/ - title: Connections