Skip to content

Conversation

@pi0
Copy link
Member

@pi0 pi0 commented Dec 30, 2025

Request constructor in Node.js (undici) has incompatible private # accessors that breaks new Request(req) (when req comes from srvx)

srvx used to have a global patch to workaround this issue, patching globally is not the best idea and can cause implicit issues even though we fixed many of those in past iteration,s more are possible... (even outside srvx by just importing it!)

This PR avoids global patch but instead exposes it as patchGlobalRequest() utility from srvx/node.

Alternatively, users can simply use new Request(req._request || req) as solution!

Summary by CodeRabbit

  • Chores

    • Updated multiple development dependencies including @cloudflare/workers-types, eslint, vitest, and others to their latest versions.
    • Upgraded package manager from pnpm@10.25.0 to pnpm@10.26.2.
    • Added fastify and get-port-please as new runtime dependencies.
  • Refactor

    • Improved Request handling in the Node adapter with optimized body and duplex parameter management.

✏️ Tip: You can customize this high-level summary in your review settings.

@pi0 pi0 changed the title refactor!(node): avoid patching global request by default refactor(node)!: avoid patching global request by default Dec 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

This pull request upgrades multiple development and runtime dependencies, refactors Node.js request handling to use the native Request constructor directly instead of global patching, exports a new optional patchGlobalRequest utility function, and updates test fixtures to use internal request properties.

Changes

Cohort / File(s) Summary
Package Dependencies
package.json
Version bumps for devDependencies (@cloudflare/workers-types, @types/bun, @types/node, eslint, vitest, etc.); packageManager upgraded to pnpm@10.26.2; new runtime dependencies added (fastify, get-port-please).
Request Adapter Refactoring
src/adapters/_node/request.ts, src/adapters/node.ts
Removed global Request patching block (PatchedRequest); NodeRequest now constructs requests directly via globalThis.Request with duplex set to "half" when a body exists. Added patchGlobalRequest() utility function as optional alternative. Export extended to include patchGlobalRequest in public API.
Test Fixture Updates
test/_fixture.ts
Modified clone construction in /req-new-req case to use internal _request property when available, falling back to original request if absent.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~13 minutes

Poem

🐰 Hop, hop! The request now runs free,
No patching needed, just Request, you see!
Dependencies leap, versions ascend high,
A cleaner adapter, a happier dev sigh! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: removing default global Request patching in the Node.js adapter.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 30, 2025

Open in StackBlitz

npm i https://pkg.pr.new/h3js/srvx@155

commit: 7138667

Copy link

@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

🧹 Nitpick comments (3)
src/adapters/_node/request.ts (2)

203-206: Consider preventing accidental re-patching.

The function checks if the current globalThis.Request has the _srvx marker to avoid re-patching. However, this check happens after creating a new PatchedRequest class. If called multiple times, it creates multiple class instances (though only one gets assigned globally).

Consider documenting that calling this function multiple times is safe, or return early if already patched.

🔎 Optional optimization
 export function patchGlobalRequest(): typeof Request {
+  // Return early if already patched
+  if ((globalThis.Request as any)._srvx) {
+    return globalThis.Request;
+  }
+  
   const NativeRequest = ((globalThis as any)[
     Symbol.for("srvx.nativeRequest")
   ] ??= globalThis.Request) as typeof globalThis.Request;

   const PatchedRequest = class Request extends NativeRequest {
     static _srvx = true;
     // ... rest of implementation
   };
-  if (!(globalThis.Request as any)._srvx) {
-    globalThis.Request = PatchedRequest as unknown as typeof globalThis.Request;
-  }
+  
+  globalThis.Request = PatchedRequest as unknown as typeof globalThis.Request;
   return PatchedRequest;
 }

179-181: Document the Symbol.for usage for native Request storage.

The code uses Symbol.for("srvx.nativeRequest") to store the original native Request. This is a global symbol that could theoretically conflict with other libraries using the same symbol name.

Consider documenting this design choice, or use a more unique symbol name.

test/_fixture.ts (1)

131-132: Document the _request fallback pattern for users.

The code now uses new Request((req as any)._request || req) instead of directly passing req. This pattern is mentioned in the PR description as an alternative to calling patchGlobalRequest().

Consider documenting this pattern in the library's migration guide or API documentation, as users upgrading will need to know about this change.

Also, consider removing the commented-out line 131 to keep the code clean.

🔎 Cleanup: Remove commented code
       case "/req-new-req": {
-        // const clone = new Request(req);
         const clone = new Request((req as any)._request || req);
         return Response.json({
           method: clone.method,
           pathname: new URL(clone.url).pathname,
           headers: Object.fromEntries(clone.headers),
         });
       }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80f95b6 and 7138667.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json
  • src/adapters/_node/request.ts
  • src/adapters/node.ts
  • test/_fixture.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/adapters/_node/request.ts (1)
src/adapters/node.ts (1)
  • patchGlobalRequest (29-29)
⏰ 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: Cloudflare Pages
🔇 Additional comments (4)
src/adapters/node.ts (1)

29-29: LGTM! Export addition aligns with PR objectives.

The addition of patchGlobalRequest to the public exports makes the global patching functionality opt-in, which aligns with the PR's goal of avoiding automatic global patching.

package.json (1)

62-93: All specified dependency versions have been verified to exist on npm and are publicly available.

Verification confirms:

  • @cloudflare/workers-types@4.20251230.0 is available (today's release)
  • @types/node@25.0.3 (major version bump) exists
  • vitest@4.0.16 and @vitest/coverage-v8@4.0.16 (major version 4) are available
  • All other updated versions in the dependency list are present on the npm registry

No version availability issues detected.

src/adapters/_node/request.ts (2)

147-155: Verify undici's duplex requirement is correctly handled.

The code sets duplex: "half" when a body is present, which is required by undici (Node.js's fetch implementation) when streaming body content. This is the correct approach for Node.js compatibility. The @ts-expect-error annotation is also appropriate since duplex is undici-specific and not part of the standard RequestInit type.


19-19: No action needed for this change; existing code patterns are compatible.

The shift to native globalThis.Request is safe because actual usage patterns don't rely on automatic patching. NodeRequest is instantiated directly as new NodeRequest({ req, res }) rather than new Request(nodeRequest). The test fixtures already use the recommended alternative pattern new Request(req._request || req), and patchGlobalRequest() remains available for explicit use if needed. No code paths that would break from this change were found in the codebase.

Comment on lines +193 to +201
constructor(
input: string | URL | globalThis.Request,
options?: RequestInit,
) {
if (typeof input === "object" && "_request" in input) {
input = (input as any)._request;
}
super(input, options);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add null check when unwrapping _request property.

The constructor checks typeof input === "object" but doesn't guard against null, which is also an object in JavaScript. This could cause a runtime error if null is passed.

🔎 Proposed fix
     constructor(
       input: string | URL | globalThis.Request,
       options?: RequestInit,
     ) {
-      if (typeof input === "object" && "_request" in input) {
+      if (input && typeof input === "object" && "_request" in input) {
         input = (input as any)._request;
       }
       super(input, options);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constructor(
input: string | URL | globalThis.Request,
options?: RequestInit,
) {
if (typeof input === "object" && "_request" in input) {
input = (input as any)._request;
}
super(input, options);
}
constructor(
input: string | URL | globalThis.Request,
options?: RequestInit,
) {
if (input && typeof input === "object" && "_request" in input) {
input = (input as any)._request;
}
super(input, options);
}
🤖 Prompt for AI Agents
In src/adapters/_node/request.ts around lines 193 to 201, the constructor
unwraps input._request but only checks typeof input === "object" which fails for
null; update the guard to check input !== null (e.g. input !== null && typeof
input === "object" && "_request" in input) before accessing _request, and ensure
the unwrap uses the narrowed type to avoid potential runtime errors when input
is null.

@pi0 pi0 merged commit aa0edc2 into main Dec 30, 2025
13 checks passed
@pi0 pi0 deleted the fix/no-global-patch branch December 30, 2025 12:53
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.

2 participants