import { createContextCache, isThenable, type MaybePromise, type SchemaTypes } from '@pothos/core'; import DataLoader, { type Options } from 'dataloader'; import type { GraphQLResolveInfo } from 'graphql'; export function rejectErrors( val: MaybePromise, ): MaybePromise<(Promise | T)[]> { if (isThenable(val)) { return val.then(rejectErrors); } return val.map((item) => (item instanceof Error ? Promise.reject(item) : item)); } const getAllDataLoaders = createContextCache(() => new Set>()); function addDataloader(context: object, loader: DataLoader) { const loaderSet = getAllDataLoaders(context); loaderSet.add(loader); return loader; } export function clearAllDataLoaders(context: object) { const loaderSet = getAllDataLoaders(context); for (const loader of loaderSet) { loader.clearAll(); } } export function loadAndSort( load: (keys: K[], context: C, args: Args, info: GraphQLResolveInfo) => MaybePromise, toKey: false | ((val: V) => K) | undefined, ) { if (!toKey) { return load; } return async (keys: K[], context: C, args: Args, info: GraphQLResolveInfo) => { const list = await load(keys, context, args, info); const map = new Map(); const results: (V | null)[] = []; for (const val of list as V[]) { if (val instanceof Error) { throw val; } if (val != null) { map.set(toKey(val), val); } } for (let i = 0; i < keys.length; i += 1) { results[i] = map.get(keys[i]) ?? null; } return results; }; } export function dataloaderGetter( loaderOptions: Options | undefined, load: (keys: K[], context: SchemaTypes['Context']) => Promise, toKey: ((val: V) => K) | undefined, sort: boolean | ((val: V) => K) | undefined, ) { const loader = (sort ? loadAndSort(load, typeof sort === 'function' ? sort : toKey) : load) as ( keys: readonly K[], context: SchemaTypes['Context'], ) => Promise; return createContextCache((context: object) => addDataloader(context, new DataLoader((keys) => loader(keys, context), loaderOptions)), ); } export function pathDataloaderGetter( loaderOptions: Options | undefined, load: ( keys: K[], context: SchemaTypes['Context'], args: Args, info: GraphQLResolveInfo, ) => Promise, toKey: ((val: V) => K) | undefined, sort: boolean | ((val: V) => K) | undefined, byPath?: boolean, ) { const cache = createContextCache(() => new Map>()); const loader = (sort ? loadAndSort(load, typeof sort === 'function' ? sort : toKey) : load) as ( keys: readonly K[], context: SchemaTypes['Context'], args: Args, info: GraphQLResolveInfo, ) => Promise; return (args: Args, ctx: SchemaTypes['Context'], info: GraphQLResolveInfo) => { const key = byPath ? cacheKey(info.path) : '*'; const map = cache(ctx); if (!map.has(key)) { map.set( key, addDataloader( ctx, new DataLoader((keys) => loader(keys, ctx, args, info), loaderOptions), ), ); } return map.get(key)!; }; } export function cacheKey(path: GraphQLResolveInfo['path'] | undefined) { if (!path) { // Root return '*'; } let key = String(path.key); let current = path.prev; while (current) { key = `${typeof current.key === 'number' ? '*' : current.key}.${key}`; current = current.prev; } return key; }