type-graphql icon indicating copy to clipboard operation
type-graphql copied to clipboard

Support for __typename

Open MichalLytek opened this issue 5 years ago • 5 comments

Right now, for interfaces and unions, TypeGraphQL determines the type of the returned value using foo instanceof Foo. This force to use class instances in every case where interfaces or unions are returned.

But this might be problematic in some cases, so it could just fallback to the __typename based detection for the easier cooperation with plain objects.

Side note

Also resolveType implementation in unions could be omitted as:

Optionally provide a custom type resolver function. If one is not provided, the default implementation will call isTypeOf on each implementing Object type.

Or maybe even better, for better compatibility, it should allow providing own resolveType function in interface and union decorators/creators.

MichalLytek avatar Oct 22 '18 20:10 MichalLytek

First of all, thanks @19majkel94 for adding this to your to-do list, it's a much-needed feature for our team. :)

Just chiming in to vocalise my support for both of the options you suggested: being able to set the __typename field to inform TypeGraphQL of the concrete type of an interface/union would be sufficient for our needs, but your second suggestion of allowing us to provide a resolveType function to the decorators/creators directly would be absolutely amazing!

luketanner avatar Mar 01 '19 02:03 luketanner

@19majkel94 Until this new feature arrives: how would I provide the type in the current version?

From your initial comment I would assume, that TypeGraphQL can do this somehow magically by itself, but I get the error Abstract type MobileNumberCPS must resolve to an Object type at runtime for field... in my implementation. I tried to provide __resolveType and __isTypeOf function that return the appropriate class name as string, but they don't get called.

This force to use class instances in every case where interfaces or unions are returned.

I don't understand this. Are you referring to the inner workings of TypeGraphQL or does my implementation need to provide a property or something in the class?

What do I need to do, that foo instanceof Foo returns the correct and expected value? Would be very grateful for any hint!

maplesteve avatar Mar 21 '19 00:03 maplesteve

@maplesteve

// where interfaces or unions are returned.
@Query(returns => MyInterface)
myInterface() {
  // use class instances 
  const response = new MyObjectType();
  response.sampleField = "sampleFieldValue";
  return response;
}

MichalLytek avatar Mar 21 '19 09:03 MichalLytek

@19majkel94 any estimation of implementation?

I tried to use your alternative without success

import { Field, ID, ObjectType } from 'type-graphql';

@ObjectType()
export class ServiceProvider {
    @Field(() => ID)
    id!: string;

    @Field()
    name!: string;

    @Field()
    address!: string;
}

@ObjectType()
export class ServiceProviderA extends ServiceProvider {}

@ObjectType()
export class ServiceProviderB extends ServiceProvider {}

Here is my resolver (using nestjs):

@Resolver(ServiceProvider)
export class ServiceProviderResolvers {
    constructor(private readonly searchService: SearchService) {}

    @Query(() => [ServiceProvider])
    async all(): Promise<(ServiceProviderA | ServiceProviderB)[]> {
        const serviceProviders = await this.searchService.all();
        const result = serviceProviders.map((serviceProvider) => {
            if (serviceProvider.type === 'A') {
                return Object.assign(new ServiceProviderA(), serviceProvider);
            }
            return Object.assign(new ServiceProviderB(), serviceProvider);
        });
        return result;
    }

    // Before using type-graphql with nestjs this was working:
    // @ResolveProperty('__resolveType')
    // resolveType(obj: ServiceProvider): string {
    //     return obj.type;
    // }
}

I still get ServiceProvider as __typename instead of ServiceProviderA or ServiceProviderB.

Actually what would be super cool is to be able to do:

@ObjectType()
export class ServiceProvider {
    @Field(() => ID)
    id!: string;

    @Field()
    name!: string;

    @Field()
    address!: string;

    @TypeName() // this decorator would say that it has to use this value as __typename
    type!: string;
}

apiel avatar Mar 26 '19 11:03 apiel

@TypeName() // this decorator would say that it has to use this value as __typename

That's a great idea, you can reuse the property data instead of forcing to use spread operator and __typename property of a new object.

Here is my resolver (using nestjs):

Nest has it's own runtime for resolving types and other things - TypeGraphQL is used only for SDL generation and nothing else.

MichalLytek avatar Mar 26 '19 15:03 MichalLytek