openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

SSE generated functions are not using the serialized body from `beforeRequest`

Open Bdthomson opened this issue 3 months ago • 3 comments

Description

For a normal request, the serialized body is used. This is because beforeRequest will generate the serialized body. Example generated method:

  const request: Client['request'] = async (options) => {
    // @ts-expect-error
    const { opts, url } = await beforeRequest(options);
    const requestInit: ReqInit = {
      redirect: 'follow',
      ...opts,
      body: opts.serializedBody,
    };

But for SSE the generator will still call beforeRequest to get the serialized body, but it doesn't use the serialized body:

  const makeSseFn =
    (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
      const { opts, url } = await beforeRequest(options);
      return createSseClient({
        ...opts,
        body: opts.body as BodyInit | null | undefined,
        headers: opts.headers as unknown as Record<string, string>,
        method,
        url,
      });
    };

This means that callers of this method have to JSON.stringify, or we have to update the generated method to use serialized body.

Reproducible example or configuration

No response

OpenAPI specification (optional)

No response

System information (optional)

No response

Bdthomson avatar Aug 28 '25 19:08 Bdthomson

Which version are you on? This might be fixed in v0.82.0

mrlubos avatar Aug 28 '25 19:08 mrlubos

I was on v0.81.1, I just upgraded to v0.82.0 (looks like it was released an hour ago?) and it works! Sorry, I should have included that in my issue.

Bdthomson avatar Aug 28 '25 19:08 Bdthomson

Context

We’re on @hey-api/[email protected] and rely on beforeRequest to produce signed SSE requests. The generated fetch client ignores the serializedBody returned from beforeRequest, so the request body never reaches the underlying fetch.

Minimal repro

import { createClient } from '@hey-api/openapi-ts/fetch';

const client = createClient({
  baseUrl: 'https://example.test',
  beforeRequest: async (options) => {
    // e.g., we serialize + HMAC-sign the SSE handshake body here
    return {
      ...options,
      serializedBody: JSON.stringify({ token: 'signed-payload' }),
      headers: { ...options.headers, 'content-type': 'application/json' },
    };
  },
});

await client.sse('/stream', {
  method: 'POST',
  body: { token: 'unsigned' },
  onMessage: () => {},
});

Expected: the POST request carries {"token":"signed-payload"} (the serializedBody from beforeRequest).

Actual: the outgoing request still sends {"token":"unsigned"} (the original body), because the generated SSE client overwrites the RequestInit body with serializedBody before beforeRequest runs, but never copies it back in afterward.

Local patch

We patched dist/clients/core/serverSentEvents.ts and dist/clients/fetch/client.ts to:

  • separate serializedBody from the rest of beforeRequest options,
  • forward it into the RequestInit we pass to new Request(url, init), and
  • reuse the existing getValidRequestBody helper so other clients stay consistent.

Would you consider reopening this? Without it, any SSE flow that needs to rewrite/serialize the body in beforeRequest (for signing, encryption, etc.) is broken.

fitchmultz avatar Oct 27 '25 14:10 fitchmultz