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.