Server-Sent Events

Lucent provides built-in Server-Sent Events support via a singleton sseManager and an Elysia plugin.

SSE is a good fit when you want one-way realtime updates from the server to the browser without the overhead of a full WebSocket connection.

Setup

Mount LucentSSE.plugin() in your main entry point:

// src/index.ts
import { Elysia } from "elysia";
import { lucent, getLucent, LucentSSE, resolveLucentUser } from "@codesordinatestudio/lucent";
import config from "./lucent.config";

const lucentApi = await getLucent({ config });
const app = new Elysia().use(await lucent(config));

app.use(
  LucentSSE.plugin({
    path: "/sse",
    resolveUser: (request) => resolveLucentUser(request, lucentApi),
  }),
);

app.listen(3000);

If you already have a Lucent Local API instance, the same helper works anywhere:

import { getLucent, LucentSSE, resolveLucentUser } from "@codesordinatestudio/lucent";
import config from "./lucent.config";

const lucent = await getLucent({ config });

app.use(
  LucentSSE.plugin({
    path: "/sse",
    resolveUser: (request) => resolveLucentUser(request, lucent),
  }),
);

Usage in Hooks

Broadcast events from anywhere via the sseManager singleton:

// src/collections/Posts.ts
import { sseManager, defineCollection } from "@codesordinatestudio/lucent";

export const Posts = defineCollection({
  slug: "posts",
  fields: [
    /* ... */
  ],
  hooks: {
    afterCreate: [
      async ({ doc }) => {
        sseManager.broadcastToChannel("posts", {
          event: "post:created",
          data: doc,
        });
      },
    ],
    afterUpdate: [
      async ({ doc }) => {
        sseManager.broadcastToChannel("posts", {
          event: "post:updated",
          data: doc,
        });
      },
    ],
  },
});

Manager API

sseManager is a singleton available throughout your app. You can also access it via LucentSSE.manager.

Channels

MethodDescription
subscribe(id, channel)Add a connection to a channel
unsubscribe(id, channel)Remove a connection from a channel
getChannelMembers(channel)Returns connection IDs subscribed to channel
getConnectionChannels(id)Returns all subscribed channels for a client
listChannels()Lists all active channels

Sending Messages

MethodDescription
sendTo(id, payload)Send a message to a single subscriber
broadcastToChannel(channel, payload)Send to everyone on a channel
broadcastAll(payload, options?)Send to every connected SSE client

Metadata

MethodDescription
setMeta(id, key, value)Store arbitrary metadata on a connection
getMeta(id, key)Read metadata from a connection

Stats

sseManager.getStats();
// { connections: 12, channels: { posts: 8, notifications: 12 } }

Client-Side Usage

const stream = new EventSource("/sse?channels=posts,notifications", {
  withCredentials: true,
});

stream.addEventListener("connected", (event) => {
  console.log("connected", JSON.parse(event.data));
});

stream.addEventListener("post:created", (event) => {
  const post = JSON.parse(event.data);
  console.log("new post", post);
});

stream.addEventListener("ping", () => {
  // heartbeat event
});

Authentication

resolveLucentUser() lets SSE reuse Lucent's existing auth stack:

  • Authorization: Bearer <token>
  • Lucent JWT cookies
  • Lucent session cookies
  • x-api-key

That makes it the easiest way to authenticate both SSE and custom HTTP handlers without rewriting token parsing.

import { LucentSSE, getLucent, resolveLucentUser } from "@codesordinatestudio/lucent";
import config from "./lucent.config";

const lucent = await getLucent({ config });

app.use(
  LucentSSE.plugin({
    path: "/sse",
    resolveUser: (request) => resolveLucentUser(request, lucent),
    onConnect: ({ user }) => !!user,
    onSubscribe: ({ user, channel }) => {
      if (!user) return false;
      if (channel.startsWith("private-") && user.role !== "admin") return false;
      return true;
    },
  }),
);

Authorization Hooks

Use onConnect to allow or reject the initial stream, and onSubscribe to control channel access.

LucentSSE.plugin({
  path: "/sse",
  resolveUser: (request) => resolveLucentUser(request, lucent),

  onConnect: ({ user }) => {
    if (!user) return "Authentication required";
    return true;
  },

  onSubscribe: ({ user, channel }) => {
    if (!user) return false;
    if (channel === "admin" && user.role !== "admin") return false;
    return true;
  },
});

Return values:

  • true to allow
  • false to deny
  • a string to deny with a custom error message

Heartbeat

LucentSSE sends a heartbeat event by default every 30 seconds to keep the stream active through proxies and load balancers.

LucentSSE.plugin({
  path: "/sse",
  heartbeat: { interval: 60_000, event: "keepalive", data: { ok: true } },
});

LucentSSE.plugin({
  path: "/sse",
  heartbeat: false,
});

Route Behavior

The SSE endpoint accepts channels in either form:

/sse?channel=posts&channel=notifications
/sse?channels=posts,notifications

On connect, Lucent sends a connected event with the generated connection id, subscribed channels, and whether the request was authenticated.