Skip to content

Typed response headers for the generated HTTP client #424

Description

@jonaslagoni

Summary

Give the generated HTTP client typed response headers. Today HttpClientResponse<T>.headers is hardcoded to Record<string, string> and populated by an untyped extractHeaders() — there is no response-header model at any layer. This is a deliberate follow-up split out of the headers companion-interface work (mirror of #413), which covers only request / user-provided headers.

Background

While planning the headers companion-interface change (unify OpenAPI/AsyncAPI headers on a class + emit <Name>Interface + widen user-provided sites to Interface | Class), we scoped typed response headers and found it is a separable, larger feature requiring a new input-processing path, so it was deferred.

  • Plan (request-side work): .claude/thoughts/shared/plans/2026-07-17-headers-companion-interface.md
  • Research: .claude/thoughts/shared/research/2026-07-16-headers-companion-interface.md

Current state

  • HttpClientResponse<T> has headers: Record<string, string> — untyped, identical for every operation. — src/codegen/generators/typescript/channels/protocols/http/common-types.ts:76-87
  • It's filled by extractHeaders(response) (lower-cases keys, no typing/filtering). — common-types.ts:365-383, called once at client.ts:348, assigned at client.ts:354.
  • Only request headers are modeled end-to-end. processOpenAPIHeaders reads only param.in === 'header' (request params), never responses[code].headers. — src/codegen/inputs/openapi/generators/headers.ts:91
  • ProcessedHeadersData.channelHeaders is a single flat map keyed by operation id (one schema slot) — no response direction. — src/codegen/generators/typescript/headers.ts:96-105
  • Header models are looked up by a single operationId key with no _Response suffix, unlike payloads which already use the ${operationId}_Response dual-key convention. — channels/openapi.ts:211 (contrast payloads channels/openapi.ts:201-203)

Bottom line: Record<string,string> at common-types.ts:84 is the only shape response headers have ever had. This needs a new processed-data path plus coordinated plumbing — not a "wire an existing type" change.

Proposed scope

  • Input processing (prerequisite): parse OpenAPI responses[code].headers maps into a ProcessedHeadersData-like structure — a new processResponseHeaders or a second responseHeaders map keyed ${operationId}_Response. — src/codegen/inputs/openapi/generators/headers.ts
  • Headers generator: add a parallel response-header map / key convention so a distinct response-header model can be generated + looked up per operation. — src/codegen/generators/typescript/headers.ts:189,208-209
  • Channel generator: add a response-headers lookup in processOperation() (analogous to headersModel at channels/openapi.ts:211) and thread it into the client. — src/codegen/generators/typescript/channels/openapi.ts:172-284
  • Render params: add response-header fields alongside the request-only channelHeaders. — src/codegen/generators/typescript/channels/types.ts:279-313
  • Client + response type: make HttpClientResponse generic over headers (e.g. HttpClientResponse<TData, THeaders = Record<string,string>>); unmarshal typed response headers (analogous to the body responseParseCode) and place them in the result, replacing/augmenting extractHeaders(). — client.ts:291,344-356, common-types.ts:76-87,365-383
  • AsyncAPI reply headers (open question — see below).

Response headers are a received surface, so — consistent with #413 leaving received/callback sites class-typed — they should be typed via .unmarshal() into a class and would not need a companion Interface.

Open questions

  • Should AsyncAPI reply headers be typed too, or OpenAPI HTTP responses only for a first cut?
  • Default the second generic to Record<string,string> for backward-compat, or always emit the typed model?
  • How to key multiple response codes per operation (single merged model vs. per-status models)?

Testing / docs (required)

  • Unit + snapshot: test/codegen/inputs/openapi.spec.ts, channels.spec.ts, headers.spec.ts
  • Runtime: test/runtime/typescript/.../http_client/ — assert typed response headers round-trip
  • Docs: docs/generators/headers.md, docs/generators/channels.md
  • Example: examples/openapi-http-client/

Breaking change

Changes generated output (HttpClientResponse shape / response-header typing) → major version bump.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions