This guide walks you through creating your first Sentinel database in just a few minutes. By the end, you’ll understand how to create stores, manage collections, work with documents, and use advanced features like querying and verification.
Your First Store
A Store is the top-level container for all your data. It maps to a directory on your filesystem:
use sentinel_dbms::Store;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a store at ./my-database
// The directory will be created if it doesn't exist
let store = Store::new("./my-database", None).await?;
println!("Store created at ./my-database");
Ok(())
}The second parameter (None) indicates we’re not using a passphrase for signing. We’ll cover signed documents later. After running this code, you’ll see a new directory:
my-database/The store is now ready to use. Let’s add some collections and documents.
Creating a Collection
Collections are namespaces for related documents. They’re represented as subdirectories within your store:
use sentinel_dbms::Store;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
// Get or create a "users" collection
let users = store.collection("users").await?;
println!("Collection 'users' ready!");
Ok(())
}Your filesystem now looks like:
my-database/
└── data/
└── users/The collection is ready for storing documents.
Inserting Documents
Documents are JSON objects stored as individual files. Use insert to create a new document:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Insert a document with ID "alice"
users.insert("alice", json!({
"name": "Alice Johnson",
"email": "[email protected]",
"role": "admin",
"department": "Engineering"
})).await?;
println!("Document 'alice' created!");
Ok(())
}This creates a file at my-database/data/users/alice.json containing:
{
"id": "alice",
"version": 1,
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"hash": "a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789a",
"signature": "",
"data": {
"name": "Alice Johnson",
"email": "[email protected]",
"role": "admin",
"department": "Engineering"
}
}Notice how Sentinel automatically adds metadata: document ID, version number, timestamps, and a BLAKE3 hash for integrity verification.
Retrieving Documents
Use get to retrieve a document by its ID:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Retrieve the document
if let Some(doc) = users.get("alice").await? {
println!("Found user: {}", doc.data()["name"]);
println!("Email: {}", doc.data()["email"]);
println!("Role: {}", doc.data()["role"]);
println!("Created at: {}", doc.created_at());
println!("Hash: {}", doc.hash());
} else {
println!("User not found");
}
Ok(())
}The get method returns Option<Document>. If the document doesn’t exist, you get None instead of an error.
Updating Documents
The update method replaces a document’s contents while preserving the file location:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Update Alice's information
users.update("alice", json!({
"name": "Alice Johnson",
"email": "[email protected]", // New email
"role": "senior_admin", // Promoted!
"department": "Engineering"
})).await?;
println!("Document updated!");
Ok(())
}The updated document gets a new timestamp and hash, allowing you to track when changes occurred.
Deleting Documents
Use delete to remove a document:
use sentinel_dbms::Store;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Delete the document
users.delete("alice").await?;
println!("Document deleted!");
Ok(())
}The delete operation is idempotent, deleting a non-existent document succeeds without error. Documents are moved to a .deleted/ subdirectory for recovery purposes.
Bulk Inserting Documents
For inserting multiple documents efficiently, use the bulk_insert method:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Prepare multiple documents
let documents = vec![
("bob", json!({"name": "Bob", "role": "user"})),
("charlie", json!({"name": "Charlie", "role": "user"})),
("david", json!({"name": "David", "role": "admin"})),
];
// Insert all at once
users.bulk_insert(documents).await?;
println!("{} documents inserted", documents.len());
Ok(())
}Listing Documents
List all documents in a collection using the list or count methods:
use sentinel_dbms::Store;
use futures::TryStreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Count documents
let count = users.count().await?;
println!("Total documents: {}", count);
// List all document IDs
let ids: Vec<_> = users.list().try_collect().await?;
println!("Document IDs:");
for id in ids {
println!(" - {}", id);
}
Ok(())
}Adding Signatures
For tamper-evident storage, create a store with a passphrase. This generates a signing key that will be used to sign all documents:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create store with passphrase for signing
let store = Store::new("./secure-database", Some("my-secret-passphrase")).await?;
let users = store.collection("users").await?;
// Insert a document (will be signed automatically)
users.insert("alice", json!({
"name": "Alice",
"email": "[email protected]"
})).await?;
// Retrieve and check signature
if let Some(doc) = users.get("alice").await? {
println!("Document ID: {}", doc.id());
println!("Signature: {}", doc.signature());
// Will show: "ed25519:..." (not empty)
}
Ok(())
}When you create a store with a passphrase:
- Sentinel generates a random 32-byte salt
- An Ed25519 signing key is derived using the configured key derivation function (Argon2id by default)
- The signing key is encrypted using the configured encryption algorithm (XChaCha20-Poly1305 by default)
- The encrypted key and salt are stored in
.keys/signing_key.json
On subsequent opens, you must provide the same passphrase to recover the signing key.
Querying Documents
Find documents matching specific criteria using the QueryBuilder API:
use sentinel_dbms::{Store, QueryBuilder, Operator, SortOrder};
use serde_json::json;
use futures::TryStreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Insert some test data
users.insert("user1", json!({"name": "Alice", "age": 30})).await?;
users.insert("user2", json!({"name": "Bob", "age": 25})).await?;
users.insert("user3", json!({"name": "Charlie", "age": 35})).await?;
// Find users older than 25
let query = QueryBuilder::new()
.filter("age", Operator::GreaterThan, json!(25))
.sort("age", SortOrder::Ascending)
.build();
let result = users.query(query).await?;
let documents: Vec<_> = result.documents.try_collect().await?;
println!("Found {} users older than 25:", documents.len());
for doc in documents {
println!(" - {}, age: {}", doc.data()["name"], doc.data()["age"]);
}
Ok(())
}Streaming All Documents
Process all documents without loading them all into memory:
use sentinel_dbms::Store;
use futures::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Stream all documents
let mut all_docs = users.all();
let mut count = 0;
while let Some(doc_result) = all_docs.next().await {
match doc_result {
Ok(doc) => {
println!("Document: {}", doc.id());
count += 1;
}
Err(e) => {
eprintln!("Error reading document: {}", e);
}
}
}
println!("Total documents processed: {}", count);
Ok(())
}Using Filters with Closures
For simple filtering, use closures with the filter method:
use sentinel_dbms::Store;
use futures::StreamExt;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
let users = store.collection("users").await?;
// Insert test data
users.insert("user1", json!({"name": "Alice", "role": "admin"})).await?;
users.insert("user2", json!({"name": "Bob", "role": "user"})).await?;
users.insert("user3", json!({"name": "Charlie", "role": "admin"})).await?;
// Find all admins
let mut admins = users.filter(|doc| {
doc.data().get("role")
.and_then(|v| v.as_str())
.map_or(false, |role| role == "admin")
});
while let Some(doc) = admins.next().await {
let doc = doc?;
println!("Admin: {}", doc.data()["name"]);
}
Ok(())
}Managing Multiple Collections
A store can contain multiple collections:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
// Create multiple collections
let users = store.collection("users").await?;
let products = store.collection("products").await?;
let orders = store.collection("orders").await?;
// Work with each collection
users.insert("alice", json!({"name": "Alice"})).await?;
products.insert("widget", json!({"name": "Widget", "price": 9.99})).await?;
orders.insert("order-123", json!({"user_id": "alice", "total": 29.97})).await?;
// List all collections in the store
let collections = store.list_collections().await?;
println!("Collections: {:?}", collections);
Ok(())
}Deleting Collections
Remove an entire collection and all its documents:
use sentinel_dbms::Store;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Store::new("./my-database", None).await?;
// Create a temporary collection
let temp = store.collection("temp").await?;
temp.insert("doc1", json!({})).await?;
// Delete the entire collection
store.delete_collection("temp").await?;
println!("Collection deleted!");
Ok(())
}What’s Next?
Congratulations! You’ve completed the quick start guide. Now that you understand the basics, explore:
- Store: Advanced store features like listing and deleting collections
- Collection: Bulk operations, streaming, and verification options
- Document: Understanding document structure and metadata
- Cryptography: Configuring algorithms and verification modes
- Querying and Filtering: Advanced query building and operators
- Error Handling: Understanding and handling errors properly