Help us shape the future of Go! Take the 2026 Go Developer Survey

delta

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package delta ports @earendil-works/chord/delta (packages/chord/src/delta): immutable JSON revisions, exact operation batches for ordered replicas, the operation applier, the path-interning wire codec, revision diffing, and the revision tracker.

A JSON value is nil, bool, float64, string, []any, or *chordjson.Object, an object that keeps JavaScript's property order; chordjson.Decode produces that representation. Immutability is an ownership contract: nothing is frozen or copied defensively, so an illegal mutation silently corrupts state.

Go mapping decisions (language mechanics, not behavior changes):

  • Every emission and traversal that visits object keys visits them in JavaScript's own-key order (chordjson.Object.All): array-index keys first in numeric order, then the other keys in insertion order. A Go map[string]any in a DiffCandidate candidate has no order; it is converted with its other keys ascending (chordjson.MapOwnKeys).
  • Path segments are strings or non-negative integers. Operations built in Go use int; decoded JSON carries float64. Both address the same element.
  • A string is UTF-16 in JavaScript. Operations that count string units ("t", the overlap scan) count UTF-16 code units.
  • JavaScript accepts an array index as a string key and rejects it at run time; Go segments are typed, so a string segment on an array is rejected with UnsafePathError exactly as upstream does.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidOp = errors.New("invalid delta operation")

ErrInvalidOp matches every TypeError under errors.Is. The error itself carries Pi's message.

View Source
var ErrNotStrictJSON = errors.New("not strict JSON")

ErrNotStrictJSON matches every StrictJSONError.

View Source
var ErrRevoked = errors.New("Cannot use a settled overlay")

ErrRevoked is the panic value for any use of a draft handle after its change settled, was aborted, or became stale (tracker.ts "Cannot use a settled overlay"). Using a settled handle is a contract violation, so it panics like upstream's TypeError instead of returning an error every read must check.

View Source
var ErrSparseArray = errors.New("Overlay arrays cannot contain holes")

ErrSparseArray reports an array write that would leave a hole (tracker.ts "Overlay arrays cannot contain holes").

Functions

func AssertSafePath

func AssertSafePath(path Path) error

AssertSafePath rejects reserved segments and segments that are not non-negative integers.

func AssertValidOp deprecated

func AssertValidOp(op Op) error

AssertValidOp checks op as AssertValidOpValue does.

Deprecated: Use AssertValidOpValue, which accepts any value, as Pi's assertValidOp takes unknown.

func AssertValidOpValue

func AssertValidOpValue(value any) error

AssertValidOpValue is Pi's assertValidOp: it checks the verb, arity and payload shape of a decoded operation: paths inline, no "#", no short forms. It does not inspect payload values. Validating against the wire grammar would be laxer than the type: a two-element ["s", value] would pass and an applier would read the value as a path, so each vocabulary has its own validator.

func AssertValidWireOp

func AssertValidWireOp(value any) error

AssertValidWireOp is AssertValidOpValue for the wire grammar, where path ids and short forms are legal.

func CloneJSON

func CloneJSON(value any) (any, error)

CloneJSON validates value as strict JSON and returns a detached copy in the canonical representation. It is the walk behind chord.CopyJSON and draft placements.

func DecodeJson

func DecodeJson(data []byte) (any, error)

DecodeJson parses JSON text into a strict JSON value whose objects are *JsonObject with their keys in document order, as JSON.parse does.

func DiffCandidate

func DiffCandidate(base, candidate JsonValue) ([]Op, JsonValue, error)

DiffCandidate returns the operations that turn base into candidate, a complete revision the caller built (typed state edits a decoded copy instead of a draft), and the candidate restored to base's container identity wherever it is deeply equal, so array alignment matches moved elements by identity. The candidate is validated as strict JSON and normalized; an invalid candidate returns an error. The result takes ownership of candidate. When nothing changed, the operations are empty and the result is base.

func IsBase

func IsBase(ops []Op) bool

IsBase reports whether a batch begins with a replacement. A tracker batch carries "r" at index 0 or not at all, so this is exact.

func IsReplace

func IsReplace(op Op) bool

IsReplace reports whether op replaces the complete value.

func Overlap

func Overlap(a, b string, scan int, limits ...int) int

Overlap returns the longest suffix of a that is a prefix of b, in UTF-16 code units, looking only at the last scan units of a. The result n always satisfies a[len-n:] == b[:n].

A probe of length h can only find overlaps of at least h, because the head must occur in a. It tries a long head first (few candidates, and it catches the large overlaps a rolling window produces), then one unit, which finds any overlap at the cost of more candidates. Candidates are bounded because repetitive output makes a long head match at thousands of positions; giving up returns 0, which emits a set: larger, never wrong.

The optional limits are Pi's `probe = 64` and `maxCandidates = 8`, in that order: the length of the first head tried, and how many positions of it are verified before giving up.

func ReservedSegments

func ReservedSegments() map[string]bool

ReservedSegments returns the segments that reach the prototype chain in JavaScript (RESERVED_SEGMENTS). Paths are data from facets, plugins and tools, so none of them is trusted; the tracker never emits these segments. The set is made on first use, so package initialization allocates nothing for it; every call returns the same map, which callers must not modify.

func Verb

func Verb(op Op) string

Verb returns the verb of op, or the empty string for a malformed tuple.

Types

type Array

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

Array is a draft handle of a JSON array with the same lifetime as Object. Held handles follow elements through reindexing.

func (*Array) Array

func (array *Array) Array(index int) *Array

Array returns the array handle at index, or nil.

func (*Array) CopyWithin

func (array *Array) CopyWithin(target, start, end int) error

CopyWithin copies the elements in [start, end) over the elements from target, by value, as Array.prototype.copyWithin. A negative index counts from the end.

func (*Array) Fill

func (array *Array) Fill(value any, start, end int) error

Fill replaces the elements in [start, end) with independent validated clones of value, taken when Fill is called. A negative index counts from the end. As tracker.ts fill, an empty range places nothing, so value is validated only when the range is not empty.

func (*Array) Get

func (array *Array) Get(index int) any

Get returns the element at index, or nil when out of range.

func (*Array) Len

func (array *Array) Len() int

Len returns the array length.

func (*Array) Object

func (array *Array) Object(index int) *Object

Object returns the object handle at index, or nil.

func (*Array) Pop

func (array *Array) Pop() any

Pop removes and returns a detached copy of the last element's present value.

func (*Array) Push

func (array *Array) Push(values ...any) (int, error)

Push appends validated clones and returns the new length. When any value is not strict JSON nothing is inserted.

func (*Array) Reorder

func (array *Array) Reorder(permutation []int) error

Reorder rearranges the elements so that the element now at position i is the one that was at permutation[i], the permutation of the "m" operation. It is the sort and reverse of tracker.ts with the resulting order given directly. permutation must name every current position exactly once.

func (*Array) Reverse

func (array *Array) Reverse()

Reverse reverses the elements in place: tracker.ts reverse, which reverses the pieces and flips each.

func (*Array) Set

func (array *Array) Set(index int, value any) error

Set places a validated clone of value at index; index may append one past the end.

func (*Array) SetLen

func (array *Array) SetLen(length int)

SetLen grows the array with nulls or shrinks it.

func (*Array) Shift

func (array *Array) Shift() any

Shift removes and returns a detached copy of the first element's present value.

func (*Array) Snapshot

func (array *Array) Snapshot() []any

Snapshot returns a detached copy of the present content, as Object.Snapshot.

func (*Array) Sort

func (array *Array) Sort(less func(a, b any) bool)

Sort stably orders the elements by less, as tracker.ts sort. less receives each element as a draft handle, or the value itself for a primitive, and may edit the draft, including the array: element writes made through the handles stay, while the result of the sort replaces the first min(length at start, length at end) slots and so overwrites slot writes and insertions the comparator made there. An element the comparator left in two slots becomes an independent clone in the later one.

func (*Array) Splice

func (array *Array) Splice(start, remove int, items ...any) ([]any, error)

Splice removes remove elements at start, inserts validated clones of items, and returns detached copies of the removed present values. A removed value never shares a container with the base revision, as a value removed from upstream's Proxy draft cannot write through.

func (*Array) Unshift

func (array *Array) Unshift(values ...any) (int, error)

Unshift inserts validated clones at the front and returns the new length.

type Change

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

Change is one open overlay draft. Once settled it keeps only the status of the preparation it produced, as tracker.ts ChangeImpl keeps #preparedStatus, so a retained Change does not retain a revision.

A Change and its draft handles may be shared between goroutines: mu orders the Change's own settlement, and each handle locks the draft.

func (*Change) Abort

func (change *Change) Abort()

Abort discards the change, or the preparation it produced.

func (*Change) Elements

func (change *Change) Elements() *Array

Elements returns the mutable draft of an array root, and nil for an object root. It panics with ErrRevoked once the change settled.

func (*Change) Prepare

func (change *Change) Prepare() (*Prepared, error)

Prepare materializes the candidate revision and its operations without changing authority. Every draft handle is unusable afterwards.

func (*Change) State

func (change *Change) State() *Object

State returns the mutable draft of an object root, and nil for an array root. It panics with ErrRevoked once the change settled.

type Decoder

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

Decoder retains defined path ids across batches. A replacement clears the dictionary; path omission never crosses a batch boundary. After a decode error discard the decoder and its replica and recover from a later "r".

func NewDecoder

func NewDecoder() *Decoder

NewDecoder returns a decoder with an empty dictionary.

func (*Decoder) Decode

func (d *Decoder) Decode(wire []WireOp) ([]Op, error)

Decode validates and restores Op tuples.

type Encoder

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

Encoder owns the path dictionary for one ordered state stream. It interns a path on its second use: a definition costs more than the path it replaces, so interning on first use loses on the many paths written exactly once.

Consecutive batches of one encoder may be concatenated into one batch (links coalesce waiting state this way): only path ids span batches, and every batch starts with an explicit path.

func NewEncoder

func NewEncoder() *Encoder

NewEncoder returns an encoder with an empty dictionary.

func (*Encoder) Encode

func (e *Encoder) Encode(ops []Op) ([]WireOp, error)

Encode converts one batch. Arity omission is scoped to the batch, so a reader that skips or reorders a batch never decodes into the wrong path; ids are the only cross-batch state. An "r" is a recovery point, so it clears the dictionary and every later batch is self-contained.

type JsonObject

type JsonObject = chordjson.Object

JsonObject is a JSON object that keeps JavaScript's property order: array-index keys first in ascending numeric order, then the other keys in insertion order. It is the object representation of every value this package stores and returns.

func JsonObjectOf

func JsonObjectOf(pairs ...any) *JsonObject

JsonObjectOf returns a JsonObject holding pairs in order; pairs alternates keys and values.

func NewJsonObject

func NewJsonObject(capacity int) *JsonObject

NewJsonObject returns an empty JsonObject with room for capacity keys.

type JsonValue

type JsonValue = any

JsonValue is a strict JSON value.

func Apply

func Apply(target JsonValue, ops []Op) (JsonValue, error)

Apply applies decoded operations to a plain mutable value and returns it, because "r" replaces the value outright and an array splice rewrites its parent's reference. A batch must be detached for exactly one replica: "r" adopts its payload rather than copying it.

func ApplyImmutable

func ApplyImmutable(target JsonValue, ops []Op) (JsonValue, error)

ApplyImmutable applies one decoded operation batch without mutating the previous immutable value. The result shares containers with both the target and the operation payloads.

func ApplyImmutableBatches deprecated

func ApplyImmutableBatches(target JsonValue, batches [][]Op) (JsonValue, error)

ApplyImmutableBatches applies a slice of decoded batches as ApplyImmutableBatchesSeq does.

Deprecated: Use ApplyImmutableBatchesSeq, which takes any batch sequence, as Pi's applyImmutableBatches takes an Iterable.

func ApplyImmutableBatchesSeq

func ApplyImmutableBatchesSeq(target JsonValue, batches iter.Seq[[]Op]) (JsonValue, error)

ApplyImmutableBatchesSeq is Pi's applyImmutableBatches: it applies decoded batches as one final-result-only replay. Containers copied for an earlier batch may be mutated while applying a later one, so no intermediate revision is exposed or safe to retain. A failure, from validation or from a batch source that panics, leaves the target untouched.

type NonEmptyPath

type NonEmptyPath = Path

NonEmptyPath is the path of an "s", "d", "a" or "t" operation, which cannot target the root. It is a Path; AssertValidOp and AssertValidWireOp reject an empty one, as upstream's [Seg, ...Seg[]] tuple type does.

type Object

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

Object is a draft handle of a JSON object. It is usable only while its change is open; any later use panics with ErrRevoked. Containers read through it are returned as *Object or *Array handles, numbers as float64.

func (*Object) Array

func (object *Object) Array(key string) *Array

Array returns the array handle at key, or nil when the value is not an array.

func (*Object) Delete

func (object *Object) Delete(key string)

Delete removes key; it is upstream's `draft.key = undefined` or `delete`.

func (*Object) Get

func (object *Object) Get(key string) any

Get returns the value at key, or nil when absent.

func (*Object) Has

func (object *Object) Has(key string) bool

Has reports whether key is present.

func (*Object) Keys

func (object *Object) Keys() []string

Keys returns the present keys in JavaScript's own-key order: array-index keys ascending, then the other keys in draft order: the base keys in their order, then keys added by this change in write order; a key deleted and added again counts as added.

func (*Object) Len

func (object *Object) Len() int

Len returns the number of present keys.

func (*Object) Lookup

func (object *Object) Lookup(key string) (any, bool)

Lookup returns the value at key and whether it is present.

func (*Object) Object

func (object *Object) Object(key string) *Object

Object returns the object handle at key, or nil when the value is not an object.

func (*Object) Set

func (object *Object) Set(key string, value any) error

Set places a validated clone of value at key. A value that is not strict JSON returns an error and leaves the draft unchanged.

func (*Object) Snapshot

func (object *Object) Snapshot() *JsonObject

Snapshot returns a detached copy of the draft's present content. Changing the copy never changes the draft, its pending placements, or the base revision, as a value read out of upstream's Proxy draft cannot write through.

type Op

type Op = []any

Op is one change tuple, the form in memory, on the wire, and on disk:

["r", value]                      replace the complete value
["s", path, value]                set an object property or array element
["d", path]                       delete an object property or array element
["a", path, text]                 append to a string
["t", path, count]                remove count UTF-16 code units from a string's front
["p", path, index, remove, items] splice an array
["m", path, permutation]          reorder an array: new[i] = old[permutation[i]]

"r" is the only operation that replaces a whole value. "s", "d", "a" and "t" cannot target the root, "p" and "m" may because a tracked value can itself be an array. Operation shape is not canonical: an array may be emptied by either a replacement or a root splice. An object an operation carries is a *JsonObject; decode operation text as Ops, since encoding/json decodes an Op's objects into Go maps, which draft handles cannot open.

func DiffRevisions

func DiffRevisions(before, after JsonValue) []Op

DiffRevisions computes a compact operation batch from two immutable JSON revisions. Operations for object keys visit keys in ascending order. A batch of more than 4096 operations, or one whose estimated size reaches the size of the whole value, becomes a root replacement.

type Ops

type Ops []Op

Ops is a batch of operations that decodes JSON with chordjson.Decode, so an object an operation carries keeps its key order. A []Op decoded by encoding/json holds map[string]any objects instead; use Ops for a decoded field.

func (*Ops) UnmarshalJSON

func (ops *Ops) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON array of operations, keeping object key order.

type Path

type Path = []any

Path is a sequence of segments.

func PathOf

func PathOf(op Op) Path

PathOf returns the path of op; nil for "r" and for a tuple without one.

type PathError

type PathError struct {
	Path any
}

PathError reports an operation path that does not resolve, or a path dictionary id that is not defined.

func NewPathError

func NewPathError(path any) *PathError

NewPathError is `new PathError(path)`.

func (*PathError) Error

func (err *PathError) Error() string

func (*PathError) Name

func (*PathError) Name() string

Name is the `name` property, "PathError" (delta/index.ts:315).

type PathRef

type PathRef = any

PathRef is a wire path reference: the path inline, or the numeric id the encoder assigned on the path's second use.

type Prepared

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

Prepared is a materialized candidate revision.

func (*Prepared) Abort

func (prepared *Prepared) Abort()

Abort prevents adoption. The candidate stays readable.

func (*Prepared) Base

func (prepared *Prepared) Base() *JsonObject

Base returns the revision the change started from, for an object root (nil for an array root; see BaseRoot).

func (*Prepared) BaseRevision

func (prepared *Prepared) BaseRevision() int

BaseRevision returns the tracker revision the change started from.

func (*Prepared) BaseRoot

func (prepared *Prepared) BaseRoot() any

BaseRoot returns the revision the change started from: a *JsonObject or a []any.

func (*Prepared) Ops

func (prepared *Prepared) Ops() []Op

Ops returns the exact operations that transform Base into Value. Payloads may be the same containers as parts of Value.

func (*Prepared) Value

func (prepared *Prepared) Value() *JsonObject

Value returns the candidate revision of an object root (nil for an array root; see ValueRoot). Empty Ops means Value is Base.

func (*Prepared) ValueRoot

func (prepared *Prepared) ValueRoot() any

ValueRoot returns the candidate revision: a *JsonObject or a []any. Empty Ops means ValueRoot is BaseRoot.

type Seg

type Seg = any

Seg is one path segment: an object key (string) or an array index (a non-negative integer).

type StrictJSONError

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

StrictJSONError reports a placement or copy that is not strict JSON. It is upstream's TypeError from tracker.ts clonePlacement and json.ts copyJson.

func (*StrictJSONError) Error

func (err *StrictJSONError) Error() string

func (*StrictJSONError) Is

func (err *StrictJSONError) Is(target error) bool

Is reports ErrNotStrictJSON.

type Tracker

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

Tracker owns one sequence of immutable revisions of an object or array root, as tracker.ts's track(initial: T extends object).

As in tracker.ts, a Change, a Prepared or a draft handle never keeps its Tracker alive, and the Tracker holds its open changes weakly: an open change the caller drops is reclaimed, and a settled one releases its draft at once.

func Track

func Track(initial *JsonObject) *Tracker

Track takes immutable ownership of an alias-free strict-JSON root in O(1). The caller must never mutate initial again.

func TrackArray

func TrackArray(initial []any) *Tracker

TrackArray is Track for an array root. Its Change drafts through Elements, and its operations address the root with the empty path.

func (*Tracker) Adopt

func (tracker *Tracker) Adopt(prepared *Prepared) error

Adopt swaps the root pointer to a prepared revision and stales every competing change.

func (*Tracker) BeginChange

func (tracker *Tracker) BeginChange() *Change

BeginChange opens an overlay draft over the latest revision. The draft never modifies that revision.

func (*Tracker) PrepareReplace

func (tracker *Tracker) PrepareReplace(value *JsonObject) *Prepared

PrepareReplace prepares a whole-root replacement. A deeply equal value keeps the current root with empty operations.

func (*Tracker) PrepareReplaceRoot

func (tracker *Tracker) PrepareReplaceRoot(value any) (*Prepared, error)

PrepareReplaceRoot is PrepareReplace for a root of either kind: tracker.ts prepareReplace takes any object root and records one root replacement, also when the kind changes. value must be a *JsonObject or a []any.

func (*Tracker) Revision

func (tracker *Tracker) Revision() int

Revision returns the number of adoptions.

func (*Tracker) Root

func (tracker *Tracker) Root() any

Root returns the latest adopted revision: a *JsonObject or a []any.

func (*Tracker) Value

func (tracker *Tracker) Value() *JsonObject

Value returns the latest adopted revision of an object root, and nil for an array root; Root returns either.

type TypeError

type TypeError struct{ Message string }

TypeError reports a malformed operation, path or value, the failures upstream raises as TypeError.

func (*TypeError) Error

func (err *TypeError) Error() string

func (*TypeError) Is

func (err *TypeError) Is(target error) bool

Is reports that a TypeError is an ErrInvalidOp, so errors.Is(err, ErrInvalidOp) holds for every malformed operation, path or value.

type UnsafePathError

type UnsafePathError struct {
	Segment any
}

UnsafePathError reports a segment that reaches the prototype chain upstream, or an array index that is not a position in the array.

func NewUnsafePathError

func NewUnsafePathError(segment any) *UnsafePathError

NewUnsafePathError is `new UnsafePathError(segment)`.

func (*UnsafePathError) Error

func (err *UnsafePathError) Error() string

Error is `unsafe path segment: ${String(segment)}` (delta/index.ts:138).

func (*UnsafePathError) Name

func (*UnsafePathError) Name() string

Name is the `name` property, "UnsafePathError" (delta/index.ts:140).

type WireOp

type WireOp []any

WireOp is what crosses a boundary. It adds two compressions to Op and nothing else: ["#", id, path] defines an id on a path's second use, a numeric path reference points at a defined id, and a shortened tuple reuses the previous operation's path (arity disambiguates). ["r", value] carries no path, so it encodes to itself.

func (*WireOp) UnmarshalJSON

func (op *WireOp) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a wire operation with chordjson.Decode, so an object it carries keeps its key order.

Jump to

Keyboard shortcuts

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