dart_crdt 0.4.0 copy "dart_crdt: ^0.4.0" to clipboard
dart_crdt: ^0.4.0 copied to clipboard

A Dart CRDT package for local-first collaborative data structures.

dart_crdt logo

dart_crdt


Coverage: 96.02%

Overview #

A pure Dart CRDT package for local-first collaborative data structures.

dart_crdt provides document state, shared collection/text types, binary update encoding, relative positions, snapshots, undo/redo, attribution metadata, and compatibility-oriented fixtures for VM, Flutter, and web runtimes.

Supported Features #

Area Support
Documents Client IDs, transactions, roots, observers, lifecycle events, and subdocuments.
Presence Awareness state, local presence fields, update exchange, and offline removals.
Shared types Arrays, maps, text, XML-like trees, nested types, embeds, and attributes.
Sync Deterministic V1/V2 updates, state vectors, merge/diff helpers, update conversion, and update application.
Editing Rich-text deltas, snapshots, relative positions, undo/redo, and attribution metadata.
Validation Serialized fixtures, malformed-input fuzzing, coverage gates, benchmarks, and compiled web checks.

Install #

dependencies:
  dart_crdt: ^0.4.0
import 'package:dart_crdt/dart_crdt.dart';

Quick Start #

Create a document and mutate a shared text type:

final doc = Doc();
final text = doc.getText('body');

text.insertText(0, 'Hello, local-first Dart.');

print(text.toPlainText()); // Hello, local-first Dart.

Shared arrays, maps, text, XML fragments/elements/text, and nested shared types are available through SharedType APIs.

final list = doc.getArray('items');
list.push('one');
list.push(SharedType(kind: SharedTypeKind.map, name: 'nested'));

final map = doc.getMap('settings');
map.setAttr('theme', 'light');

Update Exchange #

Use state updates to exchange CRDT state between replicas or providers. Updates are idempotent and can be safely applied more than once.

final local = Doc(clientId: ClientId(1));
local.getText('root').insertText(0, 'sync');

final update = encodeStateAsUpdate(local);

final remote = Doc(clientId: ClientId(9));
applyUpdate(remote, update);

print(remote.getText('root').toPlainText()); // sync

V2 update helpers are also available:

final updateV2 = encodeStateAsUpdateV2(local);
applyUpdateV2(remote, updateV2);

Providers can also merge updates, diff them against a state vector, and inspect an update's state vector without first materializing a document:

final merged = mergeUpdates([update]);
final stateVector = encodeStateVectorFromUpdate(merged);
final missing = diffUpdate(merged, stateVector);

encodeStateVectorFromUpdate and its V2 counterpart report only concrete client clocks that the update proves contiguously from clock zero. Wire Skip framing, unresolved ranges, and target-relative deltas that begin above zero do not claim the missing prefix. When inspecting a target-relative delta, combine the extracted vector with the target vector that was used to produce it.

In 0.4.0, merge/diff, update-format conversion, and update obfuscation are safe only for causally complete input. They currently materialize a temporary document and omit unresolved structs. Providers that accept out-of-order updates must retain the original update journal until all pending dependencies have integrated; encodeStateAsUpdate also emits integrated state only.

Awareness And Presence #

Awareness tracks ephemeral presence state such as users, cursors, selections, and online/offline status. It is provider-neutral: transports can broadcast the encoded updates over WebSocket, WebRTC, local storage, isolates, or any other message channel.

final local = Awareness(localClientId: ClientId(1));
final remote = Awareness(localClientId: ClientId(2));

local.setLocalState({
  'user': {'name': 'Ada'},
  'cursor': 7,
});

final update = encodeAwarenessUpdate(local);
applyAwarenessUpdate(remote, update);
print(remote.states[ClientId(1)]?.toObject());

Rich Text And Deltas #

Shared text supports formatting attributes and delta rendering.

final doc = Doc();
final text = doc.getText('body');

text.insertText(
  0,
  'Important',
  attributes: DeltaAttributes.fromJson({'bold': true}),
);

final delta = text.toDelta();
print(delta.toJson());

You can also apply package-native deltas:

final builder = DeltaBuilder()
  ..insertText(text: 'hello')
  ..retain(
    length: 5,
    attributes: DeltaAttributes.fromJson({'italic': true}),
  );
final delta = builder.done();

text.applyDelta(delta);

Undo And Redo #

UndoManager tracks CRDT transactions for a document or shared-type scope.

final doc = Doc(gc: false, clientId: ClientId(9));
final undoManager = UndoManager(doc);
final source = Doc(clientId: ClientId(1));

source.getText('root').insertText(0, 'draft');

applyUpdate(doc, encodeStateAsUpdate(source));

undoManager.undo();
undoManager.redo();
undoManager.destroy();

Tracked origins, capture timeouts, delete filters, stack events, and scoped undo/redo are supported.

Diff Attribution And Suggestions #

DiffAttributionManager can accept or reject all changes between two documents. The range-shaped acceptChanges and rejectChanges methods remain source-compatible in 0.4.0, but arbitrary partial decisions fail closed with UnsupportedError: they are supported only when the supplied range covers all remaining suggestions, in which case they delegate to acceptAllChanges or rejectAllChanges. This avoids manufacturing a non-contiguous causal history.

Rejecting all changes synchronizes the complete post-undo state to both documents, including insertion tombstones and ids created while restoring deletions, so later successor and adjacent edits can integrate normally.

Relative Positions #

Relative positions anchor to CRDT content and can be encoded, stored, sent over the network, and resolved against a document later.

Shared-text indexes use Unicode scalar values, matching Dart String.runes and the package's text mutation APIs. The portable wire format still uses UTF-16 clocks; relative-position helpers convert between the two units.

final source = Doc(clientId: ClientId(1));
source.getText('body').insertText(0, 'hello');

final doc = Doc();
final text = doc.getText('body');
applyUpdate(doc, encodeStateAsUpdate(source));

final position = createRelativePositionFromTypeIndex(text, 2);
final encoded = encodeRelativePosition(position);
final resolved = createAbsolutePositionFromRelativePosition(
  decodeRelativePosition(encoded),
  doc,
);

print(resolved?.index);

Snapshots #

Snapshots capture a document version as a state-vector and delete-set pair. Snapshot restoration requires a source document with garbage collection disabled so deleted content remains available.

final doc = Doc(gc: false);
final snap = snapshot(doc);
final bytes = encodeSnapshot(snap);
final restored = createDocFromSnapshot(doc, decodeSnapshot(bytes));

Binary Compatibility #

The package includes deterministic V1 and V2 update encoders/decoders, state vectors, delete sets, update merge/diff helpers, update format conversion, obfuscation helpers, content-id inspection, serialized fixtures, and compiled web smoke coverage.

Malformed input is validated with typed exceptions, and binary behavior is covered by unit, integration, fixture, fuzz, benchmark, and compiled web checks.

API Documentation #

Stability #

0.4.0 is a pre-1.0 release. Public APIs may change before 1.0.0, but binary behavior is covered by regression fixtures and compatibility tests.

Current known limitations:

  • Binary merge/diff, format conversion, and obfuscation omit unresolved structs from causally incomplete input. Keep the original update bytes until their dependencies have integrated.
  • encodeStateAsUpdate and encodeStateAsUpdateV2 do not yet re-emit raw pending struct updates.
  • Arbitrary partial DiffAttributionManager accept/reject decisions are not supported; select all remaining suggestions or use the explicit all-change methods.
  • Formatting attributes applied through root or nested SharedText APIs are not yet stored in CRDT structs. Plain text syncs, but formatting runs do not.

Contributing #

See the repository CONTRIBUTING.md for local setup, formatting, analyzer, tests, coverage, benchmarks, and pull request expectations.

Benchmarks #

Field Value
Mode full
Generated 2026-05-08T01:46:16.733514Z
Runtime Dart 3.10.0-247.0.dev, macos, 8 processors
Threshold 500.00 ms/iteration max per benchmark
Benchmark Workload Iterations ms/iteration
update_encoding_round_trip Build a text document and encode V1/V2 state updates. 120 1.04
text_sequential_insert Insert single text tokens at monotonically increasing slots. 120 0.52
text_append Append chunked text content to a shared text value. 120 1.18
text_prepend Prepend chunked text content to a shared text value. 120 2.13
text_middle_insert Insert chunked text content into the middle of shared text. 120 1.72
text_random_insert_delete_format Run deterministic random text insert/delete and formatting. 120 2.93
text_fragmented_delta_render Render a large fragmented formatted text value as a delta. 120 9.53
delta_apply_shallow Apply a shallow text delta with retains, inserts, and deletes. 120 2.70
delta_render_shallow Render a shallow delta to stable JSON and debug strings. 120 0.62
delta_render_deep Render nested child and attribute modifications as JSON. 120 0.18
array_random_insert_delete_nested Run deterministic array insert/delete with nested types. 120 63.93
map_set_delete_conflicts Run repeated map set/delete conflicts across clients. 120 0.42
xml_tree_insert_delete_stringify Build, edit, walk, and stringify an XML tree. 120 12.61
sync_v1_encode_apply_merge_diff Encode, apply, merge, and diff V1 state updates. 120 12.04
sync_v2_encode_apply_merge_diff Encode, apply, merge, and diff V2 state updates. 120 12.79
sync_update_format_convert Convert V1 updates to V2 and back to V1. 120 12.59
sync_pending_out_of_order_recovery Recover pending structs and delete sets from out-of-order updates. 120 0.15
metadata_id_set_algebra Run IdSet merge, diff, and intersection workloads. 120 2.23
metadata_id_map_algebra Run IdMap merge, diff, intersection, and filter workloads. 120 74.75
advanced_relative_position_create_resolve Create, encode, decode, and resolve relative positions. 120 1.24
advanced_snapshot_create_restore_containment Create, encode, decode, restore, and check snapshots. 120 1.44
advanced_undo_redo_stack Create undo stack items, undo, redo, and clear stacks. 120 5.34
advanced_attribution_diff_render_accept_reject_filter Diff, render, accept, reject, and filter attributions. 120 0.23
advanced_gc_enabled_vs_disabled Compare GC enabled and disabled delete-heavy workloads. 120 0.74

License #

Apache License 2.0. Copyright 2026 Mike Diarmid.

1
likes
150
points
993
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A Dart CRDT package for local-first collaborative data structures.

Repository (GitHub)
View/report issues
Contributing

Topics

#crdt #collaboration #local-first #offline #sync

License

unknown (license)

More

Packages that depend on dart_crdt