types

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2026 License: MIT Imports: 6 Imported by: 0

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

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 NewArray

func NewArray(branch *block.Branch) *Array

NewArray wraps the given branch as an Array.

func (*Array) Branch

func (a *Array) Branch() *block.Branch

Branch returns the underlying *block.Branch for low-level access.

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

func (a *Array) Get(idx uint64) any

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

func (a *Array) Len() uint64

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).

func (*Array) Range

func (a *Array) Range(fn func(idx uint64, value any) bool)

Range visits live elements in document order. The callback returns false to stop early. For ContentAny items with Len > 1 (squashed runs from InsertRange / Push) each element is yielded individually.

func (*Array) ToSlice

func (a *Array) ToSlice() []any

ToSlice materializes the array into a fresh []any. Convenience for tests; production callers should prefer Range to avoid the allocation.

type Attrs

type Attrs = map[string]any

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

func NewMap(branch *block.Branch) *Map

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

func (m *Map) Branch() *block.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

func (m *Map) Get(key string) any

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) Has

func (m *Map) Has(key string) bool

Has reports whether key has a live (non-tombstoned) entry.

func (*Map) Len

func (m *Map) Len() int

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

func (m *Map) Range(fn func(key string, value any) bool)

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.

func (*Map) SetText

func (m *Map) SetText(txn *doc.TransactionMut, key string) *Text

SetText inserts a freshly-constructed nested Text under key (plain-text only — see tech-debt.md for rich-text formatting).

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 NewText

func NewText(branch *block.Branch) *Text

NewText wraps the given branch as a Text.

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) Branch

func (t *Text) Branch() *block.Branch

Branch returns the underlying *block.Branch for low-level access.

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

func (t *Text) Format(txn *doc.TransactionMut, idx, length uint64, attrs Attrs) error

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

func (t *Text) Insert(txn *doc.TransactionMut, idx uint64, str string) error

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

func (t *Text) InsertEmbed(txn *doc.TransactionMut, idx uint64, value any) error

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

func (t *Text) Length() uint64

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

func (t *Text) Range(fn func(kind ChunkKind, value any, attrs Attrs) bool)

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

func (t *Text) String() 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

func (t *Text) ToDelta() []DeltaOp

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

func NewXmlText(branch *block.Branch) *XmlText

NewXmlText wraps the given branch as an XmlText, setting TypeRef = TypeRefXmlText.

func (*XmlText) ToString

func (x *XmlText) ToString() string

ToString returns the underlying text — alias for Text.String() kept for parity with XmlElement/XmlFragment ToString.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL