HTTP Readable Streams

If you need to build custom endpoints that stream data progressively (like LLM text generation), Radiant provides a seamless ctx.stream() utility right on the request context.

This uses standard HTTP Readable Streams, meaning the connection stays open while the server writes iterative chunks directly to the response body.

Writing a Stream

app.router.get("/my-stream", async (ctx) => {
  // Streams plain text sequentially
  await ctx.stream("chunk 1", { delay: 500 });
  await ctx.stream("chunk 2", { delay: 500 });
  
  // Streams an object as JSON automatically
  await ctx.stream({ finish: true });
});

Auto Server-Sent Events

You can mix custom HTTP Readable Streams with SSE by providing an event option. The framework will automatically format the chunks to strictly adhere to the event: and data: specification:

app.router.get("/ai-chat", async (ctx) => {
  // Connection start
  await ctx.stream({ status: "thinking" }, { event: "connection" });
  
  // Iterative stream
  for (const token of llm.generate()) {
    await ctx.stream({ token }, { event: "message" });
  }
  
  // Close the stream automatically simply by returning from the handler!
}, { stream: true }); // Marks the route as a streaming endpoint for OpenAPI

Notice that passing { stream: true } in the route options will automatically tag this route's content-type as text/event-stream in your generated SDK and OpenAPI specs.

Advanced Usage

If you prefer lower-level control or are importing raw web streams, Radiant exports a helper stream() from the core package that accepts Iterables or AsyncGenerators:

import { stream } from "@codesordinatestudio/radiant-bun";

app.router.get("/raw-stream", (ctx) => {
  function* generator() {
    yield "Hello";
    yield " ";
    yield "World!";
  }
  
  return stream(generator());
});