DocsConceptsContainer

Container

Containers are the fundamental building blocks in Loro for organizing and structuring collaborative data. They provide typed data structures that automatically merge when concurrent edits occur.

Container Types

Loro provides several container types, each optimized for different use cases:

  • LoroMap: Key-value pairs with Last-Write-Wins semantics
  • LoroList: Ordered sequences that merge concurrent insertions
  • LoroText: Text with character-level merging and rich text support
  • LoroTree: Hierarchical tree structures with move operations
  • LoroMovableList: Lists with reordering capabilities
  • LoroCounter: Numerical values with increment/decrement operations

Container States: Attached vs Detached

Containers in Loro exist in two distinct states that affect their behavior and identity.

Detached Containers

A container is detached when created directly using constructors:

// These containers are all detached
const  = new ();
const  = new ();
const  = new ();

Characteristics of detached containers:

  • Not yet part of any document
  • Have a default placeholder ContainerID
  • Can be used as templates or temporary data structures
  • Will get a proper ContainerID when inserted into a document

Attached Containers

A container becomes attached when it’s part of a document hierarchy:

const  = new ();
 
// Root containers are immediately attached
const  = .("myMap");
const  = .("myText");
 
// Child containers: the returned value is attached
const  = new ();
const  = .("child", );
// Note: detachedChild remains detached
// attachedChild is the attached version with proper ContainerID

Characteristics of attached containers:

  • Belong to a specific document
  • Have a proper ContainerID that uniquely identifies them
  • Changes are tracked in the document’s history
  • Can be synchronized across peers

Container IDs

Every attached container has a unique ContainerID that identifies it within the distributed system. The ID generation depends on the container type:

  • Root containers: ID derived from their name (e.g., “myMap” in doc.getMap("myMap"))
  • Child containers: ID based on the operation that created them (OpID)

This deterministic ID generation ensures that:

  • The same container can be identified across all peers
  • Container IDs are not random but contextually determined
  • A detached container cannot have its final ID until insertion

Working with Containers

Creating Root Containers

Root containers are created through the document API and are immediately attached:

const  = new ();
 
// These methods create or get root containers
const  = .("settings");
const  = .("content");
const  = .("items");
const  = .("hierarchy");

Nesting Containers

Containers can be nested to create complex data structures:

const  = new ();
const  = .("root");
 
// Method 1: Using setContainer (returns attached container)
const  = .("description", new ());
 
// Method 2: Using insertContainer for lists
const  = .("items");
const  = .(0, new ());

Container Overwrites

When initializing child containers in parallel, overwrites can occur instead of automatic merging. For example:

const : string = "hello";
const  = new ();
const  = .("map");
 
// Parallel initialization of child containers
const  = .();
const  = .("map").("text", new ());
.(0, "A");
const  = .("map").("text", new ());
.(0, "B");
 
.(.({ : "update" }));
// Result: Either { "meta": { "text": "A" } } or { "meta": { "text": "B" } }

This behavior poses a significant risk of data loss if the editing history is not preserved. Even when the complete history is available and allows for data recovery, the recovery process can be complex.

When a container holds substantial data or serves as the primary storage for document content, overwriting it can lead to the unintended hiding/loss of critical information. For this reason, it is essential to implement careful and systematic container initialization practices to prevent such issues.

Best Practices

  1. When containers might be initialized concurrently, prefer initializing them at the root level rather than as nested containers

  2. When using map containers:

    • If possible, initialize all child containers during the map container’s initialization
    • Avoid concurrent creation of child containers with the same key in the map container to prevent overwrites

The overwrite behavior occurs because parallel creation of child containers results in different container IDs, preventing automatic merging of their contents.