A powerful, elegant distributed synchronization framework that brings Git-like version control to any .NET application. Powers Kahla real-time messaging with eventual consistency, automatic conflict resolution, and offline-first capabilities.
Imagine building a multiplayer Snake game where all players see the exact same state in real-time, or a collaborative chat app that works perfectly even when offline. AiurVersionControl makes this trivially easy by treating all data changes as immutable commits that can be synced, merged, and replayed.
Key advantages:
- ✅ Offline Operations - Commit changes and query your repository without any network connection
- ✅ Automatic Sync - When connection is available, changes sync automatically
- ✅ Local-First - All operations work on local data first, perfect for building offline-capable CRUD apps
- 🔄 Distributed Event Sourcing - Every change is an immutable commit with automatic conflict resolution
- 🌐 Offline-First Architecture - Work offline, sync later with guaranteed eventual consistency
- ⚡ Real-time Sync - Bi-directional automatic push/pull between repositories
- 🎯 Type-Safe - Fully generic API works with any data type
- 🧩 CRUD Operations - High-level abstractions for collection manipulation
- 🔌 Multiple Transports - In-memory, WebSocket, HTTP, or custom implementations
- 🎮 Production Ready - Battle-tested in Kahla real-time messaging platform
| Package | Description | Use Case |
|---|---|---|
| Aiursoft.AiurEventSyncer | Core event synchronization engine | Building distributed apps with auto-sync |
| Aiursoft.AiurVersionControl | Event replay & workspace reconstruction | State management with commit history |
| Aiursoft.AiurVersionControl.Crud | High-level CRUD operations | Managing collections with Add/Update/Delete |
| Aiursoft.AiurEventSyncer.WebExtends | WebSocket transport support | Real-time web applications |
| Aiursoft.AiurStore | Immutable storage abstraction | Custom database backends |
dotnet add package Aiursoft.AiurEventSyncerCreate two repositories that automatically stay in sync:
using Aiursoft.AiurEventSyncer.Models;
using Aiursoft.AiurEventSyncer.Remotes;
// Create two repositories
var clientRepo = new Repository<string>();
var serverRepo = new Repository<string>();
// Subscribe to observe commits
clientRepo.AppendCommitsHappened.Subscribe(commits =>
{
Console.WriteLine($"[Client] Received: {commits.Last().Item}");
return Task.CompletedTask;
});
serverRepo.AppendCommitsHappened.Subscribe(commits =>
{
Console.WriteLine($"[Server] Received: {commits.Last().Item}");
return Task.CompletedTask;
});
// Attach client to server with bi-directional auto-sync
await new ObjectRemote<string>(serverRepo, autoPush: true, autoPull: true)
.AttachAsync(clientRepo);
// Now they sync automatically!
clientRepo.Commit("Hello from client");
// Output: [Client] Received: Hello from client
// [Server] Received: Hello from client
serverRepo.Commit("Hello from server");
// Output: [Server] Received: Hello from server
// [Client] Received: Hello from server
// Both repositories now have identical commit history
Console.WriteLine($"Client commits: {clientRepo.Commits.Count()}"); // 2
Console.WriteLine($"Server commits: {serverRepo.Commits.Count()}"); // 2Work with collections using familiar operations:
using Aiursoft.AiurVersionControl.Crud;
using Aiursoft.AiurVersionControl.Remotes;
// Define your model
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
}
// Create a collection repository
var library = new CollectionRepository<Book>
{
new() { Id = 1, Title = "1984" },
new() { Id = 2, Title = "Brave New World" }
};
// CRUD operations
library.Add(new Book { Id = 3, Title = "Fahrenheit 451" });
library.Patch(nameof(Book.Id), 2, nameof(Book.Title), "Updated Title");
library.Drop(nameof(Book.Id), 1);
// Sync with remote
var remoteLibrary = new CollectionRepository<Book>();
var remote = new ObjectRemoteWithWorkSpace<CollectionWorkSpace<Book>>(
library, autoPush: true, autoPull: true);
await remote.AttachAsync(remoteLibrary);
// All changes automatically sync to remoteLibrary!Build stateful applications with full commit history:
using Aiursoft.AiurVersionControl.Models;
using Aiursoft.AiurVersionControl.Remotes;
// Your state model
public class GameState : WorkSpace
{
public int Score { get; set; }
public override object Clone() => new GameState { Score = Score };
}
// Modification actions
public class UpdateScore : IModification<GameState>
{
public int Points { get; set; }
public void Apply(GameState workspace) => workspace.Score += Points;
}
// Create controlled repository
var game = new ControlledRepository<GameState>();
// Apply changes
game.ApplyChange(new UpdateScore { Points = 10 });
game.ApplyChange(new UpdateScore { Points = 5 });
Console.WriteLine(game.WorkSpace.Score); // 15
// Sync with other players
var player2 = new ControlledRepository<GameState>();
await new ObjectRemoteWithWorkSpace<GameState>(player2, true, true)
.AttachAsync(game);
// player2.WorkSpace.Score is now 15!Check out the /demos folder for complete working examples:
A fully functional multiplayer Snake game demonstrating:
- Real-time game state synchronization across multiple clients
- Deterministic conflict resolution
- Immediate visual feedback with no server round-trip delay
- Game state replay and debugging
cd demos/Aiursoft.SnakeGame
dotnet runWindows desktop app showcasing offline-first data binding.
AiurVersionControl implements a layered architecture inspired by distributed version control systems:
┌─────────────────────────────────────────┐
│ Your Application (CRUD, WorkSpace) │ ← High-level business logic
├─────────────────────────────────────────┤
│ AiurVersionControl (Event Replay) │ ← State reconstruction
├─────────────────────────────────────────┤
│ AiurEventSyncer (Sync Engine) │ ← Distributed syncing
├─────────────────────────────────────────┤
│ AiurStore (Immutable Storage) │ ← Persistence layer
└─────────────────────────────────────────┘
- Repository - Immutable commit log, similar to Git
- Commit - Atomic change event with unique ID
- Remote - Connection to another repository for syncing
- WorkSpace - Current state reconstructed from commit history
- Modification - Operation that transforms workspace state
Publish-Subscribe: Hub-and-Spoke: Peer-to-Peer:
sender → server client₁ ↔ server repo₁ ↔ repo₂
↓ client₂ ↔ server ↓ ↓
subscriber₁ client₃ ↔ server repo₃ ↔ repo₄
subscriber₂
All patterns supported with autoPush and autoPull configurations!
Instead of storing current state, AiurVersionControl stores all changes as immutable commits:
// Traditional approach
var user = dbContext.Users.Find(123);
user.Name = "Alice"; // Lost: what was the old name? when did it change?
dbContext.SaveChanges();
// AiurVersionControl approach
userRepo.Commit(new UpdateName { UserId = 123, NewName = "Alice" });
// Full history preserved! Can replay, debug, and sync.When two repositories diverge and then sync, commits are automatically merged in deterministic order:
// Repo A and B start synchronized
await new ObjectRemote<int>(repoB, autoPush: true, autoPull: true)
.AttachAsync(repoA);
// Both add commits while temporarily disconnected
repoA.Commit(100); // A's timeline: [100]
repoB.Commit(200); // B's timeline: [200]
// When reconnected, both converge to same order
await Task.Delay(100); // Allow sync
// Both now have: [100, 200] or [200, 100] - deterministic based on commit IDsAll operations work without network connection - commits, queries, and CRUD operations are purely local:
// At home with WiFi
var todoApp = new CollectionRepository<Todo>();
todoApp.Add(new Todo { Id = 1, Task = "Buy milk" });
var remote = await ConnectToServer(todoApp);
// Airplane mode! Disconnect from server
await remote.DetachAsync();
// Everything still works - completely offline!
todoApp.Add(new Todo { Id = 2, Task = "Read book" });
todoApp.Add(new Todo { Id = 3, Task = "Exercise" });
todoApp.Patch(nameof(Todo.Id), 2, nameof(Todo.Task), "Read 50 pages");
todoApp.Drop(nameof(Todo.Id), 1);
// Query your local repository - no network needed
var allTodos = todoApp.WorkSpace.ToList();
Console.WriteLine($"Offline todos count: {allTodos.Count}"); // 2
// Back online - reconnect to server
await remote.AttachAsync(todoApp);
// All offline commits automatically sync to server!
// Server now has the same state: todos 2 and 3This makes AiurVersionControl perfect for building offline-capable CRUD applications - notes apps, todo lists, inventory managers, or any app that needs to work without internet!
| Feature | AiurVersionControl | SignalR | Firebase | Event Store |
|---|---|---|---|---|
| Offline Support | ✅ Full | ❌ | ❌ | |
| Automatic Sync | ✅ Bi-directional | ✅ | ❌ | |
| Conflict Resolution | ✅ Automatic | ❌ | ❌ | |
| Type Safety | ✅ Generic | ❌ | ||
| Self-Hosted | ✅ | ✅ | ❌ | ✅ |
| CRUD Abstraction | ✅ | ❌ | ✅ | ❌ |
The framework includes comprehensive test coverage demonstrating all features:
# Run all tests
dotnet test
# Run specific test suite
dotnet test --filter "FullyQualifiedName~AiurEventSyncer.Tests"Key test scenarios:
- ✅ Bi-directional syncing with auto push/pull
- ✅ Hub-and-spoke distribution patterns
- ✅ Attach/detach/reattach functionality
- ✅ CRUD operations with conflict resolution
- ✅ Merge scenarios with concurrent modifications
// Implement your own storage (SQL, Redis, etc.)
public class SqlCommitStore : InOutDatabase<Commit<MyData>>
{
public override void Add(Commit<MyData> item) => /* SQL INSERT */;
public override IEnumerable<Commit<MyData>> GetAll() => /* SQL SELECT */;
// ... implement other methods
}
var repo = new Repository<MyData>(new SqlCommitStore());// Server side
services.AddAiurEventSyncer();
app.MapWebSocketSyncer<ChatMessage>("/sync");
// Client side
var wsRemote = new WebSocketRemote<ChatMessage>(
"wss://myapp.com/sync", autoPush: true, autoPull: true);
await wsRemote.AttachAsync(localRepo);var game = new ControlledRepository<GameState>();
// React to state changes
game.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(game.WorkSpace))
{
UI.UpdateDisplay(game.WorkSpace);
}
};
game.ApplyChange(new UpdateScore { Points = 10 });
// UI automatically updates!- .NET 10 SDK
- Any IDE (Visual Studio, VS Code, Rider)
# Restore dependencies
dotnet restore
# Build solution
dotnet build
# Run tests
dotnet test
# Pack NuGet packages
dotnet packAiurVersionControl/
├── src/
│ ├── Aiursoft.AiurEventSyncer/ # Core sync engine
│ ├── Aiursoft.AiurVersionControl/ # Version control layer
│ ├── Aiursoft.AiurVersionControl.Crud/ # CRUD operations
│ ├── Aiursoft.AiurEventSyncer.WebExtends/ # WebSocket support
│ └── Aiursoft.AiurStore/ # Storage abstraction
├── demos/
│ ├── Aiursoft.SnakeGame/ # Multiplayer game demo
│ └── SampleWPF/ # Desktop app demo
└── tests/ # Comprehensive tests
We welcome contributions! This project follows a fork-and-pull-request workflow:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - Open a Pull Request
- 🔌 Additional transport implementations (gRPC, MQTT, etc.)
- 📝 More CRUD operation types
- 🎨 Sample applications
- 📚 Documentation improvements
- 🐛 Bug reports with reproduction cases
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by Git, Event Sourcing, and CRDT research
- Powers Kahla - Production-grade real-time messaging
- Built with ❤️ by the Aiursoft team
- Live Demo: Try the Snake Game to see real-time sync in action
- NuGet Packages: Aiursoft.AiurVersionControl
- GitLab Repository: gitlab.aiursoft.com/aiursoft/aiurversioncontrol
- Kahla Application: kahla.app - See it in production
Made with 🎯 precision and ✨ elegance by Aiursoft