Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Quick Reference

Rust ↔ TypeScript at a glance

ConceptRust (umari)TypeScript (@umari/js)
Define event#[derive(Event, DomainIds, …)] struct + #[event_type]defineEvent<Data>()(type, { domainIds })
Event set#[derive(EventSet)] enum Queryevents: [A, B] array
Define foldimpl Fold for …defineFold({ domainIds, events, initial, apply })
Bind a fold.fold::<T>() (via FromDomainIds)T({ …bindings }) in the folds map
Command#[export_command] + Command::new(…)defineCommand({ … }) + exportCommand(def)
Projectorexport_projector!(T) + impl ProjectordefineProjector({ … }) + exportProjector(def)
Effectexport_effect!(T) + impl EffectdefineEffect({ … }) + exportEffect(def)
Emitemit![Event { … }]emit(Event({ … }))
Rejectanyhow::ensure! / bail!reject(msg) / invalidInput(msg)
Validate inputvalidator (#[validate(…)])input: zod schema
Call a commandprivate fn callexecute(name, input, ctx?)
Standalone foldsFoldQuery::new()…run()foldQuery({ … }).run()
SQLitefree fns + Statementsqlite.* namespace
Domain-ID casingsnake_case (user_id)camelCase (userId)

The Rust tables below describe the derive/attribute/export macros. The TypeScript equivalents are the define* / export* functions shown in the tabbed sections.

Derive macros

MacroApplies toPurpose
#[derive(Event)]StructMakes a struct a persisted event
#[derive(EventSet)]EnumCreates a typed event set for queries
#[derive(DomainIds)]StructGenerates domain_ids() method
#[derive(FromDomainIds)]StructGenerates constructor from domain ID bindings

Attribute macros

AttributePlacementPurpose
#[event_type("...")]Event structSets the event type string
#[domain_id]FieldMarks a field as a domain ID tag
#[domain_id("alt_name")]FieldDomain ID with alternate tag name
#[crypto_scope]Field on EventEncrypts the event (must be on a #[domain_id] field)
#[scope(field)]EventSet variantFilter by a single domain ID field
#[scope(field = "value")]EventSet variantHardcoded tag filter
#[from_domain_id(default)]Fold fieldUse default value, don’t bind from domain IDs
#[validate(...)]Input fieldValidation rules (validator crate)

Export macros

MacroUsage
#[export_command]Annotate the command function
export_projector!(Name);Wire up projector WASM interface
export_effect!(Name);Wire up effect WASM interface

Command / emit / reject

Command::new(input, context)           // create builder
    .fold::<T>()                        // register fold (no args)
    .fold_args::<T>(args)               // register fold with args
    .fold_with(|input| MyFold { .. })   // register fold manually
    .execute(|input, states| { .. })    // run with fold states

emit![]                                // no events
emit![Event { field: val }]            // single event
emit![EventA { .. }, EventB { .. }]    // multiple events

anyhow::ensure!(balance >= amount, "insufficient funds"); // business rejection
anyhow::bail!("user not registered");

SQLite API

execute(sql, params)       -> Result<usize, SqliteError>
execute_batch(sql)         -> Result<(), SqliteError>
query_one(sql, params)     -> Row              // traps on 0 or >1 rows
query_row(sql, params)     -> Option<Row>
last_insert_rowid()        -> Option<i64>

// Prepared (prepare(sql) -> Statement)
stmt.execute(params)       -> Result<usize, SqliteError>
stmt.query(params)         -> Vec<Row>
stmt.query_one(params)     -> Row
stmt.query_row(params)     -> Option<Row>

params![]                        // no params
params![val1, val2, val3]        // positional ?1, ?2, ?3
row.get::<&str, String>("column_name")
row.get::<usize, i64>(0)
row.tuple::<(String, String, i64)>()

Built-in fold types

TypeRust stateTypeScript stateUse for
EventFoldEventState<E>StoredEvent<E>[]Full history
LatestEventOption<StoredEvent<E>>{ value?: StoredEvent<E> }Most recent event
EventCounteru64{ count: bigint }Counting events
EventToggleToggleState<A, B>{ last?: { side, event } }Paired opposing events
SingleEventN/A (an EventSet)events: [E]Single event type queries

In Rust: cmd.fold::<EventFold<E>>(). In TypeScript: EventFold(E)({ …bindings }) in the folds map. See Fold Reference.

Event envelope fields

Rust fieldTypeScript fieldType (Rust / TS)Description
ididUuid / stringEvent unique ID
positionpositionu64 / bigintGlobal log position
event_typetypeString / stringEvent type identifier
tagstagsVec<String> / string[]Domain ID tags
timestamptimestampDateTime<Utc> / DateWhen written
correlation_idcorrelationIdUuid / stringOriginating action
causation_idcausationIdUuid / stringCommand execution
triggering_event_idtriggeringEventIdOption<Uuid> / string?Causal event
idempotency_keyidempotencyKeyOption<Uuid> / string?Deduplication
encryption_scopeencryptionScopeOption<String> / string?Encryption scope
encryption_key_idencryptionKeyIdOption<Uuid> / string?Key identifier

CommandContext

CommandContext::new()                           // auto-detect (effect or external)
    .with_correlation_id(id)
    .with_triggering_event_id(id)
    .with_idempotency_key(key)

Environment variables

Server (umari binary)

VariableDefaultDescription
UMARI_DATA_DIR./umari-dataruntime database directory
UMARI_EVENT_STORE_URLhttp://localhost:50051UmaDB event store URL
UMARI_API_ADDR127.0.0.1:3000HTTP API bind address
UMARI_API_KEY(none)required Authorization: Bearer <key>
UMARI_LOGumari=infotracing-subscriber filter
UMARI_VERBOSEfalseset log level to trace
UMARI_NO_BANNERfalsehide the startup banner
UMARI_SHUTDOWN_TIMEOUT10sgraceful shutdown deadline

CLI (umari-cli / umari client)

VariableDefaultDescription
UMARI_URLhttp://localhost:3000server URL
UMARI_API_KEY(none)bearer token sent with each request

Essential imports

use umari::prelude::*;           // everything you need
use serde::{Serialize, Deserialize};
use validator::Validate;
use schemars::JsonSchema;        // optional, for OpenAPI docs

Naming conventions

ItemConvention
Event payloadPascalCase past tense
Event type string"object.verb"
Command packagekebab-case imperative
Command inputInput (Rust struct) / inferred from schema (TS)
Projector packagekebab-case plural noun
Effect packagekebab-case verb phrase
FoldPascalCase + Fold
Fold statePascalCase + State
Event setRust enum Query / TS events: [...] array
Domain-ID fieldsnake_case (Rust) / camelCase (TS)