mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 02:44:55 +00:00
Refactoring loaders and put logic in a different file
This commit is contained in:
parent
402d1fb4b6
commit
0074ca6304
110
packages/strapi-plugin-graphql/services/Loaders.js
Normal file
110
packages/strapi-plugin-graphql/services/Loaders.js
Normal file
@ -0,0 +1,110 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Loaders.js service
|
||||
*
|
||||
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
const DataLoader = require('dataloader');
|
||||
|
||||
module.exports = {
|
||||
loaders: {},
|
||||
|
||||
createLoader: function(model) {
|
||||
this.loaders[model] = new DataLoader(keys => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// Extract queries from keys and merge similar queries.
|
||||
const { queries, map } = this.extractQueries(model, _.cloneDeep(keys));
|
||||
// Run queries in parallel.
|
||||
const results = await Promise.all(queries.map((query) => this.makeQuery(model, query)));
|
||||
// Use to match initial queries order.
|
||||
resolve(this.mapData(model, keys, map, results));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
cacheKeyFn: (key) => {
|
||||
return _.isObjectLike(key) ? JSON.stringify(_.cloneDeep(key)) : key;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
mapData: function(model, originalMap, map, results) {
|
||||
// Use map to re-dispatch data correctly based on initial keys.
|
||||
return originalMap.map((query, index) => {
|
||||
// Find the index of where we should extract the results.
|
||||
const indexResults = map.findIndex(queryMap => queryMap.indexOf(index) !== -1);
|
||||
const data = results[indexResults];
|
||||
|
||||
// Retrieving referring model.
|
||||
const ref = this.retrieveModel(model, query.options.source);
|
||||
// Extracting ids from original request to map with query results.
|
||||
const ids = query.options.query[ref.primaryKey];
|
||||
|
||||
return ids.map(id =>
|
||||
data.find(entry => (entry._id || entry.id || '').toString() === id.toString())
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
makeQuery: async function(model, query = {}) {
|
||||
// Retrieve refering model.
|
||||
const ref = this.retrieveModel(model, _.get(query.options, 'source'));
|
||||
// Run query and remove duplicated ID.
|
||||
const request = await strapi.plugins['content-manager'].services['contentmanager'].fetchAll({ model }, {
|
||||
...query.options,
|
||||
query: {
|
||||
[ref.primaryKey]: _.uniq(query.ids.map(x => x.toString()))
|
||||
},
|
||||
populate: []
|
||||
});
|
||||
|
||||
return request && request.toJSON ? request.toJSON() : request;
|
||||
},
|
||||
|
||||
retrieveModel: function(model, source) {
|
||||
// Retrieve refering model.
|
||||
return source ?
|
||||
strapi.plugins[source].models[model]:
|
||||
strapi.models[model];
|
||||
},
|
||||
|
||||
extractQueries: function(model, keys) {
|
||||
const queries = [];
|
||||
const map = [];
|
||||
|
||||
keys.forEach((current, index) => {
|
||||
// Extract query options.
|
||||
const { query, ...options } = current.options;
|
||||
// Retrieving referring model.
|
||||
const ref = this.retrieveModel(model, options.source);
|
||||
|
||||
// Find similar query.
|
||||
const indexQueries = queries.findIndex(query => _.isEqual(query.options, options));
|
||||
|
||||
if (indexQueries !== -1) {
|
||||
// Push to the same query the new IDs to fetch.
|
||||
queries[indexQueries].ids.push(...query[ref.primaryKey]);
|
||||
map[indexQueries].push(index);
|
||||
} else {
|
||||
// Create new query in the query.
|
||||
queries.push({
|
||||
ids: query[ref.primaryKey],
|
||||
options: options
|
||||
});
|
||||
|
||||
map[queries.length - 1 > 0 ? queries.length - 1 : 0] = [];
|
||||
map[queries.length - 1].push(index);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
queries,
|
||||
map
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
const DataLoader = require('dataloader');
|
||||
const pluralize = require('pluralize');
|
||||
const Aggregator = require('./Aggregator');
|
||||
const Loaders = require('./Loaders');
|
||||
const Query = require('./Query.js');
|
||||
const Mutation = require('./Mutation.js');
|
||||
const Types = require('./Types.js');
|
||||
@ -362,13 +362,8 @@ module.exports = {
|
||||
default:
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - For each association, I've to create a loader.
|
||||
// - Each loader only return one kind of data (user, product or category).
|
||||
// - Each loader should only execute one request.
|
||||
// - Maybe, I should create specific loader to handle `where` condition like this https://github.com/facebook/dataloader/blob/master/examples/Knex.md
|
||||
|
||||
this.loaders[association.collection || association.model] = this.createLoader(association.collection || association.model, association.plugin);
|
||||
// Create dynamic dataloader for query batching and caching.
|
||||
Loaders.createLoader(association.collection || association.model, association.plugin);
|
||||
|
||||
_.merge(acc.resolver[globalId], {
|
||||
[association.alias]: async (obj, options) => {
|
||||
@ -421,11 +416,6 @@ module.exports = {
|
||||
[ref.primaryKey]: arrayOfIds,
|
||||
...where.where,
|
||||
}).where;
|
||||
|
||||
if (this.loaders[association.collection || association.model]) {
|
||||
// return this.loaders[association.collection].load({ ids: arrayOfIds, query: queryOpts.query });
|
||||
return this.loaders[association.collection || association.model].load({ ids: arrayOfIds, options: queryOpts });
|
||||
}
|
||||
break;
|
||||
// falls through
|
||||
}
|
||||
@ -447,95 +437,21 @@ module.exports = {
|
||||
){
|
||||
queryOpts.query.id.symbol = 'IN';
|
||||
}
|
||||
|
||||
return association.model ?
|
||||
resolvers.fetch(params, association.plugin, []):
|
||||
Loaders.loaders[association.collection || association.model].load({ options: queryOpts });
|
||||
|
||||
const value = await (association.model
|
||||
? resolvers.fetch(params, association.plugin, [])
|
||||
: resolvers.fetchAll(params, { ...queryOpts, populate: [] }));
|
||||
// const value = await (association.model
|
||||
// ? resolvers.fetch(params, association.plugin, [])
|
||||
// : resolvers.fetchAll(params, { ...queryOpts, populate: [] }));
|
||||
|
||||
return value && value.toJSON ? value.toJSON() : value;
|
||||
// return value && value.toJSON ? value.toJSON() : value;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, initialState);
|
||||
},
|
||||
|
||||
loaders: {},
|
||||
|
||||
createLoader: function(model) {
|
||||
return new DataLoader(keys => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const { queriesIds, queriesOptions, map } = this.extractQueries(_.cloneDeep(keys));
|
||||
|
||||
const executeQueries = await Promise.all(queriesIds.map((ids, index) => this.makeQuery(model, ids, queriesOptions[index])));
|
||||
|
||||
const data = keys.map((query, index) => {
|
||||
const indexResults = map.findIndex(queryMap => queryMap.indexOf(index) !== -1);
|
||||
const results = executeQueries[indexResults];
|
||||
|
||||
return query.ids.map(id =>
|
||||
results.find(entry => (entry._id || entry.id || '').toString() === id.toString())
|
||||
);
|
||||
});
|
||||
|
||||
resolve(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
cacheKeyFn: (key) => {
|
||||
return _.isObjectLike(key) ? JSON.stringify(_.cloneDeep(key)) : key;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
makeQuery: async function(model, ids, queryOptions) {
|
||||
const request = await strapi.plugins['content-manager'].services['contentmanager'].fetchAll({ model }, {
|
||||
...queryOptions,
|
||||
query: {
|
||||
_id: _.uniq(ids.map(x => x.toString()))
|
||||
},
|
||||
populate: []
|
||||
});
|
||||
|
||||
const entries = request && request.toJSON ? request.toJSON() : request;
|
||||
|
||||
return entries;
|
||||
},
|
||||
|
||||
extractQueries: function(keys) {
|
||||
const queriesIds = [];
|
||||
const queriesOptions = [];
|
||||
const map = [[]]; // { <originalPosition> : [queries] }
|
||||
|
||||
keys.forEach((current, index) => {
|
||||
const { query, ...options } = current.options;
|
||||
|
||||
// Find similar query.
|
||||
const indexQueriesOptions = queriesOptions.findIndex(queryOption => _.isEqual(queryOption, options));
|
||||
|
||||
if (indexQueriesOptions !== -1) {
|
||||
// Push to this query the new IDs to fetch.
|
||||
queriesIds[indexQueriesOptions].push(...current.ids);
|
||||
map[indexQueriesOptions].push(index);
|
||||
} else {
|
||||
// Create new query in the query.
|
||||
// Note: The query and the query options have the same index in both arrays.
|
||||
queriesIds.push(current.ids);
|
||||
queriesOptions.push(options);
|
||||
|
||||
map[queriesIds.length - 1] = [];
|
||||
map[queriesIds.length - 1].push(index);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
queriesIds,
|
||||
queriesOptions,
|
||||
map
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user