prisma-labs / graphqlgen

⚙️ Generate type-safe resolvers based upon your GraphQL Schema
MIT License
818 stars 54 forks source link

unblock DelegatedParentResolver #428

Closed jasonkuhrt closed 5 years ago

jasonkuhrt commented 5 years ago

Currently graphqlgen does not make it possible to use a DelegatedParentResolver. I think we should remove this blocker. We can start with basic support which should be almost trivial. A completely type-safe approach, while nice, would be much more complicated.

What is a DelegatedParentResolver

The resolver signature that most apollo developers are most familiar with:

(parent, args, ctx, info) => model

However there is another resolver signature:

{
  fragment: "..."
  resolver: (parent, args, ctx, info) => model
}

It wasn't as easy as I would hope to find good documentation about this. Some references:

I have not found a good name for this kind of resolver. Since it supports resolving fields that depend on the result of a delegated parent field hence I've referred to them as a DelegatedParentResolver here.

Proposed Types

An example of a current graphqlgen resolver type:

type Dimensions {
  width: Int!
  height: Int!
}
export namespace DimensionsResolvers {
  export const defaultResolvers = {
    width: (parent: Dimensions) => parent.width,
    height: (parent: Dimensions) => parent.height
  };

  export type WidthResolver = (
    parent: Dimensions,
    args: {},
    ctx: Context,
    info: GraphQLResolveInfo
  ) => number | Promise<number>;

  export type HeightResolver = (
    parent: Dimensions,
    args: {},
    ctx: Context,
    info: GraphQLResolveInfo
  ) => number | Promise<number>;

  export interface Type {
    width: (
      parent: Dimensions,
      args: {},
      ctx: Context,
      info: GraphQLResolveInfo
    ) => number | Promise<number>;

    height: (
      parent: Dimensions,
      args: {},
      ctx: Context,
      info: GraphQLResolveInfo
    ) => number | Promise<number>;
  }
}

snapshot link

A generic take on what the new resolver type could be:

type ResolverFunction<Parent, Args, Context, Info, Model> = (
  (parent: Parent, args: Args, context: Context, info: Info) => Model | Promise<Model>
)

type Resolver<Parent, Args, Context, Info, Model> =
  | ResolverFunction<Parent, Args, Context, Info, Model>
  | { fragment: string, ResolverFunction<Parent, Args, Context, Info, Model>

One thing to think about is the impact on IDE type documentation, e.g.:

image

However, I think even the current resolver type doesn't render very well in e.g. VSCode anyway, and that problem [quality of type doc hints/rendering] is something for vscode etc. to shoulder obviously.

Users will have to, and probably already do, rely on the either interacting over inner parts of their resolver, reacting to type errors, and "peeking"/code-jumping into definitions.

Parameters

(actually that type rendering is arguably unhelpful in its own right)

image

Return

Return type is a bit trickier but can be had by hovering over the arrow:

image

Example New TS

type ResolverFunction<Parent, Args, Context, Info, Model> = (
  (parent: Parent, args: Args, context: Context, info: Info) => Model | Promise<Model>
)

type Resolver<Parent, Args, Context, Info, Model> =
  | ResolverFunction<Parent, Args, Context, Info, Model>
  | { fragment: string, ResolverFunction<Parent, Args, Context, Info, Model>
export namespace DimensionsResolvers {
  export type WidthResolver = Resolver<Dimensions, {}, Context, GraphQLResolveInfo, number>
  export type HeightResolver = Resolver<Dimensions, {}, Context, GraphQLResolveInfo, number>

  export interface Type {
    width: WidthResolver
    height: HeightResolver
  }

  export const defaultResolvers = {
    width: (parent: Dimensions) => parent.width,
    height: (parent: Dimensions) => parent.height
  };
}