MichalLytek / type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
https://typegraphql.com
MIT License
8.04k stars 678 forks source link

[FR] Default naming strategy #156

Open stevefan1999-personal opened 6 years ago

stevefan1999-personal commented 6 years ago

Is your feature request related to a problem? Please describe. I'm currently frustrated at doing this repetitively:

...
  @Field(() => Boolean, { name: 'have_children' })
  get haveChildren (): Promise<boolean> {
    return (async () => ((await this.children) || []).length > 0)()
  }

  @Field(() => Boolean, { name: 'is_children' })
  get isChildren (): Promise<boolean> {
    return (async () => (await this.parent) !== undefined)()
  }

  @Field(() => Boolean, { name: 'is_root' })
  get isRoot (): Promise<boolean> {
    return (async () => (await this.parent) === undefined)()
  }
...

And this:

...
  @Query(() => Submission, {
    nullable: true,
    name: 'submission_feed'
  })
  async feed (@Arg('id') id: number): Promise<Submission> {
    return this.submissions.findOneOrFail({ id }).fail('No such submission')
  }

  @Authorized() @Mutation(() => Submission, { name: 'submission_submit' })
  async submit (
    @Args() { url, description }: SubmitArgs,
    @Ctx('user') submitter: User
  ): Promise<Submission> {
    // secret code...
  }
...

So we're basically using camelCase in server code but we also decided to use snake_case for the name convention of our GraphQL schema so that we could get a better separation of namespaces (because GraphQL doesn't support nested queries yet, we had to resort back to oldschool C-style OOP namings)

Describe the solution you'd like Let there be a default naming strategy transformer in the method buildSchema that is undefined by default. It will be a/an (a)synchronous function with arguments (name: string, type: 'field' | 'fieldResolver' | 'resolver', target?: Function) => string, then after the basic schema skeleton is built, we do a ~DFS~ simple lookup of metadata storage data on that, then we will convert the name into various cases, for example, by using blakeembrey/change-case. If a name property is already denoted on the decorator metadata, name transformation will be ignored.

Alternative solution It seems like we could also do this externally ourselves by traversing the schema ourselves, although it will be harder to determine contexts and types and such due to loss of metadata.

It could also be manipulated before schema creation by rewriting all fields in the metadata storage

MichalLytek commented 6 years ago

Thanks for sharing your idea! 😃

I like it, it's implementable, so I've added it to the features list 😉

because GraphQL doesn't support nested queries yet

What do you mean? It might be not semantically correct but you can implement queries as a field resolvers:

query NamespaceExamples {
  submission {
    feed {
      isRoot
      haveChildren
    }
  }
}

Although it won't work with mutations correctly (no guaranteed sequential execution) 😞

I would also extend your proposal to namespaces:

@Resolver({ namespace: "submission" })
class SubmissionResolver {
  @Query(() => Submission)
  async feed (@Arg('id') id: number): Promise<Submission> {
    // secret code...
  }

  @Authorized()
  @Mutation(() => Submission)
  async submit (
    @Args() { url, description }: SubmitArgs,
    @Ctx('user') submitter: User
  ): Promise<Submission> {
    // secret code...
  }
}
type Query {
  submission_feed(id: Float!): Submission
}
type Mutation {
  submission_submit(url: URL!, description: String!): Submission
} 
await buildSchema({
  resolvers: [SubmissionResolver],
  namespaceJoin: "PascalCase" | "camelCase" | "snake_case" | NamespaceJoinFn,
});