feat: withSupabase on the @supabase/middleware engine - #88
Merged
Conversation
commit: |
mandarini
force-pushed
the
feat/plugins-option
branch
from
July 1, 2026 16:55
0e747ac to
ad449a9
Compare
This was referenced Jul 2, 2026
mandarini
force-pushed
the
feat/plugins-option
branch
2 times, most recently
from
July 9, 2026 08:14
9e402e1 to
44b05da
Compare
mandarini
force-pushed
the
feat/plugins-option
branch
from
July 21, 2026 11:34
fa1d063 to
ac091c2
Compare
…lution TypeScript doesn't apply excess property checking during overload resolution, so calls with plugins: [...] were silently matching overload 1 and typing ctx as SupabaseContext<unknown>. Adding plugins?: never to overload 1's config makes it definitively fail when plugins is present, falling through to the correct overload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The array holds middleware entries (per-request behavior from
defineMiddleware) — the word 'plugins' is reserved for the package-level
concept whose client namespace goes in createClient({ plugins }). One
word per concept: server-side composition is 'middleware', client-side
namespaces are 'plugins', a Plugin is the package that ships both.
PluginsCtx -> MiddlewareCtx; overload trick unchanged
(middleware?: never on overload 1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Graduate withPostgres and withClaims out of plugin-examples into @supabase/server/middleware/*, so the PRFAQ's built-in middleware ship from the package instead of example-local code (SDK-1163 item 5). - withPostgres reads claims from ctx.jwtClaims (already populated by withSupabase), so `middleware: [withPostgres()]` works with no separate withClaims. Keeps the RLS role-clamp and tx-local request.jwt.claims injection. pg is an optional peer dep (Node/Deno only, not Workers). - withClaims ships for the standalone agnostic pipeline() case (demo-only: no signature verification). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Append the caller-role grants hint to permission-denied errors and document the grants requirement in the withPostgres JSDoc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
JSR's slow-types check requires explicit types on public API symbols; the inferred defineMiddleware return type failed 'Verify JSR packaging'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the pkg.pr.new preview build with the released package. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
withClaims graduates from the demo-grade payload decoder to real verification: the user-mode JWT leg of verifyCredentials moves into a shared verifyUserJwt core (JWKS resolver caching, HS256 shared-secret path, sb_* passthrough), used by both. No decode-only mode remains — an invalid token short-circuits 401, a missing JWKS 500. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
withSupabaseClient contributes ctx.supabase (RLS-scoped, caller's token) and withSupabaseAdminClient contributes ctx.supabaseAdmin, wrapping the existing createContextClient / createAdminClient primitives. Composed under withSupabase they read the seeded authMode / authKeyName to mirror verified credentials exactly; standalone they work as plain engine entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
withSupabase now runs on @supabase/middleware for every request: the two public client middleware fold around the user's middleware array and handler, seeded with the verified auth identity via seedContext. The host's second fetch argument (Workers env) is forwarded so bindings reach getEnv. Public API, ctx keys, and error shapes are unchanged — client-construction failures keep their historical JSON responses (phase-guarded so handler throws still propagate), and the existing test suite passes unmodified as the parity proof. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mandarini
force-pushed
the
feat/plugins-option
branch
from
August 11, 2026 08:21
16b75f8 to
39d89be
Compare
mandarini
marked this pull request as ready for review
August 11, 2026 08:22
mandarini
force-pushed
the
feat/plugins-option
branch
2 times, most recently
from
August 11, 2026 14:22
a66525e to
39d89be
Compare
Collaborator
Author
|
You can also test this out on |
kallebysantos
approved these changes
Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
withSupabasenow runs on the@supabase/middlewareengine, and the pieces it is built from ship as public middleware. Adds amiddlewareoption so@supabase/middlewareentries can compose around the handler after the Supabase context is established — plus four first-party middleware:withSupabaseClient,withSupabaseAdminClient,withClaims, andwithPostgres.The middleware receive
ctx.supabase,ctx.jwtClaims, etc. already populated — they run after the Supabase context is created, not before.Architecture
Why
verifyAuthisn't itself a chain entry: an engine middleware contributes exactly one ctx key;verifyAuth's single pass produces four (userClaims,jwtClaims,authMode,authKeyName) via ordered multi-mode resolution. Instead,withClaimsshares the same verification core (verifyUserJwt) asverifyAuth's user mode — same code, two surfaces.What changed
src/with-supabase.ts— rewritten on the engine: the two client middleware compose around the user'smiddlewarearray and handler on every request. Public API, ctx keys, and error shapes are unchanged (the pre-existing test suite passes unmodified as the parity proof). Two named overloads (no-middleware / with-middleware) so TypeScript infers the handler'sctxconcretely;MiddlewareCtx<Entries>accumulates the entries' key contributions. Client-construction failures keep their historical JSON responses — phase-guarded so user middleware / handler throws propagate exactly as beforesrc/middleware/client(@supabase/server/middleware/client) —withSupabaseClient<Database>(): contributesctx.supabase(RLS-scoped). UnderwithSupabaseit mirrors verified credentials (token only forusermode, matched publishable key by name); standalone it attaches the raw bearer — PostgREST verifies on every querysrc/middleware/admin-client(@supabase/server/middleware/admin-client) —withSupabaseAdminClient<Database>(): contributesctx.supabaseAdmin, secret key selected from the upstreamauthKeyNamewhen the request authenticated assecretsrc/middleware/claims(@supabase/server/middleware/claims) —withClaims: now JWKS-verified only (sharedverifyUserJwtcore: JWKS resolver caching, HS256 shared-secret path,sb_*passthrough). No token → contributesnull; invalid token → 401; missing JWKS → 500. The demo-grade decoder is gonesrc/middleware/postgres(@supabase/server/middleware/postgres) —withPostgres: contributesctx.postgres, an RLS-scopedpgclient. Every query runs in its own transaction that injects the caller's claims (request.jwt.claims) and drops to their role, PostgREST-style; the role is clamped toauthenticated/anon. Table-grants hint on 42501src/core/verify-user-jwt.ts— the extracted shared verification core used by bothverifyCredentialsandwithClaimspackage.json—@supabase/middleware@^0.3.0from npm (preview build retired) + four middleware subpath exports (also injsr.json/ tsdown)withPostgresCompatibility
SupabaseContextkeys keep exact semantics; error JSON shapes are identical. New subpaths are additive; release is a minor.d.tsusesNoInfer); consumers withskipLibCheckare unaffected@supabase/middleware@0.3.0is published on both npm and JSR; the JSR dry-run packaging check passes in CITry it
🤖 Generated with Claude Code