Documentation
¶
Overview ¶
Package types holds the user-facing shared CRDT collection types: Map, Array, Text, XmlElement / XmlFragment / XmlText.
Each type is a thin wrapper around a *block.Branch — the branch owns the actual CRDT state (linked list of Items + map of map-key tails); the type wrapper exposes idiomatic Go APIs that hide the block-layer mechanics.
Construction pattern: a Doc registers root branches by name via Doc.Branch(name); the types-layer constructor (NewMap, NewArray, NewText) wraps the returned branch.
Mutating methods take a *doc.TransactionMut. Read methods do not require a transaction parameter for ergonomic reasons but the caller is responsible for holding at least a read lock on the Doc (typically via a ReadTxn).
See docs/yrs-port-notes/types-map.md for the per-method contract of the Map type, and DESIGN.md for the API style decisions.
Index ¶
- type Array
- func (a *Array) Branch() *block.Branch
- func (a *Array) Delete(txn *doc.TransactionMut, idx, length uint64)
- func (a *Array) Get(idx uint64) any
- func (a *Array) Insert(txn *doc.TransactionMut, idx uint64, value any)
- func (a *Array) InsertArray(txn *doc.TransactionMut, idx uint64) *Array
- func (a *Array) InsertMap(txn *doc.TransactionMut, idx uint64) *Map
- func (a *Array) InsertRange(txn *doc.TransactionMut, idx uint64, values []any)
- func (a *Array) InsertText(txn *doc.TransactionMut, idx uint64) *Text
- func (a *Array) Len() uint64
- func (a *Array) Push(txn *doc.TransactionMut, values ...any)
- func (a *Array) Range(fn func(idx uint64, value any) bool)
- func (a *Array) ToSlice() []any
- type Attrs
- type ChunkKind
- type DeltaOp
- type Map
- func (m *Map) Branch() *block.Branch
- func (m *Map) Clear(txn *doc.TransactionMut)
- func (m *Map) Delete(txn *doc.TransactionMut, key string)
- func (m *Map) Get(key string) any
- func (m *Map) Has(key string) bool
- func (m *Map) Len() int
- func (m *Map) Range(fn func(key string, value any) bool)
- func (m *Map) Set(txn *doc.TransactionMut, key string, value any)
- func (m *Map) SetArray(txn *doc.TransactionMut, key string) *Array
- func (m *Map) SetMap(txn *doc.TransactionMut, key string) *Map
- func (m *Map) SetText(txn *doc.TransactionMut, key string) *Text
- type Text
- func (t *Text) ApplyDelta(txn *doc.TransactionMut, ops []DeltaOp) error
- func (t *Text) Branch() *block.Branch
- func (t *Text) Delete(txn *doc.TransactionMut, idx, length uint64) error
- func (t *Text) Format(txn *doc.TransactionMut, idx, length uint64, attrs Attrs) error
- func (t *Text) Insert(txn *doc.TransactionMut, idx uint64, str string) error
- func (t *Text) InsertEmbed(txn *doc.TransactionMut, idx uint64, value any) error
- func (t *Text) InsertWithAttributes(txn *doc.TransactionMut, idx uint64, str string, attrs Attrs) error
- func (t *Text) Length() uint64
- func (t *Text) Range(fn func(kind ChunkKind, value any, attrs Attrs) bool)
- func (t *Text) String() string
- func (t *Text) ToDelta() []DeltaOp
- type XmlElement
- func (e *XmlElement) Attributes() map[string]string
- func (e *XmlElement) Branch() *block.Branch
- func (e *XmlElement) Delete(txn *doc.TransactionMut, idx, length uint64)
- func (e *XmlElement) Get(idx uint64) any
- func (e *XmlElement) GetAttribute(name string) (string, bool)
- func (e *XmlElement) InsertXmlElement(txn *doc.TransactionMut, idx uint64, nodeName string) *XmlElement
- func (e *XmlElement) InsertXmlText(txn *doc.TransactionMut, idx uint64) *XmlText
- func (e *XmlElement) Length() uint64
- func (e *XmlElement) NodeName() string
- func (e *XmlElement) Range(fn func(idx uint64, child any) bool)
- func (e *XmlElement) RemoveAttribute(txn *doc.TransactionMut, name string)
- func (e *XmlElement) SetAttribute(txn *doc.TransactionMut, name, value string)
- func (e *XmlElement) ToString() string
- type XmlFragment
- func (f *XmlFragment) Branch() *block.Branch
- func (f *XmlFragment) Delete(txn *doc.TransactionMut, idx, length uint64)
- func (f *XmlFragment) Get(idx uint64) any
- func (f *XmlFragment) InsertXmlElement(txn *doc.TransactionMut, idx uint64, nodeName string) *XmlElement
- func (f *XmlFragment) InsertXmlText(txn *doc.TransactionMut, idx uint64) *XmlText
- func (f *XmlFragment) Length() uint64
- func (f *XmlFragment) Range(fn func(idx uint64, child any) bool)
- func (f *XmlFragment) ToString() string
- type XmlText
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Array ¶
type Array struct {
// contains filtered or unexported fields
}
Array is the user-facing wrapper around a positional Branch. The branch's Start linked list holds the document-order sequence; we count live (non-deleted, countable) items to translate user-facing positions to *Item references.
Construct via NewArray; usually obtained as types.NewArray(d.Branch("name")).
Array shares its underlying Branch with Map's possible usage of the same name — Map uses Branch.Map (string-keyed), Array uses Branch.Start (positional). The two surfaces operate on disjoint fields of the same Branch and may technically coexist, though in practice each root branch is used as one type.
func (*Array) Delete ¶
func (a *Array) Delete(txn *doc.TransactionMut, idx, length uint64)
Delete removes length elements starting at idx. Items that straddle the [idx, idx+length) range are split via SplitBlock so the deletion lands on Item boundaries.
length == 0 is a no-op. Out-of-range deletions clip to Len().
func (*Array) Get ¶
Get returns the value at idx, or nil if idx is out of range or the underlying content kind is not unpacked by extractValueAt.
O(N) over the linked list. Search markers help only when callers alternate Get with Insert/Delete near the same area; pure Get loops still walk from start (no marker write occurs here).
func (*Array) Insert ¶
func (a *Array) Insert(txn *doc.TransactionMut, idx uint64, value any)
Insert inserts a single value at idx. Convenience for InsertRange with a one-element slice.
idx must be in [0, Len]; out-of-range inserts append at the end (matching JS Y.Array.insert tolerant behaviour).
func (*Array) InsertArray ¶
func (a *Array) InsertArray(txn *doc.TransactionMut, idx uint64) *Array
InsertArray inserts a freshly-constructed nested Array at idx.
func (*Array) InsertMap ¶
func (a *Array) InsertMap(txn *doc.TransactionMut, idx uint64) *Map
InsertMap inserts a freshly-constructed nested Map at idx and returns the wrapper. Per docs/yrs-port-notes/nested-types.md §6.
Like InsertRange, idx is clamped to [0, Len]; the new Map element occupies one slot (Len contribution = 1).
func (*Array) InsertRange ¶
func (a *Array) InsertRange(txn *doc.TransactionMut, idx uint64, values []any)
InsertRange inserts every value at idx in order, packed into a single ContentAny Item with Len == len(values). Per types-array.md finding 3, this matches yrs's RangePrelim squash-on-insert.
Mid-block inserts trigger Store.SplitBlock to materialize a clean boundary at idx.
func (*Array) InsertText ¶
func (a *Array) InsertText(txn *doc.TransactionMut, idx uint64) *Text
InsertText inserts a freshly-constructed nested Text at idx (plain-text only).
func (*Array) Len ¶
Len returns the number of live elements. O(1) — reads branch.BlockLen, which Item.Integrate (+countable.Len) and TransactionMut.Delete (-countable.Len) maintain.
Per types-array.md finding 5: Map keys are excluded from BlockLen; the counter only tracks positional, countable, live items.
func (*Array) Push ¶
func (a *Array) Push(txn *doc.TransactionMut, values ...any)
Push appends one or more values to the end of the array. Equivalent to InsertRange(txn, Len(), values).
type Attrs ¶
Attrs is the format-attribute map carried by KindFormat markers and exposed in Range / ToDelta output. Per docs/yrs-port-notes/types-text-rich.md §9.
Nil values inside Attrs mean "clear this attribute" — they encode to the Any-Null tag on the wire and are interpreted by currentAttributes as removing the key. To distinguish "key not present" from "key present with nil value", check via the second return of a map lookup.
type ChunkKind ¶
type ChunkKind uint8
ChunkKind discriminates the variants emitted by Text.Range.
const ( // ChunkString is a contiguous run of UTF-8 text from a KindString // item; the value passed to fn is a string. ChunkString ChunkKind = iota // ChunkEmbed is a single embedded value from a KindEmbed item; // the value passed to fn is whatever the embed payload's runtime // type is (typically map[string]any or a primitive Any). ChunkEmbed )
type DeltaOp ¶
type DeltaOp struct {
// Insert is the text to insert at the cursor. Mutually exclusive
// with Embed, Retain, Delete.
Insert string
// Embed is a single embedded value (image, mention, etc.). Mutually
// exclusive with Insert, Retain, Delete. The Embed value's runtime
// type matches whatever Y.encodeStateAsUpdate-produced JS clients
// would round-trip through Any — typically a map[string]any.
Embed any
// Retain advances the cursor by N UTF-16 units, optionally
// applying Attributes to the retained range. Mutually exclusive
// with Insert, Embed, Delete.
Retain uint64
// Delete removes N UTF-16 units from the cursor. Mutually exclusive
// with Insert, Embed, Retain.
Delete uint64
// Attributes applies to Insert, Embed, or Retain. Nil-valued keys
// inside Attributes mean "clear this attribute" on the affected
// range; absent keys preserve the current value.
Attributes Attrs
}
DeltaOp is a single op from a Quill-style delta. Exactly one of Insert / Retain / Delete is non-zero for any given op. Used by ToDelta to expose the doc and by ApplyDelta to ingest one (the ApplyDelta API ships in a follow-up commit; see tech-debt.md).
type Map ¶
type Map struct {
// contains filtered or unexported fields
}
Map is the user-facing wrapper around a map-like Branch. Construct via NewMap; usually obtained as types.NewMap(d.Branch("name")).
Map exposes Set / Get / Delete / Has / Len / Range / Clear plus a Branch accessor for low-level integration.
func NewMap ¶
NewMap wraps the given branch as a Map. The branch's Map field is initialized lazily if nil — typical for a freshly-created root branch from Doc.Branch(name).
func (*Map) Branch ¶
Branch returns the underlying *block.Branch. Useful for cross-package wiring (e.g. observers, encoders) that need to operate at the block layer.
func (*Map) Clear ¶
func (m *Map) Clear(txn *doc.TransactionMut)
Clear deletes every entry in the map.
Per types-map.md finding 4: yrs's Map::clear calls txn.delete on every entry, including already-tombstoned ones. Our txn.Delete is idempotent (early-returns on IsDeleted), so we filter here purely to avoid the no-op — semantics match yrs either way.
func (*Map) Delete ¶
func (m *Map) Delete(txn *doc.TransactionMut, key string)
Delete tombstones the entry under key. Calling Delete when the key is absent or already tombstoned is a no-op.
Per types-map.md finding 3: Delete does NOT clear branch.Map[key]. The map entry continues to point at the tombstoned item so a subsequent Set can chain off it as Origin/Left, preserving YATA convergence for concurrent writers that didn't see the delete yet.
func (*Map) Get ¶
Get returns the current value under key, or nil if the key is absent or its tail item is tombstoned.
Per types-map.md finding 5: this is one-step. branch.Map[key] is the LWW winner; if it's deleted there is no live predecessor to fall back to.
func (*Map) Len ¶
Len returns the number of live (non-tombstoned) entries.
O(N) over the map size: iterates and counts. yrs has a TODO at map.rs:158 about caching this on the Branch — we'd inherit that optimization for free if we add the cache to Branch. Tracked in tech-debt.md if it shows up in benchmarks.
func (*Map) Range ¶
Range iterates over live (key, value) pairs in unspecified order (Go map iteration). The callback returns false to stop early.
func (*Map) Set ¶
func (m *Map) Set(txn *doc.TransactionMut, key string, value any)
Set stores value under key in this map. Concurrent writes from different replicas converge to the same final value — the writer with the higher (clientID, clock) wins per YATA.
value may be of any type; the encoding-layer port (deferred per tech-debt.md) will decide how each Go type maps to a wire variant. For now value is stored as ContentAny, which round-trips through reflection.
Mirrors yrs Map::insert (yrs/src/types/map.rs ~line 73).
func (*Map) SetArray ¶
func (m *Map) SetArray(txn *doc.TransactionMut, key string) *Array
SetArray inserts a freshly-constructed nested Array under key.
func (*Map) SetMap ¶
func (m *Map) SetMap(txn *doc.TransactionMut, key string) *Map
SetMap inserts a freshly-constructed nested Map under key and returns the wrapper bound to it. Subsequent edits via the returned *Map are addressed to this map-key slot in m; concurrent peers see the same nested-map structure once they apply the resulting updates.
Per docs/yrs-port-notes/nested-types.md §5 — recommended API over overloading Set(any) so the caller never has to type-assert the returned wrapper.
type Text ¶
type Text struct {
// contains filtered or unexported fields
}
Text is the user-facing wrapper around a Branch holding plain text. The branch's positional linked list (branch.Start) holds ContentString items; per types-text.md their Item.Len is in UTF-16 code units (matching JS Yjs and the wire format).
Plain-text only in this commit. Rich-text formatting (Format markers, applyDelta, embeds, attributes) is tracked in docs/tech-debt.md.
All offsets and lengths in Insert / Delete are UTF-16 code units, not Go bytes and not runes. Use utf16.Length(string) to convert a Go string to its UTF-16 length when computing positions.
func (*Text) ApplyDelta ¶
func (t *Text) ApplyDelta(txn *doc.TransactionMut, ops []DeltaOp) error
ApplyDelta walks ops in order and dispatches each to the appropriate primitive (InsertWithAttributes / InsertEmbed / Format / Delete) while maintaining a cursor through the text.
Op semantics (mirrors JS Y.Text.applyDelta from testdata/gen/node_modules/yjs/src/types/YText.js):
- Insert string with optional Attributes — insert text at cursor, advance cursor by len(text)
- Insert non-string (Embed) — insert single embed at cursor, advance cursor by 1
- Retain N with optional Attributes — if Attributes is non-nil, apply Format(cursor, N, Attributes); advance cursor by N
- Delete N — delete N units at cursor; cursor unchanged (the text shifts left under the cursor)
All ops execute inside the supplied txn so the entire delta is atomic from peers' perspective. Errors abort mid-delta — the txn caller must decide whether to commit the partial result or roll back (the doc layer's TransactionMut has no Rollback, so the partial mutation persists; this mirrors yrs's behaviour).
Per docs/yrs-port-notes/types-text-rich.md §7. Empty ops slice is a no-op; nil ops also a no-op.
func (*Text) Delete ¶
func (t *Text) Delete(txn *doc.TransactionMut, idx, length uint64) error
Delete removes length UTF-16 code units starting at idx. Items straddling the [idx, idx+length) range are split via Store.SplitBlock at UTF-16 boundaries so the deletion lands on Item edges.
Returns an error if the range exceeds the current Length.
Mirrors yrs Text::remove_range (text.rs:361-368) → remove (text.rs:806-863) for the plain-text path; the rich-text clean_format_gap call is omitted.
func (*Text) Format ¶
Format applies attrs to the range [idx, idx+length). For each attribute whose target value differs from the current formatting: open a marker at idx, walk through the range rewriting any intermediate markers for the same key (to ensure they don't re-establish the old value mid-range), and close with a marker at idx+length restoring the prior state.
length == 0 is a no-op. nil attrs is a no-op. Per docs/yrs-port-notes/types-text-rich.md §4.
func (*Text) Insert ¶
Insert inserts str at the UTF-16 code-unit position idx. idx must be in [0, Length()]. str may contain any Unicode characters; non-BMP chars (e.g. emoji) contribute 2 UTF-16 units each.
Multiple consecutive Insert calls produce separate Items; commit-time TrySquash merges adjacent same-client adjacent-clock String items into one (yrs/src/block.rs:1987-1990).
Returns an error if idx is out of range.
func (*Text) InsertEmbed ¶
InsertEmbed inserts a single embedded value at idx. The value must be a type representable through lib0 Any encoding — typically map[string]any or one of the scalar Any types.
Embeds count as one UTF-16 position in Text.Length. They are emitted by Range / ToDelta as an Embed op rather than a string chunk.
func (*Text) InsertWithAttributes ¶
func (t *Text) InsertWithAttributes(txn *doc.TransactionMut, idx uint64, str string, attrs Attrs) error
InsertWithAttributes inserts str at idx, opening format markers before the text for any attribute whose value differs from the current formatting, and closing them afterward to restore the prior state.
nil attrs is equivalent to Insert(idx, str). An empty map (zero keys) is also equivalent.
Per docs/yrs-port-notes/types-text-rich.md §5, the algorithm mirrors yrs Text::insert_with_attributes (text.rs:280-295) + JS Y.Text._insertWithAttributes (ytype.js:330-350).
func (*Text) Length ¶
Length returns the total number of UTF-16 code units in live content. O(1) — reads branch.BlockLen which Item.Integrate (+) and TransactionMut.Delete (-) maintain.
Per types-text.md §4: for our UTF-16-only port, BlockLen is already the count of UTF-16 units across live String items.
func (*Text) Range ¶
Range walks the document and invokes fn for each (kind, value, attrs) chunk. attrs reflects the current formatting at this chunk's start.
Stops early when fn returns false. Returns immediately for an empty doc.
Per docs/yrs-port-notes/types-text-rich.md §8 — companion to ToDelta. Explicit ChunkKind discriminator means a string-typed embed payload is not misidentified as a text chunk by callers that switch on the runtime type of the value.
func (*Text) String ¶
String returns the concatenation of every live ContentString in document order as a Go UTF-8 string. O(N) over the linked list.
Mirrors yrs Text::get_string (text.rs:120-132).
func (*Text) ToDelta ¶
ToDelta returns the Quill-style delta representation of the doc. Adjacent same-attribute string chunks are coalesced into single insert ops. Embeds appear as separate ops.
The returned slice has no Retain / Delete ops — those exist only in deltas representing CHANGES, not snapshots.
type XmlElement ¶
type XmlElement struct {
// contains filtered or unexported fields
}
XmlElement is an XML element — a tag name (NodeName), an attribute map (Map-like), and a list of children (Array-like) — all backed by the same Branch.
Attributes are stored as map-keyed Items with ContentAny string values. Children are stored as positional Items. The two surfaces (map and positional) coexist on the same Branch because they use disjoint fields (branch.Map vs branch.Start).
func NewXmlElement ¶
func NewXmlElement(branch *block.Branch) *XmlElement
NewXmlElement wraps the given branch as an XmlElement. Sets TypeRef = TypeRefXmlElement if not already set; the caller is responsible for setting Branch.Name (which is the nodeName) if the branch is freshly constructed at the root level. For nested elements, use the InsertXmlElement family which sets Name automatically.
func (*XmlElement) Attributes ¶
func (e *XmlElement) Attributes() map[string]string
Attributes returns a snapshot of all live attribute (name, value) pairs. Returned map is a copy; mutate freely.
func (*XmlElement) Branch ¶
func (e *XmlElement) Branch() *block.Branch
Branch returns the underlying *block.Branch.
func (*XmlElement) Delete ¶
func (e *XmlElement) Delete(txn *doc.TransactionMut, idx, length uint64)
Delete removes length children starting at idx.
func (*XmlElement) Get ¶
func (e *XmlElement) Get(idx uint64) any
Get returns the child at idx, wrapped as *XmlElement or *XmlText.
func (*XmlElement) GetAttribute ¶
func (e *XmlElement) GetAttribute(name string) (string, bool)
GetAttribute returns the value of the named attribute. The second return reports whether the attribute is set.
func (*XmlElement) InsertXmlElement ¶
func (e *XmlElement) InsertXmlElement(txn *doc.TransactionMut, idx uint64, nodeName string) *XmlElement
InsertXmlElement inserts a new XmlElement child with nodeName at position idx. Returns the wrapper.
func (*XmlElement) InsertXmlText ¶
func (e *XmlElement) InsertXmlText(txn *doc.TransactionMut, idx uint64) *XmlText
InsertXmlText inserts a new XmlText child at position idx.
func (*XmlElement) Length ¶
func (e *XmlElement) Length() uint64
Length returns the number of live children.
func (*XmlElement) NodeName ¶
func (e *XmlElement) NodeName() string
NodeName returns the element's tag name (the JS Yjs nodeName).
func (*XmlElement) Range ¶
func (e *XmlElement) Range(fn func(idx uint64, child any) bool)
Range iterates over children. fn returns false to stop early.
func (*XmlElement) RemoveAttribute ¶
func (e *XmlElement) RemoveAttribute(txn *doc.TransactionMut, name string)
RemoveAttribute tombstones the named attribute. Calling on an unset attribute is a no-op.
func (*XmlElement) SetAttribute ¶
func (e *XmlElement) SetAttribute(txn *doc.TransactionMut, name, value string)
SetAttribute sets the named attribute to value, replacing any prior value. Reuses the Map.Set machinery — attributes are map-keyed entries on the same Branch.
func (*XmlElement) ToString ¶
func (e *XmlElement) ToString() string
ToString renders the element as an XML-like string:
<nodeName attr1="value1" attr2="value2">children…</nodeName>
or, for empty elements:
<nodeName attr1="value1"/>
Attribute keys are sorted ascending for deterministic output. Attribute values are inlined without escaping — tests should avoid attribute values containing quotes; a real HTML encoder would XML-escape them.
type XmlFragment ¶
type XmlFragment struct {
// contains filtered or unexported fields
}
XmlFragment is a root container of XmlElement and XmlText children. Mirrors Y.XmlFragment — the top-level entry for collaborative XML/DOM documents (ProseMirror, Tiptap, BlockNote all use this shape). Children are accessed positionally.
Per docs/yrs-port-notes/nested-types.md §1, the underlying Branch carries TypeRef = TypeRefXmlFragment; NewXmlFragment sets it on wrap so cross-client receivers that wrap the same root by name end up with consistent state.
func NewXmlFragment ¶
func NewXmlFragment(branch *block.Branch) *XmlFragment
NewXmlFragment wraps the given branch as an XmlFragment. Sets Branch.TypeRef = TypeRefXmlFragment.
func (*XmlFragment) Branch ¶
func (f *XmlFragment) Branch() *block.Branch
Branch returns the underlying *block.Branch.
func (*XmlFragment) Delete ¶
func (f *XmlFragment) Delete(txn *doc.TransactionMut, idx, length uint64)
Delete removes length children starting at idx.
func (*XmlFragment) Get ¶
func (f *XmlFragment) Get(idx uint64) any
Get returns the child at idx as *XmlElement, *XmlText, or nil if idx is out of range or the underlying item is of an unsupported kind.
func (*XmlFragment) InsertXmlElement ¶
func (f *XmlFragment) InsertXmlElement(txn *doc.TransactionMut, idx uint64, nodeName string) *XmlElement
InsertXmlElement inserts a new XmlElement child with the given nodeName at position idx. Returns the wrapper bound to the new child branch.
func (*XmlFragment) InsertXmlText ¶
func (f *XmlFragment) InsertXmlText(txn *doc.TransactionMut, idx uint64) *XmlText
InsertXmlText inserts a new XmlText child at position idx. Use the returned wrapper to populate the text content.
func (*XmlFragment) Length ¶
func (f *XmlFragment) Length() uint64
Length returns the number of live children.
func (*XmlFragment) Range ¶
func (f *XmlFragment) Range(fn func(idx uint64, child any) bool)
Range iterates over children in document order. fn receives the index and the wrapped child; return false to stop early.
func (*XmlFragment) ToString ¶
func (f *XmlFragment) ToString() string
ToString renders the fragment as an XML-like string by concatenating child renderings. Attribute keys are sorted ascending for deterministic output (tests and snapshots compare stable strings).
type XmlText ¶
type XmlText struct {
Text
}
XmlText is rich-text content within an XML tree. Embeds the regular Text wrapper, so every Insert / InsertWithAttributes / Format / InsertEmbed / Range / ToDelta method is available directly. The Branch's TypeRef is set to TypeRefXmlText so wire round-trips identify the child as text rather than a generic Y.Text.
func NewXmlText ¶
NewXmlText wraps the given branch as an XmlText, setting TypeRef = TypeRefXmlText.