WPGraphQL v0.8.0

Release Notes

TL;DR

This release focuses on adjusting how Nodes are resolved to prevent errors in cases where the nodes are determined to be considered private and non-viewable by the requesting user. (#1138)

More details below:

Schema Breaking Changes

Schema Breaking changes are changes to the shape of the Schema that would require clients interacting with WPGraphQL to update to remain compatible.

  • n/a: The shape of the Schema remains unchanged in this release. Clients shouldn’t need to adjust their queries/mutations to remain compatible with this release.

Internal Breaking Changes

Internal Breaking Changes are changes to internals that might require plugins that extend WPGraphQL to change in order to remain compatible.

  • BREAKING: There are changes to the AbstractConnectionResolver class that affect how connections are resolved. If you are a plugin author extending this class, you will need to update your classes that extend this.
  • BREAKING: Refactored ContentTypeConnectionResolver, TaxonomyConnectionResolver and UserRoleConnectionResolver to not extend the AbstractConnectionResolver class, but instead make use of the Relay::connectionFromArray() method
  • BREAKING: – Update Loaders (CommentLoader, MenuItemLoader, PostObjectLoader, TermObjectLoader, UserLoader) to return an array of resolved nodes (Models) instead of an array of Deferreds.
  • If your plugin extends or calls these classes, you may need to update your code. The loaders now return an array of Nodes (Models) instead of an array of Deferreds.

New Features

  • Accessibility improvements for the documentation. (See: #1049, #1150) Thanks @jacobarriola!
  • Remove resolveNode config arg from most connections registered by the core plugin as the new method for resolving connections doesn’t wait until the last second to resolve nodes
  • For example: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-a04db7b019ce5a16e141cd35799a0718L19-L21, https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-0b5f575771fc27faf7455a8fa0a05d93L79-L81

Release Summary

WPGraphQL makes use of a concept called (Deferred Resolution)[https://github.com/wp-graphql/wp-graphql/pull/722#issue-261315185], to ensure that database queries are executed as efficiently as possible.

WPGraphQL was deferring the resolution of nodes too late, causing errors when Nodes were determined to be private after being loaded.

Take the following query for example :

{
  posts {
     nodes {
         id
         databaseId
         title
     }
  }
}

This query might return a payload like so:

{
  "data": {
    "posts": {
      "nodes": [
        {
          "id": "cG9zdDoyNDI=",
          "databaseId": 242,
          "title": "Test Post"
        },
        {
          "id": "cG9zdDox",
          "databaseId": 1,
          "title": "Hello world!"
        }
      ]
    }
  },
}

Looks great! Just what we’d expect (assuming the site had only 2 posts).

Well, let’s say we had a membership plugin installed (or something similar) that used meta to determine whether a Post should be considered private or not. And let’s say that Post 242 was set to private and should not be returned to a public user.

Because of how the Deferred resolution was working (before this release), the Post would have been resolved too late to be stripped out of the results, and would return a null within the list of Post nodes, and would include an error like so:

{
  "errors": [
    {
      "debugMessage": "Cannot return null for non-nullable field Post.id",
      ...
    }
  ],
  "data": {
    "posts": {
      "nodes": [
        null,
        {
          "id": "cG9zdDox",
          "databaseId": 1,
          "title": "Hello world!"
        }
      ]
    }
  },
}

This behavior is problematic.

First, it throws errors, when there really isn’t an error. Nothing has actually gone wrong. The user is asking for Posts, and GraphQL should be able to return posts without error.

If a Post is private, it shouldn’t be exposed at all. It should be as if it doesn’t even exist in the system. Be returning a null value, we are exposing that something is there behind the scenes.

The correct behavior should be to return a list of Posts, and no errors returned. If a Post is private, it should simply not be included in the list at all.

This release fixes this issue by changing how the Deferred resolution of nodes happens.

Given the query above, resolution used to work like so:

  • Posts: Use WP_Query to get a list of Post IDs. Pass those IDs to the next level of the Resolve Tree
  • Nodes: Use the ID of each node to load the Post using a Batch resolver, pass the Post through the Model Layer to determine if it’s public or private, then either return the Post or return a null value

Because of the late resolution of the Node, this was causing the Cannot return null for non-nullable field Post.id error. There’s no way to strip a private node out of the list of returned nodes if we’re resolving nodes at the last possible tree in the Graph.

This pull request changes the behavior to resolve the nodes earlier.

Given the query above, resolution now works like so:

  • Posts: Use WP_Query to get a list of Post IDs. Pass those IDs to a Deferred Resolver. The Deferred Resolver will make a batch request and load all Nodes, passed through the Model Layer. Nodes that are null will be filtered out now. A list of resolved nodes will be passed to the next level of the Resolve Tree:
  • Nodes: Return the nodes that are passed down. Private nodes will not be passed to this level, so no errors about Cannot return null for non-nullable field Post.id will be returned.

To accomplish this, some changes to the ConnectionResolver classes were made.

Now, Nodes are resolved a step earlier, and the resolved nodes are now passed from the Connection down to Edges/Nodes.

Edges/Nodes now have the full nodes as context in their resolvers, instead of just an ID.

This can be HUGE when needing to add edge data to connections, where before an entity ID was the only context provided, and that can be too little information to be useful.

You can read more about a concrete case where the functionality was problematic, and how this release fixes it here: https://github.com/wp-graphql/wp-graphql/issues/1138#issuecomment-580269285

Changes to the Abstract Connection Resolver

Below is a list of changes the AbstractConnectionResolver Class. If your Plugin extends this class, the below information should help with upgrading.

  • AbstractConnectionResolver adds the following breaking changes:
  • abstract public function get_loader_name()
    • This method was added to tell the connection resolver what Loader to use to load nodes using Deferred resolution. In order to extend the AbstractConnectionResolver, a Loader will also need to be created. You can see the existing Loaders here.
    • see: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-015d5cec0dcc9f802dcfa99bc136f478R252-R260
  • abstract public function get_ids()
    • replaces previous get_items() method
    • This method was added as a way to tell the resolver what IDs we’re dealing with. In many cases, the IDs are returned by the query, and this method can extract them from the Query.
    • see: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-015d5cec0dcc9f802dcfa99bc136f478R297
  • abstract public function get_node_by_id()
    • This method was added to tell the loader how to resolve the node as a Model, ensuring it gets properly passed through the Model layer
    • see: https://github.com/wp-graphql/wp-graphql/compare/master…release/next?expand=1#diff-015d5cec0dcc9f802dcfa99bc136f478R328-R335
  • Changed access of some methods from public to protected as they’re not intended to be used outside of the class or extending classes
    • get_amount_requested(), get_offset(), get_query_amount(), has_next_page(), has_previous_page(), get_start_cursor(), get_end_cursor(), get_nodes(), get_cursor_for_node(), get_edges(), get_page_info()

Published by Jason Bahl

Jason is a Principal Software Engineer at WP Engine based in Denver, CO where he maintains WPGraphQL. When he's not writing code or evangelizing about GraphQL to the world, he enjoys escaping from escape rooms, playing soccer, board games and Fortnite.

Leave a comment

Your email address will not be published. Required fields are marked *