---
title: "edytor/cloudflare"
description: "Every name edytor/cloudflare exports, with its declaration and documentation (generated from the published types)."
---

{/* Generated by scripts/api-report.mjs (pnpm api:report) from the published declarations: edit the JSDoc, not this page. */}

Every name `edytor/cloudflare` exports, generated from the package's published type declarations, with the documentation its JSDoc carries. The guides explain how the pieces fit; this page is the exhaustive list. 101 names.

## AttachedDocument

One edytor document living in a Durable Object's storage: the room
logic, independent of the class that hosts it. Create it with
`attachRoom` (any Durable Object) or extend `DocumentRoom`.
The socket handlers ignore sockets without `SOCKET_TAG` and
return `false` for them.

```ts
export declare class AttachedDocument {
    readonly ctx: DurableObjectState;
    readonly maxRowBytes: number;
    readonly maxFrameBytes: number;
    readonly compactAfter: number;
    readonly saveAfter: number;
    readonly maxDocumentBytes: number;
    readonly documentWarning: number;
    readonly maxInboundFrameBytes: number;
    readonly maxBufferedBytes: number;
    readonly maxUpdatesPerSecond: number;
    readonly maxPresenceBytes: number;
    readonly maxPresencePerSecond: number;
    readonly maxPresenceFanout: number;
    failure: Error | null;
    presence: Map<number, E.AwarenessEntry>;
    refusals: Refusal[];
    refusalCounts: Partial<Record<Refusal['reason'], number>>;
    origin: {
        kind: 'fresh';
    } | {
        kind: 'restored';
        records: number;
    };
    private readonly room;
    constructor(ctx: DurableObjectState, options?: AttachRoomOptions);
    get doc(): YDoc | null;
    get facade(): DocumentOperations;
    transact<T>(fn: (facade: DocumentOperations) => T): T;
    read(): JSONDoc;
    alarm(): Promise<void>;
    reset(): Promise<void>;
    records(only?: RowKind): StoredRecord[];
    compressed(): Promise<void>;
    compact(): {
        rows: number;
    };
    lastUpdated(): number | null;
    historyWritten(): Promise<void>;
    listHistory(): Promise<HistoryEntry[]>;
    readHistory(key: string): Promise<JSONDoc | null>;
    restoreHistory(key: string, options?: {
        user?: string;
    }): Promise<RestoreResult>;
    undoRestore(options?: {
        user?: string;
    }): Promise<{
        status: 'applied' | 'noop';
    }>;
    listComments(): CommentSnapshot;
    comment(request: CommentRequest, actor: CommentActor): Promise<CommentOutcome>;
    exportBlocks(ids: string[]): ExportedBlocks;
    importBlocks(request: ImportRequest): ImportReceipt;
    commitMove(moveId: string, receipt: {
        to: string;
        ids: Record<string, string>;
    }): CommitResult;
    abortMove(moveId: string): {
        moveId: string;
        status: 'applied' | 'noop';
    };
    lateEdits(): LateEditBatch[];
    ackLateEdits(moveId: string, seq: number): void;
    applyLateEdits(batch: LateEditBatch): {
        applied: number;
        skipped: string[];
    };
    purge(): (PurgeReport & {
        horizon: number;
    }) | null;
    metrics(): RoomMetrics;
    dropWaitingDeletes(): {
        ranges: number;
    };
    fetch(request: Request): Promise<Response>;
    closeUser(userId: string, code?: number, reason?: string): {
        sockets: number;
    };
    setAccess(userId: string, access: 'write' | 'read' | 'none'): {
        sockets: number;
    };
    owns(ws: WebSocket): boolean;
    webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): boolean;
    webSocketClose(ws: WebSocket, code: number, reason: string): boolean;
    webSocketError(ws: WebSocket): boolean;
}
```

- `failure`: Why the stored container was refused (another generation, a torn record, `onLoad`…).
- `presence`: Latest presence entry per replica, for join snapshots (memory: refills after a wake).
- `refusals`: The newest `MAX_REFUSALS` refusals since this instance started (diagnostics; memory only).
- `refusalCounts`: Every refusal since this instance started, by reason.
- `origin`: How this instance came to be: a fresh container, or a restore of N stored records.
- `doc`: The live document; `null` when the stored container was refused.
- `facade`: The facade over the live document.
- `transact`: Manipulate: run `fn` on the facade in one transaction.
- `read`: The document as JSON.
- `alarm`: The room's one alarm (`room.alarm.tasks`): every task due at its time runs — the history slot (`room.history.slots`), `onSave`, the purge (`room.purge.timing`) — each re-arming itself or clearing its due row; then the alarm is set to the earliest due time left.
- `reset`: Generation cutover: drop the stored document (both tables) of a room whose storage belongs to another edytor generation, and start again from `onLoad` — reseed it from JSON.
- `records`: Reassembled logical records in write order (of one kind, with `only`); a torn record throws.
- `compressed`: Resolves once the snapshot compression in flight, if any, is stored: a compaction stores its snapshot raw, then compresses it in place.
- `compact`: Compaction: the rows become the generation record + one chunked snapshot of the live document's state (the healed state, deleted content collected but the text a replica may copy again — never a merge of the records, which keeps every keystroke's struct and every deleted character), atomically, + one `pending` record of the stored deletes still waiting.
- `lastUpdated`: When the room last stored a change (ms since the epoch), or `null` for a room that stored none since it records the time.
- `historyWritten`: Resolves once the versions the room is writing are stored (a slot a write closed is written in the background).
- `listHistory`: The room's versions, newest first: one per half-day slot that changed, for the retention (`room.history.slots`).
- `readHistory`: Version `key` as JSON (a preview), or `null` when this room holds no such version.
- `restoreHistory`: Restore version `key` as a forward edit (`room.history.restore`): one room transaction makes the visible document equal the version, keeping every id the registry holds and writing only what differs; stored, relayed to every socket, one step `undoRestore` undoes.
- `undoRestore`: Undo the last restore (`room.history.undo`): the history undo of exactly its transaction, edits made since kept.
- `listComments`: Every comment thread, oldest first, with the last change's sequence number (also over RPC).
- `comment`: One comments request by `actor` (also over RPC): add a thread, reply, resolve, reopen or delete, under the rules every store applies.
- `exportBlocks`: Export blocks `ids` for a move (`room.move`, step 1): their visible subtrees as JSON, the outermost only, in document order.
- `importBlocks`: Import a move's blocks (`room.move`, step 2) under `dest.parent` at `dest.index`, as one room transaction: each keeps its id unless this document holds it already (then `<id>~<n>`).
- `commitMove`: Commit a move (`room.move`, step 3), with the destination's acknowledgement: delete the exported blocks with their subtrees, as one room transaction, and watch them for late edits (`room.move.late`) for the grace period.
- `abortMove`: Drop an export that was not committed (`room.move`): the blocks stay.
- `lateEdits`: The late edits waiting to reach their destination (`room.move.late`), oldest first.
- `ackLateEdits`: The destination applied a move's late edits through `seq`: they are dropped here.
- `applyLateEdits`: Apply a batch of late edits of a move this room imported (`room.move.late`): each block's content and data merged three ways into what it holds now (what was written here since stays), as one room transaction.
- `purge`: The purge task, also over RPC (`room.purge.timing`): record an epoch (the state vector, now) when the document changed since the last one, purge what was deleted before the horizon — the newest epoch at least `purgeAfterDays` old — when it is newer than the last purged, and re-arm for the next epoch to pass it.
- `metrics`: The room's counters, also over RPC — see `RoomMetrics`.
- `dropWaitingDeletes`: Drop every waiting delete, stored or in memory (callable over RPC): the way out when a writer filled `MAX_WAITING_DELETES` with deletes of registered ids' items, which compaction keeps.
- `closeUser`: Revoke: close every socket of `userId` (also over RPC), `4403` (`access revoked`, final for the provider) unless you pass another `code` (`4401` redials with fresh `params`; `1011` redials too).
- `setAccess`: Change `userId`'s access on its open sockets (also over RPC): `'read'` downgrades each write socket in place (the read-only notice, then every write denied, the socket stays); `'write'` closes each read-only socket `1012` (`access changed`), which its provider redials, so `authorize` grants the new access; `'none'` closes every socket `4403` (`access revoked`), as `closeUser`.
- `owns`: Is `ws` one of this document's sockets?

## Attachment

A socket's attachment (survives hibernation): its identity, its presence
clock, and whether it subscribed to the comment changes.

```ts
export type Attachment = SocketIdentity & {
    clock: number | null;
    comments?: boolean;
};
```

## attachRoom

Attach an edytor room to any Durable Object (`attachRoom(this, opts)`,
in the constructor or a field). Its tables (`edytor_rows`,
`edytor_replicas`) live beside yours and its sockets carry
`SOCKET_TAG`. Each handler your class does not define —
`fetch`, `webSocketMessage`, `webSocketClose`, `webSocketError` and
`alarm` (a socket's credential may expire) — is installed on the
object; a class that
defines one delegates to the returned document's method (which returns
`false` for a socket that is not the document's).

```ts
declare const attachRoom: (host: DurableObject<any>, options?: AttachRoomOptions) => AttachedDocument;
```

## AttachRoomOptions

What `attachRoom` (and `DocumentRoom`) takes.

```ts
export type AttachRoomOptions = {
    onLoad?: () => Promise<LoadedDocument | null | undefined> | LoadedDocument | null | undefined;
    onSave?: (document: SavedDocument) => Promise<void> | void;
    saveAfter?: number;
    compactAfter?: number;
    maxRowBytes?: number;
    maxFrameBytes?: number;
    tablePrefix?: string;
    maxDocumentBytes?: number;
    documentWarning?: number;
    maxInboundFrameBytes?: number;
    maxBufferedBytes?: number;
    maxUpdatesPerSecond?: number;
    maxPresenceBytes?: number;
    maxPresencePerSecond?: number;
    maxPresenceFanout?: number;
    validate?: (frame: FrameValidation) => boolean | void;
    locks?: LockOptions;
    rooms?: () => MoveNamespace | undefined;
    log?: ((entry: RoomLogEntry) => void) | false;
    semantics?: DocumentSemanticsConfig;
    history?: HistoryOptions;
    purgeAfterDays?: number | false;
    now?: () => number;
    comments?: CommentOptions | false;
};
```

- `onLoad`: Retrieve: the document for a room that stores nothing yet.
- `onSave`: Save: mirror the document to your own store, `saveAfter` ms after the first unsaved change, on the object's alarm (a throw is retried by the platform).
- `saveAfter`: ms between the first unsaved change and `onSave` (default `DEFAULT_SAVE_AFTER`).
- `compactAfter`: Update records before the rows are merged into one snapshot (default `DEFAULT_COMPACT_AFTER`).
- `maxRowBytes`: Largest stored row; can only be lowered (default `DEFAULT_MAX_ROW_BYTES`).
- `maxFrameBytes`: Largest frame sent whole; can only be lowered (default 32 MiB).
- `tablePrefix`: Prefix of the document's SQL tables, beside your own (default `'edytor_'`).
- `maxDocumentBytes`: Largest document, in stored bytes (default `DEFAULT_MAX_DOCUMENT_BYTES`).
- `documentWarning`: The share of `maxDocumentBytes` past which the room logs the document's size (`size`, once per crossing), above 0 and at most 1 (default `DEFAULT_DOCUMENT_WARNING`, 0.8).
- `maxInboundFrameBytes`: Largest frame a socket may send, reassembled (default `DEFAULT_MAX_INBOUND_FRAME_BYTES`).
- `maxBufferedBytes`: The chunk sequences in flight on every socket together (default twice `maxInboundFrameBytes`, never less than it): a sequence that would pass it closes its socket `1011` (`room busy`), and its provider redials.
- `maxUpdatesPerSecond`: Sync messages per second a socket may send (default `DEFAULT_MAX_UPDATES_PER_SECOND`).
- `maxPresenceBytes`: Largest presence state a socket may publish, as JSON (default `DEFAULT_MAX_PRESENCE_BYTES`).
- `maxPresencePerSecond`: Presence messages per second a socket may send (default `DEFAULT_MAX_PRESENCE_PER_SECOND`).
- `maxPresenceFanout`: Presence frames per second the room sends over all its sockets (default `DEFAULT_MAX_PRESENCE_FANOUT`).
- `validate`: Accept, then compensate: after the room applied and stored a client frame that changed blocks, it is asked whether to keep it.
- `locks`: Per-block locks (`room.locks`): a block whose data names a user under `key` (default `lockedBy`) is that user's alone — a frame from anyone else touching it is denied and compensated, as `validate`'s denials are (`lockedBlocks`).
- `rooms`: The rooms blocks move to (`room.move`): with it, the room forwards the late edits of blocks it moved out to their destination itself, on its alarm; without it, they wait for the host's `forwardLateEdits`.
- `log`: The room's log: one entry per compaction, quota hit, denial or fault.
- `semantics`: The block roles `transact` edits obey — the document semantics your clients' plugins declare (`defaultType` also names the block an empty room is seeded with).
- `history`: Version history, in the store `HistoryOptions.store` names (a KV namespace, an R2 bucket or the room's own storage): the room writes its state twice a day — the morning's at local noon, the evening's at midnight, in `timeZone` — when it changed, for `retentionDays`; read and restore them with `listHistory`, `readHistory`, `restoreHistory` and `undoRestore` (or `routeDocumentHistory`).
- `purgeAfterDays`: Days after which deleted content is purged from the room and every replica (`room.purge.*`): default the history's `retentionDays`, else 30; `false` never purges.
- `now`: The room's clock, ms since the epoch (default `Date.now`): its slots, epochs and alarm read it.
- `comments`: Comment threads (`room.comments`): kept in the room's `threads` and `comments` tables, read and written through `routeDocumentComments` (or `listComments` and `comment` over RPC), each change sent to the sockets that subscribed.

## AuthorizeDocumentSocket

The host's decision: an identity, `null` to refuse (close `4403`,
terminal), or `{ expired: true }` for an expired credential (close
`4401`, retried).

```ts
export type AuthorizeDocumentSocket = (request: Request, documentId: string) => DocumentIdentity | ExpiredCredential | null | Promise<DocumentIdentity | ExpiredCredential | null>;
```

## closedSocket

An upgrade accepted and closed at once, so the client reads `code`: a
browser sees an HTTP error at the upgrade only as `1006`, which a
provider cannot tell from a network failure. Refuse a dial your Worker
turns away itself with it: the provider stops at `1008` or `4xxx`
(except `4401`, redialed), and redials after anything else (`CLOSE`,
`isRefusal` in `crdt/providers/room.ts`).

```ts
declare const closedSocket: (code: number, reason: string) => Response;
```

## COMMENT_ROW_BYTES

What one stored comment counts against a store's byte quota beyond its
text (its ids, author, times; a thread's row is counted with its first
comment).

```ts
declare const COMMENT_ROW_BYTES = 128;
```

## COMMENT_STATUS

The HTTP status of each refusal.

```ts
declare const COMMENT_STATUS: Readonly<Record<CommentRefusal, number>>;
```

## CommentActor

Who asks: a verified user, read-only or not; a moderator may delete anyone's comment.

```ts
export type CommentActor = {
    user: string;
    readOnly?: boolean;
    moderator?: boolean;
};
```

## commentAnchors

Every thread's anchor in `doc`: the runs of text its `comment:<id>` mark
covers, in document order (adjacent runs of one block joined). A split
or a merge moves the marked text, never copies it, so a thread keeps its
runs through both; a thread whose marked text was deleted has none.

```ts
declare const commentAnchors: (doc: CommentAnchorSource) => Map<string, CommentRun[]>;
```

## commentBytes

What a stored comment counts against a byte quota: its body's and its
thread's quote's UTF-8 bytes (the quote with the first comment only),
plus `COMMENT_ROW_BYTES`.

```ts
declare const commentBytes: (body: string, quote?: string) => number;
```

## CommentChange

What one request changed (the room broadcasts it, `onComment` receives it).

```ts
export type CommentChange = {
    type: 'added' | 'replied' | 'resolved' | 'reopened' | 'deleted' | 'removed';
    thread: CommentThread;
    comment: ThreadComment | null;
    user: string;
    at: number;
    seq: number;
};
```

- `type`: `added` a thread, `replied` to one, `resolved` or `reopened` one, `deleted` a reply, or `removed` a whole thread (its anchor marks go with it).
- `thread`: The thread after the change; for `removed`, as it was.
- `comment`: The comment added (`added`, `replied`) or deleted (`deleted`), else `null`.
- `user`: Who made the change.
- `seq`: The change's sequence number in its document (the thread's new `rev`).

## CommentOptions

The room's comment threads (`comments` of `AttachRoomOptions`).

```ts
export type CommentOptions = {
    onComment?: (change: CommentChange) => void | Promise<void>;
    maxLength?: number;
    maxBytes?: number;
    maxRequestsPerSecond?: number;
};
```

- `onComment`: Notifications: called after each change is stored and sent to the sockets (a thread added, a reply, a resolve or reopen, a delete), awaited before the request is answered.
- `maxLength`: The longest comment body (default and ceiling 10,000 characters).
- `maxBytes`: The bytes the room's comments may hold (default `DEFAULT_MAX_COMMENT_BYTES`, 4 MiB): each body and quote in UTF-8 plus 128 a comment.
- `maxRequestsPerSecond`: Comment requests per second (default `DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND`, a ten-second burst): over HTTP per verified user (past it, `429`), and a socket's comment messages per socket (past it, dropped).

## CommentOutcome

What a request did: `noop` when it changed nothing (resolving a resolved thread).

```ts
export type CommentOutcome = {
    status: 'applied';
    change: CommentChange;
} | {
    status: 'noop';
    thread: CommentThread;
} | {
    status: 'refused';
    reason: CommentRefusal;
    message: string;
};
```

## CommentRefusal

Why a request was refused (each an HTTP status: `COMMENT_STATUS`).

```ts
export type CommentRefusal = 'invalid' | 'read-only' | 'forbidden' | 'missing' | 'exists' | 'full';
```

## CommentRequest

A request to a document's comments, by the user a store verified.

```ts
export type CommentRequest = {
    op: 'add';
    thread: string;
    body: string;
    quote?: string;
    block?: string | null;
    comment?: string;
} | {
    op: 'reply';
    thread: string;
    body: string;
    comment?: string;
} | {
    op: 'resolve';
    thread: string;
} | {
    op: 'reopen';
    thread: string;
} | {
    op: 'delete';
    thread: string;
    comment?: string;
};
```

## CommentRun

One run of text a thread's anchor mark covers: `length` units from `offset` of `block`.

```ts
export type CommentRun = {
    block: string;
    offset: number;
    length: number;
};
```

## CommentSnapshot

The document's threads at sequence number `seq`.

```ts
export type CommentSnapshot = {
    seq: number;
    threads: CommentThread[];
};
```

## CommentThread

A comment thread: its first comment starts it, the others reply.

```ts
export type CommentThread = {
    id: string;
    block: string | null;
    quote: string;
    createdBy: string;
    createdAt: number;
    resolved: {
        by: string;
        at: number;
    } | null;
    comments: ThreadComment[];
    rev: number;
};
```

- `id`: The thread's id, which its anchor mark names (`comment:<id>`).
- `block`: The block it was started in (`null`: unknown).
- `quote`: The text it was started on, as it read then (at most `MAX_QUOTE_LENGTH`).
- `resolved`: Who resolved it and when; `null` while it is open.
- `comments`: Oldest first; never empty.
- `rev`: The sequence number of the last change to it (a later change has a greater one).

## CommitResult

What `commitMove` returns.

```ts
export type CommitResult = {
    moveId: string;
    status: 'applied' | 'refused';
    reason?: string;
};
```

## decideComment

Decide `request` by `actor`: the one rule of every comment store.

- A read-only actor changes nothing (`read-only`).
- `add` starts a thread under an id no thread holds (`exists`), with a
  first comment; `reply` adds one to a thread (`missing` without it).
- `resolve` and `reopen` are anyone's who may write (`noop` when the
  thread already is so).
- `delete` removes one comment, its author's or a moderator's
  (`forbidden`); the first comment, or no `comment`, removes the
  whole thread, its creator's or a moderator's.

Writes nothing: the store applies the change it returns.

```ts
declare const decideComment: (request: CommentRequest, actor: CommentActor, context: CommentContext) => CommentOutcome;
```

## DEFAULT_COMPACT_AFTER

Update records before the rows are merged into one snapshot.

```ts
declare const DEFAULT_COMPACT_AFTER = 500;
```

## DEFAULT_DOCUMENT_WARNING

The share of the document quota past which the room logs its size, by default.

```ts
declare const DEFAULT_DOCUMENT_WARNING = 0.8;
```

## DEFAULT_MAX_BUFFERED_BYTES

The chunk sequences in flight on all of a room's sockets together, by
default twice the frame quota (never less than one frame): each is one
buffer of its announced size, allocated at its start.

```ts
declare const DEFAULT_MAX_BUFFERED_BYTES: number;
```

## DEFAULT_MAX_COMMENT_BYTES

The bytes a room's comments hold at most, by default (4 MiB).

```ts
declare const DEFAULT_MAX_COMMENT_BYTES: number;
```

## DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND

Comment requests per second and user (or socket), by default.

```ts
declare const DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND = 2;
```

## DEFAULT_MAX_DOCUMENT_BYTES

Room quotas, each refused with `4413` (`quota: <name>`). The
document's size: what its records hold (uncompressed) and the updates the
engine holds waiting. An isolate has 128 MB, and the live document takes
18 to 46 bytes of heap per stored byte (prose to one short line per
block, its index included; `bench/room-memory.mjs`): 2 MiB is
about 100 MB at worst. Raise it for documents of long text.

```ts
declare const DEFAULT_MAX_DOCUMENT_BYTES: number;
```

## DEFAULT_MAX_INBOUND_FRAME_BYTES

The largest frame a socket may send, reassembled from chunks: room for a
whole document at the default quota (a client seeding an empty room).
Raise it with the document quota.

```ts
declare const DEFAULT_MAX_INBOUND_FRAME_BYTES: number;
```

## DEFAULT_MAX_PRESENCE_BYTES

The largest presence state a socket may publish, as JSON (a caret, a
name, a color: a few hundred bytes). A larger entry is ignored.

```ts
declare const DEFAULT_MAX_PRESENCE_BYTES: number;
```

## DEFAULT_MAX_PRESENCE_FANOUT

Presence frames the room sends a second, over all its sockets together
(a one-second burst). Within it each entry goes out at once; past it the
entries wait per recipient, the newest of each replica, and go out
together in one frame per recipient (`room.presence.fanout`). Ten
editors each moving a caret at the client's 20 Hz stay within it; fifty
would send 50,000 frames a second.

```ts
declare const DEFAULT_MAX_PRESENCE_FANOUT = 2000;
```

## DEFAULT_MAX_PRESENCE_PER_SECOND

Presence messages (entries and queries) a socket may send per second,
sustained, on a bucket of its own (a ten-second burst): a view
publishes at most every 50 ms (`presence.throttle`), and renews every
15 s. Past it, entries are coalesced and queries dropped.

```ts
declare const DEFAULT_MAX_PRESENCE_PER_SECOND = 50;
```

## DEFAULT_MAX_ROW_BYTES

Under SQLite-backed Durable Objects' 2 MB row cap, with room for the other columns.

```ts
declare const DEFAULT_MAX_ROW_BYTES: number;
```

## DEFAULT_MAX_UPDATES_PER_SECOND

Sync messages a socket may send per second, sustained; a burst of ten
seconds' worth is allowed (a reconnect, a paste). Far above typing
speed: a script should batch its edits into transactions.

```ts
declare const DEFAULT_MAX_UPDATES_PER_SECOND = 50;
```

## DEFAULT_MOVE_GRACE_DAYS

The default grace period: late edits are forwarded this long after the commit.

```ts
declare const DEFAULT_MOVE_GRACE_DAYS = 30;
```

## DEFAULT_RETENTION_DAYS

The default retention, in days (also the default purge horizon).

```ts
declare const DEFAULT_RETENTION_DAYS = 30;
```

## DEFAULT_SAVE_AFTER

ms between the first unsaved change and `onSave`.

```ts
declare const DEFAULT_SAVE_AFTER = 2000;
```

## DocumentIdentity

What the host verified. `userId` owns every Yjs client id the socket
writes under; `replica` is the client's `doc.clientID` (read it from a
query parameter with `requestedReplica`; omitted, the socket's
first presence entry binds it); `readOnly` refuses every write.

```ts
export type DocumentIdentity = {
    userId: string;
    replica?: number | null;
    readOnly?: boolean;
    expiresAt?: number | null;
};
```

- `expiresAt`: When the credential expires, ms since the epoch (a token's `exp` × 1000): the room closes the socket `4401` then, and the provider redials with its `params` read again.

## DocumentNamespace

A namespace whose objects host a document (`DocumentRoom`, or any object with `attachRoom`).

```ts
export type DocumentNamespace = {
    getByName(name: string): {
        fetch(request: Request): Promise<Response>;
    };
};
```

## DocumentRoom

The ready-made room: a Durable Object hosting one document, configured
by `vars` (`DocumentRoomEnv`). Subclass it to override `onLoad` and
`onSave`, and add your own RPC methods over `transact`/`read`. Its tables
are `rows` and `replicas` (no prefix).

```ts
export declare class DocumentRoom<Env = DocumentRoomEnv> extends DurableObject<Env> {
    readonly room: AttachedDocument;
    constructor(ctx: DurableObjectState, env: Env);
    protected onLoad(): Promise<LoadedDocument | null | undefined>;
    protected onSave(_document: SavedDocument): Promise<void>;
    protected validate(_frame: FrameValidation): boolean | void;
    protected onComment(_change: CommentChange): void | Promise<void>;
    protected log(entry: RoomLogEntry): void;
    protected semantics(): DocumentSemanticsConfig;
    protected history(): HistoryOptions | undefined;
    protected locks(): LockOptions | undefined;
    protected rooms(): MoveNamespace | undefined;
    protected now(): number;
    fetch(request: Request): Promise<Response>;
    webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void;
    webSocketClose(ws: WebSocket, code: number, reason: string): void;
    webSocketError(ws: WebSocket): void;
    alarm(): Promise<void>;
    listHistory(): Promise<HistoryEntry[]>;
    readHistory(key: string): Promise<JSONDoc | null>;
    restoreHistory(key: string, options?: {
        user?: string;
    }): Promise<RestoreResult>;
    undoRestore(options?: {
        user?: string;
    }): Promise<{
        status: 'applied' | 'noop';
    }>;
    historyWritten(): Promise<void>;
    exportBlocks(ids: string[]): ExportedBlocks;
    importBlocks(request: ImportRequest): ImportReceipt;
    commitMove(moveId: string, receipt: {
        to: string;
        ids: Record<string, string>;
    }): CommitResult;
    abortMove(moveId: string): {
        moveId: string;
        status: 'applied' | 'noop';
    };
    lateEdits(): LateEditBatch[];
    ackLateEdits(moveId: string, seq: number): void;
    applyLateEdits(batch: LateEditBatch): {
        applied: number;
        skipped: string[];
    };
    purge(): (PurgeReport & {
        horizon: number;
    }) | null;
    listComments(): CommentSnapshot;
    comment(request: CommentRequest, actor: CommentActor): Promise<CommentOutcome>;
    closeUser(userId: string, code?: number, reason?: string): {
        sockets: number;
    };
    setAccess(userId: string, access: 'write' | 'read' | 'none'): {
        sockets: number;
    };
    compact(): {
        rows: number;
    };
    metrics(): RoomMetrics;
    lastUpdated(): number | null;
    compressed(): Promise<void>;
    dropWaitingDeletes(): {
        ranges: number;
    };
    reset(): Promise<void>;
    transact<T>(fn: (facade: DocumentOperations) => T): T;
    read(): JSONDoc;
    get facade(): DocumentOperations;
    get doc(): YDoc | null;
    get failure(): Error | null;
    get refusals(): Refusal[];
    get refusalCounts(): AttachedDocument['refusalCounts'];
    get presence(): Map<number, AwarenessEntry>;
    get origin(): AttachedDocument['origin'];
    records(): ReturnType<AttachedDocument['records']>;
}
```

- `onLoad`: Retrieve — see `AttachRoomOptions.onLoad`.
- `onSave`: Save — see `AttachRoomOptions.onSave`.
- `validate`: Validate a client frame — see `AttachRoomOptions.validate`: `false` denies it, and the room writes its inverse.
- `onComment`: Notifications — see `CommentOptions.onComment`: each comment change, once stored.
- `log`: One log entry: `console.log` of it as JSON, unless the `EDYTOR_LOG` var is `off`.
- `semantics`: Block roles — see `AttachRoomOptions.semantics`.
- `history`: Version history — see `AttachRoomOptions.history`.
- `locks`: Per-block locks — see `AttachRoomOptions.locks`.
- `rooms`: The rooms blocks move to — see `AttachRoomOptions.rooms`.
- `now`: The room's clock — see `AttachRoomOptions.now`.
- `alarm`: Runs the room's tasks: `onSave`, the history slots, the purge.
- `listHistory`: The versions, newest first, also over RPC — see `AttachedDocument.listHistory`.
- `readHistory`: A version as JSON, also over RPC — see `AttachedDocument.readHistory`.
- `restoreHistory`: Restore a version, also over RPC — see `AttachedDocument.restoreHistory`.
- `undoRestore`: Undo the last restore, also over RPC — see `AttachedDocument.undoRestore`.
- `historyWritten`: The version writes in flight, also over RPC — see `AttachedDocument.historyWritten`.
- `exportBlocks`: Export blocks for a move, also over RPC — see `AttachedDocument.exportBlocks`.
- `importBlocks`: Import a move's blocks, also over RPC — see `AttachedDocument.importBlocks`.
- `commitMove`: Commit a move, also over RPC — see `AttachedDocument.commitMove`.
- `abortMove`: Abort an export, also over RPC — see `AttachedDocument.abortMove`.
- `lateEdits`: The late edits waiting, also over RPC — see `AttachedDocument.lateEdits`.
- `ackLateEdits`: Drop forwarded late edits, also over RPC — see `AttachedDocument.ackLateEdits`.
- `applyLateEdits`: Apply late edits, also over RPC — see `AttachedDocument.applyLateEdits`.
- `purge`: Run the purge task now, also over RPC — see `AttachedDocument.purge`.
- `listComments`: Every comment thread, also over RPC — see `AttachedDocument.listComments`.
- `comment`: One comments request, also over RPC — see `AttachedDocument.comment`.
- `closeUser`: Close every socket of a user, also over RPC — see `AttachedDocument.closeUser`.
- `setAccess`: Change a user's access on its sockets, also over RPC — see `AttachedDocument.setAccess`.
- `compact`: Compaction, also over RPC.
- `metrics`: The room's counters, also over RPC — see `RoomMetrics`.
- `lastUpdated`: When the room last stored a change, also over RPC — see `AttachedDocument.lastUpdated`.
- `compressed`: The snapshot compression in flight, also over RPC — see `AttachedDocument.compressed`.
- `dropWaitingDeletes`: Drop the stored waiting deletes, also over RPC — see `AttachedDocument.dropWaitingDeletes`.
- `reset`: Generation cutover, also over RPC — see `AttachedDocument.reset`.
- `transact`: Server-side edit — see `AttachedDocument.transact`.
- `read`: The document as JSON.

## DocumentRoomEnv

Optional knobs (strings, as `vars` arrive): row size, outgoing frame
limit and compaction threshold. The defaults are the platform limits.

```ts
export type DocumentRoomEnv = {
    EDYTOR_MAX_ROW_BYTES?: string | number;
    EDYTOR_MAX_FRAME_BYTES?: string | number;
    EDYTOR_COMPACT_AFTER?: string | number;
    EDYTOR_SAVE_AFTER?: string | number;
    EDYTOR_MAX_DOCUMENT_BYTES?: string | number;
    EDYTOR_DOCUMENT_WARNING?: string | number;
    EDYTOR_MAX_INBOUND_FRAME_BYTES?: string | number;
    EDYTOR_MAX_BUFFERED_BYTES?: string | number;
    EDYTOR_MAX_UPDATES_PER_SECOND?: string | number;
    EDYTOR_MAX_PRESENCE_BYTES?: string | number;
    EDYTOR_MAX_PRESENCE_PER_SECOND?: string | number;
    EDYTOR_MAX_PRESENCE_FANOUT?: string | number;
    EDYTOR_LOG?: string;
    EDYTOR_PURGE_AFTER_DAYS?: string | number;
    EDYTOR_HISTORY?: KVLike | R2BucketLike | 'room';
    EDYTOR_HISTORY_RETENTION_DAYS?: string | number;
    EDYTOR_HISTORY_TIME_ZONE?: string;
    EDYTOR_LOCKS?: string;
    EDYTOR_ROOMS?: MoveNamespace;
    EDYTOR_COMMENTS?: string;
    EDYTOR_MAX_COMMENT_BYTES?: string | number;
    EDYTOR_MAX_COMMENT_REQUESTS_PER_SECOND?: string | number;
};
```

- `EDYTOR_DOCUMENT_WARNING`: The share of the document quota past which the room logs its size (`documentWarning`).
- `EDYTOR_LOG`: `off`: the room logs nothing (default: one JSON line per compaction, quota hit, denial, fault).
- `EDYTOR_PURGE_AFTER_DAYS`: Days after which deleted content is purged (default: the history retention, else 30); `off` never purges.
- `EDYTOR_HISTORY`: Where the room keeps its version history (`history()`): a KV namespace or an R2 bucket binding, or the string `room` (the room's own storage, `roomHistory`).
- `EDYTOR_HISTORY_RETENTION_DAYS`: Days a version is kept (default 30).
- `EDYTOR_HISTORY_TIME_ZONE`: The IANA time zone of the history's half-day slots (default `UTC`).
- `EDYTOR_LOCKS`: Per-block locks (`locks()`): the data key naming a block's owner, e.g.
- `EDYTOR_ROOMS`: The namespace of the rooms blocks move to (`rooms()`): the room forwards late edits of moved blocks there itself.
- `EDYTOR_COMMENTS`: `off`: the room keeps no comment threads (its comments requests answer `404`).
- `EDYTOR_MAX_COMMENT_BYTES`: The bytes the room's comments may hold (`CommentOptions.maxBytes`).
- `EDYTOR_MAX_COMMENT_REQUESTS_PER_SECOND`: Comment requests per second (`CommentOptions.maxRequestsPerSecond`).

## ExpiredCredential

A credential that was valid but has expired: the dial is closed `4401`,
which the provider retries with its `params` read again.

```ts
export type ExpiredCredential = {
    expired: true;
};
```

## ExportedBlocks

What `exportBlocks` returns.

```ts
export type ExportedBlocks = {
    moveId: string;
    blocks: JSONBlock[];
};
```

## forwardLateEdits

Forward the late edits document `from` holds (`room.move.late`) to their
destinations and acknowledge them — for a room without `rooms()`, which
cannot reach the destination itself. Returns how many batches went.

```ts
declare const forwardLateEdits: (namespace: MoveNamespace, from: string) => Promise<number>;
```

## FrameValidation

One client frame the room applied, as `validate` reads it.

```ts
export type FrameValidation = {
    user: string;
    replica: number | null;
    touched: string[];
    dataChanged: boolean;
    before: (id: string) => ValidatedBlock | null;
    after: (id: string) => ValidatedBlock | null;
    facade: DocumentOperations;
};
```

- `user`: The sender's verified user.
- `replica`: The sender's replica (client id), when bound.
- `touched`: The blocks the frame changed, from the facade's change report: shown or hidden (with their subtrees), given another parent or moved among their siblings (not shifted by a sibling's move), retyped, given other data, or other content.
- `dataChanged`: The frame changed the document's own data.
- `before`: A block as it was before the frame (`null`: it did not show).
- `after`: A block as it is now (`null`: it does not show).
- `facade`: The live document.

## HISTORY_MAX_VALUE_BYTES

KV's largest value: a version above it is skipped, never split.

```ts
declare const HISTORY_MAX_VALUE_BYTES: number;
```

## HistoryEntry

One stored version, as `listHistory` returns it.

```ts
export type HistoryEntry = {
    key: string;
    date: string;
    slot: Slot;
    bytes: number;
    blocks: number;
    editors: string[];
    more: number;
    at: number;
    expiresAt: number | null;
};
```

- `key`: The store key (`history/<room>/<YYYY-MM-DD>-am|pm`).
- `date`: The local date, `YYYY-MM-DD`, in the room's time zone.
- `bytes`: Stored (compressed) bytes.
- `blocks`: Visible blocks.
- `editors`: The verified users whose edits the room stored during the slot.
- `more`: Editors left out to keep the metadata under KV's limit.
- `at`: When the version was written (ms since the epoch).
- `expiresAt`: When it expires (ms since the epoch), `null` when its store does not say.

## HistoryMetadata

A version's metadata, as the room writes it to the store.

```ts
export type HistoryMetadata = {
    bytes: number;
    blocks: number;
    editors: string[];
    more?: number;
    at: number;
};
```

## HistoryOptions

The `history` option: where and how long the room keeps its versions.

```ts
export type HistoryOptions = {
    store: HistoryStore | HistoryStoreFactory;
    retentionDays?: number;
    timeZone?: string;
    maxValueBytes?: number;
};
```

- `store`: The store: a `HistoryStore` (`kvHistory`, `r2History` or your own) or `roomHistory``()`.
- `retentionDays`: How long a version is kept, in days (default 30).
- `timeZone`: The IANA time zone whose noon and midnight end the slots (default `'UTC'`).
- `maxValueBytes`: The largest value written, in bytes; can only lower the store's own `maxValueBytes`.

## HistoryRoomStorage

What a `HistoryStoreFactory` gets: the room's own storage.

```ts
export type HistoryRoomStorage = {
    readonly sql: SqlStorage;
    transactionSync<T>(closure: () => T): T;
    readonly tablePrefix: string;
};
```

- `tablePrefix`: The room's table prefix (`''` for `DocumentRoom`, `'edytor_'` by default for `attachRoom`).

## HistoryStore

Where a room keeps its versions (`room.history.store`). Any object with
these members works; the room owns everything else: the keys (one room's
share a prefix), the values (compressed bytes, at most `maxValueBytes`),
the metadata, and the retention — it never lists or reads a version past
its `expiresAt`, and, unless the store expires values itself
(`nativeTtl`), its alarm deletes each one when it expires.

```ts
export type HistoryStore = {
    readonly maxValueBytes: number;
    readonly nativeTtl?: boolean;
    put(key: string, value: Uint8Array, options: {
        expiresAt: number;
        metadata: HistoryMetadata;
    }): Promise<void>;
    get(key: string): Promise<{
        value: Uint8Array;
        expiresAt: number | null;
    } | null>;
    list(prefix: string, cursor?: string): Promise<{
        entries: HistoryStoreRecord[];
        cursor?: string;
    }>;
    delete(key: string): Promise<void>;
};
```

- `maxValueBytes`: The largest value the store takes: a larger version is skipped (logged `history`), never split.
- `nativeTtl`: The store deletes a value at its `expiresAt` itself (KV's TTL): the room's alarm never deletes.
- `put`: Store `value` at `key` (replacing any value there) with its expiry and metadata.
- `get`: The value at `key`, with its expiry when the store knows it, or `null`.
- `list`: One page of the keys starting with `prefix`; `cursor` continues a listing, and is absent on its last page.
- `delete`: Delete `key` (a missing key is no error).

## HistoryStoreFactory

A store built from the room's own storage at first use (`roomHistory`).

```ts
export type HistoryStoreFactory = (room: HistoryRoomStorage) => HistoryStore;
```

## HistoryStoreRecord

One stored version as a `HistoryStore` lists it.

```ts
export type HistoryStoreRecord = {
    key: string;
    expiresAt: number | null;
    metadata: unknown;
};
```

- `expiresAt`: When it expires (ms since the epoch); `null` when the store does not know (it never expires by the room).
- `metadata`: The metadata the room wrote with it (`HistoryMetadata`).

## IDENTITY_HEADERS

Headers carrying the identity `routeDocumentSocket` verified — never the
client's. The user id is percent-encoded (`encodeURIComponent`): a header
value is trimmed and cannot carry every character, and a user id is any
string.

```ts
declare const IDENTITY_HEADERS: {
    readonly user: "X-Edytor-User";
    readonly replica: "X-Edytor-Replica";
    readonly access: "X-Edytor-Access";
    readonly expires: "X-Edytor-Expires";
};
```

## ImportReceipt

What `importBlocks` returns: the acknowledgement `commitMove` needs.

```ts
export type ImportReceipt = {
    moveId: string;
    status: 'applied' | 'refused';
    ids: Record<string, string>;
    reason?: string;
};
```

- `ids`: Each moved block's id in the destination (a source id it already held is renamed).

## ImportRequest

What `importBlocks` takes.

```ts
export type ImportRequest = {
    moveId: string;
    from: string;
    blocks: JSONBlock[];
    dest: MoveDestination;
};
```

- `from`: The source document's id.

## kvHistory

Versions in a Workers KV namespace (the room history's first store): KV's
TTL expires them (`nativeTtl`, `expirationTtl` = the retention, at least
KV's 60 s), its listing's `expiration` is each one's `expiresAt`, its
metadata the room's. Values up to KV's 25 MiB. Versions written by the
pre-releases that took a bare namespace as the store read as they are.

```ts
declare const kvHistory: (namespace: KVLike) => HistoryStore;
```

## KVLike

The structural subset of a KV namespace `kvHistory` uses — a
Workers `KVNamespace` binding satisfies it, and so does an in-memory
fake. As `history.store` it goes through `kvHistory`.

```ts
export type KVLike = {
    put(key: string, value: ArrayBuffer | ArrayBufferView | string, options?: {
        expirationTtl?: number;
        metadata?: unknown;
    }): Promise<unknown>;
    get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
    list(options?: {
        prefix?: string;
        cursor?: string;
        limit?: number;
    }): Promise<{
        keys: Array<{
            name: string;
            expiration?: number;
            metadata?: unknown;
        }>;
        list_complete: boolean;
        cursor?: string;
    }>;
    delete?(key: string): Promise<unknown>;
};
```

## LateEdit

One block's late edit: the state last sent (`base`) and the source's now (`src`).

```ts
export type LateEdit = {
    block: string;
    base: MovedState;
    src: MovedState;
};
```

## LateEditBatch

A batch of late edits of one move, numbered from 1 (`to`: its destination, as the source lists it).

```ts
export type LateEditBatch = {
    moveId: string;
    from: string;
    to?: string;
    seq: number;
    edits: LateEdit[];
};
```

## LoadedDocument

What `onLoad` may return: JSON, a bare v14 update, or `{ update, replicas }` (a `SavedDocument`).

```ts
export type LoadedDocument = JSONDoc | Uint8Array | Pick<SavedDocument, 'update' | 'replicas'>;
```

## lockedBlocks

The `validate` hook of per-block locks: pass it as `attachRoom`'s
`validate`, return it from `DocumentRoom.locks()`, or call it from your
own `validate` (`lockedBlocks(options)(frame) && …`).

```ts
declare const lockedBlocks: (options?: LockOptions) => (frame: FrameValidation) => boolean;
```

## LockOptions

What `lockedBlocks` reads.

```ts
export type LockOptions = {
    key?: string;
    subtree?: boolean;
    bypass?: (user: string) => boolean;
};
```

- `key`: The data key naming a block's owner (default `'lockedBy'`): a string user id locks it.
- `subtree`: A lock also holds the blocks displayed under the locked one (default `false`).
- `bypass`: Users who edit locked blocks anyway (an admin): `true` admits the frame's user.

## MAX_COMMENT_LENGTH

The longest comment body, in UTF-16 code units.

```ts
declare const MAX_COMMENT_LENGTH = 10000;
```

## MAX_COMMENTS_PER_THREAD

Comments one thread holds at most (its first included).

```ts
declare const MAX_COMMENTS_PER_THREAD = 500;
```

## MAX_REFUSALS

Refusals kept in `refusals` (the newest); `refusalCounts` counts every one.

```ts
declare const MAX_REFUSALS = 100;
```

## MAX_THREADS

Threads one document holds at most, resolved ones included.

```ts
declare const MAX_THREADS = 5000;
```

## MAX_WAITING_DELETES

Delete ranges the room keeps waiting for items it does not hold (a
relayer's delete of an author's edit a restore lost). Every update the
engine applies re-reads them, so they are capped.

```ts
declare const MAX_WAITING_DELETES = 1024;
```

## moveBlocksBetweenRooms

Move blocks `ids` of document `from` to document `to`, under
`dest.parent` at `dest.index` (`room.move`): export at the source,
import at the destination, then — with its acknowledgement — delete at
the source. A failure before the import leaves both documents as they
were (the export is aborted); a failure of the commit leaves the blocks
in both and throws: call it again with the same `moveId` (`commitMove` is
idempotent), or `abortMove`. Returns the destination ids.

```ts
declare const moveBlocksBetweenRooms: (namespace: MoveNamespace, move: {
    from: string;
    to: string;
    ids: string[];
    dest: MoveDestination;
}) => Promise<ImportReceipt>;
```

## MoveDestination

Where imported blocks land in the destination: under `parent` at `index`.

```ts
export type MoveDestination = {
    parent: string | null;
    index: number;
};
```

## MovedState

One block's state as a late edit carries it: its content and data.

```ts
export type MovedState = {
    content: NonNullable<JSONBlock['content']>;
    data: Record<string, unknown>;
};
```

## MoveNamespace

The rooms a move talks to: a Durable Object namespace of `DocumentRoom`s (or a subclass).

```ts
export type MoveNamespace = {
    getByName(name: string): MoveRoom;
};
```

## MoveRoom

The RPC surface of a room a move talks to (a `DocumentRoom` stub satisfies it).

```ts
export type MoveRoom = {
    exportBlocks(ids: string[]): Promise<ExportedBlocks> | ExportedBlocks;
    importBlocks(request: ImportRequest): Promise<ImportReceipt> | ImportReceipt;
    commitMove(moveId: string, receipt: {
        to: string;
        ids: Record<string, string>;
    }): Promise<CommitResult> | CommitResult;
    abortMove(moveId: string): Promise<unknown> | unknown;
    applyLateEdits(batch: LateEditBatch): Promise<{
        applied: number;
        skipped: string[];
    }> | {
        applied: number;
        skipped: string[];
    };
    lateEdits(): Promise<LateEditBatch[]> | LateEditBatch[];
    ackLateEdits(moveId: string, seq: number): Promise<unknown> | unknown;
};
```

## noTimers

Run an entry point with timers forbidden — a pending timer keeps a Durable Object from hibernating.

```ts
declare const noTimers: <T>(fn: () => T) => T;
```

## PROBE_HEADER

The header `routeDocumentSocket` sets on an authorized probe: `lastUpdated` or `snapshot`.

```ts
declare const PROBE_HEADER = "X-Edytor-Probe";
```

## PURGE_ORIGIN

The transaction origin of the room's purge: tracked by no history.

```ts
declare const PURGE_ORIGIN: unique symbol;
```

## PurgeReport

What one purge wrote.

```ts
export type PurgeReport = {
    removed: number;
    emptied: number;
    marks: number;
    records: number;
    candidates: number;
    claims: number;
    merged: number;
};
```

- `removed`: Blocks removed: registry entry deleted, node collected.
- `emptied`: Deleted blocks kept (they delimit a live text, or a block is placed under them) whose stream, data and claims were deleted.
- `marks`: Text delete mark records deleted.
- `records`: Restoration records deleted (their copies collected).
- `candidates`: Placement candidates dropped (runner-ups of an old, accepted winner).
- `claims`: Claims naming a removed block, deleted from the blocks that hold them.
- `merged`: Attribution records of blocks merged away before the horizon (their text shows in the block that claimed them, which unioned their contributors at the merge): deleted, their registry entries kept.

## R2_HISTORY_MAX_VALUE_BYTES

The largest value `r2History` writes (R2 takes more; the room holds a version in memory).

```ts
declare const R2_HISTORY_MAX_VALUE_BYTES: number;
```

## R2BucketLike

The structural subset of an R2 bucket `r2History` uses (a Workers `R2Bucket` binding satisfies it).

```ts
export type R2BucketLike = {
    put(key: string, value: ArrayBuffer | ArrayBufferView, options?: {
        customMetadata?: Record<string, string>;
    }): Promise<unknown>;
    get(key: string): Promise<{
        arrayBuffer(): Promise<ArrayBuffer>;
        customMetadata?: Record<string, string>;
    } | null>;
    list(options?: {
        prefix?: string;
        cursor?: string;
        limit?: number;
        include?: Array<'httpMetadata' | 'customMetadata'>;
    }): Promise<{
        objects: Array<{
            key: string;
            customMetadata?: Record<string, string>;
        }>;
        truncated: boolean;
        cursor?: string;
    }>;
    delete(keys: string | string[]): Promise<unknown>;
};
```

## r2History

Versions in an R2 bucket: one object per version, the room's metadata
and the expiry in its custom metadata (`edytor`, `edytor-expires`), a
listing by prefix. R2 expires nothing by itself here, so the room's
alarm deletes each version at its expiry (an R2 lifecycle rule on the
`history/` prefix may also do it, a day late at most). Values up to
`R2_HISTORY_MAX_VALUE_BYTES`.

```ts
declare const r2History: (bucket: R2BucketLike) => HistoryStore;
```

## Refusal

```ts
export type Refusal = {
    reason: 'generation' | 'schema' | 'malformed' | 'container' | 'identity' | 'replica' | 'read-only' | 'waiting' | 'storage' | 'internal' | 'quota' | 'access' | 'presence' | 'comments' | 'mark' | 'forged' | 'denied' | 'orphan' | 'relayed' | 'history';
    detail: unknown;
};
```

## ReplicaOwner

A client id and the user who owns it (the room's replica registry). An
empty `user` marks an id a restore without a registry left unowned: its
first authenticated writer claims it.

```ts
export type ReplicaOwner = {
    replica: number;
    user: string;
};
```

## requestedReplica

The replica a client asked for in `?replica=<doc.clientID>` (or `param`), or `null`.

```ts
declare const requestedReplica: (request: Request, param?: string) => number | null;
```

## RESTORE_ORIGIN

The transaction origin of a history restore: the room's restore history tracks it.

```ts
declare const RESTORE_ORIGIN: unique symbol;
```

## RestoreResult

What `restoreHistory` did (`room.history.restore`): `refused` when the room holds no such version.

```ts
export type RestoreResult = {
    status: 'applied' | 'noop' | 'refused';
    key: string;
    revived?: number;
    moved?: number;
    rewritten?: number;
    created?: number;
    deleted?: number;
};
```

## ROOM_HISTORY_MAX_BYTES

```ts
declare const ROOM_HISTORY_MAX_BYTES: number;
```

## ROOM_HISTORY_MAX_VALUE_BYTES

`roomHistory`'s defaults: the largest version, and the most its table holds.

```ts
declare const ROOM_HISTORY_MAX_VALUE_BYTES: number;
```

## ROOM_ORIGIN

The transaction origin of the room's own edits (`transact`, `onLoad` seeds).

```ts
declare const ROOM_ORIGIN: unique symbol;
```

## roomHistory

Versions in the room's own SQLite storage, in its `history` table
(`edytor_history` with `attachRoom`'s default prefix): no binding.
A version is stored atomically (one `transactionSync`, split into rows
of 1 MiB under the platform's 2 MB). It is counted apart from the
document quota (`maxDocumentBytes` measures what clients send; history
is the room's own), against its own cap, `maxBytes` (default
`ROOM_HISTORY_MAX_BYTES`): a version that would pass it deletes
the oldest versions first. The room's alarm deletes each version at its
expiry. `maxValueBytes` (default `ROOM_HISTORY_MAX_VALUE_BYTES`)
caps one version. A generation cutover (`reset()`) keeps the table.

```ts
declare const roomHistory: (options?: {
    maxBytes?: number;
    maxValueBytes?: number;
}) => HistoryStoreFactory;
```

## RoomLogEntry

One line of the room's log: a compaction, a quota hit, a denial, a fault.

```ts
export type RoomLogEntry = {
    edytor: 'compaction';
    ms: number;
    bytes: number;
    records: number;
    rows: number;
} | {
    edytor: 'history';
    key: string;
    bytes: number;
    editors: number;
} | {
    edytor: 'restore';
    key: string;
    user: string | null;
    undo: boolean;
} | {
    edytor: 'comment';
    type: CommentChange['type'];
    thread: string;
    user: string;
} | ({
    edytor: 'purge';
    horizon: number;
    bytes: number;
} & PurgeReport) | {
    edytor: 'quota';
    user: string;
    quota: string;
} | {
    edytor: 'size';
    bytes: number;
    limit: number;
    share: number;
} | {
    edytor: 'denied';
    user: string;
    touched: number;
} | {
    edytor: 'semantics';
    user: string;
    kinds: string[];
} | {
    edytor: 'fault';
    reason: 'storage' | 'internal';
    detail: string;
} | {
    edytor: 'move';
    moveId: string;
    role: 'out' | 'in';
    peer: string;
    blocks: number;
} | {
    edytor: 'late';
    moveId: string;
    seq: number;
    edits: number;
    structural: number;
    forwarded: boolean;
};
```

## RoomMetrics

The room's counters, since this instance started (memory: a wake
starts them again), read with `metrics()` (also over RPC). Times are
what the runtime's clock reports: Workers advance it only across I/O, so
in production synchronous work (a compaction, a fold) often reads `0`;
sizes and counts are exact.

```ts
export type RoomMetrics = {
    documentBytes: number;
    documentLimit: number;
    storedBytes: number;
    records: number;
    rows: number;
    updateRecords: number;
    waitingBytes: number;
    sockets: number;
    buffered: {
        sequences: number;
        bytes: number;
    };
    compaction: Timing & {
        lastBytes: number;
    };
    fold: Timing;
    fanOut: {
        messages: number;
        bytes: number;
    };
    quotaHits: number;
    validationDenials: number;
    refusals: Partial<Record<Refusal['reason'], number>>;
    history: {
        written: number;
        skipped: number;
        lastKey: string | null;
    };
    purge: PurgeReport & {
        runs: number;
        horizon: number | null;
    };
    since: number;
};
```

- `documentBytes`: The document's size as the document quota measures it: its records, uncompressed.
- `documentLimit`: The document quota (`maxDocumentBytes`): writes past it are refused (`4413`).
- `storedBytes`: What its rows take in storage (a snapshot compressed).
- `records`: Logical records stored (the generation record included), and their rows.
- `updateRecords`: Update records since the last compaction (compaction runs at `compactAfter`).
- `waitingBytes`: Bytes of updates held waiting for a dependency (in memory, never stored).
- `sockets`: The document's open sockets.
- `buffered`: Chunk sequences in flight on every socket, and the bytes buffered for them (`maxBufferedBytes`).
- `compaction`: Compactions run, with their time.
- `fold`: Client sync frames folded (applied, indexed, stored and relayed), with their time.
- `fanOut`: Frames sent to other sockets (relays, presence, acknowledgements to others), and their bytes.
- `quotaHits`: Writes refused by a quota (`quota` refusals) and frames `validate` denied.
- `refusals`: Every refusal, by reason (as `refusalCounts`).
- `history`: Versions written to the history, skipped (`history` refusals), and the last key written.
- `purge`: Purges run and what they wrote, summed; the last horizon purged (ms since the epoch).
- `since`: When this instance started (ms since the epoch).

## routeDocumentComments

The host Worker's door to a document's comment threads, authorized as
`routeDocumentSocket` authorizes a dial, forwarding only the
verified identity:

- `GET <url>` → every thread and the last change's sequence number
  (`{ seq, threads }`), as JSON;
- `POST <url>` with a JSON body (`{ op: 'add' | 'reply' | 'resolve' |
  'reopen' | 'delete', thread, … }`) → what it did (`{ status:
  'applied', change }` or `{ status: 'noop', thread }`), as the
  verified user.

A refusal is an HTTP status: `400` (an invalid document id or request),
`401` (`{ expired: true }`), `403` (denied, a read-only identity's
write, another user's comment, an `Origin` that `options.allowedOrigins`
does not list), `404` (no such thread or comment, or the room keeps no
comments), `405` (another method), `409` (a thread or comment id taken),
`413` (a body over 64 KiB, too many threads or comments, or the room's
comment bytes full), `429` (the user past the room's comment rate).

```ts
export declare function routeDocumentComments(request: Request, rooms: DocumentNamespace, documentId: string, authorize: AuthorizeDocumentSocket, options?: RouteDocumentOptions): Promise<Response>;
```

## routeDocumentHistory

The host Worker's door to a document's version history,
authorized as `routeDocumentSocket` authorizes a dial, forwarding
only the verified identity:

- `GET <url>` → the versions, newest first (`listHistory`), as JSON;
- `GET <url>?key=<key>` → that version as JSON (`readHistory`), `404` when
  the room holds none;
- `POST <url>?restore=<key>` → restore it as a forward edit
  (`restoreHistory`, as the verified user), `404` when the room holds none;
- `POST <url>?undo` → undo the last restore (`undoRestore`).

A refusal is an HTTP status: `400` (an invalid document id), `401`
(`{ expired: true }`), `403` (denied, or a read-only identity's
restore, or an `Origin` that `options.allowedOrigins` does not list),
`404` (the room keeps no history), `405` (another method).

```ts
export declare function routeDocumentHistory(request: Request, rooms: DocumentNamespace, documentId: string, authorize: AuthorizeDocumentSocket, options?: RouteDocumentOptions): Promise<Response>;
```

## RouteDocumentOptions

Options of `routeDocumentSocket` and `routeDocumentHistory`.

```ts
export type RouteDocumentOptions = {
    allowedOrigins?: readonly string[] | ((origin: string) => boolean);
};
```

- `allowedOrigins`: The page origins (`https://app.example.com`, scheme, host and port) whose browser requests may reach the document, or a predicate over the `Origin` header.

## routeDocumentSocket

```ts
export declare function routeDocumentSocket(request: Request, rooms: DocumentNamespace, documentId: string, authorize: AuthorizeDocumentSocket, options?: RouteDocumentOptions): Promise<Response>;
```

## SavedDocument

The document as `onSave` receives it.

```ts
export type SavedDocument = {
    value: JSONDoc;
    update: Uint8Array;
    replicas: ReplicaOwner[];
};
```

- `value`: The document as JSON.
- `update`: The full v14 state (`onLoad` takes it back).
- `replicas`: Who owns each client id, read with `update`.

## SOCKET_TAG

The tag of the document's sockets: other sockets of the object are left to you.

```ts
declare const SOCKET_TAG = "edytor";
```

## SocketIdentity

What a socket is bound to: its verified user, its replica (Yjs client id), its access.

```ts
export type SocketIdentity = {
    user: string;
    replica: number | null;
    readOnly: boolean;
    expiresAt?: number | null;
};
```

- `replica`: `null` until bound — by `authorize`, or by the socket's first presence entry.
- `expiresAt`: When its credential expires (ms since the epoch, the room's clock): the room closes it `4401` then (`room.access`).

## StoredRecord

A stored record, reassembled: its kind, record number and bytes.

```ts
export type StoredRecord = {
    kind: RowKind;
    record: number;
    bytes: Uint8Array<ArrayBuffer>;
};
```

## ThreadComment

One comment of a thread.

```ts
export type ThreadComment = {
    id: string;
    author: string;
    body: string;
    createdAt: number;
};
```

- `author`: The verified user who wrote it.
- `body`: Plain text (line breaks kept).
- `createdAt`: ms since the epoch.

## Timing

A duration series: how many, their total and longest (ms; see `RoomMetrics`).

```ts
export type Timing = {
    count: number;
    totalMs: number;
    maxMs: number;
    lastMs: number;
};
```

## ValidatedBlock

A block as `validate` reads it, before or after a frame.

```ts
export type ValidatedBlock = {
    id: string;
    type: string;
    data: Record<string, unknown>;
    content: NonNullable<JSONBlock['content']>;
    parent: string | null;
};
```

- `parent`: Its display parent (`null`: the document's root).
