3.7 KiB
bun-sse-engine
(no submodules)
Purpose
The bun-sse-engine module exports createBunSseEngine, the factory
the host calls to mount Server-Sent Events on top of Bun.serve. The
factory wires the seven collaborators (options, subscription bus,
tenant cap, event buffer, instruments, handshake pipeline, active
connection registry) into one cohesive engine value. Plugin authors
never touch the internals — they hand the engine to the host's router
which calls engine.acceptRequest(...) on every matching GET request.
Features
- Single factory: validated options, broker- or in-memory event buffer selected automatically, shared subscription bus exposed for the WS engine to plug into.
acceptRequest({ request, path, topic })runs the full handshake pipeline and returns the 200 streamingResponse.publish(envelope)shortcut: append to the buffer + broadcast on the bus in one call — used by plugins that publish in-process.activeConnectionsmap for observability dashboards (size, tenant distribution, oldest open).buildReplayBufferexposed for the conformance harness so the engine's ring-buffer semantics are pinned by the same test path every other engine uses.
Usage: Basic
import { createBunSseEngine } from "@suaveplan/api-sse-bun";
const engine = createBunSseEngine({
authenticate: async () => ({
tenantId: "acme" as never,
principal: null,
}),
});
// ...wire engine.acceptRequest into a Bun.serve handler...
engine.dispose();
Usage: Advanced
import { createBunSseEngine } from "@suaveplan/api-sse-bun";
import Bun from "bun";
const engine = createBunSseEngine({
eventBufferSize: 512,
eventBufferMs: 600_000,
keepAliveCommentMs: 30_000,
maxConnectionsPerTenant: 5_000,
compressionEnabled: true,
retryMs: 4_000,
authenticate: async (req) => {
const tenant = req.headers.get("x-tenant");
if (tenant === null) return { tenantId: null, principal: null };
return { tenantId: tenant 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 });
},
});
API Reference
interface SseAcceptInputs—{ request, path, topic }.interface BunSseEngine—bus,cap,buffer,instruments,options,activeConnections,acceptRequest(inputs),publish(envelope),dispose(),buildReplayBuffer(opts?).function createBunSseEngine(options: BunSseEngineOptions): BunSseEngine.function aggregateConnectionSnapshot(active, plugin): readonly { plugin, path, tenant, value }[]— exported so OTel snapshots can be exercised in tests independent of the global meter provider.
Implementation Notes
The factory does not start any background timers — every long-lived
resource (keep-alive scheduler, gzip stream) is owned by the
handshake pipeline and bound to a single connection. dispose() is a
synchronous best-effort: it cancels the bus and clears the active map
but does not eagerly close every open stream (the HTTP transport
handles that when the underlying socket closes). The
connectionSnapshot for the OTel gauge aggregates active connections
per (path, tenantId) so dashboards can plot capacity per route per
tenant without re-walking the connection set inside the SDK callback.