Skip to content

Conversation

@d-gubert
Copy link
Member

@d-gubert d-gubert commented Nov 18, 2025

Proposed changes (including videos or screenshots)

Upload handling function was previously loading the file contents via request.blob(), getting a ReadableStream (web api) from it, creating a new Readable stream (nodejs stream), and then piping the stream to busboy, which in turn accumulated the chunks again in a different Buffer instance.

Issue(s)

ARCH-1881

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • Bug Fixes
    • Fixes memory inefficiency during file uploads so uploaded file contents are no longer duplicated in server memory.

@changeset-bot
Copy link

changeset-bot bot commented Nov 18, 2025

🦋 Changeset detected

Latest commit: 860f117

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/ui-voip Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

Refactors getUploadFormData to require a web ReadableStream request body, pipes the stream to Busboy via Readable.fromWeb, replaces callback-style returnError patterns with Promise.withResolvers() and explicit reject/resolve flows, and updates the test spec to mock a ReadableStream body. Adds a changeset entry for the bugfix.

Changes

Cohort / File(s) Summary
Upload form data stream & errors
apps/meteor/app/api/server/lib/getUploadFormData.ts
Added runtime check that request.body is a ReadableStream; replaced returnError callback flows with Promise.withResolvers() and explicit reject()/resolve() in busboy/file/field handlers; use Readable.fromWeb(request.body) to pipe into Busboy; return the result promise.
Spec: mock request body
apps/meteor/app/api/server/lib/getUploadFormData.spec.ts
Replaced blob-based mock (blob().stream() + manual reader) with new ReadableStream that enqueues the test buffer; added ReadableStream import for testing.
Changeset
.changeset/tame-crabs-grin.md
Added a changeset entry documenting a patch that fixes double-buffering of uploaded file contents on the server.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant getUploadFormData
    participant NodeReadable as Readable.fromWeb
    participant Busboy
    participant resultPromise as Promise

    Caller->>getUploadFormData: call(request)
    getUploadFormData->>getUploadFormData: assert request.body is ReadableStream
    alt invalid body
        getUploadFormData-->>Caller: reject(MeteorError)
    else valid body
        getUploadFormData->>resultPromise: Promise.withResolvers()
        getUploadFormData->>NodeReadable: Readable.fromWeb(request.body)
        NodeReadable->>Busboy: pipe(stream)
        par Error paths
            Busboy-->>resultPromise: error / limits / invalid field -> reject(err)
            FileStream-->>resultPromise: file too large -> reject(err)
        and Success
            Busboy-->>resultPromise: finish -> resolve(uploadedFile)
        end
        resultPromise-->>Caller: return Promise
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Pay special attention to mapping of previous returnError branches to new reject() paths to ensure identical error semantics.
  • Verify Readable.fromWeb piping preserves backpressure and avoids extra buffering.
  • Check updated test accurately simulates streaming behavior and covers error/limit cases.

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • ggazzo

Poem

🐰 I hopped through streams both new and old,
Promises tidy where callbacks rolled,
Web streams now feed the pipe so keen,
No double buffers — memory clean. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: upload handler loading file contents to memory twice' directly and clearly describes the main change in the PR: fixing the double buffering issue in upload handling.
Linked Issues check ✅ Passed The PR directly addresses ARCH-1881 by refactoring the upload handler to use ReadableStream.pipe() directly with busboy, eliminating the intermediate Node.js Readable stream conversion that caused double buffering.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the double buffering issue: the spec file was updated to use ReadableStream, the handler was refactored to eliminate intermediate buffering, and a changeset entry documents the fix.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/upload-file-double-buffer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Nov 18, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@d-gubert
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/meteor/app/api/server/lib/getUploadFormData.spec.ts (1)

1-1: ReadableStream-based mock body matches implementation; minor nit on pull

Using ReadableStream from 'stream/web' and enqueuing the multipart buffer once is a good match for the new getUploadFormData behavior and avoids any blob-based double buffering in the test harness. Only minor nit: pull doesn’t use await, so you can drop the async keyword if you want to avoid an unnecessary microtask.

Also applies to: 41-44

apps/meteor/app/api/server/lib/getUploadFormData.ts (3)

2-2: Stream pipeline fix removes double buffering; confirm Promise.withResolvers / ReadableStream support

Routing request.body through Readable.fromWeb(...) after an explicit ReadableStream guard is a solid way to avoid the previous request.blob() + second buffering step, and returning the single resultPromise keeps control-flow straightforward. The only caveat is platform/tooling support: both Promise.withResolvers and ReadableStream from 'stream/web' assume a relatively modern Node/TS setup; if you still target older environments, consider a hand-rolled promise as a fallback:

-	const { promise: resultPromise, resolve, reject } = Promise.withResolvers<UploadResultWithOptionalFile<K>>();
+	let resolve!: (value: UploadResultWithOptionalFile<K>) => void;
+	let reject!: (reason?: unknown) => void;
+	const resultPromise = new Promise<UploadResultWithOptionalFile<K>>((res, rej) => {
+		resolve = res;
+		reject = rej;
+	});

Also applies to: 69-71, 91-91, 167-170


81-89: uploadedFile is always initialized, so 'No file or fields were uploaded' branch is effectively dead

Because uploadedFile is created up front as a stub object (with fields, file, etc. all undefined) and never set back to undefined, the if (!uploadedFile) check in onEnd will never trigger. The only actual “no data” failure path is now the 'No file uploaded' branch when fileOptional is false. If you don’t need the distinct 'No file or fields were uploaded' error any more, you could drop that first condition; otherwise, you’d need to change the initialization strategy (e.g., start with uploadedFile undefined and set it when you see the first field/file).

Also applies to: 97-108


110-141: Normalize rejection reasons for busboy errors/limits

Most error paths now reject with MeteorError, but a few are inconsistent: partsLimit and fieldsLimit call reject() with no reason, and filesLimit rejects with a bare string. For consumers expecting an Error-like shape (or specific MeteorError codes), that makes error handling more brittle. Consider standardizing these to MeteorError (or at least Error) and optionally calling cleanup() to detach listeners:

 bb.on('error', (err: Error) => {
-		reject(err);
+		reject(err);
 });

 bb.on('partsLimit', () => {
-		reject();
+		reject(new MeteorError('error-parts-limit-reached'));
 });
 bb.on('filesLimit', () => {
-		reject('Just 1 file is allowed');
+		reject(new MeteorError('error-too-many-files'));
 });
 bb.on('fieldsLimit', () => {
-		reject();
+		reject(new MeteorError('error-fields-limit-reached'));
 });

(Adjust error codes/messages to whatever convention you already use for upload errors.)

Also applies to: 153-165

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f77c72 and 9d5f0d2.

📒 Files selected for processing (2)
  • apps/meteor/app/api/server/lib/getUploadFormData.spec.ts (2 hunks)
  • apps/meteor/app/api/server/lib/getUploadFormData.ts (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-16T19:09:43.823Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37496
File: packages/apps-engine/tests/server/runtime/deno/LivenessManager.spec.ts:3-3
Timestamp: 2025-11-16T19:09:43.823Z
Learning: In Node.js, `EventEmitter` can be imported from either the 'events' module or the 'stream' module—both export the same reference. While 'events' is the canonical module, importing from 'stream' is valid and works correctly.

Applied to files:

  • apps/meteor/app/api/server/lib/getUploadFormData.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: 📦 Meteor Build (coverage)

@d-gubert d-gubert force-pushed the fix/upload-file-double-buffer branch from 9d5f0d2 to 1a99c43 Compare November 18, 2025 17:49
@d-gubert d-gubert added this to the 7.13.0 milestone Nov 18, 2025
@d-gubert d-gubert force-pushed the fix/upload-file-double-buffer branch from 1a99c43 to f820324 Compare November 18, 2025 18:58
@d-gubert d-gubert force-pushed the fix/upload-file-double-buffer branch from 1620b8d to 6388dd4 Compare November 18, 2025 20:31
@github-actions
Copy link
Contributor

github-actions bot commented Nov 18, 2025

📦 Docker Image Size Report

📈 Changes

Service Current Baseline Change Percent
sum of all images 1.2GiB 1.2GiB +12MiB
rocketchat 367MiB 355MiB +12MiB
omnichannel-transcript-service 141MiB 141MiB +1.8KiB
queue-worker-service 141MiB 141MiB +355B
ddp-streamer-service 127MiB 127MiB -292B
account-service 114MiB 114MiB -2.5KiB
stream-hub-service 111MiB 111MiB +227B
authorization-service 111MiB 111MiB +88B
presence-service 111MiB 111MiB +250B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/15 22:28", "11/16 01:28", "11/17 23:50", "11/18 22:53", "11/19 19:17", "11/19 19:43 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.14, 0.14, 0.14, 0.14]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "queue-worker-service" [0.14, 0.14, 0.14, 0.14, 0.14, 0.14]
  line "rocketchat" [0.36, 0.36, 0.35, 0.35, 0.35, 0.36]
  line "stream-hub-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
Loading

Statistics (last 5 days):

  • 📊 Average: 1.4GiB
  • ⬇️ Minimum: 1.2GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 1.2GiB
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-37550
  • Baseline: develop
  • Timestamp: 2025-11-19 19:43:15 UTC
  • Historical data points: 5

Updated: Wed, 19 Nov 2025 19:43:15 GMT

@codecov
Copy link

codecov bot commented Nov 19, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.96%. Comparing base (035e0c7) to head (860f117).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #37550      +/-   ##
===========================================
- Coverage    68.97%   68.96%   -0.02%     
===========================================
  Files         3359     3359              
  Lines       114214   114177      -37     
  Branches     20535    20533       -2     
===========================================
- Hits         78784    78745      -39     
- Misses       33335    33344       +9     
+ Partials      2095     2088       -7     
Flag Coverage Δ
e2e 57.39% <ø> (-0.05%) ⬇️
e2e-api 42.89% <ø> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@d-gubert d-gubert marked this pull request as ready for review November 19, 2025 10:38
@d-gubert d-gubert requested a review from a team as a code owner November 19, 2025 10:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/meteor/app/api/server/lib/getUploadFormData.ts (1)

69-71: Consider using MeteorError for consistency.

The error handling here uses a generic Error while the rest of the function uses MeteorError. For consistency across error handling paths, consider using MeteorError here as well.

Apply this diff:

-	if (!(request.body instanceof ReadableStream)) {
-		return Promise.reject(new Error('Invalid request body'));
-	}
+	if (!(request.body instanceof ReadableStream)) {
+		return Promise.reject(new MeteorError('invalid-request-body', 'Invalid request body'));
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9d5f0d2 and 6388dd4.

📒 Files selected for processing (3)
  • .changeset/tame-crabs-grin.md (1 hunks)
  • apps/meteor/app/api/server/lib/getUploadFormData.spec.ts (2 hunks)
  • apps/meteor/app/api/server/lib/getUploadFormData.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/app/api/server/lib/getUploadFormData.spec.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-16T19:09:43.823Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37496
File: packages/apps-engine/tests/server/runtime/deno/LivenessManager.spec.ts:3-3
Timestamp: 2025-11-16T19:09:43.823Z
Learning: In Node.js, `EventEmitter` can be imported from either the 'events' module or the 'stream' module—both export the same reference. While 'events' is the canonical module, importing from 'stream' is valid and works correctly.

Applied to files:

  • apps/meteor/app/api/server/lib/getUploadFormData.ts
🧬 Code graph analysis (1)
apps/meteor/app/api/server/lib/getUploadFormData.ts (1)
packages/core-services/src/index.ts (1)
  • MeteorError (56-56)
🔇 Additional comments (4)
apps/meteor/app/api/server/lib/getUploadFormData.ts (3)

97-108: LGTM!

The refactored error handling using reject and resolve is clear and correct.


167-168: Excellent fix - eliminates double buffering!

This change correctly addresses the PR objective by using Readable.fromWeb() to directly pipe the web stream to busboy, avoiding the previous approach that called request.blob() (which would buffer the entire content in memory first). The file contents are now only buffered once in busboy's onFile handler.


91-91: Promise.withResolvers() is fully supported in this project.

The project's Node.js version is set to 22.16.0 (per package.json engines), which exceeds the minimum v22.0.0 requirement for Promise.withResolvers() support. No action is needed.

.changeset/tame-crabs-grin.md (1)

1-5: LGTM!

The changeset correctly documents the patch fix for the double buffering issue.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/meteor/app/api/server/lib/getUploadFormData.ts (2)

115-118: Call cleanup() before rejecting on invalid field and truncated file to avoid lingering listeners and extra busboy events.

These two early‑error branches still don’t invoke cleanup(), so busboy keeps its event listeners attached and can continue to fire end/finish (and possibly limit) events after the promise has already been rejected. That was flagged previously and is still unresolved in this revision.

A minimal fix is to invoke cleanup() before rejecting in both cases:

 function onFile(
   fieldname: string,
   file: Readable & { truncated: boolean },
   { filename, encoding, mimeType: mimetype }: { filename: string; encoding: string; mimeType: string },
 ) {
   if (options.field && fieldname !== options.field) {
-    file.resume();
-    return reject(new MeteorError('invalid-field'));
+    cleanup();
+    file.resume();
+    return reject(new MeteorError('invalid-field'));
   }

   const fileChunks: Uint8Array[] = [];
   file.on('data', (chunk) => {
     fileChunks.push(chunk);
   });

   file.on('end', () => {
     if (file.truncated) {
-      fileChunks.length = 0;
-      return reject(new MeteorError('error-file-too-large'));
+      cleanup();
+      fileChunks.length = 0;
+      return reject(new MeteorError('error-file-too-large'));
     }

This ensures busboy’s listeners are removed as soon as you know the request should fail, preventing further callbacks after rejection and reducing the risk of subtle memory/behavior issues.

Also applies to: 125-129


153-165: Normalize busboy error/limit handlers to reject with MeteorError instead of bare values.

These handlers still reject with either undefined or a bare string, which was already called out earlier and leads to inconsistent error handling downstream (some callers will see Error instances, others raw strings or undefined).

Align them with the rest of the function by always rejecting with MeteorError instances and clear codes/reasons, e.g.:

 bb.on('error', (err: Error) => {
-  reject(err);
+  reject(err);
 });

 bb.on('partsLimit', () => {
-  reject();
+  reject(new MeteorError('error-parts-limit', 'Too many parts in multipart form'));
 });
 bb.on('filesLimit', () => {
-  reject('Just 1 file is allowed');
+  reject(new MeteorError('error-files-limit', 'Just 1 file is allowed'));
 });
 bb.on('fieldsLimit', () => {
-  reject();
+  reject(new MeteorError('error-fields-limit', 'Too many fields'));
 });

If you prefer, you can also call cleanup() in these handlers before rejecting to mirror the onFile error paths and ensure all listeners are removed on any terminal failure.

🧹 Nitpick comments (2)
apps/meteor/app/api/server/lib/getUploadFormData.ts (2)

91-91: Promise.withResolvers pattern is fine; async is now redundant and runtime support should be confirmed.

Managing completion via Promise.withResolvers gives you clear resolve/reject control; since you just return resultPromise, the async keyword on getUploadFormData is no longer needed and only adds a tiny bit of overhead. Also, Promise.withResolvers is a relatively new language feature, so it’s worth ensuring your Node/TS target and polyfills guarantee its availability in all deployment environments.

Consider:

  • Dropping async from the function declaration while still returning resultPromise, and
  • Verifying your tsconfig/lib and Node runtime version include Promise.withResolvers so this doesn’t break older environments.

Also applies to: 170-170


97-108: Tighten onEnd guard and make validation errors more informative.

Two small points here:

  • uploadedFile is initialized once and never set back to undefined, so if (!uploadedFile) is effectively unreachable right now; you can either remove this branch or set uploadedFile = undefined explicitly in error/cleanup paths if you want that guard to be meaningful.
  • options.validate.errors?.join(', ') will stringify Ajv error objects as "[object Object]"; if you care about diagnostics, consider either joining on each error’s .message or passing the raw errors array as details on MeteorError instead of interpolating it into the string.

Please verify how MeteorError is typically constructed in this codebase (code vs. reason vs. details) and whether you want structured Ajv errors available to callers rather than a generic interpolated string.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6388dd4 and 46268dc.

📒 Files selected for processing (1)
  • apps/meteor/app/api/server/lib/getUploadFormData.ts (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-16T19:09:43.823Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37496
File: packages/apps-engine/tests/server/runtime/deno/LivenessManager.spec.ts:3-3
Timestamp: 2025-11-16T19:09:43.823Z
Learning: In Node.js, `EventEmitter` can be imported from either the 'events' module or the 'stream' module—both export the same reference. While 'events' is the canonical module, importing from 'stream' is valid and works correctly.

Applied to files:

  • apps/meteor/app/api/server/lib/getUploadFormData.ts
🧬 Code graph analysis (1)
apps/meteor/app/api/server/lib/getUploadFormData.ts (1)
packages/core-services/src/index.ts (1)
  • MeteorError (56-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (1)
apps/meteor/app/api/server/lib/getUploadFormData.ts (1)

2-2: Streaming from request.body via Readable.fromWeb correctly removes the extra buffering; verify ReadableStream compatibility.

Switching from request.blob() to Readable.fromWeb(request.body) plus the ReadableStream guard is a good way to avoid double-buffering the upload in memory and satisfy the PR goal. The only thing to double‑check is that every caller runs in an environment where request.body is a Web ReadableStream instance compatible with stream/web’s ReadableStream constructor (no custom fetch/ReadableStream polyfills), otherwise the instanceof ReadableStream check will cause valid uploads to be rejected with “Invalid request body”.

Please confirm that all runtime environments using this code expose Request.body as a stream/web‑compatible ReadableStream and that this function is only called for requests that are expected to have a body (e.g., POST uploads).

Also applies to: 69-71, 167-168

@d-gubert d-gubert added the stat: QA assured Means it has been tested and approved by a company insider label Nov 19, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Nov 19, 2025
@kodiakhq kodiakhq bot merged commit 3b92e81 into develop Nov 19, 2025
86 of 88 checks passed
@kodiakhq kodiakhq bot deleted the fix/upload-file-double-buffer branch November 19, 2025 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants