Boam

Boam is an Elixir wrapper around the Boa JavaScript engine, implemented as a Rustler NIF.

The library starts a dedicated JavaScript runtime per Elixir process and lets JavaScript call back into the BEAM through an explicit dispatch bridge.

Features

Installation

Add boam to your list of dependencies in mix.exs:

def deps do
  [
    {:boam, "~> 0.1.0"}
  ]
end

Quick Start

{:ok, runtime} =
  Boam.start_link(
    exports: %{
      sum: fn [left, right] -> left + right end,
      greet: fn [name] -> "hello #{name}" end
    }
  )

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

Boam.eval(runtime, "beam.call('sum', 2, 3)")
#=> {:ok, 5}

Boam.eval(runtime, "beam.call('greet', 'Ada')")
#=> {:ok, "hello Ada"}

Startup Prelude

Run JavaScript during runtime startup with prelude::

{:ok, runtime} =
  Boam.start_link(
    prelude: [
      "globalThis.appName = 'boam';",
      "globalThis.version = 1;"
    ]
  )

Those snippets run before your first call to Boam.eval/2.

Automatic Function Exposure

If you want JavaScript functions like console.log(...) without writing wrapper code by hand, use expose::

{:ok, runtime} =
  Boam.start_link(
    expose: %{
      console: %{
        log: fn [message] -> "logged: #{message}" end,
        warn: {:dispatch, "logger.warn", fn [message] -> "warn: #{message}" end}
      }
    }
  )

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

Leaf dispatch names default to the dot-joined path, so console.log dispatches to "console.log" unless you override it with {:dispatch, "custom.name", handler}.

Manual Shim Generation

If you want to keep dispatch setup separate from runtime startup, generate the shim code yourself:

prelude =
  Boam.JS.export_prelude(%{
    console: %{
      log: true,
      error: {:dispatch, "logger.error"}
    }
  })

{:ok, runtime} =
  Boam.start_link(
    prelude: prelude,
    fallback: fn name, args -> {name, args} end
  )

Value Model

Boam intentionally restricts the bridge to JSON-compatible values:

For predictable round-tripping, return Elixir maps with string keys from dispatch handlers.

Dispatching Into Elixir

JavaScript code can call:

beam.call("name", arg1, arg2)

That request is delivered to a Boam.Dispatcher process on the BEAM side. A handler can return:

If a handler crashes, the JavaScript caller receives an error instead of hanging forever.

Architecture

Generating Docs

Generate the docs locally with:

mix docs

The generated site will be written to doc/.