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

[FR] Default naming strategy

Open stevefan1999-personal opened this issue 5 years ago • 1 comments

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

stevefan1999-personal avatar Sep 23 '18 15:09 stevefan1999-personal

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,
});

MichalLytek avatar Sep 23 '18 16:09 MichalLytek