import { createTRPCClient, httpBatchLink } from "@trpc/client"; import superjson from "superjson"; import type { AnyTRPCRouter } from "@trpc/server"; // Returns a tRPC client whose fetch is a stub honouring the provided mocks. // Mocks are keyed by procedure path ("blog.articleBySlug") returning the // raw response body. export function createMockTrpcClient( mocks: Record = {}, ) { const fetchStub: typeof fetch = async (input) => { const url = typeof input === "string" ? input : (input as Request).url; const path = new URL(url, "http://mock").pathname.replace( /^\/api\/trpc\//, "", ); // httpBatchLink joins same-tick calls into one request whose path is the // comma-separated list of procedure paths; respond with one element per // call, in order. const paths = path.split(","); const body = paths.map((p) => Object.hasOwn(mocks, p) ? { result: { data: superjson.serialize(mocks[p]) } } : { // Error payloads pass through the transformer too — serialize // them or the client throws "Unable to transform response". error: superjson.serialize({ code: -32603, message: `No mock for ${p}`, }), }, ); return new Response(JSON.stringify(body), { status: 200 }); }; // httpBatchLink requires a conditional TransformerOptions that // depends on whether the router uses a transformer; making this generic-friendly // without parameterizing twice requires here. // eslint-disable-next-line @typescript-eslint/no-explicit-any const link = httpBatchLink({ url: "http://mock/api/trpc", transformer: superjson, fetch: fetchStub, }); return createTRPCClient({ links: [link] }); }