All exports from @assistant-ui/gorp.
Types
GorpMessage
Server-to-client envelope.
type GorpMessage = {
ops: GorpOperation[];
ack?: number;
};ops is a deep patch list; ack is the per-session high-water seq the
inner has processed. Receiving the same ack twice is harmless.
RelaySerializedState
Snapshot shape returned by GorpRelay.serialize.
type RelaySerializedState<T> = { state: T };GorpSessionsState
Snapshot shape returned by GorpSessions.serialize.
type GorpSessionsState = {
sessions: Record<string, { highWater: number }>;
};GorpClient
Leaf-side replica with an optimistic view layered on top of the latest server-confirmed state.
new GorpClient<T, C>(config: GorpClient.Config<T, C>)GorpClient.Config<T, C>
{
initialState: T;
mutator: (state: T, command: C, seq: number) => void;
send: (command: C) => void;
}The mutator must be deterministic in (state, command, seq) — GorpClient
re-runs it on every replay to rebuild the optimistic view. The shape
matches GorpServer.Config so a single function can run on both sides.
send is invoked synchronously on every client.send(cmd) and again for
every pending entry when client.resync() is called.
Properties
client.state: DeepReadonly<T> // optimistic view (committed + replay(pending))
client.pending: readonly C[]
client.firstPendingSeq: number // pass on (re)connect handshakeMethods
client.send(command: C): void
client.apply(message: GorpMessage): void
client.resync(): void // re-emit pending via config.send
client.onChange(cb: () => void): () => void
client.isChangedAt(path: string[]): boolean
client.getChangedKeys(path: string[]): string[]isChangedAt / getChangedKeys are valid synchronously after apply()
or send() — they describe what moved since the last public method call.
GorpServer
Authoritative state container. Pair with GorpSessions
for the sessioned wire protocol.
new GorpServer<T, C>(config: GorpServer.Config<T, C>)GorpServer.Config<T, C>
{
initialState: T;
mutator: (state: T, command: C, seq: number) => void;
}Properties
server.state: T // live mutable proxy — writes queue ops, batched per microtask
server.state = newValue // root replace — emits a single `set []` opMutating array methods (push, pop, splice, …) throw — use
state.list = [...state.list, x] instead.
Methods
server.receive(command: C): void
server.subscribe(cb: (env: GorpMessage) => void): () => voidGorpRelay
State mirror + upstream pipe. Same state/receive/subscribe surface
as GorpServer, but state changes come from upstream GorpMessage
envelopes instead of a local mutator.
new GorpRelay<T, C>(config: GorpRelay.Config<T, C>)GorpRelay.Config<T, C>
{
initialState: T;
send: (command: C) => void; // upstream transport callback
}Properties
relay.state: DeepReadonly<T>Methods
relay.receive(command: C): void // forward upstream via config.send
relay.applyUpstream(msg: GorpMessage): void // inbound from upstream
relay.subscribe(cb: (env: GorpMessage) => void): () => void
relay.serialize(): RelaySerializedState<T>
relay.restore(state: RelaySerializedState<T>): voidGorpSessions
Wraps either a GorpServer or GorpRelay and adds the sessioned wire
protocol: per-sessionId dedup, ack flushing, and resume-after-reconnect.
new GorpSessions<C>(inner: GorpServer | GorpRelay)Methods
sessions.addClient(
sessionId: string,
fromSeq: number,
send: (msg: GorpMessage) => void,
): { receive(command: C): void; remove(): void }
sessions.close(): void
sessions.serialize(): GorpSessionsState
sessions.restore(state: GorpSessionsState): voidaddClient sends an initial { ops: [{ set [] state }], ack? } envelope
so a reconnecting client splices any already-processed pending without a
round-trip. Sessions are retained for 1 hour after their last activity;
currently-connected sessions are never pruned.
Functions
appendText
Marker for the append-text op type — streaming text concatenation
without retransmitting prior content. Works inside GorpClient's
optimistic mutator too.
import { appendText } from "@assistant-ui/gorp";
server.state.messages[id].parts[0].text = appendText(chunk);