feat(trpc): access hono context from trpc procedures (#458)
* trpc: access hono context from trpc procedures * chore: Add changesetpull/480/head
parent
9e60a47df4
commit
2526e1e685
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@hono/trpc-server': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
access hono context from trpc procedures
|
|
@ -67,6 +67,52 @@ const client = createTRPCProxyClient<AppRouter>({
|
||||||
console.log(await client.hello.query('Hono'))
|
console.log(await client.hello.query('Hono'))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
You can also access `c.env` from hono context from the trpc `ctx`. eg. here's an example using cloudflare D1 binding available as `env.DB`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { initTRPC } from '@trpc/server'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
type Env = {
|
||||||
|
DB: D1Database;
|
||||||
|
}
|
||||||
|
type HonoContext = {
|
||||||
|
env: Env,
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = initTRPC.context<HonoContext>().create()
|
||||||
|
|
||||||
|
const publicProcedure = t.procedure
|
||||||
|
const router = t.router
|
||||||
|
|
||||||
|
export const appRouter = router({
|
||||||
|
usersCount: publicProcedure.query(({ input, ctx }) => {
|
||||||
|
const result = await ctx.env.DB.prepare("SELECT count(*) from user;").all();
|
||||||
|
return result.results[0].count;
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter
|
||||||
|
```
|
||||||
|
|
||||||
|
For further control, you can optionally specify a `createContext` that in this case will receive the hono context as 2nd argument:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
app.use(
|
||||||
|
'/trpc/*',
|
||||||
|
trpcServer({
|
||||||
|
router: appRouter,
|
||||||
|
createContext: (_opts, c) => ({
|
||||||
|
// c is the hono context
|
||||||
|
var1: c.env.MY_VAR1,
|
||||||
|
var2: c.req.header('X-VAR2'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
Yusuke Wada <https://github.com/yusukebe>
|
Yusuke Wada <https://github.com/yusukebe>
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import type { AnyRouter } from '@trpc/server'
|
import type { AnyRouter } from '@trpc/server'
|
||||||
import type { FetchHandlerRequestOptions } from '@trpc/server/adapters/fetch'
|
import type { FetchCreateContextFnOptions, FetchHandlerRequestOptions } from '@trpc/server/adapters/fetch'
|
||||||
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { Context, MiddlewareHandler } from 'hono'
|
||||||
|
|
||||||
type tRPCOptions = Omit<FetchHandlerRequestOptions<AnyRouter>, 'req' | 'endpoint'> &
|
type tRPCOptions = Omit<FetchHandlerRequestOptions<AnyRouter>, 'req' | 'endpoint' | 'createContext'> &
|
||||||
Partial<Pick<FetchHandlerRequestOptions<AnyRouter>, 'endpoint'>>
|
Partial<Pick<FetchHandlerRequestOptions<AnyRouter>, 'endpoint'>> &
|
||||||
|
{ createContext? (opts: FetchCreateContextFnOptions, c: Context): Record<string, unknown> }
|
||||||
|
|
||||||
export const trpcServer = ({ endpoint = '/trpc', ...rest }: tRPCOptions): MiddlewareHandler => {
|
export const trpcServer = ({ endpoint = '/trpc', createContext, ...rest }: tRPCOptions): MiddlewareHandler => {
|
||||||
return async (c) => {
|
return async (c) => {
|
||||||
const res = fetchRequestHandler({
|
const res = fetchRequestHandler({
|
||||||
...rest,
|
...rest,
|
||||||
|
createContext: (opts) => ({
|
||||||
|
...createContext ? createContext(opts, c) : {},
|
||||||
|
// propagate env by default
|
||||||
|
env: c.env,
|
||||||
|
}),
|
||||||
endpoint,
|
endpoint,
|
||||||
req: c.req.raw,
|
req: c.req.raw,
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { initTRPC } from '@trpc/server'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { trpcServer } from '../src'
|
||||||
|
|
||||||
|
describe('tRPC Adapter Middleware passing Context', () => {
|
||||||
|
type Env = {
|
||||||
|
NAME: string;
|
||||||
|
}
|
||||||
|
type HonoContext = {
|
||||||
|
env: Env,
|
||||||
|
batch: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = initTRPC.context<HonoContext>().create()
|
||||||
|
|
||||||
|
const publicProcedure = t.procedure.use(
|
||||||
|
t.middleware((opts) => {
|
||||||
|
return opts.next({
|
||||||
|
ctx: {
|
||||||
|
// add .env into context, simulating a middleware as cloudflare pages
|
||||||
|
env: {
|
||||||
|
DB: {
|
||||||
|
getName: () => 'World'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const router = t.router
|
||||||
|
|
||||||
|
const appRouter = router({
|
||||||
|
hello: publicProcedure.query(({ ctx }) => {
|
||||||
|
return `Hello ${ctx.env.DB.getName()}, batch is ${ctx.batch}`
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
'/trpc/*',
|
||||||
|
trpcServer({
|
||||||
|
router: appRouter,
|
||||||
|
// optional createContext, additional `c` arg with the hono context
|
||||||
|
createContext: (_opts, c) => ({
|
||||||
|
batch: c.req.query('batch'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
it.only('Should return 200 response', async () => {
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
input: JSON.stringify({ '0': 'Hono' }),
|
||||||
|
batch: '1',
|
||||||
|
})
|
||||||
|
const req = new Request(`http://localhost/trpc/hello?${searchParams.toString()}`)
|
||||||
|
const res = await app.request(req)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.json()).toEqual([
|
||||||
|
{
|
||||||
|
result: {
|
||||||
|
data: 'Hello World, batch is 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue