Documentation
¶
Overview ¶
Package crdt implements the Yjs CRDT algorithms, wire-compatible with the JavaScript reference implementation.
It provides Y.Doc and the shared types (Y.Text, Y.Array, Y.Map, Y.XmlFragment, Y.XmlElement, Y.XmlText), both update codecs (V1 and V2), transactions, snapshots, relative positions, an undo manager, awareness, and garbage collection. Byte-for-byte compatibility with the reference is the correctness test and is enforced by a differential oracle rather than by hand-written expectations.
Example (Convergence) ¶
Two replicas edit the same document concurrently and converge.
This is the whole CRDT contract in one example: the edits are made independently, the updates cross in both directions, and both replicas end up with the same text regardless of the order they arrived in.
package main
import (
"fmt"
"github.com/antst/go-yjs/crdt"
)
func main() {
// Client IDs are pinned so the example's output is stable. Real documents
// should let the library pick one.
first := crdt.NewDoc("notes", crdt.WithClientID(1))
second := crdt.NewDoc("notes", crdt.WithClientID(2))
first.GetText("body").Insert(0, "hello", crdt.Object{})
second.GetText("body").Insert(0, "world", crdt.Object{})
// Each side ships the operations the other has not seen. EncodeStateVector
// says "here is what I have"; EncodeStateAsUpdate answers with the rest.
firstUpdate, err := crdt.EncodeStateAsUpdate(first, crdt.EncodeStateVector(second))
if err != nil {
panic(err)
}
secondUpdate, err := crdt.EncodeStateAsUpdate(second, crdt.EncodeStateVector(first))
if err != nil {
panic(err)
}
if err := crdt.ApplyUpdate(first, secondUpdate, nil); err != nil {
panic(err)
}
if err := crdt.ApplyUpdate(second, firstUpdate, nil); err != nil {
panic(err)
}
// Both inserted at position 0 with no knowledge of each other. The CRDT
// breaks that tie deterministically by client ID, so every replica resolves
// it the same way — and the same way the JavaScript implementation does,
// which the differential oracle checks on every push.
fmt.Println(first.GetText("body").ToString())
fmt.Println(second.GetText("body").ToString() == first.GetText("body").ToString())
}
Output: helloworld true
Example (ServerSeam) ¶
OnUpdate is the seam a server hangs persistence and broadcast off: it delivers the exact bytes to store or forward, plus the origin of the transaction that produced them.
The origin is what stops an echo loop. A server applies a remote client's update with that client as the origin, and its own OnUpdate handler then sees the origin and knows not to send those bytes back where they came from.
package main
import (
"fmt"
"github.com/antst/go-yjs/crdt"
)
func main() {
doc := crdt.NewDoc("notes", crdt.WithClientID(1))
doc.OnUpdate(func(update []byte, origin any) {
// The byte count is deliberately not printed: it is an encoding detail,
// and an example that asserts it would fail on any legitimate change to
// the wire format.
fmt.Println("bytes to persist:", len(update) > 0, "origin:", origin)
})
doc.Transact(func(*crdt.Transaction) {
doc.GetText("body").Insert(0, "hi", crdt.Object{})
}, "conn-7")
}
Output: bytes to persist: true origin: conn-7
Example (SyncHandshake) ¶
The sync protocol as a transport adapter drives it: a client announces its state, the server answers with the difference, and the client applies it.
SyncHandler owns the message framing. A transport adapter is responsible only for moving the byte slices between the two sides.
package main
import (
"bytes"
"fmt"
"github.com/antst/go-yjs/crdt"
"github.com/antst/go-yjs/protocol"
)
func main() {
server := crdt.NewDoc("notes", crdt.WithClientID(1))
server.GetText("body").Insert(0, "shared state", crdt.Object{})
client := crdt.NewDoc("notes", crdt.WithClientID(2))
// The client opens with step 1: "this is everything I already have."
step1 := protocol.EncodeSyncStep1(client)
// The server replies with step 2: only what the client is missing.
var reply bytes.Buffer
if _, err := protocol.NewSyncHandler(server).HandleMessage(step1, &reply); err != nil {
panic(err)
}
// The client applies the reply through its own handler.
var unused bytes.Buffer
if _, err := protocol.NewSyncHandler(client).HandleMessage(reply.Bytes(), &unused); err != nil {
panic(err)
}
fmt.Println(client.GetText("body").ToString())
}
Output: shared state
Index ¶
- Constants
- Variables
- func ApplyAwarenessUpdate(awareness *Awareness, update []byte, origin interface{}) error
- func ApplyUpdate(ydoc *Doc, update []uint8, transactionOrigin interface{}) error
- func ApplyUpdateV2(ydoc *Doc, update []uint8, transactionOrigin interface{}) error
- func AwarenessStateJSON(state Object) string
- func CompareIDs(a *ID, b *ID) bool
- func CompareRelativePositions(a, b *RelativePosition) bool
- func ConvertUpdateFormatV1ToV2(update []uint8) ([]uint8, error)
- func ConvertUpdateFormatV2ToV1(update []uint8) ([]uint8, error)
- func DecodeStateVector(decodedState []uint8) (map[Number]Number, error)
- func DiffUpdate(update []uint8, sv []uint8) ([]uint8, error)
- func DiffUpdateV2(update []uint8, sv []uint8) ([]uint8, error)
- func EncodeAwarenessUpdate(awareness *Awareness, clients []Number, states map[Number]Object) []byte
- func EncodeRelativePosition(rpos *RelativePosition) []uint8
- func EncodeSnapshot(snapshot *Snapshot) ([]uint8, error)
- func EncodeSnapshotV1(snapshot *Snapshot) ([]uint8, error)
- func EncodeSnapshotV2(snapshot *Snapshot) ([]uint8, error)
- func EncodeStateAsUpdate(doc *Doc, encodedTargetStateVector []uint8) ([]uint8, error)
- func EncodeStateAsUpdateV2(doc *Doc, encodedTargetStateVector []uint8) ([]uint8, error)
- func EncodeStateVector(doc *Doc) []byte
- func EncodeStateVectorFromUpdate(update []uint8) ([]uint8, error)
- func EncodeStateVectorFromUpdateV2(update []uint8) ([]uint8, error)
- func EqualSnapshots(snap1, snap2 *Snapshot) bool
- func MergeUpdates(updates [][]byte) ([]byte, error)
- func MergeUpdatesV2(updates [][]byte) ([]byte, error)
- func ModifyAwarenessUpdate(update []byte, modify func(interface{}) interface{}) ([]byte, error)
- func ObfuscateUpdate(update []uint8) ([]uint8, error)
- func ParseUpdateMeta(update []uint8) (map[Number]Number, map[Number]Number, error)
- func RemoveAwarenessStates(awareness *Awareness, clients []Number, origin interface{})
- func SnapshotContainsUpdate(snapshot *Snapshot, update []uint8) (bool, error)
- func SnapshotContainsUpdateV2(snapshot *Snapshot, update []uint8) (bool, error)
- func Transact(doc *Doc, f func(trans *Transaction), origin interface{}, local bool)
- func TypeMapGetSnapshot(parent SharedType, key string, snapshot *Snapshot) interface{}
- type AbsolutePosition
- type ArrayAny
- type Awareness
- func (a *Awareness) Destroy()
- func (a *Awareness) GetLocalState() Object
- func (a *Awareness) GetMeta() map[Number]Object
- func (a *Awareness) GetStates() map[Number]Object
- func (a *Awareness) SetLocalState(state Object) error
- func (a *Awareness) SetLocalStateField(field string, value interface{}) error
- type ChangedSubs
- type Doc
- func (doc *Doc) Destroy()
- func (doc *Doc) Get(name string, typeConstructor TypeConstructor) (SharedType, error)
- func (doc *Doc) GetArray(name string) *YArray
- func (doc *Doc) GetMap(name string) *YMap
- func (doc *Doc) GetSubdocGUIDs() Set
- func (doc *Doc) GetSubdocs() Set
- func (doc *Doc) GetText(name string) *YText
- func (doc *Doc) GetXMLFragment(name string) *YXmlFragment
- func (doc *Doc) Load()
- func (doc *Doc) Off(eventName string, handler *ObserverHandler)
- func (doc *Doc) OffUpdate(handler *ObserverHandler)
- func (doc *Doc) OffUpdateV2(handler *ObserverHandler)
- func (doc *Doc) On(eventName string, handler *ObserverHandler)
- func (doc *Doc) OnUpdate(handler func(update []byte, origin any)) *ObserverHandler
- func (doc *Doc) OnUpdateV2(handler func(update []byte, origin any)) *ObserverHandler
- func (doc *Doc) ToJSON() Object
- func (doc *Doc) Transact(f func(trans *Transaction), origin interface{})
- type DocOption
- type EventAction
- type EventOperator
- type EventOperatorKind
- type ID
- type IEventType
- type ManagedAwareness
- func (m *ManagedAwareness) Awareness() *Awareness
- func (m *ManagedAwareness) Destroy()
- func (m *ManagedAwareness) GetLocalState() Object
- func (m *ManagedAwareness) GetMeta() map[Number]Object
- func (m *ManagedAwareness) GetStates() map[Number]Object
- func (m *ManagedAwareness) On(event string, h *ObserverHandler)
- func (m *ManagedAwareness) Running() bool
- func (m *ManagedAwareness) SetLocalState(state Object) error
- func (m *ManagedAwareness) Start()
- func (m *ManagedAwareness) Stop()
- type NullType
- type Number
- type Object
- func (o Object) Delete(key string)
- func (o Object) Get(key string) (any, bool)
- func (o Object) GetOr(key string) any
- func (o Object) GetOrNull(key string) any
- func (o Object) Has(key string) bool
- func (o Object) IsNil() bool
- func (o Object) Keys() []string
- func (o Object) Len() int
- func (o Object) MarshalJSON() ([]byte, error)
- func (o Object) Range(f func(key string, value any))
- func (o *Object) Set(key string, value any)
- func (o Object) ShallowClone() Object
- func (o Object) ToMap() map[string]any
- func (o *Object) UnmarshalJSON(data []byte) error
- type Observable
- func (o *Observable) Destroy()
- func (o *Observable) Emit(name interface{}, v ...interface{})
- func (o *Observable) HasObserver(name interface{}) bool
- func (o *Observable) HasObservers() bool
- func (o *Observable) Off(name interface{}, handler *ObserverHandler) bool
- func (o *Observable) On(name interface{}, handle *ObserverHandler)
- func (o *Observable) Once(name interface{}, handler *ObserverHandler)
- type ObserverHandler
- type RelativePosition
- func CreateRelativePositionFromJSON(json Object) (*RelativePosition, error)
- func DecodeRelativePosition(uint8Array []uint8) (*RelativePosition, error)
- func NewRelativePosition(t SharedType, item *ID, assoc Number) *RelativePosition
- func NewRelativePositionFromTypeIndex(tp SharedType, index, assoc Number) *RelativePosition
- type Set
- type SharedType
- type Snapshot
- type StackItem
- type Transaction
- func (trans *Transaction) AfterState() map[Number]Number
- func (trans *Transaction) BeforeState() map[Number]Number
- func (trans *Transaction) ChangedTypes() map[SharedType]ChangedSubs
- func (trans *Transaction) Document() *Doc
- func (trans *Transaction) IsLocal() bool
- func (trans *Transaction) Meta() map[interface{}]Set
- func (trans *Transaction) Origin() any
- func (trans *Transaction) SubdocsAdded() Set
- func (trans *Transaction) SubdocsLoaded() Set
- func (trans *Transaction) SubdocsRemoved() Set
- type TypeConstructor
- type UndefinedType
- type UndoManager
- func (u *UndoManager) AddToScope(scopes ...interface{})
- func (u *UndoManager) AddTrackedOrigin(origin interface{})
- func (u *UndoManager) CanRedo() bool
- func (u *UndoManager) CanUndo() bool
- func (u *UndoManager) Clear(clearUndoStack, clearRedoStack bool)
- func (u *UndoManager) Destroy()
- func (u *UndoManager) GetDoc() *Doc
- func (u *UndoManager) Redo() *StackItem
- func (u *UndoManager) RemoveTrackedOrigin(origin interface{})
- func (u *UndoManager) StopCapturing()
- func (u *UndoManager) Undo() *StackItem
- type YArray
- func (y *YArray) Clone() *YArray
- func (y *YArray) Delete(index, length Number)
- func (y *YArray) ForEach(f func(interface{}, Number, *YArray))
- func (y *YArray) From(items ArrayAny) *YArray
- func (y *YArray) Get(index Number) interface{}
- func (y *YArray) GetLength() Number
- func (y *YArray) Insert(index Number, content ArrayAny)
- func (y *YArray) Map(f func(interface{}, Number, *YArray) interface{}) ArrayAny
- func (t *YArray) Observe(f func(interface{}, interface{}))
- func (t *YArray) ObserveDeep(f func(interface{}, interface{}))
- func (y *YArray) Push(content ArrayAny)
- func (y *YArray) Splice(start, end Number) ArrayAny
- func (y *YArray) ToArray() ArrayAny
- func (y *YArray) ToJSON() interface{}
- func (t *YArray) Unobserve(f func(interface{}, interface{}))
- func (t *YArray) UnobserveDeep(f func(interface{}, interface{}))
- func (y *YArray) Unshift(content ArrayAny)
- type YArrayEvent
- type YEvent
- type YMap
- func (y *YMap) AppendKeys(dst []string) []string
- func (y *YMap) Clear()
- func (y *YMap) Clone() *YMap
- func (y *YMap) Delete(key string)
- func (y *YMap) Entries() map[string]interface{}
- func (y *YMap) ForEach(f func(string, interface{}, *YMap)) Object
- func (y *YMap) Get(key string) interface{}
- func (t *YMap) GetLength() Number
- func (y *YMap) GetSize() Number
- func (y *YMap) Has(key string) bool
- func (y *YMap) Keys() []string
- func (t *YMap) Observe(f func(interface{}, interface{}))
- func (t *YMap) ObserveDeep(f func(interface{}, interface{}))
- func (y *YMap) Range(f func(key string, val interface{}))
- func (y *YMap) Set(key string, value interface{}) interface{}
- func (y *YMap) ToJSON() interface{}
- func (t *YMap) Unobserve(f func(interface{}, interface{}))
- func (t *YMap) UnobserveDeep(f func(interface{}, interface{}))
- func (y *YMap) Values() []interface{}
- type YMapEvent
- type YText
- func (y *YText) ApplyDelta(delta []EventOperator, sanitize bool)
- func (y *YText) Clone() *YText
- func (y *YText) Delete(index Number, length Number)
- func (y *YText) Format(index Number, length Number, attributes Object)
- func (y *YText) GetAttribute(attributeName string) interface{}
- func (y *YText) GetAttributes(snapshot *Snapshot) Object
- func (t *YText) GetLength() Number
- func (y *YText) Insert(index Number, text string, attributes Object)
- func (y *YText) InsertEmbed(index Number, embed Object, attributes Object)
- func (y *YText) Length() Number
- func (t *YText) Observe(f func(interface{}, interface{}))
- func (t *YText) ObserveDeep(f func(interface{}, interface{}))
- func (y *YText) RemoveAttribute(attributeName string)
- func (y *YText) SetAttribute(attributeName string, attributeValue interface{})
- func (y *YText) ToDelta(snapshot *Snapshot, prevSnapshot *Snapshot, ...) []EventOperator
- func (y *YText) ToJSON() interface{}
- func (y *YText) ToString() string
- func (t *YText) Unobserve(f func(interface{}, interface{}))
- func (t *YText) UnobserveDeep(f func(interface{}, interface{}))
- type YTextEvent
- type YXmlElement
- func (y *YXmlElement) Clone() *YXmlElement
- func (y *YXmlElement) GetAttribute(attributeName string) interface{}
- func (y *YXmlElement) GetAttributes() Object
- func (y *YXmlElement) GetNextSibling() SharedType
- func (y *YXmlElement) GetPrevSibling() SharedType
- func (y *YXmlElement) HasAttribute(attributeName string) bool
- func (t *YXmlElement) Observe(f func(interface{}, interface{}))
- func (t *YXmlElement) ObserveDeep(f func(interface{}, interface{}))
- func (y *YXmlElement) RemoveAttribute(attributeName string)
- func (y *YXmlElement) SetAttribute(attributeName string, attributeValue interface{})
- func (y *YXmlElement) ToString() string
- func (t *YXmlElement) Unobserve(f func(interface{}, interface{}))
- func (t *YXmlElement) UnobserveDeep(f func(interface{}, interface{}))
- type YXmlEvent
- type YXmlFragment
- func (y *YXmlFragment) Clone() *YXmlFragment
- func (y *YXmlFragment) CreateTreeWalker(filter func(SharedType) bool) *YXmlTreeWalker
- func (y *YXmlFragment) Delete(index, length Number)
- func (y *YXmlFragment) Get(index Number) interface{}
- func (y *YXmlFragment) GetFirstChild() SharedType
- func (y *YXmlFragment) GetLength() Number
- func (y *YXmlFragment) Insert(index Number, content ArrayAny)
- func (y *YXmlFragment) InsertAfter(ref SharedType, content ArrayAny)
- func (t *YXmlFragment) Observe(f func(interface{}, interface{}))
- func (t *YXmlFragment) ObserveDeep(f func(interface{}, interface{}))
- func (y *YXmlFragment) Push(content ArrayAny)
- func (y *YXmlFragment) QuerySelector(query string) SharedType
- func (y *YXmlFragment) QuerySelectorAll(query string) []SharedType
- func (y *YXmlFragment) Slice(start, end Number) ArrayAny
- func (y *YXmlFragment) ToArray() ArrayAny
- func (y *YXmlFragment) ToJSON() interface{}
- func (y *YXmlFragment) ToString() string
- func (t *YXmlFragment) Unobserve(f func(interface{}, interface{}))
- func (t *YXmlFragment) UnobserveDeep(f func(interface{}, interface{}))
- func (y *YXmlFragment) Unshift(content ArrayAny)
- type YXmlText
- func (y *YXmlText) Clone() *YXmlText
- func (t *YXmlText) GetLength() Number
- func (y *YXmlText) GetNextSibling() SharedType
- func (y *YXmlText) GetPreSibling() SharedType
- func (t *YXmlText) Observe(f func(interface{}, interface{}))
- func (t *YXmlText) ObserveDeep(f func(interface{}, interface{}))
- func (y *YXmlText) ToJSON() string
- func (y *YXmlText) ToString() string
- func (t *YXmlText) Unobserve(f func(interface{}, interface{}))
- func (t *YXmlText) UnobserveDeep(f func(interface{}, interface{}))
- type YXmlTreeWalker
Examples ¶
Constants ¶
const ( ActionAdd = "add" ActionDelete = "delete" ActionUpdate = "update" )
const OutdatedTimeout = 30 * time.Second
Variables ¶
var ( Null = NullType{} Undefined = UndefinedType{} )
var ErrMalformedAwarenessState = errors.New("awareness state is not a JSON object")
ErrMalformedAwarenessState is returned by ApplyAwarenessUpdate / applyAwarenessUpdateWithoutEvents when a client entry's state payload is valid JSON but not a JSON object (a string / array / number / bool). The Awareness state map holds Object values, so such a payload cannot be stored — an unchecked type assertion would panic and crash the process on hostile input. (The protocol package mirrors this with its own ErrMalformedAwarenessState in DecodeAwarenessMessage.)
var ErrTruncatedAwarenessFrame = errors.New("awareness frame is truncated")
ErrTruncatedAwarenessFrame is returned by ApplyAwarenessUpdate / applyAwarenessUpdateWithoutEvents when a per-client entry cannot be decoded because the frame is truncated (e.g. a state-string length prefix claims more bytes than remain). It wraps the underlying decode error, so the cause stays retrievable while callers can errors.Is it apart from ErrMalformedAwarenessState (which signals a well-formed-but-non-object state payload). Because the apply is all-or-nothing, receiving this error guarantees NOTHING in States/Meta was mutated.
var ErrUnsupportedDataValue = errors.New("unsupported data value")
ErrUnsupportedDataValue marks a value outside the JSON-shaped data domain understood by cloneDataValue. Callers must reject such a value rather than silently retain a mutable reference that defeats an ownership boundary.
Functions ¶
func ApplyAwarenessUpdate ¶
func ApplyUpdate ¶
ApplyUpdate applies a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder. A non-nil error means the document may contain structs decoded before the bad field and must be resynchronised; it does not mean the mutation was rolled back.
func ApplyUpdateV2 ¶
ApplyUpdateV2 applies a V2-encoded document update (as produced by JS Y.encodeStateAsUpdateV2 or Go EncodeStateAsUpdateV2). Its error contract is identical to ApplyUpdate.
func AwarenessStateJSON ¶
AwarenessStateJSON is the SINGLE serialization boundary for an awareness client state: a cleared/removed state (the zero Object, IsNil) serializes as the JSON literal "null", and any present state as its JSON.stringify-faithful object JSON. Emitting "null" (not "{}") for a cleared state is what makes a receiving peer REMOVE the client; "{}" is a PRESENT empty object the peer applies and never removes, leaving a ghost cursor for a disconnected/cleared client.
This guard was hand-duplicated at EncodeAwarenessUpdate and the protocol package's EncodeAwarenessMessage, and was MISSING at ModifyAwarenessUpdate (the 3rd ghost-cursor site). Centralizing it here and calling it from all three keeps the cleared-state boundary in one place. (Do NOT change Object.MarshalJSON globally: "{}" is the correct serialization for an empty object on the ContentJson / any paths; only this awareness boundary needs it.)
func CompareRelativePositions ¶
func CompareRelativePositions(a, b *RelativePosition) bool
func ConvertUpdateFormatV1ToV2 ¶
ConvertUpdateFormatV1ToV2 converts a V1-encoded update to V2.
func ConvertUpdateFormatV2ToV1 ¶
ConvertUpdateFormatV2ToV1 converts a V2-encoded update to V1.
func DecodeStateVector ¶
DecodeStateVector decodes a state-vector payload into its client clocks.
func DiffUpdateV2 ¶
DiffUpdateV2 computes the differential V2 update of a V2-encoded update against the V1-encoded state vector sv.
func EncodeAwarenessUpdate ¶
func EncodeRelativePosition ¶
func EncodeRelativePosition(rpos *RelativePosition) []uint8
func EncodeSnapshot ¶
EncodeSnapshot defaults to V1 — the historical, wire-compatible default.
func EncodeSnapshotV1 ¶
EncodeSnapshotV1 encodes a snapshot in the V1 format (yjs encodeSnapshot, a bare DSEncoderV1).
func EncodeSnapshotV2 ¶
EncodeSnapshotV2 encodes a snapshot in the V2 format (yjs encodeSnapshotV2 default) using a BARE DSEncoderV2 — the full UpdateEncoderV2 would prepend empty struct-column headers, which a snapshot must not contain.
func EncodeStateAsUpdate ¶
func EncodeStateAsUpdateV2 ¶
EncodeStateAsUpdateV2 writes the whole document (or the diff against encodedTargetStateVector, which is always V1-encoded) as a single V2 update message — byte-identical to JS Y.encodeStateAsUpdateV2. Pass nil for a full update.
func EncodeStateVector ¶
EncodeStateVector encodes the document's current state vector in the canonical V1/lib0 representation used by sync step 1. Encoder selection is an implementation detail; callers should not need a codec object merely to ask which structs a document contains.
func EncodeStateVectorFromUpdate ¶
EncodeStateVectorFromUpdate extracts the state vector from a V1-encoded update without materializing a document.
func EncodeStateVectorFromUpdateV2 ¶
EncodeStateVectorFromUpdateV2 extracts the state vector from a V2-encoded update without materializing a document.
func EqualSnapshots ¶
func MergeUpdates ¶
MergeUpdates merges V1 updates into one canonical V1 update.
func MergeUpdatesV2 ¶
MergeUpdatesV2 merges V2 updates into one canonical V2 update.
func ModifyAwarenessUpdate ¶
ModifyAwarenessUpdate modifies the content of an awareness update before re-encoding it to an awareness update.
This might be useful when you have a central server that wants to ensure that clients cant hijack somebody elses identity.
func ObfuscateUpdate ¶
ObfuscateUpdate replaces user content in a V1 update while preserving its CRDT structure, allowing the update to be shared as a reproducible fixture.
func ParseUpdateMeta ¶
ParseUpdateMeta returns the state-vector range covered by a V1 update without materializing a document.
func RemoveAwarenessStates ¶
RemoveAwarenessStates marks (remote) clients as inactive and removes them from the list of active peers. This change will be propagated to remote clients.
func SnapshotContainsUpdate ¶
SnapshotContainsUpdate is snapshotContainsUpdateWith for a V1-encoded update. Named to match the reference's default-argument pair rather than exposing the decoder, which is how the rest of this package spells the same shape (ConvertUpdateFormatWith, ParseUpdateMetaWith, MergeUpdatesWith).
func SnapshotContainsUpdateV2 ¶
SnapshotContainsUpdateV2 is snapshotContainsUpdateWith for a V2-encoded update.
func Transact ¶
func Transact(doc *Doc, f func(trans *Transaction), origin interface{}, local bool)
Transact implements the public transaction API with a fully materialized Transaction value, including writable empty Meta and subdocument sets.
func TypeMapGetSnapshot ¶
func TypeMapGetSnapshot(parent SharedType, key string, snapshot *Snapshot) interface{}
TypeMapGetSnapshot reads key from a heterogeneous map-like shared type as it existed at snapshot.
Types ¶
type AbsolutePosition ¶
type AbsolutePosition struct {
Type SharedType
Index Number
Assoc Number
}
func CreateAbsolutePositionFromRelativePosition ¶
func CreateAbsolutePositionFromRelativePosition(rpos *RelativePosition, doc *Doc) *AbsolutePosition
func NewAbsolutePosition ¶
func NewAbsolutePosition(t SharedType, index, assoc Number) *AbsolutePosition
NewAbsolutePosition constructs an absolute position against a shared type.
type ArrayAny ¶
type ArrayAny = []any
ArrayAny is the Go form of a JS Array<any>.
func TypeListToArraySnapshot ¶
func TypeListToArraySnapshot(t SharedType, snapshot *Snapshot) ArrayAny
TypeListToArraySnapshot returns the visible list contents of t at snapshot. The result is caller-owned. SharedType keeps the snapshot capability public without reopening the internal Item/AbstractType object graph.
type Awareness ¶
type Awareness struct {
*Observable
Doc *Doc
ClientID Number
// contains filtered or unexported fields
}
Awareness is the PLAIN presence type: it never starts a goroutine.
Its maps are private and every supported boundary transfers ownership: setters deep-copy caller data and getters return independent deep snapshots. This is required even for the plain type because NewManagedAwarenessFrom may attach a timer goroutine to the same value after the caller has retained its pointer. Protecting only the maps would not be enough — Object values are reference-like handles, so mutating a nested value obtained through an accessor would still race a managed writer.
PARITY LIMITATION (FR-011): this type performs no local RENEWAL. The reference's timer does two things — reaping stale remotes, and re-publishing local state so remote peers do not drop this client. Reaping is a read-time judgement and happens on access here; renewal is an outbound heartbeat triggered by elapsed time, which nothing read-triggered can reproduce. A client whose program stays quiet past the timeout will therefore be dropped by reference peers. Use ManagedAwareness where that matters; presence parity claims attach to it.
func NewAwareness ¶
func (*Awareness) GetLocalState ¶
GetLocalState returns an independent deep snapshot of this client's state. Mutating it cannot change awareness state or race a ManagedAwareness writer.
func (*Awareness) GetMeta ¶
GetMeta returns a deep snapshot of the client→metadata map (clock / lastUpdated), the read-only counterpart to GetStates. Both the map and its Object values are independent of the internal metadata.
func (*Awareness) GetStates ¶
GetStates returns a deep snapshot of the client→state map. Both the map and every mutable value reachable through it are independent of internal state.
func (*Awareness) SetLocalState ¶
SetLocalState sets (or, when state.IsNil(), clears) this client's awareness state. A nil/cleared state is represented by the zero Object value (IsNil). The state is deep-copied before it enters the awareness maps. Unsupported mutable values are rejected without changing state, clocks, or observers.
func (*Awareness) SetLocalStateField ¶
type ChangedSubs ¶
type ChangedSubs map[string]struct{}
ChangedSubs is the set of parent-sub keys changed on one shared type during a transaction. ParentSub is always a string in this implementation; list changes use the empty string where Yjs uses null. Keeping that fact in the type avoids boxing every changed map key into an interface.
func (ChangedSubs) Delete ¶
func (s ChangedSubs) Delete(key string)
Delete removes key from the changed set.
func (ChangedSubs) Has ¶
func (s ChangedSubs) Has(key string) bool
Has reports whether key was changed.
func (ChangedSubs) Range ¶
func (s ChangedSubs) Range(f func(string))
Range calls f once for every changed key.
type Doc ¶
type Doc struct {
*Observable
GUID string
ClientID Number
GC bool
ShouldLoad bool
AutoLoad bool
Meta interface{}
// contains filtered or unexported fields
}
Doc is a Yjs document: the container for shared types, the transaction boundary, and the unit of synchronisation.
func CreateDocFromSnapshot ¶
func NewDoc ¶
NewDoc constructs a document. Everything optional is a DocOption, so the common case is NewDoc("room-1") and each departure from the defaults is named at the call site.
The previous signature took gc, meta and autoLoad positionally. Every one of the seventy-odd call sites in this repository passed nil for meta and false for autoLoad — two parameters that existed only to be defaulted, in front of a reader who had to look up which bool was which.
Defaults: garbage collection ON (matching the yjs Doc constructor), no meta, no auto-load. The package's reference-compatible GC filter is always used; the internal item graph stays unexposed.
func (*Doc) Destroy ¶
func (doc *Doc) Destroy()
Destroy emits the `destroy` event and unregisters all event handlers.
func (*Doc) Get ¶
func (doc *Doc) Get(name string, typeConstructor TypeConstructor) (SharedType, error)
Get defines a shared data type.
Multiple calls of `y.get(name, TypeConstructor)` yield the same result and do not overwrite each other. I.e. `y.define(name, Y.Array) === y.define(name, Y.Array)`
After this method is called, the type is also available on `y.share.get(name)`.
Best Practices: Define all types right after the Yjs instance is created and store them in a separate object. Also use the typed methods `getText(name)`, `getArray(name)`, ..
example
const y = new Y(..)
const appState = {
document: y.getText('document')
comments: y.getArray('comments')
}
func (*Doc) GetSubdocGUIDs ¶ added in v0.1.0
func (*Doc) GetSubdocs ¶
func (*Doc) GetXMLFragment ¶ added in v0.1.0
func (doc *Doc) GetXMLFragment(name string) *YXmlFragment
func (*Doc) Load ¶
func (doc *Doc) Load()
Load notifies the parent document that this subdocument requests its data be loaded (if it is a subdocument).
`load()` might be used in the future to request any provider to load the most current data. It is safe to call `load()` multiple times.
func (*Doc) Off ¶
func (doc *Doc) Off(eventName string, handler *ObserverHandler)
func (*Doc) OffUpdate ¶
func (doc *Doc) OffUpdate(handler *ObserverHandler)
OffUpdate removes a handler registered by OnUpdate.
func (*Doc) OffUpdateV2 ¶
func (doc *Doc) OffUpdateV2(handler *ObserverHandler)
OffUpdateV2 removes a handler registered by OnUpdateV2.
func (*Doc) On ¶
func (doc *Doc) On(eventName string, handler *ObserverHandler)
func (*Doc) OnUpdate ¶
func (doc *Doc) OnUpdate(handler func(update []byte, origin any)) *ObserverHandler
OnUpdate subscribes to the document's byte-level V1 update stream: the exact bytes to persist or broadcast, plus the origin the mutating transaction was given. It returns the handler so it can be passed to OffUpdate.
WHY THIS EXISTS RATHER THAN On("update", ...). The generic observer delivers ...interface{}, so every consumer of the most important server-side hook in the library begins with a type assertion. That assertion is the whole seam a persistence layer hangs off, and getting it wrong fails SILENTLY: the handler runs, the assertion does not match, nothing is stored, no error is returned, and the document itself is perfectly correct. A relay wired that way loses every write and looks healthy doing it.
The mistake is not hypothetical, because the event name is not unique. Awareness also emits "update", with an Object payload rather than []byte, so the assertion someone copies from the awareness path compiles, runs, matches nothing, and drops the entire update stream. See TestUpdateSeamCanSilentlyDropEverything.
The generic Doc.On remains for the other events; this only removes the guesswork from the one whose payload a server cannot afford to mis-handle.
func (*Doc) OnUpdateV2 ¶
func (doc *Doc) OnUpdateV2(handler func(update []byte, origin any)) *ObserverHandler
OnUpdateV2 is OnUpdate for the V2 update stream. A document emits both, so a consumer picks the encoding it stores and subscribes to that one only — subscribing to both persists every change twice.
func (*Doc) ToJSON ¶ added in v0.1.0
ToJSON converts the entire document into a js object, recursively traversing each yjs type Doesn't log types that have not been defined (using ydoc.getType(..)).
Do not use this method and rather call toJSON directly on the shared types.
func (*Doc) Transact ¶
func (doc *Doc) Transact(f func(trans *Transaction), origin interface{})
Transact runs f inside a transaction.
Changes that happen inside of a transaction are bundled. This means that the observer fires _after_ the transaction is finished and that all changes that happened inside of the transaction are sent as one message to the other peers.
type DocOption ¶
type DocOption func(*Doc)
DocOption customizes a Doc at construction. It exists primarily to inject a deterministic ClientID (WithClientID) so byte-parity tests can pin the client id to a fixture value without monkey-patching generateNewClientID. Production callers pass no options and get a random client id as before.
func WithAutoLoad ¶
WithAutoLoad makes a subdocument load its content as soon as it is integrated into a parent, rather than waiting for an explicit Load. It is off by default.
func WithClientID ¶
WithClientID pins the document's ClientID to a fixed value instead of the random generateNewClientID(). Used by the V2 byte-parity tests to match the JS fixtures' fixed client id deterministically (and without the fragile mockey gcflags-dependent patch).
func WithGC ¶
WithGC controls garbage collection of deleted content. It is ENABLED by default, matching the yjs Doc constructor. Disable it when you need deleted items to stay addressable — snapshots and time-travel over a document's history both require that.
func WithMeta ¶
func WithMeta(meta interface{}) DocOption
WithMeta attaches arbitrary application data to the document. The library never reads it; it is carried so a service can associate a document with its own record without a side table.
func WithReadCache ¶
WithReadCache controls bounded, mutation-invalidated projections used by repeated ToString, ToDelta, map and XML reads. It is enabled by default. Disable it for large fleets of mostly-idle documents where retained heap matters more than repeated-read latency; see docs/PERFORMANCE.md.
type EventAction ¶
type EventAction struct {
Action string
OldValue interface{}
NewValue interface{}
}
type EventOperator ¶
type EventOperator struct {
// InsertText and Insert are the two arms of an insert. Keeping text typed
// avoids boxing every rendered string into an interface; Insert carries only
// embeds and nested shared types.
InsertText string
Insert interface{}
Length Number
Attributes Object
Kind EventOperatorKind
}
func NewDeleteDeltaOp ¶
func NewDeleteDeltaOp(length Number) EventOperator
NewDeleteDeltaOp constructs a delete.
func NewRetainDeltaOp ¶
func NewRetainDeltaOp(length Number, attributes Object) EventOperator
NewRetainDeltaOp constructs a retain, optionally carrying formatting attributes.
func NewTextDeltaOp ¶
func NewTextDeltaOp(text string, attributes Object) EventOperator
NewTextDeltaOp constructs a text insert, with Object{} meaning omitted attributes.
func NewValueDeltaOp ¶
func NewValueDeltaOp(value any, attributes Object) EventOperator
NewValueDeltaOp constructs an embed or nested-type insert.
func (EventOperator) GetKind ¶
func (op EventOperator) GetKind() EventOperatorKind
GetKind returns the operation represented by op. Delta operators are a tagged union in both the Yjs model and this Go representation; the accessor keeps callers independent of its physical layout.
func (EventOperator) HasAttributes ¶
func (op EventOperator) HasAttributes() bool
func (EventOperator) InsertValue ¶
func (op EventOperator) InsertValue() any
InsertValue returns the inserted text, embed, or nested type. Call it only for an insert operator.
func (EventOperator) IsInsert ¶
func (op EventOperator) IsInsert() bool
func (EventOperator) OpLength ¶
func (op EventOperator) OpLength() Number
OpLength returns the retain or delete length. It returns zero for other kinds.
type EventOperatorKind ¶
type EventOperatorKind uint8
EventOperatorKind identifies the single operation carried by an EventOperator. None is deliberately the zero value so an uninitialized operator remains a no-op.
const ( EventOperatorNone EventOperatorKind = iota EventOperatorInsertText EventOperatorInsertValue EventOperatorRetain EventOperatorDelete )
type ID ¶
type ID struct {
Client Number // client ID
Clock Number // unique per client id, continuous number
}
ID identifies a struct in the store. It is deliberately a compact value and not an IAbstractType; an unintegrated Item may temporarily hold *ID as its parent reference, which is why Item.Parent and NewItem accept interface{}.
type IEventType ¶
type IEventType interface {
GetTarget() SharedType
GetCurrentTarget() SharedType
Path() []interface{}
// contains filtered or unexported methods
}
type ManagedAwareness ¶
type ManagedAwareness struct {
// contains filtered or unexported fields
}
ManagedAwareness owns the reference's presence timer.
It exists because the reference's interval does TWO things and only one of them can be made lazy (research R6, verified in y-protocols/awareness.js):
- REAPING removes remote clients past the timeout. That is a read-time judgement about who counts as present, so the plain Awareness does it on access.
- RENEWAL re-publishes local state once half the timeout has elapsed, so remote peers do not drop this client. It is an OUTBOUND action whose trigger is elapsed time, not a read, so nothing read-triggered can reproduce it. A quiet client with no renewal is dropped by reference peers.
So the timer cannot simply be deleted — only made opt-in, which is exactly the exception Constitution II names ("unless explicitly requested by the consumer (e.g. awareness timeout cleanup)"). PRESENCE PARITY CLAIMS ATTACH TO THIS TYPE (FR-011a).
It is a SEPARATE type from Awareness rather than a mode flag so opting into a timer is explicit. Both types expose state only through deep-copying accessors: NewManagedAwarenessFrom can attach this writer to any existing Awareness, so the ownership boundary belongs to Awareness itself rather than this wrapper.
func NewManagedAwareness ¶
func NewManagedAwareness(doc *Doc) *ManagedAwareness
NewManagedAwareness wraps a fresh Awareness. It does NOT start the timer: a constructor is not an explicit request, and Constitution II permits the goroutine only when one is made.
func NewManagedAwarenessFrom ¶
func NewManagedAwarenessFrom(aw *Awareness) *ManagedAwareness
NewManagedAwarenessFrom adopts an existing Awareness, for a consumer that built one before deciding it needs the timer.
func (*ManagedAwareness) Awareness ¶
func (m *ManagedAwareness) Awareness() *Awareness
Awareness exposes the underlying presence value for encode/apply helpers that take one. Its maps remain private and its public state boundaries copy, so retaining this pointer cannot bypass the managed writer's lock.
func (*ManagedAwareness) Destroy ¶
func (m *ManagedAwareness) Destroy()
Destroy stops the timer and tears down the underlying value.
func (*ManagedAwareness) GetLocalState ¶
func (m *ManagedAwareness) GetLocalState() Object
GetLocalState returns this client's published state.
func (*ManagedAwareness) GetMeta ¶
func (m *ManagedAwareness) GetMeta() map[Number]Object
GetMeta returns a snapshot copy taken under the lock.
func (*ManagedAwareness) GetStates ¶
func (m *ManagedAwareness) GetStates() map[Number]Object
GetStates returns a snapshot copy taken under the lock.
func (*ManagedAwareness) On ¶
func (m *ManagedAwareness) On(event string, h *ObserverHandler)
On registers an observer, so a consumer sees the same change/update events the reference emits.
func (*ManagedAwareness) Running ¶
func (m *ManagedAwareness) Running() bool
Running reports whether the timer goroutine is live. Exported so a caller (and this package's tests) can assert the US6 invariant that construction starts nothing and Stop joins rather than merely signalling — otherwise "no goroutine is left behind" is only observable as a race-detector finding much later.
func (*ManagedAwareness) SetLocalState ¶
func (m *ManagedAwareness) SetLocalState(state Object) error
SetLocalState publishes an owned copy of local presence state.
func (*ManagedAwareness) Start ¶
func (m *ManagedAwareness) Start()
Start begins the reference's interval. Idempotent: calling it twice does not start two timers.
func (*ManagedAwareness) Stop ¶
func (m *ManagedAwareness) Stop()
Stop halts the timer and waits for it to exit, so a stopped value provably leaves no goroutine behind (C-P2.4). Idempotent.
type Number ¶
type Number = int
Number is the Go form of a JS Number.
func CleanupYTextFormatting ¶
CleanupYTextFormatting removes redundant formatting markers from t and returns the number of removed markers.
type Object ¶
type Object struct {
// contains filtered or unexported fields
}
Object is the Go analogue of a JS plain object: a string->any map that PRESERVES key insertion order.
Order matters for byte-parity with lib0/Yjs. lib0's writeAny emits object keys in JS insertion order (Object.keys), and JSON.stringify likewise serializes keys in insertion order. A Go map[string]any randomizes iteration order and json.Marshal sorts keys, so encoding a multi-key object through either would diverge from the JS byte stream (Y.Map / Y.Array plain-object values, multi-key format attributes, ContentJson content, ContentDoc.Opts). Object therefore carries an explicit ordered key slice alongside the value map; every encoder (WriteObject/WriteAny, the order-preserving JSON emitter) and decoder (ReadObject/ReadAny, the order-preserving JSON parser) walks keys in this order, and decoding a JS-produced update then re-encoding it reproduces identical bytes.
Object is a thin handle around a shared *objectData pointer, so copying an Object value (assignment, passing it to a function, storing it in a map) shares the same backing data — RESTORING the reference semantics the previous `type Object = map[string]any` alias had. Mutations via Set/Delete are visible through every copy, exactly as map mutations were. The zero Object has a nil handle (IsNil); newObject() allocates one.
func MakeObject ¶
MakeObject builds an Object from an even-length (key, value, key, value, ...) argument list, preserving the given order. It is the ordered replacement for the old `Object{"k": v, ...}` composite literal. Panics on an odd argument count or a non-string key, both of which are programmer errors.
func ParseAwarenessStateJSON ¶
ParseAwarenessStateJSON classifies an awareness entry's state JSON string into either a cleared state (the zero Object) or a populated state Object, with identical empty/null/object handling for BOTH the core decode path (decodeAwarenessEntries) and the protocol package (DecodeAwarenessMessage).
This is the single source of truth that removes a decode DRIFT: the core path historically wrapped the parse in `if data != ""` (so an empty state string CLEARED the client), while the protocol path parsed unconditionally — so the same empty-state frame that cleared a cursor on the core side was rejected as ErrMalformedAwarenessState on the websocket side, leaving a GHOST cursor.
Classification (identical on both sides):
- "" -> cleared state (zero Object, nil error)
- JSON null -> cleared state (zero Object, nil error)
- a JSON object -> that Object, nil error
- any other valid JSON -> zero Object, ErrMalformedAwarenessState
- invalid JSON -> zero Object, the underlying parse error
Note this returns the underlying parse error (not wrapped in ErrMalformedAwarenessState) so callers can wrap/classify it as they need; decodeAwarenessEntries wraps it in ErrMalformedAwarenessState, the protocol path maps it to its own sentinel.
func RelativePositionToJSON ¶
func RelativePositionToJSON(rpos *RelativePosition) Object
func TypeMapGetAllSnapshot ¶
func TypeMapGetAllSnapshot(parent SharedType, snapshot *Snapshot) Object
TypeMapGetAllSnapshot returns every visible key/value pair from a heterogeneous map-like shared type as it existed at snapshot.
func (Object) GetOr ¶
GetOr returns the value for key, or nil if absent (the ergonomic accessor for the common `obj[key]` read where a missing key should read as nil/zero).
func (Object) GetOrNull ¶
GetOrNull returns the value for key, coalesced to the Null sentinel when the key is absent (or holds Go nil) — matching yjs `o.get(key) ?? null`. This is the canonical accessor for attribute/format reads, where a missing key must compare equal to an explicit JS null (so equalAttrs works); distinct from GetOr, which returns Go nil. (Consolidates the former y_text.go attrOrNull helper — the single `?? null` accessor, FR-009/SC-004.)
func (Object) IsNil ¶
IsNil reports whether the Object is the uninitialized zero value (its backing handle is nil), as opposed to an explicitly-constructed empty object (newObject(), whose handle is non-nil). It is the sentinel the awareness layer uses to tell an ABSENT / cleared (JS null) state apart from a present-but-empty {} state — a distinction a `== nil` comparison gave for free when Object was a map alias.
func (Object) Keys ¶
Keys returns the keys in insertion order. The returned slice is a copy, safe to mutate without affecting the Object.
func (Object) MarshalJSON ¶
MarshalJSON makes Object a first-class encoding/json value that serializes its keys in INSERTION order (matching JS JSON.stringify), instead of the empty `{}` stdlib json would emit for a struct with only unexported fields. This means json.Marshal(anObject) — and json.Marshal of any value transitively containing Objects — produces byte-identical output to JSON.stringify for our value domain. (marshalJSONOrdered delegates here; this method is what makes plain json.Marshal correct too, e.g. ToJSON() round-trips.)
func (*Object) Set ¶
Set inserts or updates key. A new key is appended to the order; updating an existing key keeps its original position (matching JS object semantics, where re-assigning a property does not move it). Calling Set on a zero Object lazily allocates its backing store, so the zero value behaves like an empty object the moment it is written.
func (Object) ShallowClone ¶
ShallowClone returns a copy of the Object with its OWN top-level backing store (a fresh key slice + value map) but SHARING every nested value by reference — the Go analogue of JS object.assign({}, o) / lib0 map.copy. Top-level Set/Delete on the clone do not affect the original (and vice-versa), but a nested Object / []any value is the SAME reference in both.
This is what the Y.Text formatting cleanup needs (cleanupFormattingGap's startAttributes / deleteText's endAttributes): Yjs builds those via a shallow object.assign, so a nested-object-valued format attribute compares == (same reference) against the active attribute and a redundant ContentFormat marker is correctly dropped. A deep Clone() would give the nested value a fresh handle, so the reference-strict equalAttrs reported it unequal and Go kept a redundant marker — diverging the item chain / ToDelta from JS. Use ShallowClone there; use Clone() only where an independent deep copy is genuinely required.
func (Object) ToMap ¶
ToMap returns an unordered map[string]any copy of the contents. For callers (state assertions, JSON-as-map interop) that do not care about order.
func (*Object) UnmarshalJSON ¶
UnmarshalJSON decodes a JSON object into this Object, preserving the on-wire key order. A JSON `null` leaves the Object as its zero value (IsNil); a non-object JSON value is an error (Object only represents JS plain objects).
type Observable ¶
type Observable struct {
// contains filtered or unexported fields
}
func NewObservable ¶
func NewObservable() *Observable
func (*Observable) Destroy ¶
func (o *Observable) Destroy()
func (*Observable) Emit ¶
func (o *Observable) Emit(name interface{}, v ...interface{})
func (*Observable) HasObserver ¶
func (o *Observable) HasObserver(name interface{}) bool
HasObserver reports whether any handler is registered for name, under the lock. Callers that previously read the observers map directly (e.g. the transaction update-emit fast path) must use this so they don't race On/Off/Destroy on the map.
func (*Observable) HasObservers ¶
func (o *Observable) HasObservers() bool
HasObservers reports whether any event has a registered handler.
func (*Observable) Off ¶
func (o *Observable) Off(name interface{}, handler *ObserverHandler) bool
Off removes handler from name's observer set and reports whether it was present. The bool lets Emit atomically "claim" a Once handler, so two concurrent emits of the same event (e.g. the reaper and a consumer) invoke it exactly once.
func (*Observable) On ¶
func (o *Observable) On(name interface{}, handle *ObserverHandler)
func (*Observable) Once ¶
func (o *Observable) Once(name interface{}, handler *ObserverHandler)
type ObserverHandler ¶
type ObserverHandler struct {
// contains filtered or unexported fields
}
func NewObserverHandler ¶
func NewObserverHandler(f func(v ...interface{})) *ObserverHandler
type RelativePosition ¶
type RelativePosition struct {
Type *ID
Tname string
Item *ID
// A relative position is associated to a specific character. By default
// assoc >= 0, the relative position is associated to the character
// after the meant position.
// I.e. position 1 in 'ab' is associated to character 'b'.
//
// If assoc < 0, then the relative position is associated to the caharacter
// before the meant position.
Assoc Number
}
func CreateRelativePositionFromJSON ¶
func CreateRelativePositionFromJSON(json Object) (*RelativePosition, error)
CreateRelativePositionFromJSON rebuilds a position from its JSON projection.
It returns an error because it used to assert `v.(ID)` on a field that RelativePositionToJSON writes as *ID: our own round trip panicked with "interface conversion: interface {} is *y_crdt.ID, not y_crdt.ID". The two halves of one projection disagreed about their own format, and nothing caught it because nothing round-tripped them. The assertions on tname and assoc were equally bare, so a hand-assembled or externally-sourced Object could crash the process on any field.
func DecodeRelativePosition ¶
func DecodeRelativePosition(uint8Array []uint8) (*RelativePosition, error)
DecodeRelativePosition decodes a relative position from its wire bytes.
func NewRelativePosition ¶
func NewRelativePosition(t SharedType, item *ID, assoc Number) *RelativePosition
NewRelativePosition constructs a relative position against a shared type.
func NewRelativePositionFromTypeIndex ¶
func NewRelativePositionFromTypeIndex(tp SharedType, index, assoc Number) *RelativePosition
NewRelativePositionFromTypeIndex creates a relative position at an index in a package-owned shared type.
type SharedType ¶
type SharedType interface {
// contains filtered or unexported methods
}
SharedType is the sealed public handle for a heterogeneous Yjs shared type. The unexported marker deliberately prevents independent external implementations: callers may pass and compare the shared types this package creates, but cannot use the interface to reopen the private object graph.
type Snapshot ¶
type Snapshot struct {
// contains filtered or unexported fields
}
func DecodeSnapshot ¶
DecodeSnapshot defaults to V1 — the historical, wire-compatible default. CALLER BEWARE, and this is a property of the FORMAT rather than of this implementation: a snapshot encoding carries no version marker, so neither this library nor the reference can tell V1 bytes from V2 bytes. Feeding one to the other's decoder SUCCEEDS and yields a structurally valid but WRONG snapshot. Verified identical in yjs@13.6.31, where decodeSnapshotV2(v1Bytes) and decodeSnapshot(v2Bytes) both return a snapshot that fails equalSnapshots against the original.
Making a mismatch an error here would therefore be a DEVIATION from the reference, not a fix: it would reject input yjs accepts. The mitigation is on the caller — record which encoding you wrote and read it back with the matching DecodeSnapshotV1 / DecodeSnapshotV2 rather than relying on this default. Pinned by TestSnapshotCrossFormatDecodeMatchesReference.
func DecodeSnapshotV1 ¶
DecodeSnapshotV1 decodes a V1-encoded snapshot (yjs decodeSnapshot). UpdateDecoderV1 is the V1 DS decoder (V1 has no column header) and satisfies DSDecoder.
func DecodeSnapshotV2 ¶
DecodeSnapshotV2 decodes a V2-encoded snapshot (yjs decodeSnapshotV2 default), using a bare DSDecoderV2.
func EmptySnapshot ¶
func EmptySnapshot() *Snapshot
func NewSnapshotByDoc ¶
NewSnapshotByDoc returns a snapshot of doc's current state.
type StackItem ¶
type StackItem struct {
Meta map[interface{}]interface{} // Use this to save and restore metadata like selection range
// contains filtered or unexported fields
}
type Transaction ¶
type Transaction struct {
// contains filtered or unexported fields
}
func (*Transaction) AfterState ¶
func (trans *Transaction) AfterState() map[Number]Number
AfterState returns a caller-owned snapshot of the state vector immediately after the transaction.
func (*Transaction) BeforeState ¶
func (trans *Transaction) BeforeState() map[Number]Number
BeforeState returns a caller-owned snapshot of the state vector immediately before the transaction. Observers may retain or modify it without changing transaction cleanup state.
func (*Transaction) ChangedTypes ¶
func (trans *Transaction) ChangedTypes() map[SharedType]ChangedSubs
ChangedTypes returns a caller-owned projection of the shared types changed by this transaction. Both the map and each ChangedSubs set are independent of the transaction's internal observer journal.
func (*Transaction) Document ¶
func (trans *Transaction) Document() *Doc
Document returns the document whose mutation this transaction records.
func (*Transaction) IsLocal ¶
func (trans *Transaction) IsLocal() bool
IsLocal reports whether this transaction originated on the local document.
func (*Transaction) Meta ¶
func (trans *Transaction) Meta() map[interface{}]Set
Meta returns the transaction-scoped metadata map. Unlike the state and subdocument accessors, this map is intentionally live: observers use it to pass application metadata between callbacks for the same transaction.
func (*Transaction) Origin ¶
func (trans *Transaction) Origin() any
Origin returns the caller-supplied transaction origin used for echo suppression and undo tracking.
func (*Transaction) SubdocsAdded ¶
func (trans *Transaction) SubdocsAdded() Set
SubdocsAdded returns a caller-owned snapshot of subdocuments added by the transaction.
func (*Transaction) SubdocsLoaded ¶
func (trans *Transaction) SubdocsLoaded() Set
SubdocsLoaded returns a caller-owned snapshot of subdocuments requested for loading by the transaction.
func (*Transaction) SubdocsRemoved ¶
func (trans *Transaction) SubdocsRemoved() Set
SubdocsRemoved returns a caller-owned snapshot of subdocuments removed by the transaction.
type TypeConstructor ¶
type TypeConstructor func() SharedType
TypeConstructor constructs a shared type for Doc.Get.
type UndoManager ¶
type UndoManager struct {
*Observable
TrackedOrigins Set
UndoStack []*StackItem
RedoStack []*StackItem
// Whether the client is currently undoing (calling UndoManager.undo)
Undoing bool
Redoing bool
LastChange Number
// G4 options/fields (yjs UndoManagerOptions):
CaptureTimeout Number // merge window (ms); default 500
CaptureTransaction func(trans *Transaction) bool // skip a transaction's capture if false
IgnoreRemoteMapChanges bool // allow undo to overwrite a concurrent remote map write
CurrStackItem *StackItem // the stack item being built in the current capture
// contains filtered or unexported fields
}
UndoManager is a port of yjs@13.6.31 src/utils/UndoManager.js. The scope may be a shared type or the whole *Doc; trackedOrigins and the capture-coalescing window match upstream. See the per-gap notes inline.
func NewUndoManager ¶
func NewUndoManager(typeScope interface{}, captureTimeout Number, trackedOrigins Set) *UndoManager
NewUndoManager constructs an undo manager without exposing the internal item representation through a delete-filter callback. The reference-compatible allow-all filter is used; scope, capture window, and tracked origins remain caller-controlled.
func (*UndoManager) AddToScope ¶
func (u *UndoManager) AddToScope(scopes ...interface{})
AddToScope adds tracked roots, deduping. Each argument is an IAbstractType (a shared-type scope) or a *Doc (whole-document scope -> docScoped). (G7)
func (*UndoManager) AddTrackedOrigin ¶
func (u *UndoManager) AddTrackedOrigin(origin interface{})
AddTrackedOrigin / RemoveTrackedOrigin manage the tracked-origin set. (G7)
func (*UndoManager) CanRedo ¶
func (u *UndoManager) CanRedo() bool
func (*UndoManager) CanUndo ¶
func (u *UndoManager) CanUndo() bool
CanUndo / CanRedo report whether a step is available. (G6)
func (*UndoManager) Clear ¶
func (u *UndoManager) Clear(clearUndoStack, clearRedoStack bool)
Clear selectively clears the undo and/or redo stacks, unpinning their kept items so GC can reclaim them, and emits 'stack-cleared'. (G6, replaces the old no-arg Clear + the raw `RedoStack = nil` discard that leaked kept items.)
func (*UndoManager) Destroy ¶
func (u *UndoManager) Destroy()
Destroy unregisters the afterTransaction + doc-'destroy' listeners and removes self from tracked origins (yjs UndoManager.destroy). (G7)
It deliberately does NOT Clear/unpin the stacks: KeepItem is a boolean flag, not a refcount, so unpinning here would clear keep on a struct a SIBLING UndoManager (overlapping scope, same captured deletion) still needs, letting GC collect it and turning the sibling's redo into a no-op. yjs avoids exactly this by not unpinning on destroy (kept structs are released when the Doc is dropped). The stacks are released when this manager is GC'd. (The doc-'destroy' off is a Go-specific leak fix — yjs leaves that listener, accumulating on a long-lived Doc across create/destroy cycles.)
func (*UndoManager) GetDoc ¶
func (u *UndoManager) GetDoc() *Doc
func (*UndoManager) RemoveTrackedOrigin ¶
func (u *UndoManager) RemoveTrackedOrigin(origin interface{})
func (*UndoManager) StopCapturing ¶
func (u *UndoManager) StopCapturing()
StopCapturing prevents the next StackItem from being merged into the current one.
UndoManager merges Undo-StackItem if they are created within time-gap smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next StackItem won't be merged.
type YArray ¶
type YArray struct {
// contains filtered or unexported fields
}
YArray is a shared Array implementation.
func (*YArray) Insert ¶
Insert inserts new content at an index.
Important: This function expects an array of content. Not just a content object. The reason for this "weirdness" is that inserting several elements is very efficient when it is done as a single operation.
@example // Insert character 'a' at position 0 yarray.insert(0, ['a']) // Insert numbers 1, 2 at position 1 yarray.insert(1, [1, 2])
func (*YArray) Map ¶
Map returns an Array with the result of calling a provided function on every element of this YArray.
func (*YArray) Observe ¶
func (t *YArray) Observe(f func(interface{}, interface{}))
Observe all events that are created on this type.
func (*YArray) ObserveDeep ¶
func (t *YArray) ObserveDeep(f func(interface{}, interface{}))
Observe all events that are created by this type and its children.
func (*YArray) Push ¶
Push appends content to this YArray.
Routed through typeListPushGenerics rather than Insert(length): appending must land after trailing TOMBSTONES, which an index-based insert does not do. See typeListPushGenerics.
func (*YArray) Splice ¶
Splice removes deleteCount elements at index and inserts content in their place, mirroring JavaScript's Array.prototype.splice.
func (*YArray) Unobserve ¶
func (t *YArray) Unobserve(f func(interface{}, interface{}))
Unregister an observer function.
func (*YArray) UnobserveDeep ¶
func (t *YArray) UnobserveDeep(f func(interface{}, interface{}))
Unregister an observer function.
type YArrayEvent ¶
type YArrayEvent struct {
YEvent
YTrans *Transaction
}
YArrayEvent describes the changes on a YArray.
type YEvent ¶
type YEvent struct {
Changes Object
Keys map[string]EventAction // Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>}
// contains filtered or unexported fields
}
YEvent describes the changes on a YType.
func (*YEvent) GetChanges ¶
func (*YEvent) GetCurrentTarget ¶
func (y *YEvent) GetCurrentTarget() SharedType
func (*YEvent) GetDelta ¶
func (y *YEvent) GetDelta() []EventOperator
func (*YEvent) GetKeys ¶
func (y *YEvent) GetKeys() map[string]EventAction
func (*YEvent) GetTarget ¶
func (y *YEvent) GetTarget() SharedType
func (*YEvent) Path ¶
func (y *YEvent) Path() []interface{}
Path computes the path from `y` to the changed type.
@todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with.
The following property holds: @example ----------------------------------------------------------------------------
let type = y
event.path.forEach(dir => {
type = type.get(dir)
})
type === event.target // => true
----------------------------------------------------------------------------
type YMap ¶
type YMap struct {
// contains filtered or unexported fields
}
YMap is a shared Map implementation.
func (*YMap) AppendKeys ¶
AppendKeys appends the keys for each element in the YMap to dst and returns the extended slice. The caller owns the returned slice and may safely reuse or mutate it; cached key storage is never exposed. Reusing the returned slice as dst[:0] avoids the allocation that Keys must perform for its ownership guarantee; passing nil or a newly allocated destination does not.
func (*YMap) Observe ¶
func (t *YMap) Observe(f func(interface{}, interface{}))
Observe all events that are created on this type.
func (*YMap) ObserveDeep ¶
func (t *YMap) ObserveDeep(f func(interface{}, interface{}))
Observe all events that are created by this type and its children.
func (*YMap) Unobserve ¶
func (t *YMap) Unobserve(f func(interface{}, interface{}))
Unregister an observer function.
func (*YMap) UnobserveDeep ¶
func (t *YMap) UnobserveDeep(f func(interface{}, interface{}))
Unregister an observer function.
type YMapEvent ¶
type YMapEvent struct {
YEvent
KeysChanged ChangedSubs
}
YMapEvent describes the changes on a YMap.
func (*YMapEvent) GetChanges ¶
type YText ¶
type YText struct {
// contains filtered or unexported fields
}
YText represents text with formatting information.
This type replaces y-richtext as this implementation is able to handle block formats (format information on a paragraph), embeds (complex elements like pictures and videos), and text formats (**bold**, *italic*).
func NewDefaultYText ¶
func NewDefaultYText() *YText
func (*YText) ApplyDelta ¶
func (y *YText) ApplyDelta(delta []EventOperator, sanitize bool)
ApplyDelta applies a delta on this shared YText type. sanitize = true
func (*YText) GetAttribute ¶
GetAttribute returns the attribute value that belongs to the attribute name.
func (*YText) GetAttributes ¶
GetAttributes returns all attribute name/value pairs in a JSON Object.
func (*YText) InsertEmbed ¶
InsertEmbed inserts an embed at an index.
func (*YText) Observe ¶
func (t *YText) Observe(f func(interface{}, interface{}))
Observe all events that are created on this type.
func (*YText) ObserveDeep ¶
func (t *YText) ObserveDeep(f func(interface{}, interface{}))
Observe all events that are created by this type and its children.
func (*YText) RemoveAttribute ¶
RemoveAttribute removes an attribute.
func (*YText) SetAttribute ¶
SetAttribute sets or updates an attribute.
func (*YText) ToDelta ¶
func (y *YText) ToDelta(snapshot *Snapshot, prevSnapshot *Snapshot, computeYChange func(string, *ID) Object) []EventOperator
ToDelta returns the delta representation of this YText type.
func (*YText) Unobserve ¶
func (t *YText) Unobserve(f func(interface{}, interface{}))
Unregister an observer function.
func (*YText) UnobserveDeep ¶
func (t *YText) UnobserveDeep(f func(interface{}, interface{}))
Unregister an observer function.
type YTextEvent ¶
type YTextEvent struct {
YEvent
ChildListChanged bool // Whether the children changed.
KeysChanged ChangedSubs // Set of all changed attributes.
}
YTextEvent describes the changes on a YText type.
func (*YTextEvent) GetDelta ¶
func (y *YTextEvent) GetDelta() []EventOperator
GetDelta computes the changes in the delta format. A {@link https://quilljs.com/docs/delta/|Quill delta}) that represents the changes on the document.
type YXmlElement ¶
type YXmlElement struct {
YXmlFragment
NodeName string
// contains filtered or unexported fields
}
func NewYXmlElement ¶
func NewYXmlElement(nodeName string) *YXmlElement
NewYXmlElement creates a Y.XmlElement. Preliminary attributes and observer handlers are both allocated lazily: an element used only as an unobserved tree node needs neither.
func (*YXmlElement) Clone ¶
func (y *YXmlElement) Clone() *YXmlElement
Clone returns an independent XML element with cloned descendants.
func (*YXmlElement) GetAttribute ¶
func (y *YXmlElement) GetAttribute(attributeName string) interface{}
GetAttribute Returns an attribute value that belongs to the attribute name.
func (*YXmlElement) GetAttributes ¶
func (y *YXmlElement) GetAttributes() Object
GetAttributes Returns an attribute value that belongs to the attribute name.
func (*YXmlElement) GetNextSibling ¶
func (y *YXmlElement) GetNextSibling() SharedType
GetNextSibling return {YXmlElement|YXmlText|nil}
func (*YXmlElement) GetPrevSibling ¶
func (y *YXmlElement) GetPrevSibling() SharedType
GetPrevSibling return {YXmlElement|YXmlText|nil}
func (*YXmlElement) HasAttribute ¶
func (y *YXmlElement) HasAttribute(attributeName string) bool
HasAttribute Returns whether an attribute exists
func (*YXmlElement) Observe ¶
func (t *YXmlElement) Observe(f func(interface{}, interface{}))
Observe all events that are created on this type.
func (*YXmlElement) ObserveDeep ¶
func (t *YXmlElement) ObserveDeep(f func(interface{}, interface{}))
Observe all events that are created by this type and its children.
func (*YXmlElement) RemoveAttribute ¶
func (y *YXmlElement) RemoveAttribute(attributeName string)
RemoveAttribute Removes an attribute from this YXmlElement.
func (*YXmlElement) SetAttribute ¶
func (y *YXmlElement) SetAttribute(attributeName string, attributeValue interface{})
SetAttribute Sets or updates an attribute.
func (*YXmlElement) ToString ¶
func (y *YXmlElement) ToString() string
func (*YXmlElement) Unobserve ¶
func (t *YXmlElement) Unobserve(f func(interface{}, interface{}))
Unregister an observer function.
func (*YXmlElement) UnobserveDeep ¶
func (t *YXmlElement) UnobserveDeep(f func(interface{}, interface{}))
Unregister an observer function.
type YXmlEvent ¶
type YXmlEvent struct {
YEvent
ChildListChanged bool // Whether the children changed.
AttributesChanged ChangedSubs // Set of all changed attributes.
}
YXmlEvent An Event that describes changes on a YXml Element or Yxml Fragment
type YXmlFragment ¶
type YXmlFragment struct {
// contains filtered or unexported fields
}
func NewYXmlFragment ¶
func NewYXmlFragment() *YXmlFragment
func (*YXmlFragment) Clone ¶
func (y *YXmlFragment) Clone() *YXmlFragment
Clone returns an independent XML fragment with cloned nested shared values.
func (*YXmlFragment) CreateTreeWalker ¶
func (y *YXmlFragment) CreateTreeWalker(filter func(SharedType) bool) *YXmlTreeWalker
func (*YXmlFragment) Delete ¶
func (y *YXmlFragment) Delete(index, length Number)
Delete deletes elements starting from an index. Default: length = 1
func (*YXmlFragment) Get ¶
func (y *YXmlFragment) Get(index Number) interface{}
Get returns the i-th child of this YXmlFragment.
func (*YXmlFragment) GetFirstChild ¶
func (y *YXmlFragment) GetFirstChild() SharedType
func (*YXmlFragment) GetLength ¶
func (y *YXmlFragment) GetLength() Number
func (*YXmlFragment) Insert ¶
func (y *YXmlFragment) Insert(index Number, content ArrayAny)
Insert Inserts new content at an index.
@example
// Insert character 'a' at position 0
xml.insert(0, [new Y.XmlText('text')])
func (*YXmlFragment) InsertAfter ¶
func (y *YXmlFragment) InsertAfter(ref SharedType, content ArrayAny)
InsertAfter inserts new content after the given reference item.
@example
// Insert character 'a' at position 0
xml.insert(0, [new Y.XmlText('text')])
func (*YXmlFragment) Observe ¶
func (t *YXmlFragment) Observe(f func(interface{}, interface{}))
Observe all events that are created on this type.
func (*YXmlFragment) ObserveDeep ¶
func (t *YXmlFragment) ObserveDeep(f func(interface{}, interface{}))
Observe all events that are created by this type and its children.
func (*YXmlFragment) Push ¶
func (y *YXmlFragment) Push(content ArrayAny)
Push appends content to this fragment.
Deliberately NOT routed through typeListPushGenerics, unlike YArray.Push. The reference is genuinely inconsistent here: YArray.push calls typeListPushGenerics (append after the last ITEM, tombstones included) while YXmlFragment.push calls `this.insert(this.length, content)` (append at the visible INDEX, before trailing tombstones). Making the two consistent would be a deviation. Verified by the differential: routing this through the push primitive diverged 1096/3000 seeds.
func (*YXmlFragment) QuerySelector ¶
func (y *YXmlFragment) QuerySelector(query string) SharedType
QuerySelector returns the first descendant whose node name matches query, case-insensitively, or nil. Faithful to yjs: uppercase the query and filter on nodeName (src/types/YXmlFragment.js).
func (*YXmlFragment) QuerySelectorAll ¶
func (y *YXmlFragment) QuerySelectorAll(query string) []SharedType
QuerySelectorAll returns every descendant whose node name matches query, case-insensitively.
func (*YXmlFragment) Slice ¶
func (y *YXmlFragment) Slice(start, end Number) ArrayAny
Slice returns the children in [start, end) as a Go slice. Default: start = 0
func (*YXmlFragment) ToArray ¶
func (y *YXmlFragment) ToArray() ArrayAny
ToArray transforms this YXmlFragment's children into a Go slice.
func (*YXmlFragment) ToJSON ¶ added in v0.1.0
func (y *YXmlFragment) ToJSON() interface{}
func (*YXmlFragment) ToString ¶
func (y *YXmlFragment) ToString() string
ToString returns the string representation of all the children of this YXmlFragment.
func (*YXmlFragment) Unobserve ¶
func (t *YXmlFragment) Unobserve(f func(interface{}, interface{}))
Unregister an observer function.
func (*YXmlFragment) UnobserveDeep ¶
func (t *YXmlFragment) UnobserveDeep(f func(interface{}, interface{}))
Unregister an observer function.
func (*YXmlFragment) Unshift ¶
func (y *YXmlFragment) Unshift(content ArrayAny)
Unshift prepends content to this YXmlFragment.
type YXmlText ¶
type YXmlText struct {
YText
}
YXmlText Represents text in a Dom Element. In the future this type will also handle simple formatting information like bold and italic.
func NewYXmlText ¶
func NewYXmlText() *YXmlText
func (*YXmlText) GetNextSibling ¶
func (y *YXmlText) GetNextSibling() SharedType
func (*YXmlText) GetPreSibling ¶
func (y *YXmlText) GetPreSibling() SharedType
func (*YXmlText) Observe ¶
func (t *YXmlText) Observe(f func(interface{}, interface{}))
Observe all events that are created on this type.
func (*YXmlText) ObserveDeep ¶
func (t *YXmlText) ObserveDeep(f func(interface{}, interface{}))
Observe all events that are created by this type and its children.
func (*YXmlText) Unobserve ¶
func (t *YXmlText) Unobserve(f func(interface{}, interface{}))
Unregister an observer function.
func (*YXmlText) UnobserveDeep ¶
func (t *YXmlText) UnobserveDeep(f func(interface{}, interface{}))
Unregister an observer function.
type YXmlTreeWalker ¶
type YXmlTreeWalker struct {
// contains filtered or unexported fields
}
YXmlTreeWalker is a depth-first walker over an XML subtree, faithful to yjs's YXmlTreeWalker (src/types/YXmlFragment.js). It was previously a struct whose constructor returned nil and whose Filter had the wrong signature — a stub that made CreateTreeWalker, QuerySelector and QuerySelectorAll all silently do nothing (Constitution IX: no misleading placeholders).
func NewYXmlTreeWalker ¶
func NewYXmlTreeWalker(root SharedType, f func(SharedType) bool) *YXmlTreeWalker
NewYXmlTreeWalker is not supported yet.
func (*YXmlTreeWalker) Next ¶
func (w *YXmlTreeWalker) Next() SharedType
Next returns the next matching type, or nil when the walk is done — the Go shape of yjs's iterator protocol (`{value, done}`).
Source Files
¶
- abstract.go
- awareness.go
- client_struct_hybrid.go
- client_struct_hybrid_default.go
- client_struct_list.go
- client_struct_list_oracle_disabled.go
- client_struct_tree.go
- codec_varint.go
- const.go
- content_types.go
- data_clone.go
- decoding.go
- delete_set.go
- doc.go
- encoding.go
- event_handler.go
- gc.go
- id.go
- int_diff_opt_rle_codec.go
- interfaces.go
- item.go
- json_ordered.go
- list_position_index.go
- merge.go
- observable.go
- oracle_reads.go
- protocols.go
- relative_position.go
- rle_codec.go
- skip.go
- snapshot.go
- string_codec.go
- struct_decode.go
- struct_store.go
- sync.go
- transaction.go
- type_define.go
- uint_opt_rle_codec.go
- undo_manager.go
- update_decoder.go
- update_encoder.go
- updates.go
- utf8_text.go
- utils.go
- y_array.go
- y_event.go
- y_map.go
- y_string.go
- y_text.go
- y_xml.go