--- title: Operations sidebarTitle: Operations --- The operations system automatically generates type-safe Swift and TypeScript clients from Rust API definitions. Define your API once in Rust and get native clients for iOS, web, and desktop without manual synchronization. ## How It Works The system uses compile-time type extraction to discover all operations and generate client code during the build process. This eliminates the traditional API boundary. ### Define Operations Operations are either Actions (write) or Queries (read): ```rust #[derive(Debug, Clone, Serialize, Deserialize, Type)] pub struct CreateLibraryInput { pub name: String, pub description: Option, } pub struct CreateLibraryAction { input: CreateLibraryInput // action state can be held here } impl CoreAction for CreateLibraryAction { type Input = CreateLibraryInput; type Output = Library; async fn validate(&self, context: Arc) -> Result { // Check if the library already exists and return validation result Ok(ValidationResult::Success) } async fn execute(self, context: Arc) -> Result { // Create library and return it } fn action_kind(&self) -> &'static str { "libraries.create" } } ``` ### Register and Generate Register the operation with a single macro: ```rust register_core_action!(CreateLibraryAction, "libraries.create"); ``` The build process automatically: 1. Extracts type information using Specta 2. Generates Swift and TypeScript type definitions 3. Creates native API methods for each client ### Use Generated Clients Swift: ```swift let library = try await spacedrive.libraries.create( CreateLibraryInput(name: "My Library", description: nil) ) ``` TypeScript: ```typescript const library = await spacedrive.libraries.create({ name: "My Library", }); ``` ## Operation Types ### Actions Actions modify state and typically return job receipts or updated entities: ```rust pub trait LibraryAction { type Input: Send + Sync + 'static; type Output: Send + Sync + 'static; fn from_input(input: Self::Input) -> Result; async fn validate(&self, library: &Arc, context: Arc) -> Result; fn resolve_confirmation(&mut self, choice_index: usize) -> Result<(), ActionError>; async fn execute(self, library: Arc, context: Arc) -> impl Future>; fn action_kind(&self) -> &'static str; } ``` ### Validation and Confirmation Actions support a validation phase that can request user confirmation before execution. This enables safe, interactive operations with clear user feedback. #### ValidationResult The `validate()` method returns one of two results: ```rust pub enum ValidationResult { /// Action is valid and can proceed Success, /// Action requires user confirmation RequiresConfirmation(ConfirmationRequest), } pub struct ConfirmationRequest { /// Message to display to the user pub message: String, /// List of choices for the user pub choices: Vec, } ``` #### Example: File Copy with Conflict Resolution ```rust impl LibraryAction for FileCopyAction { type Input = FileCopyInput; type Output = JobReceipt; async fn validate(&self, library: &Arc, context: Arc) -> Result { // Check if destination file exists if !self.options.overwrite && self.destination_exists().await? { return Ok(ValidationResult::RequiresConfirmation(ConfirmationRequest { message: format!( "Destination file already exists: {}", self.destination.display() ), choices: vec![ "Overwrite the existing file".to_string(), "Rename the new file (e.g., file.txt -> file (1).txt)".to_string(), "Abort this copy operation".to_string(), ], })); } Ok(ValidationResult::Success) } fn resolve_confirmation(&mut self, choice_index: usize) -> Result<(), ActionError> { match choice_index { 0 => { self.on_conflict = Some(FileConflictResolution::Overwrite); Ok(()) } 1 => { self.on_conflict = Some(FileConflictResolution::AutoModifyName); Ok(()) } 2 => Err(ActionError::Cancelled), _ => Err(ActionError::Validation { field: "choice".to_string(), message: "Invalid choice selected".to_string(), }) } } async fn execute(mut self, library: Arc, context: Arc) -> Result { // Apply the conflict resolution strategy if set if let Some(resolution) = self.on_conflict { match resolution { FileConflictResolution::Overwrite => { self.options.overwrite = true; } FileConflictResolution::AutoModifyName => { self.destination = self.generate_unique_name().await?; } _ => {} } } // Execute the copy operation let job = FileCopyJob::new(self.sources, self.destination) .with_options(self.options); let receipt = library.jobs().dispatch(job).await?; Ok(receipt) } fn action_kind(&self) -> &'static str { "files.copy" } } ``` #### CLI Integration The CLI handles confirmations interactively: ```rust // In CLI handler let mut action = FileCopyAction::from_input(input)?; // Validate the action let validation_result = action.validate(&library, context).await?; match validation_result { ValidationResult::Success => { // Proceed with execution let result = action.execute(library, context).await?; } ValidationResult::RequiresConfirmation(request) => { // Prompt user for choice let choice_index = prompt_for_choice(request)?; // Resolve the confirmation action.resolve_confirmation(choice_index)?; // Now execute with resolved choice let result = action.execute(library, context).await?; } } ``` The `validate()` method takes `&self` (a reference), while `execute()` takes `self` (consumes the action). This ensures validation doesn't modify state, while execution can take ownership to transform the action into its result. ### Queries Queries retrieve data without side effects: ```rust pub trait LibraryQuery { type Input: Send + Sync + 'static; type Output: Send + Sync + 'static; fn from_input(input: Self::Input) -> QueryResult; fn execute(self, context: Arc, session: SessionContext) -> impl Future>; } ``` ## Type System All standard Rust types are supported through Specta: ```rust #[derive(Debug, Clone, Serialize, Deserialize, Type)] pub struct FileOperationResult { pub succeeded: Vec, pub failed: HashMap, pub stats: OperationStats, } #[derive(Debug, Clone, Serialize, Deserialize, Type)] pub enum OperationError { NotFound(String), PermissionDenied, DiskFull { required: u64, available: u64 }, } ``` The `Type` derive is required for all types used in operations. This enables Specta to extract type information for client generation. ## Wire Protocol Operations use a consistent wire protocol: - Actions: `action:{category}.{operation}.input.v{version}` - Queries: `query:{scope}.{operation}.v{version}` Examples: - `action:files.copy.input` - `query:library.stats` ## Adding Operations ### 1. Create Input/Output Types ```rust #[derive(Debug, Clone, Serialize, Deserialize, Type)] pub struct SearchInput { pub query: String, pub filters: SearchFilters, pub limit: u32, } #[derive(Debug, Clone, Serialize, Deserialize, Type)] pub struct SearchResult { pub items: Vec, pub total_count: u64, } ``` ### 2. Implement the Operation For a query: ```rust pub struct SearchQuery { query: String, filters: SearchFilters, limit: u32, } impl LibraryQuery for SearchQuery { type Input = SearchInput; type Output = SearchResult; fn from_input(input: Self::Input) -> QueryResult { Ok(Self { query: input.query, filters: input.filters, limit: input.limit, }) } async fn execute(self, context: Arc, session: SessionContext) -> QueryResult { // Perform search and return results } } ``` ### 3. Register It ```rust register_library_query!(SearchQuery, "search"); ``` ### 4. Build and Use After building, the operation is available in all clients automatically. ## iOS Integration The iOS app embeds the Rust core and communicates through FFI: ```rust #[no_mangle] pub extern "C" fn handle_core_msg( query: *const c_char, callback: extern "C" fn(*mut c_void, *const c_char), callback_data: *mut c_void, ) { // Parse JSON-RPC request // Execute operation using same registry // Return JSON response } ``` Swift calls through the FFI boundary using the generated types. ## Code Generation Details ### Build Process The build script runs during `cargo build`: ```rust // build.rs fn main() { generate_swift_api_code().expect("Failed to generate Swift code"); } ``` ### Type Extraction A binary extracts all registered operations: ```rust // generate_swift_types binary fn main() { let (operations, queries, types) = generate_spacedrive_api(); // Generate Swift code let swift_types = specta_swift::Swift::new().export(&types)?; let api_methods = generate_api_methods(&operations, &queries); // Write to Swift package fs::write("SpacedriveTypes.swift", swift_types)?; fs::write("SpacedriveAPI.swift", api_methods)?; } ``` ### Registration Internals The registration macros use inventory for compile-time collection: ```rust inventory::submit! { TypeExtractorEntry { extractor: SearchQuery::extract_types, identifier: "search", } } ``` ## Best Practices ### Operation Design Keep operations focused with clear inputs and outputs. Use appropriate scopes (Library vs Core) based on whether the operation needs library context. ### When to Use Validation Confirmations Use the confirmation pattern for operations that: 1. **Have Destructive Side Effects**: Deleting files, overwriting data, or making irreversible changes 2. **Encounter Conflicts**: File name collisions, duplicate entries, or conflicting states 3. **Need User Decisions**: Multiple valid approaches where user preference matters 4. **Risk Data Loss**: Operations that could result in unexpected data loss Examples of good confirmation use cases: - File copy/move when destination exists - Deleting non-empty directories - Overwriting modified files - Removing locations with indexed content - Irreversible format conversions Keep confirmations minimal - only ask when truly necessary. Don't confirm routine operations or when the intent is already clear from the input. ### Type Design Flatten structures when possible and use Rust enums for variants. Document fields as comments flow through to generated code. ### Error Handling Define specific error types for each operation: ```rust #[derive(Debug, Serialize, Deserialize, Type)] pub enum SearchError { InvalidQuery(String), IndexNotReady, TooManyResults { max: u32, requested: u32 }, } ``` ### Performance For large result sets, consider pagination or streaming: ```rust #[derive(Type)] pub struct PaginatedSearch { pub query: String, pub cursor: Option, pub limit: u32, } ``` ## Advanced Features ### Batch Operations ```rust #[derive(Type)] pub struct BatchDeleteInput { pub items: Vec, pub skip_trash: bool, } ``` ### Operation Metadata Actions can define metadata for UI presentation: ```rust impl ActionMetadata for DeleteAction { fn display_name() -> &'static str { "Delete Items" } fn description() -> &'static str { "Permanently delete selected items" } fn is_dangerous() -> bool { true } } ``` Confirmation is handled dynamically through the `validate()` method, not as static metadata. This allows context-aware confirmations based on the actual operation state. Run `cargo run --bin generate_swift_types` to debug type extraction issues. Check the generated files in `packages/swift/Sources/SpacedriveClient/Generated/`. The operations system eliminates manual API maintenance while providing type-safe, performant clients across all platforms.