Quickstart
Build the smallest Spiral application: create a Project, write a Table from Rust, and read that exact version back.
- Time
- 15 minutes
- Difficulty
- beginner
- Account
- Spiral account required
This tutorial builds the smallest Spiral application. A Rust program writes
three camera frames to a new Table called frames, publishes them in one
commit, and reads them back. You finish with a version address,
frames@v…, that reads exactly those three rows no matter what is written
later. That address is how an application, or a training job, records the
data it used.
Before you start
The Rust SDK is available as
spiral-sdk on crates.io. The
CLI is available as
spiral-cli on PyPI.
You also need:
- a Spiral account;
- Rust and Cargo, with the nightly toolchain required by the SDK;
- Python 3.11 or later with
pip, to install thespiralCLI.
1. Sign in
Install the CLI and sign in:
pip install spiral-cli
spiral loginspiral login prints a code and a URL. Open the URL, approve the code in
your browser, and the CLI finishes signing you in. If you belong to more than
one Organization, it asks which one to use.
2. Choose an Organization
An Organization is the account that holds your team. If you do not have one yet, create it; the CLI switches to it:
spiral orgs create "Quickstart"If you already have one, list your Organizations and switch:
spiral orgs ls
spiral orgs switch3. Create a Project
A Project is the workspace that holds your Tables and their history. Create one in the active Organization:
spiral projects create --description "Quickstart"The CLI prints Created project <project-id>. As the creator, you get the
admin role on it. Save the ID for the Rust program:
export SPIRAL_PROJECT=<project-id>4. Get a token
The Rust program authenticates with a bearer token. --show-token prints the
access token of your current sign-in on the last line of its output, so keep
only that line:
export SPIRAL_TOKEN="$(spiral login --show-token | tail -n 1)"The token lasts at most one hour. Run the command again when it expires.
5. Connect from Rust
Add spiral-sdk to your application with the embedded feature, and an
async runtime such as Tokio. The crate's library name is spiral, so your
code imports spiral::....
cargo add spiral-sdk --features embedded
cargo add tokio --features macros,rt-multi-threadPrepare the matching native archive before building, following Use the embedded backend. The SDK links that archive into your application at build time.
Build a client. It sends control-plane requests to the Spiral API, and it
uses the linked native runtime so that reads and writes run in your own process.
Signed storage requests go through https://spfs.spiraldb.dev:
let config = ScandalConfig {
spfs_endpoint: Some(spfs_url.parse()?),
local_files: false,
// Storage the Tables API grants with a provider credential is read without SPFS.
provider_roots: true,
refuse_custom_endpoints: false,
};
let client = Client::builder()
.api_url("https://api.spiraldb.dev")
.bearer_token(token)
.embedded_scandal(config)
.build()?;Set token to the value of SPIRAL_TOKEN and spfs_url to
https://spfs.spiraldb.dev.
6. Make rows
Make one Arrow record batch with three frames:
let schema = Arc::new(Schema::new(vec![
Field::new("frame_id", DataType::Int64, false),
Field::new("camera", DataType::Utf8, false),
]));
let frame_ids: ArrayRef = Arc::new(Int64Array::from(vec![1, 2, 3]));
let cameras: ArrayRef = Arc::new(StringArray::from(vec!["front", "left", "front"]));
let frames = RecordBatch::try_new(schema, vec![frame_ids, cameras])?;Import Arrow types through spiral::arrow_array and spiral::arrow_schema.
That way your Arrow version always matches the SDK's.
7. Create the Table and commit
Pin the head of the main branch, write the Table, and publish it in a
commit:
let project = client.project(project_id)?;
let main = project.branch("main").await?;
let table = project.write_table(frames).await?;
let mut commit = main.commit();
commit.create_table("frames", &table)?;
let committed = commit.publish().await?;
println!("committed {}", committed.commit_id());Set project_id to SPIRAL_PROJECT.
write_table writes the rows to storage and registers the Table's first
version, but nobody can see the Table yet. create_table stages the name
frames for that version, and publish records both in one new commit on
main. Pinning main first is what makes this safe: if another writer moved
main after you pinned it, publish returns an error and publishes nothing,
so you never overwrite a change you did not see.
8. Read the rows back
Read the Table from the new commit:
let table = committed.table("frames").await?;
if let Some(address) = table.resolved_address() {
println!("reading {address}");
}
let mut batches = table.scan(ScanOptions::default()).await?.into_stream();
let mut rows = 0;
while let Some(batch) = batches.next_batch().await {
let batch = batch?;
rows += batch.num_rows();
println!(
"batch: {} rows, {} columns",
batch.num_rows(),
batch.num_columns()
);
}
println!("read {rows} rows");The program prints:
committedand the new commit ID;reading frames@vand the 32-digit ID of the Table version;batch:and the row and column counts of each record batch;read 3 rows.
What you built
Your Project now has a Table, frames, and a commit on main that
publishes it. The address frames@v<version> names this exact version of the
Table. A later commit to main changes what frames@main reads, but never
what frames@v<version> reads. See
Commits and Branches for how
that works.
The rows went straight from your program to object storage. Spiral's services saw only the name, the version ID and your permissions. Architecture follows that split step by step.
Where next
- Write rows to append to or overwrite the Table.
- Commits shows how to recover a commit whose response was lost.