๐ถ Javex
Run JavaScript inside your Elixir app, sandboxed in WebAssembly.
Javex compiles JavaScript to WebAssembly with Javy and runs it on wasmtime, so you can hand a snippet of user-written JS to a Wasm sandbox and get a typed result back. Each call gets a fresh instance โ no shared state, nothing leaks between requests.
โจ Why you might want this
- ๐งฎ User-defined transforms. Let your users write small JS expressions to filter, map, or reshape data without giving them shell or BEAM access.
- ๐ Webhook / payload mappers. Accept a JS snippet from a customer and run it on incoming events.
- ๐งช Custom rules and DSLs that need real expression power without inventing a parser.
- ๐ชถ Tiny, fast. Compiled modules are typically a few KB. Cold start is sub-millisecond once a runtime is up.
- ๐ Sandboxed by default. Per-call fuel, memory, and wall-clock timeouts are first-class options.
๐ Quick start
In your mix.exs:
def deps do
[{:javex, "~> 0.1"}]
endAdd a runtime to your supervision tree:
# lib/my_app/application.ex
children = [
Javex.Runtime
]Compile and run:
js = ~S"""
Javy.IO.writeSync(1, new TextEncoder().encode(JSON.stringify({hello: "world"})));
"""
{:ok, mod} = Javex.compile(js)
{:ok, %{"hello" => "world"}} = Javex.run(mod, nil)๐ก Reading input in your JS requires a small stdin helper โ see Javy's README for the canonical
readInput()/writeOutput()snippet to drop at the top of your script.
โ๏ธ The bits you'll reach for
| You want toโฆ | Use |
|---|---|
| Compile a JS snippet | Javex.compile/2 |
| Run a compiled module with JSON I/O | Javex.run(mod, input) |
| Run with raw bytes | Javex.run(mod, bytes, encoding: :raw) |
| Cap fuel, memory, or timeout | Javex.run(mod, input, fuel: โฆ, max_memory: โฆ, timeout: โฆ) |
| Run multiple tiers (trusted / untrusted) | Javex.Runtime.start_link(name: :strict, default_fuel: โฆ, default_max_memory: โฆ) |
Persisting compiled modules? %Javex.Module{} is a plain struct โ :erlang.term_to_binary/1 round-trips it, no helper needed.
๐ก Safety knobs
{:ok, output} =
Javex.run(mod, input,
timeout: 250, # wall-clock ms
fuel: 5_000_000, # wasmtime fuel units
max_memory: 8 * 1024 * 1024 # bytes
)Errors come back as a tagged tuple, never a process exit:
{:error, %Javex.RuntimeError{kind: :timeout}} # epoch deadline elapsed
{:error, %Javex.RuntimeError{kind: :fuel_exhausted}}
{:error, %Javex.RuntimeError{kind: :oom}}
{:error, %Javex.RuntimeError{kind: :js_error}} # uncaught JS exception๐ง How it works (in 60 seconds)
-
Javy compiles your JS to a dynamically-linked Wasm module that imports QuickJS from a shared provider plugin (bundled in
priv/). Each compiled module is a few KB instead of ~1 MB. Javex.Runtimeowns one wasmtimeEngineplus the preloaded plugin. TheEngineisSend + Sync, so one runtime handles your whole BEAM. Spin up sibling runtimes when you need different resource tiers.-
Every
run/3creates a fresh wasmtimeStoreand instance โ clean JS state per call, with no measurable cold-start cost because the provider is already alive. - The Rust NIF ships precompiled for macOS (Apple Silicon + Intel) and Linux (aarch64 + x86_64 GNU). No Rust toolchain required to install.
๐ License
MIT.