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>
@suaveplan/api-plugin-sse - Server-Sent Events plugin for @suaveplan/api. Registers a typed sseService in the host ServiceRegistry, mounts a Hono streamSSE handler with per-connection heartbeat, AbortSignal threading, backpressure, and Last-Event-ID resume from a bounded per-channel ring buffer. Server-tier.
Server-Sent Events plugin for @suaveplan/api. Registers a typed sseService in the host ServiceRegistry, mounts a Hono streaming handler at GET /sse/:channel, and ships with per-connection heartbeat, AbortSignal threading on client disconnect, Last-Event-ID resume from a bounded per-channel ring buffer, and backpressure handling with drop-oldest / drop-newest overflow policies.
Documentation tree
- buffer — Bounded per-channel ring buffer that powers
Last-Event-IDresume. The SSE service appends every broadcast to the buff…- ring-buffer — Bounded per-channel ring buffer that powers
Last-Event-IDresume. The SSE service appends every broadcast to the buff…
- ring-buffer — Bounded per-channel ring buffer that powers
- error-codes — Stable error-code registry and
SseErrorclass for@suaveplan/api-plugin-sse. Every failure path in the plugin raise…- error-codes — Stable error-code registry and
SseErrorclass for@suaveplan/api-plugin-sse. Every failure path in the plugin raise…
- error-codes — Stable error-code registry and
- fanout — Single-process publish/subscribe bus consumed by the SSE service to deliver every broadcast to every active subscriber…
- in-process — Single-process publish/subscribe bus consumed by the SSE service to deliver every broadcast to every active subscriber…
- format — Serialises
SseEventobjects into thetext/event-streamwire format defined by the WHATWG HTML Standard so the SSE h… - parse — Async streaming parser for
text/event-stream. Decodes aReadableStreamDefaultReader<Uint8Array>into a sequence of… - middleware — Hono handler factory that turns a subscription on
SseServiceinto a streaming HTTP response framed in the `text/event…- sse-handler — Hono handler factory that turns a subscription on
SseServiceinto a streaming HTTP response framed in the `text/event…
- sse-handler — Hono handler factory that turns a subscription on
- otel — Provides the canonical OpenTelemetry instrument bundle emitted by the SSE plugin. Counters and a histogram cover the fo…
- instruments — Provides the canonical OpenTelemetry instrument bundle emitted by the SSE plugin. Counters and a histogram cover the fo…
- plugin — Factory producing a typed
PluginDescriptorfor the SSE plugin. Wraps the@suaveplan/api-plugindefinePluginfound…- plugin — Factory producing a typed
PluginDescriptorfor the SSE plugin. Wraps the@suaveplan/api-plugindefinePluginfound…
- plugin — Factory producing a typed
- registry — TypeScript module augmentation declaring the
sseServiceslot on the@suaveplan/api-plugin/service-registry`Service…- service-registry — TypeScript module augmentation declaring the
sseServiceslot on the@suaveplan/api-plugin/service-registry`Service…
- service-registry — TypeScript module augmentation declaring the
- event — Zod schema validating the
SseEventshape accepted bySseService.broadcast. Ensures application payloads cannot bypa… - options — Zod schema validating
SsePluginOptionsat the factory boundary.createSsePlugincalls `SsePluginOptionsSchema.safeP… - services — Core SSE service registered under the
sseServiceslot in the hostServiceRegistry. Owns subscription lifecycle (id…- service — Core SSE service registered under the
sseServiceslot in the hostServiceRegistry. Owns subscription lifecycle (id…
- service — Core SSE service registered under the
Install
bun add @suaveplan/api-plugin-sse
Quick start
import { Hono } from "hono";
import { createPluginRegistry } from "@suaveplan/api-plugin";
import { createSsePlugin } from "@suaveplan/api-plugin-sse";
const app = new Hono();
const registry = createPluginRegistry({ seedServices: { app } });
await registry.register(createSsePlugin({ heartbeatIntervalMs: 15_000 }));
await registry.init();
const sse = registry.getService("sseService");
setInterval(() => {
sse?.broadcast("notifications", { data: { ts: Date.now() } });
}, 1_000);
export default app;
A browser client connects with the native EventSource:
const source = new EventSource("/sse/notifications");
source.addEventListener("message", (evt) => console.log(evt.data));
The 3-way plug-and-play contract
This package follows the canonical API-plugin pattern pioneered by @suaveplan/api-plugin-auth: a plugin is a definePlugin consumer that owns its (1) Zod schemas at the boundary, (2) optional default route registration, and (3) typed ServiceRegistry slot. Mount or compose with any other @suaveplan/api-plugin-* package by passing them to the same plugin registry.
Modules
src/error-codes/error-codes.md— stableSSE_*codes +SseErrorsrc/buffer/ring-buffer.md— bounded per-channel ring buffer forLast-Event-IDresumesrc/fanout/in-process.md— single-process pub/sub bussrc/formatter/format.md— SSE wire-format serializersrc/formatter/parse.md— streaming parser for SSE clientssrc/schemas/options.md— Zod schema for plugin optionssrc/schemas/event.md— Zod schema for eventssrc/otel/instruments.md— OpenTelemetry instrumentssrc/services/service.md—SseServiceregistered undersseServicesrc/middleware/sse-handler.md— Hono streaming handler factorysrc/plugin/plugin.md—createSsePluginfactorysrc/registry/service-registry.md—ServiceRegistryaugmentation
Configuration
| Option | Default | Description |
|---|---|---|
heartbeatIntervalMs |
30_000 |
Heartbeat comment-frame cadence per connection |
routePrefix |
"/sse" |
Mount path for the default route (:channel appended) |
attachDefaultRoute |
true |
Skip route registration to mount manually |
retentionPerChannel |
100 |
Ring-buffer size per channel for resume |
maxConnectionsPerPrincipal |
16 |
Per-principal connection cap |
maxTotalConnections |
10_000 |
Global connection cap |
queueHighWaterMark |
256 |
Per-connection backlog threshold |
overflowPolicy |
"drop-oldest" |
drop-oldest or drop-newest |
idGenerator |
crypto.randomUUID() |
Event id source |
Observability
sse.active_connections— current open connections (UpDownCounter,channelattribute)sse.broadcasts_total{channel}— broadcasts per channelsse.heartbeats_total— heartbeat frames writtensse.message_bytes_total— bytes written to clientssse.queue_overflow_total{policy,code}— events dropped under backpressuresse.connection_duration_ms— per-connection lifetime histogram
License
MIT