0

I have the following Vue 3 useTRPCQuery() composable which lightly wraps tRPC.io's client.query() call, which aims to be type-aware and reactive.

My usage attempt is as follows:

export function useQuery(
  client: TRPCClient<AppRouter>,
  pathAndInput: [path: TPath, ...args: inferHandlerInput<TQueries[TPath]>]
) {
  const response = ref<inferQueryOutput<TPath>>()

  ;(async () => {
    const data = await client.query(*...pathAndInput*) # TS Error Here
    response.value = data
  })()

  return response
}

All works ok at runtime, but I'm seeing the following error around the pathAndInput tuple:

spread argument must either have a tuple type or be passed to a rest parameter.

I've looked at potential fixes for this, such as passing in path and ...args separately, e.g.,

export function useQuery(
  client: TRPCClient<AppRouter>,
  path: TPath,
  ...args: inferHandlerInput<TQueries[TPath]>
) {
  const response = ref<inferQueryOutput<TPath>>()

  ;(async () => {
    const data = await client.query(path, ...args)
    response.value = data
  })()

  return response
}

but I'd love to be able to have my usage of this composable as follows:

export const useTelescopes = (client: TRPCClient<AppRouter>) => {
  return {
    telescopes: useQuery(client, ['telescope.all', someExtraArgs])
  }
}

Is there something I am missing that can allow me to use a tuple in this way?

The full code for the useQuery reference is as follows:

import { ref } from 'vue'

import type {
  ProcedureRecord,
  inferHandlerInput,
  inferProcedureInput,
  inferProcedureOutput
} from '@trpc/server'

import type { TRPCClient, TRPCClientErrorLike } from '@trpc/client'

import type { AppRouter } from '../somewhere/where/your/server/is/server.ts'

export type TQueries = AppRouter['_def']['queries']
export type TError = TRPCClientErrorLike<AppRouter>

export type inferProcedures<TObj extends ProcedureRecord<any, any, any, any, any, any>> = {
  [TPath in keyof TObj]: {
    input: inferProcedureInput<TObj[TPath]>
    output: inferProcedureOutput<TObj[TPath]>
  }
}

export type TQueryValues = inferProcedures<AppRouter['_def']['queries']>

export type TPath = keyof TQueryValues & string

/**
 *
 * This is a helper method to infer the output of a query resolver
 *
 */
export type inferQueryOutput<TRouteKey extends keyof AppRouter['_def']['queries']> = inferProcedureOutput<
  AppRouter['_def']['queries'][TRouteKey]
>

/**
 *
 * This is a helper method to infer the input of a query resolver
 *
 */
export type inferQueryInput<TRouteKey extends keyof AppRouter['_def']['queries']> = inferProcedureInput<
  AppRouter['_def']['queries'][TRouteKey]
>

export function useQuery(
  client: TRPCClient<AppRouter>,
  path: TPath,
  ...args: inferHandlerInput<TQueries[TPath]>
) {
  const response = ref<inferQueryOutput<TPath>>()

  ;(async () => {
    const data = await client.query(path, ...args)
    response.value = data
  })()

  return response
}

An example AppRouter type would be:

import * as trpc from '@trpc/server';

export const appRouter = trpc
  .router<Context>()
  // Create procedure at path 'hello'
  .query('hello', {
    resolve({ ctx }) {
      return {
        greeting: `hello world`,
      };
    },
  });

type AppRouter = typeof appRouter;

which should be queryable as the following:

useQuery(client, ['hello'])
halfer
  • 19,824
  • 17
  • 99
  • 186
Micheal J. Roberts
  • 3,735
  • 4
  • 37
  • 76

0 Answers0