Files
skills/docs/api-stack/api/api-plugin-sse/src/plugin/plugin.md
2026-05-29 08:20:56 +01:00

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 the ZodError as cause.
  • 6-step onInit: (1) resolve app from registry (or throw SSE_SERVICE_NOT_AVAILABLE), (2) build ring buffer + in-process fanout, (3) build SseService, (4) register sseService, (5) optionally mount GET <routePrefix>/:channel, (6) defer to descriptor's onClose for teardown.
  • onClose terminates every open connection by calling service.close(); idempotent on second call.
  • attachDefaultRoute: false skips route registration so consumers can mount their own handler under any path / middleware stack.
  • routePrefix: "/" mounts at /:channel so callers can use SSE as their entire app surface when desired.
  • Defaults reflected in descriptor.defaults so 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. options is optional; defaults are applied by SsePluginOptionsSchema.

Implementation Notes

  • The descriptor's onInit captures its service instance in a closure so onClose can call service.close() without re-resolving the ServiceRegistry (the registry instance is the same across phases, but the closure is more robust to lifecycle edge cases).
  • Default idGenerator uses globalThis.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 — SseService itself takes the generator verbatim.
  • routePrefix === "/" is special-cased to avoid a double-slash in the mount path; the plugin uses app.get("/:channel", handler) directly in that case.
  • onClose checks state.service for undefined so it is safe to call before onInit has completed (e.g. when onInit throws and the registry sweeps up).