# `QuickBEAM`
[🔗](https://github.com/elixir-volt/quickbeam/blob/v0.10.6/lib/quickbeam.ex#L1)

QuickJS-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)
    :ok

## Handlers

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)
    :ok

## Supervision

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 for `Beam.call`/`Beam.callSync`
  * `:script` — path to a JS/TS file evaluated on startup. TypeScript files
    are automatically transformed. Files with `import` statements are
    automatically bundled — imports are resolved from the filesystem and
    `node_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: 4 MB)
  * `: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.

# `js_result`

```elixir
@type js_result() :: {:ok, term()} | {:error, QuickBEAM.JSError.t()}
```

# `runtime`

```elixir
@type runtime() :: GenServer.server()
```

# `call`

```elixir
@spec call(runtime(), String.t(), list(), keyword()) :: js_result()
```

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)
    :ok

## Options

  * `:timeout` — maximum execution time in milliseconds (default: no limit)

# `compile`

```elixir
@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.

# `coverage`

```elixir
@spec coverage(runtime()) :: {:ok, map()} | {:error, term()}
```

Get current JS coverage data for a runtime.

Returns `{:ok, %{filename => %{line => hit_count}}}`.
Coverage must be enabled via `QuickBEAM.Cover`.

# `disasm`

```elixir
@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 }")

# `disasm`

```elixir
@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 }")

# `dom_attr`

```elixir
@spec dom_attr(runtime(), String.t(), String.t()) :: {:ok, String.t() | nil}
```

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")

# `dom_find`

```elixir
@spec dom_find(runtime(), String.t()) :: {:ok, tuple() | nil}
```

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")

# `dom_find_all`

```elixir
@spec dom_find_all(runtime(), String.t()) :: {:ok, list()}
```

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

# `dom_html`

```elixir
@spec dom_html(runtime()) :: {:ok, String.t()}
```

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)

# `dom_text`

```elixir
@spec dom_text(runtime(), String.t()) :: {:ok, String.t()}
```

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")

# `eval`

```elixir
@spec eval(runtime(), String.t(), keyword()) :: js_result()
```

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)
    :ok

## Options

  * `: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}

# `eval_ts`

```elixir
@spec eval_ts(runtime(), String.t(), keyword()) :: js_result()
```

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)
    :ok

## Options

Accepts the same options as `eval/3` (e.g., `:timeout`).

# `get_global`

```elixir
@spec get_global(runtime(), String.t()) :: js_result()
```

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}

# `globals`

```elixir
@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"]

# `info`

```elixir
@spec info(runtime()) :: map()
```

Return runtime diagnostics: registered handlers, memory stats, and JS global count.

# `load_addon`

```elixir
@spec load_addon(runtime(), String.t(), keyword()) :: {:ok, term()} | {:error, term()}
```

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.

## Options

  * `:as` - set the addon's exports as a global JS variable with this name,
    making the functions callable from `eval/3` and `call/3`

## 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')")

# `load_bytecode`

```elixir
@spec load_bytecode(runtime(), binary()) :: js_result()
```

Execute precompiled bytecode from `compile/2`.

The bytecode runs in the current runtime's context, with access to all
globals, handlers, and builtins.

# `load_module`

```elixir
@spec load_module(runtime(), String.t(), String.t()) :: :ok | {:error, String.t()}
```

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

# `memory_usage`

```elixir
@spec memory_usage(runtime()) :: map()
```

Return QuickJS memory usage statistics.

# `reset`

```elixir
@spec reset(runtime()) :: :ok | {:error, String.t()}
```

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_message`

```elixir
@spec send_message(runtime(), term()) :: :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_global`

```elixir
@spec set_global(runtime(), String.t(), term()) :: :ok
```

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")

# `start`

```elixir
@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 for `Beam.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]` — both
    * `false` — 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 without `Beam.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)
  * `: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)

# `stop`

```elixir
@spec stop(runtime()) :: :ok
```

Stop a runtime and free its resources.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
