# 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `max` | `number` |  | Highest clock seen from this actor. |
| `count` | `number` |  | How many of this actor's ops we hold. A hole below `max` shows up here. |
| `hash` | `number` |  | Order-independent fold of the clocks held. Catches a hole `count` cannot see. |

### `AwarenessChange`

```ts
interface AwarenessChange
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `added` | `ActorId[]` |  |  |
| `updated` | `ActorId[]` |  |  |
| `removed` | `ActorId[]` |  |  |

### `AwarenessMessage`

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

```ts
interface AwarenessMessage
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `t` | `'awareness'` |  |  |
| `from` | `ActorId` |  |  |
| `state` | `AwarenessState \| null` |  | null ⇒ this peer is gone (an explicit, immediate tombstone; the TTL is the backup). |
| `seq` | `number` |  | Per-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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `actor` | `ActorId` |  |  |
| `timeoutMs?` | `number` |  | Drop a peer we have not heard from in this long. |
| `now?` | `() => number` |  | Wall 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name?` | `string` |  | Human name for the badge. |
| `color?` | `string` |  | CSS colour. Derived deterministically from the actor id when absent. |
| `cursor?` | `{ x: number; y: number } \| null` |  | Pointer 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `onmessage` | `((event: { data: unknown }) => void) \| null` |  |  |

**Members**

- `postMessage(data: unknown): void`
- `close(): void`

### `BroadcastChannelTransportOptions`

```ts
interface BroadcastChannelTransportOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | The room. Every peer on the same name shares a document. Namespace it by doc id. |
| `actor` | `ActorId` |  |  |
| `create?` | `(name: string) => BroadcastChannelLike` |  | Injectable factory — the default reaches for the global. |

### `ByeMessage`

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

```ts
interface ByeMessage
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `t` | `'bye'` |  |  |
| `from` | `ActorId` |  |  |

### `CausalBufferOptions`

```ts
interface CausalBufferOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `maxPending?` | `number` |  | Hard 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `ready` | `Op[]` |  | Ready NOW, in total order. Hand straight to `Replica.receive()`. |
| `held` | `number` |  | Newly 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `t` | `'hello'` |  |  |
| `from` | `ActorId` |  |  |
| `vv` | `VersionVectorJSON` |  |  |
| `awareness?` | `AwarenessState` |  | The sender's presence, so peers see the badge on the join frame, not 5s later. |

### `OpBatcherOptions`

```ts
interface OpBatcherOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `onFlush` | `(kept: Op[], dropped: Op[]) => void` |  | Where a flushed batch goes. Never called with an empty `kept`. |
| `intervalMs?` | `number` |  | How long to accumulate, in ms. 16 ≈ one frame: a drag's pointermoves collapse into one message per frame, which is the point. |
| `maxBatch?` | `number` |  | Flush 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) => unknown` |  | Injectable 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `t` | `'ops'` |  |  |
| `from` | `ActorId` |  |  |
| `ops` | `Op[]` |  |  |

### `PeerPresence`

Another peer, as far as we know.

```ts
interface PeerPresence
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `actor` | `ActorId` |  |  |
| `state` | `AwarenessState` |  |  |
| `lastSeen` | `number` |  | Wall-clock ms of the last message from them. NOT a Lamport clock — see below. |
| `seq` | `number` |  | Their sequence number for `state`. Guards against a reordered cursor sample. |

### `SyncAdapterOptions`

```ts
interface SyncAdapterOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `batch?` | `Omit<OpBatcherOptions, 'onFlush'> \| false` |  | Batching. `false` sends every op the instant it happens — legal, and a bad idea. |
| `relay?` | `boolean` |  | Forward ops that were NEW to us on to our other peers. |
| `syncIntervalMs?` | `number` |  | Periodic anti-entropy, ms. 0 disables it (the tests drive `sync()` by hand). |
| `heartbeatMs?` | `number` |  | Re-publish our awareness this often so peers do not time us out while we sit still. |
| `awarenessThrottleMs?` | `number` |  | Minimum ms between awareness sends. 60Hz in, ~20Hz out. |
| `awarenessTimeoutMs?` | `number` |  | Drop a peer's presence after this long without a word. |
| `setTimer?` | `(cb: () => void, ms: number) => unknown` |  | Injectables, 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `actor` | `ActorId` |  |  |
| `startClock?` | `number` |  | Resume 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `messagesSent` | `number` |  | Messages handed to the transport. |
| `messagesReceived` | `number` |  | Messages taken from the transport. |
| `opsSent` | `number` |  | Ops we put on the wire (post-coalescing). |
| `opsReceived` | `number` |  | Ops that arrived. |
| `opsDuplicate` | `number` |  | …of which the log had already seen: duplicate delivery, absorbed. |
| `opsHeld` | `number` |  | Ops currently HELD by the causal buffer, waiting for an `add`. |
| `syncsRequested` | `number` |  | Anti-entropy rounds we asked for. |
| `repairs` | `number` |  | Times a peer's frontier turned out to have a HOLE and we resent an actor's history. |
| `opsCoalesced` | `number` |  | Ops the batcher swallowed because a later write superseded them. |
| `reconnects` | `number` |  | Reconnects observed. |

### `SyncTransport`

A pipe that moves `SyncMessage`s 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `status` | `TransportStatus` |  |  |

**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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `seed?` | `number` |  |  |
| `dropRate?` | `number` |  | P(message is simply never delivered). |
| `duplicateRate?` | `number` |  | P(message is delivered more than once). |
| `delayRate?` | `number` |  | P(message is held in flight instead of delivered now) — this is what reorders. |
| `maxDuplicates?` | `number` |  | Max extra copies when duplicating. |

### `WebSocketLike`

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

```ts
interface WebSocketLike
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `readyState` | `number` |  |  |
| `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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `url` | `string` |  |  |
| `socketFactory?` | `(url: string) => WebSocketLike` |  | The constructor. Defaults to the global. `ws` satisfies this in Node. |
| `reconnect?` | `boolean` |  | Reconnect backoff. The first retry waits `reconnectBaseMs`, then doubles to a cap. |
| `reconnectBaseMs?` | `number` |  |  |
| `reconnectMaxMs?` | `number` |  |  |
| `setTimer?` | `(cb: () => void, ms: number) => unknown` |  | Injectable, so the backoff test does not take 30 seconds of wall clock. |
| `clearTimer?` | `(h: unknown) => void` |  |  |
