accordsync library

The Accord client for Dart: local-first writes, background sync, conflicts and refusals.

A port of @accordsync/client. Pure Dart: no Flutter needed (see accordsync_flutter for storage on devices and widgets).

Classes

AccordClient
An Accord device. Writes apply locally at once and are saved to storage; sync pushes them and pulls everyone else's, in the background or on demand. Port of @accordsync/client.
AddOp
Adds an element to a set; the op id is its tag. deps lists the element's tags the writer could see: the add replaces them, while a concurrent remove still loses.
AssignOp
Writes a value. For lww the highest clock wins; for conflict, deps lists the values the writer could see, and the assign supersedes exactly those (ADR-0003).
ConflictInfo
A conflict() field holding more than one value. Equal when the record, field and values are.
ConflictState
CounterState
ExitItem
The record left this device's read scope (ADR-0004).
FieldState
Per-field state. Each strategy's applyOp is commutative and associative over distinct ops: any delivery order of the same ops gives the same state. The replica applies each op at most once, which makes the merge idempotent. Port of strategies.ts.
Hlc
Hybrid logical clock: physical time + logical counter + node id.
HttpTransport
Talks to an Accord server over HTTPS (see the server's docs/protocol.md).
IncOp
Adds by (a positive or negative integer) to a counter.
LocalWriter
One device's replica plus the means to write to it: every local write becomes an op, applied locally first and returned so the caller can queue it for sync. Port of writer.ts.
LwwState
MemoryStorage
In-memory storage: for tests, and for apps that accept losing unsynced writes on restart.
Op
One change to one field of one record. Port of op.ts.
OpItem
An op, in the wire format.
PullItem
One item of a pull page.
PullPage
PullResult
PushResult
The server's answer to a push.
RecordSnapshot
A record's state with its history folded away (log compaction, ADR-0008).
RemoveOp
Removes the add tags in deps (the ones the writer had seen); concurrent adds survive.
Replica
An op log and the state projected from it. Pure: no I/O, no clock. Two replicas holding the same set of ops always read the same state, whatever order the ops arrived in. Port of replica.ts.
ResyncRequired
The server asks the device to drop its data and pull from zero.
SetState
SnapshotItem
A compacted record (ADR-0008).
StorageAdapter
Durable storage for a device. commit must be atomic: after a crash, either all of a transaction is visible or none of it.
StorageSnapshot
Everything a device has stored.
StorageTx
One atomic change. Applied in this order: clear, delete, put, outbox changes, meta.
StoredMeta
What a device must remember between launches.
Transport
How a client reaches the server. HttpTransport is the real one; tests can fake it.

Enums

ApplyResult
Strategy
How a field merges concurrent writes. Port of schema.ts.

Constants

absent → const Object
Marks a field with no value (never written): left out of reads and snapshots, like JavaScript's undefined. A field written with null reads as null.
defaultMaxSkewMs → const int
kinds → const Map<Strategy, List<String>>
The op kinds each strategy accepts.
maxCounter → const int
The largest counter value; one more rolls the clock into the next millisecond.
maxSafeInteger → const int
The largest integer JavaScript represents exactly: clocks and counts stay within it.
protocolVersion → const int
Version of the sync protocol spoken between Accord clients and servers.

Functions

applyOp(FieldState state, Op op) → void
Applies op to state in place. The op must already be validated against the schema.
assertNode(String node) → void
Throws unless node is a valid node (device) id: [A-Za-z0-9_-]{1,64}.
canonicalJson(Object? value) → String
JSON with object keys in a canonical order, so equal values always serialise to the same string.
compareElements(Object a, Object b) → int
Numbers first (ascending), then strings (by UTF-16 code unit), as in the TypeScript core.
compareOpIds(OpId a, OpId b) → int
Sort order for op ids: UTF-16 code units, exactly like JavaScript's < on strings.
conflict() → Strategy
counter() → Strategy
decodeOp(Object? input) → Op
Parses untrusted input into an op, or throws with the reason. Schema checks happen later.
defineSchema(Schema schema) → Schema
Checks and returns a schema.
emptyState(Strategy strategy) → FieldState
encodeOp(Op op) → Map<String, Object?>
An op as it travels over the network and sits in golden vectors. Port of wire.ts.
fieldObservedDeps(FieldState state, [Object? element]) → List<OpId>
Op ids a writer must cite in deps: live conflict values, or the tags of a set element.
fieldsOf(Schema schema, String record) → Map<String, Strategy>
jsKeyOrder(Iterable<String> keys) → List<String>
The order JavaScript gives the keys of an object built from code-unit-sorted entries.
jsNumber(num n) → String
A number as JSON.stringify prints it.
lww() → Strategy
lww(), counter(), set(), conflict(): the same declarations as the TypeScript schema.
parseOpId(String opId) → ({String device, int seq})
The device and sequence number of an op id.
randomDeviceId([Random? random]) → String
A random device id from a secure source (Random.secure). Device ids must never collide, and are never derived from the hardware.
readState(FieldState state) → Object?
A field as the app sees it: a JSON value, a sorted list (set), an int (counter), or for a conflict() field {'value': v} / {'conflicted': [{'value': v, 'opId': id}, …]}. absent when never written.
recordType(String record) → String
The type of a record id (dossier for dossier:91).
sameElement(Object? a, Object? b) → bool
Set elements compare like JavaScript's ===: 1 and 1.0 are the same element.
set() → Strategy
snapshotState(FieldState state) → Map<String, Object?>
A field's live state as JSON; tombstones are dropped (ADR-0008). Same shape as the TypeScript FieldSnapshot, so snapshots from the server load here unchanged.
stateFromSnapshot(Map<String, Object?> snap) → FieldState
strategyFor(Schema schema, String record, String field) → Strategy
The strategy for record.field, or an error naming what is wrong.

Typedefs

ConflictValue = ({OpId opId, Object? value})
One of the values a conflicted field holds, and the op that wrote it.
OpId = String
deviceId:sequence. Unique per op, so applying an op twice is a no-op.
Refusal = ({String field, OpId opId, String reason, String record})
A local write the server refused. It has already been rolled back on this device.
Schema = Map<String, Map<String, Strategy>>
Record type → field → strategy.
SyncStatus = ({int cursor, Object? lastError, int? lastSyncAt, int pending})
Where sync stands.

Exceptions / Errors

AccordException
An op, record or schema that Accord refuses, with the reason.
ClockSkewException
A remote clock too far ahead of this one: the op is refused instead of winning every merge.
HttpError