MichalLytek / MichalLytek/type-graphql

Enhanced types reflection system

Open
#296 15 comments 92 reactions 1 assignee View on GitHub

@MichalLytek is already working on this.

Since Mar 30, 2019.

Discussion :speech_balloon: Enhancement :new:
Dominant language
TypeScript
Stars
8.1k
Forks
672
PR merge metrics
No merged PRs in 30d

Description

Introduction words

TypeGraphQL was created mainly to reduce the duplication in declaring GraphQL schema types and TypeScript types by synchronizing them in one source of truth using decorators and reflection magic.

Problem description

Probably the most annoying thing in TypeGraphQL is still the types duplication:

@Field(type => [String])  // <- here we have the TypeGraphQL type hint
propertyName: string[];   // <- and here we have the TS type

The even worse thing is that this types can be silently mismatched:

class Test {
  @Field(() => [Author])   // <- wrong item type
  actors: Actor[];

  @Field()                 // <- required in schema
  optionalField?: string;  // <- optional in TS
}

Typed decorators (#221) are unfortunately only a partial solution for providing enhanced type-safety.

The root cause

The main cause of this situation is the limited limited reflection capabilities of TypeScript emitDecoratorMetadata option - it doesn't work with generics (string[], Array<number>, Promise<Recipe>) and it's not even able to detect optional/nullable fields (?: string, : number | null).

In the last few weeks I was working on a TypeScript compiler plugin that can enhance the built-in, limited reflection capabilities. The goal is to remove the need of providing the type => [String] hints by emitting full type info about properties type and methods return type.

Solution for the problem

The Proof of Concept is available in the separate repository:
https://github.com/19majkel94/typegraphql-reflection-poc

For now it can read and emit basic type info - primitives (like boolean, string), nullable types (?: string, : number | null), arrays (string[], Array<number>) and promises (Promise<Recipe>) as well as their combinations like Promise<Array<string | null> | null>.

So just by declaring TypeScript types:

@ObjectType
export class Sample {
  @Field
  dateField!: Date;

  @Field
  optionalStringField?: string;

  @Field
  nullableStringArrayField!: Array<string | null>;

  @Field
  nullableStringNullableArrayPromiseField!: Promise<Array<string | null> | null>;
}

It's possible to read their type info in runtime and generate this GraphQL schema type:

type Sample {
  dateField: Date!
  optionalStringField: String
  nullableStringArrayField: [String]!
  nullableStringNullableArrayPromiseField: [String]
}

The only drawback is that you have to use an alternative compiler, like ttypescript to run the transform plugin and emit enhanced type metadata. But as it integrates well with ts-node (ts-node -C ttypescript src/example/index.ts), I think that it's not so bad solution.

Future work

That proof of concept supports only basic subset of types. There is still a lot of work to be done (i.a. detecting args in methods, inline unions, generic types support) - you can find more info here.

If you love this idea but you can't help with the code by making some contributions, please consider supporting the project and our development efforts! ❤️

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.