import type { CreateTRPCClientOptions, TRPCClientErrorLike, inferRouterProxyClient } from '@trpc/client' import { createTRPCProxyClient } from '@trpc/client' import type { AnyMutationProcedure, AnyProcedure, AnyQueryProcedure, AnyRouter, ProcedureRouterRecord, inferProcedureInput, inferProcedureOutput, } 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' /** * 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') { // @ts-expect-error: Nuxt internal return useAsyncData(queryKey, () => (client as any)[path][lastArg](input), { ...asyncDataOptions as Record, immediate: false, }) } // @ts-expect-error: Nuxt internal 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 function createTRPCNuxtProxyClient(opts: CreateTRPCClientOptions) { const client = createTRPCProxyClient(opts) const decoratedClient = createFlatProxy((key) => { return createNuxtProxyDecoration(key, client) }) as DecoratedProcedureRecord return decoratedClient }