Search code examples
javascriptgraphqlrelayjsgraphql-js

How to pass video node from resolve/database to node definitions in graphql relay?


My node definitions looks like this:

class Store {}
let store = new Store()

let nodeDefs = nodeDefinitions(
  (globalId) => {
    let type = fromGlobalId(globalId).type
    let id = fromGlobalId(globalId).id
    if (type === 'Store') {
      return store
    }
    if (type === 'Video') {
      return docClient.query(
        Object.assign(
          {},
          {TableName: videosTable},
          {KeyConditionExpression: 'id = :id'},
          {ExpressionAttributeValues: { ':id': id }}
        )
      ).promise().then(dataToConnection)
    }
    return null
  },
  (obj) => {
    if (obj instanceof Store) {
      return storeType
    }
    if (obj instanceof Video) {
      return videoType
    }
    return null
  }
)

The problem is that video node is always null, even when actual video is being returned from the database, because for it to not be null I need to look it up based on id or somehow fetch it from database.

This is the video node I am referring to:

video: {
  type: videoType,
  args: Object.assign(
    {},
    connectionArgs,
    {id: {type: GraphQLString}}
  ),
  resolve: (_, args) => {

    return docClient.query(
      Object.assign(
        {},
        {TableName: pokemonTable},
        {KeyConditionExpression: 'id = :id'},
        {ExpressionAttributeValues: { ':id': args.id }},
        paginationToParams(args)
      )
    ).promise().then(dataToConnection)

  }

},

and

const videoType = new GraphQLObjectType({
  name: 'Video',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLID),
      resolve: (obj) => obj.id
    },
    name: { type: GraphQLString },
    url: { type: GraphQLString }
  }),
  interfaces: [nodeDefs.nodeInterface]
})

const allVideosConnection = connectionDefinitions({
  name: 'Video',
  nodeType: videoType
})

I tried doing database query directly inside node definitions, but that didn't work.

dataToConnection just converts the output of dynamoDB:

video DATA!!  { Items: 
   [ { id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754',
       url: 'http://www.pokkentournament.com/assets/img/characters/char-detail/detail-pikachuLibre.png',
       name: 'YAHOO' } ],
  Count: 1,
  ScannedCount: 1 }

into something that graphql relay understands:

video dataToConnection!!  { edges: 
   [ { cursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
       node: [Object] } ],
  pageInfo: 
   { startCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
     endCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
     hasPreviousPage: false,
     hasNextPage: false } }

and the function itself can be found here: https://github.com/dowjones/graphql-dynamodb-connections/pull/3/files

It could be the problem.

Also, asking/querying for id makes the whole video object null:

enter image description here

But omitting id from the query returns something, whether querying with relay id:

enter image description here

or database id

enter image description here

and querying for all of the videos works:

enter image description here

The interesting part is that I get exactly same problem even if I delete the video part from node definitions:

let nodeDefs = nodeDefinitions(
  (globalId) => {
    let type = fromGlobalId(globalId).type
    let id = fromGlobalId(globalId).id
    if (type === 'Store') {
      return store
    }
    return null
  },
  (obj) => {
    if (obj instanceof Store) {
      return storeType
    }
    return null
  }
)

Any ideas?

UPDATE:

I did some digging and found that interfaces in fact is undefined

const storeType = new GraphQLObjectType({
  name: 'Store',
  fields: () => ({
    id: globalIdField('Store'),

    allVideosConnection: {
      type: allVideosConnection.connectionType,
      args: Object.assign(
        {},
        connectionArgs
      ),
      resolve: (_, args) => {

        return docClient.scan(
          Object.assign(
            {},
            {TableName: pokemonTable},
            paginationToParams(args)
          )
        ).promise().then(dataToConnection)
      }
    },

    video: {
      type: videoType,
      args: Object.assign(
        {},
        connectionArgs,
        {id: {type: GraphQLString}}
      ),
      resolve: (_, args) => {

        return docClient.query(
          Object.assign(
            {},
            {TableName: pokemonTable},
            {KeyConditionExpression: 'id = :id'},
            {ExpressionAttributeValues: { ':id': args.id }},
            paginationToParams(args)
          )
        ).promise().then(dataToConnection)

      }

    }

  }),

  interfaces: [nodeDefs.nodeInterface]

})





console.dir(storeType.interfaces, { depth: null })

prints undefined

Why? I clearly define them at the top!

Also, I can do that:

enter image description here

But this doesn't work:

enter image description here

This is what is being returned in video: {} resolve:

{ edges: 
   [ { cursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
       node: 
        { id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754',
          url: 'http://www.pokkentournament.com/assets/img/characters/char-detail/detail-pikachuLibre.png',
          name: 'YAHOO' } } ],
  pageInfo: 
   { startCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
     endCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
     hasPreviousPage: false,
     hasNextPage: false } }

Somehow that's okay for allVideosConnection, but not okay (ends up null) for video

Do I need to convert ids of nodes to global IDs? using toGlobalId ? Just for video ?

Because another thing I noticed is that if I

console.log('fromGlobalId', fromGlobalId(globalId))

inside my node definitions, this query:

{
  node(id: "f4623d92-3b48-4e1a-bfcc-01ff3c8cf754") {
    id
    ...F1
  }
}

fragment F1 on Video {
  url
  name
}

becomes this:

fromGlobalId { type: '', id: '\u000e6]_v{vxsn\u001eU/\u001b}G>SW_]O\u001c>x' }

However, if I do

enter image description here

I get

globalId  U3RvcmU6
fromGlobalId { type: 'Store', id: '' }

Solution

  • So to make node definitions work, all I had to do was this:

    class Video {}
    let video = new Video()
    return Object.assign(video, data.Items[0])
    

    i.e. create class with the same name as type name and then Object.assign to it

    Just doing this, doesn't work:

    return {Video: data.Items[0]}
    

    I also need to create IDs in the database like that: Video:f4623d92-3b48-4e1a-bfcc-01ff3c8cf754, where I am essentially putting type and randomly generated unique id together separated by a colon (:) and then encode it with toGlobalId function of graphql-relay-js library (so I end up with VmlkZW86ZjQ2MjNkOTItM2I0OC00ZTFhLWJmY2MtMDFmZjNjOGNmNzU0Og==), so then I can decode it with fromGlobalId so that node definitions can retrieve both type and id({ type: 'Video', id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754:' }), after which I still need to add fromGlobalId(globalId).id.replace(/\:$/, '')) to remove the trailing colon (:). `

    Also, interfaces are not meant to be accessible, they are just for configuration.