Skip to content

Commit efde1d6

Browse files
cursoragentjoepio
andcommitted
Add a Kotlin / UniFFI SDK over atomic_lib with Iroh P2P.
Same product surface as the Python SDK: local redb, signed Loro commits, and process-global Iroh (startPeer / syncWith / live save). The ffi crate is excluded from the workspace. Resource.destroyResource avoids UniFFI's Disposable.destroy collision. JVM JUnit covers CRUD plus a two-process Iroh sync; Android AAR is later. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
1 parent 6ec0cd5 commit efde1d6

38 files changed

Lines changed: 10654 additions & 56 deletions

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,13 @@ scratchpad
4040
.bin/
4141
.fastembed_cache
4242

43-
# Python SDK (excluded from the Cargo workspace, so it has its own target/)
43+
# Python / UniFFI SDKs (excluded from the Cargo workspace, own target/)
4444
python/target
4545
python/.venv
4646
python/*.egg-info
47+
ffi/target
48+
ffi/kotlin/.gradle
49+
ffi/kotlin/build
4750
**/__pycache__
4851
**/.pytest_cache
4952
*.so

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Atomic Server is a graph database with real-time sync, built on **Loro CRDT** fo
6363
- **`data-browser`** (`browser/data-browser/`) — The web app (React + TipTap + Loro), feels similar to notion. See the related AGENTS.md
6464
- **`flutter/`** — Cross-platform canvas app (Android/iOS/Web). Uses `flutter_rust_bridge` to call `atomic_lib`. See `flutter/README.md` and `flutter/AGENTS.md`.
6565
- **`python/`** — Python SDK (`atomic_data`). PyO3 bindings over `atomic_lib` (local redb). Excluded from the Cargo workspace; build with `maturin`. See `python/README.md` and `planning/python-sdk.md`.
66+
- **`ffi/`** — UniFFI crate (`atomic-ffi`) and Kotlin SDK (`dev.atomicdata`). Same local redb + Iroh surface as Python. Excluded from the Cargo workspace. See `ffi/README.md` and `planning/kotlin-sdk.md`.
6667

6768
### Data model
6869

@@ -233,6 +234,7 @@ cd browser/lib && pnpm test # 29 JS tests
233234
cd browser && pnpm run -r build # Full workspace build
234235
cd browser && pnpm run test-e2e # Full e2e test
235236
cd python && maturin develop && pytest # Python SDK (excluded from workspace)
237+
cd ffi && cargo test && cd kotlin && ./gradlew test # Kotlin / UniFFI SDK (excluded from workspace)
236238
```
237239

238240
## Cursor Cloud specific instructions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain
88
## UNRELEASED
99

1010
- **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). No server required.
11+
- **Kotlin SDK** (`ffi/`, package `dev.atomicdata`): UniFFI bindings over `atomic_lib`. Same local redb + Iroh surface as Python (`startPeer`, `syncWith`, live push on save). JVM tests included; Android AAR is later.
1112

1213
## [v0.41.0-beta.2] - 2026-08-01
1314

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ members = [
1111
"atomic-plugin",
1212
"tools/cargo-bin",
1313
]
14-
exclude = ["flutter/rust", "python"]
14+
exclude = ["flutter/rust", "python", "ffi"]
1515

1616
# Debuginfo dominates target/ size: with ~1460 deps (tauri, actix, iroh) the
1717
# default `debug = true` produces a multi-GB tree per build flavor, and this

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This repo also includes:
2020
- [`atomic-cli`](cli/README.md) terminal client.
2121
- [`flutter`](/flutter) a Dart / Flutter client for Atomic Data, plus AtomicCanvas, a collaborative infinite drawing canvas that syncs peer-to-peer between devices.
2222
- [`python`](/python) local-first Python SDK (`atomic_data`) wrapping `atomic_lib` via PyO3.
23+
- [`ffi`](/ffi) UniFFI crate and Kotlin SDK (`dev.atomicdata`) wrapping `atomic_lib`.
2324
- [`docs`](docs/README.md) documentation / specification for Atomic Data ([docs.atomicdata.dev](https://docs.atomicdata.dev)).
2425

2526
_Status: alpha. [Breaking changes](CHANGELOG.md) are expected until 1.0._
@@ -49,7 +50,7 @@ _Status: alpha. [Breaking changes](CHANGELOG.md) are expected until 1.0._
4950
- 📲 **Invite and sharing system** with [Atomic Invites](https://docs.atomicdata.dev/invitations.html)
5051
- 🌐 **Embedded server** with support for HTTP / HTTPS / HTTP2.0 (TLS) and Built-in LetsEncrypt handshake.
5152
- 📱 **Runs on mobile**: `atomic_lib` compiles into Flutter apps through [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge), so phones get the same local-first store, signing and peer sync as the browser, not a thin REST wrapper. See [`/flutter`](/flutter).
52-
- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a [Dart / Flutter client](/flutter/lib/atomic), and a [Python SDK](/python) (`atomic_data`)
53+
- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a [Dart / Flutter client](/flutter/lib/atomic), a [Python SDK](/python) (`atomic_data`), and a [Kotlin SDK](/ffi) (`dev.atomicdata`)
5354

5455
https://user-images.githubusercontent.com/2183313/139728539-d69b899f-6f9b-44cb-a1b7-bbab68beac0c.mp4
5556

TESTING_COVERAGE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ A flow is only genuinely safe when all three are covered.
3939
| Flutter Dart | `cd flutter && flutter test` | `flutterTest` |
4040
| Flutter Rust bridge | `cargo test --manifest-path flutter/rust/Cargo.toml` | `flutterTest` |
4141
| Python SDK | `cd python && maturin develop && pytest` | **not in CI** |
42+
| Kotlin / UniFFI SDK | `cd ffi && cargo test && cd kotlin && ./gradlew test` | **not in CI** |
4243

4344
CI runs `cargo nextest run --workspace --exclude atomic-server-tauri
4445
--no-default-features --features light`. Feature unification pulls in
@@ -51,6 +52,9 @@ Two things worth knowing about the runners:
5152
`--manifest-path` step in `flutterTest`.
5253
- **`python/` is excluded from the workspace** for the same reason (PyO3).
5354
Tests are `pytest` after `maturin develop`; they are not in Dagger CI yet.
55+
- **`ffi/` is excluded from the workspace** (UniFFI + Iroh). Rust tests are
56+
`cargo test` in `ffi/`; JVM tests are `./gradlew test` in `ffi/kotlin`.
57+
Not in Dagger CI yet.
5458
- **`.config/nextest.toml` sets `retries = 2`.** A flaky test passes CI
5559
silently. Check for `FLAKY` in nextest output, not just the summary line.
5660

@@ -189,6 +193,10 @@ One 13-line smoke test, never run in CI — the pipeline has no emulator.
189193

190194
Glue: `pytest` after `maturin build` covers in-memory CRUD, file-backed reopen, and a two-process Iroh sync (`tests/test_iroh.py`). Not in Dagger CI. No WS session or blobs.
191195

196+
### 7c. Kotlin / UniFFI SDK (`ffi/`)
197+
198+
Glue: `cargo test` in `ffi/` covers the same local CRUD + Iroh-guard cases as Python, in-process. `ffi/kotlin` JUnit covers the generated bindings plus a two-process Iroh sync (`IrohTest.twoProcessIrohSync`). Not in Dagger CI. No Android AAR, WS session, or blobs. `startPeer` is process-global — only one JVM test may start Iroh.
199+
192200
### 8. Known residual races
193201

194202
None outstanding. The concurrent-writer bug that lived here — a local edit

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
- [CLI](rust-cli.md)
4848
- [Lib](rust-lib.md)
4949
- [Python](python.md)
50+
- [Kotlin](kotlin.md)
5051

5152
# Guides
5253

docs/src/atomic-server.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ It's free, open source (MIT license), and has a ton of features:
3030
- 📲 **Invite and sharing system** with [Atomic Invites](https://docs.atomicdata.dev/invitations.html)
3131
- 🌐 **Embedded server** with support for HTTP / HTTPS / HTTP2.0 (TLS) and Built-in LetsEncrypt handshake.
3232
- 📱 **Runs on mobile**: `atomic_lib` compiles into Flutter apps through [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge), so phones get the same local-first store, signing and peer sync as the browser — not a thin REST wrapper.
33-
- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a Dart / Flutter client, and a [Python SDK](python.md)
33+
- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a Dart / Flutter client, a [Python SDK](python.md), and a [Kotlin SDK](kotlin.md)

docs/src/kotlin.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{{#title Kotlin SDK for Atomic Data}}
2+
3+
# Kotlin SDK
4+
5+
Local-first [Atomic Data](atomic-data-overview.md) for the JVM. The package
6+
wraps [`atomic_lib`](rust-lib.md) through
7+
[UniFFI](https://mozilla.github.io/uniffi-rs/) — the same Rust store the
8+
browser (WASM), Flutter app, and [Python SDK](python.md) use. Reads and
9+
writes go to a local [redb](https://github.com/cberner/redb) file. Edits are
10+
signed Loro commits. A server is optional.
11+
12+
Source: [`ffi/`](https://github.com/atomicdata-dev/atomic-server/tree/develop/ffi)
13+
(crate `atomic-ffi`, Kotlin package `dev.atomicdata`).
14+
15+
Python stays on PyO3. This UniFFI surface is what Swift and the Android
16+
Binder host will share later.
17+
18+
## Install
19+
20+
From a checkout of this repo (needs a Rust toolchain and JDK 21):
21+
22+
```bash
23+
cd ffi
24+
cargo build
25+
./generate-kotlin.sh
26+
cd kotlin && ./gradlew test
27+
```
28+
29+
Point `jna.library.path` at `ffi/target/debug` so the JVM can load
30+
`libatomic_ffi`.
31+
32+
## Quick start
33+
34+
```kotlin
35+
import dev.atomicdata.Store
36+
import dev.atomicdata.Urls
37+
38+
val store = Store.open("./my-atomic-data")
39+
val setup = store.setup("Ada")
40+
41+
val note = store.create(
42+
Urls.PLAIN_TEXT,
43+
"Hello",
44+
null,
45+
mapOf("description" to "A locally stored note"),
46+
)
47+
note.set(Urls.DESCRIPTION, "Edited offline")
48+
note.save()
49+
50+
val got = store.get(note.subject())
51+
println("${got?.get("name")} ${got?.get("description")}")
52+
53+
for (child in store.query(setup.driveSubject, null, null, null, null, 0u)) {
54+
println("${child.subject()} ${child.name()}")
55+
}
56+
57+
store.flush()
58+
```
59+
60+
`setup.agentSecret` is the only way to sign writes after you reopen the
61+
store. Keep it.
62+
63+
```kotlin
64+
val store = Store.open("./my-atomic-data")
65+
store.loadAgent(secret)
66+
```
67+
68+
`Store.inMemory()` is the same API without a directory.
69+
70+
`Resource.destroyResource()` deletes the resource. It is not named
71+
`destroy()` because UniFFI already uses that for FFI handle teardown.
72+
73+
## P2P sync (Iroh)
74+
75+
```kotlin
76+
val node = store.startPeer() // did:ad:node:…
77+
store.announce(null) // optional pkarr publish
78+
// On the other device, same agent secret (or a grant), then:
79+
other.syncWith(node, null)
80+
```
81+
82+
After the first sync, connected peers get live Loro updates on `.save()`.
83+
`store.waitFor(subject, timeoutSecs)` blocks until a local or peer change
84+
lands.
85+
86+
Two Iroh nodes cannot share one JVM — `startPeer` is process-global, same
87+
as Flutter and Python. Use two processes (or two devices).
88+
89+
## What this is not (yet)
90+
91+
WebSocket-to-server `SyncSession` is not wrapped. Blobs and history are not
92+
wrapped. An Android AAR (`cargo-ndk`) and Binder host are later layers.
93+
Iroh P2P is.

docs/src/tooling.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Libraries and clients (all MIT licenced) that work great with [atomic-server](at
1010
- Client CLI (rust): [atomic-cli](rust-cli.md) for fetching & editing data
1111
- Rust library: [atomic-lib](rust-lib.md) powers `atomic-server` and `atomic-cli`, and can be used in other Rust projects ([docs.rs](https://docs.rs/atomic_lib/latest/atomic_lib/))
1212
- Python SDK: [atomic-data](python.md) wraps `atomic_lib` via PyO3 (local redb store, no server required)
13+
- Kotlin SDK: [dev.atomicdata](kotlin.md) wraps `atomic_lib` via UniFFI (local redb store, Iroh P2P)
1314
- [Raycast Extension](https://www.raycast.com/atomicdata-dev/atomic-data-browser): full-text search
1415

1516
## Want to add to this list? Some ideas for tooling

0 commit comments

Comments
 (0)