quickjs_runtime/
builder.rs

1//! contains the QuickJsRuntimeBuilder which may be used to instantiate a new QuickjsRuntimeFacade
2
3use crate::facades::QuickJsRuntimeFacade;
4use crate::quickjsrealmadapter::QuickJsRealmAdapter;
5use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
6
7use crate::jsutils::modules::{CompiledModuleLoader, NativeModuleLoader, ScriptModuleLoader};
8use crate::jsutils::{JsError, ScriptPreProcessor};
9use std::time::Duration;
10
11pub type EsRuntimeInitHooks =
12    Vec<Box<dyn FnOnce(&QuickJsRuntimeFacade) -> Result<(), JsError> + Send + 'static>>;
13
14/// the EsRuntimeBuilder is used to init an EsRuntime
15/// # Example
16/// ```rust
17/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
18/// // init a rt which may use 16MB of memory
19/// let rt = QuickJsRuntimeBuilder::new()
20/// .memory_limit(1024*1024*16)
21/// .build();
22/// ```
23pub struct QuickJsRuntimeBuilder {
24    pub(crate) script_module_loaders: Vec<Box<dyn ScriptModuleLoader + Send>>,
25    pub(crate) native_module_loaders: Vec<Box<dyn NativeModuleLoader + Send>>,
26    pub(crate) compiled_module_loaders: Vec<Box<dyn CompiledModuleLoader + Send>>,
27    pub(crate) opt_memory_limit_bytes: Option<u64>,
28    pub(crate) opt_gc_threshold: Option<u64>,
29    pub(crate) opt_max_stack_size: Option<u64>,
30    pub(crate) opt_gc_interval: Option<Duration>,
31    pub(crate) runtime_init_hooks: EsRuntimeInitHooks,
32    pub(crate) script_pre_processors: Vec<Box<dyn ScriptPreProcessor + Send>>,
33    #[allow(clippy::type_complexity)]
34    pub(crate) interrupt_handler: Option<Box<dyn Fn(&QuickJsRuntimeAdapter) -> bool + Send>>,
35}
36
37impl QuickJsRuntimeBuilder {
38    /// build an EsRuntime
39    pub fn build(self) -> QuickJsRuntimeFacade {
40        log::debug!("QuickJsRuntimeBuilder.build");
41        QuickJsRuntimeFacade::new(self)
42    }
43
44    /// init a new EsRuntimeBuilder
45    pub fn new() -> Self {
46        Self {
47            script_module_loaders: vec![],
48            native_module_loaders: vec![],
49            compiled_module_loaders: vec![],
50            opt_memory_limit_bytes: None,
51            opt_gc_threshold: None,
52            opt_max_stack_size: None,
53            opt_gc_interval: None,
54            runtime_init_hooks: vec![],
55            script_pre_processors: vec![],
56            interrupt_handler: None,
57        }
58    }
59
60    /// add a script loaders which will be used to load modules when they are imported from script
61    /// # Example
62    /// ```rust
63    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
64    /// use quickjs_runtime::jsutils::modules::ScriptModuleLoader;
65    /// use quickjs_runtime::quickjsrealmadapter::QuickJsRealmAdapter;
66    /// use quickjs_runtime::jsutils::Script;
67    /// struct MyModuleLoader {}
68    /// impl ScriptModuleLoader for MyModuleLoader {
69    ///     fn normalize_path(&self, realm: &QuickJsRealmAdapter ,ref_path: &str,path: &str) -> Option<String> {
70    ///         Some(path.to_string())
71    ///     }
72    ///
73    ///     fn load_module(&self, realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
74    ///         "export const foo = 12;".to_string()
75    ///     }
76    /// }
77    ///
78    /// let rt = QuickJsRuntimeBuilder::new()
79    ///     .script_module_loader(MyModuleLoader{})
80    ///     .build();
81    /// rt.eval_module_sync(None, Script::new("test_module.es", "import {foo} from 'some_module.mes';\nconsole.log('foo = %s', foo);")).ok().unwrap();
82    /// ```
83    pub fn script_module_loader<M: ScriptModuleLoader + Send + 'static>(
84        mut self,
85        loader: M,
86    ) -> Self {
87        self.script_module_loaders.push(Box::new(loader));
88        self
89    }
90
91    /// add a ScriptPreProcessor which will be called for all scripts which are evaluated and compiled
92    pub fn script_pre_processor<S: ScriptPreProcessor + Send + 'static>(
93        mut self,
94        processor: S,
95    ) -> Self {
96        self.script_pre_processors.push(Box::new(processor));
97        self
98    }
99
100    /// add a module loader which can load native functions and proxy classes
101    /// # Example
102    /// ```rust
103    /// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
104    /// use quickjs_runtime::jsutils::modules::NativeModuleLoader;
105    /// use quickjs_runtime::jsutils::Script;
106    /// use quickjs_runtime::quickjsvalueadapter::QuickJsValueAdapter;
107    /// use quickjs_runtime::quickjsrealmadapter::QuickJsRealmAdapter;
108    /// use quickjs_runtime::quickjs_utils::functions;
109    /// use quickjs_runtime::quickjs_utils::primitives::{from_bool, from_i32};
110    /// use quickjs_runtime::reflection::Proxy;
111    ///
112    /// struct MyModuleLoader{}
113    /// impl NativeModuleLoader for MyModuleLoader {
114    ///     fn has_module(&self, _q_ctx: &QuickJsRealmAdapter,module_name: &str) -> bool {
115    ///         module_name.eq("my_module")
116    ///     }
117    ///
118    ///     fn get_module_export_names(&self, _q_ctx: &QuickJsRealmAdapter, _module_name: &str) -> Vec<&str> {
119    ///         vec!["someVal", "someFunc", "SomeClass"]
120    ///     }
121    ///
122    ///     fn get_module_exports(&self, q_ctx: &QuickJsRealmAdapter, _module_name: &str) -> Vec<(&str, QuickJsValueAdapter)> {
123    ///         
124    ///         let js_val = from_i32(1470);
125    ///         let js_func = functions::new_function_q(
126    ///             q_ctx,
127    ///             "someFunc", |_q_ctx, _this, _args| {
128    ///                 return Ok(from_i32(432));
129    ///             }, 0)
130    ///             .ok().unwrap();
131    ///         let js_class = Proxy::new()
132    ///             .name("SomeClass")
133    ///             .static_method("doIt", |_rt, _q_ctx, _args|{
134    ///                 return Ok(from_i32(185));
135    ///             })
136    ///             .install(q_ctx, false)
137    ///             .ok().unwrap();
138    ///
139    ///         vec![("someVal", js_val), ("someFunc", js_func), ("SomeClass", js_class)]
140    ///     }
141    /// }
142    ///
143    /// let rt = QuickJsRuntimeBuilder::new()
144    /// .native_module_loader(MyModuleLoader{})
145    /// .build();
146    ///
147    /// rt.eval_module_sync(None, Script::new("test_native_mod.es", "import {someVal, someFunc, SomeClass} from 'my_module';\nlet i = (someVal + someFunc() + SomeClass.doIt());\nif (i !== 2087){throw Error('i was not 2087');}")).ok().expect("script failed");
148    /// ```
149    pub fn native_module_loader<S: NativeModuleLoader + Send + 'static>(
150        mut self,
151        module_loader: S,
152    ) -> Self
153    where
154        Self: Sized,
155    {
156        self.native_module_loaders.push(Box::new(module_loader));
157        self
158    }
159
160    /// set max memory the runtime may use
161    pub fn memory_limit(mut self, bytes: u64) -> Self {
162        self.opt_memory_limit_bytes = Some(bytes);
163        self
164    }
165
166    /// number of allocations before gc is run
167    pub fn gc_threshold(mut self, size: u64) -> Self {
168        self.opt_gc_threshold = Some(size);
169        self
170    }
171
172    /// set a max stack size
173    pub fn max_stack_size(mut self, size: u64) -> Self {
174        self.opt_max_stack_size = Some(size);
175        self
176    }
177
178    /// set a Garbage Collection interval, this will start a timer thread which will trigger a full GC every set interval
179    pub fn gc_interval(mut self, interval: Duration) -> Self {
180        self.opt_gc_interval = Some(interval);
181        self
182    }
183
184    /// add an interrupt handler, this will be called several times during script execution and may be used to cancel a running script
185    pub fn set_interrupt_handler<I: Fn(&QuickJsRuntimeAdapter) -> bool + Send + 'static>(
186        mut self,
187        interrupt_handler: I,
188    ) -> Self {
189        self.interrupt_handler = Some(Box::new(interrupt_handler));
190        self
191    }
192}
193
194impl Default for QuickJsRuntimeBuilder {
195    fn default() -> Self {
196        QuickJsRuntimeBuilder::new()
197    }
198}
199
200impl QuickJsRuntimeBuilder {
201    pub fn runtime_facade_init_hook<
202        H: FnOnce(&QuickJsRuntimeFacade) -> Result<(), JsError> + Send + 'static,
203    >(
204        mut self,
205        hook: H,
206    ) -> Self {
207        self.runtime_init_hooks.push(Box::new(hook));
208        self
209    }
210
211    pub fn realm_adapter_init_hook<
212        H: Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<(), JsError> + Send + 'static,
213    >(
214        self,
215        hook: H,
216    ) -> Self {
217        self.runtime_adapter_init_hook(move |rt| {
218            rt.add_context_init_hook(hook)?;
219            Ok(())
220        })
221    }
222
223    pub fn runtime_adapter_init_hook<
224        H: FnOnce(&QuickJsRuntimeAdapter) -> Result<(), JsError> + Send + 'static,
225    >(
226        self,
227        hook: H,
228    ) -> Self {
229        self.runtime_facade_init_hook(|rt| {
230            rt.exe_rt_task_in_event_loop(|rt| {
231                let _ = hook(rt);
232            });
233            Ok(())
234        })
235    }
236
237    pub fn compiled_module_loader<S: CompiledModuleLoader + Send + 'static>(
238        mut self,
239        module_loader: S,
240    ) -> Self {
241        self.compiled_module_loaders.push(Box::new(module_loader));
242        self
243    }
244}
245
246#[cfg(test)]
247pub mod tests {
248    use crate::builder::QuickJsRuntimeBuilder;
249    use crate::jsutils::modules::ScriptModuleLoader;
250    use crate::jsutils::Script;
251    use crate::quickjsrealmadapter::QuickJsRealmAdapter;
252
253    #[test]
254    fn test_module_loader() {
255        crate::facades::tests::init_logging();
256
257        struct MyModuleLoader {}
258        impl ScriptModuleLoader for MyModuleLoader {
259            fn normalize_path(
260                &self,
261                _realm: &QuickJsRealmAdapter,
262                _ref_path: &str,
263                path: &str,
264            ) -> Option<String> {
265                Some(path.to_string())
266            }
267
268            fn load_module(&self, _realm: &QuickJsRealmAdapter, _absolute_path: &str) -> String {
269                "export const foo = 12;".to_string()
270            }
271        }
272
273        let rt = QuickJsRuntimeBuilder::new()
274            .script_module_loader(MyModuleLoader {})
275            .build();
276        match rt.eval_module_sync(
277            None,
278            Script::new(
279                "test_module.es",
280                "import {foo} from 'some_module.mes';\nconsole.log('foo = %s', foo);",
281            ),
282        ) {
283            Ok(_) => {}
284            Err(e) => panic!("script failed {}", e),
285        }
286    }
287}