Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
160 lines
5.9 KiB
Plaintext
160 lines
5.9 KiB
Plaintext
import DataLoader from 'dataloader';
|
|
import { isValidID } from '../utilities/isValidID.js';
|
|
// Payload uses `dataloader` to solve the classic GraphQL N+1 problem.
|
|
// We keep a list of all documents requested to be populated for any given request
|
|
// and then batch together documents within the same collection,
|
|
// making only 1 find per each collection, rather than `findByID` per each requested doc.
|
|
// This dramatically improves performance for REST and Local API `depth` populations,
|
|
// and also ensures complex GraphQL queries perform lightning-fast.
|
|
const batchAndLoadDocs = (req)=>async (keys)=>{
|
|
const { payload } = req;
|
|
// Create docs array of same length as keys, using null as value
|
|
// We will replace nulls with injected docs as they are retrieved
|
|
const docs = keys.map(()=>null);
|
|
/**
|
|
* Batch IDs by their `find` args
|
|
* so we can make one find query per combination of collection, depth, locale, and fallbackLocale.
|
|
*
|
|
* Resulting shape will be as follows:
|
|
{
|
|
// key is stringified set of find args
|
|
'[null,"pages",2,0,"es","en",false,false]': [
|
|
// value is array of IDs to find with these args
|
|
'q34tl23462346234524',
|
|
'435523540194324280',
|
|
'2346245j35l3j5234532li',
|
|
],
|
|
// etc
|
|
};
|
|
*
|
|
**/ const batchByFindArgs = {};
|
|
for (const key of keys){
|
|
const [transactionID, collection, id, depth, currentDepth, locale, fallbackLocale, overrideAccess, showHiddenFields, draft, select, populate] = JSON.parse(key);
|
|
const batchKeyArray = [
|
|
transactionID,
|
|
collection,
|
|
depth,
|
|
currentDepth,
|
|
locale,
|
|
fallbackLocale,
|
|
overrideAccess,
|
|
showHiddenFields,
|
|
draft,
|
|
select,
|
|
populate
|
|
];
|
|
const batchKey = JSON.stringify(batchKeyArray);
|
|
const idType = payload.collections?.[collection]?.customIDType || payload.db.defaultIDType;
|
|
const sanitizedID = idType === 'number' ? parseFloat(id) : id;
|
|
if (isValidID(sanitizedID, idType)) {
|
|
batchByFindArgs[batchKey] = [
|
|
...batchByFindArgs[batchKey] || [],
|
|
sanitizedID
|
|
];
|
|
}
|
|
}
|
|
// Run find requests one after another, so as to not hang transactions
|
|
for (const [batchKey, ids] of Object.entries(batchByFindArgs)){
|
|
const [transactionID, collection, depth, currentDepth, locale, fallbackLocale, overrideAccess, showHiddenFields, draft, select, populate] = JSON.parse(batchKey);
|
|
req.transactionID = transactionID;
|
|
const result = await payload.find({
|
|
collection,
|
|
currentDepth,
|
|
depth,
|
|
disableErrors: true,
|
|
draft,
|
|
fallbackLocale,
|
|
locale,
|
|
overrideAccess: Boolean(overrideAccess),
|
|
pagination: false,
|
|
populate,
|
|
req,
|
|
select,
|
|
showHiddenFields: Boolean(showHiddenFields),
|
|
where: {
|
|
id: {
|
|
in: ids
|
|
}
|
|
}
|
|
});
|
|
// For each returned doc, find index in original keys
|
|
// Inject doc within docs array if index exists
|
|
for (const doc of result.docs){
|
|
const docKey = createDataloaderCacheKey({
|
|
collectionSlug: collection,
|
|
currentDepth,
|
|
depth,
|
|
docID: doc.id,
|
|
draft,
|
|
fallbackLocale,
|
|
locale,
|
|
overrideAccess,
|
|
populate,
|
|
select,
|
|
showHiddenFields,
|
|
transactionID: req.transactionID
|
|
});
|
|
const docsIndex = keys.findIndex((key)=>key === docKey);
|
|
if (docsIndex > -1) {
|
|
docs[docsIndex] = doc;
|
|
}
|
|
}
|
|
}
|
|
// Return docs array,
|
|
// which has now been injected with all fetched docs
|
|
// and should match the length of the incoming keys arg
|
|
return docs;
|
|
};
|
|
export const getDataLoader = (req)=>{
|
|
const findQueries = new Map();
|
|
const dataLoader = new DataLoader(batchAndLoadDocs(req));
|
|
dataLoader.find = (args)=>{
|
|
const key = createFindDataloaderCacheKey(args);
|
|
const cached = findQueries.get(key);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const request = req.payload.find(args);
|
|
findQueries.set(key, request);
|
|
return request;
|
|
};
|
|
return dataLoader;
|
|
};
|
|
const createFindDataloaderCacheKey = ({ collection, currentDepth, depth, disableErrors, draft, includeLockStatus, joins, limit, overrideAccess, page, pagination, populate, req, select, showHiddenFields, sort, where })=>JSON.stringify([
|
|
collection,
|
|
currentDepth,
|
|
depth,
|
|
disableErrors,
|
|
draft,
|
|
includeLockStatus,
|
|
joins,
|
|
limit,
|
|
overrideAccess,
|
|
page,
|
|
pagination,
|
|
populate,
|
|
req?.locale,
|
|
req?.fallbackLocale,
|
|
req?.user?.id,
|
|
req?.transactionID,
|
|
select,
|
|
showHiddenFields,
|
|
sort,
|
|
where
|
|
]);
|
|
export const createDataloaderCacheKey = ({ collectionSlug, currentDepth, depth, docID, draft, fallbackLocale, locale, overrideAccess, populate, select, showHiddenFields, transactionID })=>JSON.stringify([
|
|
transactionID,
|
|
collectionSlug,
|
|
docID,
|
|
depth,
|
|
currentDepth,
|
|
locale,
|
|
fallbackLocale,
|
|
overrideAccess,
|
|
showHiddenFields,
|
|
draft,
|
|
select,
|
|
populate
|
|
]);
|
|
|
|
//# sourceMappingURL=dataloader.js.map |