/// import { dehydrate, HydrationBoundary, type QueryClient, } from '@tanstack/react-query'; import type { TRPCClientError } from '@trpc/client'; import type { inferTransformedProcedureOutput } from '@trpc/server'; import { createRecursiveProxy, type AnyRouter, type inferProcedureInput, type RouterRecord, } from '@trpc/server/unstable-core-do-not-import'; import type { AnyProcedure, AnyRootTypes, inferProcedureOutput, inferRouterRootTypes, Maybe, RouterCaller, TypeError, } from '@trpc/server/unstable-core-do-not-import'; import * as React from 'react'; import { getQueryKeyInternal } from './internals/getQueryKey'; import type { TRPCFetchInfiniteQueryOptions, TRPCFetchQueryOptions, } from './shared'; const HELPERS = ['prefetch', 'prefetchInfinite']; type DecorateProcedure< TRoot extends AnyRootTypes, TProcedure extends AnyProcedure, > = { ( input: inferProcedureInput, ): Promise>; prefetch: ( input: inferProcedureInput, opts?: TRPCFetchQueryOptions< inferTransformedProcedureOutput, TRPCClientError >, ) => Promise; prefetchInfinite: ( input: inferProcedureInput, opts?: TRPCFetchInfiniteQueryOptions< inferProcedureInput, inferTransformedProcedureOutput, TRPCClientError >, ) => Promise; }; type DecorateRouterRecord< TRoot extends AnyRootTypes, TRecord extends RouterRecord, > = { [TKey in keyof TRecord]: TRecord[TKey] extends infer $Value ? $Value extends AnyProcedure ? DecorateProcedure : $Value extends RouterRecord ? DecorateRouterRecord : never : never; }; type Caller = ReturnType< RouterCaller, TRouter['_def']['record']> >; // ts-prune-ignore-next /** * @note This requires `@tanstack/react-query@^5.49.0` * @note Make sure to have `dehydrate.serializeData` and `hydrate.deserializeData` * set to your data transformer in your `QueryClient` factory. * @example * ```ts * export const createQueryClient = () => * new QueryClient({ * defaultOptions: { * dehydrate: { * serializeData: transformer.serialize, * }, * hydrate: { * deserializeData: transformer.deserialize, * }, * }, * }); * ``` */ export function createHydrationHelpers( caller: AnyRouter extends TRouter ? TypeError<'Generic parameter missing in `createHydrationHelpers`'> : Caller, getQueryClient: () => QueryClient, ) { type RootTypes = inferRouterRootTypes; const wrappedProxy = createRecursiveProxy< DecorateRouterRecord >(async (opts) => { const path = [...opts.path]; const args = [...opts.args]; const proc = path.reduce( (acc, key) => // @ts-expect-error - ?? HELPERS.includes(key) ? acc : acc[key], caller, ) as unknown as DecorateProcedure; const input = args[0]; const promise = proc(input); const helper = path.pop(); if (helper === 'prefetch') { const args1 = args[1] as Maybe< TRPCFetchInfiniteQueryOptions >; return getQueryClient().prefetchQuery({ ...args1, queryKey: getQueryKeyInternal(path, input, 'query'), queryFn: () => promise, }); } if (helper === 'prefetchInfinite') { const args1 = args[1] as Maybe< TRPCFetchInfiniteQueryOptions >; return getQueryClient().prefetchInfiniteQuery({ ...args1, queryKey: getQueryKeyInternal(path, input, 'infinite'), queryFn: () => promise, initialPageParam: args1?.initialCursor ?? null, }); } return promise; }); function HydrateClient(props: { children: React.ReactNode }) { const dehydratedState = dehydrate(getQueryClient()); return ( {props.children} ); } return { /*** * Wrapped caller with prefetch helpers * Can be used as a regular [server-side caller](https://trpc.io/docs/server/server-side-calls) * or using prefetch helpers to put the promise into the QueryClient cache * @example * ```ts * const data = await trpc.post.get("postId"); * * // or * void trpc.post.get.prefetch("postId"); * ``` */ trpc: wrappedProxy, /** * HoC to hydrate the query client for a client component * to pick up the prefetched promise and skip an initial * client-side fetch. * @example * ```tsx * // MyRSC.tsx * const MyRSC = ({ params }) => { * void trpc.post.get.prefetch(params.postId); * * return ( * * * * ); * }; * * // MyCC.tsx * "use client" * const MyCC = ({ postId }) => { * const { data: post } = trpc.post.get.useQuery(postId); * return
{post.title}
; * }; * ``` */ HydrateClient, }; }