diff --git a/README.md b/README.md index ff733d1..41fe991 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ outgoing or incoming request. ```typescript app.register((req, res) => { /** - * Skip Uint8Array responses: + * Ignore non-text responses: * if (typeof res.content !== "string") return res; * * res.content = res.content.replace("Hello", "Hi"); @@ -262,3 +262,32 @@ app.get("/", (req) => { ``` Yes, that's it. Everything else should work as you are used to. :) + +### Streaming + +```typescript +app.get("/", (req) => { + const stream = new ReadableStream({ + start(controller) { + let i = 0; + const interval = setInterval(() => { + controller.enqueue(new TextEncoder().encode("hello world!")); + + if (i === 9) { + clearInterval(interval); + controller.close(); + } + + i++; + }, 500); + } + }); + + return { + content: stream, + headers: { + "Content-Type": "text/html" + } + }; +}); +``` diff --git a/aqua.ts b/aqua.ts index edb5e49..1133d95 100644 --- a/aqua.ts +++ b/aqua.ts @@ -22,12 +22,21 @@ export type Method = | "TRACE" | "PATCH"; +type ResponseContent = + | Uint8Array + | Blob + | BufferSource + | FormData + | URLSearchParams + | ReadableStream + | string; + interface ContentResponse { statusCode?: number; headers?: Record; cookies?: Record; redirect?: string; - content: string | Uint8Array; + content: ResponseContent; } interface RedirectResponse { @@ -35,11 +44,11 @@ interface RedirectResponse { headers?: Record; cookies?: Record; redirect: string; - content?: string | Uint8Array; + content?: ResponseContent; } export type ResponseObject = ContentResponse | RedirectResponse; -export type Response = string | Uint8Array | ResponseObject; +export type Response = ResponseContent | ResponseObject; export interface Request { _internal: { @@ -237,23 +246,18 @@ export default class Aqua { return urlParameters; } - private isTextContent(response: Response): response is string { - return typeof response === "string"; - } - - private isDataContent(response: Response): response is Uint8Array { - return response instanceof Uint8Array; - } - private convertResponseToResponseObject(response: Response): ResponseObject { - if (this.isTextContent(response)) { + if (typeof response === "string") { return { headers: { "Content-Type": "text/html; charset=UTF-8" }, content: response, }; } - if (this.isDataContent(response)) { + if ( + typeof response !== "object" || + (!("content" in response) && !("redirect" in response)) + ) { return { content: response }; } diff --git a/tests/uptests.ts b/tests/uptests.ts index b3f534a..970426a 100644 --- a/tests/uptests.ts +++ b/tests/uptests.ts @@ -655,6 +655,41 @@ registerTest("Fallback handler error types working?", async () => { } }); +registerTest("Are ReadableStream responses working?", async () => { + app.get("/readable-stream", (_req) => { + const stream = new ReadableStream({ + start(controller) { + let i = 0; + const interval = setInterval(() => { + controller.enqueue(new TextEncoder().encode("hello world!")); + + if (i === 4) { + clearInterval(interval); + controller.close(); + } + + i++; + }, 50); + }, + }); + + return { + content: stream, + headers: { + "Content-Type": "text/html", + }, + }; + }); + + const content = await requestContent("/readable-stream"); + const expected = "hello world!".repeat(5); + if (content !== expected) { + throw new Error( + `Expected handler to return "${expected}". Instead got: ${content}`, + ); + } +}); + setInterval(() => { if (registeredTests === solvedTests) Deno.exit(0); }, 500);