Skip to main content

Commands

riverbase_core::command holds the command engine and its declarative front end, command_engine! (also exported as command!). The tutorial chapter walks through a full example. This page is the reference.

Two forms​

Full form: an engine, its commands, and optional helper methods. This generates the payload types, handlers, the engine struct with spawn, handler registration, and the helpers.

riverbase_core::command_engine! {
engine TodoCommandEngine {
domain_trait: generate, // or `manual` to hand-write DomainCommandEngine
meta { command_pool_size: 4 }
aggregate: TodoAggregate,
component: { msgbus: Arc<dyn MessageBus> },
}
commands { command CreateTodo { … } }
methods ( create(title: [impl Into<String>]) for CreateTodo { … } )
}

Commands-only form: handlers plus a register_{aggregate}_handlers(&mut args) function, for engines whose struct and spawn are hand-written:

riverbase_core::command_engine! {
commands PaymentAggregate {
command RegisterProvider { /* meta, payload, handle */ }
}
}
// register_payment_aggregate_handlers(&mut args);

engine { … }​

FieldMeaning
domain_trait: generate | manualGenerate the DomainCommandEngine impl (default), or write it yourself
meta { command_pool_size: N }Ractor actors for in-flight commands. Default 4, max 32. actor_pool_size is an alias.
aggregate: TypeThe aggregate type
component: { name: Type, … }Extra engine fields, passed to spawn after the log store

command Name { … }​

Block names are PascalCase. CreateTodo yields CreateTodoPayload and CreateTodoHandler.

meta​

KeyRequiredMeaning
keyyesCommand key, for example "create-todo"
titleyesHuman title, shown in .meta and OpenAPI
kindyescollection (:post) or object (:exec)
resourcesyesResources the command acts on
scope: requirednoRoutes require a {scope} path segment
link: truenoAlso expose GET …:link/{token} with a signed link token
hook: truenoAlso expose GET …:hook/{hook_token}, a JWT-signed trigger URL for webhooks
tenant_scope: exemptnoSkip tenant row checks for this command
engine_method: falsenoHide from engine method discovery
response_type, openapi_tag, openapi_internal, openapi_explorernoOpenAPI presentation

payload { … }​

Fields as name: Type, with optional #[serde(…)] and #[schemars(…)] attributes, which are forwarded. Use payload {} for commands without input.

target​

target collection(TODO_RESOURCE);   // or: target object(TODO_RESOURCE);

Adds a payload-derived target. Object commands from HTTP take their target from the path.

handle (payload, aggregate) { … }​

The body. It must return RiverbaseResult<serde_json::Value>. The two identifiers must match what you use inside the body.

methods ( … )​

Typed helpers on the engine for tests and workers:

methods (
create(title: [impl Into<String>]) for CreateTodo {
payload CreateTodoPayload { title: title.into() },
target none
}
set_done(id: [impl Into<String>]; done: [bool]) for UpdateTodo {
payload UpdateTodoPayload { done: Some(done), notes: None },
target some(CommandTarget::Object(AggregateRoot::new(TODO_RESOURCE, id.into())))
}
)

Parameter types sit in […], and multiple parameters are separated with ;.

Core types​

TypeRole
CommandEngine<S, A>The generic engine the macro wraps
CommandMetaKey, title, kind, resources, scope, link and hook flags
CommandTargetCollection { resource, scope } or Object(AggregateRoot)
CommandMessageThe command as logged and dispatched
TypedCommandHandler<A>A handler with a typed payload
CommandRegistryThe closed set of handlers an engine accepts
CommandPolicyAuthorization on every entry path (DefaultCommandPolicy when none is set)
MessageBusThe publication port: publish(topic, payload)
ProcessManagerRegistry, ProcessManagerStoreDurable multi-step workflows

Custom execute​

With domain_trait: manual, implement DomainCommandEngine yourself and forward to self.engine. For example, to publish an RxDB notification after each change:

async fn execute(&self, ctx: &EngineContext, cmdkey: &str, payload: Value, target: CommandTarget)
-> RiverbaseResult<Value>
{
let result = self.engine.execute(ctx, cmdkey, payload, target).await?;
if matches!(cmdkey, "create-todo" | "update-todo" | "clear-done") {
publish_todo_rxdb_notification(self.msgbus.as_ref(), cmdkey).await?;
}
Ok(result)
}

When the manual impl only forwards, use impl_domain_command_engine!(MyEngine) instead.