Skip to content

Events

Events let a service notify other nodes that something happened and, optionally, run a handler on those nodes when the change is received.

Mark an event class with @Event. The decorator records the class name as the event kind and adds serialize() / deserialize() helpers.

import { Event } from '@calimero-network/calimero-sdk-js';
@Event
export class ItemAdded {
constructor(
public key: string,
public value: string,
public timestamp: number
) {}
}
@Event
export class ItemRemoved {
constructor(public key: string) {}
}

There are two emitters, both imported from the package root:

import { emit, emitWithHandler } from '@calimero-network/calimero-sdk-js';
// Fire-and-forget notification (no handler runs on receivers)
emit(new ItemAdded('key1', 'value1', Date.now()));
// Emit and name a handler method to run on receiving nodes
emitWithHandler(new ItemAdded('key1', 'value1', Date.now()), 'onItemAdded');

The event payload is serialized through the ABI manifest, so it is compatible with Rust services that observe the same event type.

A handler is just a method on the logic class whose name matches the string passed to emitWithHandler:

@Logic(MyApp)
export class MyAppLogic extends MyApp {
addItem(key: string, value: string): void {
this.items.set(key, value);
emitWithHandler(new ItemAdded(key, value, Date.now()), 'onItemAdded');
}
// Runs on receiving nodes after the delta is applied
onItemAdded(event: ItemAdded): void {
this.itemCount.increment();
}
}

Handlers may run on many nodes, in any order, and can be retried. To keep every node’s state identical, a handler must be:

  1. Commutative — order-independent. this.userCount.increment() (a Counter) is safe; reading another key and appending to it assumes an ordering that may not hold.
  2. Independent — no unsynchronized shared state. Two handlers writing the same key race; writing distinct keys is fine.
  3. Idempotent — safe to run more than once. CRDT increments are; charging an external API is not.
  4. Pure — no external side effects. Mutating CRDT state and env.log(...) are fine; outbound HTTP calls are not (and are not available in the sandbox).
  • Keep handlers to a single, commutative state update.
  • Split complex reactions into multiple events rather than one big handler.
  • Test handlers under concurrent, out-of-order delivery — that is the real execution model.