devoxa / prisma-relay-cursor-connection

Extend Prisma's `findMany` method to support Relay Cursor Connections.
263 stars 19 forks source link

CustomEdge generates typescript error #848

Closed ghost closed 2 weeks ago

ghost commented 8 months ago

I use prisma-relay-cursor-connection with my NestJS backend, I created a function

async findCategories(): Promise<StoryCategoryConnection> {
    return await findManyCursorConnection(
      (x) =>
        this.prisma.storyCategory.findMany({
          ...x,
          include: {
            stories: { take: 1, include: { media: true }, orderBy: { updatedAt: 'desc' } },
          },
          orderBy: { updatedAt: 'desc' },
        }),
      () => this.prisma.storyCategory.count(),
      null,
      {
        recordToEdge: ({ stories, ...record }) => ({ node: { ...record, thumbnail: stories[0]?.media } }),
      },
    );
  }

The problem is I get an error in recordToEdge function where I add thumbnail that is a media of the last updated relation story. The error says

Object literal may only specify known properties, and 'thumbnail' does not exist in type '{ stories: ({ media: { id: string; createdAt: Date; updatedAt: Date; name: string; mimeType: string; url: string; frontId: string; backId: string; proofOfAddressId: string; proofOfFundsId: string; storyId: string; }; } & { ...; })[]; } & { ...; }'.ts(2353)
interfaces.d.ts(28, 5): The expected type comes from property 'node' which is declared here on type 'Omit<Edge<{ stories: ({ media: { id: string; createdAt: Date; updatedAt: Date; name: string; mimeType: string; url: string; frontId: string; backId: string; proofOfAddressId: string; proofOfFundsId: string; storyId: string; }; } & { ...; })[]; } & { ...; }>, "cursor">'

Error disappears when I return the list of stories in recordToEdge function, but I don't want to do it, I can also fix that passing <any, StoryCategory> as an TS argument to findManyCursorConnection but I don't want to use any and Prisma doesn't generate relations as types in e.g. StoryCategory type from prisma schema so I can't pass PrismaStoryCategory as argument instead of any because generic function doesn't see relation and I don't want to create more types than I must

It would be lovely if I could pass type I want as a first argument in findManyCursorConnection or pass it anywhere it would be possible to strictly type my edge type

btw. this problem is weird because I also did it in another function

return await findManyCursorConnection(
      (x) =>
        this.prisma.game.findMany({
          ...x,
          where,
          orderBy: { [field]: direction },
          include: { gamesOnCountries: { include: { country: true } }, category: true },
        }),
      () => this.prisma.game.count({ where }),
      { after, first, before, last },
      {
        recordToEdge: (record) => ({
          node: { ...record, countries: record.gamesOnCountries.map(({ country }) => country) },
        }),
      },
    );

And I don't return gamesOnCountries relation and typescript doesn't scream about it's not present in returned object

Another thing I spotted is that when I use recordToEdge function, type returned from findManyCursorConnection doesn't modify type returned as node, it's still a type returned from prisma query

queicherius commented 8 months ago

I'm not sure why TypeScript does not infer the CustomEdge correctly, but here is some pointers:

ghost commented 8 months ago

I'm not sure why TypeScript does not infer the CustomEdge correctly, but here is some pointers:

  1. Ok, {} was my first approach but null was for testing if it changes anything in typing, it doesn't
  2. I already found it out, but it would be lovely to type the CustomEdge first, I just tested the "inverse" generic typing and it works, I mean
    const fn = <K extends Object<T>, T extends { id: string }>(cb: () => T[], { cb }: { cb: () => K ) => {}

maybe this typing would be more "developer friendly"?