-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathplugin.ts
More file actions
105 lines (100 loc) · 3.29 KB
/
plugin.ts
File metadata and controls
105 lines (100 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { Elysia, type ExtractErrorFromHandle } from 'elysia'
import { createSupabaseContext } from '../../create-supabase-context.js'
import type { AuthError } from '../../errors.js'
import type { SupabaseContext, WithSupabaseConfig } from '../../types.js'
export class SupabaseError extends Error {
status: number
declare cause: AuthError
constructor(inner: AuthError) {
super(inner.message, { cause: inner })
this.status = inner.status
}
}
/**
* Elysia plugin that creates a {@link SupabaseContext} and makes it available in route handlers.
*
* Skips if a previous plugin already set the context, enabling route-level overrides.
* Throws a `SupabaseError` on auth failure. `.status` is on the error directly; the original
* `AuthError` is available as the typed `.cause`. Discriminate in `onError` via `code === 'SupabaseError'`.
*
* @param config - Auth modes and optional environment overrides. CORS is excluded — use Elysia's CORS utilities.
* @returns An Elysia plugin that exposes `supabaseContext`.
*
* @example App-wide auth via `.use()`
* ```ts
* import { Elysia } from 'elysia'
* import { withSupabase } from '@supabase/server/adapters/elysia'
*
* const app = new Elysia()
* .use(withSupabase({ allow: 'user' }))
* .get('/games', async ({ supabaseContext }) => {
* const { data } = await supabaseContext.supabase.from('favorite_games').select()
* return data
* })
*
* app.listen(3000)
* ```
*
* @example Per-route auth via scoped `.use()`
* ```ts
* import { Elysia } from 'elysia'
* import { withSupabase } from '@supabase/server/adapters/elysia'
*
* const app = new Elysia()
* .get('/health', () => ({ status: 'ok' }))
* .group('/api', (app) =>
* app
* .use(withSupabase({ allow: 'user' }))
* .get('/profile', async ({ supabaseContext }) => {
* return supabaseContext.userClaims
* })
* )
*
* app.listen(3000)
* ```
*/
// The explicit return type below mirrors Elysia's own generic defaults, which use
// `{}` 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<
'',
{ decorator: {}; store: {}; derive: {}; resolve: {} },
{ typebox: {}; error: { readonly SupabaseError: SupabaseError } },
{
schema: {}
standaloneSchema: {}
macro: {}
macroFn: {}
parser: {}
response: {}
},
{},
{
derive: {}
resolve: { supabaseContext: SupabaseContext }
schema: {}
standaloneSchema: {}
response: ExtractErrorFromHandle<{ supabaseContext: SupabaseContext }>
},
{
derive: {}
resolve: {}
schema: {}
standaloneSchema: {}
response: {}
}
> {
/* 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 }
const { data, error } = await createSupabaseContext(ctx.request, config)
if (error) throw new SupabaseError(error)
return { supabaseContext: data }
})
.as('scoped')
}