3.7 KiB
3.7 KiB
plugin
(no submodules)
Purpose
Factory producing a typed PluginDescriptor for the SSE plugin. Wraps the @suaveplan/api-plugin definePlugin foundation with Zod-validated options at the boundary and a 6-step onInit lifecycle that resolves the host's Hono app, constructs the ring buffer / fanout / service triple, registers sseService in the host ServiceRegistry, and mounts the default streaming route. Mirrors the 3-way plug-and-play contract pioneered by @suaveplan/api-plugin-auth so consumers compose any combination of @suaveplan/api-plugin-* packages with a single definePlugin array in their app boot.
Features
- Zod-validated options at the factory boundary — invalid input throws
SseError(SSE_INVALID_INPUT)carrying theZodErrorascause. - 6-step
onInit: (1) resolveappfrom registry (or throwSSE_SERVICE_NOT_AVAILABLE), (2) build ring buffer + in-process fanout, (3) buildSseService, (4) registersseService, (5) optionally mountGET <routePrefix>/:channel, (6) defer to descriptor'sonClosefor teardown. onCloseterminates every open connection by callingservice.close(); idempotent on second call.attachDefaultRoute: falseskips route registration so consumers can mount their own handler under any path / middleware stack.routePrefix: "/"mounts at/:channelso callers can use SSE as their entire app surface when desired.- Defaults reflected in
descriptor.defaultsso a downstream introspection tool sees every resolved value without re-parsing.
Usage: Basic
import { createSsePlugin } from "@suaveplan/api-plugin-sse/plugin/plugin";
const plugin = createSsePlugin({
heartbeatIntervalMs: 15_000,
maxConnectionsPerPrincipal: 8,
});
// Pass to your @suaveplan/api registry along with other plugins.
Usage: Advanced
import { createSsePlugin } from "@suaveplan/api-plugin-sse/plugin/plugin";
import { createPluginRegistry } from "@suaveplan/api-plugin";
import { Hono } from "hono";
const app = new Hono();
const registry = createPluginRegistry({
seedServices: { app },
});
await registry.register(
createSsePlugin({
heartbeatIntervalMs: 15_000,
retentionPerChannel: 500,
maxConnectionsPerPrincipal: 4,
maxTotalConnections: 5_000,
queueHighWaterMark: 512,
overflowPolicy: "drop-newest",
attachDefaultRoute: false, // mount manually under /v1/events/:channel
})
);
await registry.init();
const sse = registry.getService("sseService");
sse?.broadcast("welcome", { data: { message: "boot complete" } });
// Graceful shutdown closes every open SSE connection.
process.on("SIGTERM", () => registry.shutdown());
API Reference
createSsePlugin(options?: SsePluginOptions): PluginDescriptor<SsePluginConfig>— factory.optionsis optional; defaults are applied bySsePluginOptionsSchema.
Implementation Notes
- The descriptor's
onInitcaptures itsserviceinstance in a closure soonClosecan callservice.close()without re-resolving theServiceRegistry(the registry instance is the same across phases, but the closure is more robust to lifecycle edge cases). - Default
idGeneratorusesglobalThis.crypto.randomUUID()when available; falls back to a timestamp + random suffix on legacy runtimes. The plugin layer is the canonical place to apply this default —SseServiceitself takes the generator verbatim. routePrefix === "/"is special-cased to avoid a double-slash in the mount path; the plugin usesapp.get("/:channel", handler)directly in that case.onClosechecksstate.serviceforundefinedso it is safe to call beforeonInithas completed (e.g. whenonInitthrows and the registry sweeps up).