Current section
Files
Jump to
Current section
Files
doc/Boam.Runtime.md
# `Boam.Runtime`
`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`](`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:
```javascript
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:
```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}`.
# `dispatch_timeout`
```elixir
@type dispatch_timeout() :: timeout()
```
# `export_name`
```elixir
@type export_name() :: atom() | String.t()
```
# `prelude`
```elixir
@type prelude() :: String.t() | [String.t()]
```
# `start_option`
```elixir
@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()}
```
# `child_spec`
Returns a specification to start this module under a supervisor.
See `Supervisor`.
# `eval`
```elixir
@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
```elixir
{: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`
```elixir
@spec start_link([start_option()]) :: GenServer.on_start()
```
Starts a runtime process.
See the module documentation for the supported options and dispatch contract.
---
*Consult [api-reference.md](api-reference.md) for complete listing*