From 400bc78decfb8133a76096ab81ef91130d4967f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zu=CC=88m=20Eldog=CC=86an?= Date: Tue, 17 Jan 2023 23:53:38 +0300 Subject: [PATCH 1/2] feat: add vue-query --- client-vue-query.ts | 1 + .../2.usage/3.simple-vue-query.md | 177 +++++++++++++++ package.json | 10 +- pnpm-lock.yaml | 32 +++ src/{client => client-shared}/links.ts | 0 src/client-vue-query/index.ts | 81 +++++++ .../internals/getArrayQueryKey.ts | 39 ++++ src/client-vue-query/internals/getQueryKey.ts | 11 + src/client-vue-query/types.ts | 214 ++++++++++++++++++ src/client/index.ts | 2 +- tsup.config.ts | 4 +- 11 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 client-vue-query.ts create mode 100644 docs/content/1.get-started/2.usage/3.simple-vue-query.md rename src/{client => client-shared}/links.ts (100%) create mode 100644 src/client-vue-query/index.ts create mode 100644 src/client-vue-query/internals/getArrayQueryKey.ts create mode 100644 src/client-vue-query/internals/getQueryKey.ts create mode 100644 src/client-vue-query/types.ts diff --git a/client-vue-query.ts b/client-vue-query.ts new file mode 100644 index 0000000..d673e70 --- /dev/null +++ b/client-vue-query.ts @@ -0,0 +1 @@ +export * from './dist/client-vue-query/index' \ No newline at end of file diff --git a/docs/content/1.get-started/2.usage/3.simple-vue-query.md b/docs/content/1.get-started/2.usage/3.simple-vue-query.md new file mode 100644 index 0000000..73e4bad --- /dev/null +++ b/docs/content/1.get-started/2.usage/3.simple-vue-query.md @@ -0,0 +1,177 @@ +--- +title: Simple Vue Query +description: tRPC-Nuxt provides first class integration with tRPC. +--- + +# Simple Usage + +## 1. Create a tRPC router + +Initialize your tRPC backend using the `initTRPC` function and create your first router. + +::code-group + +```ts [server/trpc/trpc.ts] +/** + * This is your entry point to setup the root configuration for tRPC on the server. + * - `initTRPC` should only be used once per app. + * - We export only the functionality that we use so we can enforce which base procedures should be used + * + * Learn how to create protected base procedures and other things below: + * @see https://trpc.io/docs/v10/router + * @see https://trpc.io/docs/v10/procedures + */ +import { initTRPC } from '@trpc/server' + +const t = initTRPC.create() + +/** + * Unprotected procedure + **/ +export const publicProcedure = t.procedure; + +export const router = t.router; +export const middleware = t.middleware; +``` + +```ts [server/api/trpc/[trpc].ts] +/** + * This is the API-handler of your app that contains all your API routes. + * On a bigger app, you will probably want to split this file up into multiple files. + */ +import { createNuxtApiHandler } from 'trpc-nuxt' +import { publicProcedure, router } from '~/server/trpc/trpc' +import { z } from 'zod' + +export const appRouter = router({ + hello: publicProcedure + // This is the input schema of your procedure + .input( + z.object({ + text: z.string().nullish(), + }), + ) + .query(({ input }) => { + // This is what you're returning to your client + return { + greeting: `hello ${input?.text ?? 'world'}`, + } + }), + login: publicProcedure + // using zod schema to validate and infer input values + .input( + z.object({ + name: z.string(), + }) + ) + .mutation(({ input }) => { + // Here some login stuff would happen + return { + user: { + name: input.name, + role: "ADMIN", + }, + }; + }), +}) + +// export only the type definition of the API +// None of the actual implementation is exposed to the client +export type AppRouter = typeof appRouter; + +// export API handler +export default createNuxtApiHandler({ + router: appRouter, + createContext: () => ({}), +}) +``` + +:: + +## 2. Create Vue Query and tRPC client plugin + +Create a strongly-typed plugin using your API's type signature. + +::code-group + +```ts [plugins/1.vue-query.ts] +import type { DehydratedState, VueQueryPluginOptions } from "@tanstack/vue-query"; +import { VueQueryPlugin, QueryClient, hydrate, dehydrate } from "@tanstack/vue-query"; +// Nuxt 3 app aliases +import { useState } from "#app"; + +export default defineNuxtPlugin((nuxt) => { + const vueQueryState = useState("vue-query"); + + // Modify your Vue Query global settings here + const queryClient = new QueryClient({ + defaultOptions: { queries: { staleTime: 5000 } }, + }); + const options: VueQueryPluginOptions = { queryClient }; + + nuxt.vueApp.use(VueQueryPlugin, options); + + if (process.server) { + nuxt.hooks.hook("app:rendered", () => { + vueQueryState.value = dehydrate(queryClient); + }); + } + + if (process.client) { + nuxt.hooks.hook("app:created", () => { + hydrate(queryClient, vueQueryState.value); + }); + } +}); + +``` + +```ts [plugins/2.client.ts] +import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client' +import type { AppRouter } from '~/server/api/trpc/[trpc]' + +export default defineNuxtPlugin(() => { + /** + * createTRPCNuxtClient adds a `useQuery` composable + * built on top of `useAsyncData`. + */ + const client = createTRPCNuxtClient({ + links: [ + httpBatchLink({ + url: '/api/trpc', + }), + ], + }) + + return { + provide: { + client, + }, + } +}) +``` + +:: + +## 3. Make an API request + +```vue [pages/index.vue] + + + +``` diff --git a/package.json b/package.json index 2d03a3e..9250c69 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,11 @@ "types": "./dist/client/index.d.ts", "require": "./dist/client/index.cjs", "import": "./dist/client/index.mjs" + }, + "./client-vue-query": { + "types": "./dist/client-vue-query/index.d.ts", + "require": "./dist/client-vue-query/index.cjs", + "import": "./dist/client-vue-query/index.mjs" } }, "main": "./dist/index.mjs", @@ -22,7 +27,8 @@ "types": "./dist/index.d.ts", "files": [ "dist", - "client.d.ts" + "client.d.ts", + "client-vue-query.d.ts" ], "scripts": { "dev": "concurrently \"pnpm build --watch\" \"pnpm --filter playground dev\"", @@ -35,6 +41,7 @@ "update-deps": "taze -w && pnpm i" }, "peerDependencies": { + "@tanstack/vue-query": "^4.22.0", "@trpc/client": "^10.8.0", "@trpc/server": "^10.8.0" }, @@ -46,6 +53,7 @@ }, "devDependencies": { "@nuxt/eslint-config": "^0.1.1", + "@tanstack/vue-query": "^4.22.0", "@trpc/client": "^10.8.1", "@trpc/server": "^10.8.1", "bumpp": "^8.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c2ce17..1044c24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,7 @@ importers: .: specifiers: '@nuxt/eslint-config': ^0.1.1 + '@tanstack/vue-query': ^4.22.0 '@trpc/client': ^10.8.1 '@trpc/server': ^10.8.1 bumpp: ^8.2.1 @@ -27,6 +28,7 @@ importers: ufo: 1.0.1 devDependencies: '@nuxt/eslint-config': 0.1.1_eslint@8.30.0 + '@tanstack/vue-query': 4.22.0 '@trpc/client': 10.8.1_@trpc+server@10.8.1 '@trpc/server': 10.8.1 bumpp: 8.2.1 @@ -1299,6 +1301,32 @@ packages: resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} dev: true + /@tanstack/match-sorter-utils/8.7.6: + resolution: {integrity: sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==} + engines: {node: '>=12'} + dependencies: + remove-accents: 0.4.2 + dev: true + + /@tanstack/query-core/4.22.0: + resolution: {integrity: sha512-OeLyBKBQoT265f5G9biReijeP8mBxNFwY7ZUu1dKL+YzqpG5q5z7J/N1eT8aWyKuhyDTiUHuKm5l+oIVzbtrjw==} + dev: true + + /@tanstack/vue-query/4.22.0: + resolution: {integrity: sha512-G+b1sumfG/YHRvFqVGUJFP+QZVlImsazXhjy/xZ0rD1rCI9fSM8FMaACSHH323EKnkcor7bBjzQqCKyflyk3Ug==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.5.0 || ^3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + '@tanstack/match-sorter-utils': 8.7.6 + '@tanstack/query-core': 4.22.0 + '@vue/devtools-api': 6.4.5 + vue-demi: 0.13.11 + dev: true + /@tootallnate/once/2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -6258,6 +6286,10 @@ packages: unified: 10.1.2 dev: true + /remove-accents/0.4.2: + resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} + dev: true + /require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} diff --git a/src/client/links.ts b/src/client-shared/links.ts similarity index 100% rename from src/client/links.ts rename to src/client-shared/links.ts diff --git a/src/client-vue-query/index.ts b/src/client-vue-query/index.ts new file mode 100644 index 0000000..2d1dbad --- /dev/null +++ b/src/client-vue-query/index.ts @@ -0,0 +1,81 @@ +import { useQuery, useMutation } from "@tanstack/vue-query"; +import { + type CreateTRPCClientOptions, + type inferRouterProxyClient, + createTRPCProxyClient, +} from "@trpc/client"; +import { type AnyRouter } from "@trpc/server"; +import { createFlatProxy, createRecursiveProxy } from "@trpc/server/shared"; +import { type DecoratedProcedureRecord } from "./types"; +// @ts-expect-error: Nuxt auto-imports +import { getCurrentInstance, onScopeDispose, useAsyncData } from "#imports"; +import { getQueryKey } from "./internals/getQueryKey"; +import { getArrayQueryKey } from "./internals/getArrayQueryKey"; + +export function getClientArgs( + pathAndInput: TPathAndInput, + opts: TOptions +) { + const [path, input] = pathAndInput; + return [path, input, (opts as any)?.trpc] as const; +} + +export function createNuxtProxyDecoration( + name: string, + client: inferRouterProxyClient +) { + return createRecursiveProxy((opts) => { + const args = opts.args; + + const pathCopy = [name, ...opts.path]; + + // The last arg is for instance `.useMutation` or `.useQuery()` + const lastArg = pathCopy.pop()!; + + // The `path` ends up being something like `post.byId` + const path = pathCopy.join("."); + + if (lastArg === "useMutation") { + const actualPath = Array.isArray(path) ? path[0] : path; + return useMutation({ + ...args, + mutationKey: [actualPath.split(".")], + mutationFn: (input: any) => (client as any)[actualPath].mutate(input), + }); + } + + const [input, ...rest] = args; + + const queryKey = getQueryKey(path, input); + + // Expose queryKey helper + if (lastArg === "getQueryKey") { + return getArrayQueryKey(queryKey, (rest[0] as any) ?? "any"); + } + + if (lastArg === "useQuery") { + const { trpc, ...options } = rest[0] || ({} as any); + return useQuery({ + ...options, + queryKey, + queryFn: () => (client as any)[path].query(input, trpc), + }); + } + + return (client as any)[path][lastArg](input); + }); +} + +export function createTRPCNuxtClient( + opts: CreateTRPCClientOptions +) { + const client = createTRPCProxyClient(opts); + + const decoratedClient = createFlatProxy((key) => { + return createNuxtProxyDecoration(key, client); + }) as DecoratedProcedureRecord; + + return decoratedClient; +} + +export { httpBatchLink, httpLink } from "../client-shared/links"; diff --git a/src/client-vue-query/internals/getArrayQueryKey.ts b/src/client-vue-query/internals/getArrayQueryKey.ts new file mode 100644 index 0000000..ab0bf88 --- /dev/null +++ b/src/client-vue-query/internals/getArrayQueryKey.ts @@ -0,0 +1,39 @@ +export type QueryType = 'query' | 'infinite' | 'any'; + +export type QueryKey = [ + string[], + { input?: unknown; type?: Exclude }?, +]; + +/** + * To allow easy interactions with groups of related queries, such as + * invalidating all queries of a router, we use an array as the path when + * storing in tanstack query. This function converts from the `.` separated + * path passed around internally by both the legacy and proxy implementation. + * https://github.com/trpc/trpc/issues/2611 + **/ +export function getArrayQueryKey( + queryKey: string | [string] | [string, ...unknown[]] | unknown[], + type: QueryType, +): QueryKey { + const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey]; + const [path, input] = queryKeyArrayed; + + const arrayPath = + typeof path !== 'string' || path === '' ? [] : path.split('.'); + + // Construct a query key that is easy to destructure and flexible for + // partial selecting etc. + // https://github.com/trpc/trpc/issues/3128 + if (!input && (!type || type === 'any')) + // for `utils.invalidate()` to match all queries (including vanilla react-query) + // we don't want nested array if path is empty, i.e. `[]` instead of `[[]]` + return arrayPath.length ? [arrayPath] : ([] as unknown as QueryKey); + return [ + arrayPath, + { + ...(typeof input !== 'undefined' && { input: input }), + ...(type && type !== 'any' && { type: type }), + }, + ]; +} diff --git a/src/client-vue-query/internals/getQueryKey.ts b/src/client-vue-query/internals/getQueryKey.ts new file mode 100644 index 0000000..87fda4b --- /dev/null +++ b/src/client-vue-query/internals/getQueryKey.ts @@ -0,0 +1,11 @@ +/** + * We treat `undefined` as an input the same as omitting an `input` + * https://github.com/trpc/trpc/issues/2290 + */ +export function getQueryKey( + path: string, + input: unknown, +): [string] | [string, unknown] { + if (path.length) return input === undefined ? [path] : [path, input]; + return [] as unknown as [string]; +} diff --git a/src/client-vue-query/types.ts b/src/client-vue-query/types.ts new file mode 100644 index 0000000..24edabb --- /dev/null +++ b/src/client-vue-query/types.ts @@ -0,0 +1,214 @@ +import type { + TRPCClientErrorLike, + TRPCRequestOptions as _TRPCRequestOptions, +} from "@trpc/client"; +import { type TRPCSubscriptionObserver } from "@trpc/client/dist/internals/TRPCUntypedClient"; +import type { + AnyMutationProcedure, + AnyProcedure, + AnyQueryProcedure, + AnyRouter, + ProcedureRouterRecord, + inferProcedureInput, + inferProcedureOutput, + ProcedureArgs, + AnySubscriptionProcedure, +} from "@trpc/server"; +import { + type inferObservableValue, + type Unsubscribable, +} from "@trpc/server/observable"; +import { inferTransformedProcedureOutput } from "@trpc/server/shared"; + +import type { + // DefinedUseQueryResult, + // DehydratedState, + // InfiniteQueryObserverSuccessResult, + InitialDataFunction, + // QueryObserverSuccessResult, + // QueryOptions, + // UseInfiniteQueryOptions, + // UseInfiniteQueryResult, + UseMutationOptions, + UseMutationReturnType, + UseQueryDefinedReturnType, + UseQueryOptions, + UseQueryReturnType, + // UseQueryResult, +} from "@tanstack/vue-query"; + +interface TRPCRequestOptions extends _TRPCRequestOptions { + abortOnUnmount?: boolean; +} + +type Resolver = ( + ...args: ProcedureArgs +) => Promise>; + +type SubscriptionResolver< + TProcedure extends AnyProcedure, + TRouter extends AnyRouter +> = ( + ...args: [ + input: ProcedureArgs[0], + opts: ProcedureArgs[1] & + Partial< + TRPCSubscriptionObserver< + inferObservableValue>, + TRPCClientErrorLike + > + > + ] +) => Unsubscribable; + +export type DecorateProcedure< + TProcedure extends AnyProcedure, + TRouter extends AnyRouter, + TPath extends string, +> = TProcedure extends AnyQueryProcedure + ? { + useQuery: ProcedureUseQuery; + query: Resolver; + } + : TProcedure extends AnyMutationProcedure + ? { + useMutation: ( + opts?: UseTRPCMutationOptions< + inferProcedureInput, + TRPCClientErrorLike, + inferTransformedProcedureOutput, + TContext + >, + ) => UseTRPCMutationResult< + inferTransformedProcedureOutput, + TRPCClientErrorLike, + inferProcedureInput, + TContext + >; + mutate: Resolver; + } + : TProcedure extends AnySubscriptionProcedure + ? { + subscribe: SubscriptionResolver; + } + : never; + +/** + * @internal + */ +export type DecoratedProcedureRecord< + TProcedures extends ProcedureRouterRecord, + TRouter extends AnyRouter, + TPath extends string = '' +> = { + [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter + ? DecoratedProcedureRecord + : TProcedures[TKey] extends AnyProcedure + ? DecorateProcedure + : never; +}; + +export interface ProcedureUseQuery< + TProcedure extends AnyProcedure, + TPath extends string +> { + < + TQueryFnData = inferTransformedProcedureOutput, + TData = inferTransformedProcedureOutput + >( + input: inferProcedureInput, + opts: DefinedUseTRPCQueryOptions< + TPath, + inferProcedureInput, + TQueryFnData, + TData, + TRPCClientErrorLike + > + ): DefinedUseTRPCQueryResult>; + + < + TQueryFnData = inferTransformedProcedureOutput, + TData = inferTransformedProcedureOutput + >( + input: inferProcedureInput, + opts?: UseTRPCQueryOptions< + TPath, + inferProcedureInput, + TQueryFnData, + TData, + TRPCClientErrorLike + > + ): UseTRPCQueryResult>; +} + +export interface DefinedUseTRPCQueryOptions< + TPath, + TInput, + TOutput, + TData, + TError +> extends UseTRPCQueryOptions { + initialData: TOutput | InitialDataFunction; +} + +export type DefinedUseTRPCQueryResult = UseQueryDefinedReturnType< + TData, + TError +> & + TRPCHookResult; + +export interface UseTRPCQueryOptions + extends UseQueryOptions, + TRPCUseQueryBaseOptions {} + +export type UseTRPCQueryResult = UseQueryReturnType & + TRPCHookResult; + +export interface DefinedUseTRPCQueryOptions< + TPath, + TInput, + TOutput, + TData, + TError +> extends UseTRPCQueryOptions { + initialData: TOutput | InitialDataFunction; +} + +export interface TRPCHookResult { + trpc: { + path: string; + }; +} + +export interface TRPCUseQueryBaseOptions { + /** + * tRPC-related options + */ + trpc?: TRPCReactRequestOptions; +} + +export interface TRPCReactRequestOptions + // For RQ, we use their internal AbortSignals instead of letting the user pass their own + extends Omit { + /** + * Opt out of SSR for this query by passing `ssr: false` + */ + ssr?: boolean; + /** + * Opt out or into aborting request on unmount + */ + abortOnUnmount?: boolean; +} + + +export interface UseTRPCMutationOptions< + TInput, + TError, + TOutput, + TContext = unknown, +> extends UseMutationOptions, + TRPCUseQueryBaseOptions {} + + export type UseTRPCMutationResult = + UseMutationReturnType & TRPCHookResult; + \ No newline at end of file diff --git a/src/client/index.ts b/src/client/index.ts index b2f0b84..6e5eeb2 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -78,4 +78,4 @@ export function createTRPCNuxtClient (opts: CreateTRP export { httpBatchLink, httpLink -} from './links' +} from '../client-shared/links' \ No newline at end of file diff --git a/tsup.config.ts b/tsup.config.ts index 4c2b48e..6d82cc7 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,11 +1,11 @@ import { defineConfig } from 'tsup' export default defineConfig({ - entry: ['src/index.ts', 'src/client/index.ts'], + entry: { "index": 'src/index.ts', "client/index": 'src/client/index.ts', "client-vue-query/index": 'src/client-vue-query/index.ts' }, format: ['cjs', 'esm'], splitting: false, clean: true, - external: ['#app', '#imports', /@trpc\/client/, /@trpc\/server/], + external: ['#app', '#imports', /@trpc\/client/, /@trpc\/client-vue-query/, /@trpc\/server/], dts: true, outExtension ({ format }) { return { From 1a52a62f878c6beeb4dc66c35376212838e671a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88zu=CC=88m=20Eldog=CC=86an?= Date: Wed, 18 Jan 2023 00:03:16 +0300 Subject: [PATCH 2/2] fix: change formatting and fix docs --- .../2.usage/3.simple-vue-query.md | 28 +++++----- src/client-vue-query/index.ts | 56 +++++++++---------- .../internals/getArrayQueryKey.ts | 14 ++--- src/client-vue-query/internals/getQueryKey.ts | 4 +- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/content/1.get-started/2.usage/3.simple-vue-query.md b/docs/content/1.get-started/2.usage/3.simple-vue-query.md index 73e4bad..d26b138 100644 --- a/docs/content/1.get-started/2.usage/3.simple-vue-query.md +++ b/docs/content/1.get-started/2.usage/3.simple-vue-query.md @@ -77,7 +77,7 @@ export const appRouter = router({ // export only the type definition of the API // None of the actual implementation is exposed to the client -export type AppRouter = typeof appRouter; +export type AppRouter = typeof appRouter // export API handler export default createNuxtApiHandler({ @@ -95,39 +95,39 @@ Create a strongly-typed plugin using your API's type signature. ::code-group ```ts [plugins/1.vue-query.ts] -import type { DehydratedState, VueQueryPluginOptions } from "@tanstack/vue-query"; -import { VueQueryPlugin, QueryClient, hydrate, dehydrate } from "@tanstack/vue-query"; +import type { DehydratedState, VueQueryPluginOptions } from "@tanstack/vue-query" +import { VueQueryPlugin, QueryClient, hydrate, dehydrate } from "@tanstack/vue-query" // Nuxt 3 app aliases -import { useState } from "#app"; +import { useState } from "#app" export default defineNuxtPlugin((nuxt) => { - const vueQueryState = useState("vue-query"); + const vueQueryState = useState("vue-query") // Modify your Vue Query global settings here const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5000 } }, }); - const options: VueQueryPluginOptions = { queryClient }; + const options: VueQueryPluginOptions = { queryClient } - nuxt.vueApp.use(VueQueryPlugin, options); + nuxt.vueApp.use(VueQueryPlugin, options) if (process.server) { nuxt.hooks.hook("app:rendered", () => { - vueQueryState.value = dehydrate(queryClient); + vueQueryState.value = dehydrate(queryClient) }); } if (process.client) { nuxt.hooks.hook("app:created", () => { - hydrate(queryClient, vueQueryState.value); + hydrate(queryClient, vueQueryState.value) }); } -}); +}) ``` ```ts [plugins/2.client.ts] -import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client' +import { createTRPCNuxtClient, httpBatchLink } from "trpc-nuxt/client-vue-query" import type { AppRouter } from '~/server/api/trpc/[trpc]' export default defineNuxtPlugin(() => { @@ -160,11 +160,11 @@ export default defineNuxtPlugin(() => { const { $client } = useNuxtApp() const { isLoading, isError, data, error } = $client.hello.useQuery({ text: 'client' }) -const { mutateAsync, isLoading, isError, data, error } = $client.login.useMutation(); +const { mutateAsync, isLoading, isError, data, error } = $client.login.useMutation() const handleLogin = async () => { - const person = await mutateAsync({ name: "John Doe" }); -}; + const person = await mutateAsync({ name: "John Doe" }) +} diff --git a/src/client-vue-query/index.ts b/src/client-vue-query/index.ts index 2d1dbad..f78c86f 100644 --- a/src/client-vue-query/index.ts +++ b/src/client-vue-query/index.ts @@ -1,23 +1,23 @@ -import { useQuery, useMutation } from "@tanstack/vue-query"; +import { useQuery, useMutation } from "@tanstack/vue-query" import { type CreateTRPCClientOptions, type inferRouterProxyClient, createTRPCProxyClient, -} from "@trpc/client"; -import { type AnyRouter } from "@trpc/server"; -import { createFlatProxy, createRecursiveProxy } from "@trpc/server/shared"; -import { type DecoratedProcedureRecord } from "./types"; +} from "@trpc/client" +import { type AnyRouter } from "@trpc/server" +import { createFlatProxy, createRecursiveProxy } from "@trpc/server/shared" +import { type DecoratedProcedureRecord } from "./types" // @ts-expect-error: Nuxt auto-imports -import { getCurrentInstance, onScopeDispose, useAsyncData } from "#imports"; -import { getQueryKey } from "./internals/getQueryKey"; -import { getArrayQueryKey } from "./internals/getArrayQueryKey"; +import { getCurrentInstance, onScopeDispose, useAsyncData } from "#imports" +import { getQueryKey } from "./internals/getQueryKey" +import { getArrayQueryKey } from "./internals/getArrayQueryKey" export function getClientArgs( pathAndInput: TPathAndInput, opts: TOptions ) { - const [path, input] = pathAndInput; - return [path, input, (opts as any)?.trpc] as const; + const [path, input] = pathAndInput + return [path, input, (opts as any)?.trpc] as const } export function createNuxtProxyDecoration( @@ -25,57 +25,57 @@ export function createNuxtProxyDecoration( client: inferRouterProxyClient ) { return createRecursiveProxy((opts) => { - const args = opts.args; + const args = opts.args - const pathCopy = [name, ...opts.path]; + const pathCopy = [name, ...opts.path] // The last arg is for instance `.useMutation` or `.useQuery()` - const lastArg = pathCopy.pop()!; + const lastArg = pathCopy.pop()! // The `path` ends up being something like `post.byId` - const path = pathCopy.join("."); + const path = pathCopy.join(".") if (lastArg === "useMutation") { - const actualPath = Array.isArray(path) ? path[0] : path; + const actualPath = Array.isArray(path) ? path[0] : path return useMutation({ ...args, mutationKey: [actualPath.split(".")], mutationFn: (input: any) => (client as any)[actualPath].mutate(input), - }); + }) } - const [input, ...rest] = args; + const [input, ...rest] = args - const queryKey = getQueryKey(path, input); + const queryKey = getQueryKey(path, input) // Expose queryKey helper if (lastArg === "getQueryKey") { - return getArrayQueryKey(queryKey, (rest[0] as any) ?? "any"); + return getArrayQueryKey(queryKey, (rest[0] as any) ?? "any") } if (lastArg === "useQuery") { - const { trpc, ...options } = rest[0] || ({} as any); + const { trpc, ...options } = rest[0] || ({} as any) return useQuery({ ...options, queryKey, queryFn: () => (client as any)[path].query(input, trpc), - }); + }) } - return (client as any)[path][lastArg](input); - }); + return (client as any)[path][lastArg](input) + }) } export function createTRPCNuxtClient( opts: CreateTRPCClientOptions ) { - const client = createTRPCProxyClient(opts); + const client = createTRPCProxyClient(opts) const decoratedClient = createFlatProxy((key) => { - return createNuxtProxyDecoration(key, client); - }) as DecoratedProcedureRecord; + return createNuxtProxyDecoration(key, client) + }) as DecoratedProcedureRecord - return decoratedClient; + return decoratedClient } -export { httpBatchLink, httpLink } from "../client-shared/links"; +export { httpBatchLink, httpLink } from "../client-shared/links" diff --git a/src/client-vue-query/internals/getArrayQueryKey.ts b/src/client-vue-query/internals/getArrayQueryKey.ts index ab0bf88..aa2cbfc 100644 --- a/src/client-vue-query/internals/getArrayQueryKey.ts +++ b/src/client-vue-query/internals/getArrayQueryKey.ts @@ -1,9 +1,9 @@ -export type QueryType = 'query' | 'infinite' | 'any'; +export type QueryType = 'query' | 'infinite' | 'any' export type QueryKey = [ string[], { input?: unknown; type?: Exclude }?, -]; +] /** * To allow easy interactions with groups of related queries, such as @@ -16,11 +16,11 @@ export function getArrayQueryKey( queryKey: string | [string] | [string, ...unknown[]] | unknown[], type: QueryType, ): QueryKey { - const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey]; - const [path, input] = queryKeyArrayed; + const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey] + const [path, input] = queryKeyArrayed const arrayPath = - typeof path !== 'string' || path === '' ? [] : path.split('.'); + typeof path !== 'string' || path === '' ? [] : path.split('.') // Construct a query key that is easy to destructure and flexible for // partial selecting etc. @@ -28,12 +28,12 @@ export function getArrayQueryKey( if (!input && (!type || type === 'any')) // for `utils.invalidate()` to match all queries (including vanilla react-query) // we don't want nested array if path is empty, i.e. `[]` instead of `[[]]` - return arrayPath.length ? [arrayPath] : ([] as unknown as QueryKey); + return arrayPath.length ? [arrayPath] : ([] as unknown as QueryKey) return [ arrayPath, { ...(typeof input !== 'undefined' && { input: input }), ...(type && type !== 'any' && { type: type }), }, - ]; + ] } diff --git a/src/client-vue-query/internals/getQueryKey.ts b/src/client-vue-query/internals/getQueryKey.ts index 87fda4b..45e9303 100644 --- a/src/client-vue-query/internals/getQueryKey.ts +++ b/src/client-vue-query/internals/getQueryKey.ts @@ -6,6 +6,6 @@ export function getQueryKey( path: string, input: unknown, ): [string] | [string, unknown] { - if (path.length) return input === undefined ? [path] : [path, input]; - return [] as unknown as [string]; + if (path.length) return input === undefined ? [path] : [path, input] + return [] as unknown as [string] }