@toolctrl/realtime
@toolctrl/realtime is the public package boundary for typed realtime notification events, server-side Server-Sent Events (SSE) serialization, and the browser-facing subscriber. Downstream API and UI code should import from the package entrypoint only; internal source files are not part of the contract.
Public contract
The package exports:
REALTIME_EVENT_NAMESfor stable event names:notification.snapshot,notification.update,keepalive, anderror.NotificationStreamItemand realtime event types for notification snapshots, updates, keepalives, and redacted errors.validateRealtimeEvent(input)andisRealtimeEvent(input)for runtime validation before publishing or consuming events.serializeRealtimeEvent(input, options)for deterministic SSE frames with cursorid:lines and optional non-negativeretry:hints.serializeRealtimeComment(comment)for keepalive comments.subscribeToRealtime(options)for browser subscribers that validate SSE JSON before dispatching typed snapshot, update, keepalive, and error callbacks.
Diagnostics and secret handling
Realtime error events are diagnostics, not raw exception transports. Error payloads must stay limited to requestId, cursor, optional tenant/user identifiers, code, and a stable non-secret message.
Do not include provider payloads, tokens, cookies, authorization headers, API keys, request bodies, stack traces, raw exception messages, or arbitrary metadata in realtime events. Runtime validation rejects unsupported and secret-like keys so SSE frames can be shared with subscribers without leaking credentials or provider details.
The browser subscriber reports status transitions (connecting, open, reconnecting, closed) and redacted diagnostics through callbacks. Parse, validation, callback, EventSource, and reconnect diagnostics may include the event name, cursor, request id, retry attempt, or reconnect delay when those fields are already valid, but they must not include raw SSE payloads, headers, stack traces, tokens, provider payloads, or arbitrary metadata.
API publisher example
import { REALTIME_EVENT_NAMES, serializeRealtimeEvent, type NotificationStreamItem } from "@toolctrl/realtime";
export function publishNotificationUpdate(item: NotificationStreamItem, requestId: string) {
return serializeRealtimeEvent(
{
event: REALTIME_EVENT_NAMES.notificationUpdate,
cursor: item.cursor,
requestId,
tenantId: item.tenantId,
userId: item.userId,
item,
},
{ retry: 5000 },
);
}
UI subscriber example
import { subscribeToRealtime } from "@toolctrl/realtime";
const subscriber = subscribeToRealtime({
url: "https://api.example.com/v1/notifications/stream",
reconnect: { baseDelayMs: 1000, maxDelayMs: 30000, maxRetries: 5 },
onStatus: (status) => updateConnectionBadge(status),
onDiagnostic: (diagnostic) => logRealtimeDiagnostic(diagnostic),
onSnapshot: (event) => renderNotifications(event.items),
onUpdate: (event) => upsertNotification(event.item),
onKeepalive: (event) => markStreamAlive(event.requestId),
onError: (event) => showStreamError(event.message),
});
// Later, for route changes or component unmounts:
subscriber.close();
Importing from internal files such as @toolctrl/realtime/src/events, @toolctrl/realtime/src/server, or @toolctrl/realtime/src/client is unsupported; use @toolctrl/realtime so future internal refactors do not break downstream slices.