Embedding versionx-core
You'll learn:
- The minimum
Cargo.tomlfor embedding. - How to construct a
Coreand run one command. - How to subscribe to the event bus.
- How to shut down cleanly when your program is long-lived.
Minimum Cargo.toml
[package]
name = "my-tool"
version = "0.1.0"
edition = "2024"
[dependencies]
versionx-sdk = "0.7"
tokio = { version = "1.41", features = ["full"] }
anyhow = "1"
Construct a Core
use versionx_sdk::{Core, CoreConfig};
use camino::Utf8PathBuf;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cwd = Utf8PathBuf::from_path_buf(std::env::current_dir()?)
.map_err(|p| anyhow::anyhow!("non-utf8 path: {p:?}"))?;
let core = Core::builder()
.cwd(cwd)
.use_daemon(false) // run in-process
.build()
.await?;
// ...
Ok(())
}
Core::discover(cwd) is the shorthand when you don't need custom config.
Run a command
use versionx_sdk::commands::{StatusOptions, UpdateOptions};
let status = core.status(StatusOptions::default()).await?;
println!("{} ecosystems detected", status.ecosystems.len());
let plan = core.update(UpdateOptions::plan_only()).await?;
for bump in plan.bumps {
println!("{} {} -> {}", bump.package, bump.current, bump.next);
}
Every command returns a structured report. No stdout parsing.
Subscribe to events
use versionx_sdk::{EventBus, Event};
let bus = EventBus::new();
let mut rx = bus.subscribe();
let core = Core::builder().cwd(cwd).event_bus(bus.clone()).build().await?;
tokio::spawn(async move {
while let Ok(event) = rx.recv().await {
match event.kind.as_str() {
k if k.starts_with("adapter.") => eprintln!("{event:?}"),
_ => {}
}
}
});
core.sync(Default::default()).await?;
Event shape and taxonomy match Events & tracing.
Apply a plan
let plan = core.update(UpdateOptions::plan_only()).await?;
// Serialize, show the user, ask for approval...
let outcome = core.apply(plan).await?;
println!("{outcome:?}");
Prerequisites are re-verified inside apply. If HEAD moved or the lockfile changed, apply returns an error instead of mutating state.
Shutdown
core.shutdown().await?;
Drops the state DB connection, flushes the event bus, releases the daemon connection (if any). Optional but recommended for long-lived programs.
Common pitfalls
Corehandles are notClone. Wrap inArcif you need to share across tasks.- Blocking the runtime. Every command is
async. Usetokio::spawn_blockingfor anything you want to run off the scheduler. - State DB contention. If your program runs Versionx concurrently in the same
VERSIONX_DATA_HOME, wrap writes in a single-writer actor. The CLI does this automatically; the SDK leaves it to you.
See also
- SDK overview — when to embed vs shell out.
- Plan / apply cookbook — the safety contract in Rust.
- Custom adapters — if you're adding an ecosystem Versionx doesn't know about.