← All @molecule/* packages · App templates
@molecule/api-realtime-sseProvider bond · realtime · API (Node) · v1.0.1 · Apache-2.0
Server-Sent Events (SSE) realtime provider for molecule.dev
npm install @molecule/api-realtime-ssenpm · Source on GitHub · Implements @molecule/api-realtime
@molecule/api-realtime-sse is a provider bond on the API (Node) side: it implements the realtime core interface (@molecule/api-realtime) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import http from 'node:http'
import { createProvider } from '@molecule/api-realtime-sse'
import { setProvider } from '@molecule/api-realtime'
// Attach the SSE endpoints to the API's own HTTP server so realtime
// shares the API port (a standalone `port` binds a SECOND port that a
// containerized/proxied deployment usually does not expose — and the
// default port 3000 collides with the typical API port).
const server = http.createServer()
const sseProvider = createProvider({ httpServer: server, path: '/sse' })
// Bond it as the active realtime provider
setProvider(sseProvider)
server.listen(3000)
// When the HTTP server doesn't exist yet at wiring time (e.g. a server
// factory that creates it later), defer instead of passing httpServer:
// const sseProvider = createProvider({ deferAttach: true, path: '/sse' })
// setProvider(sseProvider)
// // once the server exists (e.g. a server-created hook):
// sseProvider.attachHttpServer(server)Works with: @molecule/api-bond, @molecule/api-realtime
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
SSE (Server-Sent Events) realtime provider for molecule.dev.
Provides a {@link RealtimeProvider} implementation using native Node.js Server-Sent Events for server-to-client push, with HTTP POST for client-to-server messages.
GET {path} — the first stream event is connected { clientId }; keep that
clientId, every POST {path} body must include it (400 without it). Handshake
auth for join guards = every subscribe query param except room/rooms, plus
the Authorization header (as auth.authorization); auth is captured once at
subscribe and reused for later POSTed joins. Join rooms at subscribe time with
?room=a&room=b or later via POST {clientId, event: 'molecule:join', data: {room}} — the verdict (molecule:joined/molecule:join-denied) arrives
on the STREAM; the POST itself acks 202. molecule:room-send into a room the
client hasn't joined is rejected 403.broadcast() throws Room "<id>" does not exist when the room matches no
managed room and no protocol room — and a protocol room ceases to exist when its
last member leaves/disconnects. Guard server-side pushes accordingly (the
-socketio bond silently no-ops instead).import http from 'node:http'
import { createProvider } from '@molecule/api-realtime-sse'
import { setProvider } from '@molecule/api-realtime'
// Attach the SSE endpoints to the API's own HTTP server so realtime
// shares the API port (a standalone `port` binds a SECOND port that a
// containerized/proxied deployment usually does not expose — and the
// default port 3000 collides with the typical API port).
const server = http.createServer()
const sseProvider = createProvider({ httpServer: server, path: '/sse' })
// Bond it as the active realtime provider
setProvider(sseProvider)
server.listen(3000)
// When the HTTP server doesn't exist yet at wiring time (e.g. a server
// factory that creates it later), defer instead of passing httpServer:
// const sseProvider = createProvider({ deferAttach: true, path: '/sse' })
// setProvider(sseProvider)
// // once the server exists (e.g. a server-created hook):
// sseProvider.attachHttpServer(server)
provider
npm install @molecule/api-realtime-sse @molecule/api-bond @molecule/api-realtime
SseRealtimeConfigConfiguration options for the SSE realtime provider.
SSE (Server-Sent Events) provides server-to-client push. Client-to-server messages are accepted via HTTP POST on the same path (configurable via {@link SseRealtimeConfig.path | path}).
interface SseRealtimeConfig {
/**
* An existing Node.js HTTP server to attach the SSE routes to. This is
* itself an explicit attach step — when given, the routes are attached
* immediately (or a standalone server is created on {@link port}
* immediately if omitted, per the rules below), regardless of
* `deferAttach`.
*/
httpServer?: HttpServer
/**
* Port to listen on for a standalone server. Passing this **explicitly**
* is an explicit instruction to bind a standalone server immediately (no
* `httpServer` needed) — env-aware for the actual value: `SSE_PORT` if
* set, else `PORT + 1000` (one above the API convention), else `3000`, so
* multiple apps can run side-by-side.
*
* **Omitting `port` (along with `httpServer` and `deferAttach`) does NOT
* bind a default port** — creating a provider must never bind a port as a
* side effect. A zero-config `createProvider()` behaves exactly like
* `{ deferAttach: true }` instead: it waits for
* {@link RealtimeProvider.attachHttpServer} and logs an info line naming
* the bond so the omission is visible.
*/
port?: number
/**
* Defer attaching the SSE routes until {@link RealtimeProvider.attachHttpServer}
* is called, instead of binding a standalone HTTP server eagerly at
* creation. Used by the server factory so SSE attaches to the API's HTTP
* server (shared port) once it exists — avoiding a standalone port a
* sandbox/proxy may not expose and that collides with the API's own port
* by default. Ignored when `httpServer` is already provided (that is
* itself an explicit attach — see the module `@remarks`). Zero-config
* (no `port`, no `httpServer`, no `deferAttach`) already behaves as if
* this were `true` — set it explicitly for readability/intent at the call
* site, not because it changes behavior over omitting it.
*
* @defaultValue false
*/
deferAttach?: boolean
/**
* Base path for SSE endpoints.
*
* - `GET {path}` — SSE event stream
* - `POST {path}` — client-to-server messages (`{ clientId, event, data, room? }`;
* `clientId` is REQUIRED — it arrives in the stream's initial `connected` event)
*
* @defaultValue '/sse'
*/
path?: string
/**
* Interval (ms) between keep-alive comment lines sent to prevent
* intermediary proxies from closing idle connections.
*
* @defaultValue 30000
*/
keepAliveInterval?: number
/**
* Custom headers to include in the SSE response.
*/
headers?: Record<string, string>
/**
* CORS origin for the SSE endpoint (`Access-Control-Allow-Origin`). Set to
* `'*'` to allow all origins, or a single origin string for a locked-down
* deployment.
*
* When omitted: outside production, defaults to `'*'` (dev convenience —
* no cross-origin risk to a real user). **In production** (`NODE_ENV ===
* 'production'`), defaults instead to `process.env.APP_ORIGIN ??
* process.env.SITE_ORIGIN` (the same env vars `@molecule/api-middleware-cors-express`
* reads for its allowlist) when either is set, so the realtime endpoints
* are NOT exposed cross-origin by default in production. If production AND
* neither is set, falls back to `'*'` but logs an actionable warning (auth
* via query params/`Authorization` header still applies, and
* credentialed-CORS rules still protect httpOnly-cookie flows, so exposure
* is limited — but should still be closed by setting this explicitly).
*
* @defaultValue '*' outside production; `APP_ORIGIN`/`SITE_ORIGIN` in production when set
*/
corsOrigin?: string
}
createProvider(config)Creates an SSE-backed {@link RealtimeProvider}.
function createProvider(config?: SseRealtimeConfig): RealtimeProvider
config — SSE provider configuration.Returns: A fully initialised RealtimeProvider backed by Server-Sent Events.
Implements @molecule/api-realtime interface.
Peer dependencies:
@molecule/api-bond ^1.0.1@molecule/api-realtime ^1.0.1@molecule/api-bond
@molecule/api-realtime
createProvider() with NO port, NO httpServer, and NO
deferAttach does NOT bind anything — creating a provider must never
bind a port as a side effect. It behaves exactly like { deferAttach: true } (waits for attachHttpServer(server)), logging an info line so
the omission is visible instead of silent. An explicit port still
binds a standalone server immediately (unchanged, back-compat for
existing standalone callers) — it just no longer happens by accident. A
standalone bind failure (e.g. EADDRINUSE) is logged via the bonded
logger naming this bond and the port, instead of crashing the process
with an unattributed error.
corsOrigin defaults to '*' outside production. In production
(NODE_ENV === 'production') it instead defaults to
process.env.APP_ORIGIN ?? process.env.SITE_ORIGIN when either is set,
so the realtime stream/message endpoints aren't exposed cross-origin by
default; only when neither is configured does it fall back to '*',
logging a warning naming the risk. Set corsOrigin explicitly to
override either way.
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
onJoinRequest was never registered — an integration bug.