If you want to update this page or add new content, please submit a pull request to the Homepage.

Document Lifecycle

In Yorkie, documents and clients follow a well-defined lifecycle with distinct states and transitions. Understanding this lifecycle helps you design robust collaborative applications that handle all edge cases -- from initial connection through disconnection, reattachment, and deletion.

Client Lifecycle

A Client must be activated before it can work with documents or channels.

StateDescription
DeactivatedThe client exists locally but has no connection to the server. No documents can be attached.
ActivatedThe client is registered with the server and can attach to documents and channels.

When a client is deactivated (via client.deactivate() or by the server's Housekeeping process), all attached documents are detached and resources are released.

Document States

A document transitions through several states during its lifecycle:

StateDescriptionAllowed Operations
nilNo document exists yet for this client.attach()
AttachingThe client is attempting to attach to a document.attach() (retry), detach(), remove()
AttachedThe document is successfully attached and synchronized.update(), subscribe(), detach(), remove(), PushPull
DetachedThe document has been detached but the client is still active.attach() (with a new Document instance)
RemovedThe document has been soft-deleted and can no longer be edited.deactivate()

Attach and Detach

Attaching a document subscribes the client to it, enabling synchronization:

1const doc = new yorkie.Document('doc-1');
2await client.attach(doc, {
3 initialPresence: { cursor: { x: 0, y: 0 } },
4});
5// doc is now in "Attached" state

Detaching stops synchronization, closes the Watch Stream, and releases resources:

1await client.detach(doc);
2// doc is now in "Detached" state

Reattaching a Document

After a document is detached, you can reattach to the same document key, but you must create a new Document instance:

1// Correct: create a new instance
2const doc2 = new yorkie.Document('doc-1');
3await client.attach(doc2);
4
5// Incorrect: reusing the detached instance is NOT supported
6// await client.attach(doc); // This will not work

This design ensures Garbage Collection consistency and prevents internal state conflicts. Detached documents are not included in GC calculations, so reattaching a stale instance could leave inconsistent tombstone nodes.

Document Removal

Documents can be removed (soft-deleted) while in the Attaching or Attached state:

1await client.remove(doc);
2// doc is now in "Removed" state -- no further edits possible

The removal process:

  1. The client sends a RemoveDocument request to the server.
  2. The server marks the document's RemovedAt field with the current time (soft delete).
  3. All other clients are notified that the document is removed.
  4. After a configured period, the Housekeeping process permanently deletes the document and its data (hard delete).

Key-ID Relationship

With document removal and recreation, the relationship between document keys and IDs is 1:N -- multiple documents can share the same key (representing different versions created at different times). Documents are identified internally by their unique ID, not just their key.

Document Status Events

Subscribe to document status changes to handle lifecycle transitions in your application:

1doc.subscribe('status', (event) => {
2 if (event.value.status === DocStatus.Attached) {
3 // Document is attached and ready for editing
4 } else if (event.value.status === DocStatus.Detached) {
5 // Document has been detached
6 } else if (event.value.status === DocStatus.Removed) {
7 // Document has been removed -- disable editing UI
8 }
9});

Client Deactivation

When a client is deactivated, all of its documents transition according to their current state:

  • Attached documents are implicitly detached
  • Removed documents remain removed
  • The client moves to the Deactivated state

The server's Housekeeping process automatically deactivates clients that have been inactive for longer than the client-deactivate-threshold (default: 24 hours). This is important for Garbage Collection -- inactive clients prevent the minVersionVector from advancing, blocking GC of tombstoned nodes. By deactivating stale clients, the system can reclaim memory more efficiently.

You can subscribe to connection events to detect when this happens:

1doc.subscribe('status', (event) => {
2 if (event.value.status === DocStatus.Detached) {
3 // Client may have been deactivated by the server
4 // Re-activate and reattach if needed
5 }
6});

PushPull Availability

The PushPull synchronization operation is only available in the Attached state. This is a key constraint -- you cannot synchronize changes for documents in any other state.

StatePushPull Available?
nilNo
AttachingNo (PushPull runs as part of the attach process)
AttachedYes
DetachedNo
RemovedNo

Best Practices

  1. Always check document status before performing operations, especially after network reconnection.
  2. Subscribe to status events to update your UI when documents are detached or removed by external events (server housekeeping, other clients).
  3. Create new Document instances when reattaching -- never reuse a detached instance.
  4. Handle the Removed state gracefully by disabling editing UI and informing the user.
  5. Use client-deactivate-threshold appropriately for your use case. Shorter thresholds improve GC efficiency but may deactivate clients on slow or intermittent connections.

Further Reading