nestjs-query icon indicating copy to clipboard operation
nestjs-query copied to clipboard

Nested DTO Relations are not generated into GraphQL SDL

Open conor-odro opened this issue 3 years ago • 10 comments

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):

  • Node Verson: 14.8.0
  • Nestjs-query Version: 0.23.0
  • Nestjs-query/query-graphql: ^0.23.0

conor-odro avatar Mar 22 '21 16:03 conor-odro

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

doug-martin avatar Mar 22 '21 17:03 doug-martin

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 {}

conor-odro avatar Mar 23 '21 09:03 conor-odro

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.

doug-martin avatar Mar 23 '21 15:03 doug-martin

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 }],

conor-odro avatar Mar 23 '21 16:03 conor-odro

@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.

doug-martin avatar Mar 24 '21 00:03 doug-martin

@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?

conor-odro avatar Mar 24 '21 10:03 conor-odro

@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*/ } };
        },
      },
    });
  }
}

doug-martin avatar Mar 25 '21 16:03 doug-martin

@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?

wowczarczyk avatar Apr 12 '21 16:04 wowczarczyk

@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.

doug-martin avatar Apr 13 '21 05:04 doug-martin

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.

wowczarczyk avatar Apr 13 '21 22:04 wowczarczyk