edytor/cloudflare
Every name edytor/cloudflare exports, with its declaration and documentation (generated from the published types).
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.
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 newestMAX_REFUSALSrefusals 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;nullwhen the stored container was refused.facade: The facade over the live document.transact: Manipulate: runfnon 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 fromonLoad— reseed it from JSON.records: Reassembled logical records in write order (of one kind, withonly); 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, + onependingrecord of the stored deletes still waiting.lastUpdated: When the room last stored a change (ms since the epoch), ornullfor 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: Versionkeyas JSON (a preview), ornullwhen this room holds no such version.restoreHistory: Restore versionkeyas 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 stepundoRestoreundoes.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 byactor(also over RPC): add a thread, reply, resolve, reopen or delete, under the rules every store applies.exportBlocks: Export blocksidsfor 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) underdest.parentatdest.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 throughseq: 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 leastpurgeAfterDaysold — 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 — seeRoomMetrics.dropWaitingDeletes: Drop every waiting delete, stored or in memory (callable over RPC): the way out when a writer filledMAX_WAITING_DELETESwith deletes of registered ids’ items, which compaction keeps.closeUser: Revoke: close every socket ofuserId(also over RPC),4403(access revoked, final for the provider) unless you pass anothercode(4401redials with freshparams;1011redials too).setAccess: ChangeuserId’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 socket1012(access changed), which its provider redials, soauthorizegrants the new access;'none'closes every socket4403(access revoked), ascloseUser.owns: Iswsone 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.
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).
declare const attachRoom: (host: DurableObject<any>, options?: AttachRoomOptions) => AttachedDocument;
AttachRoomOptions
What attachRoom (and DocumentRoom) takes.
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,saveAfterms after the first unsaved change, on the object’s alarm (a throw is retried by the platform).saveAfter: ms between the first unsaved change andonSave(defaultDEFAULT_SAVE_AFTER).compactAfter: Update records before the rows are merged into one snapshot (defaultDEFAULT_COMPACT_AFTER).maxRowBytes: Largest stored row; can only be lowered (defaultDEFAULT_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 (defaultDEFAULT_MAX_DOCUMENT_BYTES).documentWarning: The share ofmaxDocumentBytespast which the room logs the document’s size (size, once per crossing), above 0 and at most 1 (defaultDEFAULT_DOCUMENT_WARNING, 0.8).maxInboundFrameBytes: Largest frame a socket may send, reassembled (defaultDEFAULT_MAX_INBOUND_FRAME_BYTES).maxBufferedBytes: The chunk sequences in flight on every socket together (default twicemaxInboundFrameBytes, never less than it): a sequence that would pass it closes its socket1011(room busy), and its provider redials.maxUpdatesPerSecond: Sync messages per second a socket may send (defaultDEFAULT_MAX_UPDATES_PER_SECOND).maxPresenceBytes: Largest presence state a socket may publish, as JSON (defaultDEFAULT_MAX_PRESENCE_BYTES).maxPresencePerSecond: Presence messages per second a socket may send (defaultDEFAULT_MAX_PRESENCE_PER_SECOND).maxPresenceFanout: Presence frames per second the room sends over all its sockets (defaultDEFAULT_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 underkey(defaultlockedBy) is that user’s alone — a frame from anyone else touching it is denied and compensated, asvalidate’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’sforwardLateEdits.log: The room’s log: one entry per compaction, quota hit, denial or fault.semantics: The block rolestransactedits obey — the document semantics your clients’ plugins declare (defaultTypealso names the block an empty room is seeded with).history: Version history, in the storeHistoryOptions.storenames (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, intimeZone— when it changed, forretentionDays; read and restore them withlistHistory,readHistory,restoreHistoryandundoRestore(orrouteDocumentHistory).purgeAfterDays: Days after which deleted content is purged from the room and every replica (room.purge.*): default the history’sretentionDays, else 30;falsenever purges.now: The room’s clock, ms since the epoch (defaultDate.now): its slots, epochs and alarm read it.comments: Comment threads (room.comments): kept in the room’sthreadsandcommentstables, read and written throughrouteDocumentComments(orlistCommentsandcommentover 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).
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).
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).
declare const COMMENT_ROW_BYTES = 128;
COMMENT_STATUS
The HTTP status of each refusal.
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.
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.
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.
declare const commentBytes: (body: string, quote?: string) => number;
CommentChange
What one request changed (the room broadcasts it, onComment receives it).
export type CommentChange = {
type: 'added' | 'replied' | 'resolved' | 'reopened' | 'deleted' | 'removed';
thread: CommentThread;
comment: ThreadComment | null;
user: string;
at: number;
seq: number;
};
type:addeda thread,repliedto one,resolvedorreopenedone,deleteda reply, orremoveda whole thread (its anchor marks go with it).thread: The thread after the change; forremoved, as it was.comment: The comment added (added,replied) or deleted (deleted), elsenull.user: Who made the change.seq: The change’s sequence number in its document (the thread’s newrev).
CommentOptions
The room’s comment threads (comments of AttachRoomOptions).
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 (defaultDEFAULT_MAX_COMMENT_BYTES, 4 MiB): each body and quote in UTF-8 plus 128 a comment.maxRequestsPerSecond: Comment requests per second (defaultDEFAULT_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).
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).
export type CommentRefusal = 'invalid' | 'read-only' | 'forbidden' | 'missing' | 'exists' | 'full';
CommentRequest
A request to a document’s comments, by the user a store verified.
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.
export type CommentRun = {
block: string;
offset: number;
length: number;
};
CommentSnapshot
The document’s threads at sequence number seq.
export type CommentSnapshot = {
seq: number;
threads: CommentThread[];
};
CommentThread
A comment thread: its first comment starts it, the others reply.
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 mostMAX_QUOTE_LENGTH).resolved: Who resolved it and when;nullwhile 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.
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). addstarts a thread under an id no thread holds (exists), with a first comment;replyadds one to a thread (missingwithout it).resolveandreopenare anyone’s who may write (noopwhen the thread already is so).deleteremoves one comment, its author’s or a moderator’s (forbidden); the first comment, or nocomment, removes the whole thread, its creator’s or a moderator’s.
Writes nothing: the store applies the change it returns.
declare const decideComment: (request: CommentRequest, actor: CommentActor, context: CommentContext) => CommentOutcome;
DEFAULT_COMPACT_AFTER
Update records before the rows are merged into one snapshot.
declare const DEFAULT_COMPACT_AFTER = 500;
DEFAULT_DOCUMENT_WARNING
The share of the document quota past which the room logs its size, by default.
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.
declare const DEFAULT_MAX_BUFFERED_BYTES: number;
DEFAULT_MAX_COMMENT_BYTES
The bytes a room’s comments hold at most, by default (4 MiB).
declare const DEFAULT_MAX_COMMENT_BYTES: number;
DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND
Comment requests per second and user (or socket), by default.
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.
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.
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.
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.
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.
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.
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.
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.
declare const DEFAULT_MOVE_GRACE_DAYS = 30;
DEFAULT_RETENTION_DAYS
The default retention, in days (also the default purge horizon).
declare const DEFAULT_RETENTION_DAYS = 30;
DEFAULT_SAVE_AFTER
ms between the first unsaved change and onSave.
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.
export type DocumentIdentity = {
userId: string;
replica?: number | null;
readOnly?: boolean;
expiresAt?: number | null;
};
expiresAt: When the credential expires, ms since the epoch (a token’sexp× 1000): the room closes the socket4401then, and the provider redials with itsparamsread again.
DocumentNamespace
A namespace whose objects host a document (DocumentRoom, or any object with attachRoom).
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).
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 — seeAttachRoomOptions.onLoad.onSave: Save — seeAttachRoomOptions.onSave.validate: Validate a client frame — seeAttachRoomOptions.validate:falsedenies it, and the room writes its inverse.onComment: Notifications — seeCommentOptions.onComment: each comment change, once stored.log: One log entry:console.logof it as JSON, unless theEDYTOR_LOGvar isoff.semantics: Block roles — seeAttachRoomOptions.semantics.history: Version history — seeAttachRoomOptions.history.locks: Per-block locks — seeAttachRoomOptions.locks.rooms: The rooms blocks move to — seeAttachRoomOptions.rooms.now: The room’s clock — seeAttachRoomOptions.now.alarm: Runs the room’s tasks:onSave, the history slots, the purge.listHistory: The versions, newest first, also over RPC — seeAttachedDocument.listHistory.readHistory: A version as JSON, also over RPC — seeAttachedDocument.readHistory.restoreHistory: Restore a version, also over RPC — seeAttachedDocument.restoreHistory.undoRestore: Undo the last restore, also over RPC — seeAttachedDocument.undoRestore.historyWritten: The version writes in flight, also over RPC — seeAttachedDocument.historyWritten.exportBlocks: Export blocks for a move, also over RPC — seeAttachedDocument.exportBlocks.importBlocks: Import a move’s blocks, also over RPC — seeAttachedDocument.importBlocks.commitMove: Commit a move, also over RPC — seeAttachedDocument.commitMove.abortMove: Abort an export, also over RPC — seeAttachedDocument.abortMove.lateEdits: The late edits waiting, also over RPC — seeAttachedDocument.lateEdits.ackLateEdits: Drop forwarded late edits, also over RPC — seeAttachedDocument.ackLateEdits.applyLateEdits: Apply late edits, also over RPC — seeAttachedDocument.applyLateEdits.purge: Run the purge task now, also over RPC — seeAttachedDocument.purge.listComments: Every comment thread, also over RPC — seeAttachedDocument.listComments.comment: One comments request, also over RPC — seeAttachedDocument.comment.closeUser: Close every socket of a user, also over RPC — seeAttachedDocument.closeUser.setAccess: Change a user’s access on its sockets, also over RPC — seeAttachedDocument.setAccess.compact: Compaction, also over RPC.metrics: The room’s counters, also over RPC — seeRoomMetrics.lastUpdated: When the room last stored a change, also over RPC — seeAttachedDocument.lastUpdated.compressed: The snapshot compression in flight, also over RPC — seeAttachedDocument.compressed.dropWaitingDeletes: Drop the stored waiting deletes, also over RPC — seeAttachedDocument.dropWaitingDeletes.reset: Generation cutover, also over RPC — seeAttachedDocument.reset.transact: Server-side edit — seeAttachedDocument.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.
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);offnever purges.EDYTOR_HISTORY: Where the room keeps its version history (history()): a KV namespace or an R2 bucket binding, or the stringroom(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 (defaultUTC).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 answer404).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.
export type ExpiredCredential = {
expired: true;
};
ExportedBlocks
What exportBlocks returns.
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.
declare const forwardLateEdits: (namespace: MoveNamespace, from: string) => Promise<number>;
FrameValidation
One client frame the room applied, as validate reads it.
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.
declare const HISTORY_MAX_VALUE_BYTES: number;
HistoryEntry
One stored version, as listHistory returns it.
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),nullwhen its store does not say.
HistoryMetadata
A version’s metadata, as the room writes it to the store.
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.
export type HistoryOptions = {
store: HistoryStore | HistoryStoreFactory;
retentionDays?: number;
timeZone?: string;
maxValueBytes?: number;
};
store: The store: aHistoryStore(kvHistory,r2Historyor your own) orroomHistory``().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 ownmaxValueBytes.
HistoryRoomStorage
What a HistoryStoreFactory gets: the room’s own storage.
export type HistoryRoomStorage = {
readonly sql: SqlStorage;
transactionSync<T>(closure: () => T): T;
readonly tablePrefix: string;
};
tablePrefix: The room’s table prefix (''forDocumentRoom,'edytor_'by default forattachRoom).
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.
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 (loggedhistory), never split.nativeTtl: The store deletes a value at itsexpiresAtitself (KV’s TTL): the room’s alarm never deletes.put: Storevalueatkey(replacing any value there) with its expiry and metadata.get: The value atkey, with its expiry when the store knows it, ornull.list: One page of the keys starting withprefix;cursorcontinues a listing, and is absent on its last page.delete: Deletekey(a missing key is no error).
HistoryStoreFactory
A store built from the room’s own storage at first use (roomHistory).
export type HistoryStoreFactory = (room: HistoryRoomStorage) => HistoryStore;
HistoryStoreRecord
One stored version as a HistoryStore lists it.
export type HistoryStoreRecord = {
key: string;
expiresAt: number | null;
metadata: unknown;
};
expiresAt: When it expires (ms since the epoch);nullwhen 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.
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.
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.
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.
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.
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).
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).
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).
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) && …).
declare const lockedBlocks: (options?: LockOptions) => (frame: FrameValidation) => boolean;
LockOptions
What lockedBlocks reads.
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 (defaultfalse).bypass: Users who edit locked blocks anyway (an admin):trueadmits the frame’s user.
MAX_COMMENT_LENGTH
The longest comment body, in UTF-16 code units.
declare const MAX_COMMENT_LENGTH = 10000;
MAX_COMMENTS_PER_THREAD
Comments one thread holds at most (its first included).
declare const MAX_COMMENTS_PER_THREAD = 500;
MAX_REFUSALS
Refusals kept in refusals (the newest); refusalCounts counts every one.
declare const MAX_REFUSALS = 100;
MAX_THREADS
Threads one document holds at most, resolved ones included.
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.
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.
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.
export type MoveDestination = {
parent: string | null;
index: number;
};
MovedState
One block’s state as a late edit carries it: its content and data.
export type MovedState = {
content: NonNullable<JSONBlock['content']>;
data: Record<string, unknown>;
};
MoveNamespace
The rooms a move talks to: a Durable Object namespace of DocumentRooms (or a subclass).
export type MoveNamespace = {
getByName(name: string): MoveRoom;
};
MoveRoom
The RPC surface of a room a move talks to (a DocumentRoom stub satisfies it).
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.
declare const noTimers: <T>(fn: () => T) => T;
PROBE_HEADER
The header routeDocumentSocket sets on an authorized probe: lastUpdated or snapshot.
declare const PROBE_HEADER = "X-Edytor-Probe";
PURGE_ORIGIN
The transaction origin of the room’s purge: tracked by no history.
declare const PURGE_ORIGIN: unique symbol;
PurgeReport
What one purge wrote.
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).
declare const R2_HISTORY_MAX_VALUE_BYTES: number;
R2BucketLike
The structural subset of an R2 bucket r2History uses (a Workers R2Bucket binding satisfies it).
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.
declare const r2History: (bucket: R2BucketLike) => HistoryStore;
Refusal
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.
export type ReplicaOwner = {
replica: number;
user: string;
};
requestedReplica
The replica a client asked for in ?replica=<doc.clientID> (or param), or null.
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.
declare const RESTORE_ORIGIN: unique symbol;
RestoreResult
What restoreHistory did (room.history.restore): refused when the room holds no such version.
export type RestoreResult = {
status: 'applied' | 'noop' | 'refused';
key: string;
revived?: number;
moved?: number;
rewritten?: number;
created?: number;
deleted?: number;
};
ROOM_HISTORY_MAX_BYTES
declare const ROOM_HISTORY_MAX_BYTES: number;
ROOM_HISTORY_MAX_VALUE_BYTES
roomHistory’s defaults: the largest version, and the most its table holds.
declare const ROOM_HISTORY_MAX_VALUE_BYTES: number;
ROOM_ORIGIN
The transaction origin of the room’s own edits (transact, onLoad seeds).
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.
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.
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.
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 atcompactAfter).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 (quotarefusals) and framesvalidatedenied.refusals: Every refusal, by reason (asrefusalCounts).history: Versions written to the history, skipped (historyrefusals), 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).
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),404when the room holds none;POST <url>?restore=<key>→ restore it as a forward edit (restoreHistory, as the verified user),404when 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).
export declare function routeDocumentHistory(request: Request, rooms: DocumentNamespace, documentId: string, authorize: AuthorizeDocumentSocket, options?: RouteDocumentOptions): Promise<Response>;
RouteDocumentOptions
Options of routeDocumentSocket and routeDocumentHistory.
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 theOriginheader.
routeDocumentSocket
export declare function routeDocumentSocket(request: Request, rooms: DocumentNamespace, documentId: string, authorize: AuthorizeDocumentSocket, options?: RouteDocumentOptions): Promise<Response>;
SavedDocument
The document as onSave receives it.
export type SavedDocument = {
value: JSONDoc;
update: Uint8Array;
replicas: ReplicaOwner[];
};
value: The document as JSON.update: The full v14 state (onLoadtakes it back).replicas: Who owns each client id, read withupdate.
SOCKET_TAG
The tag of the document’s sockets: other sockets of the object are left to you.
declare const SOCKET_TAG = "edytor";
SocketIdentity
What a socket is bound to: its verified user, its replica (Yjs client id), its access.
export type SocketIdentity = {
user: string;
replica: number | null;
readOnly: boolean;
expiresAt?: number | null;
};
replica:nulluntil bound — byauthorize, or by the socket’s first presence entry.expiresAt: When its credential expires (ms since the epoch, the room’s clock): the room closes it4401then (room.access).
StoredRecord
A stored record, reassembled: its kind, record number and bytes.
export type StoredRecord = {
kind: RowKind;
record: number;
bytes: Uint8Array<ArrayBuffer>;
};
ThreadComment
One comment of a thread.
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).
export type Timing = {
count: number;
totalMs: number;
maxMs: number;
lastMs: number;
};
ValidatedBlock
A block as validate reads it, before or after a frame.
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).