fob_graph/runtime/mod.rs
1//! Platform runtime abstraction for Fob bundler
2//!
3//! This module defines the `Runtime` trait that abstracts platform-specific
4//! operations like file I/O and module resolution. Platform bindings
5//! (joy-native, joy-wasm) implement this trait to provide platform-specific behavior.
6
7// Platform-specific runtime implementations
8#[cfg(not(target_family = "wasm"))]
9pub mod native;
10
11#[cfg(target_family = "wasm")]
12pub mod wasm;
13
14// Test utilities (available in test builds)
15#[cfg(any(
16 all(any(test, doctest), not(target_family = "wasm")),
17 all(feature = "test-utils", not(target_family = "wasm"))
18))]
19pub mod test_utils;
20
21use async_trait::async_trait;
22use std::path::{Path, PathBuf};
23
24/// Result type for runtime operations
25pub type RuntimeResult<T> = Result<T, RuntimeError>;
26
27/// Errors that can occur during runtime operations
28#[derive(Debug, thiserror::Error)]
29pub enum RuntimeError {
30 /// File not found
31 #[error("File not found: {0}")]
32 FileNotFound(PathBuf),
33
34 /// I/O error
35 #[error("I/O error: {0}")]
36 Io(String),
37
38 /// Module resolution failed
39 #[error("Failed to resolve module '{specifier}' from '{from}': {reason}")]
40 ResolutionFailed {
41 specifier: String,
42 from: PathBuf,
43 reason: String,
44 },
45
46 /// Other runtime error
47 #[error("Runtime error: {0}")]
48 Other(String),
49}
50
51/// File metadata
52#[derive(Debug, Clone)]
53pub struct FileMetadata {
54 /// File size in bytes
55 pub size: u64,
56 /// Whether this is a directory
57 pub is_dir: bool,
58 /// Whether this is a file
59 pub is_file: bool,
60 /// Last modified timestamp (milliseconds since epoch)
61 pub modified: Option<u64>,
62}
63
64/// Platform runtime trait
65///
66/// This trait abstracts platform-specific operations. Platform bindings
67/// implement this trait to provide file I/O, module resolution, and other
68/// platform-dependent functionality.
69///
70/// # Example
71///
72/// ```rust,ignore
73/// use crate::runtime::{Runtime, RuntimeResult};
74/// use async_trait::async_trait;
75///
76/// struct MyRuntime;
77///
78/// #[async_trait]
79/// impl Runtime for MyRuntime {
80/// async fn read_file(&self, path: &Path) -> RuntimeResult<Vec<u8>> {
81/// // Platform-specific implementation
82/// std::fs::read(path).map_err(|e| RuntimeError::Io(e.to_string()))
83/// }
84///
85/// // ... implement other methods
86/// }
87/// ```
88// WASM target: Single-threaded execution
89// Educational Note: WASM is always single-threaded, so Send/Sync don't affect
90// runtime behavior. However, we still declare the trait with Send + Sync to
91// satisfy trait object requirements (Arc<dyn Runtime>). The Send bound on
92// futures is conditionally removed using ?Send from async_trait.
93//
94// IMPORTANT: We keep Send + Sync on the trait itself because:
95// 1. Arc<dyn Runtime> requires Send + Sync
96// 2. These are just marker traits on WASM (no actual threading)
97// 3. The ?Send applies to the futures returned by methods, not the trait
98#[cfg(target_family = "wasm")]
99#[async_trait(?Send)]
100pub trait Runtime: Send + Sync + std::fmt::Debug {
101 /// Read a file from the filesystem
102 async fn read_file(&self, path: &Path) -> RuntimeResult<Vec<u8>>;
103
104 /// Write a file to the filesystem
105 async fn write_file(&self, path: &Path, content: &[u8]) -> RuntimeResult<()>;
106
107 /// Get file metadata
108 async fn metadata(&self, path: &Path) -> RuntimeResult<FileMetadata>;
109
110 /// Check if a path exists
111 fn exists(&self, path: &Path) -> bool;
112
113 /// Resolve a module specifier
114 fn resolve(&self, specifier: &str, from: &Path) -> RuntimeResult<PathBuf>;
115
116 /// Create a directory
117 async fn create_dir(&self, path: &Path, recursive: bool) -> RuntimeResult<()>;
118
119 /// Remove a file
120 async fn remove_file(&self, path: &Path) -> RuntimeResult<()>;
121
122 /// Read a directory
123 async fn read_dir(&self, path: &Path) -> RuntimeResult<Vec<String>>;
124
125 /// Get the current working directory
126 ///
127 /// # Educational Note: Virtual Working Directory
128 ///
129 /// On WASM platforms, there is no OS-level current working directory.
130 /// This method allows the runtime to provide a virtual cwd, enabling
131 /// path resolution to work consistently across platforms.
132 fn get_cwd(&self) -> RuntimeResult<PathBuf>;
133}
134
135// Native target: Multi-threaded, requires Send + Sync
136#[cfg(not(target_family = "wasm"))]
137#[async_trait]
138pub trait Runtime: Send + Sync + std::fmt::Debug {
139 /// Read a file from the filesystem
140 async fn read_file(&self, path: &Path) -> RuntimeResult<Vec<u8>>;
141
142 /// Write a file to the filesystem
143 async fn write_file(&self, path: &Path, content: &[u8]) -> RuntimeResult<()>;
144
145 /// Get file metadata
146 async fn metadata(&self, path: &Path) -> RuntimeResult<FileMetadata>;
147
148 /// Check if a path exists
149 fn exists(&self, path: &Path) -> bool;
150
151 /// Resolve a module specifier
152 fn resolve(&self, specifier: &str, from: &Path) -> RuntimeResult<PathBuf>;
153
154 /// Create a directory
155 async fn create_dir(&self, path: &Path, recursive: bool) -> RuntimeResult<()>;
156
157 /// Remove a file
158 async fn remove_file(&self, path: &Path) -> RuntimeResult<()>;
159
160 /// Read a directory
161 async fn read_dir(&self, path: &Path) -> RuntimeResult<Vec<String>>;
162
163 /// Get the current working directory
164 ///
165 /// # Educational Note: Working Directory on Native
166 ///
167 /// On native platforms, this delegates to `std::env::current_dir()`.
168 /// This abstraction allows the bundler to work without directly calling
169 /// OS-specific functions, making the code platform-agnostic.
170 fn get_cwd(&self) -> RuntimeResult<PathBuf>;
171}