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);