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 { … }
| Field | Meaning |
|---|---|
domain_trait: generate | manual | Generate 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: Type | The 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
| Key | Required | Meaning |
|---|---|---|
key | yes | Command key, for example "create-todo" |
title | yes | Human title, shown in .meta and OpenAPI |
kind | yes | collection (:post) or object (:exec) |
resources | yes | Resources the command acts on |
scope: required | no | Routes require a {scope} path segment |
link: true | no | Also expose GET …:link/{token} with a signed link token |
hook: true | no | Also expose GET …:hook/{hook_token}, a JWT-signed trigger URL for webhooks |
tenant_scope: exempt | no | Skip tenant row checks for this command |
engine_method: false | no | Hide from engine method discovery |
response_type, openapi_tag, openapi_internal, openapi_explorer | no | OpenAPI 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
| Type | Role |
|---|---|
CommandEngine<S, A> | The generic engine the macro wraps |
CommandMeta | Key, title, kind, resources, scope, link and hook flags |
CommandTarget | Collection { resource, scope } or Object(AggregateRoot) |
CommandMessage | The command as logged and dispatched |
TypedCommandHandler<A> | A handler with a typed payload |
CommandRegistry | The closed set of handlers an engine accepts |
CommandPolicy | Authorization on every entry path (DefaultCommandPolicy when none is set) |
MessageBus | The publication port: publish(topic, payload) |
ProcessManagerRegistry, ProcessManagerStore | Durable 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.