diff --git a/README.md b/README.md index 981e02a..b0ee1af 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ export default defineNuxtConfig({ trpcURL: '/api/trpc', // defaults to /api/trpc }, typescript: { - strict: true // set this to true to infer input/output types + strict: true // set this to true to make input/output types work } }) ``` @@ -64,6 +64,22 @@ console.log(farewell); // => 👈 goodbye ``` +## `useTRPCAsyncData` + +A composable that wraps Nuxt's [`useAsyncData`](https://v3.nuxtjs.org/api/composables/use-async-data/) with some modifications to have better error handlings. + +```ts +const path = 'hello' +const client = useClient() + +const { + data, + pending, + error, + refresh +} = await useTRPCAsyncData(path, () => client.query(path)) +``` + ## Recipes - [Validation](/recipes/validation.md) diff --git a/package.json b/package.json index a96e276..b6a37fd 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "bumpp": "^7.1.1", "eslint": "^8.14.0", "nuxt": "^3.0.0-rc.3", + "ohash": "^0.1.0", "pnpm": "^7.1.0", + "superjson": "^1.9.1", "trpc-nuxt": "workspace:*", "zod": "^3.16.0" }, diff --git a/playground/app.vue b/playground/app.vue index 2ca4a57..663fc3d 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -1,28 +1,20 @@ diff --git a/playground/plugins/trpc.ts b/playground/plugins/trpc.ts deleted file mode 100644 index 6c1edb8..0000000 --- a/playground/plugins/trpc.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineNuxtPlugin } from '#app' - -export default defineNuxtPlugin((nuxtApp) => { - // if (process.server) { - // nuxtApp.hooks.hook('app:rendered', () => { - // nuxtApp.ssrContext[''] - // }) - // } - - // if (process.client) { - // nuxtApp.hooks.hook('app:created', () => { - // console.log('app:created') - // }) - // } - if (nuxtApp.ssrContext) - console.log('hello', nuxtApp.ssrContext) -}) diff --git a/playground/server/trpc/index.ts b/playground/server/trpc/index.ts index a59b23b..434830f 100644 --- a/playground/server/trpc/index.ts +++ b/playground/server/trpc/index.ts @@ -1,7 +1,10 @@ // ~/server/trpc/index.ts -import { z } from 'zod' +import { ZodError, z } from 'zod' import * as trpc from '@trpc/server' import type { inferAsyncReturnType } from '@trpc/server' +import type { CompatibilityEvent } from 'h3' +import type { ResponseMetaFnPayload } from 'trpc-nuxt/api' +// import superjson from 'superjson' const fakeUsers = [ { id: 1, username: 'jcena' }, @@ -37,9 +40,30 @@ export const router = trpc }, }) -export const createContext = () => { - // ... - return { - /** context data */ - } +export const createContext = (event: CompatibilityEvent) => { + event.res.setHeader('x-ssr', 1) + return {} +} + +export const responseMeta = (opts: ResponseMetaFnPayload) => { + // const nuxtApp = useNuxtApp() + // const client = useClient() + // console.log(opts) + + // if (nuxtApp.nuxtState) { + // nuxtApp.nuxtState.trpc = client.runtime.transformer.serialize({ + // ctx: opts.ctx, + // errors: opts.errors, + // }) + // } + // else { + // nuxtApp.nuxtState = { + // trpc: client.runtime.transformer.serialize({ + // ctx: opts.ctx, + // errors: opts.errors, + // }), + // } + // } + + return {} } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fabe49a..449cf4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,8 +16,10 @@ importers: fs-extra: ^10.1.0 h3: ^0.7.8 nuxt: ^3.0.0-rc.3 + ohash: ^0.1.0 pathe: ^0.3.0 pnpm: ^7.1.0 + superjson: ^1.9.1 trpc-nuxt: workspace:* ufo: ^0.8.4 zod: ^3.16.0 @@ -37,7 +39,9 @@ importers: bumpp: 7.1.1 eslint: 8.15.0 nuxt: 3.0.0-rc.3 + ohash: 0.1.0 pnpm: 7.1.1 + superjson: 1.9.1 trpc-nuxt: 'link:' zod: 3.16.0 @@ -1834,6 +1838,13 @@ packages: /cookie-es/0.5.0: resolution: {integrity: sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==} + /copy-anything/3.0.2: + resolution: {integrity: sha512-CzATjGXzUQ0EvuvgOCI6A4BGOo2bcVx8B+eC2nF862iv9fopnPQwlrbACakNCHRIJbCSBj+J/9JeDf60k64MkA==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.7 + dev: true + /core-util-is/1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3818,6 +3829,11 @@ packages: call-bind: 1.0.2 dev: true + /is-what/4.1.7: + resolution: {integrity: sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==} + engines: {node: '>=12.13'} + dev: true + /is-wsl/2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -5935,6 +5951,13 @@ packages: postcss-selector-parser: 6.0.10 dev: true + /superjson/1.9.1: + resolution: {integrity: sha512-oT3HA2nPKlU1+5taFgz/HDy+GEaY+CWEbLzaRJVD4gZ7zMVVC4GDNFdgvAZt6/VuIk6D2R7RtPAiCHwmdzlMmg==} + engines: {node: '>=10'} + dependencies: + copy-anything: 3.0.2 + dev: true + /supports-color/5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} diff --git a/src/module.ts b/src/module.ts index 6e5119e..8c002f6 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'url' import { dirname, join } from 'pathe' import { addServerHandler, defineNuxtModule } from '@nuxt/kit' @@ -18,6 +19,9 @@ export default defineNuxtModule({ trpcURL: '/api/trpc', }, async setup(options, nuxt) { + const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) + nuxt.options.build.transpile.push(runtimeDir) + const clientPath = join(nuxt.options.buildDir, 'trpc-client.ts') const handlerPath = join(nuxt.options.buildDir, 'trpc-handler.ts') @@ -29,6 +33,7 @@ export default defineNuxtModule({ nuxt.hook('autoImports:extend', (imports) => { imports.push( { name: 'useClient', from: clientPath }, + { name: 'useTRPCAsyncData', from: join(runtimeDir, 'composables') }, ) }) @@ -42,21 +47,14 @@ export default defineNuxtModule({ url: '${options.baseURL}${options.trpcURL}', }) - const useClient = () => client - - export { - useClient - } + export const useClient = () => client `) await fs.writeFile(handlerPath, ` import { createTRPCHandler } from 'trpc-nuxt/api' import * as functions from '~/server/trpc' - export default createTRPCHandler({ - router: functions.router, - ...functions - }) + export default createTRPCHandler(functions) `) }, }) diff --git a/src/runtime/api.ts b/src/runtime/api.ts index fbcfaa2..5101a1a 100644 --- a/src/runtime/api.ts +++ b/src/runtime/api.ts @@ -58,6 +58,8 @@ export function createTRPCHandler({ const $url = createURL(req.url) + event.context.hello = 'world' + const httpResponse = await resolveHTTPResponse({ router, req: { diff --git a/src/runtime/composables.ts b/src/runtime/composables.ts new file mode 100644 index 0000000..9e9bcf1 --- /dev/null +++ b/src/runtime/composables.ts @@ -0,0 +1,37 @@ +import type { + AsyncData, + KeyOfRes, + PickFrom, + _Transform, +} from 'nuxt/dist/app/composables/asyncData' +import type { AsyncDataOptions, NuxtApp } from '#app' +// @ts-expect-error: Resolved by Nuxt +import { useAsyncData, useState } from '#imports' + +export async function useTRPCAsyncData< + DataT, + DataE = Error, + Transform extends _Transform = _Transform, + PickKeys extends KeyOfRes = KeyOfRes, +>( + key: string, + handler: (ctx?: NuxtApp) => Promise, + options: AsyncDataOptions = {}, +): Promise, PickKeys>, DataE | null | true>> { + const serverError = useState(`error-${key}`, () => null) + const { error, data, ...rest } = await useAsyncData(key, handler, options) + + // Only set the value on server and if serverError is empty + if (process.server && error.value && !serverError.value) + serverError.value = error.value as DataE | true | null + + // Clear error if data is available + if (data.value) + serverError.value = null + + return { + ...rest, + data, + error: serverError, + } +}