Crate rustyscript

Source
Expand description

Rustyscript - Effortless JS Integration for Rust

Crates.io Build Status docs.rs Static Badge License

§Rustyscript - Effortless JS Integration for Rust

rustyscript provides a quick and simple way to integrate a runtime javascript or typescript component from within Rust.

It uses the v8 engine through the deno_core crate, and aims to be as simple as possible to use without sacrificing flexibility or performance.
I also have attempted to abstract away the v8 engine details so you can for the most part operate directly on rust types.

Sandboxed
By default, the code being run is entirely sandboxed from the host, having no filesystem or network access.
extensions can be added to grant additional capabilities that may violate sandboxing

Flexible
The runtime is designed to be as flexible as possible, allowing you to modify capabilities, the module loader, and more.

  • Asynchronous JS is fully supported, and the runtime can be configured to run in a multithreaded environment.
  • Typescript is supported, and will be transpired into JS for execution.
  • Node JS is supported experimentally, but is not yet fully compatible (See the NodeJS Compatibility section)

Unopinionated
Rustyscript is designed to be a thin wrapper over the Deno runtime, to remove potential pitfalls and simplify the API without sacrificing flexibility or performance.


Here is a very basic use of this crate to execute a JS module. It will:

  • Create a basic runtime
  • Load a javascript module,
  • Call a function registered as the entrypoint
  • Return the resulting value
use rustyscript::{json_args, Runtime, Module, Error};

let module = Module::new(
    "test.js",
    "
    export default (string, integer) => {
        console.log(`Hello world: string=${string}, integer=${integer}`);
        return 2;
    }
    "
);

let value: usize = Runtime::execute_module(
    &module, vec![],
    Default::default(),
    json_args!("test", 5)
)?;

assert_eq!(value, 2);

Modules can also be loaded from the filesystem with Module::load or Module::load_dir if you want to collect all modules in a given directory.


If all you need is the result of a single javascript expression, you can use:

let result: i64 = rustyscript::evaluate("5 + 5").expect("The expression was invalid!");

Or to just import a single module for use:

use rustyscript::{json_args, import};
let mut module = import("js/my_module.js").expect("Something went wrong!");
let value: String = module.call("exported_function_name", json_args!()).expect("Could not get a value!");

There are a few other utilities included, such as validate and resolve_path


A more detailed version of the crate’s usage can be seen below, which breaks down the steps instead of using the one-liner Runtime::execute_module:

use rustyscript::{json_args, Runtime, RuntimeOptions, Module, Error, Undefined};
use std::time::Duration;

let module = Module::new(
    "test.js",
    "
    let internalValue = 0;
    export const load = (value) => internalValue = value;
    export const getValue = () => internalValue;
    "
);

// Create a new runtime
let mut runtime = Runtime::new(RuntimeOptions {
    timeout: Duration::from_millis(50), // Stop execution by force after 50ms
    default_entrypoint: Some("load".to_string()), // Run this as the entrypoint function if none is registered
    ..Default::default()
})?;

// The handle returned is used to get exported functions and values from that module.
// We then call the entrypoint function, but do not need a return value.
//Load can be called multiple times, and modules can import other loaded modules
// Using `import './filename.js'`
let module_handle = runtime.load_module(&module)?;
runtime.call_entrypoint::<Undefined>(&module_handle, json_args!(2))?;

// Functions don't need to be the entrypoint to be callable!
let internal_value: i64 = runtime.call_function(Some(&module_handle), "getValue", json_args!())?;

There are also ‘_async’ and ‘immediate’ versions of most runtime functions; ‘_async’ functions return a future that resolves to the result of the operation, while ‘_immediate’ functions will make no attempt to wait for the event loop, making them suitable for using crate::js_value::Promise

Rust functions can also be registered to be called from javascript:

use rustyscript::{ Runtime, Module, serde_json::Value };

let module = Module::new("test.js", " rustyscript.functions.foo(); ");
let mut runtime = Runtime::new(Default::default())?;
runtime.register_function("foo", |args| {
    if let Some(value) = args.get(0) {
        println!("called with: {}", value);
    }
    Ok(Value::Null)
})?;
runtime.load_module(&module)?;

Asynchronous JS can be called in 2 ways;

The first is to use the ‘async’ keyword in JS, and then call the function using Runtime::call_function_async

use rustyscript::{ Runtime, Module, json_args };

let module = Module::new("test.js", "export async function foo() { return 5; }");
let mut runtime = Runtime::new(Default::default())?;

// The runtime has its own tokio runtime; you can get a handle to it with [Runtime::tokio_runtime]
// You can also build the runtime with your own tokio runtime, see [Runtime::with_tokio_runtime]
let tokio_runtime = runtime.tokio_runtime();

let result: i32 = tokio_runtime.block_on(async {
    // Top-level await is supported - we can load modules asynchronously
    let handle = runtime.load_module_async(&module).await?;

    // Call the function asynchronously
    runtime.call_function_async(Some(&handle), "foo", json_args!()).await
})?;

assert_eq!(result, 5);

The second is to use crate::js_value::Promise

use rustyscript::{ Runtime, Module, js_value::Promise, json_args };

let module = Module::new("test.js", "export async function foo() { return 5; }");

let mut runtime = Runtime::new(Default::default())?;
let handle = runtime.load_module(&module)?;

// We call the function without waiting for the event loop to run, or for the promise to resolve
// This way we can store it and wait for it later, without blocking the event loop or borrowing the runtime
let result: Promise<i32> = runtime.call_function_immediate(Some(&handle), "foo", json_args!())?;

// We can then wait for the promise to resolve
// We can do so asynchronously, using [crate::js_value::Promise::into_future]
// But we can also block the current thread:
let result = result.into_value(&mut runtime)?;
assert_eq!(result, 5);
  • See Runtime::register_async_function for registering and calling async rust from JS
  • See examples/async_javascript.rs for a more detailed example of using async JS

For better performance calling rust code, consider using an extension instead of a module - see the runtime_extensions example for details


A threaded worker can be used to run code in a separate thread, or to allow multiple concurrent runtimes.

the worker module provides a simple interface to create and interact with workers. The worker::InnerWorker trait can be implemented to provide custom worker behavior.

It also provides a default worker implementation that can be used without any additional setup:

use rustyscript::{Error, worker::{Worker, DefaultWorker, DefaultWorkerOptions}};
use std::time::Duration;

fn main() -> Result<(), Error> {
    let worker = DefaultWorker::new(DefaultWorkerOptions {
        default_entrypoint: None,
        timeout: Duration::from_secs(5),
    })?;

    let result: i32 = worker.eval("5 + 5".to_string())?;
    assert_eq!(result, 10);
    Ok(())
}

§Utility Functions

These functions provide simple one-liner access to common features of this crate:

  • evaluate; Evaluate a single JS expression and return the resulting value
  • import; Get a handle to a JS module from which you can get exported values and functions
  • resolve_path; Resolve a relative path to the current working dir
  • validate; Validate the syntax of a JS expression
  • init_platform; Initialize the V8 platform for multi-threaded applications

Commonly used features have been grouped into the following feature-sets:

  • safe_extensions - On by default, these extensions are safe to use in a sandboxed environment
  • network_extensions - These extensions break sandboxing by allowing network connectivity
  • io_extensions - These extensions break sandboxing by allowing filesystem access (WARNING: Also allows some network access)
  • all_extensions - All 3 above groups are included
  • extra_features - Enables the worker feature (enabled by default), and the snapshot_builder feature
  • node_experimental - HIGHLY EXPERIMENTAL nodeJS support that enables all available Deno extensions

§Crate features

The table below lists the available features for this crate. Features marked at Preserves Sandbox: NO break isolation between loaded JS modules and the host system. Use with caution.

More details on the features can be found in Cargo.toml

Please note that the web feature will also enable fs_import and url_import, allowing arbitrary filesystem and network access for import statements

  • This is because the deno_web crate allows both fetch and FS reads already
FeatureDescriptionPreserves SandboxDependencies
broadcast_channelImplements the web-messaging API for DenoNOdeno_broadcast_channel, deno_web, deno_webidl
cacheImplements the Cache API for DenoNOdeno_cache, deno_webidl, deno_web, deno_crypto, deno_fetch, deno_url, deno_net
consoleProvides console.* functionality from JSyesdeno_console, deno_terminal
cronImplements scheduled tasks (crons) APINOdeno_cron, deno_console
cryptoProvides crypto.* functionality from JSyesdeno_crypto, deno_webidl
ffiDynamic library ffi featuresNOdeno_ffi
fsProvides ops for interacting with the file system.NOdeno_fs, web, io
httpImplements the fetch standardNOdeno_http, web, websocket
kvImplements the Deno KV Connect protocolNOdeno_kv, web, console
urlProvides the URL, and URLPattern APIs from within JSyesdeno_webidl, deno_url
ioProvides IO primitives such as stdio streams and abstraction over File System files.NOdeno_io, rustyline, winapi, nix, libc, once_cell
webProvides the Event, TextEncoder, TextDecoder, File, Web Cryptography, and fetch APIs from within JSNOdeno_webidl, deno_web, deno_crypto, deno_fetch, deno_url, deno_net
webgpuImplements the WebGPU APINOdeno_webgpu, web
webstorageProvides the WebStorage APINOdeno_webidl, deno_webstorage
websocketProvides the WebSocket APINOdeno_web, deno_websocket
webidlProvides the webidl APIyesdeno_webidl
defaultProvides only those extensions that preserve sandboxingyesdeno_console, deno_crypto, deno_webidl, deno_url
no_extensionsDisables all extensions to the JS runtime - you can still add your own extensions in this modeyesNone
allProvides all available functionalityNOdeno_console, deno_webidl, deno_web, deno_net, deno_crypto, deno_fetch, deno_url
fs_importEnables importing arbitrary code from the filesystem through JSNONone
url_importEnables importing arbitrary code from network locations through JSNOreqwest
node_experimentalHIGHLY EXPERIMENTAL nodeJS support that enables all available Deno extensionsNOFor complete list, see Cargo.toml
workerEnables access to the threaded worker API workeryesNone
snapshot_builderEnables access to SnapshotBuilder, a runtime for creating snapshots that can improve start-timesyesNone
web_stubEnables a subset of web features that do not break sandboxingyesdeno_webidl

For an example of this crate in use, see Lavendeux

Re-exports§

pub use error::Error;
pub use deno_core;
pub use deno_core::serde_json;
pub use tokio;
pub use hyper_util;web

Modules§

error
Contains the error type for the runtime And some associated utilities
extensions
Re-exports of the deno extension crates used by this library
js_value
This module provides a way to store and use javascript values, functions, and promises The are a deserialized version of the v8::Value
module_loader
Module loader implementation for rustyscript This module provides tools for caching module data, resolving module specifiers, and loading modules
static_runtime
This module contains a macro for creating a static runtime instance It creates a safe, thread-local runtime static.
workerworker
Provides a worker thread that can be used to run javascript code in a separate thread through a channel pair It also provides a default worker implementation that can be used without any additional setup:

Macros§

async_callback
A simple helper macro to create a callback for use with Runtime::register_async_function
Takes care of deserializing arguments and serializing the result
big_json_args
Map a series of values into a form which javascript functions can understand
This forms a Vec<serde_json::Value> from the provided arguments
include_module
Creates a static module based on a statically included file
json_args
Map a series of values into a form which javascript functions can understand
module
Creates a static module
static_runtime
Create a static runtime instance This macro creates a thread-local static runtime instance
sync_callback
A simple helper macro to create a callback for use with Runtime::register_function
Takes care of deserializing arguments and serializing the result

Structs§

AllowlistWebPermissionsweb
Permissions manager for the web related extensions
BroadcastChannelWrapperbroadcast_channel
Helper struct to wrap a broadcast channel Takes care of some of the boilerplate for serialization/deserialization
DefaultWebPermissionsweb
The default permissions manager for the web related extensions
ExtensionOptions
Options for configuring extensions
KvConfigkv
Configuration for the key-value store
KvStorekv
Bi-modal key-value store for deno
Module
Represents a piece of javascript for execution.
ModuleHandle
Represents a loaded instance of a module within a runtime
ModuleWrapper
A wrapper type representing a runtime instance loaded with a single module
PermissionDeniedweb
Wrapper error for deno permissions checks.
Runtime
A runtime instance that can be used to execute JavaScript code and interact with it.
Most runtime functions have 3 variants - blocking, async, and immediate
RuntimeBuilder
A builder for creating a new runtime
RuntimeOptions
Represents the set of options accepted by the runtime constructor Represents the set of options accepted by the runtime constructor
RustyResolvernode_experimental
Package resolver for the deno_node extension
SnapshotBuildersnapshot_builder
A more restricted version of the Runtime struct that is used to create a snapshot of the runtime state This runtime should ONLY be used to create a snapshot, and not for normal use
WebOptionsweb
Options for configuring the web related extensions

Enums§

CacheBackendcache
A cache backend that can store data in memory or an sqlite database
SystemsPermissionKindweb
Knows systems permission checks performed by deno

Traits§

RsAsyncFunction
Represents an async function that can be registered with the runtime
RsFunction
Represents a function that can be registered with the runtime
WebPermissionsweb
Trait managing the permissions for the web related extensions

Functions§

evaluate
Evaluate a piece of non-ECMAScript-module JavaScript code
import
Imports a JS module into a new runtime
init_platform
Explicitly initialize the V8 platform
Note that all runtimes must have a common parent thread that initalized the V8 platform
resolve_path
Resolve a path to absolute path, relative to the current working directory or an optional base directory
validate
Validates the syntax of some JS

Type Aliases§

Undefined
For functions returning nothing. Acts as a placeholder for the return type
Should accept any type of value from javascript