Wasmex.Components (wasmex v0.15.0)

Copy Markdown

This is the entry point to support for the WebAssembly Component Model.

The Component Model is a higher-level way to interact with WebAssembly modules that provides:

  • Better type safety through interface types
  • Standardized way to define imports and exports using WIT (WebAssembly Interface Types)
  • WASI support for system interface capabilities

Basic Usage

To use a WebAssembly component:

  1. Start a component instance:

    # Using raw bytes
    bytes = File.read!("path/to/component.wasm")
    {:ok, pid} = Wasmex.Components.start_link(%{bytes: bytes})
    
    # Using a file path
    {:ok, pid} = Wasmex.Components.start_link(%{path: "path/to/component.wasm"})
    
    # With WASI support
    {:ok, pid} = Wasmex.Components.start_link(%{
    path: "path/to/component.wasm",
    wasi: %Wasmex.Wasi.WasiP2Options{}
    })
    
    # With imports (host functions the component can call)
    {:ok, pid} = Wasmex.Components.start_link(%{
    bytes: bytes,
    imports: %{
     "host_function" => {:fn, &MyModule.host_function/1}
    }
    })
  2. Call exported functions:

    {:ok, result} = Wasmex.Components.call_function(pid, "exported_function", ["param1"])

Component Interface Types

The component model supports the following WIT (WebAssembly Interface Type) types:

Supported Types

  • Primitive Types

    • Integers: s8, s16, s32, s64, u8, u16, u32, u64
    • Floats: f32, f64
    • bool
    • string
    • char (maps to Elixir strings with a single character)
      char
      "A"  # or from a code point
      937  # Ω
  • Compound Types

    • record (maps to Elixir maps with atom keys)

      record point { x: u32, y: u32 }
      %{x: 1, y: 2}
    • list<T> (maps to Elixir lists)

      list<u32>
      [1, 2, 3]
    • tuple<T1, T2> (maps to Elixir tuples)

      tuple<u32, string>
      {1, "two"}
    • option<T> (maps to :none or {:some, value})

      option<u32>
      :none  # or
      {:some, 42}
    • enum (maps to Elixir atoms)

      enum size { s, m, l }
      :s  # or :m or :l
    • variant (tagged unions, maps to atoms or tuples)

      variant filter { all, none, lt(u32) }
      :all     # variant without payload
      :none    # variant without payload
      {:lt, 7} # variant with payload
    • flags (maps to Elixir maps with boolean values)

      flags permission { read, write, exec }
      %{read: true, write: true, exec: false}
      # Note: When returned from WebAssembly, only the flags set to true are included
      # %{read: true, exec: true}
    • result<T, E> (maps to Elixir tuples with :ok/:error)

      result<u32, u32>
      {:ok, 42}      # success case
      {:error, 404}  # error case

Guest Resources

Guest-owned resources exported by a component can be constructed and called with Wasmex.Components.GuestResource. It can generate an arity-aware API from WIT:

defmodule Counter do
  use Wasmex.Components.GuestResource,
    wit: File.read!("counter.wit"),
    resource: "counter"
end

{:ok, counter} = Counter.new(component_pid, 42)
{:ok, value} = Counter.get_value(counter)
:ok = Counter.drop(counter)

Host Resources

Host-owned resources imported by a component can be implemented with Wasmex.Components.HostResource:

defmodule CounterHost do
  use Wasmex.Components.HostResource,
    wit_path: "wit",
    resource: "counter"

  def new(initial), do: MyCounter.start(initial)
  def get_value(counter), do: MyCounter.value(counter)
  def drop(counter), do: MyCounter.stop(counter)
end

{:ok, component_pid} =
  Wasmex.Components.start_link(
    bytes: component,
    imports: CounterHost.imports()
  )

Constructors return an opaque Elixir term representing the resource. Methods and the destructor receive that term. Borrowed handles remain live, while owned handles transfer ownership to the receiving callback. wit_path: resolves dependency packages from a standard wit/deps directory.

Guest-owned resource values passed through arbitrary freestanding component functions are not yet supported.

Support for the Component Model should be considered beta quality.

Options

The start_link/1 function accepts the following options:

  • :bytes - Raw WebAssembly component bytes (mutually exclusive with :path)
  • :path - Path to a WebAssembly component file (mutually exclusive with :bytes)
  • :wasi - Optional WASI configuration as Wasmex.Wasi.WasiP2Options struct for system interface capabilities
  • :imports - Optional map of host functions that can be called by the WebAssembly component
    • Keys are function names as strings
    • Values are tuples of {:fn, function} where function is the host function to call
    • Modules generated with Wasmex.Components.HostResource expose imports/0 definitions for host-owned resources

Additionally, any standard GenServer options (like :name) are supported.

Examples

# With raw bytes
{:ok, pid} = Wasmex.Components.start_link(%{
  bytes: File.read!("component.wasm"),
  name: MyComponent
})

# With WASI configuration
{:ok, pid} = Wasmex.Components.start_link(%{
  path: "component.wasm",
  wasi: %Wasmex.Wasi.WasiP2Options{
    allow_http: true
  }
})

# With host functions
{:ok, pid} = Wasmex.Components.start_link(%{
  path: "component.wasm",
  imports: %{
    "log" => {:fn, &IO.puts/1},
    "add" => {:fn, fn(a, b) -> a + b end}
  }
})

Summary

Functions

Calls an exported component function.

Returns a specification to start this module under a supervisor.

Returns the low-level component instance owned by a component server.

Starts a new WebAssembly component instance.

Types

function_name_or_path()

@type function_name_or_path() :: String.t() | atom() | [String.t() | atom()] | tuple()

Functions

call_function(pid, name_or_path, params, timeout \\ 5000)

@spec call_function(GenServer.server(), function_name_or_path(), [any()], timeout()) ::
  {:ok, any()} | {:error, any()}

Calls an exported component function.

name_or_path may be a function name or a path identifying an exported interface function. Parameters and results use the Elixir representations described in the component interface type documentation above.

The default timeout is 5 seconds. A timeout exits the calling process unless it traps exits, just like GenServer.call/3. Wasmtime component calls cannot currently be cancelled without invalidating the component instance, so a timed-out call continues in the background. Its late result is discarded and later operations on the same Store wait for it to finish. Use :infinity for calls whose duration is intentionally unbounded.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

instance(pid)

Returns the low-level component instance owned by a component server.

Guest resource APIs accept either this value or the component server directly.

start_link(opts)

Starts a new WebAssembly component instance.

Options

  • :bytes - Raw WebAssembly component bytes (mutually exclusive with :path)
  • :path - Path to a WebAssembly component file (mutually exclusive with :bytes)
  • :wasi - Optional WASI configuration as Wasmex.Wasi.WasiP2Options struct
  • :imports - Optional map of host functions that can be called by the component
  • Any standard GenServer options (like :name)

Returns

  • {:ok, pid} on success
  • {:error, reason} on failure