Your first app
-
Scaffold a crate with
cargo mero:Terminal window cargo mero new my-appcd my-appIt writes
Cargo.toml(the SDK pins, your package id under[package.metadata.calimero], and the build profiles), a workingsrc/lib.rswith a test, andGUIDE.md, the guide the registry asks every package for. -
Write the state and methods. Every app has the same shape: one state struct built from CRDT collections, an
initconstructor, calls that take&mut self, and views that take&self.use calimero_sdk::app;use calimero_storage::collections::{LwwRegister, UnorderedMap};#[app::state(emits = for<'a> Event<'a>)]pub struct KvStore {items: UnorderedMap<String, LwwRegister<String>>,}#[app::event]pub enum Event<'a> {Inserted { key: &'a str, value: &'a str },}#[app::logic]impl KvStore {// Runs once, when an instance is created.#[app::init]pub fn init() -> KvStore {KvStore { items: UnorderedMap::new() }}// A call: changes state, commits on success.pub fn set(&mut self, key: String, value: String) -> app::Result<()> {app::emit!(Event::Inserted { key: &key, value: &value });self.items.insert(key, value.into())?;Ok(())}// A view: reads state, never commits.#[app::view]pub fn get(&self, key: String) -> app::Result<Option<String>> {Ok(self.items.get(&key)?.map(|v| v.get().clone()))}}Callers pass arguments as JSON, by parameter name:
{"key": "a", "value": "1"}. -
Give each instance its own starting state by taking arguments in
init. They are the init arguments of an instance, so two organizations’ instances of the same app can start differently:#[app::init]pub fn init(title: String) -> Notes {Notes { title: LwwRegister::new(title), notes: UnorderedMap::new() }}An instance created with
{"title": "Team notes"}starts with that title. Ifinitfails, creating the instance fails withINIT_FAILEDand the app’s error. -
Test the logic in-process with
TestHost, which stands in for the node. Enable the mock store for tests only:[dev-dependencies]calimero-storage = { workspace = true, features = ["testing"] }#[cfg(test)]mod tests {use calimero_sdk::testing::TestHost;use super::*;#[test]fn set_then_get() {let mut app = TestHost::new(KvStore::init);app.call(|s| s.set("k".into(), "v".into())).unwrap();assert_eq!(app.view(|s| s.get("k".into())).unwrap(), Some("v".to_owned()));assert_eq!(app.events().len(), 1);}}Terminal window cargo test -
Build to WASM. One command compiles, optimizes and embeds the app’s ABI (its methods, events and types):
Terminal window cargo mero buildThe module lands at
res/<crate_name>.wasm. To run it, install it on a local cluster or package and publish it.
- What an app sees on a cluster: callers, organizations, events, blobs, upgrades.
- The SDK macros and collections references.