Compare commits

...

41 Commits

Author SHA1 Message Date
wobsoriano
2c2df9e2bd chore: release v0.4.3 2022-11-24 13:12:18 -08:00
wobsoriano
5cf1acfa7e feat(deps): update minimum required @trpc/server and @trpc/nuxt version to 10.0.0 2022-11-24 13:12:14 -08:00
wobsoriano
f67f7fc5f4 update docs 2022-11-24 13:10:10 -08:00
wobsoriano
e11edc59eb update docs 2022-11-24 13:08:28 -08:00
wobsoriano
e522a59a4c update docs 2022-11-24 13:07:25 -08:00
wobsoriano
1d7be4642d update docs 2022-11-24 13:07:13 -08:00
wobsoriano
3552017e4f update docs 2022-11-24 13:05:32 -08:00
wobsoriano
de690a7914 update docs 2022-11-24 13:00:40 -08:00
wobsoriano
d2666650de update docs 2022-11-24 12:57:29 -08:00
wobsoriano
e3d35c6b04 update docs 2022-11-24 12:56:59 -08:00
wobsoriano
b569afde50 update docs 2022-11-24 12:53:17 -08:00
wobsoriano
d4f3942fff update docs 2022-11-24 12:52:17 -08:00
wobsoriano
68003e9c3e update docs 2022-11-24 12:51:28 -08:00
Robert Soriano
b27938f108 Merge pull request #44 from benfavre/patch-1
Update package.json
2022-11-21 20:15:33 -08:00
Webdesign29
9e443ac559 Update package.json
Fix calling --watch for tsup when using "dev" command
2022-11-19 22:11:12 +01:00
wobsoriano
8ba002407a update lockfile 2022-11-17 02:44:00 -08:00
wobsoriano
f061b0531f chore: release v0.4.2 2022-11-17 02:42:20 -08:00
wobsoriano
f08b724df7 feat: get req,res values from event.node 2022-11-17 02:42:01 -08:00
wobsoriano
4085d5fe26 fix: request types 2022-11-17 02:41:01 -08:00
wobsoriano
13713343ce fix: request types 2022-11-17 02:39:45 -08:00
wobsoriano
d6f11bb301 feat(deps): bump h3 to 1.0.1 2022-11-17 02:38:27 -08:00
wobsoriano
c5c1f1cdba feat(deps): bump h3, ohash and ufo to 1.0.0 2022-11-17 02:38:17 -08:00
wobsoriano
0681dd554d Merge branch 'next' of https://github.com/wobsoriano/trpc-nuxt into next 2022-11-16 08:48:14 -08:00
wobsoriano
e7a76694af update docs 2022-11-16 08:48:09 -08:00
wobsoriano
da75c18ebe update local nuxt dep to stable 2022-11-16 08:42:45 -08:00
wobsoriano
6289575900 bump local trpc deps to rc.8 2022-11-16 08:40:35 -08:00
Robert Soriano
ac212672e4 Update 2.server-side-calls.md 2022-11-14 12:26:48 -08:00
Robert Soriano
b8e2a83e26 Merge pull request #42 from nkhdo/patch-1
Update 2.server-side-calls.md
2022-11-14 10:43:06 -08:00
Hoang Do
fd98b0d414 Update 2.server-side-calls.md
Fix wrong variable import
2022-11-15 00:46:06 +07:00
wobsoriano
20ab235856 docs: add auth tips 2022-11-13 11:18:57 -08:00
wobsoriano
7205c356f8 docs: update aborting procs 2022-11-13 11:08:28 -08:00
wobsoriano
e14827e0c7 docs: add aborting procs 2022-11-13 11:05:47 -08:00
wobsoriano
eea6406d5a docs: update example 2022-11-13 10:58:27 -08:00
wobsoriano
2d3409a25b docs: update example 2022-11-13 10:58:08 -08:00
wobsoriano
7a71a6e128 docs: add mutation example 2022-11-13 10:55:23 -08:00
wobsoriano
d8e89297cd docs: typo fix 2022-11-13 10:41:35 -08:00
wobsoriano
6768205318 docs: update composables 2022-11-13 10:41:22 -08:00
wobsoriano
c20a092a31 docs: add tips section 2022-11-13 10:37:24 -08:00
wobsoriano
64df8fb7ad infer error in playground 2022-11-13 09:43:23 -08:00
wobsoriano
d5567e5826 remove character 2022-11-12 21:16:19 -08:00
wobsoriano
6b898e836c update readme 2022-11-12 21:15:19 -08:00
18 changed files with 1073 additions and 1466 deletions

View File

@@ -2,6 +2,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/3AZlBZH.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>
Docs: https://trpc-nuxt.vercel.app Docs: https://trpc-nuxt.vercel.app
For version 3 of this module (tRPC v9, auto-imports, auto handlers), [go here](https://github.com/wobsoriano/trpc-nuxt/tree/v3). For version 3 of this module (tRPC v9, auto-imports, auto handlers), [go here](https://github.com/wobsoriano/trpc-nuxt/tree/v3).

View File

@@ -5,15 +5,14 @@ export default defineAppConfig({
alt: 'tRPC-Nuxt cover', alt: 'tRPC-Nuxt cover',
url: 'https://trpc-nuxt.vercel.app', url: 'https://trpc-nuxt.vercel.app',
debug: false, debug: false,
socials: {
github: 'wobsoriano/trpc-nuxt'
},
aside: { aside: {
level: 1 level: 1
}, },
footer: { footer: {
credits: { credits: true,
icon: 'IconDocus',
text: 'Powered by Docus',
href: 'https://docus.com'
},
icons: [ icons: [
{ {
label: 'NuxtJS', label: 'NuxtJS',

View File

@@ -33,8 +33,6 @@ For making typesafe API calls from your client.
Most examples use [Zod](https://github.com/colinhacks/zod) for input validation and tRPC.io highly recommends it, though it isn't required. Most examples use [Zod](https://github.com/colinhacks/zod) for input validation and tRPC.io highly recommends it, though it isn't required.
::
## Next Steps ## Next Steps
Now that you've installed the required dependencies, you are ready to start building your application. Now that you've installed the required dependencies, you are ready to start building your application.

View File

@@ -0,0 +1,40 @@
---
title: Composables
---
# Composables
It is often useful to wrap functionality of your `@trpc/client` api within other functions. For this purpose, it's necessary to be able to infer input types and output types generated by your `@trpc/server` router.
## Inference Helpers
`@trpc/server` exports the following helper types to assist with inferring these types from the `AppRouter` exported by your `@trpc/server` router:
- `inferRouterInputs<TRouter>`
- `inferRouterOutputs<TRouter>`
Let's assume we have this example query wrapped within Nuxt's [useAsyncData](https://v3.nuxtjs.org/api/composables/use-async-data/):
```ts
const { data, error } = await useAsyncData(() => $client.todo.getTodos.query())
```
We can wrap this in a composable and also set the client error types:
```ts [composables/useGetTodos.ts]
import { TRPCClientError } from '@trpc/client'
import type { inferRouterOutputs } from '@trpc/server'
import type { AppRouter } from '@/server/trpc/routers'
type RouterOutput = inferRouterOutputs<AppRouter>
type GetTodosOutput = RouterOutput['todo']['getTodos']
type ErrorOutput = TRPCClientError<AppRouter>
export default function useGetTodos() {
const { $client } = useNuxtApp()
return useAsyncData<GetTodosOutput, ErrorOutput>(() => $client.todo.getTodos.query())
}
```
Now, we have a fully-typed composable.

View File

@@ -0,0 +1,40 @@
---
title: Headers
---
# Headers
We can use the built-in [useRequestHeaders](https://v3.nuxtjs.org/api/composables/use-request-headers/) to set outgoing request headers:
```ts [plugins/client.ts]
export default defineNuxtPlugin(() => {
const headers = useRequestHeaders()
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
// headers need to be a function so it gets called dynamically
// every HTTP request
headers() {
// You can add more custom headers here
return headers
}
}),
],
})
return {
provide: {
client,
},
}
})
```
```ts [server/trpc/context.ts]
export function createContext (event: H3Event) {
console.log('cookies', parseCookies(event))
return {}
}
```

View File

@@ -0,0 +1,103 @@
---
title: Authorization
---
# Authorization
The `createContext` function is called for each incoming request so here you can add contextual information about the calling user from the request object.
::alert{type="warning"}
Before you can access request headers in any context or middleware, you need to set the outgoing request headers. See [here](/get-started/tips/headers).
::
## Create context from request headers
```ts [server/trpc/context.ts]
import { inferAsyncReturnType } from '@trpc/server'
import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils'
export async function createContext(event: H3Event) {
// Create your context based on the request object
// Will be available as `ctx` in all your resolvers
// This is just an example of something you might want to do in your ctx fn
const authorization = getRequestHeader(event, 'authorization')
async function getUserFromHeader() {
if (authorization) {
const user = await decodeAndVerifyJwtToken(authorization.split(' ')[1])
return user
}
return null
}
const user = await getUserFromHeader()
return {
user,
}
}
type Context = inferAsyncReturnType<typeof createContext>
```
## Option 1: Authorize using resolver
```ts
import { TRPCError, initTRPC } from '@trpc/server'
import type { Context } from '../context'
export const t = initTRPC.context<Context>().create()
const appRouter = t.router({
// open for anyone
hello: t.procedure
.input(z.string().nullish())
.query(({ input, ctx }) => `hello ${input ?? ctx.user?.name ?? 'world'}`),
// checked in resolver
secret: t.procedure.query(({ ctx }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return {
secret: 'sauce',
}
}),
})
```
## Option 2: Authorize using middleware
```ts
import { TRPCError, initTRPC } from '@trpc/server'
export const t = initTRPC.context<Context>().create()
const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user?.isAdmin) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return next({
ctx: {
user: ctx.user,
},
})
})
// you can reuse this for any procedure
export const protectedProcedure = t.procedure.use(isAuthed)
t.router({
// this is accessible for everyone
hello: t.procedure
.input(z.string().nullish())
.query(({ input, ctx }) => `hello ${input ?? ctx.user?.name ?? 'world'}`),
admin: t.router({
// this is accessible only to admins
secret: protectedProcedure.query(({ ctx }) => {
return {
secret: 'sauce',
}
}),
}),
})
```
This page is entirely based on [authorization docs](https://trpc.io/docs/v10/authorization) of tRPC with a minimal change made to work with Nuxt.

View File

@@ -0,0 +1,84 @@
---
title: Server Side Calls
---
# Server Side Calls
You may need to call your procedure(s) directly from the server, `createCaller()` function returns you an instance of `RouterCaller` able to execute queries and mutations.
## Input query example
We create the router with a input query and then we call the asynchronous `greeting` procedure to get the result.
::code-group
```ts [server/trpc/trpc.ts]
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.create()
export const router = t.router({
// Create procedure at path 'greeting'
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => `Hello ${input.name}`),
})
```
```ts [server/api/greeting.ts]
import { router } from '@/server/trpc/trpc'
export default eventHandler(async (event) => {
const { name } = getQuery(event)
const caller = router.createCaller({})
const greeting = await caller.greeting({ name })
return {
greeting
}
})
```
::
## Mutation example
We create the router with a mutation and then we call the asynchronous `post` procedure to get the result.
::code-group
```ts [server/trpc/trpc.ts]
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const posts = ['One', 'Two', 'Three']
const t = initTRPC.create()
export const router = t.router({
post: t.router({
add: t.procedure.input(z.string()).mutation(({ input }) => {
posts.push(input)
return posts
}),
}),
})
```
```ts [server/api/post.ts]
import { router } from '@/server/trpc/trpc'
export default eventHandler(async (event) => {
const body = await getBody(event)
const caller = router.createCaller({})
const post = await caller.post.add(body.post)
return {
post
}
})
```
::

View File

@@ -0,0 +1,24 @@
---
title: Aborting Procedures
---
# Aborting Procedures
tRPC adheres to the industry standard when it comes to aborting procedures. All you have to do is pass an `AbortSignal` to the query-options and then call its parent `AbortController`'s `abort` method.
```ts [composables/useGetTodo.ts]
export default function useGetTodo(id: number) {
const { $client } = useNuxtApp()
const ac = new AbortController()
onScopeDispose(() => {
ac.abort()
})
return useAsyncData(() => {
return $client.todo.getTodo.query(id, {
signal: ac.signal
})
})
}
```

View File

@@ -0,0 +1,4 @@
---
navigation: false
redirect: /get-started/installation
---

View File

@@ -24,8 +24,8 @@ End-to-end typesafe APIs in Nuxt applications.
#extra #extra
::list ::list
- Automatic typesafety - Automatic typesafety
- Snappy DX - Snappy DX
- Autocompletion - Autocompletion on the client, for inputs, outputs and errors
:: ::
:: ::

View File

@@ -8,12 +8,10 @@
"preview": "nuxi preview" "preview": "nuxi preview"
}, },
"dependencies": { "dependencies": {
"@vueuse/head": "^1.0.3", "nuxt": "^3.0.0"
"nuxt": "^3.0.0-rc.13",
"@nuxtjs/tailwindcss": "^6.1.3"
}, },
"devDependencies": { "devDependencies": {
"@nuxt-themes/docus": "npm:@nuxt-themes/docus-edge@latest", "@nuxt-themes/docus": "^0.3.1",
"@nuxtlabs/github-module": "npm:@nuxtlabs/github-module-edge@latest" "@nuxtlabs/github-module": "^1.5.3"
} }
} }

View File

@@ -2,7 +2,7 @@
"name": "trpc-nuxt", "name": "trpc-nuxt",
"description": "End-to-end typesafe APIs in Nuxt applications.", "description": "End-to-end typesafe APIs in Nuxt applications.",
"type": "module", "type": "module",
"version": "0.4.1", "version": "0.4.3",
"license": "MIT", "license": "MIT",
"sideEffects": false, "sideEffects": false,
"exports": { "exports": {
@@ -18,7 +18,7 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"dev": "concurrently \"pnpm build --watch\" \"pnpm --filter playground dev\"", "dev": "concurrently \"pnpm build -- --watch\" \"pnpm --filter playground dev\"",
"dev:prepare": "pnpm build && nuxt prepare playground", "dev:prepare": "pnpm build && nuxt prepare playground",
"prepublishOnly": "pnpm build", "prepublishOnly": "pnpm build",
"build": "tsup", "build": "tsup",
@@ -27,21 +27,19 @@
"release": "bumpp && npm publish" "release": "bumpp && npm publish"
}, },
"peerDependencies": { "peerDependencies": {
"@trpc/client": "^10.0.0-proxy-beta.21", "@trpc/client": "^10.0.0",
"@trpc/server": "^10.0.0-proxy-beta.21" "@trpc/server": "^10.0.0"
}, },
"dependencies": { "dependencies": {
"h3": "^0.8.6", "h3": "^1.0.1",
"nanoid": "^4.0.0", "nanoid": "^4.0.0",
"nuxt": "^3.0.0-rc.13", "ohash": "^1.0.0",
"ohash": "^0.1.5", "ufo": "^1.0.0"
"ufo": "^0.8.6"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/kit": "3.0.0-rc.13",
"@nuxtjs/eslint-config-typescript": "^11.0.0", "@nuxtjs/eslint-config-typescript": "^11.0.0",
"@trpc/client": "10.0.0-rc.7", "@trpc/client": "^10.1.0",
"@trpc/server": "10.0.0-rc.7", "@trpc/server": "^10.1.0",
"bumpp": "^8.2.1", "bumpp": "^8.2.1",
"concurrently": "^7.5.0", "concurrently": "^7.5.0",
"eslint": "^8.25.0", "eslint": "^8.25.0",

View File

@@ -9,13 +9,13 @@
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"dependencies": { "dependencies": {
"@trpc/client": "10.0.0-rc.7", "@trpc/client": "^10.1.0",
"@trpc/server": "10.0.0-rc.7", "@trpc/server": "^10.1.0",
"superjson": "^1.11.0", "superjson": "^1.11.0",
"trpc-nuxt": "workspace:*", "trpc-nuxt": "workspace:*",
"zod": "^3.19.1" "zod": "^3.19.1"
}, },
"devDependencies": { "devDependencies": {
"nuxt": "^3.0.0-rc.13" "nuxt": "^3.0.0"
} }
} }

View File

@@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
const { $client } = useNuxtApp() import { TRPCClientError } from '@trpc/client';
// const headers = useClientHeaders() import type { inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from '~~/server/trpc/routers';
// const addHeader = () => { const { $client } = useNuxtApp()
// headers.value.authorization = 'Bearer abcdefghijklmnop'
// console.log(headers.value)
// }
const addTodo = async () => { const addTodo = async () => {
const title = Math.random().toString(36).slice(2, 7) const title = Math.random().toString(36).slice(2, 7)
@@ -23,7 +21,10 @@ const addTodo = async () => {
} }
} }
const { data: todos, pending, error, refresh } = await useAsyncData(() => $client.todo.getTodos.query()) type RouterOutput = inferRouterOutputs<AppRouter>;
type ErrorOutput = TRPCClientError<AppRouter>
const { data: todos, pending, error, refresh } = await useAsyncData<RouterOutput['todo']['getTodos'], ErrorOutput>(() => $client.todo.getTodos.query())
</script> </script>
<template> <template>

View File

@@ -3,6 +3,7 @@ import superjson from 'superjson'
import type { AppRouter } from '~~/server/trpc/routers' import type { AppRouter } from '~~/server/trpc/routers'
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const headers = useRequestHeaders()
const client = createTRPCProxyClient<AppRouter>({ const client = createTRPCProxyClient<AppRouter>({
transformer: superjson, transformer: superjson,
links: [ links: [
@@ -13,7 +14,10 @@ export default defineNuxtPlugin(() => {
(opts.direction === 'down' && opts.result instanceof Error) (opts.direction === 'down' && opts.result instanceof Error)
}), }),
httpBatchLink({ httpBatchLink({
url: 'http://localhost:3000/api/trpc' url: 'http://localhost:3000/api/trpc',
headers () {
return headers
}
}) })
] ]
}) })

View File

@@ -9,9 +9,10 @@ export type Context = inferAsyncReturnType<typeof createContext>
* @link https://trpc.io/docs/context * @link https://trpc.io/docs/context
*/ */
export function createContext ( export function createContext (
opts: H3Event event: H3Event
) { ) {
// for API-response caching see https://trpc.io/docs/caching // for API-response caching see https://trpc.io/docs/caching
console.log('cookies', parseCookies(event))
return {} return {}
} }

2154
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ export interface OnErrorPayload<TRouter extends AnyRouter> {
error: TRPCError error: TRPCError
type: ProcedureType | 'unknown' type: ProcedureType | 'unknown'
path: string | undefined path: string | undefined
req: H3Event['req'] req: H3Event['node']['req']
input: unknown input: unknown
ctx: undefined | inferRouterContext<TRouter> ctx: undefined | inferRouterContext<TRouter>
} }
@@ -68,7 +68,7 @@ export function createNuxtApiHandler<TRouter extends AnyRouter> ({
const { const {
req, req,
res res
} = event } = event.node
const $url = createURL(req.url!) const $url = createURL(req.url!)