Boam.Runtime (boam v0.1.2)

Copy Markdown

GenServer wrapper around a single Boa runtime.

Each Boam.Runtime process owns one Rust resource, and that resource owns one dedicated worker thread where the Boa engine actually runs. This keeps the JavaScript engine isolated from BEAM scheduler threads while still allowing synchronous eval/2 calls from Elixir.

Options

  • :name - optional GenServer name for the runtime process
  • :dispatcher - existing dispatcher pid to receive beam.call(...) messages; if omitted, a fresh Boam.Dispatcher process is started automatically
  • :exports - map or keyword list of dispatch handlers when Boam starts its own dispatcher
  • :expose - nested tree of handlers that should also be installed as JavaScript functions during startup
  • :prelude - JavaScript source string or list of strings to evaluate during startup, after any generated :expose shims
  • :fallback - optional (name, args -> result) function used when a dispatch name is not present in :exports
  • :dispatch_timeout - timeout for a single beam.call(...) round-trip; defaults to 30_000, accepts :infinity

Dispatch Contract

JavaScript code may call:

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

The dispatcher receives the function name and arguments as JSON-compatible Elixir values and may reply with:

  • any JSON-compatible value
  • {:ok, value}
  • {:error, reason}

Returning {:error, reason} raises a JavaScript error with reason as the message.

Exposing Functions

:expose lets you define nested JavaScript functions from Elixir:

expose: %{
  console: %{
    log: fn [message] -> "logged: #{message}" end,
    warn: {:dispatch, "logger.warn", fn [message] -> "warn: #{message}" end}
  }
}

This creates console.log(...) and console.warn(...) automatically.

If you already manage your own dispatcher process, use :prelude together with Boam.JS.export_prelude/1 instead of :expose.

Notes

  • Avoid synchronously calling back into the same runtime from a dispatch handler before replying, or the call will deadlock.
  • Top-level JavaScript undefined is surfaced as {:ok, :undefined}.

Summary

Functions

Returns a specification to start this module under a supervisor.

Evaluates a JavaScript string inside the runtime.

Starts a runtime process.

Types

dispatch_timeout()

@type dispatch_timeout() :: timeout()

export_name()

@type export_name() :: atom() | String.t()

prelude()

@type prelude() :: String.t() | [String.t()]

start_option()

@type start_option() ::
  {:name, GenServer.name()}
  | {:dispatcher, pid()}
  | {:exports,
     %{optional(export_name()) => Boam.Dispatcher.handler()}
     | keyword(Boam.Dispatcher.handler())}
  | {:expose, Boam.JS.export_tree()}
  | {:prelude, prelude()}
  | {:fallback, (String.t(), list() -> term())}
  | {:dispatch_timeout, dispatch_timeout()}

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

eval(runtime, source)

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

Evaluates a JavaScript string inside the runtime.

The return value is either:

  • {:ok, value} for a JSON-compatible JavaScript result
  • {:ok, :undefined} for top-level JavaScript undefined
  • {:error, message} if evaluation or dispatch fails

Example

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

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

start_link(opts \\ [])

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

Starts a runtime process.

See the module documentation for the supported options and dispatch contract.