Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing Apollo GraphQL query into Sequelize query with include and attributes fields

I'm working with Apollo, building resolvers for my GraphQL requests.

To be efficient I want to get a list of models requested (with respective nesting) and the fields requested from each of these models. That way I can pass this information to sequelize to only join the models when required - and only pull the fields necessary.

Resolvers do pass this information on in the info object.

(obj, args, { models }, info) => ...

From the info object the fields, nested models and their respective selected fields are exposed through this path:

info.fieldNodes[0].selectionSet.selections

My problem is parsing this structure (in some sort of recursive manner I imagine) into a sensible structure for me to pass into sequelize queries.

An example GraphQL query:

{
  getCompany(id: 1) {
    id
    name
    companyOffices {
      id
      users {
        id
        title
        userLinks {
          id
          linkUrl
        }
      }
    }
  }
}

Which generates the following on info.fieldNodes[0].selectionSet.selections (pruning some fields for the sake of brevity):

[
   {
      "kind":"Field",
      "name":{
         "kind":"Name",
         "value":"id"
      }
   },
   {
      "kind":"Field",
      "name":{
         "kind":"Name",
         "value":"name"
      }
   },
   {
      "kind":"Field",
      "name":{
         "kind":"Name",
         "value":"companyOffices"
      },
      "selectionSet":{
         "kind":"SelectionSet",
         "selections":[
            {
               "kind":"Field",
               "name":{
                  "kind":"Name",
                  "value":"id"
               }
            },
            {
               "kind":"Field",
               "name":{
                  "kind":"Name",
                  "value":"users"
               },
               "selectionSet":{
                  "kind":"SelectionSet",
                  "selections":[
                     {
                        "kind":"Field",
                        "name":{
                           "kind":"Name",
                           "value":"id"
                        }
                     },
                     {
                        "kind":"Field",
                        "name":{
                           "kind":"Name",
                           "value":"title"
                        }
                     },
                     {
                        "kind":"Field",
                        "name":{
                           "kind":"Name",
                           "value":"userLinks"
                        },
                        "selectionSet":{
                           "kind":"SelectionSet",
                           "selections":[
                              {
                                 "kind":"Field",
                                 "name":{
                                    "kind":"Name",
                                    "value":"id"
                                 }
                              },
                              {
                                 "kind":"Field",
                                 "name":{
                                    "kind":"Name",
                                    "value":"linkUrl"
                                 }
                              }
                           ]
                        }
                     }
                  ]
               }
            }
         ]
      }
   }
]

Using this information I want to generate a query like the following:

  const company = await models.Company.findOne({
    where: { id: args.id },
    attributes: // DYNAMIC BASED ON QUERY
    include: // DYNAMIC BASED ON QUERY
  });

So I need to parse the GraphQL query above down to this something like this structure from the above info object:

{
  attributes: ["id", "name"],
  include: [
    {
      model: "companyOffices",
      attributes: ["id"],
      include: [
        {
          model: users,
          attributes: ["id", "title"],
          include: [{ model: "userLinks", attributes: ["id", "linkUrl"] }]
        }
      ]
    }
  ]
};

But I'm unclear how to achieve this with recursion without things getting messy. If there's an easier way of achieving this dynamic include/attributes I'm open to that too.

tl;dr - how can I transfer the models and fields of an Apollo GraphQL query into the include and attributes of a sequelize query?

like image 976
BML91 Avatar asked May 15 '19 10:05

BML91


1 Answers

It may be skirting the issue but I wonder if something like graphql-sequelize could help with something like this. If not I've used this strategy to accomplish the attributes piece of your question.

const mapAttributes = (model, { fieldNodes }) => {
  // get the fields of the Model (columns of the table)
  const columns = new Set(Object.keys(model.rawAttributes));
  const requested_attributes = fieldNodes[0].selectionSet.selections
    .map(({ name: { value } }) => value);
  // filter the attributes against the columns
  return requested_attributes.filter(attribute => columns.has(attribute));
};
User: async (
    _, // instance (not used in Type resolver)
    { username, ... }, // arguments 
    { models: { User }, ... }, // context
    info,
  ) => {
    if (username) {  
      // (only requested columns queried)
      return User.findOne({
        where: { username },
        attributes: mapAttributes(User, info),
      });
    } ... 
  }
like image 88
Donovan Hiland Avatar answered Oct 14 '22 23:10

Donovan Hiland