Skip to content

feat: add Elysia adapter#46

Open
wobsoriano wants to merge 7 commits into
supabase:mainfrom
wobsoriano:rob/elysia-adapter
Open

feat: add Elysia adapter#46
wobsoriano wants to merge 7 commits into
supabase:mainfrom
wobsoriano:rob/elysia-adapter

Conversation

@wobsoriano
Copy link
Copy Markdown
Contributor

@wobsoriano wobsoriano commented May 5, 2026

What kind of change does this PR introduce?

Feature

What is the current behavior?

@supabase/server ships framework adapters for Hono and H3 / Nuxt, but not for Elysia. Users building APIs with Elysia have to wire up Supabase auth manually with the core primitives.

What is the new behavior?

Adds @supabase/server/adapters/elysia, a first-class plugin adapter for Elysia. Elysia has been gaining significant traction in the TypeScript ecosystem, particularly in the Bun community, for its end-to-end type safety and expressive plugin API.

Usage with a plain Elysia app:

import { Elysia } from 'elysia'
import { withSupabase } from '@supabase/server/adapters/elysia'

const app = new Elysia()
  .use(withSupabase({ auth: 'user' }))
  .get('/games', async ({ supabaseContext }) => {
    const { data: myGames } = await supabaseContext.supabase
      .from('favorite_games')
      .select()

    return myGames
  })

app.listen(3000)

Per-route auth using scoped groups:

const app = new Elysia()
  .get('/health', () => ({ status: 'ok' }))
  .group('/api', (app) =>
    app
      .use(withSupabase({ auth: 'user' }))
      .get('/profile', ({ supabaseContext }) => supabaseContext.userClaims),
  )

Custom error handling via Elysia's onError:

const app = new Elysia()
  .use(withSupabase({ auth: 'user' }))
  .onError(({ code, error, status }) => {
    if (code !== 'SupabaseAuthError') return

    const cause = error.cause as { code?: string; status?: number } | undefined

    return status((cause?.status as 401) ?? 500, {
      error: error.message,
      code: cause?.code,
    })
  })

Additional context

  • Uses Elysia's .resolve() hook with .as('scoped') so the context propagates to parent app routes.
  • Auth failures throw a registered SupabaseAuthError, which integrates with Elysia's standard onError flow and preserves the original AuthError on error.cause.Consumers discriminate on code === 'SupabaseAuthError' in onError without needing to import the class.
  • Test coverage mirrors the Hono and H3 adapter tests.
  • Docs and package metadata were updated to include Elysia in the adapter tables, exports, and generated API docs.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 5, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@supabase/server@46

commit: 2fbc72f

@wobsoriano wobsoriano marked this pull request as ready for review May 5, 2026 17:49
@wobsoriano wobsoriano requested review from a team as code owners May 12, 2026 23:13
@wobsoriano wobsoriano force-pushed the rob/elysia-adapter branch from 396ca1a to 2633af3 Compare May 12, 2026 23:14
Comment thread src/adapters/elysia/plugin.ts Outdated
Comment on lines +6 to +13
class SupabaseAuthError extends Error {
status: number
constructor(message: string, status: number, cause: unknown) {
super(message, { cause })
this.status = status
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure why its SupabaseAuthError.
We have EnvError and AuthError classes.

Both sharing similar shape. Maybe better renaming it to SupabaseError instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! Renamed to SupabaseError.

The intent behind a wrapper class rather than throwing AuthError directly is to give Elysia consumers a single, stable discriminant in onError regardless of whether the failure was an auth error or an env misconfiguration.

More info https://elysiajs.com/patterns/error-handling

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhmm so what about registering the original error classes instead of wrapping it?

new Elysia()
  .error({
      EnvError,
      AuthError,
  })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I tried throwing AuthError directly with .error({ AuthError }), but Elysia's error registry inside a scoped plugin doesn't propagate to the parent's onError at runtime, so code === 'AuthError' silently fails even though TS infers it correctly.

We could keep a thin SupabaseError wrapper but expose .code and .status directly on it (no .cause indirection). That way code discrimination still works idiomatically. Open to other suggestions!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if we pass the AuthError directly, one will check it in the elysia error hook like this:

import { AuthError } from '@supabase/server/errors'

app.onError(({ error, status }) => {
  if (!(error instanceof AuthError)) return
  return status(error.status as 401, {
    error: error.message,
    code: error.code,
  })
})

but if we create a thin wrapper and expose .code and .status:

app.onError(({ code, error, status }) => {
  if (code !== 'SupabaseError') return
  return status(error.status as 401, {
    error: error.message,
    code: error.code,
  })
})

2nd is more idiomatic to Elysia, no extra import, and the code string is the standard discriminant. The first works but bypasses Elysia's custom error system entirely

Comment thread src/adapters/README.md
Comment thread package.json Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
@kallebysantos
Copy link
Copy Markdown
Member

Thanks for contributing,
Just a few questions and adjustments 💚

@wobsoriano
Copy link
Copy Markdown
Contributor Author

@kallebysantos thanks for the thorough review. Resolved the comments! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants