sync_engine
Pure-Dart, immutable CRDT primitives for flutter_sync_engine. It can be
tested with dart test and deliberately has no Flutter dependency.
Getting started
Create a Flutter application and add the core package, generator, and build runner:
flutter create my_app
cd my_app
flutter pub add sync_engine
flutter pub add --dev build_runner sync_engine_generator
Define a model in lib/todo.dart. The part directive is required so the
generated adapter is included in the library:
import 'package:sync_engine/sync_engine.dart';
part 'todo.sync.dart';
@Syncable()
class Todo {
const Todo({required this.id, required this.title});
@Id()
final String id;
@ConflictStrategy(ConflictType.lastWriteWins)
final String title;
}
Create build.yaml at the application root:
targets:
$default:
builders:
sync_engine_generator|syncable:
generate_for:
- lib/**.dart
Generate the adapter:
dart run build_runner build --delete-conflicting-outputs
The generated TodoSyncAdapter is then registered with SyncEngine:
final sync = SyncEngine(
storage: storage,
transport: transport,
nodeId: 'device-a',
adapters: {Todo: const TodoSyncAdapter()},
);
storage must implement SyncStorage, and transport must implement
SyncTransport. For durable Flutter persistence, use DriftSyncStorage from
sync_engine_drift. Its README
contains a complete database and storage setup.
LWW conflict resolution
LWWRegister uses vector clocks for causal order. If two writes are
concurrent, it selects the value from the lexicographically greater node ID.
That rule is deterministic but arbitrary; applications needing domain-specific
resolution should provide it above this primitive.
SyncEngine serializes sync cycles, validates transport acknowledgements, and
keeps the outbox cursor from advancing when a push fails. Durable persistence
is provided by sync_engine_drift; the core package ships the outbox contract
and in-memory implementation.
Libraries
- sync_engine
- Pure-Dart CRDT primitives and offline-first synchronization contracts.