ID-Based Storage
The most important pattern for collaborative applications: storing content by ID and referencing by ID.
Overview
ID-based storage separates what content exists from where it appears. Store content in a map keyed by unique IDs, and reference those IDs from elsewhere.
This prevents data loss during concurrent reordering and enables reliable cross-references.
The Problem
Storing content directly in a reorderable array:
When two users reorder the same slide concurrently, delete + insert creates copies—resulting in duplicated or lost content.
The Solution
Separate content storage from ordering:
Now reordering only moves IDs—lightweight operations that merge cleanly.
Core Operations
All operations should be wrapped in yDoc.transact() for atomicity:
| Operation | Steps |
|---|---|
| Create | content.set(id, data) + order.push([id]) |
| Reorder | Delete ID from old index, insert at new index |
| Delete | order.delete(index, 1) + content.delete(id) |
| Read | order.toArray().map(id => content.get(id)) |
Always delete from both order and content to avoid orphaned data.
Benefits
- Safe Reordering: Moving items only moves IDs, which merge cleanly
- Stable References: Other parts can reference by ID without breaking
- Efficient Updates: Content changes don’t affect order, and vice versa
- Easy Deletion: References become stale IDs that can be filtered
- Undo Granularity: Content and order changes are separate undo steps
Hierarchical Data (Trees)
ID-based storage extends naturally to trees. Store all nodes flat with parent references:
Why flat beats nested maps for trees:
- Moving nodes is a single
parentIdupdate, not delete + insert - Cross-references (shortcuts, symlinks) work naturally
- Depth changes don’t require restructuring
For sibling ordering, add a childOrder array per parent or a separate ordering map.
Common Mistakes
| Mistake | Problem | Solution |
|---|---|---|
| Using array indices as references | Indices shift when items are inserted/deleted | Use stable IDs for references |
| Deleting from order only | Orphaned content accumulates in the map | Delete from both order and content |
| Not using transactions | Sync may occur between operations | Wrap related changes in yDoc.transact() |
See Also
- Ordering with Arrays - More on Y.Array for ordering
- Transactions - Batching related changes