Yjs, Automerge, and Loro are excellent CRDT libraries — but they leave the server problem to you. You still write API endpoints. You still design a persistence layer. You still bolt on auth.
Aether-Core deletes that entire stack.
The pitch in one code snippet
Before:
function ShoppingList() {
const [items, setItems] = useState([]);
// ... plus API endpoint, database schema, auth middleware, ...
}
After:
function ShoppingList() {
const [items, setItems] = useAether('items', []);
// that's the whole change. multiplayer, offline, persisted.
}
The Python relay treats every value as opaque bytes. It doesn't implement business logic. It doesn't have endpoints. It cannot read your data.
What's new in v0.5
End-to-end value encryption. Optional client-side AES-256-GCM wraps every value before it leaves the browser. The relay sees ciphertext only. Even someone who runs the relay for you can't read your data:
const room = await AetherCrypto.roomFromPassphrase("passphrase", "salt");
const aether = new Aether("ws://relay", { transform: room.transform });
Verified end-to-end with a wire observer: two clients round-trip plaintext through the room key, while a third socket sniffing the same relay sees only {"__ae_enc": 1, "iv": "...", "ct": "..."}.
IndexedDB per-key persistence (now default). The previous localStorage default hit two hard problems: the 5-10 MB quota ceiling, and JSON-serialize-the-whole-blob on every write. Both fixed. localStorage remains as a backward-compatible fallback for private mode.
Merkle-trie anti-entropy. When two peers reconnect after a partition, epidemic gossip alone can't recover the ops that flowed while they were split. Aether-Core now ships a Merkle-trie digest exchange. Bandwidth is O(k log N) in the number of divergent keys, not O(N) total. A 1000-key mesh with 3 divergent keys reconciles in ~16 probe messages, not 1000.
LAN peer discovery via mDNS. Two relays on the same wifi find each other with zero config. Opt-in (MeshNode(discovery=True)) because some networks treat mDNS traffic as suspicious.
One-command scaffold:
npx @nishantbhatte/aether-core init
Emits relay.py, index.html, Dockerfile, Procfile. Two commands from npx init to a working relay.
Honest positioning
I'm not competing with Yjs on CRDT math. Yjs is more mature, has a bigger ecosystem, and its sync engine is excellent. The wedge is server-side: I ship auth, rate limiting, payload caps, and optional E2E encryption on by default. Yjs leaves that to you.
| Yjs / Automerge / Loro | Aether-Core | |
|---|---|---|
| CRDT sync | ✅ | ✅ |
| Backend code required | You write it | None |
| Auth built-in | ❌ bring your own | ✅ HMAC by default |
| Rate limiting built-in | ❌ | ✅ token bucket |
| Payload caps built-in | ❌ | ✅ configurable |
| E2E crypto in recommended path | Ecosystem addon | Ships in web/aether-crypto.js |
| Anti-entropy after partition | State-vector sync (O(delta)) | Merkle-trie (O(k log N)) |
| LAN auto-discovery | ❌ | ✅ mDNS opt-in |
Try it
pip install aether-zta # Python relay
npm install @nishantbhatte/aether-core # Browser client
Or scaffold a working project:
npx @nishantbhatte/aether-core init
- Repo: https://github.com/IronFighter23/aether-core
- 73 tests green on Linux/macOS/Windows × Python 3.11/3.12
- MIT licensed
Feedback welcome. Especially interested in "here's where I'd break it" from anyone who's built collaborative apps before.

Top comments (5)
This is the kind of "delete the backend" architecture that only works if the trust model is explicit. If the relay cannot read the data, the client-side conflict and recovery story becomes the product.
Yeah, exactly — that's actually by design, not just marketing. The merge logic and anti-entropy reconciliation never look at the value at all, only the HLC stamp, so the relay was always metadata-only even before encryption entered the picture. E2E just makes explicit what was already true architecturally. The honest gap right now: keys are plaintext unless you opt into hashing, and there's no write authentication yet — anyone with the room secret can write, so it's shared-trust rather than zero-trust today. That's the next thing on the list.
That design constraint is strong: if the relay only reasons over stamps and reconciliation metadata, encryption becomes a confirmation of the architecture rather than a privacy sticker added later. Write authentication feels like the next honest boundary to make explicit.
Agreed, that's the honest next line to draw. Right now it's shared-trust: anyone with the room secret can write, so "the relay can't read it" doesn't imply "the relay can vouch for who wrote it." Making that explicit is easy — the harder part is actually building it (per-writer keys, revocation, signature verification wired into the merge path), so for now it's a documented boundary rather than a shipped feature. It's next on the roadmap, not next in this release.
That is the honest tradeoff. "Server cannot read the payload" is a strong privacy property, but authorship and abuse control need their own layer. I would rather see that boundary stated plainly than hidden behind a general "secure" claim.