Skip to main content

resonate-basic-ephemeral-world-usage-rust

Core patterns for using the Resonate Rust SDK's Client APIs from the ephemeral world — initializing, registering durable functions with the

跳到安装

来源信息

仓库
resonatehq/resonate-skills
最近来源活动
2026年8月21日 13:59
检测到的 SKILL.md 语言
英语
星标
6
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
resonate-basic-ephemeral-world-usage-rust
description
Core patterns for using the Resonate Rust SDK's Client APIs from the ephemeral world — initializing, registering durable functions with the
license
Apache-2.0
# Resonate Basic Ephemeral World Usage — Rust > **SDK note.** The Resonate Rust SDK is in active development (`resonate-sdk` v0.6.0, published on crates.io). APIs may change between releases. Treat every code example as a moving target until the SDK reaches 1.0. ## Overview The ephemeral world is anywhere your Rust program starts: `main()`, an Axum or Actix handler, a CLI entry point, a background service. You use the `Resonate` client to register durable functions and invoke them top-level. Once an invocation starts, it crosses into the durable world and Resonate guarantees its completion — retries on failure, resumes after crashes, continues across process restarts. This skill covers the Client API surface. The Durable World (Context APIs inside `#[resonate::function]`) lives in `resonate-basic-durable-world-usage-rust`. ## Install The Rust SDK is published on crates.io. Add to your `Cargo.toml`: ```toml title="Cargo.toml" [dependencies] resonate = { package = "resonate-sdk", version = "0.6" } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } ``` Most programs also want `serde_json` for the `json!(...)` macro used in the promises API. ```rust use resonate::prelude::*; ``` ## Initialize Local mode (zero-dependency; in-memory promise store): ```rust use resonate::prelude::*; let resonate = Resonate::local(); ``` Remote mode (connects to a Resonate server): ```rust use resonate::prelude::*; let resonate = Resonate::new(ResonateConfig { url: Some("http://localhost:8001".into()), ..Default::default() }); ``` With explicit worker group + auth token: ```rust let resonate = Resonate::new(ResonateConfig { url: Some("https://resonate.example.com".into()), group: Some("workers".into()), token: std::env::var("RESONATE_TOKEN").ok(), ..Default::default() }); ``` The SDK reads environment variables when config fields are not set: - `RESONATE_URL` — full base URL - `RESONATE_HOST` + `RESONATE_PORT` — alternate construction - `RESONATE_TOKEN` — JWT for authenticated servers - `RESONATE_SCHEME` — `http` by default - `RESONATE_PREFIX` — prepended to all promise + task IDs for multi-tenant namespacing ## Define a durable function The `#[resonate::function]` attribute macro transforms an async function into a durable function. The SDK infers the function kind from the first parameter: | First parameter | Kind | Semantics | |---|---|---| | `&Context` | Workflow | Can orchestrate sub-tasks via `ctx.run`, `ctx.rpc`, `ctx.sleep` | | `&Info` | Leaf with metadata | Read-only access to execution metadata (id, parent, tags) | | anything else (e.g., `String`, `OrderInput`) | Pure leaf | Stateless computation; no context access | All durable functions MUST return `resonate::error::Result<T>` (aliased to `Result<T>` when you `use resonate::prelude::*`): ```rust use resonate::prelude::*; #[resonate::function] async fn process_order(ctx: &Context, order_id: String) -> Result<String> { let order = ctx.run(load_order, order_id.clone()).await?; Ok(format!("processed {}", order)) } #[resonate::function] async fn load_order(order_id: String) -> Result<String> { // pure leaf — no context, no Info Ok(format!("order-{}", order_id)) } ``` Override the registered name for RPC cross-process identification: ```rust #[resonate::function(name = "order-processor")] async fn process_order(ctx: &Context, order_id: String) -> Result<String> { // ... } ``` ## Share resources via dependencies Dependencies (DB pools, HTTP clients, config) are attached to the `Resonate` instance at construction time with the `.with_dependency<T>(value)` builder. The SDK uses Rust's type system (not a string key) — you retrieve a dependency by its type inside a durable function via `ctx.get_dependency::<T>()`. ```rust use std::sync::Arc; use sqlx::PgPool; struct AppConfig { api_key: String, } #[tokio::main] async fn main() -> Result<()> { let pool = PgPool::connect(&std::env::var("DATABASE_URL")?).await?; let config = AppConfig { api_key: std::env::var("API_KEY")? }; let resonate = Resonate::new(ResonateConfig::default()) .with_dependency(pool) .with_dependency(config); resonate.register(charge_card).unwrap(); // ... } #[resonate::function] async fn charge_card(ctx: &Context, order_id: String) -> Result<()> { let pool = ctx.get_dependency::<PgPool>(); // Arc<PgPool> let config = ctx.get_dependency::<AppConfig>(); // Arc<AppConfig> // use pool + config in a leaf (see resonate-basic-durable-world-usage-rust) Ok(()) } ``` Type-dispatched DI means you can only have one dependency per type per `Resonate` instance; for multiple values of the same logical type, wrap them in distinct newtypes (`struct PrimaryDb(PgPool)` vs `struct AnalyticsDb(PgPool)`). > **Note:** `with_dependency` is a pub fn on `Resonate` in the v0.6.0 SDK source (`resonate-sdk-rs:resonate/src/resonate.rs:284`) but not mentioned in `docs/develop/rust.mdx`. Use with the understanding that docs lag source here. ## Register Registration exposes a function to the Resonate system. Use `.unwrap()` or handle the `Result` (registration fails if the name is duplicated): ```rust let resonate = Resonate::local(); resonate.register(process_order).unwrap(); resonate.register(load_order).unwrap(); ``` One `.register` call per function per process. Register everything your process should handle before invoking anything. ## Invoke a durable function ### Same process, synchronous (awaits result) ```rust let result: String = resonate .run("invocation-id", process_order, "order-123".into()) .await?; ``` The builder accepts options before `.await`: ```rust use std::time::Duration; let result: String = resonate .run("order:123", process_order, "order-123".into()) .timeout(Duration::from_secs(60)) .version(2) .tags([("tenant".to_string(), "acme".to_string())].into()) .await?; ``` `resonate.run` is the synchronous ephemeral-world entry. For async lifecycles where you want a handle, use `.get(id)` later. ### Remote process, synchronous ```rust let result: String = resonate .rpc("invocation-id", "process_order", "order-123") .target("poll://any@workers") .await?; ``` The `target` option routes to a worker group; the called function must be registered in a process that polls that target. ### Scheduled (cron) invocation The Rust SDK has first-class cron scheduling: ```rust let schedule = resonate .schedule("daily-reconciliation", "0 2 * * *", "reconcile", "2026-04-16") .await?; // ...later, to delete: schedule.delete().await?; ``` (Python's `resonate-sdk` 0.7.4 also has a top-level `resonate.schedule(id, cron, func_name, args=, kwargs=, promise_timeout=, version=)` — confirmed against the installed `resonate/resonate.py`. Go's `0.1.0` tag has the lower-level `Schedules().Create` sub-client but not this same top-level function-dispatch convenience wrapper — see Related notes below.) ### Subscribe to an existing invocation ```rust let mut handle = resonate.get::<String>("invocation-id").await?; let result = handle.result().await?; ``` The type parameter on `.get::<T>` deserializes the result; you need to know or agree on T with the invocation site. ## External promises External promises let code outside the durable function resolve or reject it — the primitive for human-in-the-loop workflows and webhook-driven resumption: ```rust use serde_json::json; // create a promise with a timeout (ms since Unix epoch), initial param, and tags let timeout_at_ms: i64 = (std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH)? .as_millis() as i64) + 30 * 60 * 1000; // 30 minutes from now resonate.promises.create("approval:order-123", timeout_at_ms, json!({}), json!({})).await?; // elsewhere (webhook, CLI, UI): resonate.promises.resolve("approval:order-123", json!({"approved": true})).await?; // or reject: resonate.promises.reject("approval:order-123", json!({"reason": "over-budget"})).await?; // or cancel (settles as rejected_canceled): resonate.promises.cancel("approval:order-123", json!(null)).await?; // query state: let promise = resonate.promises.get("approval:order-123").await?; ``` The `data` arguments are `serde_json::Value`; use the `json!` macro for ergonomic literals. ## Graceful shutdown ```rust resonate.stop().await?; ``` Stops background tasks (heartbeat, subscription loops). Good citizen for long-running daemons or CLI tools. ## Complete example: a worker binary ```rust use resonate::prelude::*; use tokio::signal; #[tokio::main] async fn main() -> Result<()> { let resonate = Resonate::new(ResonateConfig { url: std::env::var("RESONATE_URL").ok(), group: Some("order-workers".into()), ..Default::default() }); resonate.register(process_order).unwrap(); resonate.register(load_order).unwrap(); // wait for Ctrl+C; the SDK polls in the background signal::ctrl_c().await?; resonate.stop().await?; Ok(()) } #[resonate::function] async fn process_order(ctx: &Context, order_id: String) -> Result<String> { let order = ctx.run(load_order, order_id).await?; Ok(order) } #[resonate::function] async fn load_order(order_id: String) -> Result<String> { Ok(format!("order-{}", order_id)) } ``` ## Distinct Rust idioms - **`Result<T>` everywhere** — both Client API methods and durable functions return `Result`. Use `?` for concise propagation. - **`ResonateConfig { ... , ..Default::default() }`** — Rust's struct-update syntax; always include `..Default::default()` when partially constructing. - **`Some(...)` / `.ok()` / `std::env::var(...).ok()`** for optional config — the SDK accepts `Option<String>` throughout. - **`tokio::main`** — durable functions are async; your `main` must be async. `#[tokio::main]` sets up the runtime. - **`serde_json::json!` macro** for promise data — explicit serialization; matches Rust's strong-typing discipline. - **`Duration::from_secs(N)` / `from_millis(N)`** for timeouts — not raw numbers. Use `std::time::Duration`. - **Turbofish syntax on `.rpc::<T>(...)` and `.get::<T>(...)`** — Rust's type inference needs help when the result type isn't constrained by the call site. ## Rust SDK API coverage status Honest reading of v0.6.0, verified against `resonate-sdk-rs` source (not just docs): ### In the SDK source + documented in `rust.mdx` - `Resonate::new/local`, `.register/run/rpc/schedule/get/stop/promises.*` - `#[resonate::function]` macro + 3 function kinds (Workflow / Leaf with Info / Pure leaf)
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看