Skip to content

Commit 63ea32d

Browse files
brilloutclaude
andauthored
fix(express): make toFetchHandler always resolve to a Response (#9)
Since 0.2.3, toFetchHandler is built on connectToWeb, whose WebHandler can resolve to undefined (the "no middleware handled the request" pass-through signal). That undefined is not assignable to Server['fetch'], breaking the documented `fetch: toFetchHandler(app)` pattern. Wrap connectToWeb with a 404 fallback so toFetchHandler always resolves to a Response, mirroring @universal-middleware's own nextOr404 convention for terminal handlers, while keeping the cancel-event propagation that motivated the move away from srvx's toFetchHandler. Claude-Session: https://claude.ai/code/session_01GcNoNA6CzTQiSAS589U6Yu Co-authored-by: Claude <noreply@anthropic.com>
1 parent a8f42b7 commit 63ea32d

3 files changed

Lines changed: 33 additions & 4 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@vikejs/express": patch
3+
---
4+
5+
fix: make `toFetchHandler` always resolve to a `Response`
6+
7+
Since `0.2.3`, `toFetchHandler` is built on `connectToWeb`, whose `WebHandler` can resolve to `undefined` (the "no middleware handled the request" signal). That `undefined` is not assignable to `Server['fetch']`, breaking the documented `fetch: toFetchHandler(app)` pattern.
8+
9+
`toFetchHandler` now wraps `connectToWeb` with a `404` fallback so it always resolves to a `Response` (mirroring `@universal-middleware`'s own `nextOr404` convention), while keeping the cancel-event propagation that motivated the move away from srvx's `toFetchHandler`.

packages/express/src/index.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import { describe, expect, it, vi } from "vitest";
33

44
vi.mock("vike/universal-middleware", () => ({ default: () => {} }));
55

6-
import { connectToWeb, toFetchHandler } from "./index.js";
6+
import { toFetchHandler } from "./index.js";
77

88
describe("toFetchHandler", () => {
9-
it("is connectToWeb", () => {
10-
expect(toFetchHandler).toBe(connectToWeb);
9+
it("falls back to 404 when the app doesn't handle the request", async () => {
10+
const app = express();
11+
12+
const response = await toFetchHandler(app)(new Request("http://localhost/missing"));
13+
expect(response).toBeInstanceOf(Response);
14+
expect(response.status).toBe(404);
1115
});
1216

1317
it("handles GET request", async () => {

packages/express/src/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,23 @@ import { type App, apply, connectToWeb } from "@universal-middleware/express";
22
import vikeMiddleware from "vike/universal-middleware";
33

44
export * from "@universal-middleware/express";
5-
export const toFetchHandler = connectToWeb;
5+
6+
/**
7+
* Convert an Express app into a web `fetch` handler.
8+
*
9+
* `connectToWeb()` resolves to `undefined` when the request is passed through
10+
* without being handled (Connect's `next()`, or a middleware returning `false`).
11+
* A terminal `fetch` handler must always return a `Response`, so we fall back to
12+
* a `404` — mirroring the `nextOr404` convention of `@universal-middleware`'s own
13+
* handlers. This keeps the return type assignable to `Server['fetch']`.
14+
*
15+
* (`@vikejs/express` used to re-export srvx's `toFetchHandler`, but that one
16+
* deadlocks and doesn't work on Deno/Bun — see srvx#132.)
17+
*/
18+
export function toFetchHandler(app: Parameters<typeof connectToWeb>[0]): (request: Request) => Promise<Response> {
19+
const handler = connectToWeb(app);
20+
return async (request) => (await handler(request)) ?? new Response(null, { status: 404 });
21+
}
622

723
type EnhancedMiddlewareExpress = Parameters<typeof apply>[1][number];
824

0 commit comments

Comments
 (0)