1+ // noinspection SuspiciousTypeOfGuard
2+
13import assert from "assert" ;
24import { Blob } from "buffer" ;
35import http from "http" ;
@@ -38,6 +40,18 @@ import {
3840import fetchSymbols from "undici/lib/fetch/symbols.js" ;
3941import { IncomingRequestCfProperties , RequestInitCfProperties } from "./cf" ;
4042
43+ const inspect = Symbol . for ( "nodejs.util.inspect.custom" ) ;
44+ const nonEnumerable = Object . create ( null ) ;
45+ nonEnumerable . enumerable = false ;
46+
47+ function makeEnumerable < T > ( prototype : any , instance : T , keys : ( keyof T ) [ ] ) {
48+ for ( const key of keys ) {
49+ const descriptor = Object . getOwnPropertyDescriptor ( prototype , key ) ! ;
50+ descriptor . enumerable = true ;
51+ Object . defineProperty ( instance , key , descriptor ) ;
52+ }
53+ }
54+
4155export class Headers extends BaseHeaders {
4256 getAll ( key : string ) : string [ ] {
4357 if ( key . toLowerCase ( ) !== "set-cookie" ) {
@@ -72,6 +86,7 @@ export const kInner = Symbol("kInner");
7286const kInputGated = Symbol ( "kInputGated" ) ;
7387const kFormDataFiles = Symbol ( "kFormDataFiles" ) ;
7488
89+ const enumerableBodyKeys : ( keyof Body < any > ) [ ] = [ "body" , "bodyUsed" , "headers" ] ;
7590export class Body < Inner extends BaseRequest | BaseResponse > {
7691 [ kInner ] : Inner ;
7792 [ kInputGated ] = false ;
@@ -81,6 +96,23 @@ export class Body<Inner extends BaseRequest | BaseResponse> {
8196
8297 constructor ( inner : Inner ) {
8398 this [ kInner ] = inner ;
99+
100+ makeEnumerable ( Body . prototype , this , enumerableBodyKeys ) ;
101+ Object . defineProperty ( this , kInner , nonEnumerable ) ;
102+ Object . defineProperty ( this , kInputGated , nonEnumerable ) ;
103+ Object . defineProperty ( this , kFormDataFiles , nonEnumerable ) ;
104+ }
105+
106+ [ inspect ] ( ) : Inner {
107+ return this [ kInner ] ;
108+ }
109+
110+ get headers ( ) : Headers {
111+ if ( this . #headers) return this . #headers;
112+ const headers = new Headers ( this [ kInner ] . headers ) ;
113+ // @ts -expect-error internal kGuard isn't included in type definitions
114+ headers [ fetchSymbols . kGuard ] = this [ kInner ] . headers [ fetchSymbols . kGuard ] ;
115+ return ( this . #headers = headers ) ;
84116 }
85117
86118 get body ( ) : ReadableStream | null {
@@ -191,14 +223,6 @@ export class Body<Inner extends BaseRequest | BaseResponse> {
191223 this [ kInputGated ] && ( await waitForOpenInputGate ( ) ) ;
192224 return body ;
193225 }
194-
195- get headers ( ) : Headers {
196- if ( this . #headers) return this . #headers;
197- const headers = new Headers ( this [ kInner ] . headers ) ;
198- // @ts -expect-error internal kGuard isn't included in type definitions
199- headers [ fetchSymbols . kGuard ] = this [ kInner ] . headers [ fetchSymbols . kGuard ] ;
200- return ( this . #headers = headers ) ;
201- }
202226}
203227
204228export function withInputGating < Inner extends Body < BaseRequest | BaseResponse > > (
@@ -221,23 +245,30 @@ export interface RequestInit extends BaseRequestInit {
221245 readonly cf ?: IncomingRequestCfProperties | RequestInitCfProperties ;
222246}
223247
248+ const enumerableRequestKeys : ( keyof Request ) [ ] = [
249+ "cf" ,
250+ "signal" ,
251+ "redirect" ,
252+ "url" ,
253+ "method" ,
254+ ] ;
224255export class Request extends Body < BaseRequest > {
225256 // noinspection TypeScriptFieldCanBeMadeReadonly
226257 #cf?: IncomingRequestCfProperties | RequestInitCfProperties ;
227258
228259 constructor ( input : RequestInfo , init ?: RequestInit ) {
229- // noinspection SuspiciousTypeOfGuard
230260 const cf = input instanceof Request ? input . #cf : init ?. cf ;
231261 if ( input instanceof BaseRequest && ! init ) {
232262 // For cloning
233263 super ( input ) ;
234264 } else {
235265 // Don't pass our strange hybrid Request to undici
236- // noinspection SuspiciousTypeOfGuard
237266 if ( input instanceof Request ) input = input [ kInner ] ;
238267 super ( new BaseRequest ( input , init ) ) ;
239268 }
240269 this . #cf = cf ? nonCircularClone ( cf ) : undefined ;
270+
271+ makeEnumerable ( Request . prototype , this , enumerableRequestKeys ) ;
241272 }
242273
243274 clone ( ) : Request {
@@ -301,6 +332,15 @@ export interface ResponseInit extends BaseResponseInit {
301332
302333const kWaitUntil = Symbol ( "kWaitUntil" ) ;
303334
335+ const enumerableResponseKeys : ( keyof Response ) [ ] = [
336+ "webSocket" ,
337+ "url" ,
338+ "redirected" ,
339+ "ok" ,
340+ "statusText" ,
341+ "status" ,
342+ "type" ,
343+ ] ;
304344export class Response <
305345 WaitUntil extends any [ ] = unknown [ ]
306346> extends Body < BaseResponse > {
@@ -346,6 +386,9 @@ export class Response<
346386 }
347387 this . #status = status ;
348388 this . #webSocket = webSocket ;
389+
390+ makeEnumerable ( Response . prototype , this , enumerableResponseKeys ) ;
391+ Object . defineProperty ( this , kWaitUntil , nonEnumerable ) ;
349392 }
350393
351394 clone ( ) : Response {
@@ -414,7 +457,6 @@ export async function fetch(
414457 // https://developers.cloudflare.com/workers/examples/cache-using-fetch
415458
416459 // Don't pass our strange hybrid Request to undici
417- // noinspection SuspiciousTypeOfGuard
418460 if ( input instanceof Request ) input = input [ kInner ] ;
419461 await waitForOpenOutputGate ( ) ;
420462 const baseRes = await baseFetch ( input , init ) ;
@@ -423,24 +465,13 @@ export async function fetch(
423465 return withInputGating ( res ) ;
424466}
425467
426- const requestInitKeys : ( keyof BaseRequestInit ) [ ] = [
427- "method" ,
428- "keepalive" ,
429- "headers" ,
430- "body" ,
431- "redirect" ,
432- "integrity" ,
433- "signal" ,
434- ] ;
435-
436468export function createCompatFetch (
437469 compat : Compatibility ,
438470 inner : typeof fetch = fetch
439471) : typeof fetch {
440472 const refusesUnknown = compat . isEnabled ( "fetch_refuses_unknown_protocols" ) ;
441473 const formDataFiles = compat . isEnabled ( "formdata_parser_supports_files" ) ;
442474 return async ( input , init ) => {
443- // noinspection SuspiciousTypeOfGuard
444475 const url = new URL (
445476 input instanceof Request || input instanceof BaseRequest
446477 ? input . url
@@ -455,19 +486,13 @@ export function createCompatFetch(
455486 if ( refusesUnknown ) {
456487 throw new TypeError ( `Fetch API cannot load: ${ url . toString ( ) } ` ) ;
457488 } else {
458- // Undici doesn't let you pass a Request as the init prop, without
459- // losing the headers, so if we need to rewrite the URL, we have to
460- // manually build a RequestInit dict so we don't lose those properties.
461- // noinspection SuspiciousTypeOfGuard
462- if ( input instanceof Request ) input = input [ kInner ] ;
463- if ( input instanceof BaseRequest ) {
464- const newInit : RequestInit = { } ;
465- for ( const key of requestInitKeys ) {
466- const value = init ?. [ key ] ?? input [ key ] ;
467- // @ts -expect-error RequestInit is defined as read-only
468- if ( value ) newInit [ key ] = value ;
469- }
470- init = newInit ;
489+ if ( init ) {
490+ init = new Request ( input , init ) ;
491+ } else if ( input instanceof BaseRequest ) {
492+ // BaseRequest's properties aren't enumerable, so convert to a Request
493+ init = new Request ( input ) ;
494+ } else if ( input instanceof Request ) {
495+ init = input ;
471496 }
472497 // Free to mutate this as we created url at the start of the function
473498 url . protocol = "http:" ;
0 commit comments