Skip to main content

quickjs_runtime/
facades.rs

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