Expand description
§surrealkit: Rust library
This document covers SurrealKit as a Rust library. If you are looking for the CLI, see the project README.
The library is useful when you want schema management to happen inside your process at startup — for example with an embedded SurrealDB backend (RocksDB, SpeeDB) or when running SurrealDB in the same binary during tests.
§Add to your project
[dependencies]
surrealkit = "0.7"§Concepts: sync vs rollout
SurrealKit gives you two ways to get schema into a database. Pick based on whether the database is disposable or shared.
| Sync | Rollout | |
|---|---|---|
| Mental model | Declarative desired state — “make the DB match this schema” | Staged, reviewable migration with an explicit undo |
| Applies | All changed files, idempotently | Ordered steps across start / complete / rollback phases |
| Removes objects | Automatically (prune) | Only in the complete phase, via explicit steps |
| Reversible | No | Yes (rollback) |
| Use when | Dev/test/CI, single-owner or embedded databases | Shared/production databases where you need expand→contract and a rollback path |
The two compose: use sync for everyday schema and reach for a rollout when a change needs to land safely while old and new code run side-by-side.
§Connecting
DbCfg reads connection details from environment variables (with optional overrides); connect builds the surrealdb::Surreal client and authenticates:
use surrealkit::{DbCfg, DbOverrides, connect};
let cfg = DbCfg::from_env(None, &DbOverrides::default())?;
let db = connect(&cfg).await?;For an in-process SurrealDB (e.g. mem://, rocksdb://), construct a Surreal directly and pass it to any library function:
use surrealdb::engine::any::connect;
use surrealdb::opt::Config;
use surrealdb::opt::capabilities::Capabilities;
let db = connect(("mem://", Config::new().capabilities(Capabilities::all()))).await?;
db.use_ns("my_ns").use_db("my_db").await?;§Schema sync
§embed_schema! (compile-time embedding)
embed_schema! walks your .surql files at build time and bakes them into the binary. The generated embedded_schema::sync applies any file whose content changed:
// Reads database/schema/**/*.surql relative to your Cargo.toml at compile time.
surrealkit::embed_schema!();
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let db = surrealkit::connect(&surrealkit::DbCfg::from_env(None, &Default::default())?).await?;
embedded_schema::sync(&db).await?;
Ok(())
}A custom path relative to your Cargo.toml may be passed: embed_schema!("my/schema/dir"). The generated module is always named embedded_schema.
§Sync builder (runtime control)
To build the schema slice yourself, or to customize behavior, use the Sync builder:
use surrealkit::{EmbeddedSchemaFile, Sync, Surreal};
use surrealkit::engine::any::Any;
static SCHEMA: &[EmbeddedSchemaFile] = &[EmbeddedSchemaFile {
path: "database/schema/person.surql",
sql: "DEFINE TABLE person SCHEMALESS;",
}];
// Defaults: prune = true, fail_fast = true.
Sync::embedded(SCHEMA).run(db).await?;
// Customized:
Sync::embedded(SCHEMA)
.prune(false) // don't remove objects missing from SCHEMA
.allow_all_statements(true) // permit non-DEFINE statements (INSERT/UPDATE/…)
.dry_run(true) // report what would change without applying
.run(db)
.await?;Sync calls setup internally and reads nothing from the filesystem — it never writes scaffolding files.
§EmbeddedSchemaFile: path vs sql
This trips people up, so to be explicit:
pathis a stable tracking key, not a path that must exist on disk. SurrealKit stores it in its metadata tables to identify the file, detect content changes, and prune files that disappear. Keep it stable across releases — renaming it makes SurrealKit treat the old key as deleted and the new one as added.sqlis the content that gets applied. Changingsqlwhile holdingpathconstant is exactly what triggers a re-apply on the next sync.
§Rollouts
Rollouts are defined entirely in code — no TOML or .surql files on disk required. Build a spec with RolloutSpec::builder and drive it with the Rollout facade.
§Status lifecycle
planned → running_start → ready_to_complete → running_complete → completed
│
└── running_rollback → rolled_backcompleted and rolled_back are terminal. failed and the running_* states are stuck states from an interrupted run — recover them with Rollout::abandon (or the CLI repair command). Only one rollout may be in a non-terminal state at a time.
§Lifecycle example
use surrealkit::{
Rollout, RolloutSpec, RolloutStep, RolloutPhase, RolloutCompatibility,
EmbeddedSchemaFile, EntityKey, EntityKind, Surreal,
};
use surrealkit::engine::any::Any;
// The desired schema once the rollout completes (used to compute the managed
// catalog). Pass `&[]` if your steps fully describe the entity changes.
static TARGET: &[EmbeddedSchemaFile] = &[EmbeddedSchemaFile {
path: "database/schema/account.surql",
sql: "DEFINE TABLE account SCHEMAFULL;",
}];
let spec = RolloutSpec::builder("20260604__add_account")
.name("Add account table")
.compatibility(RolloutCompatibility::Phased)
// Expand: add the new table (non-destructive).
.step(RolloutStep::apply_schema(
"create_account", RolloutPhase::Start,
"DEFINE TABLE account SCHEMAFULL;",
))
// Backfill during complete. RunSql must be safe to re-run.
.step(RolloutStep::run_sql(
"backfill", RolloutPhase::Complete,
"UPDATE account SET active = true WHERE active = NONE;",
))
// Undo the expand phase on rollback.
.step(RolloutStep::remove_entities(
"undo", RolloutPhase::Rollback,
vec![EntityKey { kind: EntityKind::Table, scope: None, name: "account".into() }],
))
.build();
let rollout = Rollout::new(spec, TARGET);
rollout.start(db).await?; // expand — blocks if another rollout is active
// ... deploy new code, drain traffic ...
rollout.complete(db).await?; // contract — or: rollout.rollback(db).await?§Step actions
Each RolloutStep carries exactly one action, built with a constructor — invalid combinations cannot be represented:
| Constructor | What it does |
|---|---|
RolloutStep::apply_schema(id, phase, sql) | Apply inline DDL (OVERWRITE is injected; safe to retry) |
RolloutStep::run_sql(id, phase, sql) | Run data-mutation SQL (must be safe to re-run) |
RolloutStep::assert_sql(id, phase, sql, expect) | Assert a query’s output equals expect |
RolloutStep::remove_entities(id, phase, entities) | REMOVE … IF EXISTS the given objects |
§Recovery / stuck rollouts
If a process dies mid-rollout, the rollout is left in a running_* or failed state and blocks new rollouts. To inspect and recover:
// Inspect the recorded state.
let rollout = Rollout::new(spec, &[]);
if let Some(report) = rollout.status(db).await? {
println!("{:?}: {:?}", report.status, report.last_error);
}
// Last resort: force a wedged rollout to `rolled_back` so a new one can start.
// This does NOT revert schema changes already applied — reconcile those with a
// fresh sync or a follow-up rollout.
Rollout::abandon(db, "20260604__add_account").await?;§Seeding
[seed] runs .surql files from a seed/ directory (lexicographic order), applying template variables:
seed(db, "database", &TemplateVars::default()).await?;§Template variables
${VAR} placeholders in schema/seed/rollout SQL are substituted from a TemplateVars map before execution (lookups are case-insensitive; undefined variables are an error that names the missing key and file). Pass them via Sync::vars(...), Rollout::vars(...), or seed.
§Metadata tables
SurrealKit maintains two internal tables in your namespace/database, created automatically:
| Table | Purpose |
|---|---|
__entity | Tracks every schema object SurrealKit manages (content hash, tracking key) |
__rollout | Tracks rollout execution state (see the status lifecycle above) |
Re-exports§
pub use config::AuthLevel;pub use config::DbCfg;pub use config::DbOverrides;pub use config::connect;pub use sync::EmbeddedSchemaFile;pub use sync::Sync;pub use rollout::Rollout;pub use rollout::RolloutAction;pub use rollout::RolloutCompatibility;pub use rollout::RolloutPhase;pub use rollout::RolloutSpec;pub use rollout::RolloutSpecBuilder;pub use rollout::RolloutStatus;pub use rollout::RolloutStatusReport;pub use rollout::RolloutStep;pub use rollout::RolloutStepStatus;pub use schema_state::EntityKey;pub use schema_state::EntityKind;pub use seed::seed;pub use typegen::SchemaTypes;pub use typegen::TypegenOpts;pub use typegen::generate;pub use variables::TemplateVars;pub use anyhow;pub use surrealdb;
Modules§
- config
- constants
- core
- engine
- Different embedded and remote database engines
- rollout
- scaffold
- schema_
state - seed
- setup
- sync
- tester
- typegen
surrealkit typegen— introspect a live database and emit a structured schema document.- variables
Macros§
- embed_
schema - Embeds
.surqlschema files at compile time.
Structs§
- Surreal
- A database client instance for embedded or remote databases.