Skip to content
D
Documentation

Sync — interfaces

reference
8 min readUpdated

Interfaces

Import these from @grafloria/engine.

Interfaces

ActorFrontier

What one peer holds from one actor. Three numbers; see the header for why not one.

ts
interface ActorFrontier

Properties

NameTypeDefaultDescription
maxnumberHighest clock seen from this actor.
countnumberHow many of this actor's ops we hold. A hole below max shows up here.
hashnumberOrder-independent fold of the clocks held. Catches a hole count cannot see.

AwarenessChange

ts
interface AwarenessChange

Properties

NameTypeDefaultDescription
addedActorId[]
updatedActorId[]
removedActorId[]

AwarenessMessage

Ephemeral presence. NEVER reaches the op log. See the header.

ts
interface AwarenessMessage

Properties

NameTypeDefaultDescription
t'awareness'
fromActorId
stateAwarenessState | nullnull ⇒ this peer is gone (an explicit, immediate tombstone; the TTL is the backup).
seqnumberPer-peer sequence. Awareness is last-write-wins PER PEER, and a reordering transport will hand you an old cursor after a new one — without this, the cursor jumps backwards. It is not a Lamport clock: peers never compare each other's awareness, only their own successive samples.

AwarenessOptions

ts
interface AwarenessOptions

Properties

NameTypeDefaultDescription
actorActorId
timeoutMs?numberDrop a peer we have not heard from in this long.
now?() => numberWall clock — injectable so the expiry tests are not a race against real time.

AwarenessState

Anything a peer publishes about ITSELF that is not a document edit.

ts
interface AwarenessState

Properties

NameTypeDefaultDescription
name?stringHuman name for the badge.
color?stringCSS colour. Derived deterministically from the actor id when absent.
cursor?{ x: number; y: number } | nullPointer position in WORLD coordinates — never screen: peers have different cameras.
selection?string[]Entity ids this peer has selected.

BroadcastChannelLike

The slice of the BroadcastChannel API we use — so a test can hand us a fake.

ts
interface BroadcastChannelLike

Properties

NameTypeDefaultDescription
onmessage((event: { data: unknown }) => void) | null

Members

  • postMessage(data: unknown): void
  • close(): void

BroadcastChannelTransportOptions

ts
interface BroadcastChannelTransportOptions

Properties

NameTypeDefaultDescription
namestringThe room. Every peer on the same name shares a document. Namespace it by doc id.
actorActorId
create?(name: string) => BroadcastChannelLikeInjectable factory — the default reaches for the global.

ByeMessage

"I am leaving." Best-effort — the TTL is what actually guarantees cleanup.

ts
interface ByeMessage

Properties

NameTypeDefaultDescription
t'bye'
fromActorId

CausalBufferOptions

ts
interface CausalBufferOptions

Properties

NameTypeDefaultDescription
maxPending?numberHard cap on held ops. A malicious or badly broken peer must not be able to make us buffer without limit; past the cap we release the oldest held op anyway (it will apply as a no-op and be logged, which is exactly what would have happened before this file existed — degraded, but bounded, and counted so it is visible).

CausalSplit

Where an op sits: released to the replica, or waiting on a dependency.

ts
interface CausalSplit

Properties

NameTypeDefaultDescription
readyOp[]Ready NOW, in total order. Hand straight to Replica.receive().
heldnumberNewly held this round (diagnostics).

HelloMessage

"I have joined." Carries the sender's frontier so an existing peer can push the newcomer the history it lacks WITHOUT the newcomer having to ask a second time.

ts
interface HelloMessage

Properties

NameTypeDefaultDescription
t'hello'
fromActorId
vvVersionVectorJSON
awareness?AwarenessStateThe sender's presence, so peers see the badge on the join frame, not 5s later.

OpBatcherOptions

ts
interface OpBatcherOptions

Properties

NameTypeDefaultDescription
onFlush(kept: Op[], dropped: Op[]) => voidWhere a flushed batch goes. Never called with an empty kept.
intervalMs?numberHow long to accumulate, in ms. 16 ≈ one frame: a drag's pointermoves collapse into one message per frame, which is the point.
maxBatch?numberFlush immediately once the queue reaches this many ops, regardless of the timer. Backpressure: a scripted bulk import can emit ten thousand ops inside one tick, and an unbounded queue would hand the transport a single ten-megabyte frame.
setTimer?(cb: () => void, ms: number) => unknownInjectable timer, so tests are deterministic and Node/browser both work.
clearTimer?(handle: unknown) => void

OpsMessage

A batch of document ops. The only message that reaches the op log.

ts
interface OpsMessage

Properties

NameTypeDefaultDescription
t'ops'
fromActorId
opsOp[]

PeerPresence

Another peer, as far as we know.

ts
interface PeerPresence

Properties

NameTypeDefaultDescription
actorActorId
stateAwarenessState
lastSeennumberWall-clock ms of the last message from them. NOT a Lamport clock — see below.
seqnumberTheir sequence number for state. Guards against a reordered cursor sample.

SyncAdapterOptions

ts
interface SyncAdapterOptions

Properties

NameTypeDefaultDescription
batch?Omit<OpBatcherOptions, 'onFlush'> | falseBatching. false sends every op the instant it happens — legal, and a bad idea.
relay?booleanForward ops that were NEW to us on to our other peers.
syncIntervalMs?numberPeriodic anti-entropy, ms. 0 disables it (the tests drive sync() by hand).
heartbeatMs?numberRe-publish our awareness this often so peers do not time us out while we sit still.
awarenessThrottleMs?numberMinimum ms between awareness sends. 60Hz in, ~20Hz out.
awarenessTimeoutMs?numberDrop a peer's presence after this long without a word.
setTimer?(cb: () => void, ms: number) => unknownInjectables, so every timing test is deterministic instead of a race.
clearTimer?(handle: unknown) => void
setInterval?(cb: () => void, ms: number) => unknown
clearInterval?(handle: unknown) => void
now?() => number

SyncSessionOptions

Also has every member of SyncAdapterOptions, listed on its own entry.

ts
interface SyncSessionOptions extends SyncAdapterOptions

Properties

NameTypeDefaultDescription
actorActorId
startClock?numberResume the Lamport clock from a persisted tail.

SyncStats

Counters. Every one of them is something a test asserts on, not decoration.

ts
interface SyncStats

Properties

NameTypeDefaultDescription
messagesSentnumberMessages handed to the transport.
messagesReceivednumberMessages taken from the transport.
opsSentnumberOps we put on the wire (post-coalescing).
opsReceivednumberOps that arrived.
opsDuplicatenumber…of which the log had already seen: duplicate delivery, absorbed.
opsHeldnumberOps currently HELD by the causal buffer, waiting for an add.
syncsRequestednumberAnti-entropy rounds we asked for.
repairsnumberTimes a peer's frontier turned out to have a HOLE and we resent an actor's history.
opsCoalescednumberOps the batcher swallowed because a later write superseded them.
reconnectsnumberReconnects observed.

SyncTransport

A pipe that moves SyncMessages between peers.

The delivery model is BROADCAST-TO-OTHERS: send() delivers to every other peer on the channel and never echoes back to the sender. (A star topology through a server, a BroadcastChannel between tabs, and a full WebRTC mesh all present this way; a point-to-point transport presents itself as a channel with one other peer.)

ts
interface SyncTransport

Properties

NameTypeDefaultDescription
statusTransportStatus

Members

  • send(message: SyncMessage): void — Broadcast to the other peers. A no-op — NOT an error — while disconnected.
  • onMessage(handler: (message: SyncMessage) => void): Unsubscribe — Inbound messages from other peers. Never our own.
  • onStatus(handler: (status: TransportStatus) => void): Unsubscribe — Connection transitions. THE hook the whole reconnect story hangs on: the adapter subscribes here and fires an anti-entropy round the moment it hears 'connected' again. A transport that never reports its status can never be caught up.
  • connect(): void — Open (or re-open) the channel. Idempotent.
  • disconnect(): void — Close the channel but stay re-openable — this is what a "drop" is.
  • close(): void — Tear down for good.

UnreliableOptions

ts
interface UnreliableOptions

Properties

NameTypeDefaultDescription
seed?number
dropRate?numberP(message is simply never delivered).
duplicateRate?numberP(message is delivered more than once).
delayRate?numberP(message is held in flight instead of delivered now) — this is what reorders.
maxDuplicates?numberMax extra copies when duplicating.

WebSocketLike

The standard WebSocket surface — satisfied by the browser global AND by ws.

ts
interface WebSocketLike

Properties

NameTypeDefaultDescription
readyStatenumber
onopen((event?: unknown) => void) | null
onmessage((event: { data: unknown }) => void) | null
onclose((event?: unknown) => void) | null
onerror((event?: unknown) => void) | null

Members

  • send(data: string): void
  • close(code?: number, reason?: string): void

WebSocketTransportOptions

ts
interface WebSocketTransportOptions

Properties

NameTypeDefaultDescription
urlstring
socketFactory?(url: string) => WebSocketLikeThe constructor. Defaults to the global. ws satisfies this in Node.
reconnect?booleanReconnect backoff. The first retry waits reconnectBaseMs, then doubles to a cap.
reconnectBaseMs?number
reconnectMaxMs?number
setTimer?(cb: () => void, ms: number) => unknownInjectable, so the backoff test does not take 30 seconds of wall clock.
clearTimer?(h: unknown) => void

Was this page helpful?

Sync — interfaces — Grafloria