this repo has no description
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

README.md

atproto-repo#

AT Protocol repository handling - CAR v1 serialization and Merkle Search Tree operations.

Features#

  • CAR v1 Support: Full Content Addressable aRchive (CAR) v1 format support

    • Async streaming reader and writer
    • Block-level verification
    • Configurable memory limits
  • Merkle Search Tree (MST): Deterministic tree structure for AT Protocol repositories

    • Insert, get, delete, and list operations
    • Tree diffing for sync operations
    • Height calculation using SHA-256
  • Storage Abstraction: Pluggable storage backends

    • In-memory storage with LRU eviction
    • Disk-backed storage with memory caching
    • Spillable buffer for streaming large files
  • Repository Structures: AT Protocol commit and record types

    • Commit serialization and validation
    • Record path handling (collection/rkey)

Installation#

Add to your Cargo.toml:

[dependencies]
atproto-repo = "0.13"

Usage#

Reading a CAR file#

use atproto_repo::car::CarReader;
use tokio::fs::File;

async fn read_car() -> anyhow::Result<()> {
    let file = File::open("repository.car").await?;
    let mut reader = CarReader::new(file).await?;

    println!("Roots: {:?}", reader.roots());

    while let Some(block) = reader.next_block().await? {
        println!("CID: {}", block.cid);
        println!("Data size: {} bytes", block.data.len());
    }

    Ok(())
}

Writing a CAR file#

use atproto_repo::car::{CarWriter, CarBlock};
use tokio::fs::File;

async fn write_car() -> anyhow::Result<()> {
    let file = File::create("output.car").await?;

    let data = b"hello world".to_vec();
    let block = CarBlock::from_data(data);
    let root = block.cid;

    let mut writer = CarWriter::new(file, vec![root.into()]).await?;
    writer.write_block(&block).await?;
    writer.finish().await?;

    Ok(())
}

Working with MST#

use atproto_repo::mst::Mst;
use atproto_repo::storage::MemoryStorage;
use atproto_repo::compute_cid;

async fn mst_operations() -> anyhow::Result<()> {
    let storage = MemoryStorage::new();
    let mut mst = Mst::new(storage);

    // Insert records
    let record_cid = compute_cid(b"record data").into();
    mst.insert("app.bsky.feed.post/abc123", record_cid).await?;

    // Lookup
    let cid = mst.get("app.bsky.feed.post/abc123").await?;
    println!("Found: {:?}", cid);

    // List all entries
    let entries = mst.entries().await?;
    for (key, cid) in entries {
        println!("{} -> {}", key, cid);
    }

    Ok(())
}

Spillable Buffer for Large Files#

use atproto_repo::storage::SpillableBuffer;

async fn stream_large_file() -> std::io::Result<()> {
    // Memory threshold: 1MB
    let mut buffer = SpillableBuffer::new(1024 * 1024);

    // Write data - spills to disk if exceeds threshold
    buffer.write_chunk(b"some data").await?;
    buffer.write_chunk(b"more data").await?;

    println!("On disk: {}", buffer.is_on_disk());

    // Convert to reader
    let reader = buffer.into_reader().await?;
    Ok(())
}

CLI Tools#

Two CLI tools are provided (requires clap feature):

atproto-repo-car#

Inspect and manipulate CAR files:

# List blocks
cargo run --features clap --bin atproto-repo-car -- ls repo.car

# Show roots
cargo run --features clap --bin atproto-repo-car -- roots repo.car

# Verify integrity
cargo run --features clap --bin atproto-repo-car -- verify repo.car

# Show statistics
cargo run --features clap --bin atproto-repo-car -- stats repo.car

atproto-repo-mst#

Inspect MST structures in repositories:

# List all keys
cargo run --features clap --bin atproto-repo-mst -- ls repo.car

# Get specific record
cargo run --features clap --bin atproto-repo-mst -- get repo.car app.bsky.feed.post/abc123

# Show tree structure
cargo run --features clap --bin atproto-repo-mst -- tree repo.car

# List collections
cargo run --features clap --bin atproto-repo-mst -- collections repo.car

Configuration#

use atproto_repo::config::{RepoConfig, LimitsConfig};

// Default configuration
let config = RepoConfig::default();

// No verification (faster but less safe)
let config = RepoConfig::no_verification();

// Custom limits
let config = RepoConfig::builder()
    .verify_cids(true)
    .limits(LimitsConfig::low_memory())
    .build();

Error Handling#

All error types follow the project convention:

error-atproto-repo-<domain>-<number> <message>: <details>

Example:

  • error-atproto-repo-car-3 CID mismatch: expected bafy..., got bafy...
  • error-atproto-repo-mst-5 Node not found: bafy...

License#

MIT OR Apache-2.0