Events
Peers uses a small in-process event bus for named payloads: subscribers match either an exact event name or a custom predicate. Tables, persistent variables, the desktop UI shell, voice, and other features share this mechanism.
Core implementation: @peers-app/peers-sdk — events.ts (subscribe, subscribeDebounce, emit, Emitter, Event, unionEvents, subscribePrefix, notifyClientConnected).
Subscribing and emitting
subscribe(name, handler)— runshandlerwhenemit({ name, data })uses that exactname. Handlers may return a boolean; returningfalsemakesemitresolve tofalse(useful for cancellation-style semantics).subscribe(filterFn, handler)— custom matching on the full{ name, data }object. Prefer a string name when possible so dispatch stays fast.subscribeDebounce(...)— same assubscribe, but the handler is debounced by milliseconds.new Emitter("myEvent")— pairs withemitter.event.subscribe(handler)for typeddatapayloads;emitter.emit(data)callsemit({ name: "myEvent", data }).
Name-based subscriptions are indexed internally so matching does not scan every subscriber on every emit.
Multi-process mode (Electron and CLI client)
In the desktop app, data and business logic run mainly in the main process while the renderer runs the web UI. The SDK marks the renderer (and the CLI when PEERS_IS_CLIENT is set) as a client.
- On the server (main),
emitruns local handlers, then may forward the same payload to the UI viarpcClientCalls.emitEvent, which the Electron host implements over Socket.IO. - On the client, the SDK assigns
rpcClientCalls.emitEventtoemit(event, dontPropagate)so incoming events run only local subscribers and are not sent back to the server.
Reference wiring:
- Renderer:
peers-electron/src/client/frontend-client.ts(afterconnect, callnotifyClientConnected()so subscriptions registered before the socket was ready are flushed to the main process). - CLI:
peers-cli/src/connection.ts(same pattern).
Selective forwarding
The main process only forwards events the client has registered interest in:
- Subscribing with an exact name registers that name with the server (reference counted; duplicate subscribers share one registration).
- Prefix-based streams (for example all
SomeTable_DataChanged_*events across group databases) usesubscribePrefix(prefix)from library code that needs it; that registers a prefix with the server.
This reduces Socket.IO traffic when many tables emit *_DataChanged_* events but the current UI only listens to a subset.
Single-process mode (PWA)
In the PWA, the app calls setSingleProcessClient(true). Client and “server” share one process, so table dataChanged emitters are not suppressed on the client, and emit does not forward through emitEvent (there is no separate renderer process to push to).
Tables and dataChanged
Each Table exposes table.dataChanged as an Event backed by a stable name (including the data context). UI code typically uses Messages().dataChanged.subscribe(...) rather than calling subscribe with the raw string name.
For ORM access patterns (list, get, proxies), see Tables. For how pvars listen for PersistentVars updates, see Variables.