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}