diff --git a/src/app.ts b/src/app.ts index c06d716b..9f46646f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,7 +7,14 @@ import { H3Event, } from "./event"; import { createError } from "./error"; -import { send, sendStream, isStream, MIMES } from "./utils"; +import { + send, + sendStream, + isStream, + MIMES, + sendWebResponse, + isWebResponse, +} from "./utils"; import type { EventHandler, LazyEventHandler } from "./types"; export interface Layer { @@ -125,6 +132,11 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { } if (val) { + // Web Response + if (isWebResponse(val)) { + return sendWebResponse(event, val); + } + // Stream if (isStream(val)) { return sendStream(event, val); diff --git a/src/utils/response.ts b/src/utils/response.ts index bf0117fe..4494564e 100644 --- a/src/utils/response.ts +++ b/src/utils/response.ts @@ -162,6 +162,10 @@ export function isStream(data: any): data is Readable | ReadableStream { return false; } +export function isWebResponse(data: any): data is Response { + return typeof Response !== "undefined" && data instanceof Response; +} + export function sendStream( event: H3Event, stream: Readable | ReadableStream @@ -270,3 +274,25 @@ export function writeEarlyHints( cb(); } } + +export function sendWebResponse(event: H3Event, response: Response) { + for (const [key, value] of response.headers.entries()) { + event.node.res.setHeader(key, value); + } + if (response.status) { + event.node.res.statusCode = sanitizeStatusCode( + response.status, + event.node.res.statusCode + ); + } + if (response.statusText) { + event.node.res.statusMessage = sanitizeStatusMessage(response.statusText); + } + if (response.redirected) { + event.node.res.setHeader("location", response.url); + } + if (!response.body) { + return event.node.res.end(); + } + return sendStream(event, response.body); +} diff --git a/test/app.test.ts b/test/app.test.ts index 2886cc5a..ef127be0 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -13,6 +13,7 @@ import { // Node.js 16 limitations const readableStreamSupported = typeof ReadableStream !== "undefined"; const blobSupported = typeof Blob !== "undefined"; +const responseSupported = typeof Response !== "undefined"; describe("app", () => { let app: App; @@ -33,6 +34,23 @@ describe("app", () => { expect(res.body).toEqual({ url: "/" }); }); + it.runIf(responseSupported)("can return Response directly", async () => { + app.use( + "/", + eventHandler( + () => + new Response("Hello World!", { + status: 201, + headers: { "x-test": "test" }, + }) + ) + ); + + const res = await request.get("/"); + expect(res.statusCode).toBe(201); + expect(res.text).toBe("Hello World!"); + }); + it("can return a 204 response", async () => { app.use( "/api",