CRDT record store: the persistence and merge layer beneath Collection.
A CrdtTable stores a set of records, each identified by a string key and
carrying an opaque value, a tombstone flag (isDeleted), a Hybrid Logical
Clock (HLC) timestamp, and a CrdtType. At this layer every record merges
last-write-wins: when two peers hold different values for the same key the
record with the higher HLC wins on merge. The richer CrdtType merge rules
(OrSet, PnCounter) are layered on top by TypedCrdtTable, which wraps
any implementation of this port — an engine only records the type and treats
the value as opaque.
Tombstones
Deleting a record never removes it from storage: put with
isDeleted: true soft-deletes the record by setting its tombstone flag.
Tombstones are included in changeset output so that peers receive and
apply deletes, not just writes. A compaction pass (Collection.compact)
may physically remove tombstones that are old enough to be safe.
Changesets and sync
changeset returns a serialisable snapshot of all records (or those modified after a given HLC), which is the wire format used by the sync protocol. merge applies an incoming changeset, advancing the local HLC and calling watch listeners when any record changes.
Implementor contract
- Causality: merge must advance the local canonical HLC to at least the maximum HLC seen in the incoming changeset before writing any record, so a local put that follows a merge is always causally later.
- Idempotency: merging the same changeset twice must be a no-op (the HLC comparison in merge makes this automatic).
- Watch: watch must return a broadcast stream; missing events while there are no listeners is acceptable, but no event must be emitted after merge or put returns without the record being durably stored.
Persistence
Set isPersistent to true in implementations that own their own
durability (e.g. a disk-backed engine that writes synchronously on every
mutation). Collection skips its own _restore and _persistDocuments
steps for persistent tables, delegating lifecycle to init and compact
instead.
Async reads
Read methods (value, liveFields, changeset) are async so a disk-backed engine can load data on demand without keeping every value in memory. liveKeys is sync and returns only metadata (key + tombstone flag), enabling operations such as Collection.length without I/O.
The default MapCrdtTable satisfies all of the above by delegating to the
crdt package's MapCrdt. Swap it for a disk-backed implementation to
bound memory use on large or long-lived collections.
- Implementers
Properties
- hashCode → int
-
The hash code for this object.
no setterinherited
- isPersistent → bool
-
Whether this table owns its own durability.
no setter
- nodeId → String
-
The identifier of this replica, as stamped into the node component of the
HLCs this table generates.
no setter
-
recoveries
→ Stream<
CrdtRecovery> -
Emits once if init found the local store unreadable and rebuilt it
(from an on-disk snapshot, or empty).
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
Methods
-
changeset(
String table, {String? sinceHlc, String? untilHlc}) → Future< CrdtChangeset> -
Returns records in
tableinCrdtChangesetformat. -
close(
) → Future< void> - Releases resources (a disk-backed engine closes its file). The default is a no-op. Called by Collection.dispose.
-
compact(
String table, {required Duration horizon}) → Future< int> -
Permanently removes tombstoned records whose wall-clock time is older
than
horizonand returns the number of reclaimed records. -
docFields(
String table, Iterable< String> docIds) → Future<Map< String, Map< >String, Object?> > -
The live field rows of the given documents, grouped by document id and
keyed by bare field path (the portion of the record key after the
"$docId\x1F"separator). Existence registers are not included; a document with no live field rows is absent from the result. -
ensureQueryIndex(
String table, String fieldPath) → Future< void> -
Prepares an index for equality lookups on
fieldPathintable, so aQueryOp.equalspredicate on that path does not scan the table. -
init(
) → Future< void> - Initializes the table from storage (called by Collection during startup when isPersistent is true).
-
liveFields(
String table, String prefix) → Future< Map< String, Object?> > -
Returns all live (non-tombstoned) records in
tablewhose key starts withprefix, as a flat map of key → value. -
liveKeys(
String table, String prefix) → Set< String> -
All live (non-tombstoned) keys in
tablewhose key starts withprefix. -
maxHlc(
String table) → String -
The highest HLC seen in
tableas a string, or the empty string when the table is empty. -
merge(
CrdtChangeset changeset) → Future< void> -
Merges a
CrdtChangesetreceived from a peer or loaded from storage. -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
put(
String table, String key, Object? value, {bool isDeleted = false, CrdtType crdtType = const LwwRegister()}) → Future< void> -
Writes or tombstones the record identified by
table/key. -
queryDocIds(
String table, QuerySpec spec) → Future< List< QueryRow> > -
Returns the ids of the live documents in
tablethat satisfyspec, in the spec's total order —(order-by value, document id)— windowed by its limit and cursor. -
rangeDigest(
String table, {String? fromHlc, String? toHlc}) → Future< RangeDigest> -
Summarises the records of
tablewhose HLC lies in the half-open range(fromHlc, toHlc](anullbound is open): how many there are, the XOR fingerprint of their identities, and the range's lowest and highest HLCs. -
recordHlc(
String table, String key) → Future< String?> -
The stored HLC string of the record at
table/key— tombstoned or live — ornullwhen no record exists. -
rekey(
List< int> newKey) → Future<void> -
Re-encrypts the store at rest under
newKey, keeping all data. -
toString(
) → String -
A string representation of this object.
inherited
-
value(
String table, String key) → Future< Object?> -
Returns the live value for
keyintable, ornullif absent or tombstoned. -
watch(
String table) → Stream< void> -
A broadcast stream that emits whenever any record in
tablechanges.
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited