# OSIRIS Architecture ## Overview OSIRIS uses a trait-based architecture for storing and retrieving typed objects with automatic indexing, Rhai scripting support, and signatory-based access control. ## Core Concepts ### 1. BaseData Every OSIRIS object must include `BaseData`, which provides: - **id**: Unique identifier (UUID or user-assigned) - **ns**: Namespace the object belongs to - **created_at**: Creation timestamp - **modified_at**: Last modification timestamp - **mime**: Optional MIME type - **size**: Optional content size ```rust pub struct BaseData { pub id: String, pub ns: String, pub created_at: OffsetDateTime, pub modified_at: OffsetDateTime, pub mime: Option, pub size: Option, } ``` ### 2. Object Trait The `Object` trait is the core abstraction for all OSIRIS objects: ```rust pub trait Object: Debug + Clone + Serialize + Deserialize + Send + Sync { /// Get the object type name fn object_type() -> &'static str where Self: Sized; /// Get base data reference fn base_data(&self) -> &BaseData; /// Get mutable base data reference fn base_data_mut(&mut self) -> &mut BaseData; /// Get index keys for this object (auto-generated from #[index] fields) fn index_keys(&self) -> Vec; /// Get list of indexed field names fn indexed_fields() -> Vec<&'static str> where Self: Sized; /// Get searchable text content fn searchable_text(&self) -> Option; /// Serialize to JSON fn to_json(&self) -> Result; /// Deserialize from JSON fn from_json(json: &str) -> Result where Self: Sized; } ``` ### 3. IndexKey Represents an index entry for a field: ```rust pub struct IndexKey { pub name: &'static str, // Field name pub value: String, // Field value } ``` ## Example: Note Object ```rust #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Note { pub base_data: BaseData, // Indexed field - marked with #[index] #[index] pub title: Option, // Searchable content (not indexed) pub content: Option, // Indexed tags - marked with #[index] #[index] pub tags: BTreeMap, } impl Object for Note { fn object_type() -> &'static str { "note" } fn base_data(&self) -> &BaseData { &self.base_data } fn base_data_mut(&mut self) -> &mut BaseData { &mut self.base_data } fn index_keys(&self) -> Vec { let mut keys = Vec::new(); // Index title if let Some(title) = &self.title { keys.push(IndexKey::new("title", title)); } // Index tags for (key, value) in &self.tags { keys.push(IndexKey::new(&format!("tag:{}", key), value)); } keys } fn indexed_fields() -> Vec<&'static str> { vec!["title", "tags"] } fn searchable_text(&self) -> Option { let mut text = String::new(); if let Some(title) = &self.title { text.push_str(title); text.push(' '); } if let Some(content) = &self.content { text.push_str(content); } if text.is_empty() { None } else { Some(text) } } } ``` ## Example: Event Object ```rust #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Event { pub base_data: BaseData, #[index] pub title: String, pub description: Option, #[index] pub start_time: OffsetDateTime, pub end_time: OffsetDateTime, #[index] pub location: Option, #[index] pub status: EventStatus, pub all_day: bool, #[index] pub category: Option, } impl Object for Event { fn object_type() -> &'static str { "event" } fn base_data(&self) -> &BaseData { &self.base_data } fn base_data_mut(&mut self) -> &mut BaseData { &mut self.base_data } fn index_keys(&self) -> Vec { let mut keys = Vec::new(); keys.push(IndexKey::new("title", &self.title)); if let Some(location) = &self.location { keys.push(IndexKey::new("location", location)); } let status_str = match self.status { EventStatus::Draft => "draft", EventStatus::Published => "published", EventStatus::Cancelled => "cancelled", }; keys.push(IndexKey::new("status", status_str)); if let Some(category) = &self.category { keys.push(IndexKey::new("category", category)); } // Index by date for day-based queries let date_str = self.start_time.date().to_string(); keys.push(IndexKey::new("date", date_str)); keys } fn indexed_fields() -> Vec<&'static str> { vec!["title", "location", "status", "category", "start_time"] } fn searchable_text(&self) -> Option { let mut text = String::new(); text.push_str(&self.title); text.push(' '); if let Some(description) = &self.description { text.push_str(description); } Some(text) } } ``` ## Storage Layer ### GenericStore The `GenericStore` provides a type-safe storage layer for any object implementing `Object`: ```rust pub struct GenericStore { client: HeroDbClient, index: FieldIndex, } impl GenericStore { /// Store an object pub async fn put(&self, obj: &T) -> Result<()>; /// Get an object by ID pub async fn get(&self, ns: &str, id: &str) -> Result; /// Delete an object pub async fn delete(&self, obj: &T) -> Result; /// Get IDs matching an index key pub async fn get_ids_by_index(&self, ns: &str, field: &str, value: &str) -> Result>; } ``` ### Usage Example ```rust use osiris::objects::Note; use osiris::store::{GenericStore, HeroDbClient}; // Create store let client = HeroDbClient::new("redis://localhost:6379", 1)?; let store = GenericStore::new(client); // Create and store a note let note = Note::new("notes".to_string()) .set_title("My Note") .set_content("This is the content") .add_tag("topic", "rust") .add_tag("priority", "high"); store.put(¬e).await?; // Retrieve the note let retrieved: Note = store.get("notes", ¬e.id()).await?; // Search by index let ids = store.get_ids_by_index("notes", "tag:topic", "rust").await?; ``` ## Index Storage ### Keyspace Design ``` obj:: → JSON serialized object idx::: → Set of object IDs scan: → Set of all object IDs in namespace ``` ### Examples ``` obj:notes:abc123 → {"base_data":{...},"title":"My Note",...} idx:notes:title:My Note → {abc123, def456} idx:notes:tag:topic:rust → {abc123, xyz789} idx:notes:mime:text/plain → {abc123} scan:notes → {abc123, def456, xyz789} ``` ## Automatic Indexing When an object is stored: 1. **Serialize** the object to JSON 2. **Store** at `obj::` 3. **Generate index keys** by calling `obj.index_keys()` 4. **Create indexes** for each key at `idx:::` 5. **Add to scan index** at `scan:` When an object is deleted: 1. **Retrieve** the object 2. **Generate index keys** 3. **Remove** from all indexes 4. **Delete** the object ## Rhai Integration OSIRIS provides full Rhai scripting support through the `rhai` module: ### OsirisContext Multi-tenant context with signatory-based access control: ```rust pub struct OsirisContext { context_id: String, participants: Vec, // Public keys members: HashMap>, store: Arc, } ``` **Key Features:** - **Signatory-based access**: All participants must be signatories - **Member management**: Add/remove members with privileges - **Generic CRUD**: `save()`, `get()`, `delete()`, `list()`, `query()` ### Rhai API Each object type provides Rhai bindings in its `rhai.rs` file: ```rust #[export_module] mod rhai_note_module { #[rhai_fn(name = "note", return_raw)] pub fn new_note(ns: String) -> Result> { Ok(Note::new(ns)) } #[rhai_fn(name = "title", return_raw)] pub fn set_title(note: Note, title: String) -> Result> { Ok(note.title(title)) } } ``` ### Engine Configuration The runner binary supports multiple configurations: ```bash # Single instance cargo run --bin runner -- runner1 --redis-url redis://localhost:6379 --db-id 1 # Multiple predefined instances cargo run --bin runner -- runner1 \ --instance freezone:redis://localhost:6379:1 \ --instance my:redis://localhost:6379:2 ``` ## Benefits of Trait-Based Architecture 1. **Type Safety**: Compile-time guarantees for object types 2. **Extensibility**: Easy to add new object types 3. **Automatic Indexing**: Index keys generated from object structure 4. **Consistency**: Same pattern as heromodels 5. **Flexibility**: Each object type controls its own indexing logic 6. **Testability**: Easy to mock and test individual object types ## Summary OSIRIS provides: - **Type-safe storage**: Any type implementing `Object` can be stored - **Automatic indexing**: Fields marked with `#[index]` are automatically indexed - **Rhai scripting**: Full scripting support with builder patterns - **Multi-tenant contexts**: Signatory-based access control - **HeroDB backend**: Redis-compatible storage with encryption - **Extensibility**: Easy to add new object types and features See [CREATING_NEW_OBJECTS.md](CREATING_NEW_OBJECTS.md) for a guide on creating custom objects.