-
-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathplugin.ts
More file actions
125 lines (120 loc) · 3.68 KB
/
Copy pathplugin.ts
File metadata and controls
125 lines (120 loc) · 3.68 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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'
/**
* Wraps an {@link AuthError} as an Elysia-compatible error.
*
* Discriminate in `onError` via `code === 'SupabaseError'`. The original
* `AuthError` is available as the typed `.cause`.
*
* @category Adapters
*/
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({ auth: '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({ auth: 'user' }))
* .get('/profile', async ({ supabaseContext }) => {
* return supabaseContext.userClaims
* })
* )
*
* app.listen(3000)
* ```
*
* @category Adapters
*/
// 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<Database = unknown>(
config?: Omit<WithSupabaseConfig, 'cors'>,
): Elysia<
'',
{ decorator: {}; store: {}; derive: {}; resolve: {} },
{ typebox: {}; error: { readonly SupabaseError: SupabaseError } },
{
schema: {}
standaloneSchema: {}
macro: {}
macroFn: {}
parser: {}
response: {}
},
{},
{
derive: {}
resolve: { supabaseContext: SupabaseContext<Database> }
schema: {}
standaloneSchema: {}
response: ExtractErrorFromHandle<{
supabaseContext: SupabaseContext<Database>
}>
},
{
derive: {}
resolve: {}
schema: {}
standaloneSchema: {}
response: {}
}
> {
/* eslint-enable @typescript-eslint/no-empty-object-type */
return new Elysia()
.error({ SupabaseError })
.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<Database>(
ctx.request,
config,
)
if (error) throw new SupabaseError(error)
return { supabaseContext: data }
},
)
.as('scoped')
}