<div align="center">
<img src="./logo-128.png" alt="Ankurah Logo" width="80" height="80">
<h1>Ankurah</h1>
<p><em>The root of all cosmic projections of state</em></p>
<p>A distributed state-management framework that enables real-time data synchronization<br>across multiple nodes with built-in observability.</p>
<p>
<a href="https://ankurah.org/getting-started.html"><strong>Get Started</strong></a> •
<a href="https://ankurah.org/what-is-ankurah.html">Documentation</a> •
<a href="https://discord.gg/XMUUxsbT5S">Discord</a>
</p>
<p>
<img src="https://img.shields.io/badge/status-beta-blue" alt="Beta Status">
<a href="https://crates.io/crates/ankurah"><img src="https://img.shields.io/crates/v/ankurah.svg" alt="Crates.io"></a>
<a href="https://docs.rs/ankurah"><img src="https://docs.rs/ankurah/badge.svg" alt="Documentation"></a>
</p>
</div>
---
## What is Ankurah?
Ankurah is a **real-time reactive database** for building collaborative and multiplayer applications. Define your data model once in Rust, and use it everywhere—native servers, browser clients, mobile apps—with automatic synchronization across all connected nodes.
### The Challenge
Building applications where multiple users interact with shared state is hard. You need real-time sync, offline support, conflict resolution, reactive UI updates, and query pushdown—and it all has to work consistently across your server (Postgres) and clients (IndexedDB in browsers). Most teams either build fragile ad-hoc solutions or accept significant compromises.
### The Approach
Ankurah is **event-sourced**: every change is an operation that can be replayed, merged, and synchronized. Entities can reference each other (graph-style navigation), and you can subscribe to live queries that automatically update as data changes anywhere in the system.
Your model is defined once in Rust with a derive macro. The same code compiles to native binaries for servers and WASM for browsers. TypeScript interfaces and React hooks are generated automatically.
### Why Ankurah?
**Local-first by default.** Your queries execute against the local cache instantly, then sync with durable servers in the background. Users get sub-millisecond reads and offline support without you writing sync logic.
**Live queries that just work.** Subscribe to a query and your UI stays in sync automatically—changes flow in from anywhere.
**Graph-ready data model.** Entities can reference each other, and you can navigate those relationships directly in queries. Full graph traversal is on the roadmap.
**Collaborative editing built in.** Text fields can use Yjs-backed CRDTs for real-time collaborative editing with automatic conflict resolution.
**One codebase, multiple backends.** Swap between Sled, Postgres, and IndexedDB without changing application code. Your server uses Postgres while browsers use IndexedDB—same queries, same models.
### Who it's for
Teams building collaborative tools, local-first apps, multiplayer experiences, or anything where users interact with shared state in real-time.
---
## Key Features
### 1️⃣ Schema-First Design
<pre><code transclude="docs/example/model/src/lib.rs#model">#[derive(Model, Debug, Serialize, Deserialize)]
pub struct Album {
#[active_type(YrsString)]
pub name: String,
pub artist: String,
pub year: i32,
}</code></pre>
<sub>YrsString is default backend for String, LWW otherwise</sub>
### 🔍 Live Queries
**Rust**
<pre><code transclude="docs/example/server/src/main.rs#livequery-rust">let q: LiveQuery<AlbumView> = ctx.query("year > 1985")?;</code></pre>
**TypeScript**
<pre><code transclude="docs/example/react-app/src/App.tsx#react-livequery">const q: AlbumLiveQuery = Album.query(ctx(), "year > 1985");</code></pre>
<sub>Views and WASM bindings generated by the Model macro</sub>
### ⚛️ React Support
<pre><code transclude="docs/example/react-app/src/App.tsx#react-component">/* creates and Binds a ReactObserver to the component */
const AlbumList = signalObserver(({ albums }: Props) => {
return (
<ul>
/* React Observer automatically tracks albums */
{albums.items.map((album) => (
<li>{album.name}</li>
))}
</ul>
);
});</code></pre>
<sub>ReactObserver tracks livequery.items</sub>
### 🗄️ Flexible Storage
**Sled**
<pre><code transclude="docs/example/server/src/main.rs#storage-sled">let storage = SledStorageEngine::new()?;</code></pre>
**Postgres**
<pre><code transclude="docs/example/server/src/main.rs#storage-postgres">let storage = Postgres::open(uri).await?;</code></pre>
**IndexedDB**
<pre><code transclude="docs/example/wasm-bindings/src/lib.rs#storage-indexeddb">let storage = IndexedDBStorageEngine::open("myapp").await?;</code></pre>
<sub>TiKV planned</sub>
### ⚡ Generated Interfaces
```typescript
export class Album {
static query(context: Context, selection: string, ...args: any): AlbumLiveQuery;
...
}
```
### 🔋 Batteries Included
WebSocket server & client • HTTP/REST endpoints • Authentication hooks • Query routing & pushdown
---
## Quick Example
**Server**
<pre><code transclude="docs/example/server/src/main.rs#server-example">let storage = SledStorageEngine::with_path(storage_dir)?;
let node = Node::new_durable(Arc::new(storage), PermissiveAgent::new());
node.system.create().await?;
let mut server = WebsocketServer::new(node);
println!("Running server...");
server.run("0.0.0.0:9797").await?;</code></pre>
**Rust Client**
<pre><code transclude="docs/example/server/src/main.rs#rust-client-example">let storage = SledStorageEngine::new_test()?;
let node = Node::new(Arc::new(storage), PermissiveAgent::new());
let _client = WebsocketClient::new(node.clone(), "ws://localhost:9797").await?;
node.system.wait_system_ready().await;
// Create album
let ctx = node.context(ankurah::policy::DEFAULT_CONTEXT)?;
let trx = ctx.begin();
trx.create(&Album { name: "Parade".into(), artist: "Prince".into(), year: 1986 }).await?;
trx.commit().await?;</code></pre>
**WASM Client**
<pre><code transclude="docs/example/wasm-bindings/src/lib.rs#client-example">let storage = IndexedDBStorageEngine::open("myapp").await?;
let node = Node::new(Arc::new(storage), PermissiveAgent::new());
let _client = WebsocketClient::new(node.clone(), server_url)?;
node.system.wait_system_ready().await;
let context = node.context(DEFAULT_CONTEXT)?;
let _albums = context.query::<ankurah_doc_example_model::AlbumView>("year > 1985")?;</code></pre>
**React Component**
<pre><code transclude="docs/example/react-app/src/App.tsx#react-component">/* creates and Binds a ReactObserver to the component */
const AlbumList = signalObserver(({ albums }: Props) => {
return (
<ul>
/* React Observer automatically tracks albums */
{albums.items.map((album) => (
<li>{album.name}</li>
))}
</ul>
);
});</code></pre>
---
## Getting Started
Get up and running quickly with our React + Sled template:
```bash
cargo generate https://github.com/ankurah/react-sled-template
```
Or run the example manually:
```bash
# Start the server
cargo run -p ankurah-example-server
# Build WASM bindings (in examples/wasm-bindings)
wasm-pack build --target web --debug
# Run the React app (in examples/react-app)
bun install && bun dev
```
Then open http://localhost:5173/ in two browser tabs (one regular, one incognito) to see real-time sync.
---
## Core Concepts
| **Model** | A struct describing fields and types for entities in a collection |
| **Collection** | A group of entities of the same type (like a database table) |
| **Entity** | A discrete identity in a collection with dynamic schema |
| **View** | A read-only, typed representation of an entity |
| **Mutable** | A mutable, typed state representation of an entity |
| **Event** | An atomic change used for synchronization and audit trail |
---
## Roadmap
**Milestone 2** - TiKV backend, Graph functionality, User-definable data types
**Milestone 3** - P2P, Portable cryptographic identities, E2EE, Hypergraph
---
<div align="center">
<a href="https://github.com/ankurah/ankurah">GitHub</a> •
<a href="https://discord.gg/XMUUxsbT5S">Discord</a> •
<a href="https://ankurah.org">Website</a>
<br><sub>Licensed under MIT or Apache-2.0</sub>
</div>