Without React
HighXClient directly — for another framework, or no framework.
HighXClient owns one connection and all of its semantics. Reach for it when
you are building a binding for another framework, using HighX without one, or
writing tests.
import { HighXClient, webSocketFactory } from "@highx/client";
const client = new HighXClient({ socket: webSocketFactory(url, WebSocket) });
const release = client.subscribe("notes.list", { color: "amber" }, (entry) => {
render(entry.value);
});
release(); // unsubscribeEverything else — reconnect, resume, deduplication, and the mutation queue — happens without your involvement. → Connection
Mutating
try {
const id = await client.mutate("notes.create", { title: "New" }, {});
} catch (error) {
if (isConflictError(error)) offerAChoice();
}mutate resolves with whatever the handler returned and rejects with a
RemoteError carrying the server's code. Match on the code,
never the message.
Options
new HighXClient({
socket: webSocketFactory(url, WebSocket),
scheduler, // inject timers
onStatusChange: (status) => setBanner(status),
maxQueuedMutations: 100,
});The socket is injected as a SocketFactory and timers as a Scheduler. That is
what lets every reconnect path be tested deterministically — no browser, no real
waiting — and it also means you can supply your own transport or clock.
The cache
client.cache is the single place query state lives. A binding reads it and
subscribes to its notifications; it must never apply frames into a cache of its
own, because once the client owns a cache, re-applying into the same one is an
infinite notification loop.
Notifications are paced; values are not. The cache records a new value synchronously — a reader always sees the latest — while the notification is collapsed within a frame. Twenty people typing costs twenty patches and one render.
Building a React-style binding
A transport is all @highx/react needs: cache, subscribe, mutate, and
close. See
writing a transport by hand for the
whole implementation — it is about twenty lines.