Skip to content

Conversation

@cirospaciari
Copy link
Member

@cirospaciari cirospaciari commented Oct 7, 2025

What does this PR do?

Just a refactoring isolating the usage of Response body this is the first step necessary for #23319

How did you verify your code works?

CI

@robobun
Copy link
Collaborator

robobun commented Oct 7, 2025

Updated 7:07 AM PT - Oct 7th, 2025

@Jarred-Sumner, your commit e0e7ce2 has 1 failures in Build #28362 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 23313

That installs a local version of the PR into your bun-23313 executable, so you can run:

bun-23313 --bun

@cirospaciari cirospaciari changed the title WIP Response refactoring handling Response refactoring handling Oct 7, 2025
@cirospaciari cirospaciari marked this pull request as ready for review October 7, 2025 02:48
@cirospaciari cirospaciari changed the title Response refactoring handling refactor(Response) isolate body usage Oct 7, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Refactors Request/Response/Body internals to use private fields and new constructors/accessors (e.g., Response.init, Request.init2, getBodyValue, getBodyReadableStream, setUrl). Call sites updated to the accessor API, owned ReadableStream ownership propagated, BakeResponse/JS bindings adjusted, and new cyclic-reference tests added.

Changes

Cohort / File(s) Change summary
Response core & Body
src/bun.js/webcore/Response.zig, src/bun.js/webcore/Body.zig, src/bun.js/webcore/Body.zig
Made Response fields private (#body, #init, #url, etc.); added Response.init and many getters/setters (getBodyValue, getBodyLen, getInitStatusCode/Text, getInitHeaders/swapInitHeaders, setInit/setInitHeaders/setUrl/getUrl/getUTF8Url/getMethod); extended Body.Value/Body APIs to accept/propagate optional owned ReadableStream (cloneWithReadableStream, tee, setPromise, run, bufferLockedBodyValue).
Fetch & Blob handling
src/bun.js/webcore/fetch.zig, src/bun.js/webcore/Blob.zig
Replaced literal Response/Body construction with Response.init(...) and updated all body access to response.getBodyValue()/body.*; adjusted buffering, finalization, toResponse, data-URL and S3 response construction, and owned_readable propagation; Blob/stream handling updated to use bodyValue accessors.
Request internals
src/bun.js/webcore/Request.zig
Renamed public fields to private (#headers, #body); added #js_ref and https flag; added init/init2 constructors (Request.init2), getBodyReadableStream/detachReadableStream, and updated cloning/finalize/toJS and other access sites to use private fields and new helpers.
Server, routes & router
src/bun.js/api/server.zig, src/bun.js/api/server/RequestContext.zig, src/bun.js/api/server/FileRoute.zig, src/bun.js/api/server/StaticRoute.zig, src/bun.js/api/filesystem_router.zig
Switched Request construction to new init variants (Request.init/Request.init2/Request.new(Request.init...)); replaced direct resp.url/resp.init.headers/resp.body.value usage with resp.setUrl, resp.getInitHeaders/swapInitHeaders, resp.getBodyValue and local bodyValue usage; updated blob/header handling and URL retrieval via getUTF8Url(...).
HTML rewriter & Bake/DevServer
src/bun.js/api/html_rewriter.zig, src/bake/DevServer.zig, src/bun.js/webcore/BakeResponse.zig
Migrated Response creation to Response.init calls; replaced direct field access with accessor API; BakeResponse constructor gained js_this parameter and call sites updated to pass it.
Node types, bindings & GlobalObject
src/bun.js/node/types.zig, src/bun.js/bindings/JSBakeResponse.cpp, src/bun.js/bindings/JSGlobalObject.zig
Replaced request/response body access with getBodyValue(); BakeResponseClass__constructForSSR signature gained js_this param and call sites pass encoded instance; added early-return path in getBodyStreamOrBytesForWasmStreaming for Locked bodies.
Runtime metadata & classes
src/bun.js/webcore/response.classes.ts
Added constructNeedsThis: true and values: ["stream"] to Request and Response class definitions.
Tests & config
test/js/web/fetch/request-cyclic-reference.test.ts, test/js/web/fetch/response-cyclic-reference.test.ts, test/internal/ban-limits.json
Added tests that stress cyclic ReadableStream references for Request/Response and verify heap counts; decremented "// autofix" value in ban-limits.json from 167→166.

Possibly related PRs

Suggested reviewers

  • alii
  • pfgithub

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 (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly describes refactoring the Response body usage, matching the PR’s main change of isolating body state behind accessors; it is concise and specific.
Description Check ✅ Passed The description uses the required template sections “What does this PR do?” and “How did you verify your code works?”, providing a concise summary and verification via CI.
✨ 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 ciro/fix-readable-stream-cycle-ref

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

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/bun.js/api/server/RequestContext.zig (1)

2225-2251: Header access refactor good; fix Content‑Range decision and avoid double header lookup

  • Minor logic bug (pre‑existing): mutating needs_content_range suppresses writing Content‑Range when it’s missing. Don’t overwrite the flag; just set status when both are true.

Apply this minimal fix:

-                has_content_range = headers_.fastHas(.ContentRange);
-                needs_content_range = needs_content_range and has_content_range;
-                if (needs_content_range) {
+                has_content_range = headers_.fastHas(.ContentRange);
+                if (needs_content_range and has_content_range) {
                     status = 206;
                 }
  • Optional: avoid two passes over headers. Swap once and reuse for content-type detection and emission.
-            const content_type, const needs_content_type, const content_type_needs_free = getContentType(
-                response.getInitHeaders(),
-                &this.blob,
-                this.allocator,
-            );
+            const swapped_headers = response.swapInitHeaders();
+            const content_type, const needs_content_type, const content_type_needs_free =
+                getContentType(swapped_headers, &this.blob, this.allocator);
             defer if (content_type_needs_free) content_type.deinit(this.allocator);
-            var has_content_disposition = false;
-            var has_content_range = false;
-            if (response.swapInitHeaders()) |headers_| {
+            var has_content_disposition = false;
+            var has_content_range = false;
+            if (swapped_headers) |headers_| {
                 defer headers_.deref();
                 has_content_disposition = headers_.fastHas(.ContentDisposition);
                 has_content_range = headers_.fastHas(.ContentRange);
-                needs_content_range = needs_content_range and has_content_range;
-                if (needs_content_range) {
+                if (needs_content_range and has_content_range) {
                     status = 206;
                 }
                 this.doWriteStatus(status);
                 this.doWriteHeaders(headers_);
             } else if (needs_content_range) {
                 status = 206;
                 this.doWriteStatus(status);
             } else {
                 this.doWriteStatus(status);
             }

This keeps later logic intact:

  • status 206 when range is needed (with or without header present)
  • later block still writes Content‑Range when needed and header absent.

Also applies to: 2287-2303

src/bun.js/webcore/Response.zig (2)

181-186: try used in function that doesn’t return an error union.

header() returns ?[]const u8 but uses try on fastGet. If fastGet doesn’t return an error union, try is invalid; if it does, propagate the error in the signature.

Apply one of:

-pub fn header(this: *const Response, name: bun.webcore.FetchHeaders.HTTPHeaderName) ?[]const u8 {
-    return if (try (this.#init.headers orelse return null).fastGet(name)) |str|
+pub fn header(this: *const Response, name: bun.webcore.FetchHeaders.HTTPHeaderName) ?[]const u8 {
+    return if ((this.init.headers orelse return null).fastGet(name)) |str|
         str.slice()
     else
         null;
 }

Or, if fastGet can throw:

-pub fn header(this: *const Response, name: bun.webcore.FetchHeaders.HTTPHeaderName) ?[]const u8 {
+pub fn header(this: *const Response, name: bun.webcore.FetchHeaders.HTTPHeaderName) bun.JSError!?[]const u8 {
-    return if (try (this.#init.headers orelse return null).fastGet(name)) |str|
+    return if (try (this.init.headers orelse return null).fastGet(name)) |str|
         str.slice()
     else
         null;
 }

795-802: Inconsistent field naming in struct literal.

Mixes .#body with .init. After removing “#”, ensure both fields are consistent:

-inline fn emptyWithStatus(_: *jsc.JSGlobalObject, status: u16) Response {
-    return bun.new(Response, .{
-        .#body = Body{
+inline fn emptyWithStatus(_: *jsc.JSGlobalObject, status: u16) Response {
+    return bun.new(Response, .{
+        .body = Body{
             .value = Body.Value{ .Null = {} },
         },
-        .init = Init{
+        .init = Init{
             .status_code = status,
         },
     });
 }
🧹 Nitpick comments (4)
src/bun.js/api/filesystem_router.zig (1)

284-285: Accessor usage is correct; consider deferring slice cleanup

Switch to getUTF8Url aligns with the new API. If it returns a ZigString.Slice requiring deinit, consider deferring path.deinit() so non-rewritten branches don’t leak.

Example (outside the changed lines):

var path: ZigString.Slice = brk: { ... };
defer path.deinit();
src/bun.js/webcore/Blob.zig (1)

1342-1400: Response.getBodyValue() refactor looks correct; small optional tidy

Semantics match prior response.body.value usage. Locked/Error/S3 paths behave the same; toReadableStream() priming before locked.readable access is correct. Optionally, reduce repetition:

  • Cache a local alias for bodyValue.Locked when used multiple times.
src/bun.js/api/server/RequestContext.zig (1)

681-691: After abort, consider marking body as Used

You clear the strong readable and abort it. Consider also setting bodyValue.* = .{ .Used = {} } to prevent any accidental re-consumption later.

src/bun.js/webcore/fetch.zig (1)

1005-1031: Comment mismatch vs behavior (nit).

The “Scenario 2b” comment says “keep loading”, but the code ignores body when promise.isEmptyOrUndefinedOrNull(). Behavior seems right; update the comment for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear 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 90c0c72 and dde97af.

📒 Files selected for processing (14)
  • src/bake/DevServer.zig (1 hunks)
  • src/bun.js/api/filesystem_router.zig (1 hunks)
  • src/bun.js/api/html_rewriter.zig (10 hunks)
  • src/bun.js/api/server.zig (1 hunks)
  • src/bun.js/api/server/FileRoute.zig (1 hunks)
  • src/bun.js/api/server/RequestContext.zig (10 hunks)
  • src/bun.js/api/server/StaticRoute.zig (3 hunks)
  • src/bun.js/node/types.zig (1 hunks)
  • src/bun.js/webcore/BakeResponse.zig (2 hunks)
  • src/bun.js/webcore/Blob.zig (2 hunks)
  • src/bun.js/webcore/Request.zig (1 hunks)
  • src/bun.js/webcore/Response.zig (28 hunks)
  • src/bun.js/webcore/fetch.zig (12 hunks)
  • test/internal/ban-limits.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/api/filesystem_router.zig
  • src/bun.js/webcore/Blob.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bake/DevServer.zig
  • src/bun.js/node/types.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/api/html_rewriter.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/api/filesystem_router.zig
  • src/bun.js/webcore/Blob.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bake/DevServer.zig
  • src/bun.js/node/types.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/api/html_rewriter.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/filesystem_router.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/api/server.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/api/html_rewriter.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/api/filesystem_router.zig
  • src/bun.js/webcore/Blob.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bun.js/node/types.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/api/html_rewriter.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/internal/ban-limits.json
🧠 Learnings (1)
📚 Learning: 2025-09-26T01:10:04.233Z
Learnt from: nektro
PR: oven-sh/bun#22781
File: test/internal/ban-limits.json:25-26
Timestamp: 2025-09-26T01:10:04.233Z
Learning: The test/internal/ban-limits.json file contains accurate counts that are validated by tests - if the counts were incorrect, the associated tests would fail, so the values in this file reflect the actual current state of the codebase.

Applied to files:

  • test/internal/ban-limits.json
🔇 Additional comments (20)
src/bake/DevServer.zig (1)

3355-3364: Response.init wiring looks solid

Switching to Response.init keeps the headers/body wiring intact and matches the new constructor shape, so the rejection path still delivers the blob-based payload correctly.

src/bun.js/node/types.zig (1)

101-106: Good move to use getBodyValue()

Using response.getBodyValue() aligns this path with the accessor API and keeps the blob conversion logic unchanged.

src/bun.js/api/server.zig (1)

1230-1233: Encapsulation via setUrl looks good

Behavior unchanged; avoids direct field mutation. Please confirm setUrl adopts/refs the cloned bun.String to prevent leaks.

src/bun.js/api/server/FileRoute.zig (1)

62-85: BodyValue/headers accessor migration LGTM

Using getBodyValue()+toBlobIfPossible(), reassigning a dupe back into the Response, and sourcing headers via getInitHeaders() preserves behavior and isolates internals.

src/bun.js/webcore/Request.zig (1)

620-649: Correct switch to Response accessors in constructInto

Method, headers, url, and body now read via getters with proper cloning/dupeRef. Ownership and error checks look correct.

src/bun.js/webcore/BakeResponse.zig (1)

108-121: Use of Response.init and alias is consistent

Construction via Response.init matches the new API and keeps imports at the bottom style. Please verify the init parameter ordering/signature matches Response.init to avoid subtle init bugs.

Also applies to: 140-142

src/bun.js/api/server/StaticRoute.zig (1)

94-153: Accessor-based body/header handling looks solid

Using getBodyValue() with toBlobIfPossible(), use()/dupe(), and getInitHeaders() preserves previous behavior and encapsulates internals. Header sanitization and ETag logic intact.

src/bun.js/api/server/RequestContext.zig (4)

1219-1225: Locked lifecycle during streaming: LGTM

Re-establishing .Locked with a strong readable during pending stream, and transitioning to .Used on resolve/reject, ensures abort hooks can find and cancel the stream and prevents reuse.

Also applies to: 1668-1675, 1722-1729


1459-1511: HEAD path via bodyValue: LGTM

Using response.getBodyValue().toBlobIfPossible() and sizing per blob/string/InternalBlob is correct. S3 async stat fallback is preserved.


1565-1580: Protecting Response for stream/file cases: LGTM

Protecting the JS value when body is Locked or when Blob needs async file read avoids GC races across render; mirrored for both direct and Promise flows and onError path.

Also applies to: 1625-1639, 2171-2186


1999-2000: Routing render through getBodyValue(): LGTM

Directly passes Response body value pointer to the renderer; aligns with accessor pattern.

src/bun.js/api/html_rewriter.zig (5)

177-187: Accessor migration (getBodyValue) looks correct.

Checking body used state via pointer is consistent with the new API. No issues.


202-210: Response.init adoption is sound.

Constructing a temporary Response via Response.init and returning the transformed payload is correct. Defer-finalize of resp and out_response is appropriate.

Also applies to: 218-235


421-436: Init/headers/url cloning via setters LGTM.

  • setInit with method/status/text clone
  • setInitHeaders with cloneThis(...)
  • setUrl with clone
    All look correct for ownership and refcounts.

Also applies to: 475-491


529-564: Error-to-body propagation via Body.Value pointer

Using sink.response.getBodyValue() in both branches is correct. One question: readable.deinit() is called unconditionally when Locked; confirm deinit() is safe when there’s no held readable (Strong is empty).


600-622: Final resolution via replace-and-resolve pattern

Replacing bodyValue then resolving prev_value with pointer to the new value is correct. Matches fetch-side flow.

src/bun.js/webcore/fetch.zig (2)

968-980: Response.init and Body.Value flow refactor LGTM.

  • Response constructed via Response.init across HTTP/data URL/file/S3 paths.
  • Body.Value obtained via response.getBodyValue() consistently.
    No issues spotted.

Also applies to: 1434-1445, 2358-2367, 2541-2574


401-414: Promise rejection path uses toErrorInstance correctly. Verify semantics.

On failure, you call body.toErrorInstance(err, globalThis) and, if a Locked.promise exists and isn’t handled, reject it. Looks right; please confirm this matches the previous behavior for buffered bodies.

src/bun.js/webcore/Response.zig (2)

13-16: Invalid Zig identifiers (“#body”, “#init”, “#url”, “#redirected”, “#reported_estimated_size”).

Zig identifiers cannot start with “#”. All occurrences must be regular field names (private by omission of pub). This will not compile.

Apply this pattern (and repeat for all usages in the file):

-#body: Body,
-#init: Init,
-#url: bun.String = bun.String.empty,
-#redirected: bool = false,
-#reported_estimated_size: usize = 0,
+body: Body,
+init: Init,
+url: bun.String = bun.String.empty,
+redirected: bool = false,
+reported_estimated_size: usize = 0,

And update all references:

-return &this.#body.value;
+return &this.body.value;
-this.#init.status_code
+this.init.status_code
-this.#url
+this.url

Also applies to: 22-23

⛔ Skipped due to learnings
Learnt from: taylordotfish
PR: oven-sh/bun#22227
File: src/allocators/allocation_scope.zig:284-314
Timestamp: 2025-09-02T18:25:27.976Z
Learning: In bun's custom Zig implementation, the `#` prefix for private fields is valid syntax and should not be flagged as invalid. The syntax `#fieldname` creates private fields that cannot be accessed from outside the defining struct, and usage like `self.#fieldname` is correct within the same struct. This applies to fields like `#parent`, `#state`, `#allocator`, `#trace`, etc. throughout the codebase.
Learnt from: taylordotfish
PR: oven-sh/bun#22227
File: src/safety/alloc.zig:93-95
Timestamp: 2025-09-02T17:14:46.924Z
Learning: In bun's Zig codebase, they use a custom extension of Zig that supports private field syntax with the `#` prefix (e.g., `#allocator`, `#trace`). This is not standard Zig syntax but is valid in their custom implementation. Fields prefixed with `#` are private fields that cannot be accessed from outside the defining struct.
Learnt from: cirospaciari
PR: oven-sh/bun#22842
File: src/bun.js/webcore/ResumableSink.zig:274-276
Timestamp: 2025-09-25T18:14:27.731Z
Learning: In Zig code, private fields are declared and accessed using the `#` prefix. When a field is declared as `#field_name`, it must be accessed as `this.#field_name`, not `this.field_name`. The `#` prefix is part of the private field access syntax and should not be removed.

809-831: Verify BodyMixin compatibility: Ensure ResponseMixin = BodyMixin(@This()) in src/bun.js/webcore/Response.zig uses the public body field or the getBodyValue() accessor after stripping the # prefix; adjust if it still relies on the private #body.

…ces (#23319)

### What does this PR do?
This is the second step to fix cyclic reference issues with the stream
please review #23313 first
### How did you verify your code works?
Test + CI

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/bun.js/api/server/RequestContext.zig (1)

2229-2254: Fix Content-Range decision logic (Partial Content status/header)

In renderMetadata, needs_content_range is ANDed with has_content_range, which suppresses 206 and header emission when Content-Range is absent. It should emit when needed and not already present.

-                has_content_disposition = headers_.fastHas(.ContentDisposition);
-                has_content_range = headers_.fastHas(.ContentRange);
-                needs_content_range = needs_content_range and has_content_range;
-                if (needs_content_range) {
+                has_content_disposition = headers_.fastHas(.ContentDisposition);
+                has_content_range = headers_.fastHas(.ContentRange);
+                // Only set 206 if we need it and the header isn't already present.
+                if (needs_content_range and !has_content_range) {
                     status = 206;
                 }

The later block correctly guards header emission with if (needs_content_range and !has_content_range) { ... }, so this change aligns status and header behavior.

Also applies to: 2291-2306

src/bun.js/webcore/Body.zig (1)

1742-1748: JSValue leak: protected but never unprotected

readable.protect() is never unprotected; Strong keeps the stream alive already. Drop protect() to avoid leaking the JS ReadableStream handle.

Apply this diff:

-            const readable = try value.toReadableStream(sink.global);
-            readable.ensureStillAlive();
-            readable.protect();
+            const readable = try value.toReadableStream(sink.global);
+            readable.ensureStillAlive();
             return try sink.bufferLockedBodyValue(value, null);
src/bun.js/webcore/Response.zig (1)

871-880: Fix emptyWithStatus: wrong field, wrong return type

Currently initializes .init (public) and returns Response by value, but fields are private and bun.new returns *Response.

-inline fn emptyWithStatus(_: *jsc.JSGlobalObject, status: u16) Response {
-    return bun.new(Response, .{
-        .#body = Body{
-            .value = Body.Value{ .Null = {} },
-        },
-        .init = Init{
-            .status_code = status,
-        },
-    });
-}
+inline fn emptyWithStatus(_: *jsc.JSGlobalObject, status: u16) *Response {
+    return bun.new(Response, .{
+        .#body = Body{
+            .value = Body.Value{ .Null = {} },
+        },
+        .#init = Init{
+            .status_code = status,
+        },
+    });
+}

Also update callers to return pointer:

pub fn @"404"(globalThis: *jsc.JSGlobalObject) *Response {
    return emptyWithStatus(globalThis, 404);
}
pub fn @"200"(globalThis: *jsc.JSGlobalObject) *Response {
    return emptyWithStatus(globalThis, 200);
}
♻️ Duplicate comments (1)
src/bun.js/webcore/fetch.zig (1)

405-414: Use promise status check instead of isHandled()

isHandled() doesn’t guarantee pending state. Prefer status(vm) != .pending to avoid double-reject or missed reject.

-                    if (promise.isHandled(globalThis.vm())) {
+                    if (promise.status(globalThis.vm()) != .pending) {
                         return;
                     }
🧹 Nitpick comments (6)
test/js/web/fetch/response-cyclic-reference.test.ts (1)

17-20: Make heapStats access robust

Use optional chaining to avoid a crash if objectTypeCounts is undefined.

-  expect(heapStats().objectTypeCounts.ReadableStream || 0).toBeLessThanOrEqual(4);
+  expect(heapStats().objectTypeCounts?.ReadableStream ?? 0).toBeLessThanOrEqual(4);
test/js/web/fetch/request-cyclic-reference.test.ts (1)

17-20: Make heapStats access robust

Same as the response test: guard access to avoid undefined property crashes.

-  expect(heapStats().objectTypeCounts.ReadableStream || 0).toBeLessThanOrEqual(4);
+  expect(heapStats().objectTypeCounts?.ReadableStream ?? 0).toBeLessThanOrEqual(4);

Optionally rename local “response” (Line 9) to “request” for clarity.

src/bun.js/api/server.zig (1)

1189-1195: Standardize Request init API usage

This uses Request.init2 while other sites use Request.init wrapped in Request.new. Consider unifying (single init name/signature) to avoid confusion and reduce divergent code paths.

src/bun.js/webcore/Body.zig (2)

1112-1113: Make debug log non-verbose by default

Use Output.scoped(..., false) per project guidance to avoid noisy logs.

As per coding guidelines

-        const log = Output.scoped(.BodyMixin, .visible);
+        const log = Output.scoped(.BodyMixin, false);

1232-1232: Gate verbose log

getArrayBuffer()’s log fires on hot path. Either keep logger disabled (scoped(..., false)) or remove this line.

src/bun.js/webcore/Response.zig (1)

658-661: Redundant re-init of #js_ref

toJS() already sets #js_ref and calls checkBodyStreamRef. Re-assigning #js_ref is unnecessary.

-    const js_value = response.toJS(globalThis);
-    response.#js_ref = .initWeak(js_value);
-    return js_value;
+    return response.toJS(globalThis);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear 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 dde97af and d98eb1f.

📒 Files selected for processing (15)
  • src/bun.js/api/html_rewriter.zig (10 hunks)
  • src/bun.js/api/server.zig (4 hunks)
  • src/bun.js/api/server/RequestContext.zig (14 hunks)
  • src/bun.js/bindings/JSBakeResponse.cpp (3 hunks)
  • src/bun.js/bindings/JSGlobalObject.zig (1 hunks)
  • src/bun.js/node/types.zig (2 hunks)
  • src/bun.js/webcore/BakeResponse.zig (5 hunks)
  • src/bun.js/webcore/Blob.zig (3 hunks)
  • src/bun.js/webcore/Body.zig (18 hunks)
  • src/bun.js/webcore/Request.zig (24 hunks)
  • src/bun.js/webcore/Response.zig (27 hunks)
  • src/bun.js/webcore/fetch.zig (10 hunks)
  • src/bun.js/webcore/response.classes.ts (4 hunks)
  • test/js/web/fetch/request-cyclic-reference.test.ts (1 hunks)
  • test/js/web/fetch/response-cyclic-reference.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/bun.js/node/types.zig
  • src/bun.js/webcore/Blob.zig
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/JSBakeResponse.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/JSBakeResponse.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When a JS class has a public constructor, implement Foo, FooPrototype, and FooConstructor (JSDestructibleObject, JSNonFinalObject, InternalFunction)
Define class properties using HashTableValue arrays in C++ bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for C++-backed classes

Files:

  • src/bun.js/bindings/JSBakeResponse.cpp
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: Use the # prefix to declare private fields in Zig structs (e.g., struct { #foo: u32 })
Prefer decl literals when initializing values in Zig (e.g., const decl: Decl = .{ .binding = 0, .value = 0 })
Place @import directives at the bottom of Zig files
Use @import("bun") instead of @import("root").bun

Files:

  • src/bun.js/bindings/JSGlobalObject.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/webcore/Body.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/bindings/JSGlobalObject.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/webcore/Body.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/bindings/JSGlobalObject.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/webcore/Body.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/js/web/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Web API tests under test/js/web/, separated by category

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/js/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests for specific features under test/js/ by module

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must live under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or roll your own random port
Prefer normalizeBunSnapshot for snapshotting test output instead of asserting raw strings
Do not write tests that assert absence of crashes (e.g., 'no panic' or 'no uncaught exception')
Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests
Avoid shell commands like find or grep in tests; use Bun’s Glob and built-in tools instead
Prefer running tests via bun bd test and use provided harness utilities (bunEnv, bunExe, tempDir)
Use Bun.spawn with proper stdio handling and await proc.exited in process-spawning tests

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
test/js/web/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Web API tests under test/js/web/

Files:

  • test/js/web/fetch/response-cyclic-reference.test.ts
  • test/js/web/fetch/request-cyclic-reference.test.ts
**/*.classes.ts

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.classes.ts: Define JavaScript class interfaces in .classes.ts using define({ name, constructor, JSType, finalize, proto: { ... } })
Use cache: true on properties that should use JSC WriteBarrier-backed caching

Files:

  • src/bun.js/webcore/response.classes.ts
src/**/*.classes.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Define JavaScriptCore class metadata in .classes.ts files to drive code generation

Files:

  • src/bun.js/webcore/response.classes.ts
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/RequestContext.zig
🧬 Code graph analysis (3)
src/bun.js/bindings/JSBakeResponse.cpp (1)
src/bun.js/bindings/bindings.cpp (9)
  • void (3111-3117)
  • void (3119-3125)
  • void (6476-6479)
  • void (6481-6485)
  • void (6571-6578)
  • void (6580-6583)
  • void (6721-6763)
  • globalObject (2561-2565)
  • globalObject (2561-2561)
test/js/web/fetch/response-cyclic-reference.test.ts (1)
test/js/node/http2/node-http2-memory-leak.js (1)
  • heapStats (8-8)
test/js/web/fetch/request-cyclic-reference.test.ts (1)
test/js/node/http2/node-http2-memory-leak.js (1)
  • heapStats (8-8)
🔇 Additional comments (20)
src/bun.js/bindings/JSGlobalObject.zig (1)

904-908: Direct stream access for Locked bodies is correct. It attempts JS-origin streams first via getBodyReadableStream and preserves the existing blob-to-stream fallback when null.

src/bun.js/webcore/response.classes.ts (1)

7-8: Confirm API surface for “values: ['stream']” and bun-types

Constructors needing this look good. Verify that exposing values: ["stream"] for Request/Response doesn’t unintentionally surface a public .stream or alter d.ts expectations. Update bun-types snapshots if needed.

Would you confirm codegen keeps “stream” internal and adjust bun-types tests if they expect no such field?

Also applies to: 16-17, 73-74, 91-92

src/bun.js/bindings/JSBakeResponse.cpp (1)

36-36: Signature plumbed correctly; verify ABI/linkage across targets

The extra js_this (EncodedJSValue) is passed consistently. Looks good. Please confirm Zig export matches exactly and that Windows SYSV_ABI is correct in all toolchains.

Also applies to: 215-217, 246-246

src/bun.js/api/server.zig (1)

1231-1233: Good: use Response.setUrl instead of direct field

Setter aligns with the new encapsulation.

src/bun.js/webcore/BakeResponse.zig (2)

21-23: js_this threading and constructor update look correct

Forwarding js_this to Response.constructor matches constructNeedsThis. No issues spotted.

Also applies to: 31-48


107-121: Response.init usage LGTM

Initialization via Response.init (status/headers/body/url/redirected) is consistent with the new API.

src/bun.js/api/server/RequestContext.zig (4)

680-685: Abort path: detaching/aborting body stream is correct

Using getBodyReadableStream + detachReadableStream + abort is the right lifecycle on abort.


1214-1221: Locking bodyValue with ReadableStream

Assigning bodyValue = .Locked with a strong ReadableStream ref ensures GC safety while streaming. Looks good.


1800-1844: doRenderWithBody signature and call site LGTM

Passing owned_readable and using response.getBodyValue()/getBodyReadableStream at call site is consistent with the new API.

Also applies to: 1999-2004


2230-2234: Header accessor refactor LGTM

getInitHeaders/swapInitHeaders integration and write path via writeHeaders are correct and reduce direct struct access.

Also applies to: 2237-2248

src/bun.js/api/html_rewriter.zig (1)

202-210: Response API migration + owned stream propagation looks correct

Init/headers/url routed via new accessors; body length/readable stream usage aligns with Body.ValueBufferer.run.

Also applies to: 421-480, 490-499

src/bun.js/webcore/Response.zig (1)

44-87: Private-field encapsulation looks consistent

setInit/setInitHeaders/getters and formatting now use #init/#body/#url/#redirected consistently.

Also applies to: 99-107, 250-306

src/bun.js/webcore/Request.zig (8)

5-14: LGTM! Private field refactoring aligns with Zig conventions.

The use of # prefix for private fields (#headers, #body, #js_ref) follows Zig best practices. The new #js_ref weak reference prevents cycles between JS and Zig objects, and the https field supports protocol tracking.


194-221: Stream lifecycle helpers look correct.

The new getBodyReadableStream and detachReadableStream helpers properly manage the stream lifecycle by checking both #js_ref (via js.gc.stream) and Locked.readable. The dual-source approach supports the ownership transfer pattern implemented in checkBodyStreamRef.


386-409: LGTM! Finalization sequence is correct.

The finalization order is proper:

  1. #js_ref.finalize() - clears JS reference
  2. finalizeWithoutDeinit() - derefs headers, url, signal
  3. #body.unref() - releases body reference
  4. Conditional bun.destroy(this) based on weak pointer

This ordering prevents use-after-free and ensures all references are properly released.


589-606: LGTM! Weak reference initialization prevents cycles.

The initialization of #js_ref with .initWeak(this_value) (line 595) prevents cyclic references between the Request object and its JavaScript wrapper. The defer cleanup logic (lines 597-605) correctly handles the case where the body is replaced during construction by comparing pointers at line 602.


831-840: Content-Type header logic is correct.

The conditional logic properly sets the Content-Type header from the blob only when:

  • Body is a Blob with non-empty content_type
  • Headers exist
  • ContentType header not already set

This prevents overwriting existing headers while ensuring blob content types are propagated.


860-885: LGTM! Stream handling in doClone is correct.

The cloning logic properly handles teed ReadableStreams (lines 870-881):

  1. Updates the cached .body value on both the cloned and original requests to point to their respective streams
  2. Calls checkBodyStreamRef to ensure ownership is transferred to js.gc.stream

This ensures both the original and cloned requests maintain correct references to their teed streams.


1002-1013: LGTM! Stream-aware cloning logic is correct.

The cloneInto function properly handles ReadableStreams:

  1. Checks #js_ref via js.gc.stream for cached stream (lines 1003-1010)
  2. Uses cloneWithReadableStream if a stream exists, which properly tees the stream
  3. Falls back to standard clone if no stream is present (line 1012)

This ensures ReadableStreams are properly teed during cloning while avoiding unnecessary work when no stream exists.


5-1075: Well-executed refactoring to isolate body usage.

This refactoring systematically moves body and headers access behind private fields and accessor APIs, which supports the PR objective of isolating Response body usage. Key improvements:

  1. Private fields: Using # prefix per Zig convention for #headers, #body, #js_ref
  2. Cycle prevention: Weak #js_ref and stream ownership transfer to js.gc.stream prevent cyclic references
  3. Stream lifecycle: New helpers (getBodyReadableStream, detachReadableStream, checkBodyStreamRef) manage stream ownership correctly
  4. Memory safety: Proper ref/unref sequences in finalize and cleanup paths

The refactoring is consistent throughout and appears to maintain correctness while improving encapsulation.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun.js/webcore/Body.zig (1)

1642-1660: Fix AnyPromise.status enum casing to match RequestContext usage.

Elsewhere you use .pending/.fulfilled/.rejected. Here it’s .Pending/.Fulfilled/.Rejected, which likely won’t compile.

Apply this patch:

-                switch (promise.status(globalThis.vm())) {
-                    .Pending => {
+                switch (promise.status(globalThis.vm())) {
+                    .pending => {
                         assignment_result.then(
                             globalThis,
                             sink,
                             onResolveStream,
                             onRejectStream,
                         );
                     },
-                    .Fulfilled => {
+                    .fulfilled => {
                         defer stream.value.unprotect();
 
                         sink.handleResolveStream(false);
                     },
-                    .Rejected => {
+                    .rejected => {
                         defer stream.value.unprotect();
 
                         sink.handleRejectStream(promise.result(globalThis.vm()), false);
                     },
                 }
♻️ Duplicate comments (2)
src/bun.js/api/server/RequestContext.zig (1)

1204-1213: Inconsistent AnyPromise status enum casing (compile-time break).

Here you switch on promise.status(globalThis.vm()) using .pending/.fulfilled/.rejected. In Body.ValueBufferer.createJSSink the code uses .Pending/.Fulfilled/.Rejected. Use one enum shape consistently for AnyPromise.status; the lowercase variants are used elsewhere in this file.

Apply the same lowercase in Body.ValueBufferer to avoid build break. See follow-up diff in Body.zig comment.

src/bun.js/webcore/Request.zig (1)

129-142: Remove the TODO comment and clarify init2's purpose.

Per the previous review, init2 is actively used in src/bun.js/api/server.zig:1189 and must be retained. The TODO "do we need this?" should be removed.

Replace the TODO with a descriptive comment explaining that init2 is a specialized constructor invoked by the server API when constructing Requests with nullable headers and a HiveRef body, clarifying why it differs from the standard init function.

🧹 Nitpick comments (1)
src/bun.js/webcore/fetch.zig (1)

444-473: Double-check buffer lifetime when sending final chunk (.temporary_and_done).

Here, after detachReadableStream(), you pass scheduled_response_buffer.items as .temporary_and_done to readable.ptr.Bytes.onData(). Since buffer_reset defaults to true (and isn’t flipped here), the defer will reset the buffer at function end. If the sink doesn’t synchronously copy, this risks use-after-free. Earlier path uses .owned_and_done with buffer_reset = false.

Recommend aligning with the owned pattern at end-of-stream:

  • set buffer_reset = false
  • discard allocatedSlice()
  • pass .owned_and_done = bun.ByteList.moveFromList(scheduled_response_buffer)

This keeps ownership/lifetime explicit and consistent with the other branch. Please confirm the ByteStream contract if you prefer temporary_and_done here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear 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 d98eb1f and e0e7ce2.

📒 Files selected for processing (6)
  • src/bun.js/api/server/RequestContext.zig (23 hunks)
  • src/bun.js/webcore/Body.zig (19 hunks)
  • src/bun.js/webcore/Request.zig (24 hunks)
  • src/bun.js/webcore/fetch.zig (10 hunks)
  • test/js/web/fetch/request-cyclic-reference.test.ts (1 hunks)
  • test/js/web/fetch/response-cyclic-reference.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/js/web/fetch/request-cyclic-reference.test.ts
  • test/js/web/fetch/response-cyclic-reference.test.ts
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: Use the # prefix to declare private fields in Zig structs (e.g., struct { #foo: u32 })
Prefer decl literals when initializing values in Zig (e.g., const decl: Decl = .{ .binding = 0, .value = 0 })
Place @import directives at the bottom of Zig files
Use @import("bun") instead of @import("root").bun

Files:

  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
  • src/bun.js/webcore/fetch.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
  • src/bun.js/webcore/fetch.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
  • src/bun.js/webcore/fetch.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server/RequestContext.zig
🔇 Additional comments (12)
src/bun.js/webcore/fetch.zig (2)

958-969: Init/accessor refactor looks good.

Response.init(...) and response.getBodyValue()/getBodyReadableStream adoption is consistent and improves encapsulation.

Also applies to: 1424-1434, 2352-2360


994-1021: Finalizer logic: verify “promise present” condition.

Early-return for streaming or non-locked is fine. For buffered (.Locked without readable_stream_ref), you keep loading if Locked.promise exists, and ignore if it doesn’t. That matches the comment (2b vs 2a). Please confirm Locked.promise cannot be a non-empty sentinel without a real Promise.

src/bun.js/api/server/RequestContext.zig (2)

680-685: ReadableStream lifetime handling looks solid.

  • Abort path detaches and aborts the response’s stream via accessors.
  • finalizeWithoutDeinit and doRender paths deinit the strong ref appropriately.

No issues spotted.

Also applies to: 755-755, 2007-2008


1456-1507: Accessor migration (getBodyValue/getBodyReadableStream) is correct.

Switching to bodyValue-based logic and updating headers via accessors keeps internals encapsulated and consistent with the new API.

Also applies to: 1561-1577, 1619-1636, 2180-2196

src/bun.js/webcore/Body.zig (2)

25-29: ReadableStream ownership propagation is well-structured.

  • cloneWithReadableStream/tee and Mixin methods properly pass an owned readable and keep Strong refs where needed.
  • ValueBufferer’s bufferLockedBodyValue holds a Strong ref to prevent premature GC.

Looks good.

Also applies to: 958-975, 1042-1079, 1477-1517, 1668-1737


1116-1141: Nice: body getters respect host-owned ReadableStream when present.

Using getBodyReadableStream to avoid creating new streams and passing it into setPromise reduces churn and prevents TDZ/GC issues.

Also applies to: 1168-1168, 1224-1252, 1261-1287, 1296-1324, 1354-1416

src/bun.js/webcore/Request.zig (6)

7-10: LGTM! Private field declarations follow Zig conventions.

The conversion to private fields using the # prefix aligns with the Zig coding guideline. The new #js_ref field properly integrates with the GC system to track the JS wrapper and manage stream ownership.

As per coding guidelines.


194-221: LGTM! Stream accessors implement correct ownership semantics.

The helpers properly prioritize js.gc.stream as the source of truth (line 200-201) and ensure cleanup in both storage locations (lines 214, 217-220). Error handling via takeException is appropriate.


588-604: LGTM! Constructor initialization and cleanup properly managed.

The constructInto function correctly initializes #js_ref with a weak reference (line 594) and uses defer/errdefer blocks to ensure proper cleanup of resources (lines 596-604). The cleanup logic properly handles the case where #body is replaced during construction (lines 601-603).


991-1031: LGTM! Clone logic correctly handles stream extraction.

The cloneInto function properly extracts readable streams from the JS reference before cloning (lines 1002-1009). The fallback to regular clone when no stream is present (line 1011) is appropriate. Error handling via errdefer ensures cleanup of partially constructed state (lines 1013, 1016, 1018).


853-857: LGTM! Body accessor correctly returns private field.

The getBodyValue function correctly returns a reference to #body.value, maintaining the established accessor pattern throughout this refactoring.


887-939: LGTM! Header accessors maintain proper encapsulation.

The header accessor functions (hasFetchHeaders, setFetchHeaders, ensureFetchHeaders, getFetchHeadersUnlessEmpty, getFetchHeaders) correctly use the private #headers field while preserving the original logic. The lazy initialization pattern in ensureFetchHeaders (lines 911-938) properly creates headers from the request context or body content-type when needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants