Skip to content

triblespace/tribles-rust

Repository files navigation

Crates.io Version docs.rs Discord Shield

🚧🚧🚧 Please note that this is work in progress, so while a lot of things have settled by now, we still favour breaking backwards compatiblity for seeminly minor improvements. 🚧🚧🚧

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

About

“Insufficient facts always invite danger.”
Mr. Spock

Trible Space is a data space and knowledge graph standard. It offers metadata management capabilities similar to file- and version-control systems, combined with the queryability and convenience of an embedded database, tailored towards use with simple blob storage. It is designed to be a holistic yet lightweight data storage solution that can be used in a variety of contexts, from embedded systems to distributed cloud services.

Our goal is to re-invent data storage from first principles and overcome the shortcomings of prior "Semantic Web"/triple-store technologies. By focusing on simplicity, canonical data formats, cryptographic identifiers, and clean distributed semantics, we aim to provide a lean, lightweight yet powerful toolkit for knowledge representation, database management, and data exchange use cases.

Features

  • Lean, Lightweight & Flexible: Data storage seamlessly scales from in-memory data organization to large-scale blob and metadata storage on S3 like services.
  • Distributed: Eventually consistent CRDT semantics (based on the CALM principle), compressed zero-copy archives, and built-in version control.
  • Predictable Performance: An optimizer-free design using novel algorithms and data structures removes the need for manual query-tuning and enables single-digit microsecond latency.
  • Fast In-Memory Datasets: Enjoy cheap copy-on-write (COW) semantics and speedy set operations, allowing you to treat entire datasets as values.
  • Compile-Time Typed Queries: Automatic type inference, type-checking, and auto-completion make writing queries a breeze. You can even create queries that span multiple datasets and native Rust data structures.
  • Low Overall Complexity: We aim for a design that feels obvious (in the best way) and makes good use of existing language facilities. A serverless design makes it completely self-sufficient for local use and requires only an S3-compatible service for distribution.
  • Easy Implementation: The spec is designed to be friendly to high- and low-level languages, or even hardware implementations.
  • Lock-Free Blob Writes: Blob data is appended with a single O_APPEND write. Each handle advances an in-memory applied_length only if no other writer has appended in between, scanning any gap to ingest missing records. Concurrent writers may duplicate blobs, but hashes guarantee consistency. Updating branch heads uses a short flush → refresh → lock → refresh → append → unlock sequence.
  • Coordinated Refresh: refresh acquires a shared file lock while scanning to avoid races with restore truncating the pile.

Community

If you have any questions or want to chat about graph databases hop into our discord.

Getting Started

Add the crate to your project:

cargo add tribles

Then spin up a repository-backed workspace, mint the attributes your program needs, populate the repository with data, and query it—all in a single program.

use tribles::prelude::*;
use tribles::prelude::blobschemas::LongString;
use tribles::repo::{memoryrepo::MemoryRepo, Repository};
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;

mod literature {
    use tribles::prelude::*;
    use tribles::prelude::blobschemas::LongString;
    use tribles::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;
    }
}

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));
    let branch_id = repo
        .create_branch("main", None)
        .expect("create branch");
    let mut ws = repo.pull(*branch_id).expect("pull workspace");

    // Workspaces stage TribleSets before committing them. The entity! macro
    // returns sets that merge cheaply into our current working set.
    let author_id = ufoid();
    let mut library = TribleSet::new();

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

    library += entity! { &author_id @
        literature::title: "Dune",
        literature::author: &author_id,
        literature::quote: ws.put::<LongString, _>(
            "Deep in the human unconscious is a pervasive need for a logical \
             universe that makes sense. But the real universe is always one \
             step beyond logic."
        ),
        literature::quote: ws.put::<LongString, _>(
            "I must not fear. Fear is the mind-killer. Fear is the little-death \
             that brings total obliteration. I will face my fear. I will permit \
             it to pass over me and through me. And when it has gone past I will \
             turn the inner eye to see its path. Where the fear has gone there \
             will be nothing. Only I will remain."
        ),
    };

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

    // `checkout(..)` returns the accumulated TribleSet for the branch.
    let catalog = ws.checkout(..)?;
    let title = "Dune";

    // Use `_?ident` when you need a fresh variable scoped to this macro call
    // without declaring it in the find! projection list.
    for (f, l, quote) in find!(
        (first: String, last: Value<_>, quote),
        pattern!(&catalog, [
            { _?author @
                literature::firstname: ?first,
                literature::lastname: ?last
            },
            {
                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.from_value::<&str>());
    }

    // Use `try_push` for a single attempt that returns a conflict workspace on
    // CAS failure; use `push` to let the repository merge and retry
    // automatically.
    if let Some(mut conflict_ws) = repo
        .try_push(&mut ws)
        .expect("push staged commits")
    {
        // Resolve conflicts by merging the returned workspace as needed.
        ws.merge(&mut conflict_ws)
            .expect("merge conflicting history");
        repo.push(&mut ws).expect("finalize push after merge");
    }

    Ok(())
}

Repository::create_branch returns an ExclusiveId guard for the new branch. Dereference it (or call release) when passing the branch identifier to Repository::pull so additional workspaces can target the same branch.

The example inlines the tribles::examples::literature namespace with attributes! so the quick-start walkthrough and crate examples stay aligned while still compiling in standalone projects. Update the 32-character hex strings when you adapt the code to keep attribute identifiers globally unique.

You can also introduce temporary equality constraints inside a pattern without adding them to the surrounding find! bindings. Prefix a variable name with _? to allocate a scoped query variable that lives only for the duration of the macro expansion:

pattern!(&set, [{
    ?author @ literature::firstname: _?name,
    literature::lastname: _?name,
}]);

This binds both attributes to the same generated variable, ensuring the first and last names match without cluttering the outer query signature.

Tribles Book

For a step-by-step narrative guide, see the Tribles Book. To build the HTML locally, first install mdbook with cargo install mdbook and then run:

./scripts/build_book.sh

For details on setting up a development environment, see Developing Locally.

Learn More

The best way to get started is to read the Tribles Book. The following links mirror the book's chapter order so you can progress from the basics to more advanced topics:

  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

License

Licensed under either of

at your option.

About

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages