Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type inference issues when organizing resolvers in separate files #83

Closed
lorefnon opened this issue Dec 17, 2023 · 2 comments
Closed

Type inference issues when organizing resolvers in separate files #83

lorefnon opened this issue Dec 17, 2023 · 2 comments

Comments

@lorefnon
Copy link
Contributor

lorefnon commented Dec 17, 2023

Hello, thanks for maintaining garph as an open source project. This is perhaps related to #78 but I am creating as a new issue because it does seem like there is an issue with types somewhere.

My goal is to restructure a project that uses garph such that resolvers are grouped into separate modules. This has proven to be surprisingly difficult because I keep running into type errors that defy intuition.

I have a minimal reproducer here that illustrates the problem.

The api-schema.ts has the type definitons for graphql. And the resolvers are located here.

The crux is that I have a Resolvers type that I infer from the schema definiton:

export type Resolvers = InferResolvers<{ Query: typeof QueryT }, { context: YogaInitialContext }>

and then use its members when defining specific resolvers, for example:

export const locations: Resolvers["Query"]["locations"] = async (_parent, { page }) => {
  return {
    locations: [],
    page: {
      current: page.num,
      size: page.size
    }
  }
}

The above type checks without issues.

However when I try to compose these resolvers into a Query/Mutation type, I get obscure type errors that are hard to fathom:

export const Query: Resolvers["Query"] = {
  locations,
}
❯ pnpm run ts:check

> [email protected] ts:check /home/lorefnon/Workspace/garph-reproducer-1702804756397
> tsc --noEmit

server/resolvers/query.ts:4:14 - error TS2322: Type '{ locations: ((parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number;
 }; }, context: YogaInitialContext, info: GraphQLResolveInfo) => any) | { ...; } | { ...; } | { ...; } | undefined; }' is not assignable to type '{ locations?:
((parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: GraphQLResolveInfo) => a
ny) | undefined; } | ({ ...; } & { ...; })'.
  Type '{ locations: ((parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: Gra
phQLResolveInfo) => any) | { ...; } | { ...; } | { ...; } | undefined; }' is not assignable to type '{ locations?: { resolve: (parent: {}, args: { page: { __typ
ename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: GraphQLResolveInfo) => any; } | { ...; } | { ...; } | undef
ined; } & { ...; }'.
    Type '{ locations: ((parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: G
raphQLResolveInfo) => any) | { ...; } | { ...; } | { ...; } | undefined; }' is not assignable to type '{ locations?: { resolve: (parent: {}, args: { page: { __t
ypename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: GraphQLResolveInfo) => any; } | { ...; } | { ...; } | und
efined; }'.
      Types of property 'locations' are incompatible.
        Type '((parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: GraphQLRes
olveInfo) => any) | { ...; } | { ...; } | { ...; } | undefined' is not assignable to type '{ resolve: (parent: {}, args: { page: { __typename?: "PageInput" | un
defined; num: number; size: number; }; }, context: YogaInitialContext, info: GraphQLResolveInfo) => any; } | { ...; } | { ...; } | undefined'.
          Type '(parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number; }; }, context: YogaInitialContext, info: GraphQLRe
solveInfo) => any' is not assignable to type '{ resolve: (parent: {}, args: { page: { __typename?: "PageInput" | undefined; num: number; size: number; }; }, con
text: YogaInitialContext, info: GraphQLResolveInfo) => any; } | { ...; } | { ...; } | undefined'.

4 export const Query: Resolvers["Query"] = {
               ~~~~~


Found 1 error in server/resolvers/query.ts:4

 ELIFECYCLE  Command failed with exit code 2.

It is not clear why an object of type { locations: Resolvers["Query"]["locations"] } is not assignable to Resolvers["Query"] when Query doesn't have any other fields.

However, if I inline the locations resolver within the Query resolver here, it typechecks fine.

@mishushakov
Copy link
Member

Do you still have the issue? Why was it closed?

@lorefnon
Copy link
Contributor Author

lorefnon commented Dec 25, 2023

Hi @mishushakov - I sorted it out - was an error at my end.

While logging the issue, I hadn't realized that Resolvers["Query"] was a union of multiple types. So Resolvers["Query"]["locations"] would end up being union of the type of that field from different members and if I wrap that into an object it would no longer be assignable to Resolvers["Query"].

So the pattern I adopted was always using Resolvers["SomeType"] when splitting, which works great.

// One file:

const QueryLocationsResolvers: Resolvers["Query"] = { locations: (...) => { ... } }

// Another file 
const QueryPricesResolvers: Resolvers["Query"] = { prices: (...) => { ... } }

// Finally while merging: 
const QueryResolvers: Resolvers["Query"] = {
    ...QueryLocationsResolvers,
    ...QueryPricesResolvers,
}

export const schema = buildSchema({
  g,
  resolvers: {
    Query: QueryResolvers,
    // ...
  },
})

Thanks for taking a look though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants