4.0 KiB
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
:channelfrom the route path; missing throwsSseError(SSE_CHANNEL_REQUIRED)for the host's error handler to translate to 400. - Reads
Last-Event-IDfrom request headers and forwards toservice.subscribefor 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: trueraisesSseError(SSE_UNAUTHENTICATED)(HTTP 401) when the resolver returnsundefined. - Heartbeat scheduler writes a
: heartbeat\n\ncomment frame everyheartbeatIntervalMsto keep idle connections alive through proxies; the timer is injectable (setIntervalImpl/clearIntervalImpl) for deterministic tests. - Backpressure handling: when the writable controller's
desiredSizefalls to zero or the in-memory backlog exceedsqueueHighWaterMark, appliesoverflowPolicyand incrementssse.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 insidestart(controller). The producer drainshandle.eventsviafor await, frames each event withformatSseFrame, 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 thesse.connection_duration_mshistogram. It is invoked from both the iterator'sfinallybranch and the stream'scancelcallback so abort and natural end converge on the same code path. - Backpressure is detected by checking
controller.desiredSize; values ofnull(no high-water mark) and positive integers are treated as "not full". Zero or negative trigger the overflow policy. - Heartbeat interval uses native
setIntervalby default; tests inject a fake to drive the timer deterministically (matching the FakeClock pattern used elsewhere ).