Nexus: The extendConnection resolve function receives the wrong root

Created on 27 Mar 2020  路  9Comments  路  Source: graphql-nexus/nexus

If I'm not mistaken, the first argument to the resolve function of an extendConnection field should be the same root as that of the nodes function (the parent). It currently appears to be passed the connection resulting from the nodes function.

typbug

Most helpful comment

After some investigation, there are two things I noticed here:

1. Extending the connection per field

export const User = objectType({
  name: 'User',
  definition(t) {
    t.id('id');
    t.string('name');
    t.connectionField('PostConnection', {
      type: 'String',
      resolve: (root, args) => {
        console.log({ root, args });
        return {
          edges: [
            {
              cursor: 'cursor1',
              node: 'node1',
            },
            {
              cursor: 'cursor2',
              node: 'node2',
            },
          ],
        };
      },
      extendConnection(t) {
        t.int('totalCount', {
          resolve: (rootFromTotalCount, argsFromTotalCount, ctx) => {
            console.log({ rootFromTotalCount, argsFromTotalCount });
            return 5;
          },
        });
      },
    });
  },
});
  • The rootFromTotalCount has type any where I guess it should have the same type as the root from the resolve function above (the one to resolve the connectionField)
  • When you actually execute the following query:
{
  user {
    id
    name
    PostConnection(first: 5) {
      edges{
        cursor
        node
      }
      totalCount
    }
  }
}

Here's what gets printed to the console:

image

I think that rootFromTotalCount and argsFromTotalCount should be the same as root and args right ? As @jamiehodge pointed out, it appears that the root in an extendConnection field get passed the result from the connection's resolve function.

2. Extending the connection globally

When extending the connection globally like in your example, the rootFromTotalCount appears to have the correct type but it looks like the totalCount function is never executed (although I query for the totalCount field)

All 9 comments

Ah, yeah I suppose it should that that same convenience shortcut. Feel free to open a PR to enable that, or I'll try and get to this (and #394) this weekend.

@tgriesser There are more issues.

The function for the global extension is never called and the response gets a non-null error. The plugin tests only check that the schema is update, but not whether the function is called.

To recap:

One-off extension: source/root and args are wrong
Global: function never called

I guess the real problem here is that the connection extensions are sub-fields, meaning that they are only passed the parent (connection) and their own args ({}).

I worked around this temporarily by using the makeResolveFn from within the resolve function and merging the result with the total.

The workaround using makeResolveFn directly has me wondering whether the plugin would be better if it didn't combine resolution with schema definition. In that respect, I think graphql-js-relay has it right, exposing functions for different kinds of resolvers. If .connectionField just made sure that the schema and types were in place, one could either import their, for example, REST-compatible resolve function or roll their own. It would also make connection extensions less brittle. At the end of the day, I wonder whether the resolution logic would be better off in separate modules.

Hi @jamiehodge,

I'm not sure if this has already been fixed or if I'm not getting the point of the issue here, but it looks like fields of extendConnection are receiving the same root & args params than nodes as shown on the screenshot below 馃憞. Am I missing something here? 馃

image

After some investigation, there are two things I noticed here:

1. Extending the connection per field

export const User = objectType({
  name: 'User',
  definition(t) {
    t.id('id');
    t.string('name');
    t.connectionField('PostConnection', {
      type: 'String',
      resolve: (root, args) => {
        console.log({ root, args });
        return {
          edges: [
            {
              cursor: 'cursor1',
              node: 'node1',
            },
            {
              cursor: 'cursor2',
              node: 'node2',
            },
          ],
        };
      },
      extendConnection(t) {
        t.int('totalCount', {
          resolve: (rootFromTotalCount, argsFromTotalCount, ctx) => {
            console.log({ rootFromTotalCount, argsFromTotalCount });
            return 5;
          },
        });
      },
    });
  },
});
  • The rootFromTotalCount has type any where I guess it should have the same type as the root from the resolve function above (the one to resolve the connectionField)
  • When you actually execute the following query:
{
  user {
    id
    name
    PostConnection(first: 5) {
      edges{
        cursor
        node
      }
      totalCount
    }
  }
}

Here's what gets printed to the console:

image

I think that rootFromTotalCount and argsFromTotalCount should be the same as root and args right ? As @jamiehodge pointed out, it appears that the root in an extendConnection field get passed the result from the connection's resolve function.

2. Extending the connection globally

When extending the connection globally like in your example, the rootFromTotalCount appears to have the correct type but it looks like the totalCount function is never executed (although I query for the totalCount field)

Thanks a lot for the explanations @tatchi, that's super useful! 馃檹

I think that rootFromTotalCount and argsFromTotalCount should be the same as root and args right ?

So, after further investigation, this is AFAIK the expected result. In your example, totalCount is a field of the <X>Connection type. eg:

type PostConnection {
  """
  https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types
  """
  edges: [PostEdge]

  """
  https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo
  """
  pageInfo: PageInfo!
  totalCount: Int # <-- totalCount field
}

Meaning that the root param _should_ be the parent (this is all handled by graphql-js anyway) and the parent here _is_ the result from the resolve function. Same goes for the args param, the totalCount field has its own args as it's on a separate type.

Indeed, I've been confused sorry 馃槄 I might have also misunderstood what @jamiehodge explained.

Do you still confirm the two other issues (any root type while extending the connection per field and the resolver not being called when extending globally) ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ryands17 picture ryands17  路  4Comments

jasonkuhrt picture jasonkuhrt  路  5Comments

StevenLangbroek picture StevenLangbroek  路  6Comments

deadcoder0904 picture deadcoder0904  路  6Comments

jasonkuhrt picture jasonkuhrt  路  3Comments