doug-martin / nestjs-query

Easy CRUD for GraphQL.
https://doug-martin.github.io/nestjs-query
MIT License
821 stars 142 forks source link

Nested DTO Relations are not generated into GraphQL SDL #1002

Open conor-odro opened 3 years ago

conor-odro commented 3 years ago

Describe the bug When using any of the Relations decorators on a nested DTO, the corresponding SDL is not generated.

Have you read the Contributing Guidelines?

Yes

To Reproduce Steps to reproduce the behavior:

  1. Create these two DTOs
  2. Add DTOs to a module dtos: [ { DTOClass: RelationTestDTO, CreateDTOClass: CreateRelationTestDTO, UpdateDTOClass: UpdateRelationTestDTO }, ],
  3. Boot API and inspect Playground Docs

Expected behavior I should be able to add any of the Relations decorators onto a nested DTO and have those properties/fields added to the GraphQL Type

Screenshots image

Desktop (please complete the following information):

doug-martin commented 3 years ago

@conor-odro can you share what the module file look like for NestedDTO? I'm guessing something is missing in there.

conor-odro commented 3 years ago

Hey @doug-martin, thanks for getting back to me

Here is a copy of the RelationTest module

import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql';
import { NestjsQueryTypeOrmModule } from '@nestjs-query/query-typeorm';
import { Module } from '@nestjs/common';
import { RelationTestDTO, CreateRelationTestDTO, UpdateRelationTestDTO } from './application/dtos/RelationTest.dto';
import { RelationTestResolver } from './application/RelationTest.resolver';
import { RelationTestAssembler } from './assemblers/RelationTest.assembler';
import { RelationTestService } from './domain/services/RelationTest.service';
import { RelationTestEntity } from './repository/entities/RelationTest.entity';

const NestJsRelationTestModule = NestjsQueryTypeOrmModule.forFeature([RelationTestEntity]);
@Module({
  imports: [
    NestjsQueryGraphQLModule.forFeature({
      imports: [NestJsRelationTestModule],
      services: [RelationTestService],
      resolvers: [],
      assemblers: [RelationTestAssembler],
      dtos: [
        { DTOClass: RelationTestDTO, CreateDTOClass: CreateRelationTestDTO, UpdateDTOClass: UpdateRelationTestDTO },
      ],
    }),
    NestJsRelationTestModule,
  ],
  providers: [RelationTestService, RelationTestResolver],
  exports: [RelationTestService, NestJsRelationTestModule],
})
export class RelationTestModule {}
doug-martin commented 3 years ago

Do you have a resolver for NestedDTO? Internally we rely on the @ResolveField resolver implementation to add the relations example from nest. If you use an auto generated resolver for NestedDTO (you can turn off create, read, update, delete to disable any root queries or mutations if you just want it to be a relation) your relations should start showing up.

I hope this helps, if you have a resolver already defined for NestedDTO if you could share that and the module I can take a look at those also.

conor-odro commented 3 years ago

Ahh ok that makes more sense! I've not got a resolver for NestedDTO, only one for RelationTestDTO.

I've attempted to use an auto-generated resolver by adding the following line to my RelationTestModule but I seem to be getting Error: No fields found to create GraphQLFilter for NestedDTO, even though I've definitely got an @Field decorator on the field property?

resolvers: [{ DTOClass: NestedDTO, EntityClass: NestedEntity }],
doug-martin commented 3 years ago

@conor-odro this is a known issue that I have not gotten around to fixing yet as most DTOs will have a @FilterableField on them. If you just add the @FilterableField instead of @Field it should work as expected.

conor-odro commented 3 years ago

@doug-martin Ahh thank you, that seems to have helped!

After the above change I was getting the error Error: Nest can't resolve dependencies of the NestedDTOAutoResolver (?, pub_sub). Please make sure that the argument NestedEntityQueryService at index [0] is available in the NestjsQueryGraphQLModule context, which I believe is due to the NestedEntity not being added to the NestjsQueryTypeOrmModule.forFeature() call?

I then ended up getting a new error RepositoryNotFoundError: No repository for "NestedEntity" was found., which I believe is simply due to not having the @Entity() decorator on the NestedEntity class. Once these changes were made I started seeing the relations appear in the GraphQL Playground.

However going down this route seems to require the NestedEntity to actually have it's own database table, which is another stumbling block as we were planning on having the NestedEntity actually be a JSONB column in the RelationTest table.

@Injectable()
@Entity('relationTest')
export class RelationTestEntity {
  // Store our NestedEntity data in JSONB
  @Column('jsonb', {
    nullable: false,
    default: {},
  })
  nested: NestedEntity;
}

I believe to achieve this I would have to create a custom resolver for the NestedDTO and implement my own @FieldResolver(s) which were responsible for handling CRUD operations on the JSONB column, is that correct?

doug-martin commented 3 years ago

@conor-odro I think a combination of RelationQueryService and NoOpQueryService could work for this and then you could just set your ServiceClass option

Something like

import { InjectQueryService, QueryService, RelationQueryService, NoOpQueryService } from '@nestjs-query/core';
import { ARelationEntity } from './a-relation.entity';
import { BRelationEntity } from './b-relation.entity';

@Injectable()
export class NestedDTOService extends RelationQueryService<NestedDTO> {
  constructor(
    @InjectQueryService(ARelationEntity) aQueryService: QueryService<ARelationEntity>,
    @InjectQueryService(BRelationEntity) bQueryService: QueryService<BRelationEntity>,
  ) {
    // provide the NoOpQueryService which will throw a NotImplemented for any query, update, create, or delete invocations. 
    super(NoOpQueryService.getInstance(), {
      aRelation: {
        // provide the service that will be used to query the relation
        service: aQueryService,
        query(nestedDTO) {
          // filter for all relations that belong to the todoItem and are completed
          return { filter: { /*create your filter to fetch the right relations*/ } };
        },
      },
      bRelation: {
        // provide the service that will be used to query the relation
        service: bQueryService,
        query(nestedDTO) {
          // filter for all relations that belong to the todoItem and are completed
          return { filter: { /*create your filter to fetch the right relations*/ } };
        },
      },
    });
  }
}
wowczarczyk commented 3 years ago

@conor-odro this is a known issue that I have not gotten around to fixing yet as most DTOs will have a @FilterableField on them. If you just add the @FilterableField instead of @Field it should work as expected.

Hey, I have a similar problem and I can't really change the DTO as it is coming from another package that uses nestjs-graphql and typeorm but not nestjs-query :(. Any plans of making it possible to use such an class in ther dtos array that potentially will have no @FilterableFields defined?

doug-martin commented 3 years ago

@wowczarczyk I'm looking into this now, the main limitation will be aggregates, filtering and sorting will not be available.

I think the only safe endpoints to enable would be createOne, createMany, deleteOne, updateOne, findOne, and query with just paging. In short endpoints that require filters would be disabled.

I haven't run into this use case before so any feedback would be appreciated.

wowczarczyk commented 3 years ago

My use case involves using DTO files from another module (that is not using nestjs-query) to satisfy the relations of DTOs from a module that uses nestjs-query.