CrdtSync 1.0.2
There is a newer version of this package available.
See the version list below for details.
See the version list below for details.
dotnet add package CrdtSync --version 1.0.2
NuGet\Install-Package CrdtSync -Version 1.0.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CrdtSync" Version="1.0.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CrdtSync" Version="1.0.2" />
<PackageReference Include="CrdtSync" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CrdtSync --version 1.0.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: CrdtSync, 1.0.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CrdtSync@1.0.2
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CrdtSync&version=1.0.2
#tool nuget:?package=CrdtSync&version=1.0.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
CrdtSync
A lightweight State-based LWW-Map CRDT (Last-Writer-Wins Map) sync engine for .NET. Mark properties with [Synced], extend SyncableEntity, and get automatic per-field merge with last-writer-wins conflict resolution.
Features
- Per-field conflict resolution — each property tracks its own timestamp, so concurrent edits to different fields on the same entity never conflict
- Last-Writer-Wins — when two devices edit the same field, the most recent write wins automatically
- Zero configuration — just inherit
SyncableEntity, add[Synced]to properties, and implementSyncKey - Transport agnostic — works with any serialization/transport layer (JSON files, REST APIs, Google Drive, databases, etc.)
- Reflection-cached — property metadata is built once per type and reused across all instances
- INotifyPropertyChanged — built-in change notification for MVVM/UI binding
- Logging — optional
Microsoft.Extensions.Loggingintegration; silent by default
Installation
dotnet add package CrdtSync
Quick Start
1. Define your entity
using CrdtSync;
public class TodoItem : SyncableEntity
{
private string _title = "";
private bool _isDone;
private int _priority;
// SyncKey matches entities across devices (must be unique)
public override string SyncKey => _title;
[Synced]
public string Title
{
get => _title;
set => SetField(ref _title, value);
}
[Synced]
public bool IsDone
{
get => _isDone;
set => SetField(ref _isDone, value);
}
[Synced(ClearValue = 0)]
public int Priority
{
get => _priority;
set => SetField(ref _priority, value);
}
}
2. Edit entities (timestamps are tracked automatically)
var item = new TodoItem { SuppressTimestamp = false };
item.Title = "Buy milk";
item.Priority = 3;
// FieldTimestamps now contains UTC timestamps for "Title" and "Priority"
3. Merge from a remote copy
// Single entity merge
bool changed = localItem.MergeFrom(remoteItem);
// Bulk merge — matches by SyncKey, returns changed count + new entities
var (mergeCount, newItems) = SyncableEntity.MergeAll(localList, remoteList, "Device1");
localList.AddRange(newItems);
4. Serialize and transport however you like
using System.Text.Json;
// Serialize
var json = JsonSerializer.Serialize(localList);
// Deserialize (SuppressTimestamp defaults to true, so deserialization won't overwrite timestamps)
var remoteList = JsonSerializer.Deserialize<List<TodoItem>>(json);
API Reference
SyncableEntity (abstract base class)
| Member | Description |
|---|---|
abstract string SyncKey |
Unique key to match entities across devices (e.g. Name, Id) |
Dictionary<string, DateTime> FieldTimestamps |
Per-field UTC timestamps for merge decisions |
bool SuppressTimestamp |
When true, property changes don't update timestamps. Defaults to true — set to false after construction/deserialization |
bool MergeFrom(SyncableEntity other) |
Merge per-field from another entity. Returns true if any value changed |
void Clear() |
Reset all [Synced] properties to their ClearValue defaults |
static (int, List<T>) MergeAll<T>(...) |
Bulk merge remote list into local list by SyncKey |
static Action<T, Random>[] BuildRandomEditActions<T>() |
Generate random edit actions per [Synced] property (useful for testing) |
static IReadOnlyList<SyncedPropertyMeta> GetSyncedProperties(Type) |
Get cached reflection metadata for a type |
protected bool SetField<T>(ref T, T, string?) |
Set a backing field, auto-update timestamp, and raise PropertyChanged |
[Synced] attribute
| Property | Type | Default | Description |
|---|---|---|---|
ClearValue |
object? |
null |
Value used by Clear(). If null, uses default(T) (empty string for string) |
DbType |
string |
"" |
Optional SQLite column type hint for DB migration scenarios |
Logging
CrdtSync uses Microsoft.Extensions.Logging. By default logging is disabled (NullLogger). To enable:
// Option A: Pass an ILogger directly
SyncableEntity.SetLogger(myLogger);
// Option B: Pass an ILoggerFactory
SyncableEntity.SetLogger(loggerFactory);
// With Serilog (add Serilog.Extensions.Logging package):
using Serilog.Extensions.Logging;
SyncableEntity.SetLogger(new SerilogLoggerFactory(Log.Logger).CreateLogger("CrdtSync"));
How It Works
CrdtSync implements a State-based LWW-Map CRDT:
- Each entity has a
FieldTimestampsdictionary mapping property names to their last-edit UTC time - When
SetFieldis called (andSuppressTimestampisfalse), the field's timestamp is set toDateTime.UtcNow - During
MergeFrom, each[Synced]property is compared by timestamp — the newer value wins MergeAllmatches entities across lists usingSyncKey, merges existing ones, and returns entities that only exist remotely
This means:
- No central server needed — any device can merge with any other device
- Offline-friendly — edits accumulate timestamps locally and resolve on next sync
- No data loss — only individual fields are overwritten, not entire objects
Supported Property Types
Any type that works with EqualityComparer<T>.Default and can be set via reflection:
string(default clear value:"")int,bool,double,float,decimalDateTime,Guid- Enums
- Any serializable reference type
Requirements
- .NET 8.0 or later
Microsoft.Extensions.Logging.Abstractions(included automatically)
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net8.0
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.