@@ -2,29 +2,169 @@ defmodule Wasmex.Components do
2
2
@ moduledoc """
3
3
This is the entry point to support for the [WebAssembly Component Model](https://component-model.bytecodealliance.org/).
4
4
5
- Support should be considered experimental at this point, with not all types yet supported.
5
+ The Component Model is a higher-level way to interact with WebAssembly modules that provides:
6
+ - Better type safety through interface types
7
+ - Standardized way to define imports and exports using WIT (WebAssembly Interface Types)
8
+ - WASI support for system interface capabilities
9
+
10
+ ## Basic Usage
11
+
12
+ To use a WebAssembly component:
13
+
14
+ 1. Start a component instance:
15
+ ```elixir
16
+ # Using raw bytes
17
+ bytes = File.read!("path/to/component.wasm")
18
+ {:ok, pid} = Wasmex.Components.start_link(%{bytes: bytes})
19
+
20
+ # Using a file path
21
+ {:ok, pid} = Wasmex.Components.start_link(%{path: "path/to/component.wasm"})
22
+
23
+ # With WASI support
24
+ {:ok, pid} = Wasmex.Components.start_link(%{
25
+ path: "path/to/component.wasm",
26
+ wasi: %Wasmex.Wasi.WasiP2Options{}
27
+ })
28
+
29
+ # With imports (host functions the component can call)
30
+ {:ok, pid} = Wasmex.Components.start_link(%{
31
+ bytes: bytes,
32
+ imports: %{
33
+ "host_function" => {:fn, &MyModule.host_function/1}
34
+ }
35
+ })
36
+ ```
37
+
38
+ 2. Call exported functions:
39
+ ```elixir
40
+ {:ok, result} = Wasmex.Components.call_function(pid, "exported_function", ["param1"])
41
+ ```
42
+
43
+ ## Component Interface Types
44
+
45
+ The component model supports the following WIT (WebAssembly Interface Type) types:
46
+
47
+ ### Currently Supported Types
48
+
49
+ - **Primitive Types**
50
+ - Integers: `s8`, `s16`, `s32`, `s64`, `u8`, `u16`, `u32`, `u64`
51
+ - Floats: `f32`, `f64`
52
+ - `bool`
53
+ - `string`
54
+
55
+ - **Compound Types**
56
+ - `record` (maps to Elixir maps with atom keys)
57
+ ```wit
58
+ record point { x: u32, y: u32 }
59
+ ```
60
+ ```elixir
61
+ %{x: 1, y: 2}
62
+ ```
63
+
64
+ - `list<T>` (maps to Elixir lists)
65
+ ```wit
66
+ list<u32>
67
+ ```
68
+ ```elixir
69
+ [1, 2, 3]
70
+ ```
71
+
72
+ - `tuple<T1, T2>` (maps to Elixir tuples)
73
+ ```wit
74
+ tuple<u32, string>
75
+ ```
76
+ ```elixir
77
+ {1, "two"}
78
+ ```
79
+
80
+ - `option<T>` (maps to `nil` or the value)
81
+ ```wit
82
+ option<u32>
83
+ ```
84
+ ```elixir
85
+ nil # or
86
+ 42
87
+ ```
88
+
89
+ ### Currently Unsupported Types
90
+
91
+ The following WIT types are not yet supported:
92
+ - `char`
93
+ - `variant` (tagged unions)
94
+ - `enum`
95
+ - `flags`
96
+ - `result` types
97
+ - Resources
98
+
99
+ Support should be considered experimental at this point.
100
+
101
+ ## Options
102
+
103
+ The `start_link/1` function accepts the following options:
104
+
105
+ * `:bytes` - Raw WebAssembly component bytes (mutually exclusive with `:path`)
106
+ * `:path` - Path to a WebAssembly component file (mutually exclusive with `:bytes`)
107
+ * `:wasi` - Optional WASI configuration as `Wasmex.Wasi.WasiP2Options` struct for system interface capabilities
108
+ * `:imports` - Optional map of host functions that can be called by the WebAssembly component
109
+ * Keys are function names as strings
110
+ * Values are tuples of `{:fn, function}` where function is the host function to call
111
+
112
+ Additionally, any standard GenServer options (like `:name`) are supported.
113
+
114
+ ### Examples
115
+
116
+ ```elixir
117
+ # With raw bytes
118
+ {:ok, pid} = Wasmex.Components.start_link(%{
119
+ bytes: File.read!("component.wasm"),
120
+ name: MyComponent
121
+ })
122
+
123
+ # With WASI configuration
124
+ {:ok, pid} = Wasmex.Components.start_link(%{
125
+ path: "component.wasm",
126
+ wasi: %Wasmex.Wasi.WasiP2Options{
127
+ args: ["arg1", "arg2"],
128
+ env: %{"KEY" => "value"},
129
+ preopened_dirs: ["/tmp"]
130
+ }
131
+ })
132
+
133
+ # With host functions
134
+ {:ok, pid} = Wasmex.Components.start_link(%{
135
+ path: "component.wasm",
136
+ imports: %{
137
+ "log" => {:fn, &IO.puts/1},
138
+ "add" => {:fn, fn(a, b) -> a + b end}
139
+ }
140
+ })
141
+ ```
6
142
"""
7
143
8
144
use GenServer
9
145
alias Wasmex.Wasi.WasiP2Options
10
146
11
- def start_link ( % { bytes: component_bytes , wasi: % WasiP2Options { } = wasi_options } ) do
12
- with { :ok , store } <- Wasmex.Components.Store . new_wasi ( wasi_options ) ,
13
- { :ok , component } <- Wasmex.Components.Component . new ( store , component_bytes ) do
14
- GenServer . start_link ( __MODULE__ , % { store: store , component: component } )
15
- end
16
- end
147
+ @ doc """
148
+ Starts a new WebAssembly component instance.
17
149
18
- def start_link ( % { bytes: component_bytes } ) do
19
- with { :ok , store } <- Wasmex.Components.Store . new ( ) ,
20
- { :ok , component } <- Wasmex.Components.Component . new ( store , component_bytes ) do
21
- GenServer . start_link ( __MODULE__ , % { store: store , component: component } )
22
- end
23
- end
150
+ ## Options
151
+
152
+ * `:bytes` - Raw WebAssembly component bytes (mutually exclusive with `:path`)
153
+ * `:path` - Path to a WebAssembly component file (mutually exclusive with `:bytes`)
154
+ * `:wasi` - Optional WASI configuration as `Wasmex.Wasi.WasiP2Options` struct
155
+ * `:imports` - Optional map of host functions that can be called by the component
156
+ * Any standard GenServer options (like `:name`)
157
+
158
+ ## Returns
159
+
160
+ * `{:ok, pid}` on success
161
+ * `{:error, reason}` on failure
162
+ """
163
+ def start_link ( opts ) when is_list ( opts ) or is_map ( opts ) do
164
+ opts = normalize_opts ( opts )
24
165
25
- def start_link ( opts ) when is_list ( opts ) do
26
166
with { :ok , store } <- build_store ( opts ) ,
27
- component_bytes <- Keyword . get ( opts , :bytes ) ,
167
+ component_bytes <- get_component_bytes ( opts ) ,
28
168
imports <- Keyword . get ( opts , :imports , % { } ) ,
29
169
{ :ok , component } <- Wasmex.Components.Component . new ( store , component_bytes ) do
30
170
GenServer . start_link (
@@ -35,6 +175,22 @@ defmodule Wasmex.Components do
35
175
end
36
176
end
37
177
178
+ defp normalize_opts ( opts ) when is_map ( opts ) do
179
+ opts
180
+ |> Map . to_list ( )
181
+ |> Keyword . new ( )
182
+ end
183
+
184
+ defp normalize_opts ( opts ) when is_list ( opts ) , do: opts
185
+
186
+ defp get_component_bytes ( opts ) do
187
+ cond do
188
+ bytes = Keyword . get ( opts , :bytes ) -> bytes
189
+ path = Keyword . get ( opts , :path ) -> File . read! ( path )
190
+ true -> raise ArgumentError , "Either :bytes or :path must be provided"
191
+ end
192
+ end
193
+
38
194
defp build_store ( opts ) do
39
195
if wasi_options = Keyword . get ( opts , :wasi ) do
40
196
Wasmex.Components.Store . new_wasi ( wasi_options )
0 commit comments