Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.1 KiB
@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 — The
compressionmodule owns the engine's optional gzip path. SSE streams compress well — UTF-8 text framed with repea…- compression — The
compressionmodule owns the engine's optional gzip path. SSE streams compress well — UTF-8 text framed with repea…
- compression — The
- engine — The
bun-sse-enginemodule exportscreateBunSseEngine, the factory the host calls to mount Server-Sent Events on top…- bun-sse-engine — The
bun-sse-enginemodule exportscreateBunSseEngine, the factory the host calls to mount Server-Sent Events on top…
- bun-sse-engine — The
- error-codes — The
error-codesmodule defines the stableSSE_BUN_*error registry used across the Bun SSE engine. Every failure mo…- error-codes — The
error-codesmodule defines the stableSSE_BUN_*error registry used across the Bun SSE engine. Every failure mo…
- error-codes — The
- event-buffer — The
event-buffermodule owns the per-(tenant, topic)replay storage the engine consults onLast-Event-Idreconnec…- event-buffer — The
event-buffermodule owns the per-(tenant, topic)replay storage the engine consults onLast-Event-Idreconnec…
- event-buffer — The
- handshake — The
handshakemodule implements the 7-step SSE handshake pipeline: topic validation,Last-Event-Idparsing, authent…- handshake — The
handshakemodule implements the 7-step SSE handshake pipeline: topic validation,Last-Event-Idparsing, authent…
- handshake — The
- instruments — The
instrumentsmodule wires the engine's OpenTelemetry surface. Every metric mandated by design doc §11.7 — connecti…- instruments — The
instrumentsmodule wires the engine's OpenTelemetry surface. Every metric mandated by design doc §11.7 — connecti…
- instruments — The
- keep-alive — The
keep-alivemodule emits invisible SSE comment lines on a fixed cadence so intermediary proxies (corporate gateway…- keep-alive — The
keep-alivemodule emits invisible SSE comment lines on a fixed cadence so intermediary proxies (corporate gateway…
- keep-alive — The
- last-event-id — The
last-event-idmodule parses and validates theLast-Event-Idheader browsers send automatically onEventSource…- last-event-id — The
last-event-idmodule parses and validates theLast-Event-Idheader browsers send automatically onEventSource…
- last-event-id — The
- limits — The
tenant-capmodule owns the per-tenant SSE connection counter the engine consults during handshake. A tenant that…- tenant-cap — The
tenant-capmodule owns the per-tenant SSE connection counter the engine consults during handshake. A tenant that…
- tenant-cap — The
- options — The
optionsmodule declares the public configuration surface accepted bycreateBunSseEngineand exposes `resolveOpt…- options — The
optionsmodule declares the public configuration surface accepted bycreateBunSseEngineand exposes `resolveOpt…
- options — The
- stream — The
sse-streammodule owns the byte-level framing for the SSE wire protocol. Every event the engine pushes to a clien…- sse-stream — The
sse-streammodule owns the byte-level framing for the SSE wire protocol. Every event the engine pushes to a clien…
- sse-stream — The
- subscription — The
subscription-busmodule owns the shared fan-out surface both the WS and SSE engines plug into. The host construct…- subscription-bus — The
subscription-busmodule owns the shared fan-out surface both the WS and SSE engines plug into. The host construct…
- subscription-bus — The
- testing — The
testingmodule exposesrunBunSseEngineConformance— a thin binding that handscreateBunSseEngineto the abstr…- conformance — The
testingmodule exposesrunBunSseEngineConformance— a thin binding that handscreateBunSseEngineto the abstr…
- conformance — The
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 forid/event/data/retry/:commentlines.event-buffer/event-buffer— in-memory ring buffer + broker delegate behind oneEventBuffersurface.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-Encodingnegotiation +node:zlibstreaming with sync-flush after every chunk.keep-alive/keep-alive— periodic:keepalivecomment scheduler with injectable timer forFakeClocktests.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 stableSSE_BUN_*codes +SseBunErrorsubclass.testing/conformance—runBunSseEngineConformance()binding to the abstractrunSseEngineConformanceharness.
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
bun add @suaveplan/api-sse-bun
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.