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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions