Repository: unjs/h3
Pull request #457 (2023-07-24 17:38:57)

feat(event): `event.body` with readable stream
<!---
☝️ PR title should follow conventional commits (https://conventionalcommits.org) 
-->

### 🔗 Linked issue

#73, Needed by #454

### ❓ Type of change

<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->

- [ ] 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
- [ ] 🐞 Bug fix (a non-breaking change that fixes an issue)
- [ ] 👌 Enhancement (improving an existing functionality like performance)
- [x] ✨ New feature (a non-breaking change that adds functionality)
- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)
- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

### 📚 Description

This PR adds an experimental getter to the `event` object in order to access body via a lazy readable stream

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] I have linked an issue or discussion.
- [ ] I have updated the documentation accordingly.



Patch:
diff --git a/src/event/event.ts b/src/event/event.ts
index dc4e949..8dbf406 100644
--- a/src/event/event.ts
+++ b/src/event/event.ts
@@ -28,6 +28,7 @@ export class H3Event implements Pick<FetchEvent, "respondWith"> {
   _headers: Headers | undefined;
   _path: string | undefined;
   _url: URL | undefined;
+  _body: BodyInit | undefined;
 
   // Response
   _handled = false;
@@ -85,6 +86,25 @@ export class H3Event implements Pick<FetchEvent, "respondWith"> {
     return this.node.res;
   }
 
+  get body() {
+    if (!this._body) {
+      this._body = new ReadableStream({
+        start: (controller) => {
+          this.node.req.on("data", (chunk) => {
+            controller.enqueue(chunk);
+          });
+          this.node.req.on("end", () => {
+            controller.close();
+          });
+          this.node.req.on("error", (err) => {
+            controller.error(err);
+          });
+        },
+      });
+    }
+    return this._body;
+  }
+
   // Implementation of FetchEvent
   respondWith(r: H3Response | PromiseLike<H3Response>): void {
     Promise.resolve(r).then((_response) => {


Test patch:
diff --git a/test/event.test.ts b/test/event.test.ts
index 31a3a94..4eb6d32 100644
--- a/test/event.test.ts
+++ b/test/event.test.ts
@@ -58,4 +58,24 @@ describe("Event", () => {
     const result = await request.get("/hello");
     expect(result.text).toMatch(/http:\/\/127.0.0.1:\d+\/hello/);
   });
+
+  it("can read request body", async () => {
+    app.use(
+      "/",
+      eventHandler(async (event) => {
+        const bodyStream = event.body as unknown as NodeJS.ReadableStream;
+        let bytes = 0;
+        for await (const chunk of bodyStream) {
+          bytes += chunk.length;
+        }
+        return {
+          bytes,
+        };
+      })
+    );
+
+    const result = await request.post("/hello").send(Buffer.from([1, 2, 3]));
+
+    expect(result.body).toMatchObject({ bytes: 3 });
+  });
 });