Skip to content

dierbei/xaeroflux

 
 

Repository files navigation

XaeroFlux

A decentralized peer-to-peer event log that syncs across devices using Iroh and SQLite.

What It Does

  • Send events from your app
  • Receive events synced from other peers
  • Store events in SQLite
  • Sync events over gossip (no central server)

Why Use It

  • Single discovery key controls who syncs with you
  • Events are deduplicated automatically
  • Works offline - syncs when connected
  • No configuration - just a discovery key and database path
  • 280 lines of Rust

Installation

[dependencies]
xaeroflux = { git = "https://github.com/block-xaero/xaeroflux" }
tokio = { version = "1", features = ["full"] }

Basic Usage

use xaeroflux::{XaeroFlux, Event, generate_event_id};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut xf = XaeroFlux::new(
        "my-app-key".to_string(),
        "events.db".to_string()
    ).await?;

    // Send an event
    let now = chrono::Utc::now().timestamp() as u64;
    xf.event_tx.send(Event {
        id: generate_event_id("hello world", &xf.node_id, now),
        payload: "hello world".to_string(),
        source: xf.node_id.clone(),
        ts: now,
    })?;

    // Receive synced events from other peers
    while let Some(event) = xf.event_rx.recv().await {
        println!("Received: {} from {}", event.payload, event.source);
    }

    Ok(())
}

Event Structure

pub struct Event {
    pub id: String,      // Unique ID (blake3 hash)
    pub payload: String, // Your data (any string)
    pub source: String,  // Node ID that created event
    pub ts: u64,        // Unix timestamp
}

How It Works

  1. You send an event via event_tx
  2. XaeroFlux stores it in SQLite
  3. XaeroFlux broadcasts it on gossip topic xsp-1.0/{discovery_key}/events
  4. Other peers with same discovery key receive and store it
  5. You receive synced events via event_rx

Events from your own node are not sent back to event_rx (you already have them).

Discovery Keys

The discovery key determines which peers sync with each other:

// These sync together (same discovery key)
let peer1 = XaeroFlux::new("project-alpha".to_string(), "db1.db".to_string()).await?;
let peer2 = XaeroFlux::new("project-alpha".to_string(), "db2.db".to_string()).await?;

// This syncs separately (different discovery key)
let peer3 = XaeroFlux::new("project-beta".to_string(), "db3.db".to_string()).await?;

CLI Demo

Build

cargo build --release --bin xaeroflux-cli

Terminal 1 - Sender

./target/release/xaeroflux-cli \
  --discovery-key demo \
  --db peer1.db \
  send "Hello from peer 1"

Terminal 2 - Receiver

./target/release/xaeroflux-cli \
  --discovery-key demo \
  --db peer2.db \
  recv

The receiver will print events sent by the first peer (and any other peers on the same discovery key).

Send Multiple Events

# Terminal 1
./target/release/xaeroflux-cli -k demo -d peer1.db send "Event 1"
./target/release/xaeroflux-cli -k demo -d peer1.db send "Event 2"
./target/release/xaeroflux-cli -k demo -d peer1.db send "Event 3"

Interactive Mode

./target/release/xaeroflux-cli -k demo -d peer1.db interactive

Type messages and press Enter. Type quit to exit.

Example: Building a Chat App

use xaeroflux::{XaeroFlux, Event, generate_event_id};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct ChatMessage {
    user: String,
    message: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut xf = XaeroFlux::new("my-chat".to_string(), "chat.db".to_string()).await?;
    let node_id = xf.node_id.clone();

    // Send chat messages
    let tx = xf.event_tx.clone();
    tokio::spawn(async move {
        let stdin = tokio::io::stdin();
        let mut lines = tokio::io::BufReader::new(stdin).lines();
        
        while let Ok(Some(line)) = lines.next_line().await {
            let msg = ChatMessage {
                user: node_id[..8].to_string(),
                message: line,
            };
            let payload = serde_json::to_string(&msg).unwrap();
            let now = chrono::Utc::now().timestamp() as u64;
            
            tx.send(Event {
                id: generate_event_id(&payload, &node_id, now),
                payload,
                source: node_id.clone(),
                ts: now,
            }).unwrap();
        }
    });

    // Receive and display messages
    while let Some(event) = xf.event_rx.recv().await {
        let msg: ChatMessage = serde_json::from_str(&event.payload)?;
        println!("[{}] {}", msg.user, msg.message);
    }

    Ok(())
}

Limitations

  • No snapshot sync - only events broadcast after joining
  • No compaction - events stored forever
  • No authentication - discovery key is only access control -- See XaeroID for this.
  • No ordering guarantees beyond timestamps

Use Cases

  • Collaborative apps (docs, whiteboards, spreadsheets)
  • Chat applications
  • Activity feeds
  • Multiplayer game state sync
  • IoT sensor data collection
  • Distributed logging

Protocol

Topic: xsp-1.0/{discovery_key}/events
Transport: Iroh gossip
Format: JSON-serialized Event struct

License

MPL v2.0

About

An extremely simple decentralized event store that syncs SQLLite stored events via iroh

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 85.1%
  • Makefile 14.9%