Graphql-code-generator: Can't return `null` from nullable filed resolver

Created on 27 Dec 2019  路  12Comments  路  Source: dotansimha/graphql-code-generator

To Reproduce
Steps to reproduce the behavior:

  1. My GraphQL schema:
type Query {
  optional: Result
}
type Result {
  mandatory: String!
}
  1. My GraphQL operations:
const resolvers: Resolvers = {
  Query: {
    optional: () => Promise.resolve("x").then(r => (r ? {} : null))
  }
};

Expected behavior
null is a valid result type, but I get:

src/resolvers.ts(4,3): error TS2322: Type '{ optional: () => Promise<Result | Promise<Result> | {} | null>; }' is not assignable to type 'QueryResolvers<MyContext, {}>'.
      Types of property 'optional' are incompatible.
        Type '() => Promise<Result | Promise<Result> | {} | null>' is not assignable to type 'ResolverFn<Maybe<ResolverTypeWrapper<Result>>, {}, MyContext, {}> | StitchingResolver<Maybe<ResolverTypeWrapper<Result>>, {}, MyContext, {}> | undefined'.
          Type '() => Promise<Result | Promise<Result> | {} | null>' is not assignable to type 'ResolverFn<Maybe<ResolverTypeWrapper<Result>>, {}, MyContext, {}>'.
            Type 'Promise<Result | Promise<Result> | {} | null>' is not assignable to type 'Result | Promise<Result> | Promise<Maybe<ResolverTypeWrapper<Result>>> | null'.
              Type 'Promise<Result | Promise<Result> | {} | null>' is not assignable to type 'Promise<Result>'.
                Type 'Result | Promise<Result> | {} | null' is not assignable to type 'Result'.
                  Type 'null' is not assignable to type 'Result'.

Environment:

  • OS: Linux
  • @graphql-codegen/...: 1.9.1
  • NodeJS: 12
enhancement plugins

Most helpful comment

@wallzero Adding my solution as the Maybe value fixes this issue.

    plugins:
      - typescript
      - typescript-resolvers
    config:
      ....
      maybeValue: 'T extends PromiseLike<infer U> ? Promise<U | null> : T | null'

All 12 comments

@kamilkisiela can you please take a look?

To be fair, I don't know if it a valid issue here. The produced error comes from TS compiler. In encountered it on a pretty big schema with complex types (tens of fields) and it wasn't easy to spot the real error.
I wonder if there is a place for improvement for generated types, to make compile errors more direct, like it was before introducing ResolverTypeWrapper.

@kamilchm I'm closing for now. If you have an idea for improvement feel free to share with us :)

@dotansimha @kamilchm This issue is related to the way Maybe wraps the ResolverType, instead of the ResolverTypeWapper wrapping the Maybe:

Example:

Schema:

type Query {
  getDraft(id: ID!): Draft,
}

Generated code

export type Maybe<T> = T | null;

export type ResolversTypes = {
  Draft: ResolverTypeWrapper<Draft>,
  ...
}
export type QueryResolvers<...> = {
  getDraft?: Resolver<Maybe<ResolversTypes['Draft']>, ParentType, ContextType, RequireFields<QueryGetDraftArgs, 'id'>>
}

This causes Maybe to wrap Promise<Draft> | Draft and wrongly make the type Promise<Draft> | Draft | null

I think the generated code should instead be

export type ResolversTypes = {
  Draft: ResolverTypeWrapper<Maybe<Draft>>,
  ...
}
export type QueryResolvers<...> = {
  getDraft?: Resolver<ResolversTypes['Draft']>, ParentType, ContextType, RequireFields<QueryGetDraftArgs, 'id'>>
}

This would correctly resolve getDraft to be Promise<Draft | null> | Draft | null

This obviously doesn't work here. As it would mean Draft would always be a maybe, but you can see the real issue in the types generated here right? This has nothing to do with the typescript compiler but the way Maybe is used.

For us right now, the only solution is to resolve the promise before returning the value, but it's a workaround which doesn't seem like it needs to exist.

Another solution to this could be to change Maybe to better extend Promise types:

export type Maybe<T> = T extends PromiseLike<infer U> ? Promise<U | null> : T | null;

@dotansimha I think this should be left open. Promises should be able to return null in case of issue.

@freshollie could you share how you are resolving the promise as a work around? I am trying to figure out a solution while keeping type safety.

@wallzero Adding my solution as the Maybe value fixes this issue.

    plugins:
      - typescript
      - typescript-resolvers
    config:
      ....
      maybeValue: 'T extends PromiseLike<infer U> ? Promise<U | null> : T | null'

maybeValue: 'T extends PromiseLike ? Promise : T | null'

So this didn't fix the issue I was having, but it did make it obvious where the issue was which I was unable to decipher before that.
After I fixed the issue I was able to remove it again.

Hmm, I'm also running into this issue.

It's not super clear how to resolve this. (maybeValue: 'T extends PromiseLike<infer U> ? Promise<U | null> : T | null' did not work for me, I still get the same stack trace)

@magicmark @lecstor if it's still relevant, can please open a new issue?

@dotansimha Sorry to be pain - but just run into this as well. Its clearly still issue - why this can't be reopened since it has all the details it needs?

@jardakotesovec There are several workarounds for this, for me, in most cases, just using the default configuration with async function works and I can safely return nulls. Do you have a reproduction for this?

Was this page helpful?
0 / 5 - 0 ratings