Sandboxed JavaScript execution via QuickJS-NG.
eval/2 and eval/3 are async-aware: if the completion value of the
guest code is a promise (an async arrow, a .then chain, ...), the
runtime drives the job queue until it settles and returns the settled
value — a rejection comes back as a structured error. Synchronous code
returns directly, as before.
Each runtime runs on a dedicated OS thread with a hard memory cap, a stack limit, and a JS compute deadline. The deadline covers JS execution only: time spent inside Elixir callbacks is excluded, so a slow host call never reads as guest misbehavior.
Failures are {:error, %ExSafejs.Error{}} with a :kind callers can
branch on — see ExSafejs.Error.
Elixir callbacks are installed as real JS functions whose dispatch path is
captured host-side; guest code may shadow or delete the global binding but
that only loses its own access. There are no reserved __* globals.
Summary
Functions
Check if a runtime is alive.
Evaluate JavaScript code and return the result.
Evaluate JavaScript code with pre-registered Elixir callbacks.
Start a new JavaScript runtime on a dedicated OS thread.
Stop a runtime. Idempotent — safe to call multiple times.
Types
@type js_result() :: {:ok, term()} | {:error, ExSafejs.Error.t()}
Functions
@spec alive?(runtime()) :: boolean()
Check if a runtime is alive.
Evaluate JavaScript code and return the result.
{:ok, 3} = ExSafejs.eval(rt, "1 + 2")
{:ok, 3} = ExSafejs.eval(rt, "(async () => 1 + 2)()")
Evaluate JavaScript code with pre-registered Elixir callbacks.
Each callback receives its arguments as a list and must return
{:ok, value} or {:error, reason} with a binary reason. A returned
{:error, reason} becomes a catchable JS exception carrying reason
verbatim — never put a secret in it. A raised exception instead
surfaces to the guest as a generic "host function failed" exception; if
the guest doesn't catch it, the eval returns a :host_error carrying the
real exception message to the Elixir caller.
callbacks = %{"add" => fn [a, b] -> {:ok, a + b} end}
{:ok, 5} = ExSafejs.eval(rt, "add(2, 3)", callbacks)Callbacks work under await too — a blocking host call is simply a value
by the time the guest sees it:
{:ok, 5} = ExSafejs.eval(rt, "(async () => await add(2, 3))()", callbacks)
Start a new JavaScript runtime on a dedicated OS thread.
Options
:timeout— max JS compute time per eval in milliseconds, excluding time spent in Elixir callbacks (default30_000):memory_limit— max JS heap in bytes (default268_435_456):max_stack_size— max JS stack in bytes (default1_048_576):gc_threshold— GC trigger threshold in bytes (default4_194_304)
@spec stop(runtime()) :: :ok
Stop a runtime. Idempotent — safe to call multiple times.