4.3 KiB
@suaveplan/api-sse — abstract SSE Connection + Broker + Last-Event-Id resume contract
Package: @suaveplan/api-sse
Status: PROPOSED
Type: new-package
Stage: 1 (Contracts) — see umbrella openspec/changes/api-plugin-foundation/
Why
SSE is the simpler cousin of WebSocket (design §11.6): same subscription model, no client→server channel, no ack semantics. Useful for activity feeds, dashboards, notifications, and as a fallback on networks that block WebSocket. Plugins declare SSE endpoints in the manifest; the host runs the engine, manages connection lifecycle, fans out subscriptions, replays buffered events on Last-Event-Id reconnect. Concrete engines (api-sse-bun) plug behind this contract.
Per design §11.6: "Reuses the same bus + broker; just a different transport." The bus shape is shared with api-ws's SubscriptionBus — this package re-declares the SSE-specific connection/broker surface and the Last-Event-Id replay contract.
What changes
Net-new package. Contract types + the replay-buffer interface + the conformance harness.
- ADDED:
./connection— abstractSseConnection:id,principal,tenantId,lastEventId: string | null,state,send(event: SseEvent),close(). - ADDED:
./event—SseEvent:{ id: string, event?: string, data: string, retry?: number }(standard SSE event field shape). - ADDED:
./broker— abstractSseBroker: same shape asWsBroker(publish/subscribe/dispose) but distinct type so engines can supply different transports. - ADDED:
./replay-buffer— abstractReplayBuffer: per-(tenant, topic)ring buffer (eventBufferSize,eventBufferMs);append(event),since(lastEventId),evictExpired(now). The host'sLast-Event-Idreconnect contract reads from here. - ADDED:
./connection-registry— abstractSseConnectionRegistry: same multi-index shape asConnectionRegistryinapi-wsbut typed forSseConnection. - ADDED:
./capability-id— adds"sse"to theCapabilityIdunion via type-level augmentation. - ADDED:
./error-codes— frozenSSE_CODES:HANDSHAKE_FAILED,PRINCIPAL_EXPIRED,BUFFER_OVERFLOW,LAST_EVENT_ID_UNKNOWN,CONNECTION_CAP_EXCEEDED,BROKER_UNAVAILABLE. - ADDED:
./testing—runSseEngineConformance(factory)reusable harness.
CapabilityId additions
This package contributes "sse" to the CapabilityId union. (No "broadcast-cross-tenant" SSE variant — SSE never accepts client input, so the host owns broadcast tenant scoping uniformly.)
Out of scope
- Concrete
api-sse-bunengine — Stage 7. - WebSocket — separate contract in
api-ws. - The shared
SubscriptionBussemantics — re-used type lives inapi-ws; this package's broker is engine-side.
Source mapping
Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.
| Requirement | Design doc section |
|---|---|
| SSE simpler-cousin shape | §11.6 |
Last-Event-Id replay buffer |
§11.5 (last paragraph) + §11.6 |
OTel surface (sse.connections, sse.events_sent_total) |
§11.7 |
| Worker-tier proxying | §11.8 |
Requirements with no source line
| Requirement | Justification |
|---|---|
runSseEngineConformance harness |
Stage-5 deliverable (§13.4). |
Verification approach
src/connection/connection.spec-conformance.test.ts—expectTypeOf<SseConnection>()declares every documented field.src/event/event.test.ts— SseEvent shape;id/event/data/retryfield handling.src/broker/broker.spec-conformance.test.ts—SseBrokerinterface declared.src/replay-buffer/replay-buffer.test.ts— append, querysince(id), time-based eviction, size cap.src/replay-buffer/last-event-id.test.ts— unknownLast-Event-Id→SSE.LAST_EVENT_ID_UNKNOWNAND fall back to live subscription (no replay).src/connection-registry/connection-registry.test.ts— multi-index lookup; idempotent remove.src/capability-id/capability-id.spec-conformance.test.ts—"sse"inCapabilityId.tests/dep-graph.test.ts— deps:@suaveplan/api-types,@suaveplan/error,@suaveplan/types,@suaveplan/api-ws(for sharedSubscriptionBustype only, peer dep),zod.
Migration impact
None — additive only.
Impact
Affected dependents
- (none) — [x] no action needed (reason: net-new; engines land Stage 7)