calebmer / graphql-resolve-batch

A GraphQL batching model which groups execution by GraphQL fields.
MIT License
267 stars 12 forks source link

Case when batch results length is (correctly) less than source length #13

Closed nicolasalliaume closed 4 years ago

nicolasalliaume commented 4 years ago

Hi 👋, I'm new to GraphQL and ended up here after trying to optimize how the resolver fetches embedded documents (I'm using mongodb+mongoose).

Currently I have the following schema:

type Query {
        workEntries: [WorkEntry]
        ...
}

type WorkEntry {
        _id: ID!,
        task: Task!,
        ...
}

type Task {
        _id: ID!,
        project: Project!,
        ...
}

type Project {
        name: String!
        ...
}

There're 1000+ WorkEntry records, and each linked to a Task, which in turns is linked to a Project.

Now, I'm trying to batch the resolution of tasks since I'd like to avoid one mongodb query per WorkEntry to fetch the Task and then another query to fetch the Project.

I tried adding a resolver for WorkEntry as follows:

WorkEntry: {
        task: createBatchResolver( workEntries =>
            Task.find( { 
                _id: { 
                    $in: workEntries.map( ( { task } ) => task ) 
                } 
            } ).exec()
        )
    },

But this is throwing an error saying it must return the same number of values. Now, this would be correct in my case since I have an "N to 1" relationship (multiple WorkEntry documents may be linked to the same task).

Is there any workaround for this? I'm I completely misunderstanding the purpose of this module?

Thanks!

calebmer commented 4 years ago

This is actually the correct behavior! You have a list of inputs that look like ['a', 'b', 'c', 'b'] and GraphQL expects to resolve objects in that order. So what you need to do is:

createBatchResolver(async workEntries => {
  const tasks = await Task.find(...).exec();
  const taskById = new Map(tasks.map(task => [task._id, task]));
  return workEntries.map(workEntry => taskById.get(workEntry.task));
})
nicolasalliaume commented 4 years ago

🎉 That worked like charm, thank you @calebmer!

Note to someone looking at this: I needed to use .toString() on ObjectIds so they're equal when adding and getting to/from the Map.