UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

160 lines (159 loc) 6.03 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 = {}; 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