-
Thanks for the amazing work! I've been really enjoying utilizing I'm curious if it's possible to create a batch resolver like they do in https://www.npmjs.com/package/graphql-resolve-batch This way it's possible to have fine grained queries but batch the requests sent upstream to the database. Happy to help explain further or be a part of implementing this if there is an appetite for it. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
Here was my first try (basically just reimplementing import { ProcedureType } from '@trpc/server';
import { ProcedureResolver } from '@trpc/server/dist/declarations/src/internals/procedure';
type BatchResolverFn<TContext, TInput, TOutput> = (args: {
inputs: TInput[];
ctx: TContext;
type: ProcedureType;
}) => Promise<TOutput[]> | TOutput[];
export const createBatchResolver = <TContext, TInput, TOutput>(
fn: BatchResolverFn<TContext, TInput, TOutput>,
): ProcedureResolver<TContext, TInput, TOutput> => {
return ({ input, ctx, type }) => {
const batcher = new Batcher(fn);
return new Promise<TOutput>((resolve, reject) => {
batcher.batch({ input, ctx, type }, resolve, reject);
});
};
};
class Batcher<TContext, TInput, TOutput> {
private inputs: TInput[] = [];
private callbacks: {
resolve: (value: TOutput | PromiseLike<TOutput>) => void;
reject: (reason?: any) => void;
}[] = [];
private ctx?: TContext;
private type?: ProcedureType;
private hasScheduledResolve = false;
constructor(private fn: BatchResolverFn<TContext, TInput, TOutput>) {}
batch = (
{
input,
ctx,
type,
}: { input: TInput; ctx: TContext; type: ProcedureType },
resolve: (value: TOutput | PromiseLike<TOutput>) => void,
reject: (reason?: any) => void,
) => {
this.ctx = ctx;
this.type = type;
this.inputs.push(input);
this.callbacks.push({ resolve, reject });
this.scheduleResolve();
};
private scheduleResolve = () => {
if (!this.hasScheduledResolve) {
this.hasScheduledResolve = true;
process.nextTick(() => {
this.resolve();
this.hasScheduledResolve = false;
});
}
};
private resolve = async () => {
const inputs = this.inputs;
const callbacks = this.callbacks;
this.inputs = [];
this.callbacks = [];
const values = await this.fn({
inputs,
ctx: this.ctx!,
type: this.type!,
});
callbacks.forEach(({ resolve, reject }, i) => {
if (values[i] instanceof Error) {
reject(values[i]);
} else {
resolve(values[i]);
}
});
};
} |
Beta Was this translation helpful? Give feedback.
-
I usually use the dataloader-package - in tRPC I have my own implementation on the client as it has some specific needs (which isn't exposed as it's just intended for internal use). You'd create a data loader in your Note, if you use Prisma, you don't have to implement a data loader at all as it's built-in. Exactly how you type your flavour of data loader is not something I can spend time helping you with, as it's not specific to tRPC. |
Beta Was this translation helpful? Give feedback.
I usually use the dataloader-package - in tRPC I have my own implementation on the client as it has some specific needs (which isn't exposed as it's just intended for internal use).
You'd create a data loader in your
createContext
-method for each request and access it in the resolver through thectx
-property.Note, if you use Prisma, you don't have to implement a data loader at all as it's built-in.
Exactly how you type your flavour of data loader is not something I can spend time helping you with, as it's not specific to tRPC.