join-monster: Cannot read property 'sqlTable' of undefined

Join-monster fails with

(node:3555) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 11): TypeError: Cannot read property 'sqlTable' of undefined

OR

{
	"errors": [
		{
			"message": "Cannot read property 'sqlTable' of undefined",
			"locations": [
				{
					"line": 2,
					"column": 2
				}
			],
			"path": [
				"orders"
			]
		}
	],
	"data": {
		"orders": null
	}
}

Here is my code

const Order =  new graphql.GraphQLObjectType({
    name    : "Order",
    sqlTable: `order`,
    uniqueKey: "id",
    fields  : () => ({
        order_id    : {
            type: graphql.GraphQLString,
        },
    }),
});

module.exports = new graphql.GraphQLSchema({
    query : new graphql.GraphQLObjectType({
        name    : "Query",
        fields  : () => ({
            orders: {
                type    : Order,
                resolve :(obj, args, context, resolveInfo) => {
                    return join_monster(resolveInfo, context, sql => {
                        return pg.query(sql);
                    });
                },
            },
        }),
    }),
});

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 5
  • Comments: 20 (6 by maintainers)

Most upvoted comments

@zhorzhz Can you please re-open this.

I looked into the graphql code for the new version and they no longer expose type._typeConfig after the type is constructed. Here’s a temporary work-around until the join-monster developers can deal with this in a better way.

Originally your code would have looked something like this:

const User = new gql.GraphQLObjectType({
  name: 'User',
  sqlTable: 'user',
  uniqueKey: 'id'
  fields: () => ({
    id: {
      type: gql.GraphQLInt
    }
  })
})

The fix is to now only define the graphql specific properties in the type constructor, then provide the join-monster properties afterward like so:

const User = new gql.GraphQLObjectType({
  name: 'User',
  fields: () => ({
    id: {
      type: gql.GraphQLInt
    }
  })
})

User._typeConfig = {
  sqlTable: 'user',
  uniqueKey: 'id'
}

@s97712 yeah, that is why I used the original object.

Another way how your solution should work would be to name the class the same (untested):

import { GraphQLObjectType as OriginalGraphQLObjectType } from 'graphql';
export class GraphQLObjectType extends OriginalGraphQLObjectType {
  // ...
}

I personally prefer duck typing over constructor name comparison, but it is clearly more efficient to just check the constructor name. Maybe the maintainers are open for a PR to change this (for more extensibility). Let us know in case you read this :bowtie:

@GlennMatthys GraphQL added the ability to pass extra metadata via extensions!

https://github.com/graphql/graphql-js/pull/2097

This feature might be useful also for join-monster: https://github.com/graphql/graphql-js/issues/1527

We just released support for graphql@15 in 3.0.0-alpha.1. Let us know if it works for you! Note the new object format:

https://join-monster.readthedocs.io/en/latest/map-to-table/

const User = new GraphQLObjectType({
  name: 'User',
  extensions: {
    joinMonster: {
      sqlTable: 'accounts', // the SQL table for this object type is called "accounts"
      uniqueKey: 'id' // id is different for every row
    }
  },
  fields: () => ({
    /*...*/
  })
})

Is anyone working on a PR to upgrade join-monster to use the new extensions property and get us all the new GraphQL features? I am not super familiar with the codebase but I can take a crack at it if no one else has started! I think it would have to result in a major version bump too because almost the whole public facing API of join-monster would have to change right?

Here’s a function which encapsulates the fix proposed by @naturalethic, so less changes have to be made:

function newJoinMonsterGraphQLObjectType(objectTypeConfig) {
  const joinMonsterConfig = {}
  const joinMonsterKeys = ['sqlTable', 'uniqueKey', 'alwaysFetch']
  joinMonsterKeys.forEach(key => {
    if (key in objectTypeConfig) {
      joinMonsterConfig[key] = objectTypeConfig[key]
    }
  })

  const GraphQLObject = new GraphQLObjectType(objectTypeConfig)
  GraphQLObject._typeConfig = joinMonsterConfig

  return GraphQLObject
}

You can use it like this:

Before (graphql version < 14)

const User = new gql.GraphQLObjectType({
  name: 'User',
  sqlTable: 'users',
  uniqueKey: 'id',
  fields: () => ({
    ...
  })
})

After (graphql v14)

const User = newJoinMonsterGraphQLObjectType({
  name: 'User',
  sqlTable: 'users',
  uniqueKey: 'id',
  fields: () => ({
    ...
  })
})

Check out the Typescript version if you prefer that.

I also had to patch objects not directly tied to a sql table, as join monster would check them anyway and run in a ReadOfUndefined-Error, assuming the gqlType._typeConfig is always defined.

Reading the issue over at graphql-js I see no other way forward than proposed: adding an extensions property to each GraphQL object. Although it will incur API breakage when implemented, it would be safer for graphql-js that foreign properties can be added in a controlled way. So basically the only thing we can do, aside from @naturalethic’s workaround, is to bug the graphql-js team to implement the extensions property.

This is quality info, thanks for the leg work and concise summary @naturalethic

As mentioned here:

https://github.com/acarl005/join-monster/issues/351

This is due incompatibility with graphql 14.

Just install the older version:

yarn add graphql@0.13.2