Responses & Errors

Streaming and Web Payloads

Stream Web ReadableStreams, binary buffers, Blobs, and Server-Sent Events with edge-compatible helpers from @taserjs/router/stream.

@taserjs/router/stream exposes Web Standard helpers — pipe, buffer, blob, and sse — that work the same on Node, Bun, Deno, Workers, and other fetch runtimes. There is no filesystem file() helper; serve disk content through your host platform or by wrapping a ReadableStream / Blob yourself.


Piping Readable Streams (pipe)

Stream dynamically generated data, AI completions, or proxied upstream bodies:

src/routes/ai/generate.post.ts
import { pipe } from "@taserjs/router/stream";
import { t } from "@taserjs/router";

export default t.post("/ai/generate").handler(async () => {
  const encoder = new TextEncoder();

  const customStream = new ReadableStream({
    async start(controller) {
      for (const word of ["Streaming", " live", " AI", " response", "..."]) {
        controller.enqueue(encoder.encode(word));
        await new Promise((resolve) => setTimeout(resolve, 200));
      }
      controller.close();
    },
  });

  return pipe(customStream, {
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
    },
  });
});

Binary Payloads (buffer)

Return generated images, PDFs, or other byte arrays:

src/routes/export/pdf.get.ts
import { buffer } from "@taserjs/router/stream";
import { t } from "@taserjs/router";

export default t.get("/export/pdf").handler(async ({ req }) => {
  const pdfBytes: Uint8Array = await generateInvoicePdf(req.query);

  return buffer(pdfBytes, {
    headers: {
      "Content-Type": "application/pdf",
      "Content-Disposition": 'attachment; filename="invoice.pdf"',
    },
  });
});

buffer() defaults Content-Type to application/octet-stream when you omit it.


Blob Payloads (blob)

Pass a Blob (for example from fetch or in-memory construction) and preserve its type when present:

src/routes/assets/logo.get.ts
import { blob } from "@taserjs/router/stream";
import { t } from "@taserjs/router";

export default t.get("/assets/logo").handler(async () => {
  const upstream = await fetch("https://cdn.example.com/logo.png");
  const logo = await upstream.blob();

  return blob(logo, {
    headers: {
      "Cache-Control": "public, max-age=86400",
    },
  });
});

Server-Sent Events (sse)

Use sse() for the W3C Event Stream protocol. It sets Content-Type: text/event-stream and Cache-Control: no-cache for you:

src/routes/events/notifications.get.ts
import { sse } from "@taserjs/router/stream";
import { t } from "@taserjs/router";

export default t.get("/events/notifications").handler(({ req }) => {
  return sse(
    async (stream) => {
      await stream.write({ event: "connected", data: { ok: true } });

      const interval = setInterval(() => {
        void stream.write({
          event: "tick",
          data: { time: new Date().toISOString() },
        });
      }, 1000);

      stream.onAbort(() => clearInterval(interval));
    },
    { signal: req.raw.signal },
  );
});

formatSSE

Prefer sse() for responses. Import formatSSE when you need to encode individual messages into SSE text for a custom ReadableStream passed to pipe().


Next Steps