| Crates.io | gun-rs |
| lib.rs | gun-rs |
| version | 1.0.4 |
| created_at | 2025-12-25 12:03:42.273324+00 |
| updated_at | 2025-12-26 23:46:17.100752+00 |
| description | A realtime, decentralized, offline-first, graph data synchronization engine (Rust port) |
| homepage | |
| repository | https://github.com/amark/gun |
| max_upload_size | |
| id | 2004470 |
| size | 1,399,381 |
A Rust port of Gun.js - a realtime, decentralized, offline-first, graph data synchronization engine.
This is an unofficial, vibe-coded port of Gun.js to Rust. This project is:
This port was created for experimentation and learning purposes. If you need a production-ready solution, please use the official Gun.js implementation.
Gun.rs stores data as a directed graph where:
// Example graph structure:
// {
// "user:alice": {
// "name": "Alice",
// "age": 30,
// "friend": "user:bob" // Reference to another node
// },
// "user:bob": {
// "name": "Bob",
// "age": 28
// }
// }
The Chain API provides a fluent interface for navigating and manipulating the graph:
// Chain methods are chained together
gun.get("user").get("alice").get("name").put("Alice");
// ^^^^ ^^^^ ^^^^ ^^^^
// Chain Chain Chain Chain
Each method returns a new Chain instance, allowing method chaining. Chains maintain context about their position in the graph hierarchy.
Souls are deterministic, unique identifiers for nodes. They are:
// Souls look like: "abc123def456..."
// They're used to reference nodes across the network
Gun.rs uses Conflict-free Replicated Data Types (CRDTs) with state-based conflict resolution:
The DAM protocol is Gun's custom P2P networking layer:
Gun.rs is designed for offline-first operation:
Multiple storage backends are available:
Gun.rs uses BLS (Boneh-Lynn-Shacham) signatures for cryptographic security:
Add to your Cargo.toml:
[dependencies]
gun = { git = "https://github.com/DIG-Network/gun.rs" }
chia_bls = "12.2"
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
Note: Gun.rs requires BLS (Boneh-Lynn-Shacham) key pairs for cryptographic security. All messages are signed and verified using BLS signatures.
use gun::Gun;
use chia_bls::{SecretKey, PublicKey};
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
// Simple instance (no networking, in-memory storage)
let gun = Gun::new(secret_key, public_key);
// With options (networking, storage, etc.)
use gun::GunOptions;
let secret_key = SecretKey::from_seed(&[1u8; 32]);
let public_key = secret_key.public_key();
let gun = Gun::with_options(secret_key, public_key, GunOptions {
peers: vec!["ws://relay.example.com/gun".to_string()],
localStorage: true,
storage_path: Some("./gun_data".to_string()),
..Default::default()
}).await?;
use gun::Gun;
use chia_bls::{SecretKey, PublicKey};
use serde_json::json;
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
let gun = Gun::new(secret_key, public_key);
// Write data
gun.get("user").get("alice").put(json!("Alice")).await?;
// Read data once
gun.get("user").get("alice").once(|data, key| {
println!("User: {:?}", data);
}).await?;
// Subscribe to updates
gun.get("user").get("alice").on(|data, key| {
println!("Updated: {:?}", data);
});
use serde_json::json;
// Put a complete object
gun.get("user").get("alice").put(json!({
"name": "Alice",
"age": 30,
"email": "alice@example.com"
})).await?;
// Update specific fields
gun.get("user").get("alice").get("age").put(json!(31)).await?;
// Create two users
gun.get("user").get("alice").put(json!({
"name": "Alice"
})).await?;
gun.get("user").get("bob").put(json!({
"name": "Bob"
})).await?;
// Create a relationship (reference)
// Note: In practice, you'd get the soul from the created node
// This is a simplified example
let alice_soul = "..."; // Soul from Alice node
gun.get("user").get("bob").get("friend").put(json!(alice_soul)).await?;
// Map over a collection
gun.get("users").map(|data, key| {
if let Some(name) = data.get("name").and_then(|v| v.as_str()) {
println!("User: {}", name);
}
});
// Add items to a collection
for i in 1..=10 {
gun.get("users").get(&format!("user_{}", i)).put(json!({
"id": i,
"name": format!("User {}", i)
})).await?;
}
// Remove all listeners from a chain
gun.get("user").get("alice").off();
// Note: off() is chain-aware and removes listeners from the current chain point
// Navigate back up the chain
let chain = gun.get("user").get("alice").get("name");
let parent = chain.back(Some(1)); // Go back 1 level -> "alice" chain
let root = chain.back(None); // Go back to root -> gun root
use gun::{Gun, GunOptions};
use chia_bls::{SecretKey, PublicKey};
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
// Single relay
let gun = Gun::with_options(
secret_key.clone(),
public_key.clone(),
GunOptions::with_relay("ws://relay.example.com/gun")
).await?;
// Multiple relays for redundancy
let secret_key2 = SecretKey::from_seed(&[1u8; 32]);
let public_key2 = secret_key2.public_key();
let gun = Gun::with_options(
secret_key2,
public_key2,
GunOptions::with_peers(vec![
"ws://relay1.example.com/gun".to_string(),
"ws://relay2.example.com/gun".to_string(),
])
).await?;
use gun::{Gun, GunOptions};
use chia_bls::{SecretKey, PublicKey};
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
// Start a relay server on port 8765
let gun = Gun::with_options(
secret_key,
public_key,
GunOptions::relay_server(8765)
).await?;
// Server will accept connections from other peers
use gun::{Gun, GunOptions};
use gun::webrtc::WebRTCOptions;
use chia_bls::{SecretKey, PublicKey};
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
let mut webrtc_opts = WebRTCOptions::default();
webrtc_opts.enabled = true;
webrtc_opts.max_connections = 10;
let mut opts = GunOptions::default();
opts.peers = vec!["ws://relay.example.com/gun".to_string()];
opts.webrtc = webrtc_opts;
let gun = Gun::with_options(secret_key, public_key, opts).await?;
use gun::{Gun, GunOptions};
use chia_bls::{SecretKey, PublicKey};
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
let opts = GunOptions {
localStorage: true,
storage_path: Some("./gun_data".to_string()),
..Default::default()
};
let gun = Gun::with_options(secret_key, public_key, opts).await?;
// Data will be persisted to ./gun_data/
use gun::{Gun, GunOptions};
use chia_bls::{SecretKey, PublicKey};
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
let opts = GunOptions {
localStorage: true,
radisk: true,
storage_path: Some("./gun_data".to_string()),
..Default::default()
};
let gun = Gun::with_options(secret_key, public_key, opts).await?;
// Uses high-performance sled database
// Check connection status
let is_connected = gun.is_connected().await;
let peer_count = gun.connected_peer_count().await;
// Wait for connection with timeout
let connected = gun.wait_for_connection(5000).await; // 5 second timeout
// Graceful shutdown
gun.shutdown().await?;
use gun::GunError;
match gun.get("key").put(data).await {
Ok(chain) => {
// Success
}
Err(GunError::InvalidData(msg)) => {
eprintln!("Invalid data: {}", msg);
}
Err(e) => {
eprintln!("Error: {}", e);
}
}
GunThe main entry point for the Gun.rs library.
Methods:
new(secret_key: SecretKey, public_key: PublicKey) -> Gun
with_options(secret_key: SecretKey, public_key: PublicKey, options: GunOptions) -> GunResult<Gun>
get(key: &str) -> Arc<Chain>
root() -> Arc<Chain>
state() -> f64
connected_peer_count() -> usize
is_connected() -> bool
wait_for_connection(timeout_ms: u64) -> bool
shutdown() -> GunResult<()>
ChainThe fluent API for interacting with the graph. All chain methods return Arc<Chain> for method chaining.
Methods:
get(key: &str) -> Arc<Chain>
put(data: Value) -> GunResult<Arc<Chain>>
serde_json::Value (numbers, strings, booleans, objects, arrays)on<F>(callback: F) -> Arc<Chain>
Fn(Value, Option<String>)once<F>(callback: F) -> GunResult<Arc<Chain>>
Fn(Value, Option<String>)map<F>(callback: F) -> Arc<Chain>
Fn(Value, Option<String>)set(item: Value) -> GunResult<Arc<Chain>>
back(amount: Option<usize>) -> Option<Arc<Chain>>
amount: Some(n) - go back n levelsamount: None - go back to rootNone if cannot go back that faroff() -> Arc<Chain>
GunOptionsConfiguration options for creating a Gun instance.
Fields:
peers: Vec<String>
localStorage: bool
falsestorage_path: Option<String>
None (uses "./gun_data" if localStorage is true)radisk: bool
falsesuper_peer: bool
falseport: Option<u16>
Nonewebrtc: WebRTCOptions
WebRTCOptions below)WebRTCOptions::default()message_predicate: Option<MessagePredicate>
true to accept, false to rejectNone (all verified messages are accepted)Methods:
default() -> GunOptions
with_relay(relay_url: &str) -> GunOptions
with_peers(peers: Vec<String>) -> GunOptions
relay_server(port: u16) -> GunOptions
WebRTCOptionsConfiguration for WebRTC peer-to-peer connections.
Fields:
ice_servers: Vec<RTCIceServer>
data_channel: RTCDataChannelInit
max_connections: usize
55room: Option<String>
Noneenabled: bool
trueMethods:
default() -> WebRTCOptions
GunErrorError type for Gun.rs operations.
Variants:
GunError::InvalidData(String)
GunError::Storage(sled::Error)
GunError::Serialization(serde_json::Error)
GunError::Network(String)
GunError::InvalidSoul(String)
GunError::NodeNotFound
GunError::Io(std::io::Error)
GunError::UrlParseError(url::ParseError)
GunError::WebRTC(String)
GunError::Crypto(String)
gun::chainChain - Main chain API typegun::coreGunCore - Core graph database engine (internal)gun::damMesh - DAM protocol mesh networking (internal)gun::errorGunError - Error typesGunResult<T> - Result type alias: Result<T, GunError>gun::graphgun::storageStorage - Storage traitMemoryStorage - In-memory storageLocalStorage - File-based storageSledStorage - Sled database storagegun::webrtcWebRTCOptions - WebRTC configurationWebRTCManager - WebRTC manager (internal)WebRTCPeer - WebRTC peer connection (internal)gun::websocketgun::seagun::typesMessagePredicate - Message filtering predicate type for custom message filteringGunResult<T> = Result<T, GunError>MessagePredicate = Arc<dyn Fn(&serde_json::Value) -> bool + Send + Sync>
None currently exported.
use gun::Gun;
use chia_bls::{SecretKey, PublicKey};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
let gun = Gun::new(secret_key, public_key);
// Write data
gun.get("user").get("alice").put(json!("Alice")).await?;
// Read data
gun.get("user").get("alice").once(|data, _key| {
println!("User: {:?}", data);
}).await?;
Ok(())
}
use gun::{Gun, GunOptions};
use chia_bls::{SecretKey, PublicKey};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate BLS key pair
let secret_key = SecretKey::from_seed(&[0u8; 32]);
let public_key = secret_key.public_key();
let gun = Gun::with_options(
secret_key,
public_key,
GunOptions::with_relay("ws://relay.example.com/gun")
).await?;
// Subscribe to updates
gun.get("chat").get("messages").on(|data, key| {
if let Some(text) = data.get("text").and_then(|v| v.as_str()) {
println!("New message: {}", text);
}
});
// Send a message
gun.get("chat").get("messages").set(json!({
"text": "Hello, world!",
"author": "Alice"
})).await?;
// Keep running
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
Ok(())
}
See examples/two_clients_webrtc.rs for a complete WebRTC example demonstrating:
See examples/two_clients.rs for a complete example demonstrating:
Run examples:
cargo run --example two_clients
cargo run --example two_clients_webrtc
See tests/ directory for comprehensive test suites:
Important: Gun.js uses a custom DAM (Directed Acyclic Mesh) protocol over WebSocket, NOT libp2p. For 1:1 behavioral compatibility, we use:
tokio-tungstenite for WebSocket transport (matches Gun.js)MIT OR Apache-2.0 OR Zlib (same as Gun.js)