Skip to content
D
Documentation

Awareness

reference
2 min readUpdated

Import it from @grafloria/engine.

The presence store for one peer: our own state, and everyone else's.

Deliberately transport-free. SyncAdapter wires it to a channel; a test wires it to nothing and drives it directly. It never sees an Op, a Replica or an OpLog, and that is not an accident — it is the containment.


WHY lastSeen IS A WALL CLOCK WHEN THE LOG WENT TO SUCH LENGTHS TO AVOID ONE

Because the question is different. The log asks "did A happen before B?", which a wall clock cannot answer across machines (they disagree, and they run backwards). Expiry asks "has it been 15 seconds since I last heard from Bob?" — measured entirely on MY clock, about MY receipts, comparing my own now() to my own earlier now(). Bob's clock is never consulted, so its wrongness cannot infect anything. Using a Lamport clock for a TIMEOUT would be the actual mistake: it has no relationship to seconds.

ts
class Awareness

Methods

  • constructor(private readonly options: AwarenessOptions)
  • get actor(): ActorId
  • getLocalState(): AwarenessState — Our own published state, and the sequence a peer will LWW it by.
  • get localSeq(): number
  • setLocalState(patch: Partial<AwarenessState>): boolean — Merge a patch into our own state and bump the sequence.

Returns false when nothing actually changed — a mouse that has not moved must not burn a sequence number, wake the throttle, or send a message. (A mousemove fires on sub-pixel jitter and on scroll; without this guard an idle user with a resting hand is a 60Hz broadcaster.)

  • applyRemote(actor: ActorId, state: AwarenessState | null, seq: number): AwarenessChange | null — Take a peer's published state.

seq is the whole reason a reordered transport does not make a cursor jump backwards: a sample older than the one we already hold is REFUSED, exactly as the LWW gate refuses a superseded op. Same idea, one scope down, and no shared machinery because the lifecycles have nothing in common.

  • remove(actor: ActorId): AwarenessChange | null — An explicit bye, or a transport that told us the peer is gone.
  • prune(): AwarenessChange | null — Drop peers we have not heard from inside the timeout.

THE GUARANTEE, as opposed to bye, which is merely the optimisation. A crashed tab, a closed laptop, a killed process and a severed cable all send no bye whatsoever, and every one of them must eventually stop showing a cursor.

  • getPeers(): PeerPresence[] — Everyone but us.
  • getPeer(actor: ActorId): PeerPresence | undefined
  • get peerCount(): number
  • clearPeers(): AwarenessChange | null — Everyone is gone (we disconnected — we can no longer vouch for anyone).
  • onChange(listener: (change: AwarenessChange) => void): () => void

Was this page helpful?

Awareness — Grafloria