Right now we can only extend other object type, input type and args type classes. But this doesn't allow us to overwrite a property type so we can't create a truly generic types like the one used for pagination.
So, to the higher order class pattern we have to introduce isAbstract option that works the same way as in resolvers inheritance. Then we can create generic types:
export function Edge<TNodeType>(NodeType: ClassType<TNodeType>) {
@ObjectType({ isAbstract: true })
abstract class EdgeType {
@Field(type => NodeType)
node: TNodeType;
@Field()
cursor: string;
}
return EdgeType;
}
We can then easily extend the generic class:
@ObjectType()
export class RecipeEdge extends Edge(Recipe) {
@Field()
personalNotes: string;
}
@ObjectType()
export class FriendshipEdge extends Edge(User) {
@Field()
friendedAt: Date;
}
So this would generate following GraphQL type:
type RecipeEdge {
node: Recipe!
cursor: String!
personalNotes: String!
}
type FriendshipEdge {
node: User!
cursor: String!
friendedAt: DateTime!
}
Available in 0.17.0-beta.8 馃殌
Docs for next version:
https://19majkel94.github.io/type-graphql/docs/next/generic-types.html
Amazing news ;) thanks @19majkel94
@19majkel94 According to your example:
@ObjectType({ name: `Paginated${TItemClass.name}Response` })
This way you propose to generate unique names for generic types. But what if I specified another name for TItemClass when had decorated it with @ObjectType('SomeOtherName')? I would like to set the name of the generated class without having to repeat that 'SomeOtherName' as a factory function parameter. Could you please expose a method to retrieve GraphQL name from the given class constructor that was decorated with @ObjectType() ?
@Veetaha
I'm not sure what you are referring to 馃槙
You can have a function that generates a classes (no isAbstract, use name for unique type names):
function Foo(TItem) {
@ObjectType({ name: "Foo" + TItem.name })
class Foo {
@Field()
bar: string;
}
return Foo;
}
const FooBar = Foo(Bar);
or as a factory for base classes (isAbstract: true, no name):
function Foo(TItem) {
@ObjectType({ isAbstract: true })
abstract class Foo {
@Field()
bar: string;
}
return Foo;
}
@ObjectType()
class FooBar extends Foo(Bar) {}
Could you describe your use case in more details, instead of trying to propose the solution for the unknown problem?
@19majkel94 Sorry if I was not verbose. The version of typegraphql that I use (namely 0.16.0) exposes ObjectType() function with the following overloads:
function ObjectType(): ClassDecorator;
function ObjectType(options: ObjectOptions): ClassDecorator;
function ObjectType(name: string, options?: ObjectOptions): ClassDecorator;
Thus, if you decorate a class the following way:
@ObjectType()
class UserType { /* ... */ }
the generated GraphQL type name will be type UserType { }.
If you want to leave your TypeScript class name being UserType but have generated GraphQL type to be called User you forward a string with the name as the first parameter to ObjectType() that overrides the default UserType.name that is used when you don't specify the name explicitly.
@ObjectType('User')
class UserType { /* ... */ }
Now the generated GraphQL type name is type User { } rather than type UserType { }.
And when I have the following factory function (taken from your examples):
export default function PaginatedResponse<TItem>(TItemClass: ClassType<TItem>) {
@ObjectType(`Paginated${TItemClass.name}Response`)
class PaginatedResponseClass {
}
return PaginatedResponseClass;
}
I can't be sure that TItemClass.name === respective graphql type name.
Suppose we have the following class:
@ObjectType('Book')
class BookType { }
So when I do this:
const PaginatedBookResponse = PaginatedResponse(BookType);
The generated graphql type will be type PaginatedBookTypeResponse {} rather than wanted type PaginatedBookResponse {}.
Thus I would like a function that returns a respective GraphQL type name for a given type, so that I could correct PaginatedResponse() factory definition:
import { getMetadata } from 'type-graphql';
export default function PaginatedResponse<TItem>(TItemClass: ClassType<TItem>) {
// should throw if TItemClass was not decorated with either @ObjectType() or @InputType()
const graphqlTypeName = getMetadata(TItemClass).name;
@ObjectType(`Paginated${graphqlTypeName}Response`)
class PaginatedResponseClass {
}
return PaginatedResponseClass;
}
P.S. I don't know why you invoke @ObjectType() with the object that has name property, as my version of 'typegraphql' declares ObjectOptions with no such property, and I pass the name as the bare first argument.
I think that getMetadata() function should return MetadataStorage or some abstraction over this type as you referenced it here
@Veetaha
Your factory function may take more arguments, like:
function PaginatedResponse<TItem>(TItemClass: ClassType<TItem>, itemName: string) {}
const PaginatedBookResponse = PaginatedResponse(BookType, "Book");
The TItemClass.name is just a shortcut for the default naming.
Accessing metadata is part of the #134.
@19majkel94 As I have said in my first comment,
I would like to set the name of the generated class without having to repeat that
'SomeOtherName'as a factory function parameter
Exposing some interface to access the metadata you define would be way better than reimplementing the wheel. I could wrap ObjectType() with my custom decorator that would store the GraphQL type name along with saving it to your MetadataStorage, so that I could access it myself without referring to your API. Or I could just forward its name as the factory function parameter and repeat myself twice, which was the thing I wanted to avoid initially.
Most helpful comment
Available in
0.17.0-beta.8馃殌Docs for
nextversion:https://19majkel94.github.io/type-graphql/docs/next/generic-types.html