Files
HiveGrid-Wireframes/hivegrid-cloud/packages/realtime

@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_NAMES for stable event names: notification.snapshot, notification.update, keepalive, and error.
  • NotificationStreamItem and realtime event types for notification snapshots, updates, keepalives, and redacted errors.
  • validateRealtimeEvent(input) and isRealtimeEvent(input) for runtime validation before publishing or consuming events.
  • serializeRealtimeEvent(input, options) for deterministic SSE frames with cursor id: lines and optional non-negative retry: 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.