import type { TRPCClientErrorLike, inferRouterProxyClient } from '@trpc/client' import { createTRPCProxyClient, httpBatchLink } from '@trpc/client' import type { AnyMutationProcedure, AnyProcedure, AnyQueryProcedure, AnyRouter, ProcedureRecord, ProcedureRouterRecord, inferHandlerInput, inferProcedureInput, inferProcedureOutput, inferRouterInputs } from '@trpc/server' import { createFlatProxy, createRecursiveProxy } from '@trpc/server/shared' import type { AsyncData, AsyncDataOptions, KeyOfRes, PickFrom, _Transform, } from 'nuxt/dist/app/composables/asyncData' import { hash } from 'ohash' import type { AppRouter } from '~~/server/trpc' /** * Calculates the key used for `useAsyncData` call */ export function getQueryKey( path: string, input: unknown, ): string { return input === undefined ? path : `${path}-${hash(input || '')}` } function createNuxtProxyDecoration(name: string, client: inferRouterProxyClient) { return createRecursiveProxy((opts) => { const args = opts.args const pathCopy = [name, ...opts.path] // The last arg is for instance `.mutate` or `.query()` // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const lastArg = pathCopy.pop()! const path = pathCopy.join('.') const [input, asyncDataOptions] = args const queryKey = getQueryKey(path, input) if (lastArg === 'mutate') { return useAsyncData(queryKey, () => (client as any)[path][lastArg](input), { ...asyncDataOptions as Record, immediate: false, }) } return useAsyncData(queryKey, () => (client as any)[path][lastArg](input), asyncDataOptions as Record) }) } /** * @internal */ export type DecorateProcedure< TProcedure extends AnyProcedure, TPath extends string, > = TProcedure extends AnyQueryProcedure ? { query: < TData = inferProcedureOutput, Transform extends _Transform = _Transform, PickKeys extends KeyOfRes = KeyOfRes, >( input: inferProcedureInput, opts?: AsyncDataOptions, ) => AsyncData, PickKeys>, TRPCClientErrorLike> } : TProcedure extends AnyMutationProcedure ? { mutate: < TData = inferProcedureOutput, Transform extends _Transform = _Transform, PickKeys extends KeyOfRes = KeyOfRes, >( input: inferProcedureInput, opts?: AsyncDataOptions, ) => AsyncData, PickKeys>, TRPCClientErrorLike> } : never /** * @internal */ export type DecoratedProcedureRecord< TProcedures extends ProcedureRouterRecord, TPath extends string = '', > = { [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter ? DecoratedProcedureRecord< TProcedures[TKey]['_def']['record'], `${TPath}${TKey & string}.` > : TProcedures[TKey] extends AnyProcedure ? DecorateProcedure : never; } export default defineNuxtPlugin(() => { const client = createTRPCProxyClient({ links: [ httpBatchLink({ url: 'http://localhost:3000/trpc', }), ], }) const newClient = createFlatProxy((key) => { return createNuxtProxyDecoration(key, client) }) as DecoratedProcedureRecord return { provide: { client: newClient, }, } })