Emit and handle events
Task: announce that something changed so clients (and optionally other methods of your app) can react.
Declare an event
Section titled “Declare an event”An event type is an enum marked #[app::event]. Wire it into your state with the
emits argument so the macro knows which events the app produces:
#[app::state(emits = for<'a> Event<'a>)]pub struct KvStore { items: UnorderedMap<String, LwwRegister<String>>, handler_counter: Counter,}
#[app::event]pub enum Event<'a> { Inserted { key: &'a str, value: &'a str }, Updated { key: &'a str, value: &'a str }, Removed { key: &'a str }, Cleared,}Emit it
Section titled “Emit it”Call app::emit! with a variant. Clients subscribed to the context receive it:
app::emit!(Event::Updated { key: &key, value: &value });Run a handler when it fires
Section titled “Run a handler when it fires”To also invoke one of your own methods when the event is emitted, use the tuple
form (event, "handler_name"). The handler is a regular method named by that
string; its parameters match the event variant’s fields. This pattern is
apps/kv-store-with-handlers:
#[app::logic]impl KvStore { pub fn set(&mut self, key: String, value: String) -> app::Result<()> { if self.items.contains(&key)? { app::emit!(( Event::Updated { key: &key, value: &value }, "update_handler" )); } else { app::emit!(( Event::Inserted { key: &key, value: &value }, "insert_handler" )); } self.items.insert(key, value.into())?; Ok(()) }
// handler: name matches the string above; params match the variant fields pub fn update_handler(&mut self, key: &str, value: &str) -> app::Result<()> { app::log!("Handler 'update_handler' called: key={key}, value={value}"); self.handler_counter.increment()?; Ok(()) }
pub fn insert_handler(&mut self, key: &str, value: &str) -> app::Result<()> { app::log!("Handler 'insert_handler' called: key={key}, value={value}"); self.handler_counter.increment()?; Ok(()) }}Exercise it
Section titled “Exercise it”meroctl --node node1 call set \ --context <context-id> --args '{"key": "hello", "value": "world"}'The matching handler runs as part of the same call, and the emitted event is published on the node’s event stream for subscribed clients.
Worked example: a chat notification flow
Section titled “Worked example: a chat notification flow”A messaging app emits MessageSent whenever a member posts, and a web client
subscribes over WebSocket to render new messages the moment they land. The event
and the method that emits it:
#[app::event]pub enum Event<'a> { MessageSent { from: &'a str, text: &'a str },}
#[app::logic]impl Chat { pub fn send_message(&mut self, from: String, text: String) -> app::Result<()> { self.messages.push(text.clone())?; app::emit!(Event::MessageSent { from: &from, text: &text }); Ok(()) }}A client subscribes by sending a single frame on the node’s WebSocket endpoint,
naming the contexts it wants events for. The method is subscribe and the
context ids go under params.contextIds:
{ "id": 1, "method": "subscribe", "params": { "contextIds": ["<context-id>"] } }After a send_message call commits, every subscriber to that context receives a
state-mutation frame. The application event rides inside it under data.events:
{ "contextId": "<context-id>", "type": "StateMutation", "data": { "newRoot": "<root-hash>", "events": [ { "kind": "MessageSent", "data": [123, 34, 102, 114, 111, 109, 34], "handler": null } ] }}Each entry in events has three fields:
kindis the variant name — here"MessageSent".datais the variant’s fields serialized to JSON, delivered as a byte array. Decode the bytes back to UTF-8 and parse them to read the fields.handleris the in-app handler name when you emitted with the tuple form above, ornullfor a plainemit!.
const ev = frame.data.events[0]; // { kind, data, handler }const { from, text } = JSON.parse( new TextDecoder().decode(new Uint8Array(ev.data)), // -> {"from":"alice","text":"hi"});renderMessage(from, text); // update the UI