@wrnexus/realtime
Rooms, presence, messaging, history, streams, and UI components.
Install the package
After WorkRoot approves private registry access, install the release-aligned package:
bun add @wrnexus/realtime@0.8.7Request 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);