Add toWeb conversion for HttpServerRequest
Adds symmetric toWeb conversion to complement the existing fromWeb, enabling bidirectional conversion between HttpServerRequest and global Request.
Changes
Public API (HttpServerRequest.ts)
- Export
toWeb: (self: HttpServerRequest) => Request
Implementation (internal/httpServerRequest.ts)
- Return original
Requestwhensource instanceof globalThis.Request(preserves identity and body streams) - Reconstruct new
Requestfor non-wrapped implementations usingtoURL(), method, and headers - Body intentionally omitted for non-wrapped requests to maintain synchronous signature
Tests (HttpServerRequest.toWeb.test.ts)
- Round-trip identity:
toWeb(fromWeb(request)) === request - URL reconstruction with protocol detection via
x-forwarded-proto - Header preservation and host fallback to localhost
Example
// Round-trip preserves original Request
const original = new Request("https://api.example.com/users", {
method: "POST",
headers: { "Authorization": "Bearer token" },
body: JSON.stringify({ name: "test" })
})
const httpReq = HttpServerRequest.fromWeb(original)
const restored = HttpServerRequest.toWeb(httpReq)
console.log(restored === original) // true - identity preserved
Limitation
Non-wrapped HttpServerRequest implementations lose body content on conversion (synchronous API constraint). Wrapped requests from fromWeb() preserve all properties including streaming body.
[!WARNING]
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
google.com
- Triggering command:
node (vitest 3)(dns block)jsonplaceholder.typicode.com
- Triggering command:
node (vitest 3)(dns block)- Triggering command:
node (vitest 1)(dns block)pkg.pr.new
- Triggering command:
node /usr/local/bin/pnpm install(dns block)- Triggering command:
node /usr/local/bin/pnpm install --no-frozen-lockfile --ignore-scripts(dns block)- Triggering command:
node /usr/local/bin/pnpm install --filter @effect/platform --ignore-scripts(dns block)www.google.com
- Triggering command:
node (vitest 3)(dns block)If you need me to access, download, or install something from one of these locations, you can either:
- Configure Actions setup steps to set up my environment, which run before the firewall is enabled
- Add the appropriate URLs or hosts to the custom allowlist in this repository's Copilot coding agent settings (admins only)
Original prompt
Add a toWeb conversion for HttpServerRequest and tests.
Background
- The public API currently exposes fromWeb: (request: Request) => HttpServerRequest implemented in packages/platform/src/internal/httpServerRequest.ts as fromWeb(request) -> ServerRequestImpl(request, removeHost(request.url)). There is no symmetric conversion back to a global Request.
- The repository already has utilities to reconstruct a URL from a ServerRequest (toURL) and the ServerRequestImpl stores the original Request as source.
Goals
Export a new conversion in the public API: packages/platform/src/HttpServerRequest.ts:
- Add: export const toWeb: (self: HttpServerRequest) => Request = internal.toWeb This mirrors the existing fromWeb export and keeps the public API symmetric.
Implement internal.toWeb in packages/platform/src/internal/httpServerRequest.ts:
- Export a new function
toWebwith signature (self: ServerRequest.HttpServerRequest) => globalThis.Request.- If the provided HttpServerRequest is a wrapper around a global Request (i.e. it has a
.sourcethat is an instance of global Request), return that original Request to preserve identity and the body/streaming behavior.- Otherwise, reconstruct a new Request using the best available information:
- URL: try to use the existing toURL(self) helper (it returns Option<URL>). If it yields a URL, use url.href; otherwise fall back to self.originalUrl or '/'.
- Method: self.method
- Headers: copy headers from self.headers. The Headers type in platform is a plain record used like self.headers["content-type"], so passing it as RequestInit.headers is appropriate (cast to any where needed).
- Body: do not attempt to synchronously convert Effect-wrapped/streaming bodies; omit the body for non-wrapping implementations. Documented limitation: only the original wrapped Request preserves its body. This keeps the API synchronous and ergonomic.
- Ensure the function uses runtime checks against globalThis.Request to avoid breaking environments without a global Request.
Add unit tests: packages/platform/test/HttpServerRequest.toWeb.test.ts
- Test A: round-trip identity for wrapped requests
- Create a global Request with url/method/body/headers
- Call fromWeb(req) to obtain HttpServerRequest
- Call toWeb(httpReq) and assert the returned object is strictly equal (===) to the original Request.
- Test B: reconstructs URL/method/headers for non-wrapped implementation
- Create a minimal stub object implementing HttpServerRequest that does not wrap a source Request but provides method, headers, url/originalUrl and necessary properties used by toWeb/toURL (headers.host when necessary).
- Call toWeb(stub) and assert the returned Request has the expected url (absolute or path depending on toURL), method, and headers values present.
- Keep tests simple and synchronous (no streaming conversions). Use the same test runner and style as other platform tests in the repository.
Notes and rationale
- Choosing a synchronous toWeb that returns a Request keeps the API symmetric and ergonomic: callers who need the original Request (common case when the request originated from a Web API) get it back; callers who construct custom HttpServerRequest instances get a best-effort reconstructed Request without body.
- Attempting to preserve streaming bodies for arbitrary HttpServerRequest implementations would require async/effectful work and would change the exported type to return an Effect/Promise. That would be surprising in a small utility and reduce ergonomics for the common case where the wrapper already has the original Request.
Files to change
- Modify: packages/platform/src/HttpServerRequest.ts (add export)
- Modify: packages/platform/src/internal/httpServerRequest.ts (add implementation for internal.toWeb)
- Add: packages/platform/test/HttpServerRequest.toWeb.test.ts (unit tests described above)
Please open a PR that implements these changes, includes tests, and a brief PR body describing the behavior and limitation around request body preservation for non-wrapping requests.
This pull request was created as a result of the following prompt from Copilot chat.
Add a toWeb conversion for HttpServerRequest and tests.
Background
- The public API currently exposes fromWeb: (request: Request) => HttpServerRequest implemented in packages/platform/src/internal/httpServerRequest.ts as fromWeb(request) -> ServerRequestImpl(request, removeHost(request.url)). There is no symmetric conversion back to a global Request.
- The repository already has utilities to reconstruct a URL from a ServerRequest (toURL) and the ServerRequestImpl stores the original Request as source.
Goals
Export a new conversion in the public API: packages/platform/src/HttpServerRequest.ts:
- Add: export const toWeb: (self: HttpServerRequest) => Request = internal.toWeb This mirrors the existing fromWeb export and keeps the public API symmetric.
Implement internal.toWeb in packages/platform/src/internal/httpServerRequest.ts:
- Export a new function
toWebwith signature (self: ServerRequest.HttpServerRequest) => globalThis.Request.- If the provided HttpServerRequest is a wrapper around a global Request (i.e. it has a
.sourcethat is an instance of global Request), return that original Request to preserve identity and the body/streaming behavior.- Otherwise, reconstruct a new Request using the best available information:
- URL: try to use the existing toURL(self) helper (it returns Option<URL>). If it yields a URL, use url.href; otherwise fall back to self.originalUrl or '/'.
- Method: self.method
- Headers: copy headers from self.headers. The Headers type in platform is a plain record used like self.headers["content-type"], so passing it as RequestInit.headers is appropriate (cast to any where needed).
- Body: do not attempt to synchronously convert Effect-wrapped/streaming bodies; omit the body for non-wrapping implementations. Documented limitation: only the original wrapped Request preserves its body. This keeps the API synchronous and ergonomic.
- Ensure the function uses runtime checks against globalThis.Request to avoid breaking environments without a global Request.
Add unit tests: packages/platform/test/HttpServerRequest.toWeb.test.ts
- Test A: round-trip identity for wrapped requests
- Create a global Request with url/method/body/headers
- Call fromWeb(req) to obtain HttpServerRequest
- Call toWeb(httpReq) and assert the returned object is strictly equal (===) to the original Request.
- Test B: reconstructs URL/method/headers for non-wrapped implementation
- Create a minimal stub object implementing HttpServerRequest that does not wrap a source Request but provides method, headers, url/originalUrl and necessary properties used by toWeb/toURL (headers.host when necessary).
- Call toWeb(stub) and assert the returned Request has the expected url (absolute or path depending on toURL), method, and headers values present.
- Keep tests simple and synchronous (no streaming conversions). Use the same test runner and style as other platform tests in the repository.
Notes and rationale
- Choosing a synchronous toWeb that returns a Request keeps the API symmetric and ergonomic: callers who need the original Request (common case when the request originated from a Web API) get it back; callers who construct custom HttpServerRequest instances get a best-effort reconstructed Request without body.
- Attempting to preserve streaming bodies for arbitrary HttpServerRequest implementations would require async/effectful work and would change the exported type to return an Effect/Promise. That would be surprising in a small utility and reduce ergonomics for the common case where the wrapper already has the original Request.
Files to change
- Modify: packages/platform/src/HttpServerRequest.ts (add export)
- Modify: packages/platform/src/internal/httpServerRequest.ts (add implementation for internal.toWeb)
- Add: packages/platform/test/HttpServerRequest.toWeb.test.ts (unit tests described above)
Please open a PR that implements these changes, includes tests, and a brief PR body describing the behavior and limitation around request body preservation for non-wrapping requests.
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
⚠️ No Changeset found
Latest commit: 967984fc7e1e736452d6ea4e5a456f91d9c903c1
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR