# @suaveplan/api-sse-bun - Bun.serve concrete engine for @suaveplan/api-sse: text/event-stream framing, Last-Event-Id replay, per-(tenant,topic) ring buffer with broker delegation, gzip streaming, keep-alive comments, per-tenant connection cap, shared subscription bus with WS engine, and the runSseEngineConformance harness bound to createBunSseEngine. `Bun.serve`-backed Server-Sent Events engine for the `@suaveplan/api` plugin system. Implements the abstract `@suaveplan/api-sse` contract and passes `runSseEngineConformance` end-to-end. Lands at api-plugin-foundation umbrella. SSE complements `api-ws-bun`: where WebSocket carries bi-directional duplex traffic, SSE is the simpler server→client stream useful for activity feeds, dashboards, notifications, and any payload pattern that does not need client→server acks. SSE also falls back gracefully on networks that block WebSocket (corporate proxies, restrictive firewalls) so a single broadcast can reach clients via either transport. ## Documentation tree - [compression](./src/compression/compression.md) — The `compression` module owns the engine's optional gzip path. SSE streams compress well — UTF-8 text framed with repea… - [compression](./src/compression/compression.md) — The `compression` module owns the engine's optional gzip path. SSE streams compress well — UTF-8 text framed with repea… - [engine](./src/engine/bun-sse-engine.md) — The `bun-sse-engine` module exports `createBunSseEngine`, the factory the host calls to mount Server-Sent Events on top… - [bun-sse-engine](./src/engine/bun-sse-engine.md) — The `bun-sse-engine` module exports `createBunSseEngine`, the factory the host calls to mount Server-Sent Events on top… - [error-codes](./src/error-codes/error-codes.md) — The `error-codes` module defines the stable `SSE_BUN_*` error registry used across the Bun SSE engine. Every failure mo… - [error-codes](./src/error-codes/error-codes.md) — The `error-codes` module defines the stable `SSE_BUN_*` error registry used across the Bun SSE engine. Every failure mo… - [event-buffer](./src/event-buffer/event-buffer.md) — The `event-buffer` module owns the per-`(tenant, topic)` replay storage the engine consults on `Last-Event-Id` reconnec… - [event-buffer](./src/event-buffer/event-buffer.md) — The `event-buffer` module owns the per-`(tenant, topic)` replay storage the engine consults on `Last-Event-Id` reconnec… - [handshake](./src/handshake/handshake.md) — The `handshake` module implements the 7-step SSE handshake pipeline: topic validation, `Last-Event-Id` parsing, authent… - [handshake](./src/handshake/handshake.md) — The `handshake` module implements the 7-step SSE handshake pipeline: topic validation, `Last-Event-Id` parsing, authent… - [instruments](./src/instruments/instruments.md) — The `instruments` module wires the engine's OpenTelemetry surface. Every metric mandated by design doc §11.7 — connecti… - [instruments](./src/instruments/instruments.md) — The `instruments` module wires the engine's OpenTelemetry surface. Every metric mandated by design doc §11.7 — connecti… - [keep-alive](./src/keep-alive/keep-alive.md) — The `keep-alive` module emits invisible SSE comment lines on a fixed cadence so intermediary proxies (corporate gateway… - [keep-alive](./src/keep-alive/keep-alive.md) — The `keep-alive` module emits invisible SSE comment lines on a fixed cadence so intermediary proxies (corporate gateway… - [last-event-id](./src/last-event-id/last-event-id.md) — The `last-event-id` module parses and validates the `Last-Event-Id` header browsers send automatically on `EventSource`… - [last-event-id](./src/last-event-id/last-event-id.md) — The `last-event-id` module parses and validates the `Last-Event-Id` header browsers send automatically on `EventSource`… - [limits](./src/limits/tenant-cap.md) — The `tenant-cap` module owns the per-tenant SSE connection counter the engine consults during handshake. A tenant that… - [tenant-cap](./src/limits/tenant-cap.md) — The `tenant-cap` module owns the per-tenant SSE connection counter the engine consults during handshake. A tenant that… - [options](./src/options/options.md) — The `options` module declares the public configuration surface accepted by `createBunSseEngine` and exposes `resolveOpt… - [options](./src/options/options.md) — The `options` module declares the public configuration surface accepted by `createBunSseEngine` and exposes `resolveOpt… - [stream](./src/stream/sse-stream.md) — The `sse-stream` module owns the byte-level framing for the SSE wire protocol. Every event the engine pushes to a clien… - [sse-stream](./src/stream/sse-stream.md) — The `sse-stream` module owns the byte-level framing for the SSE wire protocol. Every event the engine pushes to a clien… - [subscription](./src/subscription/subscription-bus.md) — The `subscription-bus` module owns the shared fan-out surface both the WS and SSE engines plug into. The host construct… - [subscription-bus](./src/subscription/subscription-bus.md) — The `subscription-bus` module owns the shared fan-out surface both the WS and SSE engines plug into. The host construct… - [testing](./src/testing/conformance.md) — The `testing` module exposes `runBunSseEngineConformance` — a thin binding that hands `createBunSseEngine` to the abstr… - [conformance](./src/testing/conformance.md) — The `testing` module exposes `runBunSseEngineConformance` — a thin binding that hands `createBunSseEngine` to the abstr… ## Modules - **`engine/bun-sse-engine`** — `createBunSseEngine(options)` factory returning the public engine value; wires options + buffer + bus + cap + instruments + handshake into one cohesive instance. - **`handshake/handshake`** — 7-step pipeline (topic validation, Last-Event-Id parse, auth, cap acquire, compression negotiate, prologue + replay, subscribe + keep-alive). - **`stream/sse-stream`** — WHATWG-compliant byte-level framing for `id`/`event`/`data`/`retry`/`:comment` lines. - **`event-buffer/event-buffer`** — in-memory ring buffer + broker delegate behind one `EventBuffer` surface. - **`subscription/subscription-bus`** — shared topic + tenant bus the WS engine reuses. - **`limits/tenant-cap`** — per-tenant connection counter enforced at handshake time. - **`compression/compression`** — `Accept-Encoding` negotiation + `node:zlib` streaming with sync-flush after every chunk. - **`keep-alive/keep-alive`** — periodic `:keepalive` comment scheduler with injectable timer for `FakeClock` tests. - **`last-event-id/last-event-id`** — header parser enforcing decimal format. - **`instruments/instruments`** — OTel meter bundle: connections gauge, events-sent counter, handshake-duration histogram, replay-hit/miss counter, compression-ratio histogram. - **`error-codes/error-codes`** — 10 stable `SSE_BUN_*` codes + `SseBunError` subclass. - **`testing/conformance`** — `runBunSseEngineConformance()` binding to the abstract `runSseEngineConformance` harness. ## Defaults | Knob | Default | Notes | |------|---------|-------| | `eventBufferSize` | 256 | Per-`(tenant, topic)` ring capacity. | | `eventBufferMs` | 300_000 | 5 min — pinned to WS reconnect window. | | `keepAliveCommentMs` | 15_000 | 15 s — inside the 30 s proxy idle window. | | `maxConnectionsPerTenant` | 1_000 | Per-tenant cap. | | `maxEventBytes` | 65_536 | 64 KiB per encoded frame. | | `compressionEnabled` | `true` | Honours `Accept-Encoding: gzip`. | | `retryMs` | 3_000 | Initial `retry:` directive. | ## Boundary vs `api-ws-bun` SSE is **server→client only**. It does **not** carry client→server messages, acks, or close codes — the client opens a long-lived GET, the server streams text/event-stream until the client disconnects or the server tears down. Use SSE when you need a uni-directional broadcast stream that defaults to working through restrictive intermediaries; use WS when you need duplex semantics, binary frames, or per-message acks. The two engines share the same subscription bus and event-buffer surface so the choice is per-route, not per-deploy. ## Install + use ```bash bun add @suaveplan/api-sse-bun ``` ```ts import { createBunSseEngine } from "@suaveplan/api-sse-bun"; const engine = createBunSseEngine({ authenticate: async (req) => ({ tenantId: (req.headers.get("x-tenant") ?? "anon") as never, principal: null, }), }); Bun.serve({ port: 0, async fetch(request) { const url = new URL(request.url); if (url.pathname === "/sse/audit") { return engine.acceptRequest({ request: { headers: request.headers, url: request.url, method: request.method, }, path: url.pathname, topic: "audit", }); } return new Response("not found", { status: 404 }); }, }); ``` ## Status Stage-7 keystone. Conformance is pinned by the abstract `runSseEngineConformance` harness from `@suaveplan/api-sse`; this package's `runBunSseEngineConformance()` re-runs it bound to `createBunSseEngine`.