typescriptgraphqltraitsmixinstypegraphql

How to use mixins in TypeGraphQL to replicate "traits" and extend types?


I am trying to replicate the idea of "traits" from PHP into TypeGraphQL types. However, between abstract classes and mixins, I got a bit lost and can't figure out what even to google....

I would like to have a bunch of traits that I can add to my types to reduce the amount of code I write. For example, I'd like a trait that adds an ID and I'd like a trait that adds timestamps.

Here's what I got so far: A basic type definition

@ObjectType()
class Hero {
    @Field(() => String, {description: "Hello world"})
    public name!: string;

    @Field(() => String, {nullable: true})
    public image!: string;
}

export {Hero};

I have added my first abstract class like this:

    @ObjectType({isAbstract: true})
    @InputType({isAbstract: true})
    class IdTrait extends BaseClass {
        @Field(() => ID)
        public id!: number;
    }

Now, if I add to my basic type class Hero extends IdTrait {...}, I get to use all 3 fields (ID, name, and image).

If I want to add

@ObjectType({isAbstract: true})
@InputType({isAbstract: true})
class TimestampsTrait {
    @Field(() => Date)
    public createdAt!: Date;

    @Field(() => Date)
    public editedAt!: Date;
}

export {Timestamps};

I won't be able to do extends IdTrait, TimestampsTrait etc... So I changed my 2 abstract classes to something like this

function withId<TClassType extends ClassType>(BaseClass: TClassType): ClassType {
    @ObjectType({isAbstract: true})
    @InputType({isAbstract: true})
    class IdTrait extends BaseClass {
        @Field(() => ID)
        public id!: number;
    }

    return IdTrait;
}

Similar for the timestamps.

I was hoping I can achieve something like this

class Hero extends withId(withTimestamps()) {
  // ...
}

But it doesn't work that way.

class Hero extends withId(TimestampsTrait) {...}

This semi-worked. I got the timestamps, but not the ID.

What would be a better way to do this? Given the possibility to have 3+ such "traits".


Solution

  • You need a base class for mixins, like HeroDetails:

    @ObjectType({isAbstract: true})
    class HeroDetails {
      @Field()
      nick!: string;
    }
    

    Then you can merge it with mixins:

    class Hero extends withId(withTimestamps(HeroDetails)) {
      // ...
    }