rustyscript/runtime.rs
1use std::{path::Path, rc::Rc, time::Duration};
2
3use deno_core::PollEventLoopOptions;
4use tokio_util::sync::CancellationToken;
5
6use crate::{
7 async_bridge::{AsyncBridge, AsyncBridgeExt, TokioRuntime},
8 inner_runtime::{InnerRuntime, RsAsyncFunction, RsFunction},
9 js_value::Function,
10 Error, Module, ModuleHandle,
11};
12
13/// Represents the set of options accepted by the runtime constructor
14pub use crate::inner_runtime::RuntimeOptions;
15
16/// For functions returning nothing. Acts as a placeholder for the return type
17/// Should accept any type of value from javascript
18///
19/// It is in fact an alias for [`crate::js_value::Value`]
20/// Note: This used to be an alias for `serde_json::Value`, but was changed for performance reasons
21pub type Undefined = crate::js_value::Value;
22
23/// A runtime instance that can be used to execute JavaScript code and interact with it.
24/// Most runtime functions have 3 variants - blocking, async, and immediate
25///
26/// For example:
27/// - `call_function` will block until the function is resolved and the event loop is empty
28/// - `call_function_async` will return a future that resolves when the function is resolved and the event loop is empty
29/// - `call_function_immediate` will return the result immediately, without resolving promises or running the event loop
30/// (See [`crate::js_value::Promise`])
31///
32/// Note: For multithreaded applications, you may need to call `init_platform` before creating a `Runtime`
33/// (See [[`crate::init_platform`])
34pub struct Runtime {
35 inner: InnerRuntime<deno_core::JsRuntime>,
36 tokio: AsyncBridge,
37}
38
39impl Runtime {
40 /// Creates a new instance of the runtime with the provided options.
41 ///
42 /// # Arguments
43 /// * `options` - A `RuntimeOptions` struct that specifies the configuration options for the runtime.
44 ///
45 /// # Returns
46 /// A `Result` containing either the initialized runtime instance on success (`Ok`) or an error on failure (`Err`).
47 ///
48 /// # Example
49 /// ```rust
50 /// use rustyscript::{ json_args, Runtime, RuntimeOptions, Module };
51 /// use std::time::Duration;
52 ///
53 /// # fn main() -> Result<(), rustyscript::Error> {
54 /// // Creates a runtime that will attempt to run function load() on start
55 /// // And which will time-out after 50ms
56 /// let mut runtime = Runtime::new(RuntimeOptions {
57 /// default_entrypoint: Some("load".to_string()),
58 /// timeout: Duration::from_millis(50),
59 /// ..Default::default()
60 /// })?;
61 ///
62 /// let module = Module::new("test.js", "
63 /// export const load = () => {
64 /// return 'Hello World!';
65 /// }
66 /// ");
67 ///
68 /// let module_handle = runtime.load_module(&module)?;
69 /// let value: String = runtime.call_entrypoint(&module_handle, json_args!())?;
70 /// assert_eq!("Hello World!", value);
71 /// # Ok(())
72 /// # }
73 /// ```
74 ///
75 /// # Errors
76 /// Can fail if the tokio runtime cannot be created,
77 /// Or if the deno runtime initialization fails (usually issues with extensions)
78 ///
79 pub fn new(options: RuntimeOptions) -> Result<Self, Error> {
80 let tokio = AsyncBridge::new(options.timeout)?;
81 let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?;
82 Ok(Self { inner, tokio })
83 }
84
85 /// Creates a new instance of the runtime with the provided options and a pre-configured tokio runtime.
86 /// See [`Runtime::new`] for more information.
87 ///
88 /// # Errors
89 /// Can fail if the deno runtime initialization fails (usually issues with extensions)
90 pub fn with_tokio_runtime(
91 options: RuntimeOptions,
92 tokio: Rc<tokio::runtime::Runtime>,
93 ) -> Result<Self, Error> {
94 let tokio = AsyncBridge::with_tokio_runtime(options.timeout, tokio);
95 let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?;
96 Ok(Self { inner, tokio })
97 }
98
99 /// Creates a new instance of the runtime with the provided options and a borrowed tokio runtime handle.
100 /// See [`Runtime::new`] for more information.
101 ///
102 /// # Errors
103 /// Can fail if the deno runtime initialization fails (usually issues with extensions)
104 pub fn with_tokio_runtime_handle(
105 options: RuntimeOptions,
106 handle: tokio::runtime::Handle,
107 ) -> Result<Self, Error> {
108 let tokio = AsyncBridge::with_runtime_handle(options.timeout, handle);
109 let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?;
110 Ok(Self { inner, tokio })
111 }
112
113 /// Access the underlying deno runtime instance directly
114 pub fn deno_runtime(&mut self) -> &mut deno_core::JsRuntime {
115 self.inner.deno_runtime()
116 }
117
118 /// Access the underlying tokio runtime used for blocking operations
119 #[must_use]
120 pub fn tokio_runtime(&self) -> TokioRuntime {
121 self.tokio.tokio_runtime()
122 }
123
124 /// Returns the timeout for the runtime
125 #[must_use]
126 pub fn timeout(&self) -> std::time::Duration {
127 self.tokio.timeout()
128 }
129
130 /// Returns the heap exhausted token for the runtime
131 /// Used to detect when the runtime has run out of memory
132 #[must_use]
133 pub fn heap_exhausted_token(&self) -> CancellationToken {
134 self.tokio.heap_exhausted_token()
135 }
136
137 /// Destroy the v8 runtime, releasing all resources
138 /// Then the internal tokio runtime will be returned
139 #[must_use]
140 pub fn into_tokio_runtime(self) -> TokioRuntime {
141 self.tokio.into_tokio_runtime()
142 }
143
144 /// Set the current working directory for the runtime
145 /// This is used to resolve relative paths in the module loader
146 ///
147 /// The runtime will begin with the current working directory of the process
148 ///
149 /// # Errors
150 /// Can fail if the given path is not valid
151 pub fn set_current_dir(&mut self, path: impl AsRef<Path>) -> Result<&Path, Error> {
152 self.inner.set_current_dir(path)
153 }
154
155 /// Get the current working directory for the runtime
156 /// This is used to resolve relative paths in the module loader
157 ///
158 /// The runtime will begin with the current working directory of the process
159 #[must_use]
160 pub fn current_dir(&self) -> &Path {
161 self.inner.current_dir()
162 }
163
164 /// Advance the JS event loop by a single tick
165 /// See [`Runtime::block_on_event_loop`] for fully running the event loop
166 ///
167 /// Returns true if the event loop has pending work, or false if it has completed
168 ///
169 /// # Arguments
170 /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`]
171 ///
172 /// # Errors
173 /// Can fail if a runtime error occurs during the event loop's execution
174 pub fn advance_event_loop(&mut self, options: PollEventLoopOptions) -> Result<bool, Error> {
175 self.block_on(|runtime| async move { runtime.inner.advance_event_loop(options).await })
176 }
177
178 /// Advance the JS event loop by a single tick
179 /// See [`Runtime::await_event_loop`] for fully running the event loop
180 ///
181 /// Returns a future that resolves true if the event loop has pending work, or false if it
182 /// has completed
183 ///
184 /// # Arguments
185 /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`]
186 ///
187 /// # Errors
188 /// Can fail if a runtime error occurs during the event loop's execution
189 pub async fn advance_event_loop_async(
190 &mut self,
191 options: PollEventLoopOptions,
192 ) -> Result<bool, Error> {
193 self.inner.advance_event_loop(options).await
194 }
195
196 /// Run the JS event loop to completion, or until a timeout is reached
197 /// Required when using the `_immediate` variants of functions
198 ///
199 /// # Arguments
200 /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`]
201 /// * `timeout` - Optional timeout for the event loop
202 ///
203 /// # Errors
204 /// Can fail if a runtime error occurs during the event loop's execution
205 pub async fn await_event_loop(
206 &mut self,
207 options: PollEventLoopOptions,
208 timeout: Option<Duration>,
209 ) -> Result<(), Error> {
210 self.inner.await_event_loop(options, timeout).await
211 }
212
213 /// Run the JS event loop to completion, or until a timeout is reached
214 /// Required when using the `_immediate` variants of functions
215 ///
216 /// This is the blocking variant of [`Runtime::await_event_loop`]
217 ///
218 /// # Arguments
219 /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`]
220 /// * `timeout` - Optional timeout for the event loop
221 ///
222 /// # Errors
223 /// Can fail if a runtime error occurs during the event loop's execution
224 pub fn block_on_event_loop(
225 &mut self,
226 options: deno_core::PollEventLoopOptions,
227 timeout: Option<Duration>,
228 ) -> Result<(), Error> {
229 self.block_on(|runtime| async move { runtime.await_event_loop(options, timeout).await })
230 }
231
232 /// Remove and return a value from the state, if one exists
233 /// ```rust
234 /// use rustyscript::{ Runtime };
235 ///
236 /// # fn main() -> Result<(), rustyscript::Error> {
237 /// let mut runtime = Runtime::new(Default::default())?;
238 /// runtime.put("test".to_string())?;
239 /// let value: String = runtime.take().unwrap();
240 /// assert_eq!(value, "test");
241 /// # Ok(())
242 /// # }
243 /// ```
244 pub fn take<T>(&mut self) -> Option<T>
245 where
246 T: 'static,
247 {
248 self.inner.take()
249 }
250
251 /// Add a value to the state
252 /// Only one value of each type is stored - additional calls to `put` overwrite the old value
253 ///
254 /// # Errors
255 /// Can fail if the inner state cannot be borrowed mutably
256 ///
257 /// ```rust
258 /// use rustyscript::{ Runtime };
259 ///
260 /// # fn main() -> Result<(), rustyscript::Error> {
261 /// let mut runtime = Runtime::new(Default::default())?;
262 /// runtime.put("test".to_string())?;
263 /// let value: String = runtime.take().unwrap();
264 /// assert_eq!(value, "test");
265 /// # Ok(())
266 /// # }
267 /// ```
268 pub fn put<T>(&mut self, value: T) -> Result<(), Error>
269 where
270 T: 'static,
271 {
272 self.inner.put(value)
273 }
274
275 /// Register a rust function to be callable from JS
276 /// - The [`crate::sync_callback`] macro can be used to simplify this process
277 ///
278 /// # Errors
279 /// Since this function borrows the state, it can fail if the state cannot be borrowed mutably
280 ///
281 /// ```rust
282 /// use rustyscript::{ Runtime, Module, serde_json::Value };
283 ///
284 /// # fn main() -> Result<(), rustyscript::Error> {
285 /// let module = Module::new("test.js", " rustyscript.functions.foo(); ");
286 /// let mut runtime = Runtime::new(Default::default())?;
287 /// runtime.register_function("foo", |args| {
288 /// if let Some(value) = args.get(0) {
289 /// println!("called with: {}", value);
290 /// }
291 /// Ok(Value::Null)
292 /// })?;
293 ///
294 /// # Ok(())
295 /// # }
296 /// ```
297 pub fn register_function<F>(&mut self, name: &str, callback: F) -> Result<(), Error>
298 where
299 F: RsFunction,
300 {
301 self.inner.register_function(name, callback)
302 }
303
304 /// Register a non-blocking rust function to be callable from JS
305 /// - The [`crate::async_callback`] macro can be used to simplify this process
306 ///
307 /// # Errors
308 /// Since this function borrows the state, it can fail if the state cannot be borrowed mutably
309 ///
310 /// ```rust
311 /// use rustyscript::{ Runtime, Module, serde_json::Value, async_callback, Error };
312 ///
313 /// # fn main() -> Result<(), rustyscript::Error> {
314 /// let module = Module::new("test.js", " rustyscript.async_functions.add(1, 2); ");
315 /// let mut runtime = Runtime::new(Default::default())?;
316 /// runtime.register_async_function("add", async_callback!(
317 /// |a: i64, b: i64| async move {
318 /// Ok::<i64, Error>(a + b)
319 /// }
320 /// ))?;
321 ///
322 /// # Ok(())
323 /// # }
324 /// ```
325 pub fn register_async_function<F>(&mut self, name: &str, callback: F) -> Result<(), Error>
326 where
327 F: RsAsyncFunction,
328 {
329 self.inner.register_async_function(name, callback)
330 }
331
332 /// Evaluate a piece of non-ECMAScript-module JavaScript code
333 /// The expression is evaluated in the global context, so changes persist
334 ///
335 /// Blocks on promise resolution, and runs the event loop to completion
336 ///
337 /// Asynchronous code is supported, partially
338 /// - Top-level await is not supported
339 /// - The event loop will be run to completion after the expression is evaluated
340 ///
341 /// For top-level await support, use one of:
342 /// - `call_function_async`
343 /// - `call_stored_function_async`
344 /// - `load_module_async`
345 /// - `load_modules_async`
346 ///
347 /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`]
348 ///
349 /// # Arguments
350 /// * `expr` - A string representing the JavaScript expression to evaluate
351 ///
352 /// # Returns
353 /// A `Result` containing the deserialized result of the expression (`T`)
354 /// or an error (`Error`) if the expression cannot be evaluated or if the
355 /// result cannot be deserialized.
356 ///
357 /// # Errors
358 /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type
359 ///
360 /// # Example
361 /// ```rust
362 /// use rustyscript::{ Runtime, Error };
363 ///
364 /// # fn main() -> Result<(), Error> {
365 /// let mut runtime = Runtime::new(Default::default())?;
366 ///
367 /// let value: u32 = runtime.eval("2 + 2")?;
368 /// assert_eq!(4, value);
369 ///
370 /// let value: String = runtime.eval("new Promise(resolve => resolve('test'))")?;
371 /// assert_eq!("test", value);
372 ///
373 /// # Ok(())
374 /// # }
375 /// ```
376 pub fn eval<T>(&mut self, expr: impl ToString) -> Result<T, Error>
377 where
378 T: serde::de::DeserializeOwned,
379 {
380 self.block_on(|runtime| async move { runtime.eval_async(expr).await })
381 }
382
383 /// Evaluate a piece of non-ECMAScript-module JavaScript code
384 /// The expression is evaluated in the global context, so changes persist
385 ///
386 /// Awaits promise resolution, and runs the event loop to completion
387 ///
388 /// Asynchronous code is supported, partially
389 /// - Top-level await is not supported
390 /// - The event loop will be run to completion after the expression is evaluated
391 ///
392 /// For top-level await support, use one of:
393 /// - `call_function_async`
394 /// - `call_stored_function_async`
395 /// - `load_module_async`
396 /// - `load_modules_async`
397 ///
398 /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`]
399 ///
400 /// # Arguments
401 /// * `expr` - A string representing the JavaScript expression to evaluate
402 ///
403 /// # Returns
404 /// A `Result` containing the deserialized result of the expression (`T`)
405 /// or an error (`Error`) if the expression cannot be evaluated or if the
406 /// result cannot be deserialized.
407 ///
408 /// # Errors
409 /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type
410 ///
411 /// # Example
412 /// For an example, see [`Runtime::eval`]
413 pub async fn eval_async<T>(&mut self, expr: impl ToString) -> Result<T, Error>
414 where
415 T: serde::de::DeserializeOwned,
416 {
417 let result = self.inner.eval(expr.to_string()).await?;
418 let result = self.inner.resolve_with_event_loop(result).await?;
419 self.inner.decode_value(result)
420 }
421
422 /// Evaluate a piece of non-ECMAScript-module JavaScript code
423 /// The expression is evaluated in the global context, so changes persist
424 ///
425 /// Does not await promise resolution, or run the event loop
426 /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`]
427 /// The event loop should be run using [`Runtime::await_event_loop`]
428 ///
429 /// Note that this function needs to be async because calls to `setTimeout` must be evaluated from within an async runtime.
430 ///
431 /// Asynchronous code is supported, partially
432 /// - Top-level await is not supported
433 ///
434 /// For top-level await support, use one of:
435 /// - `call_function_async`
436 /// - `call_stored_function_async`
437 /// - `load_module_async`
438 /// - `load_modules_async`
439 ///
440 /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`]
441 ///
442 /// # Arguments
443 /// * `expr` - A string representing the JavaScript expression to evaluate
444 ///
445 /// # Returns
446 /// A `Result` containing the deserialized result of the expression (`T`)
447 /// or an error (`Error`) if the expression cannot be evaluated or if the
448 /// result cannot be deserialized.
449 ///
450 /// # Errors
451 /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type
452 ///
453 /// # Example
454 /// For an example, see [`Runtime::eval`]
455 pub async fn eval_immediate<T>(&mut self, expr: impl ToString) -> Result<T, Error>
456 where
457 T: serde::de::DeserializeOwned,
458 {
459 let result = self.inner.eval(expr.to_string()).await?;
460 self.inner.decode_value(result)
461 }
462
463 /// Calls a stored javascript function and deserializes its return value.
464 ///
465 /// Returns a future that resolves when:
466 /// - The event loop is resolved, and
467 /// - If the value is a promise, the promise is resolved
468 ///
469 /// See [`Runtime::call_function`] for an example
470 ///
471 /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however.
472 ///
473 /// # Arguments
474 /// * `module_context` - Optional handle to a module providing global context for the function
475 /// * `function` - A The function object
476 /// * `args` - The arguments to pass to the function
477 ///
478 /// # Returns
479 /// A `Result` containing the deserialized result of the function call (`T`)
480 /// or an error (`Error`) if there are issues with calling the function,
481 /// or if the result cannot be deserialized.
482 ///
483 /// # Errors
484 /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type
485 pub async fn call_stored_function_async<T>(
486 &mut self,
487 module_context: Option<&ModuleHandle>,
488 function: &Function,
489 args: &impl serde::ser::Serialize,
490 ) -> Result<T, Error>
491 where
492 T: serde::de::DeserializeOwned,
493 {
494 let function = function.as_global(&mut self.deno_runtime().handle_scope());
495 let result = self
496 .inner
497 .call_function_by_ref(module_context, &function, args)?;
498 let result = self.inner.resolve_with_event_loop(result).await?;
499 self.inner.decode_value(result)
500 }
501
502 /// Calls a stored javascript function and deserializes its return value.
503 ///
504 /// Blocks until:
505 /// - The event loop is resolved, and
506 /// - If the value is a promise, the promise is resolved
507 ///
508 /// See [`Runtime::call_function`] for an example
509 ///
510 /// # Arguments
511 /// * `module_context` - Optional handle to a module providing global context for the function
512 /// * `function` - A The function object
513 /// * `args` - The arguments to pass to the function
514 ///
515 /// # Returns
516 /// A `Result` containing the deserialized result of the function call (`T`)
517 /// or an error (`Error`) if there are issues with calling the function,
518 /// or if the result cannot be deserialized.
519 ///
520 /// # Errors
521 /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type
522 pub fn call_stored_function<T>(
523 &mut self,
524 module_context: Option<&ModuleHandle>,
525 function: &Function,
526 args: &impl serde::ser::Serialize,
527 ) -> Result<T, Error>
528 where
529 T: deno_core::serde::de::DeserializeOwned,
530 {
531 self.block_on(|runtime| async move {
532 runtime
533 .call_stored_function_async(module_context, function, args)
534 .await
535 })
536 }
537
538 /// Calls a stored javascript function and deserializes its return value.
539 ///
540 /// Will not attempt to resolve promises, or run the event loop
541 /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`]
542 /// The event loop should be run using [`Runtime::await_event_loop`]
543 ///
544 /// See [`Runtime::call_function`] for an example
545 ///
546 /// # Arguments
547 /// * `module_context` - Optional handle to a module providing global context for the function
548 /// * `function` - A The function object
549 /// * `args` - The arguments to pass to the function
550 ///
551 /// # Returns
552 /// A `Result` containing the deserialized result of the function call (`T`)
553 /// or an error (`Error`) if there are issues with calling the function,
554 /// or if the result cannot be deserialized.
555 ///
556 /// # Errors
557 /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type
558 pub fn call_stored_function_immediate<T>(
559 &mut self,
560 module_context: Option<&ModuleHandle>,
561 function: &Function,
562 args: &impl serde::ser::Serialize,
563 ) -> Result<T, Error>
564 where
565 T: deno_core::serde::de::DeserializeOwned,
566 {
567 let function = function.as_global(&mut self.deno_runtime().handle_scope());
568 let result = self
569 .inner
570 .call_function_by_ref(module_context, &function, args)?;
571 self.inner.decode_value(result)
572 }
573
574 /// Calls a javascript function within the Deno runtime by its name and deserializes its return value.
575 ///
576 /// Returns a future that resolves when:
577 /// - The event loop is resolved, and
578 /// - If the value is a promise, the promise is resolved
579 ///
580 /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however.
581 ///
582 /// See [`Runtime::call_function`] for an example
583 ///
584 /// # Arguments
585 /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used
586 /// * `name` - A string representing the name of the javascript function to call.
587 /// * `args` - The arguments to pass to the function
588 ///
589 /// # Returns
590 /// A `Result` containing the deserialized result of the function call (`T`)
591 /// or an error (`Error`) if the function cannot be found, if there are issues with
592 /// calling the function, or if the result cannot be deserialized.
593 ///
594 /// # Errors
595 /// Fails if the function cannot be found, if there are issues with calling the function,
596 /// Or if the result cannot be deserialized into the requested type
597 pub async fn call_function_async<T>(
598 &mut self,
599 module_context: Option<&ModuleHandle>,
600 name: &str,
601 args: &impl serde::ser::Serialize,
602 ) -> Result<T, Error>
603 where
604 T: deno_core::serde::de::DeserializeOwned,
605 {
606 let function = self.inner.get_function_by_name(module_context, name)?;
607 let result = self
608 .inner
609 .call_function_by_ref(module_context, &function, args)?;
610 let result = self.inner.resolve_with_event_loop(result).await?;
611 self.inner.decode_value(result)
612 }
613
614 /// Calls a javascript function within the Deno runtime by its name and deserializes its return value.
615 ///
616 /// Blocks until:
617 /// - The event loop is resolved, and
618 /// - If the value is a promise, the promise is resolved
619 ///
620 /// # Arguments
621 /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used
622 /// * `name` - A string representing the name of the javascript function to call.
623 /// * `args` - The arguments to pass to the function
624 ///
625 /// # Returns
626 /// A `Result` containing the deserialized result of the function call (`T`)
627 /// or an error (`Error`) if the function cannot be found, if there are issues with
628 /// calling the function, or if the result cannot be deserialized.
629 ///
630 /// # Errors
631 /// Fails if the function cannot be found, if there are issues with calling the function,
632 /// Or if the result cannot be deserialized into the requested type
633 ///
634 /// # Example
635 ///
636 /// ```rust
637 /// use rustyscript::{ json_args, Runtime, Module, Error };
638 ///
639 /// # fn main() -> Result<(), Error> {
640 /// let mut runtime = Runtime::new(Default::default())?;
641 /// let module = Module::new("/path/to/module.js", "export function f() { return 2; };");
642 /// let module = runtime.load_module(&module)?;
643 /// let value: usize = runtime.call_function(Some(&module), "f", json_args!())?;
644 /// # Ok(())
645 /// # }
646 /// ```
647 pub fn call_function<T>(
648 &mut self,
649 module_context: Option<&ModuleHandle>,
650 name: &str,
651 args: &impl serde::ser::Serialize,
652 ) -> Result<T, Error>
653 where
654 T: deno_core::serde::de::DeserializeOwned,
655 {
656 self.block_on(|runtime| async move {
657 runtime
658 .call_function_async(module_context, name, args)
659 .await
660 })
661 }
662
663 /// Calls a javascript function within the Deno runtime by its name and deserializes its return value.
664 ///
665 /// Will not attempt to resolve promises, or run the event loop
666 /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`]
667 /// The event loop should be run using [`Runtime::await_event_loop`]
668 ///
669 /// # Arguments
670 /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used
671 /// * `name` - A string representing the name of the javascript function to call.
672 /// * `args` - The arguments to pass to the function
673 ///
674 /// # Returns
675 /// A `Result` containing the deserialized result of the function call (`T`)
676 /// or an error (`Error`) if the function cannot be found, if there are issues with
677 /// calling the function, or if the result cannot be deserialized.
678 ///
679 /// # Errors
680 /// Fails if the function cannot be found, if there are issues with calling the function,
681 /// Or if the result cannot be deserialized into the requested type
682 ///
683 /// # Example
684 ///
685 /// ```rust
686 /// use rustyscript::{ json_args, Runtime, Module, Error };
687 ///
688 /// # fn main() -> Result<(), Error> {
689 /// let mut runtime = Runtime::new(Default::default())?;
690 /// let module = Module::new("/path/to/module.js", "export function f() { return 2; };");
691 /// let module = runtime.load_module(&module)?;
692 /// let value: usize = runtime.call_function_immediate(Some(&module), "f", json_args!())?;
693 /// # Ok(())
694 /// # }
695 /// ```
696 pub fn call_function_immediate<T>(
697 &mut self,
698 module_context: Option<&ModuleHandle>,
699 name: &str,
700 args: &impl serde::ser::Serialize,
701 ) -> Result<T, Error>
702 where
703 T: deno_core::serde::de::DeserializeOwned,
704 {
705 let function = self.inner.get_function_by_name(module_context, name)?;
706 let result = self
707 .inner
708 .call_function_by_ref(module_context, &function, args)?;
709 self.inner.decode_value(result)
710 }
711
712 /// Get a value from a runtime instance
713 ///
714 /// Blocks until:
715 /// - The event loop is resolved, and
716 /// - If the value is a promise, the promise is resolved
717 ///
718 /// # Arguments
719 /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used
720 /// * `name` - A string representing the name of the value to find
721 ///
722 /// # Returns
723 /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found,
724 /// Or if the result cannot be deserialized into the requested type
725 ///
726 /// # Errors
727 /// Can fail if the value cannot be found, or if the result cannot be deserialized.
728 ///
729 /// # Example
730 ///
731 /// ```rust
732 /// use rustyscript::{ Runtime, Module, Error };
733 ///
734 /// # fn main() -> Result<(), Error> {
735 /// let mut runtime = Runtime::new(Default::default())?;
736 /// let module = Module::new("/path/to/module.js", "globalThis.my_value = 2;");
737 /// let module = runtime.load_module(&module)?;
738 /// let value: usize = runtime.get_value(Some(&module), "my_value")?;
739 /// # Ok(())
740 /// # }
741 /// ```
742 pub fn get_value<T>(
743 &mut self,
744 module_context: Option<&ModuleHandle>,
745 name: &str,
746 ) -> Result<T, Error>
747 where
748 T: serde::de::DeserializeOwned,
749 {
750 self.block_on(|runtime| async move { runtime.get_value_async(module_context, name).await })
751 }
752
753 /// Get a value from a runtime instance
754 ///
755 /// Returns a future that resolves when:
756 /// - The event loop is resolved, and
757 /// - If the value is a promise, the promise is resolved
758 ///
759 /// See [`Runtime::get_value`] for an example
760 ///
761 /// # Arguments
762 /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used
763 /// * `name` - A string representing the name of the value to find
764 ///
765 /// # Returns
766 /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found,
767 /// Or if the result cannot be deserialized into the requested type
768 ///
769 /// # Errors
770 /// Can fail if the value cannot be found, or if the result cannot be deserialized.
771 pub async fn get_value_async<T>(
772 &mut self,
773 module_context: Option<&ModuleHandle>,
774 name: &str,
775 ) -> Result<T, Error>
776 where
777 T: serde::de::DeserializeOwned,
778 {
779 let result = self.inner.get_value_ref(module_context, name)?;
780 let result = self.inner.resolve_with_event_loop(result).await?;
781 self.inner.decode_value(result)
782 }
783
784 /// Get a value from a runtime instance
785 ///
786 /// Will not attempt to resolve promises, or run the event loop
787 /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`]
788 /// The event loop should be run using [`Runtime::await_event_loop`]
789 ///
790 /// # Arguments
791 /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used
792 /// * `name` - A string representing the name of the value to find
793 ///
794 /// # Returns
795 /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found,
796 /// Or if the result cannot be deserialized into the requested type
797 ///
798 /// # Errors
799 /// Can fail if the value cannot be found, or if the result cannot be deserialized.
800 ///
801 /// # Example
802 ///
803 /// ```rust
804 /// use rustyscript::{ Runtime, Module, Error };
805 ///
806 /// # fn main() -> Result<(), Error> {
807 /// let mut runtime = Runtime::new(Default::default())?;
808 /// let module = Module::new("/path/to/module.js", "globalThis.my_value = 2;");
809 /// let module = runtime.load_module(&module)?;
810 /// let value: usize = runtime.get_value_immediate(Some(&module), "my_value")?;
811 /// # Ok(())
812 /// # }
813 /// ```
814 pub fn get_value_immediate<T>(
815 &mut self,
816 module_context: Option<&ModuleHandle>,
817 name: &str,
818 ) -> Result<T, Error>
819 where
820 T: serde::de::DeserializeOwned,
821 {
822 let result = self.inner.get_value_ref(module_context, name)?;
823 self.inner.decode_value(result)
824 }
825
826 /// Executes the given module, and returns a handle allowing you to extract values
827 /// and call functions
828 ///
829 /// Blocks until the module has been executed AND the event loop has fully resolved
830 /// See [`Runtime::load_module_async`] for a non-blocking variant, or use with async
831 /// background tasks
832 ///
833 /// # Arguments
834 /// * `module` - A `Module` object containing the module's filename and contents.
835 ///
836 /// # Returns
837 /// A `Result` containing a handle for the loaded module
838 /// or an error (`Error`) if there are issues with loading or executing the module
839 ///
840 /// # Errors
841 /// Can fail if the module cannot be loaded, or execution fails
842 ///
843 /// # Example
844 ///
845 /// ```rust
846 /// // Create a module with filename and contents
847 /// use rustyscript::{Runtime, Module, Error};
848 ///
849 /// # fn main() -> Result<(), Error> {
850 /// let mut runtime = Runtime::new(Default::default())?;
851 /// let module = Module::new("test.js", "export default () => 'test'");
852 /// runtime.load_module(&module);
853 /// # Ok(())
854 /// # }
855 /// ```
856 pub fn load_module(&mut self, module: &Module) -> Result<ModuleHandle, Error> {
857 self.block_on(|runtime| async move {
858 let handle = runtime.load_module_async(module).await;
859 runtime
860 .await_event_loop(PollEventLoopOptions::default(), None)
861 .await?;
862 handle
863 })
864 }
865
866 /// Executes the given module, and returns a handle allowing you to extract values
867 /// and call functions
868 ///
869 /// Returns a future that resolves to the handle for the loaded module
870 /// Makes no attempt to fully resolve the event loop - call [`Runtime::await_event_loop`]
871 /// to resolve background tasks and async listeners
872 ///
873 /// # Arguments
874 /// * `module` - A `Module` object containing the module's filename and contents.
875 ///
876 /// # Returns
877 /// A `Result` containing a handle for the loaded module
878 /// or an error (`Error`) if there are issues with loading or executing the module
879 ///
880 /// # Errors
881 /// Can fail if the module cannot be loaded, or execution fails
882 ///
883 /// See [`Runtime::load_module`] for an example
884 pub async fn load_module_async(&mut self, module: &Module) -> Result<ModuleHandle, Error> {
885 self.inner.load_modules(None, vec![module]).await
886 }
887
888 /// Executes the given module, and returns a handle allowing you to extract values
889 /// and call functions.
890 ///
891 /// Blocks until all modules have been executed AND the event loop has fully resolved
892 /// See [`Runtime::load_module_async`] for a non-blocking variant, or use with async
893 /// background tasks
894 ///
895 /// This will load 'module' as the main module, and the others as side-modules.
896 /// Only one main module can be loaded per runtime
897 ///
898 /// # Arguments
899 /// * `module` - A `Module` object containing the module's filename and contents.
900 /// * `side_modules` - A set of additional modules to be loaded into memory for use
901 ///
902 /// # Returns
903 /// A `Result` containing a handle for the loaded module
904 /// or an error (`Error`) if there are issues with loading or executing the module
905 ///
906 /// # Errors
907 /// Can fail if the module cannot be loaded, or execution fails
908 ///
909 /// # Example
910 ///
911 /// ```rust
912 /// // Create a module with filename and contents
913 /// use rustyscript::{Runtime, Module, Error};
914 ///
915 /// # fn main() -> Result<(), Error> {
916 /// let mut runtime = Runtime::new(Default::default())?;
917 /// let module = Module::new("test.js", "export default () => 'test'");
918 /// runtime.load_modules(&module, vec![]);
919 /// # Ok(())
920 /// # }
921 /// ```
922 pub fn load_modules(
923 &mut self,
924 module: &Module,
925 side_modules: Vec<&Module>,
926 ) -> Result<ModuleHandle, Error> {
927 self.block_on(move |runtime| async move {
928 let handle = runtime.load_modules_async(module, side_modules).await;
929 runtime
930 .await_event_loop(PollEventLoopOptions::default(), None)
931 .await?;
932 handle
933 })
934 }
935
936 /// Executes the given module, and returns a handle allowing you to extract values
937 /// and call functions.
938 ///
939 /// Returns a future that resolves to the handle for the loaded module
940 /// Makes no attempt to resolve the event loop - call [`Runtime::await_event_loop`] to
941 /// resolve background tasks and async listeners
942 ///
943 /// This will load 'module' as the main module, and the others as side-modules.
944 /// Only one main module can be loaded per runtime
945 ///
946 /// See [`Runtime::load_modules`] for an example
947 ///
948 /// # Arguments
949 /// * `module` - A `Module` object containing the module's filename and contents.
950 /// * `side_modules` - A set of additional modules to be loaded into memory for use
951 ///
952 /// # Returns
953 /// A `Result` containing a handle for the loaded main module, or the last side-module
954 /// or an error (`Error`) if there are issues with loading or executing the modules
955 ///
956 /// # Errors
957 /// Can fail if the modules cannot be loaded, or execution fails
958 pub async fn load_modules_async(
959 &mut self,
960 module: &Module,
961 side_modules: Vec<&Module>,
962 ) -> Result<ModuleHandle, Error> {
963 self.inner.load_modules(Some(module), side_modules).await
964 }
965
966 /// Executes the entrypoint function of a module within the Deno runtime.
967 ///
968 /// Blocks until:
969 /// - The event loop is resolved, and
970 /// - If the value is a promise, the promise is resolved
971 ///
972 /// # Arguments
973 /// * `module_context` - A handle returned by loading a module into the runtime
974 ///
975 /// # Returns
976 /// A `Result` containing the deserialized result of the entrypoint execution (`T`)
977 /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails,
978 /// or the result cannot be deserialized.
979 ///
980 /// # Errors
981 /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails,
982 /// Or if the result cannot be deserialized into the requested type
983 ///
984 /// # Example
985 ///
986 /// ```rust
987 /// use rustyscript::{json_args, Runtime, Module, Error};
988 ///
989 /// # fn main() -> Result<(), Error> {
990 /// let mut runtime = Runtime::new(Default::default())?;
991 /// let module = Module::new("test.js", "export default () => 'test'");
992 /// let module = runtime.load_module(&module)?;
993 ///
994 /// // Run the entrypoint and handle the result
995 /// let value: String = runtime.call_entrypoint(&module, json_args!())?;
996 /// # Ok(())
997 /// # }
998 /// ```
999 pub fn call_entrypoint<T>(
1000 &mut self,
1001 module_context: &ModuleHandle,
1002 args: &impl serde::ser::Serialize,
1003 ) -> Result<T, Error>
1004 where
1005 T: deno_core::serde::de::DeserializeOwned,
1006 {
1007 self.block_on(
1008 |runtime| async move { runtime.call_entrypoint_async(module_context, args).await },
1009 )
1010 }
1011
1012 /// Executes the entrypoint function of a module within the Deno runtime.
1013 ///
1014 /// Returns a future that resolves when:
1015 /// - The event loop is resolved, and
1016 /// - If the value is a promise, the promise is resolved
1017 ///
1018 /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however.
1019 ///
1020 /// See [`Runtime::call_entrypoint`] for an example
1021 ///
1022 /// # Arguments
1023 /// * `module_context` - A handle returned by loading a module into the runtime
1024 ///
1025 /// # Returns
1026 /// A `Result` containing the deserialized result of the entrypoint execution (`T`)
1027 /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails,
1028 /// or the result cannot be deserialized.
1029 ///
1030 /// # Errors
1031 /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails,
1032 /// Or if the result cannot be deserialized into the requested type
1033 pub async fn call_entrypoint_async<T>(
1034 &mut self,
1035 module_context: &ModuleHandle,
1036 args: &impl serde::ser::Serialize,
1037 ) -> Result<T, Error>
1038 where
1039 T: deno_core::serde::de::DeserializeOwned,
1040 {
1041 if let Some(entrypoint) = module_context.entrypoint() {
1042 let result = self
1043 .inner
1044 .call_function_by_ref(Some(module_context), entrypoint, args)?;
1045 let result = self.inner.resolve_with_event_loop(result).await?;
1046 self.inner.decode_value(result)
1047 } else {
1048 Err(Error::MissingEntrypoint(module_context.module().clone()))
1049 }
1050 }
1051
1052 /// Executes the entrypoint function of a module within the Deno runtime.
1053 ///
1054 /// Will not attempt to resolve promises, or run the event loop
1055 /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`]
1056 /// The event loop should be run using [`Runtime::await_event_loop`]
1057 ///
1058 /// # Arguments
1059 /// * `module_context` - A handle returned by loading a module into the runtime
1060 ///
1061 /// # Returns
1062 /// A `Result` containing the deserialized result of the entrypoint execution (`T`)
1063 /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails,
1064 /// or the result cannot be deserialized.
1065 ///
1066 /// # Errors
1067 /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails,
1068 /// Or if the result cannot be deserialized into the requested type
1069 ///
1070 /// # Example
1071 ///
1072 /// ```rust
1073 /// use rustyscript::{json_args, Runtime, Module, Error};
1074 ///
1075 /// # fn main() -> Result<(), Error> {
1076 /// let mut runtime = Runtime::new(Default::default())?;
1077 /// let module = Module::new("test.js", "export default () => 'test'");
1078 /// let module = runtime.load_module(&module)?;
1079 ///
1080 /// // Run the entrypoint and handle the result
1081 /// let value: String = runtime.call_entrypoint_immediate(&module, json_args!())?;
1082 /// # Ok(())
1083 /// # }
1084 /// ```
1085 pub fn call_entrypoint_immediate<T>(
1086 &mut self,
1087 module_context: &ModuleHandle,
1088 args: &impl serde::ser::Serialize,
1089 ) -> Result<T, Error>
1090 where
1091 T: deno_core::serde::de::DeserializeOwned,
1092 {
1093 if let Some(entrypoint) = module_context.entrypoint() {
1094 let result = self.block_on(|runtime| async move {
1095 runtime
1096 .inner
1097 .call_function_by_ref(Some(module_context), entrypoint, args)
1098 })?;
1099 self.inner.decode_value(result)
1100 } else {
1101 Err(Error::MissingEntrypoint(module_context.module().clone()))
1102 }
1103 }
1104
1105 /// Loads a module into a new runtime, executes the entry function and returns the
1106 /// result of the module's execution, deserialized into the specified Rust type (`T`).
1107 ///
1108 /// # Arguments
1109 /// * `module` - A `Module` object containing the module's filename and contents.
1110 /// * `side_modules` - A set of additional modules to be loaded into memory for use
1111 /// * `runtime_options` - Options for the creation of the runtime
1112 /// * `entrypoint_args` - Arguments to pass to the entrypoint function
1113 ///
1114 /// # Returns
1115 /// A `Result` containing the deserialized result of the entrypoint execution (`T`)
1116 /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails,
1117 /// or the result cannot be deserialized.
1118 ///
1119 /// # Errors
1120 /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails,
1121 /// Or if the result cannot be deserialized into the requested type
1122 ///
1123 /// # Example
1124 ///
1125 /// ```rust
1126 /// // Create a module with filename and contents
1127 /// use rustyscript::{json_args, Runtime, Module, Error};
1128 ///
1129 /// # fn main() -> Result<(), Error> {
1130 /// let module = Module::new("test.js", "export default () => 2");
1131 /// let value: usize = Runtime::execute_module(&module, vec![], Default::default(), json_args!())?;
1132 /// # Ok(())
1133 /// # }
1134 /// ```
1135 pub fn execute_module<T>(
1136 module: &Module,
1137 side_modules: Vec<&Module>,
1138 runtime_options: RuntimeOptions,
1139 entrypoint_args: &impl serde::ser::Serialize,
1140 ) -> Result<T, Error>
1141 where
1142 T: deno_core::serde::de::DeserializeOwned,
1143 {
1144 let mut runtime = Runtime::new(runtime_options)?;
1145 let module = runtime.load_modules(module, side_modules)?;
1146 let value: T = runtime.call_entrypoint(&module, entrypoint_args)?;
1147 Ok(value)
1148 }
1149}
1150
1151impl AsyncBridgeExt for Runtime {
1152 fn bridge(&self) -> &AsyncBridge {
1153 &self.tokio
1154 }
1155}
1156
1157#[cfg(test)]
1158mod test_runtime {
1159 use crate::json_args;
1160 use std::time::Duration;
1161
1162 use super::*;
1163 use deno_core::extension;
1164
1165 #[test]
1166 fn test_new() {
1167 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1168
1169 extension!(test_extension);
1170 Runtime::new(RuntimeOptions {
1171 extensions: vec![test_extension::init()],
1172 ..Default::default()
1173 })
1174 .expect("Could not create runtime with extensions");
1175 }
1176
1177 #[test]
1178 fn test_get_value() {
1179 let module = Module::new(
1180 "test.js",
1181 "
1182 globalThis.a = 2;
1183 export const b = 'test';
1184 export const fnc = null;
1185 ",
1186 );
1187
1188 let mut runtime =
1189 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1190 let module = runtime
1191 .load_modules(&module, vec![])
1192 .expect("Could not load module");
1193
1194 assert_eq!(
1195 2,
1196 runtime
1197 .get_value::<usize>(Some(&module), "a")
1198 .expect("Could not find global")
1199 );
1200 assert_eq!(
1201 "test",
1202 runtime
1203 .get_value::<String>(Some(&module), "b")
1204 .expect("Could not find export")
1205 );
1206 runtime
1207 .get_value::<Undefined>(Some(&module), "c")
1208 .expect_err("Could not detect null");
1209 runtime
1210 .get_value::<Undefined>(Some(&module), "d")
1211 .expect_err("Could not detect undeclared");
1212 }
1213
1214 #[test]
1215 fn test_load_module() {
1216 let mut runtime =
1217 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1218 let module = Module::new(
1219 "test.js",
1220 "
1221 export default () => 2;
1222 ",
1223 );
1224 let module = runtime
1225 .load_modules(&module, vec![])
1226 .expect("Could not load module");
1227 assert_ne!(0, module.id());
1228
1229 let mut runtime =
1230 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1231 let module1 = Module::new(
1232 "importme.js",
1233 "
1234 export const value = 2;
1235 ",
1236 );
1237 let module2 = Module::new(
1238 "test.js",
1239 "
1240 import { value } from './importme.js';
1241 rustyscript.register_entrypoint(() => value);
1242 ",
1243 );
1244 runtime
1245 .load_module(&module1)
1246 .expect("Could not load modules");
1247 let module = runtime
1248 .load_module(&module2)
1249 .expect("Could not load modules");
1250 let value: usize = runtime
1251 .call_entrypoint(&module, json_args!())
1252 .expect("Could not call exported fn");
1253 assert_eq!(2, value);
1254
1255 let mut runtime = Runtime::new(RuntimeOptions {
1256 timeout: Duration::from_millis(50),
1257 ..Default::default()
1258 })
1259 .expect("Could not create the runtime");
1260 let module = Module::new(
1261 "test.js",
1262 "
1263 await new Promise(r => setTimeout(r, 2000));
1264 ",
1265 );
1266 runtime
1267 .load_modules(&module, vec![])
1268 .expect_err("Did not interupt after timeout");
1269 }
1270
1271 #[test]
1272 fn test_load_modules() {
1273 let mut runtime =
1274 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1275 let module = Module::new(
1276 "test.js",
1277 "
1278 rustyscript.register_entrypoint(() => 2);
1279 ",
1280 );
1281 let module = runtime
1282 .load_modules(&module, vec![])
1283 .expect("Could not load module");
1284 assert_ne!(0, module.id());
1285
1286 let mut runtime =
1287 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1288 let module1 = Module::new(
1289 "importme.js",
1290 "
1291 export const value = 2;
1292 ",
1293 );
1294 let module2 = Module::new(
1295 "test.js",
1296 "
1297 import { value } from './importme.js';
1298 rustyscript.register_entrypoint(() => value);
1299 ",
1300 );
1301 let module = runtime
1302 .load_modules(&module2, vec![&module1])
1303 .expect("Could not load modules");
1304 let value: usize = runtime
1305 .call_entrypoint(&module, json_args!())
1306 .expect("Could not call exported fn");
1307 assert_eq!(2, value);
1308
1309 let mut runtime = Runtime::new(RuntimeOptions {
1310 timeout: Duration::from_millis(50),
1311 ..Default::default()
1312 })
1313 .expect("Could not create the runtime");
1314 let module = Module::new(
1315 "test.js",
1316 "
1317 await new Promise(r => setTimeout(r, 5000));
1318 ",
1319 );
1320 runtime
1321 .load_modules(&module, vec![])
1322 .expect_err("Did not interupt after timeout");
1323 }
1324
1325 #[test]
1326 fn test_call_entrypoint() {
1327 let mut runtime =
1328 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1329 let module = Module::new(
1330 "test.js",
1331 "
1332 rustyscript.register_entrypoint(() => 2);
1333 ",
1334 );
1335 let module = runtime
1336 .load_modules(&module, vec![])
1337 .expect("Could not load module");
1338 let value: usize = runtime
1339 .call_entrypoint(&module, json_args!())
1340 .expect("Could not call registered fn");
1341 assert_eq!(2, value);
1342
1343 let mut runtime = Runtime::new(RuntimeOptions {
1344 default_entrypoint: Some("load".to_string()),
1345 ..Default::default()
1346 })
1347 .expect("Could not create the runtime");
1348 let module = Module::new(
1349 "test.js",
1350 "
1351 export const load = () => 2;
1352 ",
1353 );
1354 let module = runtime
1355 .load_modules(&module, vec![])
1356 .expect("Could not load module");
1357 let value: usize = runtime
1358 .call_entrypoint(&module, json_args!())
1359 .expect("Could not call exported fn");
1360 assert_eq!(2, value);
1361
1362 let mut runtime =
1363 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1364 let module = Module::new(
1365 "test.js",
1366 "
1367 export const load = () => 2;
1368 ",
1369 );
1370 let module = runtime
1371 .load_modules(&module, vec![])
1372 .expect("Could not load module");
1373 runtime
1374 .call_entrypoint::<Undefined>(&module, json_args!())
1375 .expect_err("Did not detect no entrypoint");
1376 }
1377
1378 #[test]
1379 fn test_execute_module() {
1380 let module = Module::new(
1381 "test.js",
1382 "
1383 rustyscript.register_entrypoint(() => 2);
1384 ",
1385 );
1386 let value: usize =
1387 Runtime::execute_module(&module, vec![], RuntimeOptions::default(), json_args!())
1388 .expect("Could not exec module");
1389 assert_eq!(2, value);
1390
1391 let module = Module::new(
1392 "test.js",
1393 "
1394 function load() { return 2; }
1395 ",
1396 );
1397 Runtime::execute_module::<Undefined>(
1398 &module,
1399 vec![],
1400 RuntimeOptions::default(),
1401 json_args!(),
1402 )
1403 .expect_err("Could not detect no entrypoint");
1404 }
1405
1406 #[test]
1407 fn call_function() {
1408 let module = Module::new(
1409 "test.js",
1410 "
1411 globalThis.fna = (i) => i;
1412 export function fnb() { return 'test'; }
1413 export const fnc = 2;
1414 export const fne = () => {};
1415 ",
1416 );
1417
1418 let mut runtime =
1419 Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime");
1420 let module = runtime
1421 .load_modules(&module, vec![])
1422 .expect("Could not load module");
1423
1424 let result: usize = runtime
1425 .call_function(Some(&module), "fna", json_args!(2))
1426 .expect("Could not call global");
1427 assert_eq!(2, result);
1428
1429 let result: String = runtime
1430 .call_function(Some(&module), "fnb", json_args!())
1431 .expect("Could not call export");
1432 assert_eq!("test", result);
1433
1434 runtime
1435 .call_function::<Undefined>(Some(&module), "fnc", json_args!())
1436 .expect_err("Did not detect non-function");
1437 runtime
1438 .call_function::<Undefined>(Some(&module), "fnd", json_args!())
1439 .expect_err("Did not detect undefined");
1440 runtime
1441 .call_function::<Undefined>(Some(&module), "fne", json_args!())
1442 .expect("Did not allow undefined return");
1443 }
1444
1445 #[test]
1446 fn test_heap_exhaustion_handled() {
1447 let mut runtime = Runtime::new(RuntimeOptions {
1448 max_heap_size: Some(100 * 1024 * 1024),
1449 ..Default::default()
1450 })
1451 .expect("Could not create the runtime");
1452 let module = Module::new(
1453 "test.js",
1454 "const largeArray = new Array(40 * 1024 * 1024).fill('a');",
1455 );
1456 runtime
1457 .load_modules(&module, vec![])
1458 .expect_err("Did not detect heap exhaustion");
1459 }
1460}