# `Boam`

High-level entrypoint for running JavaScript through Boa from Elixir.

`Boam` wraps a dedicated Boa runtime behind a `GenServer`. Each runtime owns a
single Rust worker thread because Boa `Context` values are not thread-safe.

JavaScript code can call back into Elixir with `beam.call(name, ...args)`. The
dispatch itself happens through a separate BEAM process, which keeps the
runtime process focused on driving the JavaScript engine.

You can either:

  * pass your own dispatcher with `:dispatcher`
  * install JavaScript shims with `:js_expose`
  * register Elixir handlers with `:exports` when Boam manages the dispatcher
  * use legacy combined config with `:expose`
  * run startup JavaScript with `:prelude`
  * generate shim code manually with `Boam.JS.export_prelude/1`

## Quick Start

```elixir
{:ok, _dispatcher} =
  MyApp.JsDispatcher.start_link(name: MyApp.JsDispatcher)

{:ok, runtime} =
  Boam.start_link(
    dispatcher: MyApp.JsDispatcher,
    js_expose: %{math: %{sum: true}}
  )

Boam.eval(runtime, "1 + 2")
#=> {:ok, 3}

Boam.eval(runtime, "math.sum(2, 3)")
#=> {:ok, 5}
```

The built-in dispatcher is available for simpler cases:

```elixir
{:ok, runtime} =
  Boam.start_link(
    exports: %{
      "console.log" => fn [message] -> "logged: #{message}" end
    },
    js_expose: %{
      console: %{
        log: true
      }
    }
  )

Boam.eval(runtime, "console.log('hello')")
#=> {:ok, "logged: hello"}
```

## Value Bridge

Values crossing between JavaScript and Elixir are intentionally restricted to
JSON-compatible data:

  * `null` maps to `nil`
  * booleans, numbers, strings, arrays, and objects round-trip normally
  * top-level JavaScript `undefined` is returned as `:undefined`
  * `undefined` cannot be passed through `beam.call/1`

Returned JavaScript objects are serialized through Boa's JSON conversion, so
object keys come back to Elixir as strings.

## Architecture

  * [`Boam.Runtime`](`Boam.Runtime`) owns the Rust runtime resource
  * [`Boam.Dispatcher`](`Boam.Dispatcher`) receives `beam.call(...)` requests
  * [`Boam.JS`](`Boam.JS`) generates JavaScript shim preludes
  * `Boam` provides the small public entrypoint for the common case

# `start_option`

```elixir
@type start_option() ::
  {:name, GenServer.name()}
  | {:dispatcher, GenServer.server()}
  | {:exports, map() | keyword()}
  | {:js_expose, Boam.JS.export_tree()}
  | {:expose, map() | keyword()}
  | {:prelude, String.t() | [String.t()]}
  | {:fallback, (String.t(), list() -&gt; term())}
  | {:dispatch_timeout, timeout()}
```

# `dispatcher`

```elixir
@spec dispatcher(GenServer.server()) :: pid()
```

Returns the dispatcher pid attached to a runtime.

# `eval`

```elixir
@spec eval(GenServer.server(), String.t()) :: {:ok, term()} | {:error, String.t()}
```

Evaluates JavaScript source code inside a running runtime.

This is a convenience wrapper around `Boam.Runtime.eval/2`.

# `expose_js`

```elixir
@spec expose_js(GenServer.server(), Boam.JS.export_tree()) ::
  :ok | {:error, String.t()}
```

Installs JavaScript shim functions on `globalThis`.

# `notify_expose_js`

```elixir
@spec notify_expose_js(GenServer.server(), Boam.JS.export_tree()) :: :ok
```

Queues JavaScript shim installation through an asynchronous message.

# `start_link`

```elixir
@spec start_link([start_option()]) :: GenServer.on_start()
```

Starts a Boa runtime process.

This is a convenience wrapper around `Boam.Runtime.start_link/1`.

See `Boam.Runtime` for the full option contract.

---

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