doc

package
v0.9.0 Latest Latest
Warning

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

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

Documentation

Overview

Package doc owns the Doc and Transaction types — the document container plus its mutation-lifecycle wrapper.

A Doc is the smallest self-contained CRDT replica: it holds a client identifier, a block store of every Item this replica has produced or received, and a single RWMutex that gates concurrent access. Mutations occur inside a Transaction (write) or are observed inside a Transaction (read).

See docs/yrs-port-notes/transaction.md for the per-method contract and the 11-step commit lifecycle yrs runs at TransactionMut.Commit. Most of that lifecycle is not yet implemented; see tech-debt.md.

Index

Constants

View Source
const MaxClientID uint64 = (1 << 53) - 1

MaxClientID is the upper bound on Doc.ClientID values. Set to 2^53-1 to match the JS-safe-integer range that JS Yjs uses for its clientID; all values produced by NewDoc fit in [1, MaxClientID].

Variables

This section is empty.

Functions

This section is empty.

Types

type Doc

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

Doc is a single CRDT replica. Construct with NewDoc.

Doc is safe for concurrent access through ReadTxn / WriteTxn — those methods acquire the internal RWMutex appropriately. Direct field access from outside the doc package is not safe.

func NewDoc

func NewDoc() *Doc

NewDoc returns a fresh Doc with default options and a random client identifier in [1, MaxClientID].

func NewDocWithOptions

func NewDocWithOptions(opts Options) *Doc

NewDocWithOptions returns a fresh Doc with the given options. A zero Options{} is equivalent to NewDoc().

func (*Doc) Branch

func (d *Doc) Branch(name string) *block.Branch

Branch returns the root branch with the given name, creating it lazily on first access. The returned *block.Branch is the underlying state shared by all types-layer wrappers (Map, Array, Text) constructed against this name on this Doc.

Concurrency: Branch acquires the doc's write lock to insert into the root-branch registry. It MUST be called outside any active Transaction or TransactionMut on this Doc — calling it inside a transaction deadlocks (sync.RWMutex is non-reentrant).

Typical usage:

d := doc.NewDoc()
settingsBranch := d.Branch("settings")  // outside any txn
m := types.NewMap(settingsBranch)        // wrap once
// later, inside a txn:
txn := d.WriteTxn()
m.Set(txn, "color", "red")
txn.Commit()

func (*Doc) ClientID

func (d *Doc) ClientID() uint64

ClientID returns the random per-replica identifier. Stable for the life of the Doc; used as the Client field in every ID this replica produces.

func (*Doc) GC

func (d *Doc) GC() bool

GC reports whether garbage collection of fully-observed deleted items will run at transaction commit.

func (*Doc) ReadTxn

func (d *Doc) ReadTxn() *Transaction

ReadTxn acquires the doc's read lock and returns a Transaction. The caller MUST call Close (typically via defer) to release the lock.

Multiple read transactions may coexist; they block any concurrent WriteTxn until all read transactions close.

func (*Doc) WriteTxn

func (d *Doc) WriteTxn() *TransactionMut

WriteTxn acquires the doc's write lock and returns a TransactionMut. The caller MUST call Commit (typically via defer) to release the lock and run the commit lifecycle.

WriteTxn blocks until all concurrent ReadTxn / WriteTxn close. Calling WriteTxn while already holding a write lock on this Doc from the same goroutine deadlocks (Go RWMutex is not re-entrant), matching yrs's transact_mut behaviour (transact.rs:255 explicit "this will hang forever" comment).

type Options

type Options struct {
	// DisableGC turns OFF garbage collection of fully-observed
	// deleted items at transaction commit. Default (false) means GC
	// is enabled, matching JS Yjs and yrs. Set to true only if you
	// intend to use snapshots; otherwise GC may drop content the
	// snapshot needs to materialize.
	DisableGC bool

	// ClientID overrides the random client identifier. When zero
	// (the default), NewDocWithOptions generates one via crypto/rand.
	// Pass a fixed value only for deterministic tests.
	ClientID uint64
}

Options bundles per-Doc settings. The zero value (Options{}) gives the safe defaults that JS Yjs and yrs use: GC enabled, random ClientID. Fields are inverted from their natural English where needed so that Go's zero-value default matches the recommended configuration.

type Transaction

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

Transaction is a read-only transaction holding the doc's read lock for its lifetime. Created by Doc.ReadTxn; released by Close.

Mirrors yrs Transaction<'doc>. The lock is held until Close runs; Go has no Drop trait, so Close must be called explicitly. Use a `defer txn.Close()` immediately after acquisition.

A Transaction value must not be retained past its Close call. yrs enforces this with a 'doc lifetime parameter; we document the contract and trust callers (see tech-debt.md).

func (*Transaction) Close

func (t *Transaction) Close()

Close releases the read lock. Safe to call more than once; only the first call unlocks.

func (*Transaction) Doc

func (t *Transaction) Doc() *Doc

Doc returns the Doc this transaction was created from.

func (*Transaction) PendingState

func (t *Transaction) PendingState() any

PendingState returns the opaque pending-update state stored on the doc, read-only. Concrete type is *encoding.Pending; see TransactionMut.PendingState for the rationale.

func (*Transaction) Store

func (t *Transaction) Store() *store.BlockStore

Store returns the doc's BlockStore for read access. Mutations through this pointer would race with WriteTxn writers; do not mutate from within a read transaction.

type TransactionMut

type TransactionMut struct {

	// Origin is an opaque caller-supplied value attached to this
	// transaction. Mirrors yrs Origin (transaction.rs:1210-1288).
	// Visible in observer events to distinguish e.g. local edits
	// from updates applied via ApplyUpdate.
	Origin any
	// contains filtered or unexported fields
}

TransactionMut is a write transaction holding the doc's write lock for its lifetime. Created by Doc.WriteTxn; released by Commit.

Mirrors yrs TransactionMut<'doc>. Accumulates change-tracking state during the transaction; consumes it at Commit time to run the post-commit lifecycle (squash, GC, observers, update emission).

Most lifecycle steps are not yet implemented — see tech-debt.md. Commit currently only releases the lock and marks the txn closed.

func (*TransactionMut) AddChangedType

func (t *TransactionMut) AddChangedType(parent *block.Branch, parentSub *string)

AddChangedType records that a Branch (with optional map-key discriminator) saw user-observable changes during this transaction. Drives observer dispatch at Commit.

Currently records only the Branch pointer; the map-key dimension is dropped because the observer subsystem does not yet consume it. See tech-debt.md.

func (*TransactionMut) ChangedTypes

func (t *TransactionMut) ChangedTypes() []*block.Branch

ChangedTypes returns the branches with recorded changes in this transaction. Order is non-deterministic (map iteration). Primarily for tests and the future observer dispatcher.

func (*TransactionMut) Commit

func (t *TransactionMut) Commit()

Commit runs the post-commit lifecycle and releases the write lock. Safe to call more than once; subsequent calls are no-ops.

Lifecycle steps (mostly stubbed today; tech-debt.md tracks each):

  1. Squash mergeBlocks against their left neighbours.
  2. GC fully-observed deleted items if Doc.GC is true.
  3. Fire pre-emit observers on changedTypes.
  4. Emit the update event with V1 (or V2) bytes for the diff.
  5. Emit subdoc events.
  6. Fire after-commit observers.

Today: only step 0 (release the lock) runs.

func (*TransactionMut) Delete

func (t *TransactionMut) Delete(item *block.Item)

Delete tombstones an Item and records its ID for inclusion in the transaction's eventual delete-set emission. The Item must already be in the store.

Note: the recursive-delete-of-Type-children path is not yet implemented (tracked in tech-debt.md). This implementation handles the simple case integrate uses for map-key LWW tombstoning.

func (*TransactionMut) DeletedIDs

func (t *TransactionMut) DeletedIDs() []block.ID

DeletedIDs returns the IDs of items tombstoned during this transaction so far. Returned slice aliases internal state; do not mutate. Primarily for tests and the future delete-set emitter.

func (*TransactionMut) Doc

func (t *TransactionMut) Doc() *Doc

Doc returns the Doc this transaction was created from.

func (*TransactionMut) GetItem

func (t *TransactionMut) GetItem(id block.ID) *block.Item

GetItem looks up the Item containing id in the doc's BlockStore. Returns nil for GC cells or unknown IDs.

func (*TransactionMut) GetOrCreateBranch

func (t *TransactionMut) GetOrCreateBranch(name string) *block.Branch

GetOrCreateBranch returns the root branch with the given name from the doc's root-branch registry. Used by block.Repair to resolve ParentNamed references arriving from wire updates.

We do not call Doc.Branch here because Doc.Branch acquires the write lock, which we already hold inside this transaction. Touch the registry directly under the existing lock instead.

func (*TransactionMut) MaterializeCleanEnd

func (t *TransactionMut) MaterializeCleanEnd(id block.ID) *block.Item

MaterializeCleanEnd returns an Item ending exactly at id.Clock (inclusive), splitting if needed.

func (*TransactionMut) MaterializeCleanStart

func (t *TransactionMut) MaterializeCleanStart(id block.ID) *block.Item

MaterializeCleanStart returns an Item starting exactly at id.Clock, splitting the underlying block in the store if id lands mid-block. Returns nil if id is in a GC cell or unknown.

func (*TransactionMut) PendingState

func (t *TransactionMut) PendingState() any

PendingState returns the opaque pending-update state stored on the doc. Returns nil when no encoding-layer state has been installed yet. The concrete type is *encoding.Pending; the doc package does not depend on encoding so this stays any-typed at the boundary. Callers (encoding.ApplyUpdate) type-assert.

func (*TransactionMut) SetPendingState

func (t *TransactionMut) SetPendingState(s any)

SetPendingState replaces the opaque pending-update state on the doc. Pass nil to drop pending state entirely (e.g. when the queue drains to empty and the encoding layer wants to release the allocation).

func (*TransactionMut) Store

func (t *TransactionMut) Store() *store.BlockStore

Store returns the doc's BlockStore. Safe to mutate from within this transaction; the write lock prevents concurrent access.

Jump to

Keyboard shortcuts

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