@pothos/plugin-dataloader
Version:
A Pothos plugin for attaching dataloader to object types
111 lines (91 loc) • 3.15 kB
text/typescript
import { type MaybePromise, type SchemaTypes, createContextCache, isThenable } from '@pothos/core';
import DataLoader, { type Options } from 'dataloader';
import type { GraphQLResolveInfo } from 'graphql';
export function rejectErrors<T>(
val: MaybePromise<readonly (Error | T)[]>,
): MaybePromise<(Promise<T> | T)[]> {
if (isThenable(val)) {
return val.then(rejectErrors);
}
return val.map((item) => (item instanceof Error ? Promise.reject(item) : item));
}
export function loadAndSort<K, V, C, LoadResult, Args = never>(
load: (keys: K[], context: C, args: Args, info: GraphQLResolveInfo) => MaybePromise<LoadResult>,
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<K, V>();
const results = new Array<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<K, V, C>(
loaderOptions: Options<K, V, C> | undefined,
load: (keys: K[], context: SchemaTypes['Context']) => Promise<readonly (Error | V)[]>,
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<V[]>;
return createContextCache(
(context: object) => new DataLoader<K, V, C>((keys) => loader(keys, context), loaderOptions),
);
}
export function pathDataloaderGetter<K, V, C, Args>(
loaderOptions: Options<K, V, C> | undefined,
load: (
keys: K[],
context: SchemaTypes['Context'],
args: Args,
info: GraphQLResolveInfo,
) => Promise<readonly (Error | V)[]>,
toKey: ((val: V) => K) | undefined,
sort: boolean | ((val: V) => K) | undefined,
byPath?: boolean,
) {
const cache = createContextCache(() => new Map<string, DataLoader<K, V, C>>());
const loader = (sort ? loadAndSort(load, typeof sort === 'function' ? sort : toKey) : load) as (
keys: readonly K[],
context: SchemaTypes['Context'],
args: Args,
info: GraphQLResolveInfo,
) => Promise<V[]>;
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, new DataLoader<K, V, C>((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;
}