first commit
This commit is contained in:
73
docs/api-stack/api/api-plugin-sse/src/plugin/plugin.md
Normal file
73
docs/api-stack/api/api-plugin-sse/src/plugin/plugin.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# plugin
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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).
|
||||
Reference in New Issue
Block a user