Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

## [1.4.0](https://github.com/supabase/server/compare/server-v1.3.1...server-v1.4.0) (2026-07-13)


### Features

* explicit cors config shape ('default' | 'disabled' | { headers }) ([#102](https://github.com/supabase/server/issues/102)) ([297925f](https://github.com/supabase/server/commit/297925fa1149a61d148306fdb6ba28e66bc0a737))
- explicit cors config shape ('default' | 'disabled' | { headers }) ([#102](https://github.com/supabase/server/issues/102)) ([297925f](https://github.com/supabase/server/commit/297925fa1149a61d148306fdb6ba28e66bc0a737))

## [1.3.1](https://github.com/supabase/server/compare/server-v1.3.0...server-v1.3.1) (2026-07-03)

Expand Down
19 changes: 19 additions & 0 deletions docs/typescript-generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ const todosApp = new Hono()
app.route('/todos', todosApp)
```

The elysia adapter works the same way: thread `Database` through the `withSupabase<Database>()` generic and elysia infers the `supabaseContext` type, so `supabaseContext.supabase` is a fully typed `SupabaseClient<Database>` in every handler.

```ts
import { Elysia } from 'elysia'
import { withSupabase } from '@supabase/server/adapters/elysia'
import type { Database } from './database.types.ts'

const app = new Elysia()
.use(withSupabase<Database>({ auth: 'user' }))
.get('/todos', async ({ supabaseContext }) => {
const { data } = await supabaseContext.supabase
.from('todos')
.select('id, title')
return data
})

app.listen(3000)
```

## Custom schema

If your tables are in a schema other than `public`, pass it via `supabaseOptions`:
Expand Down
11 changes: 2 additions & 9 deletions jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@
"./adapters/nestjs": "./src/adapters/nestjs/index.ts"
},
"publish": {
"include": [
"src/**/*.ts",
"README.md",
"LICENSE"
],
"exclude": [
"src/**/*.test.ts",
"src/**/*.spec.ts"
]
"include": ["src/**/*.ts", "README.md", "LICENSE"],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
}
}
28 changes: 27 additions & 1 deletion src/adapters/elysia/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { Elysia } from 'elysia'
import { describe, expect, it } from 'vitest'
import { describe, expect, expectTypeOf, it } from 'vitest'

import type { SupabaseContext } from '../../types.js'
import { withSupabase } from './plugin.js'

type Database = {
public: {
Tables: {
todos: {
Row: { id: number; title: string }
Insert: { id?: number; title: string }
Update: { id?: number; title?: string }
Relationships: []
}
}
}
}

describe('elysia supabase plugin', () => {
const env = {
url: 'https://test.supabase.co',
Expand All @@ -28,6 +42,18 @@ describe('elysia supabase plugin', () => {
expect(body.hasAdmin).toBe(true)
})

it('threads the Database generic into the Supabase context', async () => {
const app = new Elysia()
.use(withSupabase<Database>({ auth: 'none', env }))
.get('/', ({ supabaseContext }) => {
expectTypeOf(supabaseContext).toEqualTypeOf<SupabaseContext<Database>>()
return { authMode: supabaseContext.authMode }
})

const res = await app.handle(new Request('http://localhost/'))
expect(res.status).toBe(200)
})

it('throws error on auth failure', async () => {
const app = new Elysia()
.use(withSupabase({ auth: 'user', env }))
Expand Down
32 changes: 21 additions & 11 deletions src/adapters/elysia/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export class SupabaseError extends Error {
// `{}` literals — switching to `object` or `Record<string, never>` would not satisfy
// the corresponding generic constraints.
/* eslint-disable @typescript-eslint/no-empty-object-type */
export function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Elysia<
export function withSupabase<Database = unknown>(
config?: Omit<WithSupabaseConfig, 'cors'>,
): Elysia<
'',
{ decorator: {}; store: {}; derive: {}; resolve: {} },
{ typebox: {}; error: { readonly SupabaseError: SupabaseError } },
Expand All @@ -85,10 +87,12 @@ export function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Elysia<
{},
{
derive: {}
resolve: { supabaseContext: SupabaseContext }
resolve: { supabaseContext: SupabaseContext<Database> }
schema: {}
standaloneSchema: {}
response: ExtractErrorFromHandle<{ supabaseContext: SupabaseContext }>
response: ExtractErrorFromHandle<{
supabaseContext: SupabaseContext<Database>
}>
},
{
derive: {}
Expand All @@ -101,15 +105,21 @@ export function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Elysia<
/* eslint-enable @typescript-eslint/no-empty-object-type */
return new Elysia()
.error({ SupabaseError })
.resolve(async (ctx): Promise<{ supabaseContext: SupabaseContext }> => {
const existing = (ctx as { supabaseContext?: SupabaseContext })
.supabaseContext
if (existing) return { supabaseContext: existing }
.resolve(
async (ctx): Promise<{ supabaseContext: SupabaseContext<Database> }> => {
const existing = (
ctx as { supabaseContext?: SupabaseContext<Database> }
).supabaseContext
if (existing) return { supabaseContext: existing }

const { data, error } = await createSupabaseContext(ctx.request, config)
if (error) throw new SupabaseError(error)
const { data, error } = await createSupabaseContext<Database>(
ctx.request,
config,
)
if (error) throw new SupabaseError(error)

return { supabaseContext: data }
})
return { supabaseContext: data }
},
)
.as('scoped')
}
Loading