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
|
trpcURL: '/api/trpc', // defaults to /api/trpc
|
||||||
},
|
},
|
||||||
typescript: {
|
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>
|
</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
|
## Recipes
|
||||||
|
|
||||||
- [Validation](/recipes/validation.md)
|
- [Validation](/recipes/validation.md)
|
||||||
|
|||||||
@@ -46,7 +46,9 @@
|
|||||||
"bumpp": "^7.1.1",
|
"bumpp": "^7.1.1",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"nuxt": "^3.0.0-rc.3",
|
"nuxt": "^3.0.0-rc.3",
|
||||||
|
"ohash": "^0.1.0",
|
||||||
"pnpm": "^7.1.0",
|
"pnpm": "^7.1.0",
|
||||||
|
"superjson": "^1.9.1",
|
||||||
"trpc-nuxt": "workspace:*",
|
"trpc-nuxt": "workspace:*",
|
||||||
"zod": "^3.16.0"
|
"zod": "^3.16.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const client = useClient()
|
const client = useClient()
|
||||||
const { data, refresh } = await useAsyncData('getUser', () => client.query('getUsers'), {
|
|
||||||
server: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const addUser = async (username: string) => {
|
const key = 'getUser'
|
||||||
try {
|
|
||||||
await client.mutation('createUser', {
|
const { data, pending, error } = await useTRPCAsyncData(key, () => client.query(key, {
|
||||||
username,
|
username: 'jcena',
|
||||||
})
|
}))
|
||||||
refresh()
|
|
||||||
console.log('user added')
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<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>
|
</div>
|
||||||
<button @click="addUser('marksx')">
|
|
||||||
add
|
|
||||||
</button>
|
|
||||||
</template>
|
</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
|
// ~/server/trpc/index.ts
|
||||||
import { z } from 'zod'
|
import { ZodError, z } from 'zod'
|
||||||
import * as trpc from '@trpc/server'
|
import * as trpc from '@trpc/server'
|
||||||
import type { inferAsyncReturnType } 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 = [
|
const fakeUsers = [
|
||||||
{ id: 1, username: 'jcena' },
|
{ id: 1, username: 'jcena' },
|
||||||
@@ -37,9 +40,30 @@ export const router = trpc
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createContext = () => {
|
export const createContext = (event: CompatibilityEvent) => {
|
||||||
// ...
|
event.res.setHeader('x-ssr', 1)
|
||||||
return {
|
return {}
|
||||||
/** context data */
|
}
|
||||||
}
|
|
||||||
|
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
|
fs-extra: ^10.1.0
|
||||||
h3: ^0.7.8
|
h3: ^0.7.8
|
||||||
nuxt: ^3.0.0-rc.3
|
nuxt: ^3.0.0-rc.3
|
||||||
|
ohash: ^0.1.0
|
||||||
pathe: ^0.3.0
|
pathe: ^0.3.0
|
||||||
pnpm: ^7.1.0
|
pnpm: ^7.1.0
|
||||||
|
superjson: ^1.9.1
|
||||||
trpc-nuxt: workspace:*
|
trpc-nuxt: workspace:*
|
||||||
ufo: ^0.8.4
|
ufo: ^0.8.4
|
||||||
zod: ^3.16.0
|
zod: ^3.16.0
|
||||||
@@ -37,7 +39,9 @@ importers:
|
|||||||
bumpp: 7.1.1
|
bumpp: 7.1.1
|
||||||
eslint: 8.15.0
|
eslint: 8.15.0
|
||||||
nuxt: 3.0.0-rc.3
|
nuxt: 3.0.0-rc.3
|
||||||
|
ohash: 0.1.0
|
||||||
pnpm: 7.1.1
|
pnpm: 7.1.1
|
||||||
|
superjson: 1.9.1
|
||||||
trpc-nuxt: 'link:'
|
trpc-nuxt: 'link:'
|
||||||
zod: 3.16.0
|
zod: 3.16.0
|
||||||
|
|
||||||
@@ -1834,6 +1838,13 @@ packages:
|
|||||||
/cookie-es/0.5.0:
|
/cookie-es/0.5.0:
|
||||||
resolution: {integrity: sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==}
|
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:
|
/core-util-is/1.0.3:
|
||||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
|
|
||||||
@@ -3818,6 +3829,11 @@ packages:
|
|||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/is-what/4.1.7:
|
||||||
|
resolution: {integrity: sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==}
|
||||||
|
engines: {node: '>=12.13'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/is-wsl/2.2.0:
|
/is-wsl/2.2.0:
|
||||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -5935,6 +5951,13 @@ packages:
|
|||||||
postcss-selector-parser: 6.0.10
|
postcss-selector-parser: 6.0.10
|
||||||
dev: true
|
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:
|
/supports-color/5.5.0:
|
||||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { fileURLToPath } from 'url'
|
||||||
import { dirname, join } from 'pathe'
|
import { dirname, join } from 'pathe'
|
||||||
|
|
||||||
import { addServerHandler, defineNuxtModule } from '@nuxt/kit'
|
import { addServerHandler, defineNuxtModule } from '@nuxt/kit'
|
||||||
@@ -18,6 +19,9 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
trpcURL: '/api/trpc',
|
trpcURL: '/api/trpc',
|
||||||
},
|
},
|
||||||
async setup(options, nuxt) {
|
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 clientPath = join(nuxt.options.buildDir, 'trpc-client.ts')
|
||||||
const handlerPath = join(nuxt.options.buildDir, 'trpc-handler.ts')
|
const handlerPath = join(nuxt.options.buildDir, 'trpc-handler.ts')
|
||||||
|
|
||||||
@@ -29,6 +33,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
nuxt.hook('autoImports:extend', (imports) => {
|
nuxt.hook('autoImports:extend', (imports) => {
|
||||||
imports.push(
|
imports.push(
|
||||||
{ name: 'useClient', from: clientPath },
|
{ name: 'useClient', from: clientPath },
|
||||||
|
{ name: 'useTRPCAsyncData', from: join(runtimeDir, 'composables') },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -42,21 +47,14 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
url: '${options.baseURL}${options.trpcURL}',
|
url: '${options.baseURL}${options.trpcURL}',
|
||||||
})
|
})
|
||||||
|
|
||||||
const useClient = () => client
|
export const useClient = () => client
|
||||||
|
|
||||||
export {
|
|
||||||
useClient
|
|
||||||
}
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
await fs.writeFile(handlerPath, `
|
await fs.writeFile(handlerPath, `
|
||||||
import { createTRPCHandler } from 'trpc-nuxt/api'
|
import { createTRPCHandler } from 'trpc-nuxt/api'
|
||||||
import * as functions from '~/server/trpc'
|
import * as functions from '~/server/trpc'
|
||||||
|
|
||||||
export default createTRPCHandler({
|
export default createTRPCHandler(functions)
|
||||||
router: functions.router,
|
|
||||||
...functions
|
|
||||||
})
|
|
||||||
`)
|
`)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ export function createTRPCHandler<Router extends AnyRouter>({
|
|||||||
|
|
||||||
const $url = createURL(req.url)
|
const $url = createURL(req.url)
|
||||||
|
|
||||||
|
event.context.hello = 'world'
|
||||||
|
|
||||||
const httpResponse = await resolveHTTPResponse({
|
const httpResponse = await resolveHTTPResponse({
|
||||||
router,
|
router,
|
||||||
req: {
|
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