Connection multiplexing
A page with many live hooks (useLiveList, useLiveAggregate) would normally open one SSE connection per subscription. Browsers cap concurrent HTTP/1.1 connections per host at ~6, so a dashboard with a dozen live regions stalls. Covara solves this by multiplexing: all live subscriptions on a client share a single SSE stream, and the server fans every subscription's events down that one connection.
It is on by default and invisible — you don't change any subscription code, client or server. useLiveList("/api/todos") and repository.subscribe(...) work exactly as before; under the hood they become channels of one shared stream.
How it works
- The client opens one stream at
GET /__covara/stream; the server replies with areadyevent carrying a connection id (cid). - Each subscription becomes a channel: the client sends a
POST /__covara/stream/:cid/subscribecontrol message (resource path, filter, include, resumeFrom, aggregate params, …). The server starts a normal subscription bound to the shared stream. - Events are framed with their channel id (
event: mux,data: { c, n, e }) and demultiplexed on the client back into the exactconnected/message/aggregate/errorevents each subscription expects. - Closing a subscription sends an
unsubscribe; closing the stream tears down every channel.
Control POSTs and the stream are separate HTTP requests. The control request only validates the subscription (auth scope, filter, limits). Errors there come back as its HTTP status. The channel itself is always started and stopped inside the stream's own request. This matters on Cloudflare Workers, where a stream and the sockets it relies on belong to the request that opened them. Anything a short-lived POST opened would die with the POST, so the channel would accept the subscription and then never deliver a change. On Workers the server queues the start and wakes the stream's request through its own Durable Object socket. The POST returns as soon as the work is queued, and a later start failure arrives as the channel's error event. On Node the channel starts inline.
Each channel keeps its own auth scope, filter, resumeFrom/catchup, and per-user/IP subscription limits — multiplexing changes only the transport, not the semantics. A subscription can never see rows another channel is scoped to.
Configuration
On by default. Disable or tune it per side:
// Server — disable the endpoint, or tune the shared stream.
createCovara({
multiplex: false, // or: { maxChannelsPerConnection: 200, heartbeatMs: 20000, maxQueueBytes: 262144 }
});
// Client — opt out of sharing (each subscription uses its own connection).
getOrCreateClient({ baseUrl: location.origin, multiplex: false });
Fallback
Multiplexing degrades gracefully. If the shared stream can't be used, each subscription transparently falls back to its own GET /subscribe connection — identical to the pre-multiplex behavior. Fallback happens when:
- the server doesn't expose the endpoint (older server, or
multiplex: false) — detected whenGET /__covara/streamreturns 404; - the runtime has no
fetch/EventSource; - a control
POSTcan't reach the process holding the stream.
That last case is the multi-isolate caveat: true single-connection multiplexing requires the control POST to land on the same process as the stream. On Node / startServer (a single process) that always holds. On multi-isolate deployments (e.g. Cloudflare Workers) a control POST may hit a different isolate; the server answers 409 stream_not_found, and on the first such response the client stops trying to multiplex for that connection and switches — stickily — to per-subscription GET /subscribe connections for all its subscriptions. It does not keep reopening the shared stream (which would loop forever and deliver nothing), so realtime stays reliable, just not connection-shared. (Edge deployments typically serve over HTTP/2 to the client anyway, which multiplexes connections at the transport layer, so little is lost.)
The shared stream endpoint also flushes its ready frame immediately, without waiting on any store read — so a slow KV/Durable Object can't leave the stream hung at zero bytes.
Reconnection
The shared stream owns reconnection. If it drops, every channel's manager reconnects and re-subscribes with its current resumeFrom, so the changelog redelivers anything missed — the same reliable, resumable delivery as a single subscription. A slow shared consumer triggers the standard backpressure policy, which resets the stream; the client reconnects and every channel catches up.
The shared connection is defensive against stuck streams so it never leaves the UI silently stale:
- Connect timeout — if the stream doesn't become ready within ~10s it's aborted and retried; after a few failures the affected subscriptions transparently fall back to their own per-subscription connections.
- Stall watchdog — every event and heartbeat resets a liveness timer; if the stream goes silent past the heartbeat window (~50s) it's treated as dead and reconnected, catching half-open connections a plain read would never notice.
- Indefinite retry — subscriptions reconnect with capped exponential backoff and jitter and keep trying (rather than giving up after a fixed count), so they recover on their own once the server or network comes back.
Related
- Subscriptions · Aggregate subscriptions · Changelog
- Invariants: contracts/subscriptions