diff --git a/src/response.ts b/src/response.ts index ac67c00..1f4fb59 100644 --- a/src/response.ts +++ b/src/response.ts @@ -4,30 +4,33 @@ import type { OutgoingHttpHeaders } from 'node:http' import { buildOutgoingHttpHeaders } from './utils' const responseCache = Symbol('responseCache') -const newGlobalResponseKey = Symbol('newGlobalResponse') export const cacheKey = Symbol('cache') export const GlobalResponse = global.Response export class Response { #body?: BodyInit | null - #init?: ResponseInit; + #init?: ResponseInit - [newGlobalResponseKey](): typeof GlobalResponse { - return new GlobalResponse( - this.#body, - this.#init instanceof Response ? this.#init[newGlobalResponseKey]() : (this.#init as any) - ) as any - } - - // @ts-ignore private get cache(): typeof GlobalResponse { delete (this as any)[cacheKey] - return ((this as any)[responseCache] ||= this[newGlobalResponseKey]()) + return ((this as any)[responseCache] ||= new GlobalResponse(this.#body, this.#init)) } constructor(body?: BodyInit | null, init?: ResponseInit) { this.#body = body - this.#init = init + if (init instanceof Response) { + const cachedGlobalResponse = (init as any)[responseCache] + if (cachedGlobalResponse) { + this.#init = cachedGlobalResponse + // instantiate GlobalResponse cache and this object always returns value from global.Response + this.cache + return + } else { + this.#init = init.#init + } + } else { + this.#init = init + } if (typeof body === 'string' || body instanceof ReadableStream) { let headers = (init?.headers || { 'content-type': 'text/plain;charset=UTF-8' }) as @@ -38,7 +41,7 @@ export class Response { headers = buildOutgoingHttpHeaders(headers) } - (this as any)[cacheKey] = [init?.status || 200, body, headers] + ;(this as any)[cacheKey] = [init?.status || 200, body, headers] } } } diff --git a/test/response.test.ts b/test/response.test.ts index 6be4071..7b7be84 100644 --- a/test/response.test.ts +++ b/test/response.test.ts @@ -4,6 +4,18 @@ import { GlobalResponse } from '../src/response' class NextResponse extends Response {} +class UpperCaseStream extends TransformStream { + constructor() { + super({ + transform(chunk, controller) { + controller.enqueue( + new TextEncoder().encode(new TextDecoder().decode(chunk).toString().toUpperCase()) + ) + }, + }) + } +} + describe('Response', () => { let server: Server let port: number @@ -54,14 +66,27 @@ describe('Response', () => { // support other class to extends from Response expect(new NextResponse()).toBeInstanceOf(Response) + }) + it('Should not lose header data', async () => { const parentResponse = new Response('OK', { headers: { 'content-type': 'application/json', - } + }, }) const childResponse = new Response('OK', parentResponse) parentResponse.headers.delete('content-type') expect(childResponse.headers.get('content-type')).toEqual('application/json') }) + + it('Nested constructors should not cause an error even if ReadableStream is specified', async () => { + const stream = new Response('hono').body + const parentResponse = new Response(stream) + const upperCaseStream = new UpperCaseStream() + const childResponse = new Response( + parentResponse.body!.pipeThrough(upperCaseStream), + parentResponse + ) + expect(await childResponse.text()).toEqual('HONO') + }) })