Allow @resolver directive in user-defined types’ fields

Fauna relies on your feedback to educate our product development process. To help us prioritize this feature in our product roadmap, please vote for this topic and complete our 2020 GraphQL Roadmap Survey.

Issue description

Right now, the @resolver directive is only allowed in the queries and mutations fields. It would be handy to use it in an object’s type fields as well so that they could resolve a value using a UDF.

Example:

type TodoList {
  title: String!
  todos: [Todo!]!
  totalTodos: Int @resolver(name: "count_todos")
}

These dynamically resolved fields might depend on other fields from within the same type. For cases in which there would be naturally no arguments for the annotated field, we might provide some context object containing the rest of the type’s information.

To share a use case that only just came up today:

I have a Company type with a relation to a list of Log documents. The client app is typically most interested in the latest log. With user-type @resolver I could create a virtual field to look up the latest log.

I am working around this by saving the latest log in the Company documents. However, this means that every time I create a new log, I have to update the related Company. This means a new root mutation for creating log documents. I otherwise could have used the generated createLog mutation.

Example of how I think (wish) this could work with user-type resolvers:

# setup.gql

type Log {
  # ...
  company: Company! @relation
}

type Company {
  # ...
  logs: [Log!] @relation
  latestLog: Log! @resolver(name: "latest_log_for_company")
}
// UDF "latest_log_for_company" body

Query( 
  Lambda(
    'parent',
    Select(['data', 0], 
      Paginate(
        Match(
          Index('company_log_by_company_sorted'), 
          Select('ref', Var('parent'))
        )
      )
    )
  )
)

GraphQL resolvers have signature (parent, args, context, info).

parent

It would be very natural, I think, to always pass to the UDF the parent Document, i.e. { ref: ..., ts: ..., data: ...}, or at least the parent Ref.

parent/root argument doesn’t really make sense for top level resolvers in FQL. I think that top level resolvers in GraphQL keep the argument for historical and/or consistency reasons, since context argument is there. Consistency in root @resolver and user-type @resolver, although obviously a breaking change, could be helpful in the long run. worth it? I don’t know.

args

top level @resolver spreads args out. This is convenient for avoiding lots of Select calls in UDF. But does it sacrifice something for expanding capabilities, e.g. providing parent or context to a UDF? I would gladly accept more typing of added Select if that were necessary to be able to use in user-type @resolver, or could customize a context arg in UDFs.

context

I can think of a few uses for passing a customizable context arg to all GraphQL UDFs.

Perhaps there could be a reserved UDF, e.g. called _createGraphQLContext, that could run at the beginning of every request. This could be updated by developers to add things related to Identity, perhaps an override for Identity in public mutations, or any other database state desired.

The context object could be an immutable thing that is passed to all UDF @resolver functions.

info

Unless the entire graphQL query is to be handed off to developers to mess with, I don’t see value in replicating the info arg. But it’s there. So I guess I’ll mention it :slight_smile: