143 lines
9.1 KiB
Markdown
143 lines
9.1 KiB
Markdown
# @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.
|
|
|
|
<!-- begin:auto-docs -->
|
|
## 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…
|
|
<!-- end:auto-docs -->
|
|
|
|
## 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`.
|
|
|