question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Parent type for resolver coming from a query resolver

See original GitHub issue

To handle typings of a query resolver that doesn’t directly return the right JSON, but passes data to other resolvers down the line (as parent data) I needed some weird stuff.

The use case is like this one: https://github.com/howtographql/howtographql/issues/619#issuecomment-385309639

Problem 1: One cannot use the query resolver type as-is, since it expects a particular return type Problem 2: One cannot use the parent/root type from the other resolver type as it is whatever the one in Problem 1 returned

I ended up using only the argument types of the query resolver and returning something arbitrary. Then I extracted the type of that return value and used that as the type of the parent argument of the other resolver. So now the return value of the query resolver is the (parent) argument type of another resolver.

// resolvers/Query.ts
export default {
  feed: async (...allArgs: ArgumentTypes<QueryResolvers.FeedResolver>) => {
     const [, { filter, skip, first, orderBy }, context] = allArgs
     ...
     return {a:1, b:2}
  }
}
// resolvers/Feed.ts
import { FeedResolvers, MyContext } from '../generated/resolvers-types'
import queryResolver from './Query'
export default {
  links: async (parent, _args, context, info) => {
    // typeof parent  is now the right one: Promise<{a: number, b: number}>
    const p = await parent
    return (await parent).links
  },
  found: async (parent, _args) => {
    return (await parent).links.length
  },
} as FeedResolvers.Resolvers<MyContext, ReturnType<typeof queryResolver.feed>>

and i defined this two helper types

export type ArgumentTypes<F> = F extends (...args: infer A) => infer R ? A : never
// The default ReturnType generic breaks because of parent being of type never in the query resolver
export type ReturnType<F> = F extends (...args: infer A) => infer R ? R : never

Convoluted but it is super useful to know exactly what the parameter is, instantly found a bug. Would it be possible to have helpers for that in the generated code somehow?

btw, I’m new to Typescript so be easy on me please

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
dotansimhacommented, Nov 23, 2018

@dbuezas By typings cannot be used directly, you mean that you need to overwrite the generics?

You don’t have to force or use as with the resolvers typings, just use the as-is for the entire resolver object. By default, the generated output of the resolvers plugin is an object matching the structure of your type. You can override it with TypeScript generics, and you are doing it only once, and not for each resolver field, because they should all be the same.

For example:

If you have a a type:

type MyType {
   id: ID!
   foo: String
   bar: Int
}

type Query {
   myType: MyType
}

Then, you resolvers with the generated typings should look like that:

import { MyTypeResolvers, QueryResolvers } from './generates.ts';

const myTypeResolvers: MyTypeResolvers.Resolvers = {
    id: (obj, args, context) => obj.id,
    foo: (obj, args, context) => obj.foo,
    bar: (obj, args, context) => obj.bar,
};

const myTypeResolver: QueryResolvers.MyTypeResolver = (root, args, context) => {
    return {id: '1', foo: '2', bar: 3};
}

export default {
    MyType: myTypeResolvers,
    Query: { myType: myTypeResolver },
};

Now, let’s say that your actual objects looks a bit different, and you are changing your myType resolver to return it:

const myTypeResolver: QueryResolvers.MyTypeResolver = (root, args, context) => {
    return { _id: '1', inner: { foo: '1', bar: 2 }};
}

So declare a TypeScript interface that matches your object:

interface MyTypeCustomStructure {
   _id: string;
   inner: { foo: string; bar: number }; 
}

Next, use it in your Query resolver generics to tell the resolver how your Return object looks like:

const myTypeResolver: QueryResolvers.MyTypeResolver<MyTypeCustomStructure> = (root, args, context) => {
    return { _id: '1', inner: { foo: '1', bar: 2 }};
}

And also, use it in the type resolvers to tell it how it looks:

const myTypeResolvers: MyTypeResolvers.Resolvers<{}, MyTypeCustomStructure> = {
    id: (obj, args, context) => obj._id,
    foo: (obj, args, context) => obj.inner.foo,
    bar: (obj, args, context) => obj.inner.bar,
};

If you wish to avoid specifying the generics each time, you can use the mappers configuration, and this way you can just tell the plugin to always match between MyType and MyTypeCustomStructure:

schema: schema.graphql
generates:
   out.ts:
      config:
           mappers:
                MyType: MyTypeCustomStructure
     plugins:
          - add:
               - "import { MyTypeCustomStructure } from './my-types.ts';"
          - typescript-common
          - typescript-server
          - typescript-resolvers
2reactions
dbuezascommented, Nov 12, 2018

Thanks for the very comprehensive answer!

My point is that the Query resolver won’t return what is expected (in your example returns a string instead), so the typings for that can’t be used directly. Furthermore I wanted to have the return value of that query be automatically in synch with the parent argument of the User resolver.

That is why I extracted the ArgumentsType from QueryResolvers.FeedResolver but let the return type to be inferred, and then extracted that inferred return type and used it as the parent type of the Feed resolver.

// resolvers/Query.ts
export default {
  feed: async (...allArgs: ArgumentTypes<QueryResolvers.FeedResolver>) => {
     const [, { filter, skip, first, orderBy }, context] = allArgs
     ....
// resolvers/Feed.ts
import { FeedResolvers, MyContext } from '../generated/resolvers-types'
import queryResolver from './Query'
export default {
  links: async (parent, _args, context, info) => {
    // typeof parent  is now the right one: Promise<{a: number, b: number}>
  ...
} as FeedResolvers.Resolvers<MyContext, ReturnType<typeof queryResolver.feed>>

(ReturnType is not the “native” one because that one assumes there are no args of type never)

Since that was useful for me, I thought you may have a comment on that approach and if it makes sense to support it directly from the generated code somehow.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Resolvers - Apollo GraphQL Docs
Each resolver function belongs to whichever type its corresponding field ... const resolvers = {. 2. Query: {. 3. user(parent, args, contextValue, info)...
Read more >
Handling the Parent Object in GraphQL Resolvers
We're using the resolver to generate data. As long as the resolver returns the same type that the schema says it should, the...
Read more >
Use the GraphQL Parent Object in a Child Resolver | egghead.io
Data resolved out of a parent resolver can be accessed by a child resolver. This is useful when you need to resolve data...
Read more >
Can a GraphQL resolver force arguments in parent to be ...
The parent value will be the same, regardless of the fields requested, unless the parent field resolver's logic actually returns a different ...
Read more >
How to get the args of the parent resolver in the Apollo Server?
Query Schema: messages(userId: ID!): Blah! type Blah { totalCount: Int! } I'm using the userId in the totalCount resolver.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found