1- use futures:: StreamExt ;
21use itertools:: Itertools ;
3- use opfs:: { DirectoryHandle , FileHandle } ;
42use serde:: { Deserialize , Serialize } ;
5- use std:: { cell :: RefCell , collections :: HashMap , rc :: Rc , str:: FromStr } ;
3+ use std:: str:: FromStr ;
64use wasm_bindgen:: prelude:: * ;
75
6+ #[ cfg( feature = "opfs" ) ]
7+ use futures:: StreamExt ;
8+ #[ cfg( feature = "opfs" ) ]
9+ use opfs:: { DirectoryHandle , FileHandle } ;
10+ #[ cfg( feature = "opfs" ) ]
11+ use std:: { cell:: RefCell , collections:: HashMap , rc:: Rc } ;
12+
813#[ wasm_bindgen( typescript_custom_section) ]
914const TS_CUSTOM_SECTION : & ' static str = r#"
1015export type DefinedValueType = 'Function' | 'Variable';
@@ -208,36 +213,30 @@ impl From<ConversionOptions> for mq_markdown::ConversionOptions {
208213 }
209214}
210215
211- #[ derive( Debug , Clone ) ]
216+ #[ derive( Debug , Clone , Default ) ]
212217pub struct WasmModuleResolver {
218+ #[ cfg( feature = "opfs" ) ]
213219 /// Cache of preloaded module contents, keyed by module name
214220 cache : Rc < RefCell < HashMap < String , String > > > ,
221+ #[ cfg( feature = "opfs" ) ]
215222 /// Root directory handle for OPFS access
216223 root_dir : Rc < RefCell < Option < opfs:: persistent:: DirectoryHandle > > > ,
224+ #[ cfg( feature = "opfs" ) ]
217225 /// Flag indicating whether OPFS is available
218226 is_available : Rc < RefCell < bool > > ,
219227}
220228
221- impl Default for WasmModuleResolver {
222- fn default ( ) -> Self {
223- Self :: new ( )
224- }
225- }
226-
227229impl WasmModuleResolver {
228230 pub fn new ( ) -> Self {
229- Self {
230- cache : Rc :: new ( RefCell :: new ( HashMap :: new ( ) ) ) ,
231- root_dir : Rc :: new ( RefCell :: new ( None ) ) ,
232- is_available : Rc :: new ( RefCell :: new ( false ) ) ,
233- }
231+ Self :: default ( )
234232 }
235233
236234 /// Initializes the OPFS root directory handle
237235 ///
238236 /// If OPFS is not available, this method will silently fail and the resolver
239237 /// will operate as a NoOp resolver (only using manually added modules via `add_module`).
240238 pub async fn initialize ( & self ) {
239+ #[ cfg( feature = "opfs" ) ]
241240 match opfs:: persistent:: app_specific_dir ( ) . await {
242241 Ok ( root) => {
243242 * self . root_dir . borrow_mut ( ) = Some ( root) ;
@@ -257,52 +256,55 @@ impl WasmModuleResolver {
257256 ///
258257 /// If OPFS is not available, this method returns immediately without error.
259258 pub async fn preload_modules ( & self ) {
260- // Skip if OPFS is not available
261- if !* self . is_available . borrow ( ) {
262- return ;
263- }
264-
265- let root = match self . root_dir . borrow ( ) . as_ref ( ) {
266- Some ( r) => r. clone ( ) ,
267- None => return , // Should not happen if is_available is true, but be defensive
268- } ;
259+ #[ cfg( feature = "opfs" ) ]
260+ {
261+ // Skip if OPFS is not available
262+ if !* self . is_available . borrow ( ) {
263+ return ;
264+ }
269265
270- let mut entries = match root . entries ( ) . await {
271- Ok ( e ) => e ,
272- Err ( _ ) => return , // Failed to get directory entries
273- } ;
266+ let root = match self . root_dir . borrow ( ) . as_ref ( ) {
267+ Some ( r ) => r . clone ( ) ,
268+ None => return , // Should not happen if is_available is true, but be defensive
269+ } ;
274270
275- while let Some ( result) = entries. next ( ) . await {
276- let ( name, entry) = match result {
271+ let mut entries = match root. entries ( ) . await {
277272 Ok ( e) => e,
278- Err ( _) => continue , // Skip entries that fail to read
273+ Err ( _) => return , // Failed to get directory entries
279274 } ;
280275
281- match entry {
282- opfs:: DirectoryEntry :: File ( file_handle) => {
283- // Only process .mq files
284- if !name. ends_with ( ".mq" ) {
276+ while let Some ( result) = entries. next ( ) . await {
277+ let ( name, entry) = match result {
278+ Ok ( e) => e,
279+ Err ( _) => continue , // Skip entries that fail to read
280+ } ;
281+
282+ match entry {
283+ opfs:: DirectoryEntry :: File ( file_handle) => {
284+ // Only process .mq files
285+ if !name. ends_with ( ".mq" ) {
286+ continue ;
287+ }
288+
289+ // Read file contents
290+ let data = match file_handle. read ( ) . await {
291+ Ok ( d) => d,
292+ Err ( _) => continue , // Skip files that fail to read
293+ } ;
294+
295+ let contents = match String :: from_utf8 ( data) {
296+ Ok ( c) => c,
297+ Err ( _) => continue , // Skip files that are not valid UTF-8
298+ } ;
299+
300+ // Store with module name (without .mq extension)
301+ let module_name = name. strip_suffix ( ".mq" ) . unwrap_or ( & name) ;
302+ self . cache . borrow_mut ( ) . insert ( module_name. to_string ( ) , contents) ;
303+ }
304+ opfs:: DirectoryEntry :: Directory ( _) => {
305+ // Skip directories for now
285306 continue ;
286307 }
287-
288- // Read file contents
289- let data = match file_handle. read ( ) . await {
290- Ok ( d) => d,
291- Err ( _) => continue , // Skip files that fail to read
292- } ;
293-
294- let contents = match String :: from_utf8 ( data) {
295- Ok ( c) => c,
296- Err ( _) => continue , // Skip files that are not valid UTF-8
297- } ;
298-
299- // Store with module name (without .mq extension)
300- let module_name = name. strip_suffix ( ".mq" ) . unwrap_or ( & name) ;
301- self . cache . borrow_mut ( ) . insert ( module_name. to_string ( ) , contents) ;
302- }
303- opfs:: DirectoryEntry :: Directory ( _) => {
304- // Skip directories for now
305- continue ;
306308 }
307309 }
308310 }
@@ -311,24 +313,32 @@ impl WasmModuleResolver {
311313 /// Manually adds a module to the cache
312314 ///
313315 /// This is useful for injecting module contents without using OPFS
314- pub fn add_module ( & self , module_name : & str , content : String ) {
315- self . cache . borrow_mut ( ) . insert ( module_name. to_string ( ) , content) ;
316+ pub fn add_module ( & self , _module_name : & str , _content : String ) {
317+ #[ cfg( feature = "opfs" ) ]
318+ self . cache . borrow_mut ( ) . insert ( _module_name. to_string ( ) , _content) ;
316319 }
317320
318321 /// Clears the module cache
319322 pub fn clear_cache ( & self ) {
323+ #[ cfg( feature = "opfs" ) ]
320324 self . cache . borrow_mut ( ) . clear ( ) ;
321325 }
322326}
323327
324328impl mq_lang:: ModuleResolver for WasmModuleResolver {
325329 fn resolve ( & self , module_name : & str ) -> Result < String , mq_lang:: ModuleError > {
326- self . cache . borrow ( ) . get ( module_name) . cloned ( ) . ok_or_else ( || {
330+ #[ cfg( feature = "opfs" ) ]
331+ return self . cache . borrow ( ) . get ( module_name) . cloned ( ) . ok_or_else ( || {
327332 mq_lang:: ModuleError :: NotFound ( std:: borrow:: Cow :: Owned ( format ! (
328333 "Module '{}' not found in cache. Use preload_modules() to load it first." ,
329334 module_name
330335 ) ) )
331- } )
336+ } ) ;
337+ #[ cfg( not( feature = "opfs" ) ) ]
338+ return Err ( mq_lang:: ModuleError :: NotFound ( std:: borrow:: Cow :: Owned ( format ! (
339+ "Module '{}' not found. Module resolution is not supported in this environment." ,
340+ module_name
341+ ) ) ) ) ;
332342 }
333343
334344 fn get_path ( & self , module_name : & str ) -> Result < String , mq_lang:: ModuleError > {
@@ -600,7 +610,6 @@ pub async fn defined_values(code: &str, module: Option<String>) -> Result<JsValu
600610#[ cfg( test) ]
601611mod tests {
602612 use super :: * ;
603- use mq_lang:: ModuleResolver ;
604613 use wasm_bindgen_test:: * ;
605614 wasm_bindgen_test_configure ! ( run_in_browser) ;
606615
@@ -760,9 +769,14 @@ mod tests {
760769 resolver. add_module ( "test" , "def foo(x): x | upcase();" . to_string ( ) ) ;
761770
762771 // Should be able to resolve it
763- let result = resolver. resolve ( "test" ) ;
764- assert ! ( result. is_ok( ) ) ;
765- assert_eq ! ( result. unwrap( ) , "def foo(x): x | upcase();" ) ;
772+ let result = mq_lang:: ModuleResolver :: resolve ( & resolver, "test" ) ;
773+ #[ cfg( feature = "opfs" ) ]
774+ {
775+ assert ! ( result. is_ok( ) ) ;
776+ assert_eq ! ( result. unwrap( ) , "def foo(x): x | upcase();" ) ;
777+ }
778+ #[ cfg( not( feature = "opfs" ) ) ]
779+ assert ! ( result. is_err( ) ) ;
766780 }
767781
768782 #[ allow( unused) ]
@@ -771,7 +785,7 @@ mod tests {
771785 let resolver = WasmModuleResolver :: new ( ) ;
772786
773787 // Should fail when module is not in cache
774- let result = resolver . resolve ( "nonexistent" ) ;
788+ let result = mq_lang :: ModuleResolver :: resolve ( & resolver , "nonexistent" ) ;
775789 assert ! ( result. is_err( ) ) ;
776790 }
777791
@@ -782,15 +796,17 @@ mod tests {
782796
783797 // Add a module
784798 resolver. add_module ( "test" , "content" . to_string ( ) ) ;
785- assert ! ( resolver. resolve( "test" ) . is_ok( ) ) ;
799+ #[ cfg( feature = "opfs" ) ]
800+ assert ! ( mq_lang:: ModuleResolver :: resolve( & resolver, "test" ) . is_ok( ) ) ;
786801
787802 // Clear cache
788803 resolver. clear_cache ( ) ;
789804
790805 // Should no longer be resolvable
791- assert ! ( resolver . resolve( "test" ) . is_err( ) ) ;
806+ assert ! ( mq_lang :: ModuleResolver :: resolve( & resolver , "test" ) . is_err( ) ) ;
792807 }
793808
809+ #[ cfg( feature = "opfs" ) ]
794810 #[ allow( unused) ]
795811 #[ wasm_bindgen_test]
796812 async fn test_opfs_create_and_import_module ( ) {
@@ -842,9 +858,8 @@ mod tests {
842858 resolver. preload_modules ( ) . await ;
843859
844860 // Verify the module was loaded into cache
845- let resolved_content = resolver
846- . resolve ( "test_module" )
847- . expect ( "Module should be found in cache" ) ;
861+ let resolved_content =
862+ mq_lang:: ModuleResolver :: resolve ( & resolver, "test_module" ) . expect ( "Module should be found in cache" ) ;
848863 assert_eq ! ( resolved_content, module_content) ;
849864
850865 // Test using the imported module in code execution
@@ -868,6 +883,7 @@ mod tests {
868883 assert_eq ! ( output. join( "" ) , "HELLO WORLD!" ) ;
869884 }
870885
886+ #[ cfg( feature = "opfs" ) ]
871887 #[ allow( unused) ]
872888 #[ wasm_bindgen_test]
873889 async fn test_opfs_multiple_modules ( ) {
@@ -920,8 +936,8 @@ mod tests {
920936 resolver. preload_modules ( ) . await ;
921937
922938 // Verify all modules are loaded
923- assert ! ( resolver . resolve( "math" ) . is_ok( ) ) ;
924- assert ! ( resolver . resolve( "string" ) . is_ok( ) ) ;
939+ assert ! ( mq_lang :: ModuleResolver :: resolve( & resolver , "math" ) . is_ok( ) ) ;
940+ assert ! ( mq_lang :: ModuleResolver :: resolve( & resolver , "string" ) . is_ok( ) ) ;
925941
926942 // Test using multiple imported modules
927943 let code = r#"
0 commit comments