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

Returning Relay-connection style responses from custom resolvers #247

Open
rinormaloku opened this issue Apr 9, 2024 · 5 comments
Open
Labels
enhancement New feature or request

Comments

@rinormaloku
Copy link

How to convert the result of the database into a relay-connection style response? This is a workaround that I am currently using:

@Query(() => ActivitiesViewConnection)
  async activitiesViews(
    @Args() query: ActivitiesViewQuery,
  ): Promise<ConnectionType<ActivitiesView>> {
    
    // custom and complex querying logic
    let qb = this.filterQueryBuilder.select(query);
    const [result, total] = await qb.getManyAndCount();

    return ActivitiesViewConnection.createFromPromise(
      (q) => Promise.resolve(result),
      query,
      (q) => Promise.resolve(total),
    );
  }

I would prefer a cleaner way of mapping the result into a relay-connection style response

@rinormaloku rinormaloku added the enhancement New feature or request label Apr 9, 2024
@TriPSs
Copy link
Owner

TriPSs commented Apr 10, 2024

This looks about right, usually what I tend to do is something like this:

    return ActivitiesViewConnection.createFromPromise(
      (q) => this.customQueryService.customQuery(q),
      query,
      (q) => this.customQueryService.customCount(q),
    );

That way not all logic goes into the resolver but to it's query service instead.

@rinormaloku
Copy link
Author

Thanks @TriPSs, that's good to know. However, there is a need for a utility function to create relay-connection style responses as the one used by nestjs-query

Digging deeper I noticed that resolvers with UnionTypes are not supported, e.g.:

@ObjectType()
export class Book {
  @Field()
  title: string;
}

@ObjectType()
export class Author {
  @Field()
  name: string;
}

export const ResultUnion = createUnionType({
  name: 'ResultUnion',
  types: () => [Author, Book] as const,
});

export const InfoQueryArgs = QueryArgsType(ResultUnion)
export const InfoConnection = InfoQueryArgs.ConnectionType;

You cannot create Query Args Type from the Union Type. This error provides more detail:

Argument of type 'Author | Book' is not assignable to parameter of type 'Class<unknown>'.
  Type 'Author' is not assignable to type 'Class<unknown>'.
    Type 'Author' provides no match for the signature 'new (...args: any[]): unknown'.ts(2345)

It makes sense for this not to work (QueryArgsType function expects DTOs), I just want to point out that there are no options to handle this case and still maintain the same API with the other resolvers?

Additionally, I looked into defining my types for PageInfo, Connection, Edges, and so on, but the issue is that it will clash with the predefined types by nestjs-query, and the internal implementations are not exported.

btw, if this is currently not supported, but you can give me some helpful pointers I can contribute this functionality.

@TriPSs
Copy link
Owner

TriPSs commented Apr 15, 2024

Interesting, I think a union type could be supported but then you would need to overwrite a lot of the service etc.

Additionally, I looked into defining my types for PageInfo, Connection, Edges, and so on, but the issue is that it will clash with the predefined types by nestjs-query, and the internal implementations are not exported.

We could checkout what you need exactly and just export them so it will make it easier for ppl to reuse, PR would awesome!

@rinormaloku
Copy link
Author

rinormaloku commented Apr 15, 2024

Btw, maybe this is a noob question (this might give you some perspective on how to answer it xD)

Let's start with a fresh plate and put aside the union types.

Using nestjs-query how can we define the following types:

@ObjectType()
class AuthorEdge {
    @Field(type => Author)
    node: Author;

    @Field()
    cursor: string;
}

@ObjectType()
class AuthorConnection {
    @Field(type => PageInfo)  ## ATTENTION
    pageInfo: PageInfo;       ## ATTENTION

    @Field(type => [AuthorEdge])
    edges: AuthorEdge[];
}

The issue is that we don't have access to the PageInfo type (or do we?).

Adding the types manually will fail because they are already defined by nestjs-query, i.e. this is not possible:

@ObjectType()
class PageInfo {
    @Field()
    hasNextPage: boolean;

    @Field()
    hasPreviousPage: boolean;

    @Field({ nullable: true })
    startCursor: string;

    @Field({ nullable: true })
    endCursor: string;
}

So what options do we have for a custom nestjs resolver that doesn't use nestjs-query to return the same response types (Utilizing Connections, Edges, and Nodes)?

Why do we need this?
This is for those cases which you are not going to use Nestjs-Query and will create a resolver directly with Nestjs. You might do that for various reasons, for e.g. a feature that's not supported by nestjs-query such as InterfaceTypes, UnionTypes, or because for a specific resolver you don't need the nestjs-query features and you opt to go for vanilla nestjs defined resolvers.

However you still want to expose a uniform API. i.e. pagination should be identical for resolvers defined by nestjs-query and by your custom resolvers in nestjs.

Let me know if I can clarify this, and as soon as you give me some direction, I can get started on contributing.

@TriPSs
Copy link
Owner

TriPSs commented Apr 16, 2024

If I understand it correctly, you could just follow Custom Endpoints from the docs? There is explained on how to create the return type with page info etc, you are not required to use the .createFromPromise; how you eventually return that response is up to you as long as it matches the connection.

To answer alteast one of your questions: we indeed to not export PageInfoType, these are created with getOrCreatePageInfoType for example, not sure why we do that as it's just a normal class which we probably could just export and use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants