diff --git a/.changeset/tall-dingos-nail.md b/.changeset/tall-dingos-nail.md new file mode 100644 index 00000000..a32ae0fd --- /dev/null +++ b/.changeset/tall-dingos-nail.md @@ -0,0 +1,5 @@ +--- +'@hono/trpc-server': minor +--- + +Support asynchronous context creation for trpc server middleware diff --git a/packages/trpc-server/src/index.ts b/packages/trpc-server/src/index.ts index c2edd943..c1658ad9 100644 --- a/packages/trpc-server/src/index.ts +++ b/packages/trpc-server/src/index.ts @@ -5,14 +5,14 @@ import type { Context, MiddlewareHandler } from 'hono' type tRPCOptions = Omit, 'req' | 'endpoint' | 'createContext'> & Partial, 'endpoint'>> & - { createContext? (opts: FetchCreateContextFnOptions, c: Context): Record } + { createContext? (opts: FetchCreateContextFnOptions, c: Context): Record | Promise> } export const trpcServer = ({ endpoint = '/trpc', createContext, ...rest }: tRPCOptions): MiddlewareHandler => { return async (c) => { const res = fetchRequestHandler({ ...rest, - createContext: (opts) => ({ - ...createContext ? createContext(opts, c) : {}, + createContext: async (opts) => ({ + ...createContext ? await createContext(opts, c) : {}, // propagate env by default env: c.env, }), diff --git a/packages/trpc-server/test/context.test.ts b/packages/trpc-server/test/context.test.ts index aea93059..3559d4ed 100644 --- a/packages/trpc-server/test/context.test.ts +++ b/packages/trpc-server/test/context.test.ts @@ -2,7 +2,7 @@ import { initTRPC } from '@trpc/server' import { Hono } from 'hono' import { trpcServer } from '../src' -describe('tRPC Adapter Middleware passing Context', () => { +describe('tRPC Adapter Middleware passing synchronous Context', () => { type Env = { NAME: string; } @@ -65,3 +65,68 @@ describe('tRPC Adapter Middleware passing Context', () => { ]) }) }) + + +describe('tRPC Adapter Middleware passing asynchronous Context', () => { + type Env = { + NAME: string; + } + type HonoContext = { + env: Env, + batch: string; + }; + + const t = initTRPC.context().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: async (_opts, c) => ({ + batch: c.req.query('batch'), + }) + }) + ) + + it.only('Should return 200 response', async () => { + const searchParams = new URLSearchParams({ + input: JSON.stringify({ '0': 'Hono' }), + batch: '2', + }) + 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 2', + }, + }, + ]) + }) +})