7.8 KiB
OpenSpec Change: enterprise-events-transport
- Change ID: enterprise-events-transport
- Package:
@suaveplan/events-transport - Category: universal
- Target path:
universal/events/events-transport - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/events-transport.md
Summary
Deliver @suaveplan/events-transport on the genesis branch from scratch at enterprise quality. The package provides cross-context event bridges for @suaveplan/events: bridges dispatches across MessageChannel (worker ↔ main thread) and BroadcastChannel (tab ↔ tab), with structured-clone-safe payload validation, W3C trace context propagation in envelopes, documented delivery guarantees per transport, idempotency via nonce dedup windows, and AbortSignal cancellation. Attaches to the event bus via the public interceptor API.
Current-state audit (genesis)
The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/events (workspace:^) |
Event bus and interceptor API |
@suaveplan/error (workspace:^) |
Base class for EventsTransportError; defineCodes for EVENTS_TRANSPORT.* codes |
@suaveplan/types (workspace:^) |
LoggerLike, Clock, Rng, IdGenerator injection interfaces |
zod (catalog:forms) |
Zod schemas for transport envelope and option types |
@opentelemetry/api (catalog:observability) |
Cross-boundary span linking, context propagation |
tslib (catalog:build) |
Runtime helpers |
Dev deps: vitest, @vitest/coverage-istanbul, fast-check (all catalog:testing).
Public API surface
Transport interface
interface EventTransport {
readonly name: string
readonly deliveryGuarantee: DeliveryGuarantee
connect(bus: EventBusLike, options?: ConnectOptions): TransportHandle
}
type DeliveryGuarantee = "at-most-once" | "at-least-once"
interface ConnectOptions {
eventFilter?: (name: string) => boolean
signal?: AbortSignal
logger?: LoggerLike
clock?: Clock
rng?: Rng
idGenerator?: IdGenerator
}
interface TransportHandle {
disconnect(): void
readonly stats: TransportStats
}
interface TransportStats {
sent: number
received: number
dropped: number
duplicatesFiltered: number
}
MessageChannel transport
// at-least-once delivery with ack protocol
function createMessageChannelTransport(options: MessageChannelTransportOptions): EventTransport
interface MessageChannelTransportOptions {
channel: MessageChannel
ackTimeoutMs?: number // default: 5000
maxRetries?: number // default: 3
nonceWindowSize?: number // dedup window for received nonces; default: 1000
}
BroadcastChannel transport
// at-most-once delivery (no ack)
function createBroadcastChannelTransport(options: BroadcastChannelTransportOptions): EventTransport
interface BroadcastChannelTransportOptions {
channelName: string
nonceWindowSize?: number // dedup window for received nonces; default: 1000
origin?: string // filter by origin; default: "*"
}
Transport envelope (wire format)
interface TransportEnvelope {
v: 1 // envelope version
event: string // event name
payload: unknown // structured-clone-safe payload
nonce: string // unique per-send dedup key
origin: string // sender identifier
ts: number // sender timestamp (ms since epoch)
traceContext?: Record<string, string> // W3C trace context headers
}
const TransportEnvelopeSchema: ZodSchema
Structured-clone safety check
// Returns true if the value can be transmitted via structured clone
function isStructuredCloneable(value: unknown): boolean
// Throws EventsTransportError if value is not structured-clone-safe
function assertStructuredCloneable(value: unknown): void
Error handling
class EventsTransportError extends SuaveplanError<EventsTransportErrorCode>
type EventsTransportErrorCode =
| "EVENTS_TRANSPORT_NOT_CLONEABLE"
| "EVENTS_TRANSPORT_ACK_TIMEOUT"
| "EVENTS_TRANSPORT_CONNECT_FAILED"
| "EVENTS_TRANSPORT_DISCONNECTED"
| "EVENTS_TRANSPORT_ENVELOPE_INVALID"
function registerEventsTransportErrorCodes(): void
Zod schemas
const MessageChannelTransportOptionsSchema: ZodSchema
const BroadcastChannelTransportOptionsSchema: ZodSchema
const ConnectOptionsSchema: ZodSchema
New additions for genesis
1. W3C Trace Context in envelopes
When @suaveplan/events-otel is in use on the sending side, serializeTraceContext() is called and the result placed in envelope.traceContext. On the receiving side, deserializeTraceContext(envelope.traceContext) restores the context before re-dispatching, linking the spans across the transport boundary.
2. Nonce dedup window
Both transports maintain a sliding window of received nonces (LRU, configurable nonceWindowSize). Duplicate nonces within the window are silently dropped and counted in TransportStats.duplicatesFiltered.
3. Ack protocol for MessageChannel
The MessageChannelTransport sender waits for an {ack: nonce} reply message before incrementing the retry counter. If the ack doesn't arrive within ackTimeoutMs, the envelope is re-sent (up to maxRetries). The receiver sends the ack immediately after re-dispatching.
4. OTel span linking
On the sending side, a span events_transport.send is created linked to the current active span. On the receiving side, a span events_transport.receive is created with a SpanLink back to the sender span (via the propagated trace context), enabling cross-process Gantt chart visualisation.
5. Property-based tests (fast-check)
- Envelope round-trip: any structured-clone-safe payload survives
JSON.stringify→JSON.parseround-trip unchanged - Nonce dedup: the same nonce delivered N times to a receiver results in exactly one re-dispatch
BroadcastChannelat-most-once: a dropped message is never delivered after disconnectMessageChannelretry: message delivered after first ack timeout still reaches receiver exactly once
File layout
universal/events/events-transport/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
core/
transport-interface/
transport-interface.ts
transport-interface.md
transport-interface.types.ts
index.ts
envelope/
envelope.ts
envelope.md
envelope.types.ts
envelope.test.ts
index.ts
nonce-window/
nonce-window.ts
nonce-window.md
nonce-window.test.ts
index.ts
transports/
message-channel/
message-channel-transport.ts
message-channel-transport.md
message-channel-transport.types.ts
message-channel-transport.test.ts
index.ts
broadcast-channel/
broadcast-channel-transport.ts
broadcast-channel-transport.md
broadcast-channel-transport.types.ts
broadcast-channel-transport.test.ts
index.ts
utils/
structured-clone-check.ts
structured-clone-check.md
structured-clone-check.test.ts
tests/
integration.test.ts
property.test.ts
Impact
Downstream packages: any application using Web Workers or multi-tab coordination via BroadcastChannel that needs @suaveplan/events to cross the context boundary.
Workspace dep prerequisites: @suaveplan/events, @suaveplan/error, @suaveplan/types must be published to Verdaccio. @suaveplan/events-otel is an optional peer for trace context injection.
Breaking changes: none (new package).