crdt

package module
v1.0.23-beta.1 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 2 Imported by: 0

README

crdt

English | 简体中文

crdt is a small Go library for composable state-based CRDTs. It provides deterministic binary state and delta frames so replicas can converge despite duplicate delivery, reordering, and temporary partitions.

Status: stable releases are published from main; APIs follow semantic versioning.

Architecture at a glance

Conceptual architecture comparison of crdt and Yjs

This is a conceptual comparison of integration boundaries, not a wire-compatibility, feature-parity, or performance claim.

Features

  • State-based G-Counter with joinable, type-isolated deltas.
  • Grow-only G-Set with a caller-defined element codec and joinable deltas.
  • Add-wins observed-remove OR-Set with a caller-defined element codec.
  • Experimental delta-replicated LWW-Set with a caller-defined element codec and deterministic HLC conflict resolution.
  • Experimental delta-replicated LWW-Map with opaque byte values and deterministic HLC conflict resolution.
  • Experimental attachment references for images, audio, video, and data: bounded metadata only, backed by an authenticated application object store.
  • Causally replicated MV-Register that preserves concurrent opaque-byte writes instead of resolving them by wall clock.
  • Experimental generic RGA list values with caller-defined canonical codecs, bounded framed state/deltas, and stable element positions.
  • Experimental strict XML fragments backed by immutable XML-node list values; XML subtree replacement is explicit rather than a hidden attribute conflict rule.
  • Local, compensating text Undo/Redo that emits ordinary RGA deltas and never rolls back remote state.
  • Hybrid logical clock (HLC) tags and a persistable clock state for replica restarts.
  • Canonical, checksummed binary frames with bounded decoding and deterministic encoding.
  • Delta batching/coalescing, versioned snapshots, and Merkle digests for anti-entropy workflows.
  • Optional exact-acknowledgement tombstone collection with membership epochs.
  • Safe concurrent access for the provided CRDT implementations.
  • Opt-in manifest-bound WebSocket and HTTP/SSE live-relay reference surfaces; disabled unless an application explicitly enables them.
  • A single-writer durable WebSocket relay reference with a bbolt operation log, exact-dot binding, bounded replay, and reconnect support.
  • Experimental framed LWW-Set, LWW-Map, legacy scalar RGA v1, OR-Tree, and inline rich-text formatting, enabled only by an explicit per-replication-group protocol policy. New Go RGA groups use compact run-v2 frames; the RGA tombstone lifecycle still requires careful retention and exact-acknowledgement handling.

Scope

The core library provides CRDT data types and wire primitives. It deliberately does not choose a membership protocol, authentication scheme, storage backend, or retry policy. The optional extensions package adds explicitly enabled WebSocket and HTTP/SSE live-relay reference surfaces, but it does not start a listener or provide durability, replay, reconnect, TLS, anti-entropy, or identity/session management. Those remain application-owned. The optional WebSocket provider reference is likewise a bounded, manifest-bound integration adapter; it does not provide durable delivery, recovery, TLS, membership, authorization policy, or production operations. The separate durable reference adds one persistent operation log and reconnect/replay flow for a single process and one protected storage volume. It still does not provide clustered storage, application CRDT checkpoint transactions, TLS, identity/session lifecycle, membership, or tombstone GC. tombstonegc.Coordinator performs safe automatic collection only after the application supplies an authoritative, authenticated active-membership view. It does not discover, authenticate, or persist that view. A checksum detects accidental frame corruption; it is not an authenticity or encryption mechanism.

Experimental LWW-Set, LWW-Map, legacy RGA v1, OR-Tree, and rich-text protocols

LWW-Set (lww.Set, TypeIDs 7/8) encodes generic elements through an application-supplied canonical lww.ElementCodec. It retains remove metadata, so persist SnapshotCurrentState(codec) (or Snapshot(codec, frontier)) and restore a same-ID replica only with NewSetFromSnapshot. Its new wire format is experimental and must be explicitly negotiated.

RGA text v1 (text, TypeIDs 11/12) accepts out-of-order deltas through a bounded delayed-integration queue, rejects incomplete snapshots, and uses an incremental indexed sequence rather than rebuilding the full visible projection after each edit. It remains experimental while its full tombstone lifecycle is validated. Persist its HLC-backed snapshot atomically.

New Go RGA groups select compact run-v2 frames (TypeIDs 19/20) through crdt.DefaultRGAFrameType(). A run-v2 group must encode with Delta.MarshalRunBinary, use RGA.MarshalRunBinary and RGA.SnapshotRunCurrentState for complete state, and bind those same IDs in its manifest. The run encoding preserves scalar RGA position semantics, but a manifest still represents exactly one wire protocol: do not mix it with a legacy v1 client or frame stream.

Experimental richtext.Document (TypeIDs 23/24) wraps a run-v2 RGA with bounded per-position LWW inline attributes. It supports opaque UTF-8 attribute strings for formatting such as bold, italics, links, and comments; it does not carry HTML, CSS, block structure, or media bytes. Use a distinct manifest with richtext.SemanticsVersion and AllowExperimental, persist its state with the shared RGA clock atomically, and render only through application-owned attribute validation. See the rich-text design.

CompactTombstones is intentionally conservative: it can collect only deleted leaves after an authenticated exact-acknowledgement epoch has durably saved a post-compaction snapshot and retired old deltas. Nodes with descendants remain structural anchors. LWW-Set, LWW-Map, legacy scalar RGA v1 (TypeIDs 11/12), and OR-Tree remain experimental and require explicit opt-in:

policy := crdt.ProtocolPolicy{AllowExperimental: true}
for _, kind := range policy.FrameTypes() {
	// This is only a local capability allowlist, not the full handshake.
	_ = kind
}

Before an experimental frame is accepted, bind it to a replica.Manifest in the authenticated handshake. The manifest includes the group, schema, epoch, codec, and semantics version. To keep one explicit opt-in while constructing a group's local replica objects, create a replica.NewSessionBuilder(..., policy) and use its NewChange, NewInbox, NewCheckpoint, and NewSession methods. The builder remains a local helper, not a handshake: the zero policy still rejects experimental manifests, and frame type IDs alone do not establish wire-semantic compatibility. The individual *WithPolicy constructors remain available for isolated use.

The zero-value policy advertises G-Counter, G-Set, OR-Set, MV-Register, PN-Counter, and default RGA run-v2 protocols. The policy is neither a global switch nor a plugin registry: unknown and reserved frame types remain unsupported. Experimental LWW-Set, LWW-Map, legacy RGA v1, OR-Tree, and rich-text replicas must persist HLC state with snapshots and retain their tombstones.

Browser and JavaScript mobile clients

The repository includes a bounded TypeScript frame-envelope decoder and a Go/ Wasm RGA client runtime under clients/typescript. The default artifact uses run-v2 TypeIDs 19/20 and semantics version 2, matching crdt.DefaultRGAFrameType(). The Wasm layer reuses the Go RGA merge, out-of-order, tombstone, and HLC implementation so a browser/WebView can merge locally instead of waiting for server arbitration.

Build and exercise the client boundary with:

make wasm
make typescript-test
make wasm-test

Authenticate the exact manifest (including protocol IDs and semantic version) before passing a received frame to the runtime; CRC-32C does not authenticate a peer. Persist the client snapshot's state, clock, and frontier atomically. Native mobile apps without a compatible WebAssembly runtime must follow the RGA run-v2 wire protocol and its vectors before joining a run-v2 group. Split a local editor transaction above 64 KiB or 16,384 runes before insertion.

Experimental attachment references

attachment.Register represents a document's images, audio, video, or other binary data as a bounded LWW-Map of immutable references. A reference contains an opaque object ID, canonical MIME type, declared byte length, and SHA-256 digest; it never carries media bytes in a CRDT delta, snapshot, log, or diagnostic. Text that users edit remains text.RGA; ordinary structured data remains lww.Map, OR-Set, or OR-Tree according to its conflict semantics.

Attachment references use the experimental LWW-Map frame IDs (9/10). Bind each replication group to a replica.Manifest with schema ID github.com/DarkInno/crdt/attachment-reference/v1, an empty codec ID, and attachment.SemanticsVersion; enable AllowExperimental on every boundary. Persist SnapshotCurrentState() with its HLC state, and retain delete metadata until the LWW tombstone lifecycle is complete.

The application owns authorization, object-store lifecycle, content scanning, rate limits, and download policy. After a fetch, call Reference.Verify before decoding or rendering: it streams the object without buffering it and rejects a short, oversized, or digest-mismatched response. Do not put signed URLs, credentials, personal data, or raw media content in Reference.ObjectID.

Requirements

  • Go 1.21 or later

Install

Install the latest stable release:

go get github.com/DarkInno/crdt@latest

For development, use a local checkout:

git clone https://github.com/DarkInno/crdt.git
cd crdt
go test ./...

Quick start

G-Counter

Each replica increments only its own component. Merge takes the per-replica maximum, so it is commutative, associative, and idempotent.

package main

import (
	"fmt"
	"log"

	"github.com/DarkInno/crdt/counter"
)

func main() {
	left, err := counter.NewGCounter("left")
	if err != nil {
		log.Fatal(err)
	}
	right, err := counter.NewGCounter("right")
	if err != nil {
		log.Fatal(err)
	}

	if _, err := left.Increment(2); err != nil {
		log.Fatal(err)
	}
	if _, err := right.Increment(3); err != nil {
		log.Fatal(err)
	}
	if err := left.Merge(right); err != nil { // Delivery order does not matter.
		log.Fatal(err)
	}

	value, err := left.Value()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(value)
	// Output: 5
}
PN-Counter

A PN-Counter supports independent increments and decrements. It stores positive and negative per-replica G-Counter components, so merges remain commutative, associative, and idempotent. Value returns an exact *big.Int; use ValueInt64 when the application requires a bounded machine integer.

counter, err := counter.NewPNCounter("cart")
if err != nil {
	log.Fatal(err)
}
if _, err := counter.Increment(7); err != nil {
	log.Fatal(err)
}
if _, err := counter.Decrement(2); err != nil {
	log.Fatal(err)
}
value, err := counter.Value()
if err != nil {
	log.Fatal(err)
}
fmt.Println(value)
// Output: 5
OR-Set delta replication

An OR-Set uses a stable codec ID and stable encoded element bytes to identify a set's element type across replicas.

package main

import (
	"fmt"
	"log"

	"github.com/DarkInno/crdt/set"
)

type stringCodec struct{}

func (stringCodec) ID() string                            { return "example.com/string/v1" }
func (stringCodec) Marshal(value string) ([]byte, error)  { return []byte(value), nil }
func (stringCodec) Unmarshal(data []byte) (string, error) { return string(data), nil }

func main() {
	codec := stringCodec{}
	left, err := set.NewORSet("left", codec)
	if err != nil {
		log.Fatal(err)
	}
	right, err := set.NewORSet("right", codec)
	if err != nil {
		log.Fatal(err)
	}

	delta, err := left.Add("item")
	if err != nil {
		log.Fatal(err)
	}
	if err := right.ApplyDelta(delta); err != nil {
		log.Fatal(err)
	}

	fmt.Println(right.Contains("item"))
	// Output: true
}

For a remove to be observed by other replicas, send the returned remove delta or merge the state. An add concurrent with a remove that did not observe its tag remains present (add-wins semantics).

End-to-end integration

For a reproducible local HTTP delivery exercise, a production-integration checklist, snapshot/restart guidance, and the expected convergence evidence, see the end-to-end integration tutorial. The runnable collaborative-workboard example models duplicate delivery, a partitioned add/remove conflict, and recovery from an OR-Set snapshot:

go run ./examples/collaborative-board

The warehouse replication example shows framed G-Set and MV-Register deltas, duplicate delivery, concurrent register values, and safe MV-Register recovery before reusing a replica ID:

go run ./examples/warehouse-replication

The experimental collaboration example uses low, explicit receive and RGA retention limits for LWW-Map, RGA, and OR-Tree. Run it only after the replication group has completed the authenticated experimental-protocol handshake described above:

go run ./examples/experimental-collaboration

The attachment collaboration example uses separate manifest-bound groups for RGA text and attachment references, persists both receiver states through snapshots, and streams an authorized download through Reference.Verify before accepting it:

go run ./examples/attachment-collaboration

The opt-in transport extension guide and its runnable provider example show one application-owned mux exposing both WebSocket and HTTP/SSE surfaces. The example demonstrates a WebSocket-to-HTTP delivery followed by an HTTP-to- WebSocket delivery; it is a bounded live relay, not a durable replication service:

go run ./examples/extensions-provider

For persistent operation replay and reconnect behavior, use the separate durable WebSocket relay reference. It is a single-writer bbolt deployment shape with bounded replay rather than a replacement for a replicated database or application checkpoint transaction.

See attachment reference integration for the manifest fields, limits, storage boundary, deletion retention, and verification requirements.

For the Chinese versions, see 集成教程 and 协作任务示例 and 仓库复制示例 and 实验协作示例 and 可选传输扩展. 另见可持久化 WebSocket relay 参考实现

Correct use in a distributed system

  • Give every live logical replica a globally unique, non-blank replica ID.
  • Persist an OR-Set snapshot atomically with its HLC state. Use ORSet.SnapshotCurrentState() when the set's own frontier is sufficient, or ORSet.Snapshot(frontier) when the replication layer has a broader acknowledgement frontier; restore with NewORSetFromSnapshot. Do not restore a same-ID OR-Set from bytes alone.
  • For automatic tombstone collection, create a coordinator with a stable replication-group ID. Each active member reports its exact ORSet.TombstoneTags() (or experimental ORTree.TombstoneTags()) under that ID and the current tombstonegc.Coordinator membership epoch; pass both values to AcknowledgeAndCompact (or AcknowledgeAndCompactTarget for the tree) for every received report. The tree target additionally refuses to remove a tombstoned node with any known child. Do not derive acknowledgements from Frontier() when delta delivery can be out of order: a maximum tag does not prove that prior tombstones were received. Removing a member requires retiring it from replication; a rejoining member must bootstrap from a post-compaction snapshot. Persist that checkpoint and bind every new frame to the next membership epoch before accepting compaction.
  • ORSet.Compact remains available only for transports that independently prove a gap-free causal prefix for every supplied frontier.
  • Persist an MV-Register state snapshot before reusing its replica ID. Its version vector, not a wall clock, proves which writes a later Set observes; recover with register.NewMVRegisterFromSnapshot.
  • Use ProtocolPolicy.FrameTypes() as an authenticated connection/setup capability advertisement. Send LWW-Set, LWW-Map, RGA, or OR-Tree frames only when both peers opt in. Persist HLC-backed snapshots atomically. RGA tombstone compaction additionally requires an authenticated exact-acknowledgement epoch, durable post-compaction checkpoint, and retirement of old deltas.
  • Keep ElementCodec.ID, Marshal, and Unmarshal deterministic and safe for concurrent calls. Encoded values must round-trip canonically.
  • Treat received bytes as untrusted. Use UnmarshalBinaryWithLimits and Unmarshal*DeltaWithLimits with limits appropriate to the transport.
  • Authenticate, authorize, encrypt, retry, and persist messages in the surrounding application. CRDT convergence does not provide those guarantees.

JSON diagnostics

Concrete CRDT state and delta objects implement json.Marshaler for structured logs and human inspection. The output is a compact, stable summary such as:

{"type":"gcounter","replica_id":"left","element_count":2,"tombstone_count":0}

It deliberately excludes application values, element keys, tags, clock state, and binary frames. JSON diagnostics cannot restore or apply a CRDT state or delta and are not a replication format; use the bounded canonical binary encoders for that.

Packages

Package Purpose
crdt Common contracts, state summaries, and mutation tags.
clock Hybrid logical clock and persisted HLC state.
counter G-Counter, PN-Counter, and their delta codecs.
set G-Set, add-wins OR-Set, and element-codec contract.
lww Experimental framed LWW-Set and LWW-Map.
attachment Experimental bounded media/data references with streaming size and SHA-256 verification.
text Experimental framed RGA collaborative text and run-v2 codec.
richtext Experimental bounded inline formatting over run-v2 RGA text.
tree Experimental framed observed-remove tree.
register In-memory LWW/max registers and framed causal MV-Register.
encoding Versioned bounded binary frames.
delta Bounded delta batches and coalescers.
snapshot Immutable state snapshots and recovery plans.
merkle Deterministic digests for anti-entropy.
tombstonegc Exact tombstone acknowledgement and epoch-scoped GC coordination.

Development and verification

go test ./...
go test -race ./...
go vet ./...
make coverage

make verify additionally runs fuzzing, staticcheck, and golangci-lint. Those two tools must be installed on PATH locally; GitHub Actions installs pinned versions. To reproduce the coverage gate in Docker:

make docker-test
Diagnostic and synchronization probes

crdt-analyze verifies one bounded frame before emitting JSON metadata (type, codec, payload size, and SHA-256 fingerprint):

go run ./cmd/crdt-analyze -file ./state.frame

crdt-sync-probe is a short-lived HTTP test utility for exercising duplicate delta delivery across hosts. It is not a production replication service. Its default listener is enforced as loopback-only; a non-empty token is required for every endpoint. Prefer -token-file (mode 0600) over -token. A non-loopback bind requires the deliberate -allow-non-loopback flag and is only appropriate for a firewall-restricted test window.

# On each receiver.
go run ./cmd/crdt-sync-probe -mode serve -replica receiver -token-file ./probe.token

# Generate one delta and send that same byte sequence to every target.
go run ./cmd/crdt-sync-probe -mode send \
  -target http://receiver-a:49511,http://receiver-b:49511 \
  -replica sender -token-file ./probe.token -duplicates 3

The probe also exercises RGA delta delivery, but it does not perform manifest or capability negotiation. /rga defaults to stable run-v2 (TypeIDs 19/20). Legacy scalar v1 (11/12) remains an experimental, explicit -rga-protocol=v1 opt-in for both endpoints; mismatched frames are rejected before text mutates. A mutation response is an empty 204 with X-CRDT-Apply-Micros; use the final authenticated /state response to compare text.protocol, visible rune count, SHA-256, and pending dependencies.

# New probe sessions use run-v2 without an extra protocol flag.
go run ./cmd/crdt-sync-probe -mode serve -replica receiver -token-file ./probe.token

go run ./cmd/crdt-sync-probe -mode send \
  -target http://receiver-a:49511,http://receiver-b:49511 \
  -replica text-sender -token-file ./probe.token \
  -counter-increment 0 -element '' \
  -rga-runes 4096 -rga-rune 'λ' -duplicates 3

RGA probe input is bounded to 16 MiB and 200,000 runes per generated delta; those are diagnostic limits, not a production capacity recommendation. It has no durable HLC state, outbox, replay, recovery, or tombstone-GC authority. run-v2 can compact linear same-replica frames, but canonical decoding is a separate CPU and allocation trade-off; benchmark both wire shapes on the target machine before selecting one.

Use make test-unit to run packages independently and make test-integration for the three-replica, recovery, batching, encoding, and anti-entropy flow.

The CI workflow enforces formatting, unit tests, race detection, vet, decoder fuzzing, static analysis, per-package coverage of at least 90%, and a Go 1.26 container verification.

Quality and performance snapshot — 2026-07-28

This historical pre-release snapshot was collected on Go 1.26.5. It is recorded evidence for that revision, not a latency or throughput guarantee for every workload; the checks below were not re-executed as part of this documentation update.

  • The recorded make verify run passed: formatting, independent-package tests, integration and extreme scenarios, the race detector, vet, four 10-second decoder fuzz campaigns, staticcheck, golangci-lint, and a per-package coverage gate of at least 90%.
  • The recorded make docker-test run passed with Go 1.26; govulncheck ./... found no known vulnerabilities.
  • A controlled three-host delivery probe confirmed idempotent duplicate delivery and rejected unauthorized, malformed, and oversized requests.
  • The supplied benchmarks cover G-Counter, PN-Counter, G-Set, OR-Set, and MV-Register Merge, ApplyDelta, and MarshalBinary. Run make benchmark on your target hardware before choosing capacity limits.

The scenario evaluation at the end of this README records the current local sample for duplicate delivery and both live-state and tombstone-heavy serialization. Exact results depend on the CPU, Go version, element codec, set size, and mutation mix.

Run make test-extreme to repeat the high-cardinality scenario in normal and race-instrumented modes. Internal investigation data and deployment runbooks are intentionally kept outside the public release tree.

Publishing releases

Before merging beta into main, run the verification commands above, review the public API, and ensure the repository is publicly reachable at the module path. The main push workflow creates the next immutable semantic-version tag and a GitHub Release with generated notes. Do not create a competing stable tag by hand.

go mod tidy
make verify
# Merge the reviewed beta -> main pull request, then wait for the main workflow.
GOPROXY=proxy.golang.org go list -m github.com/DarkInno/crdt@latest

Do not move or reuse a published tag. For Go modules, breaking changes after the first stable release require a new major-version module path such as github.com/DarkInno/crdt/v2.

Contributing

Please open an issue before proposing an API expansion. Contributions should include focused tests, preserve deterministic wire encoding, keep untrusted input bounded, and pass the verification commands above.

Scenario performance evaluation — 2026-07-28

Measured locally on an Apple M4 Pro with Go 1.26.5 (darwin/arm64). The fixture contains 128 string elements; each result is the rounded mean of three two-second samples. Allocation values were stable across samples.

Scenario GOMAXPROCS=1 GOMAXPROCS=4 Allocation per operation
Merge 56.0 µs/op 42.9 µs/op 57,768 B; 259 allocs
Duplicate ApplyDelta 131 ns/op 131 ns/op 0 B; 0 allocs
Parallel duplicate ApplyDelta 132 ns/op 105 ns/op 0 B; 0 allocs
MarshalBinary (128 live elements) 36.8 µs/op 26.6 µs/op 29,952 B; 132 allocs
MarshalBinary (tombstone-heavy) 24.8 µs/op 17.4 µs/op 15,616 B; 2 allocs

Merge, ordinary ApplyDelta, and MarshalBinary use serial benchmark loops; their GOMAXPROCS=4 values are runtime-setting samples, not four-core throughput measurements. Only the parallel duplicate-delivery row uses RunParallel. Compared with an earlier local sample using the same live-state fixture and method, MarshalBinary now uses 29,952 B and 132 allocations per operation, down from 96,312 B and 778 allocations. These are local pre-release measurements, not capacity planning or SLA guarantees; rerun make benchmark on the deployment target before setting limits.

PN-Counter performance evaluation — 2026-07-28

Measured on two independent Debian 13 (linux/amd64) hosts, each with four Intel Xeon Platinum 8272CL vCPUs and 3.8 GiB memory. The benchmark binary was built from this revision with Go 1.26.5 and run three times per setting with -benchtime=2s; values below are rounded means. The fixture has 128 replica components in each positive and negative map. MarshalBinary includes its reported encoded-throughput sample in parentheses; allocation counts were identical in all three runs.

Host (anonymized) GOMAXPROCS Merge ApplyDelta Value MarshalBinary
Host A 1 24.9 µs/op; 13,136 B; 6 allocs 149.1 ns/op; 0 B; 0 allocs 7.51 µs/op; 232 B; 10 allocs 69.4 µs/op (55.3 MB/s); 25,680 B; 10 allocs
Host A 4 18.6 µs/op; 13,136 B; 6 allocs 151.8 ns/op; 0 B; 0 allocs 7.29 µs/op; 232 B; 10 allocs 53.6 µs/op (71.7 MB/s); 25,680 B; 10 allocs
Host B 1 25.6 µs/op; 13,136 B; 6 allocs 151.8 ns/op; 0 B; 0 allocs 7.49 µs/op; 232 B; 10 allocs 70.4 µs/op (54.6 MB/s); 25,680 B; 10 allocs
Host B 4 18.5 µs/op; 13,136 B; 6 allocs 153.5 ns/op; 0 B; 0 allocs 7.31 µs/op; 232 B; 10 allocs 53.3 µs/op (72.1 MB/s); 25,680 B; 10 allocs

The GOMAXPROCS=4 rows remain serial benchmark measurements, not aggregate four-core throughput. These controlled host samples are public regression evidence for this revision, not capacity limits or SLA guarantees; rerun the same command on the deployment target before setting production limits:

GOMAXPROCS=4 go test -run='^$' \
  -bench='^BenchmarkPNCounter(Merge|ApplyDelta|Value|MarshalBinary)$' \
  -benchmem -benchtime=2s ./counter

License

SPDX-License-Identifier: MIT

Licensed under the MIT License. Copyright (c) 2026 DarkInno.

Documentation

Overview

Package crdt provides the shared contracts and protocol capability discovery used by this module's state-based CRDT implementations.

Applications normally use a concrete data type from a subpackage, such as counter for G-Counters and PN-Counters, set for add-wins OR-Sets, or clock for hybrid logical clocks. This root package contains the common CRDT, delta, snapshot, and mutation-tag contracts those implementations share.

The framed protocol table is intentionally closed. Use ProtocolPolicy during authenticated connection setup to advertise only the state and delta frame types a replication group has agreed to exchange. Experimental protocols require explicit opt-in and may change before stable promotion.

For installation, examples, and package-level guidance, see the module README at https://github.com/darkinno/crdt.

Example (LwwRegister)
package main

import (
	"fmt"

	"github.com/DarkInno/crdt/register"
)

func main() {
	writer, err := register.NewLWW("writer")
	if err != nil {
		panic(err)
	}
	reader, err := register.NewLWW("reader")
	if err != nil {
		panic(err)
	}
	if err := writer.Set([]byte("healthy")); err != nil {
		panic(err)
	}
	if err := reader.Merge(writer); err != nil {
		panic(err)
	}

	value, ok := reader.Get()
	fmt.Println(ok, string(value))
}
Output:
true healthy
Example (LwwSet)
package main

import (
	"fmt"

	"github.com/DarkInno/crdt/lww"
)

func main() {
	writer, err := lww.NewSet[string]("writer")
	if err != nil {
		panic(err)
	}
	reader, err := lww.NewSet[string]("reader")
	if err != nil {
		panic(err)
	}
	if err := writer.Add("on-call"); err != nil {
		panic(err)
	}
	if err := reader.Merge(writer); err != nil {
		panic(err)
	}

	fmt.Println(reader.Contains("on-call"))
}
Output:
true
Example (MaxRegister)
package main

import (
	"fmt"

	"github.com/DarkInno/crdt/register"
)

func main() {
	local := register.NewMax()
	remote := register.NewMax()
	if err := local.Set(8); err != nil {
		panic(err)
	}
	if err := remote.Set(13); err != nil {
		panic(err)
	}
	if err := local.Merge(remote); err != nil {
		panic(err)
	}

	value, ok := local.Get()
	fmt.Println(ok, value)
}
Output:
true 13

Index

Examples

Constants

View Source
const (
	TypeIDGCounterState   uint64 = 1
	TypeIDORSetState      uint64 = 2
	TypeIDGCounterDelta   uint64 = 3
	TypeIDORSetDelta      uint64 = 4
	TypeIDPNCounterState  uint64 = 5
	TypeIDPNCounterDelta  uint64 = 6
	TypeIDLWWSetState     uint64 = 7
	TypeIDLWWSetDelta     uint64 = 8
	TypeIDLWWMapState     uint64 = 9
	TypeIDLWWMapDelta     uint64 = 10
	TypeIDRGAState        uint64 = 11
	TypeIDRGADelta        uint64 = 12
	TypeIDGSetState       uint64 = 13
	TypeIDGSetDelta       uint64 = 14
	TypeIDMVRegisterState uint64 = 15
	TypeIDMVRegisterDelta uint64 = 16
	TypeIDORTreeState     uint64 = 17
	TypeIDORTreeDelta     uint64 = 18
	// RGA run frames retain scalar Position semantics while compacting linear
	// same-replica insertion chains. They are the default protocol for new RGA
	// replication groups; TypeIDRGAState and TypeIDRGADelta remain immutable v1
	// contracts for explicitly negotiated legacy groups.
	TypeIDRGARunState uint64 = 19
	TypeIDRGARunDelta uint64 = 20
	// List RGA frames carry one canonical caller-coded value per position.
	// They are intentionally distinct from text RGA frames: a text position is
	// one Unicode scalar while a list position is one opaque application value.
	TypeIDListRGAState uint64 = 21
	TypeIDListRGADelta uint64 = 22
	// Rich text nests a run-v2 RGA frame with bounded inline formatting
	// registers. It is experimental until cross-language vectors, renderer
	// schema, and formatting-metadata GC rules are promoted.
	TypeIDRichTextState uint64 = 23
	TypeIDRichTextDelta uint64 = 24
)

Stable frame type assignments. Values are part of the v1 wire contract and must never be reused for a different payload shape.

Variables

This section is empty.

Functions

func IsExperimentalFrame

func IsExperimentalFrame(typeID uint64) bool

IsExperimentalFrame reports whether typeID belongs to an implemented experimental protocol. Reserved or unknown type IDs return false.

func MarshalDiagnosticJSON added in v1.0.5

func MarshalDiagnosticJSON(summary StateSnapshot) ([]byte, error)

MarshalDiagnosticJSON encodes a caller-provided diagnostic summary. It is useful for CRDT delta log views that use the same schema as StateSnapshot. The summary must not contain application values or replication state.

func MarshalStateJSON added in v1.0.5

func MarshalStateJSON(value StateReporter) ([]byte, error)

MarshalStateJSON returns a compact JSON diagnostic summary for value.

This helper is intended for structured logs and human inspection. It does not encode CRDT state, deltas, or opaque application values, so its output cannot reconstruct a replica and must not be used as a wire format.

Types

type CRDT

type CRDT[T any] interface {
	Merge(other T) error
	State() StateSnapshot
}

CRDT is the common contract for state-based CRDTs.

For every concrete state type T, Merge must be commutative, associative, and idempotent. If Merge returns an error, it must leave the receiver unchanged.

type DeltaCapable

type DeltaCapable[T any, D any] interface {
	CRDT[T]
	ApplyDelta(delta D) error
}

DeltaCapable is implemented by a state-based CRDT that accepts a concrete, type-safe delta D. Delta mutators return D directly; the library does not maintain an implicitly acknowledged delta buffer.

type FrameType

type FrameType struct {
	StateID uint64
	DeltaID uint64
	UsesHLC bool
}

FrameType describes one fully implemented framed CRDT protocol. The type table is deliberately closed: reserving an ID alone must not make a payload eligible for batching or recovery before its concrete codec is available.

func DefaultRGAFrameType added in v1.0.19

func DefaultRGAFrameType() FrameType

DefaultRGAFrameType returns the compact run-v2 protocol for new RGA replication groups. Legacy scalar RGA v1 frames remain available only when a group explicitly enables experimental protocols for migration.

func FrameTypeForDelta

func FrameTypeForDelta(deltaID uint64) (FrameType, bool)

FrameTypeForDelta returns the supported protocol associated with deltaID.

func FrameTypeForState

func FrameTypeForState(stateID uint64) (FrameType, bool)

FrameTypeForState returns the supported protocol associated with stateID.

type ProtocolPolicy

type ProtocolPolicy struct {
	// AllowExperimental includes framed LWW-Set, LWW-Map, legacy scalar RGA v1,
	// generic list RGA, OR-Tree, and rich-text protocols. Keep it false until
	// the replication group has accepted their experimental API and
	// tombstone-retention lifecycle.
	AllowExperimental bool
}

ProtocolPolicy controls which implemented frame types one replication group advertises. It is a local, immutable-by-convention value for connection setup; it does not install a process-wide switch or permit runtime protocol registration.

Peers must compare FrameTypes before sending state or deltas. A matching TypeID remains necessary but is not sufficient: applications still own authentication, authorization, limits, and decoder selection.

Example
package main

import (
	"fmt"

	"github.com/DarkInno/crdt"
)

func main() {
	stable := crdt.ProtocolPolicy{}
	experimental := crdt.ProtocolPolicy{AllowExperimental: true}

	fmt.Println(stable.SupportsFrame(crdt.TypeIDRGAState))
	fmt.Println(experimental.SupportsFrame(crdt.TypeIDRGAState))
}
Output:
false
true

func (ProtocolPolicy) FrameTypes

func (p ProtocolPolicy) FrameTypes() []FrameType

FrameTypes returns a copy of every protocol enabled by p. The returned slice is stable in type-ID order and safe for callers to advertise or modify.

func (ProtocolPolicy) SupportsFrame

func (p ProtocolPolicy) SupportsFrame(typeID uint64) bool

SupportsFrame reports whether typeID is both implemented by this module and enabled by p. It applies to either a state or delta frame type ID.

type StateReporter added in v1.0.5

type StateReporter interface {
	State() StateSnapshot
}

StateReporter exposes an immutable CRDT diagnostic summary.

It intentionally excludes application values, mutation tags, clock state, and framed bytes. Use it for observability only, never to persist or replicate a CRDT.

type StateSnapshot

type StateSnapshot struct {
	Type           string `json:"type"`
	ReplicaID      string `json:"replica_id"`
	ElementCount   int    `json:"element_count"`
	TombstoneCount int    `json:"tombstone_count"`
}

StateSnapshot is an immutable summary of a CRDT state for diagnostics and observability. It never exposes mutable internal data.

type Tag

type Tag struct {
	ReplicaID string
	WallTime  uint64
	Logical   uint64
}

Tag uniquely identifies a CRDT mutation. WallTime, Logical, and ReplicaID are compared in that order. ReplicaID must be globally unique among live logical replicas; callers that reuse an ID across restarts must persist the last emitted clock state.

func (Tag) Compare

func (t Tag) Compare(other Tag) int

Compare returns -1, 0, or 1 according to the canonical ordering of tags.

func (Tag) Valid

func (t Tag) Valid() bool

Valid reports whether t is safe to use as a CRDT mutation identifier.

Directories

Path Synopsis
Package attachment replicates bounded references to externally stored images, audio, video, and arbitrary data.
Package attachment replicates bounded references to externally stored images, audio, video, and arbitrary data.
Package clock implements a hybrid logical clock for CRDT mutation tags.
Package clock implements a hybrid logical clock for CRDT mutation tags.
cmd
crdt-analyze command
Command crdt-analyze reports bounded, transport-safe metadata about one canonical CRDT frame.
Command crdt-analyze reports bounded, transport-safe metadata about one canonical CRDT frame.
crdt-cluster-sim command
Command crdt-cluster-sim exercises run-v2 RGA synchronization over real HTTP links.
Command crdt-cluster-sim exercises run-v2 RGA synchronization over real HTTP links.
crdt-rga-wasm command
crdt-rga-wasm exposes the bounded RGA browser runtime through one small syscall/js surface.
crdt-rga-wasm exposes the bounded RGA browser runtime through one small syscall/js surface.
crdt-sync-probe command
Command crdt-sync-probe exercises CRDT delta delivery over real HTTP links.
Command crdt-sync-probe exercises CRDT delta delivery over real HTTP links.
Package counter implements counter CRDT primitives.
Package counter implements counter CRDT primitives.
Package delta provides bounded batching and coalescing for encoded CRDT deltas.
Package delta provides bounded batching and coalescing for encoded CRDT deltas.
Package durable provides a single-writer WebSocket relay reference with a persistent operation log, bounded replay, and reconnect support.
Package durable provides a single-writer WebSocket relay reference with a persistent operation log, bounded replay, and reconnect support.
Package encoding provides canonical, bounded binary frames for CRDT state.
Package encoding provides canonical, bounded binary frames for CRDT state.
examples
attachment-collaboration command
Command attachment-collaboration demonstrates one document using separate, authenticated replication groups for editable text and external media references.
Command attachment-collaboration demonstrates one document using separate, authenticated replication groups for editable text and external media references.
collaborative-board command
Command collaborative-board demonstrates how an application can use CRDT deltas for a field-maintenance workboard while replicas are disconnected.
Command collaborative-board demonstrates how an application can use CRDT deltas for a field-maintenance workboard while replicas are disconnected.
experimental-collaboration command
Command experimental-collaboration demonstrates bounded framed replication for LWW-Map, RGA, and OR-Tree after an application has authenticated and negotiated the experimental protocol policy for its replication group.
Command experimental-collaboration demonstrates bounded framed replication for LWW-Map, RGA, and OR-Tree after an application has authenticated and negotiated the experimental protocol policy for its replication group.
extensions-provider command
Command extensions-provider demonstrates the opt-in WebSocket and HTTP/SSE relay surfaces mounted into an application-owned HTTP mux.
Command extensions-provider demonstrates the opt-in WebSocket and HTTP/SSE relay surfaces mounted into an application-owned HTTP mux.
warehouse-replication command
Command warehouse-replication demonstrates framed G-Set and MV-Register replication between warehouse sites and an operations dashboard.
Command warehouse-replication demonstrates framed G-Set and MV-Register replication between warehouse sites and an operations dashboard.
websocket-provider command
Command websocket-provider runs the official WebSocket transport reference against two in-memory counter replicas.
Command websocket-provider runs the official WebSocket transport reference against two in-memory counter replicas.
websocket-provider/provider
Package provider is a WebSocket CRDT transport reference implementation.
Package provider is a WebSocket CRDT transport reference implementation.
Package extensions provides opt-in, bounded live transport adapters for CRDT replication groups.
Package extensions provides opt-in, bounded live transport adapters for CRDT replication groups.
internal
wasm
Package wasm contains host-neutral state used by the browser-facing Wasm command.
Package wasm contains host-neutral state used by the browser-facing Wasm command.
Package list implements a generic, ordered Replicated Growable Array (RGA).
Package list implements a generic, ordered Replicated Growable Array (RGA).
Package lww implements last-write-wins CRDT collections.
Package lww implements last-write-wins CRDT collections.
Package membership provides a transport-independent, signed membership protocol reference for CRDT replication groups.
Package membership provides a transport-independent, signed membership protocol reference for CRDT replication groups.
Package merkle provides deterministic state digests for anti-entropy.
Package merkle provides deterministic state digests for anti-entropy.
Package observe connects a CRDT to an application-owned reactive view.
Package observe connects a CRDT to an application-owned reactive view.
providers
sqlite module
Package register implements state-based register CRDTs.
Package register implements state-based register CRDTs.
Package replica defines the transport-independent boundary around one framed CRDT replication group.
Package replica defines the transport-independent boundary around one framed CRDT replication group.
Package richtext implements bounded, inline formatted collaborative text.
Package richtext implements bounded, inline formatted collaborative text.
Package set implements set CRDT primitives.
Package set implements set CRDT primitives.
Package snapshot defines immutable, versioned CRDT state snapshots and bounded recovery plans.
Package snapshot defines immutable, versioned CRDT state snapshots and bounded recovery plans.
telemetry module
Package text implements a state-based Replicated Growable Array (RGA).
Package text implements a state-based Replicated Growable Array (RGA).
Package tombstonegc coordinates safe, automatic tombstone collection.
Package tombstonegc coordinates safe, automatic tombstone collection.
Package tree implements an observed-remove rooted tree CRDT.
Package tree implements an observed-remove rooted tree CRDT.
Package xml provides a bounded, deterministic XML fragment CRDT.
Package xml provides a bounded, deterministic XML fragment CRDT.

Jump to

Keyboard shortcuts

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