Support server sent events (SSE)
Description
text/event-stream
https://gist.github.com/montasaurus/b517b1306fdd8d41cbf857d6ec5e7c82
Not very descriptive, but I know what you mean
This would be great
@ohnoah can you provide more details about your API/how you're currently calling it?
This is the API endpoint. The generated fetch client thing just returns the stream type as a response. How I want to use this is as an event stream with .onmessage .close etc etc.. Therefore, it doesn't make sense to generate a fetch method, but instead I only really need the types so I can do that.
I saw the "stream" option in parseAs but didn't see any docs about it and not sure what that does. Is that for SSE like this?
@ohnoah oh no, stream just "streams" the raw response to you. Maybe a different naming would work better. Please let me know if you manage to hack around your issue in the meantime before it's fully supported!
I guess I'm saying it should just not generate an endpoint if the type is event stream, since the semantics of an event stream endpoint is different. It's not correct to say that you can call it with fetch and get a response back with that type.
but yeah, I can just ignore it for now so can definitely work around it
Fern support this now: https://buildwithfern.com/learn/sdks/capabilities/server-sent-events
@himself65 yup, but it's a paid feature, right ($250+ per month)? I'd prioritise it too if you sponsored a similar amount
@ohnoah would it not be good to have autogeneration of types at least, so events coming through can be typed?
@robertlagrant sounds like it would be, at least for you! Do you have an example OpenAPI spec with such events?
Hah, fair response. OpenAPI are discussing it now - might be worth a read. https://github.com/OAI/OpenAPI-Specification/discussions/4171#discussioncomment-11354714
(FYI it's Server Sent Events.) 🤓
Oops, updated the title, thanks!
I don't think SSE will get added to the OAI spec anytime soon. The closest you'll see reading the OAI proposal thread is the proposal from Speakeasy (a commercial SDK generator service), which is what they use for their SDK generation:
https://github.com/OAI/OpenAPI-Specification/discussions/4171#discussioncomment-11341721 https://www.speakeasy.com/docs/customize/runtime/server-sent-events#modeling-sse-in-openapi
From my research so far, because there's no official spec around SSE, implementations on the OAI side are going to be completely custom.
But if I was to implement something, probably the Speakeasy one since it's the most defined proposal ATM and is being used in the wild commercially. Another unfortunate side to this is that the proposal is compatible with OAI 3.1, not 3.0 (and I think hey-api only supports 3.0 at the moment)
@theogravity we support 3.1 too. Why do you have the impression we don't? Are you missing any feature? Except for SSE 😀
@theogravity we support 3.1 too. Why do you have the impression we don't? Are you missing any feature? Except for SSE 😀
Oh yay! Thought I read somewhere that 3.0 is only supported or there's only partial support for 3.1. Sorry. I'm glad that you do!
The SSE thing is unfortunately a blocker for us ATM; other than that, we love using hey-api in our projects where SSE isn't involved, and it's our go-to for Typescript generation.
@theogravity that was probably the experimental parser which has now been enabled by default and supports all versions (except OpenAPI 1.2) if you're running the latest. SSE will be prioritised this year for sure!
Thanks, I once also built my own OAI client generator from the ground up years ago and understand how difficult it is to remotely build something fully-featured when it comes to OAI. I just put in a donation to support the overall effort on this project in general; I'm not expecting anything in return (nor am I expecting SSE to be built here).
Thanks so much for the proactive response and just being awesome!
Thank you so much @theogravity! I was just coming here to thank you when I saw the notification. I'll definitely keep this thread updated when this gets prioritised, but feel free to connect with me on LinkedIn or through email if you've got any more feedback or issue!
Any updates on this? Here is the custom hook I created as a workaround for this:
// Defines the structured return shape of an SSE
type SseEventResponse = {
data: unknown;
event: string;
id?: number;
retry?: number;
};
// Converts the array of responses into a key-value pair, where the key is the event name and the value is a
// handler function receiving the event data and the sse client as arguments.
type SseHandlers<T extends SseEventResponse[]> = {
[k in T[number]["event"]]: (
data: Extract<T[number], { event: k }>["data"],
client: EventSource,
) => void | Promise<void>;
};
// Used just for typesafing in `Object.entries`
type SseHandlerEntry<T extends SseEventResponse[]> = [
keyof SseHandlers<T>,
SseHandlers<T>[keyof SseHandlers<T>],
];
// Hook options
type Opts = {
disabled?: boolean;
};
export const useSSE = <T extends SseEventResponse[]>(
endpoint: string,
handlers: SseHandlers<T>,
opts: Opts = {
disabled: false,
},
) => {
const [error, setError] = useState(false);
useEffect(() => {
if (opts.disabled) return undefined;
const sseClient = new EventSource(
env.BASE_URL+ endpoint,
);
sseClient.onerror = () => setError(true);
for (const [event, handler] of Object.entries(
handlers,
) as SseHandlerEntry<T>[]) {
sseClient.addEventListener(event, (ev) => {
const data = JSON.parse(ev.data as string) as T[number]["data"];
const execHandler = async () => await handler(data, sseClient);
void execHandler();
});
}
return () => sseClient.close();
}, [opts.disabled]);
return { error };
};
And here is an example usage:
// Generic type here is imported from `types.gen.ts`, be sure to import the correct one.
const { error } = useSSE<GenerateQrCodeResponse>(
`/session/${id}/qr`,
{
qrCodeReceived: (data) => setQrCode(data.base64Code),
linkedSuccessfully: async (_, client) => {
client.close();
await updateSession();
},
},
);
thanks @kareemmahlees,
I modified a bit your code to do a bit of types gymnastic to determine the types based on the path and only allow path that have text/event-stream
import { paths } from "@/lib/api/api.types"; // import your path from your generated code
import { useState, useEffect } from "react";
// Defines the structured return shape of an SSE
type SseEventResponse = {
data: unknown;
event: string;
id?: number;
retry?: number;
};
// Converts the array of responses into a key-value pair, where the key is the event name and the value is a
// handler function receiving the event data and the sse client as arguments.
type SseHandlers<T extends SseEventResponse[]> = {
[k in T[number]["event"]]: (
data: Extract<T[number], { event: k }>["data"],
client: EventSource
) => void | Promise<void>;
};
// Used just for typesafing in `Object.entries`
type SseHandlerEntry<T extends SseEventResponse[]> = [
keyof SseHandlers<T>,
SseHandlers<T>[keyof SseHandlers<T>]
];
// Hook options
type Opts = {
disabled?: boolean;
};
type HasEventStream<P> = {
[K in keyof P]: P[K] extends {
get: {
responses: {
200: {
content: {
"text/event-stream": any;
};
};
};
};
}
? K
: never;
}[keyof P];
type EventStreamOf<Ep extends HasEventStream<paths>> = Ep extends keyof paths
? paths[Ep]["get"]["responses"][200]["content"]["text/event-stream"]
: never;
export const useSSE = <
Ep extends HasEventStream<paths>,
T extends SseEventResponse[] = EventStreamOf<Ep>
>(
endpoint: Ep,
handlers: SseHandlers<T>,
opts: Opts = {
disabled: false,
}
) => {
// same as before ...
}
yoohoo, this will be available in v0.81.0 🍿 looking forward to your feedback as there'll be many edge cases!
Oooh! I have code that I had to hack around to get SSE to work with Hey API, I'll be able to test this out soon. Thanks! 🙏🏻
Let me know! Most notably it doesn't handle cases where the same endpoint returns stream OR regular response. If you have a use case for that, we can improve it
@mrlubos I have a use case for that, similar to how the OpenAI (not OpenAPI) Responses API supports passing in stream: true or stream: false to have a single endpoint return either application/json or text/event-stream.
@Bdthomson please open a new issue with your spec and let's chat there!