Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript error for async resolvers that can return null #55

Closed
ChristianBoehlke opened this issue Feb 22, 2019 · 13 comments
Closed

TypeScript error for async resolvers that can return null #55

ChristianBoehlke opened this issue Feb 22, 2019 · 13 comments
Labels
scope/types Having to do with types type/bug Something is not working the way it should

Comments

@ChristianBoehlke
Copy link

I am getting a TypeScript error when using async resolvers that can also return null. In the following example I could just remove the async but I would like to use await inside the resolver. Is there anything I am missing?

const Query = objectType({
  name: 'Query',
  definition(t) {
    t.int('getNumberOrNull', {
      nullable: true,
      args: { a: intArg({ required: true }) },
      async resolve(_, { a }) {
        if (a > 0) {
          return a;
        }
        return null;
      },
    });
  },
});

The error is:

Type '(_: {}, { a }: { a: number; }) => Promise<number | null>' is not assignable to type 'FieldResolver<"Query", "getNumberOrNull">'.
  Type 'Promise<number | null>' is not assignable to type 'number | PromiseLike<null> | PromiseLike<number> | null'.
    Type 'Promise<number | null>' is not assignable to type 'PromiseLike<number>'.
      Types of property 'then' are incompatible.
        Type '<TResult1 = number | null, TResult2 = never>(onfulfilled?: ((value: number | null) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<...>' is not assignable to type '<TResult1 = number, TResult2 = never>(onfulfilled?: ((value: number) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => PromiseLike<...>'.
          Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
            Types of parameters 'value' and 'value' are incompatible.
              Type 'number | null' is not assignable to type 'number'.
                Type 'null' is not assignable to type 'number'.

However, returning promises by themselves inside the resolver without an async does work:

resolve(_, { a }) {
  if (a > 0) {
    return Promise.resolve(a);
  }
  return Promise.resolve(null);
}

The async keyword changes the return type from Promise<null> | Promise<number> to Promise<null | number> which seems to be the problem?

Is this related to #28? The author also used a nullable async function and was wondering why he has to set the return type of the resolver to Promise<any> (which does the trick by ignoring the problem).

@tgriesser tgriesser added the scope/types Having to do with types label Feb 22, 2019
tgriesser added a commit that referenced this issue Feb 22, 2019
@tgriesser
Copy link
Member

Nope, it's related to #50, pushing a fix shortly

@tgriesser
Copy link
Member

Released in 0.9.15

tgriesser added a commit that referenced this issue Feb 22, 2019
* develop:
  Update examples
  v0.9.15
  Update changelog
  Fix #55 (#56)
@tgriesser tgriesser added the type/bug Something is not working the way it should label Feb 22, 2019
@ChristianBoehlke
Copy link
Author

Hey @tgriesser, thank you for the very fast response!

Unfortunately, I am still getting the TypeScript error in my project with the latest version. When I constructed the failing example, I just picked a number because it seemed to break it. I will dig a bit deeper and see what type I use that makes it fail.

@ChristianBoehlke
Copy link
Author

Hmm, maybe I am missing something or I misunderstood how backing types are meant to work. Here is a failing example:

import { join } from 'path';
import { objectType, makeSchema, intArg } from 'nexus';

export class BackingType {
  public someNumber = 5;
}

const User = objectType({
  name: 'User',
  definition(t) {
    t.int('someNumber');
  },
});

const Query = objectType({
  name: 'Query',
  definition(t) {
    t.field('user', {
      type: 'User',
      nullable: true,
      args: { a: intArg({ required: true }) },
      async resolve(_, { a }) {
        if (a > 0) {
          return new BackingType();
        }
        return null;
      },
    });
  },
});

makeSchema({
  types: [User, Query],
  outputs: {
    schema: join(__dirname, 'schema.graphql'),
    typegen: join(__dirname, 'graphql_types.ts'),
  },
  typegenAutoConfig: {
    sources: [
      {
        alias: 'models',
        source: __filename,
        typeMatch: () => new RegExp(`(BackingType)`),
      },
    ],
    debug: true,
  },
});

The BackingType class here would be my existing User model which I would like to use for the User type in GraphQL. Removing the async from the resolve function fixes the problem again but then await does not work.

And here is the error:

Type '(_: {}, { a }: { a: number; }) => Promise<BackingType | null>' is not assignable to type 'FieldResolver<"Query", "user">'.
  Type 'Promise<BackingType | null>' is not assignable to type 'PromiseLike<null> | { someNumber: MaybePromise<number>; } | PromiseLike<{ someNumber: MaybePromise<number>; }> | null'.
    Type 'Promise<BackingType | null>' is not assignable to type 'PromiseLike<{ someNumber: MaybePromise<number>; }>'.
      Types of property 'then' are incompatible.
        Type '<TResult1 = BackingType | null, TResult2 = never>(onfulfilled?: ((value: BackingType | null) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>' is not assignable to type '<TResult1 = { someNumber: MaybePromise<number>; }, TResult2 = never>(onfulfilled?: ((value: { someNumber: MaybePromise<number>; }) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => PromiseLike<TResult1 | TResult2>'.
          Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
            Types of parameters 'value' and 'value' are incompatible.
              Type 'BackingType | null' is not assignable to type '{ someNumber: MaybePromise<number>; }'.
                Type 'null' is not assignable to type '{ someNumber: MaybePromise<number>; }'.

@tgriesser tgriesser reopened this Feb 23, 2019
@tgriesser
Copy link
Member

Ah thanks for the example - I don't think you're misunderstanding how the backing types should work, I believe the issue is #50, need to clean up/add better typings for MaybePromiseDeep.

Just pushed a quick fix in 0.9.17 to see if that fixes the issue you're having currently, otherwise will look to address this more thoroughly soon.

tgriesser added a commit that referenced this issue Feb 23, 2019
* develop:
  Add a field-level authorize property (#32)
  v0.9.17
  More for #55, type error on Promise/null resolve
  v0.9.16
  Update changelog
  Fix #52, incorrect Symbol type for arg definition (#57)
  Add extendInputType (#54)
  Update examples
  v0.9.15
  Update changelog
  Fix #55 (#56)
  fix missing comma in code example (#49)
  Fix docs for shouldGenerateArtifacts default (#47)
@ChristianBoehlke
Copy link
Author

Thank you a lot! This works very well for us now.

@janicduplessis
Copy link

janicduplessis commented Mar 15, 2019

@tgriesser I'm still seeing this issue using the latest version when using async functions + nullable + backing type. I tried adding | Promise<ResultValue<TypeName, FieldName>> to the FieldResolver return type and it seems to work fine after that.

@ChristianBoehlke
Copy link
Author

We are also seeing the error again on the latest version. We are still running v0.11.2 without issues.

@tgriesser tgriesser reopened this Mar 20, 2019
@tgriesser
Copy link
Member

Ok - going to work on a better testing strategy for these sorts of things using https://github.com/Microsoft/dtslint will keep you posted

@navseth
Copy link

navseth commented Aug 13, 2019

This issue seems to be occurring in 0.12.0-beta.6 as well. Different from the original issue in the sense that I am not returning null. My queryType looks like the following. Is this resolved and if so what am I doing wrong?

const getData = queryType({
  definition(t) {
    t.field("getData", {
      type: Data,
      list: true,
      resolve: async (root, args, { apiClient }) => {
        const response = await apiClient.getData();

        return [
          {
            id: "1",
          }
        ];
      }
    });
  }
});

@bilalqureshi
Copy link

bilalqureshi commented Sep 10, 2019

The issue seems to be a persistent issue, we are using v0.11.6
and this is how the resolver looks


resolve: createBatchResolver(async instances =>
        models.Instance.findAll({
          where: {
            id: { $in: map(get('id'))(instances) },
          },
          include: [
            { model: models.Task, as: 'tasks', },
          ],
        }).then(result =>
          instances
            .map(instance => find(x => x.id === instance.id, result))
            .map(getOr(null, 'tasks'))
          )
      ),
Type 'ResolverFunction<{ assigneeId?: number | null | undefined; assistantId: number; benchmark?: number | null | undefined; clientId?: number | null | undefined; complexityRank?: number | null | undefined; ... 16 more ...; totalTimeTracked: any; }, any, any, ITask[] | null>' is not assignable to type 'FieldResolver<"Instance", "tasks">'.
  Type 'Promise<ITask[] | null>' is not assignable to type '{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; }[] | PromiseLike<null> | PromiseLike<{ assigneeId?: string | null | undefined; body?: string | ... 1 more ... | undefined; deadline?: any; id: number; name: string; }[] | null> | MaybePromise<...>[]...'.
    Type 'Promise<ITask[] | null>' is not assignable to type 'PromiseLike<{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; }[] | MaybePromise<{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; } | { ...; }>[]>'.
      Types of property 'then' are incompatible.
        Type '<TResult1 = ITask[] | null, TResult2 = never>(onfulfilled?: ((value: ITask[] | null) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<...>' is not assignable to type '<TResult1 = { assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; }[] | MaybePromise<{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; } | { ...; }>[], TResult2 = never>(onfulfilled?: (...'.
          Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
            Types of parameters 'value' and 'value' are incompatible.
              Type 'ITask[] | null' is not assignable to type '{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; }[] | MaybePromise<{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; } | { ...; }>[]'.
                Type 'null' is not assignable to type '{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; }[] | MaybePromise<{ assigneeId?: string | null | undefined; body?: string | null | undefined; deadline?: any; id: number; name: string; } | { ...; }>[]'.ts(2322)

@Sytten
Copy link
Collaborator

Sytten commented Aug 2, 2020

Related issue: #470

@jasonkuhrt
Copy link
Contributor

jasonkuhrt commented Aug 7, 2020

Should be resolved by #475

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope/types Having to do with types type/bug Something is not working the way it should
Projects
None yet
Development

No branches or pull requests

7 participants