UNPKG

@r1tsu/payload

Version:

106 lines (105 loc) 4.4 kB
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 = keys.reduce((batches, key)=>{ const [transactionID, collection, id, depth, currentDepth, locale, fallbackLocale, overrideAccess, showHiddenFields] = JSON.parse(key); const batchKeyArray = [ transactionID, collection, depth, currentDepth, locale, fallbackLocale, overrideAccess, showHiddenFields ]; const batchKey = JSON.stringify(batchKeyArray); const idType = payload.collections?.[collection].customIDType || payload.db.defaultIDType; let sanitizedID = id; if (idType === 'number') sanitizedID = parseFloat(id); if (isValidID(sanitizedID, idType)) { return { ...batches, [batchKey]: [ ...batches[batchKey] || [], sanitizedID ] }; } return batches; }, {}); // Run find requests one after another, so as to not hang transactions await Object.entries(batchByFindArgs).reduce(async (priorFind, [batchKey, ids])=>{ await priorFind; const [transactionID, collection, depth, currentDepth, locale, fallbackLocale, overrideAccess, showHiddenFields] = JSON.parse(batchKey); req.transactionID = transactionID; const result = await payload.find({ collection, currentDepth, depth, disableErrors: true, fallbackLocale, locale, overrideAccess: Boolean(overrideAccess), pagination: false, req: req, showHiddenFields: Boolean(showHiddenFields), where: { id: { in: ids } } }); // For each returned doc, find index in original keys // Inject doc within docs array if index exists result.docs.forEach((doc)=>{ const docKey = JSON.stringify([ req.transactionID, collection, doc.id, depth, currentDepth, locale, fallbackLocale, overrideAccess, showHiddenFields ]); const docsIndex = keys.findIndex((key)=>key === docKey); if (docsIndex > -1) { docs[docsIndex] = doc; } }); }, Promise.resolve()); // 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)=>new DataLoader(batchAndLoadDocs(req)); //# sourceMappingURL=dataloader.js.map