Skip to main content
Version: 0.3.36

WebAssembly: Rust

The smallest artifacts of the three guest languages (the scaffold builds to about 140 KB). Best when latency matters or you want existing Rust crates.

Prerequisites

rustup target add wasm32-wasip2

That is all. Since Rust 1.82 the wasm32-wasip2 target emits a component directly, so cargo-component is optional.

Scaffold

raisindb create function greet --lang rust --ns demo

You get a Function node under content/functions/lib/demo/greet/ and a crate under wasm/demo/greet/. The source lives outside content/ so raisindb sync never uploads your Cargo.toml as an asset.

Write the handler

use raisin_sdk::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
pub struct Input { pub name: String }

#[derive(Serialize)]
pub struct Output { pub greeting: String, pub pages: usize }

#[raisin_sdk::handler]
pub fn greet(input: Input) -> Result<Output> {
raisin_sdk::log::info(format!("greeting {}", input.name));

let children = raisin_sdk::nodes::get_children("content", "/pages", Some(50))?;
let rows = raisin_sdk::sql::query(
"SELECT id, name FROM 'content' WHERE node_type = $1",
&[serde_json::json!("raisin:Page")],
)?;

Ok(Output {
greeting: format!("Hello, {}", input.name),
pages: children.len().max(rows.as_array().map_or(0, |r| r.len())),
})
}

raisin_sdk::export!(greet);

#[raisin_sdk::handler] registers the function under the name default; export! lists the handlers and builds the dispatch table. Name a handler to put more than one in an artifact:

#[raisin_sdk::handler(name = "shout")]
pub fn shout(input: Input) -> Result<Output> { /* ... */ }

raisin_sdk::export!(greet, shout);

nodes::get_children returns Vec<serde_json::Value>; sql::query returns a serde_json::Value holding the array of rows. Both have _as::<T> variants (get_children_as, query_as) that deserialise into your own types.

Test without a server

The SDK compiles natively as well as to wasm, so handlers are ordinary Rust under cargo test. Host calls go to a mock you script; an unscripted call is an error, so a handler that starts calling something new cannot pass silently.

use greet::{raisin_dispatch, Output};
use raisin_sdk::testing::{with_mock, MockHost};

#[test]
fn it_greets() {
let mock = MockHost::new()
.expect("nodes_getChildren", r#"["content","/pages",50]"#, Ok("[]".into()))
.expect_any("sql_query", Ok(r#"[{"id":"1"}]"#.into()));

let (out, mock) = with_mock(mock, || {
raisin_dispatch("default", r#"{"name":"Ada"}"#).expect("runs")
});

let out: Output = serde_json::from_str(&out).unwrap();
assert_eq!(out.greeting, "Hello, Ada");
assert_eq!(mock.logs().len(), 1);
}

expect matches the method and its JSON argument array verbatim; expect_any ignores the arguments. raisin_dispatch is generated by export! and routes by handler name exactly as the host does. mock.calls() and mock.logs() record what the handler did.

Build, run, deploy

raisindb function build wasm/demo/greet     # -> content/.../greet/main.wasm
raisindb function test wasm/demo/greet # cargo test, no server
raisindb function run wasm/demo/greet --input '{"name":"Ada"}' --repo myapp
raisindb deploy . --repo myapp --install

Depending on the SDK directly

Scaffolds pin the SDK to a release tag of the RaisinDB repository:

[dependencies]
raisin-sdk = { git = "https://github.com/maravilla-labs/raisindb", tag = "v0.5.0" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Keep the pin. The WIT contract is versioned, and a scaffold pinned to a tag keeps building after the SDK moves on.

Keeping artifacts small

[lib]
crate-type = ["cdylib", "rlib"] # cdylib for the component, rlib for native tests

[profile.release]
opt-level = "s"
lto = true
panic = "abort"
strip = true
codegen-units = 1

Scaffolds set this already. Size affects upload and cold-start compile time, not steady-state speed.