Skip to main content

microde-application

The Rust runtime is published as the microde-application crate. Add it with

cargo add microde-application

The API uses the same composition model as the TypeScript runtime while using Rust-native traits, owned futures, and Result-based errors.

API index​

Runtime​

Modules and composition​

Supporting traits and descriptors​

Module implementation​

Modules declare their kind and override lifecycle methods as needed:

use microde_application::{MicrodeModule, ModuleFuture, ModuleKind};

struct Worker;

impl MicrodeModule for Worker {
const KIND: ModuleKind = ModuleKind::Passive;

fn run(&mut self) -> ModuleFuture {
Box::pin(async {
println!("worker complete");
Ok(())
})
}
}

initialize, setup_with_context, run_with_context, stop, teardown, shutdown, and cleanup have default implementations. Override only the phases the module needs.

Installation and composition​

install_named returns an opaque ModuleHandle for one stable module instance. Handles are required when binding relationships and cannot be used across different MicrodeApplication values.

let database = service.install_named("database", |_| DatabaseModule::new())?;
let orders = service.install_named("orders", |_| OrdersModule::new())?;
service.bind(&orders, &orders_database, &database)?;

Calling serve or run seals the composition. Bindings, providers, and dependency cycles are validated before any lifecycle callback starts.

Ports, providers, and relationships​

Port<T> identifies a typed provider contract. A module returns its owned Provider values from providers(). Consumers declare Dependency<T> and Reference<T> relationship slots from a port:

let database_port = Port::<Database>::new("database");
let database_slot = Dependency::new("database", database_port.clone());
let peer_slot = Reference::new("peer", database_port);

Dependencies participate in the lifecycle DAG and are available through SetupContext and RunContext. References do not affect lifecycle order and are available only through RunContext, so reference cycles are allowed.

Lifecycle contexts​

Use SetupContext::use_dependency during setup and RunContext::use_relationship during run:

fn setup_with_context(&mut self, context: SetupContext) -> ModuleFuture {
let database: Database = context.use_dependency(&self.database_slot)?;
Box::pin(async move { Ok(()) })
}

fn run_with_context(&mut self, context: RunContext) -> ModuleFuture {
let peer: Database = context.use_relationship(&self.peer_slot)?;
Box::pin(async move { Ok(()) })
}

Lifecycle order is dependency-first with stable instance IDs as tie-breakers. Teardown, shutdown, and cleanup use the exact reverse order.