Graphql-js: TypeScript GraphQLObjectType and TArgs

Created on 3 Sep 2019  路  7Comments  路  Source: graphql/graphql-js

The TypeScript definitions currently specify a TArgs generic type on GraphQLObjectType which then flows into GraphQLObjectTypeConfig, GraphQLFieldConfigMap and GraphQLFieldMap.

All field resolver arguments on an object therefore have to have the same base type.

This, to me, is unexpected as each resolve function can have it's own differing requirements as to what arguments and it makes it awkward to use types arguments as you need to specify something like any when creating the GraphQLObjectType instance. For example:

interface MySource { /* ... */ }
interface MyContext { /* ... */ }

interface MyFieldArgs {
    foo?: number;
    bar: string;
}

interface MyOtherFieldArgs {
    baz: string;
}

const MyType = new GraphQLObjectType<MySource, MyContext, any>({
    fields: {
        myField: {
            args: {
                bar: { type: new GraphQLNonNull(GraphQLString) },
                foo: { type: GraphQLInt },
            },
            resolve: (source, args: MyFieldArgs, context, info) => { /* ... */ },
            type: GraphQLString,
        },
        myOtherField: {
            args: {
                baz: { type: GraphQLString },
            },
            resolve: (source, args: MyOtherFieldArgs, context, info) => { /* ... */ },
            type: GraphQLString,
        },        
    },
    name: "My",
});

I suppose you could stick with the default TArgs type and then use a typescript user-defined type guard. However this is even more awkward and seems unnecessary given that we are specifying the arguments in the GraphQLFieldConfig and can be sure that graphql has already validated and asserted that the arguments match what we expect when it comes to calling the resolve function.

function isMyFieldArgs(o: any): o is MyFieldArgs {
    /* check everything ... */
    return true;
}

{
    // ...
    resolve: (source, args, context, info) => {
        if (!isMyFieldArgs(args)) {
            return undefined;
        }
        /* ... */
    },
}
needs exploration

Most helpful comment

To update this issue, it's my understanding that v15 (which is about to have a release candidate) will largely retain the existing types imported from DefinitelyTyped, and will thus include this flaw. In the following months, this codebase is to be rewritten in TypeScript (see #2104) which will hopefully include a complete redesign of the types (see #2188) that will fix this while also inferring resolver types based on GraphQL types.

All 7 comments

@williambailey Thanks for the detailed report 馃憤
We don't plan to change TS typings (except bug fixes) in 14.x.x release line.
But we are working on full TS conversion as part of 15.x.x (ETA end of this month).

+1

To update this issue, it's my understanding that v15 (which is about to have a release candidate) will largely retain the existing types imported from DefinitelyTyped, and will thus include this flaw. In the following months, this codebase is to be rewritten in TypeScript (see #2104) which will hopefully include a complete redesign of the types (see #2188) that will fix this while also inferring resolver types based on GraphQL types.

This specific issue was solved in #2488 so this issue can probably be closed.

Having just gone through the migration to v15 this is still an issue and arguebly worse since #2488.

Now we have GraphQLObjectType<TSource = any, TContext = any> so we can't specify TArgs as any at the top level and have it propagate to the children which default to TArgs = { [key: string]: any }.

This then leads to the situation where your typescript failes to build when you try to have infered and typed TArgs in your resolvers with errors such as:

Type '{ [argName: string]: any; }' is missing the following properties from type 'MyImageRequestArgs': height, width ts(2322)
definition.d.ts(447, 3): The expected type comes from property 'fields' which is declared here on type 'Readonly<GraphQLObjectTypeConfig<MySourceObject, MyContextValue>>'

MyImageRequestArgs in this instance would look something like

interface MyImageRequestArgs {
    [argName: string]: any;
    height: number;
    width: number;
}

Our workaround at the moment is to patch the GraphQLFieldConfig definition with something like

--- a/node_modules/graphql/type/definition.d.ts
+++ b/node_modules/graphql/type/definition.d.ts
@@ -511,7 +511,7 @@
 export interface GraphQLFieldConfig<
   TSource,
   TContext,
-  TArgs = { [argName: string]: any }
+  TArgs = any
 > {
   description?: Maybe<string>;
   type: GraphQLOutputType;

I will continue to see if there is something else going on that i'm missing but thought i'd give a quick update to the ticket.

Looks like someone else aready tried to fix it same way, https://github.com/graphql/graphql-js/pull/2518 just linking the PR

Was this page helpful?
0 / 5 - 0 ratings