triblespace 0.31.0

The Triblespace: A lightweight knowledge base for rust.
Documentation

Crates.io Version docs.rs Discord Shield

The mascot of trible.space, a cute fluffy trible with three eyes.

About

“We need to abolish names and places, and replace them with hashes.” — Joe Armstrong, The Mess We’re In

TribleSpace is an embedded knowledge graph with built-in version control. It combines the queryability of a database with the distributed semantics of a content-addressed storage system — all in a single append-only file or S3-compatible endpoint.

Designed from first principles to overcome the shortcomings of prior triple-store technologies, TribleSpace focuses on simplicity, cryptographic identifiers, and clean CRDT semantics to provide a lightweight yet powerful toolkit for knowledge representation, data management, and data exchange.

Features

  • Scales from memory to cloud: In-memory datasets, local pile files, and S3-compatible blob storage all use the same API.
  • Distributed by default: Eventually consistent CRDT semantics (based on the CALM principle), compressed zero-copy archives, and built-in version control with branch/merge workflows.
  • Predictable performance: An optimizer-free query engine using novel algorithms and data structures removes the need for manual query-tuning and delivers single-digit microsecond latency.
  • Datasets as values: Cheap copy-on-write (COW) semantics and fast set operations let you treat entire datasets as ordinary values — diff, merge, and compose them freely.
  • Compile-time typed queries: Automatic type inference, type-checking, and auto-completion make writing queries a breeze. Queries can span multiple datasets and native Rust data structures.
  • Serverless: No background process needed. A single pile file is completely self-sufficient for local use; add an S3-compatible service when you need distribution.

Getting Started

Add the crate to your project:

cargo add triblespace

Once the crate is installed, you can experiment immediately with the quick-start program below. It showcases the attribute macros, workspace staging, queries, and pushing commits to a repository.

use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
use triblespace::prelude::*;

mod literature {
    use triblespace::prelude::*;
    use triblespace::prelude::blobschemas::LongString;
    use triblespace::prelude::valueschemas::{Blake3, GenId, Handle, R256, ShortString};

    attributes! {
        /// The title of a work.
        ///
        /// Small doc paragraph used in the book examples.
        "A74AA63539354CDA47F387A4C3A8D54C" as pub title: ShortString;

        /// A quote from a work.
        "6A03BAF6CFB822F04DA164ADAAEB53F6" as pub quote: Handle<Blake3, LongString>;

        /// The author of a work.
        "8F180883F9FD5F787E9E0AF0DF5866B9" as pub author: GenId;

        /// The first name of an author.
        "0DBB530B37B966D137C50B943700EDB2" as pub firstname: ShortString;

        /// The last name of an author.
        "6BAA463FD4EAF45F6A103DB9433E4545" as pub lastname: ShortString;

        /// The number of pages in the work.
        "FCCE870BECA333D059D5CD68C43B98F0" as pub page_count: R256;

        /// A pen name or alternate spelling for an author.
        "D2D1B857AC92CEAA45C0737147CA417E" as pub alias: ShortString;

        /// A throwaway prototype field; omit the id to derive it from the name and schema.
        pub prototype_note: Handle<Blake3, LongString>;
    }
}

// The examples pin explicit ids for shared schemas. For quick prototypes you
// can omit the hex literal and `attributes!` will derive a deterministic id
// from the attribute name and schema (via Attribute::from_name).

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Repositories manage shared history; MemoryRepo keeps everything in-memory
    // for quick experiments. Swap in a `Pile` when you need durable storage.
    let storage = MemoryRepo::default();
    let mut repo = Repository::new(storage, SigningKey::generate(&mut OsRng), TribleSet::new())?;
    let branch_id = repo
        .create_branch("main", None)
        .expect("create branch");
    let mut ws = repo.pull(*branch_id).expect("pull workspace");

    // The entity! macro returns a rooted fragment; merge its facts into
    // a TribleSet via `+=`.
    let herbert = ufoid();
    let dune = ufoid();
    let mut library = TribleSet::new();

    library += entity! { &herbert @
        literature::firstname: "Frank",
        literature::lastname: "Herbert",
    };

    library += entity! { &dune @
        literature::title: "Dune",
        literature::author: &herbert,
        literature::quote: ws.put(
            "I must not fear. Fear is the mind-killer."
        ),
    };

    ws.commit(library, "import dune");

    // `checkout(..)` returns a Checkout — a TribleSet paired with the
    // commits that produced it, usable for incremental delta queries.
    let catalog = ws.checkout(..)?;
    let title = "Dune";

    // Multi-entity join: find quotes by authors of a given title.
    // `_?author` is a pattern-local variable that joins without projecting.
    for (f, l, quote) in find!(
        (first: String, last: String, quote),
        pattern!(&catalog, [
            { _?author @
                literature::firstname: ?first,
                literature::lastname: ?last
            },
            { _?book @
                literature::title: title,
                literature::author: _?author,
                literature::quote: ?quote
            }
        ])
    ) {
        let quote: View<str> = ws.get(quote)?;
        let quote = quote.as_ref();
        println!("'{quote}'\n - from {title} by {f} {l}.");
    }

    repo.push(&mut ws).expect("publish initial library");

    // ── Conflict resolution ────────────────────────────────────────
    // We rename the author; a collaborator independently records a
    // different name. try_push detects the conflict.

    ws.commit(
        entity! { &herbert @ literature::firstname: "Francis" },
        "use pen name",
    );

    let mut collaborator = repo.pull(*branch_id).expect("pull");
    collaborator.commit(
        entity! { &herbert @ literature::firstname: "Franklin" },
        "record legal first name",
    );
    repo.push(&mut collaborator).expect("publish collaborator");

    // try_push fails because the branch advanced. The returned
    // workspace carries the collaborator's history.
    if let Some(mut conflict_ws) = repo
        .try_push(&mut ws)
        .expect("attempt push")
    {
        // Inspect what the collaborator wrote.
        let their_catalog = conflict_ws.checkout(..)?;
        for first in find!(
            first: String,
            pattern!(&their_catalog, [{ &herbert @ literature::firstname: ?first }])
        ) {
            println!("Collaborator recorded: '{first}'.");
        }

        // Accept their history — abandon our conflicting firstname
        // commit and continue from the collaborator's state instead.
        ws = conflict_ws;

        // Record our preferred name as an alias rather than overwriting.
        ws.commit(
            entity! { &herbert @ literature::alias: "Francis" },
            "keep pen-name as alias",
        );

        repo.push(&mut ws).expect("publish resolution");
    }

    Ok(())
}

The Getting Started chapter of the book breaks this example down line by line, covers project scaffolding, and introduces more background on how repositories, workspaces, and queries interact.

Learn More

The Tribles Book is the best place to go deeper:

  1. Introduction
  2. Getting Started
  3. Architecture
  4. Query Language
  5. Incremental Queries
  6. Schemas
  7. Repository Workflows
  8. Commit Selectors
  9. Philosophy
  10. Identifiers
  11. Trible Structure
  12. Pile Format

To build the book locally: cargo install mdbook && ./scripts/build_book.sh

For development setup, see Contributing.

Community

Questions or ideas? Join the Discord.

License

Licensed under either of

at your option.