Files
skills/docs/api-stack/api/api-plugin-sse/src/middleware/sse-handler.md
2026-05-29 08:20:56 +01:00

4.0 KiB

sse-handler

(no submodules)

Purpose

Hono handler factory that turns a subscription on SseService into a streaming HTTP response framed in the text/event-stream wire format. Handles per-connection heartbeats, abort-on-disconnect via the request's AbortSignal, Last-Event-ID resume from the ring buffer, and backpressure honoring the configured queueHighWaterMark and overflowPolicy. Returns a standard Response whose body is a ReadableStream<Uint8Array> so any modern runtime (Bun, Node 18+, Cloudflare Workers, Deno) streams without an adapter shim.

Features

  • Reads :channel from the route path; missing throws SseError(SSE_CHANNEL_REQUIRED) for the host's error handler to translate to 400.
  • Reads Last-Event-ID from request headers and forwards to service.subscribe for replay.
  • Optional resolvePrincipalId(c) lets consumers wire any auth stack (e.g. c.get("user")?.id) without this package depending on @suaveplan/api-plugin-auth.
  • Optional requirePrincipal: true raises SseError(SSE_UNAUTHENTICATED) (HTTP 401) when the resolver returns undefined.
  • Heartbeat scheduler writes a : heartbeat\n\n comment frame every heartbeatIntervalMs to keep idle connections alive through proxies; the timer is injectable (setIntervalImpl/clearIntervalImpl) for deterministic tests.
  • Backpressure handling: when the writable controller's desiredSize falls to zero or the in-memory backlog exceeds queueHighWaterMark, applies overflowPolicy and increments sse.queue_overflow_total{policy, code}.
  • Sets canonical response headers: Content-Type: text/event-stream; charset=utf-8, Cache-Control: no-cache, no-transform, X-Accel-Buffering: no, Connection: keep-alive.

Usage: Basic

import { Hono } from "hono";
import { createSseRoute } from "@suaveplan/api-plugin-sse/middleware/sse-handler";

const app = new Hono();
app.get(
    "/sse/:channel",
    createSseRoute(sseService, {
        heartbeatIntervalMs: 30_000,
        queueHighWaterMark: 256,
        overflowPolicy: "drop-oldest",
    })
);

Usage: Advanced

import { Hono } from "hono";
import { createSseRoute } from "@suaveplan/api-plugin-sse/middleware/sse-handler";
import { requireAuth } from "@suaveplan/api-plugin-auth/middleware/require-auth";

const app = new Hono();

// Compose with the auth plugin: require an authenticated principal,
// scope the principalId to the subscription cap.
app.get(
    "/sse/:channel",
    requireAuth(),
    createSseRoute(sseService, {
        heartbeatIntervalMs: 15_000,
        queueHighWaterMark: 128,
        overflowPolicy: "drop-newest",
        resolvePrincipalId: (c) => c.get("user")?.id,
        requirePrincipal: true,
    })
);

API Reference

  • interface CreateSseRouteOptions — { heartbeatIntervalMs, queueHighWaterMark, overflowPolicy, resolvePrincipalId?, requirePrincipal?, setIntervalImpl?, clearIntervalImpl? }.
  • createSseRoute(service: SseService, options: CreateSseRouteOptions): Handler — returns a Hono handler.

Implementation Notes

  • The response body is constructed as new ReadableStream<Uint8Array> with an async producer running inside start(controller). The producer drains handle.events via for await, frames each event with formatSseFrame, and pushes the encoded bytes through the controller.
  • Cleanup is centralised in a single async closure that clears the heartbeat interval, calls handle.close(), and records the sse.connection_duration_ms histogram. It is invoked from both the iterator's finally branch and the stream's cancel callback so abort and natural end converge on the same code path.
  • Backpressure is detected by checking controller.desiredSize; values of null (no high-water mark) and positive integers are treated as "not full". Zero or negative trigger the overflow policy.
  • Heartbeat interval uses native setInterval by default; tests inject a fake to drive the timer deterministically (matching the FakeClock pattern used elsewhere ).