CrdtSync 1.0.2

There is a newer version of this package available.
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" />
                    
Directory.Packages.props
<PackageReference Include="CrdtSync" />
                    
Project file
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
                    
#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
                    
Install as a Cake Addin
#tool nuget:?package=CrdtSync&version=1.0.2
                    
Install as a Cake Tool

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 implement SyncKey
  • 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.Logging integration; 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:

  1. Each entity has a FieldTimestamps dictionary mapping property names to their last-edit UTC time
  2. When SetField is called (and SuppressTimestamp is false), the field's timestamp is set to DateTime.UtcNow
  3. During MergeFrom, each [Synced] property is compared by timestamp — the newer value wins
  4. MergeAll matches entities across lists using SyncKey, 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, decimal
  • DateTime, Guid
  • Enums
  • Any serializable reference type

Requirements

  • .NET 8.0 or later
  • Microsoft.Extensions.Logging.Abstractions (included automatically)

License

MIT

Product 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.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.1 88 3/23/2026
1.2.0 82 3/19/2026
1.1.0 84 3/10/2026
1.0.2 96 3/10/2026
1.0.1 84 3/9/2026
1.0.0 81 3/9/2026