graphql-compose / graphql-compose-mongoose

Mongoose model converter to GraphQL types with resolvers for graphql-compose https://github.com/nodkz/graphql-compose
MIT License
708 stars 94 forks source link

Internal error occurred during message handling. Please check your implementation. Error: Subscription field must return Async Iterable. Received: undefined. #405

Open alexpchin opened 2 years ago

alexpchin commented 2 years ago

When creating a subscription resolver as follows:

const pubsub = require('@app/lib/pubSub')

module.exports = {
  name: 'findManySubscription',
  kind: 'subscription',
  type: 'Chat',
  resolve: async payload => {
    return payload.updatePost
  },
  subscribe: async () => pubsub.asyncIterator('updatePost')
}

and adding it to:

schemaComposer.Subscription.addFields({
  chatMany: ChatTC.getResolver('findManySubscription')
})

I see the error:

Internal error occurred during message handling. Please check your implementation. Error: Subscription field must return Async Iterable. Received: undefined.

However, when adding as so:

schemaComposer.Subscription.addFields({
  chatMany: {
    type: 'Chat',
    resolve: payload => {
      return payload.updatePost
    },
    subscribe: () => pubsub.asyncIterator('updatePost')
  }
})

This is fine.

For resolvers, I am adding as per:

for (const resolver in resolvers) {
  ChatTC.addResolver(resolvers[resolver])
}

Any ideas why this wouldn't be ok?

Resousse commented 2 years ago

Very funny, that I started to resolve the same on my own, 3 days ago! My requirement was to leverage an existing resolver to resolve subscription. As per the document I tried resolve: (_id) => Task.findById(_id) Task is my mongoose model object, however, it's different of the existing mongooseResolvers.findById which I've wrapResolve to extend the working. So using directly the model instead of the compose query.

Finally, my result is :

import { getProjectionFromAST, deepmerge } from 'graphql-compose';

const TaskSubscription = {
  taskAdded: {
    type: TaskTC,
    resolve: (source, args, context, info)  =>{     
       let projection = getProjectionFromAST(info);
      return TaskQuery.taskById.resolve({ source, args:{_id : source._id.toString()}, context, info, projection })},
    subscribe: () => pubSub.asyncIterator(['TASK_ADDED']),
  }
};

const TaskMutation = {
    taskCreateOne: TaskTC.mongooseResolvers.createOne().wrapResolve(next => async rp =>  {
      const res = await next(rp);
      const _id = res?.record?._id;
      if (_id)
        pubSub.publish('TASK_ADDED', _id);
      return res;
    })};

The tricky part was:

resolve: (source, args, context, info)  =>{     
       let projection = getProjectionFromAST(info);
      return TaskQuery.taskById.resolve({ source, args:{_id : source._id.toString()}, context, info, projection })}

The second line is important to retrieve projection based of what has been sent during the Subscription {} phase. The third line is used to exec the resolver using parameters.

I will try to propose a PR to improve documentation about this. Let me know if it solves your problem?