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?
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),
});
} ...
}
I finally got a solution to this problem (kinda).
const mapAttributes = (projectors, models) => {
const map = {};
const set = [];
Object.keys(projectors).forEach((projector) => {
if (typeof projectors[projector] === 'object') {
map[projector] = mapAttributes(projectors[projector], models.slice(1));
} else {
set.push(projector);
map[models[0]] = set;
}
});
return map;
};
here projectors
is the object notation of graphql schema, same like the object we get from info.fieldNodes[0].selectionSet.selections
and models, is the orderly way of iteration. so in the example above it'll be like
['company', 'companyOffices', 'users']
etc.
From the final map we get into a neat structure from which we good get the attributes.
And finally when you are returning the argument, you may need to convert the sequelize output into graphql type
const sequelizeToGraphql = (results = [], vouchers = [], postings = []) => {
const final = { particulars: [] };
results.forEach((result) => {
vouchers.forEach((voucher) => {
final[voucher] = result[voucher];
});
const obj = {};
postings.forEach((posting) => {
obj[posting] = result[`posting.${posting}`];
});
final.particulars.push(obj);
});
return final;
};
Here vouchers
and postings
are the table name for me in my sequqlize, similarly make changes for yourself
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.