Skip to main content
Version: 0.1.0-alpha.4 (alpha)

Creating a capsule

Create a small program and run it on your development node. Start with a greeting, then try a word counter or shipping calculator. Each accepts typed input and returns either an answer or a helpful application error.

Complete Get the developer tools and Set up your workspace first. Keep its terminal open: it defines dev, your selected $Language, $Workspace, $Project and other tool paths. The steps below use those same variables on both operating systems.

Select Rust, C, TypeScript, Go, Java or C# / .NET above a code example. Your selection is shared across the examples. The build, deploy and test commands work the same way for every language; use the language you installed during setup.

1. A greeting capsule​

The first program accepts a name and returns a greeting. An empty name produces a helpful error instead. This is the complete implementation in your selected language:

Greet a visitorChoose a language
Rust
pub fn greet(name: String) -> Result<String, String> {
let name = name.trim();
if name.is_empty() {
return Err("Please enter a name.".into());
}
if name.len() > 100 {
return Err("Use a name of at most 100 bytes.".into());
}
Ok(format!("Hello, {name}!"))
}

#[cfg(target_arch = "wasm32")]
mod component {
wit_bindgen::generate!({path: "examples/tutorial_greeting", world: "service"});
struct Capsule;
impl exports::examples::greeting::api::Guest for Capsule {
fn greet(name: String) -> Result<String, String> {
super::greet(name)
}
}
export!(Capsule);
}

The WIT contract describes the input and the two possible kinds of answer:

greet: func(name: string) -> result<string, string>;

You will find this contract under app/wit in your project. Generated bindings connect it to your language's function. For now, keep the contract unchanged.

2. Create the project​

Choose the greeting template. Setup has already selected its verified bundle and template settings. Run this same command on Windows or Linux:

dev init "$Project" --bundle "$Bundle" --template "$Language/greeting" --template-sha256 "$GreetingTemplate"

Open the project in your editor:

File or folderWhat you do with it
appEdit the source and its WIT contract
latent.project.jsonReview the compiler, build recipe and source files it can use
tests/scenarios.jsonRead the named test cases
tests/*-input.json, tests/*-expected.jsonSee each input and its expected answer

3. Build and prepare the node​

After reviewing the project, run these commands in the same terminal on either OS:

dev trust --workspace "$Workspace" --project "$Project"
dev build --workspace "$Workspace" --project "$Project"
dev prepare-test --workspace "$Workspace" --consent-test-fixtures --admission "$Admission"

Expect code: success from each command. The compiler comes from the selected tool bundle. prepare-test prepares the stopped disposable node to accept this application. Setup selected trusted-local for Rust/C or signed-fixture for the other languages, including their required runtime permissions.

A signed fixture lasts 30 minutes and binds one build. If you edit that build or the fixture expires, create a fresh disposable workspace before testing it. Use the edit guide for the supported watch loop.

4. Start, deploy and test​

Open a second terminal and start the node. Keep this command running. For the default paths from setup, use the following command; substitute your workspace name if you chose another one.

Windows (PowerShell):

& (Join-Path $env:USERPROFILE 'LSF-inputs-alpha4/frontend/bin/latent-dev.exe') `
--state-root (Join-Path $env:LOCALAPPDATA 'LatentDev-tutorial') dev up --workspace test-my-greeting

Linux (Bash):

"$HOME/LSF-inputs-alpha4/frontend/bin/latent-dev" --state-root "$HOME/.latent-dev-tutorial" \
dev up --workspace test-my-greeting

Wait for the ready event. In your first terminal, publish the accepted build and run its tests with the same commands on either OS:

dev deploy --workspace "$Workspace"
dev test --workspace "$Workspace" --environment node

Expect passed: true and every required case to report status: passed. Ada returns Hello, Ada!; an empty name produces the expected application error. Some language profiles include additional runtime cases, so their case counts differ.

5. Send a request yourself​

The greeting's first input file contains ["Ada"]. Call it directly:

dev invoke --workspace "$Workspace" --service examples/my-greeting --contract examples:greeting/api@1.0.0 --function greet --input "$Project/tests/0-input.json"

The response includes category: success, the selected deployment and an encoded payload. To display the typed answer, on Windows:

$Reply = (dev invoke --workspace $Workspace --service examples/my-greeting `
--contract examples:greeting/api@1.0.0 --function greet `
--input (Join-Path $Project 'tests/0-input.json') | ConvertFrom-Json).result
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Reply.data.payload.data))

Or on Linux:

dev invoke --workspace "$Workspace" --service examples/my-greeting --contract examples:greeting/api@1.0.0 --function greet --input "$Project/tests/0-input.json" \
| python3 -c 'import base64,json,sys; print(base64.b64decode(json.load(sys.stdin)["result"]["data"]["payload"]["data"]).decode())'

Expected: [{"ok":"Hello, Ada!"}]. These are independent example calls. If a response is lost, use dev recover --workspace "$Workspace" to inspect that original call before making another one.

6. Try a word counter​

This program counts groups separated by spaces, tabs or newlines. An empty document has zero words. Very long input produces a readable error.

Count the words in a documentChoose a language
Rust
pub fn count(text: &str) -> Result<u32, String> {
if text.len() > 4096 {
return Err("Use text of at most 4096 bytes.".into());
}
u32::try_from(text.split_whitespace().count()).map_err(|_| "Too many words.".into())
}

#[cfg(target_arch = "wasm32")]
mod component {
wit_bindgen::generate!({path: "examples/tutorial_word_count", world: "service"});
struct Capsule;
impl exports::examples::word_count::api::Guest for Capsule {
fn count(text: String) -> Result<u32, String> {
super::count(&text)
}
}
export!(Capsule);
}
count: func(text: string) -> result<u32, string>;

Stop the greeting with dev down --workspace "$Workspace". Create another workspace and project using the workspace setup, for example test-my-words and My words. Reuse your downloads and existing WSL distro. Create the project with this command, then follow the same build, start, deploy and test steps with that workspace name:

dev init "$Project" --bundle "$Bundle" --template "$Language/word-count" --template-sha256 "$WordCountTemplate"

The tests send LSF runs small programs and expect 4. Look in tests/scenarios.json for the service, contract and function to use in a direct call.

7. Try a shipping calculator​

This program accepts an item count and whether delivery is express. It returns a price in cents: 500 for standard delivery or 1200 for express, plus 75 per item.

Calculate a shipping priceChoose a language
Rust
pub fn quote(items: u32, express: bool) -> Result<u32, String> {
if !(1..=100).contains(&items) {
return Err("Choose between 1 and 100 items.".into());
}
let base_cents = if express { 1200 } else { 500 };
Ok(base_cents + items * 75)
}

#[cfg(target_arch = "wasm32")]
mod component {
wit_bindgen::generate!({path: "examples/tutorial_shipping", world: "service"});
struct Capsule;
impl exports::examples::shipping::api::Guest for Capsule {
fn quote(items: u32, express: bool) -> Result<u32, String> {
super::quote(items, express)
}
}
export!(Capsule);
}
quote: func(items: u32, express: bool) -> result<u32, string>;

Use another project and workspace, then select the shipping template:

dev init "$Project" --bundle "$Bundle" --template "$Language/shipping" --template-sha256 "$ShippingTemplate"

Run the same build, start, deploy and test commands. Two items with standard delivery produce 650; express delivery produces 1350. Zero items produce Choose between 1 and 100 items. as a declared application error.

The three programs need no network, files or secrets. Their compilers can still require explicitly granted runtime clocks or entropy. To add application access to an outside service, continue with capabilities.

8. Clean up​

Stop the selected node and inspect its state:

dev down --workspace "$Workspace"
dev status --workspace "$Workspace"

Expect state: stopped. Starting up again retains the deployment without another deploy command. Keep this workspace if you are continuing with editing and recovery. When you no longer need its node data, remove that workspace explicitly:

dev purge --workspace "$Workspace" --confirm-workspace "$Workspace"

Your project source remains. Repeat cleanup for each workspace you created. The developer command guide also explains when to remove the shared LSF-owned WSL distro.