graphlink 1.0.0

A memory-safe, relational in-memory graph database.
Documentation
# GraphLink

GraphLink is a memory-safe, relational in-memory graph database library for Rust, inspired by the ergonomics of Ruby on Rails' ActiveRecord.
It lets you define complex data schemas with associations (like `has_many`, `belongs_to`, and `has_many :through`) and automatically handles memory allocation, relational integrity, and secondary indices.

## Install

```bash
cargo add graphlink
```

## Usage

GraphLink lets you link together existing structured data using in-memory, database-inspired references.
Use it as a layer on top of your existing data to easily link related data without pointers or reference counters.

To set up GraphLink, use the `define_schema!` macro to create a store struct containing all related data.
You'll need to update the model structs to match the relationships described in the schema definition, and that's it!

Here's an example:

```rust
use graphlink::{define_schema, BelongsTo, HasMany};

// Create the store struct and lots of helper methods.
define_schema! {
    // This becomes the generated store struct.
    store: Store;

    // Models are structs that are defined as regular structs with arbitrary data outside of the macro.
    // Relations must be declared here and also in the struct definition (see below).
    model Library {
        has_many Checkout;
        has_many Patron through Checkout;
    }

    model Patron {
        // Secondary indexes can be created based on struct fields.
        index unique email;
        has_many Checkout;
    }

    model Checkout {
        // BelongsTo relations can accept a deletion behavior.
        // Restrict (default) will refuse to delete the item if it still has live references.
        // Cascade will destroy linked references as well.
        belongs_to Library (on_delete = cascade);
        belongs_to Patron (on_delete = restrict);
    }
}

// Models are defined as regular structs
pub struct Library {
    // Model structs can contain arbitrary data.
    pub name: String,

    // HasMany and BelongsTo relationships need to be defined here as well as in define_schema!.
    // {Model}Id structs are defined automatically by define_schema!, and need to be referenced in the relation.
    pub checkouts: HasMany<CheckoutId>,
}

pub struct Patron {
    pub email: String,
    pub checkouts: HasMany<CheckoutId>,
}

// Model structs can have their own implementations, just like regular structs.
impl Patron {
    pub fn new(email: impl Into<String>) -> Self {
        Self {
            email: email.into(),
            checkouts: HasMany::new(),
        }
    }
}

pub struct Checkout {
    pub title: String,
    pub library: BelongsTo<LibraryId>,
    pub patron: BelongsTo<PatronId>,
}

fn main() {
    let mut store = Store::new();

    // Insert records and link them through strongly-typed IDs.
    let library_id = store.add_library(Library {
        name: "City Library".into(),
        checkouts: HasMany::new(),
    });
    let alice_id = store.add_patron(Patron::new("alice@example.com"));
    let bob_id = store.add_patron(Patron::new("bob@example.com"));

    store.add_checkout(Checkout {
        title: "Rust Atomics and Locks".into(),
        library: BelongsTo::new(library_id),
        patron: BelongsTo::new(alice_id),
    });
    store.add_checkout(Checkout {
        title: "Zero To Production".into(),
        library: BelongsTo::new(library_id),
        patron: BelongsTo::new(bob_id),
    });

    // Traverse a has_many :through association.
    if let Some(library) = store.library(library_id) {
        let emails: Vec<&str> = library
            .patrons()
            .map(|patron| patron.data.email.as_str())
            .collect();
        println!("Patrons with checkouts: {:?}", emails);
    }

    // Use a secondary index for lookups.
    if let Some(alice) = store.get_patron_by_email("alice@example.com") {
        println!("Found patron: {}", alice.data.email);
    }

    // Updates keep the secondary index in sync.
    let _ = store.update_patron(alice_id, |patron| {
        patron.email = "alicia@example.com".into();
    });

    // Safely remove records and clean up reverse links.
    let _ = store.remove_library(library_id);
}
```