QuickBEAM (QuickBEAM v0.11.0)
View SourceQuickJS-NG JavaScript engine embedded in the BEAM.
Each runtime is a GenServer holding a persistent JS context.
State, functions, and variables survive across eval/2 and call/3 calls.
iex> {:ok, rt} = QuickBEAM.start()
iex> {:ok, 3} = QuickBEAM.eval(rt, "1 + 2")
iex> QuickBEAM.stop(rt)
:okHandlers
JS code can call Elixir functions via Beam.call and Beam.callSync:
iex> {:ok, rt} = QuickBEAM.start(handlers: %{
...> "greet" => fn [name] -> "Hello, #{name}!" end
...> })
iex> QuickBEAM.eval(rt, ~s[Beam.callSync("greet", "world")])
{:ok, "Hello, world!"}
iex> QuickBEAM.stop(rt)
:okSupervision
Runtimes work as OTP children:
children = [
{QuickBEAM, name: :app, script: "priv/js/app.js", handlers: %{...}},
]
Supervisor.start_link(children, strategy: :one_for_one)Options
:name— GenServer name registration:id— child spec ID (defaults to:name, then module):handlers— map of handler name → function forBeam.call/Beam.callSync:script— path to a JS/TS file evaluated on startup. TypeScript files are automatically transformed. Files withimportstatements are automatically bundled — imports are resolved from the filesystem andnode_modules/, then compiled into a single script via OXC.:memory_limit— maximum JS heap in bytes (default: 256 MB):max_stack_size— maximum JS call stack in bytes (default: 8 MB):wasm_stack_size— WASM operand stack in bytes for guests started via the JSWebAssembly.instantiatepath (default: 65536). Distinct from:max_stack_size(the JS call stack); raise it for guests whose deep init overflows the 64 KB default.:wasm_heap_size— WASM auxiliary heap in bytes for the same path (default: 65536):max_convert_depth— maximum nesting depth for JS→BEAM value conversion (default: 32):max_convert_nodes— maximum total nodes for JS→BEAM value conversion (default: 10,000)
DOM
Each runtime has a live DOM tree backed by lexbor. JS gets document,
querySelector, createElement, etc. Elixir can read the DOM directly
via dom_find/2, dom_find_all/2, dom_text/2, dom_attr/3, and
dom_html/1 — returning Floki-compatible {tag, attrs, children} tuples.
Summary
Functions
Call a global JavaScript function by name.
Compile JavaScript source to bytecode without executing it.
Get current JS coverage data for a runtime.
Disassemble precompiled bytecode into a %QuickBEAM.Bytecode{} struct.
Compile JavaScript source and disassemble the resulting bytecode.
Get an attribute value from the first element matching a CSS selector.
Find the first element matching a CSS selector in the runtime's DOM.
Find all elements matching a CSS selector in the runtime's DOM.
Serialize the entire DOM tree to an HTML string.
Extract text content from the first element matching a CSS selector.
Evaluate JavaScript code and return the result.
Evaluate TypeScript code by transforming it to JavaScript first.
Get the value of a JS global. Works like eval(rt, "name") but safer —
the name is accessed as a property, not evaluated as code.
List global names defined in the JS context.
Return runtime diagnostics: registered handlers, memory stats, and JS global count.
Load a native addon (.node file) via N-API.
Execute precompiled bytecode from compile/2.
Load an ES module into the runtime.
Return QuickJS memory usage statistics.
Reset the runtime to a fresh JS context. Clears all state and functions.
Send a message to the runtime's JS handler.
Set a JS global variable from Elixir.
Start a new JavaScript runtime.
Stop a runtime and free its resources.
Types
@type js_result() :: {:ok, term()} | {:error, QuickBEAM.JSError.t()}
@type runtime() :: GenServer.server()
Functions
Call a global JavaScript function by name.
Arguments are converted to JS values; the return value is converted back. Promise-returning functions are automatically awaited.
iex> {:ok, rt} = QuickBEAM.start()
iex> QuickBEAM.eval(rt, "function add(a, b) { return a + b }")
iex> QuickBEAM.call(rt, "add", [2, 3])
{:ok, 5}
iex> QuickBEAM.stop(rt)
:okOptions
:timeout— maximum execution time in milliseconds (default: no limit)
@spec compile(runtime(), String.t()) :: {:ok, binary()} | {:error, QuickBEAM.JSError.t()}
Compile JavaScript source to bytecode without executing it.
Returns {:ok, bytecode} where bytecode is a binary that can be loaded
into any runtime with load_bytecode/2. Useful for precompilation, caching,
and transferring compiled code between runtimes or nodes.
Get current JS coverage data for a runtime.
Returns {:ok, %{filename => %{line => hit_count}}}.
Coverage must be enabled via QuickBEAM.Cover.
@spec disasm(binary()) :: {:ok, QuickBEAM.Bytecode.t()} | {:error, String.t()}
Disassemble precompiled bytecode into a %QuickBEAM.Bytecode{} struct.
Does not require a running runtime — creates a temporary QuickJS context internally to parse the binary format.
{:ok, bytecode} = QuickBEAM.compile(rt, "function add(a, b) { return a + b }")
{:ok, %QuickBEAM.Bytecode{}} = QuickBEAM.disasm(bytecode)Also accepts JavaScript source code and a runtime, compiling it first:
{:ok, %QuickBEAM.Bytecode{}} = QuickBEAM.disasm(rt, "function add(a, b) { return a + b }")
@spec disasm(runtime(), String.t()) :: {:ok, QuickBEAM.Bytecode.t()} | {:error, term()}
Compile JavaScript source and disassemble the resulting bytecode.
{:ok, %QuickBEAM.Bytecode{cpool: [%QuickBEAM.Bytecode{name: "add"}]}} =
QuickBEAM.disasm(rt, "function add(a, b) { return a + b }")
Get an attribute value from the first element matching a CSS selector.
Returns nil if the element or attribute is not found.
{:ok, rt} = QuickBEAM.start()
QuickBEAM.eval(rt, ~s[document.body.innerHTML = '<a href="/page">link</a>'])
{:ok, "/page"} = QuickBEAM.dom_attr(rt, "a", "href")
Find the first element matching a CSS selector in the runtime's DOM.
Returns the element as a Floki-compatible {tag, attrs, children} tuple,
or nil if no match is found. This reads the live DOM tree directly from
the native layer — no JS execution or HTML re-parsing.
{:ok, rt} = QuickBEAM.start()
QuickBEAM.eval(rt, "document.body.innerHTML = '<p class="intro">Hello</p>'")
{:ok, {"p", [{"class", "intro"}], ["Hello"]}} = QuickBEAM.dom_find(rt, "p.intro")
Find all elements matching a CSS selector in the runtime's DOM.
Returns a list of Floki-compatible {tag, attrs, children} tuples.
{:ok, rt} = QuickBEAM.start()
QuickBEAM.eval(rt, ~s[document.body.innerHTML = '<ul><li>a</li><li>b</li></ul>'])
{:ok, items} = QuickBEAM.dom_find_all(rt, "li")
length(items) # => 2
Serialize the entire DOM tree to an HTML string.
{:ok, rt} = QuickBEAM.start()
QuickBEAM.eval(rt, "document.body.innerHTML = '<p>Hello</p>'")
{:ok, html} = QuickBEAM.dom_html(rt)
Extract text content from the first element matching a CSS selector.
{:ok, rt} = QuickBEAM.start()
QuickBEAM.eval(rt, "document.body.innerHTML = '<h1>Title</h1>'")
{:ok, "Title"} = QuickBEAM.dom_text(rt, "h1")
Evaluate JavaScript code and return the result.
Top-level await is supported.
iex> {:ok, rt} = QuickBEAM.start()
iex> QuickBEAM.eval(rt, "40 + 2")
{:ok, 42}
iex> QuickBEAM.eval(rt, "await Promise.all([1, 2].map(x => Promise.resolve(x)))")
{:ok, [1, 2]}
iex> QuickBEAM.stop(rt)
:okOptions
:timeout— maximum execution time in milliseconds (default: no limit). If exceeded, the JS execution is interrupted and an error is returned. The runtime remains usable after a timeout.QuickBEAM.eval(rt, "while(true) {}", timeout: 1000) # => {:error, %QuickBEAM.JSError{message: "interrupted", ...}}:vars— a map of variable names to values, available in the code as globals. Values are converted using the standard BEAM→JS conversion. Variables are automatically cleaned up after evaluation, even if the code throws an error.QuickBEAM.eval(rt, "name.toUpperCase()", vars: %{"name" => "quickbeam"}) # => {:ok, "QUICKBEAM"} QuickBEAM.eval(rt, "items.map(i => i.price * i.qty).reduce((a, b) => a + b, 0)", vars: %{"items" => [%{"price" => 10, "qty" => 3}, %{"price" => 5, "qty" => 2}]}) # => {:ok, 40}
Evaluate TypeScript code by transforming it to JavaScript first.
Equivalent to OXC.transform!/2 followed by eval/3, but in a single call.
iex> {:ok, rt} = QuickBEAM.start()
iex> QuickBEAM.eval_ts(rt, "const x: number = 40 + 2; x")
{:ok, 42}
iex> QuickBEAM.stop(rt)
:okOptions
Accepts the same options as eval/3 (e.g., :timeout).
Get the value of a JS global. Works like eval(rt, "name") but safer —
the name is accessed as a property, not evaluated as code.
Returns the value converted to Elixir terms. For objects, returns a map of enumerable own properties. For functions, returns a map with metadata.
Examples
QuickBEAM.get_global(rt, "myVar")
{:ok, 42}
QuickBEAM.get_global(rt, "myObj")
{:ok, %{"x" => 1, "y" => 2}}
QuickBEAM.get_global(rt, "nonexistent")
{:ok, nil}
@spec globals( runtime(), keyword() ) :: {:ok, [String.t()]} | {:error, QuickBEAM.JSError.t()}
List global names defined in the JS context.
By default returns all globalThis property names. Pass user_only: true
to exclude JS builtins and QuickBEAM internals — only names defined by
your scripts.
Examples
{:ok, all} = QuickBEAM.globals(rt)
# ["Array", "Boolean", "Buffer", "Object", "console", "myVar", ...]
{:ok, mine} = QuickBEAM.globals(rt, user_only: true)
# ["myVar", "myFunc"]
Return runtime diagnostics: registered handlers, memory stats, and JS global count.
Load a native addon (.node file) via N-API.
The addon is loaded with dlopen and its napi_register_module_v1 (or
napi_module_register) entry point is called. Returns the addon's exports
as an Elixir term.
Addon initialization is process-global native state. QuickBEAM initializes a
canonical addon path once per BEAM instance. Loading that path again in the
same runtime reuses its cached exports, so multiple global aliases are safe.
Loading it in another runtime, or after reset/1, returns
{:error, {:addon_already_initialized, canonical_path}} by default.
Options
:as- set the addon's exports as a global JS variable with this name, making the functions callable fromeval/3andcall/3:allow_reinitialization- opt into calling the native initializer again in a different JavaScript context. Defaults tofalse. Only enable this when the addon explicitly documents multi-environment initialization as safe; incompatible addons can terminate the BEAM VM.
Examples
QuickBEAM.load_addon(rt, "/path/to/addon.node")
QuickBEAM.load_addon(rt, "/path/to/crc32.node", as: "crc32")
QuickBEAM.eval(rt, "crc32.crc32('hello')")
Execute precompiled bytecode from compile/2.
The bytecode runs in the current runtime's context, with access to all globals, handlers, and builtins.
Load an ES module into the runtime.
iex> {:ok, rt} = QuickBEAM.start()
iex> code = "export function add(a, b) { return a + b; }"
iex> QuickBEAM.load_module(rt, "math", code)
:ok
iex> QuickBEAM.stop(rt)
:ok
Return QuickJS memory usage statistics.
Reset the runtime to a fresh JS context. Clears all state and functions.
iex> {:ok, rt} = QuickBEAM.start()
iex> QuickBEAM.eval(rt, "globalThis.x = 42")
iex> QuickBEAM.reset(rt)
:ok
iex> QuickBEAM.eval(rt, "typeof x")
{:ok, "undefined"}
iex> QuickBEAM.stop(rt)
:ok
Send a message to the runtime's JS handler.
The message is delivered to the callback registered via Beam.onMessage
in JS. If no handler is registered, the message is silently discarded.
Set a JS global variable from Elixir.
The value is converted using the standard BEAM→JS conversion (no JSON).
Examples
QuickBEAM.set_global(rt, "config", %{"theme" => "dark", "limit" => 100})
{:ok, "dark"} = QuickBEAM.eval(rt, "config.theme")
QuickBEAM.set_global(rt, "items", [1, 2, 3])
{:ok, 3} = QuickBEAM.eval(rt, "items.length")
@spec start(keyword()) :: GenServer.on_start()
Start a new JavaScript runtime.
Returns {:ok, pid} on success.
Options
:name— register the GenServer under this name:handlers—%{String.t() => function}map forBeam.call/Beam.callSync:script— path to a JS/TS file to evaluate on startup (auto-bundles imports):apis— which API surfaces to load (default:[:browser]):browser— Web APIs (fetch, DOM, WebSocket, crypto, streams, …):node— Node.js compat (process, path, fs, os)[:browser, :node]— bothfalse— bare QuickJS engine, no polyfills
:define—%{String.t() => term()}of globals to inject before the script runs. Values are JSON-encoded. Useful for passing config withoutBeam.callSync.QuickBEAM.start(script: "build.ts", define: %{"outputDir" => "/tmp/site"}):memory_limit— maximum JS heap in bytes (default: 256 MB):max_stack_size— maximum JS call stack in bytes (default: 8 MB):wasm_stack_size— WASM operand stack in bytes for guests started via the JSWebAssembly.instantiatepath (default: 65536). Distinct from:max_stack_size(the JS call stack); raise it for guests whose deep init overflows the 64 KB default.:wasm_heap_size— WASM auxiliary heap in bytes for the same path (default: 65536):max_convert_depth— maximum nesting depth for JS→BEAM value conversion (default: 32):max_convert_nodes— maximum total nodes for JS→BEAM value conversion (default: 10,000)
@spec stop(runtime()) :: :ok
Stop a runtime and free its resources.