From 74ebe6b38c37a5fd620bb2b6b2d23eb52ad38bb4 Mon Sep 17 00:00:00 2001 From: Philippe Tremblay Date: Sat, 21 Mar 2026 01:33:33 -0400 Subject: [PATCH 1/3] fix(ts-bindings): populate response headers in fetch() instead of returning empty Headers fetch() was deserializing the HttpResponse (including headers) from BSATN but then discarding them, always returning `new Headers()`. This made it impossible for procedures to inspect response metadata like Content-Type or retry hints. Add a `deserializeHeaders()` helper that converts the BSATN-decoded HttpHeaders entries into a web-standard Headers object, and use it in fetch(). --- .../src/server/http_internal.ts | 8 ++- .../tests/http_headers.test.ts | 51 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 crates/bindings-typescript/tests/http_headers.test.ts diff --git a/crates/bindings-typescript/src/server/http_internal.ts b/crates/bindings-typescript/src/server/http_internal.ts index 69cd3a98287..cdb665857a4 100644 --- a/crates/bindings-typescript/src/server/http_internal.ts +++ b/crates/bindings-typescript/src/server/http_internal.ts @@ -27,6 +27,12 @@ export interface ResponseInit { const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder('utf-8' /* { fatal: true } */); +function deserializeHeaders(headers: HttpHeaders): Headers { + return new Headers( + headers.entries.map(({ name, value }): [string, string] => [name, textDecoder.decode(value)]) + ); +} + const makeResponse = Symbol('makeResponse'); // based on deno's type of the same name @@ -187,7 +193,7 @@ function fetch(url: URL | string, init: RequestOptions = {}) { url: uri, status: response.code, statusText: status(response.code), - headers: new Headers(), + headers: deserializeHeaders(response.headers), aborted: false, }); } diff --git a/crates/bindings-typescript/tests/http_headers.test.ts b/crates/bindings-typescript/tests/http_headers.test.ts new file mode 100644 index 00000000000..7fe5ebecc4b --- /dev/null +++ b/crates/bindings-typescript/tests/http_headers.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from 'vitest'; +import { BinaryReader, BinaryWriter } from '../src'; +import { HttpResponse, HttpHeaders } from '../src/lib/http_types'; + +describe('HttpResponse header round-trip', () => { + test('headers survive BSATN serialize/deserialize', () => { + const textEncoder = new TextEncoder(); + const textDecoder = new TextDecoder('utf-8'); + + const original: HttpResponse = { + headers: { + entries: [ + { name: 'content-type', value: textEncoder.encode('text/event-stream') }, + { name: 'x-request-id', value: textEncoder.encode('abc-123') }, + ], + }, + version: { tag: 'Http11' }, + code: 200, + }; + + const writer = new BinaryWriter(256); + HttpResponse.serialize(writer, original); + const buf = writer.getBuffer(); + + const deserialized = HttpResponse.deserialize(new BinaryReader(buf)); + + expect(deserialized.code).toBe(200); + expect(deserialized.headers.entries).toHaveLength(2); + + expect(deserialized.headers.entries[0].name).toBe('content-type'); + expect(textDecoder.decode(deserialized.headers.entries[0].value)).toBe('text/event-stream'); + + expect(deserialized.headers.entries[1].name).toBe('x-request-id'); + expect(textDecoder.decode(deserialized.headers.entries[1].value)).toBe('abc-123'); + }); + + test('empty headers round-trip correctly', () => { + const original: HttpResponse = { + headers: { entries: [] }, + version: { tag: 'Http11' }, + code: 404, + }; + + const writer = new BinaryWriter(64); + HttpResponse.serialize(writer, original); + const deserialized = HttpResponse.deserialize(new BinaryReader(writer.getBuffer())); + + expect(deserialized.code).toBe(404); + expect(deserialized.headers.entries).toHaveLength(0); + }); +}); From e4471f99ea9d4230f06a0bf442ce4fc85654d85d Mon Sep 17 00:00:00 2001 From: Philippe Tremblay Date: Thu, 9 Apr 2026 16:16:26 -0400 Subject: [PATCH 2/3] fix(ts-bindings): remove unused HttpHeaders import in test --- crates/bindings-typescript/tests/http_headers.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bindings-typescript/tests/http_headers.test.ts b/crates/bindings-typescript/tests/http_headers.test.ts index 7fe5ebecc4b..adbefab8772 100644 --- a/crates/bindings-typescript/tests/http_headers.test.ts +++ b/crates/bindings-typescript/tests/http_headers.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; import { BinaryReader, BinaryWriter } from '../src'; -import { HttpResponse, HttpHeaders } from '../src/lib/http_types'; +import { HttpResponse } from '../src/lib/http_types'; describe('HttpResponse header round-trip', () => { test('headers survive BSATN serialize/deserialize', () => { From 46f02143660623e523f80231d12fb67ffe89b85c Mon Sep 17 00:00:00 2001 From: Philippe Tremblay Date: Thu, 9 Apr 2026 19:01:03 -0400 Subject: [PATCH 3/3] fix(ts-bindings): format with prettier --- .../src/server/http_internal.ts | 5 ++++- .../tests/http_headers.test.ts | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/bindings-typescript/src/server/http_internal.ts b/crates/bindings-typescript/src/server/http_internal.ts index cdb665857a4..a58031e3a08 100644 --- a/crates/bindings-typescript/src/server/http_internal.ts +++ b/crates/bindings-typescript/src/server/http_internal.ts @@ -29,7 +29,10 @@ const textDecoder = new TextDecoder('utf-8' /* { fatal: true } */); function deserializeHeaders(headers: HttpHeaders): Headers { return new Headers( - headers.entries.map(({ name, value }): [string, string] => [name, textDecoder.decode(value)]) + headers.entries.map(({ name, value }): [string, string] => [ + name, + textDecoder.decode(value), + ]) ); } diff --git a/crates/bindings-typescript/tests/http_headers.test.ts b/crates/bindings-typescript/tests/http_headers.test.ts index adbefab8772..0cba0d90b2e 100644 --- a/crates/bindings-typescript/tests/http_headers.test.ts +++ b/crates/bindings-typescript/tests/http_headers.test.ts @@ -10,7 +10,10 @@ describe('HttpResponse header round-trip', () => { const original: HttpResponse = { headers: { entries: [ - { name: 'content-type', value: textEncoder.encode('text/event-stream') }, + { + name: 'content-type', + value: textEncoder.encode('text/event-stream'), + }, { name: 'x-request-id', value: textEncoder.encode('abc-123') }, ], }, @@ -28,10 +31,14 @@ describe('HttpResponse header round-trip', () => { expect(deserialized.headers.entries).toHaveLength(2); expect(deserialized.headers.entries[0].name).toBe('content-type'); - expect(textDecoder.decode(deserialized.headers.entries[0].value)).toBe('text/event-stream'); + expect(textDecoder.decode(deserialized.headers.entries[0].value)).toBe( + 'text/event-stream' + ); expect(deserialized.headers.entries[1].name).toBe('x-request-id'); - expect(textDecoder.decode(deserialized.headers.entries[1].value)).toBe('abc-123'); + expect(textDecoder.decode(deserialized.headers.entries[1].value)).toBe( + 'abc-123' + ); }); test('empty headers round-trip correctly', () => { @@ -43,7 +50,9 @@ describe('HttpResponse header round-trip', () => { const writer = new BinaryWriter(64); HttpResponse.serialize(writer, original); - const deserialized = HttpResponse.deserialize(new BinaryReader(writer.getBuffer())); + const deserialized = HttpResponse.deserialize( + new BinaryReader(writer.getBuffer()) + ); expect(deserialized.code).toBe(404); expect(deserialized.headers.entries).toHaveLength(0);