Skip to content
Open
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
1,300 changes: 1,300 additions & 0 deletions .chief/prds/main/claude.log

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions .chief/prds/main/prd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"project": "Express.js Adapter for `@supabase/server`",
"description": "`@supabase/server` already ships first-party adapters for Hono and H3 under `src/adapters/`. Express.js is one of the most widely deployed Node web frameworks, but Express users currently have to hand-roll a bridge from `req`/`res` to `createSupabaseContext` (which expects a Fetch `Request`). This feature adds a first-party Express 5 adapter at `src/adapters/express/` that mirrors the Hono/H3 adapter contract while feeling idiomatic in Express — context lives on `res.locals`, errors flow through `next(err)` by default but can be customized, and a small set of route helpers (`requireAuth()`, route wrappers) shorten the common cases. The adapter must be entirely additive: no changes to `core/`, no new runtime dependencies, and Express stays an **optional** peer dependency so users who don't need it pay nothing.",
"userStories": [
{
"id": "US-001",
"title": "Wire build, exports, and peer dependency for the new adapter",
"description": "As a maintainer, I need the Express adapter entry point registered in the build pipeline so `import from '@supabase/server/adapters/express'` resolves to a published artifact.",
"acceptanceCriteria": [
"Add `express` to `package.json#peerDependencies` (`^5.0.0`) and to `peerDependenciesMeta` as `optional: true`.",
"Add `express` (matching `^5.0.0`) and `@types/express` to `devDependencies` so tests can import it.",
"Add `./adapters/express` entry to `package.json#exports` mirroring the shape of the existing `./adapters/hono` and `./adapters/h3` entries (types/import/require).",
"Add the `src/adapters/express/index.ts` entry to `tsdown.config.ts`.",
"Add the same entry to `jsr.json` if the existing adapters are listed there.",
"`pnpm build` succeeds and produces `dist/adapters/express/index.{mjs,cjs,d.mts,d.cts}`.",
"`pnpm typecheck` passes."
],
"priority": 1,
"passes": true
},
{
"id": "US-002",
"title": "Bridge Express `req` to a Fetch `Request`",
"description": "As a developer, I need Express's Node `IncomingMessage` translated into a Fetch `Request` so the existing `createSupabaseContext` works unchanged.",
"acceptanceCriteria": [
"Implement an internal `toFetchRequest(req)` helper inside the adapter folder (not exported).",
"Build the absolute URL from `req.protocol`, `req.get('host')`, and `req.originalUrl` (fall back to `req.url` if `originalUrl` is unset).",
"Copy all Express request headers into `Headers`, preserving multi-value headers (e.g., `Set-Cookie`, `Cookie`).",
"Forward the request body for non-`GET`/`HEAD` methods. The body must be readable by `createSupabaseContext`'s downstream code paths even if the user has not registered `express.json()` or `express.raw()` (use the raw `IncomingMessage` stream when no parsed body exists).",
"Unit test covers: header preservation (case-insensitive), `Authorization` and `apikey` headers, multi-value `Cookie`, absolute URL composition behind `X-Forwarded-Proto` when `app.set('trust proxy', true)` is enabled, and body forwarding for `POST`.",
"No runtime dependency on `node:stream/web` polyfills — Node 20+ already provides `Request`/`Headers` globally.",
"`pnpm typecheck` and `pnpm test` pass."
],
"priority": 2,
"passes": true
},
{
"id": "US-003",
"title": "Implement `withSupabase()` middleware for Express 5",
"description": "As an Express developer, I want a single middleware factory that authenticates the request and stores the Supabase context on `res.locals` so my route handlers can use it.",
"acceptanceCriteria": [
"Export `withSupabase(config?: WithSupabaseExpressConfig): RequestHandler` from `src/adapters/express/middleware.ts`.",
"If `res.locals.supabaseContext` is already set by an earlier middleware, skip auth and call `next()` immediately (matches the Hono/H3 short-circuit pattern, enables route-level overrides).",
"Build a Fetch `Request` via the US-002 helper, call `createSupabaseContext`, and on success assign the result to `res.locals.supabaseContext`.",
"On `AuthError` from `createSupabaseContext`, invoke the configured error handler (see US-004).",
"Async errors (rejected promises inside the middleware) propagate to Express's error pipeline — verify behavior on Express 5's native async-error support without using `express-async-errors` or similar.",
"TypeScript: augment `Express.Locals` via declaration merging so `res.locals.supabaseContext` is typed as `SupabaseContext` after the middleware runs (mirrors h3's `H3EventContext` augmentation).",
"Tests cover: `'user'` mode success, `'user'` mode invalid JWT, `'publishable'` mode, `'secret'` mode, `'none'` mode, array `auth` form, missing-credential failure, and the short-circuit when `res.locals.supabaseContext` is pre-populated.",
"`pnpm typecheck` and `pnpm test` pass."
],
"priority": 3,
"passes": true
},
{
"id": "US-004",
"title": "Configurable `onError` option",
"description": "As an Express developer, I want to control how auth errors are surfaced (default: `next(error)`; alternative: send a response directly) so the adapter fits both Express-idiomatic and standalone use cases.",
"acceptanceCriteria": [
"Add `onError?: (error: AuthError, req, res, next) =\u003e void | Promise\u003cvoid\u003e` to `WithSupabaseExpressConfig`.",
"Default behavior when `onError` is omitted: call `next(error)` so the user's error middleware handles it (Express-idiomatic).",
"When `onError` is provided, the adapter calls it instead and does NOT call `next()` itself — the handler owns response/next semantics.",
"If `onError` throws or rejects, the thrown error propagates via `next(err)` so Express's error pipeline still triggers.",
"The `AuthError` passed to `onError` retains its `.status`, `.code`, and `.message` so callers can map to HTTP responses without re-parsing.",
"Tests cover: default `next(error)` path, custom `onError` that responds with `res.status(401).json(...)`, custom `onError` that throws (must surface via `next`).",
"`pnpm typecheck` and `pnpm test` pass."
],
"priority": 4,
"passes": true
},
{
"id": "US-005",
"title": "Helper — `requireAuth()`",
"description": "As an Express developer, I want a one-line guard that asserts the request is authenticated and short-circuits with 401 if not, separate from `withSupabase()` so I can compose it on a per-route basis.",
"acceptanceCriteria": [
"Export `requireAuth(modes?: AuthModeWithKey | AuthModeWithKey[]): RequestHandler` from `src/adapters/express/index.ts`.",
"Reads `res.locals.supabaseContext`; if absent, calls `next(new AuthError(...))` (the user must mount `withSupabase()` first).",
"If `modes` is provided, additionally asserts that `res.locals.supabaseContext.authMode` is one of the allowed modes, otherwise calls `next(new AuthError(..., 'INVALID_CREDENTIALS', 401))`.",
"Tests cover: passes through when context is present and mode matches, fails with 401 when context missing, fails with 401 when mode mismatches.",
"`pnpm typecheck` and `pnpm test` pass."
],
"priority": 5,
"passes": true
},
{
"id": "US-006",
"title": "Helper — `withSupabaseRoute()` route wrapper",
"description": "As an Express developer, I want to wrap an individual route handler so the Supabase context is established just for that route, without mounting global middleware.",
"acceptanceCriteria": [
"Export `withSupabaseRoute(config, handler): RequestHandler` from `src/adapters/express/index.ts`.",
"`handler` receives `(req, res, next, ctx: SupabaseContext)` — the resolved context is passed as the fourth argument so the user does not need to read `res.locals`.",
"On auth failure, the configured `onError` (or default `next(error)`) runs; `handler` is not invoked.",
"Async errors thrown by `handler` propagate via Express 5's native async handling.",
"Tests cover: success path invokes handler with `ctx`, auth failure path skips handler and surfaces error, async-thrown error in handler reaches Express error pipeline.",
"`pnpm typecheck` and `pnpm test` pass."
],
"priority": 6,
"passes": true
},
{
"id": "US-007",
"title": "Public types and barrel export",
"description": "As a TypeScript consumer, I want a single import surface that exposes the middleware, helpers, and the adapter's public types so I can extend them.",
"acceptanceCriteria": [
"`src/adapters/express/index.ts` re-exports `withSupabase`, `requireAuth`, `withSupabaseRoute`, and the `WithSupabaseExpressConfig` type.",
"No `any`, no `// @ts-ignore` anywhere in the adapter sources.",
"The declaration-merged `Express.Locals.supabaseContext` is documented in the adapter's source via JSDoc.",
"`pnpm lint` and `pnpm typecheck` pass."
],
"priority": 7,
"passes": true
},
{
"id": "US-008",
"title": "Adapter documentation",
"description": "As a new user, I want adapter docs that match the Hono/H3 docs so I can drop the Express adapter into a project without reading source.",
"acceptanceCriteria": [
"Create `docs/adapters/express.md` mirroring the structure of `docs/adapters/hono.md`.",
"Sections include: setup (peer-dep install), basic example using `withSupabase()`, per-route auth using `withSupabaseRoute()` and `requireAuth()`, custom `onError` example, CORS note (point users to `cors` npm package — adapter does not handle CORS).",
"Add a row to `src/adapters/README.md` adapter table: `Express | @supabase/server/adapters/express | ^5.0.0 | docs/adapters/express.md`.",
"Add the matching row to the top-level `README.md` adapter table."
],
"priority": 8,
"passes": true
},
{
"id": "US-009",
"title": "Adapter README discoverability",
"description": "As a maintainer, I want the Express adapter to satisfy the contributor checklist in `src/adapters/README.md` so future PRs can use it as a reference.",
"acceptanceCriteria": [
"All items in `src/adapters/README.md` \"Code quality bar\" section are met (tests for every auth mode, strict TS, no new runtime deps, matches existing adapter shape, build wired up, docs added, both tables updated).",
"`pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm build` all pass on a clean checkout."
],
"priority": 9,
"passes": true
}
]
}
Loading