Realtime events

@edgevault/realtime — subscribe to config, flag, and secret changes over hibernatable WebSockets instead of polling.

Your workspace’s Durable Object broadcasts every change over WebSockets. @edgevault/realtime is the client: auto-reconnect with exponential backoff, and a client-driven ping so the server DO can hibernate between messages instead of being kept awake by a timer.

React

import { useWorkspaceEvents } from '@edgevault/realtime/react'

const status = useWorkspaceEvents(url, (event) => {
  if (event.type === 'config.changed') refresh(event.key)
})

useWorkspaceEvents(url, onEvent) takes the WebSocket URL as its first argument — e.g. wss://api.edgevault.io/api/v1/workspaces/<id>/ws?token=… — and returns the connection status ('connecting' | 'open' | 'closed'). Pass null as the url to stay disconnected (e.g. before auth is ready).

The event union

type WorkspaceEvent =
  | { type: 'config.changed'; environmentId: string; key: string; kind: 'config' | 'flag' | 'secret'; version: number; at: number }
  | { type: 'config.deleted'; environmentId: string; key: string; at: number }
  | { type: 'environment.created'; environmentId: string; slug: string; at: number }
  | { type: 'promotion.completed'; key: string; sourceEnvironmentId: string; targetEnvironmentId: string; at: number }
  | { type: 'presence'; users: string[]; at: number }
  | { type: 'pong'; at: number }

config.changed carries the kind — secret events tell you that a secret changed, never its value.

Outside React

import { WorkspaceEventsClient } from '@edgevault/realtime'

const client = new WorkspaceEventsClient({
  url,
  onEvent: (event) => handle(event),
  onStatus: (status) => log(status), // optional
  reconnect: true, // default — exponential backoff
  pingIntervalMs: 30_000, // default — hibernation-friendly keepalive
})

client.connect()
// later:
client.close()

The console’s live dashboard runs on exactly this client — if you want to see the stream working, open a workspace in two tabs and change something.