W WRNexusJS
Realtime · Package reference

@wrnexus/realtime

Rooms, presence, messaging, history, streams, and UI components.

v0.8.7Private registryRealtime

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/realtime@0.8.7

Request preview access. Never put registry tokens in source control.

Typed rooms, secure message envelopes, browser room helpers, presence utilities, and complete realtime UI blocks for WRNexusJS.

Server rooms

import { defineRoom } from "@wrnexus/realtime";

export default defineRoom("support", {
  async authorize(context) {
    return Boolean(context.user);
  },
  message(client, message) {
    client.broadcast(message);
  },
});

The package re-exports the hardened realtime registry from @wrnexus/core, including authentication, origin checks, quotas, message-size limits, schema validation hooks, and room authorization.

Messages and browser helpers

import {
  createRealtimeMessage,
  createPresenceEvent,
  createTypingEvent,
  connectRoom,
  sendRoomMessage,
} from "@wrnexus/realtime";

const room = connectRoom("support", { query: { ticket: "T-100" } });
sendRoomMessage(
  room,
  createRealtimeMessage({
    type: "message",
    room: "support",
    data: { text: "Hello" },
  }),
);

Message IDs use Web Crypto. A runtime without secure randomness must provide an explicit message ID.

Components

Enable realtimePlugin() and use:

  • <RealtimeRoom />
  • <RealtimeMessageBubble />
  • <MessageComposer />
  • <RoomStatus />
  • <RoomMeta />
  • <PresenceList />
  • <TypingIndicator />

These package-owned blocks compose existing @wrnexus/ui components such as Card, Alert, Avatar, Badge, Button, Input, and ChatBubble.

Incoming messages can be bounded and constrained:

const message = parseRealtimeMessage(rawMessage, {
  maxBytes: 64 * 1024,
  maxDepth: 12,
  allowedTypes: ["message", "typing", "presence"],
  room: "support",
});

The parser rejects oversized payloads, circular/unsupported values, unsafe object keys, invalid message types, invalid room names, and room mismatches.

Replay, acknowledgements, SSE, and monitoring

createRealtimeHistory() keeps a bounded sequenced log per room. Clients acknowledge a sequence and resume(room, clientId) returns only missed events. A snapshot exposes room, message, acknowledgement, and sequence counts for monitoring without exposing payloads.

const history = createRealtimeHistory({ limitPerRoom: 100 });
const entry = history.publish("support", message);
history.acknowledge("support", clientId, entry.sequence);
const missed = history.resume("support", clientId);

realtimeSseResponse(stream, signal) converts the same sequenced envelope into standards-based Server-Sent Events with event IDs, event types, JSON data, cancellation, and no-cache headers.

Complete TypeScript API

Generated from the exact installed package declarations.

export { RawSocket, RealtimeBridge, RealtimeBus, RealtimeConnectMeta, RealtimeEnvelope, RealtimeHandler, RealtimeRegistry, RealtimeRegistryOptions, RealtimeSecurityOptions, RealtimeSocket, Room, RoomAuthInfo, RoomClient, RoomDefinition, RoomHandlers, Target, bridgeRealtime, createRealtimeRegistry, defineRoom, isRoomDefinition } from '@wrnexus/core';
export { RealtimePluginOptions, realtimeComponentsDir, default as realtimePlugin } from './plugin.js';
import '@wrnexus/plugin';

type RealtimeMessageType = string;
interface RealtimeMessage<T = unknown> {
    id: string;
    type: RealtimeMessageType;
    room?: string;
    senderId?: string;
    senderName?: string;
    sentAt: string;
    data: T;
    meta?: Record<string, string | number | boolean | null>;
}
interface CreateRealtimeMessageOptions<T> {
    id?: string;
    type: string;
    room?: string;
    senderId?: string;
    senderName?: string;
    sentAt?: string | Date;
    data: T;
    meta?: Record<string, string | number | boolean | null>;
}
interface ParseRealtimeMessageOptions {
    maxBytes?: number;
    maxDepth?: number;
    allowedTypes?: readonly string[];
    room?: string;
}
interface RealtimePresence {
    userId: string;
    name?: string;
    avatar?: string;
    status?: "online" | "away" | "busy" | "offline";
    joinedAt?: string;
    lastSeenAt?: string;
    meta?: Record<string, string | number | boolean | null>;
}
interface RealtimeRoomMeta {
    id: string;
    name: string;
    description?: string;
    memberCount?: number;
    onlineCount?: number;
    createdAt?: string;
    updatedAt?: string;
    private?: boolean;
    tags?: string[];
}
declare function assertRealtimeRoomName(name: string): string;
declare function createRealtimeMessage<T>(options: CreateRealtimeMessageOptions<T>): RealtimeMessage<T>;
declare function isRealtimeMessage(value: unknown): value is RealtimeMessage;
declare function parseRealtimeMessage<T = unknown>(value: string | unknown, options?: ParseRealtimeMessageOptions): RealtimeMessage<T>;
declare function createPresenceEvent(action: "join" | "leave" | "update", presence: RealtimePresence, room?: string): RealtimeMessage<{
    action: "join" | "leave" | "update";
    presence: RealtimePresence;
}>;
declare function createTypingEvent(userId: string, typing: boolean, options?: {
    room?: string;
    name?: string;
}): RealtimeMessage<{
    userId: string;
    name?: string;
    typing: boolean;
}>;
declare function roomMemberSummary(members: readonly RealtimePresence[]): {
    total: number;
    online: number;
    away: number;
    busy: number;
};

interface BrowserRoomConnection {
    readonly name: string;
    send(message: unknown): BrowserRoomConnection;
    on(type: string | ((message: unknown) => void), callback?: (message: unknown) => void): BrowserRoomConnection;
    close(): void;
}
interface WrnexusRealtimeWindow extends Window {
    wire?: {
        room?: (name: string, query?: string) => BrowserRoomConnection;
    };
}
declare function roomQuery(params: Record<string, string | number | boolean | null | undefined>): string;
declare function connectRoom(name: string, options?: {
    query?: Record<string, string | number | boolean | null | undefined>;
    window?: WrnexusRealtimeWindow;
}): BrowserRoomConnection;
declare function sendRoomMessage<T>(room: BrowserRoomConnection, message: RealtimeMessage<T> | T): BrowserRoomConnection;

interface SequencedRealtimeMessage<T = unknown> {
    sequence: number;
    message: RealtimeMessage<T>;
}
interface RealtimeHistorySnapshot {
    rooms: number;
    messages: number;
    acknowledgements: number;
    oldestSequence?: number;
    latestSequence?: number;
}
interface RealtimeHistoryOptions {
    limitPerRoom?: number;
    maxClients?: number;
}
interface RealtimeHistory {
    publish<T>(room: string, message: RealtimeMessage<T>): SequencedRealtimeMessage<T>;
    replay(room: string, afterSequence?: number, limit?: number): SequencedRealtimeMessage[];
    acknowledge(room: string, clientId: string, sequence: number): void;
    acknowledged(room: string, clientId: string): number;
    resume(room: string, clientId: string, limit?: number): SequencedRealtimeMessage[];
    snapshot(): RealtimeHistorySnapshot;
    clear(room?: string): void;
}
declare function createRealtimeHistory(options?: RealtimeHistoryOptions): RealtimeHistory;
declare function createAcknowledgement(room: string, sequence: number, clientId: string): RealtimeMessage<{
    sequence: number;
    clientId: string;
}>;
declare function realtimeSseResponse(stream: ReadableStream<SequencedRealtimeMessage>, signal?: AbortSignal): Response;

interface DatabaseChange<T = unknown> {
    table: string;
    operation: "insert" | "update" | "delete";
    key?: string | number;
    record?: T;
    occurredAt: number;
}
interface DatabaseChangeSource {
    subscribe(handler: (change: DatabaseChange) => void | Promise<void>): () => void;
}
declare function databaseChangeFeed(source: DatabaseChangeSource, publish: (topic: string, change: DatabaseChange) => void | Promise<void>, options?: {
    prefix?: string;
    allowTables?: string[];
}): () => void;
interface FileStreamFrame {
    streamId: string;
    index: number;
    total: number;
    bytes: Uint8Array;
}
declare function frameFileStream(streamId: string, bytes: Uint8Array, options?: {
    chunkBytes?: number;
    maxBytes?: number;
}): FileStreamFrame[];
declare function createFileStreamReceiver(options?: {
    maxBytes?: number;
    maxStreams?: number;
}): {
    accept(frame: FileStreamFrame): Uint8Array | null;
    snapshot: () => {
        activeStreams: number;
        bufferedBytes: number;
    };
};

export { type BrowserRoomConnection, type CreateRealtimeMessageOptions, type DatabaseChange, type DatabaseChangeSource, type FileStreamFrame, type ParseRealtimeMessageOptions, type RealtimeHistory, type RealtimeHistoryOptions, type RealtimeHistorySnapshot, type RealtimeMessage, type RealtimeMessageType, type RealtimePresence, type RealtimeRoomMeta, type SequencedRealtimeMessage, type WrnexusRealtimeWindow, assertRealtimeRoomName, connectRoom, createAcknowledgement, createFileStreamReceiver, createPresenceEvent, createRealtimeHistory, createRealtimeMessage, createTypingEvent, databaseChangeFeed, frameFileStream, isRealtimeMessage, parseRealtimeMessage, realtimeSseResponse, roomMemberSummary, roomQuery, sendRoomMessage };

Examples

Copy-ready examples from the installed package documentation.

## Server rooms

import { defineRoom } from "@wrnexus/realtime";

export default defineRoom("support", {
  async authorize(context) {
    return Boolean(context.user);
  },
  message(client, message) {
    client.broadcast(message);
  },
});

## Messages and browser helpers

import {
  createRealtimeMessage,
  createPresenceEvent,
  createTypingEvent,
  connectRoom,
  sendRoomMessage,
} from "@wrnexus/realtime";

const room = connectRoom("support", { query: { ticket: "T-100" } });
sendRoomMessage(
  room,
  createRealtimeMessage({
    type: "message",
    room: "support",
    data: { text: "Hello" },
  }),
);

Incoming messages can be bounded and constrained

const message = parseRealtimeMessage(rawMessage, {
  maxBytes: 64 * 1024,
  maxDepth: 12,
  allowedTypes: ["message", "typing", "presence"],
  room: "support",
});

message, acknowledgement, and sequence counts for monitoring without exposing payloads.

const history = createRealtimeHistory({ limitPerRoom: 100 });
const entry = history.publish("support", message);
history.acknowledge("support", clientId, entry.sequence);
const missed = history.resume("support", clientId);