quickjs_runtime/
facades.rs

1//! contains the QuickJsRuntimeFacade
2
3use crate::builder::QuickJsRuntimeBuilder;
4use crate::jsutils::{JsError, Script};
5use crate::quickjs_utils::{functions, objects};
6use crate::quickjsrealmadapter::QuickJsRealmAdapter;
7use crate::quickjsruntimeadapter::{
8    CompiledModuleLoaderAdapter, MemoryUsage, NativeModuleLoaderAdapter, QuickJsRuntimeAdapter,
9    ScriptModuleLoaderAdapter, QJS_RT,
10};
11use crate::quickjsvalueadapter::QuickJsValueAdapter;
12use crate::reflection;
13use crate::values::JsValueFacade;
14use either::{Either, Left, Right};
15use hirofa_utils::eventloop::EventLoop;
16use hirofa_utils::task_manager::TaskManager;
17use libquickjs_sys as q;
18use lru::LruCache;
19use std::cell::RefCell;
20use std::future::Future;
21use std::num::NonZeroUsize;
22use std::pin::Pin;
23use std::rc::Rc;
24use std::sync::{Arc, Weak};
25use tokio::task::JoinError;
26
27lazy_static! {
28    /// a static Multithreaded task manager used to run rust ops async and multithreaded ( in at least 2 threads)
29    static ref HELPER_TASKS: TaskManager = TaskManager::new(std::cmp::max(2, num_cpus::get()));
30}
31
32impl Drop for QuickJsRuntimeFacade {
33    fn drop(&mut self) {
34        log::trace!("> EsRuntime::drop");
35        self.clear_contexts();
36        log::trace!("< EsRuntime::drop");
37    }
38}
39
40pub struct QuickjsRuntimeFacadeInner {
41    event_loop: EventLoop,
42}
43
44impl QuickjsRuntimeFacadeInner {
45    /// this is how you add a closure to the worker thread which has an instance of the QuickJsRuntime
46    /// this will run and return synchronously
47    /// # example
48    /// ```rust
49    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
50    /// use quickjs_runtime::jsutils::Script;
51    /// use quickjs_runtime::quickjs_utils::primitives;
52    /// let rt = QuickJsRuntimeBuilder::new().build();
53    /// let res = rt.exe_rt_task_in_event_loop(|q_js_rt| {
54    ///     let q_ctx = q_js_rt.get_main_realm();
55    ///     // here you are in the worker thread and you can use the quickjs_utils
56    ///     let val_ref = q_ctx.eval(Script::new("test.es", "(11 * 6);")).ok().expect("script failed");
57    ///     primitives::to_i32(&val_ref).ok().expect("could not get i32")
58    /// });
59    /// assert_eq!(res, 66);
60    /// ```
61    pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
62    where
63        C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
64        R: Send + 'static,
65    {
66        self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
67    }
68
69    /// this is how you add a closure to the worker thread which has an instance of the QuickJsRuntime
70    /// this will run asynchronously
71    /// # example
72    /// ```rust
73    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
74    /// let rt = QuickJsRuntimeBuilder::new().build();
75    /// rt.add_rt_task_to_event_loop(|q_js_rt| {
76    ///     // here you are in the worker thread and you can use the quickjs_utils
77    ///     q_js_rt.gc();
78    /// });
79    /// ```
80    pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
81        &self,
82        consumer: C,
83    ) -> impl Future<Output = R>
84    where
85        C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
86    {
87        self.add_task_to_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
88    }
89
90    pub fn add_rt_task_to_event_loop_void<C>(&self, consumer: C)
91    where
92        C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
93    {
94        self.add_task_to_event_loop_void(|| QuickJsRuntimeAdapter::do_with(consumer))
95    }
96
97    /// this can be used to run a function in the event_queue thread for the QuickJSRuntime
98    /// without borrowing the q_js_rt
99    pub fn add_task_to_event_loop_void<C>(&self, task: C)
100    where
101        C: FnOnce() + Send + 'static,
102    {
103        self.event_loop.add_void(move || {
104            task();
105            EventLoop::add_local_void(|| {
106                QuickJsRuntimeAdapter::do_with(|q_js_rt| {
107                    q_js_rt.run_pending_jobs_if_any();
108                })
109            })
110        });
111    }
112
113    pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
114    where
115        C: FnOnce() -> R + Send + 'static,
116    {
117        self.event_loop.exe(move || {
118            let res = task();
119            EventLoop::add_local_void(|| {
120                QuickJsRuntimeAdapter::do_with(|q_js_rt| {
121                    q_js_rt.run_pending_jobs_if_any();
122                })
123            });
124            res
125        })
126    }
127
128    pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
129    where
130        C: FnOnce() -> R + Send + 'static,
131    {
132        self.event_loop.add(move || {
133            let res = task();
134            EventLoop::add_local_void(|| {
135                QuickJsRuntimeAdapter::do_with(|q_js_rt| {
136                    q_js_rt.run_pending_jobs_if_any();
137                });
138            });
139            res
140        })
141    }
142
143    /// used to add tasks from the worker threads which require run_pending_jobs_if_any to run after it
144    #[allow(dead_code)]
145    pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
146    where
147        C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
148    {
149        EventLoop::add_local_void(move || {
150            QuickJsRuntimeAdapter::do_with(|q_js_rt| {
151                consumer(q_js_rt);
152            });
153            EventLoop::add_local_void(|| {
154                QuickJsRuntimeAdapter::do_with(|q_js_rt| {
155                    q_js_rt.run_pending_jobs_if_any();
156                })
157            })
158        });
159    }
160}
161
162/// EsRuntime is the main public struct representing a JavaScript runtime.
163/// You can construct a new QuickJsRuntime by using the [QuickJsRuntimeBuilder] struct
164/// # Example
165/// ```rust
166/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
167/// let rt = QuickJsRuntimeBuilder::new().build();
168/// ```
169pub struct QuickJsRuntimeFacade {
170    inner: Arc<QuickjsRuntimeFacadeInner>,
171}
172
173impl QuickJsRuntimeFacade {
174    pub(crate) fn new(mut builder: QuickJsRuntimeBuilder) -> Self {
175        let ret = Self {
176            inner: Arc::new(QuickjsRuntimeFacadeInner {
177                event_loop: EventLoop::new(),
178            }),
179        };
180
181        ret.exe_task_in_event_loop(|| {
182            let rt_ptr = unsafe { q::JS_NewRuntime() };
183            let rt = QuickJsRuntimeAdapter::new(rt_ptr);
184            QuickJsRuntimeAdapter::init_rt_for_current_thread(rt);
185            functions::init_statics();
186            reflection::init_statics();
187        });
188
189        // init ref in q_js_rt
190
191        let rti_weak = Arc::downgrade(&ret.inner);
192
193        ret.exe_task_in_event_loop(move || {
194            QuickJsRuntimeAdapter::do_with_mut(move |m_q_js_rt| {
195                m_q_js_rt.init_rti_ref(rti_weak);
196            })
197        });
198
199        // run single job in eventQueue to init thread_local weak<rtref>
200
201        #[cfg(any(
202            feature = "settimeout",
203            feature = "setinterval",
204            feature = "console",
205            feature = "setimmediate"
206        ))]
207        {
208            let res = crate::features::init(&ret);
209            if res.is_err() {
210                panic!("could not init features: {}", res.err().unwrap());
211            }
212        }
213
214        if let Some(interval) = builder.opt_gc_interval {
215            let rti_ref: Weak<QuickjsRuntimeFacadeInner> = Arc::downgrade(&ret.inner);
216            std::thread::spawn(move || loop {
217                std::thread::sleep(interval);
218                if let Some(el) = rti_ref.upgrade() {
219                    log::debug!("running gc from gc interval thread");
220                    el.event_loop.add_void(|| {
221                        QJS_RT
222                            .try_with(|rc| {
223                                let rt = &*rc.borrow();
224                                rt.as_ref().unwrap().gc();
225                            })
226                            .expect("QJS_RT.try_with failed");
227                    });
228                } else {
229                    break;
230                }
231            });
232        }
233
234        let init_hooks: Vec<_> = builder.runtime_init_hooks.drain(..).collect();
235
236        ret.exe_task_in_event_loop(move || {
237            QuickJsRuntimeAdapter::do_with_mut(|q_js_rt| {
238                for native_module_loader in builder.native_module_loaders {
239                    q_js_rt.add_native_module_loader(NativeModuleLoaderAdapter::new(
240                        native_module_loader,
241                    ));
242                }
243                for script_module_loader in builder.script_module_loaders {
244                    q_js_rt.add_script_module_loader(ScriptModuleLoaderAdapter::new(
245                        script_module_loader,
246                    ));
247                }
248                for compiled_module_loader in builder.compiled_module_loaders {
249                    q_js_rt.add_compiled_module_loader(CompiledModuleLoaderAdapter::new(
250                        compiled_module_loader,
251                    ));
252                }
253                q_js_rt.script_pre_processors = builder.script_pre_processors;
254
255                if let Some(limit) = builder.opt_memory_limit_bytes {
256                    unsafe {
257                        q::JS_SetMemoryLimit(q_js_rt.runtime, limit as _);
258                    }
259                }
260                if let Some(threshold) = builder.opt_gc_threshold {
261                    unsafe {
262                        q::JS_SetGCThreshold(q_js_rt.runtime, threshold as _);
263                    }
264                }
265                if let Some(stack_size) = builder.opt_max_stack_size {
266                    unsafe {
267                        q::JS_SetMaxStackSize(q_js_rt.runtime, stack_size as _);
268                    }
269                }
270                if let Some(interrupt_handler) = builder.interrupt_handler {
271                    q_js_rt.set_interrupt_handler(interrupt_handler);
272                }
273            })
274        });
275
276        for hook in init_hooks {
277            match hook(&ret) {
278                Ok(_) => {}
279                Err(e) => {
280                    panic!("runtime_init_hook failed: {}", e);
281                }
282            }
283        }
284
285        ret
286    }
287
288    /// get memory usage for this runtime
289    pub async fn memory_usage(&self) -> MemoryUsage {
290        self.loop_async(|rt| rt.memory_usage()).await
291    }
292
293    pub(crate) fn clear_contexts(&self) {
294        log::trace!("EsRuntime::clear_contexts");
295        self.exe_task_in_event_loop(|| {
296            let context_ids = QuickJsRuntimeAdapter::get_context_ids();
297            for id in context_ids {
298                let _ = QuickJsRuntimeAdapter::remove_context(id.as_str());
299            }
300        });
301    }
302
303    /// this can be used to run a function in the event_queue thread for the QuickJSRuntime
304    /// without borrowing the q_js_rt
305    pub fn add_task_to_event_loop_void<C>(&self, task: C)
306    where
307        C: FnOnce() + Send + 'static,
308    {
309        self.inner.add_task_to_event_loop_void(task)
310    }
311
312    pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
313    where
314        C: FnOnce() -> R + Send + 'static,
315    {
316        self.inner.exe_task_in_event_loop(task)
317    }
318
319    pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
320    where
321        C: FnOnce() -> R + Send + 'static,
322    {
323        self.inner.add_task_to_event_loop(task)
324    }
325
326    /// this is how you add a closure to the worker thread which has an instance of the QuickJsRuntime
327    /// this will run asynchronously
328    /// # example
329    /// ```rust
330    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
331    /// let rt = QuickJsRuntimeBuilder::new().build();
332    /// rt.add_rt_task_to_event_loop(|q_js_rt| {
333    ///     // here you are in the worker thread and you can use the quickjs_utils
334    ///     q_js_rt.gc();
335    /// });
336    /// ```
337    pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
338        &self,
339        task: C,
340    ) -> impl Future<Output = R>
341    where
342        C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
343    {
344        self.inner.add_rt_task_to_event_loop(task)
345    }
346
347    pub fn add_rt_task_to_event_loop_void<C>(&self, task: C)
348    where
349        C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
350    {
351        self.inner.add_rt_task_to_event_loop_void(task)
352    }
353
354    /// used to add tasks from the worker threads which require run_pending_jobs_if_any to run after it
355    #[allow(dead_code)]
356    pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
357    where
358        C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
359    {
360        QuickjsRuntimeFacadeInner::add_local_task_to_event_loop(consumer)
361    }
362
363    pub fn builder() -> QuickJsRuntimeBuilder {
364        QuickJsRuntimeBuilder::new()
365    }
366
367    /// run the garbage collector asynchronously
368    pub async fn gc(&self) {
369        self.add_rt_task_to_event_loop(|q_js_rt| q_js_rt.gc()).await
370    }
371
372    /// run the garbage collector and wait for it to be done
373    pub fn gc_sync(&self) {
374        self.exe_rt_task_in_event_loop(|q_js_rt| q_js_rt.gc())
375    }
376
377    /// this is how you add a closure to the worker thread which has an instance of the QuickJsRuntime
378    /// this will run and return synchronously
379    /// # example
380    /// ```rust
381    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
382    /// use quickjs_runtime::jsutils::Script;
383    /// use quickjs_runtime::quickjs_utils::primitives;
384    /// let rt = QuickJsRuntimeBuilder::new().build();
385    /// let res = rt.exe_rt_task_in_event_loop(|q_js_rt| {
386    ///     let q_ctx = q_js_rt.get_main_realm();
387    ///     // here you are in the worker thread and you can use the quickjs_utils
388    ///     let val_ref = q_ctx.eval(Script::new("test.es", "(11 * 6);")).ok().expect("script failed");
389    ///     primitives::to_i32(&val_ref).ok().expect("could not get i32")
390    /// });
391    /// assert_eq!(res, 66);
392    /// ```
393    pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
394    where
395        C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
396        R: Send + 'static,
397    {
398        self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
399    }
400
401    /// this adds a rust function to JavaScript, it is added for all current and future contexts
402    /// # Example
403    /// ```rust
404    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
405    /// use quickjs_runtime::quickjs_utils::primitives;
406    /// use quickjs_runtime::jsutils::Script;
407    /// use quickjs_runtime::values::{JsValueConvertable, JsValueFacade};
408    ///  
409    /// let rt = QuickJsRuntimeBuilder::new().build();
410    ///
411    /// rt.set_function(&["com", "mycompany", "util"], "methodA", |q_ctx, args: Vec<JsValueFacade>|{
412    ///     let a = args[0].get_i32();
413    ///     let b = args[1].get_i32();
414    ///     Ok((a * b).to_js_value_facade())
415    /// }).expect("set func failed");
416    ///
417    /// let res = rt.eval_sync(None, Script::new("test.es", "let a = com.mycompany.util.methodA(13, 17); a * 2;")).ok().expect("script failed");
418    ///
419    /// assert_eq!(res.get_i32(), (13*17*2));
420    /// ```
421    pub fn set_function<F>(
422        &self,
423        namespace: &[&str],
424        name: &str,
425        function: F,
426    ) -> Result<(), JsError>
427    where
428        F: Fn(&QuickJsRealmAdapter, Vec<JsValueFacade>) -> Result<JsValueFacade, JsError>
429            + Send
430            + 'static,
431    {
432        let name = name.to_string();
433
434        let namespace = namespace
435            .iter()
436            .map(|s| s.to_string())
437            .collect::<Vec<String>>();
438
439        self.exe_rt_task_in_event_loop(move |q_js_rt| {
440            let func_rc = Rc::new(function);
441            let name = name.to_string();
442
443            q_js_rt.add_context_init_hook(move |_q_js_rt, realm| {
444                let namespace_slice = namespace.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
445                let ns = objects::get_namespace_q(realm, &namespace_slice, true)?;
446
447                let func_rc = func_rc.clone();
448
449                let func = functions::new_function_q(
450                    realm,
451                    name.as_str(),
452                    move |realm, _this_ref, args| {
453                        let mut args_facades = vec![];
454
455                        for arg_ref in args {
456                            args_facades.push(realm.to_js_value_facade(arg_ref)?);
457                        }
458
459                        let res = func_rc(realm, args_facades);
460
461                        match res {
462                            Ok(val_jsvf) => realm.from_js_value_facade(val_jsvf),
463                            Err(e) => Err(e),
464                        }
465                    },
466                    1,
467                )?;
468
469                objects::set_property2_q(realm, &ns, name.as_str(), &func, 0)?;
470
471                Ok(())
472            })
473        })
474    }
475
476    /// add a task the the "helper" thread pool
477    pub fn add_helper_task<T>(task: T)
478    where
479        T: FnOnce() + Send + 'static,
480    {
481        log::trace!("adding a helper task");
482        HELPER_TASKS.add_task(task);
483    }
484
485    /// add an async task the the "helper" thread pool
486    pub fn add_helper_task_async<R: Send + 'static, T: Future<Output = R> + Send + 'static>(
487        task: T,
488    ) -> impl Future<Output = Result<R, JoinError>> {
489        log::trace!("adding an async helper task");
490        HELPER_TASKS.add_task_async(task)
491    }
492
493    /// create a new context besides the always existing main_context
494    /// # Example
495    /// ```
496    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
497    /// use quickjs_runtime::jsutils::Script;
498    /// let rt = QuickJsRuntimeBuilder::new().build();
499    /// rt.create_context("my_context");
500    /// rt.exe_rt_task_in_event_loop(|q_js_rt| {
501    ///    let my_ctx = q_js_rt.get_context("my_context");
502    ///    my_ctx.eval(Script::new("ctx_test.es", "this.myVar = 'only exists in my_context';"));
503    /// });
504    /// ```
505    pub fn create_context(&self, id: &str) -> Result<(), JsError> {
506        let id = id.to_string();
507        self.inner
508            .event_loop
509            .exe(move || QuickJsRuntimeAdapter::create_context(id.as_str()))
510    }
511
512    /// drop a context which was created earlier with a call to [create_context()](struct.EsRuntime.html#method.create_context)
513    pub fn drop_context(&self, id: &str) -> anyhow::Result<()> {
514        let id = id.to_string();
515        self.inner
516            .event_loop
517            .exe(move || QuickJsRuntimeAdapter::remove_context(id.as_str()))
518    }
519}
520
521thread_local! {
522    // Each thread has its own LRU cache with capacity 128 to limit the number of auto-created contexts
523    // todo make this configurable via env..
524    static REALM_ID_LRU_CACHE: RefCell<LruCache<String, ()>> = RefCell::new(LruCache::new(NonZeroUsize::new(128).unwrap()));
525}
526
527fn loop_realm_func<
528    R: Send + 'static,
529    C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
530>(
531    realm_name: Option<String>,
532    consumer: C,
533) -> R {
534    // housekeeping, lru map for realms
535    // the problem with doing those in drop in a realm is that finalizers cant find the realm anymore
536    // so we need to actively delete realms here instead of just making runtimeadapter::context a lru cache
537
538    if let Some(realm_str) = realm_name.as_ref() {
539        REALM_ID_LRU_CACHE.with(|cache_cell| {
540            let mut cache = cache_cell.borrow_mut();
541            // it's ok if this str does not yet exist
542            cache.promote(realm_str);
543        });
544    }
545
546    // run in existing realm
547
548    let res: Either<R, C> = QuickJsRuntimeAdapter::do_with(|q_js_rt| {
549        if let Some(realm_str) = realm_name.as_ref() {
550            if let Some(realm) = q_js_rt.get_realm(realm_str) {
551                Left(consumer(q_js_rt, realm))
552            } else {
553                Right(consumer)
554            }
555        } else {
556            Left(consumer(q_js_rt, q_js_rt.get_main_realm()))
557        }
558    });
559
560    match res {
561        Left(r) => r,
562        Right(consumer) => {
563            // create realm first
564            // if more than max present, drop the least used realm
565
566            let realm_str = realm_name.expect("invalid state");
567
568            REALM_ID_LRU_CACHE.with(|cache_cell| {
569                let mut cache = cache_cell.borrow_mut();
570                // it's ok if this str does not yet exist
571                if cache.len() == cache.cap().get() {
572                    if let Some((_evicted_key, _evicted_value)) = cache.pop_lru() {
573                        // cleanup evicted key
574                        //QuickJsRuntimeAdapter::remove_context(evicted_key.as_str())
575                        //    .expect("could not destroy realm");
576                    }
577                }
578                cache.put(realm_str.to_string(), ());
579            });
580
581            // create realm
582
583            QuickJsRuntimeAdapter::do_with_mut(|m_rt| {
584                let ctx = QuickJsRealmAdapter::new(realm_str.to_string(), m_rt);
585                m_rt.contexts.insert(realm_str.to_string(), ctx);
586            });
587
588            QuickJsRuntimeAdapter::do_with(|q_js_rt| {
589                let realm = q_js_rt
590                    .get_realm(realm_str.as_str())
591                    .expect("invalid state");
592                let hooks = &*q_js_rt.context_init_hooks.borrow();
593                for hook in hooks {
594                    let res = hook(q_js_rt, realm);
595                    if res.is_err() {
596                        panic!("realm init hook failed: {}", res.err().unwrap());
597                    }
598                }
599
600                consumer(q_js_rt, realm)
601            })
602        }
603    }
604}
605
606impl QuickJsRuntimeFacade {
607    pub fn create_realm(&self, name: &str) -> Result<(), JsError> {
608        let name = name.to_string();
609        self.inner
610            .event_loop
611            .exe(move || QuickJsRuntimeAdapter::create_context(name.as_str()))
612    }
613
614    pub fn destroy_realm(&self, name: &str) -> anyhow::Result<()> {
615        let name = name.to_string();
616        self.exe_task_in_event_loop(move || QuickJsRuntimeAdapter::remove_context(name.as_str()))
617    }
618
619    pub fn has_realm(&self, name: &str) -> Result<bool, JsError> {
620        let name = name.to_string();
621        self.exe_rt_task_in_event_loop(move |rt| Ok(rt.get_realm(name.as_str()).is_some()))
622    }
623
624    /// add a job to the eventloop which will execute sync(placed at end of eventloop)
625    pub fn loop_sync<R: Send + 'static, C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static>(
626        &self,
627        consumer: C,
628    ) -> R {
629        self.exe_rt_task_in_event_loop(consumer)
630    }
631
632    pub fn loop_sync_mut<
633        R: Send + 'static,
634        C: FnOnce(&mut QuickJsRuntimeAdapter) -> R + Send + 'static,
635    >(
636        &self,
637        consumer: C,
638    ) -> R {
639        self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with_mut(consumer))
640    }
641
642    /// add a job to the eventloop which will execute async(placed at end of eventloop)
643    /// returns a Future which can be waited ob with .await
644    pub fn loop_async<
645        R: Send + 'static,
646        C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
647    >(
648        &self,
649        consumer: C,
650    ) -> Pin<Box<dyn Future<Output = R> + Send>> {
651        Box::pin(self.add_rt_task_to_event_loop(consumer))
652    }
653
654    /// add a job to the eventloop (placed at end of eventloop) without expecting a result
655    pub fn loop_void<C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static>(&self, consumer: C) {
656        self.add_rt_task_to_event_loop_void(consumer)
657    }
658
659    /// add a job to the eventloop which will be executed synchronously (placed at end of eventloop)
660    pub fn loop_realm_sync<
661        R: Send + 'static,
662        C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
663    >(
664        &self,
665        realm_name: Option<&str>,
666        consumer: C,
667    ) -> R {
668        let realm_name = realm_name.map(|s| s.to_string());
669        self.exe_task_in_event_loop(|| loop_realm_func(realm_name, consumer))
670    }
671
672    /// add a job to the eventloop which will be executed async (placed at end of eventloop)
673    /// returns a Future which can be waited ob with .await
674    pub fn loop_realm<
675        R: Send + 'static,
676        C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
677    >(
678        &self,
679        realm_name: Option<&str>,
680        consumer: C,
681    ) -> Pin<Box<dyn Future<Output = R>>> {
682        let realm_name = realm_name.map(|s| s.to_string());
683        Box::pin(self.add_task_to_event_loop(|| loop_realm_func(realm_name, consumer)))
684    }
685
686    /// add a job for a specific realm without expecting a result.
687    /// the job will be added to the end of the eventloop
688    pub fn loop_realm_void<
689        C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) + Send + 'static,
690    >(
691        &self,
692        realm_name: Option<&str>,
693        consumer: C,
694    ) {
695        let realm_name = realm_name.map(|s| s.to_string());
696        self.add_task_to_event_loop_void(|| loop_realm_func(realm_name, consumer));
697    }
698
699    /// Evaluate a script asynchronously
700    /// # Example
701    /// ```rust
702    /// use futures::executor::block_on;
703    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
704    /// use quickjs_runtime::jsutils::Script;
705    /// let rt = QuickJsRuntimeBuilder::new().build();
706    /// let my_script = r#"
707    ///    console.log("i'm a script");
708    /// "#;
709    /// block_on(rt.eval(None, Script::new("my_script.js", my_script))).expect("script failed");
710    /// ```
711    #[allow(clippy::type_complexity)]
712    pub fn eval(
713        &self,
714        realm_name: Option<&str>,
715        script: Script,
716    ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
717        self.loop_realm(realm_name, |_rt, realm| {
718            let res = realm.eval(script);
719            match res {
720                Ok(jsvr) => realm.to_js_value_facade(&jsvr),
721                Err(e) => Err(e),
722            }
723        })
724    }
725
726    /// Evaluate a script and return the result synchronously
727    /// # example
728    /// ```rust
729    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
730    /// use quickjs_runtime::jsutils::Script;
731    /// let rt = QuickJsRuntimeBuilder::new().build();
732    /// let script = Script::new("my_file.js", "(9 * 3);");
733    /// let res = rt.eval_sync(None, script).ok().expect("script failed");
734    /// assert_eq!(res.get_i32(), 27);
735    /// ```
736    #[allow(clippy::type_complexity)]
737    pub fn eval_sync(
738        &self,
739        realm_name: Option<&str>,
740        script: Script,
741    ) -> Result<JsValueFacade, JsError> {
742        self.loop_realm_sync(realm_name, |_rt, realm| {
743            let res = realm.eval(script);
744            match res {
745                Ok(jsvr) => realm.to_js_value_facade(&jsvr),
746                Err(e) => Err(e),
747            }
748        })
749    }
750
751    /// evaluate a module, you need this if you want to compile a script that contains static imports
752    /// e.g.
753    /// ```javascript
754    /// import {util} from 'file.js';
755    /// console.log(util(1, 2, 3));
756    /// ```
757    /// please note that the module is cached under the absolute path you passed in the Script object
758    /// and thus you should take care to make the path unique (hence the absolute_ name)
759    /// also to use this you need to build the QuickJsRuntimeFacade with a module loader
760    /// # example
761    /// ```rust
762    /// use futures::executor::block_on;
763    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
764    /// use quickjs_runtime::jsutils::modules::ScriptModuleLoader;
765    /// use quickjs_runtime::jsutils::Script;
766    /// use quickjs_runtime::quickjsrealmadapter::QuickJsRealmAdapter;
767    /// struct TestModuleLoader {}
768    /// impl ScriptModuleLoader for TestModuleLoader {
769    ///     fn normalize_path(&self, _realm: &QuickJsRealmAdapter, ref_path: &str,path: &str) -> Option<String> {
770    ///         Some(path.to_string())
771    ///     }
772    ///
773    ///     fn load_module(&self, _realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
774    ///         "export const util = function(a, b, c){return a+b+c;};".to_string()
775    ///     }
776    /// }
777    /// let rt = QuickJsRuntimeBuilder::new().script_module_loader(TestModuleLoader{}).build();
778    /// let script = Script::new("/opt/files/my_module.js", r#"
779    ///     import {util} from 'other_module.js';\n
780    ///     console.log(util(1, 2, 3));
781    /// "#);
782    /// // in real life you would .await this
783    /// let _res = block_on(rt.eval_module(None, script));
784    /// ```
785    pub fn eval_module(
786        &self,
787        realm_name: Option<&str>,
788        script: Script,
789    ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
790        self.loop_realm(realm_name, |_rt, realm| {
791            let res = realm.eval_module(script)?;
792            realm.to_js_value_facade(&res)
793        })
794    }
795
796    /// evaluate a module synchronously, you need this if you want to compile a script that contains static imports
797    /// e.g.
798    /// ```javascript
799    /// import {util} from 'file.js';
800    /// console.log(util(1, 2, 3));
801    /// ```
802    /// please note that the module is cached under the absolute path you passed in the Script object
803    /// and thus you should take care to make the path unique (hence the absolute_ name)
804    /// also to use this you need to build the QuickJsRuntimeFacade with a module loader
805    /// # example
806    /// ```rust
807    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
808    /// use quickjs_runtime::jsutils::modules::ScriptModuleLoader;
809    /// use quickjs_runtime::jsutils::Script;
810    /// use quickjs_runtime::quickjsrealmadapter::QuickJsRealmAdapter;
811    /// struct TestModuleLoader {}
812    /// impl ScriptModuleLoader for TestModuleLoader {
813    ///     fn normalize_path(&self, _realm: &QuickJsRealmAdapter, ref_path: &str,path: &str) -> Option<String> {
814    ///         Some(path.to_string())
815    ///     }
816    ///
817    ///     fn load_module(&self, _realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
818    ///         "export const util = function(a, b, c){return a+b+c;};".to_string()
819    ///     }
820    /// }
821    /// let rt = QuickJsRuntimeBuilder::new().script_module_loader(TestModuleLoader{}).build();
822    /// let script = Script::new("/opt/files/my_module.js", r#"
823    ///     import {util} from 'other_module.js';\n
824    ///     console.log(util(1, 2, 3));
825    /// "#);
826    /// let _res = rt.eval_module_sync(None, script);
827    /// ```
828    pub fn eval_module_sync(
829        &self,
830        realm_name: Option<&str>,
831        script: Script,
832    ) -> Result<JsValueFacade, JsError> {
833        self.loop_realm_sync(realm_name, |_rt, realm| {
834            let res = realm.eval_module(script)?;
835            realm.to_js_value_facade(&res)
836        })
837    }
838
839    /// invoke a function in the engine and get the result synchronously
840    /// # example
841    /// ```rust
842    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
843    /// use quickjs_runtime::jsutils::Script;
844    /// use quickjs_runtime::values::JsValueConvertable;
845    /// let rt = QuickJsRuntimeBuilder::new().build();
846    /// let script = Script::new("my_file.es", "this.com = {my: {methodA: function(a, b, someStr, someBool){return a*b;}}};");
847    /// rt.eval_sync(None, script).ok().expect("script failed");
848    /// let res = rt.invoke_function_sync(None, &["com", "my"], "methodA", vec![7i32.to_js_value_facade(), 5i32.to_js_value_facade(), "abc".to_js_value_facade(), true.to_js_value_facade()]).ok().expect("func failed");
849    /// assert_eq!(res.get_i32(), 35);
850    /// ```
851    #[warn(clippy::type_complexity)]
852    pub fn invoke_function_sync(
853        &self,
854        realm_name: Option<&str>,
855        namespace: &[&str],
856        method_name: &str,
857        args: Vec<JsValueFacade>,
858    ) -> Result<JsValueFacade, JsError> {
859        let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
860        let movable_method_name = method_name.to_string();
861
862        self.loop_realm_sync(realm_name, move |_rt, realm| {
863            let args_adapters: Vec<QuickJsValueAdapter> = args
864                .into_iter()
865                .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
866                .collect();
867
868            let namespace = movable_namespace
869                .iter()
870                .map(|s| s.as_str())
871                .collect::<Vec<&str>>();
872
873            let res = realm.invoke_function_by_name(
874                namespace.as_slice(),
875                movable_method_name.as_str(),
876                args_adapters.as_slice(),
877            );
878
879            match res {
880                Ok(jsvr) => realm.to_js_value_facade(&jsvr),
881                Err(e) => Err(e),
882            }
883        })
884    }
885
886    /// invoke a function in the engine asynchronously
887    /// N.B. func_name is not a &str because of <https://github.com/rust-lang/rust/issues/56238> (i think)
888    /// # example
889    /// ```rust
890    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
891    /// use quickjs_runtime::jsutils::Script;
892    /// use quickjs_runtime::values::JsValueConvertable;
893    /// let rt = QuickJsRuntimeBuilder::new().build();
894    /// let script = Script::new("my_file.es", "this.com = {my: {methodA: function(a, b){return a*b;}}};");
895    /// rt.eval_sync(None, script).ok().expect("script failed");
896    /// rt.invoke_function(None, &["com", "my"], "methodA", vec![7.to_js_value_facade(), 5.to_js_value_facade()]);
897    /// ```
898    #[allow(clippy::type_complexity)]
899    pub fn invoke_function(
900        &self,
901        realm_name: Option<&str>,
902        namespace: &[&str],
903        method_name: &str,
904        args: Vec<JsValueFacade>,
905    ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
906        let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
907        let movable_method_name = method_name.to_string();
908
909        self.loop_realm(realm_name, move |_rt, realm| {
910            let args_adapters: Vec<QuickJsValueAdapter> = args
911                .into_iter()
912                .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
913                .collect();
914
915            let namespace = movable_namespace
916                .iter()
917                .map(|s| s.as_str())
918                .collect::<Vec<&str>>();
919
920            let res = realm.invoke_function_by_name(
921                namespace.as_slice(),
922                movable_method_name.as_str(),
923                args_adapters.as_slice(),
924            );
925
926            match res {
927                Ok(jsvr) => realm.to_js_value_facade(&jsvr),
928                Err(e) => Err(e),
929            }
930        })
931    }
932
933    pub fn invoke_function_void(
934        &self,
935        realm_name: Option<&str>,
936        namespace: &[&str],
937        method_name: &str,
938        args: Vec<JsValueFacade>,
939    ) {
940        let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
941        let movable_method_name = method_name.to_string();
942
943        self.loop_realm_void(realm_name, move |_rt, realm| {
944            let args_adapters: Vec<QuickJsValueAdapter> = args
945                .into_iter()
946                .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
947                .collect();
948
949            let namespace = movable_namespace
950                .iter()
951                .map(|s| s.as_str())
952                .collect::<Vec<&str>>();
953
954            let res = realm
955                .invoke_function_by_name(
956                    namespace.as_slice(),
957                    movable_method_name.as_str(),
958                    args_adapters.as_slice(),
959                )
960                .map(|jsvr| realm.to_js_value_facade(&jsvr));
961
962            match res {
963                Ok(_) => {
964                    log::trace!(
965                        "js_function_invoke_void succeeded: {}",
966                        movable_method_name.as_str()
967                    );
968                }
969                Err(err) => {
970                    log::trace!(
971                        "js_function_invoke_void failed: {}: {}",
972                        movable_method_name.as_str(),
973                        err
974                    );
975                }
976            }
977        })
978    }
979}
980
981#[cfg(test)]
982lazy_static! {
983    static ref INITTED: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
984}
985
986#[cfg(test)]
987pub mod tests {
988    use crate::facades::QuickJsRuntimeFacade;
989    use crate::jsutils::modules::{NativeModuleLoader, ScriptModuleLoader};
990    use crate::jsutils::JsError;
991    use crate::jsutils::Script;
992    use crate::quickjs_utils::{primitives, promises};
993    use crate::quickjsrealmadapter::QuickJsRealmAdapter;
994    use crate::quickjsvalueadapter::QuickJsValueAdapter;
995    use crate::values::{JsValueConvertable, JsValueFacade};
996    use backtrace::Backtrace;
997    use futures::executor::block_on;
998    use log::debug;
999    use std::panic;
1000    use std::time::Duration;
1001
1002    struct TestNativeModuleLoader {}
1003    struct TestScriptModuleLoader {}
1004
1005    impl NativeModuleLoader for TestNativeModuleLoader {
1006        fn has_module(&self, _q_ctx: &QuickJsRealmAdapter, module_name: &str) -> bool {
1007            module_name.starts_with("greco://")
1008        }
1009
1010        fn get_module_export_names(
1011            &self,
1012            _q_ctx: &QuickJsRealmAdapter,
1013            _module_name: &str,
1014        ) -> Vec<&str> {
1015            vec!["a", "b", "c"]
1016        }
1017
1018        fn get_module_exports(
1019            &self,
1020            _q_ctx: &QuickJsRealmAdapter,
1021            _module_name: &str,
1022        ) -> Vec<(&str, QuickJsValueAdapter)> {
1023            vec![
1024                ("a", primitives::from_i32(1234)),
1025                ("b", primitives::from_i32(64834)),
1026                ("c", primitives::from_i32(333)),
1027            ]
1028        }
1029    }
1030
1031    impl ScriptModuleLoader for TestScriptModuleLoader {
1032        fn normalize_path(
1033            &self,
1034            _realm: &QuickJsRealmAdapter,
1035            _ref_path: &str,
1036            path: &str,
1037        ) -> Option<String> {
1038            if path.eq("notfound.mes") || path.starts_with("greco://") {
1039                None
1040            } else {
1041                Some(path.to_string())
1042            }
1043        }
1044
1045        fn load_module(&self, _realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
1046            if absolute_path.eq("notfound.mes") || absolute_path.starts_with("greco://") {
1047                panic!("tht realy should not happen");
1048            } else if absolute_path.eq("invalid.mes") {
1049                "I am the great cornholio! thou'gh shalt&s not p4arse mie!".to_string()
1050            } else {
1051                "export const foo = 'bar';\nexport const mltpl = function(a, b){return a*b;}; globalThis;".to_string()
1052            }
1053        }
1054    }
1055
1056    #[test]
1057    fn test_rt_drop() {
1058        let rt = init_test_rt();
1059        log::trace!("before drop");
1060
1061        drop(rt);
1062        log::trace!("after before drop");
1063        std::thread::sleep(Duration::from_secs(5));
1064        log::trace!("after sleep");
1065    }
1066
1067    #[test]
1068    pub fn test_stack_size() {
1069        let rt = init_test_rt();
1070        // 20 is ok, 200 fails
1071        let res = rt.eval_sync(
1072            None,
1073            Script::new(
1074                "stack_test.js",
1075                "let f = function(a){let f2 = arguments.callee; if (a < 20) {f2(a + 1);}}; f(1);",
1076            ),
1077        );
1078        match res {
1079            Ok(_) => {}
1080            Err(e) => {
1081                log::error!("fail: {}", e);
1082                panic!("fail: {}", e);
1083            }
1084        }
1085
1086        let res = rt.eval_sync(
1087            None,
1088            Script::new(
1089                "stack_test.js",
1090                "let f = function(a){let f2 = arguments.callee; if (a < 1000) {f2(a + 1);}}; f(1);",
1091            ),
1092        );
1093        if res.is_ok() {
1094            panic!("stack should have overflowed");
1095        }
1096    }
1097
1098    pub fn init_logging() {
1099        {
1100            let i_lock = &mut *crate::facades::INITTED.lock().unwrap();
1101            if !*i_lock {
1102                panic::set_hook(Box::new(|panic_info| {
1103                    let backtrace = Backtrace::new();
1104                    println!("thread panic occurred: {panic_info}\nbacktrace: {backtrace:?}");
1105                    log::error!(
1106                        "thread panic occurred: {}\nbacktrace: {:?}",
1107                        panic_info,
1108                        backtrace
1109                    );
1110                }));
1111
1112                simple_logging::log_to_file("./quickjs_runtime.log", log::LevelFilter::max())
1113                    .expect("could not init logger");
1114
1115                *i_lock = true;
1116            }
1117        }
1118    }
1119
1120    pub fn init_test_rt() -> QuickJsRuntimeFacade {
1121        init_logging();
1122
1123        QuickJsRuntimeFacade::builder()
1124            .gc_interval(Duration::from_secs(1))
1125            .max_stack_size(128 * 1024)
1126            .script_module_loader(TestScriptModuleLoader {})
1127            .native_module_loader(TestNativeModuleLoader {})
1128            .build()
1129    }
1130
1131    #[test]
1132    fn test_func() {
1133        let rt = init_test_rt();
1134        let res = rt.set_function(&["nl", "my", "utils"], "methodA", |_q_ctx, args| {
1135            if args.len() != 2 || !args.first().unwrap().is_i32() || !args.get(1).unwrap().is_i32()
1136            {
1137                Err(JsError::new_str(
1138                    "i'd really like 2 args of the int32 kind please",
1139                ))
1140            } else {
1141                let a = args.first().unwrap().get_i32();
1142                let b = args.get(1).unwrap().get_i32();
1143                Ok((a * b).to_js_value_facade())
1144            }
1145        });
1146
1147        match res {
1148            Ok(_) => {}
1149            Err(e) => {
1150                panic!("set_function failed: {}", e);
1151            }
1152        }
1153
1154        let res = rt.eval_sync(
1155            None,
1156            Script::new("test_func.es", "(nl.my.utils.methodA(13, 56));"),
1157        );
1158
1159        match res {
1160            Ok(val) => {
1161                assert!(val.is_i32());
1162                assert_eq!(val.get_i32(), 13 * 56);
1163            }
1164            Err(e) => {
1165                panic!("test_func.es failed: {}", e);
1166            }
1167        }
1168    }
1169
1170    #[test]
1171    fn test_eval_sync() {
1172        let rt = init_test_rt();
1173        let res = rt.eval_sync(None, Script::new("test.es", "console.log('foo bar');"));
1174
1175        match res {
1176            Ok(_) => {}
1177            Err(e) => {
1178                panic!("eval failed: {}", e);
1179            }
1180        }
1181
1182        let res = rt
1183            .eval_sync(None, Script::new("test.es", "(2 * 7);"))
1184            .expect("script failed");
1185
1186        assert_eq!(res.get_i32(), 14);
1187    }
1188
1189    #[test]
1190    fn t1234() {
1191        // test stack overflow
1192        let rt = init_test_rt();
1193
1194        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1195            //q_js_rt.run_pending_jobs_if_any();
1196            let q_ctx = q_js_rt.get_main_realm();
1197            let r = q_ctx.eval(Script::new(
1198                "test_async.es",
1199                "let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f();",
1200            )).ok().unwrap();
1201            log::trace!("tag = {}", r.get_tag());
1202            //std::thread::sleep(Duration::from_secs(1));
1203
1204            assert!(promises::is_promise_q(q_ctx, &r));
1205
1206            if promises::is_promise_q(q_ctx, &r) {
1207                log::info!("r IS a Promise");
1208            } else {
1209                log::error!("r is NOT a Promise");
1210            }
1211
1212            std::thread::sleep(Duration::from_secs(1));
1213
1214            //q_js_rt.run_pending_jobs_if_any();
1215        });
1216        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1217            q_js_rt.run_pending_jobs_if_any();
1218        });
1219
1220        std::thread::sleep(Duration::from_secs(1));
1221    }
1222
1223    #[test]
1224    fn test_eval_await() {
1225        let rt = init_test_rt();
1226
1227        let res = rt.eval_sync(None, Script::new(
1228            "test_async.es",
1229            "{let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f()};",
1230        ));
1231
1232        match res {
1233            Ok(esvf) => {
1234                assert!(esvf.is_js_promise());
1235                match esvf {
1236                    JsValueFacade::JsPromise { cached_promise } => {
1237                        let p_res = cached_promise
1238                            .get_promise_result_sync()
1239                            .expect("promise timed out");
1240                        if p_res.is_err() {
1241                            panic!("{:?}", p_res.err().unwrap());
1242                        }
1243                        let res = p_res.ok().unwrap();
1244                        assert!(res.is_i32());
1245                        assert_eq!(res.get_i32(), 12345);
1246                    }
1247                    _ => {}
1248                }
1249            }
1250            Err(e) => {
1251                panic!("eval failed: {}", e);
1252            }
1253        }
1254    }
1255
1256    #[test]
1257    fn test_promise() {
1258        let rt = init_test_rt();
1259
1260        let res = rt.eval_sync(None, Script::new(
1261            "testp2.es",
1262            "let test_promise_P = (new Promise(function(res, rej) {console.log('before res');res(123);console.log('after res');}).then(function (a) {console.log('prom ressed to ' + a);}).catch(function(x) {console.log('p.ca ex=' + x);}))",
1263        ));
1264
1265        match res {
1266            Ok(_) => {}
1267            Err(e) => panic!("p script failed: {}", e),
1268        }
1269        std::thread::sleep(Duration::from_secs(1));
1270    }
1271
1272    #[test]
1273    fn test_module_sync() {
1274        log::info!("> test_module_sync");
1275
1276        let rt = init_test_rt();
1277        debug!("test static import");
1278        let res: Result<JsValueFacade, JsError> = rt.eval_module_sync(
1279            None,
1280            Script::new(
1281                "test.es",
1282                "import {foo} from 'test_module.mes';\n console.log('static imp foo = ' + foo);",
1283            ),
1284        );
1285
1286        match res {
1287            Ok(_) => {
1288                log::debug!("static import ok");
1289            }
1290            Err(e) => {
1291                log::error!("static import failed: {}", e);
1292            }
1293        }
1294
1295        debug!("test dynamic import");
1296        let res: Result<JsValueFacade, JsError> = rt.eval_sync(None, Script::new(
1297            "test_dyn.es",
1298            "console.log('about to load dynamic module');let dyn_p = import('test_module.mes');dyn_p.then(function (some) {console.log('after dyn');console.log('after dyn ' + typeof some);console.log('mltpl 5, 7 = ' + some.mltpl(5, 7));});dyn_p.catch(function (x) {console.log('imp.cat x=' + x);});console.log('dyn done');",
1299        ));
1300
1301        match res {
1302            Ok(_) => {
1303                log::debug!("dynamic import ok");
1304            }
1305            Err(e) => {
1306                log::error!("dynamic import failed: {}", e);
1307            }
1308        }
1309        std::thread::sleep(Duration::from_secs(1));
1310
1311        log::info!("< test_module_sync");
1312    }
1313
1314    async fn test_async1() -> i32 {
1315        let rt = init_test_rt();
1316
1317        let a = rt
1318            .eval(None, Script::new("test_async.es", "122 + 1;"))
1319            .await;
1320        match a {
1321            Ok(a) => a.get_i32(),
1322            Err(e) => panic!("script failed: {}", e),
1323        }
1324    }
1325
1326    #[test]
1327    fn test_async() {
1328        let fut = test_async1();
1329        let res = block_on(fut);
1330        assert_eq!(res, 123);
1331    }
1332}
1333
1334#[cfg(test)]
1335pub mod abstraction_tests {
1336    use crate::builder::QuickJsRuntimeBuilder;
1337    use crate::facades::tests::init_test_rt;
1338    use crate::facades::QuickJsRuntimeFacade;
1339    use crate::jsutils::Script;
1340    use crate::values::JsValueFacade;
1341    use futures::executor::block_on;
1342    use serde::Deserialize;
1343    use serde::Serialize;
1344
1345    async fn example(rt: &QuickJsRuntimeFacade) -> JsValueFacade {
1346        // add a job for the main realm (None as realm_name)
1347        rt.loop_realm(None, |_rt_adapter, realm_adapter| {
1348            let script = Script::new("example.js", "7 + 13");
1349            let value_adapter = realm_adapter.eval(script).expect("script failed");
1350            // convert value_adapter to value_facade because value_adapter is not Send
1351            realm_adapter
1352                .to_js_value_facade(&value_adapter)
1353                .expect("conversion failed")
1354        })
1355        .await
1356    }
1357
1358    #[test]
1359    fn test1() {
1360        // start a new runtime
1361        let rt = QuickJsRuntimeBuilder::new().build();
1362        let val = block_on(example(&rt));
1363        if let JsValueFacade::I32 { val } = val {
1364            assert_eq!(val, 20);
1365        } else {
1366            panic!("not an i32");
1367        }
1368    }
1369
1370    #[tokio::test]
1371    async fn test_serde() {
1372        let json = r#"
1373            {
1374                "a": 1,
1375                "b": true,
1376                "c": {
1377                    "d": "q",
1378                    "e": [1, 2, 3.3]
1379                }
1380            }
1381        "#;
1382
1383        let value = serde_json::from_str::<serde_json::Value>(json).expect("json fail");
1384        let input: JsValueFacade = JsValueFacade::SerdeValue { value };
1385        let rt = init_test_rt();
1386
1387        let _ = rt.eval(None, Script::new("t.js", r#"
1388            function testSerde(input) {
1389                return "" + input.a + input.b + input.c.d + input.c.e[0] + input.c.e[1] + input.c.e[2];
1390            }
1391        "#)).await.expect("script failed");
1392
1393        let res = rt
1394            .invoke_function(None, &[], "testSerde", vec![input])
1395            .await
1396            .expect("func failed");
1397
1398        assert!(res.is_string());
1399        assert_eq!(res.get_str(), "1trueq123.3");
1400    }
1401
1402    #[derive(Serialize, Deserialize)]
1403    #[serde(rename_all = "camelCase")]
1404    struct User {
1405        name: String,
1406        last_name: String,
1407    }
1408
1409    #[tokio::test]
1410    async fn serde_tests_serialize() {
1411        let rtb: QuickJsRuntimeBuilder = QuickJsRuntimeBuilder::new();
1412        let rt = rtb.build();
1413
1414        // init my function
1415        rt.eval(
1416            None,
1417            Script::new(
1418                "test.js",
1419                r#"
1420                function myTest(user) {
1421                    return {
1422                        name: "proc_" + user.name,
1423                        lastName: "proc_" + user.lastName
1424                    }
1425                }
1426                "#,
1427            ),
1428        )
1429        .await
1430        .expect("script failed");
1431
1432        // create a user obj
1433        let test_user_input = User {
1434            last_name: "Anderson".to_string(),
1435            name: "Mister".to_string(),
1436        };
1437
1438        let args = vec![JsValueFacade::from_serializable(&test_user_input)
1439            .expect("could not serialize to JsValueFacade")];
1440
1441        let res: JsValueFacade = rt
1442            .invoke_function(None, &[], "myTest", args)
1443            .await
1444            .expect("func failed");
1445
1446        let json_result = res
1447            .to_json_string()
1448            .await
1449            .expect("could not serialize to json");
1450
1451        assert_eq!(
1452            json_result.as_str(),
1453            r#"{"name":"proc_Mister","lastName":"proc_Anderson"}"#
1454        );
1455
1456        // serialize back to user
1457        let user_output: User = serde_json::from_str(json_result.as_str()).unwrap();
1458        assert_eq!(user_output.name.as_str(), "proc_Mister");
1459        assert_eq!(user_output.last_name.as_str(), "proc_Anderson");
1460    }
1461
1462    #[tokio::test]
1463    async fn serde_tests_value() {
1464        let rtb: QuickJsRuntimeBuilder = QuickJsRuntimeBuilder::new();
1465        let rt = rtb.build();
1466
1467        // init my function
1468        rt.eval(
1469            None,
1470            Script::new(
1471                "test.js",
1472                r#"
1473                function myTest(user) {
1474                    return {
1475                        name: "proc_" + user.name,
1476                        lastName: "proc_" + user.lastName
1477                    }
1478                }
1479                "#,
1480            ),
1481        )
1482        .await
1483        .expect("script failed");
1484
1485        // create a user obj
1486        let test_user_input = User {
1487            last_name: "Anderson".to_string(),
1488            name: "Mister".to_string(),
1489        };
1490
1491        let input_value: serde_json::Value =
1492            serde_json::to_value(test_user_input).expect("could not to_value");
1493        let args = vec![JsValueFacade::SerdeValue { value: input_value }];
1494
1495        let res: JsValueFacade = rt
1496            .invoke_function(None, &[], "myTest", args)
1497            .await
1498            .expect("func failed");
1499
1500        // as value
1501        let value_result: serde_json::Value = res
1502            .to_serde_value()
1503            .await
1504            .expect("could not serialize to json");
1505
1506        assert!(value_result.is_object());
1507
1508        // serialize back to user
1509        let user_output: User = serde_json::from_value(value_result).unwrap();
1510        assert_eq!(user_output.name.as_str(), "proc_Mister");
1511        assert_eq!(user_output.last_name.as_str(), "proc_Anderson");
1512    }
1513
1514    #[tokio::test]
1515    async fn test_realm_lifetime() -> anyhow::Result<()> {
1516        let rt = QuickJsRuntimeBuilder::new().build();
1517
1518        rt.add_rt_task_to_event_loop(|rt| {
1519            println!("ctx list: [{}]", rt.list_contexts().join(",").as_str());
1520        })
1521        .await;
1522
1523        for x in 0..10240 {
1524            let rid = format!("x_{x}");
1525            let _ = rt
1526                .eval(Some(rid.as_str()), Script::new("x.js", "const a = 1;"))
1527                .await;
1528        }
1529
1530        rt.add_rt_task_to_event_loop(|rt| {
1531            println!("ctx list: [{}]", rt.list_contexts().join(",").as_str());
1532        })
1533        .await;
1534
1535        for x in 0..8 {
1536            let rid = format!("x_{x}");
1537            let _ = rt
1538                .eval(Some(rid.as_str()), Script::new("x.js", "const a = 1;"))
1539                .await;
1540        }
1541
1542        rt.add_rt_task_to_event_loop(|rt| {
1543            println!("ctx list: [{}]", rt.list_contexts().join(",").as_str());
1544        })
1545        .await;
1546
1547        Ok(())
1548    }
1549}