Quickstart | Get started with Resonate

Install the Resonate Server & CLI
Shell
brew install resonatehq/tap/resonate
Install the Resonate SDK
- TypeScript
- Python
- Rust
Requires Node.js 22 or later.
Shell
npm install @resonatehq/sdk
note
The Resonate Python SDK requires Python ≥3.12.
Shell
pip install resonate-sdk
Cargo.toml
TOML[dependencies]
resonate = { git = "https://github.com/resonatehq/resonate-sdk-rs", branch = "master" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
Write your first Resonate Function
A countdown as a loop. Simple, but the function can run for minutes, hours, or days, despite restarts.
- TypeScript
- Python
- Rust
countdown.ts
TypeScriptimport { Resonate, type Context } from "@resonatehq/sdk";
function* countdown(context: Context, count: number, delay: number) {
for (let i = count; i > 0; i--) {
// Run a function, persist its result
yield* context.run((context: Context) => console.log(`Countdown: ${i}`));
// Sleep
yield* context.sleep(delay * 1000);
}
console.log("Done!");
}
// Instantiate Resonate
const resonate = new Resonate({ url: "http://localhost:8001" });
// Register the function
resonate.register(countdown);
countdown.py
Pythonfrom resonate import Resonate, Context
from threading import Event
def countdown(ctx: Context, count: int, delay: int):
for i in range(count, 0, -1):
# Run a function, persist its result
yield ctx.run(ntfy, i)
# Sleep
yield ctx.sleep(delay)
print("Done!")
def ntfy(_: Context, i: int):
print(f"Countdown: {i}")
# Instantiate Resonate
resonate = Resonate.remote()
# Register the function
resonate.register(countdown)
resonate.start() # Start Resonate threads
Event().wait() # Keep the main thread alive
src/main.rs
Rustuse resonate::prelude::*;
use std::time::Duration;
#[resonate::function]
async fn countdown(ctx: &Context, count: i32, delay: u64) -> Result<()> {
for i in (1..=count).rev() {
ctx.run(notify, i).await?;
ctx.sleep(Duration::from_secs(delay)).await?;
}
println!("Done!");
Ok(())
}
#[resonate::function]
async fn notify(i: i32) -> Result<()> {
println!("Countdown: {i}");
Ok(())
}
#[tokio::main]
async fn main() {
let resonate = Resonate::new(ResonateConfig {
url: Some("http://localhost:8001".into()),
..Default::default()
});
resonate.register(countdown).unwrap();
resonate.register(notify).unwrap();
// Keep the process alive to receive work
tokio::signal::ctrl_c().await.unwrap();
}
Start the server
Shell
resonate dev
Start the worker
- TypeScript
- Python
- Rust
Shell
npx ts-node countdown.ts
Shell
python countdown.py
Shell
cargo run
Run the function
Run the function with execution ID countdown.1:
Shell
resonate invoke countdown.1 --func countdown --arg 5 --arg 60
Result
You will see the countdown in the terminal
- TypeScript
- Python
- Rust
Shell
npx ts-node countdown.ts
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!
Shell
python countdown.py
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!
Shell
cargo run
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!
What to try
After starting the function, inspect the current state of the execution using the resonate tree command. The tree command visualizes the call graph of the function execution as a graph of durable promises.
Shell
resonate tree countdown.1
Now try killing the worker mid-countdown and restarting. The countdown picks up right where it left off without missing a beat.