mirror of
https://github.com/ArthurDanjou/trpc-nuxt.git
synced 2026-01-14 12:14:40 +01:00
add useTRPCAsyncData composable
This commit is contained in:
18
README.md
18
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
|
||||
</script>
|
||||
```
|
||||
|
||||
## `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)
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
const client = useClient()
|
||||
const { data, refresh } = await useAsyncData('getUser', () => client.query('getUsers'), {
|
||||
server: true,
|
||||
})
|
||||
|
||||
const addUser = async (username: string) => {
|
||||
try {
|
||||
await client.mutation('createUser', {
|
||||
username,
|
||||
})
|
||||
refresh()
|
||||
console.log('user added')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
const key = 'getUser'
|
||||
|
||||
const { data, pending, error } = await useTRPCAsyncData(key, () => client.query(key, {
|
||||
username: 'jcena',
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ data }}
|
||||
<div v-if="data">
|
||||
{{ JSON.stringify(data, null, 2) }}
|
||||
</div>
|
||||
<div v-else-if="error">
|
||||
asdx {{ JSON.stringify(error.data, null, 2) }}
|
||||
</div>
|
||||
</div>
|
||||
<button @click="addUser('marksx')">
|
||||
add
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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<any>) => {
|
||||
// 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 {}
|
||||
}
|
||||
|
||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -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'}
|
||||
|
||||
@@ -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<ModuleOptions>({
|
||||
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<ModuleOptions>({
|
||||
nuxt.hook('autoImports:extend', (imports) => {
|
||||
imports.push(
|
||||
{ name: 'useClient', from: clientPath },
|
||||
{ name: 'useTRPCAsyncData', from: join(runtimeDir, 'composables') },
|
||||
)
|
||||
})
|
||||
|
||||
@@ -42,21 +47,14 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
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)
|
||||
`)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -58,6 +58,8 @@ export function createTRPCHandler<Router extends AnyRouter>({
|
||||
|
||||
const $url = createURL(req.url)
|
||||
|
||||
event.context.hello = 'world'
|
||||
|
||||
const httpResponse = await resolveHTTPResponse({
|
||||
router,
|
||||
req: {
|
||||
|
||||
37
src/runtime/composables.ts
Normal file
37
src/runtime/composables.ts
Normal file
@@ -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<DataT> = _Transform<DataT, DataT>,
|
||||
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>,
|
||||
>(
|
||||
key: string,
|
||||
handler: (ctx?: NuxtApp) => Promise<DataT>,
|
||||
options: AsyncDataOptions<DataT, Transform, PickKeys> = {},
|
||||
): Promise<AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true>> {
|
||||
const serverError = useState<DataE | true | null>(`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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user