Packages

Compile JavaScript to WebAssembly with Javy and run it on wasmtime, with dynamic linking by default for tiny modules and fast cold starts.

Current section

Files

Jump to
javex lib javex.ex
Raw

lib/javex.ex

defmodule Javex do
@moduledoc """
Compile JavaScript to WebAssembly with Javy and run it on wasmtime.
Javex uses **dynamic linking by default**: the compiled module imports
QuickJS from a shared provider plugin that is instantiated once per
`Javex.Runtime`. That keeps each compiled module tiny (a few KB) and
makes cold starts fast enough to spin up a fresh instance per call.
## Quick start
iex> {:ok, mod} = Javex.compile(~S\"\"\"
...> const input = JSON.parse(readInput());
...> writeOutput(JSON.stringify({ sum: input.a + input.b }));
...> \"\"\")
iex> Javex.run(mod, %{a: 1, b: 2})
{:ok, %{"sum" => 3}}
Starting a runtime is the consumer's responsibility. Add
`Javex.Runtime` (or `{Javex.Runtime, opts}`) to your application's
supervision tree. The default registered name is `Javex.Runtime`,
which is what `run/3` looks for unless you pass `:runtime`.
`compile/2` does **not** need a running runtime: it reads the bundled
provider plugin directly from `priv/` and is safe to call from one-off
scripts.
"""
alias Javex.{Module, Runtime}
@type input :: map() | list() | binary() | number() | boolean() | nil
@type output :: map() | list() | binary() | number() | boolean() | nil
@doc """
Compile a JavaScript source string into a `Javex.Module`.
## Options
* `:mode` - `:dynamic` (default) or `:static`. Dynamic modules import
QuickJS from the provider plugin and are tiny. Static modules embed
QuickJS and can run without a runtime that has the plugin loaded,
at the cost of ~1MB per module and slower cold starts.
* `:runtime` - a `Javex.Runtime` server (pid, name, or `{:via, ...}`)
to source the provider plugin from. Required when you intend to
run the module on a runtime started with a custom `:plugin_path`,
otherwise the compiled module will be hashed against the bundled
plugin and rejected as incompatible at run time.
* `:plugin` - raw plugin bytes. Advanced escape hatch when you have
the plugin in hand already and do not want Javex to source it
through a runtime or the bundled file.
When neither `:runtime` nor `:plugin` is given, the bundled
`priv/javy_plugin.wasm` is used.
## Examples
{:ok, mod} = Javex.compile(js)
{:ok, mod} = Javex.compile(js, runtime: :strict_runtime)
"""
@spec compile(String.t(), keyword()) :: {:ok, Module.t()} | {:error, term()}
def compile(source, opts \\ []) when is_binary(source) do
Module.compile(source, opts)
end
@doc """
Run a compiled module with the given input.
The default encoding is `:json`: `input` is JSON-encoded and written to the
module's stdin, and stdout is JSON-decoded into the returned term. For raw
byte I/O, pass `encoding: :raw`.
## Options
* `:runtime` - runtime to execute on. Defaults to the named default
runtime started by `Javex.Application`.
* `:encoding` - `:json` (default) or `:raw`.
* `:timeout` - hard wall-clock timeout in milliseconds. Default `5_000`.
* `:fuel` - wasmtime fuel budget for the call. Default `nil` (unlimited).
* `:max_memory` - memory cap in bytes. Default `nil`.
* `:env` - list of `{key, value}` WASI env vars.
## Examples
{:ok, %{"sum" => 3}} = Javex.run(mod, %{a: 1, b: 2})
{:ok, bytes} = Javex.run(mod, "raw in", encoding: :raw)
"""
@spec run(Module.t(), input(), keyword()) :: {:ok, output()} | {:error, term()}
def run(%Module{} = mod, input, opts \\ []) do
runtime = Keyword.get(opts, :runtime, Runtime)
Runtime.run(runtime, mod, input, opts)
end
end