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}