Packages

Embedded QuickJS-NG JavaScript engine for Elixir via Rustler NIF

Retired package: Deprecated - Use {:quickbeam, "~> 0.7.1"} — https://github.com/elixir-volt/quickbeam

Current section

Files

Jump to
quickjs_ex lib quick_js_ex runtime.ex
Raw

lib/quick_js_ex/runtime.ex

defmodule QuickJSEx.Runtime do
@moduledoc """
A GenServer wrapping a QuickJS-NG JavaScript runtime.
Each runtime runs on a dedicated OS thread with its own JS context.
Multiple runtimes can be started for concurrency.
"""
use GenServer
@enforce_keys [:resource]
defstruct [:resource]
@type t :: %__MODULE__{resource: reference()}
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, Keyword.take(opts, [:name]))
end
@spec eval(GenServer.server(), String.t()) :: QuickJSEx.js_result()
def eval(server, code) when is_binary(code) do
GenServer.call(server, {:eval, code}, :infinity)
end
@spec call(GenServer.server(), String.t(), list()) :: QuickJSEx.js_result()
def call(server, fn_name, args) when is_binary(fn_name) and is_list(args) do
GenServer.call(server, {:call, fn_name, args}, :infinity)
end
@spec load_module(GenServer.server(), String.t(), String.t()) ::
:ok | {:error, String.t()}
def load_module(server, name, code) when is_binary(name) and is_binary(code) do
GenServer.call(server, {:load_module, name, code}, :infinity)
end
@spec reset(GenServer.server()) :: :ok | {:error, String.t()}
def reset(server) do
GenServer.call(server, :reset, :infinity)
end
@spec stop(GenServer.server()) :: :ok
def stop(server) do
GenServer.stop(server)
end
@impl true
def init(_opts) do
QuickJSEx.Native.start_runtime(self())
receive do
{:ok, resource} -> {:ok, %__MODULE__{resource: resource}}
{:error, reason} -> {:stop, reason}
after
5_000 -> {:stop, :timeout}
end
end
@impl true
def handle_call({:eval, code}, _from, %{resource: resource} = state) do
case QuickJSEx.Native.eval_sync(resource, code) do
{:ok, json} -> {:reply, decode_result(json), state}
{:error, msg} -> {:reply, {:error, msg}, state}
end
end
def handle_call({:call, fn_name, args}, _from, %{resource: resource} = state) do
args_json = Jason.encode!(args)
case QuickJSEx.Native.call_sync(resource, fn_name, args_json) do
{:ok, json} -> {:reply, decode_result(json), state}
{:error, msg} -> {:reply, {:error, msg}, state}
end
end
def handle_call({:load_module, name, code}, _from, %{resource: resource} = state) do
case QuickJSEx.Native.load_module(resource, name, code) do
{:ok, _} -> {:reply, :ok, state}
{:error, msg} -> {:reply, {:error, msg}, state}
end
end
def handle_call(:reset, _from, %{resource: resource} = state) do
case QuickJSEx.Native.reset_runtime(resource) do
{:ok, _} -> {:reply, :ok, state}
{:error, msg} -> {:reply, {:error, msg}, state}
end
end
@impl true
def terminate(_reason, %{resource: resource}) do
QuickJSEx.Native.stop_runtime(resource)
:ok
end
def terminate(_reason, _state), do: :ok
@spec decode_result(String.t()) :: {:ok, term()} | {:error, String.t()}
defp decode_result(json) do
case Jason.decode(json) do
{:ok, value} -> {:ok, value}
{:error, _} -> {:ok, json}
end
end
end