graphql-nexus / nexus

Code-First, Type-Safe, GraphQL Schema Construction
https://nexusjs.org
MIT License
3.4k stars 275 forks source link

Allow promises to be returned in a field in the resolver of connection #853

Open shreyas44 opened 3 years ago

shreyas44 commented 3 years ago

The below example would return a type error because the return type of the edges field is expected to be {cursor: string, node: Bar} and Promise<{cursor: string, node: Bar}> is not allowed. However, a query that requests for those edges will resolve without any error.

Why return a Promise in the getter of edges in the first place?

Say, we need to get a list of items from a database and parse that through a utility function that creates a cursor for that record. If we directly await the query to get the list of items, then we lose the ability to batch the requests using something like dataloader, or Prismas Fluent API (which uses the dataloader pattern). Instead, we can return a promise for the edges field that waits for the response from the database, parse that through our utility function to get the cursor, and resolve the promise.

An alternative is to just use the nodes() function, but then implementing a resolver for the entire connection wouldn't be possible in this scenario.

import { ApolloServer } from "apollo-server"
import { connectionPlugin, makeSchema, objectType, queryField } from "nexus"

const bars = [
  {
    cursor: "cursor1",
    node: {
      id: "1",
      barField1: "abcd",
      barField2: "abcdef",
    }
  },
  {
    cursor: "cursor2",
    node: {
      id: "2",
      barField1: "asdf",
      barField2: "asdfasdf",
    }
  }
]

const Foo = objectType({
  name: "Foo",
  definition(t) {
    t.nonNull.id("id")
    t.nonNull.string("fooField1")
    t.nonNull.string("fooField2")

   // --- Error in this part ---
    t.connectionField("bars", {
      type: "Bar",
      resolve() {
        return {
          get edges() {
            return new Promise(resolve => {
              resolve(bars)
            })
          }
        }
      }
    })
  }
})

const Bar = objectType({
  name: "Bar",
  definition(t) {
    t.nonNull.id("id")
    t.nonNull.string("barField1")
    t.nonNull.string("barField2")
  }
})

const fooQuery = queryField("foo", {
  type: "Foo",
  resolve() {
    return {
      id: "12345",
      fooField1: "qwrew",
      fooField2: "14123"
    }
  }
})

const schema = makeSchema({
  types: [Foo, Bar, fooQuery],
  plugins: [connectionPlugin()]
})

I'm sorry for the bad example, let me know if you'd like a better one 🙂

gianluca1606 commented 3 years ago

I am also getting the same Errors on multiple resolvers and I don't understand what I am doing wrong.. but the queries and mutations are working

shreyas44 commented 3 years ago

I am also getting the same Errors on multiple resolvers and I don't understand what I am doing wrong.. but the queries and mutations are working

It depends on the situation. In my case, the property getters return promises. But the generated types don't allow promises to be returned and expect the value itself. If it's the same scenario for you, adding a @ts-ignore as a temporary fix to avoid your IDE screaming at you would probably work for now.

I could probably get into fixing this issue later this week as well.

tgriesser commented 3 years ago

I could probably get into fixing this issue later this week as well.

Yeah, if you want to see if there’s a reasonable fix for this one, that’d be great.

I remember trying to do this genetically at one point early on (making all obj properties T | Promise<T>, recursively) but it made the typings way more complicated and it didn’t seem to be worth the trouble.

Might try and think about this a bit more sometime soon and see if there’s a good middle ground.

GuessWhatBBQ commented 1 year ago

I've been facing the exact issue described in the initial comment. Trying to use the prisma fluent api results is a type error if the relation is one-to-many, meaning an array is returned instead of a single object.

So for example the following works fine with no errors:

export const Link = objectType({
    name: "Link",
    definition(t) {
        t.nonNull.int("id");
        t.field("postedBy", {
            type: "User",
            resolve(parent, args, context) {
                return context.prisma.link
                    .findUnique({ where: { id: parent.id } })
                    .postedBy();
            },
        });
    },
});

This on the other hand won't compile unless I provide the @ts-ignore directive but will execute fine at runtime:

export const User = objectType({
    name: "User",
    definition(t) {
        t.nonNull.int("id");
        t.nonNull.list.nonNull.field("links", {
            type: "Link",
            resolve(parent, args, context) {
                return context.prisma.user
                    .findUnique({ where: { id: parent.id } })
                    .links();
            },
        });
    },
});

If I'm doing something wrong I would really appreciate being pointed in the right direction for a fix