mirror of
https://github.com/ArthurDanjou/trpc-nuxt.git
synced 2026-01-25 01:10:37 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47ba58c0b7 | ||
|
|
a458ed9b3f | ||
|
|
09d043d49b | ||
|
|
3e47e2a389 | ||
|
|
f8d82c4af1 | ||
|
|
e31578bf97 | ||
|
|
88c77f6e8f | ||
|
|
eafc476544 | ||
|
|
dc86c0252e | ||
|
|
a3dadb9e50 | ||
|
|
5331535237 | ||
|
|
ae0abd2b45 | ||
|
|
273215c9e1 | ||
|
|
812ceda4a0 | ||
|
|
c48556e24e | ||
|
|
f3e0165dd8 | ||
|
|
87ed453425 | ||
|
|
d890633cb1 | ||
|
|
a48ee2551e |
28
README.md
28
README.md
@@ -4,6 +4,17 @@
|
|||||||
|
|
||||||
End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications.
|
End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<figure>
|
||||||
|
<img src="https://i.imgur.com/AjmNUxj.gif" alt="Demo" />
|
||||||
|
<figcaption>
|
||||||
|
<p align="center">
|
||||||
|
The client above is <strong>not</strong> importing any code from the server, only its type declarations.
|
||||||
|
</p>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</p>
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -63,11 +74,9 @@ const client = useClient() // auto-imported
|
|||||||
|
|
||||||
const users = await client.query('getUsers')
|
const users = await client.query('getUsers')
|
||||||
|
|
||||||
const addUser = async () => {
|
const newUser = await client.mutation('createUser', {
|
||||||
const newUser = await client.mutation('createUser', {
|
name: 'wagmi'
|
||||||
name: 'wagmi'
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## useAsyncQuery
|
## useAsyncQuery
|
||||||
@@ -86,13 +95,13 @@ const {
|
|||||||
refresh
|
refresh
|
||||||
} = await useAsyncQuery(['getUser', { id: 69 }], {
|
} = await useAsyncQuery(['getUser', { id: 69 }], {
|
||||||
// pass useAsyncData options here
|
// pass useAsyncData options here
|
||||||
server: true
|
lazy: false
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
trpc-nuxt accepts the following options exposed under `~/server/trpc.index.ts`:
|
trpc-nuxt accepts the following options exposed under `~/server/trpc/index.ts`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import * as trpc from '@trpc/server'
|
import * as trpc from '@trpc/server'
|
||||||
@@ -131,11 +140,16 @@ export const onError = (payload: OnErrorPayload<typeof router>) => {
|
|||||||
|
|
||||||
- [Validation](/recipes/validation.md)
|
- [Validation](/recipes/validation.md)
|
||||||
- [Authorization](/recipes/authorization.md)
|
- [Authorization](/recipes/authorization.md)
|
||||||
|
- [Merging Routers](/recipes/merging-routers.md)
|
||||||
- [Error Handling](/recipes/error-handling.md)
|
- [Error Handling](/recipes/error-handling.md)
|
||||||
- [Error Formatting](/recipes/error-formatting.md)
|
- [Error Formatting](/recipes/error-formatting.md)
|
||||||
|
|
||||||
Learn more about tRPC.io [here](https://trpc.io/docs).
|
Learn more about tRPC.io [here](https://trpc.io/docs).
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "trpc-nuxt",
|
"name": "trpc-nuxt",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.1.1",
|
"version": "0.1.3",
|
||||||
"packageManager": "pnpm@7.1.0",
|
"packageManager": "pnpm@7.1.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/module.cjs",
|
"main": "./dist/module.cjs",
|
||||||
"types": "./dist/types.d.ts",
|
"types": "./dist/types.d.ts",
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"prepublishOnly": "nr build",
|
"prepublishOnly": "nr build",
|
||||||
"build": "nuxt-module-build",
|
"build": "nuxt-module-build",
|
||||||
"play": "nr build && nuxi dev playground",
|
"play": "nr build && nuxi dev playground",
|
||||||
|
"build:playground": "nuxi build playground",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"release": "bumpp --commit --push --tag && pnpm publish",
|
"release": "bumpp --commit --push --tag && pnpm publish",
|
||||||
@@ -35,7 +36,6 @@
|
|||||||
"@trpc/client": "^9.23.3",
|
"@trpc/client": "^9.23.3",
|
||||||
"@trpc/server": "^9.23.2",
|
"@trpc/server": "^9.23.2",
|
||||||
"defu": "^6.0.0",
|
"defu": "^6.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
|
||||||
"h3": "^0.7.8",
|
"h3": "^0.7.8",
|
||||||
"pathe": "^0.3.0",
|
"pathe": "^0.3.0",
|
||||||
"ufo": "^0.8.4"
|
"ufo": "^0.8.4"
|
||||||
@@ -44,12 +44,11 @@
|
|||||||
"@antfu/eslint-config": "^0.23.1",
|
"@antfu/eslint-config": "^0.23.1",
|
||||||
"@antfu/ni": "^0.16.2",
|
"@antfu/ni": "^0.16.2",
|
||||||
"@nuxt/module-builder": "latest",
|
"@nuxt/module-builder": "latest",
|
||||||
"@types/fs-extra": "^9.0.13",
|
|
||||||
"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",
|
"ohash": "^0.1.0",
|
||||||
"pnpm": "^7.1.0",
|
"pnpm": "^7.1.1",
|
||||||
"superjson": "^1.9.1",
|
"superjson": "^1.9.1",
|
||||||
"trpc-nuxt": "workspace:*",
|
"trpc-nuxt": "workspace:*",
|
||||||
"zod": "^3.16.0"
|
"zod": "^3.16.0"
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const { data, pending, error } = await useAsyncQuery(['getUser', { username: 'jcena' }], {
|
|
||||||
lazy: false,
|
|
||||||
transform: data => data || null,
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="pending">
|
<div>
|
||||||
Loading...
|
<NuxtPage />
|
||||||
</div>
|
|
||||||
<div v-else-if="error">
|
|
||||||
Error: {{ JSON.stringify(error, null, 2) }}
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
User {{ JSON.stringify(data, null, 2) }}
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineNuxtConfig } from 'nuxt'
|
import { defineNuxtConfig } from 'nuxt'
|
||||||
import Module from '..'
|
import Module from '../src/module'
|
||||||
|
|
||||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
|
|||||||
55
playground/pages/index.vue
Normal file
55
playground/pages/index.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const client = useClient()
|
||||||
|
const { data: todos, pending, error, refresh } = await useAsyncQuery(['getTodos'])
|
||||||
|
|
||||||
|
const addTodo = async () => {
|
||||||
|
const title = Math.random().toString(36).slice(2, 7)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await client.mutation('addTodo', {
|
||||||
|
id: Date.now(),
|
||||||
|
userId: 69,
|
||||||
|
title,
|
||||||
|
completed: false,
|
||||||
|
})
|
||||||
|
console.log('Todo: ', result)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="pending">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error?.data?.code">
|
||||||
|
Error: {{ error.data.code }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<ul>
|
||||||
|
<li v-for="t in todos.slice(0, 10)" :key="t.id">
|
||||||
|
<NuxtLink :class="{ completed: t.completed }" :to="`/todo/${t.id}`">
|
||||||
|
Title: {{ t.title }}
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button @click="addTodo">
|
||||||
|
Add Todo
|
||||||
|
</button>
|
||||||
|
<button @click="() => refresh()">
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
18
playground/pages/todo/[id].vue
Normal file
18
playground/pages/todo/[id].vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
const { data: todo, pending, error } = await useAsyncQuery(['getTodo', Number(route.params.id)])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="pending">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error?.data?.code">
|
||||||
|
{{ error.data.code }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
ID: {{ todo.id }} <br>
|
||||||
|
Title: {{ todo.title }} <br>
|
||||||
|
Completed: {{ todo.completed }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,82 +1,35 @@
|
|||||||
// ~/server/trpc/index.ts
|
|
||||||
import { ZodError, z } from 'zod'
|
|
||||||
import * as trpc from '@trpc/server'
|
import * as trpc from '@trpc/server'
|
||||||
import type { inferAsyncReturnType } from '@trpc/server'
|
import { z } from 'zod'
|
||||||
import type { CompatibilityEvent } from 'h3'
|
|
||||||
import type { ResponseMetaFnPayload } from 'trpc-nuxt/api'
|
|
||||||
// import superjson from 'superjson'
|
|
||||||
|
|
||||||
const fakeUsers = [
|
const baseURL = 'https://jsonplaceholder.typicode.com'
|
||||||
{ id: 1, username: 'jcena' },
|
|
||||||
{ id: 2, username: 'dbatista' },
|
|
||||||
{ id: 3, username: 'jbiden' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const router = trpc
|
const TodoShape = z.object({
|
||||||
.router<inferAsyncReturnType<typeof createContext>>()
|
userId: z.number(),
|
||||||
.formatError(({ shape, error }) => {
|
id: z.number(),
|
||||||
return {
|
title: z.string(),
|
||||||
...shape,
|
completed: z.boolean(),
|
||||||
data: {
|
})
|
||||||
...shape.data,
|
|
||||||
zodError:
|
export type Todo = z.infer<typeof TodoShape>
|
||||||
error.code === 'BAD_REQUEST'
|
|
||||||
&& error.cause instanceof ZodError
|
export const router = trpc.router()
|
||||||
? error.cause.flatten()
|
.query('getTodos', {
|
||||||
: null,
|
async resolve() {
|
||||||
},
|
return await $fetch<Todo[]>(`${baseURL}/todos`)
|
||||||
}
|
|
||||||
})
|
|
||||||
.query('getUsers', {
|
|
||||||
resolve() {
|
|
||||||
return fakeUsers
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query('getUser', {
|
.query('getTodo', {
|
||||||
// validate input with Zod
|
input: z.number(),
|
||||||
input: z.object({
|
async resolve(req) {
|
||||||
username: z.string().min(5),
|
return await $fetch<Todo>(`${baseURL}/todos/${req.input}`)
|
||||||
}),
|
|
||||||
resolve(req) {
|
|
||||||
return fakeUsers.find(i => i.username === req.input.username) ?? null
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.mutation('createUser', {
|
.mutation('addTodo', {
|
||||||
input: z.object({ username: z.string().min(5) }),
|
input: TodoShape,
|
||||||
resolve(req) {
|
async resolve(req) {
|
||||||
const newUser = {
|
return await $fetch<Todo>(`${baseURL}/todos`, {
|
||||||
id: fakeUsers.length + 1,
|
method: 'POST',
|
||||||
username: req.input.username,
|
body: req.input,
|
||||||
}
|
})
|
||||||
fakeUsers.push(newUser)
|
|
||||||
return newUser
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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 {}
|
|
||||||
}
|
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -10,16 +10,14 @@ importers:
|
|||||||
'@nuxt/module-builder': latest
|
'@nuxt/module-builder': latest
|
||||||
'@trpc/client': ^9.23.3
|
'@trpc/client': ^9.23.3
|
||||||
'@trpc/server': ^9.23.2
|
'@trpc/server': ^9.23.2
|
||||||
'@types/fs-extra': ^9.0.13
|
|
||||||
bumpp: ^7.1.1
|
bumpp: ^7.1.1
|
||||||
defu: ^6.0.0
|
defu: ^6.0.0
|
||||||
eslint: ^8.14.0
|
eslint: ^8.14.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
|
ohash: ^0.1.0
|
||||||
pathe: ^0.3.0
|
pathe: ^0.3.0
|
||||||
pnpm: ^7.1.0
|
pnpm: ^7.1.1
|
||||||
superjson: ^1.9.1
|
superjson: ^1.9.1
|
||||||
trpc-nuxt: workspace:*
|
trpc-nuxt: workspace:*
|
||||||
ufo: ^0.8.4
|
ufo: ^0.8.4
|
||||||
@@ -29,7 +27,6 @@ importers:
|
|||||||
'@trpc/client': 9.23.4_@trpc+server@9.23.4
|
'@trpc/client': 9.23.4_@trpc+server@9.23.4
|
||||||
'@trpc/server': 9.23.4
|
'@trpc/server': 9.23.4
|
||||||
defu: 6.0.0
|
defu: 6.0.0
|
||||||
fs-extra: 10.1.0
|
|
||||||
h3: 0.7.8
|
h3: 0.7.8
|
||||||
pathe: 0.3.0
|
pathe: 0.3.0
|
||||||
ufo: 0.8.4
|
ufo: 0.8.4
|
||||||
@@ -37,7 +34,6 @@ importers:
|
|||||||
'@antfu/eslint-config': 0.23.1_eslint@8.15.0
|
'@antfu/eslint-config': 0.23.1_eslint@8.15.0
|
||||||
'@antfu/ni': 0.16.2
|
'@antfu/ni': 0.16.2
|
||||||
'@nuxt/module-builder': 0.1.7
|
'@nuxt/module-builder': 0.1.7
|
||||||
'@types/fs-extra': 9.0.13
|
|
||||||
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
|
||||||
@@ -945,12 +941,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
|
resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/fs-extra/9.0.13:
|
|
||||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 17.0.34
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/jsdom/16.2.14:
|
/@types/jsdom/16.2.14:
|
||||||
resolution: {integrity: sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==}
|
resolution: {integrity: sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3216,6 +3206,7 @@ packages:
|
|||||||
graceful-fs: 4.2.10
|
graceful-fs: 4.2.10
|
||||||
jsonfile: 6.1.0
|
jsonfile: 6.1.0
|
||||||
universalify: 2.0.0
|
universalify: 2.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/fs-memo/1.2.0:
|
/fs-memo/1.2.0:
|
||||||
resolution: {integrity: sha512-YEexkCpL4j03jn5SxaMHqcO6IuWuqm8JFUYhyCep7Ao89JIYmB8xoKhK7zXXJ9cCaNXpyNH5L3QtAmoxjoHW2w==}
|
resolution: {integrity: sha512-YEexkCpL4j03jn5SxaMHqcO6IuWuqm8JFUYhyCep7Ao89JIYmB8xoKhK7zXXJ9cCaNXpyNH5L3QtAmoxjoHW2w==}
|
||||||
@@ -3930,6 +3921,7 @@ packages:
|
|||||||
universalify: 2.0.0
|
universalify: 2.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs: 4.2.10
|
graceful-fs: 4.2.10
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jsx-ast-utils/3.3.0:
|
/jsx-ast-utils/3.3.0:
|
||||||
resolution: {integrity: sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==}
|
resolution: {integrity: sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==}
|
||||||
@@ -6333,6 +6325,7 @@ packages:
|
|||||||
/universalify/2.0.0:
|
/universalify/2.0.0:
|
||||||
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
|
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/unplugin/0.6.3:
|
/unplugin/0.6.3:
|
||||||
resolution: {integrity: sha512-CoW88FQfCW/yabVc4bLrjikN9HC8dEvMU4O7B6K2jsYMPK0l6iAnd9dpJwqGcmXJKRCU9vwSsy653qg+RK0G6A==}
|
resolution: {integrity: sha512-CoW88FQfCW/yabVc4bLrjikN9HC8dEvMU4O7B6K2jsYMPK0l6iAnd9dpJwqGcmXJKRCU9vwSsy653qg+RK0G6A==}
|
||||||
|
|||||||
46
recipes/merging-routers.md
Normal file
46
recipes/merging-routers.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Merging Routers
|
||||||
|
|
||||||
|
Writing all API-code in your code in the same file is not a great idea. It's easy to merge routers with other routers.
|
||||||
|
|
||||||
|
Define your routes:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ~/server/trpc/routes/posts.ts
|
||||||
|
export const posts = trpc.router()
|
||||||
|
.query('list', {
|
||||||
|
resolve() {
|
||||||
|
// ..
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ~/server/trpc/routes/users.ts
|
||||||
|
export const users = trpc.router()
|
||||||
|
.query('list', {
|
||||||
|
resolve() {
|
||||||
|
// ..
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ~/server/trpc/index.ts
|
||||||
|
import { users } from './routes/users'
|
||||||
|
import { posts } from './routes/posts'
|
||||||
|
|
||||||
|
export const router = trpc.router()
|
||||||
|
.merge('user.', users) // prefix user procedures with "user."
|
||||||
|
.merge('post.', posts) // prefix post procedures with "post."
|
||||||
|
```
|
||||||
|
|
||||||
|
and use it like this:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data: users } = await useAsyncQuery(['user.list'])
|
||||||
|
const { data: posts } = await useAsyncQuery(['post.list'])
|
||||||
|
</script>
|
||||||
|
```
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { dirname, join } from 'pathe'
|
import { join } from 'pathe'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
|
|
||||||
import { addServerHandler, defineNuxtModule } from '@nuxt/kit'
|
import { addServerHandler, addTemplate, defineNuxtModule } from '@nuxt/kit'
|
||||||
import fs from 'fs-extra'
|
|
||||||
|
|
||||||
export interface ModuleOptions {
|
export interface ModuleOptions {
|
||||||
baseURL: string
|
baseURL: string
|
||||||
@@ -21,10 +20,10 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
},
|
},
|
||||||
async setup(options, nuxt) {
|
async setup(options, nuxt) {
|
||||||
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
|
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
|
||||||
nuxt.options.build.transpile.push(runtimeDir)
|
nuxt.options.build.transpile.push(runtimeDir, '#build/trpc-client', '#build/trpc-handler')
|
||||||
|
|
||||||
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')
|
||||||
|
const trpcOptionsPath = join(nuxt.options.rootDir, 'server/trpc')
|
||||||
|
|
||||||
// Final resolved configuration
|
// Final resolved configuration
|
||||||
const finalConfig = nuxt.options.runtimeConfig.public.trpc = defu(nuxt.options.runtimeConfig.public.trpc, {
|
const finalConfig = nuxt.options.runtimeConfig.public.trpc = defu(nuxt.options.runtimeConfig.public.trpc, {
|
||||||
@@ -32,43 +31,53 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
trpcURL: options.trpcURL,
|
trpcURL: options.trpcURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
nuxt.hook('autoImports:extend', (imports) => {
|
||||||
|
imports.push(
|
||||||
|
{ name: 'useClient', from: '#build/trpc-client' },
|
||||||
|
{ name: 'useAsyncQuery', from: join(runtimeDir, 'client') },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
addServerHandler({
|
addServerHandler({
|
||||||
route: `${finalConfig.trpcURL}/*`,
|
route: `${finalConfig.trpcURL}/*`,
|
||||||
handler: handlerPath,
|
handler: handlerPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
nuxt.hook('autoImports:extend', (imports) => {
|
addTemplate({
|
||||||
imports.push(
|
filename: 'trpc-client.ts',
|
||||||
{ name: 'useClient', from: clientPath },
|
write: true,
|
||||||
{ name: 'useAsyncQuery', from: join(runtimeDir, 'client') },
|
getContents() {
|
||||||
)
|
return `
|
||||||
|
import * as trpc from '@trpc/client'
|
||||||
|
import type { router } from '${trpcOptionsPath}'
|
||||||
|
|
||||||
|
const client = trpc.createTRPCClient<typeof router>({
|
||||||
|
url: '${finalConfig.baseURL}${finalConfig.trpcURL}',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useClient = () => client
|
||||||
|
`
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await fs.ensureDir(dirname(clientPath))
|
addTemplate({
|
||||||
|
filename: 'trpc-handler.ts',
|
||||||
|
write: true,
|
||||||
|
getContents() {
|
||||||
|
return `
|
||||||
|
import { createTRPCHandler } from 'trpc-nuxt/api'
|
||||||
|
import { useRuntimeConfig } from '#imports'
|
||||||
|
import * as functions from '${trpcOptionsPath}'
|
||||||
|
|
||||||
await fs.writeFile(clientPath, `
|
const { trpc: { trpcURL } } = useRuntimeConfig().public
|
||||||
import * as trpc from '@trpc/client'
|
|
||||||
import type { router } from '~/server/trpc'
|
|
||||||
|
|
||||||
const client = trpc.createTRPCClient<typeof router>({
|
export default createTRPCHandler({
|
||||||
url: '${finalConfig.baseURL}${finalConfig.trpcURL}',
|
...functions,
|
||||||
})
|
trpcURL
|
||||||
|
})
|
||||||
export const useClient = () => client
|
`
|
||||||
`)
|
},
|
||||||
|
})
|
||||||
await fs.writeFile(handlerPath, `
|
|
||||||
import { createTRPCHandler } from 'trpc-nuxt/api'
|
|
||||||
import { useRuntimeConfig } from '#imports'
|
|
||||||
import * as functions from '~/server/trpc'
|
|
||||||
|
|
||||||
const { trpc: { trpcURL } } = useRuntimeConfig().public
|
|
||||||
|
|
||||||
export default createTRPCHandler({
|
|
||||||
...functions,
|
|
||||||
trpcURL
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ 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: {
|
||||||
|
|||||||
@@ -8,11 +8,8 @@ import type {
|
|||||||
import type { ProcedureRecord, inferHandlerInput, inferProcedureInput, inferProcedureOutput } from '@trpc/server'
|
import type { ProcedureRecord, inferHandlerInput, inferProcedureInput, inferProcedureOutput } from '@trpc/server'
|
||||||
import type { TRPCClientErrorLike } from '@trpc/client'
|
import type { TRPCClientErrorLike } from '@trpc/client'
|
||||||
import { objectHash } from 'ohash'
|
import { objectHash } from 'ohash'
|
||||||
// @ts-expect-error: Resolved by Nuxt
|
import { useAsyncData, useState } from '#app'
|
||||||
import { useAsyncData, useState } from '#imports'
|
|
||||||
// @ts-expect-error: Resolved by Nuxt
|
|
||||||
import { useClient } from '#build/trpc-client'
|
import { useClient } from '#build/trpc-client'
|
||||||
// @ts-expect-error: Resolved by Nuxt
|
|
||||||
import type { router } from '~/server/trpc'
|
import type { router } from '~/server/trpc'
|
||||||
|
|
||||||
type AppRouter = typeof router
|
type AppRouter = typeof router
|
||||||
@@ -49,7 +46,6 @@ export async function useAsyncQuery<
|
|||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
// @ts-expect-error: Resolved by Nuxt
|
|
||||||
if (process.server && error.value && !serverError.value)
|
if (process.server && error.value && !serverError.value)
|
||||||
serverError.value = error.value as any
|
serverError.value = error.value as any
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user