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.