Skip to content

Commit ac091c2

Browse files
mandariniclaude
andcommitted
refactor: adopt @supabase/middleware importable getEnv API
Port from the @supabase/web-middleware PR-9 preview to @supabase/middleware at main (0641674), which dropped ctx._runtime: - withSupabase seeds the middleware chain via seedContext() instead of faking a { _runtime } facet (the engine now marks contexts with a symbol, so the structural fake no longer works) - withPostgres defaults its connection string from the importable getEnv('SUPABASE_DB_URL') instead of ctx._runtime.getEnv - tests use vi.stubEnv for the env fallback; withClaims tests call the handler as a bare fetch entry Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent dcde02e commit ac091c2

8 files changed

Lines changed: 51 additions & 51 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201
"vitest": "^4.1.0"
202202
},
203203
"dependencies": {
204-
"@supabase/web-middleware": "https://pkg.pr.new/supabase/web-middleware/@supabase/web-middleware@9",
204+
"@supabase/middleware": "https://pkg.pr.new/supabase/middleware/@supabase/middleware@0641674",
205205
"jose": "^6.2.0"
206206
}
207207
}

pnpm-lock.yaml

Lines changed: 19 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/middleware/claims/index.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ function tokenFor(claims: Record<string, unknown>): string {
1414
return `header.${base64url(claims)}.sig`
1515
}
1616

17-
const runtime = { name: 'node' as const, getEnv: () => undefined }
18-
1917
describe('withClaims', () => {
2018
it('decodes the Bearer token payload into ctx.jwtClaims', async () => {
2119
let seen: unknown
@@ -24,13 +22,13 @@ describe('withClaims', () => {
2422
return Response.json({ ok: true })
2523
})
2624

25+
// Called bare, the way a runtime invokes a fetch entry — no prerequisites.
2726
await handler(
2827
new Request('http://localhost', {
2928
headers: {
3029
Authorization: `Bearer ${tokenFor({ sub: 'u1', role: 'authenticated' })}`,
3130
},
3231
}),
33-
{ _runtime: runtime },
3432
)
3533

3634
expect(seen).toEqual({ sub: 'u1', role: 'authenticated' })
@@ -43,7 +41,7 @@ describe('withClaims', () => {
4341
return Response.json({ ok: true })
4442
})
4543

46-
await handler(new Request('http://localhost'), { _runtime: runtime })
44+
await handler(new Request('http://localhost'))
4745

4846
expect(seen).toBeNull()
4947
})
@@ -59,7 +57,6 @@ describe('withClaims', () => {
5957
new Request('http://localhost', {
6058
headers: { Authorization: 'Bearer not-a-jwt' },
6159
}),
62-
{ _runtime: runtime },
6360
)
6461

6562
expect(seen).toBeNull()

src/middleware/claims/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineMiddleware } from '@supabase/web-middleware'
1+
import { defineMiddleware } from '@supabase/middleware'
22

33
/**
44
* Loosely-typed JWT claims contributed by {@link withClaims}.

src/middleware/postgres/index.test.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ vi.mock('pg', () => {
1919
return { default: { Pool }, Pool }
2020
})
2121

22-
const runtime = {
23-
name: 'node' as const,
24-
getEnv: (k: string) =>
25-
k === 'SUPABASE_DB_URL' ? 'postgres://localhost/test' : undefined,
26-
}
27-
22+
const { seedContext } = await import('@supabase/middleware')
2823
const { withPostgres } = await import('./index.js')
2924

3025
describe('withPostgres', () => {
@@ -33,16 +28,23 @@ describe('withPostgres', () => {
3328
h.clientQuery.mockClear()
3429
h.connect.mockClear()
3530
h.release.mockClear()
31+
// The connection-string default reads the importable getEnv, which falls
32+
// back to the host env in tests.
33+
vi.stubEnv('SUPABASE_DB_URL', 'postgres://localhost/test')
34+
})
35+
afterEach(() => {
36+
vi.unstubAllEnvs()
37+
vi.restoreAllMocks()
3638
})
37-
afterEach(() => vi.restoreAllMocks())
3839

3940
it('returns 500 when no connection string is available', async () => {
41+
vi.stubEnv('SUPABASE_DB_URL', undefined)
4042
const handler = withPostgres({ connectionString: undefined }, async () =>
4143
Response.json({ ok: true }),
4244
)
4345

4446
const res = await handler(new Request('http://localhost'), {
45-
_runtime: { name: 'node', getEnv: () => undefined },
47+
...seedContext(),
4648
jwtClaims: null,
4749
})
4850

@@ -57,7 +59,7 @@ describe('withPostgres', () => {
5759
})
5860

5961
await handler(new Request('http://localhost'), {
60-
_runtime: runtime,
62+
...seedContext(),
6163
jwtClaims: { sub: 'u1', role: 'authenticated' },
6264
})
6365

@@ -78,7 +80,7 @@ describe('withPostgres', () => {
7880
})
7981

8082
await handler(new Request('http://localhost'), {
81-
_runtime: runtime,
83+
...seedContext(),
8284
jwtClaims: { sub: 'attacker', role: 'service_role' },
8385
})
8486

@@ -112,7 +114,7 @@ describe('withPostgres', () => {
112114

113115
await expect(
114116
handler(new Request('http://localhost'), {
115-
_runtime: runtime,
117+
...seedContext(),
116118
jwtClaims: { role: 'authenticated' },
117119
}),
118120
).rejects.toThrow('boom')
@@ -151,7 +153,7 @@ describe('withPostgres', () => {
151153

152154
await expect(
153155
handler(new Request('http://localhost'), {
154-
_runtime: runtime,
156+
...seedContext(),
155157
jwtClaims: { role: 'authenticated' },
156158
}),
157159
).rejects.toThrow(

src/middleware/postgres/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineMiddleware } from '@supabase/web-middleware'
1+
import { defineMiddleware, getEnv } from '@supabase/middleware'
22
import pg from 'pg'
33

44
const { Pool } = pg
@@ -41,7 +41,7 @@ export interface PostgresApi {
4141
* @category Middleware
4242
*/
4343
export interface WithPostgresConfig {
44-
/** Defaults to `ctx._runtime.getEnv('SUPABASE_DB_URL')`. */
44+
/** Defaults to `getEnv('SUPABASE_DB_URL')` (from `@supabase/middleware`). */
4545
connectionString?: string
4646
}
4747

@@ -90,7 +90,7 @@ export const withPostgres = defineMiddleware<
9090
key: 'postgres',
9191
run: (config) => async (_req, ctx) => {
9292
const connectionString =
93-
config?.connectionString ?? ctx._runtime.getEnv('SUPABASE_DB_URL')
93+
config?.connectionString ?? getEnv('SUPABASE_DB_URL')
9494
if (!connectionString) {
9595
return Response.json({ error: 'no SUPABASE_DB_URL' }, { status: 500 })
9696
}

src/with-supabase.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest'
2-
import { defineMiddleware } from '@supabase/web-middleware'
2+
import { defineMiddleware } from '@supabase/middleware'
33

44
import { _resetAllowDeprecationWarned } from './core/utils/deprecation.js'
55
import { withSupabase } from './with-supabase.js'

src/with-supabase.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { addCorsHeaders, buildCorsHeaders, isCorsDisabled } from './cors.js'
22
import { createSupabaseContext } from './create-supabase-context.js'
33
import type { SupabaseContext, WithSupabaseConfig } from './types.js'
4-
import type { Entry } from '@supabase/web-middleware'
4+
import { seedContext } from '@supabase/middleware'
5+
import type { Entry } from '@supabase/middleware'
56

67
type AnyEntry = Entry<string, object, unknown>
78
// eslint-disable-next-line @typescript-eslint/no-explicit-any
89
type AnyHandler = (req: Request, ctx: any) => Promise<Response>
910

1011
/**
1112
* Accumulate the ctx contributions of a middleware tuple — same logic as
12-
* `pipeline`'s internal `Accumulate`, seeded from `object` (no `BaseContext`
13-
* or `_runtime` in the visible ctx type; see implementation note below).
13+
* `pipeline`'s internal `Accumulate`, seeded from `object` (the engine reserves
14+
* no ctx keys; see implementation note below).
1415
*/
1516
type MiddlewareCtx<Entries extends readonly AnyEntry[]> =
1617
Entries extends readonly [
@@ -55,7 +56,7 @@ export function withSupabase<Database = unknown>(
5556

5657
/**
5758
* Variant that accepts a `middleware` array — each `withFoo(config)` call
58-
* returns an `Entry` from `@supabase/web-middleware`. Middleware run **after**
59+
* returns an `Entry` from `@supabase/middleware`. Middleware run **after**
5960
* the Supabase context is established; they receive `ctx.supabase`,
6061
* `ctx.userClaims`, etc. already present and contribute their own typed keys
6162
* on top. (This is the server leg of a Plugin: the package's middleware goes
@@ -86,7 +87,7 @@ export function withSupabase<Database = unknown>(
8687
* (the Supabase context is merged before the middleware run) but not at the
8788
* type level — a full implementation would widen the prerequisite-validation
8889
* seed to include `SupabaseContext`. Ordering and collision checks within the
89-
* middleware array work normally via `web-middleware`'s runtime chain.
90+
* middleware array work normally via `@supabase/middleware`'s runtime chain.
9091
*/
9192
export function withSupabase<
9293
Database = unknown,
@@ -135,19 +136,10 @@ export function withSupabase<Database = unknown>(
135136
const composed = (
136137
config.middleware as readonly AnyEntry[]
137138
).reduceRight<AnyHandler>((h, entry) => entry(h), handler)
138-
// Seed _runtime so web-middleware entries recognise this as an upstream
139-
// context (isContext() checks for _runtime.getEnv). Falls through to
140-
// process.env; a full implementation would bridge to SupabaseEnv.
141-
const g = globalThis as {
142-
process?: { env?: Record<string, string | undefined> }
143-
}
144-
response = await composed(req, {
145-
...ctx,
146-
_runtime: {
147-
name: 'unknown' as const,
148-
getEnv: (key: string): string | undefined => g.process?.env?.[key],
149-
},
150-
})
139+
// seedContext() stamps the engine's context marker so middleware entries
140+
// recognise this as an upstream context. Env access happens through the
141+
// engine's importable getEnv — no per-ctx facet to bridge.
142+
response = await composed(req, { ...seedContext(), ...ctx })
151143
} else {
152144
response = await handler(req, ctx as object)
153145
}

0 commit comments

Comments
 (0)