2
2
// Define lightweight pseudo Response class and replace global.Response with it.
3
3
4
4
import type { OutgoingHttpHeaders } from 'node:http'
5
- import { buildOutgoingHttpHeaders } from './utils'
6
-
7
- interface InternalBody {
8
- source : string | Uint8Array | FormData | Blob | null
9
- stream : ReadableStream
10
- length : number | null
11
- }
12
5
13
6
const responseCache = Symbol ( 'responseCache' )
14
7
const getResponseCache = Symbol ( 'getResponseCache' )
15
8
export const cacheKey = Symbol ( 'cache' )
16
9
10
+ export type InternalCache = [
11
+ number ,
12
+ string | ReadableStream ,
13
+ Record < string , string > | Headers | OutgoingHttpHeaders ,
14
+ ]
15
+ interface LightResponse {
16
+ [ responseCache ] ?: globalThis . Response
17
+ [ cacheKey ] ?: InternalCache
18
+ }
19
+
17
20
export const GlobalResponse = global . Response
18
21
export class Response {
19
22
#body?: BodyInit | null
20
23
#init?: ResponseInit ;
21
24
22
- [ getResponseCache ] ( ) : typeof GlobalResponse {
23
- delete ( this as any ) [ cacheKey ]
24
- return ( ( this as any ) [ responseCache ] ||= new GlobalResponse ( this . #body, this . #init) )
25
+ [ getResponseCache ] ( ) : globalThis . Response {
26
+ delete ( this as LightResponse ) [ cacheKey ]
27
+ return ( ( this as LightResponse ) [ responseCache ] ||= new GlobalResponse ( this . #body, this . #init) )
25
28
}
26
29
27
30
constructor ( body ?: BodyInit | null , init ?: ResponseInit ) {
31
+ let headers : HeadersInit
28
32
this . #body = body
29
33
if ( init instanceof Response ) {
30
34
const cachedGlobalResponse = ( init as any ) [ responseCache ]
@@ -35,36 +39,48 @@ export class Response {
35
39
return
36
40
} else {
37
41
this . #init = init . #init
42
+ // clone headers to avoid sharing the same object between parent and child
43
+ headers = new Headers ( ( init . #init as ResponseInit ) . headers )
38
44
}
39
45
} else {
40
46
this . #init = init
41
47
}
42
48
43
- if ( typeof body === 'string' || typeof ( body as ReadableStream ) ?. getReader !== 'undefined' ) {
44
- let headers = ( init ?. headers || { 'content-type' : 'text/plain; charset=UTF-8' } ) as
45
- | Record < string , string >
46
- | Headers
47
- | OutgoingHttpHeaders
48
- if ( headers instanceof Headers ) {
49
- headers = buildOutgoingHttpHeaders ( headers )
50
- }
51
-
49
+ if (
50
+ typeof body === 'string' ||
51
+ typeof ( body as ReadableStream ) ?. getReader !== 'undefined' ||
52
+ body instanceof Blob ||
53
+ body instanceof Uint8Array
54
+ ) {
55
+ headers ||= init ?. headers || { 'content-type' : 'text/plain; charset=UTF-8' }
52
56
; ( this as any ) [ cacheKey ] = [ init ?. status || 200 , body , headers ]
53
57
}
54
58
}
59
+
60
+ get headers ( ) : Headers {
61
+ const cache = ( this as LightResponse ) [ cacheKey ] as InternalCache
62
+ if ( cache ) {
63
+ if ( ! ( cache [ 2 ] instanceof Headers ) ) {
64
+ cache [ 2 ] = new Headers ( cache [ 2 ] as HeadersInit )
65
+ }
66
+ return cache [ 2 ]
67
+ }
68
+ return this [ getResponseCache ] ( ) . headers
69
+ }
70
+
71
+ get status ( ) {
72
+ return (
73
+ ( ( this as LightResponse ) [ cacheKey ] as InternalCache | undefined ) ?. [ 0 ] ??
74
+ this [ getResponseCache ] ( ) . status
75
+ )
76
+ }
77
+
78
+ get ok ( ) {
79
+ const status = this . status
80
+ return status >= 200 && status < 300
81
+ }
55
82
}
56
- ; [
57
- 'body' ,
58
- 'bodyUsed' ,
59
- 'headers' ,
60
- 'ok' ,
61
- 'redirected' ,
62
- 'status' ,
63
- 'statusText' ,
64
- 'trailers' ,
65
- 'type' ,
66
- 'url' ,
67
- ] . forEach ( ( k ) => {
83
+ ; [ 'body' , 'bodyUsed' , 'redirected' , 'statusText' , 'trailers' , 'type' , 'url' ] . forEach ( ( k ) => {
68
84
Object . defineProperty ( Response . prototype , k , {
69
85
get ( ) {
70
86
return this [ getResponseCache ] ( ) [ k ]
@@ -80,26 +96,3 @@ export class Response {
80
96
} )
81
97
Object . setPrototypeOf ( Response , GlobalResponse )
82
98
Object . setPrototypeOf ( Response . prototype , GlobalResponse . prototype )
83
-
84
- const stateKey = Reflect . ownKeys ( new GlobalResponse ( ) ) . find (
85
- ( k ) => typeof k === 'symbol' && k . toString ( ) === 'Symbol(state)'
86
- ) as symbol | undefined
87
- if ( ! stateKey ) {
88
- console . warn ( 'Failed to find Response internal state key' )
89
- }
90
-
91
- export function getInternalBody (
92
- response : Response | typeof GlobalResponse
93
- ) : InternalBody | undefined {
94
- if ( ! stateKey ) {
95
- return
96
- }
97
-
98
- if ( response instanceof Response ) {
99
- response = ( response as any ) [ getResponseCache ] ( )
100
- }
101
-
102
- const state = ( response as any ) [ stateKey ] as { body ?: InternalBody } | undefined
103
-
104
- return ( state && state . body ) || undefined
105
- }
0 commit comments