TriPSs/nestjs-query

Extra CreateDTOClass fields are available in the afterInsert event if using an AutoResolver but not if using a custom CRUDResolver

RyanLackie opened this issue · 3 comments

Describe the bug
When triggering an afterInsert event within a typeorm EventSubscriber, any fields that are part of the CreateDTOClass and not part of the DTOClass, are available when the used resolver is an AutoResolver but not when the resolver is a custom CRUDResolver.

Thank you for taking a look. Please let me know if you have any questions.

To Reproduce
https://github.com/RyanLackie/nestjs-query-issue-example

Related Example Files:

ExampleEntity:

@Entity()
export class ExampleEntity {
    @PrimaryGeneratedColumn('uuid')
    id!: string;

    @Column()
    someField: string;
}

ExampleDTO:

@ObjectType('Example')
export class ExampleDTO {
    @IDField(() => ID)
    id: string;

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

CreateExampleDTO:

@InputType()
export class CreateExampleDTO {
    @Field()
    someField: string;

    @Field()
    someExtraField: string;
}

EventSubscriber

@EventSubscriber()
export class ExampleSubscriber<ExampleEntity>
    implements EntitySubscriberInterface<ExampleEntity>
{
    constructor(dataSource: DataSource) {
        dataSource.subscribers.push(this);
    }

    listenTo() {
        return ExampleEntity;
    }

    async afterInsert(event: InsertEvent<ExampleEntity>) {
        console.log('event.entity: ', event.entity);
    }
}
  1. Set the module definition to match:
@Module({
    imports: [
        NestjsQueryGraphQLModule.forFeature({
            imports: [NestjsQueryTypeOrmModule.forFeature([ExampleEntity])],
            resolvers: [
                {
                    DTOClass: ExampleDTO,
                    CreateDTOClass: CreateExampleDTO,
                    EntityClass: ExampleEntity,
                },
            ],
        }),
    ],
    providers: [ExampleSubscriber],
})
  1. Preform a createOneExample mutation with a someExtraField value
  2. The EventSubscriber should log an entity with the same field someExtraField and value of what was sent in the mutation

vs

  1. Set the module and resolver to be:
@Module({
    imports: [
        NestjsQueryGraphQLModule.forFeature({
            imports: [NestjsQueryTypeOrmModule.forFeature([ExampleEntity])],
            dtos: [
                {
                    DTOClass: ExampleDTO,
                    CreateDTOClass: CreateExampleDTO,
                },
            ],
        }),
    ],
    providers: [ExampleResolver, ExampleSubscriber],
})
export class ExampleModule {}
@Resolver(() => ExampleDTO)
export class ExampleResolver extends CRUDResolver(ExampleDTO, {
    CreateDTOClass: CreateExampleDTO,
}) {
    constructor(
        @InjectQueryService(ExampleEntity)
        readonly service: QueryService<ExampleEntity>,
    ) {
        super(service);
    }
}
  1. Preform a createOneExample mutation with a someExtraField value
  2. The EventSubscriber should now no longer log an entity with the same field someExtraField and value of what was sent in the mutation

Expected behavior
I would expect both functionalities to expose the same data to the typeorm subscriber functions.

Desktop (please complete the following information):

  • Node Version: v18.14.1
  • Nestjs-query Versions:
"@ptc-org/nestjs-query-core": "3.0.0",
"@ptc-org/nestjs-query-graphql": "3.0.0",
"@ptc-org/nestjs-query-typeorm": "3.0.0",
  • Typeorm Version: 0.3.17
TriPSs commented

If you add the field someExtraField to the entity without the @Column does it than work?

Hey, thanks for getting back so fast.

If someExtraField is added to the entity without the @Column decorator, the entity printed in the EventSubscriber has the field but it is undefined and doesn't have the value provided in the CreateExampleDTO.

Here is a minimum repo with code to reproduce what I found.

Edit: I updated the classes in the description to match what's in the linked repo.

TriPSs commented

Sorry for the late reply, completely missed this!

The issue is very weird as when you extend CRUDResolver it will be the same as defining resolvers inside your module. I think the root cause is the code below (which is executed before creating the record).

  private async ensureIsEntityAndDoesNotExist(entity: DeepPartial<Entity>): Promise<Entity> {
    if (!(entity instanceof this.EntityClass)) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      return this.ensureEntityDoesNotExist(this.repo.create(entity))
    }
    return this.ensureEntityDoesNotExist(entity)
  }

I cannot seem to find out why in one case the CreateDTOClass extends it's entity and not when you define a resolver your self.

An easy fix in this case is to make sure that your CreateDTOClass extends your entity, in case of your example:

export class CreateExampleDTO extends ExampleEntity {

I also do something like this in my own API's, there I extend the main DTO with for example PickType / OmitType from NestJS, the main DTO in it's turn than extends the entity it belongs to.

I'm going to keep this bug open so when I have a bit more time I can debug it a bit more.