first commit

This commit is contained in:
Timur Gordon
2025-10-20 22:24:25 +02:00
commit 097360ad12
48 changed files with 6712 additions and 0 deletions

157
examples/README.md Normal file
View File

@@ -0,0 +1,157 @@
# OSIRIS Examples
This directory contains examples demonstrating various features of OSIRIS.
## Prerequisites
Before running the examples, make sure HeroDB is running:
```bash
cd /path/to/herodb
cargo run --release -- --dir ./data --admin-secret mysecret --port 6379
```
## Running Examples
### Basic Usage
Demonstrates core OSIRIS functionality with Notes and Events:
```bash
cargo run --example basic_usage
```
**What it shows:**
- Creating objects with the derive macro
- Storing objects in HeroDB
- Querying by indexed fields
- Retrieving objects by ID
- Auto-generated index keys
- Cleanup/deletion
### Custom Object
Shows how to create your own custom object types:
```bash
cargo run --example custom_object
```
**What it shows:**
- Defining custom structs with `#[derive(DeriveObject)]`
- Using enums in indexed fields
- Builder pattern for object construction
- Querying by various indexed fields
- Updating objects
- Tag-based organization
## Example Structure
Each example follows this pattern:
1. **Setup** - Connect to HeroDB
2. **Create** - Build objects using builder pattern
3. **Store** - Save objects to HeroDB
4. **Query** - Search by indexed fields
5. **Retrieve** - Get objects by ID
6. **Update** - Modify and re-store objects (where applicable)
7. **Cleanup** - Delete test data
## Key Concepts Demonstrated
### Derive Macro
All examples use the `#[derive(DeriveObject)]` macro:
```rust
#[derive(Debug, Clone, Serialize, Deserialize, DeriveObject)]
pub struct MyObject {
pub base_data: BaseData,
#[index]
pub indexed_field: String,
pub non_indexed_field: String,
}
```
### Indexed Fields
Fields marked with `#[index]` are automatically indexed:
- `Option<T>` - Indexed if Some
- `BTreeMap<String, String>` - Each key-value pair indexed
- `OffsetDateTime` - Indexed as date string
- Enums - Indexed using Debug format
- Other types - Indexed using Debug format
### Querying
Query by any indexed field:
```rust
// Query by exact match
let ids = store.get_ids_by_index("namespace", "field_name", "value").await?;
// Query tags (BTreeMap fields)
let ids = store.get_ids_by_index("namespace", "tags:tag", "key=value").await?;
```
### Builder Pattern
All objects support fluent builder pattern:
```rust
let obj = MyObject::new("namespace".to_string())
.set_field1("value1")
.set_field2("value2")
.add_tag("key", "value");
```
## Troubleshooting
### Connection Refused
Make sure HeroDB is running on port 6379:
```bash
redis-cli -p 6379 PING
```
### Database Not Found
The examples use different database IDs:
- `basic_usage` - DB 1
- `custom_object` - DB 2
Make sure these databases are accessible in HeroDB.
### Compilation Errors
Ensure you have the latest dependencies:
```bash
cargo clean
cargo build --examples
```
## Next Steps
After running the examples:
1. Read the [Architecture Documentation](../docs/ARCHITECTURE.md)
2. Learn about the [Derive Macro](../docs/DERIVE_MACRO.md)
3. Check out the [Quick Start Guide](../QUICKSTART.md)
4. Explore the [source code](../src/objects/) for Note and Event implementations
## Creating Your Own Objects
Use the `custom_object` example as a template:
1. Define your struct with `base_data: BaseData`
2. Add `#[derive(DeriveObject)]`
3. Mark fields with `#[index]` for automatic indexing
4. Implement builder methods for convenience
5. Use `GenericStore` to store and query
Happy coding! 🚀

200
examples/basic_usage.rs Normal file
View File

@@ -0,0 +1,200 @@
/// Basic OSIRIS usage example
///
/// This example demonstrates:
/// - Creating objects with the derive macro
/// - Storing objects in HeroDB
/// - Querying by indexed fields
/// - Retrieving objects
///
/// Prerequisites:
/// - HeroDB running on localhost:6379
///
/// Run with:
/// ```bash
/// cargo run --example basic_usage
/// ```
use osiris::objects::{Event, Note};
use osiris::store::{BaseData, GenericStore, HeroDbClient};
use osiris::Object;
use std::collections::BTreeMap;
use time::OffsetDateTime;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 OSIRIS Basic Usage Example\n");
// Initialize HeroDB client
println!("📡 Connecting to HeroDB...");
let client = HeroDbClient::new("redis://localhost:6379", 1)?;
let store = GenericStore::new(client);
println!("✓ Connected to HeroDB (DB 1)\n");
// ========================================
// Part 1: Working with Notes
// ========================================
println!("📝 Part 1: Working with Notes");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
// Create a note with tags
let note1 = Note::new("notes".to_string())
.set_title("OSIRIS Architecture")
.set_content("OSIRIS uses a trait-based architecture with automatic indexing based on #[index] attributes.")
.add_tag("topic", "architecture")
.add_tag("project", "osiris")
.add_tag("priority", "high")
.set_mime("text/plain");
println!("Creating note: {}", note1.title.as_ref().unwrap());
println!(" ID: {}", note1.base_data.id);
println!(" Tags: {:?}", note1.tags);
// Store the note
store.put(&note1).await?;
println!("✓ Note stored\n");
// Create another note
let note2 = Note::new("notes".to_string())
.set_title("HeroDB Integration")
.set_content("HeroDB provides encrypted storage with Redis compatibility.")
.add_tag("topic", "storage")
.add_tag("project", "osiris")
.add_tag("priority", "medium")
.set_mime("text/plain");
println!("Creating note: {}", note2.title.as_ref().unwrap());
println!(" ID: {}", note2.base_data.id);
store.put(&note2).await?;
println!("✓ Note stored\n");
// Retrieve a note by ID
println!("Retrieving note by ID...");
let retrieved_note: Note = store.get("notes", &note1.base_data.id).await?;
println!("✓ Retrieved: {}", retrieved_note.title.as_ref().unwrap());
println!(" Content: {}\n", retrieved_note.content.as_ref().unwrap_or(&"(none)".to_string()));
// Query notes by tag
println!("Querying notes by tag (project=osiris)...");
let ids = store.get_ids_by_index("notes", "tags:tag", "project=osiris").await?;
println!("✓ Found {} notes with tag project=osiris", ids.len());
for id in &ids {
let note: Note = store.get("notes", id).await?;
println!(" - {}", note.title.as_ref().unwrap_or(&"(untitled)".to_string()));
}
println!();
// Query by different tag
println!("Querying notes by tag (priority=high)...");
let high_priority_ids = store.get_ids_by_index("notes", "tags:tag", "priority=high").await?;
println!("✓ Found {} high-priority notes\n", high_priority_ids.len());
// ========================================
// Part 2: Working with Events
// ========================================
println!("📅 Part 2: Working with Events");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
use osiris::objects::event::EventStatus;
// Create an event
let now = OffsetDateTime::now_utc();
let event1 = Event::new("calendar".to_string(), "Team Standup")
.set_description("Daily standup meeting")
.set_start_time(now)
.set_end_time(now + time::Duration::minutes(30))
.set_location("Room 101")
.set_status(EventStatus::Published)
.set_category("meetings")
.set_all_day(false);
println!("Creating event: {}", event1.title);
println!(" ID: {}", event1.base_data.id);
println!(" Location: {}", event1.location.as_ref().unwrap());
println!(" Status: {:?}", event1.status);
store.put(&event1).await?;
println!("✓ Event stored\n");
// Create another event
let tomorrow = now + time::Duration::days(1);
let event2 = Event::new("calendar".to_string(), "Project Review")
.set_description("Review OSIRIS implementation progress")
.set_start_time(tomorrow)
.set_end_time(tomorrow + time::Duration::hours(1))
.set_location("Conference Room A")
.set_status(EventStatus::Published)
.set_category("reviews");
println!("Creating event: {}", event2.title);
store.put(&event2).await?;
println!("✓ Event stored\n");
// Query events by location
println!("Querying events by location (Room 101)...");
let location_ids = store.get_ids_by_index("calendar", "location", "Room 101").await?;
println!("✓ Found {} events in Room 101", location_ids.len());
for id in &location_ids {
let event: Event = store.get("calendar", id).await?;
println!(" - {}", event.title);
}
println!();
// Query events by status
println!("Querying events by status (Published)...");
let status_ids = store.get_ids_by_index("calendar", "status", "Published").await?;
println!("✓ Found {} published events", status_ids.len());
for id in &status_ids {
let event: Event = store.get("calendar", id).await?;
println!(" - {} ({})", event.title, event.category.as_ref().unwrap_or(&"uncategorized".to_string()));
}
println!();
// Query events by date
let date_str = now.date().to_string();
println!("Querying events by date ({})...", date_str);
let date_ids = store.get_ids_by_index("calendar", "start_time", &date_str).await?;
println!("✓ Found {} events on {}", date_ids.len(), date_str);
println!();
// ========================================
// Part 3: Demonstrating Auto-Generated Indexes
// ========================================
println!("🔍 Part 3: Auto-Generated Indexes");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
println!("Note indexed fields: {:?}", Note::indexed_fields());
println!("Event indexed fields: {:?}", Event::indexed_fields());
println!();
println!("Note index keys for '{}': ", note1.title.as_ref().unwrap());
for key in note1.index_keys() {
println!(" - {} = {}", key.name, key.value);
}
println!();
println!("Event index keys for '{}': ", event1.title);
for key in event1.index_keys() {
println!(" - {} = {}", key.name, key.value);
}
println!();
// ========================================
// Part 4: Cleanup
// ========================================
println!("🧹 Part 4: Cleanup");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
println!("Deleting notes...");
store.delete(&note1).await?;
store.delete(&note2).await?;
println!("✓ Notes deleted\n");
println!("Deleting events...");
store.delete(&event1).await?;
store.delete(&event2).await?;
println!("✓ Events deleted\n");
println!("✅ Example completed successfully!");
Ok(())
}

295
examples/custom_object.rs Normal file
View File

@@ -0,0 +1,295 @@
/// Custom Object Example
///
/// This example demonstrates how to create your own custom object types
/// using the derive macro.
///
/// Run with:
/// ```bash
/// cargo run --example custom_object
/// ```
use osiris::store::{BaseData, GenericStore, HeroDbClient};
use osiris::{DeriveObject, Object};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use time::OffsetDateTime;
// ========================================
// Custom Object: Task
// ========================================
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TaskPriority {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TaskStatus {
Todo,
InProgress,
Done,
Blocked,
}
#[derive(Debug, Clone, Serialize, Deserialize, DeriveObject)]
pub struct Task {
pub base_data: BaseData,
/// Task title
#[index]
pub title: String,
/// Task description
pub description: Option<String>,
/// Priority level
#[index]
pub priority: TaskPriority,
/// Current status
#[index]
pub status: TaskStatus,
/// Assigned to user
#[index]
pub assignee: Option<String>,
/// Due date
#[index]
#[serde(with = "time::serde::rfc3339::option")]
pub due_date: Option<OffsetDateTime>,
/// Tags for categorization
#[index]
pub tags: BTreeMap<String, String>,
/// Estimated hours
pub estimated_hours: Option<f32>,
/// Actual hours spent
pub actual_hours: Option<f32>,
}
impl Task {
pub fn new(ns: String, title: impl ToString) -> Self {
Self {
base_data: BaseData::new(ns),
title: title.to_string(),
description: None,
priority: TaskPriority::Medium,
status: TaskStatus::Todo,
assignee: None,
due_date: None,
tags: BTreeMap::new(),
estimated_hours: None,
actual_hours: None,
}
}
pub fn set_description(mut self, description: impl ToString) -> Self {
self.description = Some(description.to_string());
self
}
pub fn set_priority(mut self, priority: TaskPriority) -> Self {
self.priority = priority;
self
}
pub fn set_status(mut self, status: TaskStatus) -> Self {
self.status = status;
self
}
pub fn set_assignee(mut self, assignee: impl ToString) -> Self {
self.assignee = Some(assignee.to_string());
self
}
pub fn set_due_date(mut self, due_date: OffsetDateTime) -> Self {
self.due_date = Some(due_date);
self
}
pub fn add_tag(mut self, key: impl ToString, value: impl ToString) -> Self {
self.tags.insert(key.to_string(), value.to_string());
self
}
pub fn set_estimated_hours(mut self, hours: f32) -> Self {
self.estimated_hours = Some(hours);
self
}
}
// ========================================
// Main Example
// ========================================
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🎯 OSIRIS Custom Object Example\n");
// Connect to HeroDB
println!("📡 Connecting to HeroDB...");
let client = HeroDbClient::new("redis://localhost:6379", 2)?;
let store = GenericStore::new(client);
println!("✓ Connected to HeroDB (DB 2)\n");
// ========================================
// Create Tasks
// ========================================
println!("📋 Creating Tasks");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let now = OffsetDateTime::now_utc();
let tomorrow = now + time::Duration::days(1);
let next_week = now + time::Duration::days(7);
// Task 1: High priority, assigned
let task1 = Task::new("tasks".to_string(), "Implement derive macro")
.set_description("Create proc macro for automatic Object trait implementation")
.set_priority(TaskPriority::High)
.set_status(TaskStatus::Done)
.set_assignee("alice")
.set_due_date(tomorrow)
.add_tag("component", "derive")
.add_tag("project", "osiris")
.set_estimated_hours(8.0);
println!("Task 1: {}", task1.title);
println!(" Priority: {:?}", task1.priority);
println!(" Status: {:?}", task1.status);
println!(" Assignee: {}", task1.assignee.as_ref().unwrap());
store.put(&task1).await?;
println!("✓ Stored\n");
// Task 2: Critical priority, blocked
let task2 = Task::new("tasks".to_string(), "Fix indexing bug")
.set_description("BTreeMap indexing has lifetime issues")
.set_priority(TaskPriority::Critical)
.set_status(TaskStatus::Blocked)
.set_assignee("bob")
.set_due_date(now)
.add_tag("type", "bug")
.add_tag("project", "osiris")
.set_estimated_hours(4.0);
println!("Task 2: {}", task2.title);
println!(" Priority: {:?}", task2.priority);
println!(" Status: {:?}", task2.status);
store.put(&task2).await?;
println!("✓ Stored\n");
// Task 3: In progress
let task3 = Task::new("tasks".to_string(), "Write documentation")
.set_description("Document the derive macro usage")
.set_priority(TaskPriority::Medium)
.set_status(TaskStatus::InProgress)
.set_assignee("alice")
.set_due_date(next_week)
.add_tag("type", "docs")
.add_tag("project", "osiris")
.set_estimated_hours(6.0);
println!("Task 3: {}", task3.title);
println!(" Priority: {:?}", task3.priority);
println!(" Status: {:?}", task3.status);
store.put(&task3).await?;
println!("✓ Stored\n");
// ========================================
// Query Tasks
// ========================================
println!("🔍 Querying Tasks");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
// Query by assignee
println!("Tasks assigned to Alice:");
let alice_tasks = store.get_ids_by_index("tasks", "assignee", "alice").await?;
for id in &alice_tasks {
let task: Task = store.get("tasks", id).await?;
println!(" - {} ({:?})", task.title, task.status);
}
println!();
// Query by priority
println!("High priority tasks:");
let high_priority = store.get_ids_by_index("tasks", "priority", "High").await?;
for id in &high_priority {
let task: Task = store.get("tasks", id).await?;
println!(" - {} (assigned to: {})",
task.title,
task.assignee.as_ref().unwrap_or(&"unassigned".to_string())
);
}
println!();
// Query by status
println!("Blocked tasks:");
let blocked = store.get_ids_by_index("tasks", "status", "Blocked").await?;
for id in &blocked {
let task: Task = store.get("tasks", id).await?;
println!(" - {} (priority: {:?})", task.title, task.priority);
}
println!();
// Query by tag
println!("Tasks tagged with project=osiris:");
let project_tasks = store.get_ids_by_index("tasks", "tags:tag", "project=osiris").await?;
println!(" Found {} tasks", project_tasks.len());
println!();
// ========================================
// Show Auto-Generated Indexes
// ========================================
println!("📊 Auto-Generated Indexes");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
println!("Task indexed fields: {:?}", Task::indexed_fields());
println!();
println!("Index keys for '{}':", task1.title);
for key in task1.index_keys() {
println!(" - {} = {}", key.name, key.value);
}
println!();
// ========================================
// Update Task Status
// ========================================
println!("✏️ Updating Task Status");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
// Retrieve, modify, and store
let mut task2_updated: Task = store.get("tasks", &task2.base_data.id).await?;
println!("Updating '{}' status from {:?} to {:?}",
task2_updated.title,
task2_updated.status,
TaskStatus::InProgress
);
task2_updated.status = TaskStatus::InProgress;
task2_updated.base_data.update_modified();
store.put(&task2_updated).await?;
println!("✓ Task updated\n");
// ========================================
// Cleanup
// ========================================
println!("🧹 Cleanup");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
store.delete(&task1).await?;
store.delete(&task2_updated).await?;
store.delete(&task3).await?;
println!("✓ All tasks deleted\n");
println!("✅ Example completed successfully!");
Ok(())
}