Documentation
¶
Overview ¶
Package store implements the per-client block storage that owns the memory for every Item in a Yjs document.
The store is responsible for:
- Owning the per-client list of BlockCells (Item or GC) in clock order.
- Looking up cells by ID via binary search.
- Producing a StateVector summarizing what the local doc knows.
- Computing clean-edge ItemSlices for sync / integration paths.
It is not responsible for:
- Splicing Items mid-clock-range (that lives on block.Item.Splice; see tech-debt.md).
- Integration / YATA conflict resolution (lives on block.Item.Integrate).
- Squashing adjacent runs (lives on block.Item.TrySquash).
- Building the delete set (separate id_set layer).
- Concurrency control (the Doc layer holds a sync.RWMutex around store ops).
See docs/yrs-port-notes/store.md for the per-method contract and the 13 invariants every mutation must preserve.
Index ¶
- type BlockCell
- type BlockSlice
- type BlockStore
- func (s *BlockStore) Contains(id block.ID) bool
- func (s *BlockStore) GetBlock(id block.ID) (BlockCell, bool)
- func (s *BlockStore) GetClient(c uint64) *ClientBlockList
- func (s *BlockStore) GetClientMut(c uint64) *ClientBlockList
- func (s *BlockStore) GetClock(c uint64) uint64
- func (s *BlockStore) GetItem(id block.ID) *block.Item
- func (s *BlockStore) GetItemCleanEnd(id block.ID) (ItemSlice, bool)
- func (s *BlockStore) GetItemCleanStart(id block.ID) (ItemSlice, bool)
- func (s *BlockStore) GetStateVector() StateVector
- func (s *BlockStore) Materialize(slc ItemSlice) *block.Item
- func (s *BlockStore) PushBlock(item *block.Item)
- func (s *BlockStore) PushGC(client, start, end uint64)
- func (s *BlockStore) SplitBlock(it *block.Item, offset uint64) *block.Item
- type CellKind
- type ClientBlockList
- func (l *ClientBlockList) CheckInvariants() error
- func (l *ClientBlockList) Clock() uint64
- func (l *ClientBlockList) FindPivot(clock uint64) (int, bool)
- func (l *ClientBlockList) Get(i int) (BlockCell, bool)
- func (l *ClientBlockList) Insert(index int, cell BlockCell)
- func (l *ClientBlockList) Len() int
- func (l *ClientBlockList) Push(cell BlockCell)
- type GC
- type GCSlice
- type ItemSlice
- type SliceKind
- type StateVector
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BlockCell ¶
type BlockCell struct {
Kind CellKind
GC GC // valid when Kind == CellKindGC
Item *block.Item // valid when Kind == CellKindItem; nil otherwise
}
BlockCell is the unit a ClientBlockList holds. It carries either a pinned *Item (for live or soft-deleted items) or an inline GC range.
Single struct + discriminator instead of an interface: keeps the ClientBlockList slice cheap to shift on insert and avoids interface dispatch on the FindPivot binary search hot path.
func CellOfItem ¶
CellOfItem builds a BlockCell wrapping the given item. Convenience constructor for callers that produce items elsewhere.
func (BlockCell) AsItem ¶
AsItem returns the underlying *Item or nil if this is a GC cell. Provided so callers don't read c.Item directly without checking Kind.
func (BlockCell) ClockStart ¶
ClockStart returns the inclusive lower bound of the cell's ID range.
type BlockSlice ¶
BlockSlice is a tagged union over ItemSlice and GCSlice.
type BlockStore ¶
type BlockStore struct {
// contains filtered or unexported fields
}
BlockStore owns the per-client block lists for a single Doc. It is not safe for concurrent access; the Doc layer holds the RWMutex.
func (*BlockStore) Contains ¶
func (s *BlockStore) Contains(id block.ID) bool
Contains reports whether the store has ever seen the given ID. Returns true even for IDs covered by GC cells — the underlying knowledge is preserved (matches JS Yjs and yrs semantics; see store.md open question 9).
func (*BlockStore) GetBlock ¶
func (s *BlockStore) GetBlock(id block.ID) (BlockCell, bool)
GetBlock returns the BlockCell containing id, or (zero, false) if no client list exists or id.Clock is out of that client's range.
func (*BlockStore) GetClient ¶
func (s *BlockStore) GetClient(c uint64) *ClientBlockList
GetClient returns the per-client list for client c, or nil if no block has ever been inserted for that client.
Mirrors yrs BlockStore::get_client (block_store.rs).
func (*BlockStore) GetClientMut ¶
func (s *BlockStore) GetClientMut(c uint64) *ClientBlockList
GetClientMut returns the per-client list for c, creating an empty list lazily if absent. The only API that constructs ClientBlockLists.
Mirrors yrs BlockStore::get_client_blocks_mut (block_store.rs:429-433).
func (*BlockStore) GetClock ¶
func (s *BlockStore) GetClock(c uint64) uint64
GetClock returns the next free clock for client c, or 0 if c is unknown. Convenience shortcut over GetClient(c).Clock().
Mirrors yrs BlockStore::get_clock (block_store.rs:419-425).
func (*BlockStore) GetItem ¶
func (s *BlockStore) GetItem(id block.ID) *block.Item
GetItem returns the *Item containing id. Returns nil for GC cells (callers must treat nil as "either not found or already collected"; use Contains to disambiguate when it matters).
Mirrors yrs BlockStore::get_item (block_store.rs:386-393).
func (*BlockStore) GetItemCleanEnd ¶
func (s *BlockStore) GetItemCleanEnd(id block.ID) (ItemSlice, bool)
GetItemCleanEnd returns the slice from the beginning of id's block up to and including id. Symmetric to GetItemCleanStart.
Mirrors yrs BlockStore::get_item_clean_end (block_store.rs:409-414).
func (*BlockStore) GetItemCleanStart ¶
func (s *BlockStore) GetItemCleanStart(id block.ID) (ItemSlice, bool)
GetItemCleanStart returns the slice from id.Clock to the end of its underlying block, without mutating anything. The caller can pass the returned slice to a future Materialize to physically split the block; here we only compute offsets.
Returns (zero, false) when id is in a GC cell, in an unknown client, or out of range.
Mirrors yrs BlockStore::get_item_clean_start (block_store.rs:399-403).
func (*BlockStore) GetStateVector ¶
func (s *BlockStore) GetStateVector() StateVector
GetStateVector materializes a fresh StateVector from the per-client Clock() values. No caching — yrs derives the SV on each call too (block_store.rs:357-364).
Iteration order over s.clients is non-deterministic; the returned map's order is also non-deterministic. Encoding paths must sort by client ID before emitting bytes.
func (*BlockStore) Materialize ¶
func (s *BlockStore) Materialize(slc ItemSlice) *block.Item
Materialize splits the underlying block on both sides (as needed) so that the returned *Item covers exactly the slice's [Start, End] range in clock units. If the slice already covers the full block on either side, the corresponding split is skipped.
Returns the (possibly new) *Item that represents the slice exactly. Always uses UTF-16 offset semantics, matching yrs's hard-coding.
Mirrors yrs/src/store.rs:295-342 Store::materialize (without the linked_by rewiring, which lives on the future Doc layer once weak references arrive).
func (*BlockStore) PushBlock ¶
func (s *BlockStore) PushBlock(item *block.Item)
PushBlock appends a fresh Item to its client's list. The caller must guarantee item.ID.Clock == s.GetClock(item.ID.Client) — i.e. the new block's first clock is exactly the next free clock for that client. Violating this breaks invariant 1 of ClientBlockList; tests should follow with CheckInvariants.
Mirrors yrs BlockStore::push_block (block_store.rs:314-326).
func (*BlockStore) PushGC ¶
func (s *BlockStore) PushGC(client, start, end uint64)
PushGC appends a fresh GC range to its client's list, with the same monotonicity precondition as PushBlock.
Mirrors yrs BlockStore::push_gc (block_store.rs:328-341).
func (*BlockStore) SplitBlock ¶
SplitBlock locates the cell containing it.ID in its client's list, calls it.Splice(offset), and inserts the new right-half cell at index+1. Returns the new *Item.
Returns nil if Splice declines (offset 0, offset >= it.Len, or non-splittable content kind) or if the item is not currently in the store.
Implicitly uses UTF-16 offset semantics — matches yrs's split_block_inner (block_store.rs:453-455). See store.md open question 7.
Mirrors yrs/src/block_store.rs:437-451.
type CellKind ¶
type CellKind uint8
CellKind discriminates BlockCell variants. Mirrors yrs BlockCell enum.
const ( // CellKindGC is a tombstone range whose content has been collected. // The ID range stays valid so that other items pointing at it via // Origin/RightOrigin continue to resolve. CellKindGC CellKind = 0 // CellKindItem is a live or tombstoned-but-content-preserved item. CellKindItem CellKind = 1 )
type ClientBlockList ¶
type ClientBlockList struct {
// contains filtered or unexported fields
}
ClientBlockList is the dense, clock-ordered list of every block produced by a single client. Invariants enforced:
- Per-client clock density: the union of cell ranges equals [0, Clock()) with no gaps and no overlaps.
- Sorted by clock: list[i].ClockStart < list[i+1].ClockStart.
- Inclusive ranges: ClockStart and ClockEnd are both inclusive; Len = End - Start + 1.
See docs/yrs-port-notes/store.md "Internal invariants".
func NewClientBlockList ¶
func NewClientBlockList() *ClientBlockList
NewClientBlockList returns an empty list. Capacity is reserved for the common case of a few-dozen blocks per client.
func (*ClientBlockList) CheckInvariants ¶
func (l *ClientBlockList) CheckInvariants() error
CheckInvariants validates structural invariants 1-3 across the list. Returns the first error found, or nil if everything is consistent.
Test-only utility; production code should not call this on the hot path. Recommended pattern: defer l.CheckInvariants() in any test that performs Push / Insert / SquashLeft to catch regressions immediately.
func (*ClientBlockList) Clock ¶
func (l *ClientBlockList) Clock() uint64
Clock returns the next free clock for this client. Equal to list[len-1].ClockEnd() + 1, or 0 for an empty list. Constant-time.
Mirrors yrs ClientBlockList::clock (block_store.rs:24-34). Used for state vector materialization and append-precondition checks.
func (*ClientBlockList) FindPivot ¶
func (l *ClientBlockList) FindPivot(clock uint64) (int, bool)
FindPivot returns the index of the cell whose [ClockStart, ClockEnd] inclusive range contains the given clock. Returns (0, false) if the list is empty or the clock is past Clock().
Implementation uses sort.Search rather than yrs's hand-rolled interpolation+binary search (block_store.rs:42-68). The interpolation is a micro-optimisation; sort.Search costs at most log2(N) extra comparisons and avoids the divide-by-zero edge case yrs has when the list contains a single block ending at clock 0.
func (*ClientBlockList) Get ¶
func (l *ClientBlockList) Get(i int) (BlockCell, bool)
Get returns the cell at index i. Returns the zero BlockCell and false if i is out of range.
func (*ClientBlockList) Insert ¶
func (l *ClientBlockList) Insert(index int, cell BlockCell)
Insert places cell at the given index, shifting later cells right. Used by split_block and materialize for the new right-half cell; caller must compute index via FindPivot and validate that the insert keeps invariants 1-3.
Mirrors yrs ClientBlockList::insert (block_store.rs:89-93).
func (*ClientBlockList) Len ¶
func (l *ClientBlockList) Len() int
Len returns the number of cells in the list.
func (*ClientBlockList) Push ¶
func (l *ClientBlockList) Push(cell BlockCell)
Push appends a cell to the list. Caller must guarantee cell.ClockStart() == l.Clock() to preserve invariant 1; in tests, follow with CheckInvariants.
Mirrors yrs ClientBlockList::push (block_store.rs:84-87).
type GC ¶
GC is a garbage-collected ID range. Both Start and End are inclusive (matching yrs BlockCell::clock_range invariant 3 in store.md).
type GCSlice ¶
GCSlice is the GC-cell counterpart to ItemSlice. Fields are absolute clocks, not offsets, because GC cells have no notion of an underlying block.
type ItemSlice ¶
type ItemSlice struct {
Ptr *block.Item
Start uint64 // inclusive offset into [Ptr.ID.Clock, Ptr.ID.Clock+Ptr.Len)
End uint64 // inclusive offset; Start <= End <= Ptr.Len-1
}
ItemSlice is a non-destructive sub-range view over an Item. Start and End are inclusive offsets within the underlying block (0..=Item.Len-1).
A read-only computation; no mutation. To actually realize the slice boundaries as separate Items, callers must run Materialize on it, which splices the underlying block. See tech-debt.md and store.md "Edge cases".
func (ItemSlice) AdjacentLeft ¶
AdjacentLeft reports whether the slice covers the leftmost element of the underlying block. Materialize uses this to skip a no-op left split.
func (ItemSlice) AdjacentRight ¶
AdjacentRight reports whether the slice covers the rightmost element of the underlying block. Materialize uses this to skip a no-op right split.
func (ItemSlice) ClockStart ¶
ClockStart returns the absolute clock of the first element in the slice.
type SliceKind ¶
type SliceKind uint8
SliceKind discriminates BlockSlice variants. Mirrors yrs slice.rs.
type StateVector ¶
StateVector summarizes what the local doc knows. For each client it holds the next free clock (exclusive). A peer can compare its own StateVector against a remote SV to compute the diff.
Iteration order is non-deterministic; wire-emitting paths must sort by client ID before encoding. See store.md open question 4.