WebAssembly Functions
Makiatto supports running WebAssembly components as edge functions and file transformers, enabling you to add dynamic functionality to your static sites.
Overview
WASM support in Makiatto provides two types of components:
- HTTP Handlers - Edge functions that handle HTTP requests
- File Transformers - Middleware that transforms files before serving them
Both component types receive node context information, allowing you to write geo-aware and cluster-aware logic.
Runtime Capabilities
The WASM runtime uses WASI (WebAssembly System Interface) with the following capabilities:
- Network access - HTTP requests and database connections to public endpoints (private IPs blocked for SSRF protection)
- File system access - Read-only access to files within the domain directory
- Environment variables - Access to configured env vars via WASI
- Memory limits - Configurable per-function memory limits
- Execution timeouts - Configurable per-function timeouts
File System Sandbox:
WASM functions have read-only access to their domain's directory, sandboxed to prevent accessing other domains or system files. The domain directory is mounted as / inside the WASM environment.
For example, if your domain is example.com with files in ./dist:
- WASM sees
/as the root - Opening
/index.htmlreads./dist/index.html - Opening
/api/data.jsonreads./dist/api/data.json - Cannot access files outside the domain directory or write files
This provides enough capability for edge computing use cases (reading templates, config files, making API calls) while maintaining security. For persistent data, use network calls to external databases or storage services.
Getting Started
First, fetch the WIT interface definitions:
maki wasm fetch
This will download WIT files to ./wit which define the interfaces your component must implement. Build your component using your language's WASM toolchain, for example:
- Rust: wit-bindgen +
wasm32-wasip2target (Rust 1.82+) - Generates bindings and compiles to components - JavaScript: Javy - JavaScript to WebAssembly compiler
- Go: TinyGo + wit-bindgen-go - Go compiler with WIT bindings
- Python: componentize-py - Python bindings and compiler
Node Context
Every WASM function receives information about the node serving the request:
#![allow(unused)] fn main() { struct NodeContext { /// Node name name: String, /// Node geographic latitude latitude: f64, /// Node geographic longitude longitude: f64, } }
This enables use cases like:
- Regional routing decisions
- A/B testing by location
- Custom load balancing logic
- Debug information showing which node served the request
HTTP Handlers
HTTP handlers are WASM components that implement the http world from the Makiatto WIT interface.
Creating a Handler
The function signature is:
handle-request: func(ctx: node-context, req: request) -> response
Where you receive:
ctx- Node context with name, latitude, longitudereq- HTTP request with method, path, query, headers, and body
And return:
response- HTTP response with status code, headers, and body
Example implementation:
package main
import (
"fmt"
// Generated by: wit-bindgen-go generate --world http --out gen ./wit
"gen/makiatto/http/handler"
)
func init() {
handler.Exports.HandleRequest = handleRequest
}
func handleRequest(ctx handler.NodeContext, req handler.Request) handler.Response {
body := fmt.Sprintf("Hello from node %s at (%f, %f)\nPath: %s",
ctx.Name, ctx.Latitude, ctx.Longitude, req.Path)
return handler.Response{
Status: 200,
Headers: []handler.Tuple2[string, string]{
{"content-type", "text/plain"},
},
Body: handler.Some([]byte(body)),
}
}
Deploying a Handler
Add the handler to your domain configuration:
# makiatto.toml
[[domain]]
name = "example.com"
path = "./dist"
[[domain.functions]]
# WASM file at ./dist/api/hello.wasm
path = "api/hello.wasm"
Sync to your cluster:
maki sync
Your function is now available at https://example.com/api/hello
The route is derived from the path - api/hello.wasm becomes /api/hello.
Configuration Options
Required fields:
path- Path to WASM file relative to domain directory (must stay within domain; route derived from this)
Optional fields:
methods- HTTP methods allowed (e.g.,["GET", "POST"]), defaults to all methodsenv- Environment variables as key-value pairs (e.g.,{ API_KEY = "secret" })env_file- Path to file containing environment variablestimeout_ms- Execution timeout in millisecondsmax_memory_mb- Maximum memory limit
File Transformers
File transformers are WASM components that process files before serving them. They implement the transform world.
Creating a Transformer
The function signature is:
transform: func(ctx: node-context, info: file-info, content: list<u8>) -> option<transform-result>
Where you receive:
ctx- Node context with name, latitude, longitudeinfo- File information (path, mime-type, size)content- File content as bytes
And return:
option<transform-result>- Either the transformed content ornullto pass through unchanged
Example implementation:
package main
import (
"fmt"
"strings"
// Generated by: wit-bindgen-go generate --world transform --out gen ./wit
"gen/makiatto/transform/transformer"
"go.bytecodealliance.org/cm"
)
func init() {
transformer.Exports.Transform = transform
}
func transform(ctx transformer.NodeContext, info transformer.FileInfo, content []byte) cm.Option[transformer.TransformResult] {
if strings.Contains(info.MimeType, "html") {
html := string(content)
transformed := fmt.Sprintf("<!-- Served by %s at (%f, %f) -->\n%s",
ctx.Name, ctx.Latitude, ctx.Longitude, html)
return cm.Some(transformer.TransformResult{
Content: []byte(transformed),
MimeType: cm.Some(info.MimeType),
Headers: []transformer.Tuple2[string, string]{
{"x-transformed", "true"},
},
})
}
return cm.None[transformer.TransformResult]()
}
Deploying a Transformer
# makiatto.toml
[[domain]]
name = "example.com"
path = "./dist"
[[domain.transforms]]
# WASM file at ./dist/transform.wasm
path = "transform.wasm"
files = "**/*.html"
Configuration Options
Required fields:
path- Path to WASM file relative to domain directory (must stay within domain)files- Glob pattern matching files to transform (e.g.,**/*.html,**/*.{js,css})
Optional fields:
env- Environment variables as key-value pairsenv_file- Path to file containing environment variablestimeout_ms- Execution timeout in millisecondsmax_memory_mb- Maximum memory limitmax_file_size_kb- Skip files larger than this size
Sequential Transforms
Multiple transforms can be applied to the same file. They execute in the order they appear in your configuration:
[[domain.transforms]]
path = "minify.wasm"
files = "**/*.html"
[[domain.transforms]]
path = "inject-analytics.wasm"
files = "**/*.html"
Each transform receives the output of the previous transform.