Skip to content

Building WASM Components

Primatomic runs WebAssembly Components (Component Model), not plain WASM modules. Components let you define a stable interface in WIT (WebAssembly Interface Types) and generate bindings for multiple languages.

This guide covers:

  1. Starting from a .wit interface (the contract)
  2. Generating language bindings
  3. Compiling your implementation into a component (.wasm)
  4. Validating the output
ToolPurpose
cargo-componentBuild Rust components (required for Rust)
wasm-toolsInspect, validate, and convert components
wit-bindgenGenerate guest bindings (Rust)
wit-bindgen-goGenerate Go bindings
jcoComponentize JavaScript/TypeScript
componentize-pyComponentize Python

Fetch the WIT interface using wkg:

Terminal window
wkg get --format wit primatomic:[email protected] --output ./wit

This downloads the interface files into your project’s wit/ directory:

your-project/
├── wit/
│ └── machine.wit
└── src/
└── lib.rs

Most toolchains can generate bindings so your IDE and type checker understand what to implement. This step is optional but recommended.

Primatomic views are reactor components (library-like, called repeatedly), not command components (executable-like).

A component prints as (component ...) while a core module prints as (module ...).

Terminal window
wasm-tools print path/to/component.wasm | head -1
wasm-tools component wit path/to/component.wasm
Terminal window
cargo install --locked wasm-tools

Rust builds components using cargo-component with the wasm32-unknown-unknown target.

Terminal window
# Install cargo-component
cargo install cargo-component
# Add the wasm32-unknown-unknown target
rustup target add wasm32-unknown-unknown
# Create a new project
cargo new --lib my-view
cd my-view

In Cargo.toml:

[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "0.41"
[profile.release]
opt-level = "s"
lto = true

Fetch the WIT interface, then generate bindings in src/lib.rs:

Terminal window
wkg get --format wit primatomic:[email protected] --output ./wit
wit_bindgen::generate!({
path: "wit",
world: "state-machine",
});
struct Component;
// Implement the generated Guest trait
Terminal window
cargo component build --release --target wasm32-unknown-unknown

Output: target/wasm32-unknown-unknown/release/<crate-name>.wasm

Terminal window
# Verify it's a component (not a module)
wasm-tools print target/wasm32-unknown-unknown/release/<crate-name>.wasm | head -1
# Verify no WASI imports
wasm-tools print target/wasm32-unknown-unknown/release/<crate-name>.wasm | grep -i wasi
# (should produce no output)
# Inspect the WIT interface
wasm-tools component wit target/wasm32-unknown-unknown/release/<crate-name>.wasm

TinyGo can build Preview 2 components using wit-bindgen-go for bindings.

Fetch the WIT interface and resolve dependencies:

Terminal window
wkg get --format wit primatomic:[email protected] --output ./wit
wkg wit build
Terminal window
go tool wit-bindgen-go generate --world state-machine --out internal ./primatomic:[email protected]

Set exported functions on the generated Exports struct (paths depend on your package structure).

Terminal window
tinygo build -target=wasip2 \
-o my-view.wasm \
--wit-package primatomic:[email protected] \
--wit-world state-machine \
main.go
Terminal window
wasm-tools print my-view.wasm | head -1
wasm-tools component wit my-view.wasm
# Verify no WASI imports (should produce no output)
wasm-tools print my-view.wasm | grep -i wasi

Use jco to componentize an ES module.

Terminal window
npm install -g @bytecodealliance/jco

Ensure your project uses ES modules ("type": "module" in package.json).

Terminal window
jco componentize \
--wit wit/machine.wit \
--world-name state-machine \
--out my-view.wasm \
--disable=all \
view.js
Terminal window
wasm-tools print my-view.wasm | head -1
wasm-tools component wit my-view.wasm

Terminal window
pip install componentize-py
Terminal window
componentize-py --wit-path wit --world state-machine bindings .

Implement the generated protocol class(es) from the bindings package.

Terminal window
componentize-py \
--wit-path wit/machine.wit \
--world state-machine \
componentize \
my_view \
-o my-view.wasm
Terminal window
wasm-tools print my-view.wasm | head -1
wasm-tools component wit my-view.wasm
# Verify no WASI imports (should produce no output)
wasm-tools print my-view.wasm | grep -i wasi

You built a core module instead of a component. Verify:

Terminal window
wasm-tools print my-view.wasm | head -1
  • (module = core wasm module (incorrect)
  • (component = component (correct)

If using Rust, ensure you’re using cargo component build (not plain cargo build).

If your component imports WASI capabilities, deployment will fail:

Terminal window
# Check for WASI imports
wasm-tools print my-view.wasm | grep -i wasi

Common causes:

  • Rust: Built with wasm32-wasip1 or wasm32-wasip2 target instead of wasm32-unknown-unknown
  • Go: Built with wasip2 target
  • Dependencies: Some crates pull in WASI dependencies transitively

Fix by rebuilding with the correct target:

Terminal window
cargo component build --release --target wasm32-unknown-unknown

If you used --disable=all with jco componentize, WASI features are intentionally removed. This is expected for Primatomic views.

Once validated, deploy your component:

Terminal window
curl -X POST https://api.primatomic.com/logs/$LOG_ID/views/my-view \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @my-view.wasm

See Quick Start for the complete deployment workflow.