From 5fb279c5ffa59c97e48d891cf93d645b33ca7af9 Mon Sep 17 00:00:00 2001 From: Elliott Mantock Date: Wed, 8 Jul 2026 11:00:07 -0700 Subject: [PATCH] fix: add hono adapter Database type override adds generic Database type override to hono adapter added test to ensure chained declarations preserve context type updated docs to include alternative hono declaration --- docs/typescript-generics.md | 20 ++++++++++++++++++++ src/adapters/hono/middleware.test.ts | 16 ++++++++++++++++ src/adapters/hono/middleware.ts | 13 +++++++++---- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/docs/typescript-generics.md b/docs/typescript-generics.md index 814cd13..ec93ecd 100644 --- a/docs/typescript-generics.md +++ b/docs/typescript-generics.md @@ -101,6 +101,26 @@ app.get('/todos', async (c) => { }) ``` +Alternatively, skip the `Env` type and thread `Database` through the `withSupabase()` generic when using chained declarations. Hono infers the `supabaseContext` variable type from the middleware, so `c.var.supabaseContext.supabase` is still a fully typed `SupabaseClient`. This plays a little nicer with hono [sub-application routes](https://hono.dev/docs/helpers/route#working-with-sub-applications). + +```ts +import { Hono } from 'hono' +import { withSupabase } from '@supabase/server/adapters/hono' +import type { Database } from './database.types.ts' + +const app = new Hono() + +const todosApp = new Hono() + .use(withSupabase({ auth: 'user' })) + .get('/', async (c) => { + const { supabase } = c.var.supabaseContext + const { data } = await supabase.from('todos').select('id, title') + return c.json(data) + }) + +app.route('/todos', todosApp) +``` + ## Custom schema If your tables are in a schema other than `public`, pass it via `supabaseOptions`: diff --git a/src/adapters/hono/middleware.test.ts b/src/adapters/hono/middleware.test.ts index a823c28..80d6f32 100644 --- a/src/adapters/hono/middleware.test.ts +++ b/src/adapters/hono/middleware.test.ts @@ -69,6 +69,22 @@ describe('hono supabase middleware', () => { expect(res.status).toBe(200) }) + it('uses the Hono adapter to type the Supabase context', async () => { + // match docs example in typescript-generics.md + const app = new Hono() + const rootApp = new Hono() + .use(withSupabase({ auth: 'none', env })) + .get('/', (c) => { + const ctx = c.var.supabaseContext + expectTypeOf(ctx).toEqualTypeOf>() + return c.json({ authMode: ctx.authMode }) + }) + app.route('/', rootApp) + + const res = await app.request('/') + expect(res.status).toBe(200) + }) + it('throws HTTPException on auth failure', async () => { const app = new Hono() app.use('*', withSupabase({ auth: 'user', env })) diff --git a/src/adapters/hono/middleware.ts b/src/adapters/hono/middleware.ts index 3fb9720..1dd00a6 100644 --- a/src/adapters/hono/middleware.ts +++ b/src/adapters/hono/middleware.ts @@ -40,11 +40,13 @@ import type { SupabaseContext, WithSupabaseConfig } from '../../types.js' * * @category Adapters */ -export function withSupabase( +export function withSupabase( config?: Omit, -): MiddlewareHandler<{ Variables: { supabaseContext: SupabaseContext } }> { +): MiddlewareHandler<{ + Variables: { supabaseContext: SupabaseContext } +}> { return createMiddleware<{ - Variables: { supabaseContext: SupabaseContext } + Variables: { supabaseContext: SupabaseContext } }>(async (c, next) => { // Skip if a previous middleware already set the context. // This enables route-level overrides: a route can use withSupabase({ auth: 'secret' }) @@ -55,7 +57,10 @@ export function withSupabase( return } - const { data: ctx, error } = await createSupabaseContext(c.req.raw, config) + const { data: ctx, error } = await createSupabaseContext( + c.req.raw, + config, + ) if (error) { throw new HTTPException(error.status as 401 | 500, { message: error.message,