Current section
Files
Jump to
Current section
Files
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