crdt

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 22 Imported by: 0

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

Examples

Constants

View Source
const (
	ActionAdd    = "add"
	ActionDelete = "delete"
	ActionUpdate = "update"
)
View Source
const OutdatedTimeout = 30 * time.Second

Variables

View Source
var (
	Null      = NullType{}
	Undefined = UndefinedType{}
)
View Source
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.)

View Source
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.

View Source
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 ApplyAwarenessUpdate(awareness *Awareness, update []byte, origin interface{}) error

func ApplyUpdate

func ApplyUpdate(ydoc *Doc, update []uint8, transactionOrigin interface{}) error

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

func ApplyUpdateV2(ydoc *Doc, update []uint8, transactionOrigin interface{}) error

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

func AwarenessStateJSON(state Object) string

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 CompareIDs

func CompareIDs(a *ID, b *ID) bool

CompareIDs compares two IDs for equality.

func CompareRelativePositions

func CompareRelativePositions(a, b *RelativePosition) bool

func ConvertUpdateFormatV1ToV2

func ConvertUpdateFormatV1ToV2(update []uint8) ([]uint8, error)

ConvertUpdateFormatV1ToV2 converts a V1-encoded update to V2.

func ConvertUpdateFormatV2ToV1

func ConvertUpdateFormatV2ToV1(update []uint8) ([]uint8, error)

ConvertUpdateFormatV2ToV1 converts a V2-encoded update to V1.

func DecodeStateVector

func DecodeStateVector(decodedState []uint8) (map[Number]Number, error)

DecodeStateVector decodes a state-vector payload into its client clocks.

func DiffUpdate

func DiffUpdate(update []uint8, sv []uint8) ([]uint8, error)

func DiffUpdateV2

func DiffUpdateV2(update []uint8, sv []uint8) ([]uint8, error)

DiffUpdateV2 computes the differential V2 update of a V2-encoded update against the V1-encoded state vector sv.

func EncodeAwarenessUpdate

func EncodeAwarenessUpdate(awareness *Awareness, clients []Number, states map[Number]Object) []byte

func EncodeRelativePosition

func EncodeRelativePosition(rpos *RelativePosition) []uint8

func EncodeSnapshot

func EncodeSnapshot(snapshot *Snapshot) ([]uint8, error)

EncodeSnapshot defaults to V1 — the historical, wire-compatible default.

func EncodeSnapshotV1

func EncodeSnapshotV1(snapshot *Snapshot) ([]uint8, error)

EncodeSnapshotV1 encodes a snapshot in the V1 format (yjs encodeSnapshot, a bare DSEncoderV1).

func EncodeSnapshotV2

func EncodeSnapshotV2(snapshot *Snapshot) ([]uint8, error)

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 EncodeStateAsUpdate(doc *Doc, encodedTargetStateVector []uint8) ([]uint8, error)

func EncodeStateAsUpdateV2

func EncodeStateAsUpdateV2(doc *Doc, encodedTargetStateVector []uint8) ([]uint8, error)

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

func EncodeStateVector(doc *Doc) []byte

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

func EncodeStateVectorFromUpdate(update []uint8) ([]uint8, error)

EncodeStateVectorFromUpdate extracts the state vector from a V1-encoded update without materializing a document.

func EncodeStateVectorFromUpdateV2

func EncodeStateVectorFromUpdateV2(update []uint8) ([]uint8, error)

EncodeStateVectorFromUpdateV2 extracts the state vector from a V2-encoded update without materializing a document.

func EqualSnapshots

func EqualSnapshots(snap1, snap2 *Snapshot) bool

func MergeUpdates

func MergeUpdates(updates [][]byte) ([]byte, error)

MergeUpdates merges V1 updates into one canonical V1 update.

func MergeUpdatesV2

func MergeUpdatesV2(updates [][]byte) ([]byte, error)

MergeUpdatesV2 merges V2 updates into one canonical V2 update.

func ModifyAwarenessUpdate

func ModifyAwarenessUpdate(update []byte, modify func(interface{}) interface{}) ([]byte, error)

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

func ObfuscateUpdate(update []uint8) ([]uint8, error)

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

func ParseUpdateMeta(update []uint8) (map[Number]Number, map[Number]Number, error)

ParseUpdateMeta returns the state-vector range covered by a V1 update without materializing a document.

func RemoveAwarenessStates

func RemoveAwarenessStates(awareness *Awareness, clients []Number, origin interface{})

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

func SnapshotContainsUpdate(snapshot *Snapshot, update []uint8) (bool, error)

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

func SnapshotContainsUpdateV2(snapshot *Snapshot, update []uint8) (bool, error)

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 NewAwareness(doc *Doc) *Awareness

func (*Awareness) Destroy

func (a *Awareness) Destroy()

func (*Awareness) GetLocalState

func (a *Awareness) GetLocalState() Object

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

func (a *Awareness) GetMeta() map[Number]Object

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

func (a *Awareness) GetStates() map[Number]Object

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

func (a *Awareness) SetLocalState(state Object) error

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

func (a *Awareness) SetLocalStateField(field string, value interface{}) error

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

func (s ChangedSubs) Add(key string)

Add records key as changed.

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 CreateDocFromSnapshot(originDoc *Doc, snapshot *Snapshot, newDoc *Doc) (*Doc, error)

func NewDoc

func NewDoc(guid string, opts ...DocOption) *Doc

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

func (doc *Doc) GetArray(name string) *YArray

func (*Doc) GetMap

func (doc *Doc) GetMap(name string) *YMap

func (*Doc) GetSubdocGUIDs added in v0.1.0

func (doc *Doc) GetSubdocGUIDs() Set

func (*Doc) GetSubdocs

func (doc *Doc) GetSubdocs() Set

func (*Doc) GetText

func (doc *Doc) GetText(name string) *YText

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

func (doc *Doc) ToJSON() Object

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

func WithAutoLoad(enabled bool) DocOption

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

func WithClientID(clientID Number) DocOption

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

func WithGC(enabled bool) DocOption

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

func WithReadCache(enabled bool) DocOption

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{}.

func GenID

func GenID(client Number, clock Number) ID

GenID generates a new ID with the given client and clock values.

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 NullType

type NullType struct {
}

NullType is the Go form of JS null.

type Number

type Number = int

Number is the Go form of a JS Number.

func CleanupYTextFormatting

func CleanupYTextFormatting(t *YText) Number

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

func MakeObject(kv ...any) Object

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

func ParseAwarenessStateJSON(data string) (Object, error)

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

func (o Object) Delete(key string)

Delete removes key (and its position in the order) if present.

func (Object) Get

func (o Object) Get(key string) (any, bool)

Get returns the value for key and whether it is present.

func (Object) GetOr

func (o Object) GetOr(key string) any

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

func (o Object) GetOrNull(key string) any

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

func (o Object) Has(key string) bool

Has reports whether key is present.

func (Object) IsNil

func (o Object) IsNil() bool

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

func (o Object) Keys() []string

Keys returns the keys in insertion order. The returned slice is a copy, safe to mutate without affecting the Object.

func (Object) Len

func (o Object) Len() int

Len returns the number of keys.

func (Object) MarshalJSON

func (o Object) MarshalJSON() ([]byte, error)

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

func (o Object) Range(f func(key string, value any))

Range calls f for each key/value pair in insertion order.

func (*Object) Set

func (o *Object) Set(key string, value any)

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

func (o Object) ShallowClone() Object

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

func (o Object) ToMap() map[string]any

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

func (o *Object) UnmarshalJSON(data []byte) error

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 Set

type Set map[any]bool

Set is the Go form of a JS Set<any>.

func NewSet

func NewSet() Set

NewSet returns a new set.

func (Set) Add

func (s Set) Add(e any)

Add adds the given element to the set.

func (Set) Delete

func (s Set) Delete(e any)

Delete deletes the given element from the set.

func (Set) Has

func (s Set) Has(e any) bool

Has returns true if the given element is in the set.

func (Set) Range

func (s Set) Range(f func(element any))

Range calls the given function for each element in the set.

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

func DecodeSnapshot(buf []uint8) (*Snapshot, error)

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

func DecodeSnapshotV1(buf []uint8) (*Snapshot, error)

DecodeSnapshotV1 decodes a V1-encoded snapshot (yjs decodeSnapshot). UpdateDecoderV1 is the V1 DS decoder (V1 has no column header) and satisfies DSDecoder.

func DecodeSnapshotV2

func DecodeSnapshotV2(buf []uint8) (*Snapshot, error)

DecodeSnapshotV2 decodes a V2-encoded snapshot (yjs decodeSnapshotV2 default), using a bare DSDecoderV2.

func EmptySnapshot

func EmptySnapshot() *Snapshot

func NewSnapshotByDoc

func NewSnapshotByDoc(doc *Doc) *Snapshot

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 UndefinedType

type UndefinedType struct {
}

UndefinedType is the Go form of JS undefined.

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

func (u *UndoManager) Redo() *StackItem

Redo last undo operation.

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.

func (*UndoManager) Undo

func (u *UndoManager) Undo() *StackItem

Undo last changes on type.

type YArray

type YArray struct {
	// contains filtered or unexported fields
}

YArray is a shared Array implementation.

func NewYArray

func NewYArray() *YArray

func (*YArray) Clone

func (y *YArray) Clone() *YArray

Clone returns an independent YArray with cloned nested shared values.

func (*YArray) Delete

func (y *YArray) Delete(index, length Number)

Delete deletes elements starting from an index.

func (*YArray) ForEach

func (y *YArray) ForEach(f func(interface{}, Number, *YArray))

ForEach executes a provided function once on every element of this YArray.

func (*YArray) From

func (y *YArray) From(items ArrayAny) *YArray

From constructs a new YArray containing the specified items.

func (*YArray) Get

func (y *YArray) Get(index Number) interface{}

Get returns the i-th element from a YArray.

func (*YArray) GetLength

func (y *YArray) GetLength() Number

func (*YArray) Insert

func (y *YArray) Insert(index Number, content ArrayAny)

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

func (y *YArray) Map(f func(interface{}, Number, *YArray) interface{}) ArrayAny

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

func (y *YArray) Push(content ArrayAny)

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

func (y *YArray) Splice(start, end Number) ArrayAny

Splice removes deleteCount elements at index and inserts content in their place, mirroring JavaScript's Array.prototype.splice.

func (*YArray) ToArray

func (y *YArray) ToArray() ArrayAny

ToArray transforms this YArray to a JavaScript Array.

func (*YArray) ToJSON added in v0.1.0

func (y *YArray) ToJSON() interface{}

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.

func (*YArray) Unshift

func (y *YArray) Unshift(content ArrayAny)

Unshift prepends content to this YArray.

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 (y *YEvent) GetChanges() Object

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 NewYMap

func NewYMap(entries map[string]interface{}) *YMap

func (*YMap) AppendKeys

func (y *YMap) AppendKeys(dst []string) []string

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

func (y *YMap) Clear()

Clear removes all elements from this YMap.

func (*YMap) Clone

func (y *YMap) Clone() *YMap

Clone returns an independent YMap with cloned nested shared values.

func (*YMap) Delete

func (y *YMap) Delete(key string)

Delete removes a specified element from this YMap.

func (*YMap) Entries

func (y *YMap) Entries() map[string]interface{}

Entries returns an iterator of [key, value] pairs.

func (*YMap) ForEach

func (y *YMap) ForEach(f func(string, interface{}, *YMap)) Object

ForEach executes a provided function once on every key-value pair.

func (*YMap) Get

func (y *YMap) Get(key string) interface{}

Get returns a specified element from this YMap.

func (*YMap) GetLength

func (t *YMap) GetLength() Number

func (*YMap) GetSize

func (y *YMap) GetSize() Number

GetSize returns the size of the YMap (count of key/value pairs).

func (*YMap) Has

func (y *YMap) Has(key string) bool

func (*YMap) Keys

func (y *YMap) Keys() []string

Keys returns the keys for each element in the YMap Type.

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

func (y *YMap) Range(f func(key string, val interface{}))

func (*YMap) Set

func (y *YMap) Set(key string, value interface{}) interface{}

Set adds or updates an element with a specified key and value.

func (*YMap) ToJSON added in v0.1.0

func (y *YMap) ToJSON() interface{}

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.

func (*YMap) Values

func (y *YMap) Values() []interface{}

Values returns the values for each element in the YMap Type.

type YMapEvent

type YMapEvent struct {
	YEvent
	KeysChanged ChangedSubs
}

YMapEvent describes the changes on a YMap.

func (*YMapEvent) GetChanges

func (y *YMapEvent) GetChanges() Object

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 NewYText

func NewYText(text string) *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) Clone

func (y *YText) Clone() *YText

Clone returns an independent YText with the same delta.

func (*YText) Delete

func (y *YText) Delete(index Number, length Number)

Delete deletes text starting from an index.

func (*YText) Format

func (y *YText) Format(index Number, length Number, attributes Object)

Format assigns properties to a range of text.

func (*YText) GetAttribute

func (y *YText) GetAttribute(attributeName string) interface{}

GetAttribute returns the attribute value that belongs to the attribute name.

func (*YText) GetAttributes

func (y *YText) GetAttributes(snapshot *Snapshot) Object

GetAttributes returns all attribute name/value pairs in a JSON Object.

func (*YText) GetLength

func (t *YText) GetLength() Number

func (*YText) Insert

func (y *YText) Insert(index Number, text string, attributes Object)

Insert text at a given index.

func (*YText) InsertEmbed

func (y *YText) InsertEmbed(index Number, embed Object, attributes Object)

InsertEmbed inserts an embed at an index.

func (*YText) Length

func (y *YText) Length() Number

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

func (y *YText) RemoveAttribute(attributeName string)

RemoveAttribute removes an attribute.

func (*YText) SetAttribute

func (y *YText) SetAttribute(attributeName string, attributeValue interface{})

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) ToJSON added in v0.1.0

func (y *YText) ToJSON() interface{}

func (*YText) ToString

func (y *YText) ToString() string

ToString returns the unformatted string 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) Clone

func (y *YXmlText) Clone() *YXmlText

Clone returns an independent XML text node with the same delta.

func (*YXmlText) GetLength

func (t *YXmlText) GetLength() Number

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

func (y *YXmlText) ToJSON() string

func (*YXmlText) ToString

func (y *YXmlText) ToString() string

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

Jump to

Keyboard shortcuts

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