{"version":3,"sources":["../../src/collections/dataloader.ts"],"sourcesContent":["import type { BatchLoadFn } from 'dataloader'\n\nimport DataLoader from 'dataloader'\n\nimport type { FindArgs } from '../database/types.js'\nimport type { Payload, TypedFallbackLocale } from '../index.js'\nimport type { PayloadRequest, PopulateType, SelectType } from '../types/index.js'\nimport type { TypeWithID } from './config/types.js'\nimport type { FindOptions } from './operations/local/find.js'\n\nimport { isValidID } from '../utilities/isValidID.js'\n\n// Payload uses `dataloader` to solve the classic GraphQL N+1 problem.\n\n// We keep a list of all documents requested to be populated for any given request\n// and then batch together documents within the same collection,\n// making only 1 find per each collection, rather than `findByID` per each requested doc.\n\n// This dramatically improves performance for REST and Local API `depth` populations,\n// and also ensures complex GraphQL queries perform lightning-fast.\n\nconst batchAndLoadDocs =\n  (req: PayloadRequest): BatchLoadFn<string, TypeWithID> =>\n  async (keys: readonly string[]): Promise<TypeWithID[]> => {\n    const { payload } = req\n\n    // Create docs array of same length as keys, using null as value\n    // We will replace nulls with injected docs as they are retrieved\n    const docs: (null | TypeWithID)[] = keys.map(() => null)\n\n    /**\n    * Batch IDs by their `find` args\n    * so we can make one find query per combination of collection, depth, locale, and fallbackLocale.\n    *\n    * Resulting shape will be as follows:\n      {\n        // key is stringified set of find args\n        '[null,\"pages\",2,0,\"es\",\"en\",false,false]': [\n          // value is array of IDs to find with these args\n          'q34tl23462346234524',\n          '435523540194324280',\n          '2346245j35l3j5234532li',\n        ],\n        // etc\n      };\n    *\n    **/\n\n    const batchByFindArgs: Record<string, string[]> = {}\n\n    for (const key of keys) {\n      const [\n        transactionID,\n        collection,\n        id,\n        depth,\n        currentDepth,\n        locale,\n        fallbackLocale,\n        overrideAccess,\n        showHiddenFields,\n        draft,\n        select,\n        populate,\n      ] = JSON.parse(key)\n\n      const batchKeyArray = [\n        transactionID,\n        collection,\n        depth,\n        currentDepth,\n        locale,\n        fallbackLocale,\n        overrideAccess,\n        showHiddenFields,\n        draft,\n        select,\n        populate,\n      ]\n\n      const batchKey = JSON.stringify(batchKeyArray)\n\n      const idType = payload.collections?.[collection]?.customIDType || payload.db.defaultIDType\n      const sanitizedID = idType === 'number' ? parseFloat(id) : id\n\n      if (isValidID(sanitizedID, idType)) {\n        batchByFindArgs[batchKey] = [...(batchByFindArgs[batchKey] || []), sanitizedID]\n      }\n    }\n\n    // Run find requests one after another, so as to not hang transactions\n\n    for (const [batchKey, ids] of Object.entries(batchByFindArgs)) {\n      const [\n        transactionID,\n        collection,\n        depth,\n        currentDepth,\n        locale,\n        fallbackLocale,\n        overrideAccess,\n        showHiddenFields,\n        draft,\n        select,\n        populate,\n      ] = JSON.parse(batchKey)\n\n      req.transactionID = transactionID\n\n      const enableTrash = Boolean(payload.collections?.[collection]?.config?.trash)\n      const selectWithDeletedAt = enableTrash && select ? { ...select, deletedAt: true } : select\n\n      const result = await payload.find({\n        collection,\n        currentDepth,\n        depth,\n        disableErrors: true,\n        draft,\n        fallbackLocale,\n        locale,\n        overrideAccess: Boolean(overrideAccess),\n        pagination: false,\n        populate,\n        req,\n        select: selectWithDeletedAt,\n        showHiddenFields: Boolean(showHiddenFields),\n        ...(enableTrash ? { trash: true } : {}),\n        where: {\n          id: {\n            in: ids,\n          },\n        },\n      })\n\n      // For each returned doc, find index in original keys\n      // Inject doc within docs array if index exists\n      for (const doc of result.docs) {\n        const docKey = createDataloaderCacheKey({\n          collectionSlug: collection,\n          currentDepth,\n          depth,\n          docID: doc.id,\n          draft,\n          fallbackLocale,\n          locale,\n          overrideAccess,\n          populate,\n          select,\n          showHiddenFields,\n          transactionID: req.transactionID!,\n        })\n        const docsIndex = keys.findIndex((key) => key === docKey)\n\n        if (docsIndex > -1) {\n          docs[docsIndex] = doc\n        }\n      }\n    }\n\n    // Return docs array,\n    // which has now been injected with all fetched docs\n    // and should match the length of the incoming keys arg\n    return docs as TypeWithID[]\n  }\n\nexport const getDataLoader = (req: PayloadRequest) => {\n  const findQueries = new Map()\n  const dataLoader = new DataLoader(batchAndLoadDocs(req)) as PayloadRequest['payloadDataLoader']\n\n  dataLoader.find = ((args: FindArgs) => {\n    const key = createFindDataloaderCacheKey(args)\n    const cached = findQueries.get(key)\n    if (cached) {\n      return cached\n    }\n    const request = req.payload.find(args)\n    findQueries.set(key, request)\n    return request\n  }) as Payload['find']\n\n  return dataLoader\n}\n\nconst createFindDataloaderCacheKey = ({\n  collection,\n  currentDepth,\n  depth,\n  disableErrors,\n  draft,\n  includeLockStatus,\n  joins,\n  limit,\n  overrideAccess,\n  page,\n  pagination,\n  populate,\n  req,\n  select,\n  showHiddenFields,\n  sort,\n  where,\n}: FindOptions<string, SelectType>): string =>\n  JSON.stringify([\n    collection,\n    currentDepth,\n    depth,\n    disableErrors,\n    draft,\n    includeLockStatus,\n    joins,\n    limit,\n    overrideAccess,\n    page,\n    pagination,\n    populate,\n    req?.locale,\n    req?.fallbackLocale,\n    req?.user?.id,\n    req?.transactionID,\n    select,\n    showHiddenFields,\n    sort,\n    where,\n  ])\n\ntype CreateCacheKeyArgs = {\n  collectionSlug: string\n  currentDepth: number\n  depth: number\n  docID: number | string\n  draft: boolean\n  fallbackLocale: TypedFallbackLocale\n  locale: string | string[]\n  overrideAccess: boolean\n  populate?: PopulateType\n  select?: SelectType\n  showHiddenFields: boolean\n  transactionID: number | Promise<number | string> | string\n}\nexport const createDataloaderCacheKey = ({\n  collectionSlug,\n  currentDepth,\n  depth,\n  docID,\n  draft,\n  fallbackLocale,\n  locale,\n  overrideAccess,\n  populate,\n  select,\n  showHiddenFields,\n  transactionID,\n}: CreateCacheKeyArgs): string =>\n  JSON.stringify([\n    transactionID,\n    collectionSlug,\n    docID,\n    depth,\n    currentDepth,\n    locale,\n    fallbackLocale,\n    overrideAccess,\n    showHiddenFields,\n    draft,\n    select,\n    populate,\n  ])\n"],"names":["DataLoader","isValidID","batchAndLoadDocs","req","keys","payload","docs","map","batchByFindArgs","key","transactionID","collection","id","depth","currentDepth","locale","fallbackLocale","overrideAccess","showHiddenFields","draft","select","populate","JSON","parse","batchKeyArray","batchKey","stringify","idType","collections","customIDType","db","defaultIDType","sanitizedID","parseFloat","ids","Object","entries","enableTrash","Boolean","config","trash","selectWithDeletedAt","deletedAt","result","find","disableErrors","pagination","where","in","doc","docKey","createDataloaderCacheKey","collectionSlug","docID","docsIndex","findIndex","getDataLoader","findQueries","Map","dataLoader","args","createFindDataloaderCacheKey","cached","get","request","set","includeLockStatus","joins","limit","page","sort","user"],"mappings":"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"}