Automerge.Windows.WinRT 0.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Automerge.Windows.WinRT --version 0.2.0
                    
NuGet\Install-Package Automerge.Windows.WinRT -Version 0.2.0
                    
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="Automerge.Windows.WinRT" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Automerge.Windows.WinRT" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Automerge.Windows.WinRT" />
                    
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 Automerge.Windows.WinRT --version 0.2.0
                    
#r "nuget: Automerge.Windows.WinRT, 0.2.0"
                    
#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 Automerge.Windows.WinRT@0.2.0
                    
#: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=Automerge.Windows.WinRT&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Automerge.Windows.WinRT&version=0.2.0
                    
Install as a Cake Tool

Automerge.Windows

Native Windows bindings for the Automerge CRDT library. Zero WebView, zero JavaScript runtime — pure native Windows (x64 + ARM64).


Architecture

┌─────────────────────────────────────────────────────────────┐
│  WinRT Projection  (Automerge.Windows.winmd / .dll)         │ ← WinUI 3 / Windows App SDK
│  C# P/Invoke Wrapper  (AutomergeWindows.dll)                │ ← .NET / console / server
└──────────────────────────────┬──────────────────────────────┘
                               │ static link
┌──────────────────────────────▼──────────────────────────────┐
│  C++ RAII Wrapper  (automerge_wrapper.lib)                   │ ← Native C++ apps
└──────────────────────────────┬──────────────────────────────┘
                               │ import lib
┌──────────────────────────────▼──────────────────────────────┐
│  C ABI — Rust core  (automerge_core.dll)                     │ ← Any FFI consumer
└─────────────────────────────────────────────────────────────┘
Layer Output Tests Use when
Rust C ABI automerge_core.dll 33 ✅ FFI from any language
C++ RAII wrapper automerge_wrapper.lib 39 ✅ Native C++17/20 apps
C# P/Invoke wrapper AutomergeWindows.dll 38 ✅ .NET 9+ apps and services
WinRT projection Automerge.Windows.winmd + .dll 18 ✅ WinUI 3 / Windows App SDK

Quick start

git clone https://github.com/arcadiogarcia/Automerge.Windows.git
cd Automerge.Windows
.\build.ps1          # builds all layers and runs all 128 tests

See BUILDING.md for prerequisites and toolchain details.


Path syntax

All layers share the same JSON-array path syntax for GetValue / get_value / AMget_value:

Path string Meaning
[] or "" Entire root object as JSON
["name"] Top-level key name
["contacts",0,"email"] contacts[0].email

Values are returned as JSON strings: strings are quoted ("Alice"), numbers are unquoted (42), booleans are true/false, null is null.


Usage — C# P/Invoke wrapper

Target: .NET 9+ console, ASP.NET, or desktop apps that don't require WinRT activation.

NuGet (recommended):

<PackageReference Include="Automerge.Windows.CSharp" Version="*" />

The package bundles automerge_core.dll for both win-x64 and win-arm64 — no extra steps needed.

Manual reference: copy AutomergeWindows.dll and automerge_core.dll next to your executable.

Basic read / write

using Automerge.Windows;

using var doc = new Document();

// Write a flat JSON object into the root.
// Only scalar values (string, number, bool, null) are supported.
doc.PutJsonRoot("""{"name":"Alice","score":42,"active":true}""");

// Read individual values by path
string name   = doc.GetValue("""["name"]""");   // → "\"Alice\""
string score  = doc.GetValue("""["score"]""");  // → "42"
string root   = doc.GetValue("[]");             // → {"name":"Alice","score":42,"active":true}

// Extension method: deserialise directly to a .NET type
int s = doc.GetValue<int>("""["score"]""");     // → 42

Persistence

// Save to bytes (e.g. write to a file or database)
byte[] bytes = doc.Save();

// Restore
using var doc2 = Document.Load(bytes);

Changes-based replication

Efficient for one-way push where one side always sends and the other applies:

using var sender   = new Document();
using var receiver = new Document();

sender.PutJsonRoot("""{"event":"hello"}""");

// All changes from genesis
byte[] allChanges = sender.GetChanges();
receiver.ApplyChanges(allChanges);

// Only changes since a known checkpoint
byte[] checkpoint = sender.GetHeads();
sender.PutJsonRoot("""{"event":"world"}""");
byte[] delta = sender.GetChanges(checkpoint);   // just the new change
receiver.ApplyChanges(delta);

Merge

Best for local in-process merging of two independently-modified documents:

// Both peers start from the same saved state
byte[] origin = new Document().Also(d => d.PutJsonRoot("""{"shared":1}""")).Save();

using var peer1 = Document.Load(origin);
using var peer2 = Document.Load(origin);

peer1.PutJsonRoot("""{"from_peer1":"hello"}""");
peer2.PutJsonRoot("""{"from_peer2":"world"}""");

peer1.Merge(peer2);  // peer1 now has all three keys

Sync protocol (bidirectional, transport-agnostic)

The sync protocol is incremental — peers only exchange what the other doesn't have yet. Use one SyncState per remote peer per connection session.

using var docLocal  = new Document();
using var docRemote = new Document();   // in practice, lives on another machine

docLocal.PutJsonRoot("""{"device":"phone","ts":1}""");

using var stateLocal  = new SyncState();
using var stateRemote = new SyncState();

// Exchange messages until both sides are done
while (true)
{
    byte[] msg = stateLocal.GenerateSyncMessage(docLocal);
    if (msg.Length == 0) break;                             // local is done
    stateRemote.ReceiveSyncMessage(docRemote, msg);

    byte[] reply = stateRemote.GenerateSyncMessage(docRemote);
    if (reply.Length > 0)
        stateLocal.ReceiveSyncMessage(docLocal, reply);
}

// docRemote now matches docLocal

Persisting sync state (for reconnecting peers):

// Before disconnect — save the SyncState so the next session is incremental
byte[] savedState = stateLocal.Save();

// On reconnect — restore and continue; only new changes are exchanged
using var resumed = SyncState.Load(savedState);

Convenience helper (for in-process / test use):

// Syncs docA and docB in-memory until both converge
AutomergeExtensions.SyncInMemory(docA, docB);

Error handling

All failures throw AutomergeNativeException. Common causes:

  • Document.Load / SyncState.Load with invalid bytes
  • ApplyChanges with corrupted change data
  • GetValue with a path that doesn't exist in the document

Usage — WinRT projection

Target: WinUI 3 / Windows App SDK apps, or any language with WinRT projection support (C#, C++/WinRT, Rust/WinRT).

NuGet (recommended):

<PackageReference Include="Automerge.Windows.WinRT" Version="*" />

Microsoft.Windows.CsWinRT is pulled in automatically as a dependency. The included Automerge.Windows.WinRT.props file wires up CsWinRTInputs so C# projections are generated at build time — no manual configuration needed. The package bundles Automerge.Windows.dll + automerge_core.dll for win-x64 and win-arm64.

Manual reference: add Automerge.Windows.winmd and Automerge.Windows.dll to your project, then add Microsoft.Windows.CsWinRT and set:

<CsWinRTInputs Include="path\to\Automerge.Windows.winmd" />
<CsWinRTIncludes>Automerge.Windows</CsWinRTIncludes>

Binary data (IBuffer) is exchanged via Windows.Storage.Streams.IBuffer. Use DataWriter / DataReader to convert to/from byte[].

Basic read / write

using Automerge.Windows;

var doc = new Document();
doc.PutJsonRoot("""{"name":"Alice","score":42}""");

string name  = doc.GetValue("""["name"]""");    // → "\"Alice\""
string score = doc.GetValue("""["score"]""");   // → "42"

Persistence

// Save returns an IBuffer
Windows.Storage.Streams.IBuffer buf = doc.Save();

// Restore
var doc2 = Document.Load(buf);

Changes and merge

var origin = new Document();
origin.PutJsonRoot("""{"shared":1}""");
var snap = origin.Save();

var peer1 = Document.Load(snap);
var peer2 = Document.Load(snap);
peer1.PutJsonRoot("""{"p1":"hello"}""");
peer2.PutJsonRoot("""{"p2":"world"}""");

peer1.Merge(peer2);

Sync protocol

var stateA = new SyncState();
var stateB = new SyncState();   // one per remote peer

for (int i = 0; i < 20; i++)
{
    var msgAB = stateA.GenerateSyncMessage(docA);
    if (msgAB.Length > 0) stateB.ReceiveSyncMessage(docB, msgAB);

    var msgBA = stateB.GenerateSyncMessage(docB);
    if (msgBA.Length > 0) stateA.ReceiveSyncMessage(docA, msgBA);

    // Stop when neither side has anything new to say
    if (msgAB.Length == 0 && msgBA.Length == 0) break;
}

Persisting sync state:

IBuffer saved = stateA.Save();
var resumed   = SyncState.Load(saved);

Error handling

Errors from the native layer are surfaced as COMException (HRESULT E_FAIL). The error message is propagated from the Rust layer.


Usage — C++ RAII wrapper

Target: Native C++17/20 applications. Link automerge_wrapper.lib and put automerge_core.dll next to the executable.

vcpkg (overlay port):

# Clone this repo somewhere, then:
vcpkg install automerge-windows --overlay-ports=<path-to-repo>/ports

Then in your CMakeLists.txt:

find_package(automerge-windows CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE automerge::wrapper)

automerge::wrapper links the C++ wrapper and automatically pulls in automerge::core (the DLL import lib). Deploy automerge_core.dll with your app.

The port file (ports/automerge-windows/portfile.cmake) downloads the pre-built release ZIP; SHAs are updated automatically on each release. See ports/automerge-windows/usage for details.

#include <automerge/Document.hpp>
#include <automerge/SyncState.hpp>
#include <automerge/Error.hpp>

using namespace automerge;

Basic read / write

Document doc;
doc.put_json_root(R"({"name":"Alice","score":42})");

std::string name  = doc.get_value(R"(["name"])");   // → "\"Alice\""
std::string score = doc.get_value(R"(["score"])");  // → "42"
std::string root  = doc.get_value("[]");            // → {"name":"Alice", ...}

Persistence

std::vector<uint8_t> bytes = doc.save();
Document doc2 = Document::load(bytes);

Changes-based replication

Document sender, receiver;
sender.put_json_root(R"({"msg":"hello"})");

// All changes
std::vector<uint8_t> all = sender.get_changes();
receiver.apply_changes(all);

// Incremental — only changes since a checkpoint
auto heads = sender.get_heads();
sender.put_json_root(R"({"msg":"world"})");
auto delta = sender.get_changes(heads);
receiver.apply_changes(delta);

Merge

Document peer1, peer2;
peer1.put_json_root(R"({"a":"from_peer1"})");
peer2.put_json_root(R"({"b":"from_peer2"})");
peer1.merge(peer2);   // peer1 now has both keys

Sync protocol

SyncState ss_a, ss_b;   // one SyncState per peer per connection

for (int i = 0; i < 20; ++i) {
    auto msg_ab = ss_a.generate_sync_message(doc_a);
    if (!msg_ab.empty()) ss_b.receive_sync_message(doc_b, msg_ab);

    auto msg_ba = ss_b.generate_sync_message(doc_b);
    if (!msg_ba.empty()) ss_a.receive_sync_message(doc_a, msg_ba);

    if (msg_ab.empty() && msg_ba.empty()) break;   // converged
}

Persisting sync state:

auto saved   = ss_a.save();
auto resumed = SyncState::load(saved);

Error handling

All errors throw automerge::AutomergeError (derived from std::runtime_error). Move semantics are supported; copying is deleted.


Usage — C ABI

Target: Any language with C FFI (Rust, Python, Go, Zig, …). Include rust-core/include/automerge_core.h and link automerge_core.dll / automerge_core.dll.lib.

Ownership rules

  • Functions that write *out_bytes / *out_doc / *out_state transfer ownership to the caller.
  • Free byte buffers with AMfree_bytes(ptr, len).
  • Exception: NUL-terminated strings returned by AMget_value must be freed with AMfree_bytes(ptr, len + 1) (one extra byte for the NUL).
  • Free documents with AMdestroy_doc.
  • Free sync states with AMfree_sync_state.
  • Check return value: AM_OK (0) = success, AM_ERR (1) = failure.
  • On failure, call AMget_last_error to retrieve the error message.

Basic example

#include "automerge_core.h"
#include <stdio.h>

AMdoc* doc = AMcreate_doc();

// Write
AMput_json_root(doc, "{\"name\":\"Alice\",\"score\":42}");

// Read
uint8_t* json = NULL;
size_t   len  = 0;
if (AMget_value(doc, "[\"name\"]", &json, &len) == AM_OK) {
    printf("name = %.*s\n", (int)len, json);  // → name = "Alice"
    AMfree_bytes(json, len + 1);
}

// Save / load
uint8_t* bytes = NULL;
size_t   bytes_len = 0;
AMsave(doc, &bytes, &bytes_len);

AMdoc* doc2 = NULL;
AMload(bytes, bytes_len, &doc2);
AMfree_bytes(bytes, bytes_len);

AMdestroy_doc(doc);
AMdestroy_doc(doc2);

Sync protocol

AMsync_state* ss_a = AMcreate_sync_state();
AMsync_state* ss_b = AMcreate_sync_state();

for (int i = 0; i < 20; ++i) {
    uint8_t* msg = NULL;  size_t msg_len = 0;
    AMgenerate_sync_message(doc_a, ss_a, &msg, &msg_len);
    if (msg && msg_len > 0) {
        AMreceive_sync_message(doc_b, ss_b, msg, msg_len);
        AMfree_bytes(msg, msg_len);
    }

    uint8_t* reply = NULL;  size_t reply_len = 0;
    AMgenerate_sync_message(doc_b, ss_b, &reply, &reply_len);
    if (reply && reply_len > 0) {
        AMreceive_sync_message(doc_a, ss_a, reply, reply_len);
        AMfree_bytes(reply, reply_len);
    }
}

AMfree_sync_state(ss_a);
AMfree_sync_state(ss_b);

Current limitations

AMput_json_root accepts only scalar values (string, number, bool, null). Nested objects and arrays require the full Automerge object-graph API (AMput_object) which is not yet exposed in this C ABI. Setting a nested value returns AM_ERR.


License

MIT

Product Compatible and additional computed target framework versions.
Universal Windows Platform uap was computed.  uap10.0 is compatible. 
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
0.6.1 98 5/11/2026
0.6.0 114 4/17/2026
0.5.0 105 4/16/2026
0.4.0 101 4/16/2026
0.3.0 98 4/16/2026
0.2.3 109 4/16/2026
0.2.2 108 4/16/2026
0.2.1 99 4/15/2026
0.2.0 100 4/15/2026
0.1.0 105 4/15/2026